Skip to content

Commit

Permalink
[FEATURE] Add is_selected and num_selected functions
Browse files Browse the repository at this point in the history
* 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"
  • Loading branch information
m-kuhn committed Oct 27, 2016
1 parent f52dfba commit 01f3c9a
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 10 deletions.
13 changes: 13 additions & 0 deletions resources/function_help/json/is_selected
@@ -0,0 +1,13 @@
{
"name": "is_selected",
"type": "function",
"description": "Returns if a feature is selected. If called with no parameters checks the current feature.",
"arguments": [
{"arg":"feature","description":"The feature which should be checked for selection"},
{"arg":"layer","description":"The layer (or its id or name) on which the selection will be checked"}
],
"examples": [
{ "expression":"is_selected()", "returns" : "True if the current feature is selected."},
{ "expression":"is_selected(get_feature('streets', 'name', \"street_name\"), 'streets')", "returns":"True if the current building's street is selected."}
]
}
12 changes: 12 additions & 0 deletions resources/function_help/json/num_selected
@@ -0,0 +1,12 @@
{
"name": "num_selected",
"type": "function",
"description": "Returns the number of selected features on a given layer. By default works on the layer on which the expression is evaluated.",
"arguments": [
{"arg":"layer","description":"The layer (or its id or name) on which the selection will be checked"}
],
"examples": [
{ "expression":"num_selected()", "returns":"The number of selected features on the current layer."},
{ "expression":"num_selected('streets')", "returns":"The number of selected features on the layer streets"}
]
}
106 changes: 97 additions & 9 deletions src/core/qgsexpression.cpp
Expand Up @@ -312,14 +312,19 @@ static QgsExpression::Node* getNode( const QVariant& value, QgsExpression* paren

QgsVectorLayer* getVectorLayer( const QVariant& value, QgsExpression* )
{
QString layerString = value.toString();
QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerString ) ); //search by id first
QgsVectorLayer* vl = value.value<QgsVectorLayer*>();
if ( !vl )
{
QList<QgsMapLayer *> layersByName = QgsMapLayerRegistry::instance()->mapLayersByName( layerString );
if ( !layersByName.isEmpty() )
QString layerString = value.toString();
vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerString ) ); //search by id first

if ( !vl )
{
vl = qobject_cast<QgsVectorLayer*>( layersByName.at( 0 ) );
QList<QgsMapLayer *> layersByName = QgsMapLayerRegistry::instance()->mapLayersByName( layerString );
if ( !layersByName.isEmpty() )
{
vl = qobject_cast<QgsVectorLayer*>( layersByName.at( 0 ) );
}
}
}

Expand Down Expand Up @@ -707,8 +712,7 @@ static QVariant fcnAggregateRelation( const QVariantList& values, const QgsExpre
}

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

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

return feat.attribute( attr );
}

static QVariant fcnIsSelected( const QVariantList& values, const QgsExpressionContext* context, QgsExpression* parent )
{
QgsVectorLayer* layer = nullptr;
QgsFeature feature;

if ( values.isEmpty() )
{
feature = context->feature();
layer = getVectorLayer( context->variable( "layer" ), parent );
}
else if ( values.size() == 1 )
{
layer = getVectorLayer( context->variable( "layer" ), parent );
feature = getFeature( values.at( 0 ), parent );
}
else if ( values.size() == 2 )
{
layer = getVectorLayer( values.at( 0 ), parent );
feature = getFeature( values.at( 1 ), parent );
}
else
{
parent->setEvalErrorString( QObject::tr( "Function `is_selected` requires no more than two parameters. %1 given." ).arg( values.length() ) );
return QVariant();
}

if ( !layer || !feature.isValid() )
{
return QVariant( QVariant::Bool );
}

return layer->selectedFeaturesIds().contains( feature.id() );
}

static QVariant fcnNumSelected( const QVariantList& values, const QgsExpressionContext* context, QgsExpression* parent )
{
QgsVectorLayer* layer = nullptr;

if ( values.isEmpty() )
layer = getVectorLayer( context->variable( "layer" ), parent );
else if ( values.count() == 1 )
layer = getVectorLayer( values.at( 0 ), parent );
else
{
parent->setEvalErrorString( QObject::tr( "Function `num_selected` requires no more than one parameter. %1 given." ).arg( values.length() ) );
return QVariant();
}

if ( !layer )
{
return QVariant( QVariant::LongLong );
}

return layer->selectedFeatureCount();
}

static QVariant fcnConcat( const QVariantList& values, const QgsExpressionContext*, QgsExpression *parent )
{
QString concat;
Expand Down Expand Up @@ -3665,10 +3725,37 @@ const QList<QgsExpression::Function*>& QgsExpression::Functions()
<< Parameter( QStringLiteral( "vertex" ) ), fcnAngleAtVertex, QStringLiteral( "GeometryGroup" ) )
<< new StaticFunction( QStringLiteral( "distance_to_vertex" ), ParameterList() << Parameter( QStringLiteral( "geometry" ) )
<< Parameter( QStringLiteral( "vertex" ) ), fcnDistanceToVertex, QStringLiteral( "GeometryGroup" ) )


// **Record** functions

<< new StaticFunction( QStringLiteral( "$id" ), 0, fcnFeatureId, QStringLiteral( "Record" ) )
<< new StaticFunction( QStringLiteral( "$currentfeature" ), 0, fcnFeature, QStringLiteral( "Record" ) )
<< new StaticFunction( QStringLiteral( "uuid" ), 0, fcnUuid, QStringLiteral( "Record" ), QString(), false, QSet<QString>(), false, QStringList() << QStringLiteral( "$uuid" ) )
<< new StaticFunction( QStringLiteral( "get_feature" ), 3, fcnGetFeature, QStringLiteral( "Record" ), QString(), false, QSet<QString>(), false, QStringList() << QStringLiteral( "getFeature" ) )

<< new StaticFunction(
QStringLiteral( "is_selected" ),
-1,
fcnIsSelected,
QStringLiteral( "Record" ),
QString(),
false,
QSet<QString>()
)

<< new StaticFunction(
QStringLiteral( "num_selected" ),
-1,
fcnNumSelected,
QStringLiteral( "Record" ),
QString(),
false,
QSet<QString>()
)

// **General** functions

<< new StaticFunction( QStringLiteral( "layer_property" ), 2, fcnGetLayerProperty, QStringLiteral( "General" ) )
<< new StaticFunction( QStringLiteral( "var" ), 1, fcnGetVariable, QStringLiteral( "General" ) )

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

//composition variables
gVariableHelpTexts.insert( QStringLiteral( "layout_numpages" ), QCoreApplication::translate( "variable_help", "Number of pages in composition." ) );
Expand Down
1 change: 1 addition & 0 deletions src/core/qgsexpressioncontext.cpp
Expand Up @@ -691,6 +691,7 @@ QgsExpressionContextScope* QgsExpressionContextUtils::layerScope( const QgsMapLa

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

const QgsVectorLayer* vLayer = dynamic_cast< const QgsVectorLayer* >( layer );
if ( vLayer )
Expand Down
2 changes: 1 addition & 1 deletion src/core/qgsexpressioncontext.h
Expand Up @@ -581,7 +581,7 @@ class CORE_EXPORT QgsExpressionContextUtils
/** Creates a new scope which contains variables and functions relating to a QgsMapLayer.
* For instance, layer name, id and fields.
*/
static QgsExpressionContextScope* layerScope( const QgsMapLayer *layer );
static QgsExpressionContextScope* layerScope( const QgsMapLayer* layer );

/** Sets a layer context variable. This variable will be contained within scopes retrieved via
* layerScope().
Expand Down
49 changes: 49 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Expand Up @@ -1355,6 +1355,55 @@ class TestQgsExpression: public QObject
QTest::newRow( "group by expression" ) << "sum(\"col1\", \"col1\" % 2)" << false << QVariant( 16 );
}

void selection()
{
QFETCH( QgsFeatureIds, selectedFeatures );
QFETCH( QString, expression );
QFETCH( QVariant, result );
QFETCH( QgsFeature, feature );
QFETCH( QgsVectorLayer*, layer );

QgsExpressionContext context;
if ( layer )
context.appendScope( QgsExpressionContextUtils::layerScope( layer ) );

QgsFeatureIds backupSelection = mMemoryLayer->selectedFeaturesIds();
context.setFeature( feature );

mMemoryLayer->selectByIds( selectedFeatures );

QgsExpression exp( expression );
QCOMPARE( exp.parserErrorString(), QString( "" ) );
exp.prepare( &context );
QVariant res = exp.evaluate( &context );
QCOMPARE( res, result );

mMemoryLayer->selectByIds( backupSelection );
}

void selection_data()
{
QTest::addColumn<QString>( "expression" );
QTest::addColumn<QgsFeatureIds>( "selectedFeatures" );
QTest::addColumn<QgsFeature>( "feature" );
QTest::addColumn<QgsVectorLayer*>( "layer" );
QTest::addColumn<QVariant>( "result" );

QgsFeature firstFeature = mMemoryLayer->getFeature( 1 );
QgsVectorLayer* noLayer = nullptr;

QTest::newRow( "empty selection num_selected" ) << "num_selected()" << QgsFeatureIds() << firstFeature << mMemoryLayer << QVariant( 0 );
QTest::newRow( "empty selection is_selected" ) << "is_selected()" << QgsFeatureIds() << firstFeature << mMemoryLayer << QVariant( false );
QTest::newRow( "two_selected" ) << "num_selected()" << ( QgsFeatureIds() << 1 << 2 ) << firstFeature << mMemoryLayer << QVariant( 2 );
QTest::newRow( "is_selected" ) << "is_selected()" << ( QgsFeatureIds() << 1 << 2 ) << firstFeature << mMemoryLayer << QVariant( true );
QTest::newRow( "not_selected" ) << "is_selected()" << ( QgsFeatureIds() << 4 << 2 ) << firstFeature << mMemoryLayer << QVariant( false );
QTest::newRow( "no layer num_selected" ) << "num_selected()" << ( QgsFeatureIds() << 4 << 2 ) << QgsFeature() << noLayer << QVariant( QVariant::LongLong );
QTest::newRow( "no layer is_selected" ) << "is_selected()" << ( QgsFeatureIds() << 4 << 2 ) << QgsFeature() << noLayer << QVariant( QVariant::Bool );
QTest::newRow( "no layer num_selected" ) << "num_selected()" << ( QgsFeatureIds() << 4 << 2 ) << QgsFeature() << noLayer << QVariant( QVariant::LongLong );
QTest::newRow( "is_selected with params" ) << "is_selected('test', get_feature('test', 'col1', 10))" << ( QgsFeatureIds() << 4 << 2 ) << QgsFeature() << noLayer << QVariant( QVariant::Bool );
QTest::newRow( "num_selected with params" ) << "num_selected('test')" << ( QgsFeatureIds() << 4 << 2 ) << QgsFeature() << noLayer << QVariant( 2 );
}

void layerAggregates()
{
QgsExpressionContext context;
Expand Down

0 comments on commit 01f3c9a

Please sign in to comment.