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