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