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