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