Skip to content
Permalink
Browse files

[FEATURE][expressions] Add @layers, @layer_ids project scope variables

which contain lists of map layers and map layers ids for all layers
from the current project

This mimics the existing @map_layers, @map_layer_ids, but unlike the
@Map variants these return ALL project layers, not just those associated
with the current context's map settings.

Sponsored by SLYR
  • Loading branch information
nyalldawson committed Mar 19, 2020
1 parent c1620d3 commit 7b7c8065b08a64aff2adf3d8e6de203c376cbff8
Showing with 108 additions and 26 deletions.
  1. +2 −0 src/core/expression/qgsexpression.cpp
  2. +37 −9 src/core/qgsproject.cpp
  3. +69 −17 tests/src/core/testqgsexpressioncontext.cpp
@@ -714,6 +714,8 @@ void QgsExpression::initVariableHelp()
sVariableHelpTexts()->insert( QStringLiteral( "project_area_units" ), QCoreApplication::translate( "variable_help", "Area unit for current project, used when calculating areas of geometries." ) );
sVariableHelpTexts()->insert( QStringLiteral( "project_distance_units" ), QCoreApplication::translate( "variable_help", "Distance unit for current project, used when calculating lengths of geometries." ) );
sVariableHelpTexts()->insert( QStringLiteral( "project_ellipsoid" ), QCoreApplication::translate( "variable_help", "Name of ellipsoid of current project, used when calculating geodetic areas and lengths of geometries." ) );
sVariableHelpTexts()->insert( QStringLiteral( "layer_ids" ), QCoreApplication::translate( "variable_help", "List of all map layer IDs from the current project." ) );
sVariableHelpTexts()->insert( QStringLiteral( "layers" ), QCoreApplication::translate( "variable_help", "List of all map layers from the current project." ) );

//layer variables
sVariableHelpTexts()->insert( QStringLiteral( "layer_name" ), QCoreApplication::translate( "variable_help", "Name of current layer." ) );
@@ -385,18 +385,23 @@ QgsProject::QgsProject( QObject *parent )

// proxy map layer store signals to this
connect( mLayerStore.get(), qgis::overload<const QStringList &>::of( &QgsMapLayerStore::layersWillBeRemoved ),
this, qgis::overload< const QStringList &>::of( &QgsProject::layersWillBeRemoved ) );
this, [ = ]( const QStringList & layers ) { mProjectScope.reset(); emit layersWillBeRemoved( layers ); } );
connect( mLayerStore.get(), qgis::overload< const QList<QgsMapLayer *> & >::of( &QgsMapLayerStore::layersWillBeRemoved ),
this, qgis::overload< const QList<QgsMapLayer *> & >::of( &QgsProject::layersWillBeRemoved ) );
this, [ = ]( const QList<QgsMapLayer *> &layers ) { mProjectScope.reset(); emit layersWillBeRemoved( layers ); } );
connect( mLayerStore.get(), qgis::overload< const QString & >::of( &QgsMapLayerStore::layerWillBeRemoved ),
this, qgis::overload< const QString & >::of( &QgsProject::layerWillBeRemoved ) );
this, [ = ]( const QString & layer ) { mProjectScope.reset(); emit layerWillBeRemoved( layer ); } );
connect( mLayerStore.get(), qgis::overload< QgsMapLayer * >::of( &QgsMapLayerStore::layerWillBeRemoved ),
this, qgis::overload< QgsMapLayer * >::of( &QgsProject::layerWillBeRemoved ) );
connect( mLayerStore.get(), qgis::overload<const QStringList & >::of( &QgsMapLayerStore::layersRemoved ), this, &QgsProject::layersRemoved );
connect( mLayerStore.get(), &QgsMapLayerStore::layerRemoved, this, &QgsProject::layerRemoved );
connect( mLayerStore.get(), &QgsMapLayerStore::allLayersRemoved, this, &QgsProject::removeAll );
connect( mLayerStore.get(), &QgsMapLayerStore::layersAdded, this, &QgsProject::layersAdded );
connect( mLayerStore.get(), &QgsMapLayerStore::layerWasAdded, this, &QgsProject::layerWasAdded );
this, [ = ]( QgsMapLayer * layer ) { mProjectScope.reset(); emit layerWillBeRemoved( layer ); } );
connect( mLayerStore.get(), qgis::overload<const QStringList & >::of( &QgsMapLayerStore::layersRemoved ), this,
[ = ]( const QStringList & layers ) { mProjectScope.reset(); emit layersRemoved( layers ); } );
connect( mLayerStore.get(), &QgsMapLayerStore::layerRemoved, this,
[ = ]( const QString & layer ) { mProjectScope.reset(); emit layerRemoved( layer ); } );
connect( mLayerStore.get(), &QgsMapLayerStore::allLayersRemoved, this,
[ = ]() { mProjectScope.reset(); emit removeAll(); } );
connect( mLayerStore.get(), &QgsMapLayerStore::layersAdded, this,
[ = ]( const QList< QgsMapLayer * > &layers ) { mProjectScope.reset(); emit layersAdded( layers ); } );
connect( mLayerStore.get(), &QgsMapLayerStore::layerWasAdded, this,
[ = ]( QgsMapLayer * layer ) { mProjectScope.reset(); emit layerWasAdded( layer ); } );

if ( QgsApplication::instance() )
{
@@ -1684,6 +1689,7 @@ const QgsLabelingEngineSettings &QgsProject::labelingEngineSettings() const

QgsMapLayerStore *QgsProject::layerStore()
{
mProjectScope.reset();
return mLayerStore.get();
}

@@ -1787,6 +1793,20 @@ QgsExpressionContextScope *QgsProject::createExpressionContextScope() const
}
mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_keywords" ), keywords, true, true ) );

// layers
QVariantList layersIds;
QVariantList layers;
const QMap<QString, QgsMapLayer *> layersInProject = mLayerStore->mapLayers();
layersIds.reserve( layersInProject.count() );
layers.reserve( layersInProject.count() );
for ( auto it = layersInProject.constBegin(); it != layersInProject.constEnd(); ++it )
{
layersIds << it.value()->id();
layers << QVariant::fromValue<QgsWeakMapLayerPointer>( QgsWeakMapLayerPointer( it.value() ) );
}
mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layer_ids" ), layersIds, true ) );
mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layers" ), layers, true ) );

mProjectScope->addFunction( QStringLiteral( "project_color" ), new GetNamedProjectColor( this ) );

return createExpressionContextScope();
@@ -3187,6 +3207,8 @@ QList<QgsMapLayer *> QgsProject::addMapLayers(
}
}

mProjectScope.reset();

return myResultList;
}

@@ -3202,31 +3224,37 @@ QgsProject::addMapLayer( QgsMapLayer *layer,

void QgsProject::removeMapLayers( const QStringList &layerIds )
{
mProjectScope.reset();
mLayerStore->removeMapLayers( layerIds );
}

void QgsProject::removeMapLayers( const QList<QgsMapLayer *> &layers )
{
mProjectScope.reset();
mLayerStore->removeMapLayers( layers );
}

void QgsProject::removeMapLayer( const QString &layerId )
{
mProjectScope.reset();
mLayerStore->removeMapLayer( layerId );
}

void QgsProject::removeMapLayer( QgsMapLayer *layer )
{
mProjectScope.reset();
mLayerStore->removeMapLayer( layer );
}

QgsMapLayer *QgsProject::takeMapLayer( QgsMapLayer *layer )
{
mProjectScope.reset();
return mLayerStore->takeMapLayer( layer );
}

void QgsProject::removeAllMapLayers()
{
mProjectScope.reset();
mLayerStore->removeAllMapLayers();
}

@@ -625,7 +625,7 @@ void TestQgsExpressionContext::globalScope()

void TestQgsExpressionContext::projectScope()
{
QgsProject *project = QgsProject::instance();
QgsProject project;
QgsProjectMetadata md;
md.setTitle( QStringLiteral( "project title" ) );
md.setAuthor( QStringLiteral( "project author" ) );
@@ -636,13 +636,13 @@ void TestQgsExpressionContext::projectScope()
keywords.insert( QStringLiteral( "voc1" ), QStringList() << "a" << "b" );
keywords.insert( QStringLiteral( "voc2" ), QStringList() << "c" << "d" );
md.setKeywords( keywords );
project->setMetadata( md );
project.setMetadata( md );

QgsExpressionContextUtils::setProjectVariable( project, QStringLiteral( "test" ), "testval" );
QgsExpressionContextUtils::setProjectVariable( project, QStringLiteral( "testdouble" ), 5.2 );
QgsExpressionContextUtils::setProjectVariable( &project, QStringLiteral( "test" ), "testval" );
QgsExpressionContextUtils::setProjectVariable( &project, QStringLiteral( "testdouble" ), 5.2 );

QgsExpressionContext context;
QgsExpressionContextScope *scope = QgsExpressionContextUtils::projectScope( project );
QgsExpressionContextScope *scope = QgsExpressionContextUtils::projectScope( &project );
context << scope;
QCOMPARE( scope->name(), tr( "Project" ) );

@@ -664,39 +664,91 @@ void TestQgsExpressionContext::projectScope()
QgsExpression expProject( QStringLiteral( "var('test')" ) );
QCOMPARE( expProject.evaluate( &context ).toString(), QString( "testval" ) );

// layers
QVERIFY( context.variable( "layers" ).isValid() );
QVERIFY( context.variable( "layer_ids" ).isValid() );
QVERIFY( context.variable( "layers" ).toList().isEmpty() );
QVERIFY( context.variable( "layer_ids" ).toList().isEmpty() );

// add layer
QgsVectorLayer *vectorLayer = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer&field=col2:integer&field=col3:integer" ), QStringLiteral( "test layer" ), QStringLiteral( "memory" ) );
QgsVectorLayer *vectorLayer2 = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer&field=col2:integer&field=col3:integer" ), QStringLiteral( "test layer" ), QStringLiteral( "memory" ) );
QgsVectorLayer *vectorLayer3 = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer&field=col2:integer&field=col3:integer" ), QStringLiteral( "test layer" ), QStringLiteral( "memory" ) );
project.addMapLayer( vectorLayer );
QgsExpressionContextScope *projectScope = QgsExpressionContextUtils::projectScope( &project );
QCOMPARE( projectScope->variable( "layers" ).toList().size(), 1 );
QCOMPARE( qvariant_cast< QObject * >( projectScope->variable( "layers" ).toList().at( 0 ) ), vectorLayer );
QCOMPARE( projectScope->variable( "layer_ids" ).toList().size(), 1 );
QVERIFY( projectScope->variable( "layer_ids" ).toList().contains( vectorLayer->id() ) );
delete projectScope;
project.addMapLayer( vectorLayer2 );
projectScope = QgsExpressionContextUtils::projectScope( &project );
QCOMPARE( projectScope->variable( "layers" ).toList().size(), 2 );
QVERIFY( project.mapLayers().values().contains( qobject_cast< QgsMapLayer * >( qvariant_cast< QObject * >( projectScope->variable( "layers" ).toList().at( 0 ) ) ) ) );
QVERIFY( project.mapLayers().values().contains( qobject_cast< QgsMapLayer * >( qvariant_cast< QObject * >( projectScope->variable( "layers" ).toList().at( 1 ) ) ) ) );
QCOMPARE( projectScope->variable( "layer_ids" ).toList().size(), 2 );
QVERIFY( projectScope->variable( "layer_ids" ).toList().contains( vectorLayer->id() ) );
QVERIFY( projectScope->variable( "layer_ids" ).toList().contains( vectorLayer2->id() ) );
delete projectScope;
project.addMapLayers( QList< QgsMapLayer * >() << vectorLayer3 );
projectScope = QgsExpressionContextUtils::projectScope( &project );
QCOMPARE( projectScope->variable( "layers" ).toList().size(), 3 );
QVERIFY( project.mapLayers().values().contains( qobject_cast< QgsMapLayer * >( qvariant_cast< QObject * >( projectScope->variable( "layers" ).toList().at( 0 ) ) ) ) );
QVERIFY( project.mapLayers().values().contains( qobject_cast< QgsMapLayer * >( qvariant_cast< QObject * >( projectScope->variable( "layers" ).toList().at( 1 ) ) ) ) );
QVERIFY( project.mapLayers().values().contains( qobject_cast< QgsMapLayer * >( qvariant_cast< QObject * >( projectScope->variable( "layers" ).toList().at( 2 ) ) ) ) );
QCOMPARE( projectScope->variable( "layer_ids" ).toList().size(), 3 );
QVERIFY( projectScope->variable( "layer_ids" ).toList().contains( vectorLayer->id() ) );
QVERIFY( projectScope->variable( "layer_ids" ).toList().contains( vectorLayer2->id() ) );
QVERIFY( projectScope->variable( "layer_ids" ).toList().contains( vectorLayer3->id() ) );
delete projectScope;
project.removeMapLayer( vectorLayer );
projectScope = QgsExpressionContextUtils::projectScope( &project );
QCOMPARE( projectScope->variable( "layers" ).toList().size(), 2 );
QVERIFY( project.mapLayers().values().contains( qobject_cast< QgsMapLayer * >( qvariant_cast< QObject * >( projectScope->variable( "layers" ).toList().at( 0 ) ) ) ) );
QVERIFY( project.mapLayers().values().contains( qobject_cast< QgsMapLayer * >( qvariant_cast< QObject * >( projectScope->variable( "layers" ).toList().at( 1 ) ) ) ) );
QCOMPARE( projectScope->variable( "layer_ids" ).toList().size(), 2 );
QVERIFY( projectScope->variable( "layer_ids" ).toList().contains( vectorLayer2->id() ) );
QVERIFY( projectScope->variable( "layer_ids" ).toList().contains( vectorLayer3->id() ) );
delete projectScope;
project.removeMapLayers( QList< QgsMapLayer * >() << vectorLayer2 << vectorLayer3 );
projectScope = QgsExpressionContextUtils::projectScope( &project );
QVERIFY( projectScope->variable( "layers" ).toList().isEmpty() );
QVERIFY( projectScope->variable( "layer_ids" ).toList().isEmpty() );
delete projectScope;

//test clearing project variables
QgsExpressionContextScope *projectScope = QgsExpressionContextUtils::projectScope( project );
projectScope = QgsExpressionContextUtils::projectScope( &project );
QVERIFY( projectScope->hasVariable( "test" ) );
QgsProject::instance()->clear();
project.clear();
delete projectScope;
projectScope = QgsExpressionContextUtils::projectScope( project );
projectScope = QgsExpressionContextUtils::projectScope( &project );
QVERIFY( !projectScope->hasVariable( "test" ) );

//test a preset project variable
QgsProject::instance()->setTitle( QStringLiteral( "test project" ) );
project.setTitle( QStringLiteral( "test project" ) );
delete projectScope;
projectScope = QgsExpressionContextUtils::projectScope( project );
projectScope = QgsExpressionContextUtils::projectScope( &project );
QCOMPARE( projectScope->variable( "project_title" ).toString(), QString( "test project" ) );
delete projectScope;

//test setProjectVariables
QVariantMap vars;
vars.insert( QStringLiteral( "newvar1" ), QStringLiteral( "val1" ) );
vars.insert( QStringLiteral( "newvar2" ), QStringLiteral( "val2" ) );
QgsExpressionContextUtils::setProjectVariables( project, vars );
projectScope = QgsExpressionContextUtils::projectScope( project );
QgsExpressionContextUtils::setProjectVariables( &project, vars );
projectScope = QgsExpressionContextUtils::projectScope( &project );

QVERIFY( !projectScope->hasVariable( "test" ) );
QCOMPARE( projectScope->variable( "newvar1" ).toString(), QString( "val1" ) );
QCOMPARE( projectScope->variable( "newvar2" ).toString(), QString( "val2" ) );
delete projectScope;

//test removeProjectVariable
QgsExpressionContextUtils::setProjectVariable( project, QStringLiteral( "key" ), "value" );
projectScope = QgsExpressionContextUtils::projectScope( project );
QgsExpressionContextUtils::setProjectVariable( &project, QStringLiteral( "key" ), "value" );
projectScope = QgsExpressionContextUtils::projectScope( &project );
QVERIFY( projectScope->hasVariable( "key" ) );
QgsExpressionContextUtils::removeProjectVariable( project, QStringLiteral( "key" ) );
projectScope = QgsExpressionContextUtils::projectScope( project );
QgsExpressionContextUtils::removeProjectVariable( &project, QStringLiteral( "key" ) );
projectScope = QgsExpressionContextUtils::projectScope( &project );
QVERIFY( !projectScope->hasVariable( "key" ) );
delete projectScope;
projectScope = nullptr;
@@ -710,7 +762,7 @@ void TestQgsExpressionContext::projectScope()
colorList << qMakePair( QColor( 30, 60, 20 ), QStringLiteral( "murky depths of hades" ) );
s.setColors( colorList );
QgsExpressionContext contextColors;
contextColors << QgsExpressionContextUtils::projectScope( project );
contextColors << QgsExpressionContextUtils::projectScope( QgsProject::instance() );

QgsExpression expProjectColor( QStringLiteral( "project_color('murky depths of hades')" ) );
QCOMPARE( expProjectColor.evaluate( &contextColors ).toString(), QString( "30,60,20" ) );

0 comments on commit 7b7c806

Please sign in to comment.
You can’t perform that action at this time.