75int QgsAttributeForm::sFormCounter = 0;
80 , mOwnsMessageBar( true )
82 , mFormNr( sFormCounter++ )
84 , mPreventFeatureRefresh( false )
85 , mIsSettingMultiEditFeatures( false )
86 , mUnsavedMultiEditChanges( false )
87 , mEditCommandMessage( tr(
"Attributes changed" ) )
100 updateContainersVisibility();
102 updateEditableState();
109 qDeleteAll( mInterfaces );
136 mInterfaces.append( iface );
152 if ( mUnsavedMultiEditChanges )
155 int res = QMessageBox::question(
this, tr(
"Multiedit Attributes" ),
156 tr(
"Apply changes to edited features?" ), QMessageBox::Yes | QMessageBox::No );
157 if ( res == QMessageBox::Yes )
162 clearMultiEditMessages();
164 mUnsavedMultiEditChanges =
false;
216 w->setContext( newContext );
222 w->setVisible( relationWidgetsVisible );
229 mSearchButtonBox->setVisible(
false );
234 mSearchButtonBox->setVisible(
false );
239 mSearchButtonBox->setVisible(
false );
243 resetMultiEdit(
false );
245 mSearchButtonBox->setVisible(
false );
249 mSearchButtonBox->setVisible(
true );
255 mSearchButtonBox->setVisible(
false );
263 mSearchButtonBox->setVisible(
false );
272 const auto constMWidgets = mWidgets;
287 QVariant mainValue = eww->
value();
289 additionalFieldValues[index] = value;
290 eww->
setValues( mainValue, additionalFieldValues );
304 mIsSettingFeature =
true;
321 mIsSettingFeature =
false;
322 const auto constMInterfaces = mInterfaces;
325 iface->featureChanged();
341 mIsSettingFeature =
false;
344bool QgsAttributeForm::saveEdits( QString *error )
347 bool changedLayer =
false;
352 bool doUpdate =
false;
372 *error = tr(
"JSON value for %1 is invalid and has not been saved" ).arg( eww->
field().
name() );
375 QVariantList dstVars = QVariantList() << dst.at( eww->
fieldIdx() );
376 QVariantList srcVars = QVariantList() << eww->
value();
377 QList<int> fieldIndexes = QList<int>() << eww->
fieldIdx();
381 for (
const QString &fieldName : additionalFields )
385 dstVars << dst.at( idx );
389 Q_ASSERT( dstVars.count() == srcVars.count() );
391 for (
int i = 0; i < dstVars.count(); i++ )
394 if ( !
qgsVariantEqual( dstVars[i], srcVars[i] ) && srcVars[i].isValid() )
396 dst[fieldIndexes[i]] = srcVars[i];
406 const auto constMInterfaces = mInterfaces;
409 if ( !iface->acceptChanges( updatedFeature ) )
419 mFeature = updatedFeature;
425 bool res = mLayer->
addFeature( updatedFeature );
444 for (
int i = 0; i < dst.count(); ++i )
447 || !dst.at( i ).isValid()
448 || !fieldIsEditable( i ) )
454 QgsDebugMsgLevel( QStringLiteral(
"dst:'%1' (type:%2, isNull:%3, isValid:%4)" )
455 .arg( dst.at( i ).toString(), dst.at( i ).typeName() ).arg(
QgsVariantUtils::isNull( dst.at( i ) ) ).arg( dst.at( i ).isValid() ), 2 );
456 QgsDebugMsgLevel( QStringLiteral(
"src:'%1' (type:%2, isNull:%3, isValid:%4)" )
457 .arg( src.at( i ).toString(), src.at( i ).typeName() ).arg(
QgsVariantUtils::isNull( src.at( i ) ) ).arg( src.at( i ).isValid() ), 2 );
459 newValues[i] = dst.at( i );
460 oldValues[i] = src.at( i );
467 if ( success && n > 0 )
494QgsFeature QgsAttributeForm::getUpdatedFeature()
const
506 QVariantList dstVars = QVariantList() << featureAttributes.at( eww->
fieldIdx() );
507 QVariantList srcVars = QVariantList() << eww->
value();
508 QList<int> fieldIndexes = QList<int>() << eww->
fieldIdx();
512 for (
const QString &fieldName : additionalFields )
516 dstVars << featureAttributes.at( idx );
520 Q_ASSERT( dstVars.count() == srcVars.count() );
522 for (
int i = 0; i < dstVars.count(); i++ )
524 if ( !
qgsVariantEqual( dstVars[i], srcVars[i] ) && srcVars[i].isValid() )
525 featureAttributes[fieldIndexes[i]] = srcVars[i];
530 return updatedFeature;
533void QgsAttributeForm::updateValuesDependencies(
const int originIdx )
535 updateValuesDependenciesDefaultValues( originIdx );
536 updateValuesDependenciesVirtualFields( originIdx );
539void QgsAttributeForm::updateValuesDependenciesDefaultValues(
const int originIdx )
541 if ( !mDefaultValueDependencies.contains( originIdx ) )
549 QgsFeature updatedFeature = getUpdatedFeature();
552 QList<QgsWidgetWrapper *> relevantWidgets = mDefaultValueDependencies.values( originIdx );
569 if ( mAlreadyUpdatedFields.contains( eww->
fieldIdx() ) )
580void QgsAttributeForm::updateValuesDependenciesVirtualFields(
const int originIdx )
582 if ( !mVirtualFieldsDependencies.contains( originIdx ) )
589 QgsFeature updatedFeature = getUpdatedFeature();
592 const QList<QgsWidgetWrapper *> relevantWidgets = mVirtualFieldsDependencies.values( originIdx );
600 if ( mAlreadyUpdatedFields.contains( eww->
fieldIdx() ) )
606 const QVariant value = exp.evaluate( &context );
612void QgsAttributeForm::updateRelatedLayerFields()
615 updateRelatedLayerFieldsDependencies();
617 if ( mRelatedLayerFieldsDependencies.isEmpty() )
624 QgsFeature updatedFeature = getUpdatedFeature();
627 const QSet<QgsEditorWidgetWrapper *> relevantWidgets = mRelatedLayerFieldsDependencies;
631 if ( mAlreadyUpdatedFields.contains( eww->
fieldIdx() ) )
637 QVariant value = exp.evaluate( &context );
642void QgsAttributeForm::resetMultiEdit(
bool promptToSave )
647 mUnsavedMultiEditChanges =
false;
651void QgsAttributeForm::multiEditMessageClicked(
const QString &link )
653 clearMultiEditMessages();
654 resetMultiEdit( link == QLatin1String(
"#apply" ) );
657void QgsAttributeForm::filterTriggered()
659 QString filter = createFilterExpression();
665void QgsAttributeForm::searchZoomTo()
667 QString filter = createFilterExpression();
668 if ( filter.isEmpty() )
674void QgsAttributeForm::searchFlash()
676 QString filter = createFilterExpression();
677 if ( filter.isEmpty() )
683void QgsAttributeForm::filterAndTriggered()
685 QString filter = createFilterExpression();
686 if ( filter.isEmpty() )
694void QgsAttributeForm::filterOrTriggered()
696 QString filter = createFilterExpression();
697 if ( filter.isEmpty() )
705void QgsAttributeForm::pushSelectedFeaturesMessage()
711 tr(
"%n matching feature(s) selected",
"matching features", count ),
717 tr(
"No matching features found" ),
731 QString filter = createFilterExpression();
732 if ( filter.isEmpty() )
736 pushSelectedFeaturesMessage();
741void QgsAttributeForm::searchSetSelection()
746void QgsAttributeForm::searchAddToSelection()
751void QgsAttributeForm::searchRemoveFromSelection()
756void QgsAttributeForm::searchIntersectSelection()
761bool QgsAttributeForm::saveMultiEdits()
765 const QList<int> fieldIndexes = mFormEditorWidgets.uniqueKeys();
766 mFormEditorWidgets.constBegin();
767 for (
int fieldIndex : fieldIndexes )
769 const QList<QgsAttributeFormEditorWidget *> widgets = mFormEditorWidgets.values( fieldIndex );
770 if ( !widgets.first()->hasChanged() )
773 if ( !widgets.first()->currentValue().isValid()
774 || !fieldIsEditable( fieldIndex ) )
781 widget->changesCommitted();
783 newAttributeValues.insert( fieldIndex, widgets.first()->currentValue() );
786 if ( newAttributeValues.isEmpty() )
794 int res = QMessageBox::information(
this, tr(
"Multiedit Attributes" ),
795 tr(
"Edits will be applied to all selected features." ), QMessageBox::Ok | QMessageBox::Cancel );
796 if ( res != QMessageBox::Ok )
807 const auto constMultiEditFeatureIds = mMultiEditFeatureIds;
810 QgsAttributeMap::const_iterator aIt = newAttributeValues.constBegin();
811 for ( ; aIt != newAttributeValues.constEnd(); ++aIt )
817 clearMultiEditMessages();
830 if ( !mButtonBox->isVisible() )
831 mMessageBar->
pushItem( mMultiEditMessageBarItem );
857 wrapper->notifyAboutToSave();
897 success = saveEdits( error );
901 success = saveMultiEdits();
906 mUnsavedMultiEditChanges =
false;
915 mValuesInitialized =
false;
916 const auto constMWidgets = mWidgets;
919 ww->setFeature( mFeature );
930 mAlreadyUpdatedFields.append( eww->
fieldIdx() );
931 updateValuesDependenciesVirtualFields( eww->
fieldIdx() );
932 mAlreadyUpdatedFields.removeAll( eww->
fieldIdx() );
935 mValuesInitialized =
true;
941 const auto widgets { findChildren< QgsAttributeFormEditorWidget * >() };
948void QgsAttributeForm::clearMultiEditMessages()
950 if ( mMultiEditUnsavedMessageBarItem )
952 if ( !mButtonBox->isVisible() )
953 mMessageBar->
popWidget( mMultiEditUnsavedMessageBarItem );
954 mMultiEditUnsavedMessageBarItem =
nullptr;
956 if ( mMultiEditMessageBarItem )
958 if ( !mButtonBox->isVisible() )
959 mMessageBar->
popWidget( mMultiEditMessageBarItem );
960 mMultiEditMessageBarItem =
nullptr;
964QString QgsAttributeForm::createFilterExpression()
const
969 QString filter = w->currentFilterExpression();
970 if ( !filter.isEmpty() )
974 if ( filters.isEmpty() )
977 QString filter = filters.join( QLatin1String(
") AND (" ) ).prepend(
'(' ).append(
')' );
986 if ( mExtraContextScope )
993void QgsAttributeForm::onAttributeChanged(
const QVariant &value,
const QVariantList &additionalFieldValues )
998 bool signalEmitted =
false;
1000 if ( mValuesInitialized )
1019 for (
int i = 0; i < additionalFields.count(); i++ )
1021 const QString fieldName = additionalFields.at( i );
1022 const QVariant value = additionalFieldValues.at( i );
1026 signalEmitted =
true;
1028 if ( mValuesInitialized )
1029 updateJoinedFields( *eww );
1035 if ( !mIsSettingMultiEditFeatures )
1037 mUnsavedMultiEditChanges =
true;
1039 QLabel *msgLabel =
new QLabel( tr(
"Unsaved multiedit changes: <a href=\"#apply\">apply changes</a> or <a href=\"#reset\">reset changes</a>." ), mMessageBar );
1040 msgLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
1041 msgLabel->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1042 connect( msgLabel, &QLabel::linkActivated,
this, &QgsAttributeForm::multiEditMessageClicked );
1043 clearMultiEditMessages();
1046 if ( !mButtonBox->isVisible() )
1047 mMessageBar->
pushItem( mMultiEditUnsavedMessageBarItem );
1050 signalEmitted =
true;
1062 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1065 if ( formEditorWidget->editorWidget() == eww )
1070 whileBlocking( formEditorWidget->editorWidget() )->setValue( value );
1073 updateConstraints( eww );
1076 if ( mValuesInitialized )
1079 mAlreadyUpdatedFields.append( eww->
fieldIdx() );
1080 updateValuesDependencies( eww->
fieldIdx() );
1081 mAlreadyUpdatedFields.removeAll( eww->
fieldIdx() );
1086 updateEditableState();
1088 if ( !signalEmitted )
1093 bool attributeHasChanged = !mIsSettingFeature;
1095 attributeHasChanged &= !mIsSettingMultiEditFeatures;
1101void QgsAttributeForm::updateAllConstraints()
1103 const auto constMWidgets = mWidgets;
1108 updateConstraints( eww );
1116 if ( currentFormValuesFeature( ft ) )
1128 updateConstraint( ft, eww );
1131 const QList<QgsEditorWidgetWrapper *> deps = constraintDependencies( eww );
1134 updateConstraint( ft, depsEww );
1142 const QVector<ContainerInformation *> infos = mContainerInformationDependency.value( eww->
field().
name() );
1143 for ( ContainerInformation *info : infos )
1145 info->apply( &context );
1150void QgsAttributeForm::updateContainersVisibility()
1154 const QVector<ContainerInformation *> infos = mContainerVisibilityCollapsedInformation;
1156 for ( ContainerInformation *info : infos )
1158 info->apply( &context );
1168 updateAllConstraints();
1184 if ( mJoinedFeatures.contains( info ) )
1201void QgsAttributeForm::updateLabels()
1203 if ( ! mLabelDataDefinedProperties.isEmpty() )
1206 if ( currentFormValuesFeature( currentFeature ) )
1210 for (
auto it = mLabelDataDefinedProperties.constBegin() ; it != mLabelDataDefinedProperties.constEnd(); ++it )
1212 QLabel *label { it.key() };
1214 const QString value { it->valueAsString( context, QString(), &ok ) };
1215 if ( ok && ! value.isEmpty() )
1217 label->setText( value );
1224void QgsAttributeForm::updateEditableState()
1226 if ( ! mEditableDataDefinedProperties.isEmpty() )
1229 if ( currentFormValuesFeature( currentFeature ) )
1233 for (
auto it = mEditableDataDefinedProperties.constBegin() ; it != mEditableDataDefinedProperties.constEnd(); ++it )
1235 QWidget *w { it.key() };
1237 const bool isEditable { it->valueAsBool( context,
true, &ok ) && mLayer && mLayer->
isEditable() };
1247 w->setEnabled( isEditable );
1255bool QgsAttributeForm::currentFormValuesFeature(
QgsFeature &feature )
1268 if ( dst.count() > eww->
fieldIdx() )
1270 QVariantList dstVars = QVariantList() << dst.at( eww->
fieldIdx() );
1271 QVariantList srcVars = QVariantList() << eww->
value();
1272 QList<int> fieldIndexes = QList<int>() << eww->
fieldIdx();
1276 for (
const QString &fieldName : additionalFields )
1279 fieldIndexes << idx;
1280 dstVars << dst.at( idx );
1284 Q_ASSERT( dstVars.count() == srcVars.count() );
1286 for (
int i = 0; i < dstVars.count(); i++ )
1292 dst[fieldIndexes[i]] = srcVars[i];
1309void QgsAttributeForm::registerContainerInformation( QgsAttributeForm::ContainerInformation *info )
1311 mContainerVisibilityCollapsedInformation.append( info );
1313 const QSet<QString> referencedColumns = info->expression.referencedColumns().unite( info->collapsedExpression.referencedColumns() );
1315 for (
const QString &col : referencedColumns )
1317 mContainerInformationDependency[ col ].append( info );
1321bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields, QStringList &descriptions )
const
1345bool QgsAttributeForm::currentFormValidHardConstraints( QStringList &invalidFields, QStringList &descriptions )
const
1366void QgsAttributeForm::onAttributeAdded(
int idx )
1368 mPreventFeatureRefresh =
false;
1372 attrs.insert( idx, QVariant(
layer()->fields().at( idx ).type() ) );
1380void QgsAttributeForm::onAttributeDeleted(
int idx )
1382 mPreventFeatureRefresh =
false;
1386 attrs.remove( idx );
1394void QgsAttributeForm::onRelatedFeaturesChanged()
1396 updateRelatedLayerFields();
1399void QgsAttributeForm::onUpdatedFields()
1401 mPreventFeatureRefresh =
false;
1418 attrs[i] = QVariant(
layer()->fields().at( i ).type() );
1428void QgsAttributeForm::onConstraintStatusChanged(
const QString &constraint,
1434 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1438 formEditorWidget->setConstraintStatus( constraint, description, err, result );
1439 if ( formEditorWidget->editorWidget() != eww )
1441 formEditorWidget->editorWidget()->updateConstraint( result, err );
1448 QList<QgsEditorWidgetWrapper *> wDeps;
1460 if ( name != ewwName )
1467 for (
const QString &colName : referencedColumns )
1469 if ( name == colName )
1471 wDeps.append( eww );
1484 return setupRelationWidgetWrapper( QString(), rel, context );
1497void QgsAttributeForm::preventFeatureRefresh()
1499 mPreventFeatureRefresh =
true;
1530 return mNeedsGeometry;
1533void QgsAttributeForm::synchronizeState()
1535 bool isEditable = ( mFeature.
isValid()
1545 const QList<QgsAttributeFormEditorWidget *> formWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1548 formWidget->setConstraintResultVisible( isEditable );
1552 bool enabled = isEditable && fieldIsEditable( eww->
fieldIdx() );
1553 ww->setEnabled( enabled );
1559 ww->setEnabled( isEditable );
1570 if ( mConstraintsFailMessageBarItem )
1572 mMessageBar->
popWidget( mConstraintsFailMessageBarItem );
1575 mMessageBar->
pushItem( mConstraintsFailMessageBarItem );
1579 QStringList invalidFields, descriptions;
1580 mValidConstraints = currentFormValidHardConstraints( invalidFields, descriptions );
1584 if ( !mValidConstraints && !mConstraintsFailMessageBarItem )
1586 mConstraintsFailMessageBarItem =
new QgsMessageBarItem( tr(
"Changes to this form will not be saved. %n field(s) don't meet their constraints.",
"invalid fields", invalidFields.size() ),
Qgis::MessageLevel::Warning, -1 );
1587 mMessageBar->
pushItem( mConstraintsFailMessageBarItem );
1589 else if ( mValidConstraints && mConstraintsFailMessageBarItem )
1591 mMessageBar->
popWidget( mConstraintsFailMessageBarItem );
1592 mConstraintsFailMessageBarItem =
nullptr;
1595 else if ( mConstraintsFailMessageBarItem )
1597 mMessageBar->
popWidget( mConstraintsFailMessageBarItem );
1598 mConstraintsFailMessageBarItem =
nullptr;
1601 isEditable = isEditable & mValidConstraints;
1606 QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
1608 okButton->setEnabled( isEditable );
1611void QgsAttributeForm::init()
1613 QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
1616 QWidget *formWidget =
nullptr;
1617 mNeedsGeometry =
false;
1619 bool buttonBoxVisible =
true;
1623 buttonBoxVisible = mButtonBox->isVisible();
1625 mButtonBox =
nullptr;
1628 if ( mSearchButtonBox )
1630 delete mSearchButtonBox;
1631 mSearchButtonBox =
nullptr;
1634 qDeleteAll( mWidgets );
1637 while ( QWidget *w = this->findChild<QWidget *>() )
1643 QVBoxLayout *vl =
new QVBoxLayout();
1644 vl->setContentsMargins( 0, 0, 0, 0 );
1646 mMessageBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1647 vl->addWidget( mMessageBar );
1652 QGridLayout *layout =
new QGridLayout();
1653 QWidget *container =
new QWidget();
1654 container->setLayout( layout );
1655 vl->addWidget( container );
1657 mFormEditorWidgets.clear();
1658 mFormWidgets.clear();
1661 setContentsMargins( 0, 0, 0, 0 );
1670 if ( file && file->open( QFile::ReadOnly ) )
1674 QFileInfo fi( file->fileName() );
1675 loader.setWorkingDirectory( fi.dir() );
1676 formWidget = loader.load( file,
this );
1679 formWidget->setWindowFlags( Qt::Widget );
1680 layout->addWidget( formWidget );
1683 mButtonBox = findChild<QDialogButtonBox *>();
1686 formWidget->installEventFilter(
this );
1698 int columnCount = 1;
1699 bool hasRootFields =
false;
1700 bool addSpacer =
true;
1709 if ( !containerDef )
1712 switch ( containerDef->
type() )
1716 tabWidget =
nullptr;
1717 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1718 if ( widgetInfo.labelStyle.overrideColor )
1720 if ( widgetInfo.labelStyle.color.isValid() )
1722 widgetInfo.widget->setStyleSheet( QStringLiteral(
"QGroupBox::title { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1725 if ( widgetInfo.labelStyle.overrideFont )
1727 widgetInfo.widget->setFont( widgetInfo.labelStyle.font );
1730 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1731 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1733 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1735 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1737 layout->setRowStretch( row, widgDef->verticalStretch() );
1751 tabWidget =
nullptr;
1752 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1753 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1754 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1756 layout->setRowStretch( row, widgDef->verticalStretch() );
1759 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1761 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1777 layout->addWidget( tabWidget, row, column, 1, 2 );
1781 QWidget *tabPage =
new QWidget( tabWidget );
1783 tabWidget->addTab( tabPage, widgDef->name() );
1784 tabWidget->
setTabStyle( tabWidget->tabBar()->count() - 1, widgDef->labelStyle() );
1788 registerContainerInformation(
new ContainerInformation( tabWidget, tabPage, containerDef->
visibilityExpression().
data() ) );
1790 QGridLayout *tabPageLayout =
new QGridLayout();
1791 tabPage->setLayout( tabPageLayout );
1793 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, tabPage, mLayer, mContext );
1794 tabPageLayout->addWidget( widgetInfo.widget );
1801 hasRootFields =
true;
1802 tabWidget =
nullptr;
1803 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1806 if ( widgetInfo.showLabel )
1808 if ( widgetInfo.labelStyle.overrideColor && widgetInfo.labelStyle.color.isValid() )
1810 collapsibleGroupBox->
setStyleSheet( QStringLiteral(
"QGroupBox::title { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1813 if ( widgetInfo.labelStyle.overrideFont )
1815 collapsibleGroupBox->setFont( widgetInfo.labelStyle.font );
1818 collapsibleGroupBox->setTitle( widgetInfo.labelText );
1821 QVBoxLayout *collapsibleGroupBoxLayout =
new QVBoxLayout();
1822 collapsibleGroupBoxLayout->addWidget( widgetInfo.widget );
1823 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
1825 QVBoxLayout *
c =
new QVBoxLayout();
1826 c->addWidget( collapsibleGroupBox );
1827 layout->addLayout(
c, row, column, 1, 2 );
1829 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1830 layout->setRowStretch( row, widgDef->verticalStretch() );
1831 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1832 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1841 hasRootFields =
true;
1842 tabWidget =
nullptr;
1843 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1844 QLabel *label =
new QLabel( widgetInfo.labelText );
1846 if ( widgetInfo.labelStyle.overrideColor )
1848 if ( widgetInfo.labelStyle.color.isValid() )
1850 label->setStyleSheet( QStringLiteral(
"QLabel { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1854 if ( widgetInfo.labelStyle.overrideFont )
1856 label->setFont( widgetInfo.labelStyle.font );
1859 label->setToolTip( widgetInfo.toolTip );
1860 if ( columnCount > 1 && !widgetInfo.labelOnTop )
1862 label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1865 label->setBuddy( widgetInfo.widget );
1868 if ( widgetInfo.widget
1869 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
1870 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
1871 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
1874 if ( !widgetInfo.showLabel )
1876 QVBoxLayout *
c =
new QVBoxLayout();
1877 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1878 c->addWidget( widgetInfo.widget );
1879 layout->addLayout(
c, row, column, 1, 2 );
1881 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1883 layout->setRowStretch( row, widgDef->verticalStretch() );
1886 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1888 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1893 else if ( widgetInfo.labelOnTop )
1895 QVBoxLayout *
c =
new QVBoxLayout();
1896 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1897 c->addWidget( label );
1898 c->addWidget( widgetInfo.widget );
1899 layout->addLayout(
c, row, column, 1, 2 );
1901 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1903 layout->setRowStretch( row, widgDef->verticalStretch() );
1906 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1908 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1915 const int widgetColumn = column + 1;
1916 layout->addWidget( label, row, column++ );
1917 layout->addWidget( widgetInfo.widget, row, column++ );
1919 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1921 layout->setRowStretch( row, widgDef->verticalStretch() );
1924 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( widgetColumn ) )
1926 layout->setColumnStretch( widgetColumn, widgDef->horizontalStretch() );
1934 const int fieldIdx = fieldElement->
idx();
1935 if ( fieldIdx >= 0 && fieldIdx < mLayer->fields().count() )
1937 const QString fieldName { mLayer->
fields().
at( fieldIdx ).
name() };
1941 if ( property.isActive() )
1943 mLabelDataDefinedProperties[ label ] = property;
1949 if ( property.isActive() )
1951 mEditableDataDefinedProperties[ widgetInfo.widget ] = property;
1958 if ( column >= columnCount * 2 )
1965 if ( hasRootFields && addSpacer )
1967 QSpacerItem *spacerItem =
new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
1968 layout->addItem( spacerItem, row, 0 );
1969 layout->setRowStretch( row, 1 );
1972 formWidget = container;
1981 formWidget =
new QWidget(
this );
1982 QGridLayout *gridLayout =
new QGridLayout( formWidget );
1983 formWidget->setLayout( gridLayout );
1989 scrollArea->setWidget( formWidget );
1990 scrollArea->setWidgetResizable(
true );
1991 scrollArea->setFrameShape( QFrame::NoFrame );
1992 scrollArea->setFrameShadow( QFrame::Plain );
1993 scrollArea->setFocusProxy(
this );
1994 layout->addWidget( scrollArea );
1998 layout->addWidget( formWidget );
2005 for (
const QgsField &field : fields )
2013 QString labelText = fieldName;
2014 labelText.replace(
'&', QLatin1String(
"&&" ) );
2018 if ( widgetSetup.
type() == QLatin1String(
"Hidden" ) )
2024 QLabel *label =
new QLabel( labelText );
2026 QSvgWidget *i =
new QSvgWidget();
2027 i->setFixedSize( 18, 18 );
2032 if ( property.isActive() )
2034 mLabelDataDefinedProperties[ label ] = property;
2040 QWidget *w =
nullptr;
2045 mFormEditorWidgets.insert( idx, formWidget );
2046 mFormWidgets.append( formWidget );
2049 label->setBuddy( eww->
widget() );
2054 if ( property.isActive() )
2056 mEditableDataDefinedProperties[ formWidget ] = property;
2062 w =
new QLabel( QStringLiteral(
"<p style=\"color: red; font-style: italic;\">%1</p>" ).arg( tr(
"Failed to create widget with type '%1'" ).arg( widgetSetup.
type() ) ) );
2067 w->setObjectName( field.name() );
2071 mWidgets.append( eww );
2072 mIconMap[eww->
widget()] = i;
2077 gridLayout->addWidget( label, row++, 0, 1, 2 );
2078 gridLayout->addWidget( w, row++, 0, 1, 2 );
2079 gridLayout->addWidget( i, row++, 0, 1, 2 );
2083 gridLayout->addWidget( label, row, 0 );
2084 gridLayout->addWidget( w, row, 1 );
2085 gridLayout->addWidget( i, row++, 2 );
2099 QVBoxLayout *collapsibleGroupBoxLayout =
new QVBoxLayout();
2100 collapsibleGroupBoxLayout->addWidget( formWidget );
2101 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
2103 gridLayout->addWidget( collapsibleGroupBox, row++, 0, 1, 2 );
2105 mWidgets.append( rww );
2106 mFormWidgets.append( formWidget );
2111 QSpacerItem *spacerItem =
new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
2112 gridLayout->addItem( spacerItem, row, 0 );
2113 gridLayout->setRowStretch( row, 1 );
2119 updateFieldDependencies();
2123 mButtonBox =
new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
2124 mButtonBox->setObjectName( QStringLiteral(
"buttonBox" ) );
2125 layout->addWidget( mButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2127 mButtonBox->setVisible( buttonBoxVisible );
2129 if ( !mSearchButtonBox )
2131 mSearchButtonBox =
new QWidget();
2132 QHBoxLayout *boxLayout =
new QHBoxLayout();
2133 boxLayout->setContentsMargins( 0, 0, 0, 0 );
2134 mSearchButtonBox->setLayout( boxLayout );
2135 mSearchButtonBox->setObjectName( QStringLiteral(
"searchButtonBox" ) );
2137 QPushButton *clearButton =
new QPushButton( tr(
"&Reset Form" ), mSearchButtonBox );
2139 boxLayout->addWidget( clearButton );
2140 boxLayout->addStretch( 1 );
2142 QPushButton *flashButton =
new QPushButton();
2143 flashButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2144 flashButton->setText( tr(
"&Flash Features" ) );
2145 connect( flashButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchFlash );
2146 boxLayout->addWidget( flashButton );
2148 QPushButton *openAttributeTableButton =
new QPushButton();
2149 openAttributeTableButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2150 openAttributeTableButton->setText( tr(
"Show in &Table" ) );
2151 openAttributeTableButton->setToolTip( tr(
"Open the attribute table editor with the filtered features" ) );
2152 connect( openAttributeTableButton, &QToolButton::clicked,
this, [ = ]
2156 boxLayout->addWidget( openAttributeTableButton );
2158 QPushButton *zoomButton =
new QPushButton();
2159 zoomButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2160 zoomButton->setText( tr(
"&Zoom to Features" ) );
2161 connect( zoomButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchZoomTo );
2162 boxLayout->addWidget( zoomButton );
2164 QToolButton *selectButton =
new QToolButton();
2165 selectButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2166 selectButton->setText( tr(
"&Select Features" ) );
2168 selectButton->setPopupMode( QToolButton::MenuButtonPopup );
2169 selectButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
2170 connect( selectButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchSetSelection );
2171 QMenu *selectMenu =
new QMenu( selectButton );
2172 QAction *selectAction =
new QAction( tr(
"Select Features" ), selectMenu );
2174 connect( selectAction, &QAction::triggered,
this, &QgsAttributeForm::searchSetSelection );
2175 selectMenu->addAction( selectAction );
2176 QAction *addSelectAction =
new QAction( tr(
"Add to Current Selection" ), selectMenu );
2178 connect( addSelectAction, &QAction::triggered,
this, &QgsAttributeForm::searchAddToSelection );
2179 selectMenu->addAction( addSelectAction );
2180 QAction *deselectAction =
new QAction( tr(
"Remove from Current Selection" ), selectMenu );
2182 connect( deselectAction, &QAction::triggered,
this, &QgsAttributeForm::searchRemoveFromSelection );
2183 selectMenu->addAction( deselectAction );
2184 QAction *filterSelectAction =
new QAction( tr(
"Filter Current Selection" ), selectMenu );
2186 connect( filterSelectAction, &QAction::triggered,
this, &QgsAttributeForm::searchIntersectSelection );
2187 selectMenu->addAction( filterSelectAction );
2188 selectButton->setMenu( selectMenu );
2189 boxLayout->addWidget( selectButton );
2193 QToolButton *filterButton =
new QToolButton();
2194 filterButton->setText( tr(
"Filter Features" ) );
2195 filterButton->setPopupMode( QToolButton::MenuButtonPopup );
2196 filterButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2197 connect( filterButton, &QToolButton::clicked,
this, &QgsAttributeForm::filterTriggered );
2198 QMenu *filterMenu =
new QMenu( filterButton );
2199 QAction *filterAndAction =
new QAction( tr(
"Filter Within (\"AND\")" ), filterMenu );
2200 connect( filterAndAction, &QAction::triggered,
this, &QgsAttributeForm::filterAndTriggered );
2201 filterMenu->addAction( filterAndAction );
2202 QAction *filterOrAction =
new QAction( tr(
"Extend Filter (\"OR\")" ), filterMenu );
2203 connect( filterOrAction, &QAction::triggered,
this, &QgsAttributeForm::filterOrTriggered );
2204 filterMenu->addAction( filterOrAction );
2205 filterButton->setMenu( filterMenu );
2206 boxLayout->addWidget( filterButton );
2210 QPushButton *closeButton =
new QPushButton( tr(
"Close" ), mSearchButtonBox );
2212 closeButton->setShortcut( Qt::Key_Escape );
2213 boxLayout->addWidget( closeButton );
2216 layout->addWidget( mSearchButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2234 const auto constMInterfaces = mInterfaces;
2245 QApplication::restoreOverrideCursor();
2248void QgsAttributeForm::cleanPython()
2250 if ( !mPyFormVarName.isNull() )
2252 QString expr = QStringLiteral(
"if '%1' in locals(): del %1\n" ).arg( mPyFormVarName );
2257void QgsAttributeForm::initPython()
2274 if ( !initFilePath.isEmpty() )
2278 if ( inputFile && inputFile->open( QFile::ReadOnly ) )
2281 QTextStream inf( inputFile );
2282#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2283 inf.setCodec(
"UTF-8" );
2285 initCode = inf.readAll();
2290 QgsLogger::warning( QStringLiteral(
"The external python file path %1 could not be opened!" ).arg( initFilePath ) );
2301 if ( initCode.isEmpty() )
2303 QgsLogger::warning( QStringLiteral(
"The python code provided in the dialog is empty!" ) );
2314 if ( !initCode.isEmpty() )
2320 tr(
"Python macro could not be run due to missing permissions." ),
2328 if (
QgsPythonRunner::eval( QStringLiteral(
"len(inspect.getfullargspec(%1)[0])" ).arg( initFunction ), numArgs ) )
2330 static int sFormId = 0;
2331 mPyFormVarName = QStringLiteral(
"_qgis_featureform_%1_%2" ).arg( mFormNr ).arg( sFormId++ );
2333 QString form = QStringLiteral(
"%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )" )
2334 .arg( mPyFormVarName )
2335 .arg( ( quint64 )
this );
2339 QgsDebugMsgLevel( QStringLiteral(
"running featureForm init: %1" ).arg( mPyFormVarName ), 2 );
2342 if ( numArgs == QLatin1String(
"3" ) )
2350 msgBox.setText( tr(
"The python init function (<code>%1</code>) does not accept three arguments as expected!<br>Please check the function name in the <b>Fields</b> tab of the layer properties." ).arg( initFunction ) );
2353 QString expr = QString(
"%1(%2)" )
2354 .arg( mLayer->editFormInit() )
2355 .arg( mPyFormVarName );
2356 QgsAttributeFormInterface *iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface *>( expr,
"QgsAttributeFormInterface" );
2366 msgBox.setText( tr(
"The python init function (<code>%1</code>) could not be found!<br>Please check the function name in the <b>Fields</b> tab of the layer properties." ).arg( initFunction ) );
2374 WidgetInfo newWidgetInfo;
2376 newWidgetInfo.labelStyle = widgetDef->
labelStyle();
2378 switch ( widgetDef->
type() )
2390 mWidgets.append( actionWrapper );
2391 newWidgetInfo.widget = actionWrapper->
widget();
2392 newWidgetInfo.showLabel =
false;
2405 if ( fldIdx < fields.
count() && fldIdx >= 0 )
2411 mFormEditorWidgets.insert( fldIdx, formWidget );
2412 mFormWidgets.append( formWidget );
2416 newWidgetInfo.widget = formWidget;
2417 mWidgets.append( eww );
2419 newWidgetInfo.widget->setObjectName( fields.
at( fldIdx ).
name() );
2420 newWidgetInfo.hint = fields.
at( fldIdx ).
comment();
2425 newWidgetInfo.labelText.replace(
'&', QLatin1String(
"&&" ) );
2426 newWidgetInfo.toolTip = QStringLiteral(
"<b>%1</b><p>%2</p>" ).arg( mLayer->
attributeDisplayName( fldIdx ), newWidgetInfo.hint );
2427 newWidgetInfo.showLabel = widgetDef->
showLabel();
2448 mWidgets.append( rww );
2449 mFormWidgets.append( formWidget );
2451 newWidgetInfo.widget = formWidget;
2452 newWidgetInfo.showLabel = relDef->
showLabel();
2453 newWidgetInfo.labelText = relDef->
label();
2454 if ( newWidgetInfo.labelText.isEmpty() )
2456 newWidgetInfo.labelOnTop =
true;
2468 if ( columnCount <= 0 )
2472 QWidget *myContainer =
nullptr;
2473 bool removeLayoutMargin =
false;
2474 switch ( container->
type() )
2479 widgetName = QStringLiteral(
"QGroupBox" );
2482 groupBox->setTitle( container->
name() );
2483 if ( newWidgetInfo.labelStyle.overrideColor )
2485 if ( newWidgetInfo.labelStyle.color.isValid() )
2487 groupBox->
setStyleSheet( QStringLiteral(
"QGroupBox::title { color: %1; }" ).arg( newWidgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2490 if ( newWidgetInfo.labelStyle.overrideFont )
2492 groupBox->setFont( newWidgetInfo.labelStyle.font );
2495 myContainer = groupBox;
2496 newWidgetInfo.widget = myContainer;
2503 QWidget *rowWidget =
new QWidget();
2504 widgetName = QStringLiteral(
"Row" );
2505 myContainer = rowWidget;
2506 newWidgetInfo.widget = myContainer;
2507 removeLayoutMargin =
true;
2508 columnCount = container->
children().size();
2514 myContainer =
new QWidget();
2518 scrollArea->setWidget( myContainer );
2519 scrollArea->setWidgetResizable(
true );
2520 scrollArea->setFrameShape( QFrame::NoFrame );
2521 widgetName = QStringLiteral(
"QScrollArea QWidget" );
2523 newWidgetInfo.widget = scrollArea;
2530 QString style {QStringLiteral(
"background-color: %1;" ).arg( container->
backgroundColor().name() )};
2531 newWidgetInfo.widget->setStyleSheet( style );
2534 QGridLayout *gbLayout =
new QGridLayout();
2535 if ( removeLayoutMargin )
2536 gbLayout->setContentsMargins( 0, 0, 0, 0 );
2537 myContainer->setLayout( gbLayout );
2541 bool addSpacer =
true;
2543 const QList<QgsAttributeEditorElement *> children = container->
children();
2547 WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context );
2559 int widgetColumn = column;
2561 if ( widgetInfo.labelText.isNull() || ! widgetInfo.showLabel )
2563 gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 );
2564 widgetColumn = column + 1;
2569 QLabel *mypLabel =
new QLabel( widgetInfo.labelText );
2571 if ( widgetInfo.labelStyle.overrideColor )
2573 if ( widgetInfo.labelStyle.color.isValid() )
2575 mypLabel->setStyleSheet( QStringLiteral(
"QLabel { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2579 if ( widgetInfo.labelStyle.overrideFont )
2581 mypLabel->setFont( widgetInfo.labelStyle.font );
2589 const int fldIdx = fieldDef->
idx();
2590 if ( fldIdx < fields.
count() && fldIdx >= 0 )
2592 const QString fieldName { fields.
at( fldIdx ).
name() };
2596 if ( property.isActive() )
2598 mLabelDataDefinedProperties[ mypLabel ] = property;
2604 if ( property.isActive() )
2606 mEditableDataDefinedProperties[ widgetInfo.widget ] = property;
2612 mypLabel->setToolTip( widgetInfo.toolTip );
2613 if ( columnCount > 1 && !widgetInfo.labelOnTop )
2615 mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
2618 mypLabel->setBuddy( widgetInfo.widget );
2620 if ( widgetInfo.labelOnTop )
2622 widgetColumn = column + 1;
2623 QVBoxLayout *
c =
new QVBoxLayout();
2624 mypLabel->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
2625 c->layout()->addWidget( mypLabel );
2626 c->layout()->addWidget( widgetInfo.widget );
2627 gbLayout->addLayout(
c, row, column, 1, 2 );
2632 widgetColumn = column + 1;
2633 gbLayout->addWidget( mypLabel, row, column++ );
2634 gbLayout->addWidget( widgetInfo.widget, row, column++ );
2638 const int childHorizontalStretch = childDef->horizontalStretch();
2639 const int existingColumnStretch = gbLayout->columnStretch( widgetColumn );
2640 if ( childHorizontalStretch > 0 && childHorizontalStretch > existingColumnStretch )
2642 gbLayout->setColumnStretch( widgetColumn, childHorizontalStretch );
2645 if ( childDef->verticalStretch() > 0 && childDef->verticalStretch() > gbLayout->rowStretch( row ) )
2647 gbLayout->setRowStretch( row, childDef->verticalStretch() );
2650 if ( column >= columnCount * 2 )
2656 if ( widgetInfo.widget
2657 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
2658 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
2659 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
2663 if ( qobject_cast<QgsAttributeFormRelationEditorWidget *>( widgetInfo.widget ) )
2669 QWidget *spacer =
new QWidget();
2670 spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
2671 gbLayout->addWidget( spacer, ++row, 0 );
2672 gbLayout->setRowStretch( row, 1 );
2675 newWidgetInfo.labelText = QString();
2676 newWidgetInfo.labelOnTop =
true;
2677 newWidgetInfo.showLabel = widgetDef->
showLabel();
2690 mWidgets.append( qmlWrapper );
2692 newWidgetInfo.widget = qmlWrapper->
widget();
2693 newWidgetInfo.labelText = elementDef->
name();
2694 newWidgetInfo.labelOnTop =
true;
2695 newWidgetInfo.showLabel = widgetDef->
showLabel();
2707 mWidgets.append( htmlWrapper );
2709 newWidgetInfo.widget = htmlWrapper->
widget();
2710 newWidgetInfo.labelText = elementDef->
name();
2711 newWidgetInfo.labelOnTop =
true;
2712 newWidgetInfo.showLabel = widgetDef->
showLabel();
2725 mWidgets.append( textWrapper );
2727 newWidgetInfo.widget = textWrapper->
widget();
2728 newWidgetInfo.labelText = elementDef->
name();
2729 newWidgetInfo.labelOnTop =
false;
2730 newWidgetInfo.showLabel = widgetDef->
showLabel();
2741 mWidgets.append( spacerWrapper );
2743 newWidgetInfo.widget = spacerWrapper->
widget();
2744 newWidgetInfo.labelOnTop =
false;
2745 newWidgetInfo.showLabel =
false;
2750 QgsDebugError( QStringLiteral(
"Unknown attribute editor widget type encountered..." ) );
2754 return newWidgetInfo;
2757void QgsAttributeForm::createWrappers()
2759 QList<QWidget *> myWidgets = findChildren<QWidget *>();
2760 const QList<QgsField> fields = mLayer->
fields().
toList();
2762 const auto constMyWidgets = myWidgets;
2763 for ( QWidget *myWidget : constMyWidgets )
2766 QVariant vRel = myWidget->property(
"qgisRelation" );
2767 if ( vRel.isValid() )
2777 mWidgets.append( rww );
2782 const auto constFields = fields;
2783 for (
const QgsField &field : constFields )
2785 if ( field.name() == myWidget->objectName() )
2790 mWidgets.append( eww );
2797void QgsAttributeForm::afterWidgetInit()
2799 bool isFirstEww =
true;
2801 const auto constMWidgets = mWidgets;
2810 setFocusProxy( eww->
widget() );
2820 if ( relationWidgetWrapper )
2833 if ( e->type() == QEvent::KeyPress )
2835 QKeyEvent *keyEvent =
dynamic_cast<QKeyEvent *
>( e );
2836 if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
2848 QSet< int > &mixedValueFields,
2849 QHash< int, QVariant > &fieldSharedValues )
const
2851 mixedValueFields.clear();
2852 fieldSharedValues.clear();
2858 for (
int i = 0; i < mLayer->
fields().count(); ++i )
2860 if ( mixedValueFields.contains( i ) )
2865 fieldSharedValues[i] = f.
attribute( i );
2869 if ( fieldSharedValues.value( i ) != f.
attribute( i ) )
2871 fieldSharedValues.remove( i );
2872 mixedValueFields.insert( i );
2878 if ( mixedValueFields.count() == mLayer->
fields().
count() )
2887void QgsAttributeForm::layerSelectionChanged()
2900 resetMultiEdit(
true );
2907 mIsSettingMultiEditFeatures =
true;
2908 mMultiEditFeatureIds = fids;
2910 if ( fids.isEmpty() )
2913 QMultiMap< int, QgsAttributeFormEditorWidget * >::const_iterator wIt = mFormEditorWidgets.constBegin();
2914 for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
2916 wIt.value()->initialize( QVariant() );
2918 mIsSettingMultiEditFeatures =
false;
2925 QSet< int > mixedValueFields;
2926 QHash< int, QVariant > fieldSharedValues;
2927 scanForEqualAttributes( fit, mixedValueFields, fieldSharedValues );
2936 if ( mCurrentFormFeature.
id() != firstFeature.
id( ) )
2941 const auto constMixedValueFields = mixedValueFields;
2942 for (
int fieldIndex : std::as_const( mixedValueFields ) )
2944 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( fieldIndex );
2945 if ( formEditorWidgets.isEmpty() )
2948 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
2949 QVariantList additionalFieldValues;
2950 for (
const QString &additionalField : additionalFields )
2951 additionalFieldValues << firstFeature.
attribute( additionalField );
2954 w->initialize( firstFeature.
attribute( fieldIndex ),
true, additionalFieldValues );
2956 QHash< int, QVariant >::const_iterator sharedValueIt = fieldSharedValues.constBegin();
2957 for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
2959 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( sharedValueIt.key() );
2960 if ( formEditorWidgets.isEmpty() )
2964 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
2965 for (
const QString &additionalField : additionalFields )
2968 if ( constMixedValueFields.contains( index ) )
2975 QVariantList additionalFieldValues;
2978 for (
const QString &additionalField : additionalFields )
2979 additionalFieldValues << firstFeature.
attribute( additionalField );
2981 w->initialize( firstFeature.
attribute( sharedValueIt.key() ),
true, additionalFieldValues );
2985 for (
const QString &additionalField : additionalFields )
2988 Q_ASSERT( fieldSharedValues.contains( index ) );
2989 additionalFieldValues << fieldSharedValues.value( index );
2992 w->initialize( sharedValueIt.value(),
false, additionalFieldValues );
2996 setMultiEditFeatureIdsRelations( fids );
2998 mIsSettingMultiEditFeatures =
false;
3003 if ( mOwnsMessageBar )
3005 mOwnsMessageBar =
false;
3006 mMessageBar = messageBar;
3016 QStringList filters;
3019 QString filter = widget->currentFilterExpression();
3020 if ( !filter.isNull() )
3021 filters <<
'(' + filter +
')';
3024 return filters.join( QLatin1String(
" AND " ) );
3029 mExtraContextScope.reset( extraScope );
3035 const bool newVisibility = expression.evaluate( expressionContext ).toBool();
3037 if ( expression.isValid() && ! expression.hasEvalError() && newVisibility != isVisible )
3045 widget->setVisible( newVisibility );
3048 isVisible = newVisibility;
3051 const bool newCollapsedState = collapsedExpression.evaluate( expressionContext ).toBool();
3053 if ( collapsedExpression.isValid() && ! collapsedExpression.hasEvalError() && newCollapsedState != isCollapsed )
3058 collapsibleGroupBox->
setCollapsed( newCollapsedState );
3059 isCollapsed = newCollapsedState;
3073 if ( infos.count() == 0 || !currentFormValuesFeature( formFeature ) )
3076 const QString hint = tr(
"No feature joined" );
3077 const auto constInfos = infos;
3080 if ( !info->isDynamicFormEnabled() )
3085 mJoinedFeatures[info] = joinFeature;
3087 if ( info->hasSubset() )
3091 const auto constSubsetNames = subsetNames;
3092 for (
const QString &field : constSubsetNames )
3094 QString prefixedName = info->prefixedFieldName( field );
3096 QString hintText = hint;
3110 for (
const QgsField &field : joinFields )
3112 QString prefixedName = info->prefixedFieldName( field );
3114 QString hintText = hint;
3128bool QgsAttributeForm::fieldIsEditable(
int fieldIndex )
const
3133void QgsAttributeForm::updateFieldDependencies()
3135 mDefaultValueDependencies.clear();
3136 mVirtualFieldsDependencies.clear();
3137 mRelatedLayerFieldsDependencies.clear();
3146 updateFieldDependenciesDefaultValue( eww );
3147 updateFieldDependenciesVirtualFields( eww );
3148 updateRelatedLayerFieldsDependencies( eww );
3156 if ( exp.needsGeometry() )
3157 mNeedsGeometry =
true;
3164 for (
const int id : allAttributeIds )
3166 mDefaultValueDependencies.insertMulti(
id, eww );
3172 const QSet<QString> referencedColumns = exp.referencedColumns();
3173 for (
const QString &referencedColumn : referencedColumns )
3175 mDefaultValueDependencies.insertMulti( mLayer->
fields().
lookupField( referencedColumn ), eww );
3183 if ( expressionField.isEmpty() )
3188 if ( exp.needsGeometry() )
3189 mNeedsGeometry =
true;
3196 for (
const int id : allAttributeIds )
3198 mVirtualFieldsDependencies.insertMulti(
id, eww );
3204 const QSet<QString> referencedColumns = exp.referencedColumns();
3205 for (
const QString &referencedColumn : referencedColumns )
3207 mVirtualFieldsDependencies.insertMulti( mLayer->
fields().
lookupField( referencedColumn ), eww );
3217 if ( expressionField.contains( QStringLiteral(
"relation_aggregate" ) )
3218 || expressionField.contains( QStringLiteral(
"get_features" ) ) )
3219 mRelatedLayerFieldsDependencies.insert( eww );
3223 mRelatedLayerFieldsDependencies.clear();
3228 if ( ! editorWidgetWrapper )
3231 updateRelatedLayerFieldsDependencies( editorWidgetWrapper );
3236void QgsAttributeForm::setMultiEditFeatureIdsRelations(
const QgsFeatureIds &fids )
3241 if ( !relationEditorWidget )
3254 mIconMap[eww->
widget()]->hide();
3268 const QString file = QStringLiteral(
"/mIconJoinNotEditable.svg" );
3269 const QString tooltip = tr(
"Join settings do not allow editing" );
3270 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3274 const QString file = QStringLiteral(
"mIconJoinHasNotUpsertOnEdit.svg" );
3275 const QString tooltip = tr(
"Join settings do not allow upsert on edit" );
3276 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3280 const QString file = QStringLiteral(
"/mIconJoinedLayerNotEditable.svg" );
3281 const QString tooltip = tr(
"Joined layer is not toggled editable" );
3282 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3288void QgsAttributeForm::reloadIcon(
const QString &file,
const QString &tooltip, QSvgWidget *sw )
3291 sw->setToolTip( tooltip );
@ Row
A row of editors (horizontal layout)
@ File
Load the Python code from an external file.
@ Environment
Use the Python code available in the Python environment.
@ NoSource
Do not use Python code at all.
@ Dialog
Use the Python code provided in the dialog.
@ DragAndDrop
"Drag and drop" layout. Needs to be configured.
@ UiFile
Load a .ui file for the layout. Needs to be configured.
@ Warning
Warning message.
@ Info
Information message.
@ Success
Used for reporting a successful operation.
@ Action
A layer action element (since QGIS 3.22)
@ QmlElement
A QML element.
@ HtmlElement
A HTML element.
@ TextElement
A text element (since QGIS 3.30)
@ SpacerElement
A spacer element (since QGIS 3.30)
SelectBehavior
Specifies how a selection should be applied.
@ SetSelection
Set selection, removing any existing selection.
@ AddToSelection
Add selection to current selection.
@ IntersectSelection
Modify current selection to include only select features which match.
@ RemoveFromSelection
Remove from current selection.
static QgsNetworkContentFetcherRegistry * networkContentFetcherRegistry()
Returns the application's network content registry used for fetching temporary files during QGIS sess...
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QString iconPath(const QString &iconFile)
Returns path to the desired icon file.
This element will load a layer action onto the form.
const QgsAction & action(const QgsVectorLayer *layer) const
Returns the (possibly lazy loaded) action for the given layer.
This is a container for attribute editors, used to group them visually in the attribute form if it is...
QgsOptionalExpression visibilityExpression() const
The visibility expression is used in the attribute form to show or hide this container based on an ex...
QgsOptionalExpression collapsedExpression() const
The collapsed expression is used in the attribute form to set the collapsed status of the group box c...
bool collapsed() const
For group box containers returns true if this group box is collapsed.
Qgis::AttributeEditorContainerType type() const
Returns the container type.
QList< QgsAttributeEditorElement * > children() const
Gets a list of the children elements of this container.
QColor backgroundColor() const
Returns the background color of the container.
int columnCount() const
Gets the number of columns in this group.
This class contains context information for attribute editor widgets.
FormMode formMode() const
Returns the form mode.
QString attributeFormModeString() const
Returns given attributeFormMode as string.
@ Embed
A form was embedded as a widget on another form.
bool allowCustomUi() const
Returns true if the attribute editor should permit use of custom UI forms.
@ SearchMode
Form values are used for searching/filtering the layer.
@ FixAttributeMode
Fix feature mode, for modifying the feature attributes without saving. The updated feature is availab...
@ IdentifyMode
Identify the feature.
@ SingleEditMode
Single edit mode, for editing a single feature.
@ AggregateSearchMode
Form is in aggregate search mode, show each widget in this mode.
@ MultiEditMode
Multi edit mode, for editing fields of multiple features at once.
void setAttributeFormMode(const Mode &attributeFormMode)
Set attributeFormMode for the edited form.
This is an abstract base class for any elements of a drag and drop form.
LabelStyle labelStyle() const
Returns the label style.
Qgis::AttributeEditorType type() const
The type of this element.
bool showLabel() const
Controls if this element should be labeled with a title (field, relation or groupname).
QString name() const
Returns the name of this element.
This element will load a field's widget onto the form.
int idx() const
Returns the index of the field.
An attribute editor widget that will represent arbitrary HTML code.
QString htmlCode() const
The Html code that will be represented within this widget.
An attribute editor widget that will represent arbitrary QML code.
QString qmlCode() const
The QML code that will be represented within this widget.
This element will load a relation editor onto the form.
const QgsRelation & relation() const
Gets the id of the relation which shall be embedded.
QVariantMap relationEditorConfiguration() const
Returns the relation editor widget configuration.
QVariant nmRelationId() const
Determines the relation id of the second relation involved in an N:M relation.
bool forceSuppressFormPopup() const
Determines the force suppress form popup status.
QString relationWidgetTypeId() const
Returns the current relation widget type id.
QString label() const
Determines the label of this element.
An attribute editor widget that will represent a spacer.
bool drawLine() const
Returns true if the spacer element will contain an horizontal line.
An attribute editor widget that will represent arbitrary text code.
QString text() const
The Text that will be represented within this widget.
A groupbox that collapses/expands when toggled.
void setStyleSheet(const QString &style)
Overridden to prepare base call and avoid crash due to specific QT versions.
void setCollapsed(bool collapse)
Collapse or uncollapse this groupbox.
A groupbox that collapses/expands when toggled and can save its collapsed and checked states.
Single scope for storing variables and functions for use within a QgsExpressionContext.
static QgsExpressionContextScope * formScope(const QgsFeature &formFeature=QgsFeature(), const QString &formMode=QString())
Creates a new scope which contains functions and variables from the current attribute form/table form...
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
Class for parsing and evaluation of expressions (formerly called "search strings").
QSet< QString > referencedColumns() const
Gets list of columns referenced by the expression.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
This class wraps a request for features to a vector layer (or directly its vector data provider).
static const QString ALL_ATTRIBUTES
A special attribute that if set matches all attributes.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
bool setAttribute(int field, const QVariant &attr)
Sets an attribute's value by field index.
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
void setFields(const QgsFields &fields, bool initAttributes=false)
Assigns a field map with the feature to allow attribute access by attribute name.
void setValid(bool validity)
Sets the validity of the feature.
bool isValid() const
Returns the validity of this feature.
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
ConstraintOrigin
Origin of constraints.
@ ConstraintOriginNotSet
Constraint is not set.
@ ConstraintOriginLayer
Constraint was set by layer.
QString constraintExpression() const
Returns the constraint expression for the field, if set.
static QString fieldToolTipExtended(const QgsField &field, const QgsVectorLayer *layer)
Returns a HTML formatted tooltip string for a field, containing details like the field name,...
Encapsulate a field in an attribute table or data source.
QString displayName() const
Returns the name to use when displaying this field.
QgsDefaultValue defaultValueDefinition
QgsFieldConstraints constraints
Container of fields for a vector layer.
QList< QgsField > toList() const
Utility function to return a list of QgsField instances.
QgsAttributeList allAttributesList() const
Utility function to get list of attribute indexes.
int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
@ OriginJoin
Field comes from a joined layer (originIndex / 1000 = index of the join, originIndex % 1000 = index w...
int count() const
Returns number of items.
FieldOrigin fieldOrigin(int fieldIdx) const
Returns the field's origin (value from an enumeration).
QgsField field(int fieldIdx) const
Returns the field at particular index (must be in range 0..N-1).
bool exists(int i) const
Returns if a field index is valid.
int size() const
Returns number of items.
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
A geometry is the spatial representation of a feature.
static QgsEditorWidgetRegistry * editorWidgetRegistry()
Returns the global editor widget registry, used for managing all known edit widget factories.
static bool pythonMacroAllowed(void(*lambda)()=nullptr, QgsMessageBar *messageBar=nullptr)
Returns true if python macros are currently allowed to be run If the global option is to ask user,...
static void warning(const QString &msg)
Goes to qWarning.
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
void editingStarted()
Emitted when editing on this layer has started.
void triggerRepaint(bool deferredUpdate=false)
Will advise the map canvas (and any other interested party) that this layer requires to be repainted.
Represents an item shown within a QgsMessageBar widget.
A bar for displaying non-blocking messages to the user.
bool popWidget(QgsMessageBarItem *item)
Remove the specified item from the bar, and display the next most recent one in the stack.
void pushMessage(const QString &text, Qgis::MessageLevel level=Qgis::MessageLevel::Info, int duration=-1)
A convenience method for pushing a message with the specified text to the bar.
void pushItem(QgsMessageBarItem *item)
Display a message item on the bar, after hiding the currently visible one and putting it in a stack.
QFile * localFile(const QString &filePathOrUrl)
Returns a QFile from a local file or to a temporary file previously fetched by the registry.
bool enabled() const
Check if this optional is enabled.
T data() const
Access the payload data.
QgsRelationManager * relationManager
static QgsProject * instance()
Returns the QgsProject singleton instance.
bool hasProperty(int key) const final
Returns true if the collection contains a property with the specified key.
QgsProperty property(int key) const final
Returns a matching property from the collection, if one exists.
A store for object properties.
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a Python statement.
static bool eval(const QString &command, QString &result)
Eval a Python statement.
This class manages a set of relations between layers.
QList< QgsRelation > referencedRelations(const QgsVectorLayer *layer=nullptr) const
Gets all relations where this layer is the referenced part (i.e.
Q_INVOKABLE QgsRelation relation(const QString &id) const
Gets access to a relation by its id.
bool isInvalidJSON()
Returns whether the text edit widget contains Invalid JSON.
Wraps a label widget to display text.
bool needsGeometry() const
Returns true if the widget needs feature geometry.
void setText(const QString &text)
Sets the text code to htmlCode.
void reinitWidget()
Clears the content and makes new initialization.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
const QgsVectorLayerJoinInfo * joinForFieldIndex(int index, const QgsFields &fields, int &sourceFieldIndex) const
Finds the vector join for a layer field index.
QList< const QgsVectorLayerJoinInfo * > joinsWhereFieldIsId(const QgsField &field) const
Returns joins where the field of a target layer is considered as an id.
QgsFeature joinedFeatureOf(const QgsVectorLayerJoinInfo *info, const QgsFeature &feature) const
Returns the joined feature corresponding to the feature.
Defines left outer join from our vector layer to some other vector layer.
QStringList * joinFieldNamesSubset() const
Returns the subset of fields to be used from joined layer.
bool isDynamicFormEnabled() const
Returns whether the form has to be dynamically updated with joined fields when a feature is being cre...
bool hasUpsertOnEdit() const
Returns whether a feature created on the target layer has to impact the joined layer by creating a ne...
bool isEditable() const
Returns whether joined fields may be edited through the form of the target layer.
QgsVectorLayer * joinLayer() const
Returns joined layer (may be nullptr if the reference was set by layer ID and not resolved yet)
static bool fieldIsEditable(const QgsVectorLayer *layer, int fieldIndex, const QgsFeature &feature)
Tests whether a field is editable for a particular feature.
Represents a vector layer which manages a vector based data sets.
QString attributeDisplayName(int index) const
Convenience function that returns the attribute alias if defined or the field name else.
void beforeRemovingExpressionField(int idx)
Will be emitted, when an expression field is going to be deleted from this vector layer.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QString expressionField(int index) const
Returns the expression used for a given expression field.
int selectedFeatureCount() const
Returns the number of features that are selected in this layer.
Q_INVOKABLE void selectByExpression(const QString &expression, Qgis::SelectBehavior behavior=Qgis::SelectBehavior::SetSelection, QgsExpressionContext *context=nullptr)
Selects matching features using an expression.
void endEditCommand()
Finish edit command and add it to undo/redo stack.
void destroyEditCommand()
Destroy active command and reverts all changes in it.
Q_INVOKABLE const QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
bool changeAttributeValues(QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues=QgsAttributeMap(), bool skipDefaultValues=false)
Changes attributes' values for a feature (but does not immediately commit the changes).
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
QgsVectorLayerJoinBuffer * joinBuffer()
Returns the join buffer object.
bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue=QVariant(), bool skipDefaultValues=false)
Changes an attribute value for a feature (but does not immediately commit the changes).
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) FINAL
Adds a single feature to the sink.
void selectionChanged(const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect)
Emitted when selection was changed.
void beforeAddingExpressionField(const QString &fieldName)
Will be emitted, when an expression field is going to be added to this vector layer.
void updatedFields()
Emitted whenever the fields available from this layer have been changed.
QVariant defaultValue(int index, const QgsFeature &feature=QgsFeature(), QgsExpressionContext *context=nullptr) const
Returns the calculated default value for the specified field index.
void beginEditCommand(const QString &text)
Create edit command for undo/redo operations.
QgsEditFormConfig editFormConfig
void beforeModifiedCheck() const
Emitted when the layer is checked for modifications. Use for last-minute additions.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsVariantEqual(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether they are equal, two NULL values are always treated a...
#define Q_NOWARN_DEPRECATED_POP
#define Q_NOWARN_DEPRECATED_PUSH
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
QMap< int, QVariant > QgsAttributeMap
QSet< QgsFeatureId > QgsFeatureIds
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)