Navigation Menu

Skip to content

Commit

Permalink
Merge pull request #7758 from 3nids/search_alllayers
Browse files Browse the repository at this point in the history
[FEATURE] add new locator filter searching across all layers
  • Loading branch information
3nids committed Sep 8, 2018
2 parents 694f86a + 5c172a6 commit e4c69ff
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 0 deletions.
106 changes: 106 additions & 0 deletions src/app/locator/qgsinbuiltlocatorfilters.cpp
Expand Up @@ -191,6 +191,10 @@ void QgsActionLocatorFilter::searchActions( const QString &string, QWidget *pare
}
}

//
// QgsActiveLayerFeaturesLocatorFilter
//

QgsActiveLayerFeaturesLocatorFilter::QgsActiveLayerFeaturesLocatorFilter( QObject *parent )
: QgsLocatorFilter( parent )
{
Expand Down Expand Up @@ -298,6 +302,108 @@ void QgsActiveLayerFeaturesLocatorFilter::triggerResult( const QgsLocatorResult
QgisApp::instance()->mapCanvas()->zoomToFeatureIds( layer, QgsFeatureIds() << id );
}

//
// QgsAllLayersFeaturesLocatorFilter
//

QgsAllLayersFeaturesLocatorFilter::QgsAllLayersFeaturesLocatorFilter( QObject *parent )
: QgsLocatorFilter( parent )
{
setUseWithoutPrefix( false );
}

QgsAllLayersFeaturesLocatorFilter *QgsAllLayersFeaturesLocatorFilter::clone() const
{
return new QgsAllLayersFeaturesLocatorFilter();
}

void QgsAllLayersFeaturesLocatorFilter::prepare( const QString &string, const QgsLocatorContext & )
{
if ( string.length() < 3 )
return;

const QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
{
QgsVectorLayer *layer = qobject_cast< QgsVectorLayer *>( it.value() );
if ( !layer || !layer->flags().testFlag( QgsMapLayer::Searchable ) )
continue;

QgsExpression expression( layer->displayExpression() );
QgsExpressionContext context;
context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) );
expression.prepare( &context );

QgsFeatureRequest req;
req.setSubsetOfAttributes( expression.referencedAttributeIndexes( layer->fields() ).toList() );
if ( !expression.needsGeometry() )
req.setFlags( QgsFeatureRequest::NoGeometry );
req.setFilterExpression( QStringLiteral( "%1 ILIKE '%%2%'" )
.arg( layer->displayExpression() )
.arg( string ) );
req.setLimit( 30 );

PreparedLayer preparedLayer;
preparedLayer.expression = expression;
preparedLayer.context = context;
preparedLayer.layerId = layer->id();
preparedLayer.layerName = layer->name();
preparedLayer.iterator = layer->getFeatures( req );
preparedLayer.layerIcon = QgsMapLayerModel::iconForLayer( layer );

mPreparedLayers.append( preparedLayer );
}
}

void QgsAllLayersFeaturesLocatorFilter::fetchResults( const QString &string, const QgsLocatorContext &, QgsFeedback *feedback )
{
int foundInCurrentLayer;
int foundInTotal = 0;
QgsFeature f;

// we cannot used const loop since iterator::nextFeature is not const
for ( PreparedLayer preparedLayer : mPreparedLayers )
{
foundInCurrentLayer = 0;
while ( preparedLayer.iterator.nextFeature( f ) )
{
if ( feedback->isCanceled() )
return;

QgsLocatorResult result;
result.group = preparedLayer.layerName;

preparedLayer.context.setFeature( f );

result.displayString = preparedLayer.expression.evaluate( &( preparedLayer.context ) ).toString();

result.userData = QVariantList() << f.id() << preparedLayer.layerId;
result.icon = preparedLayer.layerIcon;
result.score = static_cast< double >( string.length() ) / result.displayString.size();
emit resultFetched( result );

foundInCurrentLayer++;
foundInTotal++;
if ( foundInCurrentLayer >= mMaxResultsPerLayer )
break;
}
if ( foundInTotal >= mMaxTotalResults )
break;
}
}

void QgsAllLayersFeaturesLocatorFilter::triggerResult( const QgsLocatorResult &result )
{
QVariantList dataList = result.userData.toList();
QgsFeatureId id = dataList.at( 0 ).toLongLong();
QString layerId = dataList.at( 1 ).toString();
QgsVectorLayer *layer = qobject_cast< QgsVectorLayer *>( QgsProject::instance()->mapLayer( layerId ) );
if ( !layer )
return;

QgisApp::instance()->mapCanvas()->zoomToFeatureIds( layer, QgsFeatureIds() << id );
}

//
// QgsExpressionCalculatorLocatorFilter
//
Expand Down
35 changes: 35 additions & 0 deletions src/app/locator/qgsinbuiltlocatorfilters.h
Expand Up @@ -113,6 +113,41 @@ class QgsActiveLayerFeaturesLocatorFilter : public QgsLocatorFilter
QIcon mLayerIcon;
};

class APP_EXPORT QgsAllLayersFeaturesLocatorFilter : public QgsLocatorFilter
{
Q_OBJECT

public:
struct PreparedLayer
{
public:
QgsExpression expression;
QgsExpressionContext context;
QgsFeatureIterator iterator;
QString layerName;
QString layerId;
QIcon layerIcon;
} ;

QgsAllLayersFeaturesLocatorFilter( QObject *parent = nullptr );
QgsAllLayersFeaturesLocatorFilter *clone() const override;
QString name() const override { return QStringLiteral( "allfeatures" ); }
QString displayName() const override { return tr( "Features In All Layers" ); }
Priority priority() const override { return Medium; }
QString prefix() const override { return QStringLiteral( "af" ); }

void prepare( const QString &string, const QgsLocatorContext &context ) override;
void fetchResults( const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback ) override;
void triggerResult( const QgsLocatorResult &result ) override;

private:
int mMaxResultsPerLayer = 6;
int mMaxTotalResults = 12;
QList<PreparedLayer> mPreparedLayers;


};

class APP_EXPORT QgsExpressionCalculatorLocatorFilter : public QgsLocatorFilter
{
Q_OBJECT
Expand Down
1 change: 1 addition & 0 deletions src/app/qgisapp.cpp
Expand Up @@ -3125,6 +3125,7 @@ void QgisApp::createStatusBar()

mLocatorWidget->locator()->registerFilter( new QgsActionLocatorFilter( actionObjects ) );
mLocatorWidget->locator()->registerFilter( new QgsActiveLayerFeaturesLocatorFilter() );
mLocatorWidget->locator()->registerFilter( new QgsAllLayersFeaturesLocatorFilter() );
mLocatorWidget->locator()->registerFilter( new QgsExpressionCalculatorLocatorFilter() );
mLocatorWidget->locator()->registerFilter( new QgsBookmarkLocatorFilter() );
mLocatorWidget->locator()->registerFilter( new QgsSettingsLocatorFilter() );
Expand Down
1 change: 1 addition & 0 deletions src/core/locator/qgslocator.cpp
Expand Up @@ -25,6 +25,7 @@ const QList<QString> QgsLocator::CORE_FILTERS = QList<QString>() << QStringLiter
<< QStringLiteral( "layertree" )
<< QStringLiteral( "layouts" )
<< QStringLiteral( "features" )
<< QStringLiteral( "allfeatures" )
<< QStringLiteral( "calculator" )
<< QStringLiteral( "bookmarks" )
<< QStringLiteral( "optionpages" );
Expand Down
3 changes: 3 additions & 0 deletions src/core/locator/qgslocatorfilter.cpp
Expand Up @@ -70,6 +70,9 @@ void QgsLocatorFilter::setUseWithoutPrefix( bool useWithoutPrefix )

QString QgsLocatorFilter::activePrefix() const
{
// do not change this to isEmpty!
// if any issue with an in-built locator filter
// do not forget to add it in QgsLocator::CORE_FILTERS
if ( mActivePrefifx.isNull() )
return prefix();
else
Expand Down
40 changes: 40 additions & 0 deletions tests/src/app/testqgsapplocatorfilters.cpp
Expand Up @@ -36,6 +36,7 @@ class TestQgsAppLocatorFilters : public QObject
void testCalculator();
void testLayers();
void testLayouts();
void testSearchAllLayers();

private:
QgisApp *mQgisApp = nullptr;
Expand Down Expand Up @@ -145,13 +146,52 @@ void TestQgsAppLocatorFilters::testLayouts()
QCOMPARE( results.at( 0 ).userData.toString(), pl1->name() );
QCOMPARE( results.at( 1 ).userData.toString(), pl2->name() );
QCOMPARE( results.at( 2 ).userData.toString(), pl3->name() );
}

void TestQgsAppLocatorFilters::testSearchAllLayers()
{
QString layerDef = QStringLiteral( "Point?crs=epsg:4326&field=pk:integer&field=my_text:string&field=my_number:integer&key=pk" );
QgsVectorLayer *l1 = new QgsVectorLayer( layerDef, QStringLiteral( "Layer 1" ), QStringLiteral( "memory" ) );
QgsVectorLayer *l2 = new QgsVectorLayer( layerDef, QStringLiteral( "Layer 2" ), QStringLiteral( "memory" ) );

QgsProject::instance()->addMapLayers( QList< QgsMapLayer *>() << l1 << l2 );

QgsFeature f1;
f1.setAttributes( QVector<QVariant>() << 1001 << "A nice feature" << 6789 );
f1.setGeometry( QgsGeometry::fromWkt( "Point (-71.123 78.23)" ) );
QgsFeature f2;
f2.setAttributes( QVector<QVariant>() << 1002 << "Something crazy" << 2 );
f2.setGeometry( QgsGeometry::fromWkt( "Point (-72.123 78.23)" ) );
QgsFeature f3;
f3.setAttributes( QVector<QVariant>() << 2001 << "Another feature" << 6789 );
f3.setGeometry( QgsGeometry::fromWkt( "Point (-73.123 78.23)" ) );

l1->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 );
l2->dataProvider()->addFeatures( QgsFeatureList() << f3 );

QgsAllLayersFeaturesLocatorFilter filter;
QgsLocatorContext context;

QList< QgsLocatorResult > results = gatherResults( &filter, QStringLiteral( "100" ), context );
QCOMPARE( results.count(), 2 );

l1->setDisplayExpression( QStringLiteral( "\"my_text\" || ' is ' || \"my_number\"" ) );
l2->setDisplayExpression( QStringLiteral( "\"my_text\" || ' is ' || \"my_number\"" ) );

results = gatherResults( &filter, QStringLiteral( "feature is 6789" ), context );
QCOMPARE( results.count(), 2 );

l2->setFlags( l2->flags() & ~QgsMapLayer::Searchable );

results = gatherResults( &filter, QStringLiteral( "feature is 6789" ), context );
QCOMPARE( results.count(), 1 );
}

QList<QgsLocatorResult> TestQgsAppLocatorFilters::gatherResults( QgsLocatorFilter *filter, const QString &string, const QgsLocatorContext &context )
{
QSignalSpy spy( filter, &QgsLocatorFilter::resultFetched );
QgsFeedback f;
filter->prepare( string, context );
filter->fetchResults( string, context, &f );

QList< QgsLocatorResult > results;
Expand Down

0 comments on commit e4c69ff

Please sign in to comment.