80#include "moc_qgsattributeform.cpp"
82using namespace Qt::StringLiterals;
84int QgsAttributeForm::sFormCounter = 0;
90 , mFormNr( sFormCounter++ )
91 , mEditCommandMessage( tr(
"Attributes changed" ) )
103 updateContainersVisibility();
105 updateEditableState();
111 qDeleteAll( mInterfaces );
138 mInterfaces.append( iface );
143 return mFeature.isValid() && mLayer->isEditable();
154 if ( mUnsavedMultiEditChanges )
157 int res = QMessageBox::question(
this, tr(
"Multiedit Attributes" ), tr(
"Apply changes to edited features?" ), QMessageBox::Yes | QMessageBox::No );
158 if ( res == QMessageBox::Yes )
163 clearMultiEditMessages();
165 mUnsavedMultiEditChanges =
false;
221 w->setContext( newContext );
224 auto setRelationWidgetsVisible = [
this](
bool relationWidgetsVisible ) {
227 w->setVisible( relationWidgetsVisible );
234 setRelationWidgetsVisible(
true );
236 mSearchButtonBox->setVisible(
false );
240 setRelationWidgetsVisible(
true );
242 mSearchButtonBox->setVisible(
false );
246 setRelationWidgetsVisible(
true );
248 mSearchButtonBox->setVisible(
false );
252 setRelationWidgetsVisible(
true );
253 resetMultiEdit(
false );
255 mSearchButtonBox->setVisible(
false );
259 setRelationWidgetsVisible(
true );
260 mSearchButtonBox->setVisible(
true );
266 setRelationWidgetsVisible(
false );
267 mSearchButtonBox->setVisible(
false );
273 setRelationWidgetsVisible(
true );
276 mSearchButtonBox->setVisible(
false );
280 setRelationWidgetsVisible(
false );
282 mSearchButtonBox->setVisible(
false );
291 const auto constMWidgets = mWidgets;
306 QVariant mainValue = eww->
value();
308 additionalFieldValues[index] = value;
309 eww->
setValues( mainValue, additionalFieldValues );
318 mFeature.setGeometry( geometry );
323 mIsSettingFeature =
true;
341 mIsSettingFeature =
false;
342 const auto constMInterfaces = mInterfaces;
345 iface->featureChanged();
361 mIsSettingFeature =
false;
364bool QgsAttributeForm::saveEdits( QString *error )
367 bool changedLayer =
false;
372 bool doUpdate =
false;
381 QgsAttributes src = mFeature.attributes();
382 QgsAttributes dst = mFeature.attributes();
384 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
386 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
390 QgsTextEditWrapper *textEdit = qobject_cast<QgsTextEditWrapper *>( eww );
394 *error = tr(
"JSON value for %1 is invalid and has not been saved" ).arg( eww->
field().
name() );
397 QVariantList dstVars = QVariantList() << dst.at( eww->
fieldIdx() );
398 QVariantList srcVars = QVariantList() << eww->
value();
399 QList<int> fieldIndexes = QList<int>() << eww->
fieldIdx();
403 for (
const QString &fieldName : additionalFields )
407 dstVars << dst.at( idx );
411 Q_ASSERT( dstVars.count() == srcVars.count() );
413 for (
int i = 0; i < dstVars.count(); i++ )
415 if ( !
qgsVariantEqual( dstVars[i], srcVars[i] ) && srcVars[i].isValid() )
417 dst[fieldIndexes[i]] = srcVars[i];
427 const auto constMInterfaces = mInterfaces;
428 for ( QgsAttributeFormInterface *iface : constMInterfaces )
430 if ( !iface->acceptChanges( updatedFeature ) )
440 mFeature = updatedFeature;
444 mFeature.setValid(
true );
445 mLayer->beginEditCommand( mEditCommandMessage );
446 bool res = mLayer->addFeature( updatedFeature );
449 mFeature.setAttributes( updatedFeature.
attributes() );
450 mLayer->endEditCommand();
452 const QgsFields fields = mLayer->fields();
453 const QgsAttributes newValues = updatedFeature.
attributes();
454 const QVariant lastUsedValuesVariant = mLayer->property(
"AttributeFormLastUsedValues" );
456 for (
int idx = 0; idx < fields.
count(); ++idx )
461 const QVariant rememberLastUsedValuesVariant = mLayer->property(
"AttributeFormRememberLastUsedValues" );
462 QMap<int, bool> rememberLastUsedValues = rememberLastUsedValuesVariant.value<QMap<int, bool>>();
463 if ( !rememberLastUsedValues.contains( idx ) )
466 rememberLastUsedValues[idx] = remember;
467 mLayer->setProperty(
"AttributeFormRememberLastUsedValues", QVariant::fromValue<QMap<int, bool>>( rememberLastUsedValues ) );
470 const QVariant newValue = rememberLastUsedValues[idx] ? newValues.at( idx ) : QVariant();
471 if ( !lastUsedValues.contains( idx ) || lastUsedValues[idx] != newValue )
473 lastUsedValues[idx] = newValue;
474 QgsDebugMsgLevel( u
"Saving %1 for %2"_s.arg( ( newValue.toString() ).arg( idx ) ), 2 );
478 mLayer->setProperty(
"AttributeFormLastUsedValues", QVariant::fromValue<QgsAttributeMap>( lastUsedValues ) );
484 mLayer->destroyEditCommand();
488 mLayer->beginEditCommand( mEditCommandMessage );
494 for (
int i = 0; i < dst.count(); ++i )
497 || !dst.at( i ).isValid()
498 || !fieldIsEditable( i ) )
504 QgsDebugMsgLevel( u
"dst:'%1' (type:%2, isNull:%3, isValid:%4)"_s.arg( dst.at( i ).toString(), dst.at( i ).typeName() ).arg(
QgsVariantUtils::isNull( dst.at( i ) ) ).arg( dst.at( i ).isValid() ), 2 );
505 QgsDebugMsgLevel( u
"src:'%1' (type:%2, isNull:%3, isValid:%4)"_s.arg( src.at( i ).toString(), src.at( i ).typeName() ).arg(
QgsVariantUtils::isNull( src.at( i ) ) ).arg( src.at( i ).isValid() ), 2 );
507 newValues[i] = dst.at( i );
508 oldValues[i] = src.at( i );
513 auto context = std::make_unique<QgsVectorLayerToolsContext>();
514 QgsExpressionContext expressionContext = createExpressionContext( updatedFeature );
515 context->setExpressionContext( &expressionContext );
516 success = mLayer->changeAttributeValues( mFeature.id(), newValues, oldValues,
false, context.get() );
518 if ( success && n > 0 )
520 mLayer->endEditCommand();
521 mFeature.setAttributes( dst );
526 mLayer->destroyEditCommand();
540 mLayer->triggerRepaint();
545QgsFeature QgsAttributeForm::getUpdatedFeature()
const
548 QgsFeature updatedFeature = QgsFeature( mFeature );
550 QgsAttributes featureAttributes = mFeature.attributes();
551 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
553 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
557 QVariantList dstVars = QVariantList() << featureAttributes.at( eww->
fieldIdx() );
558 QVariantList srcVars = QVariantList() << eww->
value();
559 QList<int> fieldIndexes = QList<int>() << eww->
fieldIdx();
563 for (
const QString &fieldName : additionalFields )
567 dstVars << featureAttributes.at( idx );
571 Q_ASSERT( dstVars.count() == srcVars.count() );
573 for (
int i = 0; i < dstVars.count(); i++ )
575 if ( !
qgsVariantEqual( dstVars[i], srcVars[i] ) && srcVars[i].isValid() )
576 featureAttributes[fieldIndexes[i]] = srcVars[i];
581 return updatedFeature;
584void QgsAttributeForm::updateValuesDependencies(
const int originIdx )
586 updateValuesDependenciesDefaultValues( originIdx );
587 updateValuesDependenciesVirtualFields( originIdx );
590void QgsAttributeForm::updateValuesDependenciesDefaultValues(
const int originIdx )
592 if ( !mDefaultValueDependencies.contains( originIdx ) )
595 if ( !mFeature.isValid() )
614 QgsFeature updatedFeature = getUpdatedFeature();
617 QList<QgsWidgetWrapper *> relevantWidgets = mDefaultValueDependencies.values( originIdx );
618 for ( QgsWidgetWrapper *ww : std::as_const( relevantWidgets ) )
620 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
634 if ( mAlreadyUpdatedFields.contains( eww->
fieldIdx() ) )
637 QgsExpressionContext context = createExpressionContext( updatedFeature );
639 const QVariant value = mLayer->defaultValue( eww->
fieldIdx(), updatedFeature, &context );
641 mCurrentFormFeature.setAttribute( eww->
field().
name(), value );
646void QgsAttributeForm::updateValuesDependenciesVirtualFields(
const int originIdx )
648 if ( !mVirtualFieldsDependencies.contains( originIdx ) )
651 if ( !mFeature.isValid() )
655 QgsFeature updatedFeature = getUpdatedFeature();
658 const QList<QgsWidgetWrapper *> relevantWidgets = mVirtualFieldsDependencies.values( originIdx );
659 for ( QgsWidgetWrapper *ww : relevantWidgets )
661 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
666 if ( mAlreadyUpdatedFields.contains( eww->
fieldIdx() ) )
670 QgsExpressionContext context = createExpressionContext( updatedFeature );
671 QgsExpression exp( mLayer->expressionField( eww->
fieldIdx() ) );
672 const QVariant value = exp.evaluate( &context );
678void QgsAttributeForm::updateValuesDependenciesParent()
681 QgsFeature updatedFeature = getUpdatedFeature();
682 QList<int> updatedFields;
685 const QSet<QgsEditorWidgetWrapper *> relevantWidgets = mParentDependencies;
686 for ( QgsEditorWidgetWrapper *eww : relevantWidgets )
689 if ( updatedFields.contains( eww->
fieldIdx() ) )
694 QgsExpressionContext context = createExpressionContext( updatedFeature );
695 const QVariant value = mLayer->defaultValue( eww->
fieldIdx(), updatedFeature, &context );
700void QgsAttributeForm::updateRelatedLayerFields()
703 updateRelatedLayerFieldsDependencies();
705 if ( mRelatedLayerFieldsDependencies.isEmpty() )
708 if ( !mFeature.isValid() )
712 QgsFeature updatedFeature = getUpdatedFeature();
715 const QSet<QgsEditorWidgetWrapper *> relevantWidgets = mRelatedLayerFieldsDependencies;
716 for ( QgsEditorWidgetWrapper *eww : relevantWidgets )
719 if ( mAlreadyUpdatedFields.contains( eww->
fieldIdx() ) )
723 QgsExpressionContext context = createExpressionContext( updatedFeature );
724 QgsExpression exp( mLayer->expressionField( eww->
fieldIdx() ) );
725 QVariant value = exp.evaluate( &context );
730void QgsAttributeForm::resetMultiEdit(
bool promptToSave )
735 mUnsavedMultiEditChanges =
false;
739void QgsAttributeForm::multiEditMessageClicked(
const QString &link )
741 clearMultiEditMessages();
742 resetMultiEdit( link ==
"#apply"_L1 );
745void QgsAttributeForm::filterTriggered()
747 QString filter = createFilterExpression();
753void QgsAttributeForm::searchZoomTo()
755 QString filter = createFilterExpression();
756 if ( filter.isEmpty() )
762void QgsAttributeForm::searchFlash()
764 QString filter = createFilterExpression();
765 if ( filter.isEmpty() )
771void QgsAttributeForm::filterAndTriggered()
773 QString filter = createFilterExpression();
774 if ( filter.isEmpty() )
782void QgsAttributeForm::filterOrTriggered()
784 QString filter = createFilterExpression();
785 if ( filter.isEmpty() )
793void QgsAttributeForm::pushSelectedFeaturesMessage()
795 int count = mLayer->selectedFeatureCount();
798 mMessageBar->pushMessage( QString(), tr(
"%n matching feature(s) selected",
"matching features", count ),
Qgis::MessageLevel::Info );
813 QString filter = createFilterExpression();
814 if ( filter.isEmpty() )
818 pushSelectedFeaturesMessage();
823void QgsAttributeForm::searchSetSelection()
828void QgsAttributeForm::searchAddToSelection()
833void QgsAttributeForm::searchRemoveFromSelection()
838void QgsAttributeForm::searchIntersectSelection()
843bool QgsAttributeForm::saveMultiEdits()
847 const QList<int> fieldIndexes = mFormEditorWidgets.uniqueKeys();
848 for (
int fieldIndex : fieldIndexes )
850 const QList<QgsAttributeFormEditorWidget *> widgets = mFormEditorWidgets.values( fieldIndex );
851 if ( !widgets.first()->hasChanged() )
854 if ( !widgets.first()->currentValue().isValid()
855 || !fieldIsEditable( fieldIndex ) )
861 for ( QgsAttributeFormEditorWidget *widget : widgets )
862 widget->changesCommitted();
864 newAttributeValues.insert( fieldIndex, widgets.first()->currentValue() );
867 if ( newAttributeValues.isEmpty() )
875 int res = QMessageBox::information(
this, tr(
"Multiedit Attributes" ),
876 tr(
"Edits will be applied to all selected features." ), QMessageBox::Ok | QMessageBox::Cancel );
877 if ( res != QMessageBox::Ok )
884 mLayer->beginEditCommand( tr(
"Updated multiple feature attributes" ) );
888 const auto constMultiEditFeatureIds = mMultiEditFeatureIds;
891 QgsAttributeMap::const_iterator aIt = newAttributeValues.constBegin();
892 for ( ; aIt != newAttributeValues.constEnd(); ++aIt )
894 success &= mLayer->changeAttributeValue( fid, aIt.key(), aIt.value() );
898 clearMultiEditMessages();
901 mLayer->endEditCommand();
902 mLayer->triggerRepaint();
903 mMultiEditMessageBarItem =
new QgsMessageBarItem( tr(
"Attribute changes for multiple features applied." ),
Qgis::MessageLevel::Success, -1 );
907 mLayer->destroyEditCommand();
911 if ( !mButtonBox->isVisible() )
912 mMessageBar->pushItem( mMultiEditMessageBarItem );
938 wrapper->notifyAboutToSave();
981 success = saveEdits( error );
985 success = saveMultiEdits();
993 mUnsavedMultiEditChanges =
false;
1002 mValuesInitialized =
false;
1003 const auto constMWidgets = mWidgets;
1006 ww->setFeature( mFeature );
1017 mAlreadyUpdatedFields.append( eww->
fieldIdx() );
1018 updateValuesDependenciesVirtualFields( eww->
fieldIdx() );
1019 mAlreadyUpdatedFields.removeAll( eww->
fieldIdx() );
1022 mValuesInitialized =
true;
1028 const auto widgets { findChildren<QgsAttributeFormEditorWidget *>() };
1035void QgsAttributeForm::clearMultiEditMessages()
1037 if ( mMultiEditUnsavedMessageBarItem )
1039 if ( !mButtonBox->isVisible() )
1040 mMessageBar->
popWidget( mMultiEditUnsavedMessageBarItem );
1041 mMultiEditUnsavedMessageBarItem =
nullptr;
1043 if ( mMultiEditMessageBarItem )
1045 if ( !mButtonBox->isVisible() )
1046 mMessageBar->popWidget( mMultiEditMessageBarItem );
1047 mMultiEditMessageBarItem =
nullptr;
1051QString QgsAttributeForm::createFilterExpression()
const
1053 QStringList filters;
1054 for ( QgsAttributeFormWidget *w : std::as_const( mFormWidgets ) )
1056 QString filter = w->currentFilterExpression();
1057 if ( !filter.isEmpty() )
1061 if ( filters.isEmpty() )
1064 QString filter = filters.join(
") AND ("_L1 ).prepend(
'(' ).append(
')' );
1070 QgsExpressionContext context;
1073 if ( mExtraContextScope )
1075 context.
appendScope(
new QgsExpressionContextScope( *mExtraContextScope.get() ) );
1077 if ( mContext.parentFormFeature().isValid() )
1086void QgsAttributeForm::onAttributeChanged(
const QVariant &value,
const QVariantList &additionalFieldValues )
1088 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( sender() );
1091 bool signalEmitted =
false;
1093 if ( mValuesInitialized )
1096 mCurrentFormFeature.setAttribute( eww->
field().
name(), value );
1100 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1101 for ( QgsAttributeFormEditorWidget *formEditorWidget : std::as_const( formEditorWidgets ) )
1103 if ( formEditorWidget->editorWidget() == eww )
1107 formEditorWidget->editorWidget()->setValue( value );
1125 for (
int i = 0; i < additionalFields.count(); i++ )
1127 const QString fieldName = additionalFields.at( i );
1128 const QVariant value = additionalFieldValues.at( i );
1132 signalEmitted =
true;
1134 if ( mValuesInitialized )
1135 updateJoinedFields( *eww );
1141 if ( !mIsSettingMultiEditFeatures )
1143 mUnsavedMultiEditChanges =
true;
1145 QLabel *msgLabel =
new QLabel( tr(
"Unsaved multiedit changes: <a href=\"#apply\">apply changes</a> or <a href=\"#reset\">reset changes</a>." ), mMessageBar );
1146 msgLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
1147 msgLabel->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1148 connect( msgLabel, &QLabel::linkActivated,
this, &QgsAttributeForm::multiEditMessageClicked );
1149 clearMultiEditMessages();
1152 if ( !mButtonBox->isVisible() )
1153 mMessageBar->pushItem( mMultiEditUnsavedMessageBarItem );
1156 signalEmitted =
true;
1166 updateConstraints( eww );
1169 if ( mValuesInitialized && !mIsSettingMultiEditFeatures )
1172 mAlreadyUpdatedFields.append( eww->
fieldIdx() );
1173 updateValuesDependencies( eww->
fieldIdx() );
1174 mAlreadyUpdatedFields.removeAll( eww->
fieldIdx() );
1179 updateEditableState();
1181 if ( !signalEmitted )
1186 bool attributeHasChanged = !mIsSettingFeature;
1188 attributeHasChanged &= !mIsSettingMultiEditFeatures;
1194void QgsAttributeForm::updateAllConstraints()
1196 const auto constMWidgets = mWidgets;
1197 for ( QgsWidgetWrapper *ww : constMWidgets )
1199 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1201 updateConstraints( eww );
1209 if ( currentFormValuesFeature( ft ) )
1221 updateConstraint( ft, eww );
1224 const QList<QgsEditorWidgetWrapper *> deps = constraintDependencies( eww );
1226 for ( QgsEditorWidgetWrapper *depsEww : deps )
1227 updateConstraint( ft, depsEww );
1232 QgsExpressionContext context = createExpressionContext( ft );
1235 const QVector<ContainerInformation *> infos = mContainerInformationDependency.value( eww->
field().
name() );
1236 for ( ContainerInformation *info : infos )
1238 info->apply( &context );
1243void QgsAttributeForm::updateContainersVisibility()
1245 QgsExpressionContext context = createExpressionContext( mFeature );
1247 const QVector<ContainerInformation *> infos = mContainerVisibilityCollapsedInformation;
1249 for ( ContainerInformation *info : infos )
1251 info->apply( &context );
1261 updateAllConstraints();
1276 if ( mJoinedFeatures.contains( info ) )
1292void QgsAttributeForm::updateLabels()
1294 if ( !mLabelDataDefinedProperties.isEmpty() )
1296 QgsFeature currentFeature;
1297 if ( currentFormValuesFeature( currentFeature ) )
1299 QgsExpressionContext context = createExpressionContext( currentFeature );
1301 for (
auto it = mLabelDataDefinedProperties.constBegin(); it != mLabelDataDefinedProperties.constEnd(); ++it )
1303 QLabel *label { it.key() };
1305 const QString value { it->valueAsString( context, QString(), &ok ) };
1306 if ( ok && !value.isEmpty() )
1308 label->setText( value );
1313 if ( !mCustomCommentDataDefinedProperties.isEmpty() )
1315 QgsFeature currentFeature;
1316 if ( currentFormValuesFeature( currentFeature ) )
1318 QgsExpressionContext context = createExpressionContext( currentFeature );
1320 for (
auto it = mCustomCommentDataDefinedProperties.constBegin(); it != mCustomCommentDataDefinedProperties.constEnd(); ++it )
1322 QWidget *labelWidget { it.key() };
1324 const QString value { it->valueAsString( context, QString(), &ok ) };
1325 if ( ok && !value.isEmpty() )
1327 const QString fieldName = labelWidget->objectName();
1328 const QgsFields fields = mLayer->fields();
1341void QgsAttributeForm::updateEditableState()
1343 if ( !mEditableDataDefinedProperties.isEmpty() )
1345 QgsFeature currentFeature;
1346 if ( currentFormValuesFeature( currentFeature ) )
1348 QgsExpressionContext context = createExpressionContext( currentFeature );
1350 for (
auto it = mEditableDataDefinedProperties.constBegin(); it != mEditableDataDefinedProperties.constEnd(); ++it )
1352 QWidget *w { it.key() };
1354 const bool isEditable { it->valueAsBool( context,
true, &ok ) && mLayer && mLayer->isEditable() };
1357 QgsAttributeFormEditorWidget *editorWidget { qobject_cast<QgsAttributeFormEditorWidget *>( w ) };
1364 w->setEnabled( isEditable );
1372bool QgsAttributeForm::currentFormValuesFeature(
QgsFeature &feature )
1375 feature = QgsFeature( mFeature );
1376 QgsAttributes dst =
feature.attributes();
1378 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1380 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1385 if ( dst.count() > eww->
fieldIdx() )
1387 QVariantList dstVars = QVariantList() << dst.at( eww->
fieldIdx() );
1388 QVariantList srcVars = QVariantList() << eww->
value();
1389 QList<int> fieldIndexes = QList<int>() << eww->
fieldIdx();
1393 for (
const QString &fieldName : additionalFields )
1396 fieldIndexes << idx;
1397 dstVars << dst.at( idx );
1401 Q_ASSERT( dstVars.count() == srcVars.count() );
1403 for (
int i = 0; i < dstVars.count(); i++ )
1409 dst[fieldIndexes[i]] = srcVars[i];
1426void QgsAttributeForm::registerContainerInformation( QgsAttributeForm::ContainerInformation *info )
1428 mContainerVisibilityCollapsedInformation.append( info );
1432 for (
const QString &col : referencedColumns )
1434 mContainerInformationDependency[col].append( info );
1438bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields, QStringList &descriptions )
const
1440 bool valid {
true };
1442 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1444 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1462bool QgsAttributeForm::currentFormValidHardConstraints( QStringList &invalidFields, QStringList &descriptions )
const
1464 bool valid {
true };
1466 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1468 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1483void QgsAttributeForm::onAttributeAdded(
int idx )
1485 mPreventFeatureRefresh =
false;
1486 if ( mFeature.isValid() )
1488 QgsAttributes attrs = mFeature.attributes();
1490 mFeature.setFields(
layer()->fields() );
1491 mFeature.setAttributes( attrs );
1497void QgsAttributeForm::onAttributeDeleted(
int idx )
1499 mPreventFeatureRefresh =
false;
1500 if ( mFeature.isValid() )
1502 QgsAttributes attrs = mFeature.attributes();
1503 attrs.remove( idx );
1504 mFeature.setFields(
layer()->fields() );
1505 mFeature.setAttributes( attrs );
1511void QgsAttributeForm::onRelatedFeaturesChanged()
1513 updateRelatedLayerFields();
1516void QgsAttributeForm::onUpdatedFields()
1518 mPreventFeatureRefresh =
false;
1519 if ( mFeature.isValid() )
1521 QgsAttributes attrs(
layer()->fields().size() );
1524 int idx = mFeature.fields().indexFromName(
layer()->fields().at( i ).name() );
1527 attrs[i] = mFeature.attributes().at( idx );
1528 if ( mFeature.attributes().at( idx ).userType() !=
layer()->fields().at( i ).type() )
1530 attrs[i].convert(
layer()->fields().at( i ).type() );
1538 mFeature.setFields(
layer()->fields() );
1539 mFeature.setAttributes( attrs );
1547 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( sender() );
1550 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1552 for ( QgsAttributeFormEditorWidget *formEditorWidget : formEditorWidgets )
1554 formEditorWidget->setConstraintStatus( constraint, description, err, result );
1555 if ( formEditorWidget->editorWidget() != eww )
1557 formEditorWidget->editorWidget()->updateConstraint( result, err );
1564 QList<QgsEditorWidgetWrapper *> wDeps;
1568 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1571 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1576 if ( name != ewwName )
1583 for (
const QString &colName : referencedColumns )
1585 if ( name.compare( colName, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
1587 wDeps.append( eww );
1600 return setupRelationWidgetWrapper( QString(), rel, context );
1605 QgsRelationWidgetWrapper *rww =
new QgsRelationWidgetWrapper( relationWidgetTypeId, mLayer, rel,
nullptr,
this );
1606 const QVariantMap config = mLayer->editFormConfig().widgetConfig( rel.
id() );
1613QToolButton *QgsAttributeForm::createCommentInfoButton( QWidget *labelWidget )
1615 QToolButton *infoButton =
new QToolButton( labelWidget );
1616 infoButton->setIcon(
QgsApplication::getThemeIcon( u
"/mIndicatorFieldComment.svg"_s, QgsApplication::palette().color( QPalette::WindowText ) ) );
1617 infoButton->setFocusPolicy( Qt::NoFocus );
1619 infoButton->setAutoRaise(
true );
1620 infoButton->setStyleSheet(
1623 " background-color: transparent; "
1628 connect( infoButton, &QToolButton::clicked, labelWidget, [labelWidget]() { QToolTip::showText( QCursor::pos(), labelWidget->toolTip(), labelWidget ); } );
1630 infoButton->setVisible(
false );
1634void QgsAttributeForm::preventFeatureRefresh()
1636 mPreventFeatureRefresh =
true;
1641 if ( mPreventFeatureRefresh || mLayer->isEditable() || !mFeature.isValid() )
1646 if ( !mLayer->getFeatures(
QgsFeatureRequest().setFilterFid( mFeature.id() ) ).nextFeature( mFeature ) )
1655 if ( mContext.parentFormFeature().isValid() )
1657 QgsFeature parentFormFeature = mContext.parentFormFeature();
1659 mContext.setParentFormFeature( parentFormFeature );
1662 updateValuesDependenciesParent();
1676 return mNeedsGeometry;
1679void QgsAttributeForm::synchronizeState()
1681 bool isEditable =
false;
1702 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1704 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1707 const QList<QgsAttributeFormEditorWidget *> formWidgets = mFormEditorWidgets.values( eww->
fieldIdx() );
1709 for ( QgsAttributeFormEditorWidget *formWidget : formWidgets )
1711 formWidget->setConstraintResultVisible( isEditable );
1716 bool enabled = isEditable && fieldIsEditable( eww->
fieldIdx() );
1717 ww->setEnabled( enabled );
1723 ww->setEnabled( isEditable );
1733 if ( mConstraintsFailMessageBarItem )
1735 mMessageBar->popWidget( mConstraintsFailMessageBarItem );
1737 mConstraintsFailMessageBarItem =
new QgsMessageBarItem( tr(
"Multi edit mode requires at least one selected feature." ),
Qgis::MessageLevel::Info, -1 );
1738 mMessageBar->pushItem( mConstraintsFailMessageBarItem );
1742 QStringList invalidFields, descriptions;
1743 mValidConstraints = currentFormValidHardConstraints( invalidFields, descriptions );
1747 if ( !mValidConstraints && !mConstraintsFailMessageBarItem )
1749 mConstraintsFailMessageBarItem
1750 =
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 );
1751 mMessageBar->pushItem( mConstraintsFailMessageBarItem );
1753 else if ( mValidConstraints && mConstraintsFailMessageBarItem )
1755 mMessageBar->popWidget( mConstraintsFailMessageBarItem );
1756 mConstraintsFailMessageBarItem =
nullptr;
1759 else if ( mConstraintsFailMessageBarItem )
1761 mMessageBar->popWidget( mConstraintsFailMessageBarItem );
1762 mConstraintsFailMessageBarItem =
nullptr;
1765 isEditable = isEditable & mValidConstraints;
1770 QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
1773 okButton->setEnabled( isEditable );
1777void QgsAttributeForm::init()
1779 QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
1782 QWidget *formWidget =
nullptr;
1783 mNeedsGeometry =
false;
1785 bool buttonBoxVisible =
true;
1789 buttonBoxVisible = mButtonBox->isVisible();
1791 mButtonBox =
nullptr;
1794 if ( mSearchButtonBox )
1796 delete mSearchButtonBox;
1797 mSearchButtonBox =
nullptr;
1800 qDeleteAll( mWidgets );
1803 while ( QWidget *w = this->findChild<QWidget *>() )
1809 QVBoxLayout *vl =
new QVBoxLayout();
1810 vl->setContentsMargins( 0, 0, 0, 0 );
1811 mMessageBar =
new QgsMessageBar(
this );
1812 mMessageBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1813 vl->addWidget( mMessageBar );
1818 QGridLayout *layout =
new QGridLayout();
1819 QWidget *container =
new QWidget();
1820 container->setLayout( layout );
1821 vl->addWidget( container );
1823 mFormEditorWidgets.clear();
1824 mFormWidgets.clear();
1827 setContentsMargins( 0, 0, 0, 0 );
1832 QgsDebugMsgLevel( u
"loading form: %1"_s.arg( mLayer->editFormConfig().uiForm() ), 2 );
1833 const QString path = mLayer->editFormConfig().uiForm();
1835 if ( file && file->open( QFile::ReadOnly ) )
1839 QFileInfo fi( file->fileName() );
1840 loader.setWorkingDirectory( fi.dir() );
1841 formWidget = loader.load( file,
this );
1844 formWidget->setWindowFlags( Qt::Widget );
1845 layout->addWidget( formWidget );
1848 mButtonBox = findChild<QDialogButtonBox *>();
1851 formWidget->installEventFilter(
this );
1856 QgsTabWidget *tabWidget =
nullptr;
1863 int columnCount = 1;
1864 bool hasRootFields =
false;
1865 bool addSpacer =
true;
1867 const QList<QgsAttributeEditorElement *> tabs = mLayer->editFormConfig().tabs();
1869 for ( QgsAttributeEditorElement *widgDef : tabs )
1873 QgsAttributeEditorContainer *containerDef =
dynamic_cast<QgsAttributeEditorContainer *
>( widgDef );
1874 if ( !containerDef )
1877 switch ( containerDef->
type() )
1881 tabWidget =
nullptr;
1882 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1883 if ( widgetInfo.labelStyle.overrideColor )
1885 if ( widgetInfo.labelStyle.color.isValid() )
1887 widgetInfo.widget->setStyleSheet( u
"QGroupBox::title { color: %1; }"_s.arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1890 if ( widgetInfo.labelStyle.overrideFont )
1892 widgetInfo.widget->setFont( widgetInfo.labelStyle.font );
1895 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1896 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1898 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1900 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1902 layout->setRowStretch( row, widgDef->verticalStretch() );
1907 if ( widgetInfo.expandingNeeded )
1910 widgetInfo.widget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
1914 widgetInfo.widget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Maximum );
1920 registerContainerInformation(
new ContainerInformation(
1933 tabWidget =
nullptr;
1934 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1935 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1936 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1938 layout->setRowStretch( row, widgDef->verticalStretch() );
1943 if ( widgetInfo.expandingNeeded )
1946 widgetInfo.widget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
1950 widgetInfo.widget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Maximum );
1953 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1955 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1960 registerContainerInformation(
new ContainerInformation(
1975 tabWidget =
new QgsTabWidget();
1976 layout->addWidget( tabWidget, row, column, 1, 2 );
1980 QWidget *tabPage =
new QWidget( tabWidget );
1981 tabWidget->addTab( tabPage, widgDef->name() );
1982 tabWidget->
setTabStyle( tabWidget->tabBar()->count() - 1, widgDef->labelStyle() );
1986 registerContainerInformation(
new ContainerInformation( tabWidget, tabPage, containerDef->
visibilityExpression().
data() ) );
1988 QGridLayout *tabPageLayout =
new QGridLayout();
1989 tabPage->setLayout( tabPageLayout );
1991 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, tabPage, mLayer, mContext );
1992 if ( widgetInfo.expandingNeeded )
1995 widgetInfo.widget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
1997 tabPageLayout->addWidget( widgetInfo.widget );
2004 hasRootFields =
true;
2005 tabWidget =
nullptr;
2006 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
2007 QgsCollapsibleGroupBox *collapsibleGroupBox =
new QgsCollapsibleGroupBox();
2009 if ( widgetInfo.showLabel )
2011 if ( widgetInfo.labelStyle.overrideColor && widgetInfo.labelStyle.color.isValid() )
2013 collapsibleGroupBox->
setStyleSheet( u
"QGroupBox::title { color: %1; }"_s.arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2016 if ( widgetInfo.labelStyle.overrideFont )
2018 collapsibleGroupBox->setFont( widgetInfo.labelStyle.font );
2021 collapsibleGroupBox->setTitle( widgetInfo.labelText );
2024 QVBoxLayout *collapsibleGroupBoxLayout =
new QVBoxLayout();
2025 collapsibleGroupBoxLayout->addWidget( widgetInfo.widget );
2026 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
2028 QVBoxLayout *
c =
new QVBoxLayout();
2029 c->addWidget( collapsibleGroupBox );
2030 layout->addLayout(
c, row, column, 1, 2 );
2032 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
2033 layout->setRowStretch( row, widgDef->verticalStretch() );
2034 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
2035 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
2044 hasRootFields =
true;
2045 tabWidget =
nullptr;
2046 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
2047 QWidget *labelWidget =
new QWidget();
2048 QLabel *label =
new QLabel( widgetInfo.labelText, labelWidget );
2050 if ( widgetInfo.labelStyle.overrideColor )
2052 if ( widgetInfo.labelStyle.color.isValid() )
2054 label->setStyleSheet( u
"QLabel { color: %1; }"_s.arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2058 if ( widgetInfo.labelStyle.overrideFont )
2060 label->setFont( widgetInfo.labelStyle.font );
2063 labelWidget->setToolTip( widgetInfo.toolTip );
2064 if ( columnCount > 1 && !widgetInfo.labelOnTop )
2066 label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
2069 label->setBuddy( widgetInfo.widget );
2072 QToolButton *commentInfoButton = createCommentInfoButton( labelWidget );
2074 if ( !widgetInfo.hint.isEmpty() )
2075 commentInfoButton->setVisible(
true );
2078 if ( widgetInfo.widget
2079 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
2080 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
2081 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
2084 QHBoxLayout *labelLayout =
new QHBoxLayout( labelWidget );
2085 labelLayout->addWidget( label );
2086 labelLayout->addWidget( commentInfoButton );
2087 labelLayout->setContentsMargins( 0, 0, 0, 0 );
2089 if ( !widgetInfo.showLabel )
2091 QVBoxLayout *
c =
new QVBoxLayout();
2092 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
2093 c->addWidget( widgetInfo.widget );
2094 layout->addLayout(
c, row, column, 1, 2 );
2096 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
2098 layout->setRowStretch( row, widgDef->verticalStretch() );
2101 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
2103 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
2108 else if ( widgetInfo.labelOnTop )
2110 QVBoxLayout *
c =
new QVBoxLayout();
2111 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
2112 c->addWidget( labelWidget );
2113 c->addWidget( widgetInfo.widget );
2114 layout->addLayout(
c, row, column, 1, 2 );
2116 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
2118 layout->setRowStretch( row, widgDef->verticalStretch() );
2121 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
2123 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
2130 const int widgetColumn = column + 1;
2131 layout->addWidget( labelWidget, row, column++ );
2132 layout->addWidget( widgetInfo.widget, row, column++ );
2134 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
2136 layout->setRowStretch( row, widgDef->verticalStretch() );
2139 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( widgetColumn ) )
2141 layout->setColumnStretch( widgetColumn, widgDef->horizontalStretch() );
2148 const QgsAttributeEditorField *fieldElement {
static_cast<QgsAttributeEditorField *
>( widgDef ) };
2149 const int fieldIdx = fieldElement->
idx();
2150 if ( fieldIdx >= 0 && fieldIdx < mLayer->fields().count() )
2152 const QString fieldName { mLayer->fields().at( fieldIdx ).name() };
2153 label->setObjectName( fieldName );
2158 if ( property.isActive() )
2160 mLabelDataDefinedProperties[label] = property;
2163 labelWidget->setObjectName( fieldName );
2167 if ( property.isActive() )
2169 mCustomCommentDataDefinedProperties[labelWidget] = property;
2170 commentInfoButton->setVisible(
true );
2176 if ( property.isActive() )
2178 mEditableDataDefinedProperties[widgetInfo.widget] = property;
2185 if ( column >= columnCount * 2 )
2192 if ( hasRootFields && addSpacer )
2194 QSpacerItem *spacerItem =
new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
2195 layout->addItem( spacerItem, row, 0 );
2196 layout->setRowStretch( row, 1 );
2199 formWidget = container;
2208 formWidget =
new QWidget(
this );
2209 QGridLayout *gridLayout =
new QGridLayout( formWidget );
2210 formWidget->setLayout( gridLayout );
2215 QgsScrollArea *scrollArea =
new QgsScrollArea(
this );
2216 scrollArea->setWidget( formWidget );
2217 scrollArea->setWidgetResizable(
true );
2218 scrollArea->setFrameShape( QFrame::NoFrame );
2219 scrollArea->setFrameShadow( QFrame::Plain );
2220 scrollArea->setFocusProxy(
this );
2221 layout->addWidget( scrollArea );
2225 layout->addWidget( formWidget );
2230 const QgsFields fields = mLayer->fields();
2235 QSet<int> compositeHiddenFields;
2237 for (
const QgsRelation &rel : referencingRelations )
2242 const QList<QgsRelation::FieldPair> fieldPairs = rel.
fieldPairs();
2243 if ( fieldPairs.size() > 1 )
2245 for (
int i = 1; i < fieldPairs.size(); i++ )
2247 const int idx = fields.
lookupField( fieldPairs.at( i ).referencingField() );
2249 compositeHiddenFields.insert( idx );
2254 for (
const QgsField &field : fields )
2256 const QString fieldName = field.name();
2257 int idx = fields.lookupField( fieldName );
2261 if ( compositeHiddenFields.contains( idx ) )
2265 QString labelText = mLayer->attributeDisplayName( idx );
2266 labelText.replace(
'&',
"&&"_L1 );
2270 if ( widgetSetup.
type() ==
"Hidden"_L1 )
2273 bool labelOnTop = mLayer->editFormConfig().labelOnTop( idx );
2276 QWidget *labelWidget =
new QWidget();
2277 QLabel *label =
new QLabel( labelText, labelWidget );
2278 label->setObjectName( fieldName );
2283 QToolButton *commentInfoButton = createCommentInfoButton( labelWidget );
2285 if ( !field.customComment().isEmpty() || ( field.customComment().isNull() && !field.comment().isEmpty() ) )
2286 commentInfoButton->setVisible(
true );
2288 QSvgWidget *i =
new QSvgWidget();
2289 i->setFixedSize( 18, 18 );
2294 if ( property.isActive() )
2296 mLabelDataDefinedProperties[label] = property;
2299 labelWidget->setObjectName( fieldName );
2303 if ( property.isActive() )
2305 mCustomCommentDataDefinedProperties[labelWidget] = property;
2306 commentInfoButton->setVisible(
true );
2312 QWidget *w =
nullptr;
2315 QgsAttributeFormEditorWidget *formWidget =
new QgsAttributeFormEditorWidget( eww, widgetSetup.
type(),
this );
2317 mFormEditorWidgets.insert( idx, formWidget );
2318 mFormWidgets.append( formWidget );
2324 const QVariant rememberLastUsedValuesVariant = mLayer->property(
"AttributeFormRememberLastUsedValues" );
2325 QMap<int, bool> rememberLastUsedValues = rememberLastUsedValuesVariant.value<QMap<int, bool>>();
2326 if ( rememberLastUsedValues.contains( idx ) )
2328 remember = rememberLastUsedValues[idx];
2333 const QVariant rememberLastUsedValuesVariant = mLayer->property(
"AttributeFormRememberLastUsedValues" );
2334 QMap<int, bool> rememberLastUsedValues = rememberLastUsedValuesVariant.value<QMap<int, bool>>();
2335 rememberLastUsedValues[idx] = remember;
2336 mLayer->setProperty(
"AttributeFormRememberLastUsedValues", QVariant::fromValue<QMap<int, bool>>( rememberLastUsedValues ) );
2342 label->setBuddy( eww->
widget() );
2347 if ( property.isActive() )
2349 mEditableDataDefinedProperties[formWidget] = property;
2355 w =
new QLabel( u
"<p style=\"color: red; font-style: italic;\">%1</p>"_s.arg( tr(
"Failed to create widget with type '%1'" ).arg( widgetSetup.
type() ) ) );
2360 w->setObjectName( field.name() );
2364 mWidgets.append( eww );
2365 mIconMap[eww->
widget()] = i;
2368 QHBoxLayout *labelLayout =
new QHBoxLayout( labelWidget );
2369 labelLayout->addWidget( label );
2370 labelLayout->addWidget( commentInfoButton );
2371 labelLayout->setContentsMargins( 0, 0, 0, 0 );
2375 gridLayout->addWidget( labelWidget, row++, 0, 1, 2 );
2376 gridLayout->addWidget( w, row++, 0, 1, 2 );
2377 gridLayout->addWidget( i, row++, 0, 1, 2 );
2381 int widgetColumn = 0;
2382 gridLayout->addWidget( labelWidget, row, widgetColumn++ );
2383 gridLayout->addWidget( w, row, widgetColumn++ );
2384 gridLayout->addWidget( i, row++, widgetColumn++ );
2389 for (
const QgsRelation &rel : relations )
2391 QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( u
"relation_editor"_s, rel, mContext );
2393 QgsAttributeFormRelationEditorWidget *formWidget =
new QgsAttributeFormRelationEditorWidget( rww,
this );
2396 QgsCollapsibleGroupBox *collapsibleGroupBox =
new QgsCollapsibleGroupBox( rel.
name() );
2397 QVBoxLayout *collapsibleGroupBoxLayout =
new QVBoxLayout();
2398 collapsibleGroupBoxLayout->addWidget( formWidget );
2399 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
2401 gridLayout->addWidget( collapsibleGroupBox, row++, 0, 1, 2 );
2403 mWidgets.append( rww );
2404 mFormWidgets.append( formWidget );
2409 QSpacerItem *spacerItem =
new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
2410 gridLayout->addItem( spacerItem, row, 0 );
2411 gridLayout->setRowStretch( row, 1 );
2417 updateFieldDependencies();
2421 mButtonBox =
new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
2422 mButtonBox->setObjectName( u
"buttonBox"_s );
2423 layout->addWidget( mButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2425 mButtonBox->setVisible( buttonBoxVisible );
2427 if ( !mSearchButtonBox )
2429 mSearchButtonBox =
new QWidget();
2430 QHBoxLayout *boxLayout =
new QHBoxLayout();
2431 boxLayout->setContentsMargins( 0, 0, 0, 0 );
2432 mSearchButtonBox->setLayout( boxLayout );
2433 mSearchButtonBox->setObjectName( u
"searchButtonBox"_s );
2435 QPushButton *clearButton =
new QPushButton( tr(
"&Reset Form" ), mSearchButtonBox );
2437 boxLayout->addWidget( clearButton );
2438 boxLayout->addStretch( 1 );
2440 QPushButton *flashButton =
new QPushButton();
2441 flashButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2442 flashButton->setText( tr(
"&Flash Features" ) );
2443 connect( flashButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchFlash );
2444 boxLayout->addWidget( flashButton );
2446 QPushButton *openAttributeTableButton =
new QPushButton();
2447 openAttributeTableButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2448 openAttributeTableButton->setText( tr(
"Show in &Table" ) );
2449 openAttributeTableButton->setToolTip( tr(
"Open the attribute table editor with the filtered features" ) );
2451 boxLayout->addWidget( openAttributeTableButton );
2453 QPushButton *zoomButton =
new QPushButton();
2454 zoomButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2455 zoomButton->setText( tr(
"&Zoom to Features" ) );
2456 connect( zoomButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchZoomTo );
2457 boxLayout->addWidget( zoomButton );
2459 QToolButton *selectButton =
new QToolButton();
2460 selectButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2461 selectButton->setText( tr(
"&Select Features" ) );
2463 selectButton->setPopupMode( QToolButton::MenuButtonPopup );
2464 selectButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
2465 connect( selectButton, &QToolButton::clicked,
this, &QgsAttributeForm::searchSetSelection );
2466 QMenu *selectMenu =
new QMenu( selectButton );
2467 QAction *selectAction =
new QAction( tr(
"Select Features" ), selectMenu );
2469 connect( selectAction, &QAction::triggered,
this, &QgsAttributeForm::searchSetSelection );
2470 selectMenu->addAction( selectAction );
2471 QAction *addSelectAction =
new QAction( tr(
"Add to Current Selection" ), selectMenu );
2473 connect( addSelectAction, &QAction::triggered,
this, &QgsAttributeForm::searchAddToSelection );
2474 selectMenu->addAction( addSelectAction );
2475 QAction *deselectAction =
new QAction( tr(
"Remove from Current Selection" ), selectMenu );
2477 connect( deselectAction, &QAction::triggered,
this, &QgsAttributeForm::searchRemoveFromSelection );
2478 selectMenu->addAction( deselectAction );
2479 QAction *filterSelectAction =
new QAction( tr(
"Filter Current Selection" ), selectMenu );
2481 connect( filterSelectAction, &QAction::triggered,
this, &QgsAttributeForm::searchIntersectSelection );
2482 selectMenu->addAction( filterSelectAction );
2483 selectButton->setMenu( selectMenu );
2484 boxLayout->addWidget( selectButton );
2488 QToolButton *filterButton =
new QToolButton();
2489 filterButton->setText( tr(
"Filter Features" ) );
2490 filterButton->setPopupMode( QToolButton::MenuButtonPopup );
2491 filterButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2492 connect( filterButton, &QToolButton::clicked,
this, &QgsAttributeForm::filterTriggered );
2493 QMenu *filterMenu =
new QMenu( filterButton );
2494 QAction *filterAndAction =
new QAction( tr(
"Filter Within (\"AND\")" ), filterMenu );
2495 connect( filterAndAction, &QAction::triggered,
this, &QgsAttributeForm::filterAndTriggered );
2496 filterMenu->addAction( filterAndAction );
2497 QAction *filterOrAction =
new QAction( tr(
"Extend Filter (\"OR\")" ), filterMenu );
2498 connect( filterOrAction, &QAction::triggered,
this, &QgsAttributeForm::filterOrTriggered );
2499 filterMenu->addAction( filterOrAction );
2500 filterButton->setMenu( filterMenu );
2501 boxLayout->addWidget( filterButton );
2505 QPushButton *closeButton =
new QPushButton( tr(
"Close" ), mSearchButtonBox );
2507 closeButton->setShortcut( Qt::Key_Escape );
2508 boxLayout->addWidget( closeButton );
2511 layout->addWidget( mSearchButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2529 const auto constMInterfaces = mInterfaces;
2530 for ( QgsAttributeFormInterface *iface : constMInterfaces )
2540 QApplication::restoreOverrideCursor();
2543void QgsAttributeForm::cleanPython()
2545 if ( !mPyFormVarName.isNull() )
2547 QString expr = u
"if '%1' in locals(): del %1\n"_s.arg( mPyFormVarName );
2552void QgsAttributeForm::initPython()
2563 mMessageBar->pushMessage( tr(
"Security warning" ), tr(
"The attribute form contains an embedded script which has been denied execution." ),
Qgis::MessageLevel::Warning );
2567 QString initFunction = mLayer->editFormConfig().initFunction();
2568 QString initFilePath = mLayer->editFormConfig().initFilePath();
2571 switch ( mLayer->editFormConfig().initCodeSource() )
2574 if ( !initFilePath.isEmpty() )
2578 if ( inputFile && inputFile->open( QFile::ReadOnly ) )
2581 QTextStream inf( inputFile );
2582 initCode = inf.readAll();
2587 QgsLogger::warning( u
"The external python file path %1 could not be opened!"_s.arg( initFilePath ) );
2597 initCode = mLayer->editFormConfig().initCode();
2598 if ( initCode.isEmpty() )
2611 if ( !initCode.isEmpty() )
2619 mMessageBar->pushMessage( QString(), tr(
"Python macro could not be run due to missing permissions." ),
Qgis::MessageLevel::Warning );
2627 if (
QgsPythonRunner::eval( u
"len(inspect.getfullargspec(%1)[0])"_s.arg( initFunction ), numArgs ) )
2629 static int sFormId = 0;
2630 mPyFormVarName = u
"_qgis_featureform_%1_%2"_s.arg( mFormNr ).arg( sFormId++ );
2632 QString form = u
"%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )"_s.arg( mPyFormVarName ).arg( ( quint64 )
this );
2636 QgsDebugMsgLevel( u
"running featureForm init: %1"_s.arg( mPyFormVarName ), 2 );
2639 if ( numArgs ==
"3"_L1 )
2641 addInterface(
new QgsAttributeFormLegacyInterface( initFunction, mPyFormVarName,
this ) );
2648 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 )
2652 QString expr = QString(
"%1(%2)" )
2653 .arg( mLayer->editFormInit() )
2654 .arg( mPyFormVarName );
2655 QgsAttributeFormInterface *iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface *>( expr,
"QgsAttributeFormInterface" );
2665 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 ) );
2673 WidgetInfo newWidgetInfo;
2675 newWidgetInfo.labelStyle = widgetDef->
labelStyle();
2677 switch ( widgetDef->
type() )
2681 const QgsAttributeEditorAction *elementDef =
dynamic_cast<const QgsAttributeEditorAction *
>( widgetDef );
2685 QgsActionWidgetWrapper *actionWrapper =
new QgsActionWidgetWrapper( mLayer,
nullptr,
this, mMessageBar );
2689 mWidgets.append( actionWrapper );
2690 newWidgetInfo.widget = actionWrapper->
widget();
2691 newWidgetInfo.showLabel =
false;
2698 const QgsAttributeEditorField *fieldDef =
dynamic_cast<const QgsAttributeEditorField *
>( widgetDef );
2702 const QgsFields fields = vl->
fields();
2704 if ( fldIdx < fields.
count() && fldIdx >= 0 )
2709 QgsAttributeFormEditorWidget *formWidget =
new QgsAttributeFormEditorWidget( eww, widgetSetup.
type(),
this );
2710 mFormEditorWidgets.insert( fldIdx, formWidget );
2711 mFormWidgets.append( formWidget );
2717 const QVariant rememberLastUsedValuesVariant = mLayer->property(
"AttributeFormRememberLastUsedValues" );
2718 QMap<int, bool> rememberLastUsedValues = rememberLastUsedValuesVariant.value<QMap<int, bool>>();
2719 if ( rememberLastUsedValues.contains( fldIdx ) )
2721 remember = rememberLastUsedValues[fldIdx];
2725 const QVariant rememberLastUsedValuesVariant = mLayer->property(
"AttributeFormRememberLastUsedValues" );
2726 QMap<int, bool> rememberLastUsedValues = rememberLastUsedValuesVariant.value<QMap<int, bool>>();
2727 rememberLastUsedValues[idx] = remember;
2728 mLayer->setProperty(
"AttributeFormRememberLastUsedValues", QVariant::fromValue<QMap<int, bool>>( rememberLastUsedValues ) );
2734 newWidgetInfo.widget = formWidget;
2735 mWidgets.append( eww );
2737 newWidgetInfo.widget->setObjectName( fields.
at( fldIdx ).
name() );
2739 newWidgetInfo.hint = customComment.isNull() ? fields.
at( fldIdx ).
comment() : customComment;
2742 newWidgetInfo.labelOnTop = mLayer->editFormConfig().labelOnTop( fldIdx );
2743 newWidgetInfo.labelText = mLayer->attributeDisplayName( fldIdx );
2744 newWidgetInfo.labelText.replace(
'&',
"&&"_L1 );
2747 newWidgetInfo.showLabel = widgetDef->
showLabel();
2754 const QgsAttributeEditorRelation *relDef =
static_cast<const QgsAttributeEditorRelation *
>( widgetDef );
2758 QgsAttributeFormRelationEditorWidget *formWidget =
new QgsAttributeFormRelationEditorWidget( rww,
this );
2768 mWidgets.append( rww );
2769 mFormWidgets.append( formWidget );
2771 newWidgetInfo.widget = formWidget;
2772 newWidgetInfo.showLabel = relDef->
showLabel();
2773 newWidgetInfo.labelText = relDef->
label();
2774 if ( newWidgetInfo.labelText.isEmpty() )
2776 newWidgetInfo.labelOnTop =
true;
2782 const QgsAttributeEditorContainer *container =
dynamic_cast<const QgsAttributeEditorContainer *
>( widgetDef );
2788 if ( columnCount <= 0 )
2792 QWidget *myContainer =
nullptr;
2793 bool removeLayoutMargin =
false;
2794 switch ( container->
type() )
2798 QgsCollapsibleGroupBoxBasic *groupBox =
new QgsCollapsibleGroupBoxBasic();
2799 widgetName = u
"QGroupBox"_s;
2802 groupBox->setTitle( container->
name() );
2803 if ( newWidgetInfo.labelStyle.overrideColor )
2805 if ( newWidgetInfo.labelStyle.color.isValid() )
2807 groupBox->
setStyleSheet( u
"QGroupBox::title { color: %1; }"_s.arg( newWidgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2810 if ( newWidgetInfo.labelStyle.overrideFont )
2812 groupBox->setFont( newWidgetInfo.labelStyle.font );
2815 myContainer = groupBox;
2816 newWidgetInfo.widget = myContainer;
2823 QWidget *rowWidget =
new QWidget();
2824 widgetName = u
"Row"_s;
2825 myContainer = rowWidget;
2826 newWidgetInfo.widget = myContainer;
2827 removeLayoutMargin =
true;
2828 columnCount = container->
children().size();
2834 myContainer =
new QWidget();
2836 QgsScrollArea *scrollArea =
new QgsScrollArea( parent );
2838 scrollArea->setWidget( myContainer );
2839 scrollArea->setWidgetResizable(
true );
2840 scrollArea->setFrameShape( QFrame::NoFrame );
2841 widgetName = u
"QScrollArea QWidget"_s;
2843 newWidgetInfo.widget = scrollArea;
2850 QString style { u
"background-color: %1;"_s.arg( container->
backgroundColor().name() ) };
2851 newWidgetInfo.widget->setStyleSheet( style );
2854 QGridLayout *gbLayout =
new QGridLayout();
2855 if ( removeLayoutMargin )
2856 gbLayout->setContentsMargins( 0, 0, 0, 0 );
2857 myContainer->setLayout( gbLayout );
2861 bool addSpacer =
true;
2863 const QList<QgsAttributeEditorElement *> children = container->
children();
2865 for ( QgsAttributeEditorElement *childDef : children )
2867 WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context );
2871 QgsAttributeEditorContainer *containerDef =
static_cast<QgsAttributeEditorContainer *
>( childDef );
2874 registerContainerInformation(
new ContainerInformation(
2881 if ( childDef->verticalStretch() == 0 )
2883 if ( widgetInfo.expandingNeeded )
2885 widgetInfo.widget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
2889 widgetInfo.widget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Maximum );
2895 int widgetColumn = column;
2897 if ( widgetInfo.labelText.isNull() || !widgetInfo.showLabel )
2899 gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 );
2900 widgetColumn = column + 1;
2905 QWidget *labelWidget =
new QWidget();
2906 QLabel *mypLabel =
new QLabel( widgetInfo.labelText, labelWidget );
2908 if ( widgetInfo.labelStyle.overrideColor )
2910 if ( widgetInfo.labelStyle.color.isValid() )
2912 mypLabel->setStyleSheet( u
"QLabel { color: %1; }"_s.arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2916 if ( widgetInfo.labelStyle.overrideFont )
2918 mypLabel->setFont( widgetInfo.labelStyle.font );
2922 QToolButton *commentInfoButton = createCommentInfoButton( labelWidget );
2924 if ( !widgetInfo.hint.isEmpty() )
2925 commentInfoButton->setVisible(
true );
2930 const QgsAttributeEditorField *fieldDef {
static_cast<QgsAttributeEditorField *
>( childDef ) };
2931 const QgsFields fields = vl->
fields();
2932 const int fldIdx = fieldDef->
idx();
2933 if ( fldIdx < fields.
count() && fldIdx >= 0 )
2935 const QString fieldName { fields.
at( fldIdx ).
name() };
2936 mypLabel->setObjectName( fieldName );
2941 if ( property.isActive() )
2943 mLabelDataDefinedProperties[mypLabel] = property;
2949 if ( property.isActive() )
2951 mEditableDataDefinedProperties[widgetInfo.widget] = property;
2954 labelWidget->setObjectName( fieldName );
2958 if ( property.isActive() )
2960 mCustomCommentDataDefinedProperties[labelWidget] = property;
2966 labelWidget->setToolTip( widgetInfo.toolTip );
2967 if ( columnCount > 1 && !widgetInfo.labelOnTop )
2969 mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
2972 mypLabel->setBuddy( widgetInfo.widget );
2974 QHBoxLayout *labelLayout =
new QHBoxLayout( labelWidget );
2975 labelLayout->addWidget( mypLabel );
2976 labelLayout->addWidget( commentInfoButton );
2977 labelLayout->setContentsMargins( 0, 0, 0, 0 );
2979 if ( widgetInfo.labelOnTop )
2981 widgetColumn = column + 1;
2982 QVBoxLayout *
c =
new QVBoxLayout();
2983 mypLabel->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
2984 c->addWidget( labelWidget );
2985 c->layout()->addWidget( widgetInfo.widget );
2986 gbLayout->addLayout(
c, row, column, 1, 2 );
2991 widgetColumn = column + 1;
2992 gbLayout->addWidget( labelWidget, row, column++ );
2993 gbLayout->addWidget( widgetInfo.widget, row, column++ );
2997 const int childHorizontalStretch = childDef->horizontalStretch();
2998 const int existingColumnStretch = gbLayout->columnStretch( widgetColumn );
2999 if ( childHorizontalStretch > 0 && childHorizontalStretch > existingColumnStretch )
3001 gbLayout->setColumnStretch( widgetColumn, childHorizontalStretch );
3004 if ( childDef->verticalStretch() > 0 && childDef->verticalStretch() > gbLayout->rowStretch( row ) )
3006 gbLayout->setRowStretch( row, childDef->verticalStretch() );
3009 if ( column >= columnCount * 2 )
3015 if ( widgetInfo.widget
3016 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
3017 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
3018 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
3022 if ( qobject_cast<QgsAttributeFormRelationEditorWidget *>( widgetInfo.widget ) )
3028 QWidget *spacer =
new QWidget();
3029 spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
3030 gbLayout->addWidget( spacer, ++row, 0 );
3031 gbLayout->setRowStretch( row, 1 );
3034 newWidgetInfo.labelText = QString();
3035 newWidgetInfo.labelOnTop =
true;
3036 newWidgetInfo.showLabel = widgetDef->
showLabel();
3037 newWidgetInfo.expandingNeeded |= !addSpacer;
3043 const QgsAttributeEditorQmlElement *elementDef =
static_cast<const QgsAttributeEditorQmlElement *
>( widgetDef );
3044 QgsQmlWidgetWrapper *qmlWrapper =
new QgsQmlWidgetWrapper( mLayer,
nullptr,
this );
3049 mWidgets.append( qmlWrapper );
3051 newWidgetInfo.widget = qmlWrapper->
widget();
3052 newWidgetInfo.labelText = elementDef->
name();
3053 newWidgetInfo.labelOnTop =
true;
3054 newWidgetInfo.showLabel = widgetDef->
showLabel();
3060 const QgsAttributeEditorHtmlElement *elementDef =
static_cast<const QgsAttributeEditorHtmlElement *
>( widgetDef );
3062 QgsHtmlWidgetWrapper *htmlWrapper =
new QgsHtmlWidgetWrapper( mLayer,
nullptr,
this );
3066 mWidgets.append( htmlWrapper );
3068 newWidgetInfo.widget = htmlWrapper->
widget();
3069 newWidgetInfo.labelText = elementDef->
name();
3070 newWidgetInfo.labelOnTop =
true;
3071 newWidgetInfo.showLabel = widgetDef->
showLabel();
3078 const QgsAttributeEditorTextElement *elementDef =
static_cast<const QgsAttributeEditorTextElement *
>( widgetDef );
3080 QgsTextWidgetWrapper *textWrapper =
new QgsTextWidgetWrapper( mLayer,
nullptr,
this );
3084 mWidgets.append( textWrapper );
3086 newWidgetInfo.widget = textWrapper->
widget();
3087 newWidgetInfo.labelText = elementDef->
name();
3088 newWidgetInfo.labelOnTop =
false;
3089 newWidgetInfo.showLabel = widgetDef->
showLabel();
3096 const QgsAttributeEditorSpacerElement *elementDef =
static_cast<const QgsAttributeEditorSpacerElement *
>( widgetDef );
3097 QgsSpacerWidgetWrapper *spacerWrapper =
new QgsSpacerWidgetWrapper( mLayer,
nullptr,
this );
3100 mWidgets.append( spacerWrapper );
3102 newWidgetInfo.widget = spacerWrapper->
widget();
3103 newWidgetInfo.labelOnTop =
false;
3104 newWidgetInfo.showLabel =
false;
3109 QgsDebugError( u
"Unknown attribute editor widget type encountered..."_s );
3113 return newWidgetInfo;
3116void QgsAttributeForm::createWrappers()
3118 QList<QWidget *> myWidgets = findChildren<QWidget *>();
3119 const QList<QgsField> fields = mLayer->fields().
toList();
3121 const auto constMyWidgets = myWidgets;
3122 for ( QWidget *myWidget : constMyWidgets )
3125 QVariant vRel = myWidget->property(
"qgisRelation" );
3126 if ( vRel.isValid() )
3129 QgsRelation relation = relMgr->
relation( vRel.toString() );
3132 QgsRelationWidgetWrapper *rww =
new QgsRelationWidgetWrapper( mLayer, relation, myWidget,
this );
3133 rww->
setConfig( mLayer->editFormConfig().widgetConfig( relation.
id() ) );
3136 mWidgets.append( rww );
3141 const auto constFields = fields;
3142 for (
const QgsField &field : constFields )
3144 if ( field.name() == myWidget->objectName() )
3146 int idx = mLayer->fields().lookupField( field.name() );
3149 mWidgets.append( eww );
3156void QgsAttributeForm::afterWidgetInit()
3158 bool isFirstEww =
true;
3160 const auto constMWidgets = mWidgets;
3161 for ( QgsWidgetWrapper *ww : constMWidgets )
3163 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
3169 setFocusProxy( eww->
widget() );
3178 QgsRelationWidgetWrapper *relationWidgetWrapper = qobject_cast<QgsRelationWidgetWrapper *>( ww );
3179 if ( relationWidgetWrapper )
3192 if ( e->type() == QEvent::KeyPress )
3194 QKeyEvent *keyEvent =
dynamic_cast<QKeyEvent *
>( e );
3195 if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
3206void QgsAttributeForm::scanForEqualAttributes(
QgsFeatureIterator &fit, QSet<int> &mixedValueFields, QHash<int, QVariant> &fieldSharedValues )
const
3208 mixedValueFields.clear();
3209 fieldSharedValues.clear();
3215 for (
int i = 0; i < mLayer->
fields().count(); ++i )
3217 if ( mixedValueFields.contains( i ) )
3222 fieldSharedValues[i] = f.
attribute( i );
3226 if ( fieldSharedValues.value( i ) != f.
attribute( i ) )
3228 fieldSharedValues.remove( i );
3229 mixedValueFields.insert( i );
3235 if ( mixedValueFields.count() == mLayer->fields().count() )
3244void QgsAttributeForm::layerSelectionChanged()
3258 resetMultiEdit(
true );
3265 mIsSettingMultiEditFeatures =
true;
3266 mMultiEditFeatureIds = fids;
3268 if ( fids.isEmpty() )
3271 QMultiMap<int, QgsAttributeFormEditorWidget *>::const_iterator wIt = mFormEditorWidgets.constBegin();
3272 for ( ; wIt != mFormEditorWidgets.constEnd(); ++wIt )
3274 wIt.value()->initialize( QVariant() );
3276 mIsSettingMultiEditFeatures =
false;
3283 QSet<int> mixedValueFields;
3284 QHash<int, QVariant> fieldSharedValues;
3285 scanForEqualAttributes( fit, mixedValueFields, fieldSharedValues );
3288 fit = mLayer->getFeatures(
QgsFeatureRequest().setFilterFid( *fids.constBegin() ) );
3294 if ( mCurrentFormFeature.id() != firstFeature.
id() )
3299 const auto constMixedValueFields = mixedValueFields;
3300 for (
int fieldIndex : std::as_const( mixedValueFields ) )
3302 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( fieldIndex );
3303 if ( formEditorWidgets.isEmpty() )
3306 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
3307 QVariantList additionalFieldValues;
3308 for (
const QString &additionalField : additionalFields )
3309 additionalFieldValues << firstFeature.
attribute( additionalField );
3312 w->initialize( firstFeature.
attribute( fieldIndex ),
true, additionalFieldValues );
3314 QHash<int, QVariant>::const_iterator sharedValueIt = fieldSharedValues.constBegin();
3315 for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
3317 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( sharedValueIt.key() );
3318 if ( formEditorWidgets.isEmpty() )
3322 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
3323 for (
const QString &additionalField : additionalFields )
3325 int index = mLayer->fields().indexFromName( additionalField );
3326 if ( constMixedValueFields.contains( index ) )
3333 QVariantList additionalFieldValues;
3336 for (
const QString &additionalField : additionalFields )
3337 additionalFieldValues << firstFeature.
attribute( additionalField );
3339 w->initialize( firstFeature.
attribute( sharedValueIt.key() ),
true, additionalFieldValues );
3343 for (
const QString &additionalField : additionalFields )
3345 int index = mLayer->fields().indexFromName( additionalField );
3346 Q_ASSERT( fieldSharedValues.contains( index ) );
3347 additionalFieldValues << fieldSharedValues.value( index );
3350 w->initialize( sharedValueIt.value(),
false, additionalFieldValues );
3354 setMultiEditFeatureIdsRelations( fids );
3356 mIsSettingMultiEditFeatures =
false;
3361 if ( mOwnsMessageBar )
3363 mOwnsMessageBar =
false;
3364 mMessageBar = messageBar;
3374 QStringList filters;
3377 QString filter = widget->currentFilterExpression();
3378 if ( !filter.isNull() )
3379 filters <<
'(' + filter +
')';
3382 return filters.join(
" AND "_L1 );
3387 mExtraContextScope.reset( extraScope );
3392 const bool newVisibility = expression.
evaluate( expressionContext ).toBool();
3402 widget->setVisible( newVisibility );
3405 isVisible = newVisibility;
3408 const bool newCollapsedState = collapsedExpression.evaluate( expressionContext ).toBool();
3410 if ( collapsedExpression.isValid() && !collapsedExpression.hasEvalError() && newCollapsedState != isCollapsed )
3412 if ( QgsCollapsibleGroupBoxBasic * collapsibleGroupBox { qobject_cast<QgsCollapsibleGroupBoxBasic *>( widget ) } )
3414 collapsibleGroupBox->
setCollapsed( newCollapsedState );
3415 isCollapsed = newCollapsedState;
3420void QgsAttributeForm::updateJoinedFields(
const QgsEditorWidgetWrapper &eww )
3425 QgsFeature formFeature;
3429 if ( infos.count() == 0 || !currentFormValuesFeature( formFeature ) )
3432 const QString hint = tr(
"No feature joined" );
3433 const auto constInfos = infos;
3434 for (
const QgsVectorLayerJoinInfo *info : constInfos )
3436 if ( !info->isDynamicFormEnabled() )
3439 QgsFeature joinFeature = mLayer->joinBuffer()->joinedFeatureOf( info, formFeature );
3441 mJoinedFeatures[info] = joinFeature;
3443 if ( info->hasSubset() )
3447 const auto constSubsetNames = subsetNames;
3448 for (
const QString &field : constSubsetNames )
3450 QString prefixedName = info->prefixedFieldName( field );
3452 QString hintText = hint;
3465 const QgsFields joinFields = joinFeature.
fields();
3466 for (
const QgsField &field : joinFields )
3468 QString prefixedName = info->prefixedFieldName( field );
3470 QString hintText = hint;
3484bool QgsAttributeForm::fieldIsEditable(
int fieldIndex )
const
3490void QgsAttributeForm::updateFieldDependencies()
3492 mDefaultValueDependencies.clear();
3493 mVirtualFieldsDependencies.clear();
3494 mRelatedLayerFieldsDependencies.clear();
3495 mParentDependencies.clear();
3498 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
3500 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
3504 updateFieldDependenciesParent( eww );
3505 updateFieldDependenciesDefaultValue( eww );
3506 updateFieldDependenciesVirtualFields( eww );
3507 updateRelatedLayerFieldsDependencies( eww );
3511void QgsAttributeForm::updateFieldDependenciesDefaultValue( QgsEditorWidgetWrapper *eww )
3515 if ( exp.needsGeometry() )
3516 mNeedsGeometry =
true;
3521 const QList<int> allAttributeIds( mLayer->fields().allAttributesList() );
3523 for (
const int id : allAttributeIds )
3525 mDefaultValueDependencies.insertMulti(
id, eww );
3531 const QSet<QString> referencedColumns = exp.referencedColumns();
3532 for (
const QString &referencedColumn : referencedColumns )
3534 mDefaultValueDependencies.insertMulti( mLayer->fields().lookupField( referencedColumn ), eww );
3539void QgsAttributeForm::updateFieldDependenciesVirtualFields( QgsEditorWidgetWrapper *eww )
3542 if ( expressionField.isEmpty() )
3545 QgsExpression exp( expressionField );
3547 if ( exp.needsGeometry() )
3548 mNeedsGeometry =
true;
3553 const QList<int> allAttributeIds( mLayer->fields().allAttributesList() );
3555 for (
const int id : allAttributeIds )
3557 mVirtualFieldsDependencies.insertMulti(
id, eww );
3563 const QSet<QString> referencedColumns = exp.referencedColumns();
3564 for (
const QString &referencedColumn : referencedColumns )
3566 mVirtualFieldsDependencies.insertMulti( mLayer->fields().lookupField( referencedColumn ), eww );
3571void QgsAttributeForm::updateRelatedLayerFieldsDependencies( QgsEditorWidgetWrapper *eww )
3576 if ( expressionField.contains( u
"relation_aggregate"_s ) || expressionField.contains( u
"get_features"_s ) )
3577 mRelatedLayerFieldsDependencies.insert( eww );
3581 mRelatedLayerFieldsDependencies.clear();
3583 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
3585 QgsEditorWidgetWrapper *editorWidgetWrapper = qobject_cast<QgsEditorWidgetWrapper *>( ww );
3586 if ( !editorWidgetWrapper )
3589 updateRelatedLayerFieldsDependencies( editorWidgetWrapper );
3594void QgsAttributeForm::updateFieldDependenciesParent( QgsEditorWidgetWrapper *eww )
3599 const QSet<QString> referencedVariablesAndFunctions = expression.referencedVariables() + expression.referencedFunctions();
3600 for (
const QString &referenced : referencedVariablesAndFunctions )
3602 if ( referenced.startsWith(
"current_parent"_L1 ) )
3604 mParentDependencies.insert( eww );
3611void QgsAttributeForm::setMultiEditFeatureIdsRelations(
const QgsFeatureIds &fids )
3613 for ( QgsAttributeFormWidget *formWidget : mFormWidgets )
3615 QgsAttributeFormRelationEditorWidget *relationEditorWidget =
dynamic_cast<QgsAttributeFormRelationEditorWidget *
>( formWidget );
3616 if ( !relationEditorWidget )
3623void QgsAttributeForm::updateIcon( QgsEditorWidgetWrapper *eww )
3629 mIconMap[eww->
widget()]->hide();
3631 if ( !eww->
widget()->isEnabled() && mLayer->isEditable() )
3636 const QgsVectorLayerJoinInfo *info = mLayer->joinBuffer()->joinForFieldIndex( eww->
fieldIdx(), mLayer->fields(), srcFieldIndex );
3643 const QString file = u
"/mIconJoinNotEditable.svg"_s;
3644 const QString tooltip = tr(
"Join settings do not allow editing" );
3645 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3649 const QString file = u
"mIconJoinHasNotUpsertOnEdit.svg"_s;
3650 const QString tooltip = tr(
"Join settings do not allow upsert on edit" );
3651 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3655 const QString file = u
"/mIconJoinedLayerNotEditable.svg"_s;
3656 const QString tooltip = tr(
"Joined layer is not toggled editable" );
3657 reloadIcon( file, tooltip, mIconMap[eww->
widget()] );
3663void QgsAttributeForm::reloadIcon(
const QString &file,
const QString &tooltip, QSvgWidget *sw )
3666 sw->setToolTip( tooltip );
3673 QgsDebugMsgLevel( u
"reuseAllLastValues: %1"_s.arg( reuseAllLastValues ), 2 );
3677 for (
int idx = 0; idx < fields.
count(); ++idx )
3679 if ( attributes.contains( idx ) )
3681 initialAttributeValues.insert( idx, attributes.value( idx ) );
3685 const QVariant lastUsedValuesVariant =
layer->property(
"AttributeFormLastUsedValues" );
3687 if ( lastUsedValues.contains( idx ) &&
layer->dataProvider() &&
layer->dataProvider()->defaultValueClause( idx ) != lastUsedValues[idx] )
3689 initialAttributeValues.insert( idx, lastUsedValues[idx] );
@ Trusted
The project trust has not yet been determined by the user.
@ Row
A row of editors (horizontal layout).
AttributeFormReuseLastValuePolicy
Attribute form policy for reusing last entered values.
@ AllowedDefaultOn
Reuse of last values allowed and enabled by default.
@ NotAllowed
Reuse of last values not allowed.
@ 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.
@ Normal
A normal relation.
@ 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.
const QgsAction & action(const QgsVectorLayer *layer) const
Returns the (possibly lazy loaded) action for the given layer.
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.
Contains context information for attribute editor widgets.
FormMode formMode() const
Returns the form mode.
@ Embed
A form was embedded as a widget on another form.
@ 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.
@ PreviewMode
Preview mode, for previewing attribute configurations.
void setAttributeFormMode(const Mode &attributeFormMode)
Set attributeFormMode for the edited form.
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.
int idx() const
Returns the index of the field.
QString htmlCode() const
The Html code that will be represented within this widget.
QString qmlCode() const
The QML code that will be represented within this widget.
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.
bool drawLine() const
Returns true if the spacer element will contain an horizontal line.
QString text() const
The Text that will be represented within this widget.
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.
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.
QSet< QString > referencedColumns() const
Gets list of columns referenced by the expression.
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
QVariant evaluate()
Evaluate the feature and return the result.
bool isValid() const
Checks if this expression is valid.
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.
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.
bool isValid() const
Returns the validity of this feature.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
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, const QString &predefinedComment=QString())
Returns a HTML formatted tooltip string for a field, containing details like the field name,...
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.
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 allowExecutionOfEmbeddedScripts(QgsProject *project, QgsMessageBar *messageBar=nullptr)
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.
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.
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.
static Qgis::ProjectTrustStatus checkUserTrust(QgsProject *project)
Returns the current trust status of the specified project.
QgsRelationManager * relationManager
static QgsProject * instance()
Returns the QgsProject singleton instance.
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.
QList< QgsRelation > referencedRelations(const QgsVectorLayer *layer=nullptr) const
Gets all relations where this layer is the referenced part (i.e.
QList< QgsRelation > referencingRelations(const QgsVectorLayer *layer=nullptr, int fieldIdx=-2) const
Gets all relations where the specified layer (and field) is the referencing part (i....
Q_INVOKABLE QgsRelation relation(const QString &id) const
Gets access to a relation by its id.
Represents a relationship between two vector layers.
Qgis::RelationshipType type() const
Returns the type of the relation.
QList< QgsRelation::FieldPair > fieldPairs() const
Returns the field pairs which form this relation The first element of each pair are the field names o...
static const QgsSettingsEntryBool * settingsDigitizingReuseLastValues
Settings entry digitizing reuseLastValues.
bool isInvalidJSON() const
Returns whether the text edit widget contains Invalid JSON.
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.
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, QgsVectorLayerUtils::FieldIsEditableFlags flags=QgsVectorLayerUtils::FieldIsEditableFlags())
Tests whether a field is editable for a particular feature.
static QgsFeature createFeature(const QgsVectorLayer *layer, const QgsGeometry &geometry=QgsGeometry(), const QgsAttributeMap &attributes=QgsAttributeMap(), QgsExpressionContext *context=nullptr)
Creates a new feature ready for insertion into a layer.
QFlags< FieldIsEditableFlag > FieldIsEditableFlags
@ IgnoreLayerEditability
Ignores the vector layer's editable state.
Represents a vector layer which manages a vector based dataset.
bool isEditable() const final
Returns true if the provider is in editing mode.
void beforeRemovingExpressionField(int idx)
Will be emitted, when an expression field is going to be deleted from this vector layer.
QString expressionField(int index) const
Returns the expression used for a given expression field.
Q_INVOKABLE void selectByExpression(const QString &expression, Qgis::SelectBehavior behavior=Qgis::SelectBehavior::SetSelection, QgsExpressionContext *context=nullptr)
Selects matching features using an expression.
QgsVectorLayerJoinBuffer * joinBuffer()
Returns the join buffer object.
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.
void beforeModifiedCheck() const
Emitted when the layer is checked for modifications. Use for last-minute additions.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsVariantEqual(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether they are equal, two NULL values are always treated a...
#define Q_NOWARN_DEPRECATED_POP
#define Q_NOWARN_DEPRECATED_PUSH
QMap< int, QVariant > QgsAttributeMap
QSet< QgsFeatureId > QgsFeatureIds
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)