QGIS API Documentation 3.99.0-Master (d270888f95f)
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 = 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 );
1702 mMessageBar->pushItem( mConstraintsFailMessageBarItem );
1703 }
1704 else if ( mValidConstraints && mConstraintsFailMessageBarItem )
1705 {
1706 mMessageBar->popWidget( mConstraintsFailMessageBarItem );
1707 mConstraintsFailMessageBarItem = nullptr;
1708 }
1709 }
1710 else if ( mConstraintsFailMessageBarItem )
1711 {
1712 mMessageBar->popWidget( mConstraintsFailMessageBarItem );
1713 mConstraintsFailMessageBarItem = nullptr;
1714 }
1715
1716 isEditable = isEditable & mValidConstraints;
1717 }
1718 }
1719
1720 // change OK button status
1721 QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
1722 if ( okButton )
1723 {
1724 okButton->setEnabled( isEditable );
1725 }
1726}
1727
1728void QgsAttributeForm::init()
1729{
1730 QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
1731
1732 // Cleanup of any previously shown widget, we start from scratch
1733 QWidget *formWidget = nullptr;
1734 mNeedsGeometry = false;
1735
1736 bool buttonBoxVisible = true;
1737 // Cleanup button box but preserve visibility
1738 if ( mButtonBox )
1739 {
1740 buttonBoxVisible = mButtonBox->isVisible();
1741 delete mButtonBox;
1742 mButtonBox = nullptr;
1743 }
1744
1745 if ( mSearchButtonBox )
1746 {
1747 delete mSearchButtonBox;
1748 mSearchButtonBox = nullptr;
1749 }
1750
1751 qDeleteAll( mWidgets );
1752 mWidgets.clear();
1753
1754 while ( QWidget *w = this->findChild<QWidget *>() )
1755 {
1756 delete w;
1757 }
1758 delete layout();
1759
1760 QVBoxLayout *vl = new QVBoxLayout();
1761 vl->setContentsMargins( 0, 0, 0, 0 );
1762 mMessageBar = new QgsMessageBar( this );
1763 mMessageBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1764 vl->addWidget( mMessageBar );
1765
1766 setLayout( vl );
1767
1768 // Get a layout
1769 QGridLayout *layout = new QGridLayout();
1770 QWidget *container = new QWidget();
1771 container->setLayout( layout );
1772 vl->addWidget( container );
1773
1774 mFormEditorWidgets.clear();
1775 mFormWidgets.clear();
1776
1777 // a bar to warn the user with non-blocking messages
1778 setContentsMargins( 0, 0, 0, 0 );
1779
1780 // Try to load Ui-File for layout
1781 if ( mContext.allowCustomUi() && mLayer->editFormConfig().layout() == Qgis::AttributeFormLayout::UiFile && !mLayer->editFormConfig().uiForm().isEmpty() )
1782 {
1783 QgsDebugMsgLevel( u"loading form: %1"_s.arg( mLayer->editFormConfig().uiForm() ), 2 );
1784 const QString path = mLayer->editFormConfig().uiForm();
1786 if ( file && file->open( QFile::ReadOnly ) )
1787 {
1788 QUiLoader loader;
1789
1790 QFileInfo fi( file->fileName() );
1791 loader.setWorkingDirectory( fi.dir() );
1792 formWidget = loader.load( file, this );
1793 if ( formWidget )
1794 {
1795 formWidget->setWindowFlags( Qt::Widget );
1796 layout->addWidget( formWidget );
1797 formWidget->show();
1798 file->close();
1799 mButtonBox = findChild<QDialogButtonBox *>();
1800 createWrappers();
1801
1802 formWidget->installEventFilter( this );
1803 }
1804 }
1805 }
1806
1807 QgsTabWidget *tabWidget = nullptr;
1808
1809 // Tab layout
1810 if ( !formWidget && mLayer->editFormConfig().layout() == Qgis::AttributeFormLayout::DragAndDrop )
1811 {
1812 int row = 0;
1813 int column = 0;
1814 int columnCount = 1;
1815 bool hasRootFields = false;
1816 bool addSpacer = true;
1817
1818 const QList<QgsAttributeEditorElement *> tabs = mLayer->editFormConfig().tabs();
1819
1820 for ( QgsAttributeEditorElement *widgDef : tabs )
1821 {
1822 if ( widgDef->type() == Qgis::AttributeEditorType::Container )
1823 {
1824 QgsAttributeEditorContainer *containerDef = dynamic_cast<QgsAttributeEditorContainer *>( widgDef );
1825 if ( !containerDef )
1826 continue;
1827
1828 switch ( containerDef->type() )
1829 {
1831 {
1832 tabWidget = nullptr;
1833 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1834 if ( widgetInfo.labelStyle.overrideColor )
1835 {
1836 if ( widgetInfo.labelStyle.color.isValid() )
1837 {
1838 widgetInfo.widget->setStyleSheet( u"QGroupBox::title { color: %1; }"_s.arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1839 }
1840 }
1841 if ( widgetInfo.labelStyle.overrideFont )
1842 {
1843 widgetInfo.widget->setFont( widgetInfo.labelStyle.font );
1844 }
1845
1846 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1847 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1848 {
1849 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1850 }
1851 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1852 {
1853 layout->setRowStretch( row, widgDef->verticalStretch() );
1854 addSpacer = false;
1855 }
1856
1857 if ( containerDef->visibilityExpression().enabled() || containerDef->collapsedExpression().enabled() )
1858 {
1859 registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().enabled() ? containerDef->visibilityExpression().data() : QgsExpression(), containerDef->collapsed(), containerDef->collapsedExpression().enabled() ? containerDef->collapsedExpression().data() : QgsExpression() ) );
1860 }
1861 column += 2;
1862 break;
1863 }
1864
1866 {
1867 tabWidget = nullptr;
1868 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1869 layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1870 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1871 {
1872 layout->setRowStretch( row, widgDef->verticalStretch() );
1873 addSpacer = false;
1874 }
1875 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1876 {
1877 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1878 }
1879
1880 if ( containerDef->visibilityExpression().enabled() || containerDef->collapsedExpression().enabled() )
1881 {
1882 registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().enabled() ? containerDef->visibilityExpression().data() : QgsExpression(), containerDef->collapsed(), containerDef->collapsedExpression().enabled() ? containerDef->collapsedExpression().data() : QgsExpression() ) );
1883 }
1884 column += 2;
1885 break;
1886 }
1887
1889 {
1890 if ( !tabWidget )
1891 {
1892 tabWidget = new QgsTabWidget();
1893 layout->addWidget( tabWidget, row, column, 1, 2 );
1894 column += 2;
1895 }
1896
1897 QWidget *tabPage = new QWidget( tabWidget );
1898
1899 tabWidget->addTab( tabPage, widgDef->name() );
1900 tabWidget->setTabStyle( tabWidget->tabBar()->count() - 1, widgDef->labelStyle() );
1901
1902 if ( containerDef->visibilityExpression().enabled() )
1903 {
1904 registerContainerInformation( new ContainerInformation( tabWidget, tabPage, containerDef->visibilityExpression().data() ) );
1905 }
1906 QGridLayout *tabPageLayout = new QGridLayout();
1907 tabPage->setLayout( tabPageLayout );
1908
1909 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, tabPage, mLayer, mContext );
1910 tabPageLayout->addWidget( widgetInfo.widget );
1911 break;
1912 }
1913 }
1914 }
1915 else if ( widgDef->type() == Qgis::AttributeEditorType::Relation )
1916 {
1917 hasRootFields = true;
1918 tabWidget = nullptr;
1919 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1920 QgsCollapsibleGroupBox *collapsibleGroupBox = new QgsCollapsibleGroupBox();
1921
1922 if ( widgetInfo.showLabel )
1923 {
1924 if ( widgetInfo.labelStyle.overrideColor && widgetInfo.labelStyle.color.isValid() )
1925 {
1926 collapsibleGroupBox->setStyleSheet( u"QGroupBox::title { color: %1; }"_s.arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1927 }
1928
1929 if ( widgetInfo.labelStyle.overrideFont )
1930 {
1931 collapsibleGroupBox->setFont( widgetInfo.labelStyle.font );
1932 }
1933
1934 collapsibleGroupBox->setTitle( widgetInfo.labelText );
1935 }
1936
1937 QVBoxLayout *collapsibleGroupBoxLayout = new QVBoxLayout();
1938 collapsibleGroupBoxLayout->addWidget( widgetInfo.widget );
1939 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
1940
1941 QVBoxLayout *c = new QVBoxLayout();
1942 c->addWidget( collapsibleGroupBox );
1943 layout->addLayout( c, row, column, 1, 2 );
1944
1945 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1946 layout->setRowStretch( row, widgDef->verticalStretch() );
1947 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
1948 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
1949
1950 column += 2;
1951
1952 // we consider all relation editors should be expanding
1953 addSpacer = false;
1954 }
1955 else
1956 {
1957 hasRootFields = true;
1958 tabWidget = nullptr;
1959 WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1960 QLabel *label = new QLabel( widgetInfo.labelText );
1961
1962 if ( widgetInfo.labelStyle.overrideColor )
1963 {
1964 if ( widgetInfo.labelStyle.color.isValid() )
1965 {
1966 label->setStyleSheet( u"QLabel { color: %1; }"_s.arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
1967 }
1968 }
1969
1970 if ( widgetInfo.labelStyle.overrideFont )
1971 {
1972 label->setFont( widgetInfo.labelStyle.font );
1973 }
1974
1975 label->setToolTip( widgetInfo.toolTip );
1976 if ( columnCount > 1 && !widgetInfo.labelOnTop )
1977 {
1978 label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1979 }
1980
1981 label->setBuddy( widgetInfo.widget );
1982
1983 // If at least one expanding widget is present do not add a spacer
1984 if ( widgetInfo.widget
1985 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
1986 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
1987 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
1988 addSpacer = false;
1989
1990 if ( !widgetInfo.showLabel )
1991 {
1992 QVBoxLayout *c = new QVBoxLayout();
1993 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1994 c->addWidget( widgetInfo.widget );
1995 layout->addLayout( c, row, column, 1, 2 );
1996
1997 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
1998 {
1999 layout->setRowStretch( row, widgDef->verticalStretch() );
2000 addSpacer = false;
2001 }
2002 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
2003 {
2004 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
2005 }
2006
2007 column += 2;
2008 }
2009 else if ( widgetInfo.labelOnTop )
2010 {
2011 QVBoxLayout *c = new QVBoxLayout();
2012 label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
2013 c->addWidget( label );
2014 c->addWidget( widgetInfo.widget );
2015 layout->addLayout( c, row, column, 1, 2 );
2016
2017 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
2018 {
2019 layout->setRowStretch( row, widgDef->verticalStretch() );
2020 addSpacer = false;
2021 }
2022 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( column + 1 ) )
2023 {
2024 layout->setColumnStretch( column + 1, widgDef->horizontalStretch() );
2025 }
2026
2027 column += 2;
2028 }
2029 else
2030 {
2031 const int widgetColumn = column + 1;
2032 layout->addWidget( label, row, column++ );
2033 layout->addWidget( widgetInfo.widget, row, column++ );
2034
2035 if ( widgDef->verticalStretch() > 0 && widgDef->verticalStretch() > layout->rowStretch( row ) )
2036 {
2037 layout->setRowStretch( row, widgDef->verticalStretch() );
2038 addSpacer = false;
2039 }
2040 if ( widgDef->horizontalStretch() > 0 && widgDef->horizontalStretch() > layout->columnStretch( widgetColumn ) )
2041 {
2042 layout->setColumnStretch( widgetColumn, widgDef->horizontalStretch() );
2043 }
2044 }
2045
2046 // Alias DD overrides
2047 if ( widgDef->type() == Qgis::AttributeEditorType::Field )
2048 {
2049 const QgsAttributeEditorField *fieldElement { static_cast<QgsAttributeEditorField *>( widgDef ) };
2050 const int fieldIdx = fieldElement->idx();
2051 if ( fieldIdx >= 0 && fieldIdx < mLayer->fields().count() )
2052 {
2053 const QString fieldName { mLayer->fields().at( fieldIdx ).name() };
2054 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Alias ) )
2055 {
2056 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Alias ) };
2057 if ( property.isActive() )
2058 {
2059 mLabelDataDefinedProperties[label] = property;
2060 }
2061 }
2062 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Editable ) )
2063 {
2064 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Editable ) };
2065 if ( property.isActive() )
2066 {
2067 mEditableDataDefinedProperties[widgetInfo.widget] = property;
2068 }
2069 }
2070 }
2071 }
2072 }
2073
2074 if ( column >= columnCount * 2 )
2075 {
2076 column = 0;
2077 row += 1;
2078 }
2079 }
2080
2081 if ( hasRootFields && addSpacer )
2082 {
2083 QSpacerItem *spacerItem = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
2084 layout->addItem( spacerItem, row, 0 );
2085 layout->setRowStretch( row, 1 );
2086 }
2087
2088 formWidget = container;
2089 }
2090
2091 // Autogenerate Layout
2092 // If there is still no layout loaded (defined as autogenerate or other methods failed)
2093 mIconMap.clear();
2094
2095 if ( !formWidget )
2096 {
2097 formWidget = new QWidget( this );
2098 QGridLayout *gridLayout = new QGridLayout( formWidget );
2099 formWidget->setLayout( gridLayout );
2100
2101 if ( mContext.formMode() != QgsAttributeEditorContext::Embed )
2102 {
2103 // put the form into a scroll area to nicely handle cases with lots of attributes
2104 QgsScrollArea *scrollArea = new QgsScrollArea( this );
2105 scrollArea->setWidget( formWidget );
2106 scrollArea->setWidgetResizable( true );
2107 scrollArea->setFrameShape( QFrame::NoFrame );
2108 scrollArea->setFrameShadow( QFrame::Plain );
2109 scrollArea->setFocusProxy( this );
2110 layout->addWidget( scrollArea );
2111 }
2112 else
2113 {
2114 layout->addWidget( formWidget );
2115 }
2116
2117 int row = 0;
2118
2119 const QgsFields fields = mLayer->fields();
2120
2121 for ( const QgsField &field : fields )
2122 {
2123 int idx = fields.lookupField( field.name() );
2124 if ( idx < 0 )
2125 continue;
2126
2127 //show attribute alias if available
2128 QString fieldName = mLayer->attributeDisplayName( idx );
2129 QString labelText = fieldName;
2130 labelText.replace( '&', "&&"_L1 ); // need to escape '&' or they'll be replace by _ in the label text
2131
2132 const QgsEditorWidgetSetup widgetSetup = QgsGui::editorWidgetRegistry()->findBest( mLayer, field.name() );
2133
2134 if ( widgetSetup.type() == "Hidden"_L1 )
2135 continue;
2136
2137 bool labelOnTop = mLayer->editFormConfig().labelOnTop( idx );
2138
2139 // This will also create the widget
2140 QLabel *label = new QLabel( labelText );
2141 label->setToolTip( QgsFieldModel::fieldToolTipExtended( field, mLayer ) );
2142 QSvgWidget *i = new QSvgWidget();
2143 i->setFixedSize( 18, 18 );
2144
2145 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Alias ) )
2146 {
2147 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Alias ) };
2148 if ( property.isActive() )
2149 {
2150 mLabelDataDefinedProperties[label] = property;
2151 }
2152 }
2153
2154 QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( widgetSetup.type(), mLayer, idx, widgetSetup.config(), nullptr, this, mContext );
2155
2156 QWidget *w = nullptr;
2157 if ( eww )
2158 {
2159 QgsAttributeFormEditorWidget *formWidget = new QgsAttributeFormEditorWidget( eww, widgetSetup.type(), this );
2160 w = formWidget;
2161 mFormEditorWidgets.insert( idx, formWidget );
2162 mFormWidgets.append( formWidget );
2163
2164 const Qgis::AttributeFormReuseLastValuePolicy reusePolicy = mLayer->editFormConfig().reuseLastValuePolicy( idx );
2166 {
2167 bool remember = reusePolicy == Qgis::AttributeFormReuseLastValuePolicy::AllowedDefaultOn;
2168 const QVariant rememberLastUsedValuesVariant = mLayer->property( "AttributeFormRememberLastUsedValues" );
2169 QMap<int, bool> rememberLastUsedValues = rememberLastUsedValuesVariant.value<QMap<int, bool>>();
2170 if ( rememberLastUsedValues.contains( idx ) )
2171 {
2172 remember = rememberLastUsedValues[idx];
2173 }
2174
2175 formWidget->setRememberLastValue( remember );
2176 connect( formWidget, &QgsAttributeFormEditorWidget::rememberLastValueChanged, this, [this]( int idx, bool remember ) {
2177 const QVariant rememberLastUsedValuesVariant = mLayer->property( "AttributeFormRememberLastUsedValues" );
2178 QMap<int, bool> rememberLastUsedValues = rememberLastUsedValuesVariant.value<QMap<int, bool>>();
2179 rememberLastUsedValues[idx] = remember;
2180 mLayer->setProperty( "AttributeFormRememberLastUsedValues", QVariant::fromValue<QMap<int, bool>>( rememberLastUsedValues ) );
2181 } );
2182 }
2183
2184 formWidget->createSearchWidgetWrappers( mContext );
2185
2186 label->setBuddy( eww->widget() );
2187
2188 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Editable ) )
2189 {
2190 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Editable ) };
2191 if ( property.isActive() )
2192 {
2193 mEditableDataDefinedProperties[formWidget] = property;
2194 }
2195 }
2196 }
2197 else
2198 {
2199 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() ) ) );
2200 }
2201
2202
2203 if ( w )
2204 w->setObjectName( field.name() );
2205
2206 if ( eww )
2207 {
2208 mWidgets.append( eww );
2209 mIconMap[eww->widget()] = i;
2210 }
2211
2212 if ( labelOnTop )
2213 {
2214 gridLayout->addWidget( label, row++, 0, 1, 2 );
2215 gridLayout->addWidget( w, row++, 0, 1, 2 );
2216 gridLayout->addWidget( i, row++, 0, 1, 2 );
2217 }
2218 else
2219 {
2220 gridLayout->addWidget( label, row, 0 );
2221 gridLayout->addWidget( w, row, 1 );
2222 gridLayout->addWidget( i, row++, 2 );
2223 }
2224 }
2225
2226 const QList<QgsRelation> relations = QgsProject::instance()->relationManager()->referencedRelations( mLayer );
2227 for ( const QgsRelation &rel : relations )
2228 {
2229 QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( u"relation_editor"_s, rel, mContext );
2230
2231 QgsAttributeFormRelationEditorWidget *formWidget = new QgsAttributeFormRelationEditorWidget( rww, this );
2232 formWidget->createSearchWidgetWrappers( mContext );
2233
2234 QgsCollapsibleGroupBox *collapsibleGroupBox = new QgsCollapsibleGroupBox( rel.name() );
2235 QVBoxLayout *collapsibleGroupBoxLayout = new QVBoxLayout();
2236 collapsibleGroupBoxLayout->addWidget( formWidget );
2237 collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
2238
2239 gridLayout->addWidget( collapsibleGroupBox, row++, 0, 1, 2 );
2240
2241 mWidgets.append( rww );
2242 mFormWidgets.append( formWidget );
2243 }
2244
2245 if ( QgsProject::instance()->relationManager()->referencedRelations( mLayer ).isEmpty() )
2246 {
2247 QSpacerItem *spacerItem = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
2248 gridLayout->addItem( spacerItem, row, 0 );
2249 gridLayout->setRowStretch( row, 1 );
2250 row++;
2251 }
2252 }
2253
2254 // Prepare value dependencies
2255 updateFieldDependencies();
2256
2257 if ( !mButtonBox )
2258 {
2259 mButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
2260 mButtonBox->setObjectName( u"buttonBox"_s );
2261 layout->addWidget( mButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2262 }
2263 mButtonBox->setVisible( buttonBoxVisible );
2264
2265 if ( !mSearchButtonBox )
2266 {
2267 mSearchButtonBox = new QWidget();
2268 QHBoxLayout *boxLayout = new QHBoxLayout();
2269 boxLayout->setContentsMargins( 0, 0, 0, 0 );
2270 mSearchButtonBox->setLayout( boxLayout );
2271 mSearchButtonBox->setObjectName( u"searchButtonBox"_s );
2272
2273 QPushButton *clearButton = new QPushButton( tr( "&Reset Form" ), mSearchButtonBox );
2274 connect( clearButton, &QPushButton::clicked, this, &QgsAttributeForm::resetSearch );
2275 boxLayout->addWidget( clearButton );
2276 boxLayout->addStretch( 1 );
2277
2278 QPushButton *flashButton = new QPushButton();
2279 flashButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2280 flashButton->setText( tr( "&Flash Features" ) );
2281 connect( flashButton, &QToolButton::clicked, this, &QgsAttributeForm::searchFlash );
2282 boxLayout->addWidget( flashButton );
2283
2284 QPushButton *openAttributeTableButton = new QPushButton();
2285 openAttributeTableButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2286 openAttributeTableButton->setText( tr( "Show in &Table" ) );
2287 openAttributeTableButton->setToolTip( tr( "Open the attribute table editor with the filtered features" ) );
2288 connect( openAttributeTableButton, &QToolButton::clicked, this, [this] {
2289 emit openFilteredFeaturesAttributeTable( createFilterExpression() );
2290 } );
2291 boxLayout->addWidget( openAttributeTableButton );
2292
2293 QPushButton *zoomButton = new QPushButton();
2294 zoomButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2295 zoomButton->setText( tr( "&Zoom to Features" ) );
2296 connect( zoomButton, &QToolButton::clicked, this, &QgsAttributeForm::searchZoomTo );
2297 boxLayout->addWidget( zoomButton );
2298
2299 QToolButton *selectButton = new QToolButton();
2300 selectButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2301 selectButton->setText( tr( "&Select Features" ) );
2302 selectButton->setIcon( QgsApplication::getThemeIcon( u"/mIconFormSelect.svg"_s ) );
2303 selectButton->setPopupMode( QToolButton::MenuButtonPopup );
2304 selectButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
2305 connect( selectButton, &QToolButton::clicked, this, &QgsAttributeForm::searchSetSelection );
2306 QMenu *selectMenu = new QMenu( selectButton );
2307 QAction *selectAction = new QAction( tr( "Select Features" ), selectMenu );
2308 selectAction->setIcon( QgsApplication::getThemeIcon( u"/mIconFormSelect.svg"_s ) );
2309 connect( selectAction, &QAction::triggered, this, &QgsAttributeForm::searchSetSelection );
2310 selectMenu->addAction( selectAction );
2311 QAction *addSelectAction = new QAction( tr( "Add to Current Selection" ), selectMenu );
2312 addSelectAction->setIcon( QgsApplication::getThemeIcon( u"/mIconSelectAdd.svg"_s ) );
2313 connect( addSelectAction, &QAction::triggered, this, &QgsAttributeForm::searchAddToSelection );
2314 selectMenu->addAction( addSelectAction );
2315 QAction *deselectAction = new QAction( tr( "Remove from Current Selection" ), selectMenu );
2316 deselectAction->setIcon( QgsApplication::getThemeIcon( u"/mIconSelectRemove.svg"_s ) );
2317 connect( deselectAction, &QAction::triggered, this, &QgsAttributeForm::searchRemoveFromSelection );
2318 selectMenu->addAction( deselectAction );
2319 QAction *filterSelectAction = new QAction( tr( "Filter Current Selection" ), selectMenu );
2320 filterSelectAction->setIcon( QgsApplication::getThemeIcon( u"/mIconSelectIntersect.svg"_s ) );
2321 connect( filterSelectAction, &QAction::triggered, this, &QgsAttributeForm::searchIntersectSelection );
2322 selectMenu->addAction( filterSelectAction );
2323 selectButton->setMenu( selectMenu );
2324 boxLayout->addWidget( selectButton );
2325
2326 if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
2327 {
2328 QToolButton *filterButton = new QToolButton();
2329 filterButton->setText( tr( "Filter Features" ) );
2330 filterButton->setPopupMode( QToolButton::MenuButtonPopup );
2331 filterButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
2332 connect( filterButton, &QToolButton::clicked, this, &QgsAttributeForm::filterTriggered );
2333 QMenu *filterMenu = new QMenu( filterButton );
2334 QAction *filterAndAction = new QAction( tr( "Filter Within (\"AND\")" ), filterMenu );
2335 connect( filterAndAction, &QAction::triggered, this, &QgsAttributeForm::filterAndTriggered );
2336 filterMenu->addAction( filterAndAction );
2337 QAction *filterOrAction = new QAction( tr( "Extend Filter (\"OR\")" ), filterMenu );
2338 connect( filterOrAction, &QAction::triggered, this, &QgsAttributeForm::filterOrTriggered );
2339 filterMenu->addAction( filterOrAction );
2340 filterButton->setMenu( filterMenu );
2341 boxLayout->addWidget( filterButton );
2342 }
2343 else
2344 {
2345 QPushButton *closeButton = new QPushButton( tr( "Close" ), mSearchButtonBox );
2346 connect( closeButton, &QPushButton::clicked, this, &QgsAttributeForm::closed );
2347 closeButton->setShortcut( Qt::Key_Escape );
2348 boxLayout->addWidget( closeButton );
2349 }
2350
2351 layout->addWidget( mSearchButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
2352 }
2353 mSearchButtonBox->setVisible( mMode == QgsAttributeEditorContext::SearchMode );
2354
2355 afterWidgetInit();
2356
2357 connect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsAttributeForm::save );
2358 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsAttributeForm::resetValues );
2359
2360 connect( mLayer, &QgsVectorLayer::editingStarted, this, &QgsAttributeForm::synchronizeState );
2361 connect( mLayer, &QgsVectorLayer::editingStopped, this, &QgsAttributeForm::synchronizeState );
2362
2363 // This triggers a refresh of the form widget and gives a chance to re-format the
2364 // value to those widgets that have a different representation when in edit mode
2367
2368
2369 const auto constMInterfaces = mInterfaces;
2370 for ( QgsAttributeFormInterface *iface : constMInterfaces )
2371 {
2372 iface->initForm();
2373 }
2374
2375 if ( mContext.formMode() == QgsAttributeEditorContext::Embed || mMode == QgsAttributeEditorContext::SearchMode )
2376 {
2377 hideButtonBox();
2378 }
2379
2380 QApplication::restoreOverrideCursor();
2381}
2382
2383void QgsAttributeForm::cleanPython()
2384{
2385 if ( !mPyFormVarName.isNull() )
2386 {
2387 QString expr = u"if '%1' in locals(): del %1\n"_s.arg( mPyFormVarName );
2388 QgsPythonRunner::run( expr );
2389 }
2390}
2391
2392void QgsAttributeForm::initPython()
2393{
2394 cleanPython();
2395
2396 // Init Python, if init function is not empty and the combo indicates
2397 // the source for the function code
2398 if ( !mLayer->editFormConfig().initFunction().isEmpty()
2399 && mLayer->editFormConfig().initCodeSource() != Qgis::AttributeFormPythonInitCodeSource::NoSource )
2400 {
2402 if ( !allowed )
2403 {
2404 mMessageBar->pushMessage(
2405 tr( "Security warning" ),
2406 tr( "The attribute form contains an embedded script which has been denied execution." ),
2408 );
2409 return;
2410 }
2411
2412 QString initFunction = mLayer->editFormConfig().initFunction();
2413 QString initFilePath = mLayer->editFormConfig().initFilePath();
2414 QString initCode;
2415
2416 switch ( mLayer->editFormConfig().initCodeSource() )
2417 {
2419 if ( !initFilePath.isEmpty() )
2420 {
2421 QFile *inputFile = QgsApplication::networkContentFetcherRegistry()->localFile( initFilePath );
2422
2423 if ( inputFile && inputFile->open( QFile::ReadOnly ) )
2424 {
2425 // Read it into a string
2426 QTextStream inf( inputFile );
2427 initCode = inf.readAll();
2428 inputFile->close();
2429 }
2430 else // The file couldn't be opened
2431 {
2432 QgsLogger::warning( u"The external python file path %1 could not be opened!"_s.arg( initFilePath ) );
2433 }
2434 }
2435 else
2436 {
2437 QgsLogger::warning( u"The external python file path is empty!"_s );
2438 }
2439 break;
2440
2442 initCode = mLayer->editFormConfig().initCode();
2443 if ( initCode.isEmpty() )
2444 {
2445 QgsLogger::warning( u"The python code provided in the dialog is empty!"_s );
2446 }
2447 break;
2448
2451 // Nothing to do: the function code should be already in the environment
2452 break;
2453 }
2454
2455 // If we have a function code, run it
2456 if ( !initCode.isEmpty() )
2457 {
2459 {
2460 QgsPythonRunner::run( initCode );
2461 }
2462 else
2463 {
2464 mMessageBar->pushMessage( QString(), tr( "Python macro could not be run due to missing permissions." ), Qgis::MessageLevel::Warning );
2465 }
2466 }
2467
2468 QgsPythonRunner::run( u"import inspect"_s );
2469 QString numArgs;
2470
2471 // Check for eval result
2472 if ( QgsPythonRunner::eval( u"len(inspect.getfullargspec(%1)[0])"_s.arg( initFunction ), numArgs ) )
2473 {
2474 static int sFormId = 0;
2475 mPyFormVarName = u"_qgis_featureform_%1_%2"_s.arg( mFormNr ).arg( sFormId++ );
2476
2477 QString form = u"%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )"_s
2478 .arg( mPyFormVarName )
2479 .arg( ( quint64 ) this );
2480
2481 QgsPythonRunner::run( form );
2482
2483 QgsDebugMsgLevel( u"running featureForm init: %1"_s.arg( mPyFormVarName ), 2 );
2484
2485 // Legacy
2486 if ( numArgs == "3"_L1 )
2487 {
2488 addInterface( new QgsAttributeFormLegacyInterface( initFunction, mPyFormVarName, this ) );
2489 }
2490 else
2491 {
2492 // If we get here, it means that the function doesn't accept three arguments
2493 QMessageBox msgBox;
2494 msgBox.setText( tr( "The python init function (<code>%1</code>) does not accept three arguments as expected!<br>Please check the function name in the <b>Fields</b> tab of the layer properties." ).arg( initFunction ) );
2495 msgBox.exec();
2496#if 0
2497 QString expr = QString( "%1(%2)" )
2498 .arg( mLayer->editFormInit() )
2499 .arg( mPyFormVarName );
2500 QgsAttributeFormInterface *iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface *>( expr, "QgsAttributeFormInterface" );
2501 if ( iface )
2502 addInterface( iface );
2503#endif
2504 }
2505 }
2506 else
2507 {
2508 // If we get here, it means that inspect couldn't find the function
2509 QMessageBox msgBox;
2510 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 ) );
2511 msgBox.exec();
2512 }
2513 }
2514}
2515
2516QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAttributeEditorElement *widgetDef, QWidget *parent, QgsVectorLayer *vl, QgsAttributeEditorContext &context )
2517{
2518 WidgetInfo newWidgetInfo;
2519
2520 newWidgetInfo.labelStyle = widgetDef->labelStyle();
2521
2522 switch ( widgetDef->type() )
2523 {
2525 {
2526 const QgsAttributeEditorAction *elementDef = dynamic_cast<const QgsAttributeEditorAction *>( widgetDef );
2527 if ( !elementDef )
2528 break;
2529
2530 QgsActionWidgetWrapper *actionWrapper = new QgsActionWidgetWrapper( mLayer, nullptr, this, mMessageBar );
2531 actionWrapper->setAction( elementDef->action( vl ) );
2532 context.setAttributeFormMode( mMode );
2533 actionWrapper->setContext( context );
2534 mWidgets.append( actionWrapper );
2535 newWidgetInfo.widget = actionWrapper->widget();
2536 newWidgetInfo.showLabel = false;
2537
2538 break;
2539 }
2540
2542 {
2543 const QgsAttributeEditorField *fieldDef = dynamic_cast<const QgsAttributeEditorField *>( widgetDef );
2544 if ( !fieldDef )
2545 break;
2546
2547 const QgsFields fields = vl->fields();
2548 int fldIdx = fields.lookupField( fieldDef->name() );
2549 if ( fldIdx < fields.count() && fldIdx >= 0 )
2550 {
2551 const QgsEditorWidgetSetup widgetSetup = QgsGui::editorWidgetRegistry()->findBest( mLayer, fieldDef->name() );
2552
2553 QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( widgetSetup.type(), mLayer, fldIdx, widgetSetup.config(), nullptr, this, mContext );
2554 QgsAttributeFormEditorWidget *formWidget = new QgsAttributeFormEditorWidget( eww, widgetSetup.type(), this );
2555 mFormEditorWidgets.insert( fldIdx, formWidget );
2556 mFormWidgets.append( formWidget );
2557
2558 const Qgis::AttributeFormReuseLastValuePolicy reusePolicy = mLayer->editFormConfig().reuseLastValuePolicy( fldIdx );
2560 {
2561 bool remember = reusePolicy == Qgis::AttributeFormReuseLastValuePolicy::AllowedDefaultOn;
2562 const QVariant rememberLastUsedValuesVariant = mLayer->property( "AttributeFormRememberLastUsedValues" );
2563 QMap<int, bool> rememberLastUsedValues = rememberLastUsedValuesVariant.value<QMap<int, bool>>();
2564 if ( rememberLastUsedValues.contains( fldIdx ) )
2565 {
2566 remember = rememberLastUsedValues[fldIdx];
2567 }
2568 formWidget->setRememberLastValue( remember );
2569 connect( formWidget, &QgsAttributeFormEditorWidget::rememberLastValueChanged, this, [this]( int idx, bool remember ) {
2570 const QVariant rememberLastUsedValuesVariant = mLayer->property( "AttributeFormRememberLastUsedValues" );
2571 QMap<int, bool> rememberLastUsedValues = rememberLastUsedValuesVariant.value<QMap<int, bool>>();
2572 rememberLastUsedValues[idx] = remember;
2573 mLayer->setProperty( "AttributeFormRememberLastUsedValues", QVariant::fromValue<QMap<int, bool>>( rememberLastUsedValues ) );
2574 } );
2575 }
2576
2577 formWidget->createSearchWidgetWrappers( mContext );
2578
2579 newWidgetInfo.widget = formWidget;
2580 mWidgets.append( eww );
2581
2582 newWidgetInfo.widget->setObjectName( fields.at( fldIdx ).name() );
2583 newWidgetInfo.hint = fields.at( fldIdx ).comment();
2584 }
2585
2586 newWidgetInfo.labelOnTop = mLayer->editFormConfig().labelOnTop( fldIdx );
2587 newWidgetInfo.labelText = mLayer->attributeDisplayName( fldIdx );
2588 newWidgetInfo.labelText.replace( '&', "&&"_L1 ); // need to escape '&' or they'll be replace by _ in the label text
2589 newWidgetInfo.toolTip = u"<b>%1</b><p>%2</p>"_s.arg( mLayer->attributeDisplayName( fldIdx ), newWidgetInfo.hint );
2590 newWidgetInfo.showLabel = widgetDef->showLabel();
2591
2592 break;
2593 }
2594
2596 {
2597 const QgsAttributeEditorRelation *relDef = static_cast<const QgsAttributeEditorRelation *>( widgetDef );
2598
2599 QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( relDef->relationWidgetTypeId(), relDef->relation(), context );
2600
2601 QgsAttributeFormRelationEditorWidget *formWidget = new QgsAttributeFormRelationEditorWidget( rww, this );
2602 formWidget->createSearchWidgetWrappers( mContext );
2603
2604 // This needs to be after QgsAttributeFormRelationEditorWidget creation, because the widget
2605 // does not exists yet until QgsAttributeFormRelationEditorWidget is created and the setters
2606 // below directly alter the widget and check for it.
2608 rww->setNmRelationId( relDef->nmRelationId() );
2610
2611 mWidgets.append( rww );
2612 mFormWidgets.append( formWidget );
2613
2614 newWidgetInfo.widget = formWidget;
2615 newWidgetInfo.showLabel = relDef->showLabel();
2616 newWidgetInfo.labelText = relDef->label();
2617 if ( newWidgetInfo.labelText.isEmpty() )
2618 newWidgetInfo.labelText = rww->relation().name();
2619 newWidgetInfo.labelOnTop = true;
2620 break;
2621 }
2622
2624 {
2625 const QgsAttributeEditorContainer *container = dynamic_cast<const QgsAttributeEditorContainer *>( widgetDef );
2626 if ( !container )
2627 break;
2628
2629 int columnCount = container->columnCount();
2630
2631 if ( columnCount <= 0 )
2632 columnCount = 1;
2633
2634 QString widgetName;
2635 QWidget *myContainer = nullptr;
2636 bool removeLayoutMargin = false;
2637 switch ( container->type() )
2638 {
2640 {
2641 QgsCollapsibleGroupBoxBasic *groupBox = new QgsCollapsibleGroupBoxBasic();
2642 widgetName = u"QGroupBox"_s;
2643 if ( container->showLabel() )
2644 {
2645 groupBox->setTitle( container->name() );
2646 if ( newWidgetInfo.labelStyle.overrideColor )
2647 {
2648 if ( newWidgetInfo.labelStyle.color.isValid() )
2649 {
2650 groupBox->setStyleSheet( u"QGroupBox::title { color: %1; }"_s.arg( newWidgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2651 }
2652 }
2653 if ( newWidgetInfo.labelStyle.overrideFont )
2654 {
2655 groupBox->setFont( newWidgetInfo.labelStyle.font );
2656 }
2657 }
2658 myContainer = groupBox;
2659 newWidgetInfo.widget = myContainer;
2660 groupBox->setCollapsed( container->collapsed() );
2661 break;
2662 }
2663
2665 {
2666 QWidget *rowWidget = new QWidget();
2667 widgetName = u"Row"_s;
2668 myContainer = rowWidget;
2669 newWidgetInfo.widget = myContainer;
2670 removeLayoutMargin = true;
2671 columnCount = container->children().size();
2672 break;
2673 }
2674
2676 {
2677 myContainer = new QWidget();
2678
2679 QgsScrollArea *scrollArea = new QgsScrollArea( parent );
2680
2681 scrollArea->setWidget( myContainer );
2682 scrollArea->setWidgetResizable( true );
2683 scrollArea->setFrameShape( QFrame::NoFrame );
2684 widgetName = u"QScrollArea QWidget"_s;
2685
2686 newWidgetInfo.widget = scrollArea;
2687 break;
2688 }
2689 }
2690
2691 if ( container->backgroundColor().isValid() )
2692 {
2693 QString style { u"background-color: %1;"_s.arg( container->backgroundColor().name() ) };
2694 newWidgetInfo.widget->setStyleSheet( style );
2695 }
2696
2697 QGridLayout *gbLayout = new QGridLayout();
2698 if ( removeLayoutMargin )
2699 gbLayout->setContentsMargins( 0, 0, 0, 0 );
2700 myContainer->setLayout( gbLayout );
2701
2702 int row = 0;
2703 int column = 0;
2704 bool addSpacer = true;
2705
2706 const QList<QgsAttributeEditorElement *> children = container->children();
2707
2708 for ( QgsAttributeEditorElement *childDef : children )
2709 {
2710 WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context );
2711
2712 if ( childDef->type() == Qgis::AttributeEditorType::Container )
2713 {
2714 QgsAttributeEditorContainer *containerDef = static_cast<QgsAttributeEditorContainer *>( childDef );
2715 if ( containerDef->visibilityExpression().enabled() || containerDef->collapsedExpression().enabled() )
2716 {
2717 registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().enabled() ? containerDef->visibilityExpression().data() : QgsExpression(), containerDef->collapsed(), containerDef->collapsedExpression().enabled() ? containerDef->collapsedExpression().data() : QgsExpression() ) );
2718 }
2719 }
2720
2721 // column containing the actual widget, not the label
2722 int widgetColumn = column;
2723
2724 if ( widgetInfo.labelText.isNull() || !widgetInfo.showLabel )
2725 {
2726 gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 );
2727 widgetColumn = column + 1;
2728 column += 2;
2729 }
2730 else
2731 {
2732 QLabel *mypLabel = new QLabel( widgetInfo.labelText );
2733
2734 if ( widgetInfo.labelStyle.overrideColor )
2735 {
2736 if ( widgetInfo.labelStyle.color.isValid() )
2737 {
2738 mypLabel->setStyleSheet( u"QLabel { color: %1; }"_s.arg( widgetInfo.labelStyle.color.name( QColor::HexArgb ) ) );
2739 }
2740 }
2741
2742 if ( widgetInfo.labelStyle.overrideFont )
2743 {
2744 mypLabel->setFont( widgetInfo.labelStyle.font );
2745 }
2746
2747 // Alias DD overrides
2748 if ( childDef->type() == Qgis::AttributeEditorType::Field )
2749 {
2750 const QgsAttributeEditorField *fieldDef { static_cast<QgsAttributeEditorField *>( childDef ) };
2751 const QgsFields fields = vl->fields();
2752 const int fldIdx = fieldDef->idx();
2753 if ( fldIdx < fields.count() && fldIdx >= 0 )
2754 {
2755 const QString fieldName { fields.at( fldIdx ).name() };
2756 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Alias ) )
2757 {
2758 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Alias ) };
2759 if ( property.isActive() )
2760 {
2761 mLabelDataDefinedProperties[mypLabel] = property;
2762 }
2763 }
2764 if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Editable ) )
2765 {
2766 const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Editable ) };
2767 if ( property.isActive() )
2768 {
2769 mEditableDataDefinedProperties[widgetInfo.widget] = property;
2770 }
2771 }
2772 }
2773 }
2774
2775 mypLabel->setToolTip( widgetInfo.toolTip );
2776 if ( columnCount > 1 && !widgetInfo.labelOnTop )
2777 {
2778 mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
2779 }
2780
2781 mypLabel->setBuddy( widgetInfo.widget );
2782
2783 if ( widgetInfo.labelOnTop )
2784 {
2785 widgetColumn = column + 1;
2786 QVBoxLayout *c = new QVBoxLayout();
2787 mypLabel->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
2788 c->layout()->addWidget( mypLabel );
2789 c->layout()->addWidget( widgetInfo.widget );
2790 gbLayout->addLayout( c, row, column, 1, 2 );
2791 column += 2;
2792 }
2793 else
2794 {
2795 widgetColumn = column + 1;
2796 gbLayout->addWidget( mypLabel, row, column++ );
2797 gbLayout->addWidget( widgetInfo.widget, row, column++ );
2798 }
2799 }
2800
2801 const int childHorizontalStretch = childDef->horizontalStretch();
2802 const int existingColumnStretch = gbLayout->columnStretch( widgetColumn );
2803 if ( childHorizontalStretch > 0 && childHorizontalStretch > existingColumnStretch )
2804 {
2805 gbLayout->setColumnStretch( widgetColumn, childHorizontalStretch );
2806 }
2807
2808 if ( childDef->verticalStretch() > 0 && childDef->verticalStretch() > gbLayout->rowStretch( row ) )
2809 {
2810 gbLayout->setRowStretch( row, childDef->verticalStretch() );
2811 }
2812
2813 if ( column >= columnCount * 2 )
2814 {
2815 column = 0;
2816 row += 1;
2817 }
2818
2819 if ( widgetInfo.widget
2820 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
2821 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
2822 && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
2823 addSpacer = false;
2824
2825 // we consider all relation editors should be expanding
2826 if ( qobject_cast<QgsAttributeFormRelationEditorWidget *>( widgetInfo.widget ) )
2827 addSpacer = false;
2828 }
2829
2830 if ( addSpacer )
2831 {
2832 QWidget *spacer = new QWidget();
2833 spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
2834 gbLayout->addWidget( spacer, ++row, 0 );
2835 gbLayout->setRowStretch( row, 1 );
2836 }
2837
2838 newWidgetInfo.labelText = QString();
2839 newWidgetInfo.labelOnTop = true;
2840 newWidgetInfo.showLabel = widgetDef->showLabel();
2841 break;
2842 }
2843
2845 {
2846 const QgsAttributeEditorQmlElement *elementDef = static_cast<const QgsAttributeEditorQmlElement *>( widgetDef );
2847
2848 QgsQmlWidgetWrapper *qmlWrapper = new QgsQmlWidgetWrapper( mLayer, nullptr, this );
2849 qmlWrapper->setQmlCode( elementDef->qmlCode() );
2850 context.setAttributeFormMode( mMode );
2851 qmlWrapper->setContext( context );
2852
2853 mWidgets.append( qmlWrapper );
2854
2855 newWidgetInfo.widget = qmlWrapper->widget();
2856 newWidgetInfo.labelText = elementDef->name();
2857 newWidgetInfo.labelOnTop = true;
2858 newWidgetInfo.showLabel = widgetDef->showLabel();
2859 break;
2860 }
2861
2863 {
2864 const QgsAttributeEditorHtmlElement *elementDef = static_cast<const QgsAttributeEditorHtmlElement *>( widgetDef );
2865
2866 QgsHtmlWidgetWrapper *htmlWrapper = new QgsHtmlWidgetWrapper( mLayer, nullptr, this );
2867 context.setAttributeFormMode( mMode );
2868 htmlWrapper->setHtmlCode( elementDef->htmlCode() );
2869 htmlWrapper->reinitWidget();
2870 mWidgets.append( htmlWrapper );
2871
2872 newWidgetInfo.widget = htmlWrapper->widget();
2873 newWidgetInfo.labelText = elementDef->name();
2874 newWidgetInfo.labelOnTop = true;
2875 newWidgetInfo.showLabel = widgetDef->showLabel();
2876 mNeedsGeometry |= htmlWrapper->needsGeometry();
2877 break;
2878 }
2879
2881 {
2882 const QgsAttributeEditorTextElement *elementDef = static_cast<const QgsAttributeEditorTextElement *>( widgetDef );
2883
2884 QgsTextWidgetWrapper *textWrapper = new QgsTextWidgetWrapper( mLayer, nullptr, this );
2885 context.setAttributeFormMode( mMode );
2886 textWrapper->setText( elementDef->text() );
2887 textWrapper->reinitWidget();
2888 mWidgets.append( textWrapper );
2889
2890 newWidgetInfo.widget = textWrapper->widget();
2891 newWidgetInfo.labelText = elementDef->name();
2892 newWidgetInfo.labelOnTop = false;
2893 newWidgetInfo.showLabel = widgetDef->showLabel();
2894 mNeedsGeometry |= textWrapper->needsGeometry();
2895 break;
2896 }
2897
2899 {
2900 const QgsAttributeEditorSpacerElement *elementDef = static_cast<const QgsAttributeEditorSpacerElement *>( widgetDef );
2901 QgsSpacerWidgetWrapper *spacerWrapper = new QgsSpacerWidgetWrapper( mLayer, nullptr, this );
2902 spacerWrapper->setDrawLine( elementDef->drawLine() );
2903 context.setAttributeFormMode( mMode );
2904 mWidgets.append( spacerWrapper );
2905
2906 newWidgetInfo.widget = spacerWrapper->widget();
2907 newWidgetInfo.labelOnTop = false;
2908 newWidgetInfo.showLabel = false;
2909 break;
2910 }
2911
2912 default:
2913 QgsDebugError( u"Unknown attribute editor widget type encountered..."_s );
2914 break;
2915 }
2916
2917 return newWidgetInfo;
2918}
2919
2920void QgsAttributeForm::createWrappers()
2921{
2922 QList<QWidget *> myWidgets = findChildren<QWidget *>();
2923 const QList<QgsField> fields = mLayer->fields().toList();
2924
2925 const auto constMyWidgets = myWidgets;
2926 for ( QWidget *myWidget : constMyWidgets )
2927 {
2928 // Check the widget's properties for a relation definition
2929 QVariant vRel = myWidget->property( "qgisRelation" );
2930 if ( vRel.isValid() )
2931 {
2932 QgsRelationManager *relMgr = QgsProject::instance()->relationManager();
2933 QgsRelation relation = relMgr->relation( vRel.toString() );
2934 if ( relation.isValid() )
2935 {
2936 QgsRelationWidgetWrapper *rww = new QgsRelationWidgetWrapper( mLayer, relation, myWidget, this );
2937 rww->setConfig( mLayer->editFormConfig().widgetConfig( relation.id() ) );
2938 rww->setContext( mContext );
2939 rww->widget(); // Will initialize the widget
2940 mWidgets.append( rww );
2941 }
2942 }
2943 else
2944 {
2945 const auto constFields = fields;
2946 for ( const QgsField &field : constFields )
2947 {
2948 if ( field.name() == myWidget->objectName() )
2949 {
2950 int idx = mLayer->fields().lookupField( field.name() );
2951
2952 QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( mLayer, idx, myWidget, this, mContext );
2953 mWidgets.append( eww );
2954 }
2955 }
2956 }
2957 }
2958}
2959
2960void QgsAttributeForm::afterWidgetInit()
2961{
2962 bool isFirstEww = true;
2963
2964 const auto constMWidgets = mWidgets;
2965 for ( QgsWidgetWrapper *ww : constMWidgets )
2966 {
2967 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
2968
2969 if ( eww )
2970 {
2971 if ( isFirstEww )
2972 {
2973 setFocusProxy( eww->widget() );
2974 isFirstEww = false;
2975 }
2976
2977 connect( eww, &QgsEditorWidgetWrapper::valuesChanged, this, &QgsAttributeForm::onAttributeChanged, Qt::UniqueConnection );
2978 connect( eww, &QgsEditorWidgetWrapper::constraintStatusChanged, this, &QgsAttributeForm::onConstraintStatusChanged, Qt::UniqueConnection );
2979 }
2980 else
2981 {
2982 QgsRelationWidgetWrapper *relationWidgetWrapper = qobject_cast<QgsRelationWidgetWrapper *>( ww );
2983 if ( relationWidgetWrapper )
2984 {
2985 connect( relationWidgetWrapper, &QgsRelationWidgetWrapper::relatedFeaturesChanged, this, &QgsAttributeForm::onRelatedFeaturesChanged, static_cast<Qt::ConnectionType>( Qt::UniqueConnection | Qt::QueuedConnection ) );
2986 }
2987 }
2988 }
2989}
2990
2991
2992bool QgsAttributeForm::eventFilter( QObject *object, QEvent *e )
2993{
2994 Q_UNUSED( object )
2995
2996 if ( e->type() == QEvent::KeyPress )
2997 {
2998 QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( e );
2999 if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
3000 {
3001 // Re-emit to this form so it will be forwarded to parent
3002 event( e );
3003 return true;
3004 }
3005 }
3006
3007 return false;
3008}
3009
3010void QgsAttributeForm::scanForEqualAttributes( QgsFeatureIterator &fit, QSet<int> &mixedValueFields, QHash<int, QVariant> &fieldSharedValues ) const
3011{
3012 mixedValueFields.clear();
3013 fieldSharedValues.clear();
3014
3015 QgsFeature f;
3016 bool first = true;
3017 while ( fit.nextFeature( f ) )
3018 {
3019 for ( int i = 0; i < mLayer->fields().count(); ++i )
3020 {
3021 if ( mixedValueFields.contains( i ) )
3022 continue;
3023
3024 if ( first )
3025 {
3026 fieldSharedValues[i] = f.attribute( i );
3027 }
3028 else
3029 {
3030 if ( fieldSharedValues.value( i ) != f.attribute( i ) )
3031 {
3032 fieldSharedValues.remove( i );
3033 mixedValueFields.insert( i );
3034 }
3035 }
3036 }
3037 first = false;
3038
3039 if ( mixedValueFields.count() == mLayer->fields().count() )
3040 {
3041 // all attributes are mixed, no need to keep scanning
3042 break;
3043 }
3044 }
3045}
3046
3047
3048void QgsAttributeForm::layerSelectionChanged()
3049{
3050 switch ( mMode )
3051 {
3059 break;
3060
3062 resetMultiEdit( true );
3063 break;
3064 }
3065}
3066
3068{
3069 mIsSettingMultiEditFeatures = true;
3070 mMultiEditFeatureIds = fids;
3071
3072 if ( fids.isEmpty() )
3073 {
3074 // no selected features
3075 QMultiMap<int, QgsAttributeFormEditorWidget *>::const_iterator wIt = mFormEditorWidgets.constBegin();
3076 for ( ; wIt != mFormEditorWidgets.constEnd(); ++wIt )
3077 {
3078 wIt.value()->initialize( QVariant() );
3079 }
3080 mIsSettingMultiEditFeatures = false;
3081 return;
3082 }
3083
3084 QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFids( fids ) );
3085
3086 // Scan through all features to determine which attributes are initially the same
3087 QSet<int> mixedValueFields;
3088 QHash<int, QVariant> fieldSharedValues;
3089 scanForEqualAttributes( fit, mixedValueFields, fieldSharedValues );
3090
3091 // also fetch just first feature
3092 fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFid( *fids.constBegin() ) );
3093 QgsFeature firstFeature;
3094 fit.nextFeature( firstFeature );
3095
3096 // Make this feature the current form feature or the constraints will be evaluated
3097 // on a possibly wrong previously selected/current feature
3098 if ( mCurrentFormFeature.id() != firstFeature.id() )
3099 {
3100 setFeature( firstFeature );
3101 }
3102
3103 const auto constMixedValueFields = mixedValueFields;
3104 for ( int fieldIndex : std::as_const( mixedValueFields ) )
3105 {
3106 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( fieldIndex );
3107 if ( formEditorWidgets.isEmpty() )
3108 continue;
3109
3110 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
3111 QVariantList additionalFieldValues;
3112 for ( const QString &additionalField : additionalFields )
3113 additionalFieldValues << firstFeature.attribute( additionalField );
3114
3115 for ( QgsAttributeFormEditorWidget *w : formEditorWidgets )
3116 w->initialize( firstFeature.attribute( fieldIndex ), true, additionalFieldValues );
3117 }
3118 QHash<int, QVariant>::const_iterator sharedValueIt = fieldSharedValues.constBegin();
3119 for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
3120 {
3121 const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( sharedValueIt.key() );
3122 if ( formEditorWidgets.isEmpty() )
3123 continue;
3124
3125 bool mixed = false;
3126 const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
3127 for ( const QString &additionalField : additionalFields )
3128 {
3129 int index = mLayer->fields().indexFromName( additionalField );
3130 if ( constMixedValueFields.contains( index ) )
3131 {
3132 // if additional field are mixed, it is considered as mixed
3133 mixed = true;
3134 break;
3135 }
3136 }
3137 QVariantList additionalFieldValues;
3138 if ( mixed )
3139 {
3140 for ( const QString &additionalField : additionalFields )
3141 additionalFieldValues << firstFeature.attribute( additionalField );
3142 for ( QgsAttributeFormEditorWidget *w : formEditorWidgets )
3143 w->initialize( firstFeature.attribute( sharedValueIt.key() ), true, additionalFieldValues );
3144 }
3145 else
3146 {
3147 for ( const QString &additionalField : additionalFields )
3148 {
3149 int index = mLayer->fields().indexFromName( additionalField );
3150 Q_ASSERT( fieldSharedValues.contains( index ) );
3151 additionalFieldValues << fieldSharedValues.value( index );
3152 }
3153 for ( QgsAttributeFormEditorWidget *w : formEditorWidgets )
3154 w->initialize( sharedValueIt.value(), false, additionalFieldValues );
3155 }
3156 }
3157
3158 setMultiEditFeatureIdsRelations( fids );
3159
3160 mIsSettingMultiEditFeatures = false;
3161}
3162
3164{
3165 if ( mOwnsMessageBar )
3166 delete mMessageBar;
3167 mOwnsMessageBar = false;
3168 mMessageBar = messageBar;
3169}
3170
3172{
3174 {
3175 Q_ASSERT( false );
3176 }
3177
3178 QStringList filters;
3179 for ( QgsAttributeFormWidget *widget : mFormWidgets )
3180 {
3181 QString filter = widget->currentFilterExpression();
3182 if ( !filter.isNull() )
3183 filters << '(' + filter + ')';
3184 }
3185
3186 return filters.join( " AND "_L1 );
3187}
3188
3190{
3191 mExtraContextScope.reset( extraScope );
3192}
3193
3194void QgsAttributeForm::ContainerInformation::apply( QgsExpressionContext *expressionContext )
3195{
3196 const bool newVisibility = expression.evaluate( expressionContext ).toBool();
3197
3198 if ( expression.isValid() && !expression.hasEvalError() && newVisibility != isVisible )
3199 {
3200 if ( tabWidget )
3201 {
3202 tabWidget->setTabVisible( widget, newVisibility );
3203 }
3204 else
3205 {
3206 widget->setVisible( newVisibility );
3207 }
3208
3209 isVisible = newVisibility;
3210 }
3211
3212 const bool newCollapsedState = collapsedExpression.evaluate( expressionContext ).toBool();
3213
3214 if ( collapsedExpression.isValid() && !collapsedExpression.hasEvalError() && newCollapsedState != isCollapsed )
3215 {
3216 if ( QgsCollapsibleGroupBoxBasic * collapsibleGroupBox { qobject_cast<QgsCollapsibleGroupBoxBasic *>( widget ) } )
3217 {
3218 collapsibleGroupBox->setCollapsed( newCollapsedState );
3219 isCollapsed = newCollapsedState;
3220 }
3221 }
3222}
3223
3224void QgsAttributeForm::updateJoinedFields( const QgsEditorWidgetWrapper &eww )
3225{
3226 if ( !eww.layer()->fields().exists( eww.fieldIdx() ) )
3227 return;
3228
3229 QgsFeature formFeature;
3230 QgsField field = eww.layer()->fields().field( eww.fieldIdx() );
3231 QList<const QgsVectorLayerJoinInfo *> infos = eww.layer()->joinBuffer()->joinsWhereFieldIsId( field );
3232
3233 if ( infos.count() == 0 || !currentFormValuesFeature( formFeature ) )
3234 return;
3235
3236 const QString hint = tr( "No feature joined" );
3237 const auto constInfos = infos;
3238 for ( const QgsVectorLayerJoinInfo *info : constInfos )
3239 {
3240 if ( !info->isDynamicFormEnabled() )
3241 continue;
3242
3243 QgsFeature joinFeature = mLayer->joinBuffer()->joinedFeatureOf( info, formFeature );
3244
3245 mJoinedFeatures[info] = joinFeature;
3246
3247 if ( info->hasSubset() )
3248 {
3249 const QStringList subsetNames = QgsVectorLayerJoinInfo::joinFieldNamesSubset( *info );
3250
3251 const auto constSubsetNames = subsetNames;
3252 for ( const QString &field : constSubsetNames )
3253 {
3254 QString prefixedName = info->prefixedFieldName( field );
3255 QVariant val;
3256 QString hintText = hint;
3257
3258 if ( joinFeature.isValid() )
3259 {
3260 val = joinFeature.attribute( field );
3261 hintText.clear();
3262 }
3263
3264 changeAttribute( prefixedName, val, hintText );
3265 }
3266 }
3267 else
3268 {
3269 const QgsFields joinFields = joinFeature.fields();
3270 for ( const QgsField &field : joinFields )
3271 {
3272 QString prefixedName = info->prefixedFieldName( field );
3273 QVariant val;
3274 QString hintText = hint;
3275
3276 if ( joinFeature.isValid() )
3277 {
3278 val = joinFeature.attribute( field.name() );
3279 hintText.clear();
3280 }
3281
3282 changeAttribute( prefixedName, val, hintText );
3283 }
3284 }
3285 }
3286}
3287
3288bool QgsAttributeForm::fieldIsEditable( int fieldIndex ) const
3289{
3291}
3292
3293void QgsAttributeForm::updateFieldDependencies()
3294{
3295 mDefaultValueDependencies.clear();
3296 mVirtualFieldsDependencies.clear();
3297 mRelatedLayerFieldsDependencies.clear();
3298 mParentDependencies.clear();
3299
3300 //create defaultValueDependencies
3301 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
3302 {
3303 QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
3304 if ( !eww )
3305 continue;
3306
3307 updateFieldDependenciesParent( eww );
3308 updateFieldDependenciesDefaultValue( eww );
3309 updateFieldDependenciesVirtualFields( eww );
3310 updateRelatedLayerFieldsDependencies( eww );
3311 }
3312}
3313
3314void QgsAttributeForm::updateFieldDependenciesDefaultValue( QgsEditorWidgetWrapper *eww )
3315{
3316 QgsExpression exp( eww->field().defaultValueDefinition().expression() );
3317
3318 if ( exp.needsGeometry() )
3319 mNeedsGeometry = true;
3320
3321 //if a function requires all attributes, it should have the dependency of every field change
3322 if ( exp.referencedColumns().contains( QgsFeatureRequest::ALL_ATTRIBUTES ) )
3323 {
3324 const QList<int> allAttributeIds( mLayer->fields().allAttributesList() );
3325
3326 for ( const int id : allAttributeIds )
3327 {
3328 mDefaultValueDependencies.insertMulti( id, eww );
3329 }
3330 }
3331 else
3332 {
3333 //otherwise just enter for the field depending on
3334 const QSet<QString> referencedColumns = exp.referencedColumns();
3335 for ( const QString &referencedColumn : referencedColumns )
3336 {
3337 mDefaultValueDependencies.insertMulti( mLayer->fields().lookupField( referencedColumn ), eww );
3338 }
3339 }
3340}
3341
3342void QgsAttributeForm::updateFieldDependenciesVirtualFields( QgsEditorWidgetWrapper *eww )
3343{
3344 QString expressionField = eww->layer()->expressionField( eww->fieldIdx() );
3345 if ( expressionField.isEmpty() )
3346 return;
3347
3348 QgsExpression exp( expressionField );
3349
3350 if ( exp.needsGeometry() )
3351 mNeedsGeometry = true;
3352
3353 //if a function requires all attributes, it should have the dependency of every field change
3354 if ( exp.referencedColumns().contains( QgsFeatureRequest::ALL_ATTRIBUTES ) )
3355 {
3356 const QList<int> allAttributeIds( mLayer->fields().allAttributesList() );
3357
3358 for ( const int id : allAttributeIds )
3359 {
3360 mVirtualFieldsDependencies.insertMulti( id, eww );
3361 }
3362 }
3363 else
3364 {
3365 //otherwise just enter for the field depending on
3366 const QSet<QString> referencedColumns = exp.referencedColumns();
3367 for ( const QString &referencedColumn : referencedColumns )
3368 {
3369 mVirtualFieldsDependencies.insertMulti( mLayer->fields().lookupField( referencedColumn ), eww );
3370 }
3371 }
3372}
3373
3374void QgsAttributeForm::updateRelatedLayerFieldsDependencies( QgsEditorWidgetWrapper *eww )
3375{
3376 if ( eww )
3377 {
3378 QString expressionField = eww->layer()->expressionField( eww->fieldIdx() );
3379 if ( expressionField.contains( u"relation_aggregate"_s )
3380 || expressionField.contains( u"get_features"_s ) )
3381 mRelatedLayerFieldsDependencies.insert( eww );
3382 }
3383 else
3384 {
3385 mRelatedLayerFieldsDependencies.clear();
3386 //create defaultValueDependencies
3387 for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
3388 {
3389 QgsEditorWidgetWrapper *editorWidgetWrapper = qobject_cast<QgsEditorWidgetWrapper *>( ww );
3390 if ( !editorWidgetWrapper )
3391 continue;
3392
3393 updateRelatedLayerFieldsDependencies( editorWidgetWrapper );
3394 }
3395 }
3396}
3397
3398void QgsAttributeForm::updateFieldDependenciesParent( QgsEditorWidgetWrapper *eww )
3399{
3400 if ( eww && !eww->field().defaultValueDefinition().expression().isEmpty() )
3401 {
3402 const QgsExpression expression( eww->field().defaultValueDefinition().expression() );
3403 const QSet<QString> referencedVariablesAndFunctions = expression.referencedVariables() + expression.referencedFunctions();
3404 for ( const QString &referenced : referencedVariablesAndFunctions )
3405 {
3406 if ( referenced.startsWith( "current_parent"_L1 ) )
3407 {
3408 mParentDependencies.insert( eww );
3409 break;
3410 }
3411 }
3412 }
3413}
3414
3415void QgsAttributeForm::setMultiEditFeatureIdsRelations( const QgsFeatureIds &fids )
3416{
3417 for ( QgsAttributeFormWidget *formWidget : mFormWidgets )
3418 {
3419 QgsAttributeFormRelationEditorWidget *relationEditorWidget = dynamic_cast<QgsAttributeFormRelationEditorWidget *>( formWidget );
3420 if ( !relationEditorWidget )
3421 continue;
3422
3423 relationEditorWidget->setMultiEditFeatureIds( fids );
3424 }
3425}
3426
3427void QgsAttributeForm::updateIcon( QgsEditorWidgetWrapper *eww )
3428{
3429 if ( !eww->widget() || !mIconMap[eww->widget()] )
3430 return;
3431
3432 // no icon by default
3433 mIconMap[eww->widget()]->hide();
3434
3435 if ( !eww->widget()->isEnabled() && mLayer->isEditable() )
3436 {
3437 if ( mLayer->fields().fieldOrigin( eww->fieldIdx() ) == Qgis::FieldOrigin::Join )
3438 {
3439 int srcFieldIndex;
3440 const QgsVectorLayerJoinInfo *info = mLayer->joinBuffer()->joinForFieldIndex( eww->fieldIdx(), mLayer->fields(), srcFieldIndex );
3441
3442 if ( !info )
3443 return;
3444
3445 if ( !info->isEditable() )
3446 {
3447 const QString file = u"/mIconJoinNotEditable.svg"_s;
3448 const QString tooltip = tr( "Join settings do not allow editing" );
3449 reloadIcon( file, tooltip, mIconMap[eww->widget()] );
3450 }
3451 else if ( mMode == QgsAttributeEditorContext::AddFeatureMode && !info->hasUpsertOnEdit() )
3452 {
3453 const QString file = u"mIconJoinHasNotUpsertOnEdit.svg"_s;
3454 const QString tooltip = tr( "Join settings do not allow upsert on edit" );
3455 reloadIcon( file, tooltip, mIconMap[eww->widget()] );
3456 }
3457 else if ( !info->joinLayer()->isEditable() )
3458 {
3459 const QString file = u"/mIconJoinedLayerNotEditable.svg"_s;
3460 const QString tooltip = tr( "Joined layer is not toggled editable" );
3461 reloadIcon( file, tooltip, mIconMap[eww->widget()] );
3462 }
3463 }
3464 }
3465}
3466
3467void QgsAttributeForm::reloadIcon( const QString &file, const QString &tooltip, QSvgWidget *sw )
3468{
3469 sw->load( QgsApplication::iconPath( file ) );
3470 sw->setToolTip( tooltip );
3471 sw->show();
3472}
3473
3475{
3476 const bool reuseAllLastValues = QgsSettingsRegistryCore::settingsDigitizingReuseLastValues->value();
3477 QgsDebugMsgLevel( u"reuseAllLastValues: %1"_s.arg( reuseAllLastValues ), 2 );
3478
3479 const QgsFields fields = layer->fields();
3480 QgsAttributeMap initialAttributeValues;
3481 for ( int idx = 0; idx < fields.count(); ++idx )
3482 {
3483 if ( attributes.contains( idx ) )
3484 {
3485 initialAttributeValues.insert( idx, attributes.value( idx ) );
3486 }
3487 else if ( ( reuseAllLastValues || layer->editFormConfig().reuseLastValuePolicy( idx ) != Qgis::AttributeFormReuseLastValuePolicy::NotAllowed ) )
3488 {
3489 const QVariant lastUsedValuesVariant = layer->property( "AttributeFormLastUsedValues" );
3490 const QgsAttributeMap lastUsedValues = lastUsedValuesVariant.isValid() ? lastUsedValuesVariant.value<QgsAttributeMap>() : QgsAttributeMap();
3491 if ( lastUsedValues.contains( idx ) && layer->dataProvider() && layer->dataProvider()->defaultValueClause( idx ) != lastUsedValues[idx] )
3492 {
3493 initialAttributeValues.insert( idx, lastUsedValues[idx] );
3494 }
3495 }
3496 }
3497
3498 return QgsVectorLayerUtils::createFeature( layer, geometry, initialAttributeValues, &context );
3499}
@ Trusted
The project trust has not yet been determined by the user.
Definition qgis.h:473
@ Row
A row of editors (horizontal layout).
Definition qgis.h:5739
AttributeFormReuseLastValuePolicy
Attribute form policy for reusing last entered values.
Definition qgis.h:5795
@ AllowedDefaultOn
Reuse of last values allowed and enabled by default.
Definition qgis.h:5797
@ NotAllowed
Reuse of last values not allowed.
Definition qgis.h:5796
@ File
Load the Python code from an external file.
Definition qgis.h:5783
@ Environment
Use the Python code available in the Python environment.
Definition qgis.h:5785
@ NoSource
Do not use Python code at all.
Definition qgis.h:5782
@ Dialog
Use the Python code provided in the dialog.
Definition qgis.h:5784
@ DragAndDrop
"Drag and drop" layout. Needs to be configured.
Definition qgis.h:5753
@ UiFile
Load a .ui file for the layout. Needs to be configured.
Definition qgis.h:5754
@ Warning
Warning message.
Definition qgis.h:161
@ Info
Information message.
Definition qgis.h:160
@ Success
Used for reporting a successful operation.
Definition qgis.h:163
@ Join
Field originates from a joined layer.
Definition qgis.h:1765
@ Action
A layer action element.
Definition qgis.h:5723
@ Container
A container.
Definition qgis.h:5718
@ QmlElement
A QML element.
Definition qgis.h:5721
@ Relation
A relation.
Definition qgis.h:5720
@ HtmlElement
A HTML element.
Definition qgis.h:5722
@ TextElement
A text element.
Definition qgis.h:5724
@ SpacerElement
A spacer element.
Definition qgis.h:5725
SelectBehavior
Specifies how a selection should be applied.
Definition qgis.h:1829
@ SetSelection
Set selection, removing any existing selection.
Definition qgis.h:1830
@ AddToSelection
Add selection to current selection.
Definition qgis.h:1831
@ IntersectSelection
Modify current selection to include only select features which match.
Definition qgis.h:1832
@ RemoveFromSelection
Remove from current selection.
Definition qgis.h:1833
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:101
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:86
T data() const
Access the payload data.
static Qgis::ProjectTrustStatus checkUserTrust(QgsProject *project)
Returns the current trust status of the specified project.
QgsRelationManager * relationManager
Definition qgsproject.h:123
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.
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
QString id
Definition qgsrelation.h:45
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:7451
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7450
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