QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 
165  return QVariant();
166 }
167 
168 Qt::ItemFlags QgsLocatorModel::flags( const QModelIndex &index ) const
169 {
170  if ( !index.isValid() || index.row() < 0 || index.column() < 0 ||
171  index.row() >= rowCount( QModelIndex() ) || index.column() >= columnCount( QModelIndex() ) )
172  return QAbstractTableModel::flags( index );
173 
174  Qt::ItemFlags flags = QAbstractTableModel::flags( index );
175  if ( mResults.at( index.row() ).filter )
176  {
177  flags = flags & ~( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
178  }
179  return flags;
180 }
181 
183 {
184  mDeferredClearTimer.stop();
185  if ( mDeferredClear )
186  {
187  mFoundResultsFromFilterNames.clear();
188  mFoundResultsFilterGroups.clear();
189  }
190 
191  int pos = mResults.size();
192  bool addingFilter = !result.filter->displayName().isEmpty() && !mFoundResultsFromFilterNames.contains( result.filter->name() );
193  if ( addingFilter )
194  mFoundResultsFromFilterNames << result.filter->name();
195 
196  bool addingGroup = !result.group.isEmpty() && ( !mFoundResultsFilterGroups.contains( result.filter )
197  || !mFoundResultsFilterGroups.value( result.filter ).contains( result.group ) );
198  if ( addingGroup )
199  {
200  if ( !mFoundResultsFilterGroups.contains( result.filter ) )
201  mFoundResultsFilterGroups[result.filter] = QStringList();
202  mFoundResultsFilterGroups[result.filter] << result.group ;
203  }
204  if ( mDeferredClear )
205  {
206  beginResetModel();
207  mResults.clear();
208  }
209  else
210  beginInsertRows( QModelIndex(), pos, pos + ( static_cast<int>( addingFilter ) + static_cast<int>( addingGroup ) ) );
211 
212  if ( addingFilter )
213  {
214  Entry entry;
215  entry.filterTitle = result.filter->displayName();
216  entry.filter = result.filter;
217  mResults << entry;
218  }
219  if ( addingGroup )
220  {
221  Entry entry;
222  entry.filterTitle = result.filter->displayName();
223  entry.groupTitle = result.group;
224  // the sorting of groups will be achieved by order of adding groups
225  // this could be customized by adding the extra info to QgsLocatorResult
226  entry.groupSorting = mFoundResultsFilterGroups[result.filter].count();
227  entry.filter = result.filter;
228  mResults << entry;
229  }
230  Entry entry;
231  entry.result = result;
232  // keep the group title empty to allow differecing group title from results
233  entry.groupSorting = result.group.isEmpty() ? NoGroup : mFoundResultsFilterGroups[result.filter].indexOf( result.group ) + 1;
234  mResults << entry;
235 
236  if ( mDeferredClear )
237  endResetModel();
238  else
239  endInsertRows();
240 
241  mDeferredClear = false;
242 }
243 
244 
245 //
246 // QgsLocatorAutomaticModel
247 //
248 
250  : QgsLocatorModel( locator )
251  , mLocator( locator )
252 {
253  Q_ASSERT( mLocator );
254  connect( mLocator, &QgsLocator::foundResult, this, &QgsLocatorAutomaticModel::addResult );
255  connect( mLocator, &QgsLocator::finished, this, &QgsLocatorAutomaticModel::searchFinished );
256 }
257 
259 {
260  return mLocator;
261 }
262 
263 void QgsLocatorAutomaticModel::search( const QString &string )
264 {
265  if ( mLocator->isRunning() )
266  {
267  // can't do anything while a query is running, and can't block
268  // here waiting for the current query to cancel
269  // so we queue up this string until cancel has happened
270  mLocator->cancelWithoutBlocking();
271  mNextRequestedString = string;
272  mHasQueuedRequest = true;
273  return;
274  }
275  else
276  {
277  deferredClear();
278  mLocator->fetchResults( string, createContext() );
279  }
280 }
281 
283 {
284  return QgsLocatorContext();
285 }
286 
287 void QgsLocatorAutomaticModel::searchFinished()
288 {
289  if ( mHasQueuedRequest )
290  {
291  // a queued request was waiting for this - run the queued search now
292  QString nextSearch = mNextRequestedString;
293  mNextRequestedString.clear();
294  mHasQueuedRequest = false;
295  search( nextSearch );
296  }
297 }
298 
299 
300 
301 
302 
303 //
304 // QgsLocatorProxyModel
305 //
306 
308  : QSortFilterProxyModel( parent )
309 {
310  setDynamicSortFilter( true );
311  setSortLocaleAware( true );
312  setFilterCaseSensitivity( Qt::CaseInsensitive );
313  sort( 0 );
314 }
315 
316 bool QgsLocatorProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
317 {
318  // first go by filter priority
319  int leftFilterPriority = sourceModel()->data( left, QgsLocatorModel::ResultFilterPriorityRole ).toInt();
320  int rightFilterPriority = sourceModel()->data( right, QgsLocatorModel::ResultFilterPriorityRole ).toInt();
321  if ( leftFilterPriority != rightFilterPriority )
322  return leftFilterPriority < rightFilterPriority;
323 
324  // then filter name
325  QString leftFilter = sourceModel()->data( left, QgsLocatorModel::ResultFilterNameRole ).toString();
326  QString rightFilter = sourceModel()->data( right, QgsLocatorModel::ResultFilterNameRole ).toString();
327  if ( leftFilter != rightFilter )
328  return QString::localeAwareCompare( leftFilter, rightFilter ) < 0;
329 
330  // then make sure filter title or group appears before filter's results
331  int leftTypeRole = sourceModel()->data( left, QgsLocatorModel::ResultTypeRole ).toInt();
332  int rightTypeRole = sourceModel()->data( right, QgsLocatorModel::ResultTypeRole ).toInt();
333  if ( leftTypeRole != rightTypeRole )
334  return leftTypeRole < rightTypeRole;
335 
336  // make sure group title are above
337  int leftGroupRole = sourceModel()->data( left, QgsLocatorModel::ResultFilterGroupSortingRole ).toInt();
338  int rightGroupRole = sourceModel()->data( right, QgsLocatorModel::ResultFilterGroupSortingRole ).toInt();
339  if ( leftGroupRole != rightGroupRole )
340  return leftGroupRole < rightGroupRole;
341 
342  // sort filter's results by score
343  double leftScore = sourceModel()->data( left, QgsLocatorModel::ResultScoreRole ).toDouble();
344  double rightScore = sourceModel()->data( right, QgsLocatorModel::ResultScoreRole ).toDouble();
345  if ( !qgsDoubleNear( leftScore, rightScore ) )
346  return leftScore > rightScore;
347 
348  // lastly sort filter's results by string
349  leftFilter = sourceModel()->data( left, Qt::DisplayRole ).toString();
350  rightFilter = sourceModel()->data( right, Qt::DisplayRole ).toString();
351  return QString::localeAwareCompare( leftFilter, rightFilter ) < 0;
352 }
353 
354 
QgsLocatorAutomaticModel(QgsLocator *locator)
Constructor for QgsLocatorAutomaticModel, linked with the specified locator.
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
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:278
QgsLocatorProxyModel(QObject *parent=nullptr)
Constructor for QgsLocatorProxyModel, with the specified parent object.
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
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
bool isRunning() const
Returns true if a query is currently being executed by the locator.
Definition: qgslocator.cpp:237
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.