From b43943a9b0171f2313e45adf30e8b55ff9474700 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 17 Jan 2019 21:38:21 -0500 Subject: [PATCH] [FEATURE] New expression variables for legend items Adds new variables for use in data defined expressions for layout legend items, including - @legend_title - @legend_column_count - @legend_split_layers - @legend_wrap_string - @legend_filter_by_map - @legend_filter_out_atlas Additionally, if the legend is linked to a map, then expressions used in that legend will also have access to the linked variables, including @map_scale, @map_extent, etc. --- .../layout/qgslayoutitemlegend.sip.in | 3 ++ src/core/expression/qgsexpression.cpp | 9 +++++ src/core/layout/qgslayoutitemlegend.cpp | 28 ++++++++++++++ src/core/layout/qgslayoutitemlegend.h | 2 + tests/src/python/test_qgslayoutlegend.py | 38 ++++++++++++++++++- 5 files changed, 79 insertions(+), 1 deletion(-) diff --git a/python/core/auto_generated/layout/qgslayoutitemlegend.sip.in b/python/core/auto_generated/layout/qgslayoutitemlegend.sip.in index 5adf6fd975b..e4d83e89621 100644 --- a/python/core/auto_generated/layout/qgslayoutitemlegend.sip.in +++ b/python/core/auto_generated/layout/qgslayoutitemlegend.sip.in @@ -488,6 +488,9 @@ Returns the legend's renderer settings object. virtual void finalizeRestoreFromXml(); + virtual QgsExpressionContext createExpressionContext() const; + + public slots: diff --git a/src/core/expression/qgsexpression.cpp b/src/core/expression/qgsexpression.cpp index 3ea6d2fe144..63e9788619b 100644 --- a/src/core/expression/qgsexpression.cpp +++ b/src/core/expression/qgsexpression.cpp @@ -757,6 +757,15 @@ void QgsExpression::initVariableHelp() // map canvas item variables sVariableHelpTexts.insert( QStringLiteral( "canvas_cursor_point" ), QCoreApplication::translate( "variable_help", "Last cursor position on the canvas in the project's geographical coordinates." ) ); + // legend canvas item variables + sVariableHelpTexts.insert( QStringLiteral( "legend_title" ), QCoreApplication::translate( "variable_help", "Title of the legend." ) ); + sVariableHelpTexts.insert( QStringLiteral( "legend_column_count" ), QCoreApplication::translate( "variable_help", "Number of column in the legend." ) ); + sVariableHelpTexts.insert( QStringLiteral( "legend_split_layers" ), QCoreApplication::translate( "variable_help", "Boolean indicating if layers can be split in the legend." ) ); + sVariableHelpTexts.insert( QStringLiteral( "legend_wrap_string" ), QCoreApplication::translate( "variable_help", "Characters used to wrap the legend text." ) ); + sVariableHelpTexts.insert( QStringLiteral( "legend_filter_by_map" ), QCoreApplication::translate( "variable_help", "Boolean indicating if the content of the legend is filtered by the map." ) ); + sVariableHelpTexts.insert( QStringLiteral( "legend_filter_out_atlas" ), QCoreApplication::translate( "variable_help", "Boolean indicating if the Atlas is filtered out of the legend." ) ); + + // map tool capture variables sVariableHelpTexts.insert( QStringLiteral( "snapping_results" ), QCoreApplication::translate( "variable_help", "

An array with an item for each snapped point.

" diff --git a/src/core/layout/qgslayoutitemlegend.cpp b/src/core/layout/qgslayoutitemlegend.cpp index 1b275863966..8006f968a13 100644 --- a/src/core/layout/qgslayoutitemlegend.cpp +++ b/src/core/layout/qgslayoutitemlegend.cpp @@ -818,6 +818,34 @@ void QgsLayoutItemLegend::onAtlasEnded() updateFilterByMap(); } +QgsExpressionContext QgsLayoutItemLegend::createExpressionContext() const +{ + QgsExpressionContext context = QgsLayoutItem::createExpressionContext(); + + // We only want the last scope from the map's expression context, as this contains + // the map specific variables. We don't want the rest of the map's context, because that + // will contain duplicate global, project, layout, etc scopes. + + if ( mMap ) + { + context.appendScope( mMap->createExpressionContext().popScope() ); + } + + + QgsExpressionContextScope *scope = new QgsExpressionContextScope( tr( "Legend Settings" ) ); + + scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_title" ), title(), true ) ); + scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_column_count" ), columnCount(), true ) ); + scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_split_layers" ), splitLayer(), true ) ); + scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_wrap_string" ), wrapString(), true ) ); + scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_filter_by_map" ), legendFilterByMapEnabled(), true ) ); + scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "legend_filter_out_atlas" ), legendFilterOutAtlas(), true ) ); + + context.appendScope( scope ); + + return context; +} + // ------------------------------------------------------------------------- #include "qgslayertreemodellegendnode.h" #include "qgsvectorlayer.h" diff --git a/src/core/layout/qgslayoutitemlegend.h b/src/core/layout/qgslayoutitemlegend.h index 3c7dd79c742..4fa94dcc21a 100644 --- a/src/core/layout/qgslayoutitemlegend.h +++ b/src/core/layout/qgslayoutitemlegend.h @@ -436,6 +436,8 @@ class CORE_EXPORT QgsLayoutItemLegend : public QgsLayoutItem void finalizeRestoreFromXml() override; + QgsExpressionContext createExpressionContext() const override; + public slots: diff --git a/tests/src/python/test_qgslayoutlegend.py b/tests/src/python/test_qgslayoutlegend.py index bea5b2c755d..b2c726adee2 100644 --- a/tests/src/python/test_qgslayoutlegend.py +++ b/tests/src/python/test_qgslayoutlegend.py @@ -29,7 +29,8 @@ from qgis.core import (QgsLayoutItemLegend, QgsLayoutMeasurement, QgsLayoutItem, QgsLayoutPoint, - QgsLayoutSize) + QgsLayoutSize, + QgsExpression) from qgis.testing import (start_app, unittest ) @@ -269,6 +270,41 @@ class TestQgsLayoutItemLegend(unittest.TestCase, LayoutItemTestCase): self.assertEqual(legend.columnCount(), 2) self.assertEqual(legend.legendSettings().columnCount(), 5) + def testLegendScopeVariables(self): + layout = QgsLayout(QgsProject.instance()) + layout.initializeDefaults() + + legend = QgsLayoutItemLegend(layout) + legend.setTitle("Legend") + layout.addLayoutItem(legend) + + legend.setColumnCount(2) + legend.setWrapString('d') + legend.setLegendFilterOutAtlas(True) + + expc = legend.createExpressionContext() + exp1 = QgsExpression("@legend_title") + self.assertEqual(exp1.evaluate(expc), "Legend") + exp2 = QgsExpression("@legend_column_count") + self.assertEqual(exp2.evaluate(expc), 2) + exp3 = QgsExpression("@legend_wrap_string") + self.assertEqual(exp3.evaluate(expc), 'd') + exp4 = QgsExpression("@legend_split_layers") + self.assertEqual(exp4.evaluate(expc), False) + exp5 = QgsExpression("@legend_filter_out_atlas") + self.assertEqual(exp5.evaluate(expc), True) + + map = QgsLayoutItemMap(layout) + map.attemptSetSceneRect(QRectF(20, 20, 80, 80)) + map.setFrameEnabled(True) + map.setExtent(QgsRectangle(781662.375, 3339523.125, 793062.375, 3345223.125)) + layout.addLayoutItem(map) + map.setScale(15000) + legend.setLinkedMap(map) + expc2 = legend.createExpressionContext() + exp6 = QgsExpression("@map_scale") + self.assertAlmostEqual(exp6.evaluate(expc2), 15000, 2) + if __name__ == '__main__': unittest.main()