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;
374 *error = tr(
"JSON value for %1 is invalid and has not been saved" ).arg( eww->
field().
name() );
377 QVariantList dstVars = QVariantList() << dst.at( eww->
fieldIdx() );
378 QVariantList srcVars = QVariantList() << eww->
value();
379 QList<int> fieldIndexes = QList<int>() << eww->
fieldIdx();
383 for (
const QString &fieldName : additionalFields )
387 dstVars << dst.at( idx );
391 Q_ASSERT( dstVars.count() == srcVars.count() );
393 for (
int i = 0; i < dstVars.count(); i++ )
396 if ( !
qgsVariantEqual( dstVars[i], srcVars[i] ) && srcVars[i].isValid() )
398 dst[fieldIndexes[i]] = srcVars[i];
408 const auto constMInterfaces = mInterfaces;
411 if ( !iface->acceptChanges( updatedFeature ) )
421 mFeature = updatedFeature;
427 bool res = mLayer->
addFeature( updatedFeature );
446 for (
int i = 0; i < dst.count(); ++i )
449 || !dst.at( i ).isValid()
450 || !fieldIsEditable( i ) )
456 QgsDebugMsgLevel( QStringLiteral(
"dst:'%1' (type:%2, isNull:%3, isValid:%4)" )
457 .arg( dst.at( i ).toString(), dst.at( i ).typeName() ).arg(
QgsVariantUtils::isNull( dst.at( i ) ) ).arg( dst.at( i ).isValid() ), 2 );
458 QgsDebugMsgLevel( QStringLiteral(
"src:'%1' (type:%2, isNull:%3, isValid:%4)" )
459 .arg( src.at( i ).toString(), src.at( i ).typeName() ).arg(
QgsVariantUtils::isNull( src.at( i ) ) ).arg( src.at( i ).isValid() ), 2 );
461 newValues[i] = dst.at( i );
462 oldValues[i] = src.at( i );
467 std::unique_ptr<QgsVectorLayerToolsContext> context = std::make_unique<QgsVectorLayerToolsContext>();
469 context->setExpressionContext( &expressionContext );
472 if ( success && n > 0 )
499QgsFeature QgsAttributeForm::getUpdatedFeature()
const
511 QVariantList dstVars = QVariantList() << featureAttributes.at( eww->
fieldIdx() );
512 QVariantList srcVars = QVariantList() << eww->
value();
513 QList<int> fieldIndexes = QList<int>() << eww->
fieldIdx();
517 for (
const QString &fieldName : additionalFields )
521 dstVars << featureAttributes.at( idx );
525 Q_ASSERT( dstVars.count() == srcVars.count() );
527 for (
int i = 0; i < dstVars.count(); i++ )
529 if ( !
qgsVariantEqual( dstVars[i], srcVars[i] ) && srcVars[i].isValid() )
530 featureAttributes[fieldIndexes[i]] = srcVars[i];
535 return updatedFeature;
538void QgsAttributeForm::updateValuesDependencies(
const int originIdx )
540 updateValuesDependenciesDefaultValues( originIdx );
541 updateValuesDependenciesVirtualFields( originIdx );
544void QgsAttributeForm::updateValuesDependenciesDefaultValues(
const int originIdx )
546 if ( !mDefaultValueDependencies.contains( originIdx ) )
554 QgsFeature updatedFeature = getUpdatedFeature();
557 QList<QgsWidgetWrapper *> relevantWidgets = mDefaultValueDependencies.values( originIdx );
574 if ( mAlreadyUpdatedFields.contains( eww->
fieldIdx() ) )
586void QgsAttributeForm::updateValuesDependenciesVirtualFields(
const int originIdx )
588 if ( !mVirtualFieldsDependencies.contains( originIdx ) )
595 QgsFeature updatedFeature = getUpdatedFeature();
598 const QList<QgsWidgetWrapper *> relevantWidgets = mVirtualFieldsDependencies.values( originIdx );
606 if ( mAlreadyUpdatedFields.contains( eww->
fieldIdx() ) )
612 const QVariant value = exp.evaluate( &context );
618void QgsAttributeForm::updateRelatedLayerFields()
621 updateRelatedLayerFieldsDependencies();
623 if ( mRelatedLayerFieldsDependencies.isEmpty() )
630 QgsFeature updatedFeature = getUpdatedFeature();
633 const QSet<QgsEditorWidgetWrapper *> relevantWidgets = mRelatedLayerFieldsDependencies;
637 if ( mAlreadyUpdatedFields.contains( eww->
fieldIdx() ) )
643 QVariant value = exp.evaluate( &context );
648void QgsAttributeForm::resetMultiEdit(
bool promptToSave )
653 mUnsavedMultiEditChanges =
false;
657void QgsAttributeForm::multiEditMessageClicked(
const QString &link )
659 clearMultiEditMessages();
660 resetMultiEdit( link == QLatin1String(
"#apply" ) );
663void QgsAttributeForm::filterTriggered()
665 QString filter = createFilterExpression();
671void QgsAttributeForm::searchZoomTo()
673 QString filter = createFilterExpression();
674 if ( filter.isEmpty() )
680void QgsAttributeForm::searchFlash()
682 QString filter = createFilterExpression();
683 if ( filter.isEmpty() )
689void QgsAttributeForm::filterAndTriggered()
691 QString filter = createFilterExpression();
692 if ( filter.isEmpty() )
700void QgsAttributeForm::filterOrTriggered()
702 QString filter = createFilterExpression();
703 if ( filter.isEmpty() )
711void QgsAttributeForm::pushSelectedFeaturesMessage()
717 tr(
"%n matching feature(s) selected",
"matching features", count ),
723 tr(
"No matching features found" ),
737 QString filter = createFilterExpression();
738 if ( filter.isEmpty() )
742 pushSelectedFeaturesMessage();
747void QgsAttributeForm::searchSetSelection()
752void QgsAttributeForm::searchAddToSelection()
757void QgsAttributeForm::searchRemoveFromSelection()
762void QgsAttributeForm::searchIntersectSelection()
767bool QgsAttributeForm::saveMultiEdits()
771 const QList<int> fieldIndexes = mFormEditorWidgets.uniqueKeys();
772 mFormEditorWidgets.constBegin();
773 for (
int fieldIndex : fieldIndexes )
775 const QList<QgsAttributeFormEditorWidget *> widgets = mFormEditorWidgets.values( fieldIndex );
776 if ( !widgets.first()->hasChanged() )
779 if ( !widgets.first()->currentValue().isValid()
780 || !fieldIsEditable( fieldIndex ) )
787 widget->changesCommitted();
789 newAttributeValues.insert( fieldIndex, widgets.first()->currentValue() );
792 if ( newAttributeValues.isEmpty() )
800 int res = QMessageBox::information(
this, tr(
"Multiedit Attributes" ),
801 tr(
"Edits will be applied to all selected features." ), QMessageBox::Ok | QMessageBox::Cancel );
802 if ( res != QMessageBox::Ok )
813 const auto constMultiEditFeatureIds = mMultiEditFeatureIds;
816 QgsAttributeMap::const_iterator aIt = newAttributeValues.constBegin();
817 for ( ; aIt != newAttributeValues.constEnd(); ++aIt )
823 clearMultiEditMessages();
836 if ( !mButtonBox->isVisible() )
837 mMessageBar->
pushItem( mMultiEditMessageBarItem );
863 wrapper->notifyAboutToSave();
903 success = saveEdits( error );
907 success = saveMultiEdits();
912 mUnsavedMultiEditChanges =
false;
921 mValuesInitialized =
false;
922 const auto constMWidgets = mWidgets;
925 ww->setFeature( mFeature );
936 mAlreadyUpdatedFields.append( eww->
fieldIdx() );
937 updateValuesDependenciesVirtualFields( eww->
fieldIdx() );
938 mAlreadyUpdatedFields.removeAll( eww->
fieldIdx() );
941 mValuesInitialized =
true;
947 const auto widgets { findChildren< QgsAttributeFormEditorWidget * >() };
954void QgsAttributeForm::clearMultiEditMessages()
956 if ( mMultiEditUnsavedMessageBarItem )
958 if ( !mButtonBox->isVisible() )
959 mMessageBar->
popWidget( mMultiEditUnsavedMessageBarItem );
960 mMultiEditUnsavedMessageBarItem =
nullptr;
962 if ( mMultiEditMessageBarItem )
964 if ( !mButtonBox->isVisible() )
965 mMessageBar->
popWidget( mMultiEditMessageBarItem );
966 mMultiEditMessageBarItem =
nullptr;
970QString QgsAttributeForm::createFilterExpression()
const
975 QString filter = w->currentFilterExpression();
976 if ( !filter.isEmpty() )
980 if ( filters.isEmpty() )
983 QString filter = filters.join( QLatin1String(
") AND (" ) ).prepend(
'(' ).append(
')' );
992 if ( mExtraContextScope )
1005void QgsAttributeForm::onAttributeChanged(
const QVariant &value,
const QVariantList &additionalFieldValues )
1010 bool signalEmitted =
false;
1012 if ( mValuesInitialized )
1031 for (
int i = 0; i < additionalFields.count(); i++ )
1033 const QString fieldName = additionalFields.at( i );
1034 const QVariant value = additionalFieldValues.at( i );
1038 signalEmitted =
true;
1040 if ( mValuesInitialized )
1041 updateJoinedFields( *eww );
1047 if ( !mIsSettingMultiEditFeatures )
1049 mUnsavedMultiEditChanges =
true;
1051 QLabel *msgLabel =
new QLabel( tr(
"Unsaved multiedit changes: <a href=\"#apply\">apply changes</a> or <a href=\"#reset\">reset changes</a>." ), mMessageBar );
1052 msgLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
1053 msgLabel->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1054 connect( msgLabel, &QLabel::linkActivated,
this, &QgsAttributeForm::multiEditMessageClicked );
1055 clearMultiEditMessages();
1058 if ( !mButtonBox->isVisible() )
1059 mMessageBar->
pushItem( mMultiEditUnsavedMessageBarItem );
1062 signalEmitted =
true;
1074 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1077 if ( formEditorWidget->editorWidget() == eww )
1082 whileBlocking( formEditorWidget->editorWidget() )->setValue( value );
1085 updateConstraints( eww );
1088 if ( mValuesInitialized )
1091 mAlreadyUpdatedFields.append( eww->
fieldIdx() );
1092 updateValuesDependencies( eww->
fieldIdx() );
1093 mAlreadyUpdatedFields.removeAll( eww->
fieldIdx() );
1098 updateEditableState();
1100 if ( !signalEmitted )
1105 bool attributeHasChanged = !mIsSettingFeature;
1107 attributeHasChanged &= !mIsSettingMultiEditFeatures;
1113void QgsAttributeForm::updateAllConstraints()
1115 const auto constMWidgets = mWidgets;
1120 updateConstraints( eww );
1128 if ( currentFormValuesFeature( ft ) )
1140 updateConstraint( ft, eww );
1143 const QList<QgsEditorWidgetWrapper *> deps = constraintDependencies( eww );
1146 updateConstraint( ft, depsEww );
1154 const QVector<ContainerInformation *> infos = mContainerInformationDependency.value( eww->
field().
name() );
1155 for ( ContainerInformation *info : infos )
1157 info->apply( &context );
1162void QgsAttributeForm::updateContainersVisibility()
1166 const QVector<ContainerInformation *> infos = mContainerVisibilityCollapsedInformation;
1168 for ( ContainerInformation *info : infos )
1170 info->apply( &context );
1180 updateAllConstraints();
1196 if ( mJoinedFeatures.contains( info ) )
1213void QgsAttributeForm::updateLabels()
1215 if ( ! mLabelDataDefinedProperties.isEmpty() )
1218 if ( currentFormValuesFeature( currentFeature ) )
1222 for (
auto it = mLabelDataDefinedProperties.constBegin() ; it != mLabelDataDefinedProperties.constEnd(); ++it )
1224 QLabel *label { it.key() };
1226 const QString value { it->valueAsString( context, QString(), &ok ) };
1227 if ( ok && ! value.isEmpty() )
1229 label->setText( value );
1236void QgsAttributeForm::updateEditableState()
1238 if ( ! mEditableDataDefinedProperties.isEmpty() )
1241 if ( currentFormValuesFeature( currentFeature ) )
1245 for (
auto it = mEditableDataDefinedProperties.constBegin() ; it != mEditableDataDefinedProperties.constEnd(); ++it )
1247 QWidget *w { it.key() };
1249 const bool isEditable { it->valueAsBool( context,
true, &ok ) && mLayer && mLayer->
isEditable() };
1259 w->setEnabled( isEditable );
1267bool QgsAttributeForm::currentFormValuesFeature(
QgsFeature &feature )
1280 if ( dst.count() > eww->
fieldIdx() )
1282 QVariantList dstVars = QVariantList() << dst.at( eww->
fieldIdx() );
1283 QVariantList srcVars = QVariantList() << eww->
value();
1284 QList<int> fieldIndexes = QList<int>() << eww->
fieldIdx();
1288 for (
const QString &fieldName : additionalFields )
1291 fieldIndexes << idx;
1292 dstVars << dst.at( idx );
1296 Q_ASSERT( dstVars.count() == srcVars.count() );
1298 for (
int i = 0; i < dstVars.count(); i++ )
1304 dst[fieldIndexes[i]] = srcVars[i];
1321void QgsAttributeForm::registerContainerInformation( QgsAttributeForm::ContainerInformation *info )
1323 mContainerVisibilityCollapsedInformation.append( info );
1325 const QSet<QString> referencedColumns = info->expression.referencedColumns().unite( info->collapsedExpression.referencedColumns() );
1327 for (
const QString &col : referencedColumns )
1329 mContainerInformationDependency[ col ].append( info );
1333bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields, QStringList &descriptions )
const
1357bool QgsAttributeForm::currentFormValidHardConstraints( QStringList &invalidFields, QStringList &descriptions )
const
1378void QgsAttributeForm::onAttributeAdded(
int idx )
1380 mPreventFeatureRefresh =
false;
1392void QgsAttributeForm::onAttributeDeleted(
int idx )
1394 mPreventFeatureRefresh =
false;
1398 attrs.remove( idx );
1406void QgsAttributeForm::onRelatedFeaturesChanged()
1408 updateRelatedLayerFields();
1411void QgsAttributeForm::onUpdatedFields()
1413 mPreventFeatureRefresh =
false;
1440void QgsAttributeForm::onConstraintStatusChanged(
const QString &constraint,
1446 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1450 formEditorWidget->setConstraintStatus( constraint, description, err, result );
1451 if ( formEditorWidget->editorWidget() != eww )
1453 formEditorWidget->editorWidget()->updateConstraint( result, err );
1460 QList<QgsEditorWidgetWrapper *> wDeps;
1472 if ( name != ewwName )
1479 for (
const QString &colName : referencedColumns )
1481 if ( name == colName )
1483 wDeps.append( eww );
1496 return setupRelationWidgetWrapper( QString(), rel, context );
1509void QgsAttributeForm::preventFeatureRefresh()
1511 mPreventFeatureRefresh =
true;
1542 return mNeedsGeometry;
1545void QgsAttributeForm::synchronizeState()
1547 bool isEditable = ( mFeature.
isValid()
1557 const QList<QgsAttributeFormEditorWidget *> formWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1560 formWidget->setConstraintResultVisible( isEditable );
1564 bool enabled = isEditable && fieldIsEditable( eww->
fieldIdx() );
1565 ww->setEnabled( enabled );
1571 ww->setEnabled( isEditable );
1582 if ( mConstraintsFailMessageBarItem )
1584 mMessageBar->
popWidget( mConstraintsFailMessageBarItem );
1587 mMessageBar->
pushItem( mConstraintsFailMessageBarItem );
1591 QStringList invalidFields, descriptions;
1592 mValidConstraints = currentFormValidHardConstraints( invalidFields, descriptions );
1596 if ( !mValidConstraints && !mConstraintsFailMessageBarItem )
1598 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 );
1599 mMessageBar->
pushItem( mConstraintsFailMessageBarItem );
1601 else if ( mValidConstraints && mConstraintsFailMessageBarItem )
1603 mMessageBar->
popWidget( mConstraintsFailMessageBarItem );
1604 mConstraintsFailMessageBarItem =
nullptr;
1607 else if ( mConstraintsFailMessageBarItem )
1609 mMessageBar->
popWidget( mConstraintsFailMessageBarItem );
1610 mConstraintsFailMessageBarItem =
nullptr;
1613 isEditable = isEditable & mValidConstraints;
1618 QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
1620 okButton->setEnabled( isEditable );
1623void QgsAttributeForm::init()
1625 QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
1628 QWidget *formWidget =
nullptr;
1629 mNeedsGeometry =
false;
1631 bool buttonBoxVisible =
true;
1635 buttonBoxVisible = mButtonBox->isVisible();
1637 mButtonBox =
nullptr;
1640 if ( mSearchButtonBox )
1642 delete mSearchButtonBox;
1643 mSearchButtonBox =
nullptr;
1646 qDeleteAll( mWidgets );
1649 while ( QWidget *w = this->findChild<QWidget *>() )
1655 QVBoxLayout *vl =
new QVBoxLayout();
1656 vl->setContentsMargins( 0, 0, 0, 0 );
1658 mMessageBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1659 vl->addWidget( mMessageBar );
1664 QGridLayout *layout =
new QGridLayout();
1665 QWidget *container =
new QWidget();
1666 container->setLayout( layout );
1667 vl->addWidget( container );
1669 mFormEditorWidgets.clear();
1670 mFormWidgets.clear();
1673 setContentsMargins( 0, 0, 0, 0 );
1682 if ( file && file->open( QFile::ReadOnly ) )
1686 QFileInfo fi( file->fileName() );
1687 loader.setWorkingDirectory( fi.dir() );
1688 formWidget = loader.load( file,
this );
1691 formWidget->setWindowFlags( Qt::Widget );
1692 layout->addWidget( formWidget );
1695 mButtonBox = findChild<QDialogButtonBox *>();
1698 formWidget->installEventFilter(
this );
1710 int columnCount = 1;
1711 bool hasRootFields =
false;
1712 bool addSpacer =
true;
1721 if ( !containerDef )
1724 switch ( containerDef->
type() )
1728 tabWidget =
nullptr;
1729 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1730 if ( widgetInfo.labelStyle.overrideColor )
1732 if ( widgetInfo.labelStyle.color.isValid() )
1734 widgetInfo.widget->setStyleSheet( QStringLiteral(
"QGroupBox::title { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1737 if ( widgetInfo.labelStyle.overrideFont )
1739 widgetInfo.widget->setFont( widgetInfo.labelStyle.font );
1742 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1743 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1745 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1747 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1749 layout->setRowStretch( row, widgDef->verticalStretch() );
1763 tabWidget =
nullptr;
1764 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1765 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1766 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1768 layout->setRowStretch( row, widgDef->verticalStretch() );
1771 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1773 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1789 layout->addWidget( tabWidget, row, column, 1, 2 );
1793 QWidget *tabPage =
new QWidget( tabWidget );
1795 tabWidget->addTab( tabPage, widgDef->name() );
1796 tabWidget->
setTabStyle( tabWidget->tabBar()->count() - 1, widgDef->labelStyle() );
1800 registerContainerInformation(
new ContainerInformation( tabWidget, tabPage, containerDef->
visibilityExpression().
data() ) );
1802 QGridLayout *tabPageLayout =
new QGridLayout();
1803 tabPage->setLayout( tabPageLayout );
1805 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, tabPage, mLayer, mContext );
1806 tabPageLayout->addWidget( widgetInfo.widget );
1813 hasRootFields =
true;
1814 tabWidget =
nullptr;
1815 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1818 if ( widgetInfo.showLabel )
1820 if ( widgetInfo.labelStyle.overrideColor && widgetInfo.labelStyle.color.isValid() )
1822 collapsibleGroupBox->
setStyleSheet( QStringLiteral(
"QGroupBox::title { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1825 if ( widgetInfo.labelStyle.overrideFont )
1827 collapsibleGroupBox->setFont( widgetInfo.labelStyle.font );
1830 collapsibleGroupBox->setTitle( widgetInfo.labelText );
1833 QVBoxLayout *collapsibleGroupBoxLayout =
new QVBoxLayout();
1834 collapsibleGroupBoxLayout->addWidget( widgetInfo.widget );
1835 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
1837 QVBoxLayout *
c =
new QVBoxLayout();
1838 c->addWidget( collapsibleGroupBox );
1839 layout->addLayout(
c, row, column, 1, 2 );
1841 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1842 layout->setRowStretch( row, widgDef->verticalStretch() );
1843 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1844 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1853 hasRootFields =
true;
1854 tabWidget =
nullptr;
1855 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1856 QLabel *label =
new QLabel( widgetInfo.labelText );
1858 if ( widgetInfo.labelStyle.overrideColor )
1860 if ( widgetInfo.labelStyle.color.isValid() )
1862 label->setStyleSheet( QStringLiteral(
"QLabel { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1866 if ( widgetInfo.labelStyle.overrideFont )
1868 label->setFont( widgetInfo.labelStyle.font );
1871 label->setToolTip( widgetInfo.toolTip );
1872 if ( columnCount > 1 && !widgetInfo.labelOnTop )
1874 label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1877 label->setBuddy( widgetInfo.widget );
1880 if ( widgetInfo.widget
1881 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
1882 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
1883 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
1886 if ( !widgetInfo.showLabel )
1888 QVBoxLayout *
c =
new QVBoxLayout();
1889 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1890 c->addWidget( widgetInfo.widget );
1891 layout->addLayout(
c, row, column, 1, 2 );
1893 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1895 layout->setRowStretch( row, widgDef->verticalStretch() );
1898 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1900 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1905 else if ( widgetInfo.labelOnTop )
1907 QVBoxLayout *
c =
new QVBoxLayout();
1908 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1909 c->addWidget( label );
1910 c->addWidget( widgetInfo.widget );
1911 layout->addLayout(
c, row, column, 1, 2 );
1913 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1915 layout->setRowStretch( row, widgDef->verticalStretch() );
1918 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1920 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1927 const int widgetColumn = column + 1;
1928 layout->addWidget( label, row, column++ );
1929 layout->addWidget( widgetInfo.widget, row, column++ );
1931 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1933 layout->setRowStretch( row, widgDef->verticalStretch() );
1936 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( widgetColumn ) )
1938 layout->setColumnStretch( widgetColumn, widgDef->horizontalStretch() );
1946 const int fieldIdx = fieldElement->
idx();
1947 if ( fieldIdx >= 0 && fieldIdx < mLayer->fields().count() )
1949 const QString fieldName { mLayer->
fields().
at( fieldIdx ).
name() };
1953 if ( property.isActive() )
1955 mLabelDataDefinedProperties[ label ] = property;
1961 if ( property.isActive() )
1963 mEditableDataDefinedProperties[ widgetInfo.widget ] = property;
1970 if ( column >= columnCount * 2 )
1977 if ( hasRootFields && addSpacer )
1979 QSpacerItem *spacerItem =
new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
1980 layout->addItem( spacerItem, row, 0 );
1981 layout->setRowStretch( row, 1 );
1984 formWidget = container;
1993 formWidget =
new QWidget(
this );
1994 QGridLayout *gridLayout =
new QGridLayout( formWidget );
1995 formWidget->setLayout( gridLayout );
2001 scrollArea->setWidget( formWidget );
2002 scrollArea->setWidgetResizable(
true );
2003 scrollArea->setFrameShape( QFrame::NoFrame );
2004 scrollArea->setFrameShadow( QFrame::Plain );
2005 scrollArea->setFocusProxy(
this );
2006 layout->addWidget( scrollArea );
2010 layout->addWidget( formWidget );
2017 for (
const QgsField &field : fields )
2025 QString labelText = fieldName;
2026 labelText.replace(
'&', QLatin1String(
"&&" ) );
2030 if ( widgetSetup.
type() == QLatin1String(
"Hidden" ) )
2036 QLabel *label =
new QLabel( labelText );
2038 QSvgWidget *i =
new QSvgWidget();
2039 i->setFixedSize( 18, 18 );
2044 if ( property.isActive() )
2046 mLabelDataDefinedProperties[ label ] = property;
2052 QWidget *w =
nullptr;
2057 mFormEditorWidgets.insert( idx, formWidget );
2058 mFormWidgets.append( formWidget );
2061 label->setBuddy( eww->
widget() );
2066 if ( property.isActive() )
2068 mEditableDataDefinedProperties[ formWidget ] = property;
2074 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() ) ) );
2079 w->setObjectName( field.name() );
2083 mWidgets.append( eww );
2084 mIconMap[eww->
widget()] = i;
2089 gridLayout->addWidget( label, row++, 0, 1, 2 );
2090 gridLayout->addWidget( w, row++, 0, 1, 2 );
2091 gridLayout->addWidget( i, row++, 0, 1, 2 );
2095 gridLayout->addWidget( label, row, 0 );
2096 gridLayout->addWidget( w, row, 1 );
2097 gridLayout->addWidget( i, row++, 2 );
2111 QVBoxLayout *collapsibleGroupBoxLayout =
new QVBoxLayout();
2112 collapsibleGroupBoxLayout->addWidget( formWidget );
2113 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
2115 gridLayout->addWidget( collapsibleGroupBox, row++, 0, 1, 2 );
2117 mWidgets.append( rww );
2118 mFormWidgets.append( formWidget );
2123 QSpacerItem *spacerItem =
new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
2124 gridLayout->addItem( spacerItem, row, 0 );
2125 gridLayout->setRowStretch( row, 1 );
2131 updateFieldDependencies();
2135 mButtonBox =
new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
2136 mButtonBox->setObjectName( QStringLiteral(
"buttonBox" ) );
2137 layout->addWidget( mButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2139 mButtonBox->setVisible( buttonBoxVisible );
2141 if ( !mSearchButtonBox )
2143 mSearchButtonBox =
new QWidget();
2144 QHBoxLayout *boxLayout =
new QHBoxLayout();
2145 boxLayout->setContentsMargins( 0, 0, 0, 0 );
2146 mSearchButtonBox->setLayout( boxLayout );
2147 mSearchButtonBox->setObjectName( QStringLiteral(
"searchButtonBox" ) );
2149 QPushButton *clearButton =
new QPushButton( tr(
"&Reset Form" ), mSearchButtonBox );
2151 boxLayout->addWidget( clearButton );
2152 boxLayout->addStretch( 1 );
2154 QPushButton *flashButton =
new QPushButton();
2155 flashButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2156 flashButton->setText( tr(
"&Flash Features" ) );
2157 connect( flashButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchFlash );
2158 boxLayout->addWidget( flashButton );
2160 QPushButton *openAttributeTableButton =
new QPushButton();
2161 openAttributeTableButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2162 openAttributeTableButton->setText( tr(
"Show in &Table" ) );
2163 openAttributeTableButton->setToolTip( tr(
"Open the attribute table editor with the filtered features" ) );
2164 connect( openAttributeTableButton, &QToolButton::clicked,
this, [ = ]
2168 boxLayout->addWidget( openAttributeTableButton );
2170 QPushButton *zoomButton =
new QPushButton();
2171 zoomButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2172 zoomButton->setText( tr(
"&Zoom to Features" ) );
2173 connect( zoomButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchZoomTo );
2174 boxLayout->addWidget( zoomButton );
2176 QToolButton *selectButton =
new QToolButton();
2177 selectButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2178 selectButton->setText( tr(
"&Select Features" ) );
2180 selectButton->setPopupMode( QToolButton::MenuButtonPopup );
2181 selectButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
2182 connect( selectButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchSetSelection );
2183 QMenu *selectMenu =
new QMenu( selectButton );
2184 QAction *selectAction =
new QAction( tr(
"Select Features" ), selectMenu );
2186 connect( selectAction, &QAction::triggered,
this, &QgsAttributeForm::searchSetSelection );
2187 selectMenu->addAction( selectAction );
2188 QAction *addSelectAction =
new QAction( tr(
"Add to Current Selection" ), selectMenu );
2190 connect( addSelectAction, &QAction::triggered,
this, &QgsAttributeForm::searchAddToSelection );
2191 selectMenu->addAction( addSelectAction );
2192 QAction *deselectAction =
new QAction( tr(
"Remove from Current Selection" ), selectMenu );
2194 connect( deselectAction, &QAction::triggered,
this, &QgsAttributeForm::searchRemoveFromSelection );
2195 selectMenu->addAction( deselectAction );
2196 QAction *filterSelectAction =
new QAction( tr(
"Filter Current Selection" ), selectMenu );
2198 connect( filterSelectAction, &QAction::triggered,
this, &QgsAttributeForm::searchIntersectSelection );
2199 selectMenu->addAction( filterSelectAction );
2200 selectButton->setMenu( selectMenu );
2201 boxLayout->addWidget( selectButton );
2205 QToolButton *filterButton =
new QToolButton();
2206 filterButton->setText( tr(
"Filter Features" ) );
2207 filterButton->setPopupMode( QToolButton::MenuButtonPopup );
2208 filterButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2209 connect( filterButton, &QToolButton::clicked,
this, &QgsAttributeForm::filterTriggered );
2210 QMenu *filterMenu =
new QMenu( filterButton );
2211 QAction *filterAndAction =
new QAction( tr(
"Filter Within (\"AND\")" ), filterMenu );
2212 connect( filterAndAction, &QAction::triggered,
this, &QgsAttributeForm::filterAndTriggered );
2213 filterMenu->addAction( filterAndAction );
2214 QAction *filterOrAction =
new QAction( tr(
"Extend Filter (\"OR\")" ), filterMenu );
2215 connect( filterOrAction, &QAction::triggered,
this, &QgsAttributeForm::filterOrTriggered );
2216 filterMenu->addAction( filterOrAction );
2217 filterButton->setMenu( filterMenu );
2218 boxLayout->addWidget( filterButton );
2222 QPushButton *closeButton =
new QPushButton( tr(
"Close" ), mSearchButtonBox );
2224 closeButton->setShortcut( Qt::Key_Escape );
2225 boxLayout->addWidget( closeButton );
2228 layout->addWidget( mSearchButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2246 const auto constMInterfaces = mInterfaces;
2257 QApplication::restoreOverrideCursor();
2260void QgsAttributeForm::cleanPython()
2262 if ( !mPyFormVarName.isNull() )
2264 QString expr = QStringLiteral(
"if '%1' in locals(): del %1\n" ).arg( mPyFormVarName );
2269void QgsAttributeForm::initPython()
2286 if ( !initFilePath.isEmpty() )
2290 if ( inputFile && inputFile->open( QFile::ReadOnly ) )
2293 QTextStream inf( inputFile );
2294#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2295 inf.setCodec(
"UTF-8" );
2297 initCode = inf.readAll();
2302 QgsLogger::warning( QStringLiteral(
"The external python file path %1 could not be opened!" ).arg( initFilePath ) );
2313 if ( initCode.isEmpty() )
2315 QgsLogger::warning( QStringLiteral(
"The python code provided in the dialog is empty!" ) );
2326 if ( !initCode.isEmpty() )
2332 tr(
"Python macro could not be run due to missing permissions." ),
2340 if (
QgsPythonRunner::eval( QStringLiteral(
"len(inspect.getfullargspec(%1)[0])" ).arg( initFunction ), numArgs ) )
2342 static int sFormId = 0;
2343 mPyFormVarName = QStringLiteral(
"_qgis_featureform_%1_%2" ).arg( mFormNr ).arg( sFormId++ );
2345 QString form = QStringLiteral(
"%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )" )
2346 .arg( mPyFormVarName )
2347 .arg( ( quint64 )
this );
2351 QgsDebugMsgLevel( QStringLiteral(
"running featureForm init: %1" ).arg( mPyFormVarName ), 2 );
2354 if ( numArgs == QLatin1String(
"3" ) )
2362 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 ) );
2365 QString expr = QString(
"%1(%2)" )
2366 .arg( mLayer->editFormInit() )
2367 .arg( mPyFormVarName );
2368 QgsAttributeFormInterface *iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface *>( expr,
"QgsAttributeFormInterface" );
2378 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 ) );
2386 WidgetInfo newWidgetInfo;
2388 newWidgetInfo.labelStyle = widgetDef->
labelStyle();
2390 switch ( widgetDef->
type() )
2402 mWidgets.append( actionWrapper );
2403 newWidgetInfo.widget = actionWrapper->
widget();
2404 newWidgetInfo.showLabel =
false;
2417 if ( fldIdx < fields.
count() && fldIdx >= 0 )
2423 mFormEditorWidgets.insert( fldIdx, formWidget );
2424 mFormWidgets.append( formWidget );
2428 newWidgetInfo.widget = formWidget;
2429 mWidgets.append( eww );
2431 newWidgetInfo.widget->setObjectName( fields.
at( fldIdx ).
name() );
2432 newWidgetInfo.hint = fields.
at( fldIdx ).
comment();
2437 newWidgetInfo.labelText.replace(
'&', QLatin1String(
"&&" ) );
2438 newWidgetInfo.toolTip = QStringLiteral(
"<b>%1</b><p>%2</p>" ).arg( mLayer->
attributeDisplayName( fldIdx ), newWidgetInfo.hint );
2439 newWidgetInfo.showLabel = widgetDef->
showLabel();
2460 mWidgets.append( rww );
2461 mFormWidgets.append( formWidget );
2463 newWidgetInfo.widget = formWidget;
2464 newWidgetInfo.showLabel = relDef->
showLabel();
2465 newWidgetInfo.labelText = relDef->
label();
2466 if ( newWidgetInfo.labelText.isEmpty() )
2468 newWidgetInfo.labelOnTop =
true;
2480 if ( columnCount <= 0 )
2484 QWidget *myContainer =
nullptr;
2485 bool removeLayoutMargin =
false;
2486 switch ( container->
type() )
2491 widgetName = QStringLiteral(
"QGroupBox" );
2494 groupBox->setTitle( container->
name() );
2495 if ( newWidgetInfo.labelStyle.overrideColor )
2497 if ( newWidgetInfo.labelStyle.color.isValid() )
2499 groupBox->
setStyleSheet( QStringLiteral(
"QGroupBox::title { color: %1; }" ).arg( newWidgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2502 if ( newWidgetInfo.labelStyle.overrideFont )
2504 groupBox->setFont( newWidgetInfo.labelStyle.font );
2507 myContainer = groupBox;
2508 newWidgetInfo.widget = myContainer;
2515 QWidget *rowWidget =
new QWidget();
2516 widgetName = QStringLiteral(
"Row" );
2517 myContainer = rowWidget;
2518 newWidgetInfo.widget = myContainer;
2519 removeLayoutMargin =
true;
2520 columnCount = container->
children().size();
2526 myContainer =
new QWidget();
2530 scrollArea->setWidget( myContainer );
2531 scrollArea->setWidgetResizable(
true );
2532 scrollArea->setFrameShape( QFrame::NoFrame );
2533 widgetName = QStringLiteral(
"QScrollArea QWidget" );
2535 newWidgetInfo.widget = scrollArea;
2542 QString style {QStringLiteral(
"background-color: %1;" ).arg( container->
backgroundColor().name() )};
2543 newWidgetInfo.widget->setStyleSheet( style );
2546 QGridLayout *gbLayout =
new QGridLayout();
2547 if ( removeLayoutMargin )
2548 gbLayout->setContentsMargins( 0, 0, 0, 0 );
2549 myContainer->setLayout( gbLayout );
2553 bool addSpacer =
true;
2555 const QList<QgsAttributeEditorElement *> children = container->
children();
2559 WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context );
2571 int widgetColumn = column;
2573 if ( widgetInfo.labelText.isNull() || ! widgetInfo.showLabel )
2575 gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 );
2576 widgetColumn = column + 1;
2581 QLabel *mypLabel =
new QLabel( widgetInfo.labelText );
2583 if ( widgetInfo.labelStyle.overrideColor )
2585 if ( widgetInfo.labelStyle.color.isValid() )
2587 mypLabel->setStyleSheet( QStringLiteral(
"QLabel { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2591 if ( widgetInfo.labelStyle.overrideFont )
2593 mypLabel->setFont( widgetInfo.labelStyle.font );
2601 const int fldIdx = fieldDef->
idx();
2602 if ( fldIdx < fields.
count() && fldIdx >= 0 )
2604 const QString fieldName { fields.
at( fldIdx ).
name() };
2608 if ( property.isActive() )
2610 mLabelDataDefinedProperties[ mypLabel ] = property;
2616 if ( property.isActive() )
2618 mEditableDataDefinedProperties[ widgetInfo.widget ] = property;
2624 mypLabel->setToolTip( widgetInfo.toolTip );
2625 if ( columnCount > 1 && !widgetInfo.labelOnTop )
2627 mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
2630 mypLabel->setBuddy( widgetInfo.widget );
2632 if ( widgetInfo.labelOnTop )
2634 widgetColumn = column + 1;
2635 QVBoxLayout *
c =
new QVBoxLayout();
2636 mypLabel->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
2637 c->layout()->addWidget( mypLabel );
2638 c->layout()->addWidget( widgetInfo.widget );
2639 gbLayout->addLayout(
c, row, column, 1, 2 );
2644 widgetColumn = column + 1;
2645 gbLayout->addWidget( mypLabel, row, column++ );
2646 gbLayout->addWidget( widgetInfo.widget, row, column++ );
2650 const int childHorizontalStretch = childDef->horizontalStretch();
2651 const int existingColumnStretch = gbLayout->columnStretch( widgetColumn );
2652 if ( childHorizontalStretch > 0 && childHorizontalStretch > existingColumnStretch )
2654 gbLayout->setColumnStretch( widgetColumn, childHorizontalStretch );
2657 if ( childDef->verticalStretch() > 0 && childDef->verticalStretch() > gbLayout->rowStretch( row ) )
2659 gbLayout->setRowStretch( row, childDef->verticalStretch() );
2662 if ( column >= columnCount * 2 )
2668 if ( widgetInfo.widget
2669 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
2670 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
2671 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
2675 if ( qobject_cast<QgsAttributeFormRelationEditorWidget *>( widgetInfo.widget ) )
2681 QWidget *spacer =
new QWidget();
2682 spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
2683 gbLayout->addWidget( spacer, ++row, 0 );
2684 gbLayout->setRowStretch( row, 1 );
2687 newWidgetInfo.labelText = QString();
2688 newWidgetInfo.labelOnTop =
true;
2689 newWidgetInfo.showLabel = widgetDef->
showLabel();
2702 mWidgets.append( qmlWrapper );
2704 newWidgetInfo.widget = qmlWrapper->
widget();
2705 newWidgetInfo.labelText = elementDef->
name();
2706 newWidgetInfo.labelOnTop =
true;
2707 newWidgetInfo.showLabel = widgetDef->
showLabel();
2719 mWidgets.append( htmlWrapper );
2721 newWidgetInfo.widget = htmlWrapper->
widget();
2722 newWidgetInfo.labelText = elementDef->
name();
2723 newWidgetInfo.labelOnTop =
true;
2724 newWidgetInfo.showLabel = widgetDef->
showLabel();
2737 mWidgets.append( textWrapper );
2739 newWidgetInfo.widget = textWrapper->
widget();
2740 newWidgetInfo.labelText = elementDef->
name();
2741 newWidgetInfo.labelOnTop =
false;
2742 newWidgetInfo.showLabel = widgetDef->
showLabel();
2753 mWidgets.append( spacerWrapper );
2755 newWidgetInfo.widget = spacerWrapper->
widget();
2756 newWidgetInfo.labelOnTop =
false;
2757 newWidgetInfo.showLabel =
false;
2762 QgsDebugError( QStringLiteral(
"Unknown attribute editor widget type encountered..." ) );
2766 return newWidgetInfo;
2769void QgsAttributeForm::createWrappers()
2771 QList<QWidget *> myWidgets = findChildren<QWidget *>();
2772 const QList<QgsField> fields = mLayer->
fields().
toList();
2774 const auto constMyWidgets = myWidgets;
2775 for ( QWidget *myWidget : constMyWidgets )
2778 QVariant vRel = myWidget->property(
"qgisRelation" );
2779 if ( vRel.isValid() )
2789 mWidgets.append( rww );
2794 const auto constFields = fields;
2795 for (
const QgsField &field : constFields )
2797 if ( field.name() == myWidget->objectName() )
2802 mWidgets.append( eww );
2809void QgsAttributeForm::afterWidgetInit()
2811 bool isFirstEww =
true;
2813 const auto constMWidgets = mWidgets;
2822 setFocusProxy( eww->
widget() );
2832 if ( relationWidgetWrapper )
2845 if ( e->type() == QEvent::KeyPress )
2847 QKeyEvent *keyEvent =
dynamic_cast<QKeyEvent *
>( e );
2848 if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
2860 QSet< int > &mixedValueFields,
2861 QHash< int, QVariant > &fieldSharedValues )
const
2863 mixedValueFields.clear();
2864 fieldSharedValues.clear();
2870 for (
int i = 0; i < mLayer->
fields().count(); ++i )
2872 if ( mixedValueFields.contains( i ) )
2877 fieldSharedValues[i] = f.
attribute( i );
2881 if ( fieldSharedValues.value( i ) != f.
attribute( i ) )
2883 fieldSharedValues.remove( i );
2884 mixedValueFields.insert( i );
2890 if ( mixedValueFields.count() == mLayer->
fields().
count() )
2899void QgsAttributeForm::layerSelectionChanged()
2912 resetMultiEdit(
true );
2919 mIsSettingMultiEditFeatures =
true;
2920 mMultiEditFeatureIds = fids;
2922 if ( fids.isEmpty() )
2925 QMultiMap< int, QgsAttributeFormEditorWidget * >::const_iterator wIt = mFormEditorWidgets.constBegin();
2926 for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
2928 wIt.value()->initialize( QVariant() );
2930 mIsSettingMultiEditFeatures =
false;
2937 QSet< int > mixedValueFields;
2938 QHash< int, QVariant > fieldSharedValues;
2939 scanForEqualAttributes( fit, mixedValueFields, fieldSharedValues );
2948 if ( mCurrentFormFeature.
id() != firstFeature.
id( ) )
2953 const auto constMixedValueFields = mixedValueFields;
2954 for (
int fieldIndex : std::as_const( mixedValueFields ) )
2956 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( fieldIndex );
2957 if ( formEditorWidgets.isEmpty() )
2960 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
2961 QVariantList additionalFieldValues;
2962 for (
const QString &additionalField : additionalFields )
2963 additionalFieldValues << firstFeature.
attribute( additionalField );
2966 w->initialize( firstFeature.
attribute( fieldIndex ),
true, additionalFieldValues );
2968 QHash< int, QVariant >::const_iterator sharedValueIt = fieldSharedValues.constBegin();
2969 for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
2971 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( sharedValueIt.key() );
2972 if ( formEditorWidgets.isEmpty() )
2976 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
2977 for (
const QString &additionalField : additionalFields )
2980 if ( constMixedValueFields.contains( index ) )
2987 QVariantList additionalFieldValues;
2990 for (
const QString &additionalField : additionalFields )
2991 additionalFieldValues << firstFeature.
attribute( additionalField );
2993 w->initialize( firstFeature.
attribute( sharedValueIt.key() ),
true, additionalFieldValues );
2997 for (
const QString &additionalField : additionalFields )
3000 Q_ASSERT( fieldSharedValues.contains( index ) );
3001 additionalFieldValues << fieldSharedValues.value( index );
3004 w->initialize( sharedValueIt.value(),
false, additionalFieldValues );
3008 setMultiEditFeatureIdsRelations( fids );
3010 mIsSettingMultiEditFeatures =
false;
3015 if ( mOwnsMessageBar )
3017 mOwnsMessageBar =
false;
3018 mMessageBar = messageBar;
3028 QStringList filters;
3031 QString filter = widget->currentFilterExpression();
3032 if ( !filter.isNull() )
3033 filters <<
'(' + filter +
')';
3036 return filters.join( QLatin1String(
" AND " ) );
3041 mExtraContextScope.reset( extraScope );
3047 const bool newVisibility = expression.evaluate( expressionContext ).toBool();
3049 if ( expression.isValid() && ! expression.hasEvalError() && newVisibility != isVisible )
3057 widget->setVisible( newVisibility );
3060 isVisible = newVisibility;
3063 const bool newCollapsedState = collapsedExpression.evaluate( expressionContext ).toBool();
3065 if ( collapsedExpression.isValid() && ! collapsedExpression.hasEvalError() && newCollapsedState != isCollapsed )
3070 collapsibleGroupBox->
setCollapsed( newCollapsedState );
3071 isCollapsed = newCollapsedState;
3085 if ( infos.count() == 0 || !currentFormValuesFeature( formFeature ) )
3088 const QString hint = tr(
"No feature joined" );
3089 const auto constInfos = infos;
3092 if ( !info->isDynamicFormEnabled() )
3097 mJoinedFeatures[info] = joinFeature;
3099 if ( info->hasSubset() )
3103 const auto constSubsetNames = subsetNames;
3104 for (
const QString &field : constSubsetNames )
3106 QString prefixedName = info->prefixedFieldName( field );
3108 QString hintText = hint;
3122 for (
const QgsField &field : joinFields )
3124 QString prefixedName = info->prefixedFieldName( field );
3126 QString hintText = hint;
3140bool QgsAttributeForm::fieldIsEditable(
int fieldIndex )
const
3145void QgsAttributeForm::updateFieldDependencies()
3147 mDefaultValueDependencies.clear();
3148 mVirtualFieldsDependencies.clear();
3149 mRelatedLayerFieldsDependencies.clear();
3158 updateFieldDependenciesDefaultValue( eww );
3159 updateFieldDependenciesVirtualFields( eww );
3160 updateRelatedLayerFieldsDependencies( eww );
3168 if ( exp.needsGeometry() )
3169 mNeedsGeometry =
true;
3176 for (
const int id : allAttributeIds )
3178 mDefaultValueDependencies.insertMulti(
id, eww );
3184 const QSet<QString> referencedColumns = exp.referencedColumns();
3185 for (
const QString &referencedColumn : referencedColumns )
3187 mDefaultValueDependencies.insertMulti( mLayer->
fields().
lookupField( referencedColumn ), eww );
3195 if ( expressionField.isEmpty() )
3200 if ( exp.needsGeometry() )
3201 mNeedsGeometry =
true;
3208 for (
const int id : allAttributeIds )
3210 mVirtualFieldsDependencies.insertMulti(
id, eww );
3216 const QSet<QString> referencedColumns = exp.referencedColumns();
3217 for (
const QString &referencedColumn : referencedColumns )
3219 mVirtualFieldsDependencies.insertMulti( mLayer->
fields().
lookupField( referencedColumn ), eww );
3229 if ( expressionField.contains( QStringLiteral(
"relation_aggregate" ) )
3230 || expressionField.contains( QStringLiteral(
"get_features" ) ) )
3231 mRelatedLayerFieldsDependencies.insert( eww );
3235 mRelatedLayerFieldsDependencies.clear();
3240 if ( ! editorWidgetWrapper )
3243 updateRelatedLayerFieldsDependencies( editorWidgetWrapper );
3248void QgsAttributeForm::setMultiEditFeatureIdsRelations(
const QgsFeatureIds &fids )
3253 if ( !relationEditorWidget )
3266 mIconMap[eww->
widget()]->hide();
3280 const QString file = QStringLiteral(
"/mIconJoinNotEditable.svg" );
3281 const QString tooltip = tr(
"Join settings do not allow editing" );
3282 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3286 const QString file = QStringLiteral(
"mIconJoinHasNotUpsertOnEdit.svg" );
3287 const QString tooltip = tr(
"Join settings do not allow upsert on edit" );
3288 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3292 const QString file = QStringLiteral(
"/mIconJoinedLayerNotEditable.svg" );
3293 const QString tooltip = tr(
"Joined layer is not toggled editable" );
3294 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3300void QgsAttributeForm::reloadIcon(
const QString &file,
const QString &tooltip, QSvgWidget *sw )
3303 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 (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.
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.
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 pythonMacroAllowed(void(*lambda)()=nullptr, QgsMessageBar *messageBar=nullptr)
Returns true if python macros are currently allowed to be run If the global option is to ask user,...
static void warning(const QString &msg)
Goes to qWarning.
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
void editingStarted()
Emitted when editing on this layer has started.
void triggerRepaint(bool deferredUpdate=false)
Will advise the map canvas (and any other interested party) that this layer requires to be repainted.
Represents an item shown within a QgsMessageBar widget.
A bar for displaying non-blocking messages to the user.
bool popWidget(QgsMessageBarItem *item)
Remove the specified item from the bar, and display the next most recent one in the stack.
void pushMessage(const QString &text, Qgis::MessageLevel level=Qgis::MessageLevel::Info, int duration=-1)
A convenience method for pushing a message with the specified text to the bar.
void pushItem(QgsMessageBarItem *item)
Display a message item on the bar, after hiding the currently visible one and putting it in a stack.
QFile * localFile(const QString &filePathOrUrl)
Returns a QFile from a local file or to a temporary file previously fetched by the registry.
bool enabled() const
Check if this optional is enabled.
T data() const
Access the payload data.
QgsRelationManager * relationManager
static QgsProject * instance()
Returns the QgsProject singleton instance.
bool hasProperty(int key) const final
Returns true if the collection contains a property with the specified key.
QgsProperty property(int key) const final
Returns a matching property from the collection, if one exists.
A store for object properties.
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a Python statement.
static bool eval(const QString &command, QString &result)
Eval a Python statement.
This class manages a set of relations between layers.
QList< QgsRelation > referencedRelations(const QgsVectorLayer *layer=nullptr) const
Gets all relations where this layer is the referenced part (i.e.
Q_INVOKABLE QgsRelation relation(const QString &id) const
Gets access to a relation by its id.
bool isInvalidJSON()
Returns whether the text edit widget contains Invalid JSON.
Wraps a label widget to display text.
bool needsGeometry() const
Returns true if the widget needs feature geometry.
void setText(const QString &text)
Sets the text code to htmlCode.
void reinitWidget()
Clears the content and makes new initialization.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
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
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
QMap< int, QVariant > QgsAttributeMap
QSet< QgsFeatureId > QgsFeatureIds
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)