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