Skip to content

Commit e4c69ff

Browse files
authored
Merge pull request #7758 from 3nids/search_alllayers
[FEATURE] add new locator filter searching across all layers
2 parents 694f86a + 5c172a6 commit e4c69ff

File tree

6 files changed

+186
-0
lines changed

6 files changed

+186
-0
lines changed

src/app/locator/qgsinbuiltlocatorfilters.cpp

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@ void QgsActionLocatorFilter::searchActions( const QString &string, QWidget *pare
191191
}
192192
}
193193

194+
//
195+
// QgsActiveLayerFeaturesLocatorFilter
196+
//
197+
194198
QgsActiveLayerFeaturesLocatorFilter::QgsActiveLayerFeaturesLocatorFilter( QObject *parent )
195199
: QgsLocatorFilter( parent )
196200
{
@@ -298,6 +302,108 @@ void QgsActiveLayerFeaturesLocatorFilter::triggerResult( const QgsLocatorResult
298302
QgisApp::instance()->mapCanvas()->zoomToFeatureIds( layer, QgsFeatureIds() << id );
299303
}
300304

305+
//
306+
// QgsAllLayersFeaturesLocatorFilter
307+
//
308+
309+
QgsAllLayersFeaturesLocatorFilter::QgsAllLayersFeaturesLocatorFilter( QObject *parent )
310+
: QgsLocatorFilter( parent )
311+
{
312+
setUseWithoutPrefix( false );
313+
}
314+
315+
QgsAllLayersFeaturesLocatorFilter *QgsAllLayersFeaturesLocatorFilter::clone() const
316+
{
317+
return new QgsAllLayersFeaturesLocatorFilter();
318+
}
319+
320+
void QgsAllLayersFeaturesLocatorFilter::prepare( const QString &string, const QgsLocatorContext & )
321+
{
322+
if ( string.length() < 3 )
323+
return;
324+
325+
const QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
326+
for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
327+
{
328+
QgsVectorLayer *layer = qobject_cast< QgsVectorLayer *>( it.value() );
329+
if ( !layer || !layer->flags().testFlag( QgsMapLayer::Searchable ) )
330+
continue;
331+
332+
QgsExpression expression( layer->displayExpression() );
333+
QgsExpressionContext context;
334+
context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) );
335+
expression.prepare( &context );
336+
337+
QgsFeatureRequest req;
338+
req.setSubsetOfAttributes( expression.referencedAttributeIndexes( layer->fields() ).toList() );
339+
if ( !expression.needsGeometry() )
340+
req.setFlags( QgsFeatureRequest::NoGeometry );
341+
req.setFilterExpression( QStringLiteral( "%1 ILIKE '%%2%'" )
342+
.arg( layer->displayExpression() )
343+
.arg( string ) );
344+
req.setLimit( 30 );
345+
346+
PreparedLayer preparedLayer;
347+
preparedLayer.expression = expression;
348+
preparedLayer.context = context;
349+
preparedLayer.layerId = layer->id();
350+
preparedLayer.layerName = layer->name();
351+
preparedLayer.iterator = layer->getFeatures( req );
352+
preparedLayer.layerIcon = QgsMapLayerModel::iconForLayer( layer );
353+
354+
mPreparedLayers.append( preparedLayer );
355+
}
356+
}
357+
358+
void QgsAllLayersFeaturesLocatorFilter::fetchResults( const QString &string, const QgsLocatorContext &, QgsFeedback *feedback )
359+
{
360+
int foundInCurrentLayer;
361+
int foundInTotal = 0;
362+
QgsFeature f;
363+
364+
// we cannot used const loop since iterator::nextFeature is not const
365+
for ( PreparedLayer preparedLayer : mPreparedLayers )
366+
{
367+
foundInCurrentLayer = 0;
368+
while ( preparedLayer.iterator.nextFeature( f ) )
369+
{
370+
if ( feedback->isCanceled() )
371+
return;
372+
373+
QgsLocatorResult result;
374+
result.group = preparedLayer.layerName;
375+
376+
preparedLayer.context.setFeature( f );
377+
378+
result.displayString = preparedLayer.expression.evaluate( &( preparedLayer.context ) ).toString();
379+
380+
result.userData = QVariantList() << f.id() << preparedLayer.layerId;
381+
result.icon = preparedLayer.layerIcon;
382+
result.score = static_cast< double >( string.length() ) / result.displayString.size();
383+
emit resultFetched( result );
384+
385+
foundInCurrentLayer++;
386+
foundInTotal++;
387+
if ( foundInCurrentLayer >= mMaxResultsPerLayer )
388+
break;
389+
}
390+
if ( foundInTotal >= mMaxTotalResults )
391+
break;
392+
}
393+
}
394+
395+
void QgsAllLayersFeaturesLocatorFilter::triggerResult( const QgsLocatorResult &result )
396+
{
397+
QVariantList dataList = result.userData.toList();
398+
QgsFeatureId id = dataList.at( 0 ).toLongLong();
399+
QString layerId = dataList.at( 1 ).toString();
400+
QgsVectorLayer *layer = qobject_cast< QgsVectorLayer *>( QgsProject::instance()->mapLayer( layerId ) );
401+
if ( !layer )
402+
return;
403+
404+
QgisApp::instance()->mapCanvas()->zoomToFeatureIds( layer, QgsFeatureIds() << id );
405+
}
406+
301407
//
302408
// QgsExpressionCalculatorLocatorFilter
303409
//

src/app/locator/qgsinbuiltlocatorfilters.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,41 @@ class QgsActiveLayerFeaturesLocatorFilter : public QgsLocatorFilter
113113
QIcon mLayerIcon;
114114
};
115115

116+
class APP_EXPORT QgsAllLayersFeaturesLocatorFilter : public QgsLocatorFilter
117+
{
118+
Q_OBJECT
119+
120+
public:
121+
struct PreparedLayer
122+
{
123+
public:
124+
QgsExpression expression;
125+
QgsExpressionContext context;
126+
QgsFeatureIterator iterator;
127+
QString layerName;
128+
QString layerId;
129+
QIcon layerIcon;
130+
} ;
131+
132+
QgsAllLayersFeaturesLocatorFilter( QObject *parent = nullptr );
133+
QgsAllLayersFeaturesLocatorFilter *clone() const override;
134+
QString name() const override { return QStringLiteral( "allfeatures" ); }
135+
QString displayName() const override { return tr( "Features In All Layers" ); }
136+
Priority priority() const override { return Medium; }
137+
QString prefix() const override { return QStringLiteral( "af" ); }
138+
139+
void prepare( const QString &string, const QgsLocatorContext &context ) override;
140+
void fetchResults( const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback ) override;
141+
void triggerResult( const QgsLocatorResult &result ) override;
142+
143+
private:
144+
int mMaxResultsPerLayer = 6;
145+
int mMaxTotalResults = 12;
146+
QList<PreparedLayer> mPreparedLayers;
147+
148+
149+
};
150+
116151
class APP_EXPORT QgsExpressionCalculatorLocatorFilter : public QgsLocatorFilter
117152
{
118153
Q_OBJECT

src/app/qgisapp.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3125,6 +3125,7 @@ void QgisApp::createStatusBar()
31253125

31263126
mLocatorWidget->locator()->registerFilter( new QgsActionLocatorFilter( actionObjects ) );
31273127
mLocatorWidget->locator()->registerFilter( new QgsActiveLayerFeaturesLocatorFilter() );
3128+
mLocatorWidget->locator()->registerFilter( new QgsAllLayersFeaturesLocatorFilter() );
31283129
mLocatorWidget->locator()->registerFilter( new QgsExpressionCalculatorLocatorFilter() );
31293130
mLocatorWidget->locator()->registerFilter( new QgsBookmarkLocatorFilter() );
31303131
mLocatorWidget->locator()->registerFilter( new QgsSettingsLocatorFilter() );

src/core/locator/qgslocator.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const QList<QString> QgsLocator::CORE_FILTERS = QList<QString>() << QStringLiter
2525
<< QStringLiteral( "layertree" )
2626
<< QStringLiteral( "layouts" )
2727
<< QStringLiteral( "features" )
28+
<< QStringLiteral( "allfeatures" )
2829
<< QStringLiteral( "calculator" )
2930
<< QStringLiteral( "bookmarks" )
3031
<< QStringLiteral( "optionpages" );

src/core/locator/qgslocatorfilter.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ void QgsLocatorFilter::setUseWithoutPrefix( bool useWithoutPrefix )
7070

7171
QString QgsLocatorFilter::activePrefix() const
7272
{
73+
// do not change this to isEmpty!
74+
// if any issue with an in-built locator filter
75+
// do not forget to add it in QgsLocator::CORE_FILTERS
7376
if ( mActivePrefifx.isNull() )
7477
return prefix();
7578
else

tests/src/app/testqgsapplocatorfilters.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class TestQgsAppLocatorFilters : public QObject
3636
void testCalculator();
3737
void testLayers();
3838
void testLayouts();
39+
void testSearchAllLayers();
3940

4041
private:
4142
QgisApp *mQgisApp = nullptr;
@@ -145,13 +146,52 @@ void TestQgsAppLocatorFilters::testLayouts()
145146
QCOMPARE( results.at( 0 ).userData.toString(), pl1->name() );
146147
QCOMPARE( results.at( 1 ).userData.toString(), pl2->name() );
147148
QCOMPARE( results.at( 2 ).userData.toString(), pl3->name() );
149+
}
150+
151+
void TestQgsAppLocatorFilters::testSearchAllLayers()
152+
{
153+
QString layerDef = QStringLiteral( "Point?crs=epsg:4326&field=pk:integer&field=my_text:string&field=my_number:integer&key=pk" );
154+
QgsVectorLayer *l1 = new QgsVectorLayer( layerDef, QStringLiteral( "Layer 1" ), QStringLiteral( "memory" ) );
155+
QgsVectorLayer *l2 = new QgsVectorLayer( layerDef, QStringLiteral( "Layer 2" ), QStringLiteral( "memory" ) );
156+
157+
QgsProject::instance()->addMapLayers( QList< QgsMapLayer *>() << l1 << l2 );
158+
159+
QgsFeature f1;
160+
f1.setAttributes( QVector<QVariant>() << 1001 << "A nice feature" << 6789 );
161+
f1.setGeometry( QgsGeometry::fromWkt( "Point (-71.123 78.23)" ) );
162+
QgsFeature f2;
163+
f2.setAttributes( QVector<QVariant>() << 1002 << "Something crazy" << 2 );
164+
f2.setGeometry( QgsGeometry::fromWkt( "Point (-72.123 78.23)" ) );
165+
QgsFeature f3;
166+
f3.setAttributes( QVector<QVariant>() << 2001 << "Another feature" << 6789 );
167+
f3.setGeometry( QgsGeometry::fromWkt( "Point (-73.123 78.23)" ) );
168+
169+
l1->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 );
170+
l2->dataProvider()->addFeatures( QgsFeatureList() << f3 );
171+
172+
QgsAllLayersFeaturesLocatorFilter filter;
173+
QgsLocatorContext context;
174+
175+
QList< QgsLocatorResult > results = gatherResults( &filter, QStringLiteral( "100" ), context );
176+
QCOMPARE( results.count(), 2 );
148177

178+
l1->setDisplayExpression( QStringLiteral( "\"my_text\" || ' is ' || \"my_number\"" ) );
179+
l2->setDisplayExpression( QStringLiteral( "\"my_text\" || ' is ' || \"my_number\"" ) );
180+
181+
results = gatherResults( &filter, QStringLiteral( "feature is 6789" ), context );
182+
QCOMPARE( results.count(), 2 );
183+
184+
l2->setFlags( l2->flags() & ~QgsMapLayer::Searchable );
185+
186+
results = gatherResults( &filter, QStringLiteral( "feature is 6789" ), context );
187+
QCOMPARE( results.count(), 1 );
149188
}
150189

151190
QList<QgsLocatorResult> TestQgsAppLocatorFilters::gatherResults( QgsLocatorFilter *filter, const QString &string, const QgsLocatorContext &context )
152191
{
153192
QSignalSpy spy( filter, &QgsLocatorFilter::resultFetched );
154193
QgsFeedback f;
194+
filter->prepare( string, context );
155195
filter->fetchResults( string, context, &f );
156196

157197
QList< QgsLocatorResult > results;

0 commit comments

Comments
 (0)