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