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