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 );
923 updateFieldDependencies();
933 mAlreadyUpdatedFields.append( eww->
fieldIdx() );
934 updateValuesDependencies( eww->
fieldIdx() );
935 mAlreadyUpdatedFields.removeAll( eww->
fieldIdx() );
938 mValuesInitialized =
true;
944 const auto widgets { findChildren< QgsAttributeFormEditorWidget * >() };
951void QgsAttributeForm::clearMultiEditMessages()
953 if ( mMultiEditUnsavedMessageBarItem )
955 if ( !mButtonBox->isVisible() )
956 mMessageBar->
popWidget( mMultiEditUnsavedMessageBarItem );
957 mMultiEditUnsavedMessageBarItem =
nullptr;
959 if ( mMultiEditMessageBarItem )
961 if ( !mButtonBox->isVisible() )
962 mMessageBar->
popWidget( mMultiEditMessageBarItem );
963 mMultiEditMessageBarItem =
nullptr;
967QString QgsAttributeForm::createFilterExpression()
const
972 QString filter = w->currentFilterExpression();
973 if ( !filter.isEmpty() )
977 if ( filters.isEmpty() )
980 QString filter = filters.join( QLatin1String(
") AND (" ) ).prepend(
'(' ).append(
')' );
989 if ( mExtraContextScope )
996void QgsAttributeForm::onAttributeChanged(
const QVariant &value,
const QVariantList &additionalFieldValues )
1001 bool signalEmitted =
false;
1003 if ( mValuesInitialized )
1022 for (
int i = 0; i < additionalFields.count(); i++ )
1024 const QString fieldName = additionalFields.at( i );
1025 const QVariant value = additionalFieldValues.at( i );
1029 signalEmitted =
true;
1031 if ( mValuesInitialized )
1032 updateJoinedFields( *eww );
1038 if ( !mIsSettingMultiEditFeatures )
1040 mUnsavedMultiEditChanges =
true;
1042 QLabel *msgLabel =
new QLabel( tr(
"Unsaved multiedit changes: <a href=\"#apply\">apply changes</a> or <a href=\"#reset\">reset changes</a>." ), mMessageBar );
1043 msgLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
1044 msgLabel->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1045 connect( msgLabel, &QLabel::linkActivated,
this, &QgsAttributeForm::multiEditMessageClicked );
1046 clearMultiEditMessages();
1049 if ( !mButtonBox->isVisible() )
1050 mMessageBar->
pushItem( mMultiEditUnsavedMessageBarItem );
1053 signalEmitted =
true;
1063 updateConstraints( eww );
1066 if ( mValuesInitialized )
1069 mAlreadyUpdatedFields.append( eww->
fieldIdx() );
1070 updateValuesDependencies( eww->
fieldIdx() );
1071 mAlreadyUpdatedFields.removeAll( eww->
fieldIdx() );
1076 updateEditableState();
1079 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1082 if ( formEditorWidget->editorWidget() == eww )
1087 formEditorWidget->editorWidget()->blockSignals(
true );
1088 formEditorWidget->editorWidget()->setValue( value );
1089 formEditorWidget->editorWidget()->blockSignals(
false );
1092 if ( !signalEmitted )
1097 bool attributeHasChanged = !mIsSettingFeature;
1099 attributeHasChanged &= !mIsSettingMultiEditFeatures;
1105void QgsAttributeForm::updateAllConstraints()
1107 const auto constMWidgets = mWidgets;
1112 updateConstraints( eww );
1120 if ( currentFormValuesFeature( ft ) )
1132 updateConstraint( ft, eww );
1135 const QList<QgsEditorWidgetWrapper *> deps = constraintDependencies( eww );
1138 updateConstraint( ft, depsEww );
1146 const QVector<ContainerInformation *> infos = mContainerInformationDependency.value( eww->
field().
name() );
1147 for ( ContainerInformation *info : infos )
1149 info->apply( &context );
1154void QgsAttributeForm::updateContainersVisibility()
1158 const QVector<ContainerInformation *> infos = mContainerVisibilityCollapsedInformation;
1160 for ( ContainerInformation *info : infos )
1162 info->apply( &context );
1172 updateAllConstraints();
1188 if ( mJoinedFeatures.contains( info ) )
1205void QgsAttributeForm::updateLabels()
1207 if ( ! mLabelDataDefinedProperties.isEmpty() )
1210 if ( currentFormValuesFeature( currentFeature ) )
1214 for (
auto it = mLabelDataDefinedProperties.constBegin() ; it != mLabelDataDefinedProperties.constEnd(); ++it )
1216 QLabel *label { it.key() };
1218 const QString value { it->valueAsString( context, QString(), &ok ) };
1219 if ( ok && ! value.isEmpty() )
1221 label->setText( value );
1228void QgsAttributeForm::updateEditableState()
1230 if ( ! mEditableDataDefinedProperties.isEmpty() )
1233 if ( currentFormValuesFeature( currentFeature ) )
1237 for (
auto it = mEditableDataDefinedProperties.constBegin() ; it != mEditableDataDefinedProperties.constEnd(); ++it )
1239 QWidget *w { it.key() };
1241 const bool isEditable { it->valueAsBool( context,
true, &ok ) && mLayer && mLayer->
isEditable() };
1251 w->setEnabled( isEditable );
1259bool QgsAttributeForm::currentFormValuesFeature(
QgsFeature &feature )
1272 if ( dst.count() > eww->
fieldIdx() )
1274 QVariantList dstVars = QVariantList() << dst.at( eww->
fieldIdx() );
1275 QVariantList srcVars = QVariantList() << eww->
value();
1276 QList<int> fieldIndexes = QList<int>() << eww->
fieldIdx();
1280 for (
const QString &fieldName : additionalFields )
1283 fieldIndexes << idx;
1284 dstVars << dst.at( idx );
1288 Q_ASSERT( dstVars.count() == srcVars.count() );
1290 for (
int i = 0; i < dstVars.count(); i++ )
1296 dst[fieldIndexes[i]] = srcVars[i];
1313void QgsAttributeForm::registerContainerInformation( QgsAttributeForm::ContainerInformation *info )
1315 mContainerVisibilityCollapsedInformation.append( info );
1317 const QSet<QString> referencedColumns = info->expression.referencedColumns().unite( info->collapsedExpression.referencedColumns() );
1319 for (
const QString &col : referencedColumns )
1321 mContainerInformationDependency[ col ].append( info );
1325bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields, QStringList &descriptions )
const
1349bool QgsAttributeForm::currentFormValidHardConstraints( QStringList &invalidFields, QStringList &descriptions )
const
1370void QgsAttributeForm::onAttributeAdded(
int idx )
1372 mPreventFeatureRefresh =
false;
1376 attrs.insert( idx, QVariant(
layer()->fields().at( idx ).type() ) );
1384void QgsAttributeForm::onAttributeDeleted(
int idx )
1386 mPreventFeatureRefresh =
false;
1390 attrs.remove( idx );
1398void QgsAttributeForm::onRelatedFeaturesChanged()
1400 updateRelatedLayerFields();
1403void QgsAttributeForm::onUpdatedFields()
1405 mPreventFeatureRefresh =
false;
1422 attrs[i] = QVariant(
layer()->fields().at( i ).type() );
1432void QgsAttributeForm::onConstraintStatusChanged(
const QString &constraint,
1438 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1441 formEditorWidget->setConstraintStatus( constraint, description, err, result );
1446 QList<QgsEditorWidgetWrapper *> wDeps;
1458 if ( name != ewwName )
1465 for (
const QString &colName : referencedColumns )
1467 if ( name == colName )
1469 wDeps.append( eww );
1482 return setupRelationWidgetWrapper( QString(), rel, context );
1495void QgsAttributeForm::preventFeatureRefresh()
1497 mPreventFeatureRefresh =
true;
1528 return mNeedsGeometry;
1531void QgsAttributeForm::synchronizeState()
1533 bool isEditable = ( mFeature.
isValid()
1543 const QList<QgsAttributeFormEditorWidget *> formWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1546 formWidget->setConstraintResultVisible( isEditable );
1550 bool enabled = isEditable && fieldIsEditable( eww->
fieldIdx() );
1551 ww->setEnabled( enabled );
1557 ww->setEnabled( isEditable );
1568 if ( mConstraintsFailMessageBarItem )
1570 mMessageBar->
popWidget( mConstraintsFailMessageBarItem );
1573 mMessageBar->
pushItem( mConstraintsFailMessageBarItem );
1577 QStringList invalidFields, descriptions;
1578 mValidConstraints = currentFormValidHardConstraints( invalidFields, descriptions );
1582 if ( !mValidConstraints && !mConstraintsFailMessageBarItem )
1584 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 );
1585 mMessageBar->
pushItem( mConstraintsFailMessageBarItem );
1587 else if ( mValidConstraints && mConstraintsFailMessageBarItem )
1589 mMessageBar->
popWidget( mConstraintsFailMessageBarItem );
1590 mConstraintsFailMessageBarItem =
nullptr;
1593 else if ( mConstraintsFailMessageBarItem )
1595 mMessageBar->
popWidget( mConstraintsFailMessageBarItem );
1596 mConstraintsFailMessageBarItem =
nullptr;
1599 isEditable = isEditable & mValidConstraints;
1604 QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
1606 okButton->setEnabled( isEditable );
1609void QgsAttributeForm::init()
1611 QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
1614 QWidget *formWidget =
nullptr;
1615 mNeedsGeometry =
false;
1617 bool buttonBoxVisible =
true;
1621 buttonBoxVisible = mButtonBox->isVisible();
1623 mButtonBox =
nullptr;
1626 if ( mSearchButtonBox )
1628 delete mSearchButtonBox;
1629 mSearchButtonBox =
nullptr;
1632 qDeleteAll( mWidgets );
1635 while ( QWidget *w = this->findChild<QWidget *>() )
1641 QVBoxLayout *vl =
new QVBoxLayout();
1642 vl->setContentsMargins( 0, 0, 0, 0 );
1644 mMessageBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1645 vl->addWidget( mMessageBar );
1650 QGridLayout *layout =
new QGridLayout();
1651 QWidget *container =
new QWidget();
1652 container->setLayout( layout );
1653 vl->addWidget( container );
1655 mFormEditorWidgets.clear();
1656 mFormWidgets.clear();
1659 setContentsMargins( 0, 0, 0, 0 );
1668 if ( file && file->open( QFile::ReadOnly ) )
1672 QFileInfo fi( file->fileName() );
1673 loader.setWorkingDirectory( fi.dir() );
1674 formWidget = loader.load( file,
this );
1677 formWidget->setWindowFlags( Qt::Widget );
1678 layout->addWidget( formWidget );
1681 mButtonBox = findChild<QDialogButtonBox *>();
1684 formWidget->installEventFilter(
this );
1696 int columnCount = 1;
1697 bool hasRootFields =
false;
1698 bool addSpacer =
true;
1707 if ( !containerDef )
1710 switch ( containerDef->
type() )
1714 tabWidget =
nullptr;
1715 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1716 if ( widgetInfo.labelStyle.overrideColor )
1718 if ( widgetInfo.labelStyle.color.isValid() )
1720 widgetInfo.widget->setStyleSheet( QStringLiteral(
"QGroupBox::title { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1723 if ( widgetInfo.labelStyle.overrideFont )
1725 widgetInfo.widget->setFont( widgetInfo.labelStyle.font );
1728 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1729 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1731 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1733 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1735 layout->setRowStretch( row, widgDef->verticalStretch() );
1749 tabWidget =
nullptr;
1750 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1751 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1752 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1754 layout->setRowStretch( row, widgDef->verticalStretch() );
1757 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1759 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1775 layout->addWidget( tabWidget, row, column, 1, 2 );
1779 QWidget *tabPage =
new QWidget( tabWidget );
1781 tabWidget->addTab( tabPage, widgDef->name() );
1782 tabWidget->
setTabStyle( tabWidget->tabBar()->count() - 1, widgDef->labelStyle() );
1786 registerContainerInformation(
new ContainerInformation( tabWidget, tabPage, containerDef->
visibilityExpression().
data() ) );
1788 QGridLayout *tabPageLayout =
new QGridLayout();
1789 tabPage->setLayout( tabPageLayout );
1791 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, tabPage, mLayer, mContext );
1792 tabPageLayout->addWidget( widgetInfo.widget );
1799 hasRootFields =
true;
1800 tabWidget =
nullptr;
1801 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1804 if ( widgetInfo.showLabel )
1806 if ( widgetInfo.labelStyle.overrideColor && widgetInfo.labelStyle.color.isValid() )
1808 collapsibleGroupBox->
setStyleSheet( QStringLiteral(
"QGroupBox::title { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1811 if ( widgetInfo.labelStyle.overrideFont )
1813 collapsibleGroupBox->setFont( widgetInfo.labelStyle.font );
1816 collapsibleGroupBox->setTitle( widgetInfo.labelText );
1819 QVBoxLayout *collapsibleGroupBoxLayout =
new QVBoxLayout();
1820 collapsibleGroupBoxLayout->addWidget( widgetInfo.widget );
1821 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
1823 QVBoxLayout *
c =
new QVBoxLayout();
1824 c->addWidget( collapsibleGroupBox );
1825 layout->addLayout(
c, row, column, 1, 2 );
1827 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1828 layout->setRowStretch( row, widgDef->verticalStretch() );
1829 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1830 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1839 hasRootFields =
true;
1840 tabWidget =
nullptr;
1841 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1842 QLabel *label =
new QLabel( widgetInfo.labelText );
1844 if ( widgetInfo.labelStyle.overrideColor )
1846 if ( widgetInfo.labelStyle.color.isValid() )
1848 label->setStyleSheet( QStringLiteral(
"QLabel { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1852 if ( widgetInfo.labelStyle.overrideFont )
1854 label->setFont( widgetInfo.labelStyle.font );
1857 label->setToolTip( widgetInfo.toolTip );
1858 if ( columnCount > 1 && !widgetInfo.labelOnTop )
1860 label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1863 label->setBuddy( widgetInfo.widget );
1866 if ( widgetInfo.widget
1867 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
1868 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
1869 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
1872 if ( !widgetInfo.showLabel )
1874 QVBoxLayout *
c =
new QVBoxLayout();
1875 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1876 c->addWidget( widgetInfo.widget );
1877 layout->addLayout(
c, row, column, 1, 2 );
1879 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1881 layout->setRowStretch( row, widgDef->verticalStretch() );
1884 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1886 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1891 else if ( widgetInfo.labelOnTop )
1893 QVBoxLayout *
c =
new QVBoxLayout();
1894 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1895 c->addWidget( label );
1896 c->addWidget( widgetInfo.widget );
1897 layout->addLayout(
c, row, column, 1, 2 );
1899 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1901 layout->setRowStretch( row, widgDef->verticalStretch() );
1904 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1906 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1913 const int widgetColumn = column + 1;
1914 layout->addWidget( label, row, column++ );
1915 layout->addWidget( widgetInfo.widget, row, column++ );
1917 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1919 layout->setRowStretch( row, widgDef->verticalStretch() );
1922 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( widgetColumn ) )
1924 layout->setColumnStretch( widgetColumn, widgDef->horizontalStretch() );
1932 const int fieldIdx = fieldElement->
idx();
1933 if ( fieldIdx >= 0 && fieldIdx < mLayer->fields().count() )
1935 const QString fieldName { mLayer->
fields().
at( fieldIdx ).
name() };
1939 if ( property.isActive() )
1941 mLabelDataDefinedProperties[ label ] = property;
1947 if ( property.isActive() )
1949 mEditableDataDefinedProperties[ widgetInfo.widget ] = property;
1956 if ( column >= columnCount * 2 )
1963 if ( hasRootFields && addSpacer )
1965 QSpacerItem *spacerItem =
new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
1966 layout->addItem( spacerItem, row, 0 );
1967 layout->setRowStretch( row, 1 );
1970 formWidget = container;
1979 formWidget =
new QWidget(
this );
1980 QGridLayout *gridLayout =
new QGridLayout( formWidget );
1981 formWidget->setLayout( gridLayout );
1987 scrollArea->setWidget( formWidget );
1988 scrollArea->setWidgetResizable(
true );
1989 scrollArea->setFrameShape( QFrame::NoFrame );
1990 scrollArea->setFrameShadow( QFrame::Plain );
1991 scrollArea->setFocusProxy(
this );
1992 layout->addWidget( scrollArea );
1996 layout->addWidget( formWidget );
2003 for (
const QgsField &field : fields )
2011 QString labelText = fieldName;
2012 labelText.replace(
'&', QLatin1String(
"&&" ) );
2016 if ( widgetSetup.
type() == QLatin1String(
"Hidden" ) )
2022 QLabel *label =
new QLabel( labelText );
2024 QSvgWidget *i =
new QSvgWidget();
2025 i->setFixedSize( 18, 18 );
2030 if ( property.isActive() )
2032 mLabelDataDefinedProperties[ label ] = property;
2038 QWidget *w =
nullptr;
2043 mFormEditorWidgets.insert( idx, formWidget );
2044 mFormWidgets.append( formWidget );
2047 label->setBuddy( eww->
widget() );
2052 if ( property.isActive() )
2054 mEditableDataDefinedProperties[ formWidget ] = property;
2060 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() ) ) );
2065 w->setObjectName( field.name() );
2069 mWidgets.append( eww );
2070 mIconMap[eww->
widget()] = i;
2075 gridLayout->addWidget( label, row++, 0, 1, 2 );
2076 gridLayout->addWidget( w, row++, 0, 1, 2 );
2077 gridLayout->addWidget( i, row++, 0, 1, 2 );
2081 gridLayout->addWidget( label, row, 0 );
2082 gridLayout->addWidget( w, row, 1 );
2083 gridLayout->addWidget( i, row++, 2 );
2097 QVBoxLayout *collapsibleGroupBoxLayout =
new QVBoxLayout();
2098 collapsibleGroupBoxLayout->addWidget( formWidget );
2099 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
2101 gridLayout->addWidget( collapsibleGroupBox, row++, 0, 1, 2 );
2103 mWidgets.append( rww );
2104 mFormWidgets.append( formWidget );
2109 QSpacerItem *spacerItem =
new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
2110 gridLayout->addItem( spacerItem, row, 0 );
2111 gridLayout->setRowStretch( row, 1 );
2116 updateFieldDependencies();
2120 mButtonBox =
new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
2121 mButtonBox->setObjectName( QStringLiteral(
"buttonBox" ) );
2122 layout->addWidget( mButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2124 mButtonBox->setVisible( buttonBoxVisible );
2126 if ( !mSearchButtonBox )
2128 mSearchButtonBox =
new QWidget();
2129 QHBoxLayout *boxLayout =
new QHBoxLayout();
2130 boxLayout->setContentsMargins( 0, 0, 0, 0 );
2131 mSearchButtonBox->setLayout( boxLayout );
2132 mSearchButtonBox->setObjectName( QStringLiteral(
"searchButtonBox" ) );
2134 QPushButton *clearButton =
new QPushButton( tr(
"&Reset Form" ), mSearchButtonBox );
2136 boxLayout->addWidget( clearButton );
2137 boxLayout->addStretch( 1 );
2139 QPushButton *flashButton =
new QPushButton();
2140 flashButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2141 flashButton->setText( tr(
"&Flash Features" ) );
2142 connect( flashButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchFlash );
2143 boxLayout->addWidget( flashButton );
2145 QPushButton *openAttributeTableButton =
new QPushButton();
2146 openAttributeTableButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2147 openAttributeTableButton->setText( tr(
"Show in &Table" ) );
2148 openAttributeTableButton->setToolTip( tr(
"Open the attribute table editor with the filtered features" ) );
2149 connect( openAttributeTableButton, &QToolButton::clicked,
this, [ = ]
2153 boxLayout->addWidget( openAttributeTableButton );
2155 QPushButton *zoomButton =
new QPushButton();
2156 zoomButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2157 zoomButton->setText( tr(
"&Zoom to Features" ) );
2158 connect( zoomButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchZoomTo );
2159 boxLayout->addWidget( zoomButton );
2161 QToolButton *selectButton =
new QToolButton();
2162 selectButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2163 selectButton->setText( tr(
"&Select Features" ) );
2165 selectButton->setPopupMode( QToolButton::MenuButtonPopup );
2166 selectButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
2167 connect( selectButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchSetSelection );
2168 QMenu *selectMenu =
new QMenu( selectButton );
2169 QAction *selectAction =
new QAction( tr(
"Select Features" ), selectMenu );
2171 connect( selectAction, &QAction::triggered,
this, &QgsAttributeForm::searchSetSelection );
2172 selectMenu->addAction( selectAction );
2173 QAction *addSelectAction =
new QAction( tr(
"Add to Current Selection" ), selectMenu );
2175 connect( addSelectAction, &QAction::triggered,
this, &QgsAttributeForm::searchAddToSelection );
2176 selectMenu->addAction( addSelectAction );
2177 QAction *deselectAction =
new QAction( tr(
"Remove from Current Selection" ), selectMenu );
2179 connect( deselectAction, &QAction::triggered,
this, &QgsAttributeForm::searchRemoveFromSelection );
2180 selectMenu->addAction( deselectAction );
2181 QAction *filterSelectAction =
new QAction( tr(
"Filter Current Selection" ), selectMenu );
2183 connect( filterSelectAction, &QAction::triggered,
this, &QgsAttributeForm::searchIntersectSelection );
2184 selectMenu->addAction( filterSelectAction );
2185 selectButton->setMenu( selectMenu );
2186 boxLayout->addWidget( selectButton );
2190 QToolButton *filterButton =
new QToolButton();
2191 filterButton->setText( tr(
"Filter Features" ) );
2192 filterButton->setPopupMode( QToolButton::MenuButtonPopup );
2193 filterButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2194 connect( filterButton, &QToolButton::clicked,
this, &QgsAttributeForm::filterTriggered );
2195 QMenu *filterMenu =
new QMenu( filterButton );
2196 QAction *filterAndAction =
new QAction( tr(
"Filter Within (\"AND\")" ), filterMenu );
2197 connect( filterAndAction, &QAction::triggered,
this, &QgsAttributeForm::filterAndTriggered );
2198 filterMenu->addAction( filterAndAction );
2199 QAction *filterOrAction =
new QAction( tr(
"Extend Filter (\"OR\")" ), filterMenu );
2200 connect( filterOrAction, &QAction::triggered,
this, &QgsAttributeForm::filterOrTriggered );
2201 filterMenu->addAction( filterOrAction );
2202 filterButton->setMenu( filterMenu );
2203 boxLayout->addWidget( filterButton );
2207 QPushButton *closeButton =
new QPushButton( tr(
"Close" ), mSearchButtonBox );
2209 closeButton->setShortcut( Qt::Key_Escape );
2210 boxLayout->addWidget( closeButton );
2213 layout->addWidget( mSearchButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2231 const auto constMInterfaces = mInterfaces;
2242 QApplication::restoreOverrideCursor();
2245void QgsAttributeForm::cleanPython()
2247 if ( !mPyFormVarName.isNull() )
2249 QString expr = QStringLiteral(
"if '%1' in locals(): del %1\n" ).arg( mPyFormVarName );
2254void QgsAttributeForm::initPython()
2271 if ( !initFilePath.isEmpty() )
2275 if ( inputFile && inputFile->open( QFile::ReadOnly ) )
2278 QTextStream inf( inputFile );
2279#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2280 inf.setCodec(
"UTF-8" );
2282 initCode = inf.readAll();
2287 QgsLogger::warning( QStringLiteral(
"The external python file path %1 could not be opened!" ).arg( initFilePath ) );
2298 if ( initCode.isEmpty() )
2300 QgsLogger::warning( QStringLiteral(
"The python code provided in the dialog is empty!" ) );
2311 if ( !initCode.isEmpty() )
2317 tr(
"Python macro could not be run due to missing permissions." ),
2325 if (
QgsPythonRunner::eval( QStringLiteral(
"len(inspect.getfullargspec(%1)[0])" ).arg( initFunction ), numArgs ) )
2327 static int sFormId = 0;
2328 mPyFormVarName = QStringLiteral(
"_qgis_featureform_%1_%2" ).arg( mFormNr ).arg( sFormId++ );
2330 QString form = QStringLiteral(
"%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )" )
2331 .arg( mPyFormVarName )
2332 .arg( ( quint64 )
this );
2336 QgsDebugMsgLevel( QStringLiteral(
"running featureForm init: %1" ).arg( mPyFormVarName ), 2 );
2339 if ( numArgs == QLatin1String(
"3" ) )
2347 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 ) );
2350 QString expr = QString(
"%1(%2)" )
2351 .arg( mLayer->editFormInit() )
2352 .arg( mPyFormVarName );
2353 QgsAttributeFormInterface *iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface *>( expr,
"QgsAttributeFormInterface" );
2363 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 ) );
2371 WidgetInfo newWidgetInfo;
2373 newWidgetInfo.labelStyle = widgetDef->
labelStyle();
2375 switch ( widgetDef->
type() )
2387 mWidgets.append( actionWrapper );
2388 newWidgetInfo.widget = actionWrapper->
widget();
2389 newWidgetInfo.showLabel =
false;
2402 if ( fldIdx < fields.
count() && fldIdx >= 0 )
2408 mFormEditorWidgets.insert( fldIdx, formWidget );
2409 mFormWidgets.append( formWidget );
2413 newWidgetInfo.widget = formWidget;
2414 mWidgets.append( eww );
2416 newWidgetInfo.widget->setObjectName( fields.
at( fldIdx ).
name() );
2417 newWidgetInfo.hint = fields.
at( fldIdx ).
comment();
2422 newWidgetInfo.labelText.replace(
'&', QLatin1String(
"&&" ) );
2423 newWidgetInfo.toolTip = QStringLiteral(
"<b>%1</b><p>%2</p>" ).arg( mLayer->
attributeDisplayName( fldIdx ), newWidgetInfo.hint );
2424 newWidgetInfo.showLabel = widgetDef->
showLabel();
2445 mWidgets.append( rww );
2446 mFormWidgets.append( formWidget );
2448 newWidgetInfo.widget = formWidget;
2449 newWidgetInfo.showLabel = relDef->
showLabel();
2450 newWidgetInfo.labelText = relDef->
label();
2451 if ( newWidgetInfo.labelText.isEmpty() )
2453 newWidgetInfo.labelOnTop =
true;
2465 if ( columnCount <= 0 )
2469 QWidget *myContainer =
nullptr;
2470 bool removeLayoutMargin =
false;
2471 switch ( container->
type() )
2476 widgetName = QStringLiteral(
"QGroupBox" );
2479 groupBox->setTitle( container->
name() );
2480 if ( newWidgetInfo.labelStyle.overrideColor )
2482 if ( newWidgetInfo.labelStyle.color.isValid() )
2484 groupBox->
setStyleSheet( QStringLiteral(
"QGroupBox::title { color: %1; }" ).arg( newWidgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2487 if ( newWidgetInfo.labelStyle.overrideFont )
2489 groupBox->setFont( newWidgetInfo.labelStyle.font );
2492 myContainer = groupBox;
2493 newWidgetInfo.widget = myContainer;
2500 QWidget *rowWidget =
new QWidget();
2501 widgetName = QStringLiteral(
"Row" );
2502 myContainer = rowWidget;
2503 newWidgetInfo.widget = myContainer;
2504 removeLayoutMargin =
true;
2505 columnCount = container->
children().size();
2511 myContainer =
new QWidget();
2515 scrollArea->setWidget( myContainer );
2516 scrollArea->setWidgetResizable(
true );
2517 scrollArea->setFrameShape( QFrame::NoFrame );
2518 widgetName = QStringLiteral(
"QScrollArea QWidget" );
2520 newWidgetInfo.widget = scrollArea;
2527 QString style {QStringLiteral(
"background-color: %1;" ).arg( container->
backgroundColor().name() )};
2528 newWidgetInfo.widget->setStyleSheet( style );
2531 QGridLayout *gbLayout =
new QGridLayout();
2532 if ( removeLayoutMargin )
2533 gbLayout->setContentsMargins( 0, 0, 0, 0 );
2534 myContainer->setLayout( gbLayout );
2538 bool addSpacer =
true;
2540 const QList<QgsAttributeEditorElement *> children = container->
children();
2544 WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context );
2556 int widgetColumn = column;
2558 if ( widgetInfo.labelText.isNull() || ! widgetInfo.showLabel )
2560 gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 );
2561 widgetColumn = column + 1;
2566 QLabel *mypLabel =
new QLabel( widgetInfo.labelText );
2568 if ( widgetInfo.labelStyle.overrideColor )
2570 if ( widgetInfo.labelStyle.color.isValid() )
2572 mypLabel->setStyleSheet( QStringLiteral(
"QLabel { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2576 if ( widgetInfo.labelStyle.overrideFont )
2578 mypLabel->setFont( widgetInfo.labelStyle.font );
2586 const int fldIdx = fieldDef->
idx();
2587 if ( fldIdx < fields.
count() && fldIdx >= 0 )
2589 const QString fieldName { fields.
at( fldIdx ).
name() };
2593 if ( property.isActive() )
2595 mLabelDataDefinedProperties[ mypLabel ] = property;
2601 if ( property.isActive() )
2603 mEditableDataDefinedProperties[ widgetInfo.widget ] = property;
2609 mypLabel->setToolTip( widgetInfo.toolTip );
2610 if ( columnCount > 1 && !widgetInfo.labelOnTop )
2612 mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
2615 mypLabel->setBuddy( widgetInfo.widget );
2617 if ( widgetInfo.labelOnTop )
2619 widgetColumn = column + 1;
2620 QVBoxLayout *
c =
new QVBoxLayout();
2621 mypLabel->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
2622 c->layout()->addWidget( mypLabel );
2623 c->layout()->addWidget( widgetInfo.widget );
2624 gbLayout->addLayout(
c, row, column, 1, 2 );
2629 widgetColumn = column + 1;
2630 gbLayout->addWidget( mypLabel, row, column++ );
2631 gbLayout->addWidget( widgetInfo.widget, row, column++ );
2635 const int childHorizontalStretch = childDef->horizontalStretch();
2636 const int existingColumnStretch = gbLayout->columnStretch( widgetColumn );
2637 if ( childHorizontalStretch > 0 && childHorizontalStretch > existingColumnStretch )
2639 gbLayout->setColumnStretch( widgetColumn, childHorizontalStretch );
2642 if ( childDef->verticalStretch() > 0 && childDef->verticalStretch() > gbLayout->rowStretch( row ) )
2644 gbLayout->setRowStretch( row, childDef->verticalStretch() );
2647 if ( column >= columnCount * 2 )
2653 if ( widgetInfo.widget
2654 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
2655 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
2656 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
2660 if ( qobject_cast<QgsAttributeFormRelationEditorWidget *>( widgetInfo.widget ) )
2666 QWidget *spacer =
new QWidget();
2667 spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
2668 gbLayout->addWidget( spacer, ++row, 0 );
2669 gbLayout->setRowStretch( row, 1 );
2672 newWidgetInfo.labelText = QString();
2673 newWidgetInfo.labelOnTop =
true;
2674 newWidgetInfo.showLabel = widgetDef->
showLabel();
2687 mWidgets.append( qmlWrapper );
2689 newWidgetInfo.widget = qmlWrapper->
widget();
2690 newWidgetInfo.labelText = elementDef->
name();
2691 newWidgetInfo.labelOnTop =
true;
2692 newWidgetInfo.showLabel = widgetDef->
showLabel();
2704 mWidgets.append( htmlWrapper );
2706 newWidgetInfo.widget = htmlWrapper->
widget();
2707 newWidgetInfo.labelText = elementDef->
name();
2708 newWidgetInfo.labelOnTop =
true;
2709 newWidgetInfo.showLabel = widgetDef->
showLabel();
2722 mWidgets.append( textWrapper );
2724 newWidgetInfo.widget = textWrapper->
widget();
2725 newWidgetInfo.labelText = elementDef->
name();
2726 newWidgetInfo.labelOnTop =
false;
2727 newWidgetInfo.showLabel = widgetDef->
showLabel();
2738 mWidgets.append( spacerWrapper );
2740 newWidgetInfo.widget = spacerWrapper->
widget();
2741 newWidgetInfo.labelOnTop =
false;
2742 newWidgetInfo.showLabel =
false;
2747 QgsDebugError( QStringLiteral(
"Unknown attribute editor widget type encountered..." ) );
2751 return newWidgetInfo;
2754void QgsAttributeForm::createWrappers()
2756 QList<QWidget *> myWidgets = findChildren<QWidget *>();
2757 const QList<QgsField> fields = mLayer->
fields().
toList();
2759 const auto constMyWidgets = myWidgets;
2760 for ( QWidget *myWidget : constMyWidgets )
2763 QVariant vRel = myWidget->property(
"qgisRelation" );
2764 if ( vRel.isValid() )
2774 mWidgets.append( rww );
2779 const auto constFields = fields;
2780 for (
const QgsField &field : constFields )
2782 if ( field.name() == myWidget->objectName() )
2787 mWidgets.append( eww );
2794void QgsAttributeForm::afterWidgetInit()
2796 bool isFirstEww =
true;
2798 const auto constMWidgets = mWidgets;
2807 setFocusProxy( eww->
widget() );
2817 if ( relationWidgetWrapper )
2830 if ( e->type() == QEvent::KeyPress )
2832 QKeyEvent *keyEvent =
dynamic_cast<QKeyEvent *
>( e );
2833 if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
2845 QSet< int > &mixedValueFields,
2846 QHash< int, QVariant > &fieldSharedValues )
const
2848 mixedValueFields.clear();
2849 fieldSharedValues.clear();
2855 for (
int i = 0; i < mLayer->
fields().count(); ++i )
2857 if ( mixedValueFields.contains( i ) )
2862 fieldSharedValues[i] = f.
attribute( i );
2866 if ( fieldSharedValues.value( i ) != f.
attribute( i ) )
2868 fieldSharedValues.remove( i );
2869 mixedValueFields.insert( i );
2875 if ( mixedValueFields.count() == mLayer->
fields().
count() )
2884void QgsAttributeForm::layerSelectionChanged()
2897 resetMultiEdit(
true );
2904 mIsSettingMultiEditFeatures =
true;
2905 mMultiEditFeatureIds = fids;
2907 if ( fids.isEmpty() )
2910 QMultiMap< int, QgsAttributeFormEditorWidget * >::const_iterator wIt = mFormEditorWidgets.constBegin();
2911 for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
2913 wIt.value()->initialize( QVariant() );
2915 mIsSettingMultiEditFeatures =
false;
2922 QSet< int > mixedValueFields;
2923 QHash< int, QVariant > fieldSharedValues;
2924 scanForEqualAttributes( fit, mixedValueFields, fieldSharedValues );
2933 if ( mCurrentFormFeature.
id() != firstFeature.
id( ) )
2938 const auto constMixedValueFields = mixedValueFields;
2939 for (
int fieldIndex : std::as_const( mixedValueFields ) )
2941 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( fieldIndex );
2942 if ( formEditorWidgets.isEmpty() )
2945 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
2946 QVariantList additionalFieldValues;
2947 for (
const QString &additionalField : additionalFields )
2948 additionalFieldValues << firstFeature.
attribute( additionalField );
2951 w->initialize( firstFeature.
attribute( fieldIndex ),
true, additionalFieldValues );
2953 QHash< int, QVariant >::const_iterator sharedValueIt = fieldSharedValues.constBegin();
2954 for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
2956 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( sharedValueIt.key() );
2957 if ( formEditorWidgets.isEmpty() )
2961 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
2962 for (
const QString &additionalField : additionalFields )
2965 if ( constMixedValueFields.contains( index ) )
2972 QVariantList additionalFieldValues;
2975 for (
const QString &additionalField : additionalFields )
2976 additionalFieldValues << firstFeature.
attribute( additionalField );
2978 w->initialize( firstFeature.
attribute( sharedValueIt.key() ),
true, additionalFieldValues );
2982 for (
const QString &additionalField : additionalFields )
2985 Q_ASSERT( fieldSharedValues.contains( index ) );
2986 additionalFieldValues << fieldSharedValues.value( index );
2989 w->initialize( sharedValueIt.value(),
false, additionalFieldValues );
2993 setMultiEditFeatureIdsRelations( fids );
2995 mIsSettingMultiEditFeatures =
false;
3000 if ( mOwnsMessageBar )
3002 mOwnsMessageBar =
false;
3003 mMessageBar = messageBar;
3013 QStringList filters;
3016 QString filter = widget->currentFilterExpression();
3017 if ( !filter.isNull() )
3018 filters <<
'(' + filter +
')';
3021 return filters.join( QLatin1String(
" AND " ) );
3026 mExtraContextScope.reset( extraScope );
3032 const bool newVisibility = expression.evaluate( expressionContext ).toBool();
3034 if ( expression.isValid() && ! expression.hasEvalError() && newVisibility != isVisible )
3042 widget->setVisible( newVisibility );
3045 isVisible = newVisibility;
3048 const bool newCollapsedState = collapsedExpression.evaluate( expressionContext ).toBool();
3050 if ( collapsedExpression.isValid() && ! collapsedExpression.hasEvalError() && newCollapsedState != isCollapsed )
3055 collapsibleGroupBox->
setCollapsed( newCollapsedState );
3056 isCollapsed = newCollapsedState;
3070 if ( infos.count() == 0 || !currentFormValuesFeature( formFeature ) )
3073 const QString hint = tr(
"No feature joined" );
3074 const auto constInfos = infos;
3077 if ( !info->isDynamicFormEnabled() )
3082 mJoinedFeatures[info] = joinFeature;
3084 if ( info->hasSubset() )
3088 const auto constSubsetNames = subsetNames;
3089 for (
const QString &field : constSubsetNames )
3091 QString prefixedName = info->prefixedFieldName( field );
3093 QString hintText = hint;
3107 for (
const QgsField &field : joinFields )
3109 QString prefixedName = info->prefixedFieldName( field );
3111 QString hintText = hint;
3125bool QgsAttributeForm::fieldIsEditable(
int fieldIndex )
const
3130void QgsAttributeForm::updateFieldDependencies()
3132 mDefaultValueDependencies.clear();
3133 mVirtualFieldsDependencies.clear();
3134 mRelatedLayerFieldsDependencies.clear();
3143 updateFieldDependenciesDefaultValue( eww );
3144 updateFieldDependenciesVirtualFields( eww );
3145 updateRelatedLayerFieldsDependencies( eww );
3153 if ( exp.needsGeometry() )
3154 mNeedsGeometry =
true;
3156 const QSet<QString> referencedColumns = exp.referencedColumns();
3157 for (
const QString &referencedColumn : referencedColumns )
3163 for (
const int id : allAttributeIds )
3165 mDefaultValueDependencies.insertMulti(
id, eww );
3170 mDefaultValueDependencies.insertMulti( mLayer->
fields().
lookupField( referencedColumn ), eww );
3178 if ( expressionField.isEmpty() )
3183 if ( exp.needsGeometry() )
3184 mNeedsGeometry =
true;
3186 const QSet<QString> referencedColumns = exp.referencedColumns();
3187 for (
const QString &referencedColumn : referencedColumns )
3192 for (
const int id : allAttributeIds )
3194 mVirtualFieldsDependencies.insertMulti(
id, eww );
3199 mVirtualFieldsDependencies.insertMulti( mLayer->
fields().
lookupField( referencedColumn ), eww );
3209 if ( expressionField.contains( QStringLiteral(
"relation_aggregate" ) )
3210 || expressionField.contains( QStringLiteral(
"get_features" ) ) )
3211 mRelatedLayerFieldsDependencies.insert( eww );
3215 mRelatedLayerFieldsDependencies.clear();
3220 if ( ! editorWidgetWrapper )
3223 updateRelatedLayerFieldsDependencies( editorWidgetWrapper );
3228void QgsAttributeForm::setMultiEditFeatureIdsRelations(
const QgsFeatureIds &fids )
3233 if ( !relationEditorWidget )
3246 mIconMap[eww->
widget()]->hide();
3260 const QString file = QStringLiteral(
"/mIconJoinNotEditable.svg" );
3261 const QString tooltip = tr(
"Join settings do not allow editing" );
3262 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3266 const QString file = QStringLiteral(
"mIconJoinHasNotUpsertOnEdit.svg" );
3267 const QString tooltip = tr(
"Join settings do not allow upsert on edit" );
3268 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3272 const QString file = QStringLiteral(
"/mIconJoinedLayerNotEditable.svg" );
3273 const QString tooltip = tr(
"Joined layer is not toggled editable" );
3274 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3280void QgsAttributeForm::reloadIcon(
const QString &file,
const QString &tooltip, QSvgWidget *sw )
3283 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)
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 override
Returns true if the collection contains a property with the specified key.
QgsProperty property(int key) const override
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)
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
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)