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 for (
int fieldIndex : fieldIndexes )
786 const QList<QgsAttributeFormEditorWidget *> widgets = mFormEditorWidgets.values( fieldIndex );
787 if ( !widgets.first()->hasChanged() )
790 if ( !widgets.first()->currentValue().isValid()
791 || !fieldIsEditable( fieldIndex ) )
798 widget->changesCommitted();
800 newAttributeValues.insert( fieldIndex, widgets.first()->currentValue() );
803 if ( newAttributeValues.isEmpty() )
811 int res = QMessageBox::information(
this, tr(
"Multiedit Attributes" ),
812 tr(
"Edits will be applied to all selected features." ), QMessageBox::Ok | QMessageBox::Cancel );
813 if ( res != QMessageBox::Ok )
824 const auto constMultiEditFeatureIds = mMultiEditFeatureIds;
827 QgsAttributeMap::const_iterator aIt = newAttributeValues.constBegin();
828 for ( ; aIt != newAttributeValues.constEnd(); ++aIt )
834 clearMultiEditMessages();
847 if ( !mButtonBox->isVisible() )
848 mMessageBar->
pushItem( mMultiEditMessageBarItem );
874 wrapper->notifyAboutToSave();
914 success = saveEdits( error );
918 success = saveMultiEdits();
923 mUnsavedMultiEditChanges =
false;
932 mValuesInitialized =
false;
933 const auto constMWidgets = mWidgets;
936 ww->setFeature( mFeature );
947 mAlreadyUpdatedFields.append( eww->
fieldIdx() );
948 updateValuesDependenciesVirtualFields( eww->
fieldIdx() );
949 mAlreadyUpdatedFields.removeAll( eww->
fieldIdx() );
952 mValuesInitialized =
true;
958 const auto widgets { findChildren<QgsAttributeFormEditorWidget *>() };
965void QgsAttributeForm::clearMultiEditMessages()
967 if ( mMultiEditUnsavedMessageBarItem )
969 if ( !mButtonBox->isVisible() )
970 mMessageBar->
popWidget( mMultiEditUnsavedMessageBarItem );
971 mMultiEditUnsavedMessageBarItem =
nullptr;
973 if ( mMultiEditMessageBarItem )
975 if ( !mButtonBox->isVisible() )
976 mMessageBar->
popWidget( mMultiEditMessageBarItem );
977 mMultiEditMessageBarItem =
nullptr;
981QString QgsAttributeForm::createFilterExpression()
const
986 QString filter = w->currentFilterExpression();
987 if ( !filter.isEmpty() )
991 if ( filters.isEmpty() )
994 QString filter = filters.join( QLatin1String(
") AND (" ) ).prepend(
'(' ).append(
')' );
1003 if ( mExtraContextScope )
1016void QgsAttributeForm::onAttributeChanged(
const QVariant &value,
const QVariantList &additionalFieldValues )
1021 bool signalEmitted =
false;
1023 if ( mValuesInitialized )
1030 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1033 if ( formEditorWidget->editorWidget() == eww )
1037 formEditorWidget->editorWidget()->setValue( value );
1054 for (
int i = 0; i < additionalFields.count(); i++ )
1056 const QString fieldName = additionalFields.at( i );
1057 const QVariant value = additionalFieldValues.at( i );
1061 signalEmitted =
true;
1063 if ( mValuesInitialized )
1064 updateJoinedFields( *eww );
1070 if ( !mIsSettingMultiEditFeatures )
1072 mUnsavedMultiEditChanges =
true;
1074 QLabel *msgLabel =
new QLabel( tr(
"Unsaved multiedit changes: <a href=\"#apply\">apply changes</a> or <a href=\"#reset\">reset changes</a>." ), mMessageBar );
1075 msgLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
1076 msgLabel->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1077 connect( msgLabel, &QLabel::linkActivated,
this, &QgsAttributeForm::multiEditMessageClicked );
1078 clearMultiEditMessages();
1081 if ( !mButtonBox->isVisible() )
1082 mMessageBar->
pushItem( mMultiEditUnsavedMessageBarItem );
1085 signalEmitted =
true;
1095 updateConstraints( eww );
1098 if ( mValuesInitialized )
1101 mAlreadyUpdatedFields.append( eww->
fieldIdx() );
1102 updateValuesDependencies( eww->
fieldIdx() );
1103 mAlreadyUpdatedFields.removeAll( eww->
fieldIdx() );
1108 updateEditableState();
1110 if ( !signalEmitted )
1115 bool attributeHasChanged = !mIsSettingFeature;
1117 attributeHasChanged &= !mIsSettingMultiEditFeatures;
1123void QgsAttributeForm::updateAllConstraints()
1125 const auto constMWidgets = mWidgets;
1130 updateConstraints( eww );
1138 if ( currentFormValuesFeature( ft ) )
1150 updateConstraint( ft, eww );
1153 const QList<QgsEditorWidgetWrapper *> deps = constraintDependencies( eww );
1156 updateConstraint( ft, depsEww );
1164 const QVector<ContainerInformation *> infos = mContainerInformationDependency.value( eww->
field().
name() );
1165 for ( ContainerInformation *info : infos )
1167 info->apply( &context );
1172void QgsAttributeForm::updateContainersVisibility()
1176 const QVector<ContainerInformation *> infos = mContainerVisibilityCollapsedInformation;
1178 for ( ContainerInformation *info : infos )
1180 info->apply( &context );
1190 updateAllConstraints();
1205 if ( mJoinedFeatures.contains( info ) )
1221void QgsAttributeForm::updateLabels()
1223 if ( !mLabelDataDefinedProperties.isEmpty() )
1226 if ( currentFormValuesFeature( currentFeature ) )
1230 for (
auto it = mLabelDataDefinedProperties.constBegin(); it != mLabelDataDefinedProperties.constEnd(); ++it )
1232 QLabel *label { it.key() };
1234 const QString value { it->valueAsString( context, QString(), &ok ) };
1235 if ( ok && !value.isEmpty() )
1237 label->setText( value );
1244void QgsAttributeForm::updateEditableState()
1246 if ( !mEditableDataDefinedProperties.isEmpty() )
1249 if ( currentFormValuesFeature( currentFeature ) )
1253 for (
auto it = mEditableDataDefinedProperties.constBegin(); it != mEditableDataDefinedProperties.constEnd(); ++it )
1255 QWidget *w { it.key() };
1257 const bool isEditable { it->valueAsBool( context,
true, &ok ) && mLayer && mLayer->
isEditable() };
1267 w->setEnabled( isEditable );
1275bool QgsAttributeForm::currentFormValuesFeature(
QgsFeature &feature )
1288 if ( dst.count() > eww->
fieldIdx() )
1290 QVariantList dstVars = QVariantList() << dst.at( eww->
fieldIdx() );
1291 QVariantList srcVars = QVariantList() << eww->
value();
1292 QList<int> fieldIndexes = QList<int>() << eww->
fieldIdx();
1296 for (
const QString &fieldName : additionalFields )
1299 fieldIndexes << idx;
1300 dstVars << dst.at( idx );
1304 Q_ASSERT( dstVars.count() == srcVars.count() );
1306 for (
int i = 0; i < dstVars.count(); i++ )
1312 dst[fieldIndexes[i]] = srcVars[i];
1329void QgsAttributeForm::registerContainerInformation( QgsAttributeForm::ContainerInformation *info )
1331 mContainerVisibilityCollapsedInformation.append( info );
1333 const QSet<QString> referencedColumns = info->expression.referencedColumns().unite( info->collapsedExpression.referencedColumns() );
1335 for (
const QString &col : referencedColumns )
1337 mContainerInformationDependency[col].append( info );
1341bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields, QStringList &descriptions )
const
1343 bool valid {
true };
1365bool QgsAttributeForm::currentFormValidHardConstraints( QStringList &invalidFields, QStringList &descriptions )
const
1367 bool valid {
true };
1386void QgsAttributeForm::onAttributeAdded(
int idx )
1388 mPreventFeatureRefresh =
false;
1400void QgsAttributeForm::onAttributeDeleted(
int idx )
1402 mPreventFeatureRefresh =
false;
1406 attrs.remove( idx );
1414void QgsAttributeForm::onRelatedFeaturesChanged()
1416 updateRelatedLayerFields();
1419void QgsAttributeForm::onUpdatedFields()
1421 mPreventFeatureRefresh =
false;
1453 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1457 formEditorWidget->setConstraintStatus( constraint, description, err, result );
1458 if ( formEditorWidget->editorWidget() != eww )
1460 formEditorWidget->editorWidget()->updateConstraint( result, err );
1467 QList<QgsEditorWidgetWrapper *> wDeps;
1479 if ( name != ewwName )
1486 for (
const QString &colName : referencedColumns )
1488 if ( name == colName )
1490 wDeps.append( eww );
1503 return setupRelationWidgetWrapper( QString(), rel, context );
1516void QgsAttributeForm::preventFeatureRefresh()
1518 mPreventFeatureRefresh =
true;
1544 updateValuesDependenciesParent();
1558 return mNeedsGeometry;
1561void QgsAttributeForm::synchronizeState()
1563 bool isEditable = ( mFeature.
isValid()
1573 const QList<QgsAttributeFormEditorWidget *> formWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1576 formWidget->setConstraintResultVisible( isEditable );
1580 bool enabled = isEditable && fieldIsEditable( eww->
fieldIdx() );
1581 ww->setEnabled( enabled );
1587 ww->setEnabled( isEditable );
1597 if ( mConstraintsFailMessageBarItem )
1599 mMessageBar->
popWidget( mConstraintsFailMessageBarItem );
1602 mMessageBar->
pushItem( mConstraintsFailMessageBarItem );
1606 QStringList invalidFields, descriptions;
1607 mValidConstraints = currentFormValidHardConstraints( invalidFields, descriptions );
1611 if ( !mValidConstraints && !mConstraintsFailMessageBarItem )
1613 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 );
1614 mMessageBar->
pushItem( mConstraintsFailMessageBarItem );
1616 else if ( mValidConstraints && mConstraintsFailMessageBarItem )
1618 mMessageBar->
popWidget( mConstraintsFailMessageBarItem );
1619 mConstraintsFailMessageBarItem =
nullptr;
1622 else if ( mConstraintsFailMessageBarItem )
1624 mMessageBar->
popWidget( mConstraintsFailMessageBarItem );
1625 mConstraintsFailMessageBarItem =
nullptr;
1628 isEditable = isEditable & mValidConstraints;
1633 QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
1635 okButton->setEnabled( isEditable );
1638void QgsAttributeForm::init()
1640 QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
1643 QWidget *formWidget =
nullptr;
1644 mNeedsGeometry =
false;
1646 bool buttonBoxVisible =
true;
1650 buttonBoxVisible = mButtonBox->isVisible();
1652 mButtonBox =
nullptr;
1655 if ( mSearchButtonBox )
1657 delete mSearchButtonBox;
1658 mSearchButtonBox =
nullptr;
1661 qDeleteAll( mWidgets );
1664 while ( QWidget *w = this->findChild<QWidget *>() )
1670 QVBoxLayout *vl =
new QVBoxLayout();
1671 vl->setContentsMargins( 0, 0, 0, 0 );
1673 mMessageBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1674 vl->addWidget( mMessageBar );
1679 QGridLayout *layout =
new QGridLayout();
1680 QWidget *container =
new QWidget();
1681 container->setLayout( layout );
1682 vl->addWidget( container );
1684 mFormEditorWidgets.clear();
1685 mFormWidgets.clear();
1688 setContentsMargins( 0, 0, 0, 0 );
1696 if ( file && file->open( QFile::ReadOnly ) )
1700 QFileInfo fi( file->fileName() );
1701 loader.setWorkingDirectory( fi.dir() );
1702 formWidget = loader.load( file,
this );
1705 formWidget->setWindowFlags( Qt::Widget );
1706 layout->addWidget( formWidget );
1709 mButtonBox = findChild<QDialogButtonBox *>();
1712 formWidget->installEventFilter(
this );
1724 int columnCount = 1;
1725 bool hasRootFields =
false;
1726 bool addSpacer =
true;
1735 if ( !containerDef )
1738 switch ( containerDef->
type() )
1742 tabWidget =
nullptr;
1743 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1744 if ( widgetInfo.labelStyle.overrideColor )
1746 if ( widgetInfo.labelStyle.color.isValid() )
1748 widgetInfo.widget->setStyleSheet( QStringLiteral(
"QGroupBox::title { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1751 if ( widgetInfo.labelStyle.overrideFont )
1753 widgetInfo.widget->setFont( widgetInfo.labelStyle.font );
1756 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1757 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1759 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1761 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1763 layout->setRowStretch( row, widgDef->verticalStretch() );
1777 tabWidget =
nullptr;
1778 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1779 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1780 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1782 layout->setRowStretch( row, widgDef->verticalStretch() );
1785 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1787 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1803 layout->addWidget( tabWidget, row, column, 1, 2 );
1807 QWidget *tabPage =
new QWidget( tabWidget );
1809 tabWidget->addTab( tabPage, widgDef->name() );
1810 tabWidget->
setTabStyle( tabWidget->tabBar()->count() - 1, widgDef->labelStyle() );
1814 registerContainerInformation(
new ContainerInformation( tabWidget, tabPage, containerDef->
visibilityExpression().
data() ) );
1816 QGridLayout *tabPageLayout =
new QGridLayout();
1817 tabPage->setLayout( tabPageLayout );
1819 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, tabPage, mLayer, mContext );
1820 tabPageLayout->addWidget( widgetInfo.widget );
1827 hasRootFields =
true;
1828 tabWidget =
nullptr;
1829 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1832 if ( widgetInfo.showLabel )
1834 if ( widgetInfo.labelStyle.overrideColor && widgetInfo.labelStyle.color.isValid() )
1836 collapsibleGroupBox->
setStyleSheet( QStringLiteral(
"QGroupBox::title { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1839 if ( widgetInfo.labelStyle.overrideFont )
1841 collapsibleGroupBox->setFont( widgetInfo.labelStyle.font );
1844 collapsibleGroupBox->setTitle( widgetInfo.labelText );
1847 QVBoxLayout *collapsibleGroupBoxLayout =
new QVBoxLayout();
1848 collapsibleGroupBoxLayout->addWidget( widgetInfo.widget );
1849 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
1851 QVBoxLayout *
c =
new QVBoxLayout();
1852 c->addWidget( collapsibleGroupBox );
1853 layout->addLayout(
c, row, column, 1, 2 );
1855 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1856 layout->setRowStretch( row, widgDef->verticalStretch() );
1857 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1858 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1867 hasRootFields =
true;
1868 tabWidget =
nullptr;
1869 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1870 QLabel *label =
new QLabel( widgetInfo.labelText );
1872 if ( widgetInfo.labelStyle.overrideColor )
1874 if ( widgetInfo.labelStyle.color.isValid() )
1876 label->setStyleSheet( QStringLiteral(
"QLabel { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1880 if ( widgetInfo.labelStyle.overrideFont )
1882 label->setFont( widgetInfo.labelStyle.font );
1885 label->setToolTip( widgetInfo.toolTip );
1886 if ( columnCount > 1 && !widgetInfo.labelOnTop )
1888 label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1891 label->setBuddy( widgetInfo.widget );
1894 if ( widgetInfo.widget
1895 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
1896 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
1897 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
1900 if ( !widgetInfo.showLabel )
1902 QVBoxLayout *
c =
new QVBoxLayout();
1903 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1904 c->addWidget( widgetInfo.widget );
1905 layout->addLayout(
c, row, column, 1, 2 );
1907 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1909 layout->setRowStretch( row, widgDef->verticalStretch() );
1912 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1914 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1919 else if ( widgetInfo.labelOnTop )
1921 QVBoxLayout *
c =
new QVBoxLayout();
1922 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1923 c->addWidget( label );
1924 c->addWidget( widgetInfo.widget );
1925 layout->addLayout(
c, row, column, 1, 2 );
1927 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1929 layout->setRowStretch( row, widgDef->verticalStretch() );
1932 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1934 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1941 const int widgetColumn = column + 1;
1942 layout->addWidget( label, row, column++ );
1943 layout->addWidget( widgetInfo.widget, row, column++ );
1945 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1947 layout->setRowStretch( row, widgDef->verticalStretch() );
1950 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( widgetColumn ) )
1952 layout->setColumnStretch( widgetColumn, widgDef->horizontalStretch() );
1960 const int fieldIdx = fieldElement->
idx();
1961 if ( fieldIdx >= 0 && fieldIdx < mLayer->fields().count() )
1963 const QString fieldName { mLayer->
fields().
at( fieldIdx ).
name() };
1967 if ( property.isActive() )
1969 mLabelDataDefinedProperties[label] = property;
1975 if ( property.isActive() )
1977 mEditableDataDefinedProperties[widgetInfo.widget] = property;
1984 if ( column >= columnCount * 2 )
1991 if ( hasRootFields && addSpacer )
1993 QSpacerItem *spacerItem =
new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
1994 layout->addItem( spacerItem, row, 0 );
1995 layout->setRowStretch( row, 1 );
1998 formWidget = container;
2007 formWidget =
new QWidget(
this );
2008 QGridLayout *gridLayout =
new QGridLayout( formWidget );
2009 formWidget->setLayout( gridLayout );
2015 scrollArea->setWidget( formWidget );
2016 scrollArea->setWidgetResizable(
true );
2017 scrollArea->setFrameShape( QFrame::NoFrame );
2018 scrollArea->setFrameShadow( QFrame::Plain );
2019 scrollArea->setFocusProxy(
this );
2020 layout->addWidget( scrollArea );
2024 layout->addWidget( formWidget );
2031 for (
const QgsField &field : fields )
2039 QString labelText = fieldName;
2040 labelText.replace(
'&', QLatin1String(
"&&" ) );
2044 if ( widgetSetup.
type() == QLatin1String(
"Hidden" ) )
2050 QLabel *label =
new QLabel( labelText );
2052 QSvgWidget *i =
new QSvgWidget();
2053 i->setFixedSize( 18, 18 );
2058 if ( property.isActive() )
2060 mLabelDataDefinedProperties[label] = property;
2066 QWidget *w =
nullptr;
2071 mFormEditorWidgets.insert( idx, formWidget );
2072 mFormWidgets.append( formWidget );
2075 label->setBuddy( eww->
widget() );
2080 if ( property.isActive() )
2082 mEditableDataDefinedProperties[formWidget] = property;
2088 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() ) ) );
2093 w->setObjectName( field.name() );
2097 mWidgets.append( eww );
2098 mIconMap[eww->
widget()] = i;
2103 gridLayout->addWidget( label, row++, 0, 1, 2 );
2104 gridLayout->addWidget( w, row++, 0, 1, 2 );
2105 gridLayout->addWidget( i, row++, 0, 1, 2 );
2109 gridLayout->addWidget( label, row, 0 );
2110 gridLayout->addWidget( w, row, 1 );
2111 gridLayout->addWidget( i, row++, 2 );
2124 QVBoxLayout *collapsibleGroupBoxLayout =
new QVBoxLayout();
2125 collapsibleGroupBoxLayout->addWidget( formWidget );
2126 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
2128 gridLayout->addWidget( collapsibleGroupBox, row++, 0, 1, 2 );
2130 mWidgets.append( rww );
2131 mFormWidgets.append( formWidget );
2136 QSpacerItem *spacerItem =
new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
2137 gridLayout->addItem( spacerItem, row, 0 );
2138 gridLayout->setRowStretch( row, 1 );
2144 updateFieldDependencies();
2148 mButtonBox =
new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
2149 mButtonBox->setObjectName( QStringLiteral(
"buttonBox" ) );
2150 layout->addWidget( mButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2152 mButtonBox->setVisible( buttonBoxVisible );
2154 if ( !mSearchButtonBox )
2156 mSearchButtonBox =
new QWidget();
2157 QHBoxLayout *boxLayout =
new QHBoxLayout();
2158 boxLayout->setContentsMargins( 0, 0, 0, 0 );
2159 mSearchButtonBox->setLayout( boxLayout );
2160 mSearchButtonBox->setObjectName( QStringLiteral(
"searchButtonBox" ) );
2162 QPushButton *clearButton =
new QPushButton( tr(
"&Reset Form" ), mSearchButtonBox );
2164 boxLayout->addWidget( clearButton );
2165 boxLayout->addStretch( 1 );
2167 QPushButton *flashButton =
new QPushButton();
2168 flashButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2169 flashButton->setText( tr(
"&Flash Features" ) );
2170 connect( flashButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchFlash );
2171 boxLayout->addWidget( flashButton );
2173 QPushButton *openAttributeTableButton =
new QPushButton();
2174 openAttributeTableButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2175 openAttributeTableButton->setText( tr(
"Show in &Table" ) );
2176 openAttributeTableButton->setToolTip( tr(
"Open the attribute table editor with the filtered features" ) );
2177 connect( openAttributeTableButton, &QToolButton::clicked,
this, [=] {
2180 boxLayout->addWidget( openAttributeTableButton );
2182 QPushButton *zoomButton =
new QPushButton();
2183 zoomButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2184 zoomButton->setText( tr(
"&Zoom to Features" ) );
2185 connect( zoomButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchZoomTo );
2186 boxLayout->addWidget( zoomButton );
2188 QToolButton *selectButton =
new QToolButton();
2189 selectButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2190 selectButton->setText( tr(
"&Select Features" ) );
2192 selectButton->setPopupMode( QToolButton::MenuButtonPopup );
2193 selectButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
2194 connect( selectButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchSetSelection );
2195 QMenu *selectMenu =
new QMenu( selectButton );
2196 QAction *selectAction =
new QAction( tr(
"Select Features" ), selectMenu );
2198 connect( selectAction, &QAction::triggered,
this, &QgsAttributeForm::searchSetSelection );
2199 selectMenu->addAction( selectAction );
2200 QAction *addSelectAction =
new QAction( tr(
"Add to Current Selection" ), selectMenu );
2202 connect( addSelectAction, &QAction::triggered,
this, &QgsAttributeForm::searchAddToSelection );
2203 selectMenu->addAction( addSelectAction );
2204 QAction *deselectAction =
new QAction( tr(
"Remove from Current Selection" ), selectMenu );
2206 connect( deselectAction, &QAction::triggered,
this, &QgsAttributeForm::searchRemoveFromSelection );
2207 selectMenu->addAction( deselectAction );
2208 QAction *filterSelectAction =
new QAction( tr(
"Filter Current Selection" ), selectMenu );
2210 connect( filterSelectAction, &QAction::triggered,
this, &QgsAttributeForm::searchIntersectSelection );
2211 selectMenu->addAction( filterSelectAction );
2212 selectButton->setMenu( selectMenu );
2213 boxLayout->addWidget( selectButton );
2217 QToolButton *filterButton =
new QToolButton();
2218 filterButton->setText( tr(
"Filter Features" ) );
2219 filterButton->setPopupMode( QToolButton::MenuButtonPopup );
2220 filterButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2221 connect( filterButton, &QToolButton::clicked,
this, &QgsAttributeForm::filterTriggered );
2222 QMenu *filterMenu =
new QMenu( filterButton );
2223 QAction *filterAndAction =
new QAction( tr(
"Filter Within (\"AND\")" ), filterMenu );
2224 connect( filterAndAction, &QAction::triggered,
this, &QgsAttributeForm::filterAndTriggered );
2225 filterMenu->addAction( filterAndAction );
2226 QAction *filterOrAction =
new QAction( tr(
"Extend Filter (\"OR\")" ), filterMenu );
2227 connect( filterOrAction, &QAction::triggered,
this, &QgsAttributeForm::filterOrTriggered );
2228 filterMenu->addAction( filterOrAction );
2229 filterButton->setMenu( filterMenu );
2230 boxLayout->addWidget( filterButton );
2234 QPushButton *closeButton =
new QPushButton( tr(
"Close" ), mSearchButtonBox );
2236 closeButton->setShortcut( Qt::Key_Escape );
2237 boxLayout->addWidget( closeButton );
2240 layout->addWidget( mSearchButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2258 const auto constMInterfaces = mInterfaces;
2269 QApplication::restoreOverrideCursor();
2272void QgsAttributeForm::cleanPython()
2274 if ( !mPyFormVarName.isNull() )
2276 QString expr = QStringLiteral(
"if '%1' in locals(): del %1\n" ).arg( mPyFormVarName );
2281void QgsAttributeForm::initPython()
2297 if ( !initFilePath.isEmpty() )
2301 if ( inputFile && inputFile->open( QFile::ReadOnly ) )
2304 QTextStream inf( inputFile );
2305#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
2306 inf.setCodec(
"UTF-8" );
2308 initCode = inf.readAll();
2313 QgsLogger::warning( QStringLiteral(
"The external python file path %1 could not be opened!" ).arg( initFilePath ) );
2324 if ( initCode.isEmpty() )
2326 QgsLogger::warning( QStringLiteral(
"The python code provided in the dialog is empty!" ) );
2337 if ( !initCode.isEmpty() )
2349 if (
QgsPythonRunner::eval( QStringLiteral(
"len(inspect.getfullargspec(%1)[0])" ).arg( initFunction ), numArgs ) )
2351 static int sFormId = 0;
2352 mPyFormVarName = QStringLiteral(
"_qgis_featureform_%1_%2" ).arg( mFormNr ).arg( sFormId++ );
2354 QString form = QStringLiteral(
"%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )" )
2355 .arg( mPyFormVarName )
2356 .arg( ( quint64 )
this );
2360 QgsDebugMsgLevel( QStringLiteral(
"running featureForm init: %1" ).arg( mPyFormVarName ), 2 );
2363 if ( numArgs == QLatin1String(
"3" ) )
2371 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 ) );
2374 QString expr = QString(
"%1(%2)" )
2375 .arg( mLayer->editFormInit() )
2376 .arg( mPyFormVarName );
2377 QgsAttributeFormInterface *iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface *>( expr,
"QgsAttributeFormInterface" );
2387 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 ) );
2395 WidgetInfo newWidgetInfo;
2397 newWidgetInfo.labelStyle = widgetDef->
labelStyle();
2399 switch ( widgetDef->
type() )
2411 mWidgets.append( actionWrapper );
2412 newWidgetInfo.widget = actionWrapper->
widget();
2413 newWidgetInfo.showLabel =
false;
2426 if ( fldIdx < fields.
count() && fldIdx >= 0 )
2432 mFormEditorWidgets.insert( fldIdx, formWidget );
2433 mFormWidgets.append( formWidget );
2437 newWidgetInfo.widget = formWidget;
2438 mWidgets.append( eww );
2440 newWidgetInfo.widget->setObjectName( fields.
at( fldIdx ).
name() );
2441 newWidgetInfo.hint = fields.
at( fldIdx ).
comment();
2446 newWidgetInfo.labelText.replace(
'&', QLatin1String(
"&&" ) );
2447 newWidgetInfo.toolTip = QStringLiteral(
"<b>%1</b><p>%2</p>" ).arg( mLayer->
attributeDisplayName( fldIdx ), newWidgetInfo.hint );
2448 newWidgetInfo.showLabel = widgetDef->
showLabel();
2469 mWidgets.append( rww );
2470 mFormWidgets.append( formWidget );
2472 newWidgetInfo.widget = formWidget;
2473 newWidgetInfo.showLabel = relDef->
showLabel();
2474 newWidgetInfo.labelText = relDef->
label();
2475 if ( newWidgetInfo.labelText.isEmpty() )
2477 newWidgetInfo.labelOnTop =
true;
2489 if ( columnCount <= 0 )
2493 QWidget *myContainer =
nullptr;
2494 bool removeLayoutMargin =
false;
2495 switch ( container->
type() )
2500 widgetName = QStringLiteral(
"QGroupBox" );
2503 groupBox->setTitle( container->
name() );
2504 if ( newWidgetInfo.labelStyle.overrideColor )
2506 if ( newWidgetInfo.labelStyle.color.isValid() )
2508 groupBox->
setStyleSheet( QStringLiteral(
"QGroupBox::title { color: %1; }" ).arg( newWidgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2511 if ( newWidgetInfo.labelStyle.overrideFont )
2513 groupBox->setFont( newWidgetInfo.labelStyle.font );
2516 myContainer = groupBox;
2517 newWidgetInfo.widget = myContainer;
2524 QWidget *rowWidget =
new QWidget();
2525 widgetName = QStringLiteral(
"Row" );
2526 myContainer = rowWidget;
2527 newWidgetInfo.widget = myContainer;
2528 removeLayoutMargin =
true;
2529 columnCount = container->
children().size();
2535 myContainer =
new QWidget();
2539 scrollArea->setWidget( myContainer );
2540 scrollArea->setWidgetResizable(
true );
2541 scrollArea->setFrameShape( QFrame::NoFrame );
2542 widgetName = QStringLiteral(
"QScrollArea QWidget" );
2544 newWidgetInfo.widget = scrollArea;
2551 QString style { QStringLiteral(
"background-color: %1;" ).arg( container->
backgroundColor().name() ) };
2552 newWidgetInfo.widget->setStyleSheet( style );
2555 QGridLayout *gbLayout =
new QGridLayout();
2556 if ( removeLayoutMargin )
2557 gbLayout->setContentsMargins( 0, 0, 0, 0 );
2558 myContainer->setLayout( gbLayout );
2562 bool addSpacer =
true;
2564 const QList<QgsAttributeEditorElement *> children = container->
children();
2568 WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context );
2580 int widgetColumn = column;
2582 if ( widgetInfo.labelText.isNull() || !widgetInfo.showLabel )
2584 gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 );
2585 widgetColumn = column + 1;
2590 QLabel *mypLabel =
new QLabel( widgetInfo.labelText );
2592 if ( widgetInfo.labelStyle.overrideColor )
2594 if ( widgetInfo.labelStyle.color.isValid() )
2596 mypLabel->setStyleSheet( QStringLiteral(
"QLabel { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2600 if ( widgetInfo.labelStyle.overrideFont )
2602 mypLabel->setFont( widgetInfo.labelStyle.font );
2610 const int fldIdx = fieldDef->
idx();
2611 if ( fldIdx < fields.
count() && fldIdx >= 0 )
2613 const QString fieldName { fields.
at( fldIdx ).
name() };
2617 if ( property.isActive() )
2619 mLabelDataDefinedProperties[mypLabel] = property;
2625 if ( property.isActive() )
2627 mEditableDataDefinedProperties[widgetInfo.widget] = property;
2633 mypLabel->setToolTip( widgetInfo.toolTip );
2634 if ( columnCount > 1 && !widgetInfo.labelOnTop )
2636 mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
2639 mypLabel->setBuddy( widgetInfo.widget );
2641 if ( widgetInfo.labelOnTop )
2643 widgetColumn = column + 1;
2644 QVBoxLayout *
c =
new QVBoxLayout();
2645 mypLabel->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
2646 c->layout()->addWidget( mypLabel );
2647 c->layout()->addWidget( widgetInfo.widget );
2648 gbLayout->addLayout(
c, row, column, 1, 2 );
2653 widgetColumn = column + 1;
2654 gbLayout->addWidget( mypLabel, row, column++ );
2655 gbLayout->addWidget( widgetInfo.widget, row, column++ );
2659 const int childHorizontalStretch = childDef->horizontalStretch();
2660 const int existingColumnStretch = gbLayout->columnStretch( widgetColumn );
2661 if ( childHorizontalStretch > 0 && childHorizontalStretch > existingColumnStretch )
2663 gbLayout->setColumnStretch( widgetColumn, childHorizontalStretch );
2666 if ( childDef->verticalStretch() > 0 && childDef->verticalStretch() > gbLayout->rowStretch( row ) )
2668 gbLayout->setRowStretch( row, childDef->verticalStretch() );
2671 if ( column >= columnCount * 2 )
2677 if ( widgetInfo.widget
2678 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
2679 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
2680 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
2684 if ( qobject_cast<QgsAttributeFormRelationEditorWidget *>( widgetInfo.widget ) )
2690 QWidget *spacer =
new QWidget();
2691 spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
2692 gbLayout->addWidget( spacer, ++row, 0 );
2693 gbLayout->setRowStretch( row, 1 );
2696 newWidgetInfo.labelText = QString();
2697 newWidgetInfo.labelOnTop =
true;
2698 newWidgetInfo.showLabel = widgetDef->
showLabel();
2711 mWidgets.append( qmlWrapper );
2713 newWidgetInfo.widget = qmlWrapper->
widget();
2714 newWidgetInfo.labelText = elementDef->
name();
2715 newWidgetInfo.labelOnTop =
true;
2716 newWidgetInfo.showLabel = widgetDef->
showLabel();
2728 mWidgets.append( htmlWrapper );
2730 newWidgetInfo.widget = htmlWrapper->
widget();
2731 newWidgetInfo.labelText = elementDef->
name();
2732 newWidgetInfo.labelOnTop =
true;
2733 newWidgetInfo.showLabel = widgetDef->
showLabel();
2746 mWidgets.append( textWrapper );
2748 newWidgetInfo.widget = textWrapper->
widget();
2749 newWidgetInfo.labelText = elementDef->
name();
2750 newWidgetInfo.labelOnTop =
false;
2751 newWidgetInfo.showLabel = widgetDef->
showLabel();
2762 mWidgets.append( spacerWrapper );
2764 newWidgetInfo.widget = spacerWrapper->
widget();
2765 newWidgetInfo.labelOnTop =
false;
2766 newWidgetInfo.showLabel =
false;
2771 QgsDebugError( QStringLiteral(
"Unknown attribute editor widget type encountered..." ) );
2775 return newWidgetInfo;
2778void QgsAttributeForm::createWrappers()
2780 QList<QWidget *> myWidgets = findChildren<QWidget *>();
2781 const QList<QgsField> fields = mLayer->
fields().
toList();
2783 const auto constMyWidgets = myWidgets;
2784 for ( QWidget *myWidget : constMyWidgets )
2787 QVariant vRel = myWidget->property(
"qgisRelation" );
2788 if ( vRel.isValid() )
2798 mWidgets.append( rww );
2803 const auto constFields = fields;
2804 for (
const QgsField &field : constFields )
2806 if ( field.name() == myWidget->objectName() )
2811 mWidgets.append( eww );
2818void QgsAttributeForm::afterWidgetInit()
2820 bool isFirstEww =
true;
2822 const auto constMWidgets = mWidgets;
2831 setFocusProxy( eww->
widget() );
2841 if ( relationWidgetWrapper )
2854 if ( e->type() == QEvent::KeyPress )
2856 QKeyEvent *keyEvent =
dynamic_cast<QKeyEvent *
>( e );
2857 if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
2868void QgsAttributeForm::scanForEqualAttributes(
QgsFeatureIterator &fit, QSet<int> &mixedValueFields, QHash<int, QVariant> &fieldSharedValues )
const
2870 mixedValueFields.clear();
2871 fieldSharedValues.clear();
2877 for (
int i = 0; i < mLayer->
fields().count(); ++i )
2879 if ( mixedValueFields.contains( i ) )
2884 fieldSharedValues[i] = f.
attribute( i );
2888 if ( fieldSharedValues.value( i ) != f.
attribute( i ) )
2890 fieldSharedValues.remove( i );
2891 mixedValueFields.insert( i );
2897 if ( mixedValueFields.count() == mLayer->
fields().
count() )
2906void QgsAttributeForm::layerSelectionChanged()
2919 resetMultiEdit(
true );
2926 mIsSettingMultiEditFeatures =
true;
2927 mMultiEditFeatureIds = fids;
2929 if ( fids.isEmpty() )
2932 QMultiMap<int, QgsAttributeFormEditorWidget *>::const_iterator wIt = mFormEditorWidgets.constBegin();
2933 for ( ; wIt != mFormEditorWidgets.constEnd(); ++wIt )
2935 wIt.value()->initialize( QVariant() );
2937 mIsSettingMultiEditFeatures =
false;
2944 QSet<int> mixedValueFields;
2945 QHash<int, QVariant> fieldSharedValues;
2946 scanForEqualAttributes( fit, mixedValueFields, fieldSharedValues );
2955 if ( mCurrentFormFeature.
id() != firstFeature.
id() )
2960 const auto constMixedValueFields = mixedValueFields;
2961 for (
int fieldIndex : std::as_const( mixedValueFields ) )
2963 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( fieldIndex );
2964 if ( formEditorWidgets.isEmpty() )
2967 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
2968 QVariantList additionalFieldValues;
2969 for (
const QString &additionalField : additionalFields )
2970 additionalFieldValues << firstFeature.
attribute( additionalField );
2973 w->initialize( firstFeature.
attribute( fieldIndex ),
true, additionalFieldValues );
2975 QHash<int, QVariant>::const_iterator sharedValueIt = fieldSharedValues.constBegin();
2976 for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
2978 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( sharedValueIt.key() );
2979 if ( formEditorWidgets.isEmpty() )
2983 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
2984 for (
const QString &additionalField : additionalFields )
2987 if ( constMixedValueFields.contains( index ) )
2994 QVariantList additionalFieldValues;
2997 for (
const QString &additionalField : additionalFields )
2998 additionalFieldValues << firstFeature.
attribute( additionalField );
3000 w->initialize( firstFeature.
attribute( sharedValueIt.key() ),
true, additionalFieldValues );
3004 for (
const QString &additionalField : additionalFields )
3007 Q_ASSERT( fieldSharedValues.contains( index ) );
3008 additionalFieldValues << fieldSharedValues.value( index );
3011 w->initialize( sharedValueIt.value(),
false, additionalFieldValues );
3015 setMultiEditFeatureIdsRelations( fids );
3017 mIsSettingMultiEditFeatures =
false;
3022 if ( mOwnsMessageBar )
3024 mOwnsMessageBar =
false;
3025 mMessageBar = messageBar;
3035 QStringList filters;
3038 QString filter = widget->currentFilterExpression();
3039 if ( !filter.isNull() )
3040 filters <<
'(' + filter +
')';
3043 return filters.join( QLatin1String(
" AND " ) );
3048 mExtraContextScope.reset( extraScope );
3053 const bool newVisibility = expression.evaluate( expressionContext ).toBool();
3055 if ( expression.isValid() && !expression.hasEvalError() && newVisibility != isVisible )
3063 widget->setVisible( newVisibility );
3066 isVisible = newVisibility;
3069 const bool newCollapsedState = collapsedExpression.evaluate( expressionContext ).toBool();
3071 if ( collapsedExpression.isValid() && !collapsedExpression.hasEvalError() && newCollapsedState != isCollapsed )
3075 collapsibleGroupBox->
setCollapsed( newCollapsedState );
3076 isCollapsed = newCollapsedState;
3090 if ( infos.count() == 0 || !currentFormValuesFeature( formFeature ) )
3093 const QString hint = tr(
"No feature joined" );
3094 const auto constInfos = infos;
3097 if ( !info->isDynamicFormEnabled() )
3102 mJoinedFeatures[info] = joinFeature;
3104 if ( info->hasSubset() )
3108 const auto constSubsetNames = subsetNames;
3109 for (
const QString &field : constSubsetNames )
3111 QString prefixedName = info->prefixedFieldName( field );
3113 QString hintText = hint;
3127 for (
const QgsField &field : joinFields )
3129 QString prefixedName = info->prefixedFieldName( field );
3131 QString hintText = hint;
3145bool QgsAttributeForm::fieldIsEditable(
int fieldIndex )
const
3150void QgsAttributeForm::updateFieldDependencies()
3152 mDefaultValueDependencies.clear();
3153 mVirtualFieldsDependencies.clear();
3154 mRelatedLayerFieldsDependencies.clear();
3155 mParentDependencies.clear();
3164 updateFieldDependenciesParent( eww );
3165 updateFieldDependenciesDefaultValue( eww );
3166 updateFieldDependenciesVirtualFields( eww );
3167 updateRelatedLayerFieldsDependencies( eww );
3175 if ( exp.needsGeometry() )
3176 mNeedsGeometry =
true;
3183 for (
const int id : allAttributeIds )
3185 mDefaultValueDependencies.insertMulti(
id, eww );
3191 const QSet<QString> referencedColumns = exp.referencedColumns();
3192 for (
const QString &referencedColumn : referencedColumns )
3194 mDefaultValueDependencies.insertMulti( mLayer->
fields().
lookupField( referencedColumn ), eww );
3202 if ( expressionField.isEmpty() )
3207 if ( exp.needsGeometry() )
3208 mNeedsGeometry =
true;
3215 for (
const int id : allAttributeIds )
3217 mVirtualFieldsDependencies.insertMulti(
id, eww );
3223 const QSet<QString> referencedColumns = exp.referencedColumns();
3224 for (
const QString &referencedColumn : referencedColumns )
3226 mVirtualFieldsDependencies.insertMulti( mLayer->
fields().
lookupField( referencedColumn ), eww );
3236 if ( expressionField.contains( QStringLiteral(
"relation_aggregate" ) )
3237 || expressionField.contains( QStringLiteral(
"get_features" ) ) )
3238 mRelatedLayerFieldsDependencies.insert( eww );
3242 mRelatedLayerFieldsDependencies.clear();
3247 if ( !editorWidgetWrapper )
3250 updateRelatedLayerFieldsDependencies( editorWidgetWrapper );
3260 const QSet<QString> referencedVariablesAndFunctions = expression.referencedVariables() + expression.referencedFunctions();
3261 for (
const QString &referenced : referencedVariablesAndFunctions )
3263 if ( referenced.startsWith( QLatin1String(
"current_parent" ) ) )
3265 mParentDependencies.insert( eww );
3272void QgsAttributeForm::setMultiEditFeatureIdsRelations(
const QgsFeatureIds &fids )
3277 if ( !relationEditorWidget )
3290 mIconMap[eww->
widget()]->hide();
3304 const QString file = QStringLiteral(
"/mIconJoinNotEditable.svg" );
3305 const QString tooltip = tr(
"Join settings do not allow editing" );
3306 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3310 const QString file = QStringLiteral(
"mIconJoinHasNotUpsertOnEdit.svg" );
3311 const QString tooltip = tr(
"Join settings do not allow upsert on edit" );
3312 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3316 const QString file = QStringLiteral(
"/mIconJoinedLayerNotEditable.svg" );
3317 const QString tooltip = tr(
"Joined layer is not toggled editable" );
3318 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3324void QgsAttributeForm::reloadIcon(
const QString &file,
const QString &tooltip, QSvgWidget *sw )
3327 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)