QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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.size();
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 switch ( role )
73 {
74 case Qt::DisplayRole:
75 case Qt::EditRole:
76 {
77 switch ( index.column() )
78 {
79 case Name:
80 if ( !mResults.at( index.row() ).filter )
81 return mResults.at( index.row() ).result.displayString;
82 else if ( mResults.at( index.row() ).filter && mResults.at( index.row() ).groupSorting == 0 )
83 return mResults.at( index.row() ).filterTitle;
84 else
85 {
86 QString groupTitle = mResults.at( index.row() ).groupTitle;
87 groupTitle.prepend( " " );
88 return groupTitle;
89 }
90 case Description:
91 if ( !mResults.at( index.row() ).filter )
92 return mResults.at( index.row() ).result.description;
93 else
94 return QVariant();
95 }
96 break;
97 }
98
99 case Qt::FontRole:
100 if ( index.column() == Name && !mResults.at( index.row() ).groupTitle.isEmpty() )
101 {
102 QFont font;
103 font.setItalic( true );
104 return font;
105 }
106 else
107 {
108 return QVariant();
109 }
110 break;
111
112 case Qt::DecorationRole:
113 switch ( index.column() )
114 {
115 case Name:
116 if ( !mResults.at( index.row() ).filter )
117 {
118 const QIcon icon = mResults.at( index.row() ).result.icon;
119 if ( !icon.isNull() )
120 return icon;
121 return QgsApplication::getThemeIcon( QStringLiteral( "/search.svg" ) );
122 }
123 else
124 return QVariant();
125 case Description:
126 return QVariant();
127 }
128 break;
129
130 case static_cast< int >( CustomRole::ResultData ):
131 if ( !mResults.at( index.row() ).filter )
132 return QVariant::fromValue( mResults.at( index.row() ).result );
133 else
134 return QVariant();
135
136 case static_cast< int >( CustomRole::ResultType ):
137 // 0 for filter title, the group otherwise, 9999 if no group
138 return mResults.at( index.row() ).groupSorting;
139
140 case static_cast< int >( CustomRole::ResultScore ):
141 if ( mResults.at( index.row() ).filter )
142 return 0;
143 else
144 return ( mResults.at( index.row() ).result.score );
145
146 case static_cast< int >( CustomRole::ResultFilterPriority ):
147 if ( !mResults.at( index.row() ).filter )
148 return mResults.at( index.row() ).result.filter->priority();
149 else
150 return mResults.at( index.row() ).filter->priority();
151
152 case static_cast< int >( CustomRole::ResultFilterName ):
153 if ( !mResults.at( index.row() ).filter )
154 return mResults.at( index.row() ).result.filter->displayName();
155 else
156 return mResults.at( index.row() ).filterTitle;
157
158 case static_cast< int >( CustomRole::ResultFilterGroupSorting ):
159 if ( mResults.at( index.row() ).groupTitle.isEmpty() )
160 return 1;
161 else
162 return 0;
163
164 case static_cast< int >( CustomRole::ResultActions ):
165 return QVariant::fromValue( mResults.at( index.row() ).result.actions );
166 }
167
168 return QVariant();
169}
170
171Qt::ItemFlags QgsLocatorModel::flags( const QModelIndex &index ) const
172{
173 if ( !index.isValid() || index.row() < 0 || index.column() < 0 ||
174 index.row() >= rowCount( QModelIndex() ) || index.column() >= columnCount( QModelIndex() ) )
175 return QAbstractTableModel::flags( index );
176
177 Qt::ItemFlags flags = QAbstractTableModel::flags( index );
178 if ( mResults.at( index.row() ).filter )
179 {
180 flags = flags & ~( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
181 }
182 return flags;
183}
184
185QHash<int, QByteArray> QgsLocatorModel::roleNames() const
186{
187 QHash<int, QByteArray> roles;
188 roles[static_cast< int >( CustomRole::ResultData )] = "ResultData";
189 roles[static_cast< int >( CustomRole::ResultType )] = "ResultType";
190 roles[static_cast< int >( CustomRole::ResultFilterPriority )] = "ResultFilterPriority";
191 roles[static_cast< int >( CustomRole::ResultScore )] = "ResultScore";
192 roles[static_cast< int >( CustomRole::ResultFilterName )] = "ResultFilterName";
193 roles[static_cast< int >( CustomRole::ResultFilterGroupSorting )] = "ResultFilterGroupSorting";
194 roles[static_cast< int >( CustomRole::ResultActions )] = "ResultContextMenuActions";
195 roles[Qt::DisplayRole] = "Text";
196 return roles;
197}
198
200{
201 mDeferredClearTimer.stop();
202 if ( mDeferredClear )
203 {
204 mFoundResultsFromFilterNames.clear();
205 mFoundResultsFilterGroups.clear();
206 }
207
208 const int pos = mResults.size();
209 const bool addingFilter = !result.filter->displayName().isEmpty() && !mFoundResultsFromFilterNames.contains( result.filter->name() );
210 if ( addingFilter )
211 mFoundResultsFromFilterNames << result.filter->name();
212
213 const bool addingGroup = !result.group.isEmpty() && ( !mFoundResultsFilterGroups.contains( result.filter )
214 || !mFoundResultsFilterGroups.value( result.filter ).contains( result.group ) );
215 if ( addingGroup )
216 {
217 if ( !mFoundResultsFilterGroups.contains( result.filter ) )
218 mFoundResultsFilterGroups[result.filter] = QStringList();
219 mFoundResultsFilterGroups[result.filter] << result.group ;
220 }
221 if ( mDeferredClear )
222 {
223 beginResetModel();
224 mResults.clear();
225 }
226 else
227 beginInsertRows( QModelIndex(), pos, pos + ( static_cast<int>( addingFilter ) + static_cast<int>( addingGroup ) ) );
228
229 if ( addingFilter )
230 {
231 Entry entry;
232 entry.filterTitle = result.filter->displayName();
233 entry.filter = result.filter;
234 mResults << entry;
235 }
236 if ( addingGroup )
237 {
238 Entry entry;
239 entry.filterTitle = result.filter->displayName();
240 entry.groupTitle = result.group;
241 // the sorting of groups will be achieved by order of adding groups
242 // this could be customized by adding the extra info to QgsLocatorResult
243 entry.groupSorting = mFoundResultsFilterGroups[result.filter].count();
244 entry.filter = result.filter;
245 mResults << entry;
246 }
247 Entry entry;
248 entry.result = result;
249 // keep the group title empty to allow differecing group title from results
250 entry.groupSorting = result.group.isEmpty() ? NoGroup : mFoundResultsFilterGroups[result.filter].indexOf( result.group ) + 1;
251 mResults << entry;
252
253 if ( mDeferredClear )
254 endResetModel();
255 else
256 endInsertRows();
257
258 mDeferredClear = false;
259}
260
261
262//
263// QgsLocatorAutomaticModel
264//
265
267 : QgsLocatorModel( locator )
268 , mLocator( locator )
269{
270 Q_ASSERT( mLocator );
272 connect( mLocator, &QgsLocator::finished, this, &QgsLocatorAutomaticModel::searchFinished );
273}
274
276{
277 return mLocator;
278}
279
280void QgsLocatorAutomaticModel::search( const QString &string )
281{
282 if ( mLocator->isRunning() )
283 {
284 // can't do anything while a query is running, and can't block
285 // here waiting for the current query to cancel
286 // so we queue up this string until cancel has happened
287 mLocator->cancelWithoutBlocking();
288 mNextRequestedString = string;
289 mHasQueuedRequest = true;
290 return;
291 }
292 else
293 {
295 mLocator->fetchResults( string, createContext() );
296 }
297}
298
300{
301 return QgsLocatorContext();
302}
303
304void QgsLocatorAutomaticModel::searchFinished()
305{
306 if ( mHasQueuedRequest )
307 {
308 // a queued request was waiting for this - run the queued search now
309 const QString nextSearch = mNextRequestedString;
310 mNextRequestedString.clear();
311 mHasQueuedRequest = false;
312 search( nextSearch );
313 }
314}
315
316
317
318
319
320//
321// QgsLocatorProxyModel
322//
323
325 : QSortFilterProxyModel( parent )
326{
327 setDynamicSortFilter( true );
328 setSortLocaleAware( true );
329 setFilterCaseSensitivity( Qt::CaseInsensitive );
330 sort( 0 );
331}
332
333bool QgsLocatorProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
334{
335 // first go by filter priority
336 const int leftFilterPriority = sourceModel()->data( left, static_cast< int >( QgsLocatorModel::CustomRole::ResultFilterPriority ) ).toInt();
337 const int rightFilterPriority = sourceModel()->data( right, static_cast< int >( QgsLocatorModel::CustomRole::ResultFilterPriority ) ).toInt();
338 if ( leftFilterPriority != rightFilterPriority )
339 return leftFilterPriority < rightFilterPriority;
340
341 // then filter name
342 QString leftFilter = sourceModel()->data( left, static_cast< int >( QgsLocatorModel::CustomRole::ResultFilterName ) ).toString();
343 QString rightFilter = sourceModel()->data( right, static_cast< int >( QgsLocatorModel::CustomRole::ResultFilterName ) ).toString();
344 if ( leftFilter != rightFilter )
345 return QString::localeAwareCompare( leftFilter, rightFilter ) < 0;
346
347 // then make sure filter title or group appears before filter's results
348 const int leftTypeRole = sourceModel()->data( left, static_cast< int >( QgsLocatorModel::CustomRole::ResultType ) ).toInt();
349 const int rightTypeRole = sourceModel()->data( right, static_cast< int >( QgsLocatorModel::CustomRole::ResultType ) ).toInt();
350 if ( leftTypeRole != rightTypeRole )
351 return leftTypeRole < rightTypeRole;
352
353 // make sure group title are above
354 const int leftGroupRole = sourceModel()->data( left, static_cast< int >( QgsLocatorModel::CustomRole::ResultFilterGroupSorting ) ).toInt();
355 const int rightGroupRole = sourceModel()->data( right, static_cast< int >( QgsLocatorModel::CustomRole::ResultFilterGroupSorting ) ).toInt();
356 if ( leftGroupRole != rightGroupRole )
357 return leftGroupRole < rightGroupRole;
358
359 // sort filter's results by score
360 const double leftScore = sourceModel()->data( left, static_cast< int >( QgsLocatorModel::CustomRole::ResultScore ) ).toDouble();
361 const double rightScore = sourceModel()->data( right, static_cast< int >( QgsLocatorModel::CustomRole::ResultScore ) ).toDouble();
362 if ( !qgsDoubleNear( leftScore, rightScore ) )
363 return leftScore > rightScore;
364
365 // lastly sort filter's results by string
366 leftFilter = sourceModel()->data( left, Qt::DisplayRole ).toString();
367 rightFilter = sourceModel()->data( right, Qt::DisplayRole ).toString();
368 return QString::localeAwareCompare( leftFilter, rightFilter ) < 0;
369}
370
371
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 ...
@ 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
Group results within the same filter results.
@ 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.
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.
Definition: qgslocator.cpp:133
bool isRunning() const
Returns true if a query is currently being executed by the locator.
Definition: qgslocator.cpp:270
void cancelWithoutBlocking()
Triggers cancellation of any current running query without blocking.
Definition: qgslocator.cpp:264
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:5207