17#include "moc_qgsattributeform.cpp"
76int QgsAttributeForm::sFormCounter = 0;
81 , mOwnsMessageBar( true )
83 , mFormNr( sFormCounter++ )
85 , mPreventFeatureRefresh( false )
86 , mIsSettingMultiEditFeatures( false )
87 , mUnsavedMultiEditChanges( false )
88 , mEditCommandMessage( tr(
"Attributes changed" ) )
101 updateContainersVisibility();
103 updateEditableState();
109 qDeleteAll( mInterfaces );
136 mInterfaces.append( iface );
152 if ( mUnsavedMultiEditChanges )
155 int res = QMessageBox::question(
this, tr(
"Multiedit Attributes" ), tr(
"Apply changes to edited features?" ), QMessageBox::Yes | QMessageBox::No );
156 if ( res == QMessageBox::Yes )
161 clearMultiEditMessages();
163 mUnsavedMultiEditChanges =
false;
215 w->setContext( newContext );
221 w->setVisible( relationWidgetsVisible );
228 mSearchButtonBox->setVisible(
false );
233 mSearchButtonBox->setVisible(
false );
238 mSearchButtonBox->setVisible(
false );
242 resetMultiEdit(
false );
244 mSearchButtonBox->setVisible(
false );
248 mSearchButtonBox->setVisible(
true );
254 mSearchButtonBox->setVisible(
false );
262 mSearchButtonBox->setVisible(
false );
271 const auto constMWidgets = mWidgets;
286 QVariant mainValue = eww->
value();
288 additionalFieldValues[index] = value;
289 eww->
setValues( mainValue, additionalFieldValues );
303 mIsSettingFeature =
true;
320 mIsSettingFeature =
false;
321 const auto constMInterfaces = mInterfaces;
324 iface->featureChanged();
340 mIsSettingFeature =
false;
343bool QgsAttributeForm::saveEdits( QString *error )
346 bool changedLayer =
false;
351 bool doUpdate =
false;
373 *error = tr(
"JSON value for %1 is invalid and has not been saved" ).arg( eww->
field().
name() );
376 QVariantList dstVars = QVariantList() << dst.at( eww->
fieldIdx() );
377 QVariantList srcVars = QVariantList() << eww->
value();
378 QList<int> fieldIndexes = QList<int>() << eww->
fieldIdx();
382 for (
const QString &fieldName : additionalFields )
386 dstVars << dst.at( idx );
390 Q_ASSERT( dstVars.count() == srcVars.count() );
392 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)" ).arg( dst.at( i ).toString(), dst.at( i ).typeName() ).arg(
QgsVariantUtils::isNull( dst.at( i ) ) ).arg( dst.at( i ).isValid() ), 2 );
455 QgsDebugMsgLevel( QStringLiteral(
"src:'%1' (type:%2, isNull:%3, isValid:%4)" ).arg( src.at( i ).toString(), src.at( i ).typeName() ).arg(
QgsVariantUtils::isNull( src.at( i ) ) ).arg( src.at( i ).isValid() ), 2 );
457 newValues[i] = dst.at( i );
458 oldValues[i] = src.at( i );
463 std::unique_ptr<QgsVectorLayerToolsContext> context = std::make_unique<QgsVectorLayerToolsContext>();
465 context->setExpressionContext( &expressionContext );
468 if ( success && n > 0 )
495QgsFeature QgsAttributeForm::getUpdatedFeature()
const
507 QVariantList dstVars = QVariantList() << featureAttributes.at( eww->
fieldIdx() );
508 QVariantList srcVars = QVariantList() << eww->
value();
509 QList<int> fieldIndexes = QList<int>() << eww->
fieldIdx();
513 for (
const QString &fieldName : additionalFields )
517 dstVars << featureAttributes.at( idx );
521 Q_ASSERT( dstVars.count() == srcVars.count() );
523 for (
int i = 0; i < dstVars.count(); i++ )
525 if ( !
qgsVariantEqual( dstVars[i], srcVars[i] ) && srcVars[i].isValid() )
526 featureAttributes[fieldIndexes[i]] = srcVars[i];
531 return updatedFeature;
534void QgsAttributeForm::updateValuesDependencies(
const int originIdx )
536 updateValuesDependenciesDefaultValues( originIdx );
537 updateValuesDependenciesVirtualFields( originIdx );
540void QgsAttributeForm::updateValuesDependenciesDefaultValues(
const int originIdx )
542 if ( !mDefaultValueDependencies.contains( originIdx ) )
550 QgsFeature updatedFeature = getUpdatedFeature();
553 QList<QgsWidgetWrapper *> relevantWidgets = mDefaultValueDependencies.values( originIdx );
570 if ( mAlreadyUpdatedFields.contains( eww->
fieldIdx() ) )
582void QgsAttributeForm::updateValuesDependenciesVirtualFields(
const int originIdx )
584 if ( !mVirtualFieldsDependencies.contains( originIdx ) )
591 QgsFeature updatedFeature = getUpdatedFeature();
594 const QList<QgsWidgetWrapper *> relevantWidgets = mVirtualFieldsDependencies.values( originIdx );
602 if ( mAlreadyUpdatedFields.contains( eww->
fieldIdx() ) )
608 const QVariant value = exp.evaluate( &context );
614void QgsAttributeForm::updateValuesDependenciesParent()
617 QgsFeature updatedFeature = getUpdatedFeature();
618 QList<int> updatedFields;
621 const QSet<QgsEditorWidgetWrapper *> relevantWidgets = mParentDependencies;
625 if ( updatedFields.contains( eww->
fieldIdx() ) )
636void QgsAttributeForm::updateRelatedLayerFields()
639 updateRelatedLayerFieldsDependencies();
641 if ( mRelatedLayerFieldsDependencies.isEmpty() )
648 QgsFeature updatedFeature = getUpdatedFeature();
651 const QSet<QgsEditorWidgetWrapper *> relevantWidgets = mRelatedLayerFieldsDependencies;
655 if ( mAlreadyUpdatedFields.contains( eww->
fieldIdx() ) )
661 QVariant value = exp.evaluate( &context );
666void QgsAttributeForm::resetMultiEdit(
bool promptToSave )
671 mUnsavedMultiEditChanges =
false;
675void QgsAttributeForm::multiEditMessageClicked(
const QString &link )
677 clearMultiEditMessages();
678 resetMultiEdit( link == QLatin1String(
"#apply" ) );
681void QgsAttributeForm::filterTriggered()
683 QString filter = createFilterExpression();
689void QgsAttributeForm::searchZoomTo()
691 QString filter = createFilterExpression();
692 if ( filter.isEmpty() )
698void QgsAttributeForm::searchFlash()
700 QString filter = createFilterExpression();
701 if ( filter.isEmpty() )
707void QgsAttributeForm::filterAndTriggered()
709 QString filter = createFilterExpression();
710 if ( filter.isEmpty() )
718void QgsAttributeForm::filterOrTriggered()
720 QString filter = createFilterExpression();
721 if ( filter.isEmpty() )
729void QgsAttributeForm::pushSelectedFeaturesMessage()
749 QString filter = createFilterExpression();
750 if ( filter.isEmpty() )
754 pushSelectedFeaturesMessage();
759void QgsAttributeForm::searchSetSelection()
764void QgsAttributeForm::searchAddToSelection()
769void QgsAttributeForm::searchRemoveFromSelection()
774void QgsAttributeForm::searchIntersectSelection()
779bool QgsAttributeForm::saveMultiEdits()
783 const QList<int> fieldIndexes = mFormEditorWidgets.uniqueKeys();
784 mFormEditorWidgets.constBegin();
785 for (
int fieldIndex : fieldIndexes )
787 const QList<QgsAttributeFormEditorWidget *> widgets = mFormEditorWidgets.values( fieldIndex );
788 if ( !widgets.first()->hasChanged() )
791 if ( !widgets.first()->currentValue().isValid()
792 || !fieldIsEditable( fieldIndex ) )
799 widget->changesCommitted();
801 newAttributeValues.insert( fieldIndex, widgets.first()->currentValue() );
804 if ( newAttributeValues.isEmpty() )
812 int res = QMessageBox::information(
this, tr(
"Multiedit Attributes" ),
813 tr(
"Edits will be applied to all selected features." ), QMessageBox::Ok | QMessageBox::Cancel );
814 if ( res != QMessageBox::Ok )
825 const auto constMultiEditFeatureIds = mMultiEditFeatureIds;
828 QgsAttributeMap::const_iterator aIt = newAttributeValues.constBegin();
829 for ( ; aIt != newAttributeValues.constEnd(); ++aIt )
835 clearMultiEditMessages();
848 if ( !mButtonBox->isVisible() )
849 mMessageBar->
pushItem( mMultiEditMessageBarItem );
875 wrapper->notifyAboutToSave();
915 success = saveEdits( error );
919 success = saveMultiEdits();
924 mUnsavedMultiEditChanges =
false;
933 mValuesInitialized =
false;
934 const auto constMWidgets = mWidgets;
937 ww->setFeature( mFeature );
948 mAlreadyUpdatedFields.append( eww->
fieldIdx() );
949 updateValuesDependenciesVirtualFields( eww->
fieldIdx() );
950 mAlreadyUpdatedFields.removeAll( eww->
fieldIdx() );
953 mValuesInitialized =
true;
959 const auto widgets { findChildren<QgsAttributeFormEditorWidget *>() };
966void QgsAttributeForm::clearMultiEditMessages()
968 if ( mMultiEditUnsavedMessageBarItem )
970 if ( !mButtonBox->isVisible() )
971 mMessageBar->
popWidget( mMultiEditUnsavedMessageBarItem );
972 mMultiEditUnsavedMessageBarItem =
nullptr;
974 if ( mMultiEditMessageBarItem )
976 if ( !mButtonBox->isVisible() )
977 mMessageBar->
popWidget( mMultiEditMessageBarItem );
978 mMultiEditMessageBarItem =
nullptr;
982QString QgsAttributeForm::createFilterExpression()
const
987 QString filter = w->currentFilterExpression();
988 if ( !filter.isEmpty() )
992 if ( filters.isEmpty() )
995 QString filter = filters.join( QLatin1String(
") AND (" ) ).prepend(
'(' ).append(
')' );
1004 if ( mExtraContextScope )
1017void QgsAttributeForm::onAttributeChanged(
const QVariant &value,
const QVariantList &additionalFieldValues )
1022 bool signalEmitted =
false;
1024 if ( mValuesInitialized )
1031 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1034 if ( formEditorWidget->editorWidget() == eww )
1038 formEditorWidget->editorWidget()->setValue( value );
1055 for (
int i = 0; i < additionalFields.count(); i++ )
1057 const QString fieldName = additionalFields.at( i );
1058 const QVariant value = additionalFieldValues.at( i );
1062 signalEmitted =
true;
1064 if ( mValuesInitialized )
1065 updateJoinedFields( *eww );
1071 if ( !mIsSettingMultiEditFeatures )
1073 mUnsavedMultiEditChanges =
true;
1075 QLabel *msgLabel =
new QLabel( tr(
"Unsaved multiedit changes: <a href=\"#apply\">apply changes</a> or <a href=\"#reset\">reset changes</a>." ), mMessageBar );
1076 msgLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
1077 msgLabel->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1078 connect( msgLabel, &QLabel::linkActivated,
this, &QgsAttributeForm::multiEditMessageClicked );
1079 clearMultiEditMessages();
1082 if ( !mButtonBox->isVisible() )
1083 mMessageBar->
pushItem( mMultiEditUnsavedMessageBarItem );
1086 signalEmitted =
true;
1096 updateConstraints( eww );
1099 if ( mValuesInitialized )
1102 mAlreadyUpdatedFields.append( eww->
fieldIdx() );
1103 updateValuesDependencies( eww->
fieldIdx() );
1104 mAlreadyUpdatedFields.removeAll( eww->
fieldIdx() );
1109 updateEditableState();
1111 if ( !signalEmitted )
1116 bool attributeHasChanged = !mIsSettingFeature;
1118 attributeHasChanged &= !mIsSettingMultiEditFeatures;
1124void QgsAttributeForm::updateAllConstraints()
1126 const auto constMWidgets = mWidgets;
1131 updateConstraints( eww );
1139 if ( currentFormValuesFeature( ft ) )
1151 updateConstraint( ft, eww );
1154 const QList<QgsEditorWidgetWrapper *> deps = constraintDependencies( eww );
1157 updateConstraint( ft, depsEww );
1165 const QVector<ContainerInformation *> infos = mContainerInformationDependency.value( eww->
field().
name() );
1166 for ( ContainerInformation *info : infos )
1168 info->apply( &context );
1173void QgsAttributeForm::updateContainersVisibility()
1177 const QVector<ContainerInformation *> infos = mContainerVisibilityCollapsedInformation;
1179 for ( ContainerInformation *info : infos )
1181 info->apply( &context );
1191 updateAllConstraints();
1206 if ( mJoinedFeatures.contains( info ) )
1222void QgsAttributeForm::updateLabels()
1224 if ( !mLabelDataDefinedProperties.isEmpty() )
1227 if ( currentFormValuesFeature( currentFeature ) )
1231 for (
auto it = mLabelDataDefinedProperties.constBegin(); it != mLabelDataDefinedProperties.constEnd(); ++it )
1233 QLabel *label { it.key() };
1235 const QString value { it->valueAsString( context, QString(), &ok ) };
1236 if ( ok && !value.isEmpty() )
1238 label->setText( value );
1245void QgsAttributeForm::updateEditableState()
1247 if ( !mEditableDataDefinedProperties.isEmpty() )
1250 if ( currentFormValuesFeature( currentFeature ) )
1254 for (
auto it = mEditableDataDefinedProperties.constBegin(); it != mEditableDataDefinedProperties.constEnd(); ++it )
1256 QWidget *w { it.key() };
1258 const bool isEditable { it->valueAsBool( context,
true, &ok ) && mLayer && mLayer->
isEditable() };
1268 w->setEnabled( isEditable );
1276bool QgsAttributeForm::currentFormValuesFeature(
QgsFeature &feature )
1289 if ( dst.count() > eww->
fieldIdx() )
1291 QVariantList dstVars = QVariantList() << dst.at( eww->
fieldIdx() );
1292 QVariantList srcVars = QVariantList() << eww->
value();
1293 QList<int> fieldIndexes = QList<int>() << eww->
fieldIdx();
1297 for (
const QString &fieldName : additionalFields )
1300 fieldIndexes << idx;
1301 dstVars << dst.at( idx );
1305 Q_ASSERT( dstVars.count() == srcVars.count() );
1307 for (
int i = 0; i < dstVars.count(); i++ )
1313 dst[fieldIndexes[i]] = srcVars[i];
1330void QgsAttributeForm::registerContainerInformation( QgsAttributeForm::ContainerInformation *info )
1332 mContainerVisibilityCollapsedInformation.append( info );
1334 const QSet<QString> referencedColumns = info->expression.referencedColumns().unite( info->collapsedExpression.referencedColumns() );
1336 for (
const QString &col : referencedColumns )
1338 mContainerInformationDependency[col].append( info );
1342bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields, QStringList &descriptions )
const
1344 bool valid {
true };
1366bool QgsAttributeForm::currentFormValidHardConstraints( QStringList &invalidFields, QStringList &descriptions )
const
1368 bool valid {
true };
1387void QgsAttributeForm::onAttributeAdded(
int idx )
1389 mPreventFeatureRefresh =
false;
1401void QgsAttributeForm::onAttributeDeleted(
int idx )
1403 mPreventFeatureRefresh =
false;
1407 attrs.remove( idx );
1415void QgsAttributeForm::onRelatedFeaturesChanged()
1417 updateRelatedLayerFields();
1420void QgsAttributeForm::onUpdatedFields()
1422 mPreventFeatureRefresh =
false;
1454 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1458 formEditorWidget->setConstraintStatus( constraint, description, err, result );
1459 if ( formEditorWidget->editorWidget() != eww )
1461 formEditorWidget->editorWidget()->updateConstraint( result, err );
1468 QList<QgsEditorWidgetWrapper *> wDeps;
1480 if ( name != ewwName )
1487 for (
const QString &colName : referencedColumns )
1489 if ( name == colName )
1491 wDeps.append( eww );
1504 return setupRelationWidgetWrapper( QString(), rel, context );
1517void QgsAttributeForm::preventFeatureRefresh()
1519 mPreventFeatureRefresh =
true;
1545 updateValuesDependenciesParent();
1559 return mNeedsGeometry;
1562void QgsAttributeForm::synchronizeState()
1564 bool isEditable = ( mFeature.
isValid()
1574 const QList<QgsAttributeFormEditorWidget *> formWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1577 formWidget->setConstraintResultVisible( isEditable );
1581 bool enabled = isEditable && fieldIsEditable( eww->
fieldIdx() );
1582 ww->setEnabled( enabled );
1588 ww->setEnabled( isEditable );
1598 if ( mConstraintsFailMessageBarItem )
1600 mMessageBar->
popWidget( mConstraintsFailMessageBarItem );
1603 mMessageBar->
pushItem( mConstraintsFailMessageBarItem );
1607 QStringList invalidFields, descriptions;
1608 mValidConstraints = currentFormValidHardConstraints( invalidFields, descriptions );
1612 if ( !mValidConstraints && !mConstraintsFailMessageBarItem )
1614 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 );
1615 mMessageBar->
pushItem( mConstraintsFailMessageBarItem );
1617 else if ( mValidConstraints && mConstraintsFailMessageBarItem )
1619 mMessageBar->
popWidget( mConstraintsFailMessageBarItem );
1620 mConstraintsFailMessageBarItem =
nullptr;
1623 else if ( mConstraintsFailMessageBarItem )
1625 mMessageBar->
popWidget( mConstraintsFailMessageBarItem );
1626 mConstraintsFailMessageBarItem =
nullptr;
1629 isEditable = isEditable & mValidConstraints;
1634 QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
1636 okButton->setEnabled( isEditable );
1639void QgsAttributeForm::init()
1641 QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
1644 QWidget *formWidget =
nullptr;
1645 mNeedsGeometry =
false;
1647 bool buttonBoxVisible =
true;
1651 buttonBoxVisible = mButtonBox->isVisible();
1653 mButtonBox =
nullptr;
1656 if ( mSearchButtonBox )
1658 delete mSearchButtonBox;
1659 mSearchButtonBox =
nullptr;
1662 qDeleteAll( mWidgets );
1665 while ( QWidget *w = this->findChild<QWidget *>() )
1671 QVBoxLayout *vl =
new QVBoxLayout();
1672 vl->setContentsMargins( 0, 0, 0, 0 );
1674 mMessageBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1675 vl->addWidget( mMessageBar );
1680 QGridLayout *layout =
new QGridLayout();
1681 QWidget *container =
new QWidget();
1682 container->setLayout( layout );
1683 vl->addWidget( container );
1685 mFormEditorWidgets.clear();
1686 mFormWidgets.clear();
1689 setContentsMargins( 0, 0, 0, 0 );
1697 if ( file && file->open( QFile::ReadOnly ) )
1701 QFileInfo fi( file->fileName() );
1702 loader.setWorkingDirectory( fi.dir() );
1703 formWidget = loader.load( file,
this );
1706 formWidget->setWindowFlags( Qt::Widget );
1707 layout->addWidget( formWidget );
1710 mButtonBox = findChild<QDialogButtonBox *>();
1713 formWidget->installEventFilter(
this );
1725 int columnCount = 1;
1726 bool hasRootFields =
false;
1727 bool addSpacer =
true;
1736 if ( !containerDef )
1739 switch ( containerDef->
type() )
1743 tabWidget =
nullptr;
1744 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1745 if ( widgetInfo.labelStyle.overrideColor )
1747 if ( widgetInfo.labelStyle.color.isValid() )
1749 widgetInfo.widget->setStyleSheet( QStringLiteral(
"QGroupBox::title { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1752 if ( widgetInfo.labelStyle.overrideFont )
1754 widgetInfo.widget->setFont( widgetInfo.labelStyle.font );
1757 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1758 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1760 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1762 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1764 layout->setRowStretch( row, widgDef->verticalStretch() );
1778 tabWidget =
nullptr;
1779 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1780 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1781 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1783 layout->setRowStretch( row, widgDef->verticalStretch() );
1786 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1788 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1804 layout->addWidget( tabWidget, row, column, 1, 2 );
1808 QWidget *tabPage =
new QWidget( tabWidget );
1810 tabWidget->addTab( tabPage, widgDef->name() );
1811 tabWidget->
setTabStyle( tabWidget->tabBar()->count() - 1, widgDef->labelStyle() );
1815 registerContainerInformation(
new ContainerInformation( tabWidget, tabPage, containerDef->
visibilityExpression().
data() ) );
1817 QGridLayout *tabPageLayout =
new QGridLayout();
1818 tabPage->setLayout( tabPageLayout );
1820 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, tabPage, mLayer, mContext );
1821 tabPageLayout->addWidget( widgetInfo.widget );
1828 hasRootFields =
true;
1829 tabWidget =
nullptr;
1830 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1833 if ( widgetInfo.showLabel )
1835 if ( widgetInfo.labelStyle.overrideColor && widgetInfo.labelStyle.color.isValid() )
1837 collapsibleGroupBox->
setStyleSheet( QStringLiteral(
"QGroupBox::title { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1840 if ( widgetInfo.labelStyle.overrideFont )
1842 collapsibleGroupBox->setFont( widgetInfo.labelStyle.font );
1845 collapsibleGroupBox->setTitle( widgetInfo.labelText );
1848 QVBoxLayout *collapsibleGroupBoxLayout =
new QVBoxLayout();
1849 collapsibleGroupBoxLayout->addWidget( widgetInfo.widget );
1850 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
1852 QVBoxLayout *
c =
new QVBoxLayout();
1853 c->addWidget( collapsibleGroupBox );
1854 layout->addLayout(
c, row, column, 1, 2 );
1856 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1857 layout->setRowStretch( row, widgDef->verticalStretch() );
1858 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1859 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1868 hasRootFields =
true;
1869 tabWidget =
nullptr;
1870 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1871 QLabel *label =
new QLabel( widgetInfo.labelText );
1873 if ( widgetInfo.labelStyle.overrideColor )
1875 if ( widgetInfo.labelStyle.color.isValid() )
1877 label->setStyleSheet( QStringLiteral(
"QLabel { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1881 if ( widgetInfo.labelStyle.overrideFont )
1883 label->setFont( widgetInfo.labelStyle.font );
1886 label->setToolTip( widgetInfo.toolTip );
1887 if ( columnCount > 1 && !widgetInfo.labelOnTop )
1889 label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1892 label->setBuddy( widgetInfo.widget );
1895 if ( widgetInfo.widget
1896 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
1897 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
1898 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
1901 if ( !widgetInfo.showLabel )
1903 QVBoxLayout *
c =
new QVBoxLayout();
1904 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1905 c->addWidget( widgetInfo.widget );
1906 layout->addLayout(
c, row, column, 1, 2 );
1908 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1910 layout->setRowStretch( row, widgDef->verticalStretch() );
1913 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1915 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1920 else if ( widgetInfo.labelOnTop )
1922 QVBoxLayout *
c =
new QVBoxLayout();
1923 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1924 c->addWidget( label );
1925 c->addWidget( widgetInfo.widget );
1926 layout->addLayout(
c, row, column, 1, 2 );
1928 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1930 layout->setRowStretch( row, widgDef->verticalStretch() );
1933 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1935 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1942 const int widgetColumn = column + 1;
1943 layout->addWidget( label, row, column++ );
1944 layout->addWidget( widgetInfo.widget, row, column++ );
1946 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1948 layout->setRowStretch( row, widgDef->verticalStretch() );
1951 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( widgetColumn ) )
1953 layout->setColumnStretch( widgetColumn, widgDef->horizontalStretch() );
1961 const int fieldIdx = fieldElement->
idx();
1962 if ( fieldIdx >= 0 && fieldIdx < mLayer->fields().count() )
1964 const QString fieldName { mLayer->
fields().
at( fieldIdx ).
name() };
1968 if ( property.isActive() )
1970 mLabelDataDefinedProperties[label] = property;
1976 if ( property.isActive() )
1978 mEditableDataDefinedProperties[widgetInfo.widget] = property;
1985 if ( column >= columnCount * 2 )
1992 if ( hasRootFields && addSpacer )
1994 QSpacerItem *spacerItem =
new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
1995 layout->addItem( spacerItem, row, 0 );
1996 layout->setRowStretch( row, 1 );
1999 formWidget = container;
2008 formWidget =
new QWidget(
this );
2009 QGridLayout *gridLayout =
new QGridLayout( formWidget );
2010 formWidget->setLayout( gridLayout );
2016 scrollArea->setWidget( formWidget );
2017 scrollArea->setWidgetResizable(
true );
2018 scrollArea->setFrameShape( QFrame::NoFrame );
2019 scrollArea->setFrameShadow( QFrame::Plain );
2020 scrollArea->setFocusProxy(
this );
2021 layout->addWidget( scrollArea );
2025 layout->addWidget( formWidget );
2032 for (
const QgsField &field : fields )
2040 QString labelText = fieldName;
2041 labelText.replace(
'&', QLatin1String(
"&&" ) );
2045 if ( widgetSetup.
type() == QLatin1String(
"Hidden" ) )
2051 QLabel *label =
new QLabel( labelText );
2053 QSvgWidget *i =
new QSvgWidget();
2054 i->setFixedSize( 18, 18 );
2059 if ( property.isActive() )
2061 mLabelDataDefinedProperties[label] = property;
2067 QWidget *w =
nullptr;
2072 mFormEditorWidgets.insert( idx, formWidget );
2073 mFormWidgets.append( formWidget );
2076 label->setBuddy( eww->
widget() );
2081 if ( property.isActive() )
2083 mEditableDataDefinedProperties[formWidget] = property;
2089 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() ) ) );
2094 w->setObjectName( field.name() );
2098 mWidgets.append( eww );
2099 mIconMap[eww->
widget()] = i;
2104 gridLayout->addWidget( label, row++, 0, 1, 2 );
2105 gridLayout->addWidget( w, row++, 0, 1, 2 );
2106 gridLayout->addWidget( i, row++, 0, 1, 2 );
2110 gridLayout->addWidget( label, row, 0 );
2111 gridLayout->addWidget( w, row, 1 );
2112 gridLayout->addWidget( i, row++, 2 );
2125 QVBoxLayout *collapsibleGroupBoxLayout =
new QVBoxLayout();
2126 collapsibleGroupBoxLayout->addWidget( formWidget );
2127 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
2129 gridLayout->addWidget( collapsibleGroupBox, row++, 0, 1, 2 );
2131 mWidgets.append( rww );
2132 mFormWidgets.append( formWidget );
2137 QSpacerItem *spacerItem =
new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
2138 gridLayout->addItem( spacerItem, row, 0 );
2139 gridLayout->setRowStretch( row, 1 );
2145 updateFieldDependencies();
2149 mButtonBox =
new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
2150 mButtonBox->setObjectName( QStringLiteral(
"buttonBox" ) );
2151 layout->addWidget( mButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2153 mButtonBox->setVisible( buttonBoxVisible );
2155 if ( !mSearchButtonBox )
2157 mSearchButtonBox =
new QWidget();
2158 QHBoxLayout *boxLayout =
new QHBoxLayout();
2159 boxLayout->setContentsMargins( 0, 0, 0, 0 );
2160 mSearchButtonBox->setLayout( boxLayout );
2161 mSearchButtonBox->setObjectName( QStringLiteral(
"searchButtonBox" ) );
2163 QPushButton *clearButton =
new QPushButton( tr(
"&Reset Form" ), mSearchButtonBox );
2165 boxLayout->addWidget( clearButton );
2166 boxLayout->addStretch( 1 );
2168 QPushButton *flashButton =
new QPushButton();
2169 flashButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2170 flashButton->setText( tr(
"&Flash Features" ) );
2171 connect( flashButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchFlash );
2172 boxLayout->addWidget( flashButton );
2174 QPushButton *openAttributeTableButton =
new QPushButton();
2175 openAttributeTableButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2176 openAttributeTableButton->setText( tr(
"Show in &Table" ) );
2177 openAttributeTableButton->setToolTip( tr(
"Open the attribute table editor with the filtered features" ) );
2178 connect( openAttributeTableButton, &QToolButton::clicked,
this, [=] {
2181 boxLayout->addWidget( openAttributeTableButton );
2183 QPushButton *zoomButton =
new QPushButton();
2184 zoomButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2185 zoomButton->setText( tr(
"&Zoom to Features" ) );
2186 connect( zoomButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchZoomTo );
2187 boxLayout->addWidget( zoomButton );
2189 QToolButton *selectButton =
new QToolButton();
2190 selectButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2191 selectButton->setText( tr(
"&Select Features" ) );
2193 selectButton->setPopupMode( QToolButton::MenuButtonPopup );
2194 selectButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
2195 connect( selectButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchSetSelection );
2196 QMenu *selectMenu =
new QMenu( selectButton );
2197 QAction *selectAction =
new QAction( tr(
"Select Features" ), selectMenu );
2199 connect( selectAction, &QAction::triggered,
this, &QgsAttributeForm::searchSetSelection );
2200 selectMenu->addAction( selectAction );
2201 QAction *addSelectAction =
new QAction( tr(
"Add to Current Selection" ), selectMenu );
2203 connect( addSelectAction, &QAction::triggered,
this, &QgsAttributeForm::searchAddToSelection );
2204 selectMenu->addAction( addSelectAction );
2205 QAction *deselectAction =
new QAction( tr(
"Remove from Current Selection" ), selectMenu );
2207 connect( deselectAction, &QAction::triggered,
this, &QgsAttributeForm::searchRemoveFromSelection );
2208 selectMenu->addAction( deselectAction );
2209 QAction *filterSelectAction =
new QAction( tr(
"Filter Current Selection" ), selectMenu );
2211 connect( filterSelectAction, &QAction::triggered,
this, &QgsAttributeForm::searchIntersectSelection );
2212 selectMenu->addAction( filterSelectAction );
2213 selectButton->setMenu( selectMenu );
2214 boxLayout->addWidget( selectButton );
2218 QToolButton *filterButton =
new QToolButton();
2219 filterButton->setText( tr(
"Filter Features" ) );
2220 filterButton->setPopupMode( QToolButton::MenuButtonPopup );
2221 filterButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2222 connect( filterButton, &QToolButton::clicked,
this, &QgsAttributeForm::filterTriggered );
2223 QMenu *filterMenu =
new QMenu( filterButton );
2224 QAction *filterAndAction =
new QAction( tr(
"Filter Within (\"AND\")" ), filterMenu );
2225 connect( filterAndAction, &QAction::triggered,
this, &QgsAttributeForm::filterAndTriggered );
2226 filterMenu->addAction( filterAndAction );
2227 QAction *filterOrAction =
new QAction( tr(
"Extend Filter (\"OR\")" ), filterMenu );
2228 connect( filterOrAction, &QAction::triggered,
this, &QgsAttributeForm::filterOrTriggered );
2229 filterMenu->addAction( filterOrAction );
2230 filterButton->setMenu( filterMenu );
2231 boxLayout->addWidget( filterButton );
2235 QPushButton *closeButton =
new QPushButton( tr(
"Close" ), mSearchButtonBox );
2237 closeButton->setShortcut( Qt::Key_Escape );
2238 boxLayout->addWidget( closeButton );
2241 layout->addWidget( mSearchButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2259 const auto constMInterfaces = mInterfaces;
2270 QApplication::restoreOverrideCursor();
2273void QgsAttributeForm::cleanPython()
2275 if ( !mPyFormVarName.isNull() )
2277 QString expr = QStringLiteral(
"if '%1' in locals(): del %1\n" ).arg( mPyFormVarName );
2282void QgsAttributeForm::initPython()
2298 if ( !initFilePath.isEmpty() )
2302 if ( inputFile && inputFile->open( QFile::ReadOnly ) )
2305 QTextStream inf( inputFile );
2306#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
2307 inf.setCodec(
"UTF-8" );
2309 initCode = inf.readAll();
2314 QgsLogger::warning( QStringLiteral(
"The external python file path %1 could not be opened!" ).arg( initFilePath ) );
2325 if ( initCode.isEmpty() )
2327 QgsLogger::warning( QStringLiteral(
"The python code provided in the dialog is empty!" ) );
2338 if ( !initCode.isEmpty() )
2350 if (
QgsPythonRunner::eval( QStringLiteral(
"len(inspect.getfullargspec(%1)[0])" ).arg( initFunction ), numArgs ) )
2352 static int sFormId = 0;
2353 mPyFormVarName = QStringLiteral(
"_qgis_featureform_%1_%2" ).arg( mFormNr ).arg( sFormId++ );
2355 QString form = QStringLiteral(
"%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )" )
2356 .arg( mPyFormVarName )
2357 .arg( ( quint64 )
this );
2361 QgsDebugMsgLevel( QStringLiteral(
"running featureForm init: %1" ).arg( mPyFormVarName ), 2 );
2364 if ( numArgs == QLatin1String(
"3" ) )
2372 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 ) );
2375 QString expr = QString(
"%1(%2)" )
2376 .arg( mLayer->editFormInit() )
2377 .arg( mPyFormVarName );
2378 QgsAttributeFormInterface *iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface *>( expr,
"QgsAttributeFormInterface" );
2388 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 ) );
2396 WidgetInfo newWidgetInfo;
2398 newWidgetInfo.labelStyle = widgetDef->
labelStyle();
2400 switch ( widgetDef->
type() )
2412 mWidgets.append( actionWrapper );
2413 newWidgetInfo.widget = actionWrapper->
widget();
2414 newWidgetInfo.showLabel =
false;
2427 if ( fldIdx < fields.
count() && fldIdx >= 0 )
2433 mFormEditorWidgets.insert( fldIdx, formWidget );
2434 mFormWidgets.append( formWidget );
2438 newWidgetInfo.widget = formWidget;
2439 mWidgets.append( eww );
2441 newWidgetInfo.widget->setObjectName( fields.
at( fldIdx ).
name() );
2442 newWidgetInfo.hint = fields.
at( fldIdx ).
comment();
2447 newWidgetInfo.labelText.replace(
'&', QLatin1String(
"&&" ) );
2448 newWidgetInfo.toolTip = QStringLiteral(
"<b>%1</b><p>%2</p>" ).arg( mLayer->
attributeDisplayName( fldIdx ), newWidgetInfo.hint );
2449 newWidgetInfo.showLabel = widgetDef->
showLabel();
2470 mWidgets.append( rww );
2471 mFormWidgets.append( formWidget );
2473 newWidgetInfo.widget = formWidget;
2474 newWidgetInfo.showLabel = relDef->
showLabel();
2475 newWidgetInfo.labelText = relDef->
label();
2476 if ( newWidgetInfo.labelText.isEmpty() )
2478 newWidgetInfo.labelOnTop =
true;
2490 if ( columnCount <= 0 )
2494 QWidget *myContainer =
nullptr;
2495 bool removeLayoutMargin =
false;
2496 switch ( container->
type() )
2501 widgetName = QStringLiteral(
"QGroupBox" );
2504 groupBox->setTitle( container->
name() );
2505 if ( newWidgetInfo.labelStyle.overrideColor )
2507 if ( newWidgetInfo.labelStyle.color.isValid() )
2509 groupBox->
setStyleSheet( QStringLiteral(
"QGroupBox::title { color: %1; }" ).arg( newWidgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2512 if ( newWidgetInfo.labelStyle.overrideFont )
2514 groupBox->setFont( newWidgetInfo.labelStyle.font );
2517 myContainer = groupBox;
2518 newWidgetInfo.widget = myContainer;
2525 QWidget *rowWidget =
new QWidget();
2526 widgetName = QStringLiteral(
"Row" );
2527 myContainer = rowWidget;
2528 newWidgetInfo.widget = myContainer;
2529 removeLayoutMargin =
true;
2530 columnCount = container->
children().size();
2536 myContainer =
new QWidget();
2540 scrollArea->setWidget( myContainer );
2541 scrollArea->setWidgetResizable(
true );
2542 scrollArea->setFrameShape( QFrame::NoFrame );
2543 widgetName = QStringLiteral(
"QScrollArea QWidget" );
2545 newWidgetInfo.widget = scrollArea;
2552 QString style { QStringLiteral(
"background-color: %1;" ).arg( container->
backgroundColor().name() ) };
2553 newWidgetInfo.widget->setStyleSheet( style );
2556 QGridLayout *gbLayout =
new QGridLayout();
2557 if ( removeLayoutMargin )
2558 gbLayout->setContentsMargins( 0, 0, 0, 0 );
2559 myContainer->setLayout( gbLayout );
2563 bool addSpacer =
true;
2565 const QList<QgsAttributeEditorElement *> children = container->
children();
2569 WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context );
2581 int widgetColumn = column;
2583 if ( widgetInfo.labelText.isNull() || !widgetInfo.showLabel )
2585 gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 );
2586 widgetColumn = column + 1;
2591 QLabel *mypLabel =
new QLabel( widgetInfo.labelText );
2593 if ( widgetInfo.labelStyle.overrideColor )
2595 if ( widgetInfo.labelStyle.color.isValid() )
2597 mypLabel->setStyleSheet( QStringLiteral(
"QLabel { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2601 if ( widgetInfo.labelStyle.overrideFont )
2603 mypLabel->setFont( widgetInfo.labelStyle.font );
2611 const int fldIdx = fieldDef->
idx();
2612 if ( fldIdx < fields.
count() && fldIdx >= 0 )
2614 const QString fieldName { fields.
at( fldIdx ).
name() };
2618 if ( property.isActive() )
2620 mLabelDataDefinedProperties[mypLabel] = property;
2626 if ( property.isActive() )
2628 mEditableDataDefinedProperties[widgetInfo.widget] = property;
2634 mypLabel->setToolTip( widgetInfo.toolTip );
2635 if ( columnCount > 1 && !widgetInfo.labelOnTop )
2637 mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
2640 mypLabel->setBuddy( widgetInfo.widget );
2642 if ( widgetInfo.labelOnTop )
2644 widgetColumn = column + 1;
2645 QVBoxLayout *
c =
new QVBoxLayout();
2646 mypLabel->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
2647 c->layout()->addWidget( mypLabel );
2648 c->layout()->addWidget( widgetInfo.widget );
2649 gbLayout->addLayout(
c, row, column, 1, 2 );
2654 widgetColumn = column + 1;
2655 gbLayout->addWidget( mypLabel, row, column++ );
2656 gbLayout->addWidget( widgetInfo.widget, row, column++ );
2660 const int childHorizontalStretch = childDef->horizontalStretch();
2661 const int existingColumnStretch = gbLayout->columnStretch( widgetColumn );
2662 if ( childHorizontalStretch > 0 && childHorizontalStretch > existingColumnStretch )
2664 gbLayout->setColumnStretch( widgetColumn, childHorizontalStretch );
2667 if ( childDef->verticalStretch() > 0 && childDef->verticalStretch() > gbLayout->rowStretch( row ) )
2669 gbLayout->setRowStretch( row, childDef->verticalStretch() );
2672 if ( column >= columnCount * 2 )
2678 if ( widgetInfo.widget
2679 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
2680 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
2681 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
2685 if ( qobject_cast<QgsAttributeFormRelationEditorWidget *>( widgetInfo.widget ) )
2691 QWidget *spacer =
new QWidget();
2692 spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
2693 gbLayout->addWidget( spacer, ++row, 0 );
2694 gbLayout->setRowStretch( row, 1 );
2697 newWidgetInfo.labelText = QString();
2698 newWidgetInfo.labelOnTop =
true;
2699 newWidgetInfo.showLabel = widgetDef->
showLabel();
2712 mWidgets.append( qmlWrapper );
2714 newWidgetInfo.widget = qmlWrapper->
widget();
2715 newWidgetInfo.labelText = elementDef->
name();
2716 newWidgetInfo.labelOnTop =
true;
2717 newWidgetInfo.showLabel = widgetDef->
showLabel();
2729 mWidgets.append( htmlWrapper );
2731 newWidgetInfo.widget = htmlWrapper->
widget();
2732 newWidgetInfo.labelText = elementDef->
name();
2733 newWidgetInfo.labelOnTop =
true;
2734 newWidgetInfo.showLabel = widgetDef->
showLabel();
2747 mWidgets.append( textWrapper );
2749 newWidgetInfo.widget = textWrapper->
widget();
2750 newWidgetInfo.labelText = elementDef->
name();
2751 newWidgetInfo.labelOnTop =
false;
2752 newWidgetInfo.showLabel = widgetDef->
showLabel();
2763 mWidgets.append( spacerWrapper );
2765 newWidgetInfo.widget = spacerWrapper->
widget();
2766 newWidgetInfo.labelOnTop =
false;
2767 newWidgetInfo.showLabel =
false;
2772 QgsDebugError( QStringLiteral(
"Unknown attribute editor widget type encountered..." ) );
2776 return newWidgetInfo;
2779void QgsAttributeForm::createWrappers()
2781 QList<QWidget *> myWidgets = findChildren<QWidget *>();
2782 const QList<QgsField> fields = mLayer->
fields().
toList();
2784 const auto constMyWidgets = myWidgets;
2785 for ( QWidget *myWidget : constMyWidgets )
2788 QVariant vRel = myWidget->property(
"qgisRelation" );
2789 if ( vRel.isValid() )
2799 mWidgets.append( rww );
2804 const auto constFields = fields;
2805 for (
const QgsField &field : constFields )
2807 if ( field.name() == myWidget->objectName() )
2812 mWidgets.append( eww );
2819void QgsAttributeForm::afterWidgetInit()
2821 bool isFirstEww =
true;
2823 const auto constMWidgets = mWidgets;
2832 setFocusProxy( eww->
widget() );
2842 if ( relationWidgetWrapper )
2855 if ( e->type() == QEvent::KeyPress )
2857 QKeyEvent *keyEvent =
dynamic_cast<QKeyEvent *
>( e );
2858 if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
2869void QgsAttributeForm::scanForEqualAttributes(
QgsFeatureIterator &fit, QSet<int> &mixedValueFields, QHash<int, QVariant> &fieldSharedValues )
const
2871 mixedValueFields.clear();
2872 fieldSharedValues.clear();
2878 for (
int i = 0; i < mLayer->
fields().count(); ++i )
2880 if ( mixedValueFields.contains( i ) )
2885 fieldSharedValues[i] = f.
attribute( i );
2889 if ( fieldSharedValues.value( i ) != f.
attribute( i ) )
2891 fieldSharedValues.remove( i );
2892 mixedValueFields.insert( i );
2898 if ( mixedValueFields.count() == mLayer->
fields().
count() )
2907void QgsAttributeForm::layerSelectionChanged()
2920 resetMultiEdit(
true );
2927 mIsSettingMultiEditFeatures =
true;
2928 mMultiEditFeatureIds = fids;
2930 if ( fids.isEmpty() )
2933 QMultiMap<int, QgsAttributeFormEditorWidget *>::const_iterator wIt = mFormEditorWidgets.constBegin();
2934 for ( ; wIt != mFormEditorWidgets.constEnd(); ++wIt )
2936 wIt.value()->initialize( QVariant() );
2938 mIsSettingMultiEditFeatures =
false;
2945 QSet<int> mixedValueFields;
2946 QHash<int, QVariant> fieldSharedValues;
2947 scanForEqualAttributes( fit, mixedValueFields, fieldSharedValues );
2956 if ( mCurrentFormFeature.
id() != firstFeature.
id() )
2961 const auto constMixedValueFields = mixedValueFields;
2962 for (
int fieldIndex : std::as_const( mixedValueFields ) )
2964 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( fieldIndex );
2965 if ( formEditorWidgets.isEmpty() )
2968 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
2969 QVariantList additionalFieldValues;
2970 for (
const QString &additionalField : additionalFields )
2971 additionalFieldValues << firstFeature.
attribute( additionalField );
2974 w->initialize( firstFeature.
attribute( fieldIndex ),
true, additionalFieldValues );
2976 QHash<int, QVariant>::const_iterator sharedValueIt = fieldSharedValues.constBegin();
2977 for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
2979 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( sharedValueIt.key() );
2980 if ( formEditorWidgets.isEmpty() )
2984 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
2985 for (
const QString &additionalField : additionalFields )
2988 if ( constMixedValueFields.contains( index ) )
2995 QVariantList additionalFieldValues;
2998 for (
const QString &additionalField : additionalFields )
2999 additionalFieldValues << firstFeature.
attribute( additionalField );
3001 w->initialize( firstFeature.
attribute( sharedValueIt.key() ),
true, additionalFieldValues );
3005 for (
const QString &additionalField : additionalFields )
3008 Q_ASSERT( fieldSharedValues.contains( index ) );
3009 additionalFieldValues << fieldSharedValues.value( index );
3012 w->initialize( sharedValueIt.value(),
false, additionalFieldValues );
3016 setMultiEditFeatureIdsRelations( fids );
3018 mIsSettingMultiEditFeatures =
false;
3023 if ( mOwnsMessageBar )
3025 mOwnsMessageBar =
false;
3026 mMessageBar = messageBar;
3036 QStringList filters;
3039 QString filter = widget->currentFilterExpression();
3040 if ( !filter.isNull() )
3041 filters <<
'(' + filter +
')';
3044 return filters.join( QLatin1String(
" AND " ) );
3049 mExtraContextScope.reset( extraScope );
3054 const bool newVisibility = expression.evaluate( expressionContext ).toBool();
3056 if ( expression.isValid() && !expression.hasEvalError() && newVisibility != isVisible )
3064 widget->setVisible( newVisibility );
3067 isVisible = newVisibility;
3070 const bool newCollapsedState = collapsedExpression.evaluate( expressionContext ).toBool();
3072 if ( collapsedExpression.isValid() && !collapsedExpression.hasEvalError() && newCollapsedState != isCollapsed )
3076 collapsibleGroupBox->
setCollapsed( newCollapsedState );
3077 isCollapsed = newCollapsedState;
3091 if ( infos.count() == 0 || !currentFormValuesFeature( formFeature ) )
3094 const QString hint = tr(
"No feature joined" );
3095 const auto constInfos = infos;
3098 if ( !info->isDynamicFormEnabled() )
3103 mJoinedFeatures[info] = joinFeature;
3105 if ( info->hasSubset() )
3109 const auto constSubsetNames = subsetNames;
3110 for (
const QString &field : constSubsetNames )
3112 QString prefixedName = info->prefixedFieldName( field );
3114 QString hintText = hint;
3128 for (
const QgsField &field : joinFields )
3130 QString prefixedName = info->prefixedFieldName( field );
3132 QString hintText = hint;
3146bool QgsAttributeForm::fieldIsEditable(
int fieldIndex )
const
3151void QgsAttributeForm::updateFieldDependencies()
3153 mDefaultValueDependencies.clear();
3154 mVirtualFieldsDependencies.clear();
3155 mRelatedLayerFieldsDependencies.clear();
3156 mParentDependencies.clear();
3165 updateFieldDependenciesParent( eww );
3166 updateFieldDependenciesDefaultValue( eww );
3167 updateFieldDependenciesVirtualFields( eww );
3168 updateRelatedLayerFieldsDependencies( eww );
3176 if ( exp.needsGeometry() )
3177 mNeedsGeometry =
true;
3184 for (
const int id : allAttributeIds )
3186 mDefaultValueDependencies.insertMulti(
id, eww );
3192 const QSet<QString> referencedColumns = exp.referencedColumns();
3193 for (
const QString &referencedColumn : referencedColumns )
3195 mDefaultValueDependencies.insertMulti( mLayer->
fields().
lookupField( referencedColumn ), eww );
3203 if ( expressionField.isEmpty() )
3208 if ( exp.needsGeometry() )
3209 mNeedsGeometry =
true;
3216 for (
const int id : allAttributeIds )
3218 mVirtualFieldsDependencies.insertMulti(
id, eww );
3224 const QSet<QString> referencedColumns = exp.referencedColumns();
3225 for (
const QString &referencedColumn : referencedColumns )
3227 mVirtualFieldsDependencies.insertMulti( mLayer->
fields().
lookupField( referencedColumn ), eww );
3237 if ( expressionField.contains( QStringLiteral(
"relation_aggregate" ) )
3238 || expressionField.contains( QStringLiteral(
"get_features" ) ) )
3239 mRelatedLayerFieldsDependencies.insert( eww );
3243 mRelatedLayerFieldsDependencies.clear();
3248 if ( !editorWidgetWrapper )
3251 updateRelatedLayerFieldsDependencies( editorWidgetWrapper );
3261 const QSet<QString> referencedVariablesAndFunctions = expression.referencedVariables() + expression.referencedFunctions();
3262 for (
const QString &referenced : referencedVariablesAndFunctions )
3264 if ( referenced.startsWith( QLatin1String(
"current_parent" ) ) )
3266 mParentDependencies.insert( eww );
3273void QgsAttributeForm::setMultiEditFeatureIdsRelations(
const QgsFeatureIds &fids )
3278 if ( !relationEditorWidget )
3291 mIconMap[eww->
widget()]->hide();
3305 const QString file = QStringLiteral(
"/mIconJoinNotEditable.svg" );
3306 const QString tooltip = tr(
"Join settings do not allow editing" );
3307 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3311 const QString file = QStringLiteral(
"mIconJoinHasNotUpsertOnEdit.svg" );
3312 const QString tooltip = tr(
"Join settings do not allow upsert on edit" );
3313 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3317 const QString file = QStringLiteral(
"/mIconJoinedLayerNotEditable.svg" );
3318 const QString tooltip = tr(
"Joined layer is not toggled editable" );
3319 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3325void QgsAttributeForm::reloadIcon(
const QString &file,
const QString &tooltip, QSvgWidget *sw )
3328 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.
@ Join
Field originates from a joined layer.
@ Action
A layer action element.
@ QmlElement
A QML element.
@ HtmlElement
A HTML element.
@ TextElement
A text element.
@ SpacerElement
A spacer element.
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.
QgsFeature parentFormFeature() const
Returns the feature of the currently edited parent form in its actual state.
@ Embed
A form was embedded as a widget on another form.
void setParentFormFeature(const QgsFeature &feature)
Sets the feature of the currently edited parent 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 * parentFormScope(const QgsFeature &formFeature=QgsFeature(), const QString &formMode=QString())
Creates a new scope which contains functions and variables from the current parent attribute form/tab...
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...
Q_INVOKABLE 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.
Q_INVOKABLE 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.
Q_INVOKABLE int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
QgsField field(int fieldIdx) const
Returns the field at particular index (must be in range 0..N-1).
Qgis::FieldOrigin fieldOrigin(int fieldIdx) const
Returns the field's origin (value from an enumeration).
Q_INVOKABLE 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).
Q_INVOKABLE 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 pythonEmbeddedInProjectAllowed(void(*lambda)()=nullptr, QgsMessageBar *messageBar=nullptr, Qgis::PythonEmbeddedType embeddedType=Qgis::PythonEmbeddedType::Macro)
Returns true if python embedded in a project is currently allowed to be loaded.
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.
Represents a relationship between two vector layers.
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.
static QVariant createNullVariant(QMetaType::Type metaType)
Helper method to properly create a null QVariant from a metaType Returns the created QVariant.
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.
Q_INVOKABLE bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue=QVariant(), bool skipDefaultValues=false, QgsVectorLayerToolsContext *context=nullptr)
Changes an attribute value for a feature (but does not immediately commit the changes).
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
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 isEditable() const FINAL
Returns true if the provider is in editing mode.
QgsVectorLayerJoinBuffer * joinBuffer()
Returns the join buffer object.
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.
Q_INVOKABLE bool changeAttributeValues(QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues=QgsAttributeMap(), bool skipDefaultValues=false, QgsVectorLayerToolsContext *context=nullptr)
Changes attributes' values for a feature (but does not immediately commit the changes).
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)