feat(WritableProxyModel): Add syncedRemoval flag to allow source to remove edited rows

This commit is contained in:
Alex Jbanca 2024-03-10 22:14:51 +02:00 committed by Alex Jbanca
parent 67b81e1953
commit 204bfb30b6
3 changed files with 103 additions and 3 deletions

View File

@ -26,6 +26,9 @@ class WritableProxyModel : public QAbstractProxyModel
Q_OBJECT
Q_PROPERTY(bool dirty READ dirty NOTIFY dirtyChanged)
//If true, the removals are synced with the source model. Removing an edited item from source will also remove it from proxy
//If false, eemoving an edited item from source will not remove it from proxy. It will become a newly inserted row
Q_PROPERTY(bool syncedRemovals READ syncedRemovals WRITE setSyncedRemovals NOTIFY syncedRemovalsChanged)
public:
explicit WritableProxyModel(QObject* parent = nullptr);
@ -48,6 +51,7 @@ public:
Q_INVOKABLE bool set(int at, const QVariantMap& data);
bool dirty() const;
bool syncedRemovals() const;
//QAbstractProxyModel overrides
void setSourceModel(QAbstractItemModel* sourceModel) override;
@ -75,9 +79,11 @@ public:
signals:
void dirtyChanged();
void syncedRemovalsChanged();
private:
void setDirty(bool flag);
void setSyncedRemovals(bool syncedRemovals);
void onSourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles);
void onRowsAboutToBeInserted(const QModelIndex& parent, int start, int end);

View File

@ -4,6 +4,7 @@
#include <QAbstractItemModelTester>
#endif
#include <memory>
#include <QDebug>
template <typename T>
@ -23,6 +24,8 @@ public:
QSet<QPersistentModelIndex> removedRows;
QVector<int> proxyToSourceRowMapping;
bool dirty{false};
bool syncedRemovals{false};
bool syncedRemovalsInitialized{false};
void setData(const QModelIndex& index, const QVariant& value, int role);
template<typename T>
@ -244,11 +247,14 @@ int WritableProxyModelPrivate::countOffset() const
void WritableProxyModelPrivate::moveFromCacheToInserted(const QModelIndex& sourceIndex)
{
if (!q.sourceModel())
if (!q.sourceModel() || syncedRemovals)
return;
//User updated this row. Move it in inserted rows. We shouldn't delete it
auto proxyIndex = insertedRows.insert(q.mapFromSource(sourceIndex), cache.take(sourceIndex));
// syncedRemovalsInitialized cannot be changed after this point
syncedRemovalsInitialized = true;
auto itemData = q.sourceModel()->itemData(sourceIndex);
for (auto it = itemData.begin(); it != itemData.end(); ++it)
{
@ -283,7 +289,7 @@ void WritableProxyModelPrivate::checkForDirtyRemoval(const QModelIndex& sourceIn
{
if (cachedData.contains(role) && cachedData[role] == q.sourceModel()->data(sourceIndex, role))
cachedData.remove(role);
}
}
if (cachedData.isEmpty()) {
cache.remove(sourceIndex);
@ -625,6 +631,27 @@ void WritableProxyModel::setDirty(bool flag)
emit dirtyChanged();
}
bool WritableProxyModel::syncedRemovals() const
{
return d->syncedRemovals;
}
void WritableProxyModel::setSyncedRemovals(bool syncedRemovals)
{
if (d->syncedRemovalsInitialized)
{
qWarning() << "WritableProxyModel: syncedRemovals cannot be updated after it has been initialized";
return;
}
if (syncedRemovals == d->syncedRemovals)
return;
d->syncedRemovals = syncedRemovals;
d->syncedRemovalsInitialized = true;
emit syncedRemovalsChanged();
}
void WritableProxyModel::setSourceModel(QAbstractItemModel* sourceModel)
{
if (sourceModel == QAbstractProxyModel::sourceModel())
@ -992,6 +1019,12 @@ void WritableProxyModel::onRowsRemoved(const QModelIndex& parent, int first, int
void WritableProxyModel::onModelAboutToBeReset()
{
beginResetModel();
if (d->syncedRemovals)
{
d->clear();
return;
}
for (auto iter = d->cache.begin(); iter != d->cache.end();)
{
auto key = iter.key();
@ -1006,6 +1039,7 @@ void WritableProxyModel::onModelReset()
d->clearInvalidatedCache();
d->createProxyToSourceRowMap();
resetInternalData();
d->checkForDirtyRemoval({}, {});
endResetModel();
}

View File

@ -732,6 +732,66 @@ private slots:
QCOMPARE(model.data(model.index(3, 0), 1), {});
}
void updaedDataIsNotKeptAfterSourceRemove()
{
WritableProxyModel model;
QAbstractItemModelTester tester(&model);
TestSourceModel sourceModel({
{ "title", { "Token 1", "Token 2", "Token3" }},
{ "communityId", { "community_1", "community_2", "community_3" }}});
model.setSourceModel(&sourceModel);
model.setProperty("syncedRemovals", true);
QSignalSpy rowsRemovedSpy(&model, &WritableProxyModel::rowsRemoved);
QSignalSpy modelResetSpy(&model, &WritableProxyModel::modelReset);
QSignalSpy dataChangedSpy(&model, &WritableProxyModel::dataChanged);
QSignalSpy rowsInsertedSpy(&model, &WritableProxyModel::rowsInserted);
QCOMPARE(model.dirty(), false);
QCOMPARE(model.syncedRemovals(), true);
model.setData(model.index(0, 0), "Token 1.1", 0);
QCOMPARE(model.dirty(), true);
QCOMPARE(model.rowCount(), 3);
QCOMPARE(model.data(model.index(0, 0), 0), "Token 1.1");
QCOMPARE(dataChangedSpy.count(), 1);
sourceModel.remove(0);
QCOMPARE(model.dirty(), false);
QCOMPARE(model.rowCount(), 2);
QCOMPARE(model.data(model.index(0, 0), 0), "Token 2");
QCOMPARE(rowsRemovedSpy.count(), 1);
QCOMPARE(rowsRemovedSpy.first().at(1), 0);
QCOMPARE(rowsRemovedSpy.first().at(2), 0);
QCOMPARE(modelResetSpy.count(), 0);
QCOMPARE(rowsInsertedSpy.count(), 0);
model.setData(model.index(0, 0), "Token 2.1", 0);
QCOMPARE(model.dirty(), true);
QCOMPARE(model.data(model.index(0, 0), 0), "Token 2.1");
sourceModel.reset({
{ "title", { "Token 3", "Token 4" }},
{ "communityId", { "community_3", "community_4" }}
});
QCOMPARE(model.dirty(), false);
QCOMPARE(model.rowCount(), 2);
QCOMPARE(rowsRemovedSpy.count(), 1);
QCOMPARE(modelResetSpy.count(), 1);
QCOMPARE(dataChangedSpy.count(), 2);
QCOMPARE(rowsInsertedSpy.count(), 0);
}
void dataIsAccessibleAfterSourceModelMove()
{
WritableProxyModel model;