Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgslocator.cpp
3 : : --------------
4 : : begin : May 2017
5 : : copyright : (C) 2017 by Nyall Dawson
6 : : email : nyall dot dawson at gmail dot com
7 : : ***************************************************************************/
8 : :
9 : : /***************************************************************************
10 : : * *
11 : : * This program is free software; you can redistribute it and/or modify *
12 : : * it under the terms of the GNU General Public License as published by *
13 : : * the Free Software Foundation; either version 2 of the License, or *
14 : : * (at your option) any later version. *
15 : : * *
16 : : ***************************************************************************/
17 : :
18 : : #include "qgslocator.h"
19 : : #include "qgssettings.h"
20 : : #include <QtConcurrent>
21 : : #include <functional>
22 : :
23 : 15 : const QList<QString> QgsLocator::CORE_FILTERS = QList<QString>() << QStringLiteral( "actions" )
24 : 10 : << QStringLiteral( "processing_alg" )
25 : 10 : << QStringLiteral( "layertree" )
26 : 10 : << QStringLiteral( "layouts" )
27 : 10 : << QStringLiteral( "features" )
28 : 10 : << QStringLiteral( "allfeatures" )
29 : 10 : << QStringLiteral( "calculator" )
30 : 10 : << QStringLiteral( "bookmarks" )
31 : 10 : << QStringLiteral( "optionpages" )
32 : 10 : << QStringLiteral( "edit_features" )
33 : 10 : << QStringLiteral( "goto" );
34 : :
35 : 0 : QgsLocator::QgsLocator( QObject *parent )
36 : 0 : : QObject( parent )
37 : 0 : {
38 : 0 : qRegisterMetaType<QgsLocatorResult>( "QgsLocatorResult" );
39 : 0 : }
40 : :
41 : 0 : QgsLocator::~QgsLocator()
42 : 0 : {
43 : 0 : cancelRunningQuery();
44 : 0 : qDeleteAll( mFilters );
45 : 0 : }
46 : :
47 : 0 : void QgsLocator::deregisterFilter( QgsLocatorFilter *filter )
48 : : {
49 : 0 : cancelRunningQuery();
50 : 0 : mFilters.removeAll( filter );
51 : 0 : delete filter;
52 : 0 : }
53 : :
54 : 0 : QList<QgsLocatorFilter *> QgsLocator::filters( const QString &prefix )
55 : : {
56 : 0 : if ( !prefix.isEmpty() )
57 : : {
58 : 0 : QList<QgsLocatorFilter *> filters = QList<QgsLocatorFilter *>();
59 : 0 : for ( QgsLocatorFilter *filter : mFilters )
60 : : {
61 : 0 : if ( !filter->activePrefix().isEmpty() && filter->activePrefix().compare( prefix, Qt::CaseInsensitive ) == 0 )
62 : : {
63 : 0 : filters << filter;
64 : 0 : }
65 : : }
66 : 0 : return filters;
67 : 0 : }
68 : : else
69 : : {
70 : 0 : return mFilters;
71 : : }
72 : 0 : }
73 : :
74 : 0 : QMap<QString, QgsLocatorFilter *> QgsLocator::prefixedFilters() const
75 : : {
76 : 0 : QMap<QString, QgsLocatorFilter *> filters = QMap<QString, QgsLocatorFilter *>();
77 : 0 : for ( QgsLocatorFilter *filter : mFilters )
78 : : {
79 : 0 : if ( !filter->activePrefix().isEmpty() && filter->enabled() )
80 : : {
81 : : #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
82 : : filters.insertMulti( filter->activePrefix(), filter );
83 : : #else
84 : 0 : filters.insert( filter->activePrefix(), filter );
85 : : #endif
86 : 0 : }
87 : : }
88 : 0 : return filters;
89 : 0 : }
90 : :
91 : 0 : void QgsLocator::registerFilter( QgsLocatorFilter *filter )
92 : : {
93 : 0 : mFilters.append( filter );
94 : 0 : filter->setParent( this );
95 : :
96 : : // restore settings
97 : 0 : QgsSettings settings;
98 : 0 : bool enabled = settings.value( QStringLiteral( "locator_filters/enabled_%1" ).arg( filter->name() ), true, QgsSettings::Section::Gui ).toBool();
99 : 0 : bool byDefault = settings.value( QStringLiteral( "locator_filters/default_%1" ).arg( filter->name() ), filter->useWithoutPrefix(), QgsSettings::Section::Gui ).toBool();
100 : 0 : QString prefix = settings.value( QStringLiteral( "locator_filters/prefix_%1" ).arg( filter->name() ), filter->prefix(), QgsSettings::Section::Gui ).toString();
101 : 0 : if ( prefix.isEmpty() )
102 : : {
103 : 0 : prefix = filter->prefix();
104 : 0 : }
105 : :
106 : 0 : if ( !prefix.isEmpty() )
107 : : {
108 : 0 : if ( CORE_FILTERS.contains( filter->name() ) )
109 : : {
110 : : //inbuilt filter, no prefix check
111 : 0 : filter->setActivePrefix( prefix );
112 : 0 : }
113 : 0 : else if ( prefix.length() >= 3 || prefix != filter->prefix() )
114 : : {
115 : : // for plugins either the native prefix is >3 char or it has been customized by user
116 : 0 : filter->setActivePrefix( prefix );
117 : 0 : }
118 : : else
119 : : {
120 : : // otherwise set it to empty string (not NULL)
121 : 0 : filter->setActivePrefix( QString( "" ) );
122 : : }
123 : 0 : }
124 : :
125 : 0 : filter->setEnabled( enabled );
126 : 0 : filter->setUseWithoutPrefix( byDefault );
127 : 0 : }
128 : :
129 : 0 : void QgsLocator::fetchResults( const QString &string, const QgsLocatorContext &c, QgsFeedback *feedback )
130 : : {
131 : 0 : mAutocompletionList.clear();
132 : :
133 : 0 : QgsLocatorContext context( c );
134 : : // ideally this should not be required, as well behaved callers
135 : : // will NOT fire up a new fetchResults call while an existing one is
136 : : // operating/waiting to be canceled...
137 : 0 : cancelRunningQuery();
138 : :
139 : : // if no feedback object was passed, create one that is owned by this object
140 : : // to ensure that filters ALWAYS receive a valid feedback
141 : 0 : if ( !feedback )
142 : : {
143 : 0 : mOwnedFeedback.reset( new QgsFeedback() );
144 : 0 : feedback = mOwnedFeedback.get();
145 : 0 : }
146 : : else
147 : : {
148 : 0 : mOwnedFeedback.reset( nullptr );
149 : : }
150 : 0 : mFeedback = feedback;
151 : :
152 : 0 : QList< QgsLocatorFilter * > activeFilters;
153 : 0 : QString searchString = string;
154 : 0 : QString prefix = searchString.left( std::max( static_cast<int>( searchString.indexOf( ' ' ) ), 0 ) );
155 : 0 : if ( !prefix.isEmpty() )
156 : : {
157 : 0 : for ( QgsLocatorFilter *filter : std::as_const( mFilters ) )
158 : : {
159 : 0 : if ( filter->activePrefix().compare( prefix, Qt::CaseInsensitive ) == 0 && filter->enabled() )
160 : : {
161 : 0 : activeFilters << filter;
162 : 0 : }
163 : : }
164 : 0 : context.usingPrefix = !activeFilters.empty();
165 : 0 : }
166 : 0 : if ( !activeFilters.isEmpty() )
167 : : {
168 : 0 : searchString = searchString.mid( prefix.length() + 1 );
169 : 0 : }
170 : : else
171 : : {
172 : 0 : for ( QgsLocatorFilter *filter : std::as_const( mFilters ) )
173 : : {
174 : 0 : if ( filter->useWithoutPrefix() && filter->enabled() )
175 : : {
176 : 0 : activeFilters << filter;
177 : 0 : }
178 : : }
179 : : }
180 : :
181 : 0 : QList< QgsLocatorFilter *> threadedFilters;
182 : 0 : for ( QgsLocatorFilter *filter : std::as_const( activeFilters ) )
183 : : {
184 : 0 : filter->clearPreviousResults();
185 : 0 : std::unique_ptr< QgsLocatorFilter > clone( filter->clone() );
186 : 0 : connect( clone.get(), &QgsLocatorFilter::resultFetched, clone.get(), [this, filter]( QgsLocatorResult result )
187 : : {
188 : 0 : result.filter = filter;
189 : 0 : filterSentResult( result );
190 : 0 : } );
191 : 0 : QStringList autoCompleteList = clone->prepare( searchString, context );
192 : 0 : if ( context.usingPrefix )
193 : : {
194 : 0 : for ( int i = 0; i < autoCompleteList.length(); i++ )
195 : : {
196 : 0 : autoCompleteList[i].prepend( QStringLiteral( "%1 " ).arg( prefix ) );
197 : 0 : }
198 : 0 : }
199 : 0 : mAutocompletionList.append( autoCompleteList );
200 : :
201 : 0 : if ( clone->flags() & QgsLocatorFilter::FlagFast )
202 : : {
203 : : // filter is fast enough to fetch results on the main thread
204 : 0 : clone->fetchResults( searchString, context, feedback );
205 : 0 : }
206 : : else
207 : : {
208 : 0 : threadedFilters.append( clone.release() );
209 : : }
210 : 0 : }
211 : :
212 : 0 : mActiveThreads.clear();
213 : 0 : for ( QgsLocatorFilter *filter : std::as_const( threadedFilters ) )
214 : : {
215 : 0 : QThread *thread = new QThread();
216 : 0 : mActiveThreads.append( thread );
217 : 0 : filter->moveToThread( thread );
218 : 0 : connect( thread, &QThread::started, filter, [filter, searchString, context, feedback]
219 : : {
220 : 0 : int delay = filter->fetchResultsDelay();
221 : 0 : while ( delay > 0 )
222 : : {
223 : 0 : if ( feedback->isCanceled() )
224 : 0 : break;
225 : 0 : QThread::msleep( 50 );
226 : 0 : delay -= 50;
227 : : }
228 : 0 : if ( !feedback->isCanceled() )
229 : 0 : filter->fetchResults( searchString, context, feedback );
230 : 0 : filter->emit finished();
231 : 0 : }, Qt::QueuedConnection );
232 : 0 : connect( filter, &QgsLocatorFilter::finished, thread, &QThread::quit );
233 : 0 : connect( filter, &QgsLocatorFilter::finished, filter, &QgsLocatorFilter::deleteLater );
234 : 0 : connect( thread, &QThread::finished, thread, [this, thread]
235 : : {
236 : 0 : mActiveThreads.removeAll( thread );
237 : 0 : if ( mActiveThreads.empty() )
238 : 0 : emit finished();
239 : 0 : } );
240 : 0 : connect( thread, &QThread::finished, thread, &QThread::deleteLater );
241 : 0 : thread->start();
242 : : }
243 : :
244 : 0 : emit searchPrepared();
245 : :
246 : 0 : if ( mActiveThreads.empty() )
247 : 0 : emit finished();
248 : 0 : }
249 : :
250 : 0 : void QgsLocator::cancel()
251 : : {
252 : 0 : cancelRunningQuery();
253 : 0 : }
254 : :
255 : 0 : void QgsLocator::cancelWithoutBlocking()
256 : : {
257 : 0 : if ( mFeedback )
258 : 0 : mFeedback->cancel();
259 : 0 : }
260 : :
261 : 0 : bool QgsLocator::isRunning() const
262 : : {
263 : 0 : return !mActiveThreads.empty();
264 : : }
265 : :
266 : 0 : void QgsLocator::clearPreviousResults()
267 : : {
268 : 0 : for ( QgsLocatorFilter *filter : std::as_const( mFilters ) )
269 : : {
270 : 0 : if ( filter->enabled() )
271 : : {
272 : 0 : filter->clearPreviousResults();
273 : 0 : }
274 : : }
275 : 0 : }
276 : :
277 : 0 : void QgsLocator::filterSentResult( QgsLocatorResult result )
278 : : {
279 : : // if query has been canceled then discard any results we receive
280 : 0 : if ( mFeedback->isCanceled() )
281 : 0 : return;
282 : :
283 : 0 : emit foundResult( result );
284 : 0 : }
285 : :
286 : 0 : void QgsLocator::cancelRunningQuery()
287 : : {
288 : 0 : if ( !mActiveThreads.empty() )
289 : : {
290 : : // cancel existing job
291 : 0 : mFeedback->cancel();
292 : 0 : while ( !mActiveThreads.empty() )
293 : : {
294 : 0 : QCoreApplication::processEvents();
295 : : }
296 : 0 : }
297 : 0 : }
|