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();
110 qDeleteAll( mInterfaces );
137 mInterfaces.append( iface );
153 if ( mUnsavedMultiEditChanges )
156 int res = QMessageBox::question(
this, tr(
"Multiedit Attributes" ),
157 tr(
"Apply changes to edited features?" ), QMessageBox::Yes | QMessageBox::No );
158 if ( res == QMessageBox::Yes )
163 clearMultiEditMessages();
165 mUnsavedMultiEditChanges =
false;
217 w->setContext( newContext );
223 w->setVisible( relationWidgetsVisible );
230 mSearchButtonBox->setVisible(
false );
235 mSearchButtonBox->setVisible(
false );
240 mSearchButtonBox->setVisible(
false );
244 resetMultiEdit(
false );
246 mSearchButtonBox->setVisible(
false );
250 mSearchButtonBox->setVisible(
true );
256 mSearchButtonBox->setVisible(
false );
264 mSearchButtonBox->setVisible(
false );
273 const auto constMWidgets = mWidgets;
288 QVariant mainValue = eww->
value();
290 additionalFieldValues[index] = value;
291 eww->
setValues( mainValue, additionalFieldValues );
305 mIsSettingFeature =
true;
322 mIsSettingFeature =
false;
323 const auto constMInterfaces = mInterfaces;
326 iface->featureChanged();
342 mIsSettingFeature =
false;
345bool QgsAttributeForm::saveEdits( QString *error )
348 bool changedLayer =
false;
353 bool doUpdate =
false;
375 *error = tr(
"JSON value for %1 is invalid and has not been saved" ).arg( eww->
field().
name() );
378 QVariantList dstVars = QVariantList() << dst.at( eww->
fieldIdx() );
379 QVariantList srcVars = QVariantList() << eww->
value();
380 QList<int> fieldIndexes = QList<int>() << eww->
fieldIdx();
384 for (
const QString &fieldName : additionalFields )
388 dstVars << dst.at( idx );
392 Q_ASSERT( dstVars.count() == srcVars.count() );
394 for (
int i = 0; i < dstVars.count(); i++ )
397 if ( !
qgsVariantEqual( dstVars[i], srcVars[i] ) && srcVars[i].isValid() )
399 dst[fieldIndexes[i]] = srcVars[i];
409 const auto constMInterfaces = mInterfaces;
412 if ( !iface->acceptChanges( updatedFeature ) )
422 mFeature = updatedFeature;
428 bool res = mLayer->
addFeature( updatedFeature );
447 for (
int i = 0; i < dst.count(); ++i )
450 || !dst.at( i ).isValid()
451 || !fieldIsEditable( i ) )
457 QgsDebugMsgLevel( QStringLiteral(
"dst:'%1' (type:%2, isNull:%3, isValid:%4)" )
458 .arg( dst.at( i ).toString(), dst.at( i ).typeName() ).arg(
QgsVariantUtils::isNull( dst.at( i ) ) ).arg( dst.at( i ).isValid() ), 2 );
459 QgsDebugMsgLevel( QStringLiteral(
"src:'%1' (type:%2, isNull:%3, isValid:%4)" )
460 .arg( src.at( i ).toString(), src.at( i ).typeName() ).arg(
QgsVariantUtils::isNull( src.at( i ) ) ).arg( src.at( i ).isValid() ), 2 );
462 newValues[i] = dst.at( i );
463 oldValues[i] = src.at( i );
468 std::unique_ptr<QgsVectorLayerToolsContext> context = std::make_unique<QgsVectorLayerToolsContext>();
470 context->setExpressionContext( &expressionContext );
473 if ( success && n > 0 )
500QgsFeature QgsAttributeForm::getUpdatedFeature()
const
512 QVariantList dstVars = QVariantList() << featureAttributes.at( eww->
fieldIdx() );
513 QVariantList srcVars = QVariantList() << eww->
value();
514 QList<int> fieldIndexes = QList<int>() << eww->
fieldIdx();
518 for (
const QString &fieldName : additionalFields )
522 dstVars << featureAttributes.at( idx );
526 Q_ASSERT( dstVars.count() == srcVars.count() );
528 for (
int i = 0; i < dstVars.count(); i++ )
530 if ( !
qgsVariantEqual( dstVars[i], srcVars[i] ) && srcVars[i].isValid() )
531 featureAttributes[fieldIndexes[i]] = srcVars[i];
536 return updatedFeature;
539void QgsAttributeForm::updateValuesDependencies(
const int originIdx )
541 updateValuesDependenciesDefaultValues( originIdx );
542 updateValuesDependenciesVirtualFields( originIdx );
545void QgsAttributeForm::updateValuesDependenciesDefaultValues(
const int originIdx )
547 if ( !mDefaultValueDependencies.contains( originIdx ) )
555 QgsFeature updatedFeature = getUpdatedFeature();
558 QList<QgsWidgetWrapper *> relevantWidgets = mDefaultValueDependencies.values( originIdx );
575 if ( mAlreadyUpdatedFields.contains( eww->
fieldIdx() ) )
587void QgsAttributeForm::updateValuesDependenciesVirtualFields(
const int originIdx )
589 if ( !mVirtualFieldsDependencies.contains( originIdx ) )
596 QgsFeature updatedFeature = getUpdatedFeature();
599 const QList<QgsWidgetWrapper *> relevantWidgets = mVirtualFieldsDependencies.values( originIdx );
607 if ( mAlreadyUpdatedFields.contains( eww->
fieldIdx() ) )
613 const QVariant value = exp.evaluate( &context );
619void QgsAttributeForm::updateValuesDependenciesParent()
622 QgsFeature updatedFeature = getUpdatedFeature();
623 QList<int> updatedFields;
626 const QSet<QgsEditorWidgetWrapper *> relevantWidgets = mParentDependencies;
630 if ( updatedFields.contains( eww->
fieldIdx() ) )
641void QgsAttributeForm::updateRelatedLayerFields()
644 updateRelatedLayerFieldsDependencies();
646 if ( mRelatedLayerFieldsDependencies.isEmpty() )
653 QgsFeature updatedFeature = getUpdatedFeature();
656 const QSet<QgsEditorWidgetWrapper *> relevantWidgets = mRelatedLayerFieldsDependencies;
660 if ( mAlreadyUpdatedFields.contains( eww->
fieldIdx() ) )
666 QVariant value = exp.evaluate( &context );
671void QgsAttributeForm::resetMultiEdit(
bool promptToSave )
676 mUnsavedMultiEditChanges =
false;
680void QgsAttributeForm::multiEditMessageClicked(
const QString &link )
682 clearMultiEditMessages();
683 resetMultiEdit( link == QLatin1String(
"#apply" ) );
686void QgsAttributeForm::filterTriggered()
688 QString filter = createFilterExpression();
694void QgsAttributeForm::searchZoomTo()
696 QString filter = createFilterExpression();
697 if ( filter.isEmpty() )
703void QgsAttributeForm::searchFlash()
705 QString filter = createFilterExpression();
706 if ( filter.isEmpty() )
712void QgsAttributeForm::filterAndTriggered()
714 QString filter = createFilterExpression();
715 if ( filter.isEmpty() )
723void QgsAttributeForm::filterOrTriggered()
725 QString filter = createFilterExpression();
726 if ( filter.isEmpty() )
734void QgsAttributeForm::pushSelectedFeaturesMessage()
740 tr(
"%n matching feature(s) selected",
"matching features", count ),
746 tr(
"No matching features found" ),
760 QString filter = createFilterExpression();
761 if ( filter.isEmpty() )
765 pushSelectedFeaturesMessage();
770void QgsAttributeForm::searchSetSelection()
775void QgsAttributeForm::searchAddToSelection()
780void QgsAttributeForm::searchRemoveFromSelection()
785void QgsAttributeForm::searchIntersectSelection()
790bool QgsAttributeForm::saveMultiEdits()
794 const QList<int> fieldIndexes = mFormEditorWidgets.uniqueKeys();
795 mFormEditorWidgets.constBegin();
796 for (
int fieldIndex : fieldIndexes )
798 const QList<QgsAttributeFormEditorWidget *> widgets = mFormEditorWidgets.values( fieldIndex );
799 if ( !widgets.first()->hasChanged() )
802 if ( !widgets.first()->currentValue().isValid()
803 || !fieldIsEditable( fieldIndex ) )
810 widget->changesCommitted();
812 newAttributeValues.insert( fieldIndex, widgets.first()->currentValue() );
815 if ( newAttributeValues.isEmpty() )
823 int res = QMessageBox::information(
this, tr(
"Multiedit Attributes" ),
824 tr(
"Edits will be applied to all selected features." ), QMessageBox::Ok | QMessageBox::Cancel );
825 if ( res != QMessageBox::Ok )
836 const auto constMultiEditFeatureIds = mMultiEditFeatureIds;
839 QgsAttributeMap::const_iterator aIt = newAttributeValues.constBegin();
840 for ( ; aIt != newAttributeValues.constEnd(); ++aIt )
846 clearMultiEditMessages();
859 if ( !mButtonBox->isVisible() )
860 mMessageBar->
pushItem( mMultiEditMessageBarItem );
886 wrapper->notifyAboutToSave();
926 success = saveEdits( error );
930 success = saveMultiEdits();
935 mUnsavedMultiEditChanges =
false;
944 mValuesInitialized =
false;
945 const auto constMWidgets = mWidgets;
948 ww->setFeature( mFeature );
959 mAlreadyUpdatedFields.append( eww->
fieldIdx() );
960 updateValuesDependenciesVirtualFields( eww->
fieldIdx() );
961 mAlreadyUpdatedFields.removeAll( eww->
fieldIdx() );
964 mValuesInitialized =
true;
970 const auto widgets { findChildren< QgsAttributeFormEditorWidget * >() };
977void QgsAttributeForm::clearMultiEditMessages()
979 if ( mMultiEditUnsavedMessageBarItem )
981 if ( !mButtonBox->isVisible() )
982 mMessageBar->
popWidget( mMultiEditUnsavedMessageBarItem );
983 mMultiEditUnsavedMessageBarItem =
nullptr;
985 if ( mMultiEditMessageBarItem )
987 if ( !mButtonBox->isVisible() )
988 mMessageBar->
popWidget( mMultiEditMessageBarItem );
989 mMultiEditMessageBarItem =
nullptr;
993QString QgsAttributeForm::createFilterExpression()
const
998 QString filter = w->currentFilterExpression();
999 if ( !filter.isEmpty() )
1003 if ( filters.isEmpty() )
1006 QString filter = filters.join( QLatin1String(
") AND (" ) ).prepend(
'(' ).append(
')' );
1015 if ( mExtraContextScope )
1028void QgsAttributeForm::onAttributeChanged(
const QVariant &value,
const QVariantList &additionalFieldValues )
1033 bool signalEmitted =
false;
1035 if ( mValuesInitialized )
1042 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1045 if ( formEditorWidget->editorWidget() == eww )
1049 formEditorWidget->editorWidget()->setValue( value );
1066 for (
int i = 0; i < additionalFields.count(); i++ )
1068 const QString fieldName = additionalFields.at( i );
1069 const QVariant value = additionalFieldValues.at( i );
1073 signalEmitted =
true;
1075 if ( mValuesInitialized )
1076 updateJoinedFields( *eww );
1082 if ( !mIsSettingMultiEditFeatures )
1084 mUnsavedMultiEditChanges =
true;
1086 QLabel *msgLabel =
new QLabel( tr(
"Unsaved multiedit changes: <a href=\"#apply\">apply changes</a> or <a href=\"#reset\">reset changes</a>." ), mMessageBar );
1087 msgLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
1088 msgLabel->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1089 connect( msgLabel, &QLabel::linkActivated,
this, &QgsAttributeForm::multiEditMessageClicked );
1090 clearMultiEditMessages();
1093 if ( !mButtonBox->isVisible() )
1094 mMessageBar->
pushItem( mMultiEditUnsavedMessageBarItem );
1097 signalEmitted =
true;
1107 updateConstraints( eww );
1110 if ( mValuesInitialized )
1113 mAlreadyUpdatedFields.append( eww->
fieldIdx() );
1114 updateValuesDependencies( eww->
fieldIdx() );
1115 mAlreadyUpdatedFields.removeAll( eww->
fieldIdx() );
1120 updateEditableState();
1122 if ( !signalEmitted )
1127 bool attributeHasChanged = !mIsSettingFeature;
1129 attributeHasChanged &= !mIsSettingMultiEditFeatures;
1135void QgsAttributeForm::updateAllConstraints()
1137 const auto constMWidgets = mWidgets;
1142 updateConstraints( eww );
1150 if ( currentFormValuesFeature( ft ) )
1162 updateConstraint( ft, eww );
1165 const QList<QgsEditorWidgetWrapper *> deps = constraintDependencies( eww );
1168 updateConstraint( ft, depsEww );
1176 const QVector<ContainerInformation *> infos = mContainerInformationDependency.value( eww->
field().
name() );
1177 for ( ContainerInformation *info : infos )
1179 info->apply( &context );
1184void QgsAttributeForm::updateContainersVisibility()
1188 const QVector<ContainerInformation *> infos = mContainerVisibilityCollapsedInformation;
1190 for ( ContainerInformation *info : infos )
1192 info->apply( &context );
1202 updateAllConstraints();
1218 if ( mJoinedFeatures.contains( info ) )
1235void QgsAttributeForm::updateLabels()
1237 if ( ! mLabelDataDefinedProperties.isEmpty() )
1240 if ( currentFormValuesFeature( currentFeature ) )
1244 for (
auto it = mLabelDataDefinedProperties.constBegin() ; it != mLabelDataDefinedProperties.constEnd(); ++it )
1246 QLabel *label { it.key() };
1248 const QString value { it->valueAsString( context, QString(), &ok ) };
1249 if ( ok && ! value.isEmpty() )
1251 label->setText( value );
1258void QgsAttributeForm::updateEditableState()
1260 if ( ! mEditableDataDefinedProperties.isEmpty() )
1263 if ( currentFormValuesFeature( currentFeature ) )
1267 for (
auto it = mEditableDataDefinedProperties.constBegin() ; it != mEditableDataDefinedProperties.constEnd(); ++it )
1269 QWidget *w { it.key() };
1271 const bool isEditable { it->valueAsBool( context,
true, &ok ) && mLayer && mLayer->
isEditable() };
1281 w->setEnabled( isEditable );
1289bool QgsAttributeForm::currentFormValuesFeature(
QgsFeature &feature )
1302 if ( dst.count() > eww->
fieldIdx() )
1304 QVariantList dstVars = QVariantList() << dst.at( eww->
fieldIdx() );
1305 QVariantList srcVars = QVariantList() << eww->
value();
1306 QList<int> fieldIndexes = QList<int>() << eww->
fieldIdx();
1310 for (
const QString &fieldName : additionalFields )
1313 fieldIndexes << idx;
1314 dstVars << dst.at( idx );
1318 Q_ASSERT( dstVars.count() == srcVars.count() );
1320 for (
int i = 0; i < dstVars.count(); i++ )
1326 dst[fieldIndexes[i]] = srcVars[i];
1343void QgsAttributeForm::registerContainerInformation( QgsAttributeForm::ContainerInformation *info )
1345 mContainerVisibilityCollapsedInformation.append( info );
1347 const QSet<QString> referencedColumns = info->expression.referencedColumns().unite( info->collapsedExpression.referencedColumns() );
1349 for (
const QString &col : referencedColumns )
1351 mContainerInformationDependency[ col ].append( info );
1355bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields, QStringList &descriptions )
const
1379bool QgsAttributeForm::currentFormValidHardConstraints( QStringList &invalidFields, QStringList &descriptions )
const
1400void QgsAttributeForm::onAttributeAdded(
int idx )
1402 mPreventFeatureRefresh =
false;
1414void QgsAttributeForm::onAttributeDeleted(
int idx )
1416 mPreventFeatureRefresh =
false;
1420 attrs.remove( idx );
1428void QgsAttributeForm::onRelatedFeaturesChanged()
1430 updateRelatedLayerFields();
1433void QgsAttributeForm::onUpdatedFields()
1435 mPreventFeatureRefresh =
false;
1462void QgsAttributeForm::onConstraintStatusChanged(
const QString &constraint,
1468 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1472 formEditorWidget->setConstraintStatus( constraint, description, err, result );
1473 if ( formEditorWidget->editorWidget() != eww )
1475 formEditorWidget->editorWidget()->updateConstraint( result, err );
1482 QList<QgsEditorWidgetWrapper *> wDeps;
1494 if ( name != ewwName )
1501 for (
const QString &colName : referencedColumns )
1503 if ( name == colName )
1505 wDeps.append( eww );
1518 return setupRelationWidgetWrapper( QString(), rel, context );
1531void QgsAttributeForm::preventFeatureRefresh()
1533 mPreventFeatureRefresh =
true;
1559 updateValuesDependenciesParent();
1573 return mNeedsGeometry;
1576void QgsAttributeForm::synchronizeState()
1578 bool isEditable = ( mFeature.
isValid()
1588 const QList<QgsAttributeFormEditorWidget *> formWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1591 formWidget->setConstraintResultVisible( isEditable );
1595 bool enabled = isEditable && fieldIsEditable( eww->
fieldIdx() );
1596 ww->setEnabled( enabled );
1602 ww->setEnabled( isEditable );
1613 if ( mConstraintsFailMessageBarItem )
1615 mMessageBar->
popWidget( mConstraintsFailMessageBarItem );
1618 mMessageBar->
pushItem( mConstraintsFailMessageBarItem );
1622 QStringList invalidFields, descriptions;
1623 mValidConstraints = currentFormValidHardConstraints( invalidFields, descriptions );
1627 if ( !mValidConstraints && !mConstraintsFailMessageBarItem )
1629 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 );
1630 mMessageBar->
pushItem( mConstraintsFailMessageBarItem );
1632 else if ( mValidConstraints && mConstraintsFailMessageBarItem )
1634 mMessageBar->
popWidget( mConstraintsFailMessageBarItem );
1635 mConstraintsFailMessageBarItem =
nullptr;
1638 else if ( mConstraintsFailMessageBarItem )
1640 mMessageBar->
popWidget( mConstraintsFailMessageBarItem );
1641 mConstraintsFailMessageBarItem =
nullptr;
1644 isEditable = isEditable & mValidConstraints;
1649 QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
1651 okButton->setEnabled( isEditable );
1654void QgsAttributeForm::init()
1656 QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
1659 QWidget *formWidget =
nullptr;
1660 mNeedsGeometry =
false;
1662 bool buttonBoxVisible =
true;
1666 buttonBoxVisible = mButtonBox->isVisible();
1668 mButtonBox =
nullptr;
1671 if ( mSearchButtonBox )
1673 delete mSearchButtonBox;
1674 mSearchButtonBox =
nullptr;
1677 qDeleteAll( mWidgets );
1680 while ( QWidget *w = this->findChild<QWidget *>() )
1686 QVBoxLayout *vl =
new QVBoxLayout();
1687 vl->setContentsMargins( 0, 0, 0, 0 );
1689 mMessageBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1690 vl->addWidget( mMessageBar );
1695 QGridLayout *layout =
new QGridLayout();
1696 QWidget *container =
new QWidget();
1697 container->setLayout( layout );
1698 vl->addWidget( container );
1700 mFormEditorWidgets.clear();
1701 mFormWidgets.clear();
1704 setContentsMargins( 0, 0, 0, 0 );
1713 if ( file && file->open( QFile::ReadOnly ) )
1717 QFileInfo fi( file->fileName() );
1718 loader.setWorkingDirectory( fi.dir() );
1719 formWidget = loader.load( file,
this );
1722 formWidget->setWindowFlags( Qt::Widget );
1723 layout->addWidget( formWidget );
1726 mButtonBox = findChild<QDialogButtonBox *>();
1729 formWidget->installEventFilter(
this );
1741 int columnCount = 1;
1742 bool hasRootFields =
false;
1743 bool addSpacer =
true;
1752 if ( !containerDef )
1755 switch ( containerDef->
type() )
1759 tabWidget =
nullptr;
1760 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1761 if ( widgetInfo.labelStyle.overrideColor )
1763 if ( widgetInfo.labelStyle.color.isValid() )
1765 widgetInfo.widget->setStyleSheet( QStringLiteral(
"QGroupBox::title { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1768 if ( widgetInfo.labelStyle.overrideFont )
1770 widgetInfo.widget->setFont( widgetInfo.labelStyle.font );
1773 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1774 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1776 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1778 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1780 layout->setRowStretch( row, widgDef->verticalStretch() );
1794 tabWidget =
nullptr;
1795 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1796 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1797 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1799 layout->setRowStretch( row, widgDef->verticalStretch() );
1802 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1804 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1820 layout->addWidget( tabWidget, row, column, 1, 2 );
1824 QWidget *tabPage =
new QWidget( tabWidget );
1826 tabWidget->addTab( tabPage, widgDef->name() );
1827 tabWidget->
setTabStyle( tabWidget->tabBar()->count() - 1, widgDef->labelStyle() );
1831 registerContainerInformation(
new ContainerInformation( tabWidget, tabPage, containerDef->
visibilityExpression().
data() ) );
1833 QGridLayout *tabPageLayout =
new QGridLayout();
1834 tabPage->setLayout( tabPageLayout );
1836 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, tabPage, mLayer, mContext );
1837 tabPageLayout->addWidget( widgetInfo.widget );
1844 hasRootFields =
true;
1845 tabWidget =
nullptr;
1846 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1849 if ( widgetInfo.showLabel )
1851 if ( widgetInfo.labelStyle.overrideColor && widgetInfo.labelStyle.color.isValid() )
1853 collapsibleGroupBox->
setStyleSheet( QStringLiteral(
"QGroupBox::title { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1856 if ( widgetInfo.labelStyle.overrideFont )
1858 collapsibleGroupBox->setFont( widgetInfo.labelStyle.font );
1861 collapsibleGroupBox->setTitle( widgetInfo.labelText );
1864 QVBoxLayout *collapsibleGroupBoxLayout =
new QVBoxLayout();
1865 collapsibleGroupBoxLayout->addWidget( widgetInfo.widget );
1866 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
1868 QVBoxLayout *
c =
new QVBoxLayout();
1869 c->addWidget( collapsibleGroupBox );
1870 layout->addLayout(
c, row, column, 1, 2 );
1872 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1873 layout->setRowStretch( row, widgDef->verticalStretch() );
1874 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1875 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1884 hasRootFields =
true;
1885 tabWidget =
nullptr;
1886 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1887 QLabel *label =
new QLabel( widgetInfo.labelText );
1889 if ( widgetInfo.labelStyle.overrideColor )
1891 if ( widgetInfo.labelStyle.color.isValid() )
1893 label->setStyleSheet( QStringLiteral(
"QLabel { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1897 if ( widgetInfo.labelStyle.overrideFont )
1899 label->setFont( widgetInfo.labelStyle.font );
1902 label->setToolTip( widgetInfo.toolTip );
1903 if ( columnCount > 1 && !widgetInfo.labelOnTop )
1905 label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1908 label->setBuddy( widgetInfo.widget );
1911 if ( widgetInfo.widget
1912 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
1913 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
1914 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
1917 if ( !widgetInfo.showLabel )
1919 QVBoxLayout *
c =
new QVBoxLayout();
1920 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1921 c->addWidget( widgetInfo.widget );
1922 layout->addLayout(
c, row, column, 1, 2 );
1924 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1926 layout->setRowStretch( row, widgDef->verticalStretch() );
1929 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1931 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1936 else if ( widgetInfo.labelOnTop )
1938 QVBoxLayout *
c =
new QVBoxLayout();
1939 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1940 c->addWidget( label );
1941 c->addWidget( widgetInfo.widget );
1942 layout->addLayout(
c, row, column, 1, 2 );
1944 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1946 layout->setRowStretch( row, widgDef->verticalStretch() );
1949 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1951 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1958 const int widgetColumn = column + 1;
1959 layout->addWidget( label, row, column++ );
1960 layout->addWidget( widgetInfo.widget, row, column++ );
1962 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1964 layout->setRowStretch( row, widgDef->verticalStretch() );
1967 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( widgetColumn ) )
1969 layout->setColumnStretch( widgetColumn, widgDef->horizontalStretch() );
1977 const int fieldIdx = fieldElement->
idx();
1978 if ( fieldIdx >= 0 && fieldIdx < mLayer->fields().count() )
1980 const QString fieldName { mLayer->
fields().
at( fieldIdx ).
name() };
1984 if ( property.isActive() )
1986 mLabelDataDefinedProperties[ label ] = property;
1992 if ( property.isActive() )
1994 mEditableDataDefinedProperties[ widgetInfo.widget ] = property;
2001 if ( column >= columnCount * 2 )
2008 if ( hasRootFields && addSpacer )
2010 QSpacerItem *spacerItem =
new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
2011 layout->addItem( spacerItem, row, 0 );
2012 layout->setRowStretch( row, 1 );
2015 formWidget = container;
2024 formWidget =
new QWidget(
this );
2025 QGridLayout *gridLayout =
new QGridLayout( formWidget );
2026 formWidget->setLayout( gridLayout );
2032 scrollArea->setWidget( formWidget );
2033 scrollArea->setWidgetResizable(
true );
2034 scrollArea->setFrameShape( QFrame::NoFrame );
2035 scrollArea->setFrameShadow( QFrame::Plain );
2036 scrollArea->setFocusProxy(
this );
2037 layout->addWidget( scrollArea );
2041 layout->addWidget( formWidget );
2048 for (
const QgsField &field : fields )
2056 QString labelText = fieldName;
2057 labelText.replace(
'&', QLatin1String(
"&&" ) );
2061 if ( widgetSetup.
type() == QLatin1String(
"Hidden" ) )
2067 QLabel *label =
new QLabel( labelText );
2069 QSvgWidget *i =
new QSvgWidget();
2070 i->setFixedSize( 18, 18 );
2075 if ( property.isActive() )
2077 mLabelDataDefinedProperties[ label ] = property;
2083 QWidget *w =
nullptr;
2088 mFormEditorWidgets.insert( idx, formWidget );
2089 mFormWidgets.append( formWidget );
2092 label->setBuddy( eww->
widget() );
2097 if ( property.isActive() )
2099 mEditableDataDefinedProperties[ formWidget ] = property;
2105 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() ) ) );
2110 w->setObjectName( field.name() );
2114 mWidgets.append( eww );
2115 mIconMap[eww->
widget()] = i;
2120 gridLayout->addWidget( label, row++, 0, 1, 2 );
2121 gridLayout->addWidget( w, row++, 0, 1, 2 );
2122 gridLayout->addWidget( i, row++, 0, 1, 2 );
2126 gridLayout->addWidget( label, row, 0 );
2127 gridLayout->addWidget( w, row, 1 );
2128 gridLayout->addWidget( i, row++, 2 );
2142 QVBoxLayout *collapsibleGroupBoxLayout =
new QVBoxLayout();
2143 collapsibleGroupBoxLayout->addWidget( formWidget );
2144 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
2146 gridLayout->addWidget( collapsibleGroupBox, row++, 0, 1, 2 );
2148 mWidgets.append( rww );
2149 mFormWidgets.append( formWidget );
2154 QSpacerItem *spacerItem =
new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
2155 gridLayout->addItem( spacerItem, row, 0 );
2156 gridLayout->setRowStretch( row, 1 );
2162 updateFieldDependencies();
2166 mButtonBox =
new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
2167 mButtonBox->setObjectName( QStringLiteral(
"buttonBox" ) );
2168 layout->addWidget( mButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2170 mButtonBox->setVisible( buttonBoxVisible );
2172 if ( !mSearchButtonBox )
2174 mSearchButtonBox =
new QWidget();
2175 QHBoxLayout *boxLayout =
new QHBoxLayout();
2176 boxLayout->setContentsMargins( 0, 0, 0, 0 );
2177 mSearchButtonBox->setLayout( boxLayout );
2178 mSearchButtonBox->setObjectName( QStringLiteral(
"searchButtonBox" ) );
2180 QPushButton *clearButton =
new QPushButton( tr(
"&Reset Form" ), mSearchButtonBox );
2182 boxLayout->addWidget( clearButton );
2183 boxLayout->addStretch( 1 );
2185 QPushButton *flashButton =
new QPushButton();
2186 flashButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2187 flashButton->setText( tr(
"&Flash Features" ) );
2188 connect( flashButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchFlash );
2189 boxLayout->addWidget( flashButton );
2191 QPushButton *openAttributeTableButton =
new QPushButton();
2192 openAttributeTableButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2193 openAttributeTableButton->setText( tr(
"Show in &Table" ) );
2194 openAttributeTableButton->setToolTip( tr(
"Open the attribute table editor with the filtered features" ) );
2195 connect( openAttributeTableButton, &QToolButton::clicked,
this, [ = ]
2199 boxLayout->addWidget( openAttributeTableButton );
2201 QPushButton *zoomButton =
new QPushButton();
2202 zoomButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2203 zoomButton->setText( tr(
"&Zoom to Features" ) );
2204 connect( zoomButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchZoomTo );
2205 boxLayout->addWidget( zoomButton );
2207 QToolButton *selectButton =
new QToolButton();
2208 selectButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2209 selectButton->setText( tr(
"&Select Features" ) );
2211 selectButton->setPopupMode( QToolButton::MenuButtonPopup );
2212 selectButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
2213 connect( selectButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchSetSelection );
2214 QMenu *selectMenu =
new QMenu( selectButton );
2215 QAction *selectAction =
new QAction( tr(
"Select Features" ), selectMenu );
2217 connect( selectAction, &QAction::triggered,
this, &QgsAttributeForm::searchSetSelection );
2218 selectMenu->addAction( selectAction );
2219 QAction *addSelectAction =
new QAction( tr(
"Add to Current Selection" ), selectMenu );
2221 connect( addSelectAction, &QAction::triggered,
this, &QgsAttributeForm::searchAddToSelection );
2222 selectMenu->addAction( addSelectAction );
2223 QAction *deselectAction =
new QAction( tr(
"Remove from Current Selection" ), selectMenu );
2225 connect( deselectAction, &QAction::triggered,
this, &QgsAttributeForm::searchRemoveFromSelection );
2226 selectMenu->addAction( deselectAction );
2227 QAction *filterSelectAction =
new QAction( tr(
"Filter Current Selection" ), selectMenu );
2229 connect( filterSelectAction, &QAction::triggered,
this, &QgsAttributeForm::searchIntersectSelection );
2230 selectMenu->addAction( filterSelectAction );
2231 selectButton->setMenu( selectMenu );
2232 boxLayout->addWidget( selectButton );
2236 QToolButton *filterButton =
new QToolButton();
2237 filterButton->setText( tr(
"Filter Features" ) );
2238 filterButton->setPopupMode( QToolButton::MenuButtonPopup );
2239 filterButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2240 connect( filterButton, &QToolButton::clicked,
this, &QgsAttributeForm::filterTriggered );
2241 QMenu *filterMenu =
new QMenu( filterButton );
2242 QAction *filterAndAction =
new QAction( tr(
"Filter Within (\"AND\")" ), filterMenu );
2243 connect( filterAndAction, &QAction::triggered,
this, &QgsAttributeForm::filterAndTriggered );
2244 filterMenu->addAction( filterAndAction );
2245 QAction *filterOrAction =
new QAction( tr(
"Extend Filter (\"OR\")" ), filterMenu );
2246 connect( filterOrAction, &QAction::triggered,
this, &QgsAttributeForm::filterOrTriggered );
2247 filterMenu->addAction( filterOrAction );
2248 filterButton->setMenu( filterMenu );
2249 boxLayout->addWidget( filterButton );
2253 QPushButton *closeButton =
new QPushButton( tr(
"Close" ), mSearchButtonBox );
2255 closeButton->setShortcut( Qt::Key_Escape );
2256 boxLayout->addWidget( closeButton );
2259 layout->addWidget( mSearchButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2277 const auto constMInterfaces = mInterfaces;
2288 QApplication::restoreOverrideCursor();
2291void QgsAttributeForm::cleanPython()
2293 if ( !mPyFormVarName.isNull() )
2295 QString expr = QStringLiteral(
"if '%1' in locals(): del %1\n" ).arg( mPyFormVarName );
2300void QgsAttributeForm::initPython()
2317 if ( !initFilePath.isEmpty() )
2321 if ( inputFile && inputFile->open( QFile::ReadOnly ) )
2324 QTextStream inf( inputFile );
2325#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2326 inf.setCodec(
"UTF-8" );
2328 initCode = inf.readAll();
2333 QgsLogger::warning( QStringLiteral(
"The external python file path %1 could not be opened!" ).arg( initFilePath ) );
2344 if ( initCode.isEmpty() )
2346 QgsLogger::warning( QStringLiteral(
"The python code provided in the dialog is empty!" ) );
2357 if ( !initCode.isEmpty() )
2363 tr(
"Python macro could not be run due to missing permissions." ),
2371 if (
QgsPythonRunner::eval( QStringLiteral(
"len(inspect.getfullargspec(%1)[0])" ).arg( initFunction ), numArgs ) )
2373 static int sFormId = 0;
2374 mPyFormVarName = QStringLiteral(
"_qgis_featureform_%1_%2" ).arg( mFormNr ).arg( sFormId++ );
2376 QString form = QStringLiteral(
"%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )" )
2377 .arg( mPyFormVarName )
2378 .arg( ( quint64 )
this );
2382 QgsDebugMsgLevel( QStringLiteral(
"running featureForm init: %1" ).arg( mPyFormVarName ), 2 );
2385 if ( numArgs == QLatin1String(
"3" ) )
2393 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 ) );
2396 QString expr = QString(
"%1(%2)" )
2397 .arg( mLayer->editFormInit() )
2398 .arg( mPyFormVarName );
2399 QgsAttributeFormInterface *iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface *>( expr,
"QgsAttributeFormInterface" );
2409 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 ) );
2417 WidgetInfo newWidgetInfo;
2419 newWidgetInfo.labelStyle = widgetDef->
labelStyle();
2421 switch ( widgetDef->
type() )
2433 mWidgets.append( actionWrapper );
2434 newWidgetInfo.widget = actionWrapper->
widget();
2435 newWidgetInfo.showLabel =
false;
2448 if ( fldIdx < fields.
count() && fldIdx >= 0 )
2454 mFormEditorWidgets.insert( fldIdx, formWidget );
2455 mFormWidgets.append( formWidget );
2459 newWidgetInfo.widget = formWidget;
2460 mWidgets.append( eww );
2462 newWidgetInfo.widget->setObjectName( fields.
at( fldIdx ).
name() );
2463 newWidgetInfo.hint = fields.
at( fldIdx ).
comment();
2468 newWidgetInfo.labelText.replace(
'&', QLatin1String(
"&&" ) );
2469 newWidgetInfo.toolTip = QStringLiteral(
"<b>%1</b><p>%2</p>" ).arg( mLayer->
attributeDisplayName( fldIdx ), newWidgetInfo.hint );
2470 newWidgetInfo.showLabel = widgetDef->
showLabel();
2491 mWidgets.append( rww );
2492 mFormWidgets.append( formWidget );
2494 newWidgetInfo.widget = formWidget;
2495 newWidgetInfo.showLabel = relDef->
showLabel();
2496 newWidgetInfo.labelText = relDef->
label();
2497 if ( newWidgetInfo.labelText.isEmpty() )
2499 newWidgetInfo.labelOnTop =
true;
2511 if ( columnCount <= 0 )
2515 QWidget *myContainer =
nullptr;
2516 bool removeLayoutMargin =
false;
2517 switch ( container->
type() )
2522 widgetName = QStringLiteral(
"QGroupBox" );
2525 groupBox->setTitle( container->
name() );
2526 if ( newWidgetInfo.labelStyle.overrideColor )
2528 if ( newWidgetInfo.labelStyle.color.isValid() )
2530 groupBox->
setStyleSheet( QStringLiteral(
"QGroupBox::title { color: %1; }" ).arg( newWidgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2533 if ( newWidgetInfo.labelStyle.overrideFont )
2535 groupBox->setFont( newWidgetInfo.labelStyle.font );
2538 myContainer = groupBox;
2539 newWidgetInfo.widget = myContainer;
2546 QWidget *rowWidget =
new QWidget();
2547 widgetName = QStringLiteral(
"Row" );
2548 myContainer = rowWidget;
2549 newWidgetInfo.widget = myContainer;
2550 removeLayoutMargin =
true;
2551 columnCount = container->
children().size();
2557 myContainer =
new QWidget();
2561 scrollArea->setWidget( myContainer );
2562 scrollArea->setWidgetResizable(
true );
2563 scrollArea->setFrameShape( QFrame::NoFrame );
2564 widgetName = QStringLiteral(
"QScrollArea QWidget" );
2566 newWidgetInfo.widget = scrollArea;
2573 QString style {QStringLiteral(
"background-color: %1;" ).arg( container->
backgroundColor().name() )};
2574 newWidgetInfo.widget->setStyleSheet( style );
2577 QGridLayout *gbLayout =
new QGridLayout();
2578 if ( removeLayoutMargin )
2579 gbLayout->setContentsMargins( 0, 0, 0, 0 );
2580 myContainer->setLayout( gbLayout );
2584 bool addSpacer =
true;
2586 const QList<QgsAttributeEditorElement *> children = container->
children();
2590 WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context );
2602 int widgetColumn = column;
2604 if ( widgetInfo.labelText.isNull() || ! widgetInfo.showLabel )
2606 gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 );
2607 widgetColumn = column + 1;
2612 QLabel *mypLabel =
new QLabel( widgetInfo.labelText );
2614 if ( widgetInfo.labelStyle.overrideColor )
2616 if ( widgetInfo.labelStyle.color.isValid() )
2618 mypLabel->setStyleSheet( QStringLiteral(
"QLabel { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2622 if ( widgetInfo.labelStyle.overrideFont )
2624 mypLabel->setFont( widgetInfo.labelStyle.font );
2632 const int fldIdx = fieldDef->
idx();
2633 if ( fldIdx < fields.
count() && fldIdx >= 0 )
2635 const QString fieldName { fields.
at( fldIdx ).
name() };
2639 if ( property.isActive() )
2641 mLabelDataDefinedProperties[ mypLabel ] = property;
2647 if ( property.isActive() )
2649 mEditableDataDefinedProperties[ widgetInfo.widget ] = property;
2655 mypLabel->setToolTip( widgetInfo.toolTip );
2656 if ( columnCount > 1 && !widgetInfo.labelOnTop )
2658 mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
2661 mypLabel->setBuddy( widgetInfo.widget );
2663 if ( widgetInfo.labelOnTop )
2665 widgetColumn = column + 1;
2666 QVBoxLayout *
c =
new QVBoxLayout();
2667 mypLabel->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
2668 c->layout()->addWidget( mypLabel );
2669 c->layout()->addWidget( widgetInfo.widget );
2670 gbLayout->addLayout(
c, row, column, 1, 2 );
2675 widgetColumn = column + 1;
2676 gbLayout->addWidget( mypLabel, row, column++ );
2677 gbLayout->addWidget( widgetInfo.widget, row, column++ );
2681 const int childHorizontalStretch = childDef->horizontalStretch();
2682 const int existingColumnStretch = gbLayout->columnStretch( widgetColumn );
2683 if ( childHorizontalStretch > 0 && childHorizontalStretch > existingColumnStretch )
2685 gbLayout->setColumnStretch( widgetColumn, childHorizontalStretch );
2688 if ( childDef->verticalStretch() > 0 && childDef->verticalStretch() > gbLayout->rowStretch( row ) )
2690 gbLayout->setRowStretch( row, childDef->verticalStretch() );
2693 if ( column >= columnCount * 2 )
2699 if ( widgetInfo.widget
2700 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
2701 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
2702 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
2706 if ( qobject_cast<QgsAttributeFormRelationEditorWidget *>( widgetInfo.widget ) )
2712 QWidget *spacer =
new QWidget();
2713 spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
2714 gbLayout->addWidget( spacer, ++row, 0 );
2715 gbLayout->setRowStretch( row, 1 );
2718 newWidgetInfo.labelText = QString();
2719 newWidgetInfo.labelOnTop =
true;
2720 newWidgetInfo.showLabel = widgetDef->
showLabel();
2733 mWidgets.append( qmlWrapper );
2735 newWidgetInfo.widget = qmlWrapper->
widget();
2736 newWidgetInfo.labelText = elementDef->
name();
2737 newWidgetInfo.labelOnTop =
true;
2738 newWidgetInfo.showLabel = widgetDef->
showLabel();
2750 mWidgets.append( htmlWrapper );
2752 newWidgetInfo.widget = htmlWrapper->
widget();
2753 newWidgetInfo.labelText = elementDef->
name();
2754 newWidgetInfo.labelOnTop =
true;
2755 newWidgetInfo.showLabel = widgetDef->
showLabel();
2768 mWidgets.append( textWrapper );
2770 newWidgetInfo.widget = textWrapper->
widget();
2771 newWidgetInfo.labelText = elementDef->
name();
2772 newWidgetInfo.labelOnTop =
false;
2773 newWidgetInfo.showLabel = widgetDef->
showLabel();
2784 mWidgets.append( spacerWrapper );
2786 newWidgetInfo.widget = spacerWrapper->
widget();
2787 newWidgetInfo.labelOnTop =
false;
2788 newWidgetInfo.showLabel =
false;
2793 QgsDebugError( QStringLiteral(
"Unknown attribute editor widget type encountered..." ) );
2797 return newWidgetInfo;
2800void QgsAttributeForm::createWrappers()
2802 QList<QWidget *> myWidgets = findChildren<QWidget *>();
2803 const QList<QgsField> fields = mLayer->
fields().
toList();
2805 const auto constMyWidgets = myWidgets;
2806 for ( QWidget *myWidget : constMyWidgets )
2809 QVariant vRel = myWidget->property(
"qgisRelation" );
2810 if ( vRel.isValid() )
2820 mWidgets.append( rww );
2825 const auto constFields = fields;
2826 for (
const QgsField &field : constFields )
2828 if ( field.name() == myWidget->objectName() )
2833 mWidgets.append( eww );
2840void QgsAttributeForm::afterWidgetInit()
2842 bool isFirstEww =
true;
2844 const auto constMWidgets = mWidgets;
2853 setFocusProxy( eww->
widget() );
2863 if ( relationWidgetWrapper )
2876 if ( e->type() == QEvent::KeyPress )
2878 QKeyEvent *keyEvent =
dynamic_cast<QKeyEvent *
>( e );
2879 if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
2891 QSet< int > &mixedValueFields,
2892 QHash< int, QVariant > &fieldSharedValues )
const
2894 mixedValueFields.clear();
2895 fieldSharedValues.clear();
2901 for (
int i = 0; i < mLayer->
fields().count(); ++i )
2903 if ( mixedValueFields.contains( i ) )
2908 fieldSharedValues[i] = f.
attribute( i );
2912 if ( fieldSharedValues.value( i ) != f.
attribute( i ) )
2914 fieldSharedValues.remove( i );
2915 mixedValueFields.insert( i );
2921 if ( mixedValueFields.count() == mLayer->
fields().
count() )
2930void QgsAttributeForm::layerSelectionChanged()
2943 resetMultiEdit(
true );
2950 mIsSettingMultiEditFeatures =
true;
2951 mMultiEditFeatureIds = fids;
2953 if ( fids.isEmpty() )
2956 QMultiMap< int, QgsAttributeFormEditorWidget * >::const_iterator wIt = mFormEditorWidgets.constBegin();
2957 for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
2959 wIt.value()->initialize( QVariant() );
2961 mIsSettingMultiEditFeatures =
false;
2968 QSet< int > mixedValueFields;
2969 QHash< int, QVariant > fieldSharedValues;
2970 scanForEqualAttributes( fit, mixedValueFields, fieldSharedValues );
2979 if ( mCurrentFormFeature.
id() != firstFeature.
id( ) )
2984 const auto constMixedValueFields = mixedValueFields;
2985 for (
int fieldIndex : std::as_const( mixedValueFields ) )
2987 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( fieldIndex );
2988 if ( formEditorWidgets.isEmpty() )
2991 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
2992 QVariantList additionalFieldValues;
2993 for (
const QString &additionalField : additionalFields )
2994 additionalFieldValues << firstFeature.
attribute( additionalField );
2997 w->initialize( firstFeature.
attribute( fieldIndex ),
true, additionalFieldValues );
2999 QHash< int, QVariant >::const_iterator sharedValueIt = fieldSharedValues.constBegin();
3000 for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
3002 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( sharedValueIt.key() );
3003 if ( formEditorWidgets.isEmpty() )
3007 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
3008 for (
const QString &additionalField : additionalFields )
3011 if ( constMixedValueFields.contains( index ) )
3018 QVariantList additionalFieldValues;
3021 for (
const QString &additionalField : additionalFields )
3022 additionalFieldValues << firstFeature.
attribute( additionalField );
3024 w->initialize( firstFeature.
attribute( sharedValueIt.key() ),
true, additionalFieldValues );
3028 for (
const QString &additionalField : additionalFields )
3031 Q_ASSERT( fieldSharedValues.contains( index ) );
3032 additionalFieldValues << fieldSharedValues.value( index );
3035 w->initialize( sharedValueIt.value(),
false, additionalFieldValues );
3039 setMultiEditFeatureIdsRelations( fids );
3041 mIsSettingMultiEditFeatures =
false;
3046 if ( mOwnsMessageBar )
3048 mOwnsMessageBar =
false;
3049 mMessageBar = messageBar;
3059 QStringList filters;
3062 QString filter = widget->currentFilterExpression();
3063 if ( !filter.isNull() )
3064 filters <<
'(' + filter +
')';
3067 return filters.join( QLatin1String(
" AND " ) );
3072 mExtraContextScope.reset( extraScope );
3078 const bool newVisibility = expression.evaluate( expressionContext ).toBool();
3080 if ( expression.isValid() && ! expression.hasEvalError() && newVisibility != isVisible )
3088 widget->setVisible( newVisibility );
3091 isVisible = newVisibility;
3094 const bool newCollapsedState = collapsedExpression.evaluate( expressionContext ).toBool();
3096 if ( collapsedExpression.isValid() && ! collapsedExpression.hasEvalError() && newCollapsedState != isCollapsed )
3101 collapsibleGroupBox->
setCollapsed( newCollapsedState );
3102 isCollapsed = newCollapsedState;
3116 if ( infos.count() == 0 || !currentFormValuesFeature( formFeature ) )
3119 const QString hint = tr(
"No feature joined" );
3120 const auto constInfos = infos;
3123 if ( !info->isDynamicFormEnabled() )
3128 mJoinedFeatures[info] = joinFeature;
3130 if ( info->hasSubset() )
3134 const auto constSubsetNames = subsetNames;
3135 for (
const QString &field : constSubsetNames )
3137 QString prefixedName = info->prefixedFieldName( field );
3139 QString hintText = hint;
3153 for (
const QgsField &field : joinFields )
3155 QString prefixedName = info->prefixedFieldName( field );
3157 QString hintText = hint;
3171bool QgsAttributeForm::fieldIsEditable(
int fieldIndex )
const
3176void QgsAttributeForm::updateFieldDependencies()
3178 mDefaultValueDependencies.clear();
3179 mVirtualFieldsDependencies.clear();
3180 mRelatedLayerFieldsDependencies.clear();
3181 mParentDependencies.clear();
3190 updateFieldDependenciesParent( eww );
3191 updateFieldDependenciesDefaultValue( eww );
3192 updateFieldDependenciesVirtualFields( eww );
3193 updateRelatedLayerFieldsDependencies( eww );
3201 if ( exp.needsGeometry() )
3202 mNeedsGeometry =
true;
3209 for (
const int id : allAttributeIds )
3211 mDefaultValueDependencies.insertMulti(
id, eww );
3217 const QSet<QString> referencedColumns = exp.referencedColumns();
3218 for (
const QString &referencedColumn : referencedColumns )
3220 mDefaultValueDependencies.insertMulti( mLayer->
fields().
lookupField( referencedColumn ), eww );
3228 if ( expressionField.isEmpty() )
3233 if ( exp.needsGeometry() )
3234 mNeedsGeometry =
true;
3241 for (
const int id : allAttributeIds )
3243 mVirtualFieldsDependencies.insertMulti(
id, eww );
3249 const QSet<QString> referencedColumns = exp.referencedColumns();
3250 for (
const QString &referencedColumn : referencedColumns )
3252 mVirtualFieldsDependencies.insertMulti( mLayer->
fields().
lookupField( referencedColumn ), eww );
3262 if ( expressionField.contains( QStringLiteral(
"relation_aggregate" ) )
3263 || expressionField.contains( QStringLiteral(
"get_features" ) ) )
3264 mRelatedLayerFieldsDependencies.insert( eww );
3268 mRelatedLayerFieldsDependencies.clear();
3273 if ( ! editorWidgetWrapper )
3276 updateRelatedLayerFieldsDependencies( editorWidgetWrapper );
3286 const QSet< QString > referencedVariablesAndFunctions = expression.referencedVariables() + expression.referencedFunctions();
3287 for (
const QString &referenced : referencedVariablesAndFunctions )
3289 if ( referenced.startsWith( QLatin1String(
"current_parent" ) ) )
3291 mParentDependencies.insert( eww );
3298void QgsAttributeForm::setMultiEditFeatureIdsRelations(
const QgsFeatureIds &fids )
3303 if ( !relationEditorWidget )
3316 mIconMap[eww->
widget()]->hide();
3330 const QString file = QStringLiteral(
"/mIconJoinNotEditable.svg" );
3331 const QString tooltip = tr(
"Join settings do not allow editing" );
3332 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3336 const QString file = QStringLiteral(
"mIconJoinHasNotUpsertOnEdit.svg" );
3337 const QString tooltip = tr(
"Join settings do not allow upsert on edit" );
3338 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3342 const QString file = QStringLiteral(
"/mIconJoinedLayerNotEditable.svg" );
3343 const QString tooltip = tr(
"Joined layer is not toggled editable" );
3344 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3350void QgsAttributeForm::reloadIcon(
const QString &file,
const QString &tooltip, QSvgWidget *sw )
3353 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)