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.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"
}
}
}
}

View File

@ -4,6 +4,7 @@
#include <QPointer>
#include <QQmlEngine>
#include <QQmlPropertyMap>
#include <QStringList>
class ModelEntry : public QObject
{
@ -60,6 +61,8 @@ protected:
void tryItemResetOrUpdate();
void resetItem();
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;
bool itemHasCorrectRoles() const;

View File

@ -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());
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<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;
for(auto role : rolesRef)
@ -294,10 +303,26 @@ void ModelEntry::updateItem(const QList<int>& 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

View File

@ -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<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()
{
TestModel sourceModel1({{"key", {1, 2, 3}}, {"color", {"red", "blue", "green"}}});