Skip to content

Commit 01f3c9a

Browse files
committed
[FEATURE] Add is_selected and num_selected functions
* is_selected() returns if the current feature is selected * num_selected() returns the number of selected features on the current layer * is_selected(layer, feature) returns if the "feature" is selected. "feature" must be on "layer". * num_selected(layer) returns the number of selected features on "layer"
1 parent f52dfba commit 01f3c9a

File tree

6 files changed

+173
-10
lines changed

6 files changed

+173
-10
lines changed
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "is_selected",
3+
"type": "function",
4+
"description": "Returns if a feature is selected. If called with no parameters checks the current feature.",
5+
"arguments": [
6+
{"arg":"feature","description":"The feature which should be checked for selection"},
7+
{"arg":"layer","description":"The layer (or its id or name) on which the selection will be checked"}
8+
],
9+
"examples": [
10+
{ "expression":"is_selected()", "returns" : "True if the current feature is selected."},
11+
{ "expression":"is_selected(get_feature('streets', 'name', \"street_name\"), 'streets')", "returns":"True if the current building's street is selected."}
12+
]
13+
}
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "num_selected",
3+
"type": "function",
4+
"description": "Returns the number of selected features on a given layer. By default works on the layer on which the expression is evaluated.",
5+
"arguments": [
6+
{"arg":"layer","description":"The layer (or its id or name) on which the selection will be checked"}
7+
],
8+
"examples": [
9+
{ "expression":"num_selected()", "returns":"The number of selected features on the current layer."},
10+
{ "expression":"num_selected('streets')", "returns":"The number of selected features on the layer streets"}
11+
]
12+
}

src/core/qgsexpression.cpp

+97-9
Original file line numberDiff line numberDiff line change
@@ -312,14 +312,19 @@ static QgsExpression::Node* getNode( const QVariant& value, QgsExpression* paren
312312

313313
QgsVectorLayer* getVectorLayer( const QVariant& value, QgsExpression* )
314314
{
315-
QString layerString = value.toString();
316-
QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerString ) ); //search by id first
315+
QgsVectorLayer* vl = value.value<QgsVectorLayer*>();
317316
if ( !vl )
318317
{
319-
QList<QgsMapLayer *> layersByName = QgsMapLayerRegistry::instance()->mapLayersByName( layerString );
320-
if ( !layersByName.isEmpty() )
318+
QString layerString = value.toString();
319+
vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerString ) ); //search by id first
320+
321+
if ( !vl )
321322
{
322-
vl = qobject_cast<QgsVectorLayer*>( layersByName.at( 0 ) );
323+
QList<QgsMapLayer *> layersByName = QgsMapLayerRegistry::instance()->mapLayersByName( layerString );
324+
if ( !layersByName.isEmpty() )
325+
{
326+
vl = qobject_cast<QgsVectorLayer*>( layersByName.at( 0 ) );
327+
}
323328
}
324329
}
325330

@@ -707,8 +712,7 @@ static QVariant fcnAggregateRelation( const QVariantList& values, const QgsExpre
707712
}
708713

709714
// first step - find current layer
710-
QString layerId = context->variable( QStringLiteral( "layer_id" ) ).toString();
711-
QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerId ) );
715+
QgsVectorLayer* vl = getVectorLayer( context->variable( "layer" ), parent );
712716
if ( !vl )
713717
{
714718
parent->setEvalErrorString( QObject::tr( "Cannot use relation aggregate function in this context" ) );
@@ -810,8 +814,7 @@ static QVariant fcnAggregateGeneric( QgsAggregateCalculator::Aggregate aggregate
810814
}
811815

812816
// first step - find current layer
813-
QString layerId = context->variable( QStringLiteral( "layer_id" ) ).toString();
814-
QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerId ) );
817+
QgsVectorLayer* vl = getVectorLayer( context->variable( "layer" ), parent );
815818
if ( !vl )
816819
{
817820
parent->setEvalErrorString( QObject::tr( "Cannot use aggregate function in this context" ) );
@@ -1358,6 +1361,63 @@ static QVariant fcnAttribute( const QVariantList& values, const QgsExpressionCon
13581361

13591362
return feat.attribute( attr );
13601363
}
1364+
1365+
static QVariant fcnIsSelected( const QVariantList& values, const QgsExpressionContext* context, QgsExpression* parent )
1366+
{
1367+
QgsVectorLayer* layer = nullptr;
1368+
QgsFeature feature;
1369+
1370+
if ( values.isEmpty() )
1371+
{
1372+
feature = context->feature();
1373+
layer = getVectorLayer( context->variable( "layer" ), parent );
1374+
}
1375+
else if ( values.size() == 1 )
1376+
{
1377+
layer = getVectorLayer( context->variable( "layer" ), parent );
1378+
feature = getFeature( values.at( 0 ), parent );
1379+
}
1380+
else if ( values.size() == 2 )
1381+
{
1382+
layer = getVectorLayer( values.at( 0 ), parent );
1383+
feature = getFeature( values.at( 1 ), parent );
1384+
}
1385+
else
1386+
{
1387+
parent->setEvalErrorString( QObject::tr( "Function `is_selected` requires no more than two parameters. %1 given." ).arg( values.length() ) );
1388+
return QVariant();
1389+
}
1390+
1391+
if ( !layer || !feature.isValid() )
1392+
{
1393+
return QVariant( QVariant::Bool );
1394+
}
1395+
1396+
return layer->selectedFeaturesIds().contains( feature.id() );
1397+
}
1398+
1399+
static QVariant fcnNumSelected( const QVariantList& values, const QgsExpressionContext* context, QgsExpression* parent )
1400+
{
1401+
QgsVectorLayer* layer = nullptr;
1402+
1403+
if ( values.isEmpty() )
1404+
layer = getVectorLayer( context->variable( "layer" ), parent );
1405+
else if ( values.count() == 1 )
1406+
layer = getVectorLayer( values.at( 0 ), parent );
1407+
else
1408+
{
1409+
parent->setEvalErrorString( QObject::tr( "Function `num_selected` requires no more than one parameter. %1 given." ).arg( values.length() ) );
1410+
return QVariant();
1411+
}
1412+
1413+
if ( !layer )
1414+
{
1415+
return QVariant( QVariant::LongLong );
1416+
}
1417+
1418+
return layer->selectedFeatureCount();
1419+
}
1420+
13611421
static QVariant fcnConcat( const QVariantList& values, const QgsExpressionContext*, QgsExpression *parent )
13621422
{
13631423
QString concat;
@@ -3665,10 +3725,37 @@ const QList<QgsExpression::Function*>& QgsExpression::Functions()
36653725
<< Parameter( QStringLiteral( "vertex" ) ), fcnAngleAtVertex, QStringLiteral( "GeometryGroup" ) )
36663726
<< new StaticFunction( QStringLiteral( "distance_to_vertex" ), ParameterList() << Parameter( QStringLiteral( "geometry" ) )
36673727
<< Parameter( QStringLiteral( "vertex" ) ), fcnDistanceToVertex, QStringLiteral( "GeometryGroup" ) )
3728+
3729+
3730+
// **Record** functions
3731+
36683732
<< new StaticFunction( QStringLiteral( "$id" ), 0, fcnFeatureId, QStringLiteral( "Record" ) )
36693733
<< new StaticFunction( QStringLiteral( "$currentfeature" ), 0, fcnFeature, QStringLiteral( "Record" ) )
36703734
<< new StaticFunction( QStringLiteral( "uuid" ), 0, fcnUuid, QStringLiteral( "Record" ), QString(), false, QSet<QString>(), false, QStringList() << QStringLiteral( "$uuid" ) )
36713735
<< new StaticFunction( QStringLiteral( "get_feature" ), 3, fcnGetFeature, QStringLiteral( "Record" ), QString(), false, QSet<QString>(), false, QStringList() << QStringLiteral( "getFeature" ) )
3736+
3737+
<< new StaticFunction(
3738+
QStringLiteral( "is_selected" ),
3739+
-1,
3740+
fcnIsSelected,
3741+
QStringLiteral( "Record" ),
3742+
QString(),
3743+
false,
3744+
QSet<QString>()
3745+
)
3746+
3747+
<< new StaticFunction(
3748+
QStringLiteral( "num_selected" ),
3749+
-1,
3750+
fcnNumSelected,
3751+
QStringLiteral( "Record" ),
3752+
QString(),
3753+
false,
3754+
QSet<QString>()
3755+
)
3756+
3757+
// **General** functions
3758+
36723759
<< new StaticFunction( QStringLiteral( "layer_property" ), 2, fcnGetLayerProperty, QStringLiteral( "General" ) )
36733760
<< new StaticFunction( QStringLiteral( "var" ), 1, fcnGetVariable, QStringLiteral( "General" ) )
36743761

@@ -5290,6 +5377,7 @@ void QgsExpression::initVariableHelp()
52905377
//layer variables
52915378
gVariableHelpTexts.insert( QStringLiteral( "layer_name" ), QCoreApplication::translate( "variable_help", "Name of current layer." ) );
52925379
gVariableHelpTexts.insert( QStringLiteral( "layer_id" ), QCoreApplication::translate( "variable_help", "ID of current layer." ) );
5380+
gVariableHelpTexts.insert( QStringLiteral( "layer" ), QCoreApplication::translate( "variable_help", "The current layer." ) );
52935381

52945382
//composition variables
52955383
gVariableHelpTexts.insert( QStringLiteral( "layout_numpages" ), QCoreApplication::translate( "variable_help", "Number of pages in composition." ) );

src/core/qgsexpressioncontext.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,7 @@ QgsExpressionContextScope* QgsExpressionContextUtils::layerScope( const QgsMapLa
691691

692692
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layer_name" ), layer->name(), true ) );
693693
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layer_id" ), layer->id(), true ) );
694+
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layer" ), QVariant::fromValue<QgsMapLayer*>( const_cast<QgsMapLayer*>( layer ) ), true ) );
694695

695696
const QgsVectorLayer* vLayer = dynamic_cast< const QgsVectorLayer* >( layer );
696697
if ( vLayer )

src/core/qgsexpressioncontext.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ class CORE_EXPORT QgsExpressionContextUtils
581581
/** Creates a new scope which contains variables and functions relating to a QgsMapLayer.
582582
* For instance, layer name, id and fields.
583583
*/
584-
static QgsExpressionContextScope* layerScope( const QgsMapLayer *layer );
584+
static QgsExpressionContextScope* layerScope( const QgsMapLayer* layer );
585585

586586
/** Sets a layer context variable. This variable will be contained within scopes retrieved via
587587
* layerScope().

tests/src/core/testqgsexpression.cpp

+49
Original file line numberDiff line numberDiff line change
@@ -1355,6 +1355,55 @@ class TestQgsExpression: public QObject
13551355
QTest::newRow( "group by expression" ) << "sum(\"col1\", \"col1\" % 2)" << false << QVariant( 16 );
13561356
}
13571357

1358+
void selection()
1359+
{
1360+
QFETCH( QgsFeatureIds, selectedFeatures );
1361+
QFETCH( QString, expression );
1362+
QFETCH( QVariant, result );
1363+
QFETCH( QgsFeature, feature );
1364+
QFETCH( QgsVectorLayer*, layer );
1365+
1366+
QgsExpressionContext context;
1367+
if ( layer )
1368+
context.appendScope( QgsExpressionContextUtils::layerScope( layer ) );
1369+
1370+
QgsFeatureIds backupSelection = mMemoryLayer->selectedFeaturesIds();
1371+
context.setFeature( feature );
1372+
1373+
mMemoryLayer->selectByIds( selectedFeatures );
1374+
1375+
QgsExpression exp( expression );
1376+
QCOMPARE( exp.parserErrorString(), QString( "" ) );
1377+
exp.prepare( &context );
1378+
QVariant res = exp.evaluate( &context );
1379+
QCOMPARE( res, result );
1380+
1381+
mMemoryLayer->selectByIds( backupSelection );
1382+
}
1383+
1384+
void selection_data()
1385+
{
1386+
QTest::addColumn<QString>( "expression" );
1387+
QTest::addColumn<QgsFeatureIds>( "selectedFeatures" );
1388+
QTest::addColumn<QgsFeature>( "feature" );
1389+
QTest::addColumn<QgsVectorLayer*>( "layer" );
1390+
QTest::addColumn<QVariant>( "result" );
1391+
1392+
QgsFeature firstFeature = mMemoryLayer->getFeature( 1 );
1393+
QgsVectorLayer* noLayer = nullptr;
1394+
1395+
QTest::newRow( "empty selection num_selected" ) << "num_selected()" << QgsFeatureIds() << firstFeature << mMemoryLayer << QVariant( 0 );
1396+
QTest::newRow( "empty selection is_selected" ) << "is_selected()" << QgsFeatureIds() << firstFeature << mMemoryLayer << QVariant( false );
1397+
QTest::newRow( "two_selected" ) << "num_selected()" << ( QgsFeatureIds() << 1 << 2 ) << firstFeature << mMemoryLayer << QVariant( 2 );
1398+
QTest::newRow( "is_selected" ) << "is_selected()" << ( QgsFeatureIds() << 1 << 2 ) << firstFeature << mMemoryLayer << QVariant( true );
1399+
QTest::newRow( "not_selected" ) << "is_selected()" << ( QgsFeatureIds() << 4 << 2 ) << firstFeature << mMemoryLayer << QVariant( false );
1400+
QTest::newRow( "no layer num_selected" ) << "num_selected()" << ( QgsFeatureIds() << 4 << 2 ) << QgsFeature() << noLayer << QVariant( QVariant::LongLong );
1401+
QTest::newRow( "no layer is_selected" ) << "is_selected()" << ( QgsFeatureIds() << 4 << 2 ) << QgsFeature() << noLayer << QVariant( QVariant::Bool );
1402+
QTest::newRow( "no layer num_selected" ) << "num_selected()" << ( QgsFeatureIds() << 4 << 2 ) << QgsFeature() << noLayer << QVariant( QVariant::LongLong );
1403+
QTest::newRow( "is_selected with params" ) << "is_selected('test', get_feature('test', 'col1', 10))" << ( QgsFeatureIds() << 4 << 2 ) << QgsFeature() << noLayer << QVariant( QVariant::Bool );
1404+
QTest::newRow( "num_selected with params" ) << "num_selected('test')" << ( QgsFeatureIds() << 4 << 2 ) << QgsFeature() << noLayer << QVariant( 2 );
1405+
}
1406+
13581407
void layerAggregates()
13591408
{
13601409
QgsExpressionContext context;

0 commit comments

Comments
 (0)