QGIS API Documentation 4.1.0-Master (376402f9aeb)
Loading...
Searching...
No Matches
qgsattributeform.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsattributeform.cpp
3 --------------------------------------
4 Date : 3.5.2014
5 Copyright : (C) 2014 Matthias Kuhn
6 Email : matthias at opengis dot ch
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsattributeform.h"
17
19#include "qgsapplication.h"
36#include "qgsfeatureiterator.h"
37#include "qgsfeaturerequest.h"
38#include "qgsfieldmodel.h"
39#include "qgsgui.h"
41#include "qgslogger.h"
42#include "qgsmessagebar.h"
43#include "qgsmessagebaritem.h"
45#include "qgsproject.h"
46#include "qgsprojectutils.h"
47#include "qgspythonrunner.h"
48#include "qgsqmlwidgetwrapper.h"
49#include "qgsrelationmanager.h"
51#include "qgsscrollarea.h"
55#include "qgstabwidget.h"
56#include "qgstexteditwrapper.h"
61#include "qgsvectorlayerutils.h"
62
63#include <QDir>
64#include <QFile>
65#include <QFileInfo>
66#include <QFormLayout>
67#include <QGridLayout>
68#include <QKeyEvent>
69#include <QLabel>
70#include <QMenu>
71#include <QMessageBox>
72#include <QPushButton>
73#include <QString>
74#include <QSvgWidget>
75#include <QTextStream>
76#include <QToolButton>
77#include <QToolTip>
78#include <QUiLoader>
79
80#include "moc_qgsattributeform.cpp"
81
82using namespace Qt::StringLiterals;
83
84int QgsAttributeForm::sFormCounter = 0;
85
87 : QWidget( parent )
88 , mLayer( vl )
89 , mContext( context )
90 , mFormNr( sFormCounter++ )
91 , mEditCommandMessage( tr( "Attributes changed" ) )
92{
93 init();
94 initPython();
96
97 connect( vl, &QgsVectorLayer::updatedFields, this, &QgsAttributeForm::onUpdatedFields );
98 connect( vl, &QgsVectorLayer::beforeAddingExpressionField, this, &QgsAttributeForm::preventFeatureRefresh );
99 connect( vl, &QgsVectorLayer::beforeRemovingExpressionField, this, &QgsAttributeForm::preventFeatureRefresh );
100 connect( vl, &QgsVectorLayer::selectionChanged, this, &QgsAttributeForm::layerSelectionChanged );
101 connect( this, &QgsAttributeForm::modeChanged, this, &QgsAttributeForm::updateContainersVisibility );
102
103 updateContainersVisibility();
104 updateLabels();
105 updateEditableState();
106}
107
109{
110 cleanPython();
111 qDeleteAll( mInterfaces );
112}
113
115{
116 mButtonBox->hide();
117
118 // Make sure that changes are taken into account if somebody tries to figure out if there have been some
121}
122
124{
125 mButtonBox->show();
126
127 disconnect( mLayer, &QgsVectorLayer::beforeModifiedCheck, this, &QgsAttributeForm::save );
128}
129
131{
132 disconnect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsAttributeForm::save );
133 disconnect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsAttributeForm::resetValues );
134}
135
137{
138 mInterfaces.append( iface );
139}
140
142{
143 return mFeature.isValid() && mLayer->isEditable();
144}
145
147{
148 if ( mode == mMode )
149 return;
150
152 {
153 //switching out of multi edit mode triggers a save
154 if ( mUnsavedMultiEditChanges )
155 {
156 // prompt for save
157 int res = QMessageBox::question( this, tr( "Multiedit Attributes" ), tr( "Apply changes to edited features?" ), QMessageBox::Yes | QMessageBox::No );
158 if ( res == QMessageBox::Yes )
159 {
160 save();
161 }
162 }
163 clearMultiEditMessages();
164 }
165 mUnsavedMultiEditChanges = false;
166
167 mMode = mode;
168
169 if ( mButtonBox->isVisible() && mMode == QgsAttributeEditorContext::SingleEditMode )
170 {
172 }
173 else
174 {
175 disconnect( mLayer, &QgsVectorLayer::beforeModifiedCheck, this, &QgsAttributeForm::save );
176 }
177
178 //update all form editor widget modes to match
179 for ( QgsAttributeFormWidget *w : std::as_const( mFormWidgets ) )
180 {
181 switch ( mode )
182 {
185 break;
186
189 break;
190
193 break;
194
197 break;
198
201 break;
202
205 break;
206
209 break;
210
213 break;
214 }
215 }
216 //update all form editor widget modes to match
217 for ( QgsWidgetWrapper *w : std::as_const( mWidgets ) )
218 {
219 QgsAttributeEditorContext newContext = w->context();
220 newContext.setAttributeFormMode( mMode );
221 w->setContext( newContext );
222 }
223
224 auto setRelationWidgetsVisible = [this]( bool relationWidgetsVisible ) {
225 for ( QgsAttributeFormRelationEditorWidget *w : findChildren<QgsAttributeFormRelationEditorWidget *>() )
226 {
227 w->setVisible( relationWidgetsVisible );
228 }
229 };
230
231 switch ( mode )
232 {
234 setRelationWidgetsVisible( true );
235 setFeature( mFeature );
236 mSearchButtonBox->setVisible( false );
237 break;
238
240 setRelationWidgetsVisible( true );
241 synchronizeState();
242 mSearchButtonBox->setVisible( false );
243 break;
244
246 setRelationWidgetsVisible( true );
247 synchronizeState();
248 mSearchButtonBox->setVisible( false );
249 break;
250
252 setRelationWidgetsVisible( true );
253 resetMultiEdit( false );
254 synchronizeState();
255 mSearchButtonBox->setVisible( false );
256 break;
257
259 setRelationWidgetsVisible( true );
260 mSearchButtonBox->setVisible( true );
261 synchronizeState();
263 break;
264
266 setRelationWidgetsVisible( false );
267 mSearchButtonBox->setVisible( false );
268 synchronizeState();
270 break;
271
273 setRelationWidgetsVisible( true );
274 setFeature( mFeature );
275 synchronizeState();
276 mSearchButtonBox->setVisible( false );
277 break;
278
280 setRelationWidgetsVisible( false );
281 synchronizeState();
282 mSearchButtonBox->setVisible( false );
283 break;
284 }
285
286 emit modeChanged( mMode );
287}
288
289void QgsAttributeForm::changeAttribute( const QString &field, const QVariant &value, const QString &hintText )
290{
291 const auto constMWidgets = mWidgets;
292 for ( QgsWidgetWrapper *ww : constMWidgets )
293 {
294 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
295 if ( eww )
296 {
297 if ( eww->field().name() == field )
298 {
299 eww->setValues( value, QVariantList() );
300 eww->setHint( hintText );
301 }
302 // see if the field is present in additional fields of the editor widget
303 int index = eww->additionalFields().indexOf( field );
304 if ( index >= 0 )
305 {
306 QVariant mainValue = eww->value();
307 QVariantList additionalFieldValues = eww->additionalFieldValues();
308 additionalFieldValues[index] = value;
309 eww->setValues( mainValue, additionalFieldValues );
310 eww->setHint( hintText );
311 }
312 }
313 }
314}
315
317{
318 mFeature.setGeometry( geometry );
319}
320
322{
323 mIsSettingFeature = true;
324 mFeature = feature;
325 mCurrentFormFeature = feature;
326
327 switch ( mMode )
328 {
334 {
335 resetValues();
336
337 synchronizeState();
338
339 // Settings of feature is done when we trigger the attribute form interface
340 // Issue https://github.com/qgis/QGIS/issues/29667
341 mIsSettingFeature = false;
342 const auto constMInterfaces = mInterfaces;
343 for ( QgsAttributeFormInterface *iface : constMInterfaces )
344 {
345 iface->featureChanged();
346 }
347 break;
348 }
351 {
352 resetValues();
353 break;
354 }
356 {
357 //ignore setFeature
358 break;
359 }
360 }
361 mIsSettingFeature = false;
362}
363
364bool QgsAttributeForm::saveEdits( QString *error )
365{
366 bool success = true;
367 bool changedLayer = false;
368
369 QgsFeature updatedFeature = QgsFeature( mFeature );
370 if ( mFeature.isValid() || mMode == QgsAttributeEditorContext::AddFeatureMode )
371 {
372 bool doUpdate = false;
373
374 // An add dialog should perform an action by default
375 // and not only if attributes have "changed"
377 {
378 doUpdate = true;
379 }
380
381 QgsAttributes src = mFeature.attributes();
382 QgsAttributes dst = mFeature.attributes();
383
384 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
385 {
386 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
387 if ( eww )
388 {
389 // check for invalid JSON values
390 QgsTextEditWrapper *textEdit = qobject_cast<QgsTextEditWrapper *>( eww );
391 if ( textEdit && textEdit->isInvalidJSON() )
392 {
393 if ( error )
394 *error = tr( "JSON value for %1 is invalid and has not been saved" ).arg( eww->field().name() );
395 return false;
396 }
397 QVariantList dstVars = QVariantList() << dst.at( eww->fieldIdx() );
398 QVariantList srcVars = QVariantList() << eww->value();
399 QList<int> fieldIndexes = QList<int>() << eww->fieldIdx();
400
401 // append additional fields
402 const QStringList additionalFields = eww->additionalFields();
403 for ( const QString &fieldName : additionalFields )
404 {
405 int idx = eww->layer()->fields().lookupField( fieldName );
406 fieldIndexes << idx;
407 dstVars << dst.at( idx );
408 }
409 srcVars.append( eww->additionalFieldValues() );
410
411 Q_ASSERT( dstVars.count() == srcVars.count() );
412
413 for ( int i = 0; i < dstVars.count(); i++ )
414 {
415 if ( !qgsVariantEqual( dstVars[i], srcVars[i] ) && srcVars[i].isValid() )
416 {
417 dst[fieldIndexes[i]] = srcVars[i];
418
419 doUpdate = true;
420 }
421 }
422 }
423 }
424
425 updatedFeature.setAttributes( dst );
426
427 const auto constMInterfaces = mInterfaces;
428 for ( QgsAttributeFormInterface *iface : constMInterfaces )
429 {
430 if ( !iface->acceptChanges( updatedFeature ) )
431 {
432 doUpdate = false;
433 }
434 }
435
436 if ( doUpdate )
437 {
439 {
440 mFeature = updatedFeature;
441 }
443 {
444 mFeature.setValid( true );
445 mLayer->beginEditCommand( mEditCommandMessage );
446 bool res = mLayer->addFeature( updatedFeature );
447 if ( res )
448 {
449 mFeature.setAttributes( updatedFeature.attributes() );
450 mLayer->endEditCommand();
451
452 const QgsFields fields = mLayer->fields();
453 const QgsAttributes newValues = updatedFeature.attributes();
454 const QVariant lastUsedValuesVariant = mLayer->property( "AttributeFormLastUsedValues" );
455 QgsAttributeMap lastUsedValues = lastUsedValuesVariant.isValid() ? lastUsedValuesVariant.value<QgsAttributeMap>() : QgsAttributeMap();
456 for ( int idx = 0; idx < fields.count(); ++idx )
457 {
458 const Qgis::AttributeFormReuseLastValuePolicy reusePolicy = mLayer->editFormConfig().reuseLastValuePolicy( idx );
460 {
461 const QVariant rememberLastUsedValuesVariant = mLayer->property( "AttributeFormRememberLastUsedValues" );
462 QMap<int, bool> rememberLastUsedValues = rememberLastUsedValuesVariant.value<QMap<int, bool>>();
463 if ( !rememberLastUsedValues.contains( idx ) )
464 {
465 const bool remember = reusePolicy == Qgis::AttributeFormReuseLastValuePolicy::AllowedDefaultOn;
466 rememberLastUsedValues[idx] = remember;
467 mLayer->setProperty( "AttributeFormRememberLastUsedValues", QVariant::fromValue<QMap<int, bool>>( rememberLastUsedValues ) );
468 }
469
470 const QVariant newValue = rememberLastUsedValues[idx] ? newValues.at( idx ) : QVariant();
471 if ( !lastUsedValues.contains( idx ) || lastUsedValues[idx] != newValue )
472 {
473 lastUsedValues[idx] = newValue;
474 QgsDebugMsgLevel( u"Saving %1 for %2"_s.arg( ( newValue.toString() ).arg( idx ) ), 2 );
475 }
476 }
477 }
478 mLayer->setProperty( "AttributeFormLastUsedValues", QVariant::fromValue<QgsAttributeMap>( lastUsedValues ) );
479
481 changedLayer = true;
482 }
483 else
484 mLayer->destroyEditCommand();
485 }
486 else
487 {
488 mLayer->beginEditCommand( mEditCommandMessage );
489
490 QgsAttributeMap newValues;
491 QgsAttributeMap oldValues;
492
493 int n = 0;
494 for ( int i = 0; i < dst.count(); ++i )
495 {
496 if ( qgsVariantEqual( dst.at( i ), src.at( i ) ) // If field is not changed...
497 || !dst.at( i ).isValid() // or the widget returns invalid (== do not change)
498 || !fieldIsEditable( i ) ) // or the field cannot be edited ...
499 {
500 continue;
501 }
502
503 QgsDebugMsgLevel( u"Updating field %1"_s.arg( i ), 2 );
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 );
506
507 newValues[i] = dst.at( i );
508 oldValues[i] = src.at( i );
509
510 n++;
511 }
512
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() );
517
518 if ( success && n > 0 )
519 {
520 mLayer->endEditCommand();
521 mFeature.setAttributes( dst );
522 changedLayer = true;
523 }
524 else
525 {
526 mLayer->destroyEditCommand();
527 }
528 }
529 }
530 }
531
532 emit featureSaved( updatedFeature );
533
534 // [MD] Refresh canvas only when absolutely necessary - it interferes with other stuff (#11361).
535 // This code should be revisited - and the signals should be fired (+ layer repainted)
536 // only when actually doing any changes. I am unsure if it is actually a good idea
537 // to call save() whenever some code asks for vector layer's modified status
538 // (which is the case when attribute table is open)
539 if ( changedLayer )
540 mLayer->triggerRepaint();
541
542 return success;
543}
544
545QgsFeature QgsAttributeForm::getUpdatedFeature() const
546{
547 // create updated Feature
548 QgsFeature updatedFeature = QgsFeature( mFeature );
549
550 QgsAttributes featureAttributes = mFeature.attributes();
551 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
552 {
553 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
554 if ( !eww )
555 continue;
556
557 QVariantList dstVars = QVariantList() << featureAttributes.at( eww->fieldIdx() );
558 QVariantList srcVars = QVariantList() << eww->value();
559 QList<int> fieldIndexes = QList<int>() << eww->fieldIdx();
560
561 // append additional fields
562 const QStringList additionalFields = eww->additionalFields();
563 for ( const QString &fieldName : additionalFields )
564 {
565 int idx = eww->layer()->fields().lookupField( fieldName );
566 fieldIndexes << idx;
567 dstVars << featureAttributes.at( idx );
568 }
569 srcVars.append( eww->additionalFieldValues() );
570
571 Q_ASSERT( dstVars.count() == srcVars.count() );
572
573 for ( int i = 0; i < dstVars.count(); i++ )
574 {
575 if ( !qgsVariantEqual( dstVars[i], srcVars[i] ) && srcVars[i].isValid() )
576 featureAttributes[fieldIndexes[i]] = srcVars[i];
577 }
578 }
579 updatedFeature.setAttributes( featureAttributes );
580
581 return updatedFeature;
582}
583
584void QgsAttributeForm::updateValuesDependencies( const int originIdx )
585{
586 updateValuesDependenciesDefaultValues( originIdx );
587 updateValuesDependenciesVirtualFields( originIdx );
588}
589
590void QgsAttributeForm::updateValuesDependenciesDefaultValues( const int originIdx )
591{
592 if ( !mDefaultValueDependencies.contains( originIdx ) )
593 return;
594
595 if ( !mFeature.isValid() )
596 {
597 switch ( mMode )
598 {
605 return;
606
609 break;
610 }
611 }
612
613 // create updated Feature
614 QgsFeature updatedFeature = getUpdatedFeature();
615
616 // go through depending fields and update the fields with defaultexpression
617 QList<QgsWidgetWrapper *> relevantWidgets = mDefaultValueDependencies.values( originIdx );
618 for ( QgsWidgetWrapper *ww : std::as_const( relevantWidgets ) )
619 {
620 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
621 if ( eww )
622 {
623 // Update only on form opening (except when applyOnUpdate is activated)
624 if ( mValuesInitialized && !eww->field().defaultValueDefinition().applyOnUpdate() )
625 continue;
626
627 // Update only when mMode is AddFeatureMode (except when applyOnUpdate is activated)
629 {
630 continue;
631 }
632
633 //do not update when this widget is already updating (avoid recursions)
634 if ( mAlreadyUpdatedFields.contains( eww->fieldIdx() ) )
635 continue;
636
637 QgsExpressionContext context = createExpressionContext( updatedFeature );
638
639 const QVariant value = mLayer->defaultValue( eww->fieldIdx(), updatedFeature, &context );
640 eww->setValue( value );
641 mCurrentFormFeature.setAttribute( eww->field().name(), value );
642 }
643 }
644}
645
646void QgsAttributeForm::updateValuesDependenciesVirtualFields( const int originIdx )
647{
648 if ( !mVirtualFieldsDependencies.contains( originIdx ) )
649 return;
650
651 if ( !mFeature.isValid() )
652 return;
653
654 // create updated Feature
655 QgsFeature updatedFeature = getUpdatedFeature();
656
657 // go through depending fields and update the virtual field with its expression
658 const QList<QgsWidgetWrapper *> relevantWidgets = mVirtualFieldsDependencies.values( originIdx );
659 for ( QgsWidgetWrapper *ww : relevantWidgets )
660 {
661 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
662 if ( !eww )
663 continue;
664
665 //do not update when this widget is already updating (avoid recursions)
666 if ( mAlreadyUpdatedFields.contains( eww->fieldIdx() ) )
667 continue;
668
669 // Update value
670 QgsExpressionContext context = createExpressionContext( updatedFeature );
671 QgsExpression exp( mLayer->expressionField( eww->fieldIdx() ) );
672 const QVariant value = exp.evaluate( &context );
673 updatedFeature.setAttribute( eww->fieldIdx(), value );
674 eww->setValue( value );
675 }
676}
677
678void QgsAttributeForm::updateValuesDependenciesParent()
679{
680 // create updated Feature
681 QgsFeature updatedFeature = getUpdatedFeature();
682 QList<int> updatedFields;
683
684 // go through fields dependent to parent feature value(s)
685 const QSet<QgsEditorWidgetWrapper *> relevantWidgets = mParentDependencies;
686 for ( QgsEditorWidgetWrapper *eww : relevantWidgets )
687 {
688 //do not update when this widget is already updating (avoid recursions)
689 if ( updatedFields.contains( eww->fieldIdx() ) )
690 continue;
691
692 // Update value
693 updatedFields << eww->fieldIdx();
694 QgsExpressionContext context = createExpressionContext( updatedFeature );
695 const QVariant value = mLayer->defaultValue( eww->fieldIdx(), updatedFeature, &context );
696 eww->setValue( value );
697 }
698}
699
700void QgsAttributeForm::updateRelatedLayerFields()
701{
702 // Synchronize dependencies
703 updateRelatedLayerFieldsDependencies();
704
705 if ( mRelatedLayerFieldsDependencies.isEmpty() )
706 return;
707
708 if ( !mFeature.isValid() )
709 return;
710
711 // create updated Feature
712 QgsFeature updatedFeature = getUpdatedFeature();
713
714 // go through depending fields and update the fields with virtual field
715 const QSet<QgsEditorWidgetWrapper *> relevantWidgets = mRelatedLayerFieldsDependencies;
716 for ( QgsEditorWidgetWrapper *eww : relevantWidgets )
717 {
718 //do not update when this widget is already updating (avoid recursions)
719 if ( mAlreadyUpdatedFields.contains( eww->fieldIdx() ) )
720 continue;
721
722 // Update value
723 QgsExpressionContext context = createExpressionContext( updatedFeature );
724 QgsExpression exp( mLayer->expressionField( eww->fieldIdx() ) );
725 QVariant value = exp.evaluate( &context );
726 eww->setValue( value );
727 }
728}
729
730void QgsAttributeForm::resetMultiEdit( bool promptToSave )
731{
732 if ( promptToSave )
733 save();
734
735 mUnsavedMultiEditChanges = false;
736 setMultiEditFeatureIds( mLayer->selectedFeatureIds() );
737}
738
739void QgsAttributeForm::multiEditMessageClicked( const QString &link )
740{
741 clearMultiEditMessages();
742 resetMultiEdit( link == "#apply"_L1 );
743}
744
745void QgsAttributeForm::filterTriggered()
746{
747 QString filter = createFilterExpression();
748 emit filterExpressionSet( filter, ReplaceFilter );
749 if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
751}
752
753void QgsAttributeForm::searchZoomTo()
754{
755 QString filter = createFilterExpression();
756 if ( filter.isEmpty() )
757 return;
758
759 emit zoomToFeatures( filter );
760}
761
762void QgsAttributeForm::searchFlash()
763{
764 QString filter = createFilterExpression();
765 if ( filter.isEmpty() )
766 return;
767
768 emit flashFeatures( filter );
769}
770
771void QgsAttributeForm::filterAndTriggered()
772{
773 QString filter = createFilterExpression();
774 if ( filter.isEmpty() )
775 return;
776
777 if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
779 emit filterExpressionSet( filter, FilterAnd );
780}
781
782void QgsAttributeForm::filterOrTriggered()
783{
784 QString filter = createFilterExpression();
785 if ( filter.isEmpty() )
786 return;
787
788 if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
790 emit filterExpressionSet( filter, FilterOr );
791}
792
793void QgsAttributeForm::pushSelectedFeaturesMessage()
794{
795 int count = mLayer->selectedFeatureCount();
796 if ( count > 0 )
797 {
798 mMessageBar->pushMessage( QString(), tr( "%n matching feature(s) selected", "matching features", count ), Qgis::MessageLevel::Info );
799 }
800 else
801 {
802 mMessageBar->pushMessage( QString(), tr( "No matching features found" ), Qgis::MessageLevel::Info );
803 }
804}
805
806void QgsAttributeForm::displayWarning( const QString &message )
807{
808 mMessageBar->pushMessage( QString(), message, Qgis::MessageLevel::Warning );
809}
810
811void QgsAttributeForm::runSearchSelect( Qgis::SelectBehavior behavior )
812{
813 QString filter = createFilterExpression();
814 if ( filter.isEmpty() )
815 return;
816
817 mLayer->selectByExpression( filter, behavior );
818 pushSelectedFeaturesMessage();
819 if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
821}
822
823void QgsAttributeForm::searchSetSelection()
824{
825 runSearchSelect( Qgis::SelectBehavior::SetSelection );
826}
827
828void QgsAttributeForm::searchAddToSelection()
829{
830 runSearchSelect( Qgis::SelectBehavior::AddToSelection );
831}
832
833void QgsAttributeForm::searchRemoveFromSelection()
834{
836}
837
838void QgsAttributeForm::searchIntersectSelection()
839{
841}
842
843bool QgsAttributeForm::saveMultiEdits()
844{
845 //find changed attributes
846 QgsAttributeMap newAttributeValues;
847 const QList<int> fieldIndexes = mFormEditorWidgets.uniqueKeys();
848 for ( int fieldIndex : fieldIndexes )
849 {
850 const QList<QgsAttributeFormEditorWidget *> widgets = mFormEditorWidgets.values( fieldIndex );
851 if ( !widgets.first()->hasChanged() )
852 continue;
853
854 if ( !widgets.first()->currentValue().isValid() // if the widget returns invalid (== do not change)
855 || !fieldIsEditable( fieldIndex ) ) // or the field cannot be edited ...
856 {
857 continue;
858 }
859
860 // let editor know we've accepted the changes
861 for ( QgsAttributeFormEditorWidget *widget : widgets )
862 widget->changesCommitted();
863
864 newAttributeValues.insert( fieldIndex, widgets.first()->currentValue() );
865 }
866
867 if ( newAttributeValues.isEmpty() )
868 {
869 //nothing to change
870 return true;
871 }
872
873#if 0
874 // prompt for save
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 )
878 {
879 resetMultiEdit();
880 return false;
881 }
882#endif
883
884 mLayer->beginEditCommand( tr( "Updated multiple feature attributes" ) );
885
886 bool success = true;
887
888 const auto constMultiEditFeatureIds = mMultiEditFeatureIds;
889 for ( QgsFeatureId fid : constMultiEditFeatureIds )
890 {
891 QgsAttributeMap::const_iterator aIt = newAttributeValues.constBegin();
892 for ( ; aIt != newAttributeValues.constEnd(); ++aIt )
893 {
894 success &= mLayer->changeAttributeValue( fid, aIt.key(), aIt.value() );
895 }
896 }
897
898 clearMultiEditMessages();
899 if ( success )
900 {
901 mLayer->endEditCommand();
902 mLayer->triggerRepaint();
903 mMultiEditMessageBarItem = new QgsMessageBarItem( tr( "Attribute changes for multiple features applied." ), Qgis::MessageLevel::Success, -1 );
904 }
905 else
906 {
907 mLayer->destroyEditCommand();
908 mMultiEditMessageBarItem = new QgsMessageBarItem( tr( "Changes could not be applied." ), Qgis::MessageLevel::Warning, 0 );
909 }
910
911 if ( !mButtonBox->isVisible() )
912 mMessageBar->pushItem( mMultiEditMessageBarItem );
913 return success;
914}
915
917{
918 return saveWithDetails( nullptr );
919}
920
922{
923 if ( error )
924 error->clear();
925
926 if ( mIsSaving )
927 return true;
928
929 if ( mContext.formMode() == QgsAttributeEditorContext::Embed && !mValidConstraints )
930 {
931 // the feature isn't saved (as per the warning provided), but we return true
932 // so switching features still works
933 return true;
934 }
935
936 for ( QgsWidgetWrapper *wrapper : std::as_const( mWidgets ) )
937 {
938 wrapper->notifyAboutToSave();
939 }
940
941 // only do the dirty checks when editing an existing feature - for new
942 // features we need to add them even if the attributes are unchanged from the initial
943 // default values
944 switch ( mMode )
945 {
950 if ( !mDirty )
951 return true;
952 break;
953
957 break;
958
960 return true;
961 }
962
963 mIsSaving = true;
964
965 bool success = true;
966
967 emit beforeSave( success );
968
969 // Somebody wants to prevent this form from saving
970 if ( !success )
971 return false;
972
973 switch ( mMode )
974 {
981 success = saveEdits( error );
982 break;
983
985 success = saveMultiEdits();
986 break;
987
989 break;
990 }
991
992 mIsSaving = false;
993 mUnsavedMultiEditChanges = false;
994 mDirty = false;
995
996 return success;
997}
998
999
1001{
1002 mValuesInitialized = false;
1003 const auto constMWidgets = mWidgets;
1004 for ( QgsWidgetWrapper *ww : constMWidgets )
1005 {
1006 ww->setFeature( mFeature );
1007 }
1008
1009 // Update dependent virtual fields (not default values / not referencing layer values)
1010 for ( QgsWidgetWrapper *ww : constMWidgets )
1011 {
1012 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1013 if ( !eww )
1014 continue;
1015
1016 // Append field index here, so it's not updated recursively
1017 mAlreadyUpdatedFields.append( eww->fieldIdx() );
1018 updateValuesDependenciesVirtualFields( eww->fieldIdx() );
1019 mAlreadyUpdatedFields.removeAll( eww->fieldIdx() );
1020 }
1021
1022 mValuesInitialized = true;
1023 mDirty = false;
1024}
1025
1027{
1028 const auto widgets { findChildren<QgsAttributeFormEditorWidget *>() };
1029 for ( QgsAttributeFormEditorWidget *w : widgets )
1030 {
1031 w->resetSearch();
1032 }
1033}
1034
1035void QgsAttributeForm::clearMultiEditMessages()
1036{
1037 if ( mMultiEditUnsavedMessageBarItem )
1038 {
1039 if ( !mButtonBox->isVisible() )
1040 mMessageBar->popWidget( mMultiEditUnsavedMessageBarItem );
1041 mMultiEditUnsavedMessageBarItem = nullptr;
1042 }
1043 if ( mMultiEditMessageBarItem )
1044 {
1045 if ( !mButtonBox->isVisible() )
1046 mMessageBar->popWidget( mMultiEditMessageBarItem );
1047 mMultiEditMessageBarItem = nullptr;
1048 }
1049}
1050
1051QString QgsAttributeForm::createFilterExpression() const
1052{
1053 QStringList filters;
1054 for ( QgsAttributeFormWidget *w : std::as_const( mFormWidgets ) )
1055 {
1056 QString filter = w->currentFilterExpression();
1057 if ( !filter.isEmpty() )
1058 filters << filter;
1059 }
1060
1061 if ( filters.isEmpty() )
1062 return QString();
1063
1064 QString filter = filters.join( ") AND ("_L1 ).prepend( '(' ).append( ')' );
1065 return filter;
1066}
1067
1068QgsExpressionContext QgsAttributeForm::createExpressionContext( const QgsFeature &feature ) const
1069{
1070 QgsExpressionContext context;
1072 context.appendScope( QgsExpressionContextUtils::formScope( feature, mContext.attributeFormModeString() ) );
1073 if ( mExtraContextScope )
1074 {
1075 context.appendScope( new QgsExpressionContextScope( *mExtraContextScope.get() ) );
1076 }
1077 if ( mContext.parentFormFeature().isValid() )
1078 {
1079 context.appendScope( QgsExpressionContextUtils::parentFormScope( mContext.parentFormFeature() ) );
1080 }
1081 context.setFeature( feature );
1082 return context;
1083}
1084
1085
1086void QgsAttributeForm::onAttributeChanged( const QVariant &value, const QVariantList &additionalFieldValues )
1087{
1088 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( sender() );
1089 Q_ASSERT( eww );
1090
1091 bool signalEmitted = false;
1092
1093 if ( mValuesInitialized )
1094 mDirty = true;
1095
1096 mCurrentFormFeature.setAttribute( eww->field().name(), value );
1097
1098 // Update other widgets pointing to the same field, required to happen now to insure
1099 // currentFormValuesFeature() gets the right value when processing constraints
1100 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->fieldIdx() );
1101 for ( QgsAttributeFormEditorWidget *formEditorWidget : std::as_const( formEditorWidgets ) )
1102 {
1103 if ( formEditorWidget->editorWidget() == eww )
1104 continue;
1105
1106 // formEditorWidget and eww points to the same field, so update its value
1107 formEditorWidget->editorWidget()->setValue( value );
1108 }
1109
1110 switch ( mMode )
1111 {
1117 {
1119 emit attributeChanged( eww->field().name(), value );
1121 emit widgetValueChanged( eww->field().name(), value, !mIsSettingFeature );
1122
1123 // also emit the signal for additional values
1124 const QStringList additionalFields = eww->additionalFields();
1125 for ( int i = 0; i < additionalFields.count(); i++ )
1126 {
1127 const QString fieldName = additionalFields.at( i );
1128 const QVariant value = additionalFieldValues.at( i );
1129 emit widgetValueChanged( fieldName, value, !mIsSettingFeature );
1130 }
1131
1132 signalEmitted = true;
1133
1134 if ( mValuesInitialized )
1135 updateJoinedFields( *eww );
1136
1137 break;
1138 }
1140 {
1141 if ( !mIsSettingMultiEditFeatures )
1142 {
1143 mUnsavedMultiEditChanges = true;
1144
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();
1150
1151 mMultiEditUnsavedMessageBarItem = new QgsMessageBarItem( msgLabel, Qgis::MessageLevel::Warning );
1152 if ( !mButtonBox->isVisible() )
1153 mMessageBar->pushItem( mMultiEditUnsavedMessageBarItem );
1154
1155 emit widgetValueChanged( eww->field().name(), value, false );
1156 signalEmitted = true;
1157 }
1158 break;
1159 }
1162 //nothing to do
1163 break;
1164 }
1165
1166 updateConstraints( eww );
1167
1168 // Update dependent fields (only if form is not initializing)
1169 if ( mValuesInitialized && !mIsSettingMultiEditFeatures )
1170 {
1171 //append field index here, so it's not updated recursive
1172 mAlreadyUpdatedFields.append( eww->fieldIdx() );
1173 updateValuesDependencies( eww->fieldIdx() );
1174 mAlreadyUpdatedFields.removeAll( eww->fieldIdx() );
1175 }
1176
1177 // Updates expression controlled labels and editable state
1178 updateLabels();
1179 updateEditableState();
1180
1181 if ( !signalEmitted )
1182 {
1184 emit attributeChanged( eww->field().name(), value );
1186 bool attributeHasChanged = !mIsSettingFeature;
1188 attributeHasChanged &= !mIsSettingMultiEditFeatures;
1189
1190 emit widgetValueChanged( eww->field().name(), value, attributeHasChanged );
1191 }
1192}
1193
1194void QgsAttributeForm::updateAllConstraints()
1195{
1196 const auto constMWidgets = mWidgets;
1197 for ( QgsWidgetWrapper *ww : constMWidgets )
1198 {
1199 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1200 if ( eww )
1201 updateConstraints( eww );
1202 }
1203}
1204
1205void QgsAttributeForm::updateConstraints( QgsEditorWidgetWrapper *eww )
1206{
1207 // get the current feature set in the form
1208 QgsFeature ft;
1209 if ( currentFormValuesFeature( ft ) )
1210 {
1211 // if the layer is NOT being edited then we only check layer based constraints, and not
1212 // any constraints enforced by the provider. Because:
1213 // 1. we want to keep browsing features nice and responsive. It's nice to give feedback as to whether
1214 // the value checks out, but not if it's too slow to do so. Some constraints (e.g., unique) can be
1215 // expensive to test. A user can freely remove a layer-based constraint if it proves to be too slow
1216 // to test, but they are unlikely to have any control over provider-side constraints
1217 // 2. the provider has already accepted the value, so presumably it doesn't violate the constraint
1218 // and there's no point rechecking!
1219
1220 // update eww constraint
1221 updateConstraint( ft, eww );
1222
1223 // update eww dependencies constraint
1224 const QList<QgsEditorWidgetWrapper *> deps = constraintDependencies( eww );
1225
1226 for ( QgsEditorWidgetWrapper *depsEww : deps )
1227 updateConstraint( ft, depsEww );
1228
1229 // sync OK button status
1230 synchronizeState();
1231
1232 QgsExpressionContext context = createExpressionContext( ft );
1233
1234 // Recheck visibility/collapsed state for all containers which are controlled by this value
1235 const QVector<ContainerInformation *> infos = mContainerInformationDependency.value( eww->field().name() );
1236 for ( ContainerInformation *info : infos )
1237 {
1238 info->apply( &context );
1239 }
1240 }
1241}
1242
1243void QgsAttributeForm::updateContainersVisibility()
1244{
1245 QgsExpressionContext context = createExpressionContext( mFeature );
1246
1247 const QVector<ContainerInformation *> infos = mContainerVisibilityCollapsedInformation;
1248
1249 for ( ContainerInformation *info : infos )
1250 {
1251 info->apply( &context );
1252 }
1253
1254 // Update the constraints if not in multi edit, because
1255 // when mode changes to multi edit, constraints have been already
1256 // updated and a further update will use current form feature values,
1257 // possibly empty for mixed values, leading to false positive
1258 // constraints violations.
1260 {
1261 updateAllConstraints();
1262 }
1263}
1264
1265void QgsAttributeForm::updateConstraint( const QgsFeature &ft, QgsEditorWidgetWrapper *eww )
1266{
1268
1269 if ( eww->layer()->fields().fieldOrigin( eww->fieldIdx() ) == Qgis::FieldOrigin::Join )
1270 {
1271 int srcFieldIdx;
1272 const QgsVectorLayerJoinInfo *info = eww->layer()->joinBuffer()->joinForFieldIndex( eww->fieldIdx(), eww->layer()->fields(), srcFieldIdx );
1273
1274 if ( info && info->joinLayer() && info->isDynamicFormEnabled() )
1275 {
1276 if ( mJoinedFeatures.contains( info ) )
1277 {
1278 eww->updateConstraint( info->joinLayer(), srcFieldIdx, mJoinedFeatures[info], constraintOrigin );
1279 return;
1280 }
1281 else // if we are here, it means there's not joined field for this feature
1282 {
1283 eww->updateConstraint( QgsFeature() );
1284 return;
1285 }
1286 }
1287 }
1288 // default constraint update
1289 eww->updateConstraint( ft, constraintOrigin );
1290}
1291
1292void QgsAttributeForm::updateLabels()
1293{
1294 if ( !mLabelDataDefinedProperties.isEmpty() )
1295 {
1296 QgsFeature currentFeature;
1297 if ( currentFormValuesFeature( currentFeature ) )
1298 {
1299 QgsExpressionContext context = createExpressionContext( currentFeature );
1300
1301 for ( auto it = mLabelDataDefinedProperties.constBegin(); it != mLabelDataDefinedProperties.constEnd(); ++it )
1302 {
1303 QLabel *label { it.key() };
1304 bool ok;
1305 const QString value { it->valueAsString( context, QString(), &ok ) };
1306 if ( ok && !value.isEmpty() )
1307 {
1308 label->setText( value );
1309 }
1310 }
1311 }
1312 }
1313 if ( !mCustomCommentDataDefinedProperties.isEmpty() )
1314 {
1315 QgsFeature currentFeature;
1316 if ( currentFormValuesFeature( currentFeature ) )
1317 {
1318 QgsExpressionContext context = createExpressionContext( currentFeature );
1319
1320 for ( auto it = mCustomCommentDataDefinedProperties.constBegin(); it != mCustomCommentDataDefinedProperties.constEnd(); ++it )
1321 {
1322 QWidget *labelWidget { it.key() };
1323 bool ok;
1324 const QString value { it->valueAsString( context, QString(), &ok ) };
1325 if ( ok && !value.isEmpty() )
1326 {
1327 const QString fieldName = labelWidget->objectName();
1328 const QgsFields fields = mLayer->fields();
1329 int idx = fields.lookupField( fieldName );
1330
1331 if ( idx >= 0 )
1332 {
1333 labelWidget->setToolTip( QgsFieldModel::fieldToolTipExtended( fields.at( idx ), mLayer, value ) );
1334 }
1335 }
1336 }
1337 }
1338 }
1339}
1340
1341void QgsAttributeForm::updateEditableState()
1342{
1343 if ( !mEditableDataDefinedProperties.isEmpty() )
1344 {
1345 QgsFeature currentFeature;
1346 if ( currentFormValuesFeature( currentFeature ) )
1347 {
1348 QgsExpressionContext context = createExpressionContext( currentFeature );
1349
1350 for ( auto it = mEditableDataDefinedProperties.constBegin(); it != mEditableDataDefinedProperties.constEnd(); ++it )
1351 {
1352 QWidget *w { it.key() };
1353 bool ok;
1354 const bool isEditable { it->valueAsBool( context, true, &ok ) && mLayer && mLayer->isEditable() }; // *NOPAD*
1355 if ( ok )
1356 {
1357 QgsAttributeFormEditorWidget *editorWidget { qobject_cast<QgsAttributeFormEditorWidget *>( w ) };
1358 if ( editorWidget )
1359 {
1360 editorWidget->editorWidget()->setEnabled( isEditable );
1361 }
1362 else
1363 {
1364 w->setEnabled( isEditable );
1365 }
1366 }
1367 }
1368 }
1369 }
1370}
1371
1372bool QgsAttributeForm::currentFormValuesFeature( QgsFeature &feature )
1373{
1374 bool rc = true;
1375 feature = QgsFeature( mFeature );
1376 QgsAttributes dst = feature.attributes();
1377
1378 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1379 {
1380 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1381
1382 if ( !eww )
1383 continue;
1384
1385 if ( dst.count() > eww->fieldIdx() )
1386 {
1387 QVariantList dstVars = QVariantList() << dst.at( eww->fieldIdx() );
1388 QVariantList srcVars = QVariantList() << eww->value();
1389 QList<int> fieldIndexes = QList<int>() << eww->fieldIdx();
1390
1391 // append additional fields
1392 const QStringList additionalFields = eww->additionalFields();
1393 for ( const QString &fieldName : additionalFields )
1394 {
1395 int idx = eww->layer()->fields().lookupField( fieldName );
1396 fieldIndexes << idx;
1397 dstVars << dst.at( idx );
1398 }
1399 srcVars.append( eww->additionalFieldValues() );
1400
1401 Q_ASSERT( dstVars.count() == srcVars.count() );
1402
1403 for ( int i = 0; i < dstVars.count(); i++ )
1404 {
1405 // need to check dstVar.isNull() != srcVar.isNull()
1406 // otherwise if dstVar=NULL and scrVar=0, then dstVar = srcVar
1407 if ( ( !qgsVariantEqual( dstVars[i], srcVars[i] ) || QgsVariantUtils::isNull( dstVars[i] ) != QgsVariantUtils::isNull( srcVars[i] ) ) && srcVars[i].isValid() )
1408 {
1409 dst[fieldIndexes[i]] = srcVars[i];
1410 }
1411 }
1412 }
1413 else
1414 {
1415 rc = false;
1416 break;
1417 }
1418 }
1419
1420 feature.setAttributes( dst );
1421
1422 return rc;
1423}
1424
1425
1426void QgsAttributeForm::registerContainerInformation( QgsAttributeForm::ContainerInformation *info )
1427{
1428 mContainerVisibilityCollapsedInformation.append( info );
1429
1430 const QSet<QString> referencedColumns = info->expression.referencedColumns().unite( info->collapsedExpression.referencedColumns() );
1431
1432 for ( const QString &col : referencedColumns )
1433 {
1434 mContainerInformationDependency[col].append( info );
1435 }
1436}
1437
1438bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields, QStringList &descriptions ) const
1439{
1440 bool valid { true };
1441
1442 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1443 {
1444 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1445 if ( eww )
1446 {
1447 if ( !eww->isValidConstraint() )
1448 {
1449 invalidFields.append( eww->field().displayName() );
1450
1451 descriptions.append( eww->constraintFailureReason() );
1452
1453 if ( eww->isBlockingCommit() )
1454 valid = false; // continue to get all invalid fields
1455 }
1456 }
1457 }
1458
1459 return valid;
1460}
1461
1462bool QgsAttributeForm::currentFormValidHardConstraints( QStringList &invalidFields, QStringList &descriptions ) const
1463{
1464 bool valid { true };
1465
1466 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1467 {
1468 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1469 if ( eww )
1470 {
1471 if ( eww->isBlockingCommit() )
1472 {
1473 invalidFields.append( eww->field().displayName() );
1474 descriptions.append( eww->constraintFailureReason() );
1475 valid = false; // continue to get all invalid fields
1476 }
1477 }
1478 }
1479
1480 return valid;
1481}
1482
1483void QgsAttributeForm::onAttributeAdded( int idx )
1484{
1485 mPreventFeatureRefresh = false;
1486 if ( mFeature.isValid() )
1487 {
1488 QgsAttributes attrs = mFeature.attributes();
1489 attrs.insert( idx, QgsVariantUtils::createNullVariant( layer()->fields().at( idx ).type() ) );
1490 mFeature.setFields( layer()->fields() );
1491 mFeature.setAttributes( attrs );
1492 }
1493 init();
1494 setFeature( mFeature );
1495}
1496
1497void QgsAttributeForm::onAttributeDeleted( int idx )
1498{
1499 mPreventFeatureRefresh = false;
1500 if ( mFeature.isValid() )
1501 {
1502 QgsAttributes attrs = mFeature.attributes();
1503 attrs.remove( idx );
1504 mFeature.setFields( layer()->fields() );
1505 mFeature.setAttributes( attrs );
1506 }
1507 init();
1508 setFeature( mFeature );
1509}
1510
1511void QgsAttributeForm::onRelatedFeaturesChanged()
1512{
1513 updateRelatedLayerFields();
1514}
1515
1516void QgsAttributeForm::onUpdatedFields()
1517{
1518 mPreventFeatureRefresh = false;
1519 if ( mFeature.isValid() )
1520 {
1521 QgsAttributes attrs( layer()->fields().size() );
1522 for ( int i = 0; i < layer()->fields().size(); i++ )
1523 {
1524 int idx = mFeature.fields().indexFromName( layer()->fields().at( i ).name() );
1525 if ( idx != -1 )
1526 {
1527 attrs[i] = mFeature.attributes().at( idx );
1528 if ( mFeature.attributes().at( idx ).userType() != layer()->fields().at( i ).type() )
1529 {
1530 attrs[i].convert( layer()->fields().at( i ).type() );
1531 }
1532 }
1533 else
1534 {
1535 attrs[i] = QgsVariantUtils::createNullVariant( layer()->fields().at( i ).type() );
1536 }
1537 }
1538 mFeature.setFields( layer()->fields() );
1539 mFeature.setAttributes( attrs );
1540 }
1541 init();
1542 setFeature( mFeature );
1543}
1544
1545void QgsAttributeForm::onConstraintStatusChanged( const QString &constraint, const QString &description, const QString &err, QgsEditorWidgetWrapper::ConstraintResult result )
1546{
1547 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( sender() );
1548 Q_ASSERT( eww );
1549
1550 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->fieldIdx() );
1551
1552 for ( QgsAttributeFormEditorWidget *formEditorWidget : formEditorWidgets )
1553 {
1554 formEditorWidget->setConstraintStatus( constraint, description, err, result );
1555 if ( formEditorWidget->editorWidget() != eww )
1556 {
1557 formEditorWidget->editorWidget()->updateConstraint( result, err );
1558 }
1559 }
1560}
1561
1562QList<QgsEditorWidgetWrapper *> QgsAttributeForm::constraintDependencies( QgsEditorWidgetWrapper *w )
1563{
1564 QList<QgsEditorWidgetWrapper *> wDeps;
1565 QString name = w->field().name();
1566
1567 // for each widget in the current form
1568 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1569 {
1570 // get the wrapper
1571 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1572 if ( eww )
1573 {
1574 // compare name to not compare w to itself
1575 QString ewwName = eww->field().name();
1576 if ( name != ewwName )
1577 {
1578 // get expression and referencedColumns
1579 QgsExpression expr = eww->layer()->fields().at( eww->fieldIdx() ).constraints().constraintExpression();
1580
1581 const auto referencedColumns = expr.referencedColumns();
1582
1583 for ( const QString &colName : referencedColumns )
1584 {
1585 if ( name.compare( colName, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
1586 {
1587 wDeps.append( eww );
1588 break;
1589 }
1590 }
1591 }
1592 }
1593 }
1594
1595 return wDeps;
1596}
1597
1598QgsRelationWidgetWrapper *QgsAttributeForm::setupRelationWidgetWrapper( const QgsRelation &rel, const QgsAttributeEditorContext &context )
1599{
1600 return setupRelationWidgetWrapper( QString(), rel, context );
1601}
1602
1603QgsRelationWidgetWrapper *QgsAttributeForm::setupRelationWidgetWrapper( const QString &relationWidgetTypeId, const QgsRelation &rel, const QgsAttributeEditorContext &context )
1604{
1605 QgsRelationWidgetWrapper *rww = new QgsRelationWidgetWrapper( relationWidgetTypeId, mLayer, rel, nullptr, this );
1606 const QVariantMap config = mLayer->editFormConfig().widgetConfig( rel.id() );
1607 rww->setConfig( config );
1608 rww->setContext( context );
1609
1610 return rww;
1611}
1612
1613QToolButton *QgsAttributeForm::createCommentInfoButton( QWidget *labelWidget )
1614{
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 );
1618 // looks like an icon, but on click the tooltip of the label appears
1619 infoButton->setAutoRaise( true );
1620 infoButton->setStyleSheet(
1621 "QToolButton { "
1622 " border: none; "
1623 " background-color: transparent; "
1624 " padding: 0px; "
1625 "}"
1626 );
1627 // connect button to label (for the label's tooltip)
1628 connect( infoButton, &QToolButton::clicked, labelWidget, [labelWidget]() { QToolTip::showText( QCursor::pos(), labelWidget->toolTip(), labelWidget ); } );
1629 // invisible per default
1630 infoButton->setVisible( false );
1631 return infoButton;
1632}
1633
1634void QgsAttributeForm::preventFeatureRefresh()
1635{
1636 mPreventFeatureRefresh = true;
1637}
1638
1640{
1641 if ( mPreventFeatureRefresh || mLayer->isEditable() || !mFeature.isValid() )
1642 return;
1643
1644 // reload feature if layer changed although not editable
1645 // (datasource probably changed bypassing QgsVectorLayer)
1646 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeature.id() ) ).nextFeature( mFeature ) )
1647 return;
1648
1649 init();
1650 setFeature( mFeature );
1651}
1652
1653void QgsAttributeForm::parentFormValueChanged( const QString &attribute, const QVariant &newValue )
1654{
1655 if ( mContext.parentFormFeature().isValid() )
1656 {
1657 QgsFeature parentFormFeature = mContext.parentFormFeature();
1658 parentFormFeature.setAttribute( attribute, newValue );
1659 mContext.setParentFormFeature( parentFormFeature );
1660 }
1661
1662 updateValuesDependenciesParent();
1663
1664 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1665 {
1666 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1667 if ( eww )
1668 {
1669 eww->parentFormValueChanged( attribute, newValue );
1670 }
1671 }
1672}
1673
1675{
1676 return mNeedsGeometry;
1677}
1678
1679void QgsAttributeForm::synchronizeState()
1680{
1681 bool isEditable = false;
1682 switch ( mMode )
1683 {
1690 isEditable = mFeature.isValid() && mLayer->isEditable();
1691 break;
1692
1694 isEditable = mLayer->isEditable();
1695 break;
1696
1698 isEditable = true;
1699 break;
1700 }
1701
1702 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1703 {
1704 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1705 if ( eww )
1706 {
1707 const QList<QgsAttributeFormEditorWidget *> formWidgets = mFormEditorWidgets.values( eww->fieldIdx() );
1708
1709 for ( QgsAttributeFormEditorWidget *formWidget : formWidgets )
1710 {
1711 formWidget->setConstraintResultVisible( isEditable );
1712 }
1713
1714 eww->setConstraintResultVisible( isEditable );
1715
1716 bool enabled = isEditable && fieldIsEditable( eww->fieldIdx() );
1717 ww->setEnabled( enabled );
1718
1719 updateIcon( eww );
1720 }
1721 else // handle QgsWidgetWrapper different than QgsEditorWidgetWrapper
1722 {
1723 ww->setEnabled( isEditable );
1724 }
1725 }
1726
1727
1729 {
1730 if ( mMode == QgsAttributeEditorContext::Mode::MultiEditMode && mLayer->selectedFeatureCount() == 0 )
1731 {
1732 isEditable = false;
1733 if ( mConstraintsFailMessageBarItem )
1734 {
1735 mMessageBar->popWidget( mConstraintsFailMessageBarItem );
1736 }
1737 mConstraintsFailMessageBarItem = new QgsMessageBarItem( tr( "Multi edit mode requires at least one selected feature." ), Qgis::MessageLevel::Info, -1 );
1738 mMessageBar->pushItem( mConstraintsFailMessageBarItem );
1739 }
1740 else
1741 {
1742 QStringList invalidFields, descriptions;
1743 mValidConstraints = currentFormValidHardConstraints( invalidFields, descriptions );
1744
1745 if ( isEditable && mContext.formMode() == QgsAttributeEditorContext::Embed )
1746 {
1747 if ( !mValidConstraints && !mConstraintsFailMessageBarItem )
1748 {
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 );
1752 }
1753 else if ( mValidConstraints && mConstraintsFailMessageBarItem )
1754 {
1755 mMessageBar->popWidget( mConstraintsFailMessageBarItem );
1756 mConstraintsFailMessageBarItem = nullptr;
1757 }
1758 }
1759 else if ( mConstraintsFailMessageBarItem )
1760 {
1761 mMessageBar->popWidget( mConstraintsFailMessageBarItem );
1762 mConstraintsFailMessageBarItem = nullptr;
1763 }
1764
1765 isEditable = isEditable & mValidConstraints;
1766 }
1767 }
1768
1769 // change OK button status
1770 QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
1771 if ( okButton )
1772 {
1773 okButton->setEnabled( isEditable );
1774 }
1775}
1776
1777void QgsAttributeForm::init()
1778{
1779 QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
1780
1781 // Cleanup of any previously shown widget, we start from scratch
1782 QWidget *formWidget = nullptr;
1783 mNeedsGeometry = false;
1784
1785 bool buttonBoxVisible = true;
1786 // Cleanup button box but preserve visibility
1787 if ( mButtonBox )
1788 {
1789 buttonBoxVisible = mButtonBox->isVisible();
1790 delete mButtonBox;
1791 mButtonBox = nullptr;
1792 }
1793
1794 if ( mSearchButtonBox )
1795 {
1796 delete mSearchButtonBox;
1797 mSearchButtonBox = nullptr;
1798 }
1799
1800 qDeleteAll( mWidgets );
1801 mWidgets.clear();
1802
1803 while ( QWidget *w = this->findChild<QWidget *>() )
1804 {
1805 delete w;
1806 }
1807 delete layout();
1808
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 );
1814
1815 setLayout( vl );
1816
1817 // Get a layout
1818 QGridLayout *layout = new QGridLayout();
1819 QWidget *container = new QWidget();
1820 container->setLayout( layout );
1821 vl->addWidget( container );
1822
1823 mFormEditorWidgets.clear();
1824 mFormWidgets.clear();
1825
1826 // a bar to warn the user with non-blocking messages
1827 setContentsMargins( 0, 0, 0, 0 );
1828
1829 // Try to load Ui-File for layout
1830 if ( mContext.allowCustomUi() && mLayer->editFormConfig().layout() == Qgis::AttributeFormLayout::UiFile && !mLayer->editFormConfig().uiForm().isEmpty() )
1831 {
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 ) )
1836 {
1837 QUiLoader loader;
1838
1839 QFileInfo fi( file->fileName() );
1840 loader.setWorkingDirectory( fi.dir() );
1841 formWidget = loader.load( file, this );
1842 if ( formWidget )
1843 {
1844 formWidget->setWindowFlags( Qt::Widget );
1845 layout->addWidget( formWidget );
1846 formWidget->show();
1847 file->close();
1848 mButtonBox = findChild<QDialogButtonBox *>();
1849 createWrappers();
1850
1851 formWidget->installEventFilter( this );
1852 }
1853 }
1854 }
1855
1856 QgsTabWidget *tabWidget = nullptr;
1857
1858 // Tab layout
1859 if ( !formWidget && mLayer->editFormConfig().layout() == Qgis::AttributeFormLayout::DragAndDrop )
1860 {
1861 int row = 0;
1862 int column = 0;
1863 int columnCount = 1;
1864 bool hasRootFields = false;
1865 bool addSpacer = true;
1866
1867 const QList<QgsAttributeEditorElement *> tabs = mLayer->editFormConfig().tabs();
1868
1869 for ( QgsAttributeEditorElement *widgDef : tabs )
1870 {
1871 if ( widgDef->type() == Qgis::AttributeEditorType::Container )
1872 {
1873 QgsAttributeEditorContainer *containerDef = dynamic_cast<QgsAttributeEditorContainer *>( widgDef );
1874 if ( !containerDef )
1875 continue;
1876
1877 switch ( containerDef->type() )
1878 {
1880 {
1881 tabWidget = nullptr;
1882 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1883 if ( widgetInfo.labelStyle.overrideColor )
1884 {
1885 if ( widgetInfo.labelStyle.color.isValid() )
1886 {
1887 widgetInfo.widget->setStyleSheet( u"QGroupBox::title { color: %1; }"_s.arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1888 }
1889 }
1890 if ( widgetInfo.labelStyle.overrideFont )
1891 {
1892 widgetInfo.widget->setFont( widgetInfo.labelStyle.font );
1893 }
1894
1895 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1896 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1897 {
1898 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1899 }
1900 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1901 {
1902 layout->setRowStretch( row, widgDef->verticalStretch() );
1903 addSpacer = false;
1904 }
1905 else
1906 {
1907 if ( widgetInfo.expandingNeeded )
1908 {
1909 addSpacer = false;
1910 widgetInfo.widget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
1911 }
1912 else
1913 {
1914 widgetInfo.widget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Maximum );
1915 }
1916 }
1917
1918 if ( containerDef->visibilityExpression().enabled() || containerDef->collapsedExpression().enabled() )
1919 {
1920 registerContainerInformation( new ContainerInformation(
1921 widgetInfo.widget,
1922 containerDef->visibilityExpression().enabled() ? containerDef->visibilityExpression().data() : QgsExpression(),
1923 containerDef->collapsed(),
1924 containerDef->collapsedExpression().enabled() ? containerDef->collapsedExpression().data() : QgsExpression()
1925 ) );
1926 }
1927 column += 2;
1928 break;
1929 }
1930
1932 {
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 ) )
1937 {
1938 layout->setRowStretch( row, widgDef->verticalStretch() );
1939 addSpacer = false;
1940 }
1941 else
1942 {
1943 if ( widgetInfo.expandingNeeded )
1944 {
1945 addSpacer = false;
1946 widgetInfo.widget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
1947 }
1948 else
1949 {
1950 widgetInfo.widget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Maximum );
1951 }
1952 }
1953 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1954 {
1955 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1956 }
1957
1958 if ( containerDef->visibilityExpression().enabled() || containerDef->collapsedExpression().enabled() )
1959 {
1960 registerContainerInformation( new ContainerInformation(
1961 widgetInfo.widget,
1962 containerDef->visibilityExpression().enabled() ? containerDef->visibilityExpression().data() : QgsExpression(),
1963 containerDef->collapsed(),
1964 containerDef->collapsedExpression().enabled() ? containerDef->collapsedExpression().data() : QgsExpression()
1965 ) );
1966 }
1967 column += 2;
1968 break;
1969 }
1970
1972 {
1973 if ( !tabWidget )
1974 {
1975 tabWidget = new QgsTabWidget();
1976 layout->addWidget( tabWidget, row, column, 1, 2 );
1977 column += 2;
1978 }
1979
1980 QWidget *tabPage = new QWidget( tabWidget );
1981 tabWidget->addTab( tabPage, widgDef->name() );
1982 tabWidget->setTabStyle( tabWidget->tabBar()->count() - 1, widgDef->labelStyle() );
1983
1984 if ( containerDef->visibilityExpression().enabled() )
1985 {
1986 registerContainerInformation( new ContainerInformation( tabWidget, tabPage, containerDef->visibilityExpression().data() ) );
1987 }
1988 QGridLayout *tabPageLayout = new QGridLayout();
1989 tabPage->setLayout( tabPageLayout );
1990
1991 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, tabPage, mLayer, mContext );
1992 if ( widgetInfo.expandingNeeded )
1993 {
1994 addSpacer = false;
1995 widgetInfo.widget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
1996 }
1997 tabPageLayout->addWidget( widgetInfo.widget );
1998 break;
1999 }
2000 }
2001 }
2002 else if ( widgDef->type() == Qgis::AttributeEditorType::Relation )
2003 {
2004 hasRootFields = true;
2005 tabWidget = nullptr;
2006 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
2007 QgsCollapsibleGroupBox *collapsibleGroupBox = new QgsCollapsibleGroupBox();
2008
2009 if ( widgetInfo.showLabel )
2010 {
2011 if ( widgetInfo.labelStyle.overrideColor && widgetInfo.labelStyle.color.isValid() )
2012 {
2013 collapsibleGroupBox->setStyleSheet( u"QGroupBox::title { color: %1; }"_s.arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2014 }
2015
2016 if ( widgetInfo.labelStyle.overrideFont )
2017 {
2018 collapsibleGroupBox->setFont( widgetInfo.labelStyle.font );
2019 }
2020
2021 collapsibleGroupBox->setTitle( widgetInfo.labelText );
2022 }
2023
2024 QVBoxLayout *collapsibleGroupBoxLayout = new QVBoxLayout();
2025 collapsibleGroupBoxLayout->addWidget( widgetInfo.widget );
2026 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
2027
2028 QVBoxLayout *c = new QVBoxLayout();
2029 c->addWidget( collapsibleGroupBox );
2030 layout->addLayout( c, row, column, 1, 2 );
2031
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() );
2036
2037 column += 2;
2038
2039 // we consider all relation editors should be expanding
2040 addSpacer = false;
2041 }
2042 else
2043 {
2044 hasRootFields = true;
2045 tabWidget = nullptr;
2046 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
2047 QWidget *labelWidget = new QWidget(); //to group label and comment icon in one widget
2048 QLabel *label = new QLabel( widgetInfo.labelText, labelWidget );
2049
2050 if ( widgetInfo.labelStyle.overrideColor )
2051 {
2052 if ( widgetInfo.labelStyle.color.isValid() )
2053 {
2054 label->setStyleSheet( u"QLabel { color: %1; }"_s.arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2055 }
2056 }
2057
2058 if ( widgetInfo.labelStyle.overrideFont )
2059 {
2060 label->setFont( widgetInfo.labelStyle.font );
2061 }
2062
2063 labelWidget->setToolTip( widgetInfo.toolTip );
2064 if ( columnCount > 1 && !widgetInfo.labelOnTop )
2065 {
2066 label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
2067 }
2068
2069 label->setBuddy( widgetInfo.widget );
2070
2071 // Indicator button when a comment is available for the field
2072 QToolButton *commentInfoButton = createCommentInfoButton( labelWidget );
2073 // Only visible when there is a comment available
2074 if ( !widgetInfo.hint.isEmpty() )
2075 commentInfoButton->setVisible( true );
2076
2077 // If at least one expanding widget is present do not add a spacer
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 )
2082 addSpacer = false;
2083
2084 QHBoxLayout *labelLayout = new QHBoxLayout( labelWidget );
2085 labelLayout->addWidget( label );
2086 labelLayout->addWidget( commentInfoButton );
2087 labelLayout->setContentsMargins( 0, 0, 0, 0 );
2088
2089 if ( !widgetInfo.showLabel )
2090 {
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 );
2095
2096 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
2097 {
2098 layout->setRowStretch( row, widgDef->verticalStretch() );
2099 addSpacer = false;
2100 }
2101 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
2102 {
2103 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
2104 }
2105
2106 column += 2;
2107 }
2108 else if ( widgetInfo.labelOnTop )
2109 {
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 );
2115
2116 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
2117 {
2118 layout->setRowStretch( row, widgDef->verticalStretch() );
2119 addSpacer = false;
2120 }
2121 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
2122 {
2123 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
2124 }
2125
2126 column += 2;
2127 }
2128 else
2129 {
2130 const int widgetColumn = column + 1;
2131 layout->addWidget( labelWidget, row, column++ );
2132 layout->addWidget( widgetInfo.widget, row, column++ );
2133
2134 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
2135 {
2136 layout->setRowStretch( row, widgDef->verticalStretch() );
2137 addSpacer = false;
2138 }
2139 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( widgetColumn ) )
2140 {
2141 layout->setColumnStretch( widgetColumn, widgDef->horizontalStretch() );
2142 }
2143 }
2144
2145 // Alias DD overrides
2146 if ( widgDef->type() == Qgis::AttributeEditorType::Field )
2147 {
2148 const QgsAttributeEditorField *fieldElement { static_cast<QgsAttributeEditorField *>( widgDef ) };
2149 const int fieldIdx = fieldElement->idx();
2150 if ( fieldIdx >= 0 && fieldIdx < mLayer->fields().count() )
2151 {
2152 const QString fieldName { mLayer->fields().at( fieldIdx ).name() };
2153 label->setObjectName( fieldName );
2154
2155 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Alias ) )
2156 {
2157 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Alias ) };
2158 if ( property.isActive() )
2159 {
2160 mLabelDataDefinedProperties[label] = property;
2161 }
2162 }
2163 labelWidget->setObjectName( fieldName );
2164 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::CustomComment ) )
2165 {
2166 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::CustomComment ) };
2167 if ( property.isActive() )
2168 {
2169 mCustomCommentDataDefinedProperties[labelWidget] = property;
2170 commentInfoButton->setVisible( true );
2171 }
2172 }
2173 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Editable ) )
2174 {
2175 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Editable ) };
2176 if ( property.isActive() )
2177 {
2178 mEditableDataDefinedProperties[widgetInfo.widget] = property;
2179 }
2180 }
2181 }
2182 }
2183 }
2184
2185 if ( column >= columnCount * 2 )
2186 {
2187 column = 0;
2188 row += 1;
2189 }
2190 }
2191
2192 if ( hasRootFields && addSpacer )
2193 {
2194 QSpacerItem *spacerItem = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
2195 layout->addItem( spacerItem, row, 0 );
2196 layout->setRowStretch( row, 1 );
2197 }
2198
2199 formWidget = container;
2200 }
2201
2202 // Autogenerate Layout
2203 // If there is still no layout loaded (defined as autogenerate or other methods failed)
2204 mIconMap.clear();
2205
2206 if ( !formWidget )
2207 {
2208 formWidget = new QWidget( this );
2209 QGridLayout *gridLayout = new QGridLayout( formWidget );
2210 formWidget->setLayout( gridLayout );
2211
2212 if ( mContext.formMode() != QgsAttributeEditorContext::Embed )
2213 {
2214 // put the form into a scroll area to nicely handle cases with lots of attributes
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 );
2222 }
2223 else
2224 {
2225 layout->addWidget( formWidget );
2226 }
2227
2228 int row = 0;
2229
2230 const QgsFields fields = mLayer->fields();
2231
2232 // Collect non-first fields of composite foreign keys — these should be
2233 // hidden since the RelationReference widget on the first field manages
2234 // all composite key values internally
2235 QSet<int> compositeHiddenFields;
2236 const QList<QgsRelation> referencingRelations = QgsProject::instance()->relationManager()->referencingRelations( mLayer );
2237 for ( const QgsRelation &rel : referencingRelations )
2238 {
2239 if ( rel.type() != Qgis::RelationshipType::Normal )
2240 continue;
2241
2242 const QList<QgsRelation::FieldPair> fieldPairs = rel.fieldPairs();
2243 if ( fieldPairs.size() > 1 )
2244 {
2245 for ( int i = 1; i < fieldPairs.size(); i++ )
2246 {
2247 const int idx = fields.lookupField( fieldPairs.at( i ).referencingField() );
2248 if ( idx >= 0 )
2249 compositeHiddenFields.insert( idx );
2250 }
2251 }
2252 }
2253
2254 for ( const QgsField &field : fields )
2255 {
2256 const QString fieldName = field.name();
2257 int idx = fields.lookupField( fieldName );
2258 if ( idx < 0 )
2259 continue;
2260
2261 if ( compositeHiddenFields.contains( idx ) )
2262 continue;
2263
2264 //show attribute alias if available
2265 QString labelText = mLayer->attributeDisplayName( idx );
2266 labelText.replace( '&', "&&"_L1 ); // need to escape '&' or they'll be replace by _ in the label text
2267
2268 const QgsEditorWidgetSetup widgetSetup = QgsGui::editorWidgetRegistry()->findBest( mLayer, field.name() );
2269
2270 if ( widgetSetup.type() == "Hidden"_L1 )
2271 continue;
2272
2273 bool labelOnTop = mLayer->editFormConfig().labelOnTop( idx );
2274
2275 // This will also create the widget
2276 QWidget *labelWidget = new QWidget(); //to group label and comment icon in one widget
2277 QLabel *label = new QLabel( labelText, labelWidget );
2278 label->setObjectName( fieldName );
2279
2280 labelWidget->setToolTip( QgsFieldModel::fieldToolTipExtended( field, mLayer ) );
2281
2282 // Indicator button when a comment is available for the field
2283 QToolButton *commentInfoButton = createCommentInfoButton( labelWidget );
2284 // Only visible when there is a comment available (not visible when custom comment is an empty string)
2285 if ( !field.customComment().isEmpty() || ( field.customComment().isNull() && !field.comment().isEmpty() ) )
2286 commentInfoButton->setVisible( true );
2287
2288 QSvgWidget *i = new QSvgWidget();
2289 i->setFixedSize( 18, 18 );
2290
2291 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Alias ) )
2292 {
2293 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Alias ) };
2294 if ( property.isActive() )
2295 {
2296 mLabelDataDefinedProperties[label] = property;
2297 }
2298 }
2299 labelWidget->setObjectName( fieldName );
2300 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::CustomComment ) )
2301 {
2302 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::CustomComment ) };
2303 if ( property.isActive() )
2304 {
2305 mCustomCommentDataDefinedProperties[labelWidget] = property;
2306 commentInfoButton->setVisible( true );
2307 }
2308 }
2309
2310 QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( widgetSetup.type(), mLayer, idx, widgetSetup.config(), nullptr, this, mContext );
2311
2312 QWidget *w = nullptr;
2313 if ( eww )
2314 {
2315 QgsAttributeFormEditorWidget *formWidget = new QgsAttributeFormEditorWidget( eww, widgetSetup.type(), this );
2316 w = formWidget;
2317 mFormEditorWidgets.insert( idx, formWidget );
2318 mFormWidgets.append( formWidget );
2319
2320 const Qgis::AttributeFormReuseLastValuePolicy reusePolicy = mLayer->editFormConfig().reuseLastValuePolicy( idx );
2322 {
2323 bool remember = reusePolicy == Qgis::AttributeFormReuseLastValuePolicy::AllowedDefaultOn;
2324 const QVariant rememberLastUsedValuesVariant = mLayer->property( "AttributeFormRememberLastUsedValues" );
2325 QMap<int, bool> rememberLastUsedValues = rememberLastUsedValuesVariant.value<QMap<int, bool>>();
2326 if ( rememberLastUsedValues.contains( idx ) )
2327 {
2328 remember = rememberLastUsedValues[idx];
2329 }
2330
2331 formWidget->setRememberLastValue( remember );
2332 connect( formWidget, &QgsAttributeFormEditorWidget::rememberLastValueChanged, this, [this]( int idx, bool remember ) {
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 ) );
2337 } );
2338 }
2339
2340 formWidget->createSearchWidgetWrappers( mContext );
2341
2342 label->setBuddy( eww->widget() );
2343
2344 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Editable ) )
2345 {
2346 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Editable ) };
2347 if ( property.isActive() )
2348 {
2349 mEditableDataDefinedProperties[formWidget] = property;
2350 }
2351 }
2352 }
2353 else
2354 {
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() ) ) );
2356 }
2357
2358
2359 if ( w )
2360 w->setObjectName( field.name() );
2361
2362 if ( eww )
2363 {
2364 mWidgets.append( eww );
2365 mIconMap[eww->widget()] = i;
2366 }
2367
2368 QHBoxLayout *labelLayout = new QHBoxLayout( labelWidget );
2369 labelLayout->addWidget( label );
2370 labelLayout->addWidget( commentInfoButton );
2371 labelLayout->setContentsMargins( 0, 0, 0, 0 );
2372
2373 if ( labelOnTop )
2374 {
2375 gridLayout->addWidget( labelWidget, row++, 0, 1, 2 );
2376 gridLayout->addWidget( w, row++, 0, 1, 2 );
2377 gridLayout->addWidget( i, row++, 0, 1, 2 );
2378 }
2379 else
2380 {
2381 int widgetColumn = 0;
2382 gridLayout->addWidget( labelWidget, row, widgetColumn++ );
2383 gridLayout->addWidget( w, row, widgetColumn++ );
2384 gridLayout->addWidget( i, row++, widgetColumn++ );
2385 }
2386 }
2387
2388 const QList<QgsRelation> relations = QgsProject::instance()->relationManager()->referencedRelations( mLayer );
2389 for ( const QgsRelation &rel : relations )
2390 {
2391 QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( u"relation_editor"_s, rel, mContext );
2392
2393 QgsAttributeFormRelationEditorWidget *formWidget = new QgsAttributeFormRelationEditorWidget( rww, this );
2394 formWidget->createSearchWidgetWrappers( mContext );
2395
2396 QgsCollapsibleGroupBox *collapsibleGroupBox = new QgsCollapsibleGroupBox( rel.name() );
2397 QVBoxLayout *collapsibleGroupBoxLayout = new QVBoxLayout();
2398 collapsibleGroupBoxLayout->addWidget( formWidget );
2399 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
2400
2401 gridLayout->addWidget( collapsibleGroupBox, row++, 0, 1, 2 );
2402
2403 mWidgets.append( rww );
2404 mFormWidgets.append( formWidget );
2405 }
2406
2407 if ( QgsProject::instance()->relationManager()->referencedRelations( mLayer ).isEmpty() )
2408 {
2409 QSpacerItem *spacerItem = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
2410 gridLayout->addItem( spacerItem, row, 0 );
2411 gridLayout->setRowStretch( row, 1 );
2412 row++;
2413 }
2414 }
2415
2416 // Prepare value dependencies
2417 updateFieldDependencies();
2418
2419 if ( !mButtonBox )
2420 {
2421 mButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
2422 mButtonBox->setObjectName( u"buttonBox"_s );
2423 layout->addWidget( mButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2424 }
2425 mButtonBox->setVisible( buttonBoxVisible );
2426
2427 if ( !mSearchButtonBox )
2428 {
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 );
2434
2435 QPushButton *clearButton = new QPushButton( tr( "&Reset Form" ), mSearchButtonBox );
2436 connect( clearButton, &QPushButton::clicked, this, &QgsAttributeForm::resetSearch );
2437 boxLayout->addWidget( clearButton );
2438 boxLayout->addStretch( 1 );
2439
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 );
2445
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" ) );
2450 connect( openAttributeTableButton, &QToolButton::clicked, this, [this] { emit openFilteredFeaturesAttributeTable( createFilterExpression() ); } );
2451 boxLayout->addWidget( openAttributeTableButton );
2452
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 );
2458
2459 QToolButton *selectButton = new QToolButton();
2460 selectButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2461 selectButton->setText( tr( "&Select Features" ) );
2462 selectButton->setIcon( QgsApplication::getThemeIcon( u"/mIconFormSelect.svg"_s ) );
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 );
2468 selectAction->setIcon( QgsApplication::getThemeIcon( u"/mIconFormSelect.svg"_s ) );
2469 connect( selectAction, &QAction::triggered, this, &QgsAttributeForm::searchSetSelection );
2470 selectMenu->addAction( selectAction );
2471 QAction *addSelectAction = new QAction( tr( "Add to Current Selection" ), selectMenu );
2472 addSelectAction->setIcon( QgsApplication::getThemeIcon( u"/mIconSelectAdd.svg"_s ) );
2473 connect( addSelectAction, &QAction::triggered, this, &QgsAttributeForm::searchAddToSelection );
2474 selectMenu->addAction( addSelectAction );
2475 QAction *deselectAction = new QAction( tr( "Remove from Current Selection" ), selectMenu );
2476 deselectAction->setIcon( QgsApplication::getThemeIcon( u"/mIconSelectRemove.svg"_s ) );
2477 connect( deselectAction, &QAction::triggered, this, &QgsAttributeForm::searchRemoveFromSelection );
2478 selectMenu->addAction( deselectAction );
2479 QAction *filterSelectAction = new QAction( tr( "Filter Current Selection" ), selectMenu );
2480 filterSelectAction->setIcon( QgsApplication::getThemeIcon( u"/mIconSelectIntersect.svg"_s ) );
2481 connect( filterSelectAction, &QAction::triggered, this, &QgsAttributeForm::searchIntersectSelection );
2482 selectMenu->addAction( filterSelectAction );
2483 selectButton->setMenu( selectMenu );
2484 boxLayout->addWidget( selectButton );
2485
2486 if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
2487 {
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 );
2502 }
2503 else
2504 {
2505 QPushButton *closeButton = new QPushButton( tr( "Close" ), mSearchButtonBox );
2506 connect( closeButton, &QPushButton::clicked, this, &QgsAttributeForm::closed );
2507 closeButton->setShortcut( Qt::Key_Escape );
2508 boxLayout->addWidget( closeButton );
2509 }
2510
2511 layout->addWidget( mSearchButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2512 }
2513 mSearchButtonBox->setVisible( mMode == QgsAttributeEditorContext::SearchMode );
2514
2515 afterWidgetInit();
2516
2517 connect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsAttributeForm::save );
2518 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsAttributeForm::resetValues );
2519
2520 connect( mLayer, &QgsVectorLayer::editingStarted, this, &QgsAttributeForm::synchronizeState );
2521 connect( mLayer, &QgsVectorLayer::editingStopped, this, &QgsAttributeForm::synchronizeState );
2522
2523 // This triggers a refresh of the form widget and gives a chance to re-format the
2524 // value to those widgets that have a different representation when in edit mode
2527
2528
2529 const auto constMInterfaces = mInterfaces;
2530 for ( QgsAttributeFormInterface *iface : constMInterfaces )
2531 {
2532 iface->initForm();
2533 }
2534
2535 if ( mContext.formMode() == QgsAttributeEditorContext::Embed || mMode == QgsAttributeEditorContext::SearchMode )
2536 {
2537 hideButtonBox();
2538 }
2539
2540 QApplication::restoreOverrideCursor();
2541}
2542
2543void QgsAttributeForm::cleanPython()
2544{
2545 if ( !mPyFormVarName.isNull() )
2546 {
2547 QString expr = u"if '%1' in locals(): del %1\n"_s.arg( mPyFormVarName );
2548 QgsPythonRunner::run( expr );
2549 }
2550}
2551
2552void QgsAttributeForm::initPython()
2553{
2554 cleanPython();
2555
2556 // Init Python, if init function is not empty and the combo indicates
2557 // the source for the function code
2558 if ( !mLayer->editFormConfig().initFunction().isEmpty() && mLayer->editFormConfig().initCodeSource() != Qgis::AttributeFormPythonInitCodeSource::NoSource )
2559 {
2561 if ( !allowed )
2562 {
2563 mMessageBar->pushMessage( tr( "Security warning" ), tr( "The attribute form contains an embedded script which has been denied execution." ), Qgis::MessageLevel::Warning );
2564 return;
2565 }
2566
2567 QString initFunction = mLayer->editFormConfig().initFunction();
2568 QString initFilePath = mLayer->editFormConfig().initFilePath();
2569 QString initCode;
2570
2571 switch ( mLayer->editFormConfig().initCodeSource() )
2572 {
2574 if ( !initFilePath.isEmpty() )
2575 {
2576 QFile *inputFile = QgsApplication::networkContentFetcherRegistry()->localFile( initFilePath );
2577
2578 if ( inputFile && inputFile->open( QFile::ReadOnly ) )
2579 {
2580 // Read it into a string
2581 QTextStream inf( inputFile );
2582 initCode = inf.readAll();
2583 inputFile->close();
2584 }
2585 else // The file couldn't be opened
2586 {
2587 QgsLogger::warning( u"The external python file path %1 could not be opened!"_s.arg( initFilePath ) );
2588 }
2589 }
2590 else
2591 {
2592 QgsLogger::warning( u"The external python file path is empty!"_s );
2593 }
2594 break;
2595
2597 initCode = mLayer->editFormConfig().initCode();
2598 if ( initCode.isEmpty() )
2599 {
2600 QgsLogger::warning( u"The python code provided in the dialog is empty!"_s );
2601 }
2602 break;
2603
2606 // Nothing to do: the function code should be already in the environment
2607 break;
2608 }
2609
2610 // If we have a function code, run it
2611 if ( !initCode.isEmpty() )
2612 {
2614 {
2615 QgsPythonRunner::run( initCode );
2616 }
2617 else
2618 {
2619 mMessageBar->pushMessage( QString(), tr( "Python macro could not be run due to missing permissions." ), Qgis::MessageLevel::Warning );
2620 }
2621 }
2622
2623 QgsPythonRunner::run( u"import inspect"_s );
2624 QString numArgs;
2625
2626 // Check for eval result
2627 if ( QgsPythonRunner::eval( u"len(inspect.getfullargspec(%1)[0])"_s.arg( initFunction ), numArgs ) )
2628 {
2629 static int sFormId = 0;
2630 mPyFormVarName = u"_qgis_featureform_%1_%2"_s.arg( mFormNr ).arg( sFormId++ );
2631
2632 QString form = u"%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )"_s.arg( mPyFormVarName ).arg( ( quint64 ) this );
2633
2634 QgsPythonRunner::run( form );
2635
2636 QgsDebugMsgLevel( u"running featureForm init: %1"_s.arg( mPyFormVarName ), 2 );
2637
2638 // Legacy
2639 if ( numArgs == "3"_L1 )
2640 {
2641 addInterface( new QgsAttributeFormLegacyInterface( initFunction, mPyFormVarName, this ) );
2642 }
2643 else
2644 {
2645 // If we get here, it means that the function doesn't accept three arguments
2646 QMessageBox msgBox;
2647 msgBox.setText(
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 )
2649 );
2650 msgBox.exec();
2651#if 0
2652 QString expr = QString( "%1(%2)" )
2653 .arg( mLayer->editFormInit() )
2654 .arg( mPyFormVarName );
2655 QgsAttributeFormInterface *iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface *>( expr, "QgsAttributeFormInterface" );
2656 if ( iface )
2657 addInterface( iface );
2658#endif
2659 }
2660 }
2661 else
2662 {
2663 // If we get here, it means that inspect couldn't find the function
2664 QMessageBox msgBox;
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 ) );
2666 msgBox.exec();
2667 }
2668 }
2669}
2670
2671QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAttributeEditorElement *widgetDef, QWidget *parent, QgsVectorLayer *vl, QgsAttributeEditorContext &context )
2672{
2673 WidgetInfo newWidgetInfo;
2674
2675 newWidgetInfo.labelStyle = widgetDef->labelStyle();
2676
2677 switch ( widgetDef->type() )
2678 {
2680 {
2681 const QgsAttributeEditorAction *elementDef = dynamic_cast<const QgsAttributeEditorAction *>( widgetDef );
2682 if ( !elementDef )
2683 break;
2684
2685 QgsActionWidgetWrapper *actionWrapper = new QgsActionWidgetWrapper( mLayer, nullptr, this, mMessageBar );
2686 actionWrapper->setAction( elementDef->action( vl ) );
2687 context.setAttributeFormMode( mMode );
2688 actionWrapper->setContext( context );
2689 mWidgets.append( actionWrapper );
2690 newWidgetInfo.widget = actionWrapper->widget();
2691 newWidgetInfo.showLabel = false;
2692
2693 break;
2694 }
2695
2697 {
2698 const QgsAttributeEditorField *fieldDef = dynamic_cast<const QgsAttributeEditorField *>( widgetDef );
2699 if ( !fieldDef )
2700 break;
2701
2702 const QgsFields fields = vl->fields();
2703 int fldIdx = fields.lookupField( fieldDef->name() );
2704 if ( fldIdx < fields.count() && fldIdx >= 0 )
2705 {
2706 const QgsEditorWidgetSetup widgetSetup = QgsGui::editorWidgetRegistry()->findBest( mLayer, fieldDef->name() );
2707
2708 QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( widgetSetup.type(), mLayer, fldIdx, widgetSetup.config(), nullptr, this, mContext );
2709 QgsAttributeFormEditorWidget *formWidget = new QgsAttributeFormEditorWidget( eww, widgetSetup.type(), this );
2710 mFormEditorWidgets.insert( fldIdx, formWidget );
2711 mFormWidgets.append( formWidget );
2712
2713 const Qgis::AttributeFormReuseLastValuePolicy reusePolicy = mLayer->editFormConfig().reuseLastValuePolicy( fldIdx );
2715 {
2716 bool remember = reusePolicy == Qgis::AttributeFormReuseLastValuePolicy::AllowedDefaultOn;
2717 const QVariant rememberLastUsedValuesVariant = mLayer->property( "AttributeFormRememberLastUsedValues" );
2718 QMap<int, bool> rememberLastUsedValues = rememberLastUsedValuesVariant.value<QMap<int, bool>>();
2719 if ( rememberLastUsedValues.contains( fldIdx ) )
2720 {
2721 remember = rememberLastUsedValues[fldIdx];
2722 }
2723 formWidget->setRememberLastValue( remember );
2724 connect( formWidget, &QgsAttributeFormEditorWidget::rememberLastValueChanged, this, [this]( int idx, bool remember ) {
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 ) );
2729 } );
2730 }
2731
2732 formWidget->createSearchWidgetWrappers( mContext );
2733
2734 newWidgetInfo.widget = formWidget;
2735 mWidgets.append( eww );
2736
2737 newWidgetInfo.widget->setObjectName( fields.at( fldIdx ).name() );
2738 const QString customComment = fields.at( fldIdx ).customComment();
2739 newWidgetInfo.hint = customComment.isNull() ? fields.at( fldIdx ).comment() : customComment;
2740 }
2741
2742 newWidgetInfo.labelOnTop = mLayer->editFormConfig().labelOnTop( fldIdx );
2743 newWidgetInfo.labelText = mLayer->attributeDisplayName( fldIdx );
2744 newWidgetInfo.labelText.replace( '&', "&&"_L1 ); // need to escape '&' or they'll be replace by _ in the label text
2745
2746 newWidgetInfo.toolTip = ( QgsFieldModel::fieldToolTipExtended( fields.at( fldIdx ), mLayer ) );
2747 newWidgetInfo.showLabel = widgetDef->showLabel();
2748
2749 break;
2750 }
2751
2753 {
2754 const QgsAttributeEditorRelation *relDef = static_cast<const QgsAttributeEditorRelation *>( widgetDef );
2755
2756 QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( relDef->relationWidgetTypeId(), relDef->relation(), context );
2757
2758 QgsAttributeFormRelationEditorWidget *formWidget = new QgsAttributeFormRelationEditorWidget( rww, this );
2759 formWidget->createSearchWidgetWrappers( mContext );
2760
2761 // This needs to be after QgsAttributeFormRelationEditorWidget creation, because the widget
2762 // does not exists yet until QgsAttributeFormRelationEditorWidget is created and the setters
2763 // below directly alter the widget and check for it.
2765 rww->setNmRelationId( relDef->nmRelationId() );
2767
2768 mWidgets.append( rww );
2769 mFormWidgets.append( formWidget );
2770
2771 newWidgetInfo.widget = formWidget;
2772 newWidgetInfo.showLabel = relDef->showLabel();
2773 newWidgetInfo.labelText = relDef->label();
2774 if ( newWidgetInfo.labelText.isEmpty() )
2775 newWidgetInfo.labelText = rww->relation().name();
2776 newWidgetInfo.labelOnTop = true;
2777 break;
2778 }
2779
2781 {
2782 const QgsAttributeEditorContainer *container = dynamic_cast<const QgsAttributeEditorContainer *>( widgetDef );
2783 if ( !container )
2784 break;
2785
2786 int columnCount = container->columnCount();
2787
2788 if ( columnCount <= 0 )
2789 columnCount = 1;
2790
2791 QString widgetName;
2792 QWidget *myContainer = nullptr;
2793 bool removeLayoutMargin = false;
2794 switch ( container->type() )
2795 {
2797 {
2798 QgsCollapsibleGroupBoxBasic *groupBox = new QgsCollapsibleGroupBoxBasic();
2799 widgetName = u"QGroupBox"_s;
2800 if ( container->showLabel() )
2801 {
2802 groupBox->setTitle( container->name() );
2803 if ( newWidgetInfo.labelStyle.overrideColor )
2804 {
2805 if ( newWidgetInfo.labelStyle.color.isValid() )
2806 {
2807 groupBox->setStyleSheet( u"QGroupBox::title { color: %1; }"_s.arg( newWidgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2808 }
2809 }
2810 if ( newWidgetInfo.labelStyle.overrideFont )
2811 {
2812 groupBox->setFont( newWidgetInfo.labelStyle.font );
2813 }
2814 }
2815 myContainer = groupBox;
2816 newWidgetInfo.widget = myContainer;
2817 groupBox->setCollapsed( container->collapsed() );
2818 break;
2819 }
2820
2822 {
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();
2829 break;
2830 }
2831
2833 {
2834 myContainer = new QWidget();
2835
2836 QgsScrollArea *scrollArea = new QgsScrollArea( parent );
2837
2838 scrollArea->setWidget( myContainer );
2839 scrollArea->setWidgetResizable( true );
2840 scrollArea->setFrameShape( QFrame::NoFrame );
2841 widgetName = u"QScrollArea QWidget"_s;
2842
2843 newWidgetInfo.widget = scrollArea;
2844 break;
2845 }
2846 }
2847
2848 if ( container->backgroundColor().isValid() )
2849 {
2850 QString style { u"background-color: %1;"_s.arg( container->backgroundColor().name() ) };
2851 newWidgetInfo.widget->setStyleSheet( style );
2852 }
2853
2854 QGridLayout *gbLayout = new QGridLayout();
2855 if ( removeLayoutMargin )
2856 gbLayout->setContentsMargins( 0, 0, 0, 0 );
2857 myContainer->setLayout( gbLayout );
2858
2859 int row = 0;
2860 int column = 0;
2861 bool addSpacer = true;
2862
2863 const QList<QgsAttributeEditorElement *> children = container->children();
2864
2865 for ( QgsAttributeEditorElement *childDef : children )
2866 {
2867 WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context );
2868
2869 if ( childDef->type() == Qgis::AttributeEditorType::Container )
2870 {
2871 QgsAttributeEditorContainer *containerDef = static_cast<QgsAttributeEditorContainer *>( childDef );
2872 if ( containerDef->visibilityExpression().enabled() || containerDef->collapsedExpression().enabled() )
2873 {
2874 registerContainerInformation( new ContainerInformation(
2875 widgetInfo.widget,
2876 containerDef->visibilityExpression().enabled() ? containerDef->visibilityExpression().data() : QgsExpression(),
2877 containerDef->collapsed(),
2878 containerDef->collapsedExpression().enabled() ? containerDef->collapsedExpression().data() : QgsExpression()
2879 ) );
2880 }
2881 if ( childDef->verticalStretch() == 0 )
2882 {
2883 if ( widgetInfo.expandingNeeded )
2884 {
2885 widgetInfo.widget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
2886 }
2887 else
2888 {
2889 widgetInfo.widget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Maximum );
2890 }
2891 }
2892 }
2893
2894 // column containing the actual widget, not the label
2895 int widgetColumn = column;
2896
2897 if ( widgetInfo.labelText.isNull() || !widgetInfo.showLabel )
2898 {
2899 gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 );
2900 widgetColumn = column + 1;
2901 column += 2;
2902 }
2903 else
2904 {
2905 QWidget *labelWidget = new QWidget(); //to group label and comment icon in one widget
2906 QLabel *mypLabel = new QLabel( widgetInfo.labelText, labelWidget );
2907
2908 if ( widgetInfo.labelStyle.overrideColor )
2909 {
2910 if ( widgetInfo.labelStyle.color.isValid() )
2911 {
2912 mypLabel->setStyleSheet( u"QLabel { color: %1; }"_s.arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2913 }
2914 }
2915
2916 if ( widgetInfo.labelStyle.overrideFont )
2917 {
2918 mypLabel->setFont( widgetInfo.labelStyle.font );
2919 }
2920
2921 // Indicator button when a comment is available for the field
2922 QToolButton *commentInfoButton = createCommentInfoButton( labelWidget );
2923 // Only visible when there is a comment available
2924 if ( !widgetInfo.hint.isEmpty() )
2925 commentInfoButton->setVisible( true );
2926
2927 // Alias DD overrides
2928 if ( childDef->type() == Qgis::AttributeEditorType::Field )
2929 {
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 )
2934 {
2935 const QString fieldName { fields.at( fldIdx ).name() };
2936 mypLabel->setObjectName( fieldName );
2937
2938 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Alias ) )
2939 {
2940 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Alias ) };
2941 if ( property.isActive() )
2942 {
2943 mLabelDataDefinedProperties[mypLabel] = property;
2944 }
2945 }
2946 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Editable ) )
2947 {
2948 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Editable ) };
2949 if ( property.isActive() )
2950 {
2951 mEditableDataDefinedProperties[widgetInfo.widget] = property;
2952 }
2953 }
2954 labelWidget->setObjectName( fieldName );
2955 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::CustomComment ) )
2956 {
2957 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::CustomComment ) };
2958 if ( property.isActive() )
2959 {
2960 mCustomCommentDataDefinedProperties[labelWidget] = property;
2961 }
2962 }
2963 }
2964 }
2965
2966 labelWidget->setToolTip( widgetInfo.toolTip );
2967 if ( columnCount > 1 && !widgetInfo.labelOnTop )
2968 {
2969 mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
2970 }
2971
2972 mypLabel->setBuddy( widgetInfo.widget );
2973
2974 QHBoxLayout *labelLayout = new QHBoxLayout( labelWidget );
2975 labelLayout->addWidget( mypLabel );
2976 labelLayout->addWidget( commentInfoButton );
2977 labelLayout->setContentsMargins( 0, 0, 0, 0 );
2978
2979 if ( widgetInfo.labelOnTop )
2980 {
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 );
2987 column += 2;
2988 }
2989 else
2990 {
2991 widgetColumn = column + 1;
2992 gbLayout->addWidget( labelWidget, row, column++ );
2993 gbLayout->addWidget( widgetInfo.widget, row, column++ );
2994 }
2995 }
2996
2997 const int childHorizontalStretch = childDef->horizontalStretch();
2998 const int existingColumnStretch = gbLayout->columnStretch( widgetColumn );
2999 if ( childHorizontalStretch > 0 && childHorizontalStretch > existingColumnStretch )
3000 {
3001 gbLayout->setColumnStretch( widgetColumn, childHorizontalStretch );
3002 }
3003
3004 if ( childDef->verticalStretch() > 0 && childDef->verticalStretch() > gbLayout->rowStretch( row ) )
3005 {
3006 gbLayout->setRowStretch( row, childDef->verticalStretch() );
3007 }
3008
3009 if ( column >= columnCount * 2 )
3010 {
3011 column = 0;
3012 row += 1;
3013 }
3014
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 )
3019 addSpacer = false;
3020
3021 // we consider all relation editors should be expanding
3022 if ( qobject_cast<QgsAttributeFormRelationEditorWidget *>( widgetInfo.widget ) )
3023 addSpacer = false;
3024 }
3025
3026 if ( addSpacer )
3027 {
3028 QWidget *spacer = new QWidget();
3029 spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
3030 gbLayout->addWidget( spacer, ++row, 0 );
3031 gbLayout->setRowStretch( row, 1 );
3032 }
3033
3034 newWidgetInfo.labelText = QString();
3035 newWidgetInfo.labelOnTop = true;
3036 newWidgetInfo.showLabel = widgetDef->showLabel();
3037 newWidgetInfo.expandingNeeded |= !addSpacer;
3038 break;
3039 }
3040
3042 {
3043 const QgsAttributeEditorQmlElement *elementDef = static_cast<const QgsAttributeEditorQmlElement *>( widgetDef );
3044 QgsQmlWidgetWrapper *qmlWrapper = new QgsQmlWidgetWrapper( mLayer, nullptr, this );
3045 qmlWrapper->setQmlCode( elementDef->qmlCode() );
3046 context.setAttributeFormMode( mMode );
3047 qmlWrapper->setContext( context );
3048
3049 mWidgets.append( qmlWrapper );
3050
3051 newWidgetInfo.widget = qmlWrapper->widget();
3052 newWidgetInfo.labelText = elementDef->name();
3053 newWidgetInfo.labelOnTop = true;
3054 newWidgetInfo.showLabel = widgetDef->showLabel();
3055 break;
3056 }
3057
3059 {
3060 const QgsAttributeEditorHtmlElement *elementDef = static_cast<const QgsAttributeEditorHtmlElement *>( widgetDef );
3061
3062 QgsHtmlWidgetWrapper *htmlWrapper = new QgsHtmlWidgetWrapper( mLayer, nullptr, this );
3063 context.setAttributeFormMode( mMode );
3064 htmlWrapper->setHtmlCode( elementDef->htmlCode() );
3065 htmlWrapper->reinitWidget();
3066 mWidgets.append( htmlWrapper );
3067
3068 newWidgetInfo.widget = htmlWrapper->widget();
3069 newWidgetInfo.labelText = elementDef->name();
3070 newWidgetInfo.labelOnTop = true;
3071 newWidgetInfo.showLabel = widgetDef->showLabel();
3072 mNeedsGeometry |= htmlWrapper->needsGeometry();
3073 break;
3074 }
3075
3077 {
3078 const QgsAttributeEditorTextElement *elementDef = static_cast<const QgsAttributeEditorTextElement *>( widgetDef );
3079
3080 QgsTextWidgetWrapper *textWrapper = new QgsTextWidgetWrapper( mLayer, nullptr, this );
3081 context.setAttributeFormMode( mMode );
3082 textWrapper->setText( elementDef->text() );
3083 textWrapper->reinitWidget();
3084 mWidgets.append( textWrapper );
3085
3086 newWidgetInfo.widget = textWrapper->widget();
3087 newWidgetInfo.labelText = elementDef->name();
3088 newWidgetInfo.labelOnTop = false;
3089 newWidgetInfo.showLabel = widgetDef->showLabel();
3090 mNeedsGeometry |= textWrapper->needsGeometry();
3091 break;
3092 }
3093
3095 {
3096 const QgsAttributeEditorSpacerElement *elementDef = static_cast<const QgsAttributeEditorSpacerElement *>( widgetDef );
3097 QgsSpacerWidgetWrapper *spacerWrapper = new QgsSpacerWidgetWrapper( mLayer, nullptr, this );
3098 spacerWrapper->setDrawLine( elementDef->drawLine() );
3099 context.setAttributeFormMode( mMode );
3100 mWidgets.append( spacerWrapper );
3101
3102 newWidgetInfo.widget = spacerWrapper->widget();
3103 newWidgetInfo.labelOnTop = false;
3104 newWidgetInfo.showLabel = false;
3105 break;
3106 }
3107
3108 default:
3109 QgsDebugError( u"Unknown attribute editor widget type encountered..."_s );
3110 break;
3111 }
3112
3113 return newWidgetInfo;
3114}
3115
3116void QgsAttributeForm::createWrappers()
3117{
3118 QList<QWidget *> myWidgets = findChildren<QWidget *>();
3119 const QList<QgsField> fields = mLayer->fields().toList();
3120
3121 const auto constMyWidgets = myWidgets;
3122 for ( QWidget *myWidget : constMyWidgets )
3123 {
3124 // Check the widget's properties for a relation definition
3125 QVariant vRel = myWidget->property( "qgisRelation" );
3126 if ( vRel.isValid() )
3127 {
3128 QgsRelationManager *relMgr = QgsProject::instance()->relationManager();
3129 QgsRelation relation = relMgr->relation( vRel.toString() );
3130 if ( relation.isValid() )
3131 {
3132 QgsRelationWidgetWrapper *rww = new QgsRelationWidgetWrapper( mLayer, relation, myWidget, this );
3133 rww->setConfig( mLayer->editFormConfig().widgetConfig( relation.id() ) );
3134 rww->setContext( mContext );
3135 rww->widget(); // Will initialize the widget
3136 mWidgets.append( rww );
3137 }
3138 }
3139 else
3140 {
3141 const auto constFields = fields;
3142 for ( const QgsField &field : constFields )
3143 {
3144 if ( field.name() == myWidget->objectName() )
3145 {
3146 int idx = mLayer->fields().lookupField( field.name() );
3147
3148 QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( mLayer, idx, myWidget, this, mContext );
3149 mWidgets.append( eww );
3150 }
3151 }
3152 }
3153 }
3154}
3155
3156void QgsAttributeForm::afterWidgetInit()
3157{
3158 bool isFirstEww = true;
3159
3160 const auto constMWidgets = mWidgets;
3161 for ( QgsWidgetWrapper *ww : constMWidgets )
3162 {
3163 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
3164
3165 if ( eww )
3166 {
3167 if ( isFirstEww )
3168 {
3169 setFocusProxy( eww->widget() );
3170 isFirstEww = false;
3171 }
3172
3173 connect( eww, &QgsEditorWidgetWrapper::valuesChanged, this, &QgsAttributeForm::onAttributeChanged, Qt::UniqueConnection );
3174 connect( eww, &QgsEditorWidgetWrapper::constraintStatusChanged, this, &QgsAttributeForm::onConstraintStatusChanged, Qt::UniqueConnection );
3175 }
3176 else
3177 {
3178 QgsRelationWidgetWrapper *relationWidgetWrapper = qobject_cast<QgsRelationWidgetWrapper *>( ww );
3179 if ( relationWidgetWrapper )
3180 {
3181 connect( relationWidgetWrapper, &QgsRelationWidgetWrapper::relatedFeaturesChanged, this, &QgsAttributeForm::onRelatedFeaturesChanged, static_cast<Qt::ConnectionType>( Qt::UniqueConnection | Qt::QueuedConnection ) );
3182 }
3183 }
3184 }
3185}
3186
3187
3188bool QgsAttributeForm::eventFilter( QObject *object, QEvent *e )
3189{
3190 Q_UNUSED( object )
3191
3192 if ( e->type() == QEvent::KeyPress )
3193 {
3194 QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( e );
3195 if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
3196 {
3197 // Re-emit to this form so it will be forwarded to parent
3198 event( e );
3199 return true;
3200 }
3201 }
3202
3203 return false;
3204}
3205
3206void QgsAttributeForm::scanForEqualAttributes( QgsFeatureIterator &fit, QSet<int> &mixedValueFields, QHash<int, QVariant> &fieldSharedValues ) const
3207{
3208 mixedValueFields.clear();
3209 fieldSharedValues.clear();
3210
3211 QgsFeature f;
3212 bool first = true;
3213 while ( fit.nextFeature( f ) )
3214 {
3215 for ( int i = 0; i < mLayer->fields().count(); ++i )
3216 {
3217 if ( mixedValueFields.contains( i ) )
3218 continue;
3219
3220 if ( first )
3221 {
3222 fieldSharedValues[i] = f.attribute( i );
3223 }
3224 else
3225 {
3226 if ( fieldSharedValues.value( i ) != f.attribute( i ) )
3227 {
3228 fieldSharedValues.remove( i );
3229 mixedValueFields.insert( i );
3230 }
3231 }
3232 }
3233 first = false;
3234
3235 if ( mixedValueFields.count() == mLayer->fields().count() )
3236 {
3237 // all attributes are mixed, no need to keep scanning
3238 break;
3239 }
3240 }
3241}
3242
3243
3244void QgsAttributeForm::layerSelectionChanged()
3245{
3246 switch ( mMode )
3247 {
3255 break;
3256
3258 resetMultiEdit( true );
3259 break;
3260 }
3261}
3262
3264{
3265 mIsSettingMultiEditFeatures = true;
3266 mMultiEditFeatureIds = fids;
3267
3268 if ( fids.isEmpty() )
3269 {
3270 // no selected features
3271 QMultiMap<int, QgsAttributeFormEditorWidget *>::const_iterator wIt = mFormEditorWidgets.constBegin();
3272 for ( ; wIt != mFormEditorWidgets.constEnd(); ++wIt )
3273 {
3274 wIt.value()->initialize( QVariant() );
3275 }
3276 mIsSettingMultiEditFeatures = false;
3277 return;
3278 }
3279
3280 QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFids( fids ) );
3281
3282 // Scan through all features to determine which attributes are initially the same
3283 QSet<int> mixedValueFields;
3284 QHash<int, QVariant> fieldSharedValues;
3285 scanForEqualAttributes( fit, mixedValueFields, fieldSharedValues );
3286
3287 // also fetch just first feature
3288 fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFid( *fids.constBegin() ) );
3289 QgsFeature firstFeature;
3290 fit.nextFeature( firstFeature );
3291
3292 // Make this feature the current form feature or the constraints will be evaluated
3293 // on a possibly wrong previously selected/current feature
3294 if ( mCurrentFormFeature.id() != firstFeature.id() )
3295 {
3296 setFeature( firstFeature );
3297 }
3298
3299 const auto constMixedValueFields = mixedValueFields;
3300 for ( int fieldIndex : std::as_const( mixedValueFields ) )
3301 {
3302 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( fieldIndex );
3303 if ( formEditorWidgets.isEmpty() )
3304 continue;
3305
3306 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
3307 QVariantList additionalFieldValues;
3308 for ( const QString &additionalField : additionalFields )
3309 additionalFieldValues << firstFeature.attribute( additionalField );
3310
3311 for ( QgsAttributeFormEditorWidget *w : formEditorWidgets )
3312 w->initialize( firstFeature.attribute( fieldIndex ), true, additionalFieldValues );
3313 }
3314 QHash<int, QVariant>::const_iterator sharedValueIt = fieldSharedValues.constBegin();
3315 for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
3316 {
3317 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( sharedValueIt.key() );
3318 if ( formEditorWidgets.isEmpty() )
3319 continue;
3320
3321 bool mixed = false;
3322 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
3323 for ( const QString &additionalField : additionalFields )
3324 {
3325 int index = mLayer->fields().indexFromName( additionalField );
3326 if ( constMixedValueFields.contains( index ) )
3327 {
3328 // if additional field are mixed, it is considered as mixed
3329 mixed = true;
3330 break;
3331 }
3332 }
3333 QVariantList additionalFieldValues;
3334 if ( mixed )
3335 {
3336 for ( const QString &additionalField : additionalFields )
3337 additionalFieldValues << firstFeature.attribute( additionalField );
3338 for ( QgsAttributeFormEditorWidget *w : formEditorWidgets )
3339 w->initialize( firstFeature.attribute( sharedValueIt.key() ), true, additionalFieldValues );
3340 }
3341 else
3342 {
3343 for ( const QString &additionalField : additionalFields )
3344 {
3345 int index = mLayer->fields().indexFromName( additionalField );
3346 Q_ASSERT( fieldSharedValues.contains( index ) );
3347 additionalFieldValues << fieldSharedValues.value( index );
3348 }
3349 for ( QgsAttributeFormEditorWidget *w : formEditorWidgets )
3350 w->initialize( sharedValueIt.value(), false, additionalFieldValues );
3351 }
3352 }
3353
3354 setMultiEditFeatureIdsRelations( fids );
3355
3356 mIsSettingMultiEditFeatures = false;
3357}
3358
3360{
3361 if ( mOwnsMessageBar )
3362 delete mMessageBar;
3363 mOwnsMessageBar = false;
3364 mMessageBar = messageBar;
3365}
3366
3368{
3370 {
3371 Q_ASSERT( false );
3372 }
3373
3374 QStringList filters;
3375 for ( QgsAttributeFormWidget *widget : mFormWidgets )
3376 {
3377 QString filter = widget->currentFilterExpression();
3378 if ( !filter.isNull() )
3379 filters << '(' + filter + ')';
3380 }
3381
3382 return filters.join( " AND "_L1 );
3383}
3384
3386{
3387 mExtraContextScope.reset( extraScope );
3388}
3389
3390void QgsAttributeForm::ContainerInformation::apply( QgsExpressionContext *expressionContext )
3391{
3392 const bool newVisibility = expression.evaluate( expressionContext ).toBool();
3393
3394 if ( expression.isValid() && !expression.hasEvalError() && newVisibility != isVisible )
3395 {
3396 if ( tabWidget )
3397 {
3398 tabWidget->setTabVisible( widget, newVisibility );
3399 }
3400 else
3401 {
3402 widget->setVisible( newVisibility );
3403 }
3404
3405 isVisible = newVisibility;
3406 }
3407
3408 const bool newCollapsedState = collapsedExpression.evaluate( expressionContext ).toBool();
3409
3410 if ( collapsedExpression.isValid() && !collapsedExpression.hasEvalError() && newCollapsedState != isCollapsed )
3411 {
3412 if ( QgsCollapsibleGroupBoxBasic * collapsibleGroupBox { qobject_cast<QgsCollapsibleGroupBoxBasic *>( widget ) } )
3413 {
3414 collapsibleGroupBox->setCollapsed( newCollapsedState );
3415 isCollapsed = newCollapsedState;
3416 }
3417 }
3418}
3419
3420void QgsAttributeForm::updateJoinedFields( const QgsEditorWidgetWrapper &eww )
3421{
3422 if ( !eww.layer()->fields().exists( eww.fieldIdx() ) )
3423 return;
3424
3425 QgsFeature formFeature;
3426 QgsField field = eww.layer()->fields().field( eww.fieldIdx() );
3427 QList<const QgsVectorLayerJoinInfo *> infos = eww.layer()->joinBuffer()->joinsWhereFieldIsId( field );
3428
3429 if ( infos.count() == 0 || !currentFormValuesFeature( formFeature ) )
3430 return;
3431
3432 const QString hint = tr( "No feature joined" );
3433 const auto constInfos = infos;
3434 for ( const QgsVectorLayerJoinInfo *info : constInfos )
3435 {
3436 if ( !info->isDynamicFormEnabled() )
3437 continue;
3438
3439 QgsFeature joinFeature = mLayer->joinBuffer()->joinedFeatureOf( info, formFeature );
3440
3441 mJoinedFeatures[info] = joinFeature;
3442
3443 if ( info->hasSubset() )
3444 {
3445 const QStringList subsetNames = QgsVectorLayerJoinInfo::joinFieldNamesSubset( *info );
3446
3447 const auto constSubsetNames = subsetNames;
3448 for ( const QString &field : constSubsetNames )
3449 {
3450 QString prefixedName = info->prefixedFieldName( field );
3451 QVariant val;
3452 QString hintText = hint;
3453
3454 if ( joinFeature.isValid() )
3455 {
3456 val = joinFeature.attribute( field );
3457 hintText.clear();
3458 }
3459
3460 changeAttribute( prefixedName, val, hintText );
3461 }
3462 }
3463 else
3464 {
3465 const QgsFields joinFields = joinFeature.fields();
3466 for ( const QgsField &field : joinFields )
3467 {
3468 QString prefixedName = info->prefixedFieldName( field );
3469 QVariant val;
3470 QString hintText = hint;
3471
3472 if ( joinFeature.isValid() )
3473 {
3474 val = joinFeature.attribute( field.name() );
3475 hintText.clear();
3476 }
3477
3478 changeAttribute( prefixedName, val, hintText );
3479 }
3480 }
3481 }
3482}
3483
3484bool QgsAttributeForm::fieldIsEditable( int fieldIndex ) const
3485{
3488}
3489
3490void QgsAttributeForm::updateFieldDependencies()
3491{
3492 mDefaultValueDependencies.clear();
3493 mVirtualFieldsDependencies.clear();
3494 mRelatedLayerFieldsDependencies.clear();
3495 mParentDependencies.clear();
3496
3497 //create defaultValueDependencies
3498 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
3499 {
3500 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
3501 if ( !eww )
3502 continue;
3503
3504 updateFieldDependenciesParent( eww );
3505 updateFieldDependenciesDefaultValue( eww );
3506 updateFieldDependenciesVirtualFields( eww );
3507 updateRelatedLayerFieldsDependencies( eww );
3508 }
3509}
3510
3511void QgsAttributeForm::updateFieldDependenciesDefaultValue( QgsEditorWidgetWrapper *eww )
3512{
3513 QgsExpression exp( eww->field().defaultValueDefinition().expression() );
3514
3515 if ( exp.needsGeometry() )
3516 mNeedsGeometry = true;
3517
3518 //if a function requires all attributes, it should have the dependency of every field change
3519 if ( exp.referencedColumns().contains( QgsFeatureRequest::ALL_ATTRIBUTES ) )
3520 {
3521 const QList<int> allAttributeIds( mLayer->fields().allAttributesList() );
3522
3523 for ( const int id : allAttributeIds )
3524 {
3525 mDefaultValueDependencies.insertMulti( id, eww );
3526 }
3527 }
3528 else
3529 {
3530 //otherwise just enter for the field depending on
3531 const QSet<QString> referencedColumns = exp.referencedColumns();
3532 for ( const QString &referencedColumn : referencedColumns )
3533 {
3534 mDefaultValueDependencies.insertMulti( mLayer->fields().lookupField( referencedColumn ), eww );
3535 }
3536 }
3537}
3538
3539void QgsAttributeForm::updateFieldDependenciesVirtualFields( QgsEditorWidgetWrapper *eww )
3540{
3541 QString expressionField = eww->layer()->expressionField( eww->fieldIdx() );
3542 if ( expressionField.isEmpty() )
3543 return;
3544
3545 QgsExpression exp( expressionField );
3546
3547 if ( exp.needsGeometry() )
3548 mNeedsGeometry = true;
3549
3550 //if a function requires all attributes, it should have the dependency of every field change
3551 if ( exp.referencedColumns().contains( QgsFeatureRequest::ALL_ATTRIBUTES ) )
3552 {
3553 const QList<int> allAttributeIds( mLayer->fields().allAttributesList() );
3554
3555 for ( const int id : allAttributeIds )
3556 {
3557 mVirtualFieldsDependencies.insertMulti( id, eww );
3558 }
3559 }
3560 else
3561 {
3562 //otherwise just enter for the field depending on
3563 const QSet<QString> referencedColumns = exp.referencedColumns();
3564 for ( const QString &referencedColumn : referencedColumns )
3565 {
3566 mVirtualFieldsDependencies.insertMulti( mLayer->fields().lookupField( referencedColumn ), eww );
3567 }
3568 }
3569}
3570
3571void QgsAttributeForm::updateRelatedLayerFieldsDependencies( QgsEditorWidgetWrapper *eww )
3572{
3573 if ( eww )
3574 {
3575 QString expressionField = eww->layer()->expressionField( eww->fieldIdx() );
3576 if ( expressionField.contains( u"relation_aggregate"_s ) || expressionField.contains( u"get_features"_s ) )
3577 mRelatedLayerFieldsDependencies.insert( eww );
3578 }
3579 else
3580 {
3581 mRelatedLayerFieldsDependencies.clear();
3582 //create defaultValueDependencies
3583 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
3584 {
3585 QgsEditorWidgetWrapper *editorWidgetWrapper = qobject_cast<QgsEditorWidgetWrapper *>( ww );
3586 if ( !editorWidgetWrapper )
3587 continue;
3588
3589 updateRelatedLayerFieldsDependencies( editorWidgetWrapper );
3590 }
3591 }
3592}
3593
3594void QgsAttributeForm::updateFieldDependenciesParent( QgsEditorWidgetWrapper *eww )
3595{
3596 if ( eww && !eww->field().defaultValueDefinition().expression().isEmpty() )
3597 {
3598 const QgsExpression expression( eww->field().defaultValueDefinition().expression() );
3599 const QSet<QString> referencedVariablesAndFunctions = expression.referencedVariables() + expression.referencedFunctions();
3600 for ( const QString &referenced : referencedVariablesAndFunctions )
3601 {
3602 if ( referenced.startsWith( "current_parent"_L1 ) )
3603 {
3604 mParentDependencies.insert( eww );
3605 break;
3606 }
3607 }
3608 }
3609}
3610
3611void QgsAttributeForm::setMultiEditFeatureIdsRelations( const QgsFeatureIds &fids )
3612{
3613 for ( QgsAttributeFormWidget *formWidget : mFormWidgets )
3614 {
3615 QgsAttributeFormRelationEditorWidget *relationEditorWidget = dynamic_cast<QgsAttributeFormRelationEditorWidget *>( formWidget );
3616 if ( !relationEditorWidget )
3617 continue;
3618
3619 relationEditorWidget->setMultiEditFeatureIds( fids );
3620 }
3621}
3622
3623void QgsAttributeForm::updateIcon( QgsEditorWidgetWrapper *eww )
3624{
3625 if ( !eww->widget() || !mIconMap[eww->widget()] )
3626 return;
3627
3628 // no icon by default
3629 mIconMap[eww->widget()]->hide();
3630
3631 if ( !eww->widget()->isEnabled() && mLayer->isEditable() )
3632 {
3633 if ( mLayer->fields().fieldOrigin( eww->fieldIdx() ) == Qgis::FieldOrigin::Join )
3634 {
3635 int srcFieldIndex;
3636 const QgsVectorLayerJoinInfo *info = mLayer->joinBuffer()->joinForFieldIndex( eww->fieldIdx(), mLayer->fields(), srcFieldIndex );
3637
3638 if ( !info )
3639 return;
3640
3641 if ( !info->isEditable() )
3642 {
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()] );
3646 }
3647 else if ( mMode == QgsAttributeEditorContext::AddFeatureMode && !info->hasUpsertOnEdit() )
3648 {
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()] );
3652 }
3653 else if ( !info->joinLayer()->isEditable() )
3654 {
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()] );
3658 }
3659 }
3660 }
3661}
3662
3663void QgsAttributeForm::reloadIcon( const QString &file, const QString &tooltip, QSvgWidget *sw )
3664{
3665 sw->load( QgsApplication::iconPath( file ) );
3666 sw->setToolTip( tooltip );
3667 sw->show();
3668}
3669
3671{
3672 const bool reuseAllLastValues = QgsSettingsRegistryCore::settingsDigitizingReuseLastValues->value();
3673 QgsDebugMsgLevel( u"reuseAllLastValues: %1"_s.arg( reuseAllLastValues ), 2 );
3674
3675 const QgsFields fields = layer->fields();
3676 QgsAttributeMap initialAttributeValues;
3677 for ( int idx = 0; idx < fields.count(); ++idx )
3678 {
3679 if ( attributes.contains( idx ) )
3680 {
3681 initialAttributeValues.insert( idx, attributes.value( idx ) );
3682 }
3683 else if ( ( reuseAllLastValues || layer->editFormConfig().reuseLastValuePolicy( idx ) != Qgis::AttributeFormReuseLastValuePolicy::NotAllowed ) )
3684 {
3685 const QVariant lastUsedValuesVariant = layer->property( "AttributeFormLastUsedValues" );
3686 const QgsAttributeMap lastUsedValues = lastUsedValuesVariant.isValid() ? lastUsedValuesVariant.value<QgsAttributeMap>() : QgsAttributeMap();
3687 if ( lastUsedValues.contains( idx ) && layer->dataProvider() && layer->dataProvider()->defaultValueClause( idx ) != lastUsedValues[idx] )
3688 {
3689 initialAttributeValues.insert( idx, lastUsedValues[idx] );
3690 }
3691 }
3692 }
3693
3694 return QgsVectorLayerUtils::createFeature( layer, geometry, initialAttributeValues, &context );
3695}
@ Trusted
The project trust has not yet been determined by the user.
Definition qgis.h:478
@ Row
A row of editors (horizontal layout).
Definition qgis.h:5985
AttributeFormReuseLastValuePolicy
Attribute form policy for reusing last entered values.
Definition qgis.h:6041
@ AllowedDefaultOn
Reuse of last values allowed and enabled by default.
Definition qgis.h:6043
@ NotAllowed
Reuse of last values not allowed.
Definition qgis.h:6042
@ File
Load the Python code from an external file.
Definition qgis.h:6029
@ Environment
Use the Python code available in the Python environment.
Definition qgis.h:6031
@ NoSource
Do not use Python code at all.
Definition qgis.h:6028
@ Dialog
Use the Python code provided in the dialog.
Definition qgis.h:6030
@ DragAndDrop
"Drag and drop" layout. Needs to be configured.
Definition qgis.h:5999
@ UiFile
Load a .ui file for the layout. Needs to be configured.
Definition qgis.h:6000
@ Warning
Warning message.
Definition qgis.h:162
@ Info
Information message.
Definition qgis.h:161
@ Success
Used for reporting a successful operation.
Definition qgis.h:164
@ Normal
A normal relation.
Definition qgis.h:4692
@ Join
Field originates from a joined layer.
Definition qgis.h:1827
@ Action
A layer action element.
Definition qgis.h:5969
@ Container
A container.
Definition qgis.h:5964
@ QmlElement
A QML element.
Definition qgis.h:5967
@ Relation
A relation.
Definition qgis.h:5966
@ HtmlElement
A HTML element.
Definition qgis.h:5968
@ TextElement
A text element.
Definition qgis.h:5970
@ SpacerElement
A spacer element.
Definition qgis.h:5971
SelectBehavior
Specifies how a selection should be applied.
Definition qgis.h:1891
@ SetSelection
Set selection, removing any existing selection.
Definition qgis.h:1892
@ AddToSelection
Add selection to current selection.
Definition qgis.h:1893
@ IntersectSelection
Modify current selection to include only select features which match.
Definition qgis.h:1894
@ RemoveFromSelection
Remove from current selection.
Definition qgis.h:1895
void setAction(const QgsAction &action)
Sets the action.
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.
A widget consisting of both an editor widget and additional widgets for controlling the behavior of t...
void setRememberLastValue(bool remember)
Sets whether the widget value will be remembered for potential reuse when creating new features.
void rememberLastValueChanged(int index, bool remember)
Emitted when the widget's remember last value toggle changes.
QgsEditorWidgetWrapper * editorWidget() const
Returns the editor widget wrapper.
void createSearchWidgetWrappers(const QgsAttributeEditorContext &context=QgsAttributeEditorContext()) override
Creates the search widget wrappers for the widget used when the form is in search mode.
Interface class for custom attribute forms.
Widget to show for child relations on an attribute form.
void setMultiEditFeatureIds(const QgsFeatureIds &fids)
Set multiple feature to edit simultaneously.
void createSearchWidgetWrappers(const QgsAttributeEditorContext &context=QgsAttributeEditorContext()) override
Creates the search widget wrappers for the widget used when the form is in search mode.
Base class for all widgets shown on a QgsAttributeForm.
@ SearchMode
Layer search/filter mode.
@ MultiEditMode
Multi edit mode, both the editor widget and a QgsMultiEditToolButton is shown.
@ DefaultMode
Default mode, only the editor widget is shown.
@ AggregateSearchMode
Embedded in a search form, show additional aggregate function toolbutton.
void showButtonBox()
Shows the button box (OK/Cancel) and disables auto-commit.
void parentFormValueChanged(const QString &attribute, const QVariant &newValue)
Is called in embedded forms when an attribute value in the parent form has changed to newValue.
QgsVectorLayer * layer()
Returns the layer for which this form is shown.
void setFeature(const QgsFeature &feature)
Update all editors to correspond to a different feature.
void setMultiEditFeatureIds(const QgsFeatureIds &fids)
Sets all feature IDs which are to be edited if the form is in multiedit mode.
void refreshFeature()
reload current feature
void beforeSave(bool &ok)
Will be emitted before the feature is saved.
void changeGeometry(const QgsGeometry &geometry)
Changes the geometry of the feature attached to the form.
bool eventFilter(QObject *object, QEvent *event) override
Intercepts keypress on custom form (escape should not close it).
bool saveWithDetails(QString *error=nullptr)
Save all the values from the editors to the layer.
void addInterface(QgsAttributeFormInterface *iface)
Takes ownership.
bool needsGeometry() const
Returns true if any of the form widgets need feature geometry.
void setExtraContextScope(QgsExpressionContextScope *extraScope)
Sets an additional expression context scope to be used for calculations in this form.
void changeAttribute(const QString &field, const QVariant &value, const QString &hintText=QString())
Call this to change the content of a given attribute.
bool save()
Save all the values from the editors to the layer.
void filterExpressionSet(const QString &expression, QgsAttributeForm::FilterType type)
Emitted when a filter expression is set using the form.
void hideButtonBox()
Hides the button box (OK/Cancel) and enables auto-commit.
void closed()
Emitted when the user selects the close option from the form's button bar.
void resetSearch()
Resets the search/filter form values.
void widgetValueChanged(const QString &attribute, const QVariant &value, bool attributeChanged)
Notifies about changes of attributes.
void openFilteredFeaturesAttributeTable(const QString &filter)
Emitted when the user chooses to open the attribute table dialog with a filtered set of features.
QString aggregateFilter() const
The aggregate filter is only useful if the form is in AggregateFilter mode.
void flashFeatures(const QString &filter)
Emitted when the user chooses to flash a filtered set of features.
void resetValues()
Sets all values to the values of the current feature.
QgsAttributeForm(QgsVectorLayer *vl, const QgsFeature &feature=QgsFeature(), const QgsAttributeEditorContext &context=QgsAttributeEditorContext(), QWidget *parent=nullptr)
void disconnectButtonBox()
Disconnects the button box (OK/Cancel) from the accept/resetValues slots If this method is called,...
bool editable()
Returns if the form is currently in editable mode.
void setMessageBar(QgsMessageBar *messageBar)
Sets the message bar to display feedback from the form in.
void modeChanged(QgsAttributeEditorContext::Mode mode)
Emitted when the form changes mode.
static QgsFeature createFeature(QgsVectorLayer *layer, const QgsGeometry &geometry, const QgsAttributeMap &attributes, QgsExpressionContext &context)
Creates a new feature for a given layer taking into account attribute form-specific context such as t...
const QgsFeature & feature() const
Returns feature of attribute form.
void featureSaved(const QgsFeature &feature)
Emitted when a feature is changed or added.
void displayWarning(const QString &message)
Displays a warning message in the form message bar.
void zoomToFeatures(const QString &filter)
Emitted when the user chooses to zoom to a filtered set of features.
Q_DECL_DEPRECATED void attributeChanged(const QString &attribute, const QVariant &value)
Notifies about changes of attributes, this signal is not emitted when the value is set back to the or...
void setMode(QgsAttributeEditorContext::Mode mode)
Sets the current mode of the form.
@ ReplaceFilter
Filter should replace any existing filter.
@ FilterOr
Filter should be combined using "OR".
@ FilterAnd
Filter should be combined using "AND".
QgsAttributeEditorContext::Mode mode() const
Returns the current mode of the form.
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.
QgsEditorWidgetSetup findBest(const QgsVectorLayer *vl, const QString &fieldName) const
Find the best editor widget and its configuration for a given field.
QgsEditorWidgetWrapper * create(const QString &widgetId, QgsVectorLayer *vl, int fieldIdx, const QVariantMap &config, QWidget *editor, QWidget *parent, const QgsAttributeEditorContext &context=QgsAttributeEditorContext())
Create an attribute editor widget wrapper of a given type for a given field.
QString type() const
Returns the widget type to use.
QVariantMap config() const
Returns the widget configuration.
Manages an editor widget.
virtual QVariant value() const =0
Will be used to access the widget's value.
virtual QVariantList additionalFieldValues() const
Will be used to access the widget's values for potential additional fields handled by the widget.
int fieldIdx() const
Access the field index.
virtual void parentFormValueChanged(const QString &attribute, const QVariant &value)
Is called in embedded form widgets when an attribute value in the parent form has changed.
virtual QStringList additionalFields() const
Returns the list of additional fields which the editor handles.
QString constraintFailureReason() const
Returns the reason why a constraint check has failed (or an empty string if constraint check was succ...
void setEnabled(bool enabled) override
Is used to enable or disable the edit functionality of the managed widget.
void valuesChanged(const QVariant &value, const QVariantList &additionalFieldValues=QVariantList())
Emit this signal, whenever the value changed.
bool isValidConstraint() const
Gets the current constraint status.
void updateConstraint(const QgsFeature &featureContext, QgsFieldConstraints::ConstraintOrigin constraintOrigin=QgsFieldConstraints::ConstraintOriginNotSet)
Update constraint.
void setValues(const QVariant &value, const QVariantList &additionalValues)
Is called when the value of the widget or additional field values needs to be changed.
virtual void setHint(const QString &hintText)
Add a hint text on the widget.
QgsField field() const
Access the field.
ConstraintResult
Result of constraint checks.
void constraintStatusChanged(const QString &constraint, const QString &desc, const QString &err, QgsEditorWidgetWrapper::ConstraintResult status)
Emit this signal when the constraint status changed.
virtual void setValue(const QVariant &value)
Is called when the value of the widget needs to be changed.
bool isBlockingCommit() const
Returns true if the widget is preventing the feature from being committed.
void setConstraintResultVisible(bool constraintResultVisible)
Sets whether the constraint result is visible.
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...
Definition qgsfeature.h:60
Q_INVOKABLE bool setAttribute(int field, const QVariant &attr)
Sets an attribute's value by field index.
QgsAttributes attributes
Definition qgsfeature.h:69
QgsFields fields
Definition qgsfeature.h:70
QgsFeatureId id
Definition qgsfeature.h:68
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 name
Definition qgsfield.h:65
QString displayName() const
Returns the name to use when displaying this field.
Definition qgsfield.cpp:96
QgsDefaultValue defaultValueDefinition
Definition qgsfield.h:67
QString customComment
Definition qgsfield.h:71
QString comment
Definition qgsfield.h:64
QgsFieldConstraints constraints
Definition qgsfield.h:68
Container of fields for a vector layer.
Definition qgsfields.h:46
int count
Definition qgsfields.h:50
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.
Definition qgsgui.cpp:109
static bool allowExecutionOfEmbeddedScripts(QgsProject *project, QgsMessageBar *messageBar=nullptr)
Returns true if python embedded in a project is currently allowed to be loaded.
Definition qgsgui.cpp:398
void reinitWidget()
Clears the content and makes new initialization.
void setHtmlCode(const QString &htmlCode)
Sets the HTML code to htmlCode.
bool needsGeometry() const
Returns true if the widget needs feature geometry.
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.
Definition qgsoptional.h:76
T data() const
Access the payload data.
Definition qgsoptional.h:94
static Qgis::ProjectTrustStatus checkUserTrust(QgsProject *project)
Returns the current trust status of the specified project.
QgsRelationManager * relationManager
Definition qgsproject.h:125
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.
void setQmlCode(const QString &qmlCode)
writes the qmlCode into a temporary file
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.
A widget wrapper for relation widgets.
void relatedFeaturesChanged()
Emit this signal, whenever the related features changed.
QgsRelation relation() const
The relation for which this wrapper is created.
void setWidgetConfig(const QVariantMap &config)
Will set the config of this widget wrapper to the specified config.
void setForceSuppressFormPopup(bool forceSuppressFormPopup)
Sets force suppress form popup status to forceSuppressFormPopup for this widget and for the vectorLay...
void setNmRelationId(const QVariant &nmRelationId=QVariant())
Sets nmRelationId for the relation id of the second relation involved in an N:M relation.
Represents a relationship between two vector layers.
Definition qgsrelation.h:42
QString name
Definition qgsrelation.h:52
Qgis::RelationshipType type() const
Returns the type of the relation.
QString id
Definition qgsrelation.h:45
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.
void setDrawLine(bool drawLine)
Sets a flag to define if the spacer element will contain an horizontal line.
void setTabVisible(QWidget *tab, bool visible)
Control the visibility for the tab with the given widget.
void setTabStyle(int tabIndex, const QgsAttributeEditorElement::LabelStyle &labelStyle)
Sets the optional custom labelStyle for the tab identified by tabIndex.
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.
Manages an editor widget.
QWidget * widget()
Access the widget managed by this wrapper.
void setConfig(const QVariantMap &config)
Will set the config of this wrapper to the specified config.
QgsVectorLayer * layer() const
Returns the vector layer associated with the widget.
void setContext(const QgsAttributeEditorContext &context)
Set the context in which this widget is shown.
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...
Definition qgis.cpp:657
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:7678
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7677
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)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59