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