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