Skip to content

Commit 3e2b4fc

Browse files
authored
Merge pull request #3968 from nyalldawson/item_vars
[FEATURE] item_variables expression function inside compositions
2 parents a7806d1 + db1009c commit 3e2b4fc

File tree

8 files changed

+192
-0
lines changed

8 files changed

+192
-0
lines changed

python/core/qgsexpressioncontext.sip

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,21 @@ class QgsScopedExpressionFunction : QgsExpression::Function
2222
bool handlesNull = false,
2323
bool isContextual = true );
2424

25+
/**
26+
* Create a new QgsScopedExpressionFunction using named parameters.
27+
*
28+
* @note Added in QGIS 3.0
29+
*/
30+
QgsScopedExpressionFunction( const QString& fnname,
31+
const QgsExpression::ParameterList& params,
32+
const QString& group,
33+
const QString& helpText = QString(),
34+
bool usesGeometry = false,
35+
const QSet<QString>& referencedColumns = QSet<QString>(),
36+
bool lazyEval = false,
37+
bool handlesNull = false,
38+
bool isContextual = true );
39+
2540
virtual ~QgsScopedExpressionFunction();
2641

2742
virtual QVariant func( const QVariantList& values, const QgsExpressionContext* context, QgsExpression* parent ) = 0;
@@ -247,6 +262,13 @@ class QgsExpressionContext
247262
*/
248263
QVariant variable( const QString& name ) const;
249264

265+
/**
266+
* Returns a map of variable name to value representing all the expression variables
267+
* contained by the context.
268+
* @note added in QGIS 3.0
269+
*/
270+
QVariantMap variablesToMap() const;
271+
250272
/** Returns true if the specified variable name is intended to be highlighted to the
251273
* user. This is used by the expression builder to more prominently display the
252274
* variable.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "item_variables",
3+
"type": "function",
4+
"description": "Returns a map of variables from a composer item inside this composition.",
5+
"arguments": [ {"arg":"id","description":"composer item ID"}],
6+
"examples": [ { "expression":"map_get(item_variables('main_map'), 'map_scale')", "returns":"2000"}
7+
]
8+
}

src/core/composer/qgscomposermap.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1834,6 +1834,13 @@ QgsExpressionContext QgsComposerMap::createExpressionContext() const
18341834
QgsGeometry centerPoint = QgsGeometry::fromPoint( extent.center() );
18351835
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent_center" ), QVariant::fromValue( centerPoint ), true ) );
18361836

1837+
if ( mComposition )
1838+
{
1839+
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs" ), mComposition->mapSettings().destinationCrs().authid(), true ) );
1840+
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_definition" ), mComposition->mapSettings().destinationCrs().toProj4(), true ) );
1841+
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_units" ), QgsUnitTypes::toString( mComposition->mapSettings().mapUnits() ), true ) );
1842+
}
1843+
18371844
context.appendScope( scope );
18381845

18391846
return context;

src/core/qgsexpression.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5825,6 +5825,9 @@ void QgsExpression::initVariableHelp()
58255825
gVariableHelpTexts.insert( QStringLiteral( "map_extent_center" ), QCoreApplication::translate( "variable_help", "Center of map." ) );
58265826
gVariableHelpTexts.insert( QStringLiteral( "map_extent_width" ), QCoreApplication::translate( "variable_help", "Width of map." ) );
58275827
gVariableHelpTexts.insert( QStringLiteral( "map_extent_height" ), QCoreApplication::translate( "variable_help", "Height of map." ) );
5828+
gVariableHelpTexts.insert( QStringLiteral( "map_crs" ), QCoreApplication::translate( "variable_help", "Coordinate reference system of map (e.g., 'EPSG:4326')." ) );
5829+
gVariableHelpTexts.insert( QStringLiteral( "map_crs_definition" ), QCoreApplication::translate( "variable_help", "Coordinate reference system of map (full definition)." ) );
5830+
gVariableHelpTexts.insert( QStringLiteral( "map_units" ), QCoreApplication::translate( "variable_help", "Units for map measurements." ) );
58285831

58295832
gVariableHelpTexts.insert( QStringLiteral( "row_number" ), QCoreApplication::translate( "variable_help", "Stores the number of the current row." ) );
58305833
gVariableHelpTexts.insert( QStringLiteral( "grid_number" ), QCoreApplication::translate( "variable_help", "Current grid annotation value." ) );

src/core/qgsexpressioncontext.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,17 @@ QVariant QgsExpressionContext::variable( const QString& name ) const
274274
return scope ? scope->variable( name ) : QVariant();
275275
}
276276

277+
QVariantMap QgsExpressionContext::variablesToMap() const
278+
{
279+
QStringList names = variableNames();
280+
QVariantMap m;
281+
Q_FOREACH ( const QString& name, names )
282+
{
283+
m.insert( name, variable( name ) );
284+
}
285+
return m;
286+
}
287+
277288
bool QgsExpressionContext::isHighlightedVariable( const QString &name ) const
278289
{
279290
return mHighlightedVariables.contains( name );
@@ -598,6 +609,41 @@ class GetNamedProjectColor : public QgsScopedExpressionFunction
598609

599610
};
600611

612+
class GetComposerItemVariables : public QgsScopedExpressionFunction
613+
{
614+
public:
615+
GetComposerItemVariables( const QgsComposition* c )
616+
: QgsScopedExpressionFunction( QStringLiteral( "item_variables" ), QgsExpression::ParameterList() << QgsExpression::Parameter( QStringLiteral( "id" ) ), QStringLiteral( "Composition" ) )
617+
, mComposition( c )
618+
{}
619+
620+
virtual QVariant func( const QVariantList& values, const QgsExpressionContext*, QgsExpression* ) override
621+
{
622+
if ( !mComposition )
623+
return QVariant();
624+
625+
QString id = values.at( 0 ).toString().toLower();
626+
627+
const QgsComposerItem* item = mComposition->getComposerItemById( id );
628+
if ( !item )
629+
return QVariant();
630+
631+
QgsExpressionContext c = item->createExpressionContext();
632+
633+
return c.variablesToMap();
634+
}
635+
636+
QgsScopedExpressionFunction* clone() const override
637+
{
638+
return new GetComposerItemVariables( mComposition );
639+
}
640+
641+
private:
642+
643+
const QgsComposition* mComposition;
644+
645+
};
646+
601647
///@endcond
602648

603649
QgsExpressionContextScope* QgsExpressionContextUtils::projectScope( const QgsProject* project )
@@ -757,6 +803,10 @@ QgsExpressionContextScope* QgsExpressionContextUtils::mapSettingsScope( const Qg
757803
QgsGeometry centerPoint = QgsGeometry::fromPoint( mapSettings.visibleExtent().center() );
758804
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_extent_center" ), QVariant::fromValue( centerPoint ), true ) );
759805

806+
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs" ), mapSettings.destinationCrs().authid(), true ) );
807+
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_crs_definition" ), mapSettings.destinationCrs().toProj4(), true ) );
808+
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_units" ), QgsUnitTypes::toString( mapSettings.mapUnits() ), true ) );
809+
760810
return scope;
761811
}
762812

@@ -807,6 +857,9 @@ QgsExpressionContextScope *QgsExpressionContextUtils::compositionScope( const Qg
807857
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_pagewidth" ), composition->paperWidth(), true ) );
808858
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_dpi" ), composition->printResolution(), true ) );
809859

860+
861+
scope->addFunction( QStringLiteral( "item_variables" ), new GetComposerItemVariables( composition ) );
862+
810863
return scope;
811864
}
812865

@@ -959,6 +1012,7 @@ QgsExpressionContext QgsExpressionContextUtils::createFeatureBasedContext( const
9591012
void QgsExpressionContextUtils::registerContextFunctions()
9601013
{
9611014
QgsExpression::registerFunction( new GetNamedProjectColor( nullptr ) );
1015+
QgsExpression::registerFunction( new GetComposerItemVariables( nullptr ) );
9621016
}
9631017

9641018
bool QgsScopedExpressionFunction::usesGeometry( const QgsExpression::NodeFunction* node ) const

src/core/qgsexpressioncontext.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,25 @@ class CORE_EXPORT QgsScopedExpressionFunction : public QgsExpression::Function
6363
, mReferencedColumns( referencedColumns )
6464
{}
6565

66+
/**
67+
* Create a new QgsScopedExpressionFunction using named parameters.
68+
*
69+
* @note Added in QGIS 3.0
70+
*/
71+
QgsScopedExpressionFunction( const QString& fnname,
72+
const QgsExpression::ParameterList& params,
73+
const QString& group,
74+
const QString& helpText = QString(),
75+
bool usesGeometry = false,
76+
const QSet<QString>& referencedColumns = QSet<QString>(),
77+
bool lazyEval = false,
78+
bool handlesNull = false,
79+
bool isContextual = true )
80+
: QgsExpression::Function( fnname, params, group, helpText, lazyEval, handlesNull, isContextual )
81+
, mUsesGeometry( usesGeometry )
82+
, mReferencedColumns( referencedColumns )
83+
{}
84+
6685
virtual QVariant func( const QVariantList& values, const QgsExpressionContext* context, QgsExpression* parent ) override = 0;
6786

6887
/** Returns a clone of the function.
@@ -303,6 +322,13 @@ class CORE_EXPORT QgsExpressionContext
303322
*/
304323
QVariant variable( const QString& name ) const;
305324

325+
/**
326+
* Returns a map of variable name to value representing all the expression variables
327+
* contained by the context.
328+
* @note added in QGIS 3.0
329+
*/
330+
QVariantMap variablesToMap() const;
331+
306332
/** Returns true if the specified variable name is intended to be highlighted to the
307333
* user. This is used by the expression builder to more prominently display the
308334
* variable.

tests/src/core/testqgscomposition.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
#include <QObject>
3232
#include "qgstest.h"
33+
#include "qgstestutils.h"
3334

3435
class TestQgsComposition : public QObject
3536
{
@@ -55,6 +56,7 @@ class TestQgsComposition : public QObject
5556
void resizeToContentsMultiPage();
5657
void georeference();
5758
void variablesEdited();
59+
void itemVariablesFunction();
5860

5961
private:
6062
QgsComposition *mComposition;
@@ -599,5 +601,43 @@ void TestQgsComposition::variablesEdited()
599601
QVERIFY( spyVariablesChanged.count() == 2 );
600602
}
601603

604+
void TestQgsComposition::itemVariablesFunction()
605+
{
606+
QgsRectangle extent( 2000, 2800, 2500, 2900 );
607+
QgsMapSettings ms;
608+
ms.setExtent( extent );
609+
QgsComposition* composition = new QgsComposition( ms, QgsProject::instance() );
610+
611+
QgsExpression e( "map_get( item_variables( 'map_id' ), 'map_scale' )" );
612+
// no map
613+
QgsExpressionContext c = composition->createExpressionContext();
614+
QVariant r = e.evaluate( &c );
615+
QVERIFY( !r.isValid() );
616+
617+
QgsComposerMap* map = new QgsComposerMap( composition );
618+
map->setNewExtent( extent );
619+
map->setSceneRect( QRectF( 30, 60, 200, 100 ) );
620+
composition->addComposerMap( map );
621+
map->setId( "map_id" );
622+
623+
c = composition->createExpressionContext();
624+
r = e.evaluate( &c );
625+
QGSCOMPARENEAR( r.toDouble(), 1.38916e+08, 100 );
626+
627+
QgsExpression e2( "map_get( item_variables( 'map_id' ), 'map_crs' )" );
628+
r = e2.evaluate( &c );
629+
QCOMPARE( r.toString(), QString( "EPSG:4326" ) );
630+
631+
QgsExpression e3( "map_get( item_variables( 'map_id' ), 'map_crs_definition' )" );
632+
r = e3.evaluate( &c );
633+
QCOMPARE( r.toString(), QString( "+proj=longlat +datum=WGS84 +no_defs" ) );
634+
635+
QgsExpression e4( "map_get( item_variables( 'map_id' ), 'map_units' )" );
636+
r = e4.evaluate( &c );
637+
QCOMPARE( r.toString(), QString( "degrees" ) );
638+
639+
delete composition;
640+
}
641+
602642
QGSTEST_MAIN( TestQgsComposition )
603643
#include "testqgscomposition.moc"

tests/src/core/testqgsexpressioncontext.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class TestQgsExpressionContext : public QObject
5151

5252
void cache();
5353

54+
void valuesAsMap();
55+
5456
private:
5557

5658
class GetTestValueFunction : public QgsScopedExpressionFunction
@@ -688,5 +690,35 @@ void TestQgsExpressionContext::cache()
688690
QVERIFY( !c.cachedValue( "test" ).isValid() );
689691
}
690692

693+
void TestQgsExpressionContext::valuesAsMap()
694+
{
695+
QgsExpressionContext context;
696+
697+
//test retrieving from empty context
698+
QVERIFY( context.variablesToMap().isEmpty() );
699+
700+
//add a scope to the context
701+
QgsExpressionContextScope* s1 = new QgsExpressionContextScope();
702+
s1->setVariable( "v1", "t1" );
703+
s1->setVariable( "v2", "t2" );
704+
context << s1;
705+
706+
QVariantMap m = context.variablesToMap();
707+
QCOMPARE( m.size(), 2 );
708+
QCOMPARE( m.value( "v1" ).toString(), QString( "t1" ) );
709+
QCOMPARE( m.value( "v2" ).toString(), QString( "t2" ) );
710+
711+
QgsExpressionContextScope* s2 = new QgsExpressionContextScope();
712+
s2->setVariable( "v2", "t2a" );
713+
s2->setVariable( "v3", "t3" );
714+
context << s2;
715+
716+
m = context.variablesToMap();
717+
QCOMPARE( m.size(), 3 );
718+
QCOMPARE( m.value( "v1" ).toString(), QString( "t1" ) );
719+
QCOMPARE( m.value( "v2" ).toString(), QString( "t2a" ) );
720+
QCOMPARE( m.value( "v3" ).toString(), QString( "t3" ) );
721+
}
722+
691723
QGSTEST_MAIN( TestQgsExpressionContext )
692724
#include "testqgsexpressioncontext.moc"

0 commit comments

Comments
 (0)