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