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