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