QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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 
QgsLocatorAutomaticModel(QgsLocator *locator)
Constructor for QgsLocatorAutomaticModel, linked with the specified locator.
QHash< int, QByteArray > roleNames() const override
void cancelWithoutBlocking()
Triggers cancellation of any current running query without blocking.
Definition: qgslocator.cpp:231
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:124
The actions to be shown for the given result in a context menu.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:280
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:237
QgsLocator * locator()
Returns a pointer to the locator utilized by this model.
Qt::ItemFlags flags(const QModelIndex &index) const override
QString group
Group the results by categories If left as empty string, this means that results are all shown withou...
void search(const QString &string)
Enqueues a search for a specified string within the model.
void finished()
Emitted when locator has finished a query, either as a result of successful completion or early cance...
static const int NoGroup
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
Group results within the same filter results.
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:57
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.