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