From 6b334a40855b37e293b8851429e43a40e18bdbae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Cie=C5=9Blak?= Date: Wed, 13 Nov 2024 16:47:50 +0100 Subject: [PATCH] StatusQ: ModelEntryChangeTracker component introduced --- .../tests/tst_ModelEntryChangeTracker.qml | 100 ++++++++++++++++++ .../Core/Utils/ModelEntryChangeTracker.qml | 77 ++++++++++++++ ui/StatusQ/src/StatusQ/Core/Utils/qmldir | 1 + ui/StatusQ/src/statusq.qrc | 1 + 4 files changed, 179 insertions(+) create mode 100644 storybook/qmlTests/tests/tst_ModelEntryChangeTracker.qml create mode 100644 ui/StatusQ/src/StatusQ/Core/Utils/ModelEntryChangeTracker.qml diff --git a/storybook/qmlTests/tests/tst_ModelEntryChangeTracker.qml b/storybook/qmlTests/tests/tst_ModelEntryChangeTracker.qml new file mode 100644 index 0000000000..8b93d05b30 --- /dev/null +++ b/storybook/qmlTests/tests/tst_ModelEntryChangeTracker.qml @@ -0,0 +1,100 @@ +import QtQuick 2.15 +import QtTest 1.15 + +import StatusQ.Core.Utils 0.1 + +Item { + id: root + + width: 600 + height: 400 + + Component { + id: componentUnderTest + + ModelEntryChangeTracker { + id: tracker + + model: ListModel { + ListElement { key: "a"; name: "AA" } + ListElement { key: "b"; name: "BB" } + ListElement { key: "c"; name: "CC" } + } + + role: "key" + key: "b" + + readonly property SignalSpy itemChangedSpy: SignalSpy { + target: tracker + signalName: "itemChanged" + } + } + } + + TestCase { + name: "ModelEntryChangeTracker" + + function test_change() { + const tracker = createTemporaryObject(componentUnderTest, this) + compare(tracker.itemChangedSpy.count, 0) + + tracker.model.setProperty(0, "name", "AAA") + compare(tracker.itemChangedSpy.count, 0) + compare(tracker.revision, 0) + + tracker.model.setProperty(1, "name", "BBB") + compare(tracker.itemChangedSpy.count, 1) + compare(tracker.revision, 1) + } + + function test_insertion() { + const tracker = createTemporaryObject(componentUnderTest, this, + { key: "d" }) + compare(tracker.itemChangedSpy.count, 0) + + tracker.model.setProperty(0, "name", "AAA") + tracker.model.setProperty(1, "name", "BBB") + compare(tracker.itemChangedSpy.count, 0) + compare(tracker.revision, 0) + + tracker.model.insert(1, { "key": "d", name: "DD" }) + compare(tracker.itemChangedSpy.count, 0) + compare(tracker.revision, 0) + + tracker.model.setProperty(1, "name", "DDD") + compare(tracker.itemChangedSpy.count, 1) + compare(tracker.revision, 1) + } + + function test_reinitOnRemoval() { + const tracker = createTemporaryObject(componentUnderTest, this) + compare(tracker.itemChangedSpy.count, 0) + + tracker.model.append({ "key": "b", name: "BB2" }) + compare(tracker.itemChangedSpy.count, 0) + compare(tracker.revision, 0) + + tracker.model.setProperty(3, "name", "BBB2") + compare(tracker.itemChangedSpy.count, 0) + compare(tracker.revision, 0) + + tracker.model.remove(1) + compare(tracker.itemChangedSpy.count, 0) + compare(tracker.revision, 0) + + tracker.model.setProperty(2, "name", "BBBB2") + compare(tracker.itemChangedSpy.count, 1) + compare(tracker.revision, 1) + } + + function test_modelChanged() { + const tracker = createTemporaryObject(componentUnderTest, this) + + const model = tracker.model + tracker.model = null + + model.setProperty(1, "name", "BBB") + compare(tracker.itemChangedSpy.count, 0) + } + } +} diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/ModelEntryChangeTracker.qml b/ui/StatusQ/src/StatusQ/Core/Utils/ModelEntryChangeTracker.qml new file mode 100644 index 0000000000..e401ef2828 --- /dev/null +++ b/ui/StatusQ/src/StatusQ/Core/Utils/ModelEntryChangeTracker.qml @@ -0,0 +1,77 @@ +import QtQml 2.15 + +QObject { + id: root + + property var model + property string role + property var key + + readonly property alias revision: d.revision + + signal itemChanged + + onModelChanged: d.initPersistentIndex() + onRoleChanged: d.initPersistentIndex() + onKeyChanged: d.initPersistentIndex() + + QtObject { + id: d + + property var persistentIndex + property int revision: 0 + + function initPersistentIndex() { + d.persistentIndex = null + + if (!model) + return + + const idx = ModelUtils.indexOf(root.model, root.role, root.key) + + if (idx === -1) + return + + d.persistentIndex = ModelUtils.persistentIndex(root.model, idx) + } + + Component.onCompleted: initPersistentIndex() + } + + Connections { + target: root.model ?? null + + function onDataChanged(topLeft, bottomRight) { + if (!d.persistentIndex || !d.persistentIndex.valid) + return + + const row = d.persistentIndex.row + + if (topLeft.row >= row && bottomRight.row <= row) { + d.revision++ + root.itemChanged() + } + } + + function onRowsInserted() { + if (d.persistentIndex && d.valid) + return + + d.initPersistentIndex() + } + + function onRowsRemoved() { + if (!!d.persistentIndex && !d.valid) + d.initPersistentIndex() + } + + function onLayoutChanged() { + if (!!d.persistentIndex || !d.valid) + d.initPersistentIndex() + } + + function onModelReset() { + d.initPersistentIndex() + } + } +} diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/qmldir b/ui/StatusQ/src/StatusQ/Core/Utils/qmldir index e6b99df069..f98c58d52d 100644 --- a/ui/StatusQ/src/StatusQ/Core/Utils/qmldir +++ b/ui/StatusQ/src/StatusQ/Core/Utils/qmldir @@ -8,6 +8,7 @@ JSONListModel 0.1 JSONListModel.qml LazyStackLayout 0.1 LazyStackLayout.qml ModelChangeGuard 0.1 ModelChangeGuard.qml ModelChangeTracker 0.1 ModelChangeTracker.qml +ModelEntryChangeTracker 0.1 ModelEntryChangeTracker.qml ModelsComparator 0.1 ModelsComparator.qml QObject 0.1 QObject.qml SearchFilter 0.1 SearchFilter.qml diff --git a/ui/StatusQ/src/statusq.qrc b/ui/StatusQ/src/statusq.qrc index b8adcf5354..8c776ba514 100644 --- a/ui/StatusQ/src/statusq.qrc +++ b/ui/StatusQ/src/statusq.qrc @@ -199,6 +199,7 @@ StatusQ/Core/Utils/LazyStackLayout.qml StatusQ/Core/Utils/ModelChangeGuard.qml StatusQ/Core/Utils/ModelChangeTracker.qml + StatusQ/Core/Utils/ModelEntryChangeTracker.qml StatusQ/Core/Utils/ModelUtils.qml StatusQ/Core/Utils/ModelsComparator.qml StatusQ/Core/Utils/OperatorsUtils.qml