Skip to content

Commit 981afb3

Browse files
committedFeb 12, 2018
Make QgsLocator more thread safe
- add a clone() method to filters, and always search using the clone instead of the original filter - add a prepare() method to filters, which is always run in the main thread and can be used to prepare the filter for safe background execution (e.g. creating feature iterators in advance) - don't use QtConcurrent to perform searches in background threads, since it is not safe to use with QObjects - instead manually create threads and ensure that cloned objects are always moved to the thread that they will run in, to ensure that they correctly have thread affinity with the thread in which they are executed
1 parent 5431a2d commit 981afb3

File tree

12 files changed

+236
-75
lines changed

12 files changed

+236
-75
lines changed
 

‎.ci/travis/linux/blacklist.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@ PyQgsSpatialiteProvider
3030
# Flaky, see https://travis-ci.org/qgis/QGIS/jobs/297708174
3131
PyQgsServerAccessControl
3232

33-
# Flaky, see https://dash.orfeo-toolbox.org/testDetails.php?test=61670866&build=297397
34-
PyQgsLocator
35-
3633
# Need a local postgres installation
3734
PyQgsAuthManagerPKIPostgresTest
3835
PyQgsAuthManagerPasswordPostgresTest

‎python/core/locator/qgslocatorfilter.sip.in

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ Abstract base class for filters which collect locator results.
7272
QgsLocatorFilter( QObject *parent = 0 );
7373
%Docstring
7474
Constructor for QgsLocatorFilter.
75+
%End
76+
77+
virtual QgsLocatorFilter *clone() const = 0 /Factory/;
78+
%Docstring
79+
Creates a clone of the filter. New requests are always executed in a
80+
clone of the original filter.
7581
%End
7682

7783
virtual QString name() const = 0;
@@ -108,6 +114,20 @@ results from this filter.
108114
be ignored.
109115
%End
110116

117+
virtual void prepare( const QString &string, const QgsLocatorContext &context );
118+
%Docstring
119+
Prepares the filter instance for an upcoming search for the specified ``string``. This method is always called
120+
from the main thread, and individual filter subclasses should perform whatever
121+
tasks are required in order to allow a subsequent search to safely execute
122+
on a background thread.
123+
%End
124+
125+
void executeSearchAndDelete( const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback );
126+
%Docstring
127+
Executes a search for this filter instance, and then deletes the current instance
128+
of the filter.
129+
%End
130+
111131
virtual void fetchResults( const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback ) = 0;
112132
%Docstring
113133
Retrieves the filter results for a specified search ``string``. The ``context``
@@ -190,6 +210,8 @@ custom configuration widget.
190210

191211
signals:
192212

213+
void finished();
214+
193215
void resultFetched( const QgsLocatorResult &result );
194216
%Docstring
195217
Should be emitted by filters whenever they encounter a matching result

‎python/plugins/processing/gui/AlgorithmLocatorFilter.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ class AlgorithmLocatorFilter(QgsLocatorFilter):
3939

4040
def __init__(self, parent=None):
4141
super(AlgorithmLocatorFilter, self).__init__(parent)
42+
self.found_results = []
43+
44+
def clone(self):
45+
return AlgorithmLocatorFilter()
4246

4347
def name(self):
4448
return 'processing_alg'
@@ -52,10 +56,10 @@ def priority(self):
5256
def prefix(self):
5357
return 'a'
5458

55-
def fetchResults(self, string, context, feedback):
59+
def prepare(self, string, context):
60+
# collect results in main thread, since this method is inexpensive and
61+
# accessing the processing registry is not thread safe
5662
for a in QgsApplication.processingRegistry().algorithms():
57-
if feedback.isCanceled():
58-
return
5963
if a.flags() & QgsProcessingAlgorithm.FlagHideFromToolbox:
6064
continue
6165

@@ -69,7 +73,11 @@ def fetchResults(self, string, context, feedback):
6973
result.score = float(len(string)) / len(a.displayName())
7074
else:
7175
result.score = 0
72-
self.resultFetched.emit(result)
76+
self.found_results.append(result)
77+
78+
def fetchResults(self, string, context, feedback):
79+
for result in self.found_results:
80+
self.resultFetched.emit(result)
7381

7482
def triggerResult(self, result):
7583
alg = QgsApplication.processingRegistry().createAlgorithmById(result.userData)

‎src/app/locator/qgsinbuiltlocatorfilters.cpp

Lines changed: 82 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -31,28 +31,39 @@ QgsLayerTreeLocatorFilter::QgsLayerTreeLocatorFilter( QObject *parent )
3131
: QgsLocatorFilter( parent )
3232
{}
3333

34-
void QgsLayerTreeLocatorFilter::fetchResults( const QString &string, const QgsLocatorContext &, QgsFeedback *feedback )
34+
QgsLayerTreeLocatorFilter *QgsLayerTreeLocatorFilter::clone() const
3535
{
36+
return new QgsLayerTreeLocatorFilter();
37+
}
38+
39+
void QgsLayerTreeLocatorFilter::prepare( const QString &string, const QgsLocatorContext & )
40+
{
41+
// collect results in main thread, since this method is inexpensive and
42+
// accessing the layer tree root is not thread safe
3643
QgsLayerTree *tree = QgsProject::instance()->layerTreeRoot();
37-
QList<QgsLayerTreeLayer *> layers = tree->findLayers();
38-
Q_FOREACH ( QgsLayerTreeLayer *layer, layers )
44+
const QList<QgsLayerTreeLayer *> layers = tree->findLayers();
45+
for ( QgsLayerTreeLayer *layer : layers )
3946
{
40-
if ( feedback->isCanceled() )
41-
return;
42-
4347
if ( layer->layer() && stringMatches( layer->layer()->name(), string ) )
4448
{
4549
QgsLocatorResult result;
46-
result.filter = this;
4750
result.displayString = layer->layer()->name();
4851
result.userData = layer->layerId();
4952
result.icon = QgsMapLayerModel::iconForLayer( layer->layer() );
5053
result.score = static_cast< double >( string.length() ) / layer->layer()->name().length();
51-
emit resultFetched( result );
54+
mResults.append( result );
5255
}
5356
}
5457
}
5558

59+
void QgsLayerTreeLocatorFilter::fetchResults( const QString &, const QgsLocatorContext &, QgsFeedback * )
60+
{
61+
for ( const QgsLocatorResult &result : qgis::as_const( mResults ) )
62+
{
63+
emit resultFetched( result );
64+
}
65+
}
66+
5667
void QgsLayerTreeLocatorFilter::triggerResult( const QgsLocatorResult &result )
5768
{
5869
QString layerId = result.userData.toString();
@@ -68,27 +79,39 @@ QgsLayoutLocatorFilter::QgsLayoutLocatorFilter( QObject *parent )
6879
: QgsLocatorFilter( parent )
6980
{}
7081

71-
void QgsLayoutLocatorFilter::fetchResults( const QString &string, const QgsLocatorContext &, QgsFeedback *feedback )
82+
QgsLayoutLocatorFilter *QgsLayoutLocatorFilter::clone() const
7283
{
84+
return new QgsLayoutLocatorFilter();
85+
}
86+
87+
void QgsLayoutLocatorFilter::prepare( const QString &string, const QgsLocatorContext & )
88+
{
89+
// collect results in main thread, since this method is inexpensive and
90+
// accessing the project layouts is not thread safe
91+
7392
const QList< QgsMasterLayoutInterface * > layouts = QgsProject::instance()->layoutManager()->layouts();
7493
for ( QgsMasterLayoutInterface *layout : layouts )
7594
{
76-
if ( feedback->isCanceled() )
77-
return;
78-
7995
if ( layout && stringMatches( layout->name(), string ) )
8096
{
8197
QgsLocatorResult result;
82-
result.filter = this;
8398
result.displayString = layout->name();
8499
result.userData = layout->name();
85100
//result.icon = QgsMapLayerModel::iconForLayer( layer->layer() );
86101
result.score = static_cast< double >( string.length() ) / layout->name().length();
87-
emit resultFetched( result );
102+
mResults.append( result );
88103
}
89104
}
90105
}
91106

107+
void QgsLayoutLocatorFilter::fetchResults( const QString &, const QgsLocatorContext &, QgsFeedback * )
108+
{
109+
for ( const QgsLocatorResult &result : qgis::as_const( mResults ) )
110+
{
111+
emit resultFetched( result );
112+
}
113+
}
114+
92115
void QgsLayoutLocatorFilter::triggerResult( const QgsLocatorResult &result )
93116
{
94117
QString layoutName = result.userData.toString();
@@ -109,19 +132,32 @@ QgsActionLocatorFilter::QgsActionLocatorFilter( const QList<QWidget *> &parentOb
109132
setUseWithoutPrefix( false );
110133
}
111134

112-
void QgsActionLocatorFilter::fetchResults( const QString &string, const QgsLocatorContext &, QgsFeedback *feedback )
135+
QgsActionLocatorFilter *QgsActionLocatorFilter::clone() const
113136
{
137+
return new QgsActionLocatorFilter( mActionParents );
138+
}
139+
140+
void QgsActionLocatorFilter::prepare( const QString &string, const QgsLocatorContext & )
141+
{
142+
// collect results in main thread, since this method is inexpensive and
143+
// accessing the gui actions is not thread safe
144+
114145
QList<QAction *> found;
115146

116-
Q_FOREACH ( QWidget *object, mActionParents )
147+
for ( QWidget *object : qgis::as_const( mActionParents ) )
117148
{
118-
if ( feedback->isCanceled() )
119-
return;
120-
121149
searchActions( string, object, found );
122150
}
123151
}
124152

153+
void QgsActionLocatorFilter::fetchResults( const QString &, const QgsLocatorContext &, QgsFeedback * )
154+
{
155+
for ( const QgsLocatorResult &result : qgis::as_const( mResults ) )
156+
{
157+
emit resultFetched( result );
158+
}
159+
}
160+
125161
void QgsActionLocatorFilter::triggerResult( const QgsLocatorResult &result )
126162
{
127163
QAction *action = qobject_cast< QAction * >( qvariant_cast<QObject *>( result.userData ) );
@@ -131,8 +167,8 @@ void QgsActionLocatorFilter::triggerResult( const QgsLocatorResult &result )
131167

132168
void QgsActionLocatorFilter::searchActions( const QString &string, QWidget *parent, QList<QAction *> &found )
133169
{
134-
QList< QWidget *> children = parent->findChildren<QWidget *>();
135-
Q_FOREACH ( QWidget *widget, children )
170+
const QList< QWidget *> children = parent->findChildren<QWidget *>();
171+
for ( QWidget *widget : children )
136172
{
137173
searchActions( string, widget, found );
138174
}
@@ -154,12 +190,11 @@ void QgsActionLocatorFilter::searchActions( const QString &string, QWidget *pare
154190
if ( stringMatches( searchText, string ) )
155191
{
156192
QgsLocatorResult result;
157-
result.filter = this;
158193
result.displayString = searchText;
159194
result.userData = QVariant::fromValue( action );
160195
result.icon = action->icon();
161196
result.score = static_cast< double >( string.length() ) / searchText.length();
162-
emit resultFetched( result );
197+
mResults.append( result );
163198
found << action;
164199
}
165200
}
@@ -171,7 +206,12 @@ QgsActiveLayerFeaturesLocatorFilter::QgsActiveLayerFeaturesLocatorFilter( QObjec
171206
setUseWithoutPrefix( false );
172207
}
173208

174-
void QgsActiveLayerFeaturesLocatorFilter::fetchResults( const QString &string, const QgsLocatorContext &, QgsFeedback *feedback )
209+
QgsActiveLayerFeaturesLocatorFilter *QgsActiveLayerFeaturesLocatorFilter::clone() const
210+
{
211+
return new QgsActiveLayerFeaturesLocatorFilter();
212+
}
213+
214+
void QgsActiveLayerFeaturesLocatorFilter::prepare( const QString &string, const QgsLocatorContext & )
175215
{
176216
if ( string.length() < 3 )
177217
return;
@@ -183,11 +223,9 @@ void QgsActiveLayerFeaturesLocatorFilter::fetchResults( const QString &string, c
183223
if ( !layer )
184224
return;
185225

186-
int found = 0;
187-
QgsExpression dispExpression( layer->displayExpression() );
188-
QgsExpressionContext context;
189-
context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) );
190-
dispExpression.prepare( &context );
226+
mDispExpression = QgsExpression( layer->displayExpression() );
227+
mContext.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) );
228+
mDispExpression.prepare( &mContext );
191229

192230
// build up request expression
193231
QStringList expressionParts;
@@ -211,17 +249,25 @@ void QgsActiveLayerFeaturesLocatorFilter::fetchResults( const QString &string, c
211249
req.setFlags( QgsFeatureRequest::NoGeometry );
212250
req.setFilterExpression( expression );
213251
req.setLimit( 30 );
252+
mIterator = layer->getFeatures( req );
253+
254+
mLayerId = layer->id();
255+
mLayerIcon = QgsMapLayerModel::iconForLayer( layer );
256+
}
257+
258+
void QgsActiveLayerFeaturesLocatorFilter::fetchResults( const QString &string, const QgsLocatorContext &, QgsFeedback *feedback )
259+
{
260+
int found = 0;
214261
QgsFeature f;
215-
QgsFeatureIterator it = layer->getFeatures( req );
216-
while ( it.nextFeature( f ) )
262+
263+
while ( mIterator.nextFeature( f ) )
217264
{
218265
if ( feedback->isCanceled() )
219266
return;
220267

221268
QgsLocatorResult result;
222-
result.filter = this;
223269

224-
context.setFeature( f );
270+
mContext.setFeature( f );
225271

226272
// find matching field content
227273
Q_FOREACH ( const QVariant &var, f.attributes() )
@@ -236,10 +282,10 @@ void QgsActiveLayerFeaturesLocatorFilter::fetchResults( const QString &string, c
236282
if ( result.displayString.isEmpty() )
237283
continue; //not sure how this result slipped through...
238284

239-
result.description = dispExpression.evaluate( &context ).toString();
285+
result.description = mDispExpression.evaluate( &mContext ).toString();
240286

241-
result.userData = QVariantList() << f.id() << layer->id();
242-
result.icon = QgsMapLayerModel::iconForLayer( layer );
287+
result.userData = QVariantList() << f.id() << mLayerId;
288+
result.icon = mLayerIcon;
243289
result.score = static_cast< double >( string.length() ) / result.displayString.size();
244290
emit resultFetched( result );
245291

‎src/app/locator/qgsinbuiltlocatorfilters.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
#define QGSINBUILTLOCATORFILTERS_H
2020

2121
#include "qgslocatorfilter.h"
22+
#include "qgsexpressioncontext.h"
23+
#include "qgsfeatureiterator.h"
24+
2225
class QAction;
2326

2427
class QgsLayerTreeLocatorFilter : public QgsLocatorFilter
@@ -28,14 +31,20 @@ class QgsLayerTreeLocatorFilter : public QgsLocatorFilter
2831
public:
2932

3033
QgsLayerTreeLocatorFilter( QObject *parent = nullptr );
34+
QgsLayerTreeLocatorFilter *clone() const override;
3135
QString name() const override { return QStringLiteral( "layertree" ); }
3236
QString displayName() const override { return tr( "Project Layers" ); }
3337
Priority priority() const override { return Highest; }
3438
QString prefix() const override { return QStringLiteral( "l" ); }
3539

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

44+
private:
45+
46+
QVector< QgsLocatorResult > mResults;
47+
3948
};
4049

4150
class QgsLayoutLocatorFilter : public QgsLocatorFilter
@@ -45,14 +54,19 @@ class QgsLayoutLocatorFilter : public QgsLocatorFilter
4554
public:
4655

4756
QgsLayoutLocatorFilter( QObject *parent = nullptr );
57+
QgsLayoutLocatorFilter *clone() const override;
4858
QString name() const override { return QStringLiteral( "layouts" ); }
4959
QString displayName() const override { return tr( "Project Layouts" ); }
5060
Priority priority() const override { return Highest; }
5161
QString prefix() const override { return QStringLiteral( "pl" ); }
5262

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

67+
private:
68+
69+
QVector< QgsLocatorResult > mResults;
5670
};
5771

5872
class QgsActionLocatorFilter : public QgsLocatorFilter
@@ -62,16 +76,19 @@ class QgsActionLocatorFilter : public QgsLocatorFilter
6276
public:
6377

6478
QgsActionLocatorFilter( const QList<QWidget *> &parentObjectsForActions, QObject *parent = nullptr );
79+
QgsActionLocatorFilter *clone() const override;
6580
QString name() const override { return QStringLiteral( "actions" ); }
6681
QString displayName() const override { return tr( "Actions" ); }
6782
Priority priority() const override { return Lowest; }
6883
QString prefix() const override { return QStringLiteral( "." ); }
6984

85+
void prepare( const QString &string, const QgsLocatorContext &context ) override;
7086
void fetchResults( const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback ) override;
7187
void triggerResult( const QgsLocatorResult &result ) override;
7288
private:
7389

7490
QList< QWidget * > mActionParents;
91+
QVector< QgsLocatorResult > mResults;
7592

7693
void searchActions( const QString &string, QWidget *parent, QList< QAction *> &found );
7794

@@ -84,13 +101,23 @@ class QgsActiveLayerFeaturesLocatorFilter : public QgsLocatorFilter
84101
public:
85102

86103
QgsActiveLayerFeaturesLocatorFilter( QObject *parent = nullptr );
104+
QgsActiveLayerFeaturesLocatorFilter *clone() const override;
87105
QString name() const override { return QStringLiteral( "features" ); }
88106
QString displayName() const override { return tr( "Active Layer Features" ); }
89107
Priority priority() const override { return Medium; }
90108
QString prefix() const override { return QStringLiteral( "f" ); }
91109

110+
void prepare( const QString &string, const QgsLocatorContext &context ) override;
92111
void fetchResults( const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback ) override;
93112
void triggerResult( const QgsLocatorResult &result ) override;
113+
114+
private:
115+
116+
QgsExpression mDispExpression;
117+
QgsExpressionContext mContext;
118+
QgsFeatureIterator mIterator;
119+
QString mLayerId;
120+
QIcon mLayerIcon;
94121
};
95122

96123

‎src/core/locator/qgslocator.cpp

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ QgsLocator::QgsLocator( QObject *parent )
2424
: QObject( parent )
2525
{
2626
qRegisterMetaType<QgsLocatorResult>( "QgsLocatorResult" );
27-
28-
connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsLocator::finished );
2927
}
3028

3129
QgsLocator::~QgsLocator()
@@ -58,7 +56,6 @@ void QgsLocator::registerFilter( QgsLocatorFilter *filter )
5856
{
5957
mFilters.append( filter );
6058
filter->setParent( this );
61-
connect( filter, &QgsLocatorFilter::resultFetched, this, &QgsLocator::filterSentResult, Qt::QueuedConnection );
6259

6360
if ( !filter->prefix().isEmpty() )
6461
{
@@ -104,34 +101,63 @@ void QgsLocator::fetchResults( const QString &string, const QgsLocatorContext &c
104101
}
105102
mFeedback = feedback;
106103

107-
mActiveFilters.clear();
104+
QList< QgsLocatorFilter * > activeFilters;
108105
QString searchString = string;
109106
if ( searchString.indexOf( ' ' ) > 0 )
110107
{
111108
QString prefix = searchString.left( searchString.indexOf( ' ' ) );
112109
if ( mPrefixedFilters.contains( prefix ) && mPrefixedFilters.value( prefix )->enabled() )
113110
{
114-
mActiveFilters << mPrefixedFilters.value( prefix );
111+
activeFilters << mPrefixedFilters.value( prefix );
115112
searchString = searchString.mid( prefix.length() + 1 );
116113
}
117114
}
118-
if ( mActiveFilters.isEmpty() )
115+
if ( activeFilters.isEmpty() )
119116
{
120-
Q_FOREACH ( QgsLocatorFilter *filter, mFilters )
117+
for ( QgsLocatorFilter *filter : qgis::as_const( mFilters ) )
121118
{
122119
if ( filter->useWithoutPrefix() && filter->enabled() )
123-
mActiveFilters << filter;
120+
activeFilters << filter;
124121
}
125122
}
126123

127-
auto gatherFilterResults = [searchString, context, feedback]( QgsLocatorFilter * filter )
124+
QList< QgsLocatorFilter *> clonedFilters;
125+
for ( QgsLocatorFilter *filter : qgis::as_const( activeFilters ) )
128126
{
129-
if ( !feedback->isCanceled() )
130-
filter->fetchResults( searchString, context, feedback );
131-
};
127+
QgsLocatorFilter *clone = filter->clone();
128+
connect( clone, &QgsLocatorFilter::resultFetched, clone, [this, filter]( QgsLocatorResult result )
129+
{
130+
result.filter = filter;
131+
emit filterSentResult( result );
132+
}, Qt::QueuedConnection );
133+
clone->prepare( searchString, context );
134+
clonedFilters.append( clone );
135+
}
132136

133-
mFuture = QtConcurrent::map( mActiveFilters, gatherFilterResults );
134-
mFutureWatcher.setFuture( mFuture );
137+
mActiveThreads.clear();
138+
for ( QgsLocatorFilter *filter : qgis::as_const( clonedFilters ) )
139+
{
140+
QThread *thread = new QThread();
141+
mActiveThreads.append( thread );
142+
filter->moveToThread( thread );
143+
connect( thread, &QThread::started, filter, [filter, searchString, context, feedback]
144+
{
145+
filter->executeSearchAndDelete( searchString, context, feedback );
146+
}, Qt::QueuedConnection );
147+
connect( filter, &QgsLocatorFilter::finished, thread, [thread]
148+
{
149+
thread->quit();
150+
} );
151+
connect( filter, &QgsLocatorFilter::finished, filter, &QgsLocatorFilter::deleteLater );
152+
connect( thread, &QThread::finished, thread, [this, thread]
153+
{
154+
mActiveThreads.removeAll( thread );
155+
if ( mActiveThreads.empty() )
156+
emit finished();
157+
} );
158+
connect( thread, &QThread::finished, thread, &QThread::deleteLater );
159+
thread->start();
160+
}
135161
}
136162

137163
void QgsLocator::cancel()
@@ -147,7 +173,7 @@ void QgsLocator::cancelWithoutBlocking()
147173

148174
bool QgsLocator::isRunning() const
149175
{
150-
return mFuture.isRunning();
176+
return !mActiveThreads.empty();
151177
}
152178

153179
void QgsLocator::filterSentResult( QgsLocatorResult result )
@@ -156,22 +182,18 @@ void QgsLocator::filterSentResult( QgsLocatorResult result )
156182
if ( mFeedback->isCanceled() )
157183
return;
158184

159-
if ( !result.filter )
160-
{
161-
// filter forgot to set itself
162-
result.filter = qobject_cast< QgsLocatorFilter *>( sender() );
163-
}
164-
165185
emit foundResult( result );
166186
}
167187

168188
void QgsLocator::cancelRunningQuery()
169189
{
170-
if ( mFuture.isRunning() )
190+
if ( !mActiveThreads.empty() )
171191
{
172192
// cancel existing job
173193
mFeedback->cancel();
174-
mFuture.cancel();
175-
mFuture.waitForFinished();
194+
while ( !mActiveThreads.empty() )
195+
{
196+
QCoreApplication::processEvents();
197+
}
176198
}
177199
}

‎src/core/locator/qgslocator.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,8 @@ class CORE_EXPORT QgsLocator : public QObject
155155
std::unique_ptr< QgsFeedback > mOwnedFeedback;
156156

157157
QList< QgsLocatorFilter * > mFilters;
158-
QList< QgsLocatorFilter * > mActiveFilters;
159158
QMap< QString, QgsLocatorFilter *> mPrefixedFilters;
160-
QFuture< void > mFuture;
161-
QFutureWatcher< void > mFutureWatcher;
159+
QList< QThread * > mActiveThreads;
162160

163161
void cancelRunningQuery();
164162

‎src/core/locator/qgslocatorfilter.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,22 @@
1818

1919
#include "qgslocatorfilter.h"
2020
#include "qgsstringutils.h"
21+
#include "qgsfeedback.h"
22+
#include <QThread>
2123

2224
QgsLocatorFilter::QgsLocatorFilter( QObject *parent )
2325
: QObject( parent )
2426
{
2527

2628
}
2729

30+
void QgsLocatorFilter::executeSearchAndDelete( const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback )
31+
{
32+
if ( !feedback->isCanceled() )
33+
fetchResults( string, context, feedback );
34+
emit finished();
35+
}
36+
2837
bool QgsLocatorFilter::stringMatches( const QString &candidate, const QString &search )
2938
{
3039
return candidate.contains( search, Qt::CaseInsensitive );

‎src/core/locator/qgslocatorfilter.h

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class CORE_EXPORT QgsLocatorResult
5353
{}
5454

5555
/**
56-
* Filter from which the result was obtained.
56+
* Filter from which the result was obtained. This is automatically set.
5757
*/
5858
QgsLocatorFilter *filter = nullptr;
5959

@@ -111,6 +111,12 @@ class CORE_EXPORT QgsLocatorFilter : public QObject
111111
*/
112112
QgsLocatorFilter( QObject *parent = nullptr );
113113

114+
/**
115+
* Creates a clone of the filter. New requests are always executed in a
116+
* clone of the original filter.
117+
*/
118+
virtual QgsLocatorFilter *clone() const = 0 SIP_FACTORY;
119+
114120
/**
115121
* Returns the unique name for the filter. This should be an untranslated string identifying the filter.
116122
* \see displayName()
@@ -140,6 +146,20 @@ class CORE_EXPORT QgsLocatorFilter : public QObject
140146
*/
141147
virtual QString prefix() const { return QString(); }
142148

149+
/**
150+
* Prepares the filter instance for an upcoming search for the specified \a string. This method is always called
151+
* from the main thread, and individual filter subclasses should perform whatever
152+
* tasks are required in order to allow a subsequent search to safely execute
153+
* on a background thread.
154+
*/
155+
virtual void prepare( const QString &string, const QgsLocatorContext &context ) { Q_UNUSED( string ); Q_UNUSED( context ); }
156+
157+
/**
158+
* Executes a search for this filter instance, and then deletes the current instance
159+
* of the filter.
160+
*/
161+
void executeSearchAndDelete( const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback );
162+
143163
/**
144164
* Retrieves the filter results for a specified search \a string. The \a context
145165
* argument encapsulates the context relating to the search (such as a map
@@ -214,6 +234,8 @@ class CORE_EXPORT QgsLocatorFilter : public QObject
214234

215235
signals:
216236

237+
void finished();
238+
217239
/**
218240
* Should be emitted by filters whenever they encounter a matching result
219241
* during within their fetchResults() implementation.

‎src/gui/locator/qgslocatorwidget.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,11 @@ QgsLocatorFilterFilter::QgsLocatorFilterFilter( QgsLocatorWidget *locator, QObje
404404
, mLocator( locator )
405405
{}
406406

407+
QgsLocatorFilterFilter *QgsLocatorFilterFilter::clone() const
408+
{
409+
return new QgsLocatorFilterFilter( mLocator );
410+
}
411+
407412
void QgsLocatorFilterFilter::fetchResults( const QString &string, const QgsLocatorContext &, QgsFeedback *feedback )
408413
{
409414
if ( !string.isEmpty() )
@@ -423,7 +428,6 @@ void QgsLocatorFilterFilter::fetchResults( const QString &string, const QgsLocat
423428
continue;
424429

425430
QgsLocatorResult result;
426-
result.filter = this;
427431
result.displayString = fIt.key();
428432
result.description = fIt.value()->displayName();
429433
result.userData = fIt.key() + ' ';

‎src/gui/locator/qgslocatorwidget.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ class QgsLocatorFilterFilter : public QgsLocatorFilter
134134

135135
QgsLocatorFilterFilter( QgsLocatorWidget *widget, QObject *parent = nullptr );
136136

137+
QgsLocatorFilterFilter *clone() const override SIP_FACTORY;
138+
137139
QString name() const override { return QStringLiteral( "filters" );}
138140
QString displayName() const override { return QString(); }
139141
Priority priority() const override { return static_cast< QgsLocatorFilter::Priority>( -1 ); /** shh, we cheat!**/ }

‎tests/src/python/test_qgslocator.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ def __init__(self, prefix, parent=None):
3434
super().__init__(parent)
3535
self.prefix = prefix
3636

37+
def clone(self):
38+
return test_filter(self.prefix)
39+
3740
def name(self):
3841
return 'test'
3942

@@ -42,9 +45,9 @@ def displayName(self):
4245

4346
def fetchResults(self, string, context, feedback):
4447
for i in range(3):
45-
#if feedback.isCanceled():
46-
# return
47-
sleep(0.00001)
48+
if feedback.isCanceled():
49+
return
50+
sleep(0.001)
4851
result = QgsLocatorResult()
4952
result.displayString = self.prefix + str(i)
5053
self.resultFetched.emit(result)
@@ -97,6 +100,7 @@ def got_hit(result):
97100
# one filter
98101
l = QgsLocator()
99102
filter_a = test_filter('a')
103+
100104
l.registerFilter(filter_a)
101105

102106
l.foundResult.connect(got_hit)

0 commit comments

Comments
 (0)
Please sign in to comment.