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