QGIS API Documentation 3.41.0-Master (45a0abf3bec)
Loading...
Searching...
No Matches
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 "moc_qgshistoryentrymodel.cpp"
18#include "qgshistoryentrynode.h"
20#include "qgsgui.h"
21#include "qgshistoryentry.h"
22#include "qgshistoryprovider.h"
23#include "qgsapplication.h"
24
25#include <QIcon>
26
28class QgsHistoryEntryRootNode : public QgsHistoryEntryGroup
29{
30 public:
31 QVariant data( int = Qt::DisplayRole ) const override;
32
33 void addEntryNode( const QgsHistoryEntry &entry, QgsHistoryEntryNode *node, QgsHistoryEntryModel *model );
34
39 static QString dateGroup( const QDateTime &timestamp, QString &sortKey );
40
41 QgsHistoryEntryDateGroupNode *dateNode( const QDateTime &timestamp, QgsHistoryEntryModel *model );
42
43 private:
44
45 QMap< QString, QgsHistoryEntryDateGroupNode * > mDateGroupNodes;
46
47};
48
49class QgsHistoryEntryDateGroupNode : public QgsHistoryEntryGroup
50{
51 public:
52
53 QgsHistoryEntryDateGroupNode( const QString &title, const QString &key )
54 : mTitle( title )
55 , mKey( key )
56 {
57
58 }
59
60 QVariant data( int role = Qt::DisplayRole ) const override
61 {
62 switch ( role )
63 {
64 case Qt::DisplayRole:
65 case Qt::ToolTipRole:
66 return mTitle;
67
68 case Qt::DecorationRole:
69 return QgsApplication::getThemeIcon( QStringLiteral( "mIconFolder.svg" ) );
70
71 default:
72 break;
73 }
74
75 return QVariant();
76 }
77
78 QString mTitle;
79 QString mKey;
80
81};
83
84QgsHistoryEntryModel::QgsHistoryEntryModel( const QString &providerId, Qgis::HistoryProviderBackends backends, QgsHistoryProviderRegistry *registry, const QgsHistoryWidgetContext &context, QObject *parent )
85 : QAbstractItemModel( parent )
86 , mContext( context )
87 , mRegistry( registry ? registry : QgsGui::historyProviderRegistry() )
88 , mProviderId( providerId )
89 , mBackends( backends )
90{
91 mRootNode = std::make_unique< QgsHistoryEntryRootNode>();
92
93 // populate with existing entries
94 const QList< QgsHistoryEntry > entries = mRegistry->queryEntries( QDateTime(), QDateTime(), mProviderId, mBackends );
95 for ( const QgsHistoryEntry &entry : entries )
96 {
97 QgsAbstractHistoryProvider *provider = mRegistry->providerById( entry.providerId );
98 if ( !provider )
99 continue;
100
101 if ( QgsHistoryEntryNode *node = provider->createNodeForEntry( entry, mContext ) )
102 {
103 mIdToNodeHash.insert( entry.id, node );
104 mRootNode->addEntryNode( entry, node, nullptr );
105 }
106 }
107
108 connect( mRegistry, &QgsHistoryProviderRegistry::entryAdded, this, &QgsHistoryEntryModel::entryAdded );
109 connect( mRegistry, &QgsHistoryProviderRegistry::entryUpdated, this, &QgsHistoryEntryModel::entryUpdated );
110 connect( mRegistry, &QgsHistoryProviderRegistry::historyCleared, this, &QgsHistoryEntryModel::historyCleared );
111}
112
117
118int QgsHistoryEntryModel::rowCount( const QModelIndex &parent ) const
119{
121 if ( !n )
122 return 0;
123
124 return n->childCount();
125}
126
127int QgsHistoryEntryModel::columnCount( const QModelIndex &parent ) const
128{
129 Q_UNUSED( parent )
130 return 1;
131}
132
133QModelIndex QgsHistoryEntryModel::index( int row, int column, const QModelIndex &parent ) const
134{
135 if ( column < 0 || column >= columnCount( parent ) ||
136 row < 0 || row >= rowCount( parent ) )
137 return QModelIndex();
138
139 QgsHistoryEntryGroup *n = dynamic_cast< QgsHistoryEntryGroup * >( index2node( parent ) );
140 if ( !n )
141 return QModelIndex(); // have no children
142
143 return createIndex( row, column, n->childAt( row ) );
144}
145
146QModelIndex QgsHistoryEntryModel::parent( const QModelIndex &child ) const
147{
148 if ( !child.isValid() )
149 return QModelIndex();
150
151 if ( QgsHistoryEntryNode *n = index2node( child ) )
152 {
153 return indexOfParentNode( n->parent() ); // must not be null
154 }
155 else
156 {
157 Q_ASSERT( false );
158 return QModelIndex();
159 }
160}
161
162QVariant QgsHistoryEntryModel::data( const QModelIndex &index, int role ) const
163{
164 if ( !index.isValid() || index.column() > 1 )
165 return QVariant();
166
168 if ( !node )
169 return QVariant();
170
171 return node->data( role );
172}
173
174Qt::ItemFlags QgsHistoryEntryModel::flags( const QModelIndex &index ) const
175{
176 if ( !index.isValid() )
177 {
178 Qt::ItemFlags rootFlags = Qt::ItemFlags();
179 return rootFlags;
180 }
181
182 Qt::ItemFlags f = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
183 return f;
184}
185
187{
188 if ( !index.isValid() )
189 return mRootNode.get();
190
191 return reinterpret_cast<QgsHistoryEntryNode *>( index.internalPointer() );
192
193}
194
195void QgsHistoryEntryModel::entryAdded( long long id, const QgsHistoryEntry &entry, Qgis::HistoryProviderBackend backend )
196{
197 // ignore entries we don't care about
198 if ( !( mBackends & backend ) )
199 return;
200 if ( !mProviderId.isEmpty() && entry.providerId != mProviderId )
201 return;
202
203 QgsAbstractHistoryProvider *provider = mRegistry->providerById( entry.providerId );
204 if ( !provider )
205 return;
206
207 if ( QgsHistoryEntryNode *node = provider->createNodeForEntry( entry, mContext ) )
208 {
209 mIdToNodeHash.insert( id, node );
210 mRootNode->addEntryNode( entry, node, this );
211 }
212}
213
214void QgsHistoryEntryModel::entryUpdated( long long id, const QVariantMap &entry, Qgis::HistoryProviderBackend backend )
215{
216 // ignore entries we don't care about
217 if ( !( mBackends & backend ) )
218 return;
219
220 // an update is a remove + reinsert operation
221 if ( QgsHistoryEntryNode *node = mIdToNodeHash.value( id ) )
222 {
223 bool ok = false;
224 QgsHistoryEntry historyEntry = mRegistry->entry( id, ok, backend );
225 historyEntry.entry = entry;
226 const QString providerId = historyEntry.providerId;
227 QgsAbstractHistoryProvider *provider = mRegistry->providerById( providerId );
228 if ( !provider )
229 return;
230
231 const QModelIndex nodeIndex = node2index( node );
232 const int existingChildRows = node->childCount();
233 provider->updateNodeForEntry( node, historyEntry, mContext );
234 const int newChildRows = node->childCount();
235
236 if ( newChildRows < existingChildRows )
237 {
238 beginRemoveRows( nodeIndex, newChildRows, existingChildRows - 1 );
239 endRemoveRows();
240 }
241 else if ( existingChildRows < newChildRows )
242 {
243 beginInsertRows( nodeIndex, existingChildRows, newChildRows - 1 );
244 endInsertRows();
245 }
246
247 const QModelIndex topLeft = index( 0, 0, nodeIndex );
248 const QModelIndex bottomRight = index( newChildRows - 1, columnCount() - 1, nodeIndex );
249 emit dataChanged( topLeft, bottomRight );
250 emit dataChanged( nodeIndex, nodeIndex );
251 }
252}
253
254void QgsHistoryEntryModel::historyCleared( Qgis::HistoryProviderBackend backend, const QString &providerId )
255{
256 // ignore entries we don't care about
257 if ( !( mBackends & backend ) )
258 return;
259
260 if ( !mProviderId.isEmpty() && !providerId.isEmpty() && providerId != mProviderId )
261 return;
262
263 beginResetModel();
264 mRootNode->clear();
265 mIdToNodeHash.clear();
266 endResetModel();
267}
268
269QModelIndex QgsHistoryEntryModel::node2index( QgsHistoryEntryNode *node ) const
270{
271 if ( !node || !node->parent() )
272 return QModelIndex(); // this is the only root item -> invalid index
273
274 QModelIndex parentIndex = node2index( node->parent() );
275
276 int row = node->parent()->indexOf( node );
277 Q_ASSERT( row >= 0 );
278 return index( row, 0, parentIndex );
279}
280
281QModelIndex QgsHistoryEntryModel::indexOfParentNode( QgsHistoryEntryNode *parentNode ) const
282{
283 Q_ASSERT( parentNode );
284
285 QgsHistoryEntryGroup *grandParentNode = parentNode->parent();
286 if ( !grandParentNode )
287 return QModelIndex(); // root node -> invalid index
288
289 int row = grandParentNode->indexOf( parentNode );
290 Q_ASSERT( row >= 0 );
291
292 return createIndex( row, 0, parentNode );
293}
294
295//
296// QgsHistoryEntryRootNode
297//
299QVariant QgsHistoryEntryRootNode::data( int ) const
300{
301 return QVariant();
302}
303
304void QgsHistoryEntryRootNode::addEntryNode( const QgsHistoryEntry &entry, QgsHistoryEntryNode *node, QgsHistoryEntryModel *model )
305{
306 QgsHistoryEntryDateGroupNode *targetDateNode = dateNode( entry.timestamp, model );
307
308 if ( model )
309 {
310 const QModelIndex dateNodeIndex = model->node2index( targetDateNode );
311 model->beginInsertRows( dateNodeIndex, 0, 0 );
312 }
313 targetDateNode->insertChild( 0, node );
314 if ( model )
315 {
316 model->endInsertRows();
317 }
318}
319
320QString QgsHistoryEntryRootNode::dateGroup( const QDateTime &timestamp, QString &sortKey )
321{
322 QString groupString;
323 if ( timestamp.date() == QDateTime::currentDateTime().date() )
324 {
325 groupString = QObject::tr( "Today" );
326 sortKey = QStringLiteral( "0" );
327 }
328 else
329 {
330 const qint64 intervalDays = timestamp.date().daysTo( QDateTime::currentDateTime().date() );
331 if ( intervalDays == 1 )
332 {
333 groupString = QObject::tr( "Yesterday" );
334 sortKey = QStringLiteral( "1" );
335 }
336 else if ( intervalDays < 8 )
337 {
338 groupString = QObject::tr( "Last 7 days" );
339 sortKey = QStringLiteral( "2" );
340 }
341 else
342 {
343 // a bit of trickiness here, we need dates ordered descending
344 sortKey = QStringLiteral( "3: %1 %2" ).arg( QDate::currentDate().year() - timestamp.date().year(), 5, 10, QLatin1Char( '0' ) )
345 .arg( 12 - timestamp.date().month(), 2, 10, QLatin1Char( '0' ) );
346 groupString = timestamp.toString( QStringLiteral( "MMMM yyyy" ) );
347 }
348 }
349 return groupString;
350}
351
352QgsHistoryEntryDateGroupNode *QgsHistoryEntryRootNode::dateNode( const QDateTime &timestamp, QgsHistoryEntryModel *model )
353{
354 QString dateGroupKey;
355 const QString dateTitle = dateGroup( timestamp, dateGroupKey );
356
357 QgsHistoryEntryDateGroupNode *node = mDateGroupNodes.value( dateGroupKey );
358 if ( !node )
359 {
360 node = new QgsHistoryEntryDateGroupNode( dateTitle, dateGroupKey );
361 mDateGroupNodes[ dateGroupKey ] = node;
362
363 int targetIndex = 0;
364 bool isInsert = false;
365 for ( const auto &child : mChildren )
366 {
367 if ( QgsHistoryEntryDateGroupNode *candidateNode = dynamic_cast< QgsHistoryEntryDateGroupNode * >( child.get() ) )
368 {
369 if ( candidateNode->mKey > dateGroupKey )
370 {
371 isInsert = true;
372 break;
373 }
374 }
375 targetIndex++;
376 }
377
378 if ( isInsert )
379 {
380 if ( model )
381 {
382 model->beginInsertRows( QModelIndex(), targetIndex, targetIndex );
383 }
384 insertChild( targetIndex, node );
385 if ( model )
386 {
387 model->endInsertRows();
388 }
389 }
390 else
391 {
392 if ( model )
393 {
394 model->beginInsertRows( QModelIndex(), childCount(), childCount() );
395 }
396 addChild( node );
397 if ( model )
398 {
399 model->endInsertRows();
400 }
401 }
402 }
403
404 return node;
405}
HistoryProviderBackend
History provider backends.
Definition qgis.h:3254
QFlags< HistoryProviderBackend > HistoryProviderBackends
Definition qgis.h:3259
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:64
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....