[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"
This commit is contained in:
Matthias Kuhn 2016-10-27 14:45:13 +02:00
parent f52dfba21e
commit 01f3c9ae38
6 changed files with 173 additions and 10 deletions

View File

@ -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."}
]
}

View File

@ -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"}
]
}

View File

@ -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 ) );
}
}
}
@ -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" ) );
@ -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" ) );
@ -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;
@ -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" ) )
@ -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." ) );

View File

@ -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 )

View File

@ -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().

View File

@ -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;