diff --git a/python/core/auto_generated/qgsexpressioncontext.sip.in b/python/core/auto_generated/qgsexpressioncontext.sip.in index e1074b35b09..8fcb1e27614 100644 --- a/python/core/auto_generated/qgsexpressioncontext.sip.in +++ b/python/core/auto_generated/qgsexpressioncontext.sip.in @@ -333,6 +333,49 @@ Removes any feature associated with the scope. .. seealso:: :py:func:`hasFeature` .. versionadded:: 3.0 +%End + + bool hasGeometry() const; +%Docstring +Returns ``True`` if the scope has a geometry associated with it. + +.. seealso:: :py:func:`geometry` + +.. versionadded:: 3.24 +%End + + QgsGeometry geometry() const; +%Docstring +Sets the geometry associated with the scope. + +.. seealso:: :py:func:`setGeometry` + +.. seealso:: :py:func:`hasGeometry` + +.. versionadded:: 3.24 +%End + + void setGeometry( const QgsGeometry &geometry ); +%Docstring +Convenience function for setting a ``geometry`` for the scope. Any existing +geometry set by the scope will be overwritten. + +.. seealso:: :py:func:`removeGeometry` + +.. seealso:: :py:func:`geometry` + +.. versionadded:: 3.24 +%End + + void removeGeometry(); +%Docstring +Removes any geometry associated with the scope. + +.. seealso:: :py:func:`setGeometry` + +.. seealso:: :py:func:`hasGeometry` + +.. versionadded:: 3.24 %End void setFields( const QgsFields &fields ); @@ -692,6 +735,35 @@ Returns ``True`` if the context has a feature associated with it. Convenience function for retrieving the feature for the context, if set. .. seealso:: :py:func:`setFeature` +%End + + void setGeometry( const QgsGeometry &geometry ); +%Docstring +Convenience function for setting a ``geometry`` for the context. The geometry +will be set within the last scope of the context, so will override any +existing geometries within the context. + +.. seealso:: :py:func:`geometry` + +.. versionadded:: 3.24 +%End + + bool hasGeometry() const; +%Docstring +Returns ``True`` if the context has a geometry associated with it. + +.. seealso:: :py:func:`geometry` + +.. versionadded:: 3.24 +%End + + QgsGeometry geometry() const; +%Docstring +Convenience function for retrieving the geometry for the context, if set. + +.. seealso:: :py:func:`setGeometry` + +.. versionadded:: 3.24 %End void setFields( const QgsFields &fields ); diff --git a/src/core/qgsexpressioncontext.cpp b/src/core/qgsexpressioncontext.cpp index 82a1b034bb0..4070bb12d03 100644 --- a/src/core/qgsexpressioncontext.cpp +++ b/src/core/qgsexpressioncontext.cpp @@ -45,6 +45,8 @@ QgsExpressionContextScope::QgsExpressionContextScope( const QgsExpressionContext , mVariables( other.mVariables ) , mHasFeature( other.mHasFeature ) , mFeature( other.mFeature ) + , mHasGeometry( other.mHasGeometry ) + , mGeometry( other.mGeometry ) { QHash::const_iterator it = other.mFunctions.constBegin(); for ( ; it != other.mFunctions.constEnd(); ++it ) @@ -59,6 +61,8 @@ QgsExpressionContextScope &QgsExpressionContextScope::operator=( const QgsExpres mVariables = other.mVariables; mHasFeature = other.mHasFeature; mFeature = other.mFeature; + mHasGeometry = other.mHasGeometry; + mGeometry = other.mGeometry; qDeleteAll( mFunctions ); mFunctions.clear(); @@ -529,8 +533,7 @@ void QgsExpressionContext::setFeature( const QgsFeature &feature ) bool QgsExpressionContext::hasFeature() const { - const auto constMStack = mStack; - for ( const QgsExpressionContextScope *scope : constMStack ) + for ( const QgsExpressionContextScope *scope : mStack ) { if ( scope->hasFeature() ) return true; @@ -551,6 +554,37 @@ QgsFeature QgsExpressionContext::feature() const return QgsFeature(); } +void QgsExpressionContext::setGeometry( const QgsGeometry &geometry ) +{ + if ( mStack.isEmpty() ) + mStack.append( new QgsExpressionContextScope() ); + + mStack.last()->setGeometry( geometry ); +} + +bool QgsExpressionContext::hasGeometry() const +{ + for ( const QgsExpressionContextScope *scope : mStack ) + { + if ( scope->hasGeometry() ) + return true; + } + return false; +} + +QgsGeometry QgsExpressionContext::geometry() const +{ + //iterate through stack backwards, so that higher priority variables take precedence + QList< QgsExpressionContextScope * >::const_iterator it = mStack.constEnd(); + while ( it != mStack.constBegin() ) + { + --it; + if ( ( *it )->hasGeometry() ) + return ( *it )->geometry(); + } + return QgsGeometry(); +} + void QgsExpressionContext::setFields( const QgsFields &fields ) { if ( mStack.isEmpty() ) diff --git a/src/core/qgsexpressioncontext.h b/src/core/qgsexpressioncontext.h index 6da3a0bf5cf..ee8759a968c 100644 --- a/src/core/qgsexpressioncontext.h +++ b/src/core/qgsexpressioncontext.h @@ -326,6 +326,39 @@ class CORE_EXPORT QgsExpressionContextScope */ void removeFeature() { mHasFeature = false; mFeature = QgsFeature(); } + /** + * Returns TRUE if the scope has a geometry associated with it. + * \see geometry() + * \since QGIS 3.24 + */ + bool hasGeometry() const { return mHasGeometry; } + + /** + * Sets the geometry associated with the scope. + * \see setGeometry() + * \see hasGeometry() + * \since QGIS 3.24 + */ + QgsGeometry geometry() const { return mGeometry; } + + /** + * Convenience function for setting a \a geometry for the scope. Any existing + * geometry set by the scope will be overwritten. + + * \see removeGeometry() + * \see geometry() + * \since QGIS 3.24 + */ + void setGeometry( const QgsGeometry &geometry ) { mHasGeometry = true; mGeometry = geometry; } + + /** + * Removes any geometry associated with the scope. + * \see setGeometry() + * \see hasGeometry() + * \since QGIS 3.24 + */ + void removeGeometry() { mHasGeometry = false; mGeometry = QgsGeometry(); } + /** * Convenience function for setting a fields for the scope. Any existing * fields set by the scope will be overwritten. @@ -353,6 +386,8 @@ class CORE_EXPORT QgsExpressionContextScope QHash mFunctions; bool mHasFeature = false; QgsFeature mFeature; + bool mHasGeometry = false; + QgsGeometry mGeometry; }; /** @@ -650,6 +685,30 @@ class CORE_EXPORT QgsExpressionContext */ QgsFeature feature() const; + /** + * Convenience function for setting a \a geometry for the context. The geometry + * will be set within the last scope of the context, so will override any + * existing geometries within the context. + + * \see geometry() + * \since QGIS 3.24 + */ + void setGeometry( const QgsGeometry &geometry ); + + /** + * Returns TRUE if the context has a geometry associated with it. + * \see geometry() + * \since QGIS 3.24 + */ + bool hasGeometry() const; + + /** + * Convenience function for retrieving the geometry for the context, if set. + * \see setGeometry() + * \since QGIS 3.24 + */ + QgsGeometry geometry() const; + /** * Convenience function for setting a fields for the context. The fields * will be set within the last scope of the context, so will override any diff --git a/tests/src/core/testqgsexpressioncontext.cpp b/tests/src/core/testqgsexpressioncontext.cpp index 314097a215b..0c2ee0e2dec 100644 --- a/tests/src/core/testqgsexpressioncontext.cpp +++ b/tests/src/core/testqgsexpressioncontext.cpp @@ -44,6 +44,7 @@ class TestQgsExpressionContext : public QObject void contextStackFunctions(); void evaluate(); void setFeature(); + void setGeometry(); void setFields(); void takeScopes(); void highlighted(); @@ -486,6 +487,35 @@ void TestQgsExpressionContext::setFeature() QCOMPARE( contextWithScope.feature().id(), 50LL ); } +void TestQgsExpressionContext::setGeometry() +{ + QgsGeometry g( QgsGeometry::fromPointXY( QgsPointXY( 1, 2 ) ) ); + QgsExpressionContextScope scope; + scope.setGeometry( g ); + QVERIFY( scope.hasGeometry() ); + QCOMPARE( scope.geometry().asWkt(), QStringLiteral( "Point (1 2)" ) ); + scope.removeGeometry(); + QVERIFY( !scope.hasGeometry() ); + QVERIFY( scope.geometry().isNull() ); + + //test setting a geometry in a context with no scopes + QgsExpressionContext emptyContext; + QVERIFY( !emptyContext.hasGeometry() ); + QVERIFY( emptyContext.geometry().isNull() ); + emptyContext.setGeometry( g ); + //setGeometry should have created a scope + QCOMPARE( emptyContext.scopeCount(), 1 ); + QVERIFY( emptyContext.hasGeometry() ); + QCOMPARE( emptyContext.geometry().asWkt(), QStringLiteral( "Point (1 2)" ) ); + + QgsExpressionContext contextWithScope; + contextWithScope << new QgsExpressionContextScope(); + contextWithScope.setGeometry( g ); + QCOMPARE( contextWithScope.scopeCount(), 1 ); + QVERIFY( contextWithScope.hasGeometry() ); + QCOMPARE( contextWithScope.geometry().asWkt(), QStringLiteral( "Point (1 2)" ) ); +} + void TestQgsExpressionContext::setFields() { QgsFields fields;