QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
qgshistoryentrymodel.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgshistoryentrymodel.cpp
3 --------------------------
4 begin : April 2023
5 copyright : (C) 2023 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8/***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
17#include "qgshistoryentrynode.h"
19#include "qgsgui.h"
20#include "qgshistoryentry.h"
21#include "qgshistoryprovider.h"
22#include "qgsapplication.h"
23
24#include <QIcon>
25
27class QgsHistoryEntryRootNode : public QgsHistoryEntryGroup
28{
29 public:
30 QVariant data( int = Qt::DisplayRole ) const override;
31
32 void addEntryNode( const QgsHistoryEntry &entry, QgsHistoryEntryNode *node, QgsHistoryEntryModel *model );
33
38 static QString dateGroup( const QDateTime &timestamp, QString &sortKey );
39
40 QgsHistoryEntryDateGroupNode *dateNode( const QDateTime &timestamp, QgsHistoryEntryModel *model );
41
42 private:
43
44 QMap< QString, QgsHistoryEntryDateGroupNode * > mDateGroupNodes;
45
46};
47
48class QgsHistoryEntryDateGroupNode : public QgsHistoryEntryGroup
49{
50 public:
51
52 QgsHistoryEntryDateGroupNode( const QString &title, const QString &key )
53 : mTitle( title )
54 , mKey( key )
55 {
56
57 }
58
59 QVariant data( int role = Qt::DisplayRole ) const override
60 {
61 switch ( role )
62 {
63 case Qt::DisplayRole:
64 case Qt::ToolTipRole:
65 return mTitle;
66
67 case Qt::DecorationRole:
68 return QgsApplication::getThemeIcon( QStringLiteral( "mIconFolder.svg" ) );
69
70 default:
71 break;
72 }
73
74 return QVariant();
75 }
76
77 QString mTitle;
78 QString mKey;
79
80};
82
83QgsHistoryEntryModel::QgsHistoryEntryModel( const QString &providerId, Qgis::HistoryProviderBackends backends, QgsHistoryProviderRegistry *registry, const QgsHistoryWidgetContext &context, QObject *parent )
84 : QAbstractItemModel( parent )
85 , mContext( context )
86 , mRegistry( registry ? registry : QgsGui::historyProviderRegistry() )
87 , mProviderId( providerId )
88 , mBackends( backends )
89{
90 mRootNode = std::make_unique< QgsHistoryEntryRootNode>();
91
92 // populate with existing entries
93 const QList< QgsHistoryEntry > entries = mRegistry->queryEntries( QDateTime(), QDateTime(), mProviderId, mBackends );
94 for ( const QgsHistoryEntry &entry : entries )
95 {
96 QgsAbstractHistoryProvider *provider = mRegistry->providerById( entry.providerId );
97 if ( !provider )
98 continue;
99
100 if ( QgsHistoryEntryNode *node = provider->createNodeForEntry( entry.entry, mContext ) )
101 {
102 mIdToNodeHash.insert( entry.id, node );
103 mRootNode->addEntryNode( entry, node, nullptr );
104 }
105 }
106
107 connect( mRegistry, &QgsHistoryProviderRegistry::entryAdded, this, &QgsHistoryEntryModel::entryAdded );
108 connect( mRegistry, &QgsHistoryProviderRegistry::entryUpdated, this, &QgsHistoryEntryModel::entryUpdated );
109 connect( mRegistry, &QgsHistoryProviderRegistry::historyCleared, this, &QgsHistoryEntryModel::historyCleared );
110}
111
113{
114
115}
116
117int QgsHistoryEntryModel::rowCount( const QModelIndex &parent ) const
118{
120 if ( !n )
121 return 0;
122
123 return n->childCount();
124}
125
126int QgsHistoryEntryModel::columnCount( const QModelIndex &parent ) const
127{
128 Q_UNUSED( parent )
129 return 1;
130}
131
132QModelIndex QgsHistoryEntryModel::index( int row, int column, const QModelIndex &parent ) const
133{
134 if ( column < 0 || column >= columnCount( parent ) ||
135 row < 0 || row >= rowCount( parent ) )
136 return QModelIndex();
137
138 QgsHistoryEntryGroup *n = dynamic_cast< QgsHistoryEntryGroup * >( index2node( parent ) );
139 if ( !n )
140 return QModelIndex(); // have no children
141
142 return createIndex( row, column, n->childAt( row ) );
143}
144
145QModelIndex QgsHistoryEntryModel::parent( const QModelIndex &child ) const
146{
147 if ( !child.isValid() )
148 return QModelIndex();
149
150 if ( QgsHistoryEntryNode *n = index2node( child ) )
151 {
152 return indexOfParentNode( n->parent() ); // must not be null
153 }
154 else
155 {
156 Q_ASSERT( false );
157 return QModelIndex();
158 }
159}
160
161QVariant QgsHistoryEntryModel::data( const QModelIndex &index, int role ) const
162{
163 if ( !index.isValid() || index.column() > 1 )
164 return QVariant();
165
167 if ( !node )
168 return QVariant();
169
170 return node->data( role );
171}
172
173Qt::ItemFlags QgsHistoryEntryModel::flags( const QModelIndex &index ) const
174{
175 if ( !index.isValid() )
176 {
177 Qt::ItemFlags rootFlags = Qt::ItemFlags();
178 return rootFlags;
179 }
180
181 Qt::ItemFlags f = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
182 return f;
183}
184
186{
187 if ( !index.isValid() )
188 return mRootNode.get();
189
190 return reinterpret_cast<QgsHistoryEntryNode *>( index.internalPointer() );
191
192}
193
194void QgsHistoryEntryModel::entryAdded( long long id, const QgsHistoryEntry &entry, Qgis::HistoryProviderBackend backend )
195{
196 // ignore entries we don't care about
197 if ( !( mBackends & backend ) )
198 return;
199 if ( !mProviderId.isEmpty() && entry.providerId != mProviderId )
200 return;
201
202 QgsAbstractHistoryProvider *provider = mRegistry->providerById( entry.providerId );
203 if ( !provider )
204 return;
205
206 if ( QgsHistoryEntryNode *node = provider->createNodeForEntry( entry.entry, mContext ) )
207 {
208 mIdToNodeHash.insert( id, node );
209 mRootNode->addEntryNode( entry, node, this );
210 }
211}
212
213void QgsHistoryEntryModel::entryUpdated( long long id, const QVariantMap &entry, Qgis::HistoryProviderBackend backend )
214{
215 // ignore entries we don't care about
216 if ( !( mBackends & backend ) )
217 return;
218
219 // an update is a remove + reinsert operation
220 if ( QgsHistoryEntryNode *node = mIdToNodeHash.value( id ) )
221 {
222 bool ok = false;
223 QgsHistoryEntry historyEntry = mRegistry->entry( id, ok, backend );
224 historyEntry.entry = entry;
225 const QString providerId = historyEntry.providerId;
226 QgsAbstractHistoryProvider *provider = mRegistry->providerById( providerId );
227 if ( !provider )
228 return;
229
230 const QModelIndex nodeIndex = node2index( node );
231 const int existingChildRows = node->childCount();
232 provider->updateNodeForEntry( node, historyEntry, mContext );
233 const int newChildRows = node->childCount();
234
235 if ( newChildRows < existingChildRows )
236 {
237 beginRemoveRows( nodeIndex, newChildRows, existingChildRows - 1 );
238 endRemoveRows();
239 }
240 else if ( existingChildRows < newChildRows )
241 {
242 beginInsertRows( nodeIndex, existingChildRows, newChildRows - 1 );
243 endInsertRows();
244 }
245
246 const QModelIndex topLeft = index( 0, 0, nodeIndex );
247 const QModelIndex bottomRight = index( newChildRows - 1, columnCount() - 1, nodeIndex );
248 emit dataChanged( topLeft, bottomRight );
249 emit dataChanged( nodeIndex, nodeIndex );
250 }
251}
252
253void QgsHistoryEntryModel::historyCleared( Qgis::HistoryProviderBackend backend, const QString &providerId )
254{
255 // ignore entries we don't care about
256 if ( !( mBackends & backend ) )
257 return;
258
259 if ( !mProviderId.isEmpty() && !providerId.isEmpty() && providerId != mProviderId )
260 return;
261
262 beginResetModel();
263 mRootNode->clear();
264 mIdToNodeHash.clear();
265 endResetModel();
266}
267
268QModelIndex QgsHistoryEntryModel::node2index( QgsHistoryEntryNode *node ) const
269{
270 if ( !node || !node->parent() )
271 return QModelIndex(); // this is the only root item -> invalid index
272
273 QModelIndex parentIndex = node2index( node->parent() );
274
275 int row = node->parent()->indexOf( node );
276 Q_ASSERT( row >= 0 );
277 return index( row, 0, parentIndex );
278}
279
280QModelIndex QgsHistoryEntryModel::indexOfParentNode( QgsHistoryEntryNode *parentNode ) const
281{
282 Q_ASSERT( parentNode );
283
284 QgsHistoryEntryGroup *grandParentNode = parentNode->parent();
285 if ( !grandParentNode )
286 return QModelIndex(); // root node -> invalid index
287
288 int row = grandParentNode->indexOf( parentNode );
289 Q_ASSERT( row >= 0 );
290
291 return createIndex( row, 0, parentNode );
292}
293
294//
295// QgsHistoryEntryRootNode
296//
298QVariant QgsHistoryEntryRootNode::data( int ) const
299{
300 return QVariant();
301}
302
303void QgsHistoryEntryRootNode::addEntryNode( const QgsHistoryEntry &entry, QgsHistoryEntryNode *node, QgsHistoryEntryModel *model )
304{
305 QgsHistoryEntryDateGroupNode *targetDateNode = dateNode( entry.timestamp, model );
306
307 if ( model )
308 {
309 const QModelIndex dateNodeIndex = model->node2index( targetDateNode );
310 model->beginInsertRows( dateNodeIndex, 0, 0 );
311 }
312 targetDateNode->insertChild( 0, node );
313 if ( model )
314 {
315 model->endInsertRows();
316 }
317}
318
319QString QgsHistoryEntryRootNode::dateGroup( const QDateTime &timestamp, QString &sortKey )
320{
321 QString groupString;
322 if ( timestamp.date() == QDateTime::currentDateTime().date() )
323 {
324 groupString = QObject::tr( "Today" );
325 sortKey = QStringLiteral( "0" );
326 }
327 else
328 {
329 const qint64 intervalDays = timestamp.date().daysTo( QDateTime::currentDateTime().date() );
330 if ( intervalDays == 1 )
331 {
332 groupString = QObject::tr( "Yesterday" );
333 sortKey = QStringLiteral( "1" );
334 }
335 else if ( intervalDays < 8 )
336 {
337 groupString = QObject::tr( "Last 7 days" );
338 sortKey = QStringLiteral( "2" );
339 }
340 else
341 {
342 // a bit of trickiness here, we need dates ordered descending
343 sortKey = QStringLiteral( "3: %1 %2" ).arg( QDate::currentDate().year() - timestamp.date().year(), 5, 10, QLatin1Char( '0' ) )
344 .arg( 12 - timestamp.date().month(), 2, 10, QLatin1Char( '0' ) );
345 groupString = timestamp.toString( QStringLiteral( "MMMM yyyy" ) );
346 }
347 }
348 return groupString;
349}
350
351QgsHistoryEntryDateGroupNode *QgsHistoryEntryRootNode::dateNode( const QDateTime &timestamp, QgsHistoryEntryModel *model )
352{
353 QString dateGroupKey;
354 const QString dateTitle = dateGroup( timestamp, dateGroupKey );
355
356 QgsHistoryEntryDateGroupNode *node = mDateGroupNodes.value( dateGroupKey );
357 if ( !node )
358 {
359 node = new QgsHistoryEntryDateGroupNode( dateTitle, dateGroupKey );
360 mDateGroupNodes[ dateGroupKey ] = node;
361
362 int targetIndex = 0;
363 bool isInsert = false;
364 for ( const auto &child : mChildren )
365 {
366 if ( QgsHistoryEntryDateGroupNode *candidateNode = dynamic_cast< QgsHistoryEntryDateGroupNode * >( child.get() ) )
367 {
368 if ( candidateNode->mKey > dateGroupKey )
369 {
370 isInsert = true;
371 break;
372 }
373 }
374 targetIndex++;
375 }
376
377 if ( isInsert )
378 {
379 if ( model )
380 {
381 model->beginInsertRows( QModelIndex(), targetIndex, targetIndex );
382 }
383 insertChild( targetIndex, node );
384 if ( model )
385 {
386 model->endInsertRows();
387 }
388 }
389 else
390 {
391 if ( model )
392 {
393 model->beginInsertRows( QModelIndex(), childCount(), childCount() );
394 }
395 addChild( node );
396 if ( model )
397 {
398 model->endInsertRows();
399 }
400 }
401 }
402
403 return node;
404}
HistoryProviderBackend
History provider backends.
Definition: qgis.h:2417
Abstract base class for objects which track user history (i.e.
virtual void updateNodeForEntry(QgsHistoryEntryNode *node, const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context)
Updates an existing history node for the given entry.
virtual QgsHistoryEntryNode * createNodeForEntry(const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context)
Creates a new history node for the given entry.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
QgsGui is a singleton class containing various registry and other global members related to GUI class...
Definition: qgsgui.h:60
Base class for history entry "group" nodes, which contain children of their own.
QgsHistoryEntryNode * childAt(int index)
Returns the child at the specified index.
int indexOf(QgsHistoryEntryNode *child) const
Returns the index of the specified child node.
An item model representing history entries in a hierarchical tree structure.
QgsHistoryEntryModel(const QString &providerId=QString(), Qgis::HistoryProviderBackends backends=Qgis::HistoryProviderBackend::LocalProfile, QgsHistoryProviderRegistry *registry=nullptr, const QgsHistoryWidgetContext &context=QgsHistoryWidgetContext(), QObject *parent=nullptr)
Constructor for QgsHistoryEntryModel, with the specified parent object.
QgsHistoryEntryNode * index2node(const QModelIndex &index) const
Returns node for given index.
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
Qt::ItemFlags flags(const QModelIndex &index) const override
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const final
int columnCount(const QModelIndex &parent=QModelIndex()) const final
QModelIndex parent(const QModelIndex &child) const final
int rowCount(const QModelIndex &parent=QModelIndex()) const final
Base class for nodes representing a QgsHistoryEntry.
virtual QVariant data(int role=Qt::DisplayRole) const =0
Returns the node's data for the specified model role.
QgsHistoryEntryGroup * parent()
Returns the node's parent node.
virtual int childCount() const
Returns the number of child nodes owned by this node.
Encapsulates a history entry.
QDateTime timestamp
Entry timestamp.
QString providerId
Associated history provider ID.
QVariantMap entry
Entry details.
The QgsHistoryProviderRegistry is a registry for objects which track user history (i....
QgsAbstractHistoryProvider * providerById(const QString &id)
Returns the provider with matching id, or nullptr if no matching provider is registered.
void entryAdded(long long id, const QgsHistoryEntry &entry, Qgis::HistoryProviderBackend backend)
Emitted when an entry is added.
QgsHistoryEntry entry(long long id, bool &ok, Qgis::HistoryProviderBackend backend=Qgis::HistoryProviderBackend::LocalProfile) const
Returns the entry with matching ID, from the specified backend.
QList< QgsHistoryEntry > queryEntries(const QDateTime &start=QDateTime(), const QDateTime &end=QDateTime(), const QString &providerId=QString(), Qgis::HistoryProviderBackends backends=Qgis::HistoryProviderBackend::LocalProfile) const
Queries history entries which occurred between the specified start and end times.
void entryUpdated(long long id, const QVariantMap &entry, Qgis::HistoryProviderBackend backend)
Emitted when an entry is updated.
void historyCleared(Qgis::HistoryProviderBackend backend, const QString &providerId)
Emitted when the history is cleared for a backend.
Contains settings which reflect the context in which a history widget is shown, e....