QGIS API Documentation  3.0.2-Girona (307d082)
qgslocator.cpp
Go to the documentation of this file.
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 QgsLocator::QgsLocator( QObject *parent )
24  : QObject( parent )
25 {
26  qRegisterMetaType<QgsLocatorResult>( "QgsLocatorResult" );
27 }
28 
30 {
31  cancelRunningQuery();
32  qDeleteAll( mFilters );
33 }
34 
36 {
37  cancelRunningQuery();
38  mFilters.removeAll( filter );
39  QString key = mPrefixedFilters.key( filter );
40  if ( !key.isEmpty() )
41  mPrefixedFilters.remove( key );
42  delete filter;
43 }
44 
45 QList<QgsLocatorFilter *> QgsLocator::filters()
46 {
47  return mFilters;
48 }
49 
50 QMap<QString, QgsLocatorFilter *> QgsLocator::prefixedFilters() const
51 {
52  return mPrefixedFilters;
53 }
54 
56 {
57  mFilters.append( filter );
58  filter->setParent( this );
59 
60  if ( !filter->prefix().isEmpty() )
61  {
62  if ( filter->name() == QStringLiteral( "actions" ) || filter->name() == QStringLiteral( "processing_alg" )
63  || filter->name() == QStringLiteral( "layertree" ) || filter->name() == QStringLiteral( "layouts" )
64  || filter->name() == QStringLiteral( "features" ) )
65  {
66  //inbuilt filter, no prefix check
67  mPrefixedFilters.insert( filter->prefix(), filter );
68  }
69  else if ( filter->prefix().length() >= 3 )
70  {
71  mPrefixedFilters.insert( filter->prefix(), filter );
72  }
73  }
74 
75  // restore settings
76  QgsSettings settings;
77  bool enabled = settings.value( QStringLiteral( "locator_filters/enabled_%1" ).arg( filter->name() ), true, QgsSettings::Section::Gui ).toBool();
78  bool byDefault = settings.value( QStringLiteral( "locator_filters/default_%1" ).arg( filter->name() ), filter->useWithoutPrefix(), QgsSettings::Section::Gui ).toBool();
79 
80  filter->setEnabled( enabled );
81  filter->setUseWithoutPrefix( byDefault );
82 }
83 
84 void QgsLocator::fetchResults( const QString &string, const QgsLocatorContext &c, QgsFeedback *feedback )
85 {
86  QgsLocatorContext context( c );
87  // ideally this should not be required, as well behaved callers
88  // will NOT fire up a new fetchResults call while an existing one is
89  // operating/waiting to be canceled...
90  cancelRunningQuery();
91 
92  // if no feedback object was passed, create one that is owned by this object
93  // to ensure that filters ALWAYS receive a valid feedback
94  if ( !feedback )
95  {
96  mOwnedFeedback.reset( new QgsFeedback() );
97  feedback = mOwnedFeedback.get();
98  }
99  else
100  {
101  mOwnedFeedback.reset( nullptr );
102  }
103  mFeedback = feedback;
104 
105  QList< QgsLocatorFilter * > activeFilters;
106  QString searchString = string;
107  if ( searchString.indexOf( ' ' ) > 0 )
108  {
109  QString prefix = searchString.left( searchString.indexOf( ' ' ) );
110  if ( mPrefixedFilters.contains( prefix ) && mPrefixedFilters.value( prefix )->enabled() )
111  {
112  activeFilters << mPrefixedFilters.value( prefix );
113  searchString = searchString.mid( prefix.length() + 1 );
114  }
115  context.usingPrefix = !activeFilters.empty();
116  }
117  if ( activeFilters.isEmpty() )
118  {
119  for ( QgsLocatorFilter *filter : qgis::as_const( mFilters ) )
120  {
121  if ( filter->useWithoutPrefix() && filter->enabled() )
122  activeFilters << filter;
123  }
124  }
125 
126  QList< QgsLocatorFilter *> threadedFilters;
127  for ( QgsLocatorFilter *filter : qgis::as_const( activeFilters ) )
128  {
129  std::unique_ptr< QgsLocatorFilter > clone( filter->clone() );
130  connect( clone.get(), &QgsLocatorFilter::resultFetched, clone.get(), [this, filter]( QgsLocatorResult result )
131  {
132  result.filter = filter;
133  emit filterSentResult( result );
134  } );
135  clone->prepare( searchString, context );
136 
137  if ( clone->flags() & QgsLocatorFilter::FlagFast )
138  {
139  // filter is fast enough to fetch results on the main thread
140  clone->fetchResults( searchString, context, feedback );
141  }
142  else
143  {
144  // run filter in background
145  threadedFilters.append( clone.release() );
146  }
147  }
148 
149  mActiveThreads.clear();
150  for ( QgsLocatorFilter *filter : qgis::as_const( threadedFilters ) )
151  {
152  QThread *thread = new QThread();
153  mActiveThreads.append( thread );
154  filter->moveToThread( thread );
155  connect( thread, &QThread::started, filter, [filter, searchString, context, feedback]
156  {
157  if ( !feedback->isCanceled() )
158  filter->fetchResults( searchString, context, feedback );
159  filter->emit finished();
160  }, Qt::QueuedConnection );
161  connect( filter, &QgsLocatorFilter::finished, thread, &QThread::quit );
162  connect( filter, &QgsLocatorFilter::finished, filter, &QgsLocatorFilter::deleteLater );
163  connect( thread, &QThread::finished, thread, [this, thread]
164  {
165  mActiveThreads.removeAll( thread );
166  if ( mActiveThreads.empty() )
167  emit finished();
168  } );
169  connect( thread, &QThread::finished, thread, &QThread::deleteLater );
170  thread->start();
171  }
172 
173  if ( mActiveThreads.empty() )
174  emit finished();
175 }
176 
178 {
179  cancelRunningQuery();
180 }
181 
183 {
184  if ( mFeedback )
185  mFeedback->cancel();
186 }
187 
189 {
190  return !mActiveThreads.empty();
191 }
192 
193 void QgsLocator::filterSentResult( QgsLocatorResult result )
194 {
195  // if query has been canceled then discard any results we receive
196  if ( mFeedback->isCanceled() )
197  return;
198 
199  emit foundResult( result );
200 }
201 
202 void QgsLocator::cancelRunningQuery()
203 {
204  if ( !mActiveThreads.empty() )
205  {
206  // cancel existing job
207  mFeedback->cancel();
208  while ( !mActiveThreads.empty() )
209  {
210  QCoreApplication::processEvents();
211  }
212  }
213 }
void registerFilter(QgsLocatorFilter *filter)
Registers a filter within the locator.
Definition: qgslocator.cpp:55
void cancelWithoutBlocking()
Triggers cancelation of any current running query without blocking.
Definition: qgslocator.cpp:182
void resultFetched(const QgsLocatorResult &result)
Should be emitted by filters whenever they encounter a matching result during within their fetchResul...
void cancel()
Tells the internal routines that the current operation should be canceled. This should be run by the ...
Definition: qgsfeedback.h:85
This class is a composition of two QSettings instances:
Definition: qgssettings.h:57
QgsLocator(QObject *parent=nullptr)
Constructor for QgsLocator.
Definition: qgslocator.cpp:23
void fetchResults(const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback=nullptr)
Triggers the background fetching of filter results for a specified search string. ...
Definition: qgslocator.cpp:84
~QgsLocator() override
Destructor for QgsLocator.
Definition: qgslocator.cpp:29
bool isRunning() const
Returns true if a query is currently being executed by the locator.
Definition: qgslocator.cpp:188
void setEnabled(bool enabled)
Sets whether the filter is enabled.
void finished()
Emitted when locator has finished a query, either as a result of successful completion or early cance...
Base class for feedback objects to be used for cancelation of something running in a worker thread...
Definition: qgsfeedback.h:44
void setUseWithoutPrefix(bool useWithoutPrefix)
Sets whether the filter should be used when no prefix is entered.
void cancel()
Cancels any current running query, and blocks until query is completely canceled by all filters...
Definition: qgslocator.cpp:177
void finished()
Emitted when the filter finishes fetching results.
Encapsulates the properties relating to the context of a locator search.
Encapsulates properties of an individual matching result found by a QgsLocatorFilter.
Abstract base class for filters which collect locator results.
bool useWithoutPrefix() const
Returns true if the filter should be used when no prefix is entered.
void foundResult(const QgsLocatorResult &result)
Emitted whenever a filter encounters a matching result after the fetchResults() method is called...
QList< QgsLocatorFilter * > filters()
Returns the list of filters registered in the locator.
Definition: qgslocator.cpp:45
bool usingPrefix
Will be true if search is being conducted using a filter prefix.
virtual QString prefix() const
Returns the search prefix character(s) for this filter.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
virtual QString name() const =0
Returns the unique name for the filter.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), const Section section=NoSection) const
Returns the value for setting key.
Filter finds results quickly and can be safely run in the main thread.
QMap< QString, QgsLocatorFilter * > prefixedFilters() const
Returns a map of prefix to filter, for all registered filters with valid prefixes.
Definition: qgslocator.cpp:50
void deregisterFilter(QgsLocatorFilter *filter)
Deregisters a filter from the locator and deletes it.
Definition: qgslocator.cpp:35