QGIS API Documentation 3.43.0-Master (9e873c7bc91)
Loading...
Searching...
No Matches
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#include <QPalette>
20
21#include "qgslocatormodel.h"
22#include "moc_qgslocatormodel.cpp"
23#include "qgslocator.h"
24#include "qgsapplication.h"
25#include "qgslogger.h"
26
27
28//
29// QgsLocatorModel
30//
31
33 : QAbstractTableModel( parent )
34{
35 mDeferredClearTimer.setInterval( 100 );
36 mDeferredClearTimer.setSingleShot( true );
37 connect( &mDeferredClearTimer, &QTimer::timeout, this, &QgsLocatorModel::clear );
38}
39
41{
42 mDeferredClearTimer.stop();
43 mDeferredClear = false;
44
45 beginResetModel();
46 mResults.clear();
47 mFoundResultsFromFilterNames.clear();
48 mFoundResultsFilterGroups.clear();
49 endResetModel();
50}
51
53{
54 mDeferredClear = true;
55 mDeferredClearTimer.start();
56}
57
58int QgsLocatorModel::rowCount( const QModelIndex & ) const
59{
60 return mResults.count();
61}
62
63int QgsLocatorModel::columnCount( const QModelIndex & ) const
64{
65 return 2;
66}
67
68QVariant QgsLocatorModel::data( const QModelIndex &index, int role ) const
69{
70 if ( !index.isValid() || index.row() < 0 || index.column() < 0 ||
71 index.row() >= rowCount( QModelIndex() ) || index.column() >= columnCount( QModelIndex() ) )
72 return QVariant();
73
74 const Entry &entry = mResults.at( index.row() );
75 switch ( role )
76 {
77 case Qt::DisplayRole:
78 case Qt::EditRole:
79 {
80 switch ( static_cast<Column>( index.column() ) )
81 {
82 case Name:
83 {
84 QVariant v;
85 switch ( entry.type )
86 {
87 case EntryType::Filter:
88 {
89 v = entry.filterTitle;
90 break;
91 }
92
93 case EntryType::Group:
94 {
95 v = QStringLiteral( " " ).append( entry.groupTitle );
96 break;
97 }
98
99 case EntryType::Result:
100 {
101 v = entry.result.displayString;
102 break;
103 }
104 }
105
106 return v;
107 }
108
109 case Description:
110 if ( entry.type == EntryType::Result )
111 return entry.result.description;
112 else
113 return QVariant();
114 }
115 break;
116 }
117
118 case Qt::FontRole:
119 {
120 if ( index.column() == Name )
121 {
122 QFont font;
123 font.setBold( entry.type == EntryType::Filter );
124 font.setItalic( entry.type == EntryType::Group );
125 return font;
126 }
127 else
128 {
129 return QVariant();
130 }
131 break;
132 }
133
134 case Qt::BackgroundRole:
135 {
136 return entry.type == EntryType::Result ? QPalette().base() : QPalette().alternateBase();
137 }
138
139 case Qt::ForegroundRole:
140 {
141 return QPalette().text();
142 }
143
144
145 case Qt::DecorationRole:
146 switch ( static_cast<Column>( index.column() ) )
147 {
148 case Name:
149 if ( entry.type == EntryType::Result )
150 {
151 const QIcon &icon = entry.result.icon;
152 if ( !icon.isNull() )
153 return icon;
154 return QgsApplication::getThemeIcon( QStringLiteral( "/search.svg" ) );
155 }
156 else
157 return QVariant();
158 case Description:
159 return QVariant();
160 }
161 break;
162
163 case static_cast< int >( CustomRole::ResultData ):
164 if ( entry.type == EntryType::Result )
165 return QVariant::fromValue( entry.result );
166 else
167 return QVariant();
168
169 case static_cast< int >( CustomRole::ResultType ):
170 return static_cast<int>( entry.type );
171
172 case static_cast< int >( CustomRole::ResultScore ):
173 if ( entry.filter )
174 return 0;
175 else
176 return ( entry.result.score );
177
178 case static_cast< int >( CustomRole::ResultFilterPriority ):
179 return entry.filter->priority();
180
181 case static_cast< int >( CustomRole::ResultFilterName ):
182 return entry.filterTitle;
183
184 case static_cast< int >( CustomRole::ResultFilterGroupTitle ):
185 return entry.groupTitle;
186
187 case static_cast< int >( CustomRole::ResultFilterGroupScore ):
188 return entry.groupScore;
189
190 case static_cast< int >( CustomRole::ResultActions ):
191 return QVariant::fromValue( entry.result.actions );
192 }
193
194 return QVariant();
195}
196
197Qt::ItemFlags QgsLocatorModel::flags( const QModelIndex &index ) const
198{
199 if ( !index.isValid() || index.row() < 0 || index.column() < 0 ||
200 index.row() >= rowCount( QModelIndex() ) || index.column() >= columnCount( QModelIndex() ) )
201 return QAbstractTableModel::flags( index );
202
203 Qt::ItemFlags flags = QAbstractTableModel::flags( index );
204 if ( mResults.at( index.row() ).type != QgsLocatorModel::EntryType::Result )
205 {
206 flags = flags & ~( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
207 }
208 return flags;
209}
210
211QHash<int, QByteArray> QgsLocatorModel::roleNames() const
212{
213 QHash<int, QByteArray> roles;
214 roles[static_cast< int >( CustomRole::ResultData )] = "ResultData";
215 roles[static_cast< int >( CustomRole::ResultType )] = "ResultType";
216 roles[static_cast< int >( CustomRole::ResultFilterPriority )] = "ResultFilterPriority";
217 roles[static_cast< int >( CustomRole::ResultScore )] = "ResultScore";
218 roles[static_cast< int >( CustomRole::ResultFilterName )] = "ResultFilterName";
219 roles[static_cast< int >( CustomRole::ResultFilterGroupSorting )] = "ResultFilterGroupSorting"; // Deprecated
220 roles[static_cast< int >( CustomRole::ResultFilterGroupTitle )] = "ResultFilterGroupTitle";
221 roles[static_cast< int >( CustomRole::ResultFilterGroupScore )] = "ResultFilterGroupScore";
222 roles[static_cast< int >( CustomRole::ResultActions )] = "ResultContextMenuActions";
223 roles[Qt::DisplayRole] = "Text";
224 return roles;
225}
226
228{
229 mDeferredClearTimer.stop();
230 if ( mDeferredClear )
231 {
232 mFoundResultsFromFilterNames.clear();
233 mFoundResultsFilterGroups.clear();
234 }
235
236 const int pos = mResults.size();
237 const bool addingFilter = !result.filter->displayName().isEmpty() && !mFoundResultsFromFilterNames.contains( result.filter->name() );
238 if ( addingFilter )
239 mFoundResultsFromFilterNames << result.filter->name();
240
241 const bool addingGroup = !result.group.isEmpty() && ( !mFoundResultsFilterGroups.contains( result.filter )
242 || !mFoundResultsFilterGroups.value( result.filter ).contains( std::pair( result.group, result.groupScore ) ) );
243 if ( addingGroup )
244 {
245 if ( !mFoundResultsFilterGroups.contains( result.filter ) )
246 mFoundResultsFilterGroups[result.filter] = QList<std::pair<QString, double>>();
247
248 mFoundResultsFilterGroups[result.filter] << std::pair( result.group, result.groupScore );
249 }
250
251 if ( mDeferredClear )
252 {
253 beginResetModel();
254 mResults.clear();
255 }
256 else
257 {
258 beginInsertRows( QModelIndex(), pos, pos + ( static_cast<int>( addingFilter ) + static_cast<int>( addingGroup ) ) );
259 }
260
261 const double groupScore = result.group.isEmpty() ? NoGroup : result.groupScore;
262 if ( addingFilter )
263 {
264 Entry entry;
265 entry.type = EntryType::Filter;
266 entry.filterTitle = result.filter->displayName();
267 entry.filter = result.filter;
268 mResults << entry;
269 }
270 if ( addingGroup )
271 {
272 Entry entry;
273 entry.type = EntryType::Group;
274 entry.filterTitle = result.filter->displayName();
275 entry.groupTitle = result.group;
276 entry.groupScore = groupScore;
277 entry.filter = result.filter;
278 mResults << entry;
279 }
280 Entry entry;
281 entry.type = EntryType::Result;
282 entry.filter = result.filter;
283 entry.filterTitle = result.filter->displayName();
284 entry.result = result;
285 entry.groupTitle = result.group;
286 entry.groupScore = groupScore;
287 mResults << entry;
288
289 if ( mDeferredClear )
290 endResetModel();
291 else
292 endInsertRows();
293
294 mDeferredClear = false;
295}
296
297
298//
299// QgsLocatorAutomaticModel
300//
301
303 : QgsLocatorModel( locator )
304 , mLocator( locator )
305{
306 Q_ASSERT( mLocator );
308 connect( mLocator, &QgsLocator::finished, this, &QgsLocatorAutomaticModel::searchFinished );
309}
310
312{
313 return mLocator;
314}
315
316void QgsLocatorAutomaticModel::search( const QString &string )
317{
318 if ( mLocator->isRunning() )
319 {
320 // can't do anything while a query is running, and can't block
321 // here waiting for the current query to cancel
322 // so we queue up this string until cancel has happened
323 mLocator->cancelWithoutBlocking();
324 mNextRequestedString = string;
325 mHasQueuedRequest = true;
326 return;
327 }
328 else
329 {
331 mLocator->fetchResults( string, createContext() );
332 }
333}
334
339
340void QgsLocatorAutomaticModel::searchFinished()
341{
342 if ( mHasQueuedRequest )
343 {
344 // a queued request was waiting for this - run the queued search now
345 const QString nextSearch = mNextRequestedString;
346 mNextRequestedString.clear();
347 mHasQueuedRequest = false;
348 search( nextSearch );
349 }
350}
351
352
353
354
355
356//
357// QgsLocatorProxyModel
358//
359
361 : QSortFilterProxyModel( parent )
362{
363 setDynamicSortFilter( true );
364 setSortLocaleAware( true );
365 setFilterCaseSensitivity( Qt::CaseInsensitive );
366 sort( 0 );
367}
368
369bool QgsLocatorProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
370{
371 typedef QgsLocatorModel::CustomRole CustomRole;
372
373 // sort by filter priority
374 const QAbstractItemModel *lSourceModel = sourceModel();
375 const int leftFilterPriority = lSourceModel->data( left, static_cast< int >( CustomRole::ResultFilterPriority ) ).toInt();
376 const int rightFilterPriority = lSourceModel->data( right, static_cast< int >( CustomRole::ResultFilterPriority ) ).toInt();
377 if ( leftFilterPriority != rightFilterPriority )
378 return leftFilterPriority < rightFilterPriority;
379
380 // sort by filter name
381 QString leftFilter = lSourceModel->data( left, static_cast< int >( CustomRole::ResultFilterName ) ).toString();
382 QString rightFilter = lSourceModel->data( right, static_cast< int >( CustomRole::ResultFilterName ) ).toString();
383 if ( leftFilter != rightFilter )
384 return QString::localeAwareCompare( leftFilter, rightFilter ) < 0;
385
386 // make sure filter title appears before
387 const int leftTypeRole = lSourceModel->data( left, static_cast< int >( CustomRole::ResultType ) ).toInt();
388 const int rightTypeRole = lSourceModel->data( right, static_cast< int >( CustomRole::ResultType ) ).toInt();
389 if ( leftTypeRole != rightTypeRole && ( leftTypeRole == 0 || rightTypeRole == 0 ) )
390 return leftTypeRole < rightTypeRole;
391
392 // sort by group score
393 const double leftGroupScoreRole = lSourceModel->data( left, static_cast< double >( CustomRole::ResultFilterGroupScore ) ).toDouble();
394 const double rightGroupScoreRole = lSourceModel->data( right, static_cast< double >( CustomRole::ResultFilterGroupScore ) ).toDouble();
395 if ( leftGroupScoreRole != rightGroupScoreRole )
396 return leftGroupScoreRole > rightGroupScoreRole;
397
398 // sort by group name alphabetically
399 QString leftGroupTitle = lSourceModel->data( left, static_cast< int >( CustomRole::ResultFilterGroupTitle ) ).toString();
400 QString rightGroupTitle = lSourceModel->data( right, static_cast< int >( CustomRole::ResultFilterGroupTitle ) ).toString();
401 if ( leftGroupTitle != rightGroupTitle )
402 return QString::localeAwareCompare( leftGroupTitle, rightGroupTitle ) < 0;
403
404 // make sure group appears before filter's results
405 if ( leftTypeRole != rightTypeRole )
406 return leftTypeRole < rightTypeRole;
407
408 // sort results by score
409 const double leftScore = lSourceModel->data( left, static_cast< int >( CustomRole::ResultScore ) ).toDouble();
410 const double rightScore = lSourceModel->data( right, static_cast< int >( CustomRole::ResultScore ) ).toDouble();
411 if ( !qgsDoubleNear( leftScore, rightScore ) )
412 return leftScore > rightScore;
413
414 // sort results alphabetically
415 leftFilter = lSourceModel->data( left, Qt::DisplayRole ).toString();
416 rightFilter = lSourceModel->data( right, Qt::DisplayRole ).toString();
417 return QString::localeAwareCompare( leftFilter, rightFilter ) < 0;
418}
419
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
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 ...
CustomRole
Custom model roles.
@ ResultFilterGroupTitle
Group title.
@ ResultScore
Result match score, used by QgsLocatorProxyModel for sorting roles.
@ ResultFilterPriority
Result priority, used by QgsLocatorProxyModel for sorting roles.
@ ResultFilterGroupScore
Group score.
@ ResultFilterName
Associated filter name which created the result.
@ ResultData
QgsLocatorResult data.
@ ResultFilterGroupSorting
Custom value for sorting.
@ ResultActions
The actions to be shown for the given result in a context menu.
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
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.
double groupScore
Specifies the score of the group to allow ordering.
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:61
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.
bool isRunning() const
Returns true if a query is currently being executed by the locator.
void cancelWithoutBlocking()
Triggers cancellation of any current running query without blocking.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6202