QGIS API Documentation  3.0.2-Girona (307d082)
qgslocatormodel.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslocatormodel.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 
19 #include "qgslocatormodel.h"
20 #include "qgslocator.h"
21 #include "qgsapplication.h"
22 #include "qgslogger.h"
23 
24 //
25 // QgsLocatorModel
26 //
27 
29  : QAbstractTableModel( parent )
30 {
31  mDeferredClearTimer.setInterval( 100 );
32  mDeferredClearTimer.setSingleShot( true );
33  connect( &mDeferredClearTimer, &QTimer::timeout, this, &QgsLocatorModel::clear );
34 }
35 
37 {
38  mDeferredClearTimer.stop();
39  mDeferredClear = false;
40 
41  beginResetModel();
42  mResults.clear();
43  mFoundResultsFromFilterNames.clear();
44  endResetModel();
45 }
46 
48 {
49  mDeferredClear = true;
50  mDeferredClearTimer.start();
51 }
52 
53 int QgsLocatorModel::rowCount( const QModelIndex & ) const
54 {
55  return mResults.size();
56 }
57 
58 int QgsLocatorModel::columnCount( const QModelIndex & ) const
59 {
60  return 2;
61 }
62 
63 QVariant QgsLocatorModel::data( const QModelIndex &index, int role ) const
64 {
65  if ( !index.isValid() || index.row() < 0 || index.column() < 0 ||
66  index.row() >= rowCount( QModelIndex() ) || index.column() >= columnCount( QModelIndex() ) )
67  return QVariant();
68 
69  switch ( role )
70  {
71  case Qt::DisplayRole:
72  case Qt::EditRole:
73  {
74  switch ( index.column() )
75  {
76  case Name:
77  if ( !mResults.at( index.row() ).filter )
78  return mResults.at( index.row() ).result.displayString;
79  else
80  return mResults.at( index.row() ).filterTitle;
81  case Description:
82  if ( !mResults.at( index.row() ).filter )
83  return mResults.at( index.row() ).result.description;
84  else
85  return QVariant();
86  }
87  break;
88  }
89 
90  case Qt::DecorationRole:
91  switch ( index.column() )
92  {
93  case Name:
94  if ( !mResults.at( index.row() ).filter )
95  {
96  QIcon icon = mResults.at( index.row() ).result.icon;
97  if ( !icon.isNull() )
98  return icon;
99  return QgsApplication::getThemeIcon( QStringLiteral( "/search.svg" ) );
100  }
101  else
102  return QVariant();
103  case Description:
104  return QVariant();
105  }
106  break;
107 
108  case ResultDataRole:
109  if ( !mResults.at( index.row() ).filter )
110  return QVariant::fromValue( mResults.at( index.row() ).result );
111  else
112  return QVariant();
113 
114  case ResultTypeRole:
115  if ( mResults.at( index.row() ).filter )
116  return 0;
117  else
118  return 1;
119 
120  case ResultScoreRole:
121  if ( mResults.at( index.row() ).filter )
122  return 0;
123  else
124  return ( mResults.at( index.row() ).result.score );
125 
127  if ( !mResults.at( index.row() ).filter )
128  return mResults.at( index.row() ).result.filter->priority();
129  else
130  return mResults.at( index.row() ).filter->priority();
131 
133  if ( !mResults.at( index.row() ).filter )
134  return mResults.at( index.row() ).result.filter->displayName();
135  else
136  return mResults.at( index.row() ).filterTitle;
137  }
138 
139  return QVariant();
140 }
141 
142 Qt::ItemFlags QgsLocatorModel::flags( const QModelIndex &index ) const
143 {
144  if ( !index.isValid() || index.row() < 0 || index.column() < 0 ||
145  index.row() >= rowCount( QModelIndex() ) || index.column() >= columnCount( QModelIndex() ) )
146  return QAbstractTableModel::flags( index );
147 
148  Qt::ItemFlags flags = QAbstractTableModel::flags( index );
149  if ( !mResults.at( index.row() ).filterTitle.isEmpty() )
150  {
151  flags = flags & ~( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
152  }
153  return flags;
154 }
155 
157 {
158  mDeferredClearTimer.stop();
159  if ( mDeferredClear )
160  {
161  mFoundResultsFromFilterNames.clear();
162  }
163 
164  int pos = mResults.size();
165  bool addingFilter = !result.filter->displayName().isEmpty() && !mFoundResultsFromFilterNames.contains( result.filter->name() );
166  if ( addingFilter )
167  mFoundResultsFromFilterNames << result.filter->name();
168 
169  if ( mDeferredClear )
170  {
171  beginResetModel();
172  mResults.clear();
173  }
174  else
175  beginInsertRows( QModelIndex(), pos, pos + ( addingFilter ? 1 : 0 ) );
176 
177  if ( addingFilter )
178  {
179  Entry entry;
180  entry.filterTitle = result.filter->displayName();
181  entry.filter = result.filter;
182  mResults << entry;
183  }
184  Entry entry;
185  entry.result = result;
186  mResults << entry;
187 
188  if ( mDeferredClear )
189  endResetModel();
190  else
191  endInsertRows();
192 
193  mDeferredClear = false;
194 }
195 
196 
197 //
198 // QgsLocatorAutomaticModel
199 //
200 
202  : QgsLocatorModel( locator )
203  , mLocator( locator )
204 {
205  Q_ASSERT( mLocator );
206  connect( mLocator, &QgsLocator::foundResult, this, &QgsLocatorAutomaticModel::addResult );
207  connect( mLocator, &QgsLocator::finished, this, &QgsLocatorAutomaticModel::searchFinished );
208 }
209 
211 {
212  return mLocator;
213 }
214 
215 void QgsLocatorAutomaticModel::search( const QString &string )
216 {
217  if ( mLocator->isRunning() )
218  {
219  // can't do anything while a query is running, and can't block
220  // here waiting for the current query to cancel
221  // so we queue up this string until cancel has happened
222  mLocator->cancelWithoutBlocking();
223  mNextRequestedString = string;
224  mHasQueuedRequest = true;
225  return;
226  }
227  else
228  {
229  deferredClear();
230  mLocator->fetchResults( string, createContext() );
231  }
232 }
233 
235 {
236  return QgsLocatorContext();
237 }
238 
239 void QgsLocatorAutomaticModel::searchFinished()
240 {
241  if ( mHasQueuedRequest )
242  {
243  // a queued request was waiting for this - run the queued search now
244  QString nextSearch = mNextRequestedString;
245  mNextRequestedString.clear();
246  mHasQueuedRequest = false;
247  search( nextSearch );
248  }
249 }
250 
251 
252 
253 
254 
255 //
256 // QgsLocatorProxyModel
257 //
258 
260  : QSortFilterProxyModel( parent )
261 {
262  setDynamicSortFilter( true );
263  setSortLocaleAware( true );
264  setFilterCaseSensitivity( Qt::CaseInsensitive );
265  sort( 0 );
266 }
267 
268 bool QgsLocatorProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
269 {
270  // first go by filter priority
271  int leftFilterPriority = sourceModel()->data( left, QgsLocatorModel::ResultFilterPriorityRole ).toInt();
272  int rightFilterPriority = sourceModel()->data( right, QgsLocatorModel::ResultFilterPriorityRole ).toInt();
273  if ( leftFilterPriority != rightFilterPriority )
274  return leftFilterPriority < rightFilterPriority;
275 
276  // then filter name
277  QString leftFilter = sourceModel()->data( left, QgsLocatorModel::ResultFilterNameRole ).toString();
278  QString rightFilter = sourceModel()->data( right, QgsLocatorModel::ResultFilterNameRole ).toString();
279  if ( leftFilter != rightFilter )
280  return QString::localeAwareCompare( leftFilter, rightFilter ) < 0;
281 
282  // then make sure filter title appears before filter's results
283  int leftTypeRole = sourceModel()->data( left, QgsLocatorModel::ResultTypeRole ).toInt();
284  int rightTypeRole = sourceModel()->data( right, QgsLocatorModel::ResultTypeRole ).toInt();
285  if ( leftTypeRole != rightTypeRole )
286  return leftTypeRole < rightTypeRole;
287 
288  // sort filter's results by score
289  double leftScore = sourceModel()->data( left, QgsLocatorModel::ResultScoreRole ).toDouble();
290  double rightScore = sourceModel()->data( right, QgsLocatorModel::ResultScoreRole ).toDouble();
291  if ( !qgsDoubleNear( leftScore, rightScore ) )
292  return leftScore > rightScore;
293 
294  // lastly sort filter's results by string
295  leftFilter = sourceModel()->data( left, Qt::DisplayRole ).toString();
296  rightFilter = sourceModel()->data( right, Qt::DisplayRole ).toString();
297  return QString::localeAwareCompare( leftFilter, rightFilter ) < 0;
298 }
299 
300 
QgsLocatorAutomaticModel(QgsLocator *locator)
Constructor for QgsLocatorAutomaticModel, linked with the specified locator.
void cancelWithoutBlocking()
Triggers cancelation of any current running query without blocking.
Definition: qgslocator.cpp:182
virtual QString displayName() const =0
Returns a translated, user-friendly name for the filter.
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
QgsLocatorProxyModel(QObject *parent=nullptr)
Constructor for QgsLocatorProxyModel, with the specified parent object.
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
bool isRunning() const
Returns true if a query is currently being executed by the locator.
Definition: qgslocator.cpp:188
QgsLocator * locator()
Returns a pointer to the locator utilized by this model.
Qt::ItemFlags flags(const QModelIndex &index) const override
void search(const QString &string)
Enqueues a search for a specified string within the model.
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:251
void finished()
Emitted when locator has finished a query, either as a result of successful completion or early cance...
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
QgsLocatorModel(QObject *parent=nullptr)
Constructor for QgsLocatorModel.
int rowCount(const QModelIndex &parent=QModelIndex()) const override
int columnCount(const QModelIndex &parent=QModelIndex()) const override
Encapsulates the properties relating to the context of a locator search.
Encapsulates properties of an individual matching result found by a QgsLocatorFilter.
Handles the management of QgsLocatorFilter objects and async collection of search results from them...
Definition: qgslocator.h:54
void foundResult(const QgsLocatorResult &result)
Emitted whenever a filter encounters a matching result after the fetchResults() method is called...
An abstract list model for displaying the results of locator searches.
Result priority, used by QgsLocatorProxyModel for sorting roles.
void deferredClear()
Resets the model and clears all existing results after a short delay, or whenever the next result is ...
Result match score, used by QgsLocatorProxyModel for sorting roles.
QgsLocatorFilter * filter
Filter from which the result was obtained.
virtual QString name() const =0
Returns the unique name for the filter.
QgsLocatorResult data.
void addResult(const QgsLocatorResult &result)
Adds a new result to the model.
void clear()
Resets the model and clears all existing results.
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
virtual QgsLocatorContext createContext()
Returns a new locator context for searches.
Associated filter name which created the result.