QGIS API Documentation 3.41.0-Master (af5edcb665c)
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 for ( int fieldIndex : fieldIndexes )
785 {
786 const QList<QgsAttributeFormEditorWidget *> widgets = mFormEditorWidgets.values( fieldIndex );
787 if ( !widgets.first()->hasChanged() )
788 continue;
789
790 if ( !widgets.first()->currentValue().isValid() // if the widget returns invalid (== do not change)
791 || !fieldIsEditable( fieldIndex ) ) // or the field cannot be edited ...
792 {
793 continue;
794 }
795
796 // let editor know we've accepted the changes
797 for ( QgsAttributeFormEditorWidget *widget : widgets )
798 widget->changesCommitted();
799
800 newAttributeValues.insert( fieldIndex, widgets.first()->currentValue() );
801 }
802
803 if ( newAttributeValues.isEmpty() )
804 {
805 //nothing to change
806 return true;
807 }
808
809#if 0
810 // prompt for save
811 int res = QMessageBox::information( this, tr( "Multiedit Attributes" ),
812 tr( "Edits will be applied to all selected features." ), QMessageBox::Ok | QMessageBox::Cancel );
813 if ( res != QMessageBox::Ok )
814 {
815 resetMultiEdit();
816 return false;
817 }
818#endif
819
820 mLayer->beginEditCommand( tr( "Updated multiple feature attributes" ) );
821
822 bool success = true;
823
824 const auto constMultiEditFeatureIds = mMultiEditFeatureIds;
825 for ( QgsFeatureId fid : constMultiEditFeatureIds )
826 {
827 QgsAttributeMap::const_iterator aIt = newAttributeValues.constBegin();
828 for ( ; aIt != newAttributeValues.constEnd(); ++aIt )
829 {
830 success &= mLayer->changeAttributeValue( fid, aIt.key(), aIt.value() );
831 }
832 }
833
834 clearMultiEditMessages();
835 if ( success )
836 {
837 mLayer->endEditCommand();
838 mLayer->triggerRepaint();
839 mMultiEditMessageBarItem = new QgsMessageBarItem( tr( "Attribute changes for multiple features applied." ), Qgis::MessageLevel::Success, -1 );
840 }
841 else
842 {
843 mLayer->destroyEditCommand();
844 mMultiEditMessageBarItem = new QgsMessageBarItem( tr( "Changes could not be applied." ), Qgis::MessageLevel::Warning, 0 );
845 }
846
847 if ( !mButtonBox->isVisible() )
848 mMessageBar->pushItem( mMultiEditMessageBarItem );
849 return success;
850}
851
853{
854 return saveWithDetails( nullptr );
855}
856
858{
859 if ( error )
860 error->clear();
861
862 if ( mIsSaving )
863 return true;
864
865 if ( mContext.formMode() == QgsAttributeEditorContext::Embed && !mValidConstraints )
866 {
867 // the feature isn't saved (as per the warning provided), but we return true
868 // so switching features still works
869 return true;
870 }
871
872 for ( QgsWidgetWrapper *wrapper : std::as_const( mWidgets ) )
873 {
874 wrapper->notifyAboutToSave();
875 }
876
877 // only do the dirty checks when editing an existing feature - for new
878 // features we need to add them even if the attributes are unchanged from the initial
879 // default values
880 switch ( mMode )
881 {
886 if ( !mDirty )
887 return true;
888 break;
889
893 break;
894 }
895
896 mIsSaving = true;
897
898 bool success = true;
899
900 emit beforeSave( success );
901
902 // Somebody wants to prevent this form from saving
903 if ( !success )
904 return false;
905
906 switch ( mMode )
907 {
914 success = saveEdits( error );
915 break;
916
918 success = saveMultiEdits();
919 break;
920 }
921
922 mIsSaving = false;
923 mUnsavedMultiEditChanges = false;
924 mDirty = false;
925
926 return success;
927}
928
929
931{
932 mValuesInitialized = false;
933 const auto constMWidgets = mWidgets;
934 for ( QgsWidgetWrapper *ww : constMWidgets )
935 {
936 ww->setFeature( mFeature );
937 }
938
939 // Update dependent virtual fields (not default values / not referencing layer values)
940 for ( QgsWidgetWrapper *ww : constMWidgets )
941 {
942 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
943 if ( !eww )
944 continue;
945
946 // Append field index here, so it's not updated recursively
947 mAlreadyUpdatedFields.append( eww->fieldIdx() );
948 updateValuesDependenciesVirtualFields( eww->fieldIdx() );
949 mAlreadyUpdatedFields.removeAll( eww->fieldIdx() );
950 }
951
952 mValuesInitialized = true;
953 mDirty = false;
954}
955
957{
958 const auto widgets { findChildren<QgsAttributeFormEditorWidget *>() };
959 for ( QgsAttributeFormEditorWidget *w : widgets )
960 {
961 w->resetSearch();
962 }
963}
964
965void QgsAttributeForm::clearMultiEditMessages()
966{
967 if ( mMultiEditUnsavedMessageBarItem )
968 {
969 if ( !mButtonBox->isVisible() )
970 mMessageBar->popWidget( mMultiEditUnsavedMessageBarItem );
971 mMultiEditUnsavedMessageBarItem = nullptr;
972 }
973 if ( mMultiEditMessageBarItem )
974 {
975 if ( !mButtonBox->isVisible() )
976 mMessageBar->popWidget( mMultiEditMessageBarItem );
977 mMultiEditMessageBarItem = nullptr;
978 }
979}
980
981QString QgsAttributeForm::createFilterExpression() const
982{
983 QStringList filters;
984 for ( QgsAttributeFormWidget *w : std::as_const( mFormWidgets ) )
985 {
986 QString filter = w->currentFilterExpression();
987 if ( !filter.isEmpty() )
988 filters << filter;
989 }
990
991 if ( filters.isEmpty() )
992 return QString();
993
994 QString filter = filters.join( QLatin1String( ") AND (" ) ).prepend( '(' ).append( ')' );
995 return filter;
996}
997
998QgsExpressionContext QgsAttributeForm::createExpressionContext( const QgsFeature &feature ) const
999{
1000 QgsExpressionContext context;
1003 if ( mExtraContextScope )
1004 {
1005 context.appendScope( new QgsExpressionContextScope( *mExtraContextScope.get() ) );
1006 }
1007 if ( mContext.parentFormFeature().isValid() )
1008 {
1010 }
1011 context.setFeature( feature );
1012 return context;
1013}
1014
1015
1016void QgsAttributeForm::onAttributeChanged( const QVariant &value, const QVariantList &additionalFieldValues )
1017{
1018 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( sender() );
1019 Q_ASSERT( eww );
1020
1021 bool signalEmitted = false;
1022
1023 if ( mValuesInitialized )
1024 mDirty = true;
1025
1026 mCurrentFormFeature.setAttribute( eww->field().name(), value );
1027
1028 // Update other widgets pointing to the same field, required to happen now to insure
1029 // currentFormValuesFeature() gets the right value when processing constraints
1030 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->fieldIdx() );
1031 for ( QgsAttributeFormEditorWidget *formEditorWidget : std::as_const( formEditorWidgets ) )
1032 {
1033 if ( formEditorWidget->editorWidget() == eww )
1034 continue;
1035
1036 // formEditorWidget and eww points to the same field, so update its value
1037 formEditorWidget->editorWidget()->setValue( value );
1038 }
1039
1040 switch ( mMode )
1041 {
1046 {
1048 emit attributeChanged( eww->field().name(), value );
1050 emit widgetValueChanged( eww->field().name(), value, !mIsSettingFeature );
1051
1052 // also emit the signal for additional values
1053 const QStringList additionalFields = eww->additionalFields();
1054 for ( int i = 0; i < additionalFields.count(); i++ )
1055 {
1056 const QString fieldName = additionalFields.at( i );
1057 const QVariant value = additionalFieldValues.at( i );
1058 emit widgetValueChanged( fieldName, value, !mIsSettingFeature );
1059 }
1060
1061 signalEmitted = true;
1062
1063 if ( mValuesInitialized )
1064 updateJoinedFields( *eww );
1065
1066 break;
1067 }
1069 {
1070 if ( !mIsSettingMultiEditFeatures )
1071 {
1072 mUnsavedMultiEditChanges = true;
1073
1074 QLabel *msgLabel = new QLabel( tr( "Unsaved multiedit changes: <a href=\"#apply\">apply changes</a> or <a href=\"#reset\">reset changes</a>." ), mMessageBar );
1075 msgLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
1076 msgLabel->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1077 connect( msgLabel, &QLabel::linkActivated, this, &QgsAttributeForm::multiEditMessageClicked );
1078 clearMultiEditMessages();
1079
1080 mMultiEditUnsavedMessageBarItem = new QgsMessageBarItem( msgLabel, Qgis::MessageLevel::Warning );
1081 if ( !mButtonBox->isVisible() )
1082 mMessageBar->pushItem( mMultiEditUnsavedMessageBarItem );
1083
1084 emit widgetValueChanged( eww->field().name(), value, false );
1085 signalEmitted = true;
1086 }
1087 break;
1088 }
1091 //nothing to do
1092 break;
1093 }
1094
1095 updateConstraints( eww );
1096
1097 // Update dependent fields (only if form is not initializing)
1098 if ( mValuesInitialized )
1099 {
1100 //append field index here, so it's not updated recursive
1101 mAlreadyUpdatedFields.append( eww->fieldIdx() );
1102 updateValuesDependencies( eww->fieldIdx() );
1103 mAlreadyUpdatedFields.removeAll( eww->fieldIdx() );
1104 }
1105
1106 // Updates expression controlled labels and editable state
1107 updateLabels();
1108 updateEditableState();
1109
1110 if ( !signalEmitted )
1111 {
1113 emit attributeChanged( eww->field().name(), value );
1115 bool attributeHasChanged = !mIsSettingFeature;
1117 attributeHasChanged &= !mIsSettingMultiEditFeatures;
1118
1119 emit widgetValueChanged( eww->field().name(), value, attributeHasChanged );
1120 }
1121}
1122
1123void QgsAttributeForm::updateAllConstraints()
1124{
1125 const auto constMWidgets = mWidgets;
1126 for ( QgsWidgetWrapper *ww : constMWidgets )
1127 {
1128 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1129 if ( eww )
1130 updateConstraints( eww );
1131 }
1132}
1133
1134void QgsAttributeForm::updateConstraints( QgsEditorWidgetWrapper *eww )
1135{
1136 // get the current feature set in the form
1137 QgsFeature ft;
1138 if ( currentFormValuesFeature( ft ) )
1139 {
1140 // if the layer is NOT being edited then we only check layer based constraints, and not
1141 // any constraints enforced by the provider. Because:
1142 // 1. we want to keep browsing features nice and responsive. It's nice to give feedback as to whether
1143 // the value checks out, but not if it's too slow to do so. Some constraints (e.g., unique) can be
1144 // expensive to test. A user can freely remove a layer-based constraint if it proves to be too slow
1145 // to test, but they are unlikely to have any control over provider-side constraints
1146 // 2. the provider has already accepted the value, so presumably it doesn't violate the constraint
1147 // and there's no point rechecking!
1148
1149 // update eww constraint
1150 updateConstraint( ft, eww );
1151
1152 // update eww dependencies constraint
1153 const QList<QgsEditorWidgetWrapper *> deps = constraintDependencies( eww );
1154
1155 for ( QgsEditorWidgetWrapper *depsEww : deps )
1156 updateConstraint( ft, depsEww );
1157
1158 // sync OK button status
1159 synchronizeState();
1160
1161 QgsExpressionContext context = createExpressionContext( ft );
1162
1163 // Recheck visibility/collapsed state for all containers which are controlled by this value
1164 const QVector<ContainerInformation *> infos = mContainerInformationDependency.value( eww->field().name() );
1165 for ( ContainerInformation *info : infos )
1166 {
1167 info->apply( &context );
1168 }
1169 }
1170}
1171
1172void QgsAttributeForm::updateContainersVisibility()
1173{
1174 QgsExpressionContext context = createExpressionContext( mFeature );
1175
1176 const QVector<ContainerInformation *> infos = mContainerVisibilityCollapsedInformation;
1177
1178 for ( ContainerInformation *info : infos )
1179 {
1180 info->apply( &context );
1181 }
1182
1183 // Update the constraints if not in multi edit, because
1184 // when mode changes to multi edit, constraints have been already
1185 // updated and a further update will use current form feature values,
1186 // possibly empty for mixed values, leading to false positive
1187 // constraints violations.
1189 {
1190 updateAllConstraints();
1191 }
1192}
1193
1194void QgsAttributeForm::updateConstraint( const QgsFeature &ft, QgsEditorWidgetWrapper *eww )
1195{
1197
1198 if ( eww->layer()->fields().fieldOrigin( eww->fieldIdx() ) == Qgis::FieldOrigin::Join )
1199 {
1200 int srcFieldIdx;
1201 const QgsVectorLayerJoinInfo *info = eww->layer()->joinBuffer()->joinForFieldIndex( eww->fieldIdx(), eww->layer()->fields(), srcFieldIdx );
1202
1203 if ( info && info->joinLayer() && info->isDynamicFormEnabled() )
1204 {
1205 if ( mJoinedFeatures.contains( info ) )
1206 {
1207 eww->updateConstraint( info->joinLayer(), srcFieldIdx, mJoinedFeatures[info], constraintOrigin );
1208 return;
1209 }
1210 else // if we are here, it means there's not joined field for this feature
1211 {
1212 eww->updateConstraint( QgsFeature() );
1213 return;
1214 }
1215 }
1216 }
1217 // default constraint update
1218 eww->updateConstraint( ft, constraintOrigin );
1219}
1220
1221void QgsAttributeForm::updateLabels()
1222{
1223 if ( !mLabelDataDefinedProperties.isEmpty() )
1224 {
1225 QgsFeature currentFeature;
1226 if ( currentFormValuesFeature( currentFeature ) )
1227 {
1228 QgsExpressionContext context = createExpressionContext( currentFeature );
1229
1230 for ( auto it = mLabelDataDefinedProperties.constBegin(); it != mLabelDataDefinedProperties.constEnd(); ++it )
1231 {
1232 QLabel *label { it.key() };
1233 bool ok;
1234 const QString value { it->valueAsString( context, QString(), &ok ) };
1235 if ( ok && !value.isEmpty() )
1236 {
1237 label->setText( value );
1238 }
1239 }
1240 }
1241 }
1242}
1243
1244void QgsAttributeForm::updateEditableState()
1245{
1246 if ( !mEditableDataDefinedProperties.isEmpty() )
1247 {
1248 QgsFeature currentFeature;
1249 if ( currentFormValuesFeature( currentFeature ) )
1250 {
1251 QgsExpressionContext context = createExpressionContext( currentFeature );
1252
1253 for ( auto it = mEditableDataDefinedProperties.constBegin(); it != mEditableDataDefinedProperties.constEnd(); ++it )
1254 {
1255 QWidget *w { it.key() };
1256 bool ok;
1257 const bool isEditable { it->valueAsBool( context, true, &ok ) && mLayer && mLayer->isEditable() }; // *NOPAD*
1258 if ( ok )
1259 {
1260 QgsAttributeFormEditorWidget *editorWidget { qobject_cast<QgsAttributeFormEditorWidget *>( w ) };
1261 if ( editorWidget )
1262 {
1263 editorWidget->editorWidget()->setEnabled( isEditable );
1264 }
1265 else
1266 {
1267 w->setEnabled( isEditable );
1268 }
1269 }
1270 }
1271 }
1272 }
1273}
1274
1275bool QgsAttributeForm::currentFormValuesFeature( QgsFeature &feature )
1276{
1277 bool rc = true;
1278 feature = QgsFeature( mFeature );
1280
1281 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1282 {
1283 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1284
1285 if ( !eww )
1286 continue;
1287
1288 if ( dst.count() > eww->fieldIdx() )
1289 {
1290 QVariantList dstVars = QVariantList() << dst.at( eww->fieldIdx() );
1291 QVariantList srcVars = QVariantList() << eww->value();
1292 QList<int> fieldIndexes = QList<int>() << eww->fieldIdx();
1293
1294 // append additional fields
1295 const QStringList additionalFields = eww->additionalFields();
1296 for ( const QString &fieldName : additionalFields )
1297 {
1298 int idx = eww->layer()->fields().lookupField( fieldName );
1299 fieldIndexes << idx;
1300 dstVars << dst.at( idx );
1301 }
1302 srcVars.append( eww->additionalFieldValues() );
1303
1304 Q_ASSERT( dstVars.count() == srcVars.count() );
1305
1306 for ( int i = 0; i < dstVars.count(); i++ )
1307 {
1308 // need to check dstVar.isNull() != srcVar.isNull()
1309 // otherwise if dstVar=NULL and scrVar=0, then dstVar = srcVar
1310 if ( ( !qgsVariantEqual( dstVars[i], srcVars[i] ) || QgsVariantUtils::isNull( dstVars[i] ) != QgsVariantUtils::isNull( srcVars[i] ) ) && srcVars[i].isValid() )
1311 {
1312 dst[fieldIndexes[i]] = srcVars[i];
1313 }
1314 }
1315 }
1316 else
1317 {
1318 rc = false;
1319 break;
1320 }
1321 }
1322
1323 feature.setAttributes( dst );
1324
1325 return rc;
1326}
1327
1328
1329void QgsAttributeForm::registerContainerInformation( QgsAttributeForm::ContainerInformation *info )
1330{
1331 mContainerVisibilityCollapsedInformation.append( info );
1332
1333 const QSet<QString> referencedColumns = info->expression.referencedColumns().unite( info->collapsedExpression.referencedColumns() );
1334
1335 for ( const QString &col : referencedColumns )
1336 {
1337 mContainerInformationDependency[col].append( info );
1338 }
1339}
1340
1341bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields, QStringList &descriptions ) const
1342{
1343 bool valid { true };
1344
1345 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1346 {
1347 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1348 if ( eww )
1349 {
1350 if ( !eww->isValidConstraint() )
1351 {
1352 invalidFields.append( eww->field().displayName() );
1353
1354 descriptions.append( eww->constraintFailureReason() );
1355
1356 if ( eww->isBlockingCommit() )
1357 valid = false; // continue to get all invalid fields
1358 }
1359 }
1360 }
1361
1362 return valid;
1363}
1364
1365bool QgsAttributeForm::currentFormValidHardConstraints( QStringList &invalidFields, QStringList &descriptions ) const
1366{
1367 bool valid { true };
1368
1369 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1370 {
1371 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1372 if ( eww )
1373 {
1374 if ( eww->isBlockingCommit() )
1375 {
1376 invalidFields.append( eww->field().displayName() );
1377 descriptions.append( eww->constraintFailureReason() );
1378 valid = false; // continue to get all invalid fields
1379 }
1380 }
1381 }
1382
1383 return valid;
1384}
1385
1386void QgsAttributeForm::onAttributeAdded( int idx )
1387{
1388 mPreventFeatureRefresh = false;
1389 if ( mFeature.isValid() )
1390 {
1391 QgsAttributes attrs = mFeature.attributes();
1392 attrs.insert( idx, QgsVariantUtils::createNullVariant( layer()->fields().at( idx ).type() ) );
1393 mFeature.setFields( layer()->fields() );
1394 mFeature.setAttributes( attrs );
1395 }
1396 init();
1397 setFeature( mFeature );
1398}
1399
1400void QgsAttributeForm::onAttributeDeleted( int idx )
1401{
1402 mPreventFeatureRefresh = false;
1403 if ( mFeature.isValid() )
1404 {
1405 QgsAttributes attrs = mFeature.attributes();
1406 attrs.remove( idx );
1407 mFeature.setFields( layer()->fields() );
1408 mFeature.setAttributes( attrs );
1409 }
1410 init();
1411 setFeature( mFeature );
1412}
1413
1414void QgsAttributeForm::onRelatedFeaturesChanged()
1415{
1416 updateRelatedLayerFields();
1417}
1418
1419void QgsAttributeForm::onUpdatedFields()
1420{
1421 mPreventFeatureRefresh = false;
1422 if ( mFeature.isValid() )
1423 {
1424 QgsAttributes attrs( layer()->fields().size() );
1425 for ( int i = 0; i < layer()->fields().size(); i++ )
1426 {
1427 int idx = mFeature.fields().indexFromName( layer()->fields().at( i ).name() );
1428 if ( idx != -1 )
1429 {
1430 attrs[i] = mFeature.attributes().at( idx );
1431 if ( mFeature.attributes().at( idx ).userType() != layer()->fields().at( i ).type() )
1432 {
1433 attrs[i].convert( layer()->fields().at( i ).type() );
1434 }
1435 }
1436 else
1437 {
1438 attrs[i] = QgsVariantUtils::createNullVariant( layer()->fields().at( i ).type() );
1439 }
1440 }
1441 mFeature.setFields( layer()->fields() );
1442 mFeature.setAttributes( attrs );
1443 }
1444 init();
1445 setFeature( mFeature );
1446}
1447
1448void QgsAttributeForm::onConstraintStatusChanged( const QString &constraint, const QString &description, const QString &err, QgsEditorWidgetWrapper::ConstraintResult result )
1449{
1450 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( sender() );
1451 Q_ASSERT( eww );
1452
1453 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->fieldIdx() );
1454
1455 for ( QgsAttributeFormEditorWidget *formEditorWidget : formEditorWidgets )
1456 {
1457 formEditorWidget->setConstraintStatus( constraint, description, err, result );
1458 if ( formEditorWidget->editorWidget() != eww )
1459 {
1460 formEditorWidget->editorWidget()->updateConstraint( result, err );
1461 }
1462 }
1463}
1464
1465QList<QgsEditorWidgetWrapper *> QgsAttributeForm::constraintDependencies( QgsEditorWidgetWrapper *w )
1466{
1467 QList<QgsEditorWidgetWrapper *> wDeps;
1468 QString name = w->field().name();
1469
1470 // for each widget in the current form
1471 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1472 {
1473 // get the wrapper
1474 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1475 if ( eww )
1476 {
1477 // compare name to not compare w to itself
1478 QString ewwName = eww->field().name();
1479 if ( name != ewwName )
1480 {
1481 // get expression and referencedColumns
1482 QgsExpression expr = eww->layer()->fields().at( eww->fieldIdx() ).constraints().constraintExpression();
1483
1484 const auto referencedColumns = expr.referencedColumns();
1485
1486 for ( const QString &colName : referencedColumns )
1487 {
1488 if ( name == colName )
1489 {
1490 wDeps.append( eww );
1491 break;
1492 }
1493 }
1494 }
1495 }
1496 }
1497
1498 return wDeps;
1499}
1500
1501QgsRelationWidgetWrapper *QgsAttributeForm::setupRelationWidgetWrapper( const QgsRelation &rel, const QgsAttributeEditorContext &context )
1502{
1503 return setupRelationWidgetWrapper( QString(), rel, context );
1504}
1505
1506QgsRelationWidgetWrapper *QgsAttributeForm::setupRelationWidgetWrapper( const QString &relationWidgetTypeId, const QgsRelation &rel, const QgsAttributeEditorContext &context )
1507{
1508 QgsRelationWidgetWrapper *rww = new QgsRelationWidgetWrapper( relationWidgetTypeId, mLayer, rel, nullptr, this );
1509 const QVariantMap config = mLayer->editFormConfig().widgetConfig( rel.id() );
1510 rww->setConfig( config );
1511 rww->setContext( context );
1512
1513 return rww;
1514}
1515
1516void QgsAttributeForm::preventFeatureRefresh()
1517{
1518 mPreventFeatureRefresh = true;
1519}
1520
1522{
1523 if ( mPreventFeatureRefresh || mLayer->isEditable() || !mFeature.isValid() )
1524 return;
1525
1526 // reload feature if layer changed although not editable
1527 // (datasource probably changed bypassing QgsVectorLayer)
1528 if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeature.id() ) ).nextFeature( mFeature ) )
1529 return;
1530
1531 init();
1532 setFeature( mFeature );
1533}
1534
1535void QgsAttributeForm::parentFormValueChanged( const QString &attribute, const QVariant &newValue )
1536{
1537 if ( mContext.parentFormFeature().isValid() )
1538 {
1539 QgsFeature parentFormFeature = mContext.parentFormFeature();
1540 parentFormFeature.setAttribute( attribute, newValue );
1541 mContext.setParentFormFeature( parentFormFeature );
1542 }
1543
1544 updateValuesDependenciesParent();
1545
1546 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1547 {
1548 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1549 if ( eww )
1550 {
1551 eww->parentFormValueChanged( attribute, newValue );
1552 }
1553 }
1554}
1555
1557{
1558 return mNeedsGeometry;
1559}
1560
1561void QgsAttributeForm::synchronizeState()
1562{
1563 bool isEditable = ( mFeature.isValid()
1566 && mLayer->isEditable();
1567
1568 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1569 {
1570 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1571 if ( eww )
1572 {
1573 const QList<QgsAttributeFormEditorWidget *> formWidgets = mFormEditorWidgets.values( eww->fieldIdx() );
1574
1575 for ( QgsAttributeFormEditorWidget *formWidget : formWidgets )
1576 formWidget->setConstraintResultVisible( isEditable );
1577
1578 eww->setConstraintResultVisible( isEditable );
1579
1580 bool enabled = isEditable && fieldIsEditable( eww->fieldIdx() );
1581 ww->setEnabled( enabled );
1582
1583 updateIcon( eww );
1584 }
1585 else // handle QgsWidgetWrapper different than QgsEditorWidgetWrapper
1586 {
1587 ww->setEnabled( isEditable );
1588 }
1589 }
1590
1591
1593 {
1595 {
1596 isEditable = false;
1597 if ( mConstraintsFailMessageBarItem )
1598 {
1599 mMessageBar->popWidget( mConstraintsFailMessageBarItem );
1600 }
1601 mConstraintsFailMessageBarItem = new QgsMessageBarItem( tr( "Multi edit mode requires at least one selected feature." ), Qgis::MessageLevel::Info, -1 );
1602 mMessageBar->pushItem( mConstraintsFailMessageBarItem );
1603 }
1604 else
1605 {
1606 QStringList invalidFields, descriptions;
1607 mValidConstraints = currentFormValidHardConstraints( invalidFields, descriptions );
1608
1609 if ( isEditable && mContext.formMode() == QgsAttributeEditorContext::Embed )
1610 {
1611 if ( !mValidConstraints && !mConstraintsFailMessageBarItem )
1612 {
1613 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 );
1614 mMessageBar->pushItem( mConstraintsFailMessageBarItem );
1615 }
1616 else if ( mValidConstraints && mConstraintsFailMessageBarItem )
1617 {
1618 mMessageBar->popWidget( mConstraintsFailMessageBarItem );
1619 mConstraintsFailMessageBarItem = nullptr;
1620 }
1621 }
1622 else if ( mConstraintsFailMessageBarItem )
1623 {
1624 mMessageBar->popWidget( mConstraintsFailMessageBarItem );
1625 mConstraintsFailMessageBarItem = nullptr;
1626 }
1627
1628 isEditable = isEditable & mValidConstraints;
1629 }
1630 }
1631
1632 // change OK button status
1633 QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
1634 if ( okButton )
1635 okButton->setEnabled( isEditable );
1636}
1637
1638void QgsAttributeForm::init()
1639{
1640 QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
1641
1642 // Cleanup of any previously shown widget, we start from scratch
1643 QWidget *formWidget = nullptr;
1644 mNeedsGeometry = false;
1645
1646 bool buttonBoxVisible = true;
1647 // Cleanup button box but preserve visibility
1648 if ( mButtonBox )
1649 {
1650 buttonBoxVisible = mButtonBox->isVisible();
1651 delete mButtonBox;
1652 mButtonBox = nullptr;
1653 }
1654
1655 if ( mSearchButtonBox )
1656 {
1657 delete mSearchButtonBox;
1658 mSearchButtonBox = nullptr;
1659 }
1660
1661 qDeleteAll( mWidgets );
1662 mWidgets.clear();
1663
1664 while ( QWidget *w = this->findChild<QWidget *>() )
1665 {
1666 delete w;
1667 }
1668 delete layout();
1669
1670 QVBoxLayout *vl = new QVBoxLayout();
1671 vl->setContentsMargins( 0, 0, 0, 0 );
1672 mMessageBar = new QgsMessageBar( this );
1673 mMessageBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1674 vl->addWidget( mMessageBar );
1675
1676 setLayout( vl );
1677
1678 // Get a layout
1679 QGridLayout *layout = new QGridLayout();
1680 QWidget *container = new QWidget();
1681 container->setLayout( layout );
1682 vl->addWidget( container );
1683
1684 mFormEditorWidgets.clear();
1685 mFormWidgets.clear();
1686
1687 // a bar to warn the user with non-blocking messages
1688 setContentsMargins( 0, 0, 0, 0 );
1689
1690 // Try to load Ui-File for layout
1691 if ( mContext.allowCustomUi() && mLayer->editFormConfig().layout() == Qgis::AttributeFormLayout::UiFile && !mLayer->editFormConfig().uiForm().isEmpty() )
1692 {
1693 QgsDebugMsgLevel( QStringLiteral( "loading form: %1" ).arg( mLayer->editFormConfig().uiForm() ), 2 );
1694 const QString path = mLayer->editFormConfig().uiForm();
1696 if ( file && file->open( QFile::ReadOnly ) )
1697 {
1698 QUiLoader loader;
1699
1700 QFileInfo fi( file->fileName() );
1701 loader.setWorkingDirectory( fi.dir() );
1702 formWidget = loader.load( file, this );
1703 if ( formWidget )
1704 {
1705 formWidget->setWindowFlags( Qt::Widget );
1706 layout->addWidget( formWidget );
1707 formWidget->show();
1708 file->close();
1709 mButtonBox = findChild<QDialogButtonBox *>();
1710 createWrappers();
1711
1712 formWidget->installEventFilter( this );
1713 }
1714 }
1715 }
1716
1717 QgsTabWidget *tabWidget = nullptr;
1718
1719 // Tab layout
1720 if ( !formWidget && mLayer->editFormConfig().layout() == Qgis::AttributeFormLayout::DragAndDrop )
1721 {
1722 int row = 0;
1723 int column = 0;
1724 int columnCount = 1;
1725 bool hasRootFields = false;
1726 bool addSpacer = true;
1727
1728 const QList<QgsAttributeEditorElement *> tabs = mLayer->editFormConfig().tabs();
1729
1730 for ( QgsAttributeEditorElement *widgDef : tabs )
1731 {
1732 if ( widgDef->type() == Qgis::AttributeEditorType::Container )
1733 {
1734 QgsAttributeEditorContainer *containerDef = dynamic_cast<QgsAttributeEditorContainer *>( widgDef );
1735 if ( !containerDef )
1736 continue;
1737
1738 switch ( containerDef->type() )
1739 {
1741 {
1742 tabWidget = nullptr;
1743 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1744 if ( widgetInfo.labelStyle.overrideColor )
1745 {
1746 if ( widgetInfo.labelStyle.color.isValid() )
1747 {
1748 widgetInfo.widget->setStyleSheet( QStringLiteral( "QGroupBox::title { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1749 }
1750 }
1751 if ( widgetInfo.labelStyle.overrideFont )
1752 {
1753 widgetInfo.widget->setFont( widgetInfo.labelStyle.font );
1754 }
1755
1756 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1757 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1758 {
1759 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1760 }
1761 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1762 {
1763 layout->setRowStretch( row, widgDef->verticalStretch() );
1764 addSpacer = false;
1765 }
1766
1767 if ( containerDef->visibilityExpression().enabled() || containerDef->collapsedExpression().enabled() )
1768 {
1769 registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().enabled() ? containerDef->visibilityExpression().data() : QgsExpression(), containerDef->collapsed(), containerDef->collapsedExpression().enabled() ? containerDef->collapsedExpression().data() : QgsExpression() ) );
1770 }
1771 column += 2;
1772 break;
1773 }
1774
1776 {
1777 tabWidget = nullptr;
1778 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1779 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1780 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1781 {
1782 layout->setRowStretch( row, widgDef->verticalStretch() );
1783 addSpacer = false;
1784 }
1785 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1786 {
1787 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1788 }
1789
1790 if ( containerDef->visibilityExpression().enabled() || containerDef->collapsedExpression().enabled() )
1791 {
1792 registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().enabled() ? containerDef->visibilityExpression().data() : QgsExpression(), containerDef->collapsed(), containerDef->collapsedExpression().enabled() ? containerDef->collapsedExpression().data() : QgsExpression() ) );
1793 }
1794 column += 2;
1795 break;
1796 }
1797
1799 {
1800 if ( !tabWidget )
1801 {
1802 tabWidget = new QgsTabWidget();
1803 layout->addWidget( tabWidget, row, column, 1, 2 );
1804 column += 2;
1805 }
1806
1807 QWidget *tabPage = new QWidget( tabWidget );
1808
1809 tabWidget->addTab( tabPage, widgDef->name() );
1810 tabWidget->setTabStyle( tabWidget->tabBar()->count() - 1, widgDef->labelStyle() );
1811
1812 if ( containerDef->visibilityExpression().enabled() )
1813 {
1814 registerContainerInformation( new ContainerInformation( tabWidget, tabPage, containerDef->visibilityExpression().data() ) );
1815 }
1816 QGridLayout *tabPageLayout = new QGridLayout();
1817 tabPage->setLayout( tabPageLayout );
1818
1819 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, tabPage, mLayer, mContext );
1820 tabPageLayout->addWidget( widgetInfo.widget );
1821 break;
1822 }
1823 }
1824 }
1825 else if ( widgDef->type() == Qgis::AttributeEditorType::Relation )
1826 {
1827 hasRootFields = true;
1828 tabWidget = nullptr;
1829 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1830 QgsCollapsibleGroupBox *collapsibleGroupBox = new QgsCollapsibleGroupBox();
1831
1832 if ( widgetInfo.showLabel )
1833 {
1834 if ( widgetInfo.labelStyle.overrideColor && widgetInfo.labelStyle.color.isValid() )
1835 {
1836 collapsibleGroupBox->setStyleSheet( QStringLiteral( "QGroupBox::title { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1837 }
1838
1839 if ( widgetInfo.labelStyle.overrideFont )
1840 {
1841 collapsibleGroupBox->setFont( widgetInfo.labelStyle.font );
1842 }
1843
1844 collapsibleGroupBox->setTitle( widgetInfo.labelText );
1845 }
1846
1847 QVBoxLayout *collapsibleGroupBoxLayout = new QVBoxLayout();
1848 collapsibleGroupBoxLayout->addWidget( widgetInfo.widget );
1849 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
1850
1851 QVBoxLayout *c = new QVBoxLayout();
1852 c->addWidget( collapsibleGroupBox );
1853 layout->addLayout( c, row, column, 1, 2 );
1854
1855 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1856 layout->setRowStretch( row, widgDef->verticalStretch() );
1857 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1858 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1859
1860 column += 2;
1861
1862 // we consider all relation editors should be expanding
1863 addSpacer = false;
1864 }
1865 else
1866 {
1867 hasRootFields = true;
1868 tabWidget = nullptr;
1869 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1870 QLabel *label = new QLabel( widgetInfo.labelText );
1871
1872 if ( widgetInfo.labelStyle.overrideColor )
1873 {
1874 if ( widgetInfo.labelStyle.color.isValid() )
1875 {
1876 label->setStyleSheet( QStringLiteral( "QLabel { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1877 }
1878 }
1879
1880 if ( widgetInfo.labelStyle.overrideFont )
1881 {
1882 label->setFont( widgetInfo.labelStyle.font );
1883 }
1884
1885 label->setToolTip( widgetInfo.toolTip );
1886 if ( columnCount > 1 && !widgetInfo.labelOnTop )
1887 {
1888 label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1889 }
1890
1891 label->setBuddy( widgetInfo.widget );
1892
1893 // If at least one expanding widget is present do not add a spacer
1894 if ( widgetInfo.widget
1895 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
1896 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
1897 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
1898 addSpacer = false;
1899
1900 if ( !widgetInfo.showLabel )
1901 {
1902 QVBoxLayout *c = new QVBoxLayout();
1903 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1904 c->addWidget( widgetInfo.widget );
1905 layout->addLayout( c, row, column, 1, 2 );
1906
1907 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1908 {
1909 layout->setRowStretch( row, widgDef->verticalStretch() );
1910 addSpacer = false;
1911 }
1912 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1913 {
1914 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1915 }
1916
1917 column += 2;
1918 }
1919 else if ( widgetInfo.labelOnTop )
1920 {
1921 QVBoxLayout *c = new QVBoxLayout();
1922 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1923 c->addWidget( label );
1924 c->addWidget( widgetInfo.widget );
1925 layout->addLayout( c, row, column, 1, 2 );
1926
1927 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1928 {
1929 layout->setRowStretch( row, widgDef->verticalStretch() );
1930 addSpacer = false;
1931 }
1932 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1933 {
1934 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1935 }
1936
1937 column += 2;
1938 }
1939 else
1940 {
1941 const int widgetColumn = column + 1;
1942 layout->addWidget( label, row, column++ );
1943 layout->addWidget( widgetInfo.widget, row, column++ );
1944
1945 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1946 {
1947 layout->setRowStretch( row, widgDef->verticalStretch() );
1948 addSpacer = false;
1949 }
1950 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( widgetColumn ) )
1951 {
1952 layout->setColumnStretch( widgetColumn, widgDef->horizontalStretch() );
1953 }
1954 }
1955
1956 // Alias DD overrides
1957 if ( widgDef->type() == Qgis::AttributeEditorType::Field )
1958 {
1959 const QgsAttributeEditorField *fieldElement { static_cast<QgsAttributeEditorField *>( widgDef ) };
1960 const int fieldIdx = fieldElement->idx();
1961 if ( fieldIdx >= 0 && fieldIdx < mLayer->fields().count() )
1962 {
1963 const QString fieldName { mLayer->fields().at( fieldIdx ).name() };
1965 {
1967 if ( property.isActive() )
1968 {
1969 mLabelDataDefinedProperties[label] = property;
1970 }
1971 }
1973 {
1975 if ( property.isActive() )
1976 {
1977 mEditableDataDefinedProperties[widgetInfo.widget] = property;
1978 }
1979 }
1980 }
1981 }
1982 }
1983
1984 if ( column >= columnCount * 2 )
1985 {
1986 column = 0;
1987 row += 1;
1988 }
1989 }
1990
1991 if ( hasRootFields && addSpacer )
1992 {
1993 QSpacerItem *spacerItem = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
1994 layout->addItem( spacerItem, row, 0 );
1995 layout->setRowStretch( row, 1 );
1996 }
1997
1998 formWidget = container;
1999 }
2000
2001 // Autogenerate Layout
2002 // If there is still no layout loaded (defined as autogenerate or other methods failed)
2003 mIconMap.clear();
2004
2005 if ( !formWidget )
2006 {
2007 formWidget = new QWidget( this );
2008 QGridLayout *gridLayout = new QGridLayout( formWidget );
2009 formWidget->setLayout( gridLayout );
2010
2011 if ( mContext.formMode() != QgsAttributeEditorContext::Embed )
2012 {
2013 // put the form into a scroll area to nicely handle cases with lots of attributes
2014 QgsScrollArea *scrollArea = new QgsScrollArea( this );
2015 scrollArea->setWidget( formWidget );
2016 scrollArea->setWidgetResizable( true );
2017 scrollArea->setFrameShape( QFrame::NoFrame );
2018 scrollArea->setFrameShadow( QFrame::Plain );
2019 scrollArea->setFocusProxy( this );
2020 layout->addWidget( scrollArea );
2021 }
2022 else
2023 {
2024 layout->addWidget( formWidget );
2025 }
2026
2027 int row = 0;
2028
2029 const QgsFields fields = mLayer->fields();
2030
2031 for ( const QgsField &field : fields )
2032 {
2033 int idx = fields.lookupField( field.name() );
2034 if ( idx < 0 )
2035 continue;
2036
2037 //show attribute alias if available
2038 QString fieldName = mLayer->attributeDisplayName( idx );
2039 QString labelText = fieldName;
2040 labelText.replace( '&', QLatin1String( "&&" ) ); // need to escape '&' or they'll be replace by _ in the label text
2041
2042 const QgsEditorWidgetSetup widgetSetup = QgsGui::editorWidgetRegistry()->findBest( mLayer, field.name() );
2043
2044 if ( widgetSetup.type() == QLatin1String( "Hidden" ) )
2045 continue;
2046
2047 bool labelOnTop = mLayer->editFormConfig().labelOnTop( idx );
2048
2049 // This will also create the widget
2050 QLabel *label = new QLabel( labelText );
2051 label->setToolTip( QgsFieldModel::fieldToolTipExtended( field, mLayer ) );
2052 QSvgWidget *i = new QSvgWidget();
2053 i->setFixedSize( 18, 18 );
2054
2056 {
2058 if ( property.isActive() )
2059 {
2060 mLabelDataDefinedProperties[label] = property;
2061 }
2062 }
2063
2064 QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( widgetSetup.type(), mLayer, idx, widgetSetup.config(), nullptr, this, mContext );
2065
2066 QWidget *w = nullptr;
2067 if ( eww )
2068 {
2069 QgsAttributeFormEditorWidget *formWidget = new QgsAttributeFormEditorWidget( eww, widgetSetup.type(), this );
2070 w = formWidget;
2071 mFormEditorWidgets.insert( idx, formWidget );
2072 mFormWidgets.append( formWidget );
2073 formWidget->createSearchWidgetWrappers( mContext );
2074
2075 label->setBuddy( eww->widget() );
2076
2078 {
2080 if ( property.isActive() )
2081 {
2082 mEditableDataDefinedProperties[formWidget] = property;
2083 }
2084 }
2085 }
2086 else
2087 {
2088 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() ) ) );
2089 }
2090
2091
2092 if ( w )
2093 w->setObjectName( field.name() );
2094
2095 if ( eww )
2096 {
2097 mWidgets.append( eww );
2098 mIconMap[eww->widget()] = i;
2099 }
2100
2101 if ( labelOnTop )
2102 {
2103 gridLayout->addWidget( label, row++, 0, 1, 2 );
2104 gridLayout->addWidget( w, row++, 0, 1, 2 );
2105 gridLayout->addWidget( i, row++, 0, 1, 2 );
2106 }
2107 else
2108 {
2109 gridLayout->addWidget( label, row, 0 );
2110 gridLayout->addWidget( w, row, 1 );
2111 gridLayout->addWidget( i, row++, 2 );
2112 }
2113 }
2114
2115 const QList<QgsRelation> relations = QgsProject::instance()->relationManager()->referencedRelations( mLayer );
2116 for ( const QgsRelation &rel : relations )
2117 {
2118 QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( QStringLiteral( "relation_editor" ), rel, mContext );
2119
2121 formWidget->createSearchWidgetWrappers( mContext );
2122
2123 QgsCollapsibleGroupBox *collapsibleGroupBox = new QgsCollapsibleGroupBox( rel.name() );
2124 QVBoxLayout *collapsibleGroupBoxLayout = new QVBoxLayout();
2125 collapsibleGroupBoxLayout->addWidget( formWidget );
2126 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
2127
2128 gridLayout->addWidget( collapsibleGroupBox, row++, 0, 1, 2 );
2129
2130 mWidgets.append( rww );
2131 mFormWidgets.append( formWidget );
2132 }
2133
2134 if ( QgsProject::instance()->relationManager()->referencedRelations( mLayer ).isEmpty() )
2135 {
2136 QSpacerItem *spacerItem = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
2137 gridLayout->addItem( spacerItem, row, 0 );
2138 gridLayout->setRowStretch( row, 1 );
2139 row++;
2140 }
2141 }
2142
2143 // Prepare value dependencies
2144 updateFieldDependencies();
2145
2146 if ( !mButtonBox )
2147 {
2148 mButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
2149 mButtonBox->setObjectName( QStringLiteral( "buttonBox" ) );
2150 layout->addWidget( mButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2151 }
2152 mButtonBox->setVisible( buttonBoxVisible );
2153
2154 if ( !mSearchButtonBox )
2155 {
2156 mSearchButtonBox = new QWidget();
2157 QHBoxLayout *boxLayout = new QHBoxLayout();
2158 boxLayout->setContentsMargins( 0, 0, 0, 0 );
2159 mSearchButtonBox->setLayout( boxLayout );
2160 mSearchButtonBox->setObjectName( QStringLiteral( "searchButtonBox" ) );
2161
2162 QPushButton *clearButton = new QPushButton( tr( "&Reset Form" ), mSearchButtonBox );
2163 connect( clearButton, &QPushButton::clicked, this, &QgsAttributeForm::resetSearch );
2164 boxLayout->addWidget( clearButton );
2165 boxLayout->addStretch( 1 );
2166
2167 QPushButton *flashButton = new QPushButton();
2168 flashButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2169 flashButton->setText( tr( "&Flash Features" ) );
2170 connect( flashButton, &QToolButton::clicked, this, &QgsAttributeForm::searchFlash );
2171 boxLayout->addWidget( flashButton );
2172
2173 QPushButton *openAttributeTableButton = new QPushButton();
2174 openAttributeTableButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2175 openAttributeTableButton->setText( tr( "Show in &Table" ) );
2176 openAttributeTableButton->setToolTip( tr( "Open the attribute table editor with the filtered features" ) );
2177 connect( openAttributeTableButton, &QToolButton::clicked, this, [=] {
2178 emit openFilteredFeaturesAttributeTable( createFilterExpression() );
2179 } );
2180 boxLayout->addWidget( openAttributeTableButton );
2181
2182 QPushButton *zoomButton = new QPushButton();
2183 zoomButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2184 zoomButton->setText( tr( "&Zoom to Features" ) );
2185 connect( zoomButton, &QToolButton::clicked, this, &QgsAttributeForm::searchZoomTo );
2186 boxLayout->addWidget( zoomButton );
2187
2188 QToolButton *selectButton = new QToolButton();
2189 selectButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2190 selectButton->setText( tr( "&Select Features" ) );
2191 selectButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFormSelect.svg" ) ) );
2192 selectButton->setPopupMode( QToolButton::MenuButtonPopup );
2193 selectButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
2194 connect( selectButton, &QToolButton::clicked, this, &QgsAttributeForm::searchSetSelection );
2195 QMenu *selectMenu = new QMenu( selectButton );
2196 QAction *selectAction = new QAction( tr( "Select Features" ), selectMenu );
2197 selectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFormSelect.svg" ) ) );
2198 connect( selectAction, &QAction::triggered, this, &QgsAttributeForm::searchSetSelection );
2199 selectMenu->addAction( selectAction );
2200 QAction *addSelectAction = new QAction( tr( "Add to Current Selection" ), selectMenu );
2201 addSelectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectAdd.svg" ) ) );
2202 connect( addSelectAction, &QAction::triggered, this, &QgsAttributeForm::searchAddToSelection );
2203 selectMenu->addAction( addSelectAction );
2204 QAction *deselectAction = new QAction( tr( "Remove from Current Selection" ), selectMenu );
2205 deselectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectRemove.svg" ) ) );
2206 connect( deselectAction, &QAction::triggered, this, &QgsAttributeForm::searchRemoveFromSelection );
2207 selectMenu->addAction( deselectAction );
2208 QAction *filterSelectAction = new QAction( tr( "Filter Current Selection" ), selectMenu );
2209 filterSelectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectIntersect.svg" ) ) );
2210 connect( filterSelectAction, &QAction::triggered, this, &QgsAttributeForm::searchIntersectSelection );
2211 selectMenu->addAction( filterSelectAction );
2212 selectButton->setMenu( selectMenu );
2213 boxLayout->addWidget( selectButton );
2214
2215 if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
2216 {
2217 QToolButton *filterButton = new QToolButton();
2218 filterButton->setText( tr( "Filter Features" ) );
2219 filterButton->setPopupMode( QToolButton::MenuButtonPopup );
2220 filterButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2221 connect( filterButton, &QToolButton::clicked, this, &QgsAttributeForm::filterTriggered );
2222 QMenu *filterMenu = new QMenu( filterButton );
2223 QAction *filterAndAction = new QAction( tr( "Filter Within (\"AND\")" ), filterMenu );
2224 connect( filterAndAction, &QAction::triggered, this, &QgsAttributeForm::filterAndTriggered );
2225 filterMenu->addAction( filterAndAction );
2226 QAction *filterOrAction = new QAction( tr( "Extend Filter (\"OR\")" ), filterMenu );
2227 connect( filterOrAction, &QAction::triggered, this, &QgsAttributeForm::filterOrTriggered );
2228 filterMenu->addAction( filterOrAction );
2229 filterButton->setMenu( filterMenu );
2230 boxLayout->addWidget( filterButton );
2231 }
2232 else
2233 {
2234 QPushButton *closeButton = new QPushButton( tr( "Close" ), mSearchButtonBox );
2235 connect( closeButton, &QPushButton::clicked, this, &QgsAttributeForm::closed );
2236 closeButton->setShortcut( Qt::Key_Escape );
2237 boxLayout->addWidget( closeButton );
2238 }
2239
2240 layout->addWidget( mSearchButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2241 }
2242 mSearchButtonBox->setVisible( mMode == QgsAttributeEditorContext::SearchMode );
2243
2244 afterWidgetInit();
2245
2246 connect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsAttributeForm::save );
2247 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsAttributeForm::resetValues );
2248
2249 connect( mLayer, &QgsVectorLayer::editingStarted, this, &QgsAttributeForm::synchronizeState );
2250 connect( mLayer, &QgsVectorLayer::editingStopped, this, &QgsAttributeForm::synchronizeState );
2251
2252 // This triggers a refresh of the form widget and gives a chance to re-format the
2253 // value to those widgets that have a different representation when in edit mode
2256
2257
2258 const auto constMInterfaces = mInterfaces;
2259 for ( QgsAttributeFormInterface *iface : constMInterfaces )
2260 {
2261 iface->initForm();
2262 }
2263
2265 {
2266 hideButtonBox();
2267 }
2268
2269 QApplication::restoreOverrideCursor();
2270}
2271
2272void QgsAttributeForm::cleanPython()
2273{
2274 if ( !mPyFormVarName.isNull() )
2275 {
2276 QString expr = QStringLiteral( "if '%1' in locals(): del %1\n" ).arg( mPyFormVarName );
2277 QgsPythonRunner::run( expr );
2278 }
2279}
2280
2281void QgsAttributeForm::initPython()
2282{
2283 cleanPython();
2284
2285 // Init Python, if init function is not empty and the combo indicates
2286 // the source for the function code
2287 if ( !mLayer->editFormConfig().initFunction().isEmpty()
2289 {
2290 QString initFunction = mLayer->editFormConfig().initFunction();
2291 QString initFilePath = mLayer->editFormConfig().initFilePath();
2292 QString initCode;
2293
2294 switch ( mLayer->editFormConfig().initCodeSource() )
2295 {
2297 if ( !initFilePath.isEmpty() )
2298 {
2299 QFile *inputFile = QgsApplication::networkContentFetcherRegistry()->localFile( initFilePath );
2300
2301 if ( inputFile && inputFile->open( QFile::ReadOnly ) )
2302 {
2303 // Read it into a string
2304 QTextStream inf( inputFile );
2305#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
2306 inf.setCodec( "UTF-8" );
2307#endif
2308 initCode = inf.readAll();
2309 inputFile->close();
2310 }
2311 else // The file couldn't be opened
2312 {
2313 QgsLogger::warning( QStringLiteral( "The external python file path %1 could not be opened!" ).arg( initFilePath ) );
2314 }
2315 }
2316 else
2317 {
2318 QgsLogger::warning( QStringLiteral( "The external python file path is empty!" ) );
2319 }
2320 break;
2321
2323 initCode = mLayer->editFormConfig().initCode();
2324 if ( initCode.isEmpty() )
2325 {
2326 QgsLogger::warning( QStringLiteral( "The python code provided in the dialog is empty!" ) );
2327 }
2328 break;
2329
2332 // Nothing to do: the function code should be already in the environment
2333 break;
2334 }
2335
2336 // If we have a function code, run it
2337 if ( !initCode.isEmpty() )
2338 {
2340 QgsPythonRunner::run( initCode );
2341 else
2342 mMessageBar->pushMessage( QString(), tr( "Python macro could not be run due to missing permissions." ), Qgis::MessageLevel::Warning );
2343 }
2344
2345 QgsPythonRunner::run( QStringLiteral( "import inspect" ) );
2346 QString numArgs;
2347
2348 // Check for eval result
2349 if ( QgsPythonRunner::eval( QStringLiteral( "len(inspect.getfullargspec(%1)[0])" ).arg( initFunction ), numArgs ) )
2350 {
2351 static int sFormId = 0;
2352 mPyFormVarName = QStringLiteral( "_qgis_featureform_%1_%2" ).arg( mFormNr ).arg( sFormId++ );
2353
2354 QString form = QStringLiteral( "%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )" )
2355 .arg( mPyFormVarName )
2356 .arg( ( quint64 ) this );
2357
2358 QgsPythonRunner::run( form );
2359
2360 QgsDebugMsgLevel( QStringLiteral( "running featureForm init: %1" ).arg( mPyFormVarName ), 2 );
2361
2362 // Legacy
2363 if ( numArgs == QLatin1String( "3" ) )
2364 {
2365 addInterface( new QgsAttributeFormLegacyInterface( initFunction, mPyFormVarName, this ) );
2366 }
2367 else
2368 {
2369 // If we get here, it means that the function doesn't accept three arguments
2370 QMessageBox msgBox;
2371 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 ) );
2372 msgBox.exec();
2373#if 0
2374 QString expr = QString( "%1(%2)" )
2375 .arg( mLayer->editFormInit() )
2376 .arg( mPyFormVarName );
2377 QgsAttributeFormInterface *iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface *>( expr, "QgsAttributeFormInterface" );
2378 if ( iface )
2379 addInterface( iface );
2380#endif
2381 }
2382 }
2383 else
2384 {
2385 // If we get here, it means that inspect couldn't find the function
2386 QMessageBox msgBox;
2387 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 ) );
2388 msgBox.exec();
2389 }
2390 }
2391}
2392
2393QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAttributeEditorElement *widgetDef, QWidget *parent, QgsVectorLayer *vl, QgsAttributeEditorContext &context )
2394{
2395 WidgetInfo newWidgetInfo;
2396
2397 newWidgetInfo.labelStyle = widgetDef->labelStyle();
2398
2399 switch ( widgetDef->type() )
2400 {
2402 {
2403 const QgsAttributeEditorAction *elementDef = dynamic_cast<const QgsAttributeEditorAction *>( widgetDef );
2404 if ( !elementDef )
2405 break;
2406
2407 QgsActionWidgetWrapper *actionWrapper = new QgsActionWidgetWrapper( mLayer, nullptr, this );
2408 actionWrapper->setAction( elementDef->action( vl ) );
2409 context.setAttributeFormMode( mMode );
2410 actionWrapper->setContext( context );
2411 mWidgets.append( actionWrapper );
2412 newWidgetInfo.widget = actionWrapper->widget();
2413 newWidgetInfo.showLabel = false;
2414
2415 break;
2416 }
2417
2419 {
2420 const QgsAttributeEditorField *fieldDef = dynamic_cast<const QgsAttributeEditorField *>( widgetDef );
2421 if ( !fieldDef )
2422 break;
2423
2424 const QgsFields fields = vl->fields();
2425 int fldIdx = fields.lookupField( fieldDef->name() );
2426 if ( fldIdx < fields.count() && fldIdx >= 0 )
2427 {
2428 const QgsEditorWidgetSetup widgetSetup = QgsGui::editorWidgetRegistry()->findBest( mLayer, fieldDef->name() );
2429
2430 QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( widgetSetup.type(), mLayer, fldIdx, widgetSetup.config(), nullptr, this, mContext );
2431 QgsAttributeFormEditorWidget *formWidget = new QgsAttributeFormEditorWidget( eww, widgetSetup.type(), this );
2432 mFormEditorWidgets.insert( fldIdx, formWidget );
2433 mFormWidgets.append( formWidget );
2434
2435 formWidget->createSearchWidgetWrappers( mContext );
2436
2437 newWidgetInfo.widget = formWidget;
2438 mWidgets.append( eww );
2439
2440 newWidgetInfo.widget->setObjectName( fields.at( fldIdx ).name() );
2441 newWidgetInfo.hint = fields.at( fldIdx ).comment();
2442 }
2443
2444 newWidgetInfo.labelOnTop = mLayer->editFormConfig().labelOnTop( fldIdx );
2445 newWidgetInfo.labelText = mLayer->attributeDisplayName( fldIdx );
2446 newWidgetInfo.labelText.replace( '&', QLatin1String( "&&" ) ); // need to escape '&' or they'll be replace by _ in the label text
2447 newWidgetInfo.toolTip = QStringLiteral( "<b>%1</b><p>%2</p>" ).arg( mLayer->attributeDisplayName( fldIdx ), newWidgetInfo.hint );
2448 newWidgetInfo.showLabel = widgetDef->showLabel();
2449
2450 break;
2451 }
2452
2454 {
2455 const QgsAttributeEditorRelation *relDef = static_cast<const QgsAttributeEditorRelation *>( widgetDef );
2456
2457 QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( relDef->relationWidgetTypeId(), relDef->relation(), context );
2458
2460 formWidget->createSearchWidgetWrappers( mContext );
2461
2462 // This needs to be after QgsAttributeFormRelationEditorWidget creation, because the widget
2463 // does not exists yet until QgsAttributeFormRelationEditorWidget is created and the setters
2464 // below directly alter the widget and check for it.
2466 rww->setNmRelationId( relDef->nmRelationId() );
2468
2469 mWidgets.append( rww );
2470 mFormWidgets.append( formWidget );
2471
2472 newWidgetInfo.widget = formWidget;
2473 newWidgetInfo.showLabel = relDef->showLabel();
2474 newWidgetInfo.labelText = relDef->label();
2475 if ( newWidgetInfo.labelText.isEmpty() )
2476 newWidgetInfo.labelText = rww->relation().name();
2477 newWidgetInfo.labelOnTop = true;
2478 break;
2479 }
2480
2482 {
2483 const QgsAttributeEditorContainer *container = dynamic_cast<const QgsAttributeEditorContainer *>( widgetDef );
2484 if ( !container )
2485 break;
2486
2487 int columnCount = container->columnCount();
2488
2489 if ( columnCount <= 0 )
2490 columnCount = 1;
2491
2492 QString widgetName;
2493 QWidget *myContainer = nullptr;
2494 bool removeLayoutMargin = false;
2495 switch ( container->type() )
2496 {
2498 {
2500 widgetName = QStringLiteral( "QGroupBox" );
2501 if ( container->showLabel() )
2502 {
2503 groupBox->setTitle( container->name() );
2504 if ( newWidgetInfo.labelStyle.overrideColor )
2505 {
2506 if ( newWidgetInfo.labelStyle.color.isValid() )
2507 {
2508 groupBox->setStyleSheet( QStringLiteral( "QGroupBox::title { color: %1; }" ).arg( newWidgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2509 }
2510 }
2511 if ( newWidgetInfo.labelStyle.overrideFont )
2512 {
2513 groupBox->setFont( newWidgetInfo.labelStyle.font );
2514 }
2515 }
2516 myContainer = groupBox;
2517 newWidgetInfo.widget = myContainer;
2518 groupBox->setCollapsed( container->collapsed() );
2519 break;
2520 }
2521
2523 {
2524 QWidget *rowWidget = new QWidget();
2525 widgetName = QStringLiteral( "Row" );
2526 myContainer = rowWidget;
2527 newWidgetInfo.widget = myContainer;
2528 removeLayoutMargin = true;
2529 columnCount = container->children().size();
2530 break;
2531 }
2532
2534 {
2535 myContainer = new QWidget();
2536
2537 QgsScrollArea *scrollArea = new QgsScrollArea( parent );
2538
2539 scrollArea->setWidget( myContainer );
2540 scrollArea->setWidgetResizable( true );
2541 scrollArea->setFrameShape( QFrame::NoFrame );
2542 widgetName = QStringLiteral( "QScrollArea QWidget" );
2543
2544 newWidgetInfo.widget = scrollArea;
2545 break;
2546 }
2547 }
2548
2549 if ( container->backgroundColor().isValid() )
2550 {
2551 QString style { QStringLiteral( "background-color: %1;" ).arg( container->backgroundColor().name() ) };
2552 newWidgetInfo.widget->setStyleSheet( style );
2553 }
2554
2555 QGridLayout *gbLayout = new QGridLayout();
2556 if ( removeLayoutMargin )
2557 gbLayout->setContentsMargins( 0, 0, 0, 0 );
2558 myContainer->setLayout( gbLayout );
2559
2560 int row = 0;
2561 int column = 0;
2562 bool addSpacer = true;
2563
2564 const QList<QgsAttributeEditorElement *> children = container->children();
2565
2566 for ( QgsAttributeEditorElement *childDef : children )
2567 {
2568 WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context );
2569
2570 if ( childDef->type() == Qgis::AttributeEditorType::Container )
2571 {
2572 QgsAttributeEditorContainer *containerDef = static_cast<QgsAttributeEditorContainer *>( childDef );
2573 if ( containerDef->visibilityExpression().enabled() || containerDef->collapsedExpression().enabled() )
2574 {
2575 registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().enabled() ? containerDef->visibilityExpression().data() : QgsExpression(), containerDef->collapsed(), containerDef->collapsedExpression().enabled() ? containerDef->collapsedExpression().data() : QgsExpression() ) );
2576 }
2577 }
2578
2579 // column containing the actual widget, not the label
2580 int widgetColumn = column;
2581
2582 if ( widgetInfo.labelText.isNull() || !widgetInfo.showLabel )
2583 {
2584 gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 );
2585 widgetColumn = column + 1;
2586 column += 2;
2587 }
2588 else
2589 {
2590 QLabel *mypLabel = new QLabel( widgetInfo.labelText );
2591
2592 if ( widgetInfo.labelStyle.overrideColor )
2593 {
2594 if ( widgetInfo.labelStyle.color.isValid() )
2595 {
2596 mypLabel->setStyleSheet( QStringLiteral( "QLabel { color: %1; }" ).arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2597 }
2598 }
2599
2600 if ( widgetInfo.labelStyle.overrideFont )
2601 {
2602 mypLabel->setFont( widgetInfo.labelStyle.font );
2603 }
2604
2605 // Alias DD overrides
2606 if ( childDef->type() == Qgis::AttributeEditorType::Field )
2607 {
2608 const QgsAttributeEditorField *fieldDef { static_cast<QgsAttributeEditorField *>( childDef ) };
2609 const QgsFields fields = vl->fields();
2610 const int fldIdx = fieldDef->idx();
2611 if ( fldIdx < fields.count() && fldIdx >= 0 )
2612 {
2613 const QString fieldName { fields.at( fldIdx ).name() };
2615 {
2617 if ( property.isActive() )
2618 {
2619 mLabelDataDefinedProperties[mypLabel] = property;
2620 }
2621 }
2623 {
2625 if ( property.isActive() )
2626 {
2627 mEditableDataDefinedProperties[widgetInfo.widget] = property;
2628 }
2629 }
2630 }
2631 }
2632
2633 mypLabel->setToolTip( widgetInfo.toolTip );
2634 if ( columnCount > 1 && !widgetInfo.labelOnTop )
2635 {
2636 mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
2637 }
2638
2639 mypLabel->setBuddy( widgetInfo.widget );
2640
2641 if ( widgetInfo.labelOnTop )
2642 {
2643 widgetColumn = column + 1;
2644 QVBoxLayout *c = new QVBoxLayout();
2645 mypLabel->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
2646 c->layout()->addWidget( mypLabel );
2647 c->layout()->addWidget( widgetInfo.widget );
2648 gbLayout->addLayout( c, row, column, 1, 2 );
2649 column += 2;
2650 }
2651 else
2652 {
2653 widgetColumn = column + 1;
2654 gbLayout->addWidget( mypLabel, row, column++ );
2655 gbLayout->addWidget( widgetInfo.widget, row, column++ );
2656 }
2657 }
2658
2659 const int childHorizontalStretch = childDef->horizontalStretch();
2660 const int existingColumnStretch = gbLayout->columnStretch( widgetColumn );
2661 if ( childHorizontalStretch > 0 && childHorizontalStretch > existingColumnStretch )
2662 {
2663 gbLayout->setColumnStretch( widgetColumn, childHorizontalStretch );
2664 }
2665
2666 if ( childDef->verticalStretch() > 0 && childDef->verticalStretch() > gbLayout->rowStretch( row ) )
2667 {
2668 gbLayout->setRowStretch( row, childDef->verticalStretch() );
2669 }
2670
2671 if ( column >= columnCount * 2 )
2672 {
2673 column = 0;
2674 row += 1;
2675 }
2676
2677 if ( widgetInfo.widget
2678 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
2679 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
2680 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
2681 addSpacer = false;
2682
2683 // we consider all relation editors should be expanding
2684 if ( qobject_cast<QgsAttributeFormRelationEditorWidget *>( widgetInfo.widget ) )
2685 addSpacer = false;
2686 }
2687
2688 if ( addSpacer )
2689 {
2690 QWidget *spacer = new QWidget();
2691 spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
2692 gbLayout->addWidget( spacer, ++row, 0 );
2693 gbLayout->setRowStretch( row, 1 );
2694 }
2695
2696 newWidgetInfo.labelText = QString();
2697 newWidgetInfo.labelOnTop = true;
2698 newWidgetInfo.showLabel = widgetDef->showLabel();
2699 break;
2700 }
2701
2703 {
2704 const QgsAttributeEditorQmlElement *elementDef = static_cast<const QgsAttributeEditorQmlElement *>( widgetDef );
2705
2706 QgsQmlWidgetWrapper *qmlWrapper = new QgsQmlWidgetWrapper( mLayer, nullptr, this );
2707 qmlWrapper->setQmlCode( elementDef->qmlCode() );
2708 context.setAttributeFormMode( mMode );
2709 qmlWrapper->setContext( context );
2710
2711 mWidgets.append( qmlWrapper );
2712
2713 newWidgetInfo.widget = qmlWrapper->widget();
2714 newWidgetInfo.labelText = elementDef->name();
2715 newWidgetInfo.labelOnTop = true;
2716 newWidgetInfo.showLabel = widgetDef->showLabel();
2717 break;
2718 }
2719
2721 {
2722 const QgsAttributeEditorHtmlElement *elementDef = static_cast<const QgsAttributeEditorHtmlElement *>( widgetDef );
2723
2724 QgsHtmlWidgetWrapper *htmlWrapper = new QgsHtmlWidgetWrapper( mLayer, nullptr, this );
2725 context.setAttributeFormMode( mMode );
2726 htmlWrapper->setHtmlCode( elementDef->htmlCode() );
2727 htmlWrapper->reinitWidget();
2728 mWidgets.append( htmlWrapper );
2729
2730 newWidgetInfo.widget = htmlWrapper->widget();
2731 newWidgetInfo.labelText = elementDef->name();
2732 newWidgetInfo.labelOnTop = true;
2733 newWidgetInfo.showLabel = widgetDef->showLabel();
2734 mNeedsGeometry |= htmlWrapper->needsGeometry();
2735 break;
2736 }
2737
2739 {
2740 const QgsAttributeEditorTextElement *elementDef = static_cast<const QgsAttributeEditorTextElement *>( widgetDef );
2741
2742 QgsTextWidgetWrapper *textWrapper = new QgsTextWidgetWrapper( mLayer, nullptr, this );
2743 context.setAttributeFormMode( mMode );
2744 textWrapper->setText( elementDef->text() );
2745 textWrapper->reinitWidget();
2746 mWidgets.append( textWrapper );
2747
2748 newWidgetInfo.widget = textWrapper->widget();
2749 newWidgetInfo.labelText = elementDef->name();
2750 newWidgetInfo.labelOnTop = false;
2751 newWidgetInfo.showLabel = widgetDef->showLabel();
2752 mNeedsGeometry |= textWrapper->needsGeometry();
2753 break;
2754 }
2755
2757 {
2758 const QgsAttributeEditorSpacerElement *elementDef = static_cast<const QgsAttributeEditorSpacerElement *>( widgetDef );
2759 QgsSpacerWidgetWrapper *spacerWrapper = new QgsSpacerWidgetWrapper( mLayer, nullptr, this );
2760 spacerWrapper->setDrawLine( elementDef->drawLine() );
2761 context.setAttributeFormMode( mMode );
2762 mWidgets.append( spacerWrapper );
2763
2764 newWidgetInfo.widget = spacerWrapper->widget();
2765 newWidgetInfo.labelOnTop = false;
2766 newWidgetInfo.showLabel = false;
2767 break;
2768 }
2769
2770 default:
2771 QgsDebugError( QStringLiteral( "Unknown attribute editor widget type encountered..." ) );
2772 break;
2773 }
2774
2775 return newWidgetInfo;
2776}
2777
2778void QgsAttributeForm::createWrappers()
2779{
2780 QList<QWidget *> myWidgets = findChildren<QWidget *>();
2781 const QList<QgsField> fields = mLayer->fields().toList();
2782
2783 const auto constMyWidgets = myWidgets;
2784 for ( QWidget *myWidget : constMyWidgets )
2785 {
2786 // Check the widget's properties for a relation definition
2787 QVariant vRel = myWidget->property( "qgisRelation" );
2788 if ( vRel.isValid() )
2789 {
2791 QgsRelation relation = relMgr->relation( vRel.toString() );
2792 if ( relation.isValid() )
2793 {
2794 QgsRelationWidgetWrapper *rww = new QgsRelationWidgetWrapper( mLayer, relation, myWidget, this );
2795 rww->setConfig( mLayer->editFormConfig().widgetConfig( relation.id() ) );
2796 rww->setContext( mContext );
2797 rww->widget(); // Will initialize the widget
2798 mWidgets.append( rww );
2799 }
2800 }
2801 else
2802 {
2803 const auto constFields = fields;
2804 for ( const QgsField &field : constFields )
2805 {
2806 if ( field.name() == myWidget->objectName() )
2807 {
2808 int idx = mLayer->fields().lookupField( field.name() );
2809
2810 QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( mLayer, idx, myWidget, this, mContext );
2811 mWidgets.append( eww );
2812 }
2813 }
2814 }
2815 }
2816}
2817
2818void QgsAttributeForm::afterWidgetInit()
2819{
2820 bool isFirstEww = true;
2821
2822 const auto constMWidgets = mWidgets;
2823 for ( QgsWidgetWrapper *ww : constMWidgets )
2824 {
2825 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
2826
2827 if ( eww )
2828 {
2829 if ( isFirstEww )
2830 {
2831 setFocusProxy( eww->widget() );
2832 isFirstEww = false;
2833 }
2834
2835 connect( eww, &QgsEditorWidgetWrapper::valuesChanged, this, &QgsAttributeForm::onAttributeChanged, Qt::UniqueConnection );
2836 connect( eww, &QgsEditorWidgetWrapper::constraintStatusChanged, this, &QgsAttributeForm::onConstraintStatusChanged, Qt::UniqueConnection );
2837 }
2838 else
2839 {
2840 QgsRelationWidgetWrapper *relationWidgetWrapper = qobject_cast<QgsRelationWidgetWrapper *>( ww );
2841 if ( relationWidgetWrapper )
2842 {
2843 connect( relationWidgetWrapper, &QgsRelationWidgetWrapper::relatedFeaturesChanged, this, &QgsAttributeForm::onRelatedFeaturesChanged, static_cast<Qt::ConnectionType>( Qt::UniqueConnection | Qt::QueuedConnection ) );
2844 }
2845 }
2846 }
2847}
2848
2849
2850bool QgsAttributeForm::eventFilter( QObject *object, QEvent *e )
2851{
2852 Q_UNUSED( object )
2853
2854 if ( e->type() == QEvent::KeyPress )
2855 {
2856 QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( e );
2857 if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
2858 {
2859 // Re-emit to this form so it will be forwarded to parent
2860 event( e );
2861 return true;
2862 }
2863 }
2864
2865 return false;
2866}
2867
2868void QgsAttributeForm::scanForEqualAttributes( QgsFeatureIterator &fit, QSet<int> &mixedValueFields, QHash<int, QVariant> &fieldSharedValues ) const
2869{
2870 mixedValueFields.clear();
2871 fieldSharedValues.clear();
2872
2873 QgsFeature f;
2874 bool first = true;
2875 while ( fit.nextFeature( f ) )
2876 {
2877 for ( int i = 0; i < mLayer->fields().count(); ++i )
2878 {
2879 if ( mixedValueFields.contains( i ) )
2880 continue;
2881
2882 if ( first )
2883 {
2884 fieldSharedValues[i] = f.attribute( i );
2885 }
2886 else
2887 {
2888 if ( fieldSharedValues.value( i ) != f.attribute( i ) )
2889 {
2890 fieldSharedValues.remove( i );
2891 mixedValueFields.insert( i );
2892 }
2893 }
2894 }
2895 first = false;
2896
2897 if ( mixedValueFields.count() == mLayer->fields().count() )
2898 {
2899 // all attributes are mixed, no need to keep scanning
2900 break;
2901 }
2902 }
2903}
2904
2905
2906void QgsAttributeForm::layerSelectionChanged()
2907{
2908 switch ( mMode )
2909 {
2916 break;
2917
2919 resetMultiEdit( true );
2920 break;
2921 }
2922}
2923
2925{
2926 mIsSettingMultiEditFeatures = true;
2927 mMultiEditFeatureIds = fids;
2928
2929 if ( fids.isEmpty() )
2930 {
2931 // no selected features
2932 QMultiMap<int, QgsAttributeFormEditorWidget *>::const_iterator wIt = mFormEditorWidgets.constBegin();
2933 for ( ; wIt != mFormEditorWidgets.constEnd(); ++wIt )
2934 {
2935 wIt.value()->initialize( QVariant() );
2936 }
2937 mIsSettingMultiEditFeatures = false;
2938 return;
2939 }
2940
2941 QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFids( fids ) );
2942
2943 // Scan through all features to determine which attributes are initially the same
2944 QSet<int> mixedValueFields;
2945 QHash<int, QVariant> fieldSharedValues;
2946 scanForEqualAttributes( fit, mixedValueFields, fieldSharedValues );
2947
2948 // also fetch just first feature
2949 fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFid( *fids.constBegin() ) );
2950 QgsFeature firstFeature;
2951 fit.nextFeature( firstFeature );
2952
2953 // Make this feature the current form feature or the constraints will be evaluated
2954 // on a possibly wrong previously selected/current feature
2955 if ( mCurrentFormFeature.id() != firstFeature.id() )
2956 {
2957 setFeature( firstFeature );
2958 }
2959
2960 const auto constMixedValueFields = mixedValueFields;
2961 for ( int fieldIndex : std::as_const( mixedValueFields ) )
2962 {
2963 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( fieldIndex );
2964 if ( formEditorWidgets.isEmpty() )
2965 continue;
2966
2967 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
2968 QVariantList additionalFieldValues;
2969 for ( const QString &additionalField : additionalFields )
2970 additionalFieldValues << firstFeature.attribute( additionalField );
2971
2972 for ( QgsAttributeFormEditorWidget *w : formEditorWidgets )
2973 w->initialize( firstFeature.attribute( fieldIndex ), true, additionalFieldValues );
2974 }
2975 QHash<int, QVariant>::const_iterator sharedValueIt = fieldSharedValues.constBegin();
2976 for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
2977 {
2978 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( sharedValueIt.key() );
2979 if ( formEditorWidgets.isEmpty() )
2980 continue;
2981
2982 bool mixed = false;
2983 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
2984 for ( const QString &additionalField : additionalFields )
2985 {
2986 int index = mLayer->fields().indexFromName( additionalField );
2987 if ( constMixedValueFields.contains( index ) )
2988 {
2989 // if additional field are mixed, it is considered as mixed
2990 mixed = true;
2991 break;
2992 }
2993 }
2994 QVariantList additionalFieldValues;
2995 if ( mixed )
2996 {
2997 for ( const QString &additionalField : additionalFields )
2998 additionalFieldValues << firstFeature.attribute( additionalField );
2999 for ( QgsAttributeFormEditorWidget *w : formEditorWidgets )
3000 w->initialize( firstFeature.attribute( sharedValueIt.key() ), true, additionalFieldValues );
3001 }
3002 else
3003 {
3004 for ( const QString &additionalField : additionalFields )
3005 {
3006 int index = mLayer->fields().indexFromName( additionalField );
3007 Q_ASSERT( fieldSharedValues.contains( index ) );
3008 additionalFieldValues << fieldSharedValues.value( index );
3009 }
3010 for ( QgsAttributeFormEditorWidget *w : formEditorWidgets )
3011 w->initialize( sharedValueIt.value(), false, additionalFieldValues );
3012 }
3013 }
3014
3015 setMultiEditFeatureIdsRelations( fids );
3016
3017 mIsSettingMultiEditFeatures = false;
3018}
3019
3021{
3022 if ( mOwnsMessageBar )
3023 delete mMessageBar;
3024 mOwnsMessageBar = false;
3025 mMessageBar = messageBar;
3026}
3027
3029{
3031 {
3032 Q_ASSERT( false );
3033 }
3034
3035 QStringList filters;
3036 for ( QgsAttributeFormWidget *widget : mFormWidgets )
3037 {
3038 QString filter = widget->currentFilterExpression();
3039 if ( !filter.isNull() )
3040 filters << '(' + filter + ')';
3041 }
3042
3043 return filters.join( QLatin1String( " AND " ) );
3044}
3045
3047{
3048 mExtraContextScope.reset( extraScope );
3049}
3050
3051void QgsAttributeForm::ContainerInformation::apply( QgsExpressionContext *expressionContext )
3052{
3053 const bool newVisibility = expression.evaluate( expressionContext ).toBool();
3054
3055 if ( expression.isValid() && !expression.hasEvalError() && newVisibility != isVisible )
3056 {
3057 if ( tabWidget )
3058 {
3059 tabWidget->setTabVisible( widget, newVisibility );
3060 }
3061 else
3062 {
3063 widget->setVisible( newVisibility );
3064 }
3065
3066 isVisible = newVisibility;
3067 }
3068
3069 const bool newCollapsedState = collapsedExpression.evaluate( expressionContext ).toBool();
3070
3071 if ( collapsedExpression.isValid() && !collapsedExpression.hasEvalError() && newCollapsedState != isCollapsed )
3072 {
3073 if ( QgsCollapsibleGroupBoxBasic * collapsibleGroupBox { qobject_cast<QgsCollapsibleGroupBoxBasic *>( widget ) } )
3074 {
3075 collapsibleGroupBox->setCollapsed( newCollapsedState );
3076 isCollapsed = newCollapsedState;
3077 }
3078 }
3079}
3080
3081void QgsAttributeForm::updateJoinedFields( const QgsEditorWidgetWrapper &eww )
3082{
3083 if ( !eww.layer()->fields().exists( eww.fieldIdx() ) )
3084 return;
3085
3086 QgsFeature formFeature;
3087 QgsField field = eww.layer()->fields().field( eww.fieldIdx() );
3088 QList<const QgsVectorLayerJoinInfo *> infos = eww.layer()->joinBuffer()->joinsWhereFieldIsId( field );
3089
3090 if ( infos.count() == 0 || !currentFormValuesFeature( formFeature ) )
3091 return;
3092
3093 const QString hint = tr( "No feature joined" );
3094 const auto constInfos = infos;
3095 for ( const QgsVectorLayerJoinInfo *info : constInfos )
3096 {
3097 if ( !info->isDynamicFormEnabled() )
3098 continue;
3099
3100 QgsFeature joinFeature = mLayer->joinBuffer()->joinedFeatureOf( info, formFeature );
3101
3102 mJoinedFeatures[info] = joinFeature;
3103
3104 if ( info->hasSubset() )
3105 {
3106 const QStringList subsetNames = QgsVectorLayerJoinInfo::joinFieldNamesSubset( *info );
3107
3108 const auto constSubsetNames = subsetNames;
3109 for ( const QString &field : constSubsetNames )
3110 {
3111 QString prefixedName = info->prefixedFieldName( field );
3112 QVariant val;
3113 QString hintText = hint;
3114
3115 if ( joinFeature.isValid() )
3116 {
3117 val = joinFeature.attribute( field );
3118 hintText.clear();
3119 }
3120
3121 changeAttribute( prefixedName, val, hintText );
3122 }
3123 }
3124 else
3125 {
3126 const QgsFields joinFields = joinFeature.fields();
3127 for ( const QgsField &field : joinFields )
3128 {
3129 QString prefixedName = info->prefixedFieldName( field );
3130 QVariant val;
3131 QString hintText = hint;
3132
3133 if ( joinFeature.isValid() )
3134 {
3135 val = joinFeature.attribute( field.name() );
3136 hintText.clear();
3137 }
3138
3139 changeAttribute( prefixedName, val, hintText );
3140 }
3141 }
3142 }
3143}
3144
3145bool QgsAttributeForm::fieldIsEditable( int fieldIndex ) const
3146{
3147 return QgsVectorLayerUtils::fieldIsEditable( mLayer, fieldIndex, mFeature );
3148}
3149
3150void QgsAttributeForm::updateFieldDependencies()
3151{
3152 mDefaultValueDependencies.clear();
3153 mVirtualFieldsDependencies.clear();
3154 mRelatedLayerFieldsDependencies.clear();
3155 mParentDependencies.clear();
3156
3157 //create defaultValueDependencies
3158 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
3159 {
3160 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
3161 if ( !eww )
3162 continue;
3163
3164 updateFieldDependenciesParent( eww );
3165 updateFieldDependenciesDefaultValue( eww );
3166 updateFieldDependenciesVirtualFields( eww );
3167 updateRelatedLayerFieldsDependencies( eww );
3168 }
3169}
3170
3171void QgsAttributeForm::updateFieldDependenciesDefaultValue( QgsEditorWidgetWrapper *eww )
3172{
3174
3175 if ( exp.needsGeometry() )
3176 mNeedsGeometry = true;
3177
3178 //if a function requires all attributes, it should have the dependency of every field change
3179 if ( exp.referencedColumns().contains( QgsFeatureRequest::ALL_ATTRIBUTES ) )
3180 {
3181 const QList<int> allAttributeIds( mLayer->fields().allAttributesList() );
3182
3183 for ( const int id : allAttributeIds )
3184 {
3185 mDefaultValueDependencies.insertMulti( id, eww );
3186 }
3187 }
3188 else
3189 {
3190 //otherwise just enter for the field depending on
3191 const QSet<QString> referencedColumns = exp.referencedColumns();
3192 for ( const QString &referencedColumn : referencedColumns )
3193 {
3194 mDefaultValueDependencies.insertMulti( mLayer->fields().lookupField( referencedColumn ), eww );
3195 }
3196 }
3197}
3198
3199void QgsAttributeForm::updateFieldDependenciesVirtualFields( QgsEditorWidgetWrapper *eww )
3200{
3201 QString expressionField = eww->layer()->expressionField( eww->fieldIdx() );
3202 if ( expressionField.isEmpty() )
3203 return;
3204
3205 QgsExpression exp( expressionField );
3206
3207 if ( exp.needsGeometry() )
3208 mNeedsGeometry = true;
3209
3210 //if a function requires all attributes, it should have the dependency of every field change
3211 if ( exp.referencedColumns().contains( QgsFeatureRequest::ALL_ATTRIBUTES ) )
3212 {
3213 const QList<int> allAttributeIds( mLayer->fields().allAttributesList() );
3214
3215 for ( const int id : allAttributeIds )
3216 {
3217 mVirtualFieldsDependencies.insertMulti( id, eww );
3218 }
3219 }
3220 else
3221 {
3222 //otherwise just enter for the field depending on
3223 const QSet<QString> referencedColumns = exp.referencedColumns();
3224 for ( const QString &referencedColumn : referencedColumns )
3225 {
3226 mVirtualFieldsDependencies.insertMulti( mLayer->fields().lookupField( referencedColumn ), eww );
3227 }
3228 }
3229}
3230
3231void QgsAttributeForm::updateRelatedLayerFieldsDependencies( QgsEditorWidgetWrapper *eww )
3232{
3233 if ( eww )
3234 {
3235 QString expressionField = eww->layer()->expressionField( eww->fieldIdx() );
3236 if ( expressionField.contains( QStringLiteral( "relation_aggregate" ) )
3237 || expressionField.contains( QStringLiteral( "get_features" ) ) )
3238 mRelatedLayerFieldsDependencies.insert( eww );
3239 }
3240 else
3241 {
3242 mRelatedLayerFieldsDependencies.clear();
3243 //create defaultValueDependencies
3244 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
3245 {
3246 QgsEditorWidgetWrapper *editorWidgetWrapper = qobject_cast<QgsEditorWidgetWrapper *>( ww );
3247 if ( !editorWidgetWrapper )
3248 continue;
3249
3250 updateRelatedLayerFieldsDependencies( editorWidgetWrapper );
3251 }
3252 }
3253}
3254
3255void QgsAttributeForm::updateFieldDependenciesParent( QgsEditorWidgetWrapper *eww )
3256{
3257 if ( eww && !eww->field().defaultValueDefinition().expression().isEmpty() )
3258 {
3259 const QgsExpression expression( eww->field().defaultValueDefinition().expression() );
3260 const QSet<QString> referencedVariablesAndFunctions = expression.referencedVariables() + expression.referencedFunctions();
3261 for ( const QString &referenced : referencedVariablesAndFunctions )
3262 {
3263 if ( referenced.startsWith( QLatin1String( "current_parent" ) ) )
3264 {
3265 mParentDependencies.insert( eww );
3266 break;
3267 }
3268 }
3269 }
3270}
3271
3272void QgsAttributeForm::setMultiEditFeatureIdsRelations( const QgsFeatureIds &fids )
3273{
3274 for ( QgsAttributeFormWidget *formWidget : mFormWidgets )
3275 {
3276 QgsAttributeFormRelationEditorWidget *relationEditorWidget = dynamic_cast<QgsAttributeFormRelationEditorWidget *>( formWidget );
3277 if ( !relationEditorWidget )
3278 continue;
3279
3280 relationEditorWidget->setMultiEditFeatureIds( fids );
3281 }
3282}
3283
3284void QgsAttributeForm::updateIcon( QgsEditorWidgetWrapper *eww )
3285{
3286 if ( !eww->widget() || !mIconMap[eww->widget()] )
3287 return;
3288
3289 // no icon by default
3290 mIconMap[eww->widget()]->hide();
3291
3292 if ( !eww->widget()->isEnabled() && mLayer->isEditable() )
3293 {
3294 if ( mLayer->fields().fieldOrigin( eww->fieldIdx() ) == Qgis::FieldOrigin::Join )
3295 {
3296 int srcFieldIndex;
3297 const QgsVectorLayerJoinInfo *info = mLayer->joinBuffer()->joinForFieldIndex( eww->fieldIdx(), mLayer->fields(), srcFieldIndex );
3298
3299 if ( !info )
3300 return;
3301
3302 if ( !info->isEditable() )
3303 {
3304 const QString file = QStringLiteral( "/mIconJoinNotEditable.svg" );
3305 const QString tooltip = tr( "Join settings do not allow editing" );
3306 reloadIcon( file, tooltip, mIconMap[eww->widget()] );
3307 }
3308 else if ( mMode == QgsAttributeEditorContext::AddFeatureMode && !info->hasUpsertOnEdit() )
3309 {
3310 const QString file = QStringLiteral( "mIconJoinHasNotUpsertOnEdit.svg" );
3311 const QString tooltip = tr( "Join settings do not allow upsert on edit" );
3312 reloadIcon( file, tooltip, mIconMap[eww->widget()] );
3313 }
3314 else if ( !info->joinLayer()->isEditable() )
3315 {
3316 const QString file = QStringLiteral( "/mIconJoinedLayerNotEditable.svg" );
3317 const QString tooltip = tr( "Joined layer is not toggled editable" );
3318 reloadIcon( file, tooltip, mIconMap[eww->widget()] );
3319 }
3320 }
3321 }
3322}
3323
3324void QgsAttributeForm::reloadIcon( const QString &file, const QString &tooltip, QSvgWidget *sw )
3325{
3326 sw->load( QgsApplication::iconPath( file ) );
3327 sw->setToolTip( tooltip );
3328 sw->show();
3329}
@ 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:1681
@ 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:6643
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6642
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