diff --git a/storybook/pages/ModelEntryPage.qml b/storybook/pages/ModelEntryPage.qml index ab850bcbda..f168246ea8 100644 --- a/storybook/pages/ModelEntryPage.qml +++ b/storybook/pages/ModelEntryPage.qml @@ -1,6 +1,8 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQml.Models 2.15 import StatusQ 0.1 import StatusQ.Core.Utils 0.1 @@ -20,6 +22,32 @@ Control { sourceModel: usersModel key: "pubKey" value: pubKeySelector.currentText + + onItemChanged: signalsModel.append({ signal: "Item changed", value: "", row: itemData.row, item: itemData.item, roles: JSON.stringify(itemData.roles), available: itemData.available}) + onRowChanged: signalsModel.append({ signal: "Row changed", value: "", row: itemData.row, item: itemData.item, roles: JSON.stringify(itemData.roles), available: itemData.available}) + onAvailableChanged: signalsModel.append({ signal: "Available changed",value: "", row: itemData.row, item: itemData.item, roles: JSON.stringify(itemData.roles), available: itemData.available}) + onRolesChanged: signalsModel.append({ signal: "Roles changed", value: "", row: itemData.row, item: itemData.item, roles: JSON.stringify(itemData.roles), available: itemData.available}) + } + + Instantiator { + model: itemData.roles + delegate: QtObject { + property var connection: { + return Qt.createQmlObject(` + import QtQml 2.15 + Connections { + target: itemData.item + function on${modelData.charAt(0).toUpperCase() + modelData.slice(1)}Changed() { + signalsModel.append({ signal: "${modelData} changed", value: itemData.item.${modelData}, row: itemData.row, item: itemData.item, roles: JSON.stringify(itemData.roles), available: itemData.available}) + } + } + `, this, "dynamicConnectionOn${modelData}") + } + } + } + + ListModel { + id: signalsModel } contentItem: ColumnLayout { @@ -97,9 +125,28 @@ Control { } } } + GenericListView { + Layout.fillWidth: true + Layout.fillHeight: true + visible: showSignals.checked && !!count + model: signalsModel + header: Label { + width: parent.width + text: "Item Signals" + font.bold: true + font.pixelSize: 16 + bottomPadding: 20 + Button { + anchors.right: parent.right + text: "clear" + onClicked: signalsModel.clear() + } + } + } Pane { contentItem: RowLayout { ComboBox { + Layout.preferredWidth: 250 id: pubKeySelector model: [...ModelUtils.modelToFlatArray(usersModel, "pubKey"), "none"] } @@ -110,6 +157,10 @@ Control { itemData.cacheOnRemoval = checked } } + CheckBox { + id: showSignals + text: "Show signals" + } } } } diff --git a/ui/StatusQ/include/StatusQ/modelentry.h b/ui/StatusQ/include/StatusQ/modelentry.h index 357dc0e7c8..2fd8a7ffee 100644 --- a/ui/StatusQ/include/StatusQ/modelentry.h +++ b/ui/StatusQ/include/StatusQ/modelentry.h @@ -4,6 +4,7 @@ #include #include #include +#include class ModelEntry : public QObject { @@ -60,6 +61,8 @@ protected: void tryItemResetOrUpdate(); void resetItem(); void updateItem(const QList& roles = {}); + QStringList fillItem(const QList& roles = {}); + void notifyItemChanges(const QStringList& roles); QModelIndex findIndexInRange(int start, int end, const QList& roles = {}) const; bool itemHasCorrectRoles() const; diff --git a/ui/StatusQ/src/modelentry.cpp b/ui/StatusQ/src/modelentry.cpp index d1b8f36cf7..5c8c7b2aaa 100644 --- a/ui/StatusQ/src/modelentry.cpp +++ b/ui/StatusQ/src/modelentry.cpp @@ -105,7 +105,7 @@ void ModelEntry::setSourceModel(QAbstractItemModel* sourceModel) setRow(-1); return; } - + setAvailable(false); setIndex({}); }); connect(m_sourceModel, @@ -168,8 +168,8 @@ void ModelEntry::setIndex(const QModelIndex& index) m_index = index; - tryItemResetOrUpdate(); - setRow(m_index.row()); + setRow(m_index.row()); + tryItemResetOrUpdate(); } void ModelEntry::setAvailable(bool available) @@ -265,7 +265,7 @@ void ModelEntry::resetItem() m_item.reset(new QQmlPropertyMap()); - updateItem(); + fillItem(); if(!m_index.isValid()) { @@ -283,8 +283,17 @@ void ModelEntry::resetItem() void ModelEntry::updateItem(const QList& roles /*{}*/) { - if(!m_index.isValid() || !m_sourceModel) return; + const auto updatedRoles = fillItem(roles); + notifyItemChanges(updatedRoles); + setItemRemovedFromModel(false); +} + +QStringList ModelEntry::fillItem(const QList& roles /*{}*/) +{ + if(!m_index.isValid() || !m_sourceModel) return {}; + + QStringList filledRoles; const auto& rolesRef = roles.isEmpty() ? m_sourceModel->roleNames().keys() : roles; for(auto role : rolesRef) @@ -294,10 +303,26 @@ void ModelEntry::updateItem(const QList& roles /*{}*/) if(roleValue == m_item->value(roleName)) continue; + filledRoles.append(roleName); m_item->insert(roleName, roleValue); - emit m_item->valueChanged(roleName, roleValue); } - setItemRemovedFromModel(false); + + return filledRoles; +} + +void ModelEntry::notifyItemChanges(const QStringList& roles) +{ + if (roles.contains(m_key)) + { + emit itemChanged(); + return; + } + + for(auto role : roles) + { + auto value = m_item->value(role); + emit m_item->valueChanged(role, value); + } } bool ModelEntry::itemHasCorrectRoles() const diff --git a/ui/StatusQ/tests/tst_ModelEntry.cpp b/ui/StatusQ/tests/tst_ModelEntry.cpp index 5be98bf89e..50f8ef0bfd 100644 --- a/ui/StatusQ/tests/tst_ModelEntry.cpp +++ b/ui/StatusQ/tests/tst_ModelEntry.cpp @@ -1215,7 +1215,7 @@ private slots: QCOMPARE(sourceModelChangedSpy.count(), 1); QCOMPARE(keyChangedSpy.count(), 1); QCOMPARE(valueChangedSpy.count(), 2); - QCOMPARE(itemChangedSpy.count(), 1); + QCOMPARE(itemChangedSpy.count(), 2); QCOMPARE(availableChangedSpy.count(), 1); QCOMPARE(rolesChangedSpy.count(), 1); QCOMPARE(testObject->row(), 0); @@ -1444,6 +1444,10 @@ private slots: QCOMPARE(testObject->item()->isEmpty(), false); QCOMPARE(testObject->roles().size(), 2); QVERIFY(testObject->item()->value("key") != QVariant{}); + if(testObject->available()) + QCOMPARE(testObject->row(), 0); + else + QCOMPARE(testObject->row(), -1); }); auto itemChangedConnection = connect(testObject, &ModelEntry::itemChanged, this, [this]() { @@ -1497,6 +1501,47 @@ private slots: QCOMPARE(testObject->item()->value("color"), {}); } + void itemSignalsTest() + { + // Testing the signals of the item object + // Expected: + // 1. changes in model role values produce valueChanged signals only for the roles that changed + // 2. changes in the model role used for filtering should produce itemChanged signals and no valueChanged signals + + QQmlEngine engine; + ListModelWrapper sourceModel( + engine, QJsonArray{QJsonObject{{"key", 1}, {"color", "red"}, {"size", "small"}}, QJsonObject{{"key", 2}, {"color", "blue"}, {"size", "medium"}}}); + + QSignalSpy itemChangedSpy(testObject, &ModelEntry::itemChanged); + + // setting the initial source model + QCOMPARE(sourceModelProperty.write(testObject, QVariant::fromValue(sourceModel.model())), + true); + // setting the filter + QCOMPARE(keyProperty.write(testObject, "key"), true); + QCOMPARE(valueProperty.write(testObject, 1), true); + + QCOMPARE(itemChangedSpy.count(), 1); + + QSignalSpy valueChangedSpy(testObject->item(), &QQmlPropertyMap::valueChanged); + + // change the value of the item + sourceModel.setProperty(0, "color", "yellow"); + QCOMPARE(valueChangedSpy.count(), 1); + QCOMPARE(valueChangedSpy.at(0).at(0).toString(), "color"); + QCOMPARE(valueChangedSpy.at(0).at(1).toString(), "yellow"); + + sourceModel.setProperty(0, "size", "large"); + QCOMPARE(valueChangedSpy.count(), 2); + QCOMPARE(valueChangedSpy.at(1).at(0).toString(), "size"); + QCOMPARE(valueChangedSpy.at(1).at(1).toString(), "large"); + + // change the filter to the second item + QCOMPARE(valueProperty.write(testObject, 2), true); + QCOMPARE(itemChangedSpy.count(), 2); + QCOMPARE(valueChangedSpy.count(), 2); + } + void itemObjectCleanupTest() { TestModel sourceModel1({{"key", {1, 2, 3}}, {"color", {"red", "blue", "green"}}});