chore(ModelEntry): Emit itemChanged event when the ModelEntry points to another model item

+ emit row changed after data is avaialble

to squash - modelEntry
This commit is contained in:
Alex Jbanca 2024-07-15 10:45:28 +03:00 committed by Alex Jbanca
parent 357ba99495
commit 73bcacbfc0
4 changed files with 132 additions and 8 deletions

View File

@ -1,6 +1,8 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import QtQuick.Window 2.15
import QtQml.Models 2.15
import StatusQ 0.1 import StatusQ 0.1
import StatusQ.Core.Utils 0.1 import StatusQ.Core.Utils 0.1
@ -20,6 +22,32 @@ Control {
sourceModel: usersModel sourceModel: usersModel
key: "pubKey" key: "pubKey"
value: pubKeySelector.currentText 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 { 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 { Pane {
contentItem: RowLayout { contentItem: RowLayout {
ComboBox { ComboBox {
Layout.preferredWidth: 250
id: pubKeySelector id: pubKeySelector
model: [...ModelUtils.modelToFlatArray(usersModel, "pubKey"), "none"] model: [...ModelUtils.modelToFlatArray(usersModel, "pubKey"), "none"]
} }
@ -110,6 +157,10 @@ Control {
itemData.cacheOnRemoval = checked itemData.cacheOnRemoval = checked
} }
} }
CheckBox {
id: showSignals
text: "Show signals"
}
} }
} }
} }

View File

@ -4,6 +4,7 @@
#include <QPointer> #include <QPointer>
#include <QQmlEngine> #include <QQmlEngine>
#include <QQmlPropertyMap> #include <QQmlPropertyMap>
#include <QStringList>
class ModelEntry : public QObject class ModelEntry : public QObject
{ {
@ -60,6 +61,8 @@ protected:
void tryItemResetOrUpdate(); void tryItemResetOrUpdate();
void resetItem(); void resetItem();
void updateItem(const QList<int>& roles = {}); void updateItem(const QList<int>& roles = {});
QStringList fillItem(const QList<int>& roles = {});
void notifyItemChanges(const QStringList& roles);
QModelIndex findIndexInRange(int start, int end, const QList<int>& roles = {}) const; QModelIndex findIndexInRange(int start, int end, const QList<int>& roles = {}) const;
bool itemHasCorrectRoles() const; bool itemHasCorrectRoles() const;

View File

@ -105,7 +105,7 @@ void ModelEntry::setSourceModel(QAbstractItemModel* sourceModel)
setRow(-1); setRow(-1);
return; return;
} }
setAvailable(false);
setIndex({}); setIndex({});
}); });
connect(m_sourceModel, connect(m_sourceModel,
@ -168,8 +168,8 @@ void ModelEntry::setIndex(const QModelIndex& index)
m_index = index; m_index = index;
tryItemResetOrUpdate();
setRow(m_index.row()); setRow(m_index.row());
tryItemResetOrUpdate();
} }
void ModelEntry::setAvailable(bool available) void ModelEntry::setAvailable(bool available)
@ -265,7 +265,7 @@ void ModelEntry::resetItem()
m_item.reset(new QQmlPropertyMap()); m_item.reset(new QQmlPropertyMap());
updateItem(); fillItem();
if(!m_index.isValid()) if(!m_index.isValid())
{ {
@ -283,8 +283,17 @@ void ModelEntry::resetItem()
void ModelEntry::updateItem(const QList<int>& roles /*{}*/) void ModelEntry::updateItem(const QList<int>& roles /*{}*/)
{ {
if(!m_index.isValid() || !m_sourceModel) return; const auto updatedRoles = fillItem(roles);
notifyItemChanges(updatedRoles);
setItemRemovedFromModel(false);
}
QStringList ModelEntry::fillItem(const QList<int>& roles /*{}*/)
{
if(!m_index.isValid() || !m_sourceModel) return {};
QStringList filledRoles;
const auto& rolesRef = roles.isEmpty() ? m_sourceModel->roleNames().keys() : roles; const auto& rolesRef = roles.isEmpty() ? m_sourceModel->roleNames().keys() : roles;
for(auto role : rolesRef) for(auto role : rolesRef)
@ -294,10 +303,26 @@ void ModelEntry::updateItem(const QList<int>& roles /*{}*/)
if(roleValue == m_item->value(roleName)) continue; if(roleValue == m_item->value(roleName)) continue;
filledRoles.append(roleName);
m_item->insert(roleName, roleValue); 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 bool ModelEntry::itemHasCorrectRoles() const

View File

@ -1215,7 +1215,7 @@ private slots:
QCOMPARE(sourceModelChangedSpy.count(), 1); QCOMPARE(sourceModelChangedSpy.count(), 1);
QCOMPARE(keyChangedSpy.count(), 1); QCOMPARE(keyChangedSpy.count(), 1);
QCOMPARE(valueChangedSpy.count(), 2); QCOMPARE(valueChangedSpy.count(), 2);
QCOMPARE(itemChangedSpy.count(), 1); QCOMPARE(itemChangedSpy.count(), 2);
QCOMPARE(availableChangedSpy.count(), 1); QCOMPARE(availableChangedSpy.count(), 1);
QCOMPARE(rolesChangedSpy.count(), 1); QCOMPARE(rolesChangedSpy.count(), 1);
QCOMPARE(testObject->row(), 0); QCOMPARE(testObject->row(), 0);
@ -1444,6 +1444,10 @@ private slots:
QCOMPARE(testObject->item()->isEmpty(), false); QCOMPARE(testObject->item()->isEmpty(), false);
QCOMPARE(testObject->roles().size(), 2); QCOMPARE(testObject->roles().size(), 2);
QVERIFY(testObject->item()->value("key") != QVariant{}); 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]() { auto itemChangedConnection = connect(testObject, &ModelEntry::itemChanged, this, [this]() {
@ -1497,6 +1501,47 @@ private slots:
QCOMPARE(testObject->item()->value("color"), {}); 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<QAbstractItemModel*>(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() void itemObjectCleanupTest()
{ {
TestModel sourceModel1({{"key", {1, 2, 3}}, {"color", {"red", "blue", "green"}}}); TestModel sourceModel1({{"key", {1, 2, 3}}, {"color", {"red", "blue", "green"}}});