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