From 54e3668edd386e3df9743945c5fe798af5095f45 Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Sun, 28 Apr 2024 16:22:54 +0700 Subject: [PATCH 1/4] [attribute form] Add parent feature scope when adding a child feature through the relation editor widget --- .../core/auto_generated/qgstrackedvectorlayertools.sip.in | 3 ++- .../core/auto_generated/vector/qgsvectorlayertools.sip.in | 3 ++- python/core/auto_generated/qgstrackedvectorlayertools.sip.in | 3 ++- python/core/auto_generated/vector/qgsvectorlayertools.sip.in | 3 ++- src/app/qgsfeatureaction.cpp | 2 ++ src/app/qgsguivectorlayertools.cpp | 4 ++-- src/app/qgsguivectorlayertools.h | 5 +++-- src/core/qgstrackedvectorlayertools.cpp | 5 ++--- src/core/qgstrackedvectorlayertools.h | 4 +++- src/core/vector/qgsvectorlayertools.h | 4 +++- src/gui/qgsabstractrelationeditorwidget.cpp | 4 +++- tests/src/gui/testqgsrelationreferencewidget.cpp | 3 ++- 12 files changed, 28 insertions(+), 15 deletions(-) diff --git a/python/PyQt6/core/auto_generated/qgstrackedvectorlayertools.sip.in b/python/PyQt6/core/auto_generated/qgstrackedvectorlayertools.sip.in index ca103b0fe903..5b720368f1cc 100644 --- a/python/PyQt6/core/auto_generated/qgstrackedvectorlayertools.sip.in +++ b/python/PyQt6/core/auto_generated/qgstrackedvectorlayertools.sip.in @@ -20,7 +20,7 @@ class QgsTrackedVectorLayerTools : QgsVectorLayerTools Constructor for QgsTrackedVectorLayerTools. %End - virtual bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feature, QWidget *parentWidget = 0, bool showModal = true, bool hideParent = false ) const; + virtual bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feature, QWidget *parentWidget = 0, bool showModal = true, bool hideParent = false, QgsExpressionContextScope *scope = 0 ) const; %Docstring This method calls the addFeature method of the backend :py:class:`QgsVectorLayerTools` @@ -32,6 +32,7 @@ This method calls the addFeature method of the backend :py:class:`QgsVectorLayer :param parentWidget: The widget calling this function to be passed to the used dialog :param showModal: If the used dialog should be modal or not :param hideParent: If the parent widget should be hidden, when the used dialog is opened +:param scope: A context scope to be used to calculate feature expression-based values :return: ``True`` in case of success, ``False`` if the operation failed/was aborted %End diff --git a/python/PyQt6/core/auto_generated/vector/qgsvectorlayertools.sip.in b/python/PyQt6/core/auto_generated/vector/qgsvectorlayertools.sip.in index 85c5a1eee2e3..c37a098bb3da 100644 --- a/python/PyQt6/core/auto_generated/vector/qgsvectorlayertools.sip.in +++ b/python/PyQt6/core/auto_generated/vector/qgsvectorlayertools.sip.in @@ -28,7 +28,7 @@ in your application. QgsVectorLayerTools(); - virtual bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues = QgsAttributeMap(), const QgsGeometry &defaultGeometry = QgsGeometry(), QgsFeature *feature /Out/ = 0, QWidget *parentWidget = 0, bool showModal = true, bool hideParent = false ) const = 0; + virtual bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues = QgsAttributeMap(), const QgsGeometry &defaultGeometry = QgsGeometry(), QgsFeature *feature /Out/ = 0, QWidget *parentWidget = 0, bool showModal = true, bool hideParent = false, QgsExpressionContextScope *scope = 0 ) const = 0; %Docstring This method should/will be called, whenever a new feature will be added to the layer @@ -38,6 +38,7 @@ This method should/will be called, whenever a new feature will be added to the l :param parentWidget: The widget calling this function to be passed to the used dialog :param showModal: If the used dialog should be modal or not :param hideParent: If the parent widget should be hidden, when the used dialog is opened +:param scope: A context scope to be used to calculate feature expression-based values :return: - ``True`` in case of success, ``False`` if the operation failed/was aborted - feature: Updated feature after adding will be written back to this diff --git a/python/core/auto_generated/qgstrackedvectorlayertools.sip.in b/python/core/auto_generated/qgstrackedvectorlayertools.sip.in index ca103b0fe903..5b720368f1cc 100644 --- a/python/core/auto_generated/qgstrackedvectorlayertools.sip.in +++ b/python/core/auto_generated/qgstrackedvectorlayertools.sip.in @@ -20,7 +20,7 @@ class QgsTrackedVectorLayerTools : QgsVectorLayerTools Constructor for QgsTrackedVectorLayerTools. %End - virtual bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feature, QWidget *parentWidget = 0, bool showModal = true, bool hideParent = false ) const; + virtual bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feature, QWidget *parentWidget = 0, bool showModal = true, bool hideParent = false, QgsExpressionContextScope *scope = 0 ) const; %Docstring This method calls the addFeature method of the backend :py:class:`QgsVectorLayerTools` @@ -32,6 +32,7 @@ This method calls the addFeature method of the backend :py:class:`QgsVectorLayer :param parentWidget: The widget calling this function to be passed to the used dialog :param showModal: If the used dialog should be modal or not :param hideParent: If the parent widget should be hidden, when the used dialog is opened +:param scope: A context scope to be used to calculate feature expression-based values :return: ``True`` in case of success, ``False`` if the operation failed/was aborted %End diff --git a/python/core/auto_generated/vector/qgsvectorlayertools.sip.in b/python/core/auto_generated/vector/qgsvectorlayertools.sip.in index 85c5a1eee2e3..c37a098bb3da 100644 --- a/python/core/auto_generated/vector/qgsvectorlayertools.sip.in +++ b/python/core/auto_generated/vector/qgsvectorlayertools.sip.in @@ -28,7 +28,7 @@ in your application. QgsVectorLayerTools(); - virtual bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues = QgsAttributeMap(), const QgsGeometry &defaultGeometry = QgsGeometry(), QgsFeature *feature /Out/ = 0, QWidget *parentWidget = 0, bool showModal = true, bool hideParent = false ) const = 0; + virtual bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues = QgsAttributeMap(), const QgsGeometry &defaultGeometry = QgsGeometry(), QgsFeature *feature /Out/ = 0, QWidget *parentWidget = 0, bool showModal = true, bool hideParent = false, QgsExpressionContextScope *scope = 0 ) const = 0; %Docstring This method should/will be called, whenever a new feature will be added to the layer @@ -38,6 +38,7 @@ This method should/will be called, whenever a new feature will be added to the l :param parentWidget: The widget calling this function to be passed to the used dialog :param showModal: If the used dialog should be modal or not :param hideParent: If the parent widget should be hidden, when the used dialog is opened +:param scope: A context scope to be used to calculate feature expression-based values :return: - ``True`` in case of success, ``False`` if the operation failed/was aborted - feature: Updated feature after adding will be written back to this diff --git a/src/app/qgsfeatureaction.cpp b/src/app/qgsfeatureaction.cpp index cac3d2b796bf..26d51147fc15 100644 --- a/src/app/qgsfeatureaction.cpp +++ b/src/app/qgsfeatureaction.cpp @@ -203,7 +203,9 @@ QgsFeatureAction::AddFeatureResult QgsFeatureAction::addFeature( const QgsAttrib // values and field constraints QgsExpressionContext context = mLayer->createExpressionContext(); if ( scope ) + { context.appendScope( scope.release() ); + } const QgsFeature newFeature = QgsVectorLayerUtils::createFeature( mLayer, mFeature->geometry(), initialAttributeValues, &context ); diff --git a/src/app/qgsguivectorlayertools.cpp b/src/app/qgsguivectorlayertools.cpp index 94157f17dba2..acd58434155e 100644 --- a/src/app/qgsguivectorlayertools.cpp +++ b/src/app/qgsguivectorlayertools.cpp @@ -27,7 +27,7 @@ #include "qgsvectorlayer.h" #include "qgsvectorlayerutils.h" -bool QgsGuiVectorLayerTools::addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feat, QWidget *parentWidget, bool showModal, bool hideParent ) const +bool QgsGuiVectorLayerTools::addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feat, QWidget *parentWidget, bool showModal, bool hideParent, QgsExpressionContextScope *scope ) const { QgsFeature *f = feat; if ( !feat ) @@ -37,7 +37,7 @@ bool QgsGuiVectorLayerTools::addFeature( QgsVectorLayer *layer, const QgsAttribu QgsFeatureAction *a = new QgsFeatureAction( tr( "Add feature" ), *f, layer, QUuid(), -1, parentWidget ); a->setForceSuppressFormPopup( forceSuppressFormPopup() ); connect( a, &QgsFeatureAction::addFeatureFinished, a, &QObject::deleteLater ); - const QgsFeatureAction::AddFeatureResult result = a->addFeature( defaultValues, showModal, nullptr, hideParent ); + const QgsFeatureAction::AddFeatureResult result = a->addFeature( defaultValues, showModal, std::unique_ptr( scope ), hideParent ); if ( !feat ) delete f; diff --git a/src/app/qgsguivectorlayertools.h b/src/app/qgsguivectorlayertools.h index 400995960b6b..57be6215afeb 100644 --- a/src/app/qgsguivectorlayertools.h +++ b/src/app/qgsguivectorlayertools.h @@ -44,10 +44,11 @@ class QgsGuiVectorLayerTools : public QgsVectorLayerTools * \param parentWidget The widget calling this function to be passed to the used dialog * \param showModal If the used dialog should be modal or not * \param hideParent If the parent widget should be hidden, when the used dialog is opened + * \param scope A context scope to be used to calculate feature expression-based values * - * \returns TRUE in case of success, FALSE if the operation failed/was aborted + * \returns TRUE in case of success, FALSE if the operation failed/was aborted */ - bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feat = nullptr, QWidget *parentWidget = nullptr, bool showModal = true, bool hideParent = false ) const override; + bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feat = nullptr, QWidget *parentWidget = nullptr, bool showModal = true, bool hideParent = false, QgsExpressionContextScope *scope = nullptr ) const override; /** * This should be called, whenever a vector layer should be switched to edit mode. If successful diff --git a/src/core/qgstrackedvectorlayertools.cpp b/src/core/qgstrackedvectorlayertools.cpp index 3256f0fd89ac..b6b753b785b4 100644 --- a/src/core/qgstrackedvectorlayertools.cpp +++ b/src/core/qgstrackedvectorlayertools.cpp @@ -17,15 +17,14 @@ #include "qgsvectorlayer.h" -bool QgsTrackedVectorLayerTools::addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feature, QWidget *parentWidget, bool showModal, bool hideParent ) const +bool QgsTrackedVectorLayerTools::addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feature, QWidget *parentWidget, bool showModal, bool hideParent, QgsExpressionContextScope *scope ) const { QgsFeature *f = feature; if ( !feature ) f = new QgsFeature(); const_cast( mBackend )->setForceSuppressFormPopup( forceSuppressFormPopup() ); - - if ( mBackend->addFeature( layer, defaultValues, defaultGeometry, f, parentWidget, showModal, hideParent ) ) + if ( mBackend->addFeature( layer, defaultValues, defaultGeometry, f, parentWidget, showModal, hideParent, scope ) ) { mAddedFeatures[layer].insert( f->id() ); if ( !feature ) diff --git a/src/core/qgstrackedvectorlayertools.h b/src/core/qgstrackedvectorlayertools.h index f12b127d9a78..64bb534394a4 100644 --- a/src/core/qgstrackedvectorlayertools.h +++ b/src/core/qgstrackedvectorlayertools.h @@ -18,6 +18,7 @@ #include "qgis_core.h" #include "qgsvectorlayertools.h" +#include "qgsexpressioncontext.h" /** * \ingroup core @@ -43,10 +44,11 @@ class CORE_EXPORT QgsTrackedVectorLayerTools : public QgsVectorLayerTools * \param parentWidget The widget calling this function to be passed to the used dialog * \param showModal If the used dialog should be modal or not * \param hideParent If the parent widget should be hidden, when the used dialog is opened + * \param scope A context scope to be used to calculate feature expression-based values * * \returns TRUE in case of success, FALSE if the operation failed/was aborted */ - bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feature, QWidget *parentWidget = nullptr, bool showModal = true, bool hideParent = false ) const override; + bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feature, QWidget *parentWidget = nullptr, bool showModal = true, bool hideParent = false, QgsExpressionContextScope *scope = nullptr ) const override; bool startEditing( QgsVectorLayer *layer ) const override; bool stopEditing( QgsVectorLayer *layer, bool allowCancel ) const override; bool saveEdits( QgsVectorLayer *layer ) const override; diff --git a/src/core/vector/qgsvectorlayertools.h b/src/core/vector/qgsvectorlayertools.h index ac9dac7dfd7e..d8cc9e1386eb 100644 --- a/src/core/vector/qgsvectorlayertools.h +++ b/src/core/vector/qgsvectorlayertools.h @@ -20,6 +20,7 @@ #include "qgis_sip.h" #include +#include "qgsexpressioncontext.h" #include "qgsfeature.h" #include "qgsgeometry.h" @@ -56,10 +57,11 @@ class CORE_EXPORT QgsVectorLayerTools : public QObject * \param parentWidget The widget calling this function to be passed to the used dialog * \param showModal If the used dialog should be modal or not * \param hideParent If the parent widget should be hidden, when the used dialog is opened + * \param scope A context scope to be used to calculate feature expression-based values * \returns TRUE in case of success, FALSE if the operation failed/was aborted * */ - virtual bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues = QgsAttributeMap(), const QgsGeometry &defaultGeometry = QgsGeometry(), QgsFeature *feature SIP_OUT = nullptr, QWidget *parentWidget = nullptr, bool showModal = true, bool hideParent = false ) const = 0; + virtual bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues = QgsAttributeMap(), const QgsGeometry &defaultGeometry = QgsGeometry(), QgsFeature *feature SIP_OUT = nullptr, QWidget *parentWidget = nullptr, bool showModal = true, bool hideParent = false, QgsExpressionContextScope *scope = nullptr ) const = 0; // TODO QGIS 4: remove const qualifier diff --git a/src/gui/qgsabstractrelationeditorwidget.cpp b/src/gui/qgsabstractrelationeditorwidget.cpp index cd03a80e089c..808a73010be7 100644 --- a/src/gui/qgsabstractrelationeditorwidget.cpp +++ b/src/gui/qgsabstractrelationeditorwidget.cpp @@ -19,6 +19,7 @@ #include "qgsfeatureiterator.h" #include "qgsexpression.h" +#include "qgsexpressioncontextutils.h" #include "qgsfeature.h" #include "qgsfeatureselectiondlg.h" #include "qgsrelation.h" @@ -286,8 +287,9 @@ QgsFeatureIds QgsAbstractRelationEditorWidget::addFeature( const QgsGeometry &ge for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs ) keyAttrs.insert( fields.indexFromName( fieldPair.referencingField() ), mFeatureList.first().attribute( fieldPair.referencedField() ) ); + QgsExpressionContextScope *scope = QgsExpressionContextUtils::parentFormScope( mFeatureList.first(), mEditorContext.attributeFormModeString() ); QgsFeature linkFeature; - if ( !vlTools->addFeature( mRelation.referencingLayer(), keyAttrs, geometry, &linkFeature, this, true, true ) ) + if ( !vlTools->addFeature( mRelation.referencingLayer(), keyAttrs, geometry, &linkFeature, this, true, true, scope ) ) return QgsFeatureIds(); addedFeatureIds.insert( linkFeature.id() ); diff --git a/tests/src/gui/testqgsrelationreferencewidget.cpp b/tests/src/gui/testqgsrelationreferencewidget.cpp index 546f1b854183..9530dfcfabf0 100644 --- a/tests/src/gui/testqgsrelationreferencewidget.cpp +++ b/tests/src/gui/testqgsrelationreferencewidget.cpp @@ -609,11 +609,12 @@ void TestQgsRelationReferenceWidget::testIdentifyOnMap() // referenced layer class DummyVectorLayerTools : public QgsVectorLayerTools // clazy:exclude=missing-qobject-macro { - bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &, const QgsGeometry &, QgsFeature *feat = nullptr, QWidget *parentWidget = nullptr, bool showModal = true, bool hideParent = false ) const override + bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &, const QgsGeometry &, QgsFeature *feat = nullptr, QWidget *parentWidget = nullptr, bool showModal = true, bool hideParent = false, QgsExpressionContextScope *scope = nullptr ) const override { Q_UNUSED( parentWidget ); Q_UNUSED( showModal ); Q_UNUSED( hideParent ); + Q_UNUSED( scope ); feat->setAttribute( QStringLiteral( "pk" ), 13 ); feat->setAttribute( QStringLiteral( "material" ), QStringLiteral( "steel" ) ); feat->setAttribute( QStringLiteral( "diameter" ), 140 ); From 83d61b5458e875f3d90e442658d4943d0d7d2141 Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Sun, 28 Apr 2024 18:24:41 +0700 Subject: [PATCH 2/4] [atribute form] Add parent context when editing a child feature through the relation editor widget --- .../core/auto_generated/vector/qgsvectorlayer.sip.in | 8 ++++++-- .../core/auto_generated/vector/qgsvectorlayer.sip.in | 8 ++++++-- src/core/vector/qgsvectorlayer.cpp | 12 ++++++------ src/core/vector/qgsvectorlayer.h | 10 +++++++--- .../attributeformconfig/qgsattributetypedialog.cpp | 1 + src/gui/qgsattributeform.cpp | 12 +++++++++++- tests/src/python/test_qgsrelationeditwidget.py | 4 ++-- 7 files changed, 39 insertions(+), 16 deletions(-) diff --git a/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in index b54fb80e6f10..6cb83580ecea 100644 --- a/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in +++ b/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in @@ -1777,7 +1777,7 @@ be updated. This can be used to override default field value expressions. .. seealso:: :py:func:`updateFeature` %End - bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant(), bool skipDefaultValues = false ); + bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant(), bool skipDefaultValues = false, QgsExpressionContext *context = 0 ); %Docstring Changes an attribute value for a feature (but does not immediately commit the changes). The ``fid`` argument specifies the ID of the feature to be changed. @@ -1796,6 +1796,8 @@ so it is more efficient to explicitly pass an ``oldValue`` if it is already avai If ``skipDefaultValues`` is set to ``True``, default field values will not be updated. This can be used to override default field value expressions. +If ``context`` is provided, it will be used when updating default values. + :return: ``True`` if the feature's attribute was successfully changed. .. note:: @@ -1815,7 +1817,7 @@ be updated. This can be used to override default field value expressions. .. seealso:: :py:func:`updateFeature` %End - bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues = QgsAttributeMap(), bool skipDefaultValues = false ); + bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues = QgsAttributeMap(), bool skipDefaultValues = false, QgsExpressionContext *context = 0 ); %Docstring Changes attributes' values for a feature (but does not immediately commit the changes). @@ -1835,6 +1837,8 @@ If ``skipDefaultValues`` is set to ``True``, default field values will not be updated. This can be used to override default field value expressions. +If ``context`` is provided, it will be used when updating default values. + :return: ``True`` if feature's attributes was successfully changed. .. note:: diff --git a/python/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/core/auto_generated/vector/qgsvectorlayer.sip.in index b54fb80e6f10..6cb83580ecea 100644 --- a/python/core/auto_generated/vector/qgsvectorlayer.sip.in +++ b/python/core/auto_generated/vector/qgsvectorlayer.sip.in @@ -1777,7 +1777,7 @@ be updated. This can be used to override default field value expressions. .. seealso:: :py:func:`updateFeature` %End - bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant(), bool skipDefaultValues = false ); + bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant(), bool skipDefaultValues = false, QgsExpressionContext *context = 0 ); %Docstring Changes an attribute value for a feature (but does not immediately commit the changes). The ``fid`` argument specifies the ID of the feature to be changed. @@ -1796,6 +1796,8 @@ so it is more efficient to explicitly pass an ``oldValue`` if it is already avai If ``skipDefaultValues`` is set to ``True``, default field values will not be updated. This can be used to override default field value expressions. +If ``context`` is provided, it will be used when updating default values. + :return: ``True`` if the feature's attribute was successfully changed. .. note:: @@ -1815,7 +1817,7 @@ be updated. This can be used to override default field value expressions. .. seealso:: :py:func:`updateFeature` %End - bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues = QgsAttributeMap(), bool skipDefaultValues = false ); + bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues = QgsAttributeMap(), bool skipDefaultValues = false, QgsExpressionContext *context = 0 ); %Docstring Changes attributes' values for a feature (but does not immediately commit the changes). @@ -1835,6 +1837,8 @@ If ``skipDefaultValues`` is set to ``True``, default field values will not be updated. This can be used to override default field value expressions. +If ``context`` is provided, it will be used when updating default values. + :return: ``True`` if feature's attributes was successfully changed. .. note:: diff --git a/src/core/vector/qgsvectorlayer.cpp b/src/core/vector/qgsvectorlayer.cpp index bc49a72b2049..0717519fe834 100644 --- a/src/core/vector/qgsvectorlayer.cpp +++ b/src/core/vector/qgsvectorlayer.cpp @@ -1002,7 +1002,7 @@ void QgsVectorLayer::setExtent3D( const QgsBox3D &r ) mValidExtent3D = true; } -void QgsVectorLayer::updateDefaultValues( QgsFeatureId fid, QgsFeature feature ) +void QgsVectorLayer::updateDefaultValues( QgsFeatureId fid, QgsFeature feature, QgsExpressionContext *context ) { QGIS_PROTECT_QOBJECT_THREAD_ACCESS @@ -1016,7 +1016,7 @@ void QgsVectorLayer::updateDefaultValues( QgsFeatureId fid, QgsFeature feature ) { if ( idx < 0 || idx >= size ) continue; - feature.setAttribute( idx, defaultValue( idx, feature ) ); + feature.setAttribute( idx, defaultValue( idx, feature, context ) ); updateFeature( feature, true ); } } @@ -3398,7 +3398,7 @@ bool QgsVectorLayer::changeGeometry( QgsFeatureId fid, QgsGeometry &geom, bool s } -bool QgsVectorLayer::changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue, bool skipDefaultValues ) +bool QgsVectorLayer::changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue, bool skipDefaultValues, QgsExpressionContext *context ) { QGIS_PROTECT_QOBJECT_THREAD_ACCESS @@ -3426,12 +3426,12 @@ bool QgsVectorLayer::changeAttributeValue( QgsFeatureId fid, int field, const QV } if ( result && !skipDefaultValues && !mDefaultValueOnUpdateFields.isEmpty() ) - updateDefaultValues( fid ); + updateDefaultValues( fid, QgsFeature(), context ); return result; } -bool QgsVectorLayer::changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues, bool skipDefaultValues ) +bool QgsVectorLayer::changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues, bool skipDefaultValues, QgsExpressionContext *context ) { QGIS_PROTECT_QOBJECT_THREAD_ACCESS @@ -3488,7 +3488,7 @@ bool QgsVectorLayer::changeAttributeValues( QgsFeatureId fid, const QgsAttribute if ( result && !skipDefaultValues && !mDefaultValueOnUpdateFields.isEmpty() ) { - updateDefaultValues( fid ); + updateDefaultValues( fid, QgsFeature(), context ); } return result; diff --git a/src/core/vector/qgsvectorlayer.h b/src/core/vector/qgsvectorlayer.h index 07a184243aae..12566cd4cac7 100644 --- a/src/core/vector/qgsvectorlayer.h +++ b/src/core/vector/qgsvectorlayer.h @@ -1735,6 +1735,8 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte * If \a skipDefaultValues is set to TRUE, default field values will not * be updated. This can be used to override default field value expressions. * + * If \a context is provided, it will be used when updating default values. + * * \returns TRUE if the feature's attribute was successfully changed. * * \note Calls to changeAttributeValue() are only valid for layers in which edits have been enabled @@ -1747,7 +1749,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte * \see changeGeometry() * \see updateFeature() */ - Q_INVOKABLE bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant(), bool skipDefaultValues = false ); + Q_INVOKABLE bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant(), bool skipDefaultValues = false, QgsExpressionContext *context = nullptr ); /** * Changes attributes' values for a feature (but does not immediately @@ -1768,6 +1770,8 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte * be updated. This can be used to override default field value * expressions. * + * If \a context is provided, it will be used when updating default values. + * * \returns TRUE if feature's attributes was successfully changed. * * \note Calls to changeAttributeValues() are only valid for layers in @@ -1783,7 +1787,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte * \see changeAttributeValue() * */ - Q_INVOKABLE bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues = QgsAttributeMap(), bool skipDefaultValues = false ); + Q_INVOKABLE bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues = QgsAttributeMap(), bool skipDefaultValues = false, QgsExpressionContext *context = nullptr ); /** * Add an attribute field (but does not commit it) @@ -2794,7 +2798,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte void onAfterCommitChangesDependency(); private: - void updateDefaultValues( QgsFeatureId fid, QgsFeature feature = QgsFeature() ); + void updateDefaultValues( QgsFeatureId fid, QgsFeature feature = QgsFeature(), QgsExpressionContext *context = nullptr ); /** * Returns TRUE if the layer is in read-only mode diff --git a/src/gui/attributeformconfig/qgsattributetypedialog.cpp b/src/gui/attributeformconfig/qgsattributetypedialog.cpp index 71a4c12ba44a..f8df465844ae 100644 --- a/src/gui/attributeformconfig/qgsattributetypedialog.cpp +++ b/src/gui/attributeformconfig/qgsattributetypedialog.cpp @@ -352,6 +352,7 @@ QgsExpressionContext QgsAttributeTypeDialog::createExpressionContext() const << QgsExpressionContextUtils::projectScope( QgsProject::instance() ) << QgsExpressionContextUtils::layerScope( mLayer ) << QgsExpressionContextUtils::formScope( ) + << QgsExpressionContextUtils::parentFormScope( QgsFeature() ) << QgsExpressionContextUtils::mapToolCaptureScope( QList() ); return context; diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index e62ccf9275a4..a4e64074961e 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -353,7 +353,9 @@ bool QgsAttributeForm::saveEdits( QString *error ) // An add dialog should perform an action by default // and not only if attributes have "changed" if ( mMode == QgsAttributeEditorContext::AddFeatureMode || mMode == QgsAttributeEditorContext::FixAttributeMode ) + { doUpdate = true; + } QgsAttributes src = mFeature.attributes(); QgsAttributes dst = mFeature.attributes(); @@ -461,7 +463,8 @@ bool QgsAttributeForm::saveEdits( QString *error ) n++; } - success = mLayer->changeAttributeValues( mFeature.id(), newValues, oldValues ); + QgsExpressionContext context = createExpressionContext( updatedFeature ); + success = mLayer->changeAttributeValues( mFeature.id(), newValues, oldValues, false, &context ); if ( success && n > 0 ) { @@ -569,6 +572,7 @@ void QgsAttributeForm::updateValuesDependenciesDefaultValues( const int originId continue; QgsExpressionContext context = createExpressionContext( updatedFeature ); + const QVariant value = mLayer->defaultValue( eww->fieldIdx(), updatedFeature, &context ); eww->setValue( value ); mCurrentFormFeature.setAttribute( eww->field().name(), value ); @@ -983,7 +987,13 @@ QgsExpressionContext QgsAttributeForm::createExpressionContext( const QgsFeature context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) ); context.appendScope( QgsExpressionContextUtils::formScope( feature, mContext.attributeFormModeString() ) ); if ( mExtraContextScope ) + { context.appendScope( new QgsExpressionContextScope( *mExtraContextScope.get() ) ); + } + if ( mContext.parentFormFeature().isValid() ) + { + context.appendScope( QgsExpressionContextUtils::parentFormScope( mContext.parentFormFeature() ) ); + } context.setFeature( feature ); return context; } diff --git a/tests/src/python/test_qgsrelationeditwidget.py b/tests/src/python/test_qgsrelationeditwidget.py index ece758559334..cf69ff3d44be 100644 --- a/tests/src/python/test_qgsrelationeditwidget.py +++ b/tests/src/python/test_qgsrelationeditwidget.py @@ -341,7 +341,7 @@ def test_add_feature_geometry(self): # Mock vector layer tool to just set default value on created feature class DummyVlTools(QgsVectorLayerTools): - def addFeature(self, layer, defaultValues, defaultGeometry, parentWidget=None, showModal=True, hideParent=False): + def addFeature(self, layer, defaultValues, defaultGeometry, parentWidget=None, showModal=True, hideParent=False, scope=None): f = QgsFeature(layer.fields()) for idx, value in defaultValues.items(): f.setAttribute(idx, value) @@ -440,7 +440,7 @@ def setValues(self, values): """ self.values = values - def addFeature(self, layer, defaultValues, defaultGeometry, parentWidget=None, showModal=True, hideParent=False): + def addFeature(self, layer, defaultValues, defaultGeometry, parentWidget=None, showModal=True, hideParent=False, scope=None): """ Overrides the addFeature method :param layer: vector layer From 19cb97109cc5d0fa6cc1f39be310b0ee49e9ff19 Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Mon, 29 Apr 2024 16:54:16 +0700 Subject: [PATCH 3/4] Add test covering new context parameter to QgsVector change attribute(s) functions --- tests/src/python/test_qgsvectorlayer.py | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/src/python/test_qgsvectorlayer.py b/tests/src/python/test_qgsvectorlayer.py index 7513d1bd1c1a..3832d8a27a18 100644 --- a/tests/src/python/test_qgsvectorlayer.py +++ b/tests/src/python/test_qgsvectorlayer.py @@ -1139,6 +1139,36 @@ def checkBefore(): self.assertTrue(layer.commitChanges()) checkAfter() + def test_ChangeAttributeValuesWithContext(self): + layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer", + "addfeat", "memory") + + layer.setDefaultValueDefinition(0, QgsDefaultValue("geom_to_wkt(@current_parent_geometry)", True)) + + f = QgsFeature() + f.setAttributes(["test", 123]) + f.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(100, 200))) + + assert layer.dataProvider().addFeatures([f]) + assert layer.featureCount() == 1 + fid = 1 + + fields = QgsFields() + fields.append(QgsField("parenttxt", QVariant.String)) + fields.append(QgsField("parentinteger", QVariant.Int)) + pf = QgsFeature(fields) + pf.setAttributes(["parent", 789]) + pf.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(1, 2))) + + layer.startEditing() + + context = layer.createExpressionContext() + context.appendScope(QgsExpressionContextUtils.parentFormScope(pf)) + self.assertTrue(layer.changeAttributeValues(fid, {1: 100}, {}, False, context)) + + f = layer.getFeature(1) + self.assertEqual(f.attributes(), ["Point (1 2)", 100]) + def test_ChangeAttributeAfterAddFeature(self): layer = createLayerWithOnePoint() layer.dataProvider().deleteFeatures([1]) # no need for this feature From 96a711a2aeb721e3b79ce753fcde6bb55fa7733e Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Wed, 22 May 2024 11:20:29 +0700 Subject: [PATCH 4/4] Address review: add vector layer tools context class, implement addFeatureV2 --- .../qgstrackedvectorlayertools.sip.in | 11 +- .../vector/qgsvectorlayer.sip.in | 8 +- .../vector/qgsvectorlayertools.sip.in | 26 +++- .../vector/qgsvectorlayertoolscontext.sip.in | 115 +++++++++++++++++ python/PyQt6/core/core_auto.sip | 1 + .../qgstrackedvectorlayertools.sip.in | 11 +- .../vector/qgsvectorlayer.sip.in | 8 +- .../vector/qgsvectorlayertools.sip.in | 26 +++- .../vector/qgsvectorlayertoolscontext.sip.in | 115 +++++++++++++++++ python/core/core_auto.sip | 1 + src/app/qgsguivectorlayertools.cpp | 13 +- src/app/qgsguivectorlayertools.h | 9 +- src/core/CMakeLists.txt | 2 + src/core/qgstrackedvectorlayertools.cpp | 6 +- src/core/qgstrackedvectorlayertools.h | 7 +- src/core/vector/qgsvectorlayer.cpp | 8 +- src/core/vector/qgsvectorlayer.h | 9 +- src/core/vector/qgsvectorlayertools.h | 33 ++++- .../vector/qgsvectorlayertoolscontext.cpp | 78 ++++++++++++ src/core/vector/qgsvectorlayertoolscontext.h | 117 ++++++++++++++++++ src/gui/qgsabstractrelationeditorwidget.cpp | 9 +- src/gui/qgsattributeform.cpp | 7 +- .../gui/testqgsrelationreferencewidget.cpp | 8 +- .../src/python/test_qgsrelationeditwidget.py | 4 +- tests/src/python/test_qgsvectorlayer.py | 7 +- 25 files changed, 570 insertions(+), 69 deletions(-) create mode 100644 python/PyQt6/core/auto_generated/vector/qgsvectorlayertoolscontext.sip.in create mode 100644 python/core/auto_generated/vector/qgsvectorlayertoolscontext.sip.in create mode 100644 src/core/vector/qgsvectorlayertoolscontext.cpp create mode 100644 src/core/vector/qgsvectorlayertoolscontext.h diff --git a/python/PyQt6/core/auto_generated/qgstrackedvectorlayertools.sip.in b/python/PyQt6/core/auto_generated/qgstrackedvectorlayertools.sip.in index 5b720368f1cc..53cbd7c08c3d 100644 --- a/python/PyQt6/core/auto_generated/qgstrackedvectorlayertools.sip.in +++ b/python/PyQt6/core/auto_generated/qgstrackedvectorlayertools.sip.in @@ -20,7 +20,7 @@ class QgsTrackedVectorLayerTools : QgsVectorLayerTools Constructor for QgsTrackedVectorLayerTools. %End - virtual bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feature, QWidget *parentWidget = 0, bool showModal = true, bool hideParent = false, QgsExpressionContextScope *scope = 0 ) const; + virtual bool addFeatureV2( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feature /Out/, const QgsVectorLayerToolsContext &context ) const; %Docstring This method calls the addFeature method of the backend :py:class:`QgsVectorLayerTools` @@ -28,13 +28,10 @@ This method calls the addFeature method of the backend :py:class:`QgsVectorLayer :param layer: The layer to which the feature should be added :param defaultValues: Default values for the feature to add :param defaultGeometry: A default geometry to add to the feature -:param feature: A pointer to the feature -:param parentWidget: The widget calling this function to be passed to the used dialog -:param showModal: If the used dialog should be modal or not -:param hideParent: If the parent widget should be hidden, when the used dialog is opened -:param scope: A context scope to be used to calculate feature expression-based values +:param context: A context object to be used for e.g. to calculate feature expression-based values (since QGIS 3.38) -:return: ``True`` in case of success, ``False`` if the operation failed/was aborted +:return: - ``True`` in case of success, ``False`` if the operation failed/was aborted + - feature: A pointer to the feature %End virtual bool startEditing( QgsVectorLayer *layer ) const; diff --git a/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in index 6cb83580ecea..1e9c11afd9e9 100644 --- a/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in +++ b/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in @@ -1777,7 +1777,7 @@ be updated. This can be used to override default field value expressions. .. seealso:: :py:func:`updateFeature` %End - bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant(), bool skipDefaultValues = false, QgsExpressionContext *context = 0 ); + bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant(), bool skipDefaultValues = false, QgsVectorLayerToolsContext *context = 0 ); %Docstring Changes an attribute value for a feature (but does not immediately commit the changes). The ``fid`` argument specifies the ID of the feature to be changed. @@ -1796,7 +1796,7 @@ so it is more efficient to explicitly pass an ``oldValue`` if it is already avai If ``skipDefaultValues`` is set to ``True``, default field values will not be updated. This can be used to override default field value expressions. -If ``context`` is provided, it will be used when updating default values. +If ``context`` is provided, it will be used when updating default values (since QGIS 3.38). :return: ``True`` if the feature's attribute was successfully changed. @@ -1817,7 +1817,7 @@ If ``context`` is provided, it will be used when updating default values. .. seealso:: :py:func:`updateFeature` %End - bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues = QgsAttributeMap(), bool skipDefaultValues = false, QgsExpressionContext *context = 0 ); + bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues = QgsAttributeMap(), bool skipDefaultValues = false, QgsVectorLayerToolsContext *context = 0 ); %Docstring Changes attributes' values for a feature (but does not immediately commit the changes). @@ -1837,7 +1837,7 @@ If ``skipDefaultValues`` is set to ``True``, default field values will not be updated. This can be used to override default field value expressions. -If ``context`` is provided, it will be used when updating default values. +If ``context`` is provided, it will be used when updating default values (since QGIS 3.38). :return: ``True`` if feature's attributes was successfully changed. diff --git a/python/PyQt6/core/auto_generated/vector/qgsvectorlayertools.sip.in b/python/PyQt6/core/auto_generated/vector/qgsvectorlayertools.sip.in index c37a098bb3da..e6eeebdb50d5 100644 --- a/python/PyQt6/core/auto_generated/vector/qgsvectorlayertools.sip.in +++ b/python/PyQt6/core/auto_generated/vector/qgsvectorlayertools.sip.in @@ -28,7 +28,7 @@ in your application. QgsVectorLayerTools(); - virtual bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues = QgsAttributeMap(), const QgsGeometry &defaultGeometry = QgsGeometry(), QgsFeature *feature /Out/ = 0, QWidget *parentWidget = 0, bool showModal = true, bool hideParent = false, QgsExpressionContextScope *scope = 0 ) const = 0; + virtual bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues = QgsAttributeMap(), const QgsGeometry &defaultGeometry = QgsGeometry(), QgsFeature *feature /Out/ = 0, QWidget *parentWidget = 0, bool showModal = true, bool hideParent = false ) const; %Docstring This method should/will be called, whenever a new feature will be added to the layer @@ -38,10 +38,32 @@ This method should/will be called, whenever a new feature will be added to the l :param parentWidget: The widget calling this function to be passed to the used dialog :param showModal: If the used dialog should be modal or not :param hideParent: If the parent widget should be hidden, when the used dialog is opened -:param scope: A context scope to be used to calculate feature expression-based values :return: - ``True`` in case of success, ``False`` if the operation failed/was aborted - feature: Updated feature after adding will be written back to this + +.. note:: + + addFeature or addFeatureV2 must be overwritten when implementing a class inheriting from QgsVectorLayerTools +%End + + virtual bool addFeatureV2( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues = QgsAttributeMap(), const QgsGeometry &defaultGeometry = QgsGeometry(), QgsFeature *feature /Out/ = 0, const QgsVectorLayerToolsContext &context = QgsVectorLayerToolsContext() ) const; +%Docstring +This method should/will be called, whenever a new feature will be added to the layer + +:param layer: The layer to which the feature should be added +:param defaultValues: Default values for the feature to add +:param defaultGeometry: A default geometry to add to the feature +:param context: A context object to be used for e.g. to calculate feature expression-based values (since QGIS 3.38) + +:return: - ``True`` in case of success, ``False`` if the operation failed/was aborted + - feature: Updated feature after adding will be written back to this + +.. note:: + + addFeature or addFeatureV2 must be overwritten when implementing a class inheriting from QgsVectorLayerTools + +.. versionadded:: 3.38 %End diff --git a/python/PyQt6/core/auto_generated/vector/qgsvectorlayertoolscontext.sip.in b/python/PyQt6/core/auto_generated/vector/qgsvectorlayertoolscontext.sip.in new file mode 100644 index 000000000000..371e2f5c4359 --- /dev/null +++ b/python/PyQt6/core/auto_generated/vector/qgsvectorlayertoolscontext.sip.in @@ -0,0 +1,115 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/vector/qgsvectorlayertoolscontext.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + +class QgsVectorLayerToolsContext +{ +%Docstring(signature="appended") +Contains settings which reflect the context in which vector layer tool operations should +consider. + +.. versionadded:: 3.38 +%End + +%TypeHeaderCode +#include "qgsvectorlayertoolscontext.h" +%End + public: + + QgsVectorLayerToolsContext(); +%Docstring +Constructor for QgsVectorLayerToolsContext. +%End + + QgsVectorLayerToolsContext( const QgsVectorLayerToolsContext &other ); +%Docstring +Copy constructor. + +:param other: source QgsVectorLayerToolsContext +%End + + + void setExpressionContext( const QgsExpressionContext *context ); +%Docstring +Sets the optional expression context used by the vector layer tools. + +:param context: expression context pointer. Ownership is not transferred. + +.. seealso:: :py:func:`expressionContext` + +.. seealso:: :py:func:`setAdditionalExpressionContextScope` +%End + + QgsExpressionContext *expressionContext() const; +%Docstring +Returns the optional expression context used by the vector layer tools. + +.. seealso:: :py:func:`setExpressionContext` + +.. seealso:: :py:func:`additionalExpressionContextScope` +%End + + void setAdditionalExpressionContextScope( const QgsExpressionContextScope *scope ); +%Docstring +Sets an additional expression context scope to be made available when calculating expressions. + +:param scope: additional scope. Ownership is not transferred and a copy will be made. + +.. seealso:: :py:func:`additionalExpressionContextScope` +%End + + const QgsExpressionContextScope *additionalExpressionContextScope() const; +%Docstring +Returns an additional expression context scope to be made available when calculating expressions. + +.. seealso:: :py:func:`setAdditionalExpressionContextScope` +%End + + QWidget *parentWidget() const; +%Docstring +Returns the widget which should be parented to tools dialogues. +%End + + void setParentWidget( QWidget *parent ); +%Docstring +Sets the widget which should be parented to tools' dialogues. + +:param parent: the widget actign as parent +%End + + bool showModal() const; +%Docstring +Returns whether tools' dialogues should be modal. +%End + + void setShowModal( bool modal ); +%Docstring +Sets whether tools' dialogues should be modal. +%End + + bool hideParent() const; +%Docstring +Returns whether the parent widget should be hidden when showing tools' dialogues. +%End + + void setHideParent( bool hide ); +%Docstring +Sets whether the parent widget should be hidden when showing tools' dialogues. +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/vector/qgsvectorlayertoolscontext.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/PyQt6/core/core_auto.sip b/python/PyQt6/core/core_auto.sip index e0e0d9a1076f..c5361cba7cb8 100644 --- a/python/PyQt6/core/core_auto.sip +++ b/python/PyQt6/core/core_auto.sip @@ -771,6 +771,7 @@ %Include auto_generated/vector/qgsvectorlayerselectionproperties.sip %Include auto_generated/vector/qgsvectorlayertemporalproperties.sip %Include auto_generated/vector/qgsvectorlayertools.sip +%Include auto_generated/vector/qgsvectorlayertoolscontext.sip %Include auto_generated/vector/qgsvectorlayerundocommand.sip %Include auto_generated/vector/qgsvectorlayerundopassthroughcommand.sip %Include auto_generated/vector/qgsvectorlayerutils.sip diff --git a/python/core/auto_generated/qgstrackedvectorlayertools.sip.in b/python/core/auto_generated/qgstrackedvectorlayertools.sip.in index 5b720368f1cc..53cbd7c08c3d 100644 --- a/python/core/auto_generated/qgstrackedvectorlayertools.sip.in +++ b/python/core/auto_generated/qgstrackedvectorlayertools.sip.in @@ -20,7 +20,7 @@ class QgsTrackedVectorLayerTools : QgsVectorLayerTools Constructor for QgsTrackedVectorLayerTools. %End - virtual bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feature, QWidget *parentWidget = 0, bool showModal = true, bool hideParent = false, QgsExpressionContextScope *scope = 0 ) const; + virtual bool addFeatureV2( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feature /Out/, const QgsVectorLayerToolsContext &context ) const; %Docstring This method calls the addFeature method of the backend :py:class:`QgsVectorLayerTools` @@ -28,13 +28,10 @@ This method calls the addFeature method of the backend :py:class:`QgsVectorLayer :param layer: The layer to which the feature should be added :param defaultValues: Default values for the feature to add :param defaultGeometry: A default geometry to add to the feature -:param feature: A pointer to the feature -:param parentWidget: The widget calling this function to be passed to the used dialog -:param showModal: If the used dialog should be modal or not -:param hideParent: If the parent widget should be hidden, when the used dialog is opened -:param scope: A context scope to be used to calculate feature expression-based values +:param context: A context object to be used for e.g. to calculate feature expression-based values (since QGIS 3.38) -:return: ``True`` in case of success, ``False`` if the operation failed/was aborted +:return: - ``True`` in case of success, ``False`` if the operation failed/was aborted + - feature: A pointer to the feature %End virtual bool startEditing( QgsVectorLayer *layer ) const; diff --git a/python/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/core/auto_generated/vector/qgsvectorlayer.sip.in index 6cb83580ecea..1e9c11afd9e9 100644 --- a/python/core/auto_generated/vector/qgsvectorlayer.sip.in +++ b/python/core/auto_generated/vector/qgsvectorlayer.sip.in @@ -1777,7 +1777,7 @@ be updated. This can be used to override default field value expressions. .. seealso:: :py:func:`updateFeature` %End - bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant(), bool skipDefaultValues = false, QgsExpressionContext *context = 0 ); + bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant(), bool skipDefaultValues = false, QgsVectorLayerToolsContext *context = 0 ); %Docstring Changes an attribute value for a feature (but does not immediately commit the changes). The ``fid`` argument specifies the ID of the feature to be changed. @@ -1796,7 +1796,7 @@ so it is more efficient to explicitly pass an ``oldValue`` if it is already avai If ``skipDefaultValues`` is set to ``True``, default field values will not be updated. This can be used to override default field value expressions. -If ``context`` is provided, it will be used when updating default values. +If ``context`` is provided, it will be used when updating default values (since QGIS 3.38). :return: ``True`` if the feature's attribute was successfully changed. @@ -1817,7 +1817,7 @@ If ``context`` is provided, it will be used when updating default values. .. seealso:: :py:func:`updateFeature` %End - bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues = QgsAttributeMap(), bool skipDefaultValues = false, QgsExpressionContext *context = 0 ); + bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues = QgsAttributeMap(), bool skipDefaultValues = false, QgsVectorLayerToolsContext *context = 0 ); %Docstring Changes attributes' values for a feature (but does not immediately commit the changes). @@ -1837,7 +1837,7 @@ If ``skipDefaultValues`` is set to ``True``, default field values will not be updated. This can be used to override default field value expressions. -If ``context`` is provided, it will be used when updating default values. +If ``context`` is provided, it will be used when updating default values (since QGIS 3.38). :return: ``True`` if feature's attributes was successfully changed. diff --git a/python/core/auto_generated/vector/qgsvectorlayertools.sip.in b/python/core/auto_generated/vector/qgsvectorlayertools.sip.in index c37a098bb3da..e6eeebdb50d5 100644 --- a/python/core/auto_generated/vector/qgsvectorlayertools.sip.in +++ b/python/core/auto_generated/vector/qgsvectorlayertools.sip.in @@ -28,7 +28,7 @@ in your application. QgsVectorLayerTools(); - virtual bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues = QgsAttributeMap(), const QgsGeometry &defaultGeometry = QgsGeometry(), QgsFeature *feature /Out/ = 0, QWidget *parentWidget = 0, bool showModal = true, bool hideParent = false, QgsExpressionContextScope *scope = 0 ) const = 0; + virtual bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues = QgsAttributeMap(), const QgsGeometry &defaultGeometry = QgsGeometry(), QgsFeature *feature /Out/ = 0, QWidget *parentWidget = 0, bool showModal = true, bool hideParent = false ) const; %Docstring This method should/will be called, whenever a new feature will be added to the layer @@ -38,10 +38,32 @@ This method should/will be called, whenever a new feature will be added to the l :param parentWidget: The widget calling this function to be passed to the used dialog :param showModal: If the used dialog should be modal or not :param hideParent: If the parent widget should be hidden, when the used dialog is opened -:param scope: A context scope to be used to calculate feature expression-based values :return: - ``True`` in case of success, ``False`` if the operation failed/was aborted - feature: Updated feature after adding will be written back to this + +.. note:: + + addFeature or addFeatureV2 must be overwritten when implementing a class inheriting from QgsVectorLayerTools +%End + + virtual bool addFeatureV2( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues = QgsAttributeMap(), const QgsGeometry &defaultGeometry = QgsGeometry(), QgsFeature *feature /Out/ = 0, const QgsVectorLayerToolsContext &context = QgsVectorLayerToolsContext() ) const; +%Docstring +This method should/will be called, whenever a new feature will be added to the layer + +:param layer: The layer to which the feature should be added +:param defaultValues: Default values for the feature to add +:param defaultGeometry: A default geometry to add to the feature +:param context: A context object to be used for e.g. to calculate feature expression-based values (since QGIS 3.38) + +:return: - ``True`` in case of success, ``False`` if the operation failed/was aborted + - feature: Updated feature after adding will be written back to this + +.. note:: + + addFeature or addFeatureV2 must be overwritten when implementing a class inheriting from QgsVectorLayerTools + +.. versionadded:: 3.38 %End diff --git a/python/core/auto_generated/vector/qgsvectorlayertoolscontext.sip.in b/python/core/auto_generated/vector/qgsvectorlayertoolscontext.sip.in new file mode 100644 index 000000000000..371e2f5c4359 --- /dev/null +++ b/python/core/auto_generated/vector/qgsvectorlayertoolscontext.sip.in @@ -0,0 +1,115 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/vector/qgsvectorlayertoolscontext.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + +class QgsVectorLayerToolsContext +{ +%Docstring(signature="appended") +Contains settings which reflect the context in which vector layer tool operations should +consider. + +.. versionadded:: 3.38 +%End + +%TypeHeaderCode +#include "qgsvectorlayertoolscontext.h" +%End + public: + + QgsVectorLayerToolsContext(); +%Docstring +Constructor for QgsVectorLayerToolsContext. +%End + + QgsVectorLayerToolsContext( const QgsVectorLayerToolsContext &other ); +%Docstring +Copy constructor. + +:param other: source QgsVectorLayerToolsContext +%End + + + void setExpressionContext( const QgsExpressionContext *context ); +%Docstring +Sets the optional expression context used by the vector layer tools. + +:param context: expression context pointer. Ownership is not transferred. + +.. seealso:: :py:func:`expressionContext` + +.. seealso:: :py:func:`setAdditionalExpressionContextScope` +%End + + QgsExpressionContext *expressionContext() const; +%Docstring +Returns the optional expression context used by the vector layer tools. + +.. seealso:: :py:func:`setExpressionContext` + +.. seealso:: :py:func:`additionalExpressionContextScope` +%End + + void setAdditionalExpressionContextScope( const QgsExpressionContextScope *scope ); +%Docstring +Sets an additional expression context scope to be made available when calculating expressions. + +:param scope: additional scope. Ownership is not transferred and a copy will be made. + +.. seealso:: :py:func:`additionalExpressionContextScope` +%End + + const QgsExpressionContextScope *additionalExpressionContextScope() const; +%Docstring +Returns an additional expression context scope to be made available when calculating expressions. + +.. seealso:: :py:func:`setAdditionalExpressionContextScope` +%End + + QWidget *parentWidget() const; +%Docstring +Returns the widget which should be parented to tools dialogues. +%End + + void setParentWidget( QWidget *parent ); +%Docstring +Sets the widget which should be parented to tools' dialogues. + +:param parent: the widget actign as parent +%End + + bool showModal() const; +%Docstring +Returns whether tools' dialogues should be modal. +%End + + void setShowModal( bool modal ); +%Docstring +Sets whether tools' dialogues should be modal. +%End + + bool hideParent() const; +%Docstring +Returns whether the parent widget should be hidden when showing tools' dialogues. +%End + + void setHideParent( bool hide ); +%Docstring +Sets whether the parent widget should be hidden when showing tools' dialogues. +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/vector/qgsvectorlayertoolscontext.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index e0e0d9a1076f..c5361cba7cb8 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -771,6 +771,7 @@ %Include auto_generated/vector/qgsvectorlayerselectionproperties.sip %Include auto_generated/vector/qgsvectorlayertemporalproperties.sip %Include auto_generated/vector/qgsvectorlayertools.sip +%Include auto_generated/vector/qgsvectorlayertoolscontext.sip %Include auto_generated/vector/qgsvectorlayerundocommand.sip %Include auto_generated/vector/qgsvectorlayerundopassthroughcommand.sip %Include auto_generated/vector/qgsvectorlayerutils.sip diff --git a/src/app/qgsguivectorlayertools.cpp b/src/app/qgsguivectorlayertools.cpp index acd58434155e..7469c5e903ae 100644 --- a/src/app/qgsguivectorlayertools.cpp +++ b/src/app/qgsguivectorlayertools.cpp @@ -26,19 +26,20 @@ #include "qgsmessageviewer.h" #include "qgsvectorlayer.h" #include "qgsvectorlayerutils.h" +#include "qgsvectorlayertoolscontext.h" -bool QgsGuiVectorLayerTools::addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feat, QWidget *parentWidget, bool showModal, bool hideParent, QgsExpressionContextScope *scope ) const +bool QgsGuiVectorLayerTools::addFeatureV2( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feature, const QgsVectorLayerToolsContext &context ) const { - QgsFeature *f = feat; - if ( !feat ) + QgsFeature *f = feature; + if ( !feature ) f = new QgsFeature(); f->setGeometry( defaultGeometry ); - QgsFeatureAction *a = new QgsFeatureAction( tr( "Add feature" ), *f, layer, QUuid(), -1, parentWidget ); + QgsFeatureAction *a = new QgsFeatureAction( tr( "Add feature" ), *f, layer, QUuid(), -1, context.parentWidget() ); a->setForceSuppressFormPopup( forceSuppressFormPopup() ); connect( a, &QgsFeatureAction::addFeatureFinished, a, &QObject::deleteLater ); - const QgsFeatureAction::AddFeatureResult result = a->addFeature( defaultValues, showModal, std::unique_ptr( scope ), hideParent ); - if ( !feat ) + const QgsFeatureAction::AddFeatureResult result = a->addFeature( defaultValues, context.showModal(), std::unique_ptr( context.additionalExpressionContextScope() ? new QgsExpressionContextScope( *context.additionalExpressionContextScope() ) : nullptr ), context.hideParent() ); + if ( !feature ) delete f; switch ( result ) diff --git a/src/app/qgsguivectorlayertools.h b/src/app/qgsguivectorlayertools.h index 57be6215afeb..57c956c3b39c 100644 --- a/src/app/qgsguivectorlayertools.h +++ b/src/app/qgsguivectorlayertools.h @@ -40,15 +40,12 @@ class QgsGuiVectorLayerTools : public QgsVectorLayerTools * \param layer The layer to which the feature should be added * \param defaultValues Default values for the feature to add * \param defaultGeometry A default geometry to add to the feature - * \param feat A pointer to the feature - * \param parentWidget The widget calling this function to be passed to the used dialog - * \param showModal If the used dialog should be modal or not - * \param hideParent If the parent widget should be hidden, when the used dialog is opened - * \param scope A context scope to be used to calculate feature expression-based values + * \param feature A pointer to the feature + * \param context A context object to be used for e.g. to calculate feature expression-based values (since QGIS 3.38) * * \returns TRUE in case of success, FALSE if the operation failed/was aborted */ - bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feat = nullptr, QWidget *parentWidget = nullptr, bool showModal = true, bool hideParent = false, QgsExpressionContextScope *scope = nullptr ) const override; + bool addFeatureV2( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feature, const QgsVectorLayerToolsContext &context ) const override; /** * This should be called, whenever a vector layer should be switched to edit mode. If successful diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ac53902c20ad..7e550d220824 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -955,6 +955,7 @@ set(QGIS_CORE_SRCS vector/qgsvectorlayerselectionproperties.cpp vector/qgsvectorlayertemporalproperties.cpp vector/qgsvectorlayertools.cpp + vector/qgsvectorlayertoolscontext.cpp vector/qgsvectorlayerundocommand.cpp vector/qgsvectorlayerundopassthroughcommand.cpp vector/qgsvectorlayerutils.cpp @@ -2029,6 +2030,7 @@ set(QGIS_CORE_HDRS vector/qgsvectorlayerselectionproperties.h vector/qgsvectorlayertemporalproperties.h vector/qgsvectorlayertools.h + vector/qgsvectorlayertoolscontext.h vector/qgsvectorlayerundocommand.h vector/qgsvectorlayerundopassthroughcommand.h vector/qgsvectorlayerutils.h diff --git a/src/core/qgstrackedvectorlayertools.cpp b/src/core/qgstrackedvectorlayertools.cpp index b6b753b785b4..9a07efe6f766 100644 --- a/src/core/qgstrackedvectorlayertools.cpp +++ b/src/core/qgstrackedvectorlayertools.cpp @@ -13,18 +13,20 @@ * (at your option) any later version. * * * ***************************************************************************/ + #include "qgstrackedvectorlayertools.h" #include "qgsvectorlayer.h" +#include "qgsvectorlayertoolscontext.h" -bool QgsTrackedVectorLayerTools::addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feature, QWidget *parentWidget, bool showModal, bool hideParent, QgsExpressionContextScope *scope ) const +bool QgsTrackedVectorLayerTools::addFeatureV2( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feature, const QgsVectorLayerToolsContext &context ) const { QgsFeature *f = feature; if ( !feature ) f = new QgsFeature(); const_cast( mBackend )->setForceSuppressFormPopup( forceSuppressFormPopup() ); - if ( mBackend->addFeature( layer, defaultValues, defaultGeometry, f, parentWidget, showModal, hideParent, scope ) ) + if ( mBackend->addFeatureV2( layer, defaultValues, defaultGeometry, f, context ) ) { mAddedFeatures[layer].insert( f->id() ); if ( !feature ) diff --git a/src/core/qgstrackedvectorlayertools.h b/src/core/qgstrackedvectorlayertools.h index 64bb534394a4..760b7e2271e6 100644 --- a/src/core/qgstrackedvectorlayertools.h +++ b/src/core/qgstrackedvectorlayertools.h @@ -41,14 +41,11 @@ class CORE_EXPORT QgsTrackedVectorLayerTools : public QgsVectorLayerTools * \param defaultValues Default values for the feature to add * \param defaultGeometry A default geometry to add to the feature * \param feature A pointer to the feature - * \param parentWidget The widget calling this function to be passed to the used dialog - * \param showModal If the used dialog should be modal or not - * \param hideParent If the parent widget should be hidden, when the used dialog is opened - * \param scope A context scope to be used to calculate feature expression-based values + * \param context A context object to be used for e.g. to calculate feature expression-based values (since QGIS 3.38) * * \returns TRUE in case of success, FALSE if the operation failed/was aborted */ - bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feature, QWidget *parentWidget = nullptr, bool showModal = true, bool hideParent = false, QgsExpressionContextScope *scope = nullptr ) const override; + bool addFeatureV2( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues, const QgsGeometry &defaultGeometry, QgsFeature *feature SIP_OUT, const QgsVectorLayerToolsContext &context ) const override; bool startEditing( QgsVectorLayer *layer ) const override; bool stopEditing( QgsVectorLayer *layer, bool allowCancel ) const override; bool saveEdits( QgsVectorLayer *layer ) const override; diff --git a/src/core/vector/qgsvectorlayer.cpp b/src/core/vector/qgsvectorlayer.cpp index 0717519fe834..12af50d7fdfb 100644 --- a/src/core/vector/qgsvectorlayer.cpp +++ b/src/core/vector/qgsvectorlayer.cpp @@ -3398,7 +3398,7 @@ bool QgsVectorLayer::changeGeometry( QgsFeatureId fid, QgsGeometry &geom, bool s } -bool QgsVectorLayer::changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue, bool skipDefaultValues, QgsExpressionContext *context ) +bool QgsVectorLayer::changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue, bool skipDefaultValues, QgsVectorLayerToolsContext *context ) { QGIS_PROTECT_QOBJECT_THREAD_ACCESS @@ -3426,12 +3426,12 @@ bool QgsVectorLayer::changeAttributeValue( QgsFeatureId fid, int field, const QV } if ( result && !skipDefaultValues && !mDefaultValueOnUpdateFields.isEmpty() ) - updateDefaultValues( fid, QgsFeature(), context ); + updateDefaultValues( fid, QgsFeature(), context ? context->expressionContext() : nullptr ); return result; } -bool QgsVectorLayer::changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues, bool skipDefaultValues, QgsExpressionContext *context ) +bool QgsVectorLayer::changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues, bool skipDefaultValues, QgsVectorLayerToolsContext *context ) { QGIS_PROTECT_QOBJECT_THREAD_ACCESS @@ -3488,7 +3488,7 @@ bool QgsVectorLayer::changeAttributeValues( QgsFeatureId fid, const QgsAttribute if ( result && !skipDefaultValues && !mDefaultValueOnUpdateFields.isEmpty() ) { - updateDefaultValues( fid, QgsFeature(), context ); + updateDefaultValues( fid, QgsFeature(), context ? context->expressionContext() : nullptr ); } return result; diff --git a/src/core/vector/qgsvectorlayer.h b/src/core/vector/qgsvectorlayer.h index 12566cd4cac7..496e05370ea8 100644 --- a/src/core/vector/qgsvectorlayer.h +++ b/src/core/vector/qgsvectorlayer.h @@ -33,6 +33,7 @@ #include "qgsfeaturesource.h" #include "qgsfields.h" #include "qgsvectordataprovider.h" +#include "qgsvectorlayertoolscontext.h" #include "qgsvectorsimplifymethod.h" #include "qgseditformconfig.h" #include "qgsattributetableconfig.h" @@ -1735,7 +1736,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte * If \a skipDefaultValues is set to TRUE, default field values will not * be updated. This can be used to override default field value expressions. * - * If \a context is provided, it will be used when updating default values. + * If \a context is provided, it will be used when updating default values (since QGIS 3.38). * * \returns TRUE if the feature's attribute was successfully changed. * @@ -1749,7 +1750,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte * \see changeGeometry() * \see updateFeature() */ - Q_INVOKABLE bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant(), bool skipDefaultValues = false, QgsExpressionContext *context = nullptr ); + Q_INVOKABLE bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant(), bool skipDefaultValues = false, QgsVectorLayerToolsContext *context = nullptr ); /** * Changes attributes' values for a feature (but does not immediately @@ -1770,7 +1771,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte * be updated. This can be used to override default field value * expressions. * - * If \a context is provided, it will be used when updating default values. + * If \a context is provided, it will be used when updating default values (since QGIS 3.38). * * \returns TRUE if feature's attributes was successfully changed. * @@ -1787,7 +1788,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte * \see changeAttributeValue() * */ - Q_INVOKABLE bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues = QgsAttributeMap(), bool skipDefaultValues = false, QgsExpressionContext *context = nullptr ); + Q_INVOKABLE bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues = QgsAttributeMap(), bool skipDefaultValues = false, QgsVectorLayerToolsContext *context = nullptr ); /** * Add an attribute field (but does not commit it) diff --git a/src/core/vector/qgsvectorlayertools.h b/src/core/vector/qgsvectorlayertools.h index d8cc9e1386eb..780bd2a4e46e 100644 --- a/src/core/vector/qgsvectorlayertools.h +++ b/src/core/vector/qgsvectorlayertools.h @@ -23,6 +23,7 @@ #include "qgsexpressioncontext.h" #include "qgsfeature.h" #include "qgsgeometry.h" +#include "qgsvectorlayertoolscontext.h" class QgsFeatureRequest; class QgsVectorLayer; @@ -57,11 +58,37 @@ class CORE_EXPORT QgsVectorLayerTools : public QObject * \param parentWidget The widget calling this function to be passed to the used dialog * \param showModal If the used dialog should be modal or not * \param hideParent If the parent widget should be hidden, when the used dialog is opened - * \param scope A context scope to be used to calculate feature expression-based values - * \returns TRUE in case of success, FALSE if the operation failed/was aborted + * \returns TRUE in case of success, FALSE if the operation failed/was aborted * + * \note addFeature or addFeatureV2 must be overwritten when implementing a class inheriting from QgsVectorLayerTools */ - virtual bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues = QgsAttributeMap(), const QgsGeometry &defaultGeometry = QgsGeometry(), QgsFeature *feature SIP_OUT = nullptr, QWidget *parentWidget = nullptr, bool showModal = true, bool hideParent = false, QgsExpressionContextScope *scope = nullptr ) const = 0; + virtual bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues = QgsAttributeMap(), const QgsGeometry &defaultGeometry = QgsGeometry(), QgsFeature *feature SIP_OUT = nullptr, QWidget *parentWidget = nullptr, bool showModal = true, bool hideParent = false ) const + { + QgsVectorLayerToolsContext context; + context.setParentWidget( parentWidget ); + context.setShowModal( showModal ); + context.setHideParent( hideParent ); + return addFeatureV2( layer, defaultValues, defaultGeometry, feature, context ); + } + + /** + * This method should/will be called, whenever a new feature will be added to the layer + * + * \param layer The layer to which the feature should be added + * \param defaultValues Default values for the feature to add + * \param defaultGeometry A default geometry to add to the feature + * \param feature Updated feature after adding will be written back to this + * \param context A context object to be used for e.g. to calculate feature expression-based values (since QGIS 3.38) + * \returns TRUE in case of success, FALSE if the operation failed/was aborted + * + * \note addFeature or addFeatureV2 must be overwritten when implementing a class inheriting from QgsVectorLayerTools + * \since QGIS 3.38 + */ + virtual bool addFeatureV2( QgsVectorLayer *layer, const QgsAttributeMap &defaultValues = QgsAttributeMap(), const QgsGeometry &defaultGeometry = QgsGeometry(), QgsFeature *feature SIP_OUT = nullptr, const QgsVectorLayerToolsContext &context = QgsVectorLayerToolsContext() ) const + { + Q_UNUSED( context ) + return addFeature( layer, defaultValues, defaultGeometry, feature, context.parentWidget(), context.showModal(), context.hideParent() ); + } // TODO QGIS 4: remove const qualifier diff --git a/src/core/vector/qgsvectorlayertoolscontext.cpp b/src/core/vector/qgsvectorlayertoolscontext.cpp new file mode 100644 index 000000000000..03c1e074dd8f --- /dev/null +++ b/src/core/vector/qgsvectorlayertoolscontext.cpp @@ -0,0 +1,78 @@ +/*************************************************************************** + qgsvectorlayertoolscontext.cpp + ------------------------ + begin : May 2024 + copyright : (C) 2024 by Mathieu Pellerin + email : mathieu at opengis dot ch + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsvectorlayertoolscontext.h" +#include "qgsexpressioncontextutils.h" + +QgsVectorLayerToolsContext::QgsVectorLayerToolsContext( const QgsVectorLayerToolsContext &other ) + : mParentWidget( other.mParentWidget ) + , mShowModal( other.mShowModal ) + , mHideParent( other.mHideParent ) +{ + if ( other.mAdditionalExpressionContextScope ) + { + mAdditionalExpressionContextScope.reset( new QgsExpressionContextScope( *other.mAdditionalExpressionContextScope ) ); + } + if ( other.mExpressionContext ) + { + mExpressionContext.reset( new QgsExpressionContext( *other.mExpressionContext ) ); + } +} + +QgsVectorLayerToolsContext &QgsVectorLayerToolsContext::operator=( const QgsVectorLayerToolsContext &other ) +{ + mParentWidget = other.mParentWidget; + mShowModal = other.mShowModal; + mHideParent = other.mHideParent; + if ( other.mAdditionalExpressionContextScope ) + { + mAdditionalExpressionContextScope.reset( new QgsExpressionContextScope( *other.mAdditionalExpressionContextScope ) ); + } + if ( other.mExpressionContext ) + { + mExpressionContext.reset( new QgsExpressionContext( *other.mExpressionContext ) ); + } + else + { + mExpressionContext.reset(); + } + return *this; +} + +void QgsVectorLayerToolsContext::setExpressionContext( const QgsExpressionContext *context ) +{ + if ( context ) + mExpressionContext.reset( new QgsExpressionContext( *context ) ); + else + mExpressionContext.reset(); +} + +QgsExpressionContext *QgsVectorLayerToolsContext::expressionContext() const +{ + return mExpressionContext.get(); +} + +void QgsVectorLayerToolsContext::setAdditionalExpressionContextScope( const QgsExpressionContextScope *scope ) +{ + if ( scope ) + mAdditionalExpressionContextScope.reset( new QgsExpressionContextScope( *scope ) ); + else + mAdditionalExpressionContextScope.reset(); +} + +const QgsExpressionContextScope *QgsVectorLayerToolsContext::additionalExpressionContextScope() const +{ + return mAdditionalExpressionContextScope.get(); +} diff --git a/src/core/vector/qgsvectorlayertoolscontext.h b/src/core/vector/qgsvectorlayertoolscontext.h new file mode 100644 index 000000000000..d2074c608b8d --- /dev/null +++ b/src/core/vector/qgsvectorlayertoolscontext.h @@ -0,0 +1,117 @@ +/*************************************************************************** + qgsvectorlayertoolscontext.h + ------------------------ + begin : May 2024 + copyright : (C) 2024 by Mathieu Pellerin + email : mathieu at opengis dot ch + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSVECTORLAYERTOOLSCONTEXT_H +#define QGSVECTORLAYERTOOLSCONTEXT_H + +#include "qgsexpressioncontext.h" +#include "qgis_core.h" + +#include + +/** + * \ingroup core + * \class QgsVectorLayerToolsContext + * \brief Contains settings which reflect the context in which vector layer tool operations should + * consider. + * \since QGIS 3.38 + */ +class CORE_EXPORT QgsVectorLayerToolsContext +{ + public: + + /** + * Constructor for QgsVectorLayerToolsContext. + */ + QgsVectorLayerToolsContext() = default; + + /** + * Copy constructor. + * \param other source QgsVectorLayerToolsContext + */ + QgsVectorLayerToolsContext( const QgsVectorLayerToolsContext &other ); + + QgsVectorLayerToolsContext &operator=( const QgsVectorLayerToolsContext &other ); + + /** + * Sets the optional expression context used by the vector layer tools. + * \param context expression context pointer. Ownership is not transferred. + * \see expressionContext() + * \see setAdditionalExpressionContextScope() + */ + void setExpressionContext( const QgsExpressionContext *context ); + + /** + * Returns the optional expression context used by the vector layer tools. + * \see setExpressionContext() + * \see additionalExpressionContextScope() + */ + QgsExpressionContext *expressionContext() const; + + /** + * Sets an additional expression context scope to be made available when calculating expressions. + * \param scope additional scope. Ownership is not transferred and a copy will be made. + * \see additionalExpressionContextScope() + */ + void setAdditionalExpressionContextScope( const QgsExpressionContextScope *scope ); + + /** + * Returns an additional expression context scope to be made available when calculating expressions. + * \see setAdditionalExpressionContextScope() + */ + const QgsExpressionContextScope *additionalExpressionContextScope() const; + + /** + * Returns the widget which should be parented to tools dialogues. + */ + QWidget *parentWidget() const { return mParentWidget; } + + /** + * Sets the widget which should be parented to tools' dialogues. + * \param parent the widget actign as parent + */ + void setParentWidget( QWidget *parent ) { mParentWidget = parent; } + + /** + * Returns whether tools' dialogues should be modal. + */ + bool showModal() const { return mShowModal; } + + /** + * Sets whether tools' dialogues should be modal. + */ + void setShowModal( bool modal ) { mShowModal = modal; } + + /** + * Returns whether the parent widget should be hidden when showing tools' dialogues. + */ + bool hideParent() const { return mHideParent; } + + /** + * Sets whether the parent widget should be hidden when showing tools' dialogues. + */ + void setHideParent( bool hide ) { mHideParent = hide; } + + private: + + std::unique_ptr< QgsExpressionContext > mExpressionContext; + std::unique_ptr< QgsExpressionContextScope > mAdditionalExpressionContextScope; + + QWidget *mParentWidget = nullptr; + bool mShowModal = true; + bool mHideParent = false; +}; + +#endif // QGSVECTORLAYERTOOLSCONTEXT_H diff --git a/src/gui/qgsabstractrelationeditorwidget.cpp b/src/gui/qgsabstractrelationeditorwidget.cpp index 808a73010be7..b82081be896f 100644 --- a/src/gui/qgsabstractrelationeditorwidget.cpp +++ b/src/gui/qgsabstractrelationeditorwidget.cpp @@ -287,9 +287,14 @@ QgsFeatureIds QgsAbstractRelationEditorWidget::addFeature( const QgsGeometry &ge for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs ) keyAttrs.insert( fields.indexFromName( fieldPair.referencingField() ), mFeatureList.first().attribute( fieldPair.referencedField() ) ); - QgsExpressionContextScope *scope = QgsExpressionContextUtils::parentFormScope( mFeatureList.first(), mEditorContext.attributeFormModeString() ); + QgsVectorLayerToolsContext context; + context.setParentWidget( this ); + context.setShowModal( true ); + context.setHideParent( true ); + std::unique_ptr scope( QgsExpressionContextUtils::parentFormScope( mFeatureList.first(), mEditorContext.attributeFormModeString() ) ); + context.setAdditionalExpressionContextScope( scope.get() ); QgsFeature linkFeature; - if ( !vlTools->addFeature( mRelation.referencingLayer(), keyAttrs, geometry, &linkFeature, this, true, true, scope ) ) + if ( !vlTools->addFeatureV2( mRelation.referencingLayer(), keyAttrs, geometry, &linkFeature, context ) ) return QgsFeatureIds(); addedFeatureIds.insert( linkFeature.id() ); diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index a4e64074961e..1f206899fb3f 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -44,6 +44,7 @@ #include "qgstabwidget.h" #include "qgsscrollarea.h" #include "qgsvectorlayerjoinbuffer.h" +#include "qgsvectorlayertoolscontext.h" #include "qgsvectorlayerutils.h" #include "qgsactionwidgetwrapper.h" #include "qgsqmlwidgetwrapper.h" @@ -463,8 +464,10 @@ bool QgsAttributeForm::saveEdits( QString *error ) n++; } - QgsExpressionContext context = createExpressionContext( updatedFeature ); - success = mLayer->changeAttributeValues( mFeature.id(), newValues, oldValues, false, &context ); + std::unique_ptr context = std::make_unique(); + QgsExpressionContext expressionContext = createExpressionContext( updatedFeature ); + context->setExpressionContext( &expressionContext ); + success = mLayer->changeAttributeValues( mFeature.id(), newValues, oldValues, false, context.get() ); if ( success && n > 0 ) { diff --git a/tests/src/gui/testqgsrelationreferencewidget.cpp b/tests/src/gui/testqgsrelationreferencewidget.cpp index 9530dfcfabf0..80f7da84e602 100644 --- a/tests/src/gui/testqgsrelationreferencewidget.cpp +++ b/tests/src/gui/testqgsrelationreferencewidget.cpp @@ -31,6 +31,7 @@ #include "qgsgui.h" #include "qgsmapcanvas.h" #include "qgsvectorlayertools.h" +#include "qgsvectorlayertoolscontext.h" #include "qgsadvanceddigitizingdockwidget.h" #include "qgsmaptooldigitizefeature.h" @@ -609,12 +610,9 @@ void TestQgsRelationReferenceWidget::testIdentifyOnMap() // referenced layer class DummyVectorLayerTools : public QgsVectorLayerTools // clazy:exclude=missing-qobject-macro { - bool addFeature( QgsVectorLayer *layer, const QgsAttributeMap &, const QgsGeometry &, QgsFeature *feat = nullptr, QWidget *parentWidget = nullptr, bool showModal = true, bool hideParent = false, QgsExpressionContextScope *scope = nullptr ) const override + bool addFeatureV2( QgsVectorLayer *layer, const QgsAttributeMap &, const QgsGeometry &, QgsFeature *feat, const QgsVectorLayerToolsContext &context ) const override { - Q_UNUSED( parentWidget ); - Q_UNUSED( showModal ); - Q_UNUSED( hideParent ); - Q_UNUSED( scope ); + Q_UNUSED( context ); feat->setAttribute( QStringLiteral( "pk" ), 13 ); feat->setAttribute( QStringLiteral( "material" ), QStringLiteral( "steel" ) ); feat->setAttribute( QStringLiteral( "diameter" ), 140 ); diff --git a/tests/src/python/test_qgsrelationeditwidget.py b/tests/src/python/test_qgsrelationeditwidget.py index cf69ff3d44be..ece758559334 100644 --- a/tests/src/python/test_qgsrelationeditwidget.py +++ b/tests/src/python/test_qgsrelationeditwidget.py @@ -341,7 +341,7 @@ def test_add_feature_geometry(self): # Mock vector layer tool to just set default value on created feature class DummyVlTools(QgsVectorLayerTools): - def addFeature(self, layer, defaultValues, defaultGeometry, parentWidget=None, showModal=True, hideParent=False, scope=None): + def addFeature(self, layer, defaultValues, defaultGeometry, parentWidget=None, showModal=True, hideParent=False): f = QgsFeature(layer.fields()) for idx, value in defaultValues.items(): f.setAttribute(idx, value) @@ -440,7 +440,7 @@ def setValues(self, values): """ self.values = values - def addFeature(self, layer, defaultValues, defaultGeometry, parentWidget=None, showModal=True, hideParent=False, scope=None): + def addFeature(self, layer, defaultValues, defaultGeometry, parentWidget=None, showModal=True, hideParent=False): """ Overrides the addFeature method :param layer: vector layer diff --git a/tests/src/python/test_qgsvectorlayer.py b/tests/src/python/test_qgsvectorlayer.py index 3832d8a27a18..881578a0bb0f 100644 --- a/tests/src/python/test_qgsvectorlayer.py +++ b/tests/src/python/test_qgsvectorlayer.py @@ -78,6 +78,7 @@ QgsVectorLayerJoinInfo, QgsVectorLayerSelectedFeatureSource, QgsVectorLayerSimpleLabeling, + QgsVectorLayerToolsContext, QgsWkbTypes, ) from qgis.gui import QgsAttributeTableModel, QgsGui @@ -1162,8 +1163,10 @@ def test_ChangeAttributeValuesWithContext(self): layer.startEditing() - context = layer.createExpressionContext() - context.appendScope(QgsExpressionContextUtils.parentFormScope(pf)) + expressionContext = layer.createExpressionContext() + expressionContext.appendScope(QgsExpressionContextUtils.parentFormScope(pf)) + context = QgsVectorLayerToolsContext() + context.setExpressionContext(expressionContext) self.assertTrue(layer.changeAttributeValues(fid, {1: 100}, {}, False, context)) f = layer.getFeature(1)