StatusQ: Adding WritableProxyModel
WritableProxyModel is a QAbstractProxyModel that allows you to modify the data without modifying the source model. It is useful for implementing a "dirty" state for a model, where you can modify the data and then commit the changes to the source model. Supported features (reimplemented): - setData - setItemData - removeRows - insertRows - moveRows (TODO) - toVariantMap - to be continued...
This commit is contained in:
parent
19a6ba915c
commit
6da897e733
|
@ -0,0 +1,201 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import StatusQ 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
|
||||
Item {
|
||||
id: root
|
||||
ListModel {
|
||||
id: listModel
|
||||
Component.onCompleted: {
|
||||
for (var i = 1; i < 1000; ++i) {
|
||||
listModel.append({
|
||||
name: "Item " + i,
|
||||
key: i
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: listModel2
|
||||
Component.onCompleted: {
|
||||
for (var i = 10; i < 50; ++i) {
|
||||
listModel2.append({
|
||||
name: "Item " + i,
|
||||
key: i
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
ColumnLayout {
|
||||
Layout.maximumWidth: root.width / 2
|
||||
Layout.fillHeight: true
|
||||
ListView {
|
||||
id: listView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
model: listModel
|
||||
clip: true
|
||||
delegate: DropArea {
|
||||
height: draggableDelegate.implicitHeight
|
||||
width: draggableDelegate.implicitWidth
|
||||
onEntered: function(drag) {
|
||||
const from = drag.source.visualIndex
|
||||
const to = draggableDelegate.visualIndex
|
||||
if (to === from)
|
||||
return
|
||||
listModel.move(from, to, 1)
|
||||
drag.accept()
|
||||
}
|
||||
StatusDraggableListItem {
|
||||
id: draggableDelegate
|
||||
implicitWidth: 300
|
||||
dragParent: root
|
||||
visualIndex: index
|
||||
draggable: listView.count > 1
|
||||
text: name
|
||||
secondaryTitle: "Key: " + key
|
||||
onClicked: {
|
||||
listModel.setProperty(index, "name", listModel.get(index).name + "!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 100
|
||||
|
||||
RowLayout{
|
||||
Button {
|
||||
text: "Insert"
|
||||
onClicked: {
|
||||
listModel.insert(parseInt(insertIndex.text),{
|
||||
name: "Item " + (listModel.count + 1),
|
||||
key: listModel.count + 1
|
||||
})
|
||||
}
|
||||
}
|
||||
TextField {
|
||||
id: insertIndex
|
||||
text: "Insert at"
|
||||
cursorVisible: false
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
}
|
||||
}
|
||||
RowLayout{
|
||||
Button {
|
||||
text: "Remove"
|
||||
onClicked: {
|
||||
listModel.remove(parseInt(removeIndex.text), 1)
|
||||
}
|
||||
}
|
||||
TextField {
|
||||
id: removeIndex
|
||||
text: "Remove from"
|
||||
cursorVisible: false
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
text: writableProxyModel.dirty ? "Dirty" : "Clean"
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
border.color: writableProxyModel.dirty ? "red" : "green"
|
||||
border.width: 2
|
||||
}
|
||||
}
|
||||
ListView {
|
||||
id: listViewWritable
|
||||
Layout.preferredWidth: implicitWidth
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
implicitWidth: 300
|
||||
model: WritableProxyModel {
|
||||
id: writableProxyModel
|
||||
sourceModel: listModel
|
||||
}
|
||||
delegate: StatusDraggableListItem {
|
||||
id: draggableDelegate
|
||||
implicitWidth: 300
|
||||
dragParent: root
|
||||
visualIndex: index
|
||||
draggable: listView.count > 1
|
||||
text: model.name
|
||||
secondaryTitle: "Key: " + model.key
|
||||
onClicked: {
|
||||
model.name = model.name + "!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout{
|
||||
Button {
|
||||
text: "Insert"
|
||||
onClicked: {
|
||||
if(writableProxyModel.insert(parseInt(insertWritableIndex.text)))
|
||||
{
|
||||
let item = writableProxyModel.get(parseInt(insertWritableIndex.text))
|
||||
item.name = "New " + (listView.count + 1)
|
||||
item.key = listView.count + 1
|
||||
writableProxyModel.set(parseInt(insertWritableIndex.text), item)
|
||||
}
|
||||
}
|
||||
}
|
||||
TextField {
|
||||
id: insertWritableIndex
|
||||
text: "Insert at"
|
||||
cursorVisible: false
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
}
|
||||
}
|
||||
RowLayout{
|
||||
Button {
|
||||
text: "Remove"
|
||||
onClicked: {
|
||||
writableProxyModel.remove(parseInt(removeWritableIndex.text))
|
||||
}
|
||||
}
|
||||
TextField {
|
||||
id: removeWritableIndex
|
||||
text: "Remove from"
|
||||
cursorVisible: false
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Button {
|
||||
text: "Swap models"
|
||||
onClicked: {
|
||||
if (writableProxyModel.sourceModel === listModel) {
|
||||
writableProxyModel.sourceModel = listModel2
|
||||
} else {
|
||||
writableProxyModel.sourceModel = listModel
|
||||
}
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: "Revert"
|
||||
onClicked: {
|
||||
writableProxyModel.revert()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,6 +29,10 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
|||
find_package(Qt5 COMPONENTS
|
||||
Core Qml Gui Quick QuickControls2 REQUIRED)
|
||||
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
find_package(Qt5 COMPONENTS QuickTest REQUIRED)
|
||||
endif()
|
||||
|
||||
add_subdirectory(../../vendor/SortFilterProxyModel SortFilterProxyModel)
|
||||
add_subdirectory(../../vendor/qzxing/src qzxing)
|
||||
|
||||
|
@ -105,6 +109,7 @@ add_library(StatusQ SHARED
|
|||
include/StatusQ/stringutilsinternal.h
|
||||
include/StatusQ/submodelproxymodel.h
|
||||
include/StatusQ/sumaggregator.h
|
||||
include/StatusQ/writableproxymodel.h
|
||||
src/QClipboardProxy.cpp
|
||||
src/aggregator.cpp
|
||||
src/concatmodel.cpp
|
||||
|
@ -123,6 +128,7 @@ add_library(StatusQ SHARED
|
|||
src/stringutilsinternal.cpp
|
||||
src/submodelproxymodel.cpp
|
||||
src/sumaggregator.cpp
|
||||
src/writableproxymodel.cpp
|
||||
|
||||
# wallet
|
||||
src/wallet/managetokenscontroller.cpp
|
||||
|
@ -170,6 +176,12 @@ target_link_libraries(StatusQ PRIVATE
|
|||
qzxing
|
||||
)
|
||||
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_link_libraries(StatusQ PRIVATE
|
||||
Qt5::QuickTest
|
||||
)
|
||||
endif()
|
||||
|
||||
target_include_directories(StatusQ PUBLIC include)
|
||||
|
||||
install(TARGETS StatusQ
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
#pragma once
|
||||
|
||||
#include <QAbstractProxyModel>
|
||||
|
||||
// WritableProxyModel is a QAbstractProxyModel that allows you to modify the data without modifying the source model.
|
||||
// It is useful for implementing a "dirty" state for a model, where you can modify the data and then commit the changes
|
||||
// to the source model.
|
||||
|
||||
// Supported features (reimplemented):
|
||||
// - setData
|
||||
// - setItemData
|
||||
// - removeRows
|
||||
// - insertRows
|
||||
// - moveRows (TODO)
|
||||
// - toVariantMap
|
||||
// - to be continued...
|
||||
|
||||
// Limitations:
|
||||
// - only 1 column (no grid models)
|
||||
// - no parent index (no tree models)
|
||||
|
||||
class WritableProxyModelPrivate;
|
||||
|
||||
class WritableProxyModel : public QAbstractProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(bool dirty READ dirty NOTIFY dirtyChanged)
|
||||
|
||||
public:
|
||||
explicit WritableProxyModel(QObject* parent = nullptr);
|
||||
~WritableProxyModel();
|
||||
|
||||
Q_INVOKABLE QVariantMap toVariantMap() const;
|
||||
Q_INVOKABLE bool insert(int at);
|
||||
Q_INVOKABLE bool remove(int at);
|
||||
//Returns a VariantMap of the data at the given index
|
||||
//The map contains the role names as keys and the data as values
|
||||
Q_INVOKABLE QVariantMap get(int at) const;
|
||||
//Sets the data at the given index
|
||||
//The map contains the role names as keys and the data as values
|
||||
Q_INVOKABLE bool set(int at, const QVariantMap& data);
|
||||
|
||||
bool dirty() const;
|
||||
|
||||
//QAbstractProxyModel overrides
|
||||
void setSourceModel(QAbstractItemModel* sourceModel) override;
|
||||
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
|
||||
|
||||
QMap<int, QVariant> itemData(const QModelIndex &index) const override;
|
||||
bool setItemData(const QModelIndex& index, const QMap<int, QVariant>& roles) override;
|
||||
|
||||
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
|
||||
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
|
||||
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
||||
QModelIndex sibling(int row, int column, const QModelIndex &idx) const override;
|
||||
QModelIndex parent(const QModelIndex &child) const override;
|
||||
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
|
||||
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
|
||||
|
||||
bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
|
||||
void revert() override;
|
||||
|
||||
// TODO: implement these
|
||||
// bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) override;
|
||||
// bool submit() override;
|
||||
|
||||
signals:
|
||||
void dirtyChanged();
|
||||
|
||||
private:
|
||||
void setDirty(bool flag);
|
||||
|
||||
void handleSourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles);
|
||||
void handleRowsAboutToBeInserted(const QModelIndex &parent, int start, int end);
|
||||
void handleRowsInserted(const QModelIndex &parent, int first, int last);
|
||||
void handleRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end);
|
||||
void handleRowsRemoved(const QModelIndex &parent, int first, int last);
|
||||
void handleModelAboutToBeReset();
|
||||
void handleModelReset();
|
||||
void handleLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint);
|
||||
void handleLayoutChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint);
|
||||
void handleRowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow);
|
||||
|
||||
bool m_dirty{false};
|
||||
|
||||
QScopedPointer<WritableProxyModelPrivate> d;
|
||||
friend class WritableProxyModelPrivate;
|
||||
};
|
|
@ -18,6 +18,7 @@
|
|||
#include "StatusQ/stringutilsinternal.h"
|
||||
#include "StatusQ/submodelproxymodel.h"
|
||||
#include "StatusQ/sumaggregator.h"
|
||||
#include "StatusQ/writableproxymodel.h"
|
||||
|
||||
#include "wallet/managetokenscontroller.h"
|
||||
#include "wallet/managetokensmodel.h"
|
||||
|
@ -50,6 +51,7 @@ public:
|
|||
qmlRegisterType<RoleRename>("StatusQ", 0, 1, "RoleRename");
|
||||
qmlRegisterType<RolesRenamingModel>("StatusQ", 0, 1, "RolesRenamingModel");
|
||||
qmlRegisterType<SumAggregator>("StatusQ", 0, 1, "SumAggregator");
|
||||
qmlRegisterType<WritableProxyModel>("StatusQ", 0, 1, "WritableProxyModel");
|
||||
|
||||
qmlRegisterSingletonType<QClipboardProxy>("StatusQ", 0, 1, "QClipboardProxy", &QClipboardProxy::qmlInstance);
|
||||
|
||||
|
|
|
@ -0,0 +1,838 @@
|
|||
#include "StatusQ/writableproxymodel.h"
|
||||
#include <QSet>
|
||||
#ifdef QT_DEBUG
|
||||
#include <QAbstractItemModelTester>
|
||||
#endif
|
||||
#include <memory>
|
||||
|
||||
|
||||
template <typename T>
|
||||
using IndexedValues = QHash<T /*key*/, QMap<int/*role*/, QVariant/*value*/>>;
|
||||
|
||||
class WritableProxyModelPrivate
|
||||
{
|
||||
public:
|
||||
explicit WritableProxyModelPrivate(WritableProxyModel& q)
|
||||
: q(q)
|
||||
{
|
||||
}
|
||||
|
||||
WritableProxyModel& q;
|
||||
IndexedValues<QPersistentModelIndex> cache;
|
||||
IndexedValues<QModelIndex> insertedRows;
|
||||
int rowsAboutToBeInserted = 0;
|
||||
QSet<QPersistentModelIndex> removedRows;
|
||||
QVector<int> proxyToSourceRowMapping;
|
||||
//internal operations can change dirty flag
|
||||
bool canUpdateDirtyFlag = true;
|
||||
|
||||
inline void setData(const QModelIndex& index, const QVariant& value, int role);
|
||||
template<typename T>
|
||||
inline void setData(const QModelIndex& index, const QVariant& value, int role, IndexedValues<T>& indexedMap);
|
||||
|
||||
inline QVariant data(const QModelIndex &index, int role, bool& found) const;
|
||||
template<typename T>
|
||||
inline QVariant data(const QModelIndex &index, int role, bool& found, const IndexedValues<T>& indexedMap) const;
|
||||
|
||||
inline int proxyToSourceRow(int row) const;
|
||||
inline int sourceToProxyRow(int row) const;
|
||||
QVector<QPair<int, int>> sourceRowRangesBetween(int start, int end) const;
|
||||
|
||||
//Simple mapping. No sorting, no moving
|
||||
//TODO: add mapping for temporarely moved rows
|
||||
void createProxyToSourceRowMap();
|
||||
|
||||
inline bool contains(const QModelIndex& sourceIndex) const;
|
||||
inline void clear();
|
||||
inline void clearInvalidatedCache();
|
||||
|
||||
void moveFromCacheToInserted(const QModelIndex& sourceIndex);
|
||||
void adjustInsertedRowsBy(int start, int offset);
|
||||
|
||||
|
||||
//Fix for missing role names in source model
|
||||
void applyRoleNamesFix();
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
inline void WritableProxyModelPrivate::setData(const QModelIndex &index, const QVariant &value, int role, IndexedValues<T>& indexedMap)
|
||||
{
|
||||
auto valueMap = indexedMap.take(index);
|
||||
valueMap[role] = value;
|
||||
indexedMap.insert(index, valueMap);
|
||||
}
|
||||
|
||||
inline void WritableProxyModelPrivate::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
if(proxyToSourceRowMapping[index.row()] >= 0)
|
||||
{
|
||||
setData(q.mapToSource(index), value, role, cache);
|
||||
return;
|
||||
}
|
||||
|
||||
setData(index, value, role, insertedRows);
|
||||
}
|
||||
|
||||
inline QVariant WritableProxyModelPrivate::data(const QModelIndex &index, int role, bool& found) const
|
||||
{
|
||||
if(index.row() < 0 || index.row() >= proxyToSourceRowMapping.size())
|
||||
{
|
||||
found = false;
|
||||
return {};
|
||||
}
|
||||
|
||||
if(proxyToSourceRowMapping[index.row()] >= 0)
|
||||
{
|
||||
//value in cache (updated role value)
|
||||
return data(q.mapToSource(index), role, found, cache);
|
||||
}
|
||||
//value in inserted rows
|
||||
return data(index, role, found, insertedRows);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline QVariant WritableProxyModelPrivate::data(const QModelIndex &index, int role, bool& found, const IndexedValues<T>& indexedMap) const
|
||||
{
|
||||
QVariant value;
|
||||
auto it = indexedMap.find(index);
|
||||
if (it != indexedMap.end()) {
|
||||
auto valueMap = it.value();
|
||||
auto it2 = valueMap.find(role);
|
||||
if (it2 != valueMap.end()) {
|
||||
value = it2.value();
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
int WritableProxyModelPrivate::proxyToSourceRow(int row) const
|
||||
{
|
||||
if(row < 0 || row >= proxyToSourceRowMapping.size())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return proxyToSourceRowMapping[row];
|
||||
}
|
||||
|
||||
int WritableProxyModelPrivate::sourceToProxyRow(int row) const
|
||||
{
|
||||
for (int i = 0; i < proxyToSourceRowMapping.size(); ++i) {
|
||||
if(proxyToSourceRowMapping[i] == row)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void WritableProxyModelPrivate::createProxyToSourceRowMap()
|
||||
{
|
||||
if(!q.sourceModel())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto sourceModel = q.sourceModel();
|
||||
|
||||
proxyToSourceRowMapping.clear();
|
||||
int sourceIter = 0;
|
||||
for (int i = 0; i < q.rowCount(); ++i) {
|
||||
if(insertedRows.contains(q.index(i, 0)))
|
||||
{
|
||||
proxyToSourceRowMapping.append(-1);
|
||||
continue;
|
||||
}
|
||||
|
||||
while(removedRows.contains(q.sourceModel()->index(sourceIter, 0)) && sourceIter < sourceModel->rowCount())
|
||||
{
|
||||
++sourceIter;
|
||||
}
|
||||
|
||||
proxyToSourceRowMapping.append(sourceIter);
|
||||
sourceIter++;
|
||||
}
|
||||
}
|
||||
|
||||
bool WritableProxyModelPrivate::contains(const QModelIndex& sourceIndex) const {
|
||||
return cache.contains(sourceIndex) || insertedRows.contains(q.mapFromSource(sourceIndex));
|
||||
}
|
||||
|
||||
void WritableProxyModelPrivate::WritableProxyModelPrivate::clear()
|
||||
{
|
||||
cache.clear();
|
||||
removedRows.clear();
|
||||
insertedRows.clear();
|
||||
proxyToSourceRowMapping.clear();
|
||||
}
|
||||
|
||||
void WritableProxyModelPrivate::clearInvalidatedCache()
|
||||
{
|
||||
for(auto iter = removedRows.begin(); iter != removedRows.end();)
|
||||
{
|
||||
if(iter->isValid())
|
||||
{
|
||||
++iter;
|
||||
continue;
|
||||
}
|
||||
|
||||
iter = removedRows.erase(iter);
|
||||
}
|
||||
|
||||
for(auto iter = cache.begin(); iter != cache.end();)
|
||||
{
|
||||
if(iter.key().isValid())
|
||||
{
|
||||
++iter;
|
||||
continue;
|
||||
}
|
||||
|
||||
iter = cache.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
void WritableProxyModelPrivate::moveFromCacheToInserted(const QModelIndex& sourceIndex)
|
||||
{
|
||||
//User updated this row. Move it in inserted rows. We shouldn't delete it
|
||||
insertedRows.insert(q.mapFromSource(sourceIndex), cache.take(sourceIndex));
|
||||
auto itemData = q.sourceModel()->itemData(sourceIndex);
|
||||
auto proxyIndex = q.mapFromSource(sourceIndex);
|
||||
for(auto it = itemData.begin(); it != itemData.end(); ++it)
|
||||
{
|
||||
if(insertedRows[proxyIndex].contains(it.key()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
insertedRows[proxyIndex][it.key()] = it.value();
|
||||
}
|
||||
}
|
||||
|
||||
void WritableProxyModelPrivate::applyRoleNamesFix()
|
||||
{
|
||||
if(!q.sourceModel())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(q.sourceModel()->rowCount() != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto connectionPtr = new QMetaObject::Connection();
|
||||
*connectionPtr =
|
||||
QObject::connect(q.sourceModel(), &QAbstractItemModel::rowsInserted, &q, [this, connectionPtr]() {
|
||||
QObject::disconnect(*connectionPtr);
|
||||
q.resetInternalData();
|
||||
delete connectionPtr;
|
||||
});
|
||||
}
|
||||
|
||||
QVector<QPair<int, int>> WritableProxyModelPrivate::sourceRowRangesBetween(int start, int end) const
|
||||
{
|
||||
QVector<QPair<int, int>> result;
|
||||
int currentStart = -1;
|
||||
int currentEnd = -1;
|
||||
for(int i = start; i <= end; ++i)
|
||||
{
|
||||
auto proxyRow = sourceToProxyRow(i);
|
||||
if(proxyRow >= 0)
|
||||
{
|
||||
if(currentStart == -1)
|
||||
{
|
||||
currentStart = proxyRow;
|
||||
currentEnd = currentStart;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentEnd = proxyRow;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(currentStart != -1)
|
||||
{
|
||||
result.append({ currentStart, currentEnd });
|
||||
currentStart = -1;
|
||||
currentEnd = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(currentStart != -1)
|
||||
{
|
||||
result.append({ currentStart, currentEnd });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void WritableProxyModelPrivate::adjustInsertedRowsBy(int start, int offset)
|
||||
{
|
||||
IndexedValues<QModelIndex> newInsertedRows;
|
||||
for(auto iter = insertedRows.begin(); iter != insertedRows.end(); ++iter)
|
||||
{
|
||||
auto key = iter.key();
|
||||
auto value = iter.value();
|
||||
if(key.row() >= start)
|
||||
{
|
||||
newInsertedRows.insert(key.siblingAtRow(key.row() + offset), value);
|
||||
}
|
||||
else
|
||||
{
|
||||
newInsertedRows.insert(key, value);
|
||||
}
|
||||
}
|
||||
insertedRows.swap(newInsertedRows);
|
||||
}
|
||||
|
||||
WritableProxyModel::WritableProxyModel(QObject* parent)
|
||||
: QAbstractProxyModel(parent)
|
||||
, d(new WritableProxyModelPrivate(*this))
|
||||
{
|
||||
#ifdef QT_DEBUG
|
||||
// Enable the model tester on debug builds to catch any model errors
|
||||
new QAbstractItemModelTester(this, QAbstractItemModelTester::FailureReportingMode::Warning, this);
|
||||
#endif
|
||||
}
|
||||
|
||||
WritableProxyModel::~WritableProxyModel() = default;
|
||||
|
||||
QVariantMap WritableProxyModel::toVariantMap() const
|
||||
{
|
||||
QVariantMap result;
|
||||
int rowCount = this->rowCount();
|
||||
for(int row = 0; row < rowCount; ++row)
|
||||
{
|
||||
auto index = this->index(row, 0);
|
||||
auto data = itemData(index);
|
||||
QVariantMap rowMap;
|
||||
for(auto it = data.begin(); it != data.end(); ++it)
|
||||
{
|
||||
rowMap[QString::number(it.key())] = it.value();
|
||||
}
|
||||
result[QString::number(row)] = rowMap;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool WritableProxyModel::insert(int at)
|
||||
{
|
||||
if(at < 0 || at > rowCount())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return insertRows(at, 1);
|
||||
}
|
||||
|
||||
bool WritableProxyModel::remove(int at)
|
||||
{
|
||||
if(at < 0 || at >= rowCount())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return removeRows(at, 1);
|
||||
}
|
||||
|
||||
QVariantMap WritableProxyModel::get(int at) const
|
||||
{
|
||||
if(at < 0 || at >= rowCount())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto index = this->index(at, 0);
|
||||
auto data = this->itemData(index);
|
||||
auto roleNames = this->roleNames();
|
||||
QVariantMap rowMap;
|
||||
for(auto it = data.begin(); it != data.end(); ++it)
|
||||
{
|
||||
auto roleName = roleNames[it.key()];
|
||||
rowMap[roleName] = it.value();
|
||||
}
|
||||
return rowMap;
|
||||
}
|
||||
|
||||
bool WritableProxyModel::set(int at, const QVariantMap& data)
|
||||
{
|
||||
if(at < 0 || at >= rowCount())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto index = this->index(at, 0);
|
||||
auto itemData = this->itemData(index);
|
||||
auto roleNames = this->roleNames();
|
||||
|
||||
for(auto it = data.begin(); it != data.end(); ++it)
|
||||
{
|
||||
auto role = roleNames.key(it.key().toUtf8(), -1);
|
||||
itemData[role] = it.value();
|
||||
}
|
||||
|
||||
return setItemData(index, itemData);
|
||||
}
|
||||
|
||||
bool WritableProxyModel::dirty() const
|
||||
{
|
||||
return m_dirty;
|
||||
}
|
||||
|
||||
void WritableProxyModel::setDirty(bool flag)
|
||||
{
|
||||
if(m_dirty == flag || !d->canUpdateDirtyFlag)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_dirty = flag;
|
||||
emit dirtyChanged();
|
||||
}
|
||||
|
||||
void WritableProxyModel::setSourceModel(QAbstractItemModel* sourceModel)
|
||||
{
|
||||
if(sourceModel == QAbstractProxyModel::sourceModel())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
|
||||
d->clear();
|
||||
|
||||
if(QAbstractProxyModel::sourceModel())
|
||||
{
|
||||
disconnect(QAbstractProxyModel::sourceModel(), nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
setDirty(false);
|
||||
QAbstractProxyModel::setSourceModel(sourceModel);
|
||||
|
||||
if(!sourceModel)
|
||||
{
|
||||
endResetModel();
|
||||
return;
|
||||
}
|
||||
|
||||
d->applyRoleNamesFix();
|
||||
|
||||
d->createProxyToSourceRowMap();
|
||||
connect(sourceModel, &QAbstractItemModel::dataChanged, this, &WritableProxyModel::handleSourceDataChanged);
|
||||
connect(sourceModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &WritableProxyModel::handleRowsAboutToBeInserted);
|
||||
connect(sourceModel, &QAbstractItemModel::rowsInserted, this, &WritableProxyModel::handleRowsInserted);
|
||||
connect(sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &WritableProxyModel::handleRowsAboutToBeRemoved);
|
||||
connect(sourceModel, &QAbstractItemModel::rowsRemoved, this, &WritableProxyModel::handleRowsRemoved);
|
||||
connect(sourceModel, &QAbstractItemModel::modelAboutToBeReset, this, &WritableProxyModel::handleModelAboutToBeReset);
|
||||
connect(sourceModel, &QAbstractItemModel::modelReset, this, &WritableProxyModel::handleModelReset);
|
||||
connect(sourceModel, &QAbstractItemModel::rowsMoved, this, &WritableProxyModel::handleRowsMoved);
|
||||
connect(sourceModel, &QAbstractItemModel::layoutAboutToBeChanged, this, &WritableProxyModel::handleLayoutAboutToBeChanged);
|
||||
connect(sourceModel, &QAbstractItemModel::layoutChanged, this, &WritableProxyModel::handleModelReset);
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
int WritableProxyModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
if(parent.isValid())
|
||||
{
|
||||
return 0; //no children
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int WritableProxyModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if(parent.isValid())
|
||||
{
|
||||
return 0; //no children
|
||||
}
|
||||
|
||||
if(!sourceModel())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return sourceModel()->rowCount(parent) + d->insertedRows.count() + d->rowsAboutToBeInserted - d->removedRows.count();
|
||||
}
|
||||
|
||||
QModelIndex WritableProxyModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if(parent.isValid())
|
||||
{
|
||||
return {}; //no children
|
||||
}
|
||||
|
||||
if(row < 0 || column < 0 || row >= rowCount(parent) || column >= columnCount(parent))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return createIndex(row, column);
|
||||
}
|
||||
|
||||
QModelIndex WritableProxyModel::sibling(int row, int column, const QModelIndex &idx) const
|
||||
{
|
||||
if(!idx.isValid())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
if(row < 0 || column < 0 || row >= rowCount(idx.parent()) || column >= columnCount(idx.parent()))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return createIndex(row, column);
|
||||
}
|
||||
|
||||
QModelIndex WritableProxyModel::parent(const QModelIndex &child) const
|
||||
{
|
||||
//no children. List models only
|
||||
return {};
|
||||
}
|
||||
|
||||
QModelIndex WritableProxyModel::mapToSource(const QModelIndex &proxyIndex) const
|
||||
{
|
||||
if(!proxyIndex.isValid())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
if(auto row = d->proxyToSourceRow(proxyIndex.row()); row >= 0) {
|
||||
return sourceModel()->index(row, proxyIndex.column());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QModelIndex WritableProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
|
||||
{
|
||||
if(!sourceIndex.isValid())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
if(auto row = d->sourceToProxyRow(sourceIndex.row()); row >= 0) {
|
||||
return index(row, sourceIndex.column());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool WritableProxyModel::hasChildren(const QModelIndex &parent) const
|
||||
{
|
||||
if(parent.isValid())
|
||||
{
|
||||
return false; //no children
|
||||
}
|
||||
|
||||
if(!sourceModel())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return rowCount(parent) > 0;
|
||||
}
|
||||
|
||||
void WritableProxyModel::revert()
|
||||
{
|
||||
beginResetModel();
|
||||
d->clear();
|
||||
d->createProxyToSourceRowMap();
|
||||
setDirty(false);
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
|
||||
QVariant WritableProxyModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if(!index.isValid())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
if(!sourceModel())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
if(role == -1)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
auto data = d->data(index, role, found);
|
||||
|
||||
if(found)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
return QAbstractProxyModel::data(index, role);
|
||||
}
|
||||
|
||||
bool WritableProxyModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||
{
|
||||
if(!index.isValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
d->setData(index, value, role);
|
||||
|
||||
setDirty(true);
|
||||
|
||||
emit dataChanged(index, index, { role });
|
||||
return true;
|
||||
}
|
||||
|
||||
QMap<int, QVariant> WritableProxyModel::itemData(const QModelIndex &index) const
|
||||
{
|
||||
if(!index.isValid())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return QAbstractProxyModel::itemData(index);
|
||||
}
|
||||
|
||||
bool WritableProxyModel::setItemData(const QModelIndex& index, const QMap<int, QVariant>& roles)
|
||||
{
|
||||
if(!index.isValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(QAbstractProxyModel::itemData(index) == roles)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
setDirty(true);
|
||||
return QAbstractProxyModel::setItemData(index, roles);
|
||||
}
|
||||
|
||||
bool WritableProxyModel::removeRows(int row, int count, const QModelIndex &parent)
|
||||
{
|
||||
if(row < 0 || count < 1 || row + count > rowCount(parent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
beginRemoveRows(parent, row, row + count - 1);
|
||||
for (int i = row; i < row + count; ++i) {
|
||||
auto sourceIndex = mapToSource(index(i, 0, parent));
|
||||
if(sourceIndex.isValid())
|
||||
{
|
||||
d->removedRows.insert(sourceIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
d->insertedRows.remove(index(i, 0, parent));
|
||||
}
|
||||
|
||||
d->adjustInsertedRowsBy(i, -1);
|
||||
}
|
||||
|
||||
d->createProxyToSourceRowMap();
|
||||
endRemoveRows();
|
||||
setDirty(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WritableProxyModel::insertRows(int row, int count, const QModelIndex& parent)
|
||||
{
|
||||
if(row < 0 || count < 1 || row > rowCount(parent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
beginInsertRows(parent, row, row + count - 1);
|
||||
d->rowsAboutToBeInserted += count;
|
||||
d->adjustInsertedRowsBy(row, count);
|
||||
for (int i = row; i < row + count; ++i) {
|
||||
d->insertedRows.insert(index(i, 0, parent), {});
|
||||
}
|
||||
d->rowsAboutToBeInserted -= count;
|
||||
d->createProxyToSourceRowMap();
|
||||
endInsertRows();
|
||||
setDirty(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void WritableProxyModel::handleSourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles)
|
||||
{
|
||||
if(!topLeft.isValid() || !bottomRight.isValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!dirty())
|
||||
{
|
||||
auto begin = mapFromSource(topLeft);
|
||||
auto end = mapFromSource(bottomRight);
|
||||
emit dataChanged(begin, end, roles);
|
||||
return;
|
||||
}
|
||||
|
||||
auto begin = qMin(topLeft.row(), rowCount() - 1);
|
||||
auto end = qMin(bottomRight.row(), rowCount() - 1);
|
||||
for(int row = topLeft.row(); row <= end; ++row)
|
||||
{
|
||||
auto sourceIndex = sourceModel()->index(row, 0);
|
||||
|
||||
if(d->contains(sourceIndex) || d->removedRows.contains(sourceIndex))
|
||||
{
|
||||
if(begin < row - 1)
|
||||
{
|
||||
emit dataChanged(mapFromSource(sourceModel()->index(begin, 0)), mapFromSource(sourceModel()->index(row - 1, 0)), roles);
|
||||
}
|
||||
|
||||
begin = row + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if(begin <= end)
|
||||
{
|
||||
emit dataChanged(mapFromSource(sourceModel()->index(begin, 0)), mapFromSource(sourceModel()->index(end, 0)), roles);
|
||||
}
|
||||
}
|
||||
|
||||
void WritableProxyModel::handleRowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
|
||||
{
|
||||
if(parent.isValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!dirty())
|
||||
{
|
||||
beginInsertRows({}, start, start + end - start);
|
||||
return;
|
||||
}
|
||||
|
||||
auto sourceRowRanges = d->sourceRowRangesBetween(start, qMax(end, sourceModel()->rowCount()));
|
||||
if(sourceRowRanges.isEmpty())
|
||||
{
|
||||
//append
|
||||
beginInsertRows({}, rowCount(), rowCount() + end - start);
|
||||
return;
|
||||
}
|
||||
|
||||
beginInsertRows({}, sourceRowRanges.first().first, sourceRowRanges.first().first + end - start);
|
||||
d->rowsAboutToBeInserted += end - start;
|
||||
d->adjustInsertedRowsBy(sourceRowRanges.first().second, end - start);
|
||||
d->rowsAboutToBeInserted -= end - start;
|
||||
}
|
||||
|
||||
void WritableProxyModel::handleRowsInserted(const QModelIndex &parent, int first, int last)
|
||||
{
|
||||
if(parent.isValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
d->createProxyToSourceRowMap();
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void WritableProxyModel::handleRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
|
||||
{
|
||||
if(parent.isValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for(int row = start; row <= end; ++row)
|
||||
{
|
||||
auto sourceIndex = sourceModel()->index(row, 0);
|
||||
|
||||
if(d->cache.contains(sourceIndex))
|
||||
{
|
||||
d->moveFromCacheToInserted(sourceIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto sourceRemoveRanges = d->sourceRowRangesBetween(start, end);
|
||||
for(auto& sourceRemoveRange : sourceRemoveRanges)
|
||||
{
|
||||
auto proxyStart = sourceRemoveRange.first;
|
||||
auto proxyEnd = sourceRemoveRange.second;
|
||||
if(proxyStart == -1 || proxyEnd == -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
d->canUpdateDirtyFlag = false;
|
||||
removeRows(proxyStart, proxyEnd - proxyStart + 1);
|
||||
d->canUpdateDirtyFlag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WritableProxyModel::handleRowsRemoved(const QModelIndex &parent, int first, int last)
|
||||
{
|
||||
if(parent.isValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
d->clearInvalidatedCache();
|
||||
d->createProxyToSourceRowMap();
|
||||
}
|
||||
|
||||
void WritableProxyModel::handleModelAboutToBeReset()
|
||||
{
|
||||
beginResetModel();
|
||||
for(auto iter = d->cache.begin(); iter != d->cache.end();)
|
||||
{
|
||||
auto key = iter.key();
|
||||
iter++;
|
||||
d->moveFromCacheToInserted(key);
|
||||
}
|
||||
}
|
||||
|
||||
void WritableProxyModel::handleModelReset()
|
||||
{
|
||||
d->clearInvalidatedCache();
|
||||
d->createProxyToSourceRowMap();
|
||||
resetInternalData();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void WritableProxyModel::handleRowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow)
|
||||
{
|
||||
if(sourceParent.isValid() || destinationParent.isValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
d->clearInvalidatedCache();
|
||||
d->createProxyToSourceRowMap();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void WritableProxyModel::handleLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint)
|
||||
{
|
||||
if(!sourceParents.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
beginResetModel();
|
||||
}
|
||||
|
||||
void WritableProxyModel::handleLayoutChanged(const QList<QPersistentModelIndex> &sourceParents, QAbstractItemModel::LayoutChangeHint hint)
|
||||
{
|
||||
d->clearInvalidatedCache();
|
||||
d->createProxyToSourceRowMap();
|
||||
endResetModel();
|
||||
}
|
|
@ -77,3 +77,7 @@ add_test(NAME SumAggregatorTest COMMAND SumAggregatorTest)
|
|||
add_executable(ConcatModelTest tst_ConcatModel.cpp)
|
||||
target_link_libraries(ConcatModelTest PRIVATE Qt5::Test StatusQ StatusQTestLib)
|
||||
add_test(NAME ConcatModelTest COMMAND ConcatModelTest)
|
||||
|
||||
add_executable(WritableProxyModelTest tst_WritableProxyModel.cpp)
|
||||
target_link_libraries(WritableProxyModelTest PRIVATE Qt5::Qml Qt5::Test StatusQ)
|
||||
add_test(NAME WritableProxyModelTest COMMAND WritableProxyModelTest)
|
||||
|
|
|
@ -0,0 +1,739 @@
|
|||
#include <QAbstractItemModelTester>
|
||||
#include <QAbstractListModel>
|
||||
#include <QTest>
|
||||
#include <QSignalSpy>
|
||||
|
||||
#include "StatusQ/writableproxymodel.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class TestSourceModel : public QAbstractListModel {
|
||||
|
||||
public:
|
||||
explicit TestSourceModel(QList<QPair<QString, QVariantList>> data)
|
||||
: m_data(std::move(data))
|
||||
{
|
||||
m_roles.reserve(m_data.size());
|
||||
|
||||
for (auto i = 0; i < m_data.size(); i++)
|
||||
m_roles.insert(i, m_data.at(i).first.toUtf8());
|
||||
}
|
||||
|
||||
int rowCount(const QModelIndex &parent) const override
|
||||
{
|
||||
if(parent.isValid()) return 0; //no children
|
||||
|
||||
if(m_data.isEmpty()) return 0;
|
||||
|
||||
return m_data.first().second.size();
|
||||
}
|
||||
|
||||
QVariant data(const QModelIndex& index, int role) const override
|
||||
{
|
||||
if (!index.isValid() || role < 0 || role >= m_data.size())
|
||||
return {};
|
||||
|
||||
const auto row = index.row();
|
||||
|
||||
if (role >= m_data.length() || row >= m_data.at(0).second.length())
|
||||
return {};
|
||||
|
||||
return m_data.at(role).second.at(row);
|
||||
}
|
||||
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override
|
||||
{
|
||||
if (!index.isValid() || role < 0 || role >= m_data.size())
|
||||
return false;
|
||||
|
||||
const auto row = index.row();
|
||||
|
||||
if (role >= m_data.length() || row >= m_data.at(0).second.length())
|
||||
return false;
|
||||
|
||||
m_data[role].second[row] = value;
|
||||
emit dataChanged(index, index, { role });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) override
|
||||
{
|
||||
if (sourceParent.isValid() || destinationParent.isValid())
|
||||
return false;
|
||||
|
||||
if (sourceRow < 0 || sourceRow + count > m_data.at(0).second.size())
|
||||
return false;
|
||||
|
||||
if (destinationChild < 0 || destinationChild > m_data.at(0).second.size())
|
||||
return false;
|
||||
|
||||
if (sourceRow == destinationChild)
|
||||
return true;
|
||||
|
||||
if(!beginMoveRows(sourceParent, sourceRow, sourceRow, destinationParent, destinationChild))
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
for (int j = 0; j < m_data.size(); j++) {
|
||||
auto& roleVariantList = m_data[j].second;
|
||||
roleVariantList.move(sourceRow, destinationChild);
|
||||
}
|
||||
}
|
||||
|
||||
endMoveRows();
|
||||
return true;
|
||||
}
|
||||
|
||||
void insert(int index, QVariantList row)
|
||||
{
|
||||
beginInsertRows(QModelIndex{}, index, index);
|
||||
|
||||
assert(row.size() == m_data.size());
|
||||
|
||||
for (int i = 0; i < m_data.size(); i++) {
|
||||
auto& roleVariantList = m_data[i].second;
|
||||
assert(index <= roleVariantList.size());
|
||||
roleVariantList.insert(index, row.at(i));
|
||||
}
|
||||
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void remove(int index)
|
||||
{
|
||||
beginRemoveRows(QModelIndex{}, index, index);
|
||||
|
||||
for (int i = 0; i < m_data.size(); i++) {
|
||||
auto& roleVariantList = m_data[i].second;
|
||||
assert(index < roleVariantList.size());
|
||||
roleVariantList.removeAt(index);
|
||||
}
|
||||
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override
|
||||
{
|
||||
return m_roles;
|
||||
}
|
||||
|
||||
void reset(QList<QPair<QString, QVariantList>> data = {})
|
||||
{
|
||||
beginResetModel();
|
||||
m_data = std::move(data);
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
private:
|
||||
QList<QPair<QString, QVariantList>> m_data;
|
||||
QHash<int, QByteArray> m_roles;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class TestWritableProxyModel : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void initializationTest()
|
||||
{
|
||||
WritableProxyModel model;
|
||||
QAbstractItemModelTester tester(&model);
|
||||
|
||||
TestSourceModel sourceModel({});
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
QCOMPARE(model.rowCount(), 0);
|
||||
QCOMPARE(model.columnCount(), 1);
|
||||
QCOMPARE(model.dirty(), false);
|
||||
}
|
||||
|
||||
void basicAccessTest()
|
||||
{
|
||||
WritableProxyModel model;
|
||||
QAbstractItemModelTester tester(&model);
|
||||
|
||||
TestSourceModel sourceModel({
|
||||
{ "title", { "Token 1", "Token 2" }},
|
||||
{ "communityId", { "community_1", "community_2" }}
|
||||
});
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
QCOMPARE(model.rowCount(), 2);
|
||||
QCOMPARE(model.columnCount(), 1);
|
||||
QCOMPARE(model.dirty(), false);
|
||||
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 1"));
|
||||
QCOMPARE(model.data(model.index(1, 0), 0), QVariant("Token 2"));
|
||||
QCOMPARE(model.data(model.index(0, 0), 1), QVariant("community_1"));
|
||||
QCOMPARE(model.data(model.index(1, 0), 1), QVariant("community_2"));
|
||||
QCOMPARE(model.data(model.index(0, 1), 0), QVariant());
|
||||
QCOMPARE(model.data(model.index(1, 1), 0), QVariant());
|
||||
QCOMPARE(model.data(model.index(0, 1), 1), QVariant());
|
||||
QCOMPARE(model.data(model.index(1, 1), 1), QVariant());
|
||||
}
|
||||
|
||||
void basicSourceModelDataChange()
|
||||
{
|
||||
WritableProxyModel model;
|
||||
QAbstractItemModelTester tester(&model);
|
||||
|
||||
TestSourceModel sourceModel({
|
||||
{ "title", { "Token 1", "Token 2" }},
|
||||
{ "communityId", { "community_1", "community_2" }}
|
||||
});
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
QCOMPARE(model.dirty(), false);
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 1"));
|
||||
|
||||
QSignalSpy dataChangedSpy(&model, &WritableProxyModel::dataChanged);
|
||||
|
||||
sourceModel.setData(model.index(0, 0), "Token 1.1", 0);
|
||||
|
||||
QCOMPARE(model.dirty(), false);
|
||||
QCOMPARE(dataChangedSpy.count(), 1);
|
||||
|
||||
QCOMPARE(dataChangedSpy.first().at(0), model.index(0, 0));
|
||||
QCOMPARE(dataChangedSpy.first().at(1), model.index(0, 0));
|
||||
QCOMPARE(dataChangedSpy.first().at(2).value<QVector<int>>(), { 0 });
|
||||
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 1.1"));
|
||||
}
|
||||
|
||||
void basicSourceModelRemove()
|
||||
{
|
||||
WritableProxyModel model;
|
||||
QAbstractItemModelTester tester(&model);
|
||||
|
||||
TestSourceModel sourceModel({
|
||||
{ "title", { "Token 1", "Token 2" }},
|
||||
{ "communityId", { "community_1", "community_2" }}
|
||||
});
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
QSignalSpy rowRemovedSpy(&model, &WritableProxyModel::rowsRemoved);
|
||||
|
||||
QCOMPARE(model.dirty(), false);
|
||||
QCOMPARE(model.rowCount(), 2);
|
||||
|
||||
sourceModel.remove(0);
|
||||
QCOMPARE(model.rowCount(), 1);
|
||||
QCOMPARE(model.dirty(), false);
|
||||
QCOMPARE(rowRemovedSpy.count(), 1);
|
||||
QCOMPARE(rowRemovedSpy.first().at(1), 0);
|
||||
QCOMPARE(rowRemovedSpy.first().at(2), 0);
|
||||
}
|
||||
|
||||
void basicSourceModelInsert()
|
||||
{
|
||||
WritableProxyModel model;
|
||||
QAbstractItemModelTester tester(&model);
|
||||
|
||||
TestSourceModel sourceModel({
|
||||
{ "title", { "Token 1", "Token 2" }},
|
||||
{ "communityId", { "community_1", "community_2" }}
|
||||
});
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
QSignalSpy rowInsertedSpy(&model, &WritableProxyModel::rowsInserted);
|
||||
|
||||
QCOMPARE(model.dirty(), false);
|
||||
QCOMPARE(model.rowCount(), 2);
|
||||
|
||||
sourceModel.insert(0, { "Token 0", "community_0" });
|
||||
QCOMPARE(model.rowCount(), 3);
|
||||
QCOMPARE(model.dirty(), false);
|
||||
QCOMPARE(rowInsertedSpy.count(), 1);
|
||||
QCOMPARE(rowInsertedSpy.first().at(1), 0);
|
||||
QCOMPARE(rowInsertedSpy.first().at(2), 0);
|
||||
}
|
||||
|
||||
void basicSourceModelReset()
|
||||
{
|
||||
WritableProxyModel model;
|
||||
QAbstractItemModelTester tester(&model);
|
||||
|
||||
TestSourceModel sourceModel({
|
||||
{ "title", { "Token 1", "Token 2" }},
|
||||
{ "communityId", { "community_1", "community_2" }}
|
||||
});
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
QSignalSpy modelResetSpy(&model, &WritableProxyModel::modelReset);
|
||||
|
||||
QCOMPARE(model.dirty(), false);
|
||||
QCOMPARE(model.rowCount(), 2);
|
||||
|
||||
sourceModel.reset();
|
||||
|
||||
QCOMPARE(model.dirty(), false);
|
||||
QCOMPARE(modelResetSpy.count(), 1);
|
||||
}
|
||||
|
||||
void basicProxyDataChange()
|
||||
{
|
||||
WritableProxyModel model;
|
||||
QAbstractItemModelTester tester(&model);
|
||||
|
||||
TestSourceModel sourceModel({
|
||||
{ "title", { "Token 1", "Token 2" }},
|
||||
{ "communityId", { "community_1", "community_2" }}
|
||||
});
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
QCOMPARE(model.dirty(), false);
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 1"));
|
||||
|
||||
QSignalSpy dataChangedSpy(&model, &WritableProxyModel::dataChanged);
|
||||
|
||||
model.setData(model.index(0, 0), "Token 1.1", 0);
|
||||
|
||||
QCOMPARE(model.dirty(), true);
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 1.1"));
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(0, 0), 0), QVariant("Token 1"));
|
||||
QCOMPARE(dataChangedSpy.count(), 1);
|
||||
QCOMPARE(dataChangedSpy.first().at(0), model.index(0, 0));
|
||||
QCOMPARE(dataChangedSpy.first().at(1), model.index(0, 0));
|
||||
QCOMPARE(dataChangedSpy.first().at(2).value<QVector<int>>(), { 0 });
|
||||
|
||||
model.setData(model.index(1, 0), "Token 2.1", 0);
|
||||
|
||||
QCOMPARE(model.dirty(), true);
|
||||
QCOMPARE(model.data(model.index(1, 0), 0), QVariant("Token 2.1"));
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(1, 0), 0), QVariant("Token 2"));
|
||||
QCOMPARE(dataChangedSpy.count(), 2);
|
||||
QCOMPARE(dataChangedSpy.last().at(0), model.index(1, 0));
|
||||
QCOMPARE(dataChangedSpy.last().at(1), model.index(1, 0));
|
||||
QCOMPARE(dataChangedSpy.last().at(2).value<QVector<int>>(), { 0 });
|
||||
|
||||
model.setData(model.index(0, 0), "community_1.1", 1);
|
||||
|
||||
QCOMPARE(model.dirty(), true);
|
||||
QCOMPARE(model.data(model.index(0, 0), 1), QVariant("community_1.1"));
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(0, 0), 1), QVariant("community_1"));
|
||||
QCOMPARE(dataChangedSpy.count(), 3);
|
||||
QCOMPARE(dataChangedSpy.last().at(0), model.index(0, 0));
|
||||
QCOMPARE(dataChangedSpy.last().at(1), model.index(0, 0));
|
||||
QCOMPARE(dataChangedSpy.last().at(2).value<QVector<int>>(), { 1 });
|
||||
|
||||
model.setData(model.index(1, 0), "community_2.1", 1);
|
||||
|
||||
QCOMPARE(model.dirty(), true);
|
||||
QCOMPARE(model.data(model.index(1, 0), 1), QVariant("community_2.1"));
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(1, 0), 1), QVariant("community_2"));
|
||||
QCOMPARE(dataChangedSpy.count(), 4);
|
||||
QCOMPARE(dataChangedSpy.last().at(0), model.index(1, 0));
|
||||
QCOMPARE(dataChangedSpy.last().at(1), model.index(1, 0));
|
||||
QCOMPARE(dataChangedSpy.last().at(2).value<QVector<int>>(), { 1 });
|
||||
|
||||
model.setItemData(model.index(0, 0), { { 0, "Token 1.2" }, { 1, "community_1.2" } });
|
||||
|
||||
QCOMPARE(model.dirty(), true);
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 1.2"));
|
||||
QCOMPARE(model.data(model.index(0, 0), 1), QVariant("community_1.2"));
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(0, 0), 0), QVariant("Token 1"));
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(0, 0), 1), QVariant("community_1"));
|
||||
QCOMPARE(dataChangedSpy.count(), 6);
|
||||
QCOMPARE(dataChangedSpy.last().at(0), model.index(0, 0));
|
||||
QCOMPARE(dataChangedSpy.last().at(1), model.index(0, 0));
|
||||
QCOMPARE(dataChangedSpy.last().at(2).value<QVector<int>>(), { 1 });
|
||||
}
|
||||
|
||||
void basicProxyRemove()
|
||||
{
|
||||
WritableProxyModel model;
|
||||
QAbstractItemModelTester tester(&model);
|
||||
|
||||
TestSourceModel sourceModel({
|
||||
{ "title", { "Token 1", "Token 2", "Token 3", "Token 4", "Toke 5"}},
|
||||
{ "communityId", { "community_1", "community_2", "community_3", "community_4", "community_5" }}
|
||||
});
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
QSignalSpy rowRemovedSpy(&model, &WritableProxyModel::rowsRemoved);
|
||||
|
||||
QCOMPARE(model.dirty(), false);
|
||||
QCOMPARE(model.rowCount(), 5);
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 1"));
|
||||
model.removeRows(0, 2);
|
||||
|
||||
QCOMPARE(model.rowCount(), 3);
|
||||
QCOMPARE(model.dirty(), true);
|
||||
QCOMPARE(rowRemovedSpy.count(), 1);
|
||||
QCOMPARE(rowRemovedSpy.last().at(1), 0);
|
||||
QCOMPARE(rowRemovedSpy.last().at(2), 1);
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 3"));
|
||||
|
||||
model.removeRows(0, 1);
|
||||
|
||||
QCOMPARE(model.rowCount(), 2);
|
||||
QCOMPARE(model.dirty(), true);
|
||||
QCOMPARE(rowRemovedSpy.count(), 2);
|
||||
QCOMPARE(rowRemovedSpy.last().at(1), 0);
|
||||
QCOMPARE(rowRemovedSpy.last().at(2), 0);
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 4"));
|
||||
}
|
||||
|
||||
void basicProxyInsert()
|
||||
{
|
||||
WritableProxyModel model;
|
||||
QAbstractItemModelTester tester(&model);
|
||||
|
||||
TestSourceModel sourceModel({
|
||||
{ "title", { "Token 1", "Token 2" }},
|
||||
{ "communityId", { "community_1", "community_2" }}
|
||||
});
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
QSignalSpy rowInsertedSpy(&model, &WritableProxyModel::rowsInserted);
|
||||
|
||||
QCOMPARE(model.dirty(), false);
|
||||
QCOMPARE(model.rowCount(), 2);
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 1"));
|
||||
|
||||
model.insertRows(0, 1);
|
||||
|
||||
QCOMPARE(model.rowCount(), 3);
|
||||
QCOMPARE(model.dirty(), true);
|
||||
QCOMPARE(rowInsertedSpy.count(), 1);
|
||||
QCOMPARE(rowInsertedSpy.first().at(1), 0);
|
||||
QCOMPARE(rowInsertedSpy.first().at(2), 0);
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant());
|
||||
|
||||
model.setData(model.index(0, 0), "Token 0", 0);
|
||||
|
||||
QCOMPARE(model.data(model.index(-1, 0), 1), QVariant());
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 0"));
|
||||
QCOMPARE(model.data(model.index(0, 0), 1), QVariant());
|
||||
QCOMPARE(model.data(model.index(1, 0), 0), QVariant("Token 1"));
|
||||
QCOMPARE(model.data(model.index(1, 0), 1), QVariant("community_1"));
|
||||
QCOMPARE(model.data(model.index(2, 0), 0), QVariant("Token 2"));
|
||||
QCOMPARE(model.data(model.index(2, 0), 1), QVariant("community_2"));
|
||||
QCOMPARE(model.data(model.index(3, 0), 0), QVariant());
|
||||
|
||||
model.setData(model.index(0, 0), "community_0", 1);
|
||||
|
||||
QCOMPARE(model.data(model.index(-1, 0), 1), QVariant());
|
||||
QCOMPARE(model.data(model.index(0, 0), 1), QVariant("community_0"));
|
||||
QCOMPARE(model.data(model.index(1, 0), 1), QVariant("community_1"));
|
||||
QCOMPARE(model.data(model.index(2, 0), 1), QVariant("community_2"));
|
||||
QCOMPARE(model.data(model.index(3, 0), 1), QVariant());
|
||||
}
|
||||
|
||||
void updatedDataChangesInSourceModel()
|
||||
{
|
||||
WritableProxyModel model;
|
||||
QAbstractItemModelTester tester(&model);
|
||||
|
||||
TestSourceModel sourceModel({
|
||||
{ "title", { "Token 1", "Token 2" }},
|
||||
{ "communityId", { "community_1", "community_2" }}
|
||||
});
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
QSignalSpy dataChangedSpy(&model, &WritableProxyModel::dataChanged);
|
||||
|
||||
model.setData(model.index(0, 0), "Token 1.1", 0);
|
||||
sourceModel.setData(sourceModel.index(0, 0), "Token 1.2", 0);
|
||||
|
||||
QCOMPARE(model.dirty(), true);
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 1.1"));
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(0, 0), 0), QVariant("Token 1.2"));
|
||||
QCOMPARE(dataChangedSpy.count(), 1);
|
||||
QCOMPARE(dataChangedSpy.first().at(0), model.index(0, 0));
|
||||
QCOMPARE(dataChangedSpy.first().at(1), model.index(0, 0));
|
||||
QCOMPARE(dataChangedSpy.first().at(2).value<QVector<int>>(), { 0 });
|
||||
}
|
||||
|
||||
void removedDataChangesInSourceModel()
|
||||
{
|
||||
WritableProxyModel model;
|
||||
QAbstractItemModelTester tester(&model);
|
||||
|
||||
TestSourceModel sourceModel({
|
||||
{ "title", { "Token 1", "Token 2" }},
|
||||
{ "communityId", { "community_1", "community_2" }}
|
||||
});
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
QSignalSpy dataChangedSpy(&model, &WritableProxyModel::dataChanged);
|
||||
|
||||
model.removeRows(0, 1);
|
||||
sourceModel.setData(sourceModel.index(0, 0), "Token 1.2", 0);
|
||||
|
||||
QCOMPARE(model.dirty(), true);
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 2"));
|
||||
QCOMPARE(dataChangedSpy.count(), 0);
|
||||
}
|
||||
|
||||
void updatedDataIsKeptAfterSourceModelRemove()
|
||||
{
|
||||
WritableProxyModel model;
|
||||
QAbstractItemModelTester tester(&model);
|
||||
|
||||
TestSourceModel sourceModel({
|
||||
{ "title", { "Token 1", "Token 2" }},
|
||||
{ "communityId", { "community_1", "community_2" }}
|
||||
});
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
model.setData(model.index(0, 0), "Token 1.1", 0);
|
||||
|
||||
QSignalSpy rowsRemovedSpy(&model, &WritableProxyModel::rowsRemoved);
|
||||
QSignalSpy modelResetSpy(&model, &WritableProxyModel::modelReset);
|
||||
QSignalSpy dataChangedSpy(&model, &WritableProxyModel::dataChanged);
|
||||
QSignalSpy rowsInsertedSpy(&model, &WritableProxyModel::rowsInserted);
|
||||
|
||||
sourceModel.remove(0);
|
||||
|
||||
QCOMPARE(model.dirty(), true);
|
||||
QCOMPARE(model.rowCount(), 2);
|
||||
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 1.1"));
|
||||
QCOMPARE(model.data(model.index(1, 0), 0), QVariant("Token 2"));
|
||||
QCOMPARE(model.data(model.index(0, 0), 1), QVariant("community_1"));
|
||||
QCOMPARE(model.data(model.index(1, 0), 1), QVariant("community_2"));
|
||||
|
||||
QCOMPARE(rowsRemovedSpy.count(), 0);
|
||||
QCOMPARE(modelResetSpy.count(), 0);
|
||||
QCOMPARE(dataChangedSpy.count(), 0);
|
||||
QCOMPARE(rowsInsertedSpy.count(), 0);
|
||||
}
|
||||
|
||||
void updatedDataIsKeptAfterSourceModelResetToEmpty()
|
||||
{
|
||||
WritableProxyModel model;
|
||||
QAbstractItemModelTester tester(&model);
|
||||
|
||||
TestSourceModel sourceModel({
|
||||
{ "title", { "Token 1", "Token 2" }},
|
||||
{ "communityId", { "community_1", "community_2" }}
|
||||
});
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
model.setData(model.index(0, 0), "Token 1.1", 0);
|
||||
|
||||
QSignalSpy rowsRemovedSpy(&model, &WritableProxyModel::rowsRemoved);
|
||||
QSignalSpy modelResetSpy(&model, &WritableProxyModel::modelReset);
|
||||
QSignalSpy dataChangedSpy(&model, &WritableProxyModel::dataChanged);
|
||||
QSignalSpy rowsInsertedSpy(&model, &WritableProxyModel::rowsInserted);
|
||||
|
||||
sourceModel.reset();
|
||||
|
||||
QCOMPARE(model.dirty(), true);
|
||||
QCOMPARE(model.rowCount(), 1);
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 1.1"));
|
||||
QCOMPARE(model.data(model.index(1, 0), 0), QVariant());
|
||||
QCOMPARE(model.data(model.index(0, 0), 1), QVariant("community_1"));
|
||||
QCOMPARE(model.data(model.index(1, 0), 1), QVariant());
|
||||
|
||||
QCOMPARE(rowsRemovedSpy.count(), 0);
|
||||
QCOMPARE(modelResetSpy.count(), 1);
|
||||
QCOMPARE(dataChangedSpy.count(), 0);
|
||||
QCOMPARE(rowsInsertedSpy.count(), 0);
|
||||
}
|
||||
|
||||
void updatedDataIsKeptAfterSourceModelResetToNew()
|
||||
{
|
||||
WritableProxyModel model;
|
||||
QAbstractItemModelTester tester(&model);
|
||||
|
||||
TestSourceModel sourceModel({
|
||||
{ "title", { "Token 1", "Token 2" }},
|
||||
{ "communityId", { "community_1", "community_2" }}
|
||||
});
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
model.setData(model.index(1, 0), "Token 2.1", 0);
|
||||
|
||||
QSignalSpy rowsRemovedSpy(&model, &WritableProxyModel::rowsRemoved);
|
||||
QSignalSpy modelResetSpy(&model, &WritableProxyModel::modelReset);
|
||||
QSignalSpy dataChangedSpy(&model, &WritableProxyModel::dataChanged);
|
||||
QSignalSpy rowsInsertedSpy(&model, &WritableProxyModel::rowsInserted);
|
||||
|
||||
sourceModel.reset({
|
||||
{ "title", { "Token 3", "Token 4" }},
|
||||
{ "communityId", { "community_3", "community_4" }}
|
||||
});
|
||||
|
||||
QCOMPARE(model.dirty(), true);
|
||||
QCOMPARE(model.rowCount(), 3);
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 3"));
|
||||
QCOMPARE(model.data(model.index(1, 0), 0), QVariant("Token 2.1"));
|
||||
QCOMPARE(model.data(model.index(2, 0), 0), QVariant("Token 4"));
|
||||
QCOMPARE(model.data(model.index(3, 0), 1), QVariant());
|
||||
|
||||
QCOMPARE(model.data(model.index(0, 0), 1), QVariant("community_3"));
|
||||
QCOMPARE(model.data(model.index(1, 0), 1), QVariant("community_2"));
|
||||
QCOMPARE(model.data(model.index(2, 0), 1), QVariant("community_4"));
|
||||
QCOMPARE(model.data(model.index(3, 0), 1), QVariant());
|
||||
|
||||
QCOMPARE(rowsRemovedSpy.count(), 0);
|
||||
QCOMPARE(modelResetSpy.count(), 1);
|
||||
QCOMPARE(dataChangedSpy.count(), 0);
|
||||
QCOMPARE(rowsInsertedSpy.count(), 0);
|
||||
}
|
||||
|
||||
void dataIsAccessibleAfterSourceModelMove()
|
||||
{
|
||||
WritableProxyModel model;
|
||||
QAbstractItemModelTester tester(&model);
|
||||
|
||||
TestSourceModel sourceModel({
|
||||
{ "title", { "Token 1", "Token 2" }},
|
||||
{ "communityId", { "community_1", "community_2" }}
|
||||
});
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
model.setData(model.index(0, 0), "Token 1.1", 0);
|
||||
sourceModel.moveRows({}, 1, 1, {}, 0);
|
||||
|
||||
QCOMPARE(model.dirty(), true);
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(0, 0), 0), QVariant("Token 2"));
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 2"));
|
||||
QCOMPARE(model.data(model.index(1, 0), 0), QVariant("Token 1.1"));
|
||||
}
|
||||
|
||||
void proxyRemovedButSourceModelIsMovingRow()
|
||||
{
|
||||
WritableProxyModel model;
|
||||
QAbstractItemModelTester tester(&model);
|
||||
|
||||
TestSourceModel sourceModel({
|
||||
{ "title", { "Token 1", "Token 2", "Token 3" }},
|
||||
{ "communityId", { "community_1", "community_2", "community_3" }}
|
||||
});
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
model.removeRows(2, 1);
|
||||
|
||||
QCOMPARE(model.dirty(), true);
|
||||
QCOMPARE(model.data(model.index(2, 0), 0), QVariant());
|
||||
QCOMPARE(model.data(model.index(1, 0), 0), QVariant("Token 2"));
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 1"));
|
||||
|
||||
sourceModel.moveRows({}, 2, 1, {}, 0);
|
||||
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(0, 0), 0), QVariant("Token 3"));
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(1, 0), 0), QVariant("Token 1"));
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(2, 0), 0), QVariant("Token 2"));
|
||||
|
||||
QCOMPARE(model.data(model.index(2, 0), 0), QVariant());
|
||||
QCOMPARE(model.data(model.index(1, 0), 0), QVariant("Token 2"));
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 1"));
|
||||
|
||||
sourceModel.moveRows({}, 1, 1, {}, 0);
|
||||
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(0, 0), 0), QVariant("Token 1"));
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(1, 0), 0), QVariant("Token 3"));
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(2, 0), 0), QVariant("Token 2"));
|
||||
|
||||
QCOMPARE(model.data(model.index(2, 0), 0), QVariant());
|
||||
QCOMPARE(model.data(model.index(1, 0), 0), QVariant("Token 2"));
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 1"));
|
||||
|
||||
sourceModel.moveRows({}, 0, 1, {}, 2);
|
||||
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(0, 0), 0), QVariant("Token 3"));
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(1, 0), 0), QVariant("Token 2"));
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(2, 0), 0), QVariant("Token 1"));
|
||||
|
||||
QCOMPARE(model.data(model.index(2, 0), 0), QVariant());
|
||||
QCOMPARE(model.data(model.index(1, 0), 0), QVariant("Token 1"));
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 2"));
|
||||
}
|
||||
|
||||
void proxyInsertedButSourceMovesRows()
|
||||
{
|
||||
WritableProxyModel model;
|
||||
QAbstractItemModelTester tester(&model);
|
||||
|
||||
TestSourceModel sourceModel({
|
||||
{ "title", { "Token 1", "Token 2", "Token 3" }},
|
||||
{ "communityId", { "community_1", "community_2", "community_3" }}
|
||||
});
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
model.insertRows(0, 1);
|
||||
model.setData(model.index(0, 0), "Token 0", 0);
|
||||
model.setData(model.index(0, 0), "community_0", 1);
|
||||
|
||||
model.insertRows(4, 1);
|
||||
model.setData(model.index(4, 0), "Token 4", 0);
|
||||
model.setData(model.index(4, 0), "community_4", 1);
|
||||
|
||||
model.removeRows(1, 1);
|
||||
|
||||
/*
|
||||
Token 0
|
||||
Token 1 -> removed
|
||||
Token 2
|
||||
Token 3
|
||||
Token 4
|
||||
*/
|
||||
|
||||
QCOMPARE(model.dirty(), true);
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 0"));
|
||||
//QCOMPARE(model.data(model.index(1, 0), 0), QVariant("Token 1")); -> removed
|
||||
QCOMPARE(model.data(model.index(1, 0), 0), QVariant("Token 2"));
|
||||
QCOMPARE(model.data(model.index(2, 0), 0), QVariant("Token 3"));
|
||||
QCOMPARE(model.data(model.index(3, 0), 0), QVariant("Token 4"));
|
||||
|
||||
sourceModel.moveRows({}, 2, 1, {}, 0);
|
||||
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(0, 0), 0), QVariant("Token 3"));
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(1, 0), 0), QVariant("Token 1"));
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(2, 0), 0), QVariant("Token 2"));
|
||||
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 0"));
|
||||
QCOMPARE(model.data(model.index(1, 0), 0), QVariant("Token 3"));
|
||||
QCOMPARE(model.data(model.index(2, 0), 0), QVariant("Token 2"));
|
||||
QCOMPARE(model.data(model.index(3, 0), 0), QVariant("Token 4"));
|
||||
|
||||
sourceModel.moveRows({}, 1, 1, {}, 0);
|
||||
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(0, 0), 0), QVariant("Token 1"));
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(1, 0), 0), QVariant("Token 3"));
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(2, 0), 0), QVariant("Token 2"));
|
||||
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 0"));
|
||||
QCOMPARE(model.data(model.index(1, 0), 0), QVariant("Token 3"));
|
||||
QCOMPARE(model.data(model.index(2, 0), 0), QVariant("Token 2"));
|
||||
QCOMPARE(model.data(model.index(3, 0), 0), QVariant("Token 4"));
|
||||
|
||||
sourceModel.moveRows({}, 2, 1, {}, 0);
|
||||
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(0, 0), 0), QVariant("Token 2"));
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(1, 0), 0), QVariant("Token 1"));
|
||||
QCOMPARE(sourceModel.data(sourceModel.index(2, 0), 0), QVariant("Token 3"));
|
||||
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 0"));
|
||||
QCOMPARE(model.data(model.index(1, 0), 0), QVariant("Token 2"));
|
||||
QCOMPARE(model.data(model.index(2, 0), 0), QVariant("Token 3"));
|
||||
QCOMPARE(model.data(model.index(3, 0), 0), QVariant("Token 4"));
|
||||
QCOMPARE(model.data(model.index(4, 0), 0), QVariant());
|
||||
|
||||
auto map = model.toVariantMap();
|
||||
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), map.value("0").value<QVariantMap>().value("0"));
|
||||
QCOMPARE(model.data(model.index(1, 0), 0), map.value("1").value<QVariantMap>().value("0"));
|
||||
QCOMPARE(model.data(model.index(2, 0), 0), map.value("2").value<QVariantMap>().value("0"));
|
||||
QCOMPARE(model.data(model.index(3, 0), 0), map.value("3").value<QVariantMap>().value("0"));
|
||||
|
||||
QCOMPARE(model.data(model.index(0, 0), 1), map.value("0").value<QVariantMap>().value("1"));
|
||||
QCOMPARE(model.data(model.index(1, 0), 1), map.value("1").value<QVariantMap>().value("1"));
|
||||
QCOMPARE(model.data(model.index(2, 0), 1), map.value("2").value<QVariantMap>().value("1"));
|
||||
QCOMPARE(model.data(model.index(3, 0), 1), map.value("3").value<QVariantMap>().value("1"));
|
||||
|
||||
model.revert();
|
||||
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), QVariant("Token 2"));
|
||||
QCOMPARE(model.data(model.index(1, 0), 0), QVariant("Token 1"));
|
||||
QCOMPARE(model.data(model.index(2, 0), 0), QVariant("Token 3"));
|
||||
QCOMPARE(model.data(model.index(3, 0), 0), QVariant());
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestWritableProxyModel)
|
||||
#include "tst_WritableProxyModel.moc"
|
Loading…
Reference in New Issue