QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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 #include <QFont>
19 
20 #include "qgslocatormodel.h"
21 #include "qgslocator.h"
22 #include "qgsapplication.h"
23 #include "qgslogger.h"
24 
25 
26 //
27 // QgsLocatorModel
28 //
29 
31  : QAbstractTableModel( parent )
32 {
33  mDeferredClearTimer.setInterval( 100 );
34  mDeferredClearTimer.setSingleShot( true );
35  connect( &mDeferredClearTimer, &QTimer::timeout, this, &QgsLocatorModel::clear );
36 }
37 
39 {
40  mDeferredClearTimer.stop();
41  mDeferredClear = false;
42 
43  beginResetModel();
44  mResults.clear();
45  mFoundResultsFromFilterNames.clear();
46  mFoundResultsFilterGroups.clear();
47  endResetModel();
48 }
49 
51 {
52  mDeferredClear = true;
53  mDeferredClearTimer.start();
54 }
55 
56 int QgsLocatorModel::rowCount( const QModelIndex & ) const
57 {
58  return mResults.size();
59 }
60 
61 int QgsLocatorModel::columnCount( const QModelIndex & ) const
62 {
63  return 2;
64 }
65 
66 QVariant QgsLocatorModel::data( const QModelIndex &index, int role ) const
67 {
68  if ( !index.isValid() || index.row() < 0 || index.column() < 0 ||
69  index.row() >= rowCount( QModelIndex() ) || index.column() >= columnCount( QModelIndex() ) )
70  return QVariant();
71 
72  switch ( role )
73  {
74  case Qt::DisplayRole:
75  case Qt::EditRole:
76  {
77  switch ( index.column() )
78  {
79  case Name:
80  if ( !mResults.at( index.row() ).filter )
81  return mResults.at( index.row() ).result.displayString;
82  else if ( mResults.at( index.row() ).filter && mResults.at( index.row() ).groupSorting == 0 )
83  return mResults.at( index.row() ).filterTitle;
84  else
85  {
86  QString groupTitle = mResults.at( index.row() ).groupTitle;
87  groupTitle.prepend( " " );
88  return groupTitle;
89  }
90  case Description:
91  if ( !mResults.at( index.row() ).filter )
92  return mResults.at( index.row() ).result.description;
93  else
94  return QVariant();
95  }
96  break;
97  }
98 
99  case Qt::FontRole:
100  if ( index.column() == Name && !mResults.at( index.row() ).groupTitle.isEmpty() )
101  {
102  QFont font;
103  font.setItalic( true );
104  return font;
105  }
106  else
107  {
108  return QVariant();
109  }
110  break;
111 
112  case Qt::DecorationRole:
113  switch ( index.column() )
114  {
115  case Name:
116  if ( !mResults.at( index.row() ).filter )
117  {
118  QIcon icon = mResults.at( index.row() ).result.icon;
119  if ( !icon.isNull() )
120  return icon;
121  return QgsApplication::getThemeIcon( QStringLiteral( "/search.svg" ) );
122  }
123  else
124  return QVariant();
125  case Description:
126  return QVariant();
127  }
128  break;
129 
130  case ResultDataRole:
131  if ( !mResults.at( index.row() ).filter )
132  return QVariant::fromValue( mResults.at( index.row() ).result );
133  else
134  return QVariant();
135 
136  case ResultTypeRole:
137  // 0 for filter title, the group otherwise, 9999 if no group
138  return mResults.at( index.row() ).groupSorting;
139 
140  case ResultScoreRole:
141  if ( mResults.at( index.row() ).filter )
142  return 0;
143  else
144  return ( mResults.at( index.row() ).result.score );
145 
147  if ( !mResults.at( index.row() ).filter )
148  return mResults.at( index.row() ).result.filter->priority();
149  else
150  return mResults.at( index.row() ).filter->priority();
151 
153  if ( !mResults.at( index.row() ).filter )
154  return mResults.at( index.row() ).result.filter->displayName();
155  else
156  return mResults.at( index.row() ).filterTitle;
157 
159  if ( mResults.at( index.row() ).groupTitle.isEmpty() )
160  return 1;
161  else
162  return 0;
163 
164  case ResultActionsRole:
165  return QVariant::fromValue( mResults.at( index.row() ).result.actions );
166  }
167 
168  return QVariant();
169 }
170 
171 Qt::ItemFlags QgsLocatorModel::flags( const QModelIndex &index ) const
172 {
173  if ( !index.isValid() || index.row() < 0 || index.column() < 0 ||
174  index.row() >= rowCount( QModelIndex() ) || index.column() >= columnCount( QModelIndex() ) )
175  return QAbstractTableModel::flags( index );
176 
177  Qt::ItemFlags flags = QAbstractTableModel::flags( index );
178  if ( mResults.at( index.row() ).filter )
179  {
180  flags = flags & ~( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
181  }
182  return flags;
183 }
184 
185 QHash<int, QByteArray> QgsLocatorModel::roleNames() const
186 {
187  QHash<int, QByteArray> roles;
188  roles[ResultDataRole] = "ResultData";
189  roles[ResultTypeRole] = "ResultType";
190  roles[ResultFilterPriorityRole] = "ResultFilterPriority";
191  roles[ResultScoreRole] = "ResultScore";
192  roles[ResultFilterNameRole] = "ResultFilterName";
193  roles[ResultFilterGroupSortingRole] = "ResultFilterGroupSorting";
194  roles[ResultActionsRole] = "ResultContextMenuActions";
195  roles[Qt::DisplayRole] = "Text";
196  return roles;
197 }
198 
200 {
201  mDeferredClearTimer.stop();
202  if ( mDeferredClear )
203  {
204  mFoundResultsFromFilterNames.clear();
205  mFoundResultsFilterGroups.clear();
206  }
207 
208  int pos = mResults.size();
209  bool addingFilter = !result.filter->displayName().isEmpty() && !mFoundResultsFromFilterNames.contains( result.filter->name() );
210  if ( addingFilter )
211  mFoundResultsFromFilterNames << result.filter->name();
212 
213  bool addingGroup = !result.group.isEmpty() && ( !mFoundResultsFilterGroups.contains( result.filter )
214  || !mFoundResultsFilterGroups.value( result.filter ).contains( result.group ) );
215  if ( addingGroup )
216  {
217  if ( !mFoundResultsFilterGroups.contains( result.filter ) )
218  mFoundResultsFilterGroups[result.filter] = QStringList();
219  mFoundResultsFilterGroups[result.filter] << result.group ;
220  }
221  if ( mDeferredClear )
222  {
223  beginResetModel();
224  mResults.clear();
225  }
226  else
227  beginInsertRows( QModelIndex(), pos, pos + ( static_cast<int>( addingFilter ) + static_cast<int>( addingGroup ) ) );
228 
229  if ( addingFilter )
230  {
231  Entry entry;
232  entry.filterTitle = result.filter->displayName();
233  entry.filter = result.filter;
234  mResults << entry;
235  }
236  if ( addingGroup )
237  {
238  Entry entry;
239  entry.filterTitle = result.filter->displayName();
240  entry.groupTitle = result.group;
241  // the sorting of groups will be achieved by order of adding groups
242  // this could be customized by adding the extra info to QgsLocatorResult
243  entry.groupSorting = mFoundResultsFilterGroups[result.filter].count();
244  entry.filter = result.filter;
245  mResults << entry;
246  }
247  Entry entry;
248  entry.result = result;
249  // keep the group title empty to allow differecing group title from results
250  entry.groupSorting = result.group.isEmpty() ? NoGroup : mFoundResultsFilterGroups[result.filter].indexOf( result.group ) + 1;
251  mResults << entry;
252 
253  if ( mDeferredClear )
254  endResetModel();
255  else
256  endInsertRows();
257 
258  mDeferredClear = false;
259 }
260 
261 
262 //
263 // QgsLocatorAutomaticModel
264 //
265 
267  : QgsLocatorModel( locator )
268  , mLocator( locator )
269 {
270  Q_ASSERT( mLocator );
271  connect( mLocator, &QgsLocator::foundResult, this, &QgsLocatorAutomaticModel::addResult );
272  connect( mLocator, &QgsLocator::finished, this, &QgsLocatorAutomaticModel::searchFinished );
273 }
274 
276 {
277  return mLocator;
278 }
279 
280 void QgsLocatorAutomaticModel::search( const QString &string )
281 {
282  if ( mLocator->isRunning() )
283  {
284  // can't do anything while a query is running, and can't block
285  // here waiting for the current query to cancel
286  // so we queue up this string until cancel has happened
287  mLocator->cancelWithoutBlocking();
288  mNextRequestedString = string;
289  mHasQueuedRequest = true;
290  return;
291  }
292  else
293  {
294  deferredClear();
295  mLocator->fetchResults( string, createContext() );
296  }
297 }
298 
300 {
301  return QgsLocatorContext();
302 }
303 
304 void QgsLocatorAutomaticModel::searchFinished()
305 {
306  if ( mHasQueuedRequest )
307  {
308  // a queued request was waiting for this - run the queued search now
309  QString nextSearch = mNextRequestedString;
310  mNextRequestedString.clear();
311  mHasQueuedRequest = false;
312  search( nextSearch );
313  }
314 }
315 
316 
317 
318 
319 
320 //
321 // QgsLocatorProxyModel
322 //
323 
325  : QSortFilterProxyModel( parent )
326 {
327  setDynamicSortFilter( true );
328  setSortLocaleAware( true );
329  setFilterCaseSensitivity( Qt::CaseInsensitive );
330  sort( 0 );
331 }
332 
333 bool QgsLocatorProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
334 {
335  // first go by filter priority
336  int leftFilterPriority = sourceModel()->data( left, QgsLocatorModel::ResultFilterPriorityRole ).toInt();
337  int rightFilterPriority = sourceModel()->data( right, QgsLocatorModel::ResultFilterPriorityRole ).toInt();
338  if ( leftFilterPriority != rightFilterPriority )
339  return leftFilterPriority < rightFilterPriority;
340 
341  // then filter name
342  QString leftFilter = sourceModel()->data( left, QgsLocatorModel::ResultFilterNameRole ).toString();
343  QString rightFilter = sourceModel()->data( right, QgsLocatorModel::ResultFilterNameRole ).toString();
344  if ( leftFilter != rightFilter )
345  return QString::localeAwareCompare( leftFilter, rightFilter ) < 0;
346 
347  // then make sure filter title or group appears before filter's results
348  int leftTypeRole = sourceModel()->data( left, QgsLocatorModel::ResultTypeRole ).toInt();
349  int rightTypeRole = sourceModel()->data( right, QgsLocatorModel::ResultTypeRole ).toInt();
350  if ( leftTypeRole != rightTypeRole )
351  return leftTypeRole < rightTypeRole;
352 
353  // make sure group title are above
354  int leftGroupRole = sourceModel()->data( left, QgsLocatorModel::ResultFilterGroupSortingRole ).toInt();
355  int rightGroupRole = sourceModel()->data( right, QgsLocatorModel::ResultFilterGroupSortingRole ).toInt();
356  if ( leftGroupRole != rightGroupRole )
357  return leftGroupRole < rightGroupRole;
358 
359  // sort filter's results by score
360  double leftScore = sourceModel()->data( left, QgsLocatorModel::ResultScoreRole ).toDouble();
361  double rightScore = sourceModel()->data( right, QgsLocatorModel::ResultScoreRole ).toDouble();
362  if ( !qgsDoubleNear( leftScore, rightScore ) )
363  return leftScore > rightScore;
364 
365  // lastly sort filter's results by string
366  leftFilter = sourceModel()->data( left, Qt::DisplayRole ).toString();
367  rightFilter = sourceModel()->data( right, Qt::DisplayRole ).toString();
368  return QString::localeAwareCompare( leftFilter, rightFilter ) < 0;
369 }
370 
371 
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
QgsLocator * locator()
Returns a pointer to the locator utilized by this model.
QgsLocatorAutomaticModel(QgsLocator *locator)
Constructor for QgsLocatorAutomaticModel, linked with the specified locator.
void search(const QString &string)
Enqueues a search for a specified string within the model.
virtual QgsLocatorContext createContext()
Returns a new locator context for searches.
Encapsulates the properties relating to the context of a locator search.
virtual QString displayName() const =0
Returns a translated, user-friendly name for the filter.
virtual QString name() const =0
Returns the unique name for the filter.
An abstract list model for displaying the results of locator searches.
int columnCount(const QModelIndex &parent=QModelIndex()) const override
void deferredClear()
Resets the model and clears all existing results after a short delay, or whenever the next result is ...
Qt::ItemFlags flags(const QModelIndex &index) const override
QHash< int, QByteArray > roleNames() const override
void addResult(const QgsLocatorResult &result)
Adds a new result to the model.
QgsLocatorModel(QObject *parent=nullptr)
Constructor for QgsLocatorModel.
void clear()
Resets the model and clears all existing results.
int rowCount(const QModelIndex &parent=QModelIndex()) const override
static const int NoGroup
@ ResultScoreRole
Result match score, used by QgsLocatorProxyModel for sorting roles.
@ ResultFilterNameRole
Associated filter name which created the result.
@ ResultDataRole
QgsLocatorResult data.
@ ResultActionsRole
The actions to be shown for the given result in a context menu.
@ ResultFilterPriorityRole
Result priority, used by QgsLocatorProxyModel for sorting roles.
@ ResultFilterGroupSortingRole
Group results within the same filter results.
@ ResultTypeRole
Result type.
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
QgsLocatorProxyModel(QObject *parent=nullptr)
Constructor for QgsLocatorProxyModel, with the specified parent object.
Encapsulates properties of an individual matching result found by a QgsLocatorFilter.
QString group
Group the results by categories If left as empty string, this means that results are all shown withou...
QgsLocatorFilter * filter
Filter from which the result was obtained.
Handles the management of QgsLocatorFilter objects and async collection of search results from them.
Definition: qgslocator.h:58
void finished()
Emitted when locator has finished a query, either as a result of successful completion or early cance...
void foundResult(const QgsLocatorResult &result)
Emitted whenever a filter encounters a matching result after the fetchResults() method is called.
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:129
bool isRunning() const
Returns true if a query is currently being executed by the locator.
Definition: qgslocator.cpp:261
void cancelWithoutBlocking()
Triggers cancellation of any current running query without blocking.
Definition: qgslocator.cpp:255
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:316