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