Merge pull request #57271 from nirvn/current_parent

[attribute form] Add parent feature scope when adding/editing a child feature through the relation editor widget
This commit is contained in:
Mathieu Pellerin 2024-06-03 13:39:10 +07:00 committed by GitHub
commit 2c38dc8cbd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 621 additions and 54 deletions

View File

@ -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 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,12 +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 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;

View File

@ -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, 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,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 (since QGIS 3.38).
: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, QgsVectorLayerToolsContext *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 (since QGIS 3.38).
:return: ``True`` if feature's attributes was successfully changed.
.. note::

View File

@ -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 ) const;
%Docstring
This method should/will be called, whenever a new feature will be added to the layer
@ -41,6 +41,29 @@ This method should/will be called, whenever a new feature will be added to the l
: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

View File

@ -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 *
************************************************************************/

View File

@ -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

View File

@ -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 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,12 +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 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;

View File

@ -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, 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,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 (since QGIS 3.38).
: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, QgsVectorLayerToolsContext *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 (since QGIS 3.38).
:return: ``True`` if feature's attributes was successfully changed.
.. note::

View File

@ -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 ) const;
%Docstring
This method should/will be called, whenever a new feature will be added to the layer
@ -41,6 +41,29 @@ This method should/will be called, whenever a new feature will be added to the l
: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

View File

@ -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 *
************************************************************************/

View File

@ -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

View File

@ -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 );

View File

@ -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 ) 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, nullptr, hideParent );
if ( !feat )
const QgsFeatureAction::AddFeatureResult result = a->addFeature( defaultValues, context.showModal(), std::unique_ptr<QgsExpressionContextScope>( context.additionalExpressionContextScope() ? new QgsExpressionContextScope( *context.additionalExpressionContextScope() ) : nullptr ), context.hideParent() );
if ( !feature )
delete f;
switch ( result )

View File

@ -40,14 +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 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
* \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 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

View File

@ -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

View File

@ -13,19 +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 ) 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<QgsVectorLayerTools *>( mBackend )->setForceSuppressFormPopup( forceSuppressFormPopup() );
if ( mBackend->addFeature( layer, defaultValues, defaultGeometry, f, parentWidget, showModal, hideParent ) )
if ( mBackend->addFeatureV2( layer, defaultValues, defaultGeometry, f, context ) )
{
mAddedFeatures[layer].insert( f->id() );
if ( !feature )

View File

@ -18,6 +18,7 @@
#include "qgis_core.h"
#include "qgsvectorlayertools.h"
#include "qgsexpressioncontext.h"
/**
* \ingroup core
@ -40,13 +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 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 ) 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;

View File

@ -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, 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 );
updateDefaultValues( fid, QgsFeature(), context ? context->expressionContext() : nullptr );
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, 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 );
updateDefaultValues( fid, QgsFeature(), context ? context->expressionContext() : nullptr );
}
return result;

View File

@ -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,6 +1736,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 (since QGIS 3.38).
*
* \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 +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 );
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
@ -1768,6 +1771,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 (since QGIS 3.38).
*
* \returns TRUE if feature's attributes was successfully changed.
*
* \note Calls to changeAttributeValues() are only valid for layers in
@ -1783,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 );
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)
@ -2794,7 +2799,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

View File

@ -20,8 +20,10 @@
#include "qgis_sip.h"
#include <QObject>
#include "qgsexpressioncontext.h"
#include "qgsfeature.h"
#include "qgsgeometry.h"
#include "qgsvectorlayertoolscontext.h"
class QgsFeatureRequest;
class QgsVectorLayer;
@ -56,10 +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
* \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 ) 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

View File

@ -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();
}

View File

@ -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 <memory>
/**
* \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

View File

@ -352,6 +352,7 @@ QgsExpressionContext QgsAttributeTypeDialog::createExpressionContext() const
<< QgsExpressionContextUtils::projectScope( QgsProject::instance() )
<< QgsExpressionContextUtils::layerScope( mLayer )
<< QgsExpressionContextUtils::formScope( )
<< QgsExpressionContextUtils::parentFormScope( QgsFeature() )
<< QgsExpressionContextUtils::mapToolCaptureScope( QList<QgsPointLocator::Match>() );
return context;

View File

@ -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,14 @@ QgsFeatureIds QgsAbstractRelationEditorWidget::addFeature( const QgsGeometry &ge
for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs )
keyAttrs.insert( fields.indexFromName( fieldPair.referencingField() ), mFeatureList.first().attribute( fieldPair.referencedField() ) );
QgsVectorLayerToolsContext context;
context.setParentWidget( this );
context.setShowModal( true );
context.setHideParent( true );
std::unique_ptr<QgsExpressionContextScope> scope( QgsExpressionContextUtils::parentFormScope( mFeatureList.first(), mEditorContext.attributeFormModeString() ) );
context.setAdditionalExpressionContextScope( scope.get() );
QgsFeature linkFeature;
if ( !vlTools->addFeature( mRelation.referencingLayer(), keyAttrs, geometry, &linkFeature, this, true, true ) )
if ( !vlTools->addFeatureV2( mRelation.referencingLayer(), keyAttrs, geometry, &linkFeature, context ) )
return QgsFeatureIds();
addedFeatureIds.insert( linkFeature.id() );

View File

@ -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"
@ -353,7 +354,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 +464,10 @@ bool QgsAttributeForm::saveEdits( QString *error )
n++;
}
success = mLayer->changeAttributeValues( mFeature.id(), newValues, oldValues );
std::unique_ptr<QgsVectorLayerToolsContext> context = std::make_unique<QgsVectorLayerToolsContext>();
QgsExpressionContext expressionContext = createExpressionContext( updatedFeature );
context->setExpressionContext( &expressionContext );
success = mLayer->changeAttributeValues( mFeature.id(), newValues, oldValues, false, context.get() );
if ( success && n > 0 )
{
@ -569,6 +575,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 +990,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;
}

View File

@ -31,6 +31,7 @@
#include "qgsgui.h"
#include "qgsmapcanvas.h"
#include "qgsvectorlayertools.h"
#include "qgsvectorlayertoolscontext.h"
#include "qgsadvanceddigitizingdockwidget.h"
#include "qgsmaptooldigitizefeature.h"
@ -609,11 +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 ) 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( context );
feat->setAttribute( QStringLiteral( "pk" ), 13 );
feat->setAttribute( QStringLiteral( "material" ), QStringLiteral( "steel" ) );
feat->setAttribute( QStringLiteral( "diameter" ), 140 );

View File

@ -78,6 +78,7 @@ from qgis.core import (
QgsVectorLayerJoinInfo,
QgsVectorLayerSelectedFeatureSource,
QgsVectorLayerSimpleLabeling,
QgsVectorLayerToolsContext,
QgsWkbTypes,
)
from qgis.gui import QgsAttributeTableModel, QgsGui
@ -1139,6 +1140,38 @@ class TestQgsVectorLayer(QgisTestCase, FeatureSourceTestCase):
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()
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)
self.assertEqual(f.attributes(), ["Point (1 2)", 100])
def test_ChangeAttributeAfterAddFeature(self):
layer = createLayerWithOnePoint()
layer.dataProvider().deleteFeatures([1]) # no need for this feature