Skip to content

Commit a71671c

Browse files
committedApr 22, 2023
Create a model for QgsHistoryEntries
A dynamic tree item model showing the entries in date groupings
1 parent dee7605 commit a71671c

File tree

6 files changed

+757
-28
lines changed

6 files changed

+757
-28
lines changed
 
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/************************************************************************
2+
* This file has been generated automatically from *
3+
* *
4+
* src/gui/history/qgshistoryentrymodel.h *
5+
* *
6+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
7+
************************************************************************/
8+
9+
10+
11+
12+
13+
class QgsHistoryEntryModel : QAbstractItemModel
14+
{
15+
%Docstring(signature="appended")
16+
An item model representing history entries in a hierarchical tree structure.
17+
18+
.. versionadded:: 3.32
19+
%End
20+
21+
%TypeHeaderCode
22+
#include "qgshistoryentrymodel.h"
23+
%End
24+
public:
25+
26+
QgsHistoryEntryModel( const QString &providerId = QString(),
27+
Qgis::HistoryProviderBackends backends = Qgis::HistoryProviderBackend::LocalProfile,
28+
QgsHistoryProviderRegistry *registry = 0,
29+
QObject *parent /TransferThis/ = 0 );
30+
%Docstring
31+
Constructor for QgsHistoryEntryModel, with the specified ``parent`` object.
32+
33+
If ``providerId`` is specified then the model will contain only items from the matching
34+
history provider.
35+
If ``backends`` is specified then the model will be filtered to only matching backends.
36+
37+
If no ``registry`` is specified then the singleton :py:class:`QgsHistoryProviderRegistry` from :py:func:`QgsGui.historyProviderRegistry()`
38+
will be used.
39+
%End
40+
41+
~QgsHistoryEntryModel();
42+
43+
int rowCount( const QModelIndex &parent = QModelIndex() ) const final;
44+
int columnCount( const QModelIndex &parent = QModelIndex() ) const final;
45+
QModelIndex index( int row, int column, const QModelIndex &parent = QModelIndex() ) const final;
46+
QModelIndex parent( const QModelIndex &child ) const final;
47+
virtual QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const;
48+
49+
virtual Qt::ItemFlags flags( const QModelIndex &index ) const;
50+
51+
52+
QgsHistoryEntryNode *index2node( const QModelIndex &index ) const;
53+
%Docstring
54+
Returns node for given index. Returns root node for invalid index.
55+
%End
56+
57+
};
58+
59+
60+
61+
62+
/************************************************************************
63+
* This file has been generated automatically from *
64+
* *
65+
* src/gui/history/qgshistoryentrymodel.h *
66+
* *
67+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
68+
************************************************************************/

‎python/gui/gui_auto.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@
342342
%Include auto_generated/effects/qgspainteffectwidget.sip
343343
%Include auto_generated/elevation/qgselevationprofilecanvas.sip
344344
%Include auto_generated/history/qgshistoryentry.sip
345+
%Include auto_generated/history/qgshistoryentrymodel.sip
345346
%Include auto_generated/history/qgshistoryentrynode.sip
346347
%Include auto_generated/history/qgshistoryprovider.sip
347348
%Include auto_generated/history/qgshistoryproviderregistry.sip

‎src/gui/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ set(QGIS_GUI_SRCS
237237
elevation/qgselevationprofilelayertreeview.cpp
238238

239239
history/qgshistoryentry.cpp
240+
history/qgshistoryentrymodel.cpp
240241
history/qgshistoryentrynode.cpp
241242
history/qgshistoryprovider.cpp
242243
history/qgshistoryproviderregistry.cpp
@@ -1145,6 +1146,7 @@ set(QGIS_GUI_HDRS
11451146
elevation/qgselevationprofilelayertreeview.h
11461147

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

‎tests/src/python/test_qgshistoryproviderregistry.py

Lines changed: 192 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@
1111

1212
import qgis # NOQA switch sip api
1313
from qgis.PyQt import sip
14-
from qgis.PyQt.QtCore import QDate, QDateTime
14+
from qgis.PyQt.QtCore import (
15+
QDate,
16+
QDateTime,
17+
QTime,
18+
QModelIndex
19+
)
1520
from qgis.PyQt.QtTest import QSignalSpy
1621

1722
from qgis.core import Qgis
@@ -21,18 +26,40 @@
2126
QgsHistoryProviderRegistry,
2227
QgsGui,
2328
QgsHistoryEntryNode,
24-
QgsHistoryEntryGroup
29+
QgsHistoryEntryGroup,
30+
QgsHistoryEntryModel
2531
)
2632
from qgis.testing import start_app, unittest
2733

2834
start_app()
2935

3036

37+
class TestEntryNode(QgsHistoryEntryGroup):
38+
39+
def __init__(self, entry):
40+
super().__init__()
41+
self.entry = entry
42+
43+
def data(self, role):
44+
return self.entry
45+
46+
3147
class TestHistoryProvider(QgsAbstractHistoryProvider):
3248

3349
def id(self) -> str:
3450
return 'test_provider'
3551

52+
def createNodeForEntry(self, entry):
53+
return TestEntryNode(entry)
54+
55+
def updateNodeForEntry(self, node, entry):
56+
node.entry = entry
57+
58+
new_child_node = TestEntryNode('my child')
59+
node.addChild(new_child_node)
60+
new_child_node = TestEntryNode('my child 2')
61+
node.addChild(new_child_node)
62+
3663

3764
class TestHistoryProvider2(QgsAbstractHistoryProvider):
3865

@@ -66,20 +93,23 @@ def test_entry(self):
6693
entry = QgsHistoryEntry()
6794
self.assertFalse(entry.isValid())
6895

69-
entry = QgsHistoryEntry('my provider', QDateTime(2021, 1, 2, 3, 4, 5), {'somevar': 5})
96+
entry = QgsHistoryEntry('my provider', QDateTime(2021, 1, 2, 3, 4, 5),
97+
{'somevar': 5})
7098
self.assertTrue(entry.isValid())
7199

72100
self.assertEqual(entry.providerId, 'my provider')
73101
self.assertEqual(entry.timestamp, QDateTime(2021, 1, 2, 3, 4, 5))
74102
self.assertEqual(entry.entry, {'somevar': 5})
75103

76-
self.assertEqual(str(entry), '<QgsHistoryEntry: my provider 2021-01-02T03:04:05>')
104+
self.assertEqual(str(entry),
105+
'<QgsHistoryEntry: my provider 2021-01-02T03:04:05>')
77106

78107
entry = QgsHistoryEntry({'somevar': 7})
79108
self.assertTrue(entry.isValid())
80109

81110
self.assertFalse(entry.providerId)
82-
self.assertEqual(entry.timestamp.date(), QDateTime.currentDateTime().date())
111+
self.assertEqual(entry.timestamp.date(),
112+
QDateTime.currentDateTime().date())
83113
self.assertEqual(entry.entry, {'somevar': 7})
84114

85115
def test_registry_providers(self):
@@ -98,7 +128,8 @@ def test_registry_providers(self):
98128
self.assertFalse(reg.addProvider(provider1))
99129

100130
self.assertTrue(reg.addProvider(provider2))
101-
self.assertCountEqual(reg.providerIds(), ['test_provider', 'test_provider2'])
131+
self.assertCountEqual(reg.providerIds(),
132+
['test_provider', 'test_provider2'])
102133

103134
self.assertFalse(reg.removeProvider('x'))
104135
self.assertTrue(reg.removeProvider('test_provider'))
@@ -117,15 +148,19 @@ def test_registry_entries(self):
117148
added_spy = QSignalSpy(reg.entryAdded)
118149
updated_spy = QSignalSpy(reg.entryUpdated)
119150

120-
id_1, ok = reg.addEntry('my provider', {'some var': 5, 'other_var': [1, 2, 3], 'final_var': {'a': 'b'}},
151+
id_1, ok = reg.addEntry('my provider',
152+
{'some var': 5, 'other_var': [1, 2, 3],
153+
'final_var': {'a': 'b'}},
121154
QgsHistoryProviderRegistry.HistoryEntryOptions())
122155
self.assertTrue(ok)
123156

124157
self.assertEqual(len(added_spy), 1)
125158
self.assertEqual(added_spy[-1][0], id_1)
126159
self.assertEqual(added_spy[-1][1].providerId, 'my provider')
127160
self.assertEqual(added_spy[-1][1].id, id_1)
128-
self.assertEqual(added_spy[-1][1].entry, {'some var': 5, 'other_var': [1, 2, 3], 'final_var': {'a': 'b'}})
161+
self.assertEqual(added_spy[-1][1].entry,
162+
{'some var': 5, 'other_var': [1, 2, 3],
163+
'final_var': {'a': 'b'}})
129164

130165
id_2, ok = reg.addEntry('my provider 2', {'some var': 6},
131166
QgsHistoryProviderRegistry.HistoryEntryOptions())
@@ -139,12 +174,16 @@ def test_registry_entries(self):
139174
self.assertEqual(len(reg.queryEntries()), 2)
140175
self.assertEqual(reg.queryEntries()[0].providerId, 'my provider')
141176
self.assertEqual(reg.queryEntries()[0].id, id_1)
142-
self.assertEqual(reg.queryEntries()[0].timestamp.date(), QDateTime.currentDateTime().date())
143-
self.assertEqual(reg.queryEntries()[0].entry, {'some var': 5, 'other_var': [1, 2, 3], 'final_var': {'a': 'b'}})
177+
self.assertEqual(reg.queryEntries()[0].timestamp.date(),
178+
QDateTime.currentDateTime().date())
179+
self.assertEqual(reg.queryEntries()[0].entry,
180+
{'some var': 5, 'other_var': [1, 2, 3],
181+
'final_var': {'a': 'b'}})
144182

145183
self.assertEqual(reg.queryEntries()[1].providerId, 'my provider 2')
146184
self.assertEqual(reg.queryEntries()[1].id, id_2)
147-
self.assertEqual(reg.queryEntries()[1].timestamp.date(), QDateTime.currentDateTime().date())
185+
self.assertEqual(reg.queryEntries()[1].timestamp.date(),
186+
QDateTime.currentDateTime().date())
148187
self.assertEqual(reg.queryEntries()[1].entry, {'some var': 6})
149188

150189
entry, ok = reg.entry(1111)
@@ -153,16 +192,20 @@ def test_registry_entries(self):
153192
self.assertTrue(ok)
154193
self.assertEqual(entry.providerId, 'my provider')
155194
self.assertEqual(entry.id, id_1)
156-
self.assertEqual(entry.timestamp.date(), QDateTime.currentDateTime().date())
157-
self.assertEqual(entry.entry, {'some var': 5, 'other_var': [1, 2, 3], 'final_var': {'a': 'b'}})
195+
self.assertEqual(entry.timestamp.date(),
196+
QDateTime.currentDateTime().date())
197+
self.assertEqual(entry.entry, {'some var': 5, 'other_var': [1, 2, 3],
198+
'final_var': {'a': 'b'}})
158199
entry, ok = reg.entry(id_2)
159200
self.assertTrue(ok)
160201
self.assertEqual(entry.providerId, 'my provider 2')
161202
self.assertEqual(entry.id, id_2)
162-
self.assertEqual(entry.timestamp.date(), QDateTime.currentDateTime().date())
203+
self.assertEqual(entry.timestamp.date(),
204+
QDateTime.currentDateTime().date())
163205
self.assertEqual(entry.entry, {'some var': 6})
164206

165-
entry = QgsHistoryEntry('my provider 3', QDateTime(2021, 1, 2, 3, 4, 5), {'var': 7})
207+
entry = QgsHistoryEntry('my provider 3',
208+
QDateTime(2021, 1, 2, 3, 4, 5), {'var': 7})
166209
id_3, ok = reg.addEntry(entry)
167210
self.assertTrue(ok)
168211
self.assertEqual(len(added_spy), 3)
@@ -174,41 +217,52 @@ def test_registry_entries(self):
174217
self.assertEqual(len(reg.queryEntries()), 3)
175218
self.assertEqual(reg.queryEntries()[0].providerId, 'my provider')
176219
self.assertEqual(reg.queryEntries()[0].id, id_1)
177-
self.assertEqual(reg.queryEntries()[0].timestamp.date(), QDateTime.currentDateTime().date())
178-
self.assertEqual(reg.queryEntries()[0].entry, {'some var': 5, 'other_var': [1, 2, 3], 'final_var': {'a': 'b'}})
220+
self.assertEqual(reg.queryEntries()[0].timestamp.date(),
221+
QDateTime.currentDateTime().date())
222+
self.assertEqual(reg.queryEntries()[0].entry,
223+
{'some var': 5, 'other_var': [1, 2, 3],
224+
'final_var': {'a': 'b'}})
179225
self.assertEqual(reg.queryEntries()[1].providerId, 'my provider 2')
180226
self.assertEqual(reg.queryEntries()[1].id, id_2)
181-
self.assertEqual(reg.queryEntries()[1].timestamp.date(), QDateTime.currentDateTime().date())
227+
self.assertEqual(reg.queryEntries()[1].timestamp.date(),
228+
QDateTime.currentDateTime().date())
182229
self.assertEqual(reg.queryEntries()[1].entry, {'some var': 6})
183230
self.assertEqual(reg.queryEntries()[2].providerId, 'my provider 3')
184231
self.assertEqual(reg.queryEntries()[2].id, id_3)
185-
self.assertEqual(reg.queryEntries()[2].timestamp.date(), QDate(2021, 1, 2))
232+
self.assertEqual(reg.queryEntries()[2].timestamp.date(),
233+
QDate(2021, 1, 2))
186234
self.assertEqual(reg.queryEntries()[2].entry, {'var': 7})
187235

188236
# query by provider
189237
entries = reg.queryEntries(providerId='my provider')
190238
self.assertEqual(len(entries), 1)
191239
self.assertEqual(entries[0].providerId, 'my provider')
192240
self.assertEqual(entries[0].id, id_1)
193-
self.assertEqual(entries[0].timestamp.date(), QDateTime.currentDateTime().date())
194-
self.assertEqual(entries[0].entry, {'some var': 5, 'other_var': [1, 2, 3], 'final_var': {'a': 'b'}})
241+
self.assertEqual(entries[0].timestamp.date(),
242+
QDateTime.currentDateTime().date())
243+
self.assertEqual(entries[0].entry,
244+
{'some var': 5, 'other_var': [1, 2, 3],
245+
'final_var': {'a': 'b'}})
195246

196247
entries = reg.queryEntries(providerId='my provider 2')
197248
self.assertEqual(len(entries), 1)
198249
self.assertEqual(entries[0].providerId, 'my provider 2')
199250
self.assertEqual(entries[0].id, id_2)
200-
self.assertEqual(entries[0].timestamp.date(), QDateTime.currentDateTime().date())
251+
self.assertEqual(entries[0].timestamp.date(),
252+
QDateTime.currentDateTime().date())
201253
self.assertEqual(entries[0].entry, {'some var': 6})
202254

203255
# query by date
204-
entries = reg.queryEntries(start=QDateTime(3022, 1, 2, 3, 4, 5)) # this test will break in 3022, sorry
256+
entries = reg.queryEntries(start=QDateTime(3022, 1, 2, 3, 4,
257+
5)) # this test will break in 3022, sorry
205258
self.assertEqual(len(entries), 0)
206259
entries = reg.queryEntries(end=QDateTime(2020, 1, 2, 3, 4, 5))
207260
self.assertEqual(len(entries), 0)
208261

209262
entries = reg.queryEntries(start=QDateTime(2021, 3, 2, 3, 4, 5))
210263
self.assertEqual(len(entries), 2)
211-
self.assertCountEqual([e.providerId for e in entries], ['my provider', 'my provider 2'])
264+
self.assertCountEqual([e.providerId for e in entries],
265+
['my provider', 'my provider 2'])
212266
entries = reg.queryEntries(end=QDateTime(2021, 3, 2, 3, 4, 5))
213267
self.assertEqual(len(entries), 1)
214268
self.assertEqual(entries[0].providerId, 'my provider 3')
@@ -222,19 +276,23 @@ def test_registry_entries(self):
222276
entry, ok = reg.entry(id_1)
223277
self.assertEqual(entry.providerId, 'my provider')
224278
self.assertEqual(entry.id, id_1)
225-
self.assertEqual(entry.timestamp.date(), QDateTime.currentDateTime().date())
279+
self.assertEqual(entry.timestamp.date(),
280+
QDateTime.currentDateTime().date())
226281
self.assertEqual(entry.entry, {'new_props': 54})
227282

228283
clear_spy = QSignalSpy(reg.historyCleared)
229-
self.assertTrue(reg.clearHistory(Qgis.HistoryProviderBackend.LocalProfile))
284+
self.assertTrue(
285+
reg.clearHistory(Qgis.HistoryProviderBackend.LocalProfile))
230286
self.assertEqual(len(clear_spy), 1)
231287

232288
self.assertFalse(reg.queryEntries())
233289

234290
# bulk add entries
235291
self.assertTrue(reg.addEntries([
236-
QgsHistoryEntry('my provider 4', QDateTime(2021, 1, 2, 3, 4, 5), {'var': 7}),
237-
QgsHistoryEntry('my provider 5', QDateTime(2021, 1, 2, 3, 4, 5), {'var': 8})
292+
QgsHistoryEntry('my provider 4', QDateTime(2021, 1, 2, 3, 4, 5),
293+
{'var': 7}),
294+
QgsHistoryEntry('my provider 5', QDateTime(2021, 1, 2, 3, 4, 5),
295+
{'var': 8})
238296
]))
239297
self.assertEqual(len(reg.queryEntries()), 2)
240298

@@ -325,6 +383,112 @@ def test_nodes(self):
325383
group.clear()
326384
self.assertEqual(group.childCount(), 0)
327385

386+
def test_model(self):
387+
registry = QgsHistoryProviderRegistry()
388+
provider = TestHistoryProvider()
389+
registry.addProvider(provider)
390+
registry.addEntry(provider.id(), {'a': 1})
391+
registry.addEntry(provider.id(), {'a': 2})
392+
registry.addEntry(provider.id(), {'a': 3})
393+
394+
model = QgsHistoryEntryModel(provider.id(), registry=registry)
395+
# find Today group
396+
self.assertEqual(model.rowCount(), 1)
397+
date_group_1_index = model.index(0, 0, QModelIndex())
398+
self.assertEqual(model.data(date_group_1_index), 'Today')
399+
400+
self.assertEqual(model.rowCount(date_group_1_index), 3)
401+
entry_1_index = model.index(0, 0, date_group_1_index)
402+
self.assertEqual(model.data(entry_1_index), {'a': 1})
403+
entry_2_index = model.index(1, 0, date_group_1_index)
404+
self.assertEqual(model.data(entry_2_index), {'a': 2})
405+
entry_3_index = model.index(2, 0, date_group_1_index)
406+
self.assertEqual(model.data(entry_3_index), {'a': 3})
407+
408+
# an entry from yesterday
409+
yesterday = QDateTime.currentDateTime().addDays(-1)
410+
yesterday_entry = QgsHistoryEntry(provider.id(), yesterday, {'a': 4})
411+
registry.addEntry(yesterday_entry)
412+
413+
self.assertEqual(model.rowCount(), 2)
414+
yesterday_index = model.index(1, 0, QModelIndex())
415+
self.assertEqual(model.data(yesterday_index), 'Yesterday')
416+
417+
self.assertEqual(model.rowCount(yesterday_index), 1)
418+
entry_4_index = model.index(0, 0, yesterday_index)
419+
self.assertEqual(model.data(entry_4_index), {'a': 4})
420+
421+
# another entry from yesterday
422+
yesterday_entry2 = QgsHistoryEntry(provider.id(), yesterday, {'a': 5})
423+
registry.addEntry(yesterday_entry2)
424+
425+
self.assertEqual(model.data(yesterday_index), 'Yesterday')
426+
427+
self.assertEqual(model.rowCount(yesterday_index), 2)
428+
self.assertEqual(model.data(entry_4_index), {'a': 4})
429+
entry_5_index = model.index(1, 0, yesterday_index)
430+
self.assertEqual(model.data(entry_5_index), {'a': 5})
431+
432+
# an entry from an earlier month
433+
earlier_entry = QgsHistoryEntry(provider.id(), QDateTime(QDate(2020, 6, 3), QTime(12, 13, 14)), {'a': 6})
434+
earlier_entry_id, _ = registry.addEntry(earlier_entry)
435+
436+
self.assertEqual(model.rowCount(), 3)
437+
june2020_index = model.index(2, 0, QModelIndex())
438+
self.assertEqual(model.data(date_group_1_index), 'Today')
439+
self.assertEqual(model.data(yesterday_index), 'Yesterday')
440+
self.assertEqual(model.data(june2020_index), 'June 2020')
441+
442+
self.assertEqual(model.rowCount(june2020_index), 1)
443+
entry_6_index = model.index(0, 0, june2020_index)
444+
self.assertEqual(model.data(entry_6_index), {'a': 6})
445+
446+
# an entry from an earlier month which is later than the previous one
447+
earlier_entry2 = QgsHistoryEntry(provider.id(), QDateTime(QDate(2020, 10, 3), QTime(12, 13, 14)), {'a': 7})
448+
registry.addEntry(earlier_entry2)
449+
450+
self.assertEqual(model.rowCount(), 4)
451+
october2020_index = model.index(2, 0, QModelIndex())
452+
self.assertEqual(model.data(date_group_1_index), 'Today')
453+
self.assertEqual(model.data(yesterday_index), 'Yesterday')
454+
self.assertEqual(model.data(june2020_index), 'June 2020')
455+
self.assertEqual(model.data(october2020_index), 'October 2020')
456+
457+
self.assertEqual(model.rowCount(october2020_index), 1)
458+
entry_7_index = model.index(0, 0, october2020_index)
459+
self.assertEqual(model.data(entry_7_index), {'a': 7})
460+
461+
# an entry from last week
462+
last_week = QDateTime.currentDateTime().addDays(-7)
463+
last_week_entry = QgsHistoryEntry(provider.id(), last_week, {'a': 'last_week'})
464+
registry.addEntry(last_week_entry)
465+
466+
self.assertEqual(model.rowCount(), 5)
467+
last_week_index = model.index(2, 0, QModelIndex())
468+
self.assertEqual(model.data(date_group_1_index), 'Today')
469+
self.assertEqual(model.data(yesterday_index), 'Yesterday')
470+
self.assertEqual(model.data(june2020_index), 'June 2020')
471+
self.assertEqual(model.data(october2020_index), 'October 2020')
472+
self.assertEqual(model.data(last_week_index), 'Last 7 days')
473+
474+
self.assertEqual(model.rowCount(last_week_index), 1)
475+
entry_last_week_index = model.index(0, 0, last_week_index)
476+
self.assertEqual(model.data(entry_last_week_index), {'a': 'last_week'})
477+
478+
# update an entry
479+
registry.updateEntry(earlier_entry_id, {'a': 8})
480+
entry_6_index = model.index(0, 0, june2020_index)
481+
self.assertEqual(model.data(entry_6_index), {'a': 8})
482+
self.assertEqual(model.rowCount(entry_6_index), 2)
483+
entry_6a_index = model.index(0, 0, entry_6_index)
484+
self.assertEqual(model.data(entry_6a_index), 'my child')
485+
entry_6b_index = model.index(1, 0, entry_6_index)
486+
self.assertEqual(model.data(entry_6b_index), 'my child 2')
487+
488+
# clear
489+
registry.clearHistory(Qgis.HistoryProviderBackend.LocalProfile)
490+
self.assertEqual(model.rowCount(), 0)
491+
328492

329493
if __name__ == '__main__':
330494
unittest.main()

0 commit comments

Comments
 (0)
Please sign in to comment.