Skip to content

Commit

Permalink
[FEATURE][expressions] Add @layers, @layer_ids project scope variables
Browse files Browse the repository at this point in the history
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 20, 2020
1 parent c1620d3 commit 7b7c806
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 26 deletions.
2 changes: 2 additions & 0 deletions src/core/expression/qgsexpression.cpp
Expand Up @@ -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." ) );
Expand Down
46 changes: 37 additions & 9 deletions src/core/qgsproject.cpp
Expand Up @@ -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() )
{
Expand Down Expand Up @@ -1684,6 +1689,7 @@ const QgsLabelingEngineSettings &QgsProject::labelingEngineSettings() const

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

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -3187,6 +3207,8 @@ QList<QgsMapLayer *> QgsProject::addMapLayers(
}
}

mProjectScope.reset();

return myResultList;
}

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

Expand Down
86 changes: 69 additions & 17 deletions tests/src/core/testqgsexpressioncontext.cpp
Expand Up @@ -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" ) );
Expand All @@ -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" ) );

Expand All @@ -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;
Expand All @@ -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" ) );
Expand Down

0 comments on commit 7b7c806

Please sign in to comment.