QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
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
30#include "qgsfeatureiterator.h"
31#include "qgsgui.h"
32#include "qgsproject.h"
33#include "qgspythonrunner.h"
38#include "qgsmessagebar.h"
39#include "qgsmessagebaritem.h"
42#include "qgsrelationmanager.h"
43#include "qgslogger.h"
44#include "qgstabwidget.h"
45#include "qgssettings.h"
46#include "qgsscrollarea.h"
48#include "qgsvectorlayerutils.h"
50#include "qgsqmlwidgetwrapper.h"
53#include "qgsapplication.h"
55#include "qgsfeaturerequest.h"
56#include "qgstexteditwrapper.h"
57#include "qgsfieldmodel.h"
59
60#include <QDir>
61#include <QTextStream>
62#include <QFileInfo>
63#include <QFile>
64#include <QFormLayout>
65#include <QGridLayout>
66#include <QKeyEvent>
67#include <QLabel>
68#include <QPushButton>
69#include <QUiLoader>
70#include <QMessageBox>
71#include <QToolButton>
72#include <QMenu>
73#include <QSvgWidget>
74
75int QgsAttributeForm::sFormCounter = 0;
76
77QgsAttributeForm::QgsAttributeForm( QgsVectorLayer *vl, const QgsFeature &feature, const QgsAttributeEditorContext &context, QWidget *parent )
78 : QWidget( parent )
79 , mLayer( vl )
80 , mOwnsMessageBar( true )
81 , mContext( context )
82 , mFormNr( sFormCounter++ )
83 , mIsSaving( false )
84 , mPreventFeatureRefresh( false )
85 , mIsSettingMultiEditFeatures( false )
86 , mUnsavedMultiEditChanges( false )
87 , mEditCommandMessage( tr( "Attributes changed" ) )
88 , mMode( QgsAttributeEditorContext::SingleEditMode )
89{
90 init();
91 initPython();
93
94 connect( vl, &QgsVectorLayer::updatedFields, this, &QgsAttributeForm::onUpdatedFields );
95 connect( vl, &QgsVectorLayer::beforeAddingExpressionField, this, &QgsAttributeForm::preventFeatureRefresh );
96 connect( vl, &QgsVectorLayer::beforeRemovingExpressionField, this, &QgsAttributeForm::preventFeatureRefresh );
97 connect( vl, &QgsVectorLayer::selectionChanged, this, &QgsAttributeForm::layerSelectionChanged );
98 connect( this, &QgsAttributeForm::modeChanged, this, &QgsAttributeForm::updateContainersVisibility );
99
100 updateContainersVisibility();
101 updateLabels();
102 updateEditableState();
103
104}
105
107{
108 cleanPython();
109 qDeleteAll( mInterfaces );
110}
111
113{
114 mButtonBox->hide();
115
116 // Make sure that changes are taken into account if somebody tries to figure out if there have been some
119}
120
122{
123 mButtonBox->show();
124
125 disconnect( mLayer, &QgsVectorLayer::beforeModifiedCheck, this, &QgsAttributeForm::save );
126}
127
129{
130 disconnect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsAttributeForm::save );
131 disconnect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsAttributeForm::resetValues );
132}
133
135{
136 mInterfaces.append( iface );
137}
138
140{
141 return mFeature.isValid() && mLayer->isEditable();
142}
143
145{
146 if ( mode == mMode )
147 return;
148
150 {
151 //switching out of multi edit mode triggers a save
152 if ( mUnsavedMultiEditChanges )
153 {
154 // prompt for save
155 int res = QMessageBox::question( this, tr( "Multiedit Attributes" ),
156 tr( "Apply changes to edited features?" ), QMessageBox::Yes | QMessageBox::No );
157 if ( res == QMessageBox::Yes )
158 {
159 save();
160 }
161 }
162 clearMultiEditMessages();
163 }
164 mUnsavedMultiEditChanges = false;
165
166 mMode = mode;
167
168 if ( mButtonBox->isVisible() && mMode == QgsAttributeEditorContext::SingleEditMode )
169 {
171 }
172 else
173 {
174 disconnect( mLayer, &QgsVectorLayer::beforeModifiedCheck, this, &QgsAttributeForm::save );
175 }
176
177 //update all form editor widget modes to match
178 for ( QgsAttributeFormWidget *w : std::as_const( mFormWidgets ) )
179 {
180 switch ( mode )
181 {
184 break;
185
188 break;
189
192 break;
193
196 break;
197
200 break;
201
204 break;
205
208 break;
209 }
210 }
211 //update all form editor widget modes to match
212 for ( QgsWidgetWrapper *w : std::as_const( mWidgets ) )
213 {
214 QgsAttributeEditorContext newContext = w->context();
215 newContext.setAttributeFormMode( mMode );
216 w->setContext( newContext );
217 }
218
219 bool relationWidgetsVisible = ( mMode != QgsAttributeEditorContext::AggregateSearchMode );
220 for ( QgsAttributeFormRelationEditorWidget *w : findChildren< QgsAttributeFormRelationEditorWidget * >() )
221 {
222 w->setVisible( relationWidgetsVisible );
223 }
224
225 switch ( mode )
226 {
228 setFeature( mFeature );
229 mSearchButtonBox->setVisible( false );
230 break;
231
233 synchronizeState();
234 mSearchButtonBox->setVisible( false );
235 break;
236
238 synchronizeState();
239 mSearchButtonBox->setVisible( false );
240 break;
241
243 resetMultiEdit( false );
244 synchronizeState();
245 mSearchButtonBox->setVisible( false );
246 break;
247
249 mSearchButtonBox->setVisible( true );
250 synchronizeState();
252 break;
253
255 mSearchButtonBox->setVisible( false );
256 synchronizeState();
258 break;
259
261 setFeature( mFeature );
262 synchronizeState();
263 mSearchButtonBox->setVisible( false );
264 break;
265 }
266
267 emit modeChanged( mMode );
268}
269
270void QgsAttributeForm::changeAttribute( const QString &field, const QVariant &value, const QString &hintText )
271{
272 const auto constMWidgets = mWidgets;
273 for ( QgsWidgetWrapper *ww : constMWidgets )
274 {
275 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
276 if ( eww )
277 {
278 if ( eww->field().name() == field )
279 {
280 eww->setValues( value, QVariantList() );
281 eww->setHint( hintText );
282 }
283 // see if the field is present in additional fields of the editor widget
284 int index = eww->additionalFields().indexOf( field );
285 if ( index >= 0 )
286 {
287 QVariant mainValue = eww->value();
288 QVariantList additionalFieldValues = eww->additionalFieldValues();
289 additionalFieldValues[index] = value;
290 eww->setValues( mainValue, additionalFieldValues );
291 eww->setHint( hintText );
292 }
293 }
294 }
295}
296
298{
299 mFeature.setGeometry( geometry );
300}
301
303{
304 mIsSettingFeature = true;
305 mFeature = feature;
306 mCurrentFormFeature = feature;
307
308 switch ( mMode )
309 {
314 {
315 resetValues();
316
317 synchronizeState();
318
319 // Settings of feature is done when we trigger the attribute form interface
320 // Issue https://github.com/qgis/QGIS/issues/29667
321 mIsSettingFeature = false;
322 const auto constMInterfaces = mInterfaces;
323 for ( QgsAttributeFormInterface *iface : constMInterfaces )
324 {
325 iface->featureChanged();
326 }
327 break;
328 }
331 {
332 resetValues();
333 break;
334 }
336 {
337 //ignore setFeature
338 break;
339 }
340 }
341 mIsSettingFeature = false;
342}
343
344bool QgsAttributeForm::saveEdits( QString *error )
345{
346 bool success = true;
347 bool changedLayer = false;
348
349 QgsFeature updatedFeature = QgsFeature( mFeature );
350 if ( mFeature.isValid() || mMode == QgsAttributeEditorContext::AddFeatureMode )
351 {
352 bool doUpdate = false;
353
354 // An add dialog should perform an action by default
355 // and not only if attributes have "changed"
357 doUpdate = true;
358
359 QgsAttributes src = mFeature.attributes();
360 QgsAttributes dst = mFeature.attributes();
361
362 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
363 {
364 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
365 if ( eww )
366 {
367 // check for invalid JSON values
368 QgsTextEditWrapper *textEdit = qobject_cast<QgsTextEditWrapper *>( eww );
369 if ( textEdit && textEdit->isInvalidJSON() )
370 {
371 if ( error )
372 *error = tr( "JSON value for %1 is invalid and has not been saved" ).arg( eww->field().name() );
373 return false;
374 }
375 QVariantList dstVars = QVariantList() << dst.at( eww->fieldIdx() );
376 QVariantList srcVars = QVariantList() << eww->value();
377 QList<int> fieldIndexes = QList<int>() << eww->fieldIdx();
378
379 // append additional fields
380 const QStringList additionalFields = eww->additionalFields();
381 for ( const QString &fieldName : additionalFields )
382 {
383 int idx = eww->layer()->fields().lookupField( fieldName );
384 fieldIndexes << idx;
385 dstVars << dst.at( idx );
386 }
387 srcVars.append( eww->additionalFieldValues() );
388
389 Q_ASSERT( dstVars.count() == srcVars.count() );
390
391 for ( int i = 0; i < dstVars.count(); i++ )
392 {
393
394 if ( !qgsVariantEqual( dstVars[i], srcVars[i] ) && srcVars[i].isValid() )
395 {
396 dst[fieldIndexes[i]] = srcVars[i];
397
398 doUpdate = true;
399 }
400 }
401 }
402 }
403
404 updatedFeature.setAttributes( dst );
405
406 const auto constMInterfaces = mInterfaces;
407 for ( QgsAttributeFormInterface *iface : constMInterfaces )
408 {
409 if ( !iface->acceptChanges( updatedFeature ) )
410 {
411 doUpdate = false;
412 }
413 }
414
415 if ( doUpdate )
416 {
418 {
419 mFeature = updatedFeature;
420 }
422 {
423 mFeature.setValid( true );
424 mLayer->beginEditCommand( mEditCommandMessage );
425 bool res = mLayer->addFeature( updatedFeature );
426 if ( res )
427 {
428 mFeature.setAttributes( updatedFeature.attributes() );
429 mLayer->endEditCommand();
431 changedLayer = true;
432 }
433 else
434 mLayer->destroyEditCommand();
435 }
436 else
437 {
438 mLayer->beginEditCommand( mEditCommandMessage );
439
440 QgsAttributeMap newValues;
441 QgsAttributeMap oldValues;
442
443 int n = 0;
444 for ( int i = 0; i < dst.count(); ++i )
445 {
446 if ( qgsVariantEqual( dst.at( i ), src.at( i ) ) // If field is not changed...
447 || !dst.at( i ).isValid() // or the widget returns invalid (== do not change)
448 || !fieldIsEditable( i ) ) // or the field cannot be edited ...
449 {
450 continue;
451 }
452
453 QgsDebugMsgLevel( QStringLiteral( "Updating field %1" ).arg( i ), 2 );
454 QgsDebugMsgLevel( QStringLiteral( "dst:'%1' (type:%2, isNull:%3, isValid:%4)" )
455 .arg( dst.at( i ).toString(), dst.at( i ).typeName() ).arg( QgsVariantUtils::isNull( dst.at( i ) ) ).arg( dst.at( i ).isValid() ), 2 );
456 QgsDebugMsgLevel( QStringLiteral( "src:'%1' (type:%2, isNull:%3, isValid:%4)" )
457 .arg( src.at( i ).toString(), src.at( i ).typeName() ).arg( QgsVariantUtils::isNull( src.at( i ) ) ).arg( src.at( i ).isValid() ), 2 );
458
459 newValues[i] = dst.at( i );
460 oldValues[i] = src.at( i );
461
462 n++;
463 }
464
465 success = mLayer->changeAttributeValues( mFeature.id(), newValues, oldValues );
466
467 if ( success && n > 0 )
468 {
469 mLayer->endEditCommand();
470 mFeature.setAttributes( dst );
471 changedLayer = true;
472 }
473 else
474 {
475 mLayer->destroyEditCommand();
476 }
477 }
478 }
479 }
480
481 emit featureSaved( updatedFeature );
482
483 // [MD] Refresh canvas only when absolutely necessary - it interferes with other stuff (#11361).
484 // This code should be revisited - and the signals should be fired (+ layer repainted)
485 // only when actually doing any changes. I am unsure if it is actually a good idea
486 // to call save() whenever some code asks for vector layer's modified status
487 // (which is the case when attribute table is open)
488 if ( changedLayer )
489 mLayer->triggerRepaint();
490
491 return success;
492}
493
494QgsFeature QgsAttributeForm::getUpdatedFeature() const
495{
496 // create updated Feature
497 QgsFeature updatedFeature = QgsFeature( mFeature );
498
499 QgsAttributes featureAttributes = mFeature.attributes();
500 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
501 {
502 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
503 if ( !eww )
504 continue;
505
506 QVariantList dstVars = QVariantList() << featureAttributes.at( eww->fieldIdx() );
507 QVariantList srcVars = QVariantList() << eww->value();
508 QList<int> fieldIndexes = QList<int>() << eww->fieldIdx();
509
510 // append additional fields
511 const QStringList additionalFields = eww->additionalFields();
512 for ( const QString &fieldName : additionalFields )
513 {
514 int idx = eww->layer()->fields().lookupField( fieldName );
515 fieldIndexes << idx;
516 dstVars << featureAttributes.at( idx );
517 }
518 srcVars.append( eww->additionalFieldValues() );
519
520 Q_ASSERT( dstVars.count() == srcVars.count() );
521
522 for ( int i = 0; i < dstVars.count(); i++ )
523 {
524 if ( !qgsVariantEqual( dstVars[i], srcVars[i] ) && srcVars[i].isValid() )
525 featureAttributes[fieldIndexes[i]] = srcVars[i];
526 }
527 }
528 updatedFeature.setAttributes( featureAttributes );
529
530 return updatedFeature;
531}
532
533void QgsAttributeForm::updateValuesDependencies( const int originIdx )
534{
535 updateValuesDependenciesDefaultValues( originIdx );
536 updateValuesDependenciesVirtualFields( originIdx );
537}
538
539void QgsAttributeForm::updateValuesDependenciesDefaultValues( const int originIdx )
540{
541 if ( !mDefaultValueDependencies.contains( originIdx ) )
542 return;
543
544 if ( !mFeature.isValid()
546 return;
547
548 // create updated Feature
549 QgsFeature updatedFeature = getUpdatedFeature();
550
551 // go through depending fields and update the fields with defaultexpression
552 QList<QgsWidgetWrapper *> relevantWidgets = mDefaultValueDependencies.values( originIdx );
553 for ( QgsWidgetWrapper *ww : std::as_const( relevantWidgets ) )
554 {
555 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
556 if ( eww )
557 {
558 // Update only on form opening (except when applyOnUpdate is activated)
559 if ( mValuesInitialized && !eww->field().defaultValueDefinition().applyOnUpdate() )
560 continue;
561
562 // Update only when mMode is AddFeatureMode (except when applyOnUpdate is activated)
564 {
565 continue;
566 }
567
568 //do not update when this widget is already updating (avoid recursions)
569 if ( mAlreadyUpdatedFields.contains( eww->fieldIdx() ) )
570 continue;
571
572 QgsExpressionContext context = createExpressionContext( updatedFeature );
573 const QVariant value = mLayer->defaultValue( eww->fieldIdx(), updatedFeature, &context );
574 eww->setValue( value );
575 mCurrentFormFeature.setAttribute( eww->field().name(), value );
576 }
577 }
578}
579
580void QgsAttributeForm::updateValuesDependenciesVirtualFields( const int originIdx )
581{
582 if ( !mVirtualFieldsDependencies.contains( originIdx ) )
583 return;
584
585 if ( !mFeature.isValid() )
586 return;
587
588 // create updated Feature
589 QgsFeature updatedFeature = getUpdatedFeature();
590
591 // go through depending fields and update the virtual field with its expression
592 const QList<QgsWidgetWrapper *> relevantWidgets = mVirtualFieldsDependencies.values( originIdx );
593 for ( QgsWidgetWrapper *ww : relevantWidgets )
594 {
595 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
596 if ( !eww )
597 continue;
598
599 //do not update when this widget is already updating (avoid recursions)
600 if ( mAlreadyUpdatedFields.contains( eww->fieldIdx() ) )
601 continue;
602
603 // Update value
604 QgsExpressionContext context = createExpressionContext( updatedFeature );
605 QgsExpression exp( mLayer->expressionField( eww->fieldIdx() ) );
606 const QVariant value = exp.evaluate( &context );
607 updatedFeature.setAttribute( eww->fieldIdx(), value );
608 eww->setValue( value );
609 }
610}
611
612void QgsAttributeForm::updateRelatedLayerFields()
613{
614 // Synchronize dependencies
615 updateRelatedLayerFieldsDependencies();
616
617 if ( mRelatedLayerFieldsDependencies.isEmpty() )
618 return;
619
620 if ( !mFeature.isValid() )
621 return;
622
623 // create updated Feature
624 QgsFeature updatedFeature = getUpdatedFeature();
625
626 // go through depending fields and update the fields with virtual field
627 const QSet<QgsEditorWidgetWrapper *> relevantWidgets = mRelatedLayerFieldsDependencies;
628 for ( QgsEditorWidgetWrapper *eww : relevantWidgets )
629 {
630 //do not update when this widget is already updating (avoid recursions)
631 if ( mAlreadyUpdatedFields.contains( eww->fieldIdx() ) )
632 continue;
633
634 // Update value
635 QgsExpressionContext context = createExpressionContext( updatedFeature );
636 QgsExpression exp( mLayer->expressionField( eww->fieldIdx() ) );
637 QVariant value = exp.evaluate( &context );
638 eww->setValue( value );
639 }
640}
641
642void QgsAttributeForm::resetMultiEdit( bool promptToSave )
643{
644 if ( promptToSave )
645 save();
646
647 mUnsavedMultiEditChanges = false;
649}
650
651void QgsAttributeForm::multiEditMessageClicked( const QString &link )
652{
653 clearMultiEditMessages();
654 resetMultiEdit( link == QLatin1String( "#apply" ) );
655}
656
657void QgsAttributeForm::filterTriggered()
658{
659 QString filter = createFilterExpression();
660 emit filterExpressionSet( filter, ReplaceFilter );
661 if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
663}
664
665void QgsAttributeForm::searchZoomTo()
666{
667 QString filter = createFilterExpression();
668 if ( filter.isEmpty() )
669 return;
670
671 emit zoomToFeatures( filter );
672}
673
674void QgsAttributeForm::searchFlash()
675{
676 QString filter = createFilterExpression();
677 if ( filter.isEmpty() )
678 return;
679
680 emit flashFeatures( filter );
681}
682
683void QgsAttributeForm::filterAndTriggered()
684{
685 QString filter = createFilterExpression();
686 if ( filter.isEmpty() )
687 return;
688
689 if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
691 emit filterExpressionSet( filter, FilterAnd );
692}
693
694void QgsAttributeForm::filterOrTriggered()
695{
696 QString filter = createFilterExpression();
697 if ( filter.isEmpty() )
698 return;
699
700 if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
702 emit filterExpressionSet( filter, FilterOr );
703}
704
705void QgsAttributeForm::pushSelectedFeaturesMessage()
706{
707 int count = mLayer->selectedFeatureCount();
708 if ( count > 0 )
709 {
710 mMessageBar->pushMessage( QString(),
711 tr( "%n matching feature(s) selected", "matching features", count ),
712 Qgis::MessageLevel::Info );
713 }
714 else
715 {
716 mMessageBar->pushMessage( QString(),
717 tr( "No matching features found" ),
718 Qgis::MessageLevel::Info );
719 }
720}
721
722void QgsAttributeForm::displayWarning( const QString &message )
723{
724 mMessageBar->pushMessage( QString(),
725 message,
726 Qgis::MessageLevel::Warning );
727}
728
729void QgsAttributeForm::runSearchSelect( Qgis::SelectBehavior behavior )
730{
731 QString filter = createFilterExpression();
732 if ( filter.isEmpty() )
733 return;
734
735 mLayer->selectByExpression( filter, behavior );
736 pushSelectedFeaturesMessage();
737 if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
739}
740
741void QgsAttributeForm::searchSetSelection()
742{
743 runSearchSelect( Qgis::SelectBehavior::SetSelection );
744}
745
746void QgsAttributeForm::searchAddToSelection()
747{
748 runSearchSelect( Qgis::SelectBehavior::AddToSelection );
749}
750
751void QgsAttributeForm::searchRemoveFromSelection()
752{
754}
755
756void QgsAttributeForm::searchIntersectSelection()
757{
759}
760
761bool QgsAttributeForm::saveMultiEdits()
762{
763 //find changed attributes
764 QgsAttributeMap newAttributeValues;
765 const QList<int> fieldIndexes = mFormEditorWidgets.uniqueKeys();
766 mFormEditorWidgets.constBegin();
767 for ( int fieldIndex : fieldIndexes )
768 {
769 const QList<QgsAttributeFormEditorWidget *> widgets = mFormEditorWidgets.values( fieldIndex );
770 if ( !widgets.first()->hasChanged() )
771 continue;
772
773 if ( !widgets.first()->currentValue().isValid() // if the widget returns invalid (== do not change)
774 || !fieldIsEditable( fieldIndex ) ) // or the field cannot be edited ...
775 {
776 continue;
777 }
778
779 // let editor know we've accepted the changes
780 for ( QgsAttributeFormEditorWidget *widget : widgets )
781 widget->changesCommitted();
782
783 newAttributeValues.insert( fieldIndex, widgets.first()->currentValue() );
784 }
785
786 if ( newAttributeValues.isEmpty() )
787 {
788 //nothing to change
789 return true;
790 }
791
792#if 0
793 // prompt for save
794 int res = QMessageBox::information( this, tr( "Multiedit Attributes" ),
795 tr( "Edits will be applied to all selected features." ), QMessageBox::Ok | QMessageBox::Cancel );
796 if ( res != QMessageBox::Ok )
797 {
798 resetMultiEdit();
799 return false;
800 }
801#endif
802
803 mLayer->beginEditCommand( tr( "Updated multiple feature attributes" ) );
804
805 bool success = true;
806
807 const auto constMultiEditFeatureIds = mMultiEditFeatureIds;
808 for ( QgsFeatureId fid : constMultiEditFeatureIds )
809 {
810 QgsAttributeMap::const_iterator aIt = newAttributeValues.constBegin();
811 for ( ; aIt != newAttributeValues.constEnd(); ++aIt )
812 {
813 success &= mLayer->changeAttributeValue( fid, aIt.key(), aIt.value() );
814 }
815 }
816
817 clearMultiEditMessages();
818 if ( success )
819 {
820 mLayer->endEditCommand();
821 mLayer->triggerRepaint();
822 mMultiEditMessageBarItem = new QgsMessageBarItem( tr( "Attribute changes for multiple features applied." ), Qgis::MessageLevel::Success, -1 );
823 }
824 else
825 {
826 mLayer->destroyEditCommand();
827 mMultiEditMessageBarItem = new QgsMessageBarItem( tr( "Changes could not be applied." ), Qgis::MessageLevel::Warning, 0 );
828 }
829
830 if ( !mButtonBox->isVisible() )
831 mMessageBar->pushItem( mMultiEditMessageBarItem );
832 return success;
833}
834
836{
837 return saveWithDetails( nullptr );
838}
839
841{
842 if ( error )
843 error->clear();
844
845 if ( mIsSaving )
846 return true;
847
848 if ( mContext.formMode() == QgsAttributeEditorContext::Embed && !mValidConstraints )
849 {
850 // the feature isn't saved (as per the warning provided), but we return true
851 // so switching features still works
852 return true;
853 }
854
855 for ( QgsWidgetWrapper *wrapper : std::as_const( mWidgets ) )
856 {
857 wrapper->notifyAboutToSave();
858 }
859
860 // only do the dirty checks when editing an existing feature - for new
861 // features we need to add them even if the attributes are unchanged from the initial
862 // default values
863 switch ( mMode )
864 {
869 if ( !mDirty )
870 return true;
871 break;
872
876 break;
877 }
878
879 mIsSaving = true;
880
881 bool success = true;
882
883 emit beforeSave( success );
884
885 // Somebody wants to prevent this form from saving
886 if ( !success )
887 return false;
888
889 switch ( mMode )
890 {
897 success = saveEdits( error );
898 break;
899
901 success = saveMultiEdits();
902 break;
903 }
904
905 mIsSaving = false;
906 mUnsavedMultiEditChanges = false;
907 mDirty = false;
908
909 return success;
910}
911
912
914{
915 mValuesInitialized = false;
916 const auto constMWidgets = mWidgets;
917 for ( QgsWidgetWrapper *ww : constMWidgets )
918 {
919 ww->setFeature( mFeature );
920 }
921
922 // Prepare value dependencies
923 updateFieldDependencies();
924
925 // Update dependent fields
926 for ( QgsWidgetWrapper *ww : constMWidgets )
927 {
928 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
929 if ( !eww )
930 continue;
931
932 // Append field index here, so it's not updated recursively
933 mAlreadyUpdatedFields.append( eww->fieldIdx() );
934 updateValuesDependencies( eww->fieldIdx() );
935 mAlreadyUpdatedFields.removeAll( eww->fieldIdx() );
936 }
937
938 mValuesInitialized = true;
939 mDirty = false;
940}
941
943{
944 const auto widgets { findChildren< QgsAttributeFormEditorWidget * >() };
945 for ( QgsAttributeFormEditorWidget *w : widgets )
946 {
947 w->resetSearch();
948 }
949}
950
951void QgsAttributeForm::clearMultiEditMessages()
952{
953 if ( mMultiEditUnsavedMessageBarItem )
954 {
955 if ( !mButtonBox->isVisible() )
956 mMessageBar->popWidget( mMultiEditUnsavedMessageBarItem );
957 mMultiEditUnsavedMessageBarItem = nullptr;
958 }
959 if ( mMultiEditMessageBarItem )
960 {
961 if ( !mButtonBox->isVisible() )
962 mMessageBar->popWidget( mMultiEditMessageBarItem );
963 mMultiEditMessageBarItem = nullptr;
964 }
965}
966
967QString QgsAttributeForm::createFilterExpression() const
968{
969 QStringList filters;
970 for ( QgsAttributeFormWidget *w : std::as_const( mFormWidgets ) )
971 {
972 QString filter = w->currentFilterExpression();
973 if ( !filter.isEmpty() )
974 filters << filter;
975 }
976
977 if ( filters.isEmpty() )
978 return QString();
979
980 QString filter = filters.join( QLatin1String( ") AND (" ) ).prepend( '(' ).append( ')' );
981 return filter;
982}
983
984QgsExpressionContext QgsAttributeForm::createExpressionContext( const QgsFeature &feature ) const
985{
986 QgsExpressionContext context;
989 if ( mExtraContextScope )
990 context.appendScope( new QgsExpressionContextScope( *mExtraContextScope.get() ) );
991 context.setFeature( feature );
992 return context;
993}
994
995
996void QgsAttributeForm::onAttributeChanged( const QVariant &value, const QVariantList &additionalFieldValues )
997{
998 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( sender() );
999 Q_ASSERT( eww );
1000
1001 bool signalEmitted = false;
1002
1003 if ( mValuesInitialized )
1004 mDirty = true;
1005
1006 mCurrentFormFeature.setAttribute( eww->field().name(), value );
1007
1008 switch ( mMode )
1009 {
1014 {
1016 emit attributeChanged( eww->field().name(), value );
1018 emit widgetValueChanged( eww->field().name(), value, !mIsSettingFeature );
1019
1020 // also emit the signal for additional values
1021 const QStringList additionalFields = eww->additionalFields();
1022 for ( int i = 0; i < additionalFields.count(); i++ )
1023 {
1024 const QString fieldName = additionalFields.at( i );
1025 const QVariant value = additionalFieldValues.at( i );
1026 emit widgetValueChanged( fieldName, value, !mIsSettingFeature );
1027 }
1028
1029 signalEmitted = true;
1030
1031 if ( mValuesInitialized )
1032 updateJoinedFields( *eww );
1033
1034 break;
1035 }
1037 {
1038 if ( !mIsSettingMultiEditFeatures )
1039 {
1040 mUnsavedMultiEditChanges = true;
1041
1042 QLabel *msgLabel = new QLabel( tr( "Unsaved multiedit changes: <a href=\"#apply\">apply changes</a> or <a href=\"#reset\">reset changes</a>." ), mMessageBar );
1043 msgLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
1044 msgLabel->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1045 connect( msgLabel, &QLabel::linkActivated, this, &QgsAttributeForm::multiEditMessageClicked );
1046 clearMultiEditMessages();
1047
1048 mMultiEditUnsavedMessageBarItem = new QgsMessageBarItem( msgLabel, Qgis::MessageLevel::Warning );
1049 if ( !mButtonBox->isVisible() )
1050 mMessageBar->pushItem( mMultiEditUnsavedMessageBarItem );
1051
1052 emit widgetValueChanged( eww->field().name(), value, false );
1053 signalEmitted = true;
1054 }
1055 break;
1056 }
1059 //nothing to do
1060 break;
1061 }
1062
1063 updateConstraints( eww );
1064
1065 // Update dependent fields (only if form is not initializing)
1066 if ( mValuesInitialized )
1067 {
1068 //append field index here, so it's not updated recursive
1069 mAlreadyUpdatedFields.append( eww->fieldIdx() );
1070 updateValuesDependencies( eww->fieldIdx() );
1071 mAlreadyUpdatedFields.removeAll( eww->fieldIdx() );
1072 }
1073
1074 // Updates expression controlled labels and editable state
1075 updateLabels();
1076 updateEditableState();
1077
1078 // Update other widgets pointing to the same field
1079 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->fieldIdx() );
1080 for ( QgsAttributeFormEditorWidget *formEditorWidget : formEditorWidgets )
1081 {
1082 if ( formEditorWidget->editorWidget() == eww )
1083 continue;
1084
1085 // formEditorWidget and eww points to the same field, so block signals
1086 // as there is no need to handle valueChanged again for each duplicate
1087 formEditorWidget->editorWidget()->blockSignals( true );
1088 formEditorWidget->editorWidget()->setValue( value );
1089 formEditorWidget->editorWidget()->blockSignals( false );
1090 }
1091
1092 if ( !signalEmitted )
1093 {
1095 emit attributeChanged( eww->field().name(), value );
1097 bool attributeHasChanged = !mIsSettingFeature;
1099 attributeHasChanged &= !mIsSettingMultiEditFeatures;
1100
1101 emit widgetValueChanged( eww->field().name(), value, attributeHasChanged );
1102 }
1103}
1104
1105void QgsAttributeForm::updateAllConstraints()
1106{
1107 const auto constMWidgets = mWidgets;
1108 for ( QgsWidgetWrapper *ww : constMWidgets )
1109 {
1110 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1111 if ( eww )
1112 updateConstraints( eww );
1113 }
1114}
1115
1116void QgsAttributeForm::updateConstraints( QgsEditorWidgetWrapper *eww )
1117{
1118 // get the current feature set in the form
1119 QgsFeature ft;
1120 if ( currentFormValuesFeature( ft ) )
1121 {
1122 // if the layer is NOT being edited then we only check layer based constraints, and not
1123 // any constraints enforced by the provider. Because:
1124 // 1. we want to keep browsing features nice and responsive. It's nice to give feedback as to whether
1125 // the value checks out, but not if it's too slow to do so. Some constraints (e.g., unique) can be
1126 // expensive to test. A user can freely remove a layer-based constraint if it proves to be too slow
1127 // to test, but they are unlikely to have any control over provider-side constraints
1128 // 2. the provider has already accepted the value, so presumably it doesn't violate the constraint
1129 // and there's no point rechecking!
1130
1131 // update eww constraint
1132 updateConstraint( ft, eww );
1133
1134 // update eww dependencies constraint
1135 const QList<QgsEditorWidgetWrapper *> deps = constraintDependencies( eww );
1136
1137 for ( QgsEditorWidgetWrapper *depsEww : deps )
1138 updateConstraint( ft, depsEww );
1139
1140 // sync OK button status
1141 synchronizeState();
1142
1143 QgsExpressionContext context = createExpressionContext( ft );
1144
1145 // Recheck visibility/collapsed state for all containers which are controlled by this value
1146 const QVector<ContainerInformation *> infos = mContainerInformationDependency.value( eww->field().name() );
1147 for ( ContainerInformation *info : infos )
1148 {
1149 info->apply( &context );
1150 }
1151 }
1152}
1153
1154void QgsAttributeForm::updateContainersVisibility()
1155{
1156 QgsExpressionContext context = createExpressionContext( mFeature );
1157
1158 const QVector<ContainerInformation *> infos = mContainerVisibilityCollapsedInformation;
1159
1160 for ( ContainerInformation *info : infos )
1161 {
1162 info->apply( &context );
1163 }
1164
1165 // Update the constraints if not in multi edit, because
1166 // when mode changes to multi edit, constraints have been already
1167 // updated and a further update will use current form feature values,
1168 // possibly empty for mixed values, leading to false positive
1169 // constraints violations.
1170 if ( mMode != QgsAttributeEditorContext::Mode::MultiEditMode )
1171 {
1172 updateAllConstraints();
1173 }
1174}
1175
1176void QgsAttributeForm::updateConstraint( const QgsFeature &ft, QgsEditorWidgetWrapper *eww )
1177{
1178
1180
1181 if ( eww->layer()->fields().fieldOrigin( eww->fieldIdx() ) == QgsFields::OriginJoin )
1182 {
1183 int srcFieldIdx;
1184 const QgsVectorLayerJoinInfo *info = eww->layer()->joinBuffer()->joinForFieldIndex( eww->fieldIdx(), eww->layer()->fields(), srcFieldIdx );
1185
1186 if ( info && info->joinLayer() && info->isDynamicFormEnabled() )
1187 {
1188 if ( mJoinedFeatures.contains( info ) )
1189 {
1190 eww->updateConstraint( info->joinLayer(), srcFieldIdx, mJoinedFeatures[info], constraintOrigin );
1191 return;
1192 }
1193 else // if we are here, it means there's not joined field for this feature
1194 {
1195 eww->updateConstraint( QgsFeature() );
1196 return;
1197 }
1198 }
1199 }
1200 // default constraint update
1201 eww->updateConstraint( ft, constraintOrigin );
1202
1203}
1204
1205void QgsAttributeForm::updateLabels()
1206{
1207 if ( ! mLabelDataDefinedProperties.isEmpty() )
1208 {
1209 QgsFeature currentFeature;
1210 if ( currentFormValuesFeature( currentFeature ) )
1211 {
1212 QgsExpressionContext context = createExpressionContext( currentFeature );
1213
1214 for ( auto it = mLabelDataDefinedProperties.constBegin() ; it != mLabelDataDefinedProperties.constEnd(); ++it )
1215 {
1216 QLabel *label { it.key() };
1217 bool ok;
1218 const QString value { it->valueAsString( context, QString(), &ok ) };
1219 if ( ok && ! value.isEmpty() )
1220 {
1221 label->setText( value );
1222 }
1223 }
1224 }
1225 }
1226}
1227
1228void QgsAttributeForm::updateEditableState()
1229{
1230 if ( ! mEditableDataDefinedProperties.isEmpty() )
1231 {
1232 QgsFeature currentFeature;
1233 if ( currentFormValuesFeature( currentFeature ) )
1234 {
1235 QgsExpressionContext context = createExpressionContext( currentFeature );
1236
1237 for ( auto it = mEditableDataDefinedProperties.constBegin() ; it != mEditableDataDefinedProperties.constEnd(); ++it )
1238 {
1239 QWidget *w { it.key() };
1240 bool ok;
1241 const bool isEditable { it->valueAsBool( context, true, &ok ) && mLayer && mLayer->isEditable() }; // *NOPAD*
1242 if ( ok )
1243 {
1244 QgsAttributeFormEditorWidget *editorWidget { qobject_cast<QgsAttributeFormEditorWidget *>( w ) };
1245 if ( editorWidget )
1246 {
1247 editorWidget->editorWidget()->setEnabled( isEditable );
1248 }
1249 else
1250 {
1251 w->setEnabled( isEditable );
1252 }
1253 }
1254 }
1255 }
1256 }
1257}
1258
1259bool QgsAttributeForm::currentFormValuesFeature( QgsFeature &feature )
1260{
1261 bool rc = true;
1262 feature = QgsFeature( mFeature );
1264
1265 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1266 {
1267 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1268
1269 if ( !eww )
1270 continue;
1271
1272 if ( dst.count() > eww->fieldIdx() )
1273 {
1274 QVariantList dstVars = QVariantList() << dst.at( eww->fieldIdx() );
1275 QVariantList srcVars = QVariantList() << eww->value();
1276 QList<int> fieldIndexes = QList<int>() << eww->fieldIdx();
1277
1278 // append additional fields
1279 const QStringList additionalFields = eww->additionalFields();
1280 for ( const QString &fieldName : additionalFields )
1281 {
1282 int idx = eww->layer()->fields().lookupField( fieldName );
1283 fieldIndexes << idx;
1284 dstVars << dst.at( idx );
1285 }
1286 srcVars.append( eww->additionalFieldValues() );
1287
1288 Q_ASSERT( dstVars.count() == srcVars.count() );
1289
1290 for ( int i = 0; i < dstVars.count(); i++ )
1291 {
1292 // need to check dstVar.isNull() != srcVar.isNull()
1293 // otherwise if dstVar=NULL and scrVar=0, then dstVar = srcVar
1294 if ( ( !qgsVariantEqual( dstVars[i], srcVars[i] ) || QgsVariantUtils::isNull( dstVars[i] ) != QgsVariantUtils::isNull( srcVars[i] ) ) && srcVars[i].isValid() )
1295 {
1296 dst[fieldIndexes[i]] = srcVars[i];
1297 }
1298 }
1299 }
1300 else
1301 {
1302 rc = false;
1303 break;
1304 }
1305 }
1306
1307 feature.setAttributes( dst );
1308
1309 return rc;
1310}
1311
1312
1313void QgsAttributeForm::registerContainerInformation( QgsAttributeForm::ContainerInformation *info )
1314{
1315 mContainerVisibilityCollapsedInformation.append( info );
1316
1317 const QSet<QString> referencedColumns = info->expression.referencedColumns().unite( info->collapsedExpression.referencedColumns() );
1318
1319 for ( const QString &col : referencedColumns )
1320 {
1321 mContainerInformationDependency[ col ].append( info );
1322 }
1323}
1324
1325bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields, QStringList &descriptions ) const
1326{
1327 bool valid{ true };
1328
1329 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1330 {
1331 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1332 if ( eww )
1333 {
1334 if ( ! eww->isValidConstraint() )
1335 {
1336 invalidFields.append( eww->field().displayName() );
1337
1338 descriptions.append( eww->constraintFailureReason() );
1339
1340 if ( eww->isBlockingCommit() )
1341 valid = false; // continue to get all invalid fields
1342 }
1343 }
1344 }
1345
1346 return valid;
1347}
1348
1349bool QgsAttributeForm::currentFormValidHardConstraints( QStringList &invalidFields, QStringList &descriptions ) const
1350{
1351 bool valid{ true };
1352
1353 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1354 {
1355 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1356 if ( eww )
1357 {
1358 if ( eww->isBlockingCommit() )
1359 {
1360 invalidFields.append( eww->field().displayName() );
1361 descriptions.append( eww->constraintFailureReason() );
1362 valid = false; // continue to get all invalid fields
1363 }
1364 }
1365 }
1366
1367 return valid;
1368}
1369
1370void QgsAttributeForm::onAttributeAdded( int idx )
1371{
1372 mPreventFeatureRefresh = false;
1373 if ( mFeature.isValid() )
1374 {
1375 QgsAttributes attrs = mFeature.attributes();
1376 attrs.insert( idx, QVariant( layer()->fields().at( idx ).type() ) );
1377 mFeature.setFields( layer()->fields() );
1378 mFeature.setAttributes( attrs );
1379 }
1380 init();
1381 setFeature( mFeature );
1382}
1383
1384void QgsAttributeForm::onAttributeDeleted( int idx )
1385{
1386 mPreventFeatureRefresh = false;
1387 if ( mFeature.isValid() )
1388 {
1389 QgsAttributes attrs = mFeature.attributes();
1390 attrs.remove( idx );
1391 mFeature.setFields( layer()->fields() );
1392 mFeature.setAttributes( attrs );
1393 }
1394 init();
1395 setFeature( mFeature );
1396}
1397
1398void QgsAttributeForm::onRelatedFeaturesChanged()
1399{
1400 updateRelatedLayerFields();
1401}
1402
1403void QgsAttributeForm::onUpdatedFields()
1404{
1405 mPreventFeatureRefresh = false;
1406 if ( mFeature.isValid() )
1407 {
1408 QgsAttributes attrs( layer()->fields().size() );
1409 for ( int i = 0; i < layer()->fields().size(); i++ )
1410 {
1411 int idx = mFeature.fields().indexFromName( layer()->fields().at( i ).name() );
1412 if ( idx != -1 )
1413 {
1414 attrs[i] = mFeature.attributes().at( idx );
1415 if ( mFeature.attributes().at( idx ).type() != layer()->fields().at( i ).type() )
1416 {
1417 attrs[i].convert( layer()->fields().at( i ).type() );
1418 }
1419 }
1420 else
1421 {
1422 attrs[i] = QVariant( layer()->fields().at( i ).type() );
1423 }
1424 }
1425 mFeature.setFields( layer()->fields() );
1426 mFeature.setAttributes( attrs );
1427 }
1428 init();
1429 setFeature( mFeature );
1430}
1431
1432void QgsAttributeForm::onConstraintStatusChanged( const QString &constraint,
1433 const QString &description, const QString &err, QgsEditorWidgetWrapper::ConstraintResult result )
1434{
1435 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( sender() );
1436 Q_ASSERT( eww );
1437
1438 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->fieldIdx() );
1439
1440 for ( QgsAttributeFormEditorWidget *formEditorWidget : formEditorWidgets )
1441 formEditorWidget->setConstraintStatus( constraint, description, err, result );
1442}
1443
1444QList<QgsEditorWidgetWrapper *> QgsAttributeForm::constraintDependencies( QgsEditorWidgetWrapper *w )
1445{
1446 QList<QgsEditorWidgetWrapper *> wDeps;
1447 QString name = w->field().name();
1448
1449 // for each widget in the current form
1450 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1451 {
1452 // get the wrapper
1453 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1454 if ( eww )
1455 {
1456 // compare name to not compare w to itself
1457 QString ewwName = eww->field().name();
1458 if ( name != ewwName )
1459 {
1460 // get expression and referencedColumns
1461 QgsExpression expr = eww->layer()->fields().at( eww->fieldIdx() ).constraints().constraintExpression();
1462
1463 const auto referencedColumns = expr.referencedColumns();
1464
1465 for ( const QString &colName : referencedColumns )
1466 {
1467 if ( name == colName )
1468 {
1469 wDeps.append( eww );
1470 break;
1471 }
1472 }
1473 }
1474 }
1475 }
1476
1477 return wDeps;
1478}
1479
1480QgsRelationWidgetWrapper *QgsAttributeForm::setupRelationWidgetWrapper( const QgsRelation &rel, const QgsAttributeEditorContext &context )
1481{
1482 return setupRelationWidgetWrapper( QString(), rel, context );
1483}
1484
1485QgsRelationWidgetWrapper *QgsAttributeForm::setupRelationWidgetWrapper( const QString &relationWidgetTypeId, const QgsRelation &rel, const QgsAttributeEditorContext &context )
1486{
1487 QgsRelationWidgetWrapper *rww = new QgsRelationWidgetWrapper( relationWidgetTypeId, mLayer, rel, nullptr, this );
1488 const QVariantMap config = mLayer->editFormConfig().widgetConfig( rel.id() );
1489 rww->setConfig( config );
1490 rww->setContext( context );
1491
1492 return rww;
1493}
1494
1495void QgsAttributeForm::preventFeatureRefresh()
1496{
1497 mPreventFeatureRefresh = true;
1498}
1499
1501{
1502 if ( mPreventFeatureRefresh || mLayer->isEditable() || !mFeature.isValid() )
1503 return;
1504
1505 // reload feature if layer changed although not editable
1506 // (datasource probably changed bypassing QgsVectorLayer)
1507 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeature.id() ) ).nextFeature( mFeature ) )
1508 return;
1509
1510 init();
1511 setFeature( mFeature );
1512}
1513
1514void QgsAttributeForm::parentFormValueChanged( const QString &attribute, const QVariant &newValue )
1515{
1516 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1517 {
1518 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1519 if ( eww )
1520 {
1521 eww->parentFormValueChanged( attribute, newValue );
1522 }
1523 }
1524}
1525
1527{
1528 return mNeedsGeometry;
1529}
1530
1531void QgsAttributeForm::synchronizeState()
1532{
1533 bool isEditable = ( mFeature.isValid()
1535 || mMode == QgsAttributeEditorContext::MultiEditMode ) && mLayer->isEditable();
1536
1537 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1538 {
1539
1540 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1541 if ( eww )
1542 {
1543 const QList<QgsAttributeFormEditorWidget *> formWidgets = mFormEditorWidgets.values( eww->fieldIdx() );
1544
1545 for ( QgsAttributeFormEditorWidget *formWidget : formWidgets )
1546 formWidget->setConstraintResultVisible( isEditable );
1547
1548 eww->setConstraintResultVisible( isEditable );
1549
1550 bool enabled = isEditable && fieldIsEditable( eww->fieldIdx() );
1551 ww->setEnabled( enabled );
1552
1553 updateIcon( eww );
1554 }
1555 else // handle QgsWidgetWrapper different than QgsEditorWidgetWrapper
1556 {
1557 ww->setEnabled( isEditable );
1558 }
1559
1560 }
1561
1562
1564 {
1565 if ( mMode == QgsAttributeEditorContext::Mode::MultiEditMode && mLayer->selectedFeatureCount() == 0 )
1566 {
1567 isEditable = false;
1568 if ( mConstraintsFailMessageBarItem )
1569 {
1570 mMessageBar->popWidget( mConstraintsFailMessageBarItem );
1571 }
1572 mConstraintsFailMessageBarItem = new QgsMessageBarItem( tr( "Multi edit mode requires at least one selected feature." ), Qgis::MessageLevel::Info, -1 );
1573 mMessageBar->pushItem( mConstraintsFailMessageBarItem );
1574 }
1575 else
1576 {
1577 QStringList invalidFields, descriptions;
1578 mValidConstraints = currentFormValidHardConstraints( invalidFields, descriptions );
1579
1580 if ( isEditable && mContext.formMode() == QgsAttributeEditorContext::Embed )
1581 {
1582 if ( !mValidConstraints && !mConstraintsFailMessageBarItem )
1583 {
1584 mConstraintsFailMessageBarItem = new QgsMessageBarItem( tr( "Changes to this form will not be saved. %n field(s) don't meet their constraints.", "invalid fields", invalidFields.size() ), Qgis::MessageLevel::Warning, -1 );
1585 mMessageBar->pushItem( mConstraintsFailMessageBarItem );
1586 }
1587 else if ( mValidConstraints && mConstraintsFailMessageBarItem )
1588 {
1589 mMessageBar->popWidget( mConstraintsFailMessageBarItem );
1590 mConstraintsFailMessageBarItem = nullptr;
1591 }
1592 }
1593 else if ( mConstraintsFailMessageBarItem )
1594 {
1595 mMessageBar->popWidget( mConstraintsFailMessageBarItem );
1596 mConstraintsFailMessageBarItem = nullptr;
1597 }
1598
1599 isEditable = isEditable & mValidConstraints;
1600 }
1601 }
1602
1603 // change OK button status
1604 QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
1605 if ( okButton )
1606 okButton->setEnabled( isEditable );
1607}
1608
1609void QgsAttributeForm::init()
1610{
1611 QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
1612
1613 // Cleanup of any previously shown widget, we start from scratch
1614 QWidget *formWidget = nullptr;
1615 mNeedsGeometry = false;
1616
1617 bool buttonBoxVisible = true;
1618 // Cleanup button box but preserve visibility
1619 if ( mButtonBox )
1620 {
1621 buttonBoxVisible = mButtonBox->isVisible();
1622 delete mButtonBox;
1623 mButtonBox = nullptr;
1624 }
1625
1626 if ( mSearchButtonBox )
1627 {
1628 delete mSearchButtonBox;
1629 mSearchButtonBox = nullptr;
1630 }
1631
1632 qDeleteAll( mWidgets );
1633 mWidgets.clear();
1634
1635 while ( QWidget *w = this->findChild<QWidget *>() )
1636 {
1637 delete w;
1638 }
1639 delete layout();
1640
1641 QVBoxLayout *vl = new QVBoxLayout();
1642 vl->setContentsMargins( 0, 0, 0, 0 );
1643 mMessageBar = new QgsMessageBar( this );
1644 mMessageBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1645 vl->addWidget( mMessageBar );
1646
1647 setLayout( vl );
1648
1649 // Get a layout
1650 QGridLayout *layout = new QGridLayout();
1651 QWidget *container = new QWidget();
1652 container->setLayout( layout );
1653 vl->addWidget( container );
1654
1655 mFormEditorWidgets.clear();
1656 mFormWidgets.clear();
1657
1658 // a bar to warn the user with non-blocking messages
1659 setContentsMargins( 0, 0, 0, 0 );
1660
1661 // Try to load Ui-File for layout
1662 if ( mContext.allowCustomUi() && mLayer->editFormConfig().layout() == Qgis::AttributeFormLayout::UiFile &&
1663 !mLayer->editFormConfig().uiForm().isEmpty() )
1664 {
1665 QgsDebugMsgLevel( QStringLiteral( "loading form: %1" ).arg( mLayer->editFormConfig().uiForm() ), 2 );
1666 const QString path = mLayer->editFormConfig().uiForm();
1668 if ( file && file->open( QFile::ReadOnly ) )
1669 {
1670 QUiLoader loader;
1671
1672 QFileInfo fi( file->fileName() );
1673 loader.setWorkingDirectory( fi.dir() );
1674 formWidget = loader.load( file, this );
1675 if ( formWidget )
1676 {
1677 formWidget->setWindowFlags( Qt::Widget );
1678 layout->addWidget( formWidget );
1679 formWidget->show();
1680 file->close();
1681 mButtonBox = findChild<QDialogButtonBox *>();
1682 createWrappers();
1683
1684 formWidget->installEventFilter( this );
1685 }
1686 }
1687 }
1688
1689 QgsTabWidget *tabWidget = nullptr;
1690
1691 // Tab layout
1692 if ( !formWidget && mLayer->editFormConfig().layout() == Qgis::AttributeFormLayout::DragAndDrop )
1693 {
1694 int row = 0;
1695 int column = 0;
1696 int columnCount = 1;
1697 bool hasRootFields = false;
1698 bool addSpacer = true;
1699
1700 const QList<QgsAttributeEditorElement *> tabs = mLayer->editFormConfig().tabs();
1701
1702 for ( QgsAttributeEditorElement *widgDef : tabs )
1703 {
1704 if ( widgDef->type() == Qgis::AttributeEditorType::Container )
1705 {
1706 QgsAttributeEditorContainer *containerDef = dynamic_cast<QgsAttributeEditorContainer *>( widgDef );
1707 if ( !containerDef )
1708 continue;
1709
1710 switch ( containerDef->type() )
1711 {
1713 {
1714 tabWidget = nullptr;
1715 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1716 if ( widgetInfo.labelStyle.overrideColor )
1717 {
1718 if ( widgetInfo.labelStyle.color.isValid() )
1719 {
1720 widgetInfo.widget->setStyleSheet( QStringLiteral( "QGroupBox::title { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1721 }
1722 }
1723 if ( widgetInfo.labelStyle.overrideFont )
1724 {
1725 widgetInfo.widget->setFont( widgetInfo.labelStyle.font );
1726 }
1727
1728 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1729 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1730 {
1731 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1732 }
1733 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1734 {
1735 layout->setRowStretch( row, widgDef->verticalStretch() );
1736 addSpacer = false;
1737 }
1738
1739 if ( containerDef->visibilityExpression().enabled() || containerDef->collapsedExpression().enabled() )
1740 {
1741 registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().enabled() ? containerDef->visibilityExpression().data() : QgsExpression(), containerDef->collapsed(), containerDef->collapsedExpression().enabled() ? containerDef->collapsedExpression().data() : QgsExpression() ) );
1742 }
1743 column += 2;
1744 break;
1745 }
1746
1748 {
1749 tabWidget = nullptr;
1750 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1751 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1752 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1753 {
1754 layout->setRowStretch( row, widgDef->verticalStretch() );
1755 addSpacer = false;
1756 }
1757 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1758 {
1759 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1760 }
1761
1762 if ( containerDef->visibilityExpression().enabled() || containerDef->collapsedExpression().enabled() )
1763 {
1764 registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().enabled() ? containerDef->visibilityExpression().data() : QgsExpression(), containerDef->collapsed(), containerDef->collapsedExpression().enabled() ? containerDef->collapsedExpression().data() : QgsExpression() ) );
1765 }
1766 column += 2;
1767 break;
1768 }
1769
1771 {
1772 if ( !tabWidget )
1773 {
1774 tabWidget = new QgsTabWidget();
1775 layout->addWidget( tabWidget, row, column, 1, 2 );
1776 column += 2;
1777 }
1778
1779 QWidget *tabPage = new QWidget( tabWidget );
1780
1781 tabWidget->addTab( tabPage, widgDef->name() );
1782 tabWidget->setTabStyle( tabWidget->tabBar()->count() - 1, widgDef->labelStyle() );
1783
1784 if ( containerDef->visibilityExpression().enabled() )
1785 {
1786 registerContainerInformation( new ContainerInformation( tabWidget, tabPage, containerDef->visibilityExpression().data() ) );
1787 }
1788 QGridLayout *tabPageLayout = new QGridLayout();
1789 tabPage->setLayout( tabPageLayout );
1790
1791 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, tabPage, mLayer, mContext );
1792 tabPageLayout->addWidget( widgetInfo.widget );
1793 break;
1794 }
1795 }
1796 }
1797 else if ( widgDef->type() == Qgis::AttributeEditorType::Relation )
1798 {
1799 hasRootFields = true;
1800 tabWidget = nullptr;
1801 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1802 QgsCollapsibleGroupBox *collapsibleGroupBox = new QgsCollapsibleGroupBox();
1803
1804 if ( widgetInfo.showLabel )
1805 {
1806 if ( widgetInfo.labelStyle.overrideColor && widgetInfo.labelStyle.color.isValid() )
1807 {
1808 collapsibleGroupBox->setStyleSheet( QStringLiteral( "QGroupBox::title { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1809 }
1810
1811 if ( widgetInfo.labelStyle.overrideFont )
1812 {
1813 collapsibleGroupBox->setFont( widgetInfo.labelStyle.font );
1814 }
1815
1816 collapsibleGroupBox->setTitle( widgetInfo.labelText );
1817 }
1818
1819 QVBoxLayout *collapsibleGroupBoxLayout = new QVBoxLayout();
1820 collapsibleGroupBoxLayout->addWidget( widgetInfo.widget );
1821 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
1822
1823 QVBoxLayout *c = new QVBoxLayout();
1824 c->addWidget( collapsibleGroupBox );
1825 layout->addLayout( c, row, column, 1, 2 );
1826
1827 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1828 layout->setRowStretch( row, widgDef->verticalStretch() );
1829 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1830 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1831
1832 column += 2;
1833
1834 // we consider all relation editors should be expanding
1835 addSpacer = false;
1836 }
1837 else
1838 {
1839 hasRootFields = true;
1840 tabWidget = nullptr;
1841 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1842 QLabel *label = new QLabel( widgetInfo.labelText );
1843
1844 if ( widgetInfo.labelStyle.overrideColor )
1845 {
1846 if ( widgetInfo.labelStyle.color.isValid() )
1847 {
1848 label->setStyleSheet( QStringLiteral( "QLabel { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1849 }
1850 }
1851
1852 if ( widgetInfo.labelStyle.overrideFont )
1853 {
1854 label->setFont( widgetInfo.labelStyle.font );
1855 }
1856
1857 label->setToolTip( widgetInfo.toolTip );
1858 if ( columnCount > 1 && !widgetInfo.labelOnTop )
1859 {
1860 label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1861 }
1862
1863 label->setBuddy( widgetInfo.widget );
1864
1865 // If at least one expanding widget is present do not add a spacer
1866 if ( widgetInfo.widget
1867 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
1868 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
1869 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
1870 addSpacer = false;
1871
1872 if ( !widgetInfo.showLabel )
1873 {
1874 QVBoxLayout *c = new QVBoxLayout();
1875 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1876 c->addWidget( widgetInfo.widget );
1877 layout->addLayout( c, row, column, 1, 2 );
1878
1879 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1880 {
1881 layout->setRowStretch( row, widgDef->verticalStretch() );
1882 addSpacer = false;
1883 }
1884 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1885 {
1886 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1887 }
1888
1889 column += 2;
1890 }
1891 else if ( widgetInfo.labelOnTop )
1892 {
1893 QVBoxLayout *c = new QVBoxLayout();
1894 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1895 c->addWidget( label );
1896 c->addWidget( widgetInfo.widget );
1897 layout->addLayout( c, row, column, 1, 2 );
1898
1899 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1900 {
1901 layout->setRowStretch( row, widgDef->verticalStretch() );
1902 addSpacer = false;
1903 }
1904 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1905 {
1906 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1907 }
1908
1909 column += 2;
1910 }
1911 else
1912 {
1913 const int widgetColumn = column + 1;
1914 layout->addWidget( label, row, column++ );
1915 layout->addWidget( widgetInfo.widget, row, column++ );
1916
1917 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1918 {
1919 layout->setRowStretch( row, widgDef->verticalStretch() );
1920 addSpacer = false;
1921 }
1922 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( widgetColumn ) )
1923 {
1924 layout->setColumnStretch( widgetColumn, widgDef->horizontalStretch() );
1925 }
1926 }
1927
1928 // Alias DD overrides
1929 if ( widgDef->type() == Qgis::AttributeEditorType::Field )
1930 {
1931 const QgsAttributeEditorField *fieldElement { static_cast<QgsAttributeEditorField *>( widgDef ) };
1932 const int fieldIdx = fieldElement->idx();
1933 if ( fieldIdx >= 0 && fieldIdx < mLayer->fields().count() )
1934 {
1935 const QString fieldName { mLayer->fields().at( fieldIdx ).name() };
1936 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Alias ) )
1937 {
1938 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Alias ) };
1939 if ( property.isActive() )
1940 {
1941 mLabelDataDefinedProperties[ label ] = property;
1942 }
1943 }
1944 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Editable ) )
1945 {
1946 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Editable ) };
1947 if ( property.isActive() )
1948 {
1949 mEditableDataDefinedProperties[ widgetInfo.widget ] = property;
1950 }
1951 }
1952 }
1953 }
1954 }
1955
1956 if ( column >= columnCount * 2 )
1957 {
1958 column = 0;
1959 row += 1;
1960 }
1961 }
1962
1963 if ( hasRootFields && addSpacer )
1964 {
1965 QSpacerItem *spacerItem = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
1966 layout->addItem( spacerItem, row, 0 );
1967 layout->setRowStretch( row, 1 );
1968 }
1969
1970 formWidget = container;
1971 }
1972
1973 // Autogenerate Layout
1974 // If there is still no layout loaded (defined as autogenerate or other methods failed)
1975 mIconMap.clear();
1976
1977 if ( !formWidget )
1978 {
1979 formWidget = new QWidget( this );
1980 QGridLayout *gridLayout = new QGridLayout( formWidget );
1981 formWidget->setLayout( gridLayout );
1982
1983 if ( mContext.formMode() != QgsAttributeEditorContext::Embed )
1984 {
1985 // put the form into a scroll area to nicely handle cases with lots of attributes
1986 QgsScrollArea *scrollArea = new QgsScrollArea( this );
1987 scrollArea->setWidget( formWidget );
1988 scrollArea->setWidgetResizable( true );
1989 scrollArea->setFrameShape( QFrame::NoFrame );
1990 scrollArea->setFrameShadow( QFrame::Plain );
1991 scrollArea->setFocusProxy( this );
1992 layout->addWidget( scrollArea );
1993 }
1994 else
1995 {
1996 layout->addWidget( formWidget );
1997 }
1998
1999 int row = 0;
2000
2001 const QgsFields fields = mLayer->fields();
2002
2003 for ( const QgsField &field : fields )
2004 {
2005 int idx = fields.lookupField( field.name() );
2006 if ( idx < 0 )
2007 continue;
2008
2009 //show attribute alias if available
2010 QString fieldName = mLayer->attributeDisplayName( idx );
2011 QString labelText = fieldName;
2012 labelText.replace( '&', QLatin1String( "&&" ) ); // need to escape '&' or they'll be replace by _ in the label text
2013
2014 const QgsEditorWidgetSetup widgetSetup = QgsGui::editorWidgetRegistry()->findBest( mLayer, field.name() );
2015
2016 if ( widgetSetup.type() == QLatin1String( "Hidden" ) )
2017 continue;
2018
2019 bool labelOnTop = mLayer->editFormConfig().labelOnTop( idx );
2020
2021 // This will also create the widget
2022 QLabel *label = new QLabel( labelText );
2023 label->setToolTip( QgsFieldModel::fieldToolTipExtended( field, mLayer ) );
2024 QSvgWidget *i = new QSvgWidget();
2025 i->setFixedSize( 18, 18 );
2026
2027 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Alias ) )
2028 {
2029 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Alias ) };
2030 if ( property.isActive() )
2031 {
2032 mLabelDataDefinedProperties[ label ] = property;
2033 }
2034 }
2035
2036 QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( widgetSetup.type(), mLayer, idx, widgetSetup.config(), nullptr, this, mContext );
2037
2038 QWidget *w = nullptr;
2039 if ( eww )
2040 {
2041 QgsAttributeFormEditorWidget *formWidget = new QgsAttributeFormEditorWidget( eww, widgetSetup.type(), this );
2042 w = formWidget;
2043 mFormEditorWidgets.insert( idx, formWidget );
2044 mFormWidgets.append( formWidget );
2045 formWidget->createSearchWidgetWrappers( mContext );
2046
2047 label->setBuddy( eww->widget() );
2048
2049 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Editable ) )
2050 {
2051 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Editable ) };
2052 if ( property.isActive() )
2053 {
2054 mEditableDataDefinedProperties[ formWidget ] = property;
2055 }
2056 }
2057 }
2058 else
2059 {
2060 w = new QLabel( QStringLiteral( "<p style=\"color: red; font-style: italic;\">%1</p>" ).arg( tr( "Failed to create widget with type '%1'" ).arg( widgetSetup.type() ) ) );
2061 }
2062
2063
2064 if ( w )
2065 w->setObjectName( field.name() );
2066
2067 if ( eww )
2068 {
2069 mWidgets.append( eww );
2070 mIconMap[eww->widget()] = i;
2071 }
2072
2073 if ( labelOnTop )
2074 {
2075 gridLayout->addWidget( label, row++, 0, 1, 2 );
2076 gridLayout->addWidget( w, row++, 0, 1, 2 );
2077 gridLayout->addWidget( i, row++, 0, 1, 2 );
2078 }
2079 else
2080 {
2081 gridLayout->addWidget( label, row, 0 );
2082 gridLayout->addWidget( w, row, 1 );
2083 gridLayout->addWidget( i, row++, 2 );
2084 }
2085
2086 }
2087
2088 const QList<QgsRelation> relations = QgsProject::instance()->relationManager()->referencedRelations( mLayer );
2089 for ( const QgsRelation &rel : relations )
2090 {
2091 QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( QStringLiteral( "relation_editor" ), rel, mContext );
2092
2094 formWidget->createSearchWidgetWrappers( mContext );
2095
2096 QgsCollapsibleGroupBox *collapsibleGroupBox = new QgsCollapsibleGroupBox( rel.name() );
2097 QVBoxLayout *collapsibleGroupBoxLayout = new QVBoxLayout();
2098 collapsibleGroupBoxLayout->addWidget( formWidget );
2099 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
2100
2101 gridLayout->addWidget( collapsibleGroupBox, row++, 0, 1, 2 );
2102
2103 mWidgets.append( rww );
2104 mFormWidgets.append( formWidget );
2105 }
2106
2107 if ( QgsProject::instance()->relationManager()->referencedRelations( mLayer ).isEmpty() )
2108 {
2109 QSpacerItem *spacerItem = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
2110 gridLayout->addItem( spacerItem, row, 0 );
2111 gridLayout->setRowStretch( row, 1 );
2112 row++;
2113 }
2114 }
2115
2116 updateFieldDependencies();
2117
2118 if ( !mButtonBox )
2119 {
2120 mButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
2121 mButtonBox->setObjectName( QStringLiteral( "buttonBox" ) );
2122 layout->addWidget( mButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2123 }
2124 mButtonBox->setVisible( buttonBoxVisible );
2125
2126 if ( !mSearchButtonBox )
2127 {
2128 mSearchButtonBox = new QWidget();
2129 QHBoxLayout *boxLayout = new QHBoxLayout();
2130 boxLayout->setContentsMargins( 0, 0, 0, 0 );
2131 mSearchButtonBox->setLayout( boxLayout );
2132 mSearchButtonBox->setObjectName( QStringLiteral( "searchButtonBox" ) );
2133
2134 QPushButton *clearButton = new QPushButton( tr( "&Reset Form" ), mSearchButtonBox );
2135 connect( clearButton, &QPushButton::clicked, this, &QgsAttributeForm::resetSearch );
2136 boxLayout->addWidget( clearButton );
2137 boxLayout->addStretch( 1 );
2138
2139 QPushButton *flashButton = new QPushButton();
2140 flashButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2141 flashButton->setText( tr( "&Flash Features" ) );
2142 connect( flashButton, &QToolButton::clicked, this, &QgsAttributeForm::searchFlash );
2143 boxLayout->addWidget( flashButton );
2144
2145 QPushButton *openAttributeTableButton = new QPushButton();
2146 openAttributeTableButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2147 openAttributeTableButton->setText( tr( "Show in &Table" ) );
2148 openAttributeTableButton->setToolTip( tr( "Open the attribute table editor with the filtered features" ) );
2149 connect( openAttributeTableButton, &QToolButton::clicked, this, [ = ]
2150 {
2151 emit openFilteredFeaturesAttributeTable( createFilterExpression() );
2152 } );
2153 boxLayout->addWidget( openAttributeTableButton );
2154
2155 QPushButton *zoomButton = new QPushButton();
2156 zoomButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2157 zoomButton->setText( tr( "&Zoom to Features" ) );
2158 connect( zoomButton, &QToolButton::clicked, this, &QgsAttributeForm::searchZoomTo );
2159 boxLayout->addWidget( zoomButton );
2160
2161 QToolButton *selectButton = new QToolButton();
2162 selectButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2163 selectButton->setText( tr( "&Select Features" ) );
2164 selectButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFormSelect.svg" ) ) );
2165 selectButton->setPopupMode( QToolButton::MenuButtonPopup );
2166 selectButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
2167 connect( selectButton, &QToolButton::clicked, this, &QgsAttributeForm::searchSetSelection );
2168 QMenu *selectMenu = new QMenu( selectButton );
2169 QAction *selectAction = new QAction( tr( "Select Features" ), selectMenu );
2170 selectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFormSelect.svg" ) ) );
2171 connect( selectAction, &QAction::triggered, this, &QgsAttributeForm::searchSetSelection );
2172 selectMenu->addAction( selectAction );
2173 QAction *addSelectAction = new QAction( tr( "Add to Current Selection" ), selectMenu );
2174 addSelectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectAdd.svg" ) ) );
2175 connect( addSelectAction, &QAction::triggered, this, &QgsAttributeForm::searchAddToSelection );
2176 selectMenu->addAction( addSelectAction );
2177 QAction *deselectAction = new QAction( tr( "Remove from Current Selection" ), selectMenu );
2178 deselectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectRemove.svg" ) ) );
2179 connect( deselectAction, &QAction::triggered, this, &QgsAttributeForm::searchRemoveFromSelection );
2180 selectMenu->addAction( deselectAction );
2181 QAction *filterSelectAction = new QAction( tr( "Filter Current Selection" ), selectMenu );
2182 filterSelectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectIntersect.svg" ) ) );
2183 connect( filterSelectAction, &QAction::triggered, this, &QgsAttributeForm::searchIntersectSelection );
2184 selectMenu->addAction( filterSelectAction );
2185 selectButton->setMenu( selectMenu );
2186 boxLayout->addWidget( selectButton );
2187
2188 if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
2189 {
2190 QToolButton *filterButton = new QToolButton();
2191 filterButton->setText( tr( "Filter Features" ) );
2192 filterButton->setPopupMode( QToolButton::MenuButtonPopup );
2193 filterButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2194 connect( filterButton, &QToolButton::clicked, this, &QgsAttributeForm::filterTriggered );
2195 QMenu *filterMenu = new QMenu( filterButton );
2196 QAction *filterAndAction = new QAction( tr( "Filter Within (\"AND\")" ), filterMenu );
2197 connect( filterAndAction, &QAction::triggered, this, &QgsAttributeForm::filterAndTriggered );
2198 filterMenu->addAction( filterAndAction );
2199 QAction *filterOrAction = new QAction( tr( "Extend Filter (\"OR\")" ), filterMenu );
2200 connect( filterOrAction, &QAction::triggered, this, &QgsAttributeForm::filterOrTriggered );
2201 filterMenu->addAction( filterOrAction );
2202 filterButton->setMenu( filterMenu );
2203 boxLayout->addWidget( filterButton );
2204 }
2205 else
2206 {
2207 QPushButton *closeButton = new QPushButton( tr( "Close" ), mSearchButtonBox );
2208 connect( closeButton, &QPushButton::clicked, this, &QgsAttributeForm::closed );
2209 closeButton->setShortcut( Qt::Key_Escape );
2210 boxLayout->addWidget( closeButton );
2211 }
2212
2213 layout->addWidget( mSearchButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2214 }
2215 mSearchButtonBox->setVisible( mMode == QgsAttributeEditorContext::SearchMode );
2216
2217 afterWidgetInit();
2218
2219 connect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsAttributeForm::save );
2220 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsAttributeForm::resetValues );
2221
2222 connect( mLayer, &QgsVectorLayer::editingStarted, this, &QgsAttributeForm::synchronizeState );
2223 connect( mLayer, &QgsVectorLayer::editingStopped, this, &QgsAttributeForm::synchronizeState );
2224
2225 // This triggers a refresh of the form widget and gives a chance to re-format the
2226 // value to those widgets that have a different representation when in edit mode
2229
2230
2231 const auto constMInterfaces = mInterfaces;
2232 for ( QgsAttributeFormInterface *iface : constMInterfaces )
2233 {
2234 iface->initForm();
2235 }
2236
2238 {
2239 hideButtonBox();
2240 }
2241
2242 QApplication::restoreOverrideCursor();
2243}
2244
2245void QgsAttributeForm::cleanPython()
2246{
2247 if ( !mPyFormVarName.isNull() )
2248 {
2249 QString expr = QStringLiteral( "if '%1' in locals(): del %1\n" ).arg( mPyFormVarName );
2250 QgsPythonRunner::run( expr );
2251 }
2252}
2253
2254void QgsAttributeForm::initPython()
2255{
2256 cleanPython();
2257
2258 // Init Python, if init function is not empty and the combo indicates
2259 // the source for the function code
2260 if ( !mLayer->editFormConfig().initFunction().isEmpty()
2261 && mLayer->editFormConfig().initCodeSource() != Qgis::AttributeFormPythonInitCodeSource::NoSource )
2262 {
2263
2264 QString initFunction = mLayer->editFormConfig().initFunction();
2265 QString initFilePath = mLayer->editFormConfig().initFilePath();
2266 QString initCode;
2267
2268 switch ( mLayer->editFormConfig().initCodeSource() )
2269 {
2270 case Qgis::AttributeFormPythonInitCodeSource::File:
2271 if ( !initFilePath.isEmpty() )
2272 {
2273 QFile *inputFile = QgsApplication::networkContentFetcherRegistry()->localFile( initFilePath );
2274
2275 if ( inputFile && inputFile->open( QFile::ReadOnly ) )
2276 {
2277 // Read it into a string
2278 QTextStream inf( inputFile );
2279#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2280 inf.setCodec( "UTF-8" );
2281#endif
2282 initCode = inf.readAll();
2283 inputFile->close();
2284 }
2285 else // The file couldn't be opened
2286 {
2287 QgsLogger::warning( QStringLiteral( "The external python file path %1 could not be opened!" ).arg( initFilePath ) );
2288 }
2289 }
2290 else
2291 {
2292 QgsLogger::warning( QStringLiteral( "The external python file path is empty!" ) );
2293 }
2294 break;
2295
2296 case Qgis::AttributeFormPythonInitCodeSource::Dialog:
2297 initCode = mLayer->editFormConfig().initCode();
2298 if ( initCode.isEmpty() )
2299 {
2300 QgsLogger::warning( QStringLiteral( "The python code provided in the dialog is empty!" ) );
2301 }
2302 break;
2303
2304 case Qgis::AttributeFormPythonInitCodeSource::Environment:
2305 case Qgis::AttributeFormPythonInitCodeSource::NoSource:
2306 // Nothing to do: the function code should be already in the environment
2307 break;
2308 }
2309
2310 // If we have a function code, run it
2311 if ( !initCode.isEmpty() )
2312 {
2314 QgsPythonRunner::run( initCode );
2315 else
2316 mMessageBar->pushMessage( QString(),
2317 tr( "Python macro could not be run due to missing permissions." ),
2318 Qgis::MessageLevel::Warning );
2319 }
2320
2321 QgsPythonRunner::run( QStringLiteral( "import inspect" ) );
2322 QString numArgs;
2323
2324 // Check for eval result
2325 if ( QgsPythonRunner::eval( QStringLiteral( "len(inspect.getfullargspec(%1)[0])" ).arg( initFunction ), numArgs ) )
2326 {
2327 static int sFormId = 0;
2328 mPyFormVarName = QStringLiteral( "_qgis_featureform_%1_%2" ).arg( mFormNr ).arg( sFormId++ );
2329
2330 QString form = QStringLiteral( "%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )" )
2331 .arg( mPyFormVarName )
2332 .arg( ( quint64 ) this );
2333
2334 QgsPythonRunner::run( form );
2335
2336 QgsDebugMsgLevel( QStringLiteral( "running featureForm init: %1" ).arg( mPyFormVarName ), 2 );
2337
2338 // Legacy
2339 if ( numArgs == QLatin1String( "3" ) )
2340 {
2341 addInterface( new QgsAttributeFormLegacyInterface( initFunction, mPyFormVarName, this ) );
2342 }
2343 else
2344 {
2345 // If we get here, it means that the function doesn't accept three arguments
2346 QMessageBox msgBox;
2347 msgBox.setText( tr( "The python init function (<code>%1</code>) does not accept three arguments as expected!<br>Please check the function name in the <b>Fields</b> tab of the layer properties." ).arg( initFunction ) );
2348 msgBox.exec();
2349#if 0
2350 QString expr = QString( "%1(%2)" )
2351 .arg( mLayer->editFormInit() )
2352 .arg( mPyFormVarName );
2353 QgsAttributeFormInterface *iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface *>( expr, "QgsAttributeFormInterface" );
2354 if ( iface )
2355 addInterface( iface );
2356#endif
2357 }
2358 }
2359 else
2360 {
2361 // If we get here, it means that inspect couldn't find the function
2362 QMessageBox msgBox;
2363 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 ) );
2364 msgBox.exec();
2365 }
2366 }
2367}
2368
2369QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAttributeEditorElement *widgetDef, QWidget *parent, QgsVectorLayer *vl, QgsAttributeEditorContext &context )
2370{
2371 WidgetInfo newWidgetInfo;
2372
2373 newWidgetInfo.labelStyle = widgetDef->labelStyle();
2374
2375 switch ( widgetDef->type() )
2376 {
2377 case Qgis::AttributeEditorType::Action:
2378 {
2379 const QgsAttributeEditorAction *elementDef = dynamic_cast<const QgsAttributeEditorAction *>( widgetDef );
2380 if ( !elementDef )
2381 break;
2382
2383 QgsActionWidgetWrapper *actionWrapper = new QgsActionWidgetWrapper( mLayer, nullptr, this );
2384 actionWrapper->setAction( elementDef->action( vl ) );
2385 context.setAttributeFormMode( mMode );
2386 actionWrapper->setContext( context );
2387 mWidgets.append( actionWrapper );
2388 newWidgetInfo.widget = actionWrapper->widget();
2389 newWidgetInfo.showLabel = false;
2390
2391 break;
2392 }
2393
2394 case Qgis::AttributeEditorType::Field:
2395 {
2396 const QgsAttributeEditorField *fieldDef = dynamic_cast<const QgsAttributeEditorField *>( widgetDef );
2397 if ( !fieldDef )
2398 break;
2399
2400 const QgsFields fields = vl->fields();
2401 int fldIdx = fields.lookupField( fieldDef->name() );
2402 if ( fldIdx < fields.count() && fldIdx >= 0 )
2403 {
2404 const QgsEditorWidgetSetup widgetSetup = QgsGui::editorWidgetRegistry()->findBest( mLayer, fieldDef->name() );
2405
2406 QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( widgetSetup.type(), mLayer, fldIdx, widgetSetup.config(), nullptr, this, mContext );
2407 QgsAttributeFormEditorWidget *formWidget = new QgsAttributeFormEditorWidget( eww, widgetSetup.type(), this );
2408 mFormEditorWidgets.insert( fldIdx, formWidget );
2409 mFormWidgets.append( formWidget );
2410
2411 formWidget->createSearchWidgetWrappers( mContext );
2412
2413 newWidgetInfo.widget = formWidget;
2414 mWidgets.append( eww );
2415
2416 newWidgetInfo.widget->setObjectName( fields.at( fldIdx ).name() );
2417 newWidgetInfo.hint = fields.at( fldIdx ).comment();
2418 }
2419
2420 newWidgetInfo.labelOnTop = mLayer->editFormConfig().labelOnTop( fldIdx );
2421 newWidgetInfo.labelText = mLayer->attributeDisplayName( fldIdx );
2422 newWidgetInfo.labelText.replace( '&', QLatin1String( "&&" ) ); // need to escape '&' or they'll be replace by _ in the label text
2423 newWidgetInfo.toolTip = QStringLiteral( "<b>%1</b><p>%2</p>" ).arg( mLayer->attributeDisplayName( fldIdx ), newWidgetInfo.hint );
2424 newWidgetInfo.showLabel = widgetDef->showLabel();
2425
2426 break;
2427 }
2428
2429 case Qgis::AttributeEditorType::Relation:
2430 {
2431 const QgsAttributeEditorRelation *relDef = static_cast<const QgsAttributeEditorRelation *>( widgetDef );
2432
2433 QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( relDef->relationWidgetTypeId(), relDef->relation(), context );
2434
2436 formWidget->createSearchWidgetWrappers( mContext );
2437
2438 // This needs to be after QgsAttributeFormRelationEditorWidget creation, because the widget
2439 // does not exists yet until QgsAttributeFormRelationEditorWidget is created and the setters
2440 // below directly alter the widget and check for it.
2442 rww->setNmRelationId( relDef->nmRelationId() );
2444
2445 mWidgets.append( rww );
2446 mFormWidgets.append( formWidget );
2447
2448 newWidgetInfo.widget = formWidget;
2449 newWidgetInfo.showLabel = relDef->showLabel();
2450 newWidgetInfo.labelText = relDef->label();
2451 if ( newWidgetInfo.labelText.isEmpty() )
2452 newWidgetInfo.labelText = rww->relation().name();
2453 newWidgetInfo.labelOnTop = true;
2454 break;
2455 }
2456
2457 case Qgis::AttributeEditorType::Container:
2458 {
2459 const QgsAttributeEditorContainer *container = dynamic_cast<const QgsAttributeEditorContainer *>( widgetDef );
2460 if ( !container )
2461 break;
2462
2463 int columnCount = container->columnCount();
2464
2465 if ( columnCount <= 0 )
2466 columnCount = 1;
2467
2468 QString widgetName;
2469 QWidget *myContainer = nullptr;
2470 bool removeLayoutMargin = false;
2471 switch ( container->type() )
2472 {
2474 {
2476 widgetName = QStringLiteral( "QGroupBox" );
2477 if ( container->showLabel() )
2478 {
2479 groupBox->setTitle( container->name() );
2480 if ( newWidgetInfo.labelStyle.overrideColor )
2481 {
2482 if ( newWidgetInfo.labelStyle.color.isValid() )
2483 {
2484 groupBox->setStyleSheet( QStringLiteral( "QGroupBox::title { color: %1; }" ).arg( newWidgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2485 }
2486 }
2487 if ( newWidgetInfo.labelStyle.overrideFont )
2488 {
2489 groupBox->setFont( newWidgetInfo.labelStyle.font );
2490 }
2491 }
2492 myContainer = groupBox;
2493 newWidgetInfo.widget = myContainer;
2494 groupBox->setCollapsed( container->collapsed() );
2495 break;
2496 }
2497
2499 {
2500 QWidget *rowWidget = new QWidget();
2501 widgetName = QStringLiteral( "Row" );
2502 myContainer = rowWidget;
2503 newWidgetInfo.widget = myContainer;
2504 removeLayoutMargin = true;
2505 columnCount = container->children().size();
2506 break;
2507 }
2508
2510 {
2511 myContainer = new QWidget();
2512
2513 QgsScrollArea *scrollArea = new QgsScrollArea( parent );
2514
2515 scrollArea->setWidget( myContainer );
2516 scrollArea->setWidgetResizable( true );
2517 scrollArea->setFrameShape( QFrame::NoFrame );
2518 widgetName = QStringLiteral( "QScrollArea QWidget" );
2519
2520 newWidgetInfo.widget = scrollArea;
2521 break;
2522 }
2523 }
2524
2525 if ( container->backgroundColor().isValid() )
2526 {
2527 QString style {QStringLiteral( "background-color: %1;" ).arg( container->backgroundColor().name() )};
2528 newWidgetInfo.widget->setStyleSheet( style );
2529 }
2530
2531 QGridLayout *gbLayout = new QGridLayout();
2532 if ( removeLayoutMargin )
2533 gbLayout->setContentsMargins( 0, 0, 0, 0 );
2534 myContainer->setLayout( gbLayout );
2535
2536 int row = 0;
2537 int column = 0;
2538 bool addSpacer = true;
2539
2540 const QList<QgsAttributeEditorElement *> children = container->children();
2541
2542 for ( QgsAttributeEditorElement *childDef : children )
2543 {
2544 WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context );
2545
2546 if ( childDef->type() == Qgis::AttributeEditorType::Container )
2547 {
2548 QgsAttributeEditorContainer *containerDef = static_cast<QgsAttributeEditorContainer *>( childDef );
2549 if ( containerDef->visibilityExpression().enabled() || containerDef->collapsedExpression().enabled() )
2550 {
2551 registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().enabled() ? containerDef->visibilityExpression().data() : QgsExpression(), containerDef->collapsed(), containerDef->collapsedExpression().enabled() ? containerDef->collapsedExpression().data() : QgsExpression() ) );
2552 }
2553 }
2554
2555 // column containing the actual widget, not the label
2556 int widgetColumn = column;
2557
2558 if ( widgetInfo.labelText.isNull() || ! widgetInfo.showLabel )
2559 {
2560 gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 );
2561 widgetColumn = column + 1;
2562 column += 2;
2563 }
2564 else
2565 {
2566 QLabel *mypLabel = new QLabel( widgetInfo.labelText );
2567
2568 if ( widgetInfo.labelStyle.overrideColor )
2569 {
2570 if ( widgetInfo.labelStyle.color.isValid() )
2571 {
2572 mypLabel->setStyleSheet( QStringLiteral( "QLabel { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2573 }
2574 }
2575
2576 if ( widgetInfo.labelStyle.overrideFont )
2577 {
2578 mypLabel->setFont( widgetInfo.labelStyle.font );
2579 }
2580
2581 // Alias DD overrides
2582 if ( childDef->type() == Qgis::AttributeEditorType::Field )
2583 {
2584 const QgsAttributeEditorField *fieldDef { static_cast<QgsAttributeEditorField *>( childDef ) };
2585 const QgsFields fields = vl->fields();
2586 const int fldIdx = fieldDef->idx();
2587 if ( fldIdx < fields.count() && fldIdx >= 0 )
2588 {
2589 const QString fieldName { fields.at( fldIdx ).name() };
2590 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Alias ) )
2591 {
2592 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Alias ) };
2593 if ( property.isActive() )
2594 {
2595 mLabelDataDefinedProperties[ mypLabel ] = property;
2596 }
2597 }
2598 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Editable ) )
2599 {
2600 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Editable ) };
2601 if ( property.isActive() )
2602 {
2603 mEditableDataDefinedProperties[ widgetInfo.widget ] = property;
2604 }
2605 }
2606 }
2607 }
2608
2609 mypLabel->setToolTip( widgetInfo.toolTip );
2610 if ( columnCount > 1 && !widgetInfo.labelOnTop )
2611 {
2612 mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
2613 }
2614
2615 mypLabel->setBuddy( widgetInfo.widget );
2616
2617 if ( widgetInfo.labelOnTop )
2618 {
2619 widgetColumn = column + 1;
2620 QVBoxLayout *c = new QVBoxLayout();
2621 mypLabel->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
2622 c->layout()->addWidget( mypLabel );
2623 c->layout()->addWidget( widgetInfo.widget );
2624 gbLayout->addLayout( c, row, column, 1, 2 );
2625 column += 2;
2626 }
2627 else
2628 {
2629 widgetColumn = column + 1;
2630 gbLayout->addWidget( mypLabel, row, column++ );
2631 gbLayout->addWidget( widgetInfo.widget, row, column++ );
2632 }
2633 }
2634
2635 const int childHorizontalStretch = childDef->horizontalStretch();
2636 const int existingColumnStretch = gbLayout->columnStretch( widgetColumn );
2637 if ( childHorizontalStretch > 0 && childHorizontalStretch > existingColumnStretch )
2638 {
2639 gbLayout->setColumnStretch( widgetColumn, childHorizontalStretch );
2640 }
2641
2642 if ( childDef->verticalStretch() > 0 && childDef->verticalStretch() > gbLayout->rowStretch( row ) )
2643 {
2644 gbLayout->setRowStretch( row, childDef->verticalStretch() );
2645 }
2646
2647 if ( column >= columnCount * 2 )
2648 {
2649 column = 0;
2650 row += 1;
2651 }
2652
2653 if ( widgetInfo.widget
2654 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
2655 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
2656 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
2657 addSpacer = false;
2658
2659 // we consider all relation editors should be expanding
2660 if ( qobject_cast<QgsAttributeFormRelationEditorWidget *>( widgetInfo.widget ) )
2661 addSpacer = false;
2662 }
2663
2664 if ( addSpacer )
2665 {
2666 QWidget *spacer = new QWidget();
2667 spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
2668 gbLayout->addWidget( spacer, ++row, 0 );
2669 gbLayout->setRowStretch( row, 1 );
2670 }
2671
2672 newWidgetInfo.labelText = QString();
2673 newWidgetInfo.labelOnTop = true;
2674 newWidgetInfo.showLabel = widgetDef->showLabel();
2675 break;
2676 }
2677
2678 case Qgis::AttributeEditorType::QmlElement:
2679 {
2680 const QgsAttributeEditorQmlElement *elementDef = static_cast<const QgsAttributeEditorQmlElement *>( widgetDef );
2681
2682 QgsQmlWidgetWrapper *qmlWrapper = new QgsQmlWidgetWrapper( mLayer, nullptr, this );
2683 qmlWrapper->setQmlCode( elementDef->qmlCode() );
2684 context.setAttributeFormMode( mMode );
2685 qmlWrapper->setContext( context );
2686
2687 mWidgets.append( qmlWrapper );
2688
2689 newWidgetInfo.widget = qmlWrapper->widget();
2690 newWidgetInfo.labelText = elementDef->name();
2691 newWidgetInfo.labelOnTop = true;
2692 newWidgetInfo.showLabel = widgetDef->showLabel();
2693 break;
2694 }
2695
2696 case Qgis::AttributeEditorType::HtmlElement:
2697 {
2698 const QgsAttributeEditorHtmlElement *elementDef = static_cast<const QgsAttributeEditorHtmlElement *>( widgetDef );
2699
2700 QgsHtmlWidgetWrapper *htmlWrapper = new QgsHtmlWidgetWrapper( mLayer, nullptr, this );
2701 context.setAttributeFormMode( mMode );
2702 htmlWrapper->setHtmlCode( elementDef->htmlCode() );
2703 htmlWrapper->reinitWidget();
2704 mWidgets.append( htmlWrapper );
2705
2706 newWidgetInfo.widget = htmlWrapper->widget();
2707 newWidgetInfo.labelText = elementDef->name();
2708 newWidgetInfo.labelOnTop = true;
2709 newWidgetInfo.showLabel = widgetDef->showLabel();
2710 mNeedsGeometry |= htmlWrapper->needsGeometry();
2711 break;
2712 }
2713
2714 case Qgis::AttributeEditorType::TextElement:
2715 {
2716 const QgsAttributeEditorTextElement *elementDef = static_cast<const QgsAttributeEditorTextElement *>( widgetDef );
2717
2718 QgsTextWidgetWrapper *textWrapper = new QgsTextWidgetWrapper( mLayer, nullptr, this );
2719 context.setAttributeFormMode( mMode );
2720 textWrapper->setText( elementDef->text() );
2721 textWrapper->reinitWidget();
2722 mWidgets.append( textWrapper );
2723
2724 newWidgetInfo.widget = textWrapper->widget();
2725 newWidgetInfo.labelText = elementDef->name();
2726 newWidgetInfo.labelOnTop = false;
2727 newWidgetInfo.showLabel = widgetDef->showLabel();
2728 mNeedsGeometry |= textWrapper->needsGeometry();
2729 break;
2730 }
2731
2732 case Qgis::AttributeEditorType::SpacerElement:
2733 {
2734 const QgsAttributeEditorSpacerElement *elementDef = static_cast<const QgsAttributeEditorSpacerElement *>( widgetDef );
2735 QgsSpacerWidgetWrapper *spacerWrapper = new QgsSpacerWidgetWrapper( mLayer, nullptr, this );
2736 spacerWrapper->setDrawLine( elementDef->drawLine() );
2737 context.setAttributeFormMode( mMode );
2738 mWidgets.append( spacerWrapper );
2739
2740 newWidgetInfo.widget = spacerWrapper->widget();
2741 newWidgetInfo.labelOnTop = false;
2742 newWidgetInfo.showLabel = false;
2743 break;
2744 }
2745
2746 default:
2747 QgsDebugError( QStringLiteral( "Unknown attribute editor widget type encountered..." ) );
2748 break;
2749 }
2750
2751 return newWidgetInfo;
2752}
2753
2754void QgsAttributeForm::createWrappers()
2755{
2756 QList<QWidget *> myWidgets = findChildren<QWidget *>();
2757 const QList<QgsField> fields = mLayer->fields().toList();
2758
2759 const auto constMyWidgets = myWidgets;
2760 for ( QWidget *myWidget : constMyWidgets )
2761 {
2762 // Check the widget's properties for a relation definition
2763 QVariant vRel = myWidget->property( "qgisRelation" );
2764 if ( vRel.isValid() )
2765 {
2767 QgsRelation relation = relMgr->relation( vRel.toString() );
2768 if ( relation.isValid() )
2769 {
2770 QgsRelationWidgetWrapper *rww = new QgsRelationWidgetWrapper( mLayer, relation, myWidget, this );
2771 rww->setConfig( mLayer->editFormConfig().widgetConfig( relation.id() ) );
2772 rww->setContext( mContext );
2773 rww->widget(); // Will initialize the widget
2774 mWidgets.append( rww );
2775 }
2776 }
2777 else
2778 {
2779 const auto constFields = fields;
2780 for ( const QgsField &field : constFields )
2781 {
2782 if ( field.name() == myWidget->objectName() )
2783 {
2784 int idx = mLayer->fields().lookupField( field.name() );
2785
2786 QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( mLayer, idx, myWidget, this, mContext );
2787 mWidgets.append( eww );
2788 }
2789 }
2790 }
2791 }
2792}
2793
2794void QgsAttributeForm::afterWidgetInit()
2795{
2796 bool isFirstEww = true;
2797
2798 const auto constMWidgets = mWidgets;
2799 for ( QgsWidgetWrapper *ww : constMWidgets )
2800 {
2801 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
2802
2803 if ( eww )
2804 {
2805 if ( isFirstEww )
2806 {
2807 setFocusProxy( eww->widget() );
2808 isFirstEww = false;
2809 }
2810
2811 connect( eww, &QgsEditorWidgetWrapper::valuesChanged, this, &QgsAttributeForm::onAttributeChanged, Qt::UniqueConnection );
2812 connect( eww, &QgsEditorWidgetWrapper::constraintStatusChanged, this, &QgsAttributeForm::onConstraintStatusChanged, Qt::UniqueConnection );
2813 }
2814 else
2815 {
2816 QgsRelationWidgetWrapper *relationWidgetWrapper = qobject_cast<QgsRelationWidgetWrapper *>( ww );
2817 if ( relationWidgetWrapper )
2818 {
2819 connect( relationWidgetWrapper, &QgsRelationWidgetWrapper::relatedFeaturesChanged, this, &QgsAttributeForm::onRelatedFeaturesChanged, static_cast<Qt::ConnectionType>( Qt::UniqueConnection | Qt::QueuedConnection ) );
2820 }
2821 }
2822 }
2823}
2824
2825
2826bool QgsAttributeForm::eventFilter( QObject *object, QEvent *e )
2827{
2828 Q_UNUSED( object )
2829
2830 if ( e->type() == QEvent::KeyPress )
2831 {
2832 QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( e );
2833 if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
2834 {
2835 // Re-emit to this form so it will be forwarded to parent
2836 event( e );
2837 return true;
2838 }
2839 }
2840
2841 return false;
2842}
2843
2844void QgsAttributeForm::scanForEqualAttributes( QgsFeatureIterator &fit,
2845 QSet< int > &mixedValueFields,
2846 QHash< int, QVariant > &fieldSharedValues ) const
2847{
2848 mixedValueFields.clear();
2849 fieldSharedValues.clear();
2850
2851 QgsFeature f;
2852 bool first = true;
2853 while ( fit.nextFeature( f ) )
2854 {
2855 for ( int i = 0; i < mLayer->fields().count(); ++i )
2856 {
2857 if ( mixedValueFields.contains( i ) )
2858 continue;
2859
2860 if ( first )
2861 {
2862 fieldSharedValues[i] = f.attribute( i );
2863 }
2864 else
2865 {
2866 if ( fieldSharedValues.value( i ) != f.attribute( i ) )
2867 {
2868 fieldSharedValues.remove( i );
2869 mixedValueFields.insert( i );
2870 }
2871 }
2872 }
2873 first = false;
2874
2875 if ( mixedValueFields.count() == mLayer->fields().count() )
2876 {
2877 // all attributes are mixed, no need to keep scanning
2878 break;
2879 }
2880 }
2881}
2882
2883
2884void QgsAttributeForm::layerSelectionChanged()
2885{
2886 switch ( mMode )
2887 {
2894 break;
2895
2897 resetMultiEdit( true );
2898 break;
2899 }
2900}
2901
2903{
2904 mIsSettingMultiEditFeatures = true;
2905 mMultiEditFeatureIds = fids;
2906
2907 if ( fids.isEmpty() )
2908 {
2909 // no selected features
2910 QMultiMap< int, QgsAttributeFormEditorWidget * >::const_iterator wIt = mFormEditorWidgets.constBegin();
2911 for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
2912 {
2913 wIt.value()->initialize( QVariant() );
2914 }
2915 mIsSettingMultiEditFeatures = false;
2916 return;
2917 }
2918
2919 QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFids( fids ) );
2920
2921 // Scan through all features to determine which attributes are initially the same
2922 QSet< int > mixedValueFields;
2923 QHash< int, QVariant > fieldSharedValues;
2924 scanForEqualAttributes( fit, mixedValueFields, fieldSharedValues );
2925
2926 // also fetch just first feature
2927 fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFid( *fids.constBegin() ) );
2928 QgsFeature firstFeature;
2929 fit.nextFeature( firstFeature );
2930
2931 // Make this feature the current form feature or the constraints will be evaluated
2932 // on a possibly wrong previously selected/current feature
2933 if ( mCurrentFormFeature.id() != firstFeature.id( ) )
2934 {
2935 setFeature( firstFeature );
2936 }
2937
2938 const auto constMixedValueFields = mixedValueFields;
2939 for ( int fieldIndex : std::as_const( mixedValueFields ) )
2940 {
2941 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( fieldIndex );
2942 if ( formEditorWidgets.isEmpty() )
2943 continue;
2944
2945 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
2946 QVariantList additionalFieldValues;
2947 for ( const QString &additionalField : additionalFields )
2948 additionalFieldValues << firstFeature.attribute( additionalField );
2949
2950 for ( QgsAttributeFormEditorWidget *w : formEditorWidgets )
2951 w->initialize( firstFeature.attribute( fieldIndex ), true, additionalFieldValues );
2952 }
2953 QHash< int, QVariant >::const_iterator sharedValueIt = fieldSharedValues.constBegin();
2954 for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
2955 {
2956 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( sharedValueIt.key() );
2957 if ( formEditorWidgets.isEmpty() )
2958 continue;
2959
2960 bool mixed = false;
2961 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
2962 for ( const QString &additionalField : additionalFields )
2963 {
2964 int index = mLayer->fields().indexFromName( additionalField );
2965 if ( constMixedValueFields.contains( index ) )
2966 {
2967 // if additional field are mixed, it is considered as mixed
2968 mixed = true;
2969 break;
2970 }
2971 }
2972 QVariantList additionalFieldValues;
2973 if ( mixed )
2974 {
2975 for ( const QString &additionalField : additionalFields )
2976 additionalFieldValues << firstFeature.attribute( additionalField );
2977 for ( QgsAttributeFormEditorWidget *w : formEditorWidgets )
2978 w->initialize( firstFeature.attribute( sharedValueIt.key() ), true, additionalFieldValues );
2979 }
2980 else
2981 {
2982 for ( const QString &additionalField : additionalFields )
2983 {
2984 int index = mLayer->fields().indexFromName( additionalField );
2985 Q_ASSERT( fieldSharedValues.contains( index ) );
2986 additionalFieldValues << fieldSharedValues.value( index );
2987 }
2988 for ( QgsAttributeFormEditorWidget *w : formEditorWidgets )
2989 w->initialize( sharedValueIt.value(), false, additionalFieldValues );
2990 }
2991 }
2992
2993 setMultiEditFeatureIdsRelations( fids );
2994
2995 mIsSettingMultiEditFeatures = false;
2996}
2997
2999{
3000 if ( mOwnsMessageBar )
3001 delete mMessageBar;
3002 mOwnsMessageBar = false;
3003 mMessageBar = messageBar;
3004}
3005
3007{
3009 {
3010 Q_ASSERT( false );
3011 }
3012
3013 QStringList filters;
3014 for ( QgsAttributeFormWidget *widget : mFormWidgets )
3015 {
3016 QString filter = widget->currentFilterExpression();
3017 if ( !filter.isNull() )
3018 filters << '(' + filter + ')';
3019 }
3020
3021 return filters.join( QLatin1String( " AND " ) );
3022}
3023
3025{
3026 mExtraContextScope.reset( extraScope );
3027}
3028
3029void QgsAttributeForm::ContainerInformation::apply( QgsExpressionContext *expressionContext )
3030{
3031
3032 const bool newVisibility = expression.evaluate( expressionContext ).toBool();
3033
3034 if ( expression.isValid() && ! expression.hasEvalError() && newVisibility != isVisible )
3035 {
3036 if ( tabWidget )
3037 {
3038 tabWidget->setTabVisible( widget, newVisibility );
3039 }
3040 else
3041 {
3042 widget->setVisible( newVisibility );
3043 }
3044
3045 isVisible = newVisibility;
3046 }
3047
3048 const bool newCollapsedState = collapsedExpression.evaluate( expressionContext ).toBool();
3049
3050 if ( collapsedExpression.isValid() && ! collapsedExpression.hasEvalError() && newCollapsedState != isCollapsed )
3051 {
3052
3053 if ( QgsCollapsibleGroupBoxBasic * collapsibleGroupBox { qobject_cast<QgsCollapsibleGroupBoxBasic *>( widget ) } )
3054 {
3055 collapsibleGroupBox->setCollapsed( newCollapsedState );
3056 isCollapsed = newCollapsedState;
3057 }
3058 }
3059}
3060
3061void QgsAttributeForm::updateJoinedFields( const QgsEditorWidgetWrapper &eww )
3062{
3063 if ( !eww.layer()->fields().exists( eww.fieldIdx() ) )
3064 return;
3065
3066 QgsFeature formFeature;
3067 QgsField field = eww.layer()->fields().field( eww.fieldIdx() );
3068 QList<const QgsVectorLayerJoinInfo *> infos = eww.layer()->joinBuffer()->joinsWhereFieldIsId( field );
3069
3070 if ( infos.count() == 0 || !currentFormValuesFeature( formFeature ) )
3071 return;
3072
3073 const QString hint = tr( "No feature joined" );
3074 const auto constInfos = infos;
3075 for ( const QgsVectorLayerJoinInfo *info : constInfos )
3076 {
3077 if ( !info->isDynamicFormEnabled() )
3078 continue;
3079
3080 QgsFeature joinFeature = mLayer->joinBuffer()->joinedFeatureOf( info, formFeature );
3081
3082 mJoinedFeatures[info] = joinFeature;
3083
3084 if ( info->hasSubset() )
3085 {
3086 const QStringList subsetNames = QgsVectorLayerJoinInfo::joinFieldNamesSubset( *info );
3087
3088 const auto constSubsetNames = subsetNames;
3089 for ( const QString &field : constSubsetNames )
3090 {
3091 QString prefixedName = info->prefixedFieldName( field );
3092 QVariant val;
3093 QString hintText = hint;
3094
3095 if ( joinFeature.isValid() )
3096 {
3097 val = joinFeature.attribute( field );
3098 hintText.clear();
3099 }
3100
3101 changeAttribute( prefixedName, val, hintText );
3102 }
3103 }
3104 else
3105 {
3106 const QgsFields joinFields = joinFeature.fields();
3107 for ( const QgsField &field : joinFields )
3108 {
3109 QString prefixedName = info->prefixedFieldName( field );
3110 QVariant val;
3111 QString hintText = hint;
3112
3113 if ( joinFeature.isValid() )
3114 {
3115 val = joinFeature.attribute( field.name() );
3116 hintText.clear();
3117 }
3118
3119 changeAttribute( prefixedName, val, hintText );
3120 }
3121 }
3122 }
3123}
3124
3125bool QgsAttributeForm::fieldIsEditable( int fieldIndex ) const
3126{
3127 return QgsVectorLayerUtils::fieldIsEditable( mLayer, fieldIndex, mFeature );
3128}
3129
3130void QgsAttributeForm::updateFieldDependencies()
3131{
3132 mDefaultValueDependencies.clear();
3133 mVirtualFieldsDependencies.clear();
3134 mRelatedLayerFieldsDependencies.clear();
3135
3136 //create defaultValueDependencies
3137 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
3138 {
3139 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
3140 if ( ! eww )
3141 continue;
3142
3143 updateFieldDependenciesDefaultValue( eww );
3144 updateFieldDependenciesVirtualFields( eww );
3145 updateRelatedLayerFieldsDependencies( eww );
3146 }
3147}
3148
3149void QgsAttributeForm::updateFieldDependenciesDefaultValue( QgsEditorWidgetWrapper *eww )
3150{
3152
3153 if ( exp.needsGeometry() )
3154 mNeedsGeometry = true;
3155
3156 const QSet<QString> referencedColumns = exp.referencedColumns();
3157 for ( const QString &referencedColumn : referencedColumns )
3158 {
3159 if ( referencedColumn == QgsFeatureRequest::ALL_ATTRIBUTES )
3160 {
3161 const QList<int> allAttributeIds( mLayer->fields().allAttributesList() );
3162
3163 for ( const int id : allAttributeIds )
3164 {
3165 mDefaultValueDependencies.insertMulti( id, eww );
3166 }
3167 }
3168 else
3169 {
3170 mDefaultValueDependencies.insertMulti( mLayer->fields().lookupField( referencedColumn ), eww );
3171 }
3172 }
3173}
3174
3175void QgsAttributeForm::updateFieldDependenciesVirtualFields( QgsEditorWidgetWrapper *eww )
3176{
3177 QString expressionField = eww->layer()->expressionField( eww->fieldIdx() );
3178 if ( expressionField.isEmpty() )
3179 return;
3180
3181 QgsExpression exp( expressionField );
3182
3183 if ( exp.needsGeometry() )
3184 mNeedsGeometry = true;
3185
3186 const QSet<QString> referencedColumns = exp.referencedColumns();
3187 for ( const QString &referencedColumn : referencedColumns )
3188 {
3189 if ( referencedColumn == QgsFeatureRequest::ALL_ATTRIBUTES )
3190 {
3191 const QList<int> allAttributeIds( mLayer->fields().allAttributesList() );
3192 for ( const int id : allAttributeIds )
3193 {
3194 mVirtualFieldsDependencies.insertMulti( id, eww );
3195 }
3196 }
3197 else
3198 {
3199 mVirtualFieldsDependencies.insertMulti( mLayer->fields().lookupField( referencedColumn ), eww );
3200 }
3201 }
3202}
3203
3204void QgsAttributeForm::updateRelatedLayerFieldsDependencies( QgsEditorWidgetWrapper *eww )
3205{
3206 if ( eww )
3207 {
3208 QString expressionField = eww->layer()->expressionField( eww->fieldIdx() );
3209 if ( expressionField.contains( QStringLiteral( "relation_aggregate" ) )
3210 || expressionField.contains( QStringLiteral( "get_features" ) ) )
3211 mRelatedLayerFieldsDependencies.insert( eww );
3212 }
3213 else
3214 {
3215 mRelatedLayerFieldsDependencies.clear();
3216 //create defaultValueDependencies
3217 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
3218 {
3219 QgsEditorWidgetWrapper *editorWidgetWrapper = qobject_cast<QgsEditorWidgetWrapper *>( ww );
3220 if ( ! editorWidgetWrapper )
3221 continue;
3222
3223 updateRelatedLayerFieldsDependencies( editorWidgetWrapper );
3224 }
3225 }
3226}
3227
3228void QgsAttributeForm::setMultiEditFeatureIdsRelations( const QgsFeatureIds &fids )
3229{
3230 for ( QgsAttributeFormWidget *formWidget : mFormWidgets )
3231 {
3232 QgsAttributeFormRelationEditorWidget *relationEditorWidget = dynamic_cast<QgsAttributeFormRelationEditorWidget *>( formWidget );
3233 if ( !relationEditorWidget )
3234 continue;
3235
3236 relationEditorWidget->setMultiEditFeatureIds( fids );
3237 }
3238}
3239
3240void QgsAttributeForm::updateIcon( QgsEditorWidgetWrapper *eww )
3241{
3242 if ( !eww->widget() || !mIconMap[eww->widget()] )
3243 return;
3244
3245 // no icon by default
3246 mIconMap[eww->widget()]->hide();
3247
3248 if ( !eww->widget()->isEnabled() && mLayer->isEditable() )
3249 {
3250 if ( mLayer->fields().fieldOrigin( eww->fieldIdx() ) == QgsFields::OriginJoin )
3251 {
3252 int srcFieldIndex;
3253 const QgsVectorLayerJoinInfo *info = mLayer->joinBuffer()->joinForFieldIndex( eww->fieldIdx(), mLayer->fields(), srcFieldIndex );
3254
3255 if ( !info )
3256 return;
3257
3258 if ( !info->isEditable() )
3259 {
3260 const QString file = QStringLiteral( "/mIconJoinNotEditable.svg" );
3261 const QString tooltip = tr( "Join settings do not allow editing" );
3262 reloadIcon( file, tooltip, mIconMap[eww->widget()] );
3263 }
3264 else if ( mMode == QgsAttributeEditorContext::AddFeatureMode && !info->hasUpsertOnEdit() )
3265 {
3266 const QString file = QStringLiteral( "mIconJoinHasNotUpsertOnEdit.svg" );
3267 const QString tooltip = tr( "Join settings do not allow upsert on edit" );
3268 reloadIcon( file, tooltip, mIconMap[eww->widget()] );
3269 }
3270 else if ( !info->joinLayer()->isEditable() )
3271 {
3272 const QString file = QStringLiteral( "/mIconJoinedLayerNotEditable.svg" );
3273 const QString tooltip = tr( "Joined layer is not toggled editable" );
3274 reloadIcon( file, tooltip, mIconMap[eww->widget()] );
3275 }
3276 }
3277 }
3278}
3279
3280void QgsAttributeForm::reloadIcon( const QString &file, const QString &tooltip, QSvgWidget *sw )
3281{
3282 sw->load( QgsApplication::iconPath( file ) );
3283 sw->setToolTip( tooltip );
3284 sw->show();
3285}
@ Row
A row of editors (horizontal layout)
SelectBehavior
Specifies how a selection should be applied.
Definition: qgis.h:1167
@ SetSelection
Set selection, removing any existing selection.
@ AddToSelection
Add selection to current selection.
@ IntersectSelection
Modify current selection to include only select features which match.
@ RemoveFromSelection
Remove from current selection.
Wraps a button widget to launch a layer action.
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.
This element will load a layer action onto the form.
const QgsAction & action(const QgsVectorLayer *layer) const
Returns the (possibly lazy loaded) action for the given layer.
This is a container for attribute editors, used to group them visually in the attribute form if it is...
QgsOptionalExpression visibilityExpression() const
The visibility expression is used in the attribute form to show or hide this container based on an ex...
QgsOptionalExpression collapsedExpression() const
The collapsed expression is used in the attribute form to set the collapsed status of the group box c...
bool collapsed() const
For group box containers returns true if this group box is collapsed.
Qgis::AttributeEditorContainerType type() const
Returns the container type.
QList< QgsAttributeEditorElement * > children() const
Gets a list of the children elements of this container.
QColor backgroundColor() const
Returns the background color of the container.
int columnCount() const
Gets the number of columns in this group.
This class contains context information for attribute editor widgets.
FormMode formMode() const
Returns the form mode.
QString attributeFormModeString() const
Returns given attributeFormMode as string.
@ Embed
A form was embedded as a widget on another form.
bool allowCustomUi() const
Returns true if the attribute editor should permit use of custom UI forms.
@ SearchMode
Form values are used for searching/filtering the layer.
@ FixAttributeMode
Fix feature mode, for modifying the feature attributes without saving. The updated feature is availab...
@ IdentifyMode
Identify the feature.
@ SingleEditMode
Single edit mode, for editing a single feature.
@ AggregateSearchMode
Form is in aggregate search mode, show each widget in this mode.
@ MultiEditMode
Multi edit mode, for editing fields of multiple features at once.
void setAttributeFormMode(const Mode &attributeFormMode)
Set attributeFormMode for the edited form.
This is an abstract base class for any elements of a drag and drop form.
LabelStyle labelStyle() const
Returns the label style.
Qgis::AttributeEditorType type() const
The type of this element.
bool showLabel() const
Controls if this element should be labeled with a title (field, relation or groupname).
QString name() const
Returns the name of this element.
This element will load a field's widget onto the form.
int idx() const
Returns the index of the field.
An attribute editor widget that will represent arbitrary HTML code.
QString htmlCode() const
The Html code that will be represented within this widget.
An attribute editor widget that will represent arbitrary QML code.
QString qmlCode() const
The QML code that will be represented within this widget.
This element will load a relation editor onto the form.
const QgsRelation & relation() const
Gets the id of the relation which shall be embedded.
QVariantMap relationEditorConfiguration() const
Returns the relation editor widget configuration.
QVariant nmRelationId() const
Determines the relation id of the second relation involved in an N:M relation.
bool forceSuppressFormPopup() const
Determines the force suppress form popup status.
QString relationWidgetTypeId() const
Returns the current relation widget type id.
QString label() const
Determines the label of this element.
An attribute editor widget that will represent a spacer.
bool drawLine() const
Returns true if the spacer element will contain an horizontal line.
An attribute editor widget that will represent arbitrary text code.
QString text() const
The Text that will be represented within this widget.
A widget consisting of both an editor widget and additional widgets for controlling the behavior of t...
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.
This class helps to support legacy open form scripts to be compatible with the new QgsAttributeForm s...
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() override
QgsAttributeForm(QgsVectorLayer *vl, const QgsFeature &feature=QgsFeature(), const QgsAttributeEditorContext &context=QgsAttributeEditorContext(), QWidget *parent=nullptr)
const QgsFeature & feature()
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.
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.
A vector of attributes.
Definition: qgsattributes.h:59
A groupbox that collapses/expands when toggled.
void setStyleSheet(const QString &style)
Overridden to prepare base call and avoid crash due to specific QT versions.
void setCollapsed(bool collapse)
Collapse or uncollapse this groupbox.
A groupbox that collapses/expands when toggled and can save its collapsed and checked states.
Q_GADGET QString expression
Qgis::AttributeFormPythonInitCodeSource initCodeSource() const
Returns Python code source for edit form initialization (if it shall be loaded from a file,...
QString initCode() const
Gets Python code for edit form initialization.
QVariantMap widgetConfig(const QString &widgetName) const
Gets the configuration for the editor widget with the given name.
QString initFilePath() const
Gets Python external file path for edit form initialization.
QgsPropertyCollection dataDefinedFieldProperties(const QString &fieldName) const
Returns data defined properties for fieldName.
bool labelOnTop(int idx) const
If this returns true, the widget at the given index will receive its label on the previous line while...
QString uiForm() const
Returns the path or URL to the .ui form.
QString initFunction() const
Gets Python function for edit form initialization.
QList< QgsAttributeEditorElement * > tabs() const
Returns a list of tabs for EditorLayout::TabLayout obtained from the invisible root container.
Qgis::AttributeFormLayout layout() const
Gets the active layout style for the attribute editor for this layer.
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.
Holder for the widget type and its configuration for a field.
QVariantMap config() const
Manages an editor widget Widget and wrapper share the same parent.
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 * formScope(const QgsFeature &formFeature=QgsFeature(), const QString &formMode=QString())
Creates a new scope which contains functions and variables from the current attribute form/table form...
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
Class for parsing and evaluation of expressions (formerly called "search strings").
QSet< QString > referencedColumns() const
Gets list of columns referenced by the expression.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
static const QString ALL_ATTRIBUTES
A special attribute that if set matches all attributes.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
bool setAttribute(int field, const QVariant &attr)
Sets an attribute's value by field index.
Definition: qgsfeature.cpp:262
QgsAttributes attributes
Definition: qgsfeature.h:65
QgsFields fields
Definition: qgsfeature.h:66
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:160
void setFields(const QgsFields &fields, bool initAttributes=false)
Assigns a field map with the feature to allow attribute access by attribute name.
Definition: qgsfeature.cpp:195
void setValid(bool validity)
Sets the validity of the feature.
Definition: qgsfeature.cpp:221
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:216
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:335
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:167
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
ConstraintOrigin
Origin of constraints.
@ ConstraintOriginNotSet
Constraint is not set.
@ ConstraintOriginLayer
Constraint was set by layer.
QString constraintExpression() const
Returns the constraint expression for the field, if set.
static QString fieldToolTipExtended(const QgsField &field, const QgsVectorLayer *layer)
Returns a HTML formatted tooltip string for a field, containing details like the field name,...
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:53
QString name
Definition: qgsfield.h:62
QString displayName() const
Returns the name to use when displaying this field.
Definition: qgsfield.cpp:88
QVariant::Type type
Definition: qgsfield.h:60
QgsDefaultValue defaultValueDefinition
Definition: qgsfield.h:64
QString comment
Definition: qgsfield.h:61
QgsFieldConstraints constraints
Definition: qgsfield.h:65
Container of fields for a vector layer.
Definition: qgsfields.h:45
QList< QgsField > toList() const
Utility function to return a list of QgsField instances.
Definition: qgsfields.cpp:212
QgsAttributeList allAttributesList() const
Utility function to get list of attribute indexes.
Definition: qgsfields.cpp:386
int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:202
@ OriginJoin
Field comes from a joined layer (originIndex / 1000 = index of the join, originIndex % 1000 = index w...
Definition: qgsfields.h:52
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
FieldOrigin fieldOrigin(int fieldIdx) const
Returns the field's origin (value from an enumeration).
Definition: qgsfields.cpp:189
QgsField field(int fieldIdx) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:168
bool exists(int i) const
Returns if a field index is valid.
Definition: qgsfields.cpp:153
int size() const
Returns number of items.
Definition: qgsfields.cpp:138
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:359
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:164
static QgsEditorWidgetRegistry * editorWidgetRegistry()
Returns the global editor widget registry, used for managing all known edit widget factories.
Definition: qgsgui.cpp:88
static bool pythonMacroAllowed(void(*lambda)()=nullptr, QgsMessageBar *messageBar=nullptr)
Returns true if python macros are currently allowed to be run If the global option is to ask user,...
Definition: qgsgui.cpp:344
Wraps a QQuickWidget to display HTML code.
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.
Definition: qgslogger.cpp:131
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
void editingStarted()
Emitted when editing on this layer has started.
void triggerRepaint(bool deferredUpdate=false)
Will advise the map canvas (and any other interested party) that this layer requires to be repainted.
Represents an item shown within a QgsMessageBar widget.
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:61
bool popWidget(QgsMessageBarItem *item)
Remove the specified item from the bar, and display the next most recent one in the stack.
void pushMessage(const QString &text, Qgis::MessageLevel level=Qgis::MessageLevel::Info, int duration=-1)
A convenience method for pushing a message with the specified text to the bar.
void pushItem(QgsMessageBarItem *item)
Display a message item on the bar, after hiding the currently visible one and putting it in a stack.
QFile * localFile(const QString &filePathOrUrl)
Returns a QFile from a local file or to a temporary file previously fetched by the registry.
bool enabled() const
Check if this optional is enabled.
Definition: qgsoptional.h:89
T data() const
Access the payload data.
Definition: qgsoptional.h:119
QgsRelationManager * relationManager
Definition: qgsproject.h:117
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:484
bool hasProperty(int key) const override
Returns true if the collection contains a property with the specified key.
QgsProperty property(int key) const override
Returns a matching property from the collection, if one exists.
A store for object properties.
Definition: qgsproperty.h:230
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.
Wraps a QQuickWidget to display QML code.
void setQmlCode(const QString &qmlCode)
writes the qmlCode into a temporary file
This class manages a set of relations between layers.
QList< QgsRelation > referencedRelations(const QgsVectorLayer *layer=nullptr) const
Gets all relations where this layer is the referenced part (i.e.
Q_INVOKABLE QgsRelation relation(const QString &id) const
Gets access to a relation by its id.
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.
QString name
Definition: qgsrelation.h:48
Q_GADGET QString id
Definition: qgsrelation.h:45
bool isValid
Definition: qgsrelation.h:49
A QScrollArea subclass with improved scrolling behavior.
Definition: qgsscrollarea.h:42
Wraps a spacer widget.
void setDrawLine(bool drawLine)
Sets a flag to define if the spacer element will contain an horizontal line.
The QgsTabWidget class is the same as the QTabWidget but with additional methods to temporarily hide/...
Definition: qgstabwidget.h:33
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.
Wraps a text widget.
bool isInvalidJSON()
Returns whether the text edit widget contains Invalid JSON.
Wraps a label widget to display text.
bool needsGeometry() const
Returns true if the widget needs feature geometry.
void setText(const QString &text)
Sets the text code to htmlCode.
void reinitWidget()
Clears the content and makes new initialization.
static bool isNull(const QVariant &variant)
Returns true if the specified variant should be considered a NULL value.
const QgsVectorLayerJoinInfo * joinForFieldIndex(int index, const QgsFields &fields, int &sourceFieldIndex) const
Finds the vector join for a layer field index.
QList< const QgsVectorLayerJoinInfo * > joinsWhereFieldIsId(const QgsField &field) const
Returns joins where the field of a target layer is considered as an id.
QgsFeature joinedFeatureOf(const QgsVectorLayerJoinInfo *info, const QgsFeature &feature) const
Returns the joined feature corresponding to the feature.
Defines left outer join from our vector layer to some other vector layer.
QStringList * joinFieldNamesSubset() const
Returns the subset of fields to be used from joined layer.
bool isDynamicFormEnabled() const
Returns whether the form has to be dynamically updated with joined fields when a feature is being cre...
bool hasUpsertOnEdit() const
Returns whether a feature created on the target layer has to impact the joined layer by creating a ne...
bool isEditable() const
Returns whether joined fields may be edited through the form of the target layer.
QgsVectorLayer * joinLayer() const
Returns joined layer (may be nullptr if the reference was set by layer ID and not resolved yet)
static bool fieldIsEditable(const QgsVectorLayer *layer, int fieldIndex, const QgsFeature &feature)
Tests whether a field is editable for a particular feature.
Represents a vector layer which manages a vector based data sets.
QString attributeDisplayName(int index) const
Convenience function that returns the attribute alias if defined or the field name else.
void beforeRemovingExpressionField(int idx)
Will be emitted, when an expression field is going to be deleted from this vector layer.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QString expressionField(int index) const
Returns the expression used for a given expression field.
int selectedFeatureCount() const
Returns the number of features that are selected in this layer.
Q_INVOKABLE void selectByExpression(const QString &expression, Qgis::SelectBehavior behavior=Qgis::SelectBehavior::SetSelection, QgsExpressionContext *context=nullptr)
Selects matching features using an expression.
void endEditCommand()
Finish edit command and add it to undo/redo stack.
void destroyEditCommand()
Destroy active command and reverts all changes in it.
Q_INVOKABLE const QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
bool changeAttributeValues(QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues=QgsAttributeMap(), bool skipDefaultValues=false)
Changes attributes' values for a feature (but does not immediately commit the changes).
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
QgsVectorLayerJoinBuffer * joinBuffer()
Returns the join buffer object.
bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue=QVariant(), bool skipDefaultValues=false)
Changes an attribute value for a feature (but does not immediately commit the changes).
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) FINAL
Adds a single feature to the sink.
void selectionChanged(const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect)
Emitted when selection was changed.
void beforeAddingExpressionField(const QString &fieldName)
Will be emitted, when an expression field is going to be added to this vector layer.
void updatedFields()
Emitted whenever the fields available from this layer have been changed.
QVariant defaultValue(int index, const QgsFeature &feature=QgsFeature(), QgsExpressionContext *context=nullptr) const
Returns the calculated default value for the specified field index.
void beginEditCommand(const QString &text)
Create edit command for undo/redo operations.
QgsEditFormConfig editFormConfig
void beforeModifiedCheck() const
Emitted when the layer is checked for modifications. Use for last-minute additions.
Manages an editor widget Widget and wrapper share the same parent.
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:247
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:4572
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:4571
QMap< int, QVariant > QgsAttributeMap
Definition: qgsattributes.h:42
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
const QgsField & field
Definition: qgsfield.h:554
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugError(str)
Definition: qgslogger.h:38