feat(StatusQ.Models): Adding SingleModelItemData helper component (#14891)
SingleModelItemData is a generic component that can provide a live object extract from an arbitrary QAbstractItemModel*
This commit is contained in:
parent
f1308f3b28
commit
4e81f8f220
|
@ -0,0 +1,154 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import StatusQ 0.1
|
||||||
|
import StatusQ.Core.Utils 0.1
|
||||||
|
|
||||||
|
import Models 1.0
|
||||||
|
import Storybook 1.0
|
||||||
|
|
||||||
|
Control {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
UsersModel {
|
||||||
|
id: usersModel
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelEntry {
|
||||||
|
id: itemData
|
||||||
|
sourceModel: usersModel
|
||||||
|
key: "pubKey"
|
||||||
|
value: pubKeySelector.currentText
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
Pane {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
background: Rectangle {
|
||||||
|
border.width: 1
|
||||||
|
border.color: "lightgray"
|
||||||
|
}
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
Label {
|
||||||
|
text: "User with pubKey " + itemData.value
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: "Data available: " + itemData.available
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: "Keys: " + itemData.roles
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: "Item removed from model: " + itemData.itemRemovedFromModel
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
active: itemData.available
|
||||||
|
sourceComponent: Pane {
|
||||||
|
background: Rectangle {
|
||||||
|
border.width: 1
|
||||||
|
border.color: "lightgray"
|
||||||
|
}
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
Repeater {
|
||||||
|
model: itemData.roles
|
||||||
|
delegate: Label {
|
||||||
|
text: modelData + ": " + itemData.item[modelData]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GenericListView {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
model: usersModel
|
||||||
|
insetComponent: RowLayout {
|
||||||
|
Button {
|
||||||
|
height: 20
|
||||||
|
font.pixelSize: 11
|
||||||
|
text: "remove"
|
||||||
|
highlighted: model.index === itemData.row
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
usersModel.remove(model.index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
height: 20
|
||||||
|
font.pixelSize: 11
|
||||||
|
text: "edit"
|
||||||
|
highlighted: model.index === itemData.row
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
menu.row = model.index
|
||||||
|
menu.popup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Pane {
|
||||||
|
contentItem: RowLayout {
|
||||||
|
ComboBox {
|
||||||
|
id: pubKeySelector
|
||||||
|
model: [...ModelUtils.modelToFlatArray(usersModel, "pubKey"), "none"]
|
||||||
|
}
|
||||||
|
CheckBox {
|
||||||
|
text: "Cache item on removal"
|
||||||
|
checked: itemData.cacheOnRemoval
|
||||||
|
onCheckedChanged: {
|
||||||
|
itemData.cacheOnRemoval = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
id: menu
|
||||||
|
|
||||||
|
property int row: -1
|
||||||
|
|
||||||
|
readonly property var modelItem: usersModel.get(row)
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
Label {
|
||||||
|
text: "Edit user"
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
TextField {
|
||||||
|
id: pubKeyField
|
||||||
|
placeholderText: "pubKey"
|
||||||
|
enabled: !!menu.modelItem
|
||||||
|
text: !!menu.modelItem ? menu.modelItem.pubKey : ""
|
||||||
|
onAccepted: usersModel.setProperty(menu.row, "pubKey", pubKeyField.text)
|
||||||
|
}
|
||||||
|
TextField {
|
||||||
|
id: displayNameField
|
||||||
|
placeholderText: "displayName"
|
||||||
|
enabled: !!menu.modelItem
|
||||||
|
text: !!menu.modelItem ? menu.modelItem.displayName : ""
|
||||||
|
onAccepted: usersModel.setProperty(menu.row, "displayName", displayNameField.text)
|
||||||
|
}
|
||||||
|
TextField {
|
||||||
|
id: ensNameField
|
||||||
|
placeholderText: "ensName"
|
||||||
|
enabled: !!menu.modelItem
|
||||||
|
text: !!menu.modelItem ? menu.modelItem.ensName : ""
|
||||||
|
onAccepted: usersModel.setProperty(menu.row, "ensName", ensNameField.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// category: Models
|
|
@ -207,3 +207,5 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// category: Models
|
|
@ -101,12 +101,15 @@ add_library(StatusQ SHARED
|
||||||
include/StatusQ/formatteddoubleproperty.h
|
include/StatusQ/formatteddoubleproperty.h
|
||||||
include/StatusQ/functionaggregator.h
|
include/StatusQ/functionaggregator.h
|
||||||
include/StatusQ/leftjoinmodel.h
|
include/StatusQ/leftjoinmodel.h
|
||||||
|
include/StatusQ/modelentry.h
|
||||||
include/StatusQ/modelsyncedcontainer.h
|
include/StatusQ/modelsyncedcontainer.h
|
||||||
include/StatusQ/modelutilsinternal.h
|
include/StatusQ/modelutilsinternal.h
|
||||||
include/StatusQ/movablemodel.h
|
include/StatusQ/movablemodel.h
|
||||||
include/StatusQ/permissionutilsinternal.h
|
include/StatusQ/permissionutilsinternal.h
|
||||||
include/StatusQ/rolesrenamingmodel.h
|
include/StatusQ/rolesrenamingmodel.h
|
||||||
include/StatusQ/rxvalidator.h
|
include/StatusQ/rxvalidator.h
|
||||||
|
include/StatusQ/snapshotmodel.h
|
||||||
|
include/StatusQ/snapshotobject.h
|
||||||
include/StatusQ/singleroleaggregator.h
|
include/StatusQ/singleroleaggregator.h
|
||||||
include/StatusQ/statussyntaxhighlighter.h
|
include/StatusQ/statussyntaxhighlighter.h
|
||||||
include/StatusQ/statuswindow.h
|
include/StatusQ/statuswindow.h
|
||||||
|
@ -124,6 +127,7 @@ add_library(StatusQ SHARED
|
||||||
src/formatteddoubleproperty.cpp
|
src/formatteddoubleproperty.cpp
|
||||||
src/functionaggregator.cpp
|
src/functionaggregator.cpp
|
||||||
src/leftjoinmodel.cpp
|
src/leftjoinmodel.cpp
|
||||||
|
src/modelentry.cpp
|
||||||
src/modelutilsinternal.cpp
|
src/modelutilsinternal.cpp
|
||||||
src/movablemodel.cpp
|
src/movablemodel.cpp
|
||||||
src/permissionutilsinternal.cpp
|
src/permissionutilsinternal.cpp
|
||||||
|
@ -131,6 +135,8 @@ add_library(StatusQ SHARED
|
||||||
src/rolesrenamingmodel.cpp
|
src/rolesrenamingmodel.cpp
|
||||||
src/rxvalidator.cpp
|
src/rxvalidator.cpp
|
||||||
src/singleroleaggregator.cpp
|
src/singleroleaggregator.cpp
|
||||||
|
src/snapshotmodel.cpp
|
||||||
|
src/snapshotobject.cpp
|
||||||
src/statussyntaxhighlighter.cpp
|
src/statussyntaxhighlighter.cpp
|
||||||
src/statuswindow.cpp
|
src/statuswindow.cpp
|
||||||
src/stringutilsinternal.cpp
|
src/stringutilsinternal.cpp
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAbstractItemModel>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QQmlPropertyMap>
|
||||||
|
|
||||||
|
class ModelEntry : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
////////////// input
|
||||||
|
// the source model to get the item from
|
||||||
|
Q_PROPERTY(QAbstractItemModel* sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged REQUIRED)
|
||||||
|
// the key role used to search for the item
|
||||||
|
Q_PROPERTY(QString key READ key WRITE setKey NOTIFY keyChanged REQUIRED)
|
||||||
|
// the value role used to cache the item
|
||||||
|
Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged REQUIRED)
|
||||||
|
// whether to cache the item when it is removed from the model
|
||||||
|
// if true, the item will be cached and available until another source model is used or the cacheOnRemoval is set to false
|
||||||
|
Q_PROPERTY(bool cacheOnRemoval READ cacheOnRemoval WRITE setCacheOnRemoval NOTIFY cacheOnRemovalChanged)
|
||||||
|
|
||||||
|
///////////// output
|
||||||
|
// the item found in the source model
|
||||||
|
Q_PROPERTY(QQmlPropertyMap* item READ item NOTIFY itemChanged)
|
||||||
|
// whether the item is available
|
||||||
|
Q_PROPERTY(bool available READ available NOTIFY availableChanged)
|
||||||
|
// the roles of the item
|
||||||
|
Q_PROPERTY(QStringList roles READ roles NOTIFY rolesChanged)
|
||||||
|
// the row of the item in the source model, -1 if not available or removed
|
||||||
|
Q_PROPERTY(int row READ row NOTIFY rowChanged)
|
||||||
|
// whether the item was removed from the model. This flag is only set when cacheOnRemoval is true
|
||||||
|
Q_PROPERTY(bool itemRemovedFromModel READ itemRemovedFromModel NOTIFY itemRemovedFromModelChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ModelEntry(QObject* parent = nullptr);
|
||||||
|
|
||||||
|
QAbstractItemModel* sourceModel() const;
|
||||||
|
QString key() const;
|
||||||
|
QVariant value() const;
|
||||||
|
bool cacheOnRemoval() const;
|
||||||
|
|
||||||
|
QQmlPropertyMap* item() const;
|
||||||
|
bool available() const;
|
||||||
|
const QStringList& roles() const;
|
||||||
|
int row() const;
|
||||||
|
bool itemRemovedFromModel() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void setSourceModel(QAbstractItemModel* sourceModel);
|
||||||
|
void setKey(const QString& key);
|
||||||
|
void setValue(const QVariant& value);
|
||||||
|
void setIndex(const QModelIndex& index);
|
||||||
|
void setAvailable(bool available);
|
||||||
|
void setRoles(const QStringList& roles);
|
||||||
|
void setRow(int row);
|
||||||
|
void setCacheOnRemoval(bool cacheOnRemoval);
|
||||||
|
void setItemRemovedFromModel(bool itemRemovedFromModel);
|
||||||
|
|
||||||
|
void resetIndex();
|
||||||
|
void tryItemResetOrUpdate();
|
||||||
|
void resetItem();
|
||||||
|
void updateItem(const QList<int>& roles = {});
|
||||||
|
|
||||||
|
QModelIndex findIndexInRange(int start, int end, const QList<int>& roles = {}) const;
|
||||||
|
bool itemHasCorrectRoles() const;
|
||||||
|
void cacheItem();
|
||||||
|
void resetCachedItem();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void sourceModelChanged();
|
||||||
|
void keyChanged();
|
||||||
|
void valueChanged();
|
||||||
|
void itemChanged();
|
||||||
|
void availableChanged();
|
||||||
|
void rolesChanged();
|
||||||
|
void rowChanged();
|
||||||
|
void cacheOnRemovalChanged();
|
||||||
|
void itemRemovedFromModelChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QScopedPointer<QQmlPropertyMap> m_item{nullptr};
|
||||||
|
QPointer<QAbstractItemModel> m_sourceModel{nullptr};
|
||||||
|
QPersistentModelIndex m_index;
|
||||||
|
bool m_available{false};
|
||||||
|
QStringList m_roles;
|
||||||
|
int m_row{-1};
|
||||||
|
bool m_cacheOnRemoval{false};
|
||||||
|
bool m_itemRemovedFromModel{false};
|
||||||
|
QVariant m_value;
|
||||||
|
QString m_key;
|
||||||
|
};
|
|
@ -2,21 +2,26 @@
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
|
|
||||||
class SnapshotModel : public QAbstractListModel {
|
class SnapshotModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit SnapshotModel(QObject* parent = nullptr);
|
explicit SnapshotModel(QObject* parent = nullptr);
|
||||||
explicit SnapshotModel(const QAbstractItemModel& model, bool recursive = true,
|
explicit SnapshotModel(const QAbstractItemModel& model, bool recursive = true, QObject* parent = nullptr);
|
||||||
QObject* parent = nullptr);
|
|
||||||
|
~SnapshotModel();
|
||||||
|
|
||||||
int rowCount(const QModelIndex& parent = {}) const override;
|
int rowCount(const QModelIndex& parent = {}) const override;
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
QVariant data(const QModelIndex& index, int role) const override;
|
QVariant data(const QModelIndex& index, int role) const override;
|
||||||
|
|
||||||
void grabSnapshot(const QAbstractItemModel& model, bool recursive = true);
|
void grabSnapshot(const QAbstractItemModel& model, bool recursive = true);
|
||||||
|
void clearSnapshot();
|
||||||
|
|
||||||
QVariant data(int row, int role) const;
|
QVariant data(int row, int role) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
QHash<int, QList<QVariant>> m_data;
|
QHash<int, QList<QVariant>> m_data;
|
||||||
QHash<int, QByteArray> m_roles;
|
QHash<int, QByteArray> m_roles;
|
||||||
};
|
};
|
|
@ -0,0 +1,38 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QVariantMap>
|
||||||
|
|
||||||
|
class QAbstractItemModel;
|
||||||
|
class SnapshotObject : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(QVariant snapshot READ snapshot NOTIFY snapshotChanged)
|
||||||
|
Q_PROPERTY(bool available READ available NOTIFY availableChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit SnapshotObject(QObject* parent = nullptr);
|
||||||
|
explicit SnapshotObject(const QObject* object, QObject* parent);
|
||||||
|
|
||||||
|
QVariant snapshot() const;
|
||||||
|
bool available() const;
|
||||||
|
|
||||||
|
Q_INVOKABLE void grabSnapshot(const QObject* object);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void snapshotChanged();
|
||||||
|
void availableChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setAvailable(bool available);
|
||||||
|
void setSnapshot(const QVariant& snapshot);
|
||||||
|
|
||||||
|
QVariantMap objectToVariantMap(const QObject* object);
|
||||||
|
QVariant objectToVariant(const QObject* object);
|
||||||
|
QVariant modelToVariant(const QAbstractItemModel* model);
|
||||||
|
void insertIntoVariantMap(QVariantMap& map, const QString& key, const QVariant& value);
|
||||||
|
|
||||||
|
QVariant m_snapshot;
|
||||||
|
bool m_available{false};
|
||||||
|
};
|
|
@ -0,0 +1,346 @@
|
||||||
|
#include "StatusQ/modelentry.h"
|
||||||
|
|
||||||
|
#include "StatusQ/snapshotmodel.h"
|
||||||
|
#include "StatusQ/snapshotobject.h"
|
||||||
|
|
||||||
|
ModelEntry::ModelEntry(QObject* parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_item(new QQmlPropertyMap(this))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
QQmlPropertyMap* ModelEntry::item() const
|
||||||
|
{
|
||||||
|
return m_item.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
QAbstractItemModel* ModelEntry::sourceModel() const
|
||||||
|
{
|
||||||
|
return m_sourceModel.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ModelEntry::key() const
|
||||||
|
{
|
||||||
|
return m_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ModelEntry::value() const
|
||||||
|
{
|
||||||
|
return m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModelEntry::available() const
|
||||||
|
{
|
||||||
|
return m_available;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStringList& ModelEntry::roles() const
|
||||||
|
{
|
||||||
|
return m_roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ModelEntry::row() const
|
||||||
|
{
|
||||||
|
return m_row;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModelEntry::cacheOnRemoval() const
|
||||||
|
{
|
||||||
|
return m_cacheOnRemoval;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModelEntry::itemRemovedFromModel() const
|
||||||
|
{
|
||||||
|
return m_itemRemovedFromModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelEntry::setSourceModel(QAbstractItemModel* sourceModel)
|
||||||
|
{
|
||||||
|
if(m_sourceModel == sourceModel) return;
|
||||||
|
|
||||||
|
if(m_sourceModel)
|
||||||
|
{
|
||||||
|
disconnect(m_sourceModel, nullptr, this, nullptr);
|
||||||
|
}
|
||||||
|
m_sourceModel = sourceModel;
|
||||||
|
|
||||||
|
resetCachedItem();
|
||||||
|
resetIndex();
|
||||||
|
|
||||||
|
if(!m_sourceModel)
|
||||||
|
{
|
||||||
|
emit sourceModelChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(m_sourceModel, &QAbstractItemModel::modelReset, this, [this]() { resetIndex(); });
|
||||||
|
connect(m_sourceModel, &QAbstractItemModel::rowsInserted, this, [this]() {
|
||||||
|
if(!m_index.isValid())
|
||||||
|
{
|
||||||
|
resetIndex();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(m_sourceModel,
|
||||||
|
&QAbstractItemModel::rowsMoved,
|
||||||
|
this,
|
||||||
|
[this](const QModelIndex& parent, int start, int end, const QModelIndex& destination, int row) {
|
||||||
|
if(!m_index.isValid()) return;
|
||||||
|
|
||||||
|
if(m_index.row() >= destination.row() && m_index.row() <= destination.row() + (end - start))
|
||||||
|
{
|
||||||
|
emit rowChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(m_sourceModel,
|
||||||
|
&QAbstractItemModel::rowsAboutToBeRemoved,
|
||||||
|
this,
|
||||||
|
[this](const QModelIndex& parent, int first, int last) {
|
||||||
|
if(!m_index.isValid()) return;
|
||||||
|
|
||||||
|
if(m_index.row() < first || m_index.row() > last) return;
|
||||||
|
|
||||||
|
if(m_cacheOnRemoval)
|
||||||
|
{
|
||||||
|
cacheItem();
|
||||||
|
setItemRemovedFromModel(true);
|
||||||
|
setRow(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIndex({});
|
||||||
|
});
|
||||||
|
connect(m_sourceModel,
|
||||||
|
&QAbstractItemModel::dataChanged,
|
||||||
|
this,
|
||||||
|
[this](const QModelIndex& topLeft, const QModelIndex& bottomRight, const QList<int>& roles = QList<int>()) {
|
||||||
|
if(!m_index.isValid())
|
||||||
|
{
|
||||||
|
auto index = findIndexInRange(topLeft.row(), bottomRight.row() + 1);
|
||||||
|
setIndex(index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the index is still valid
|
||||||
|
auto index = findIndexInRange(m_index.row(), m_index.row() + 1, roles);
|
||||||
|
if(index != m_index)
|
||||||
|
{
|
||||||
|
setIndex(index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(topLeft.row() <= m_index.row() && m_index.row() <= bottomRight.row())
|
||||||
|
{
|
||||||
|
updateItem(roles);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(m_sourceModel, &QAbstractItemModel::layoutChanged, this, [this]() {
|
||||||
|
if(!m_index.isValid())
|
||||||
|
{
|
||||||
|
// Resetting just to cover cases where the rows are removed after the layout change
|
||||||
|
resetItem();
|
||||||
|
}
|
||||||
|
setRow(m_index.row());
|
||||||
|
});
|
||||||
|
|
||||||
|
emit sourceModelChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelEntry::setKey(const QString& key)
|
||||||
|
{
|
||||||
|
if(m_key == key) return;
|
||||||
|
|
||||||
|
m_key = key;
|
||||||
|
resetIndex();
|
||||||
|
emit keyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelEntry::setValue(const QVariant& value)
|
||||||
|
{
|
||||||
|
if(m_value == value) return;
|
||||||
|
|
||||||
|
m_value = value;
|
||||||
|
resetIndex();
|
||||||
|
emit valueChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelEntry::setIndex(const QModelIndex& index)
|
||||||
|
{
|
||||||
|
if(m_index == index) return;
|
||||||
|
|
||||||
|
m_index = index;
|
||||||
|
|
||||||
|
tryItemResetOrUpdate();
|
||||||
|
setRow(m_index.row());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelEntry::setAvailable(bool available)
|
||||||
|
{
|
||||||
|
if(available == m_available) return;
|
||||||
|
|
||||||
|
m_available = available;
|
||||||
|
emit availableChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelEntry::setRoles(const QStringList& roles)
|
||||||
|
{
|
||||||
|
if(m_roles.size() == roles.size() && !m_roles.empty() &&
|
||||||
|
std::all_of(roles.begin(), roles.end(), [this](const QString& role) { return m_roles.contains(role); }))
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_roles = roles;
|
||||||
|
emit rolesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelEntry::setRow(int row)
|
||||||
|
{
|
||||||
|
if(m_row == row) return;
|
||||||
|
|
||||||
|
m_row = row;
|
||||||
|
emit rowChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelEntry::setCacheOnRemoval(bool cacheOnRemoval)
|
||||||
|
{
|
||||||
|
if(m_cacheOnRemoval == cacheOnRemoval) return;
|
||||||
|
|
||||||
|
resetCachedItem();
|
||||||
|
|
||||||
|
m_cacheOnRemoval = cacheOnRemoval;
|
||||||
|
emit cacheOnRemovalChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelEntry::setItemRemovedFromModel(bool itemRemovedFromModel)
|
||||||
|
{
|
||||||
|
if(m_itemRemovedFromModel == itemRemovedFromModel) return;
|
||||||
|
|
||||||
|
m_itemRemovedFromModel = itemRemovedFromModel;
|
||||||
|
emit itemRemovedFromModelChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex ModelEntry::findIndexInRange(int start, int end, const QList<int>& roles) const
|
||||||
|
{
|
||||||
|
if(!m_sourceModel || m_key.isEmpty()) return {};
|
||||||
|
|
||||||
|
auto keysForRole = m_sourceModel->roleNames().keys(m_key.toUtf8());
|
||||||
|
|
||||||
|
// no matching roles found
|
||||||
|
if(keysForRole.isEmpty() || (!roles.isEmpty() && !roles.contains(keysForRole.first()))) return {};
|
||||||
|
|
||||||
|
for(int i = start; i < end; i++)
|
||||||
|
{
|
||||||
|
auto index = m_sourceModel->index(i, 0);
|
||||||
|
auto data = index.data(keysForRole.first());
|
||||||
|
if(data == m_value) return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelEntry::resetIndex()
|
||||||
|
{
|
||||||
|
auto index = QModelIndex();
|
||||||
|
if(m_sourceModel) index = findIndexInRange(0, m_sourceModel->rowCount());
|
||||||
|
|
||||||
|
setIndex(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelEntry::tryItemResetOrUpdate()
|
||||||
|
{
|
||||||
|
if(!m_index.isValid() || !itemHasCorrectRoles())
|
||||||
|
{
|
||||||
|
resetItem();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateItem();
|
||||||
|
setAvailable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelEntry::resetItem()
|
||||||
|
{
|
||||||
|
// Signal order is important here
|
||||||
|
if(!m_index.isValid())
|
||||||
|
{
|
||||||
|
setAvailable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_item.reset(new QQmlPropertyMap());
|
||||||
|
|
||||||
|
updateItem();
|
||||||
|
|
||||||
|
if(!m_index.isValid())
|
||||||
|
{
|
||||||
|
setRoles(m_item->keys());
|
||||||
|
}
|
||||||
|
|
||||||
|
emit itemChanged();
|
||||||
|
|
||||||
|
if(m_index.isValid())
|
||||||
|
{
|
||||||
|
setRoles(m_item->keys());
|
||||||
|
setAvailable(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelEntry::updateItem(const QList<int>& roles /*{}*/)
|
||||||
|
{
|
||||||
|
if(!m_index.isValid() || !m_sourceModel) return;
|
||||||
|
|
||||||
|
const auto& rolesRef = roles.isEmpty() ? m_sourceModel->roleNames().keys() : roles;
|
||||||
|
|
||||||
|
for(auto role : rolesRef)
|
||||||
|
{
|
||||||
|
auto roleName = m_sourceModel->roleNames().value(role);
|
||||||
|
auto roleValue = m_index.data(role);
|
||||||
|
|
||||||
|
if(roleValue == m_item->value(roleName)) continue;
|
||||||
|
|
||||||
|
m_item->insert(roleName, roleValue);
|
||||||
|
emit m_item->valueChanged(roleName, roleValue);
|
||||||
|
}
|
||||||
|
setItemRemovedFromModel(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModelEntry::itemHasCorrectRoles() const
|
||||||
|
{
|
||||||
|
if(!m_sourceModel || !m_item) return false;
|
||||||
|
|
||||||
|
auto itemKeys = m_item->keys();
|
||||||
|
auto modelRoles = m_sourceModel->roleNames().values();
|
||||||
|
|
||||||
|
return std::all_of(modelRoles.cbegin(),
|
||||||
|
modelRoles.cend(),
|
||||||
|
[itemKeys](const QByteArray& role) { return itemKeys.contains(role); }) &&
|
||||||
|
itemKeys.size() == modelRoles.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelEntry::cacheItem()
|
||||||
|
{
|
||||||
|
if(!m_cacheOnRemoval) return;
|
||||||
|
|
||||||
|
for(const auto& role : qAsConst(m_roles))
|
||||||
|
{
|
||||||
|
auto roleName = m_sourceModel->roleNames().key(role.toUtf8());
|
||||||
|
auto roleValue = m_index.data(roleName);
|
||||||
|
|
||||||
|
if(roleValue.canConvert<QAbstractItemModel*>())
|
||||||
|
{
|
||||||
|
m_item->insert(role,
|
||||||
|
QVariant::fromValue(new SnapshotModel(*roleValue.value<QAbstractItemModel*>(), true, m_item.data())));
|
||||||
|
}
|
||||||
|
else if(roleValue.canConvert<QObject*>())
|
||||||
|
{
|
||||||
|
const auto obj = roleValue.value<QObject*>();
|
||||||
|
const auto snapshot = new SnapshotObject(obj, m_item.data());
|
||||||
|
m_item->insert(role, QVariant::fromValue(snapshot->snapshot()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelEntry::resetCachedItem()
|
||||||
|
{
|
||||||
|
if(!m_cacheOnRemoval || !m_itemRemovedFromModel) return;
|
||||||
|
|
||||||
|
resetIndex();
|
||||||
|
tryItemResetOrUpdate();
|
||||||
|
setItemRemovedFromModel(false);
|
||||||
|
}
|
|
@ -16,6 +16,8 @@
|
||||||
#include "StatusQ/permissionutilsinternal.h"
|
#include "StatusQ/permissionutilsinternal.h"
|
||||||
#include "StatusQ/rolesrenamingmodel.h"
|
#include "StatusQ/rolesrenamingmodel.h"
|
||||||
#include "StatusQ/rxvalidator.h"
|
#include "StatusQ/rxvalidator.h"
|
||||||
|
#include "StatusQ/modelentry.h"
|
||||||
|
#include "StatusQ/snapshotobject.h"
|
||||||
#include "StatusQ/statussyntaxhighlighter.h"
|
#include "StatusQ/statussyntaxhighlighter.h"
|
||||||
#include "StatusQ/statuswindow.h"
|
#include "StatusQ/statuswindow.h"
|
||||||
#include "StatusQ/stringutilsinternal.h"
|
#include "StatusQ/stringutilsinternal.h"
|
||||||
|
@ -62,6 +64,8 @@ public:
|
||||||
qmlRegisterType<FormattedDoubleProperty>("StatusQ", 0, 1, "FormattedDoubleProperty");
|
qmlRegisterType<FormattedDoubleProperty>("StatusQ", 0, 1, "FormattedDoubleProperty");
|
||||||
|
|
||||||
qmlRegisterSingletonType<QClipboardProxy>("StatusQ", 0, 1, "QClipboardProxy", &QClipboardProxy::qmlInstance);
|
qmlRegisterSingletonType<QClipboardProxy>("StatusQ", 0, 1, "QClipboardProxy", &QClipboardProxy::qmlInstance);
|
||||||
|
qmlRegisterType<ModelEntry>("StatusQ", 0, 1, "ModelEntry");
|
||||||
|
qmlRegisterType<SnapshotObject>("StatusQ", 0, 1, "SnapshotObject");
|
||||||
|
|
||||||
qmlRegisterSingletonType<ModelUtilsInternal>(
|
qmlRegisterSingletonType<ModelUtilsInternal>(
|
||||||
"StatusQ.Internal", 0, 1, "ModelUtils", &ModelUtilsInternal::qmlInstance);
|
"StatusQ.Internal", 0, 1, "ModelUtils", &ModelUtilsInternal::qmlInstance);
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
#include "StatusQ/snapshotmodel.h"
|
||||||
|
|
||||||
|
#include "StatusQ/snapshotobject.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
SnapshotModel::SnapshotModel(QObject* parent)
|
||||||
|
: QAbstractListModel(parent)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
SnapshotModel::SnapshotModel(const QAbstractItemModel& model, bool recursive, QObject* parent)
|
||||||
|
: QAbstractListModel(parent)
|
||||||
|
{
|
||||||
|
grabSnapshot(model, recursive);
|
||||||
|
}
|
||||||
|
|
||||||
|
SnapshotModel::~SnapshotModel()
|
||||||
|
{
|
||||||
|
clearSnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
int SnapshotModel::rowCount(const QModelIndex& parent) const
|
||||||
|
{
|
||||||
|
if(parent.isValid()) return 0;
|
||||||
|
|
||||||
|
return m_data.size() ? m_data.begin()->size() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> SnapshotModel::roleNames() const
|
||||||
|
{
|
||||||
|
return m_roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant SnapshotModel::data(const QModelIndex& index, int role) const
|
||||||
|
{
|
||||||
|
if(!index.isValid() || !m_roles.contains(role) || index.row() >= rowCount())
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_data[role][index.row()];
|
||||||
|
}
|
||||||
|
|
||||||
|
void SnapshotModel::grabSnapshot(const QAbstractItemModel& model, bool recursive)
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
clearSnapshot();
|
||||||
|
|
||||||
|
m_roles = model.roleNames();
|
||||||
|
|
||||||
|
auto roles = m_roles.keys();
|
||||||
|
auto count = model.rowCount();
|
||||||
|
|
||||||
|
for(auto role : roles)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
QVariant data = model.data(model.index(i, 0), role);
|
||||||
|
|
||||||
|
if(recursive && data.canConvert<QAbstractItemModel*>())
|
||||||
|
{
|
||||||
|
const auto submodel = data.value<QAbstractItemModel*>();
|
||||||
|
|
||||||
|
m_data[role].push_back(QVariant::fromValue(new SnapshotModel(*submodel, true, this)));
|
||||||
|
}
|
||||||
|
else if(recursive && data.canConvert<QObject*>())
|
||||||
|
{
|
||||||
|
const auto submodelObject = data.value<QObject*>();
|
||||||
|
const auto snapshot = new SnapshotObject(submodelObject, this);
|
||||||
|
connect(this, &SnapshotModel::modelAboutToBeReset, snapshot, &SnapshotObject::deleteLater);
|
||||||
|
m_data[role].push_back(snapshot->snapshot());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_data[role].push_back(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SnapshotModel::clearSnapshot()
|
||||||
|
{
|
||||||
|
for (auto& data : m_data.values())
|
||||||
|
{
|
||||||
|
for (auto& item : data)
|
||||||
|
{
|
||||||
|
if (item.canConvert<SnapshotModel*>())
|
||||||
|
{
|
||||||
|
item.value<SnapshotModel*>()->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_data.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant SnapshotModel::data(int row, int role) const
|
||||||
|
{
|
||||||
|
return data(index(row), role);
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
#include "StatusQ/snapshotobject.h"
|
||||||
|
|
||||||
|
#include "StatusQ/snapshotmodel.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QMetaProperty>
|
||||||
|
|
||||||
|
SnapshotObject::SnapshotObject(QObject* parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
SnapshotObject::SnapshotObject(const QObject* object, QObject* parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
grabSnapshot(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant SnapshotObject::snapshot() const
|
||||||
|
{
|
||||||
|
return m_snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SnapshotObject::available() const
|
||||||
|
{
|
||||||
|
return m_available;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SnapshotObject::setAvailable(bool available)
|
||||||
|
{
|
||||||
|
if(m_available == available) return;
|
||||||
|
|
||||||
|
m_available = available;
|
||||||
|
emit availableChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SnapshotObject::setSnapshot(const QVariant& snapshot)
|
||||||
|
{
|
||||||
|
if(m_snapshot == snapshot) return;
|
||||||
|
|
||||||
|
m_snapshot = snapshot;
|
||||||
|
|
||||||
|
// available emit order is important
|
||||||
|
if (!m_snapshot.isValid()) setAvailable(false);
|
||||||
|
|
||||||
|
emit snapshotChanged();
|
||||||
|
|
||||||
|
if (m_snapshot.isValid()) setAvailable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SnapshotObject::grabSnapshot(const QObject* object)
|
||||||
|
{
|
||||||
|
if(!object)
|
||||||
|
{
|
||||||
|
setSnapshot({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try cast to QAbstractItemModel
|
||||||
|
if(const auto model = qobject_cast<const QAbstractItemModel*>(object))
|
||||||
|
{
|
||||||
|
setSnapshot(modelToVariant(model));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSnapshot(QVariant::fromValue(objectToVariantMap(object)));
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantMap SnapshotObject::objectToVariantMap(const QObject* object)
|
||||||
|
{
|
||||||
|
if(!object)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantMap item;
|
||||||
|
|
||||||
|
const auto metaObject = object->metaObject();
|
||||||
|
const auto count = metaObject->propertyCount();
|
||||||
|
const auto propertyOffset = metaObject->propertyOffset();
|
||||||
|
|
||||||
|
for(int i = propertyOffset; i < propertyOffset + count; i++)
|
||||||
|
{
|
||||||
|
const auto property = metaObject->property(i);
|
||||||
|
const auto name = property.name();
|
||||||
|
const auto value = property.read(object);
|
||||||
|
|
||||||
|
insertIntoVariantMap(item, name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto dynamicPropertyNames = object->dynamicPropertyNames();
|
||||||
|
for(const auto& name : dynamicPropertyNames)
|
||||||
|
{
|
||||||
|
const auto value = object->property(name);
|
||||||
|
insertIntoVariantMap(item, name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant SnapshotObject::objectToVariant(const QObject* object)
|
||||||
|
{
|
||||||
|
if(auto model = qobject_cast<const QAbstractItemModel*>(object))
|
||||||
|
{
|
||||||
|
return modelToVariant(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {objectToVariantMap(object)};
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant SnapshotObject::modelToVariant(const QAbstractItemModel* model)
|
||||||
|
{
|
||||||
|
if(!model)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto modelSnapshot = new SnapshotModel(*model, true, this);
|
||||||
|
connect(this, &SnapshotObject::snapshotChanged, modelSnapshot, [modelSnapshot]() { modelSnapshot->deleteLater(); });
|
||||||
|
return QVariant::fromValue(modelSnapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SnapshotObject::insertIntoVariantMap(QVariantMap& map, const QString& key, const QVariant& value)
|
||||||
|
{
|
||||||
|
if(value.canConvert<QObject*>())
|
||||||
|
{
|
||||||
|
map.insert(key, objectToVariant(value.value<QObject*>()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.insert(key, value);
|
||||||
|
}
|
|
@ -23,13 +23,11 @@ add_library(StatusQTestLib
|
||||||
src/TestHelpers/modeltestutils.h
|
src/TestHelpers/modeltestutils.h
|
||||||
src/TestHelpers/persistentindexestester.cpp
|
src/TestHelpers/persistentindexestester.cpp
|
||||||
src/TestHelpers/persistentindexestester.h
|
src/TestHelpers/persistentindexestester.h
|
||||||
src/TestHelpers/snapshotmodel.cpp
|
|
||||||
src/TestHelpers/snapshotmodel.h
|
|
||||||
src/TestHelpers/testmodel.cpp
|
src/TestHelpers/testmodel.cpp
|
||||||
src/TestHelpers/testmodel.h
|
src/TestHelpers/testmodel.h
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(StatusQTestLib PUBLIC Qt5::Core Qt5::Quick Qt5::Test)
|
target_link_libraries(StatusQTestLib PUBLIC Qt5::Core Qt5::Quick Qt5::Test StatusQ)
|
||||||
target_include_directories(StatusQTestLib PUBLIC src)
|
target_include_directories(StatusQTestLib PUBLIC src)
|
||||||
|
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
@ -101,3 +99,11 @@ add_test(NAME MovableModelTest COMMAND MovableModelTest)
|
||||||
add_executable(ModelSyncedContainerTest tst_ModelSyncedContainer.cpp)
|
add_executable(ModelSyncedContainerTest tst_ModelSyncedContainer.cpp)
|
||||||
target_link_libraries(ModelSyncedContainerTest PRIVATE StatusQ StatusQTestLib)
|
target_link_libraries(ModelSyncedContainerTest PRIVATE StatusQ StatusQTestLib)
|
||||||
add_test(NAME ModelSyncedContainerTest COMMAND ModelSyncedContainerTest)
|
add_test(NAME ModelSyncedContainerTest COMMAND ModelSyncedContainerTest)
|
||||||
|
|
||||||
|
add_executable(ModelEntryTest tst_ModelEntry.cpp)
|
||||||
|
target_link_libraries(ModelEntryTest PRIVATE StatusQ StatusQTestLib)
|
||||||
|
add_test(NAME ModelEntryTest COMMAND ModelEntryTest)
|
||||||
|
|
||||||
|
add_executable(SnapshotObjectTest tst_SnapshotObject.cpp)
|
||||||
|
target_link_libraries(SnapshotObjectTest PRIVATE StatusQ StatusQTestLib)
|
||||||
|
add_test(NAME SnapshotObjectTest COMMAND SnapshotObjectTest)
|
|
@ -1,6 +1,5 @@
|
||||||
#include "persistentindexestester.h"
|
#include "persistentindexestester.h"
|
||||||
|
#include "StatusQ/snapshotmodel.h"
|
||||||
#include <TestHelpers/snapshotmodel.h>
|
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
#include "snapshotmodel.h"
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
|
|
||||||
SnapshotModel::SnapshotModel(QObject* parent)
|
|
||||||
: QAbstractListModel(parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
SnapshotModel::SnapshotModel(const QAbstractItemModel& model, bool recursive,
|
|
||||||
QObject* parent)
|
|
||||||
: QAbstractListModel(parent)
|
|
||||||
{
|
|
||||||
grabSnapshot(model, recursive);
|
|
||||||
}
|
|
||||||
|
|
||||||
int SnapshotModel::rowCount(const QModelIndex& parent) const
|
|
||||||
{
|
|
||||||
if(parent.isValid())
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return m_data.size() ? m_data.begin()->size() : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> SnapshotModel::roleNames() const
|
|
||||||
{
|
|
||||||
return m_roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant SnapshotModel::data(const QModelIndex& index, int role) const
|
|
||||||
{
|
|
||||||
if (!index.isValid() || !m_roles.contains(role)
|
|
||||||
|| index.row() >= rowCount()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_data[role][index.row()];
|
|
||||||
}
|
|
||||||
|
|
||||||
void SnapshotModel::grabSnapshot(const QAbstractItemModel& model, bool recursive)
|
|
||||||
{
|
|
||||||
m_roles = model.roleNames();
|
|
||||||
m_data.clear();
|
|
||||||
|
|
||||||
auto roles = m_roles.keys();
|
|
||||||
auto count = model.rowCount();
|
|
||||||
|
|
||||||
for (auto role : roles) {
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
QVariant data = model.data(model.index(i, 0), role);
|
|
||||||
|
|
||||||
if (recursive && data.canConvert<QAbstractItemModel*>()) {
|
|
||||||
const auto submodel = data.value<QAbstractItemModel*>();
|
|
||||||
|
|
||||||
m_data[role].push_back(
|
|
||||||
QVariant::fromValue(
|
|
||||||
new SnapshotModel(*submodel, true, this)));
|
|
||||||
} else {
|
|
||||||
m_data[role].push_back(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant SnapshotModel::data(int row, int role) const
|
|
||||||
{
|
|
||||||
return data(index(row), role);
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -7,12 +7,12 @@
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
|
|
||||||
#include <StatusQ/movablemodel.h>
|
#include <StatusQ/movablemodel.h>
|
||||||
|
#include <StatusQ/snapshotmodel.h>
|
||||||
|
|
||||||
#include <TestHelpers/listmodelwrapper.h>
|
#include <TestHelpers/listmodelwrapper.h>
|
||||||
#include <TestHelpers/modelsignalsspy.h>
|
#include <TestHelpers/modelsignalsspy.h>
|
||||||
#include <TestHelpers/modeltestutils.h>
|
#include <TestHelpers/modeltestutils.h>
|
||||||
#include <TestHelpers/persistentindexestester.h>
|
#include <TestHelpers/persistentindexestester.h>
|
||||||
#include <TestHelpers/snapshotmodel.h>
|
|
||||||
|
|
||||||
class TestMovableModel : public QObject
|
class TestMovableModel : public QObject
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
#include "StatusQ/snapshotobject.h"
|
||||||
|
|
||||||
|
#include <QSignalSpy>
|
||||||
|
#include <QTest>
|
||||||
|
|
||||||
|
#include <QQmlListProperty>
|
||||||
|
#include <QQmlPropertyMap>
|
||||||
|
#include <QStandardItemModel>
|
||||||
|
#include <QScopedPointer>
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
class SimpleObject : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(bool boolProperty MEMBER m_boolProperty)
|
||||||
|
Q_PROPERTY(int intProperty MEMBER m_intProperty)
|
||||||
|
Q_PROPERTY(QString stringProperty MEMBER m_stringProperty)
|
||||||
|
Q_PROPERTY(QVariant variantProperty MEMBER m_variantProperty)
|
||||||
|
public:
|
||||||
|
SimpleObject(bool boolProperty = true,
|
||||||
|
int intProperty = 5,
|
||||||
|
const QString& stringProperty = "string",
|
||||||
|
const QVariant& variantProperty = "variant",
|
||||||
|
QObject* parent = nullptr)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_boolProperty(boolProperty)
|
||||||
|
, m_intProperty(intProperty)
|
||||||
|
, m_stringProperty(stringProperty)
|
||||||
|
, m_variantProperty(variantProperty)
|
||||||
|
{ }
|
||||||
|
bool m_boolProperty;
|
||||||
|
int m_intProperty;
|
||||||
|
QString m_stringProperty;
|
||||||
|
QVariant m_variantProperty;
|
||||||
|
};
|
||||||
|
|
||||||
|
class QObjectTest : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(bool boolProperty MEMBER m_boolProperty)
|
||||||
|
Q_PROPERTY(int intProperty MEMBER m_intProperty)
|
||||||
|
Q_PROPERTY(QString stringProperty MEMBER m_stringProperty)
|
||||||
|
Q_PROPERTY(QVariant variantProperty MEMBER m_variantProperty)
|
||||||
|
Q_PROPERTY(QVariantList variantListProperty MEMBER m_variantListProperty)
|
||||||
|
Q_PROPERTY(QVariantMap variantMapProperty MEMBER m_variantMapProperty)
|
||||||
|
Q_PROPERTY(QObject* objectProperty MEMBER m_objectProperty)
|
||||||
|
Q_PROPERTY(QStandardItemModel* standardItemModel MEMBER m_standardItemModel)
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool m_boolProperty{true};
|
||||||
|
int m_intProperty{5};
|
||||||
|
QString m_stringProperty{"string"};
|
||||||
|
QVariant m_variantProperty{"variant"};
|
||||||
|
QVariantList m_variantListProperty{"variant1", "variant2"};
|
||||||
|
QVariantMap m_variantMapProperty{{"key1", "value1"}, {"key2", "value2"}};
|
||||||
|
QObject* m_objectProperty{new SimpleObject(true, 45, "stringVal", "variantVal", this)};
|
||||||
|
QStandardItemModel* m_standardItemModel = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SnapshotObjectTest : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void snapshotQObjectTest()
|
||||||
|
{
|
||||||
|
QScopedPointer<SnapshotObject> snapshotObject {new SnapshotObject()};
|
||||||
|
|
||||||
|
QSignalSpy snapshotChangedSpy(snapshotObject.data(), &SnapshotObject::snapshotChanged);
|
||||||
|
QSignalSpy availableChangedSpy(snapshotObject.data(), &SnapshotObject::availableChanged);
|
||||||
|
|
||||||
|
QVERIFY(snapshotObject->snapshot().isNull());
|
||||||
|
QVERIFY(!snapshotObject->available());
|
||||||
|
|
||||||
|
// grabSnapshot(nullptr) should clear the snapshot and set available to false
|
||||||
|
snapshotObject->grabSnapshot(nullptr);
|
||||||
|
|
||||||
|
QCOMPARE(snapshotChangedSpy.count(), 0);
|
||||||
|
QCOMPARE(availableChangedSpy.count(), 0);
|
||||||
|
QVERIFY(snapshotObject->snapshot().isNull());
|
||||||
|
QVERIFY(!snapshotObject->available());
|
||||||
|
|
||||||
|
{
|
||||||
|
// grabSnapshot(new SimpleObject) should set the snapshot and set available to true
|
||||||
|
QScopedPointer<SimpleObject> testObject {new SimpleObject(true, 45, "stringVal", "variantVal")};
|
||||||
|
const auto snapshotObjPtr = snapshotObject.data();
|
||||||
|
auto connection = connect(snapshotObjPtr, &SnapshotObject::availableChanged, [snapshotObjPtr]() {
|
||||||
|
// the snapshot object must change after the available property
|
||||||
|
QVERIFY(snapshotObjPtr->snapshot().isValid());
|
||||||
|
});
|
||||||
|
snapshotObject->grabSnapshot(testObject.data());
|
||||||
|
|
||||||
|
QCOMPARE(snapshotChangedSpy.count(), 1);
|
||||||
|
QCOMPARE(availableChangedSpy.count(), 1);
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["boolProperty"].toBool(), true);
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["intProperty"].toInt(), 45);
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["stringProperty"].toString(), "stringVal");
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["variantProperty"].toString(), "variantVal");
|
||||||
|
|
||||||
|
QVERIFY(snapshotObject->available());
|
||||||
|
|
||||||
|
disconnect(connection);
|
||||||
|
// delete the test object and check that the snapshot is still available
|
||||||
|
}
|
||||||
|
|
||||||
|
QCOMPARE(snapshotChangedSpy.count(), 1);
|
||||||
|
QCOMPARE(availableChangedSpy.count(), 1);
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["boolProperty"].toBool(), true);
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["intProperty"].toInt(), 45);
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["stringProperty"].toString(), "stringVal");
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["variantProperty"].toString(), "variantVal");
|
||||||
|
|
||||||
|
QVERIFY(snapshotObject->available());
|
||||||
|
|
||||||
|
{
|
||||||
|
// grabshapshot(new QObjectTest) should set the snapshot and set available to true
|
||||||
|
auto snapshotObjPtr = snapshotObject.data();
|
||||||
|
auto connection = connect(snapshotObject.data(), &SnapshotObject::availableChanged, [snapshotObjPtr]() {
|
||||||
|
// the snapshot object must change after the available property
|
||||||
|
QVERIFY(snapshotObjPtr->snapshot().isValid());
|
||||||
|
});
|
||||||
|
QScopedPointer<QObjectTest> testObject {new QObjectTest()};
|
||||||
|
testObject->m_standardItemModel = new QStandardItemModel(this);
|
||||||
|
testObject->m_standardItemModel->insertRow(0, new QStandardItem("item1"));
|
||||||
|
testObject->m_standardItemModel->insertRow(1, new QStandardItem("item2"));
|
||||||
|
|
||||||
|
snapshotObject->grabSnapshot(testObject.data());
|
||||||
|
disconnect(connection);
|
||||||
|
|
||||||
|
// the testObject and the model is destroyed here
|
||||||
|
}
|
||||||
|
|
||||||
|
QCOMPARE(snapshotChangedSpy.count(), 2);
|
||||||
|
QCOMPARE(availableChangedSpy.count(), 1);
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["boolProperty"].toBool(), true);
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["intProperty"].toInt(), 5);
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["stringProperty"].toString(), "string");
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["variantProperty"].toString(), "variant");
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["variantListProperty"].toList().size(), 2);
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["variantListProperty"].toList().at(0).toString(), "variant1");
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["variantListProperty"].toList().at(1).toString(), "variant2");
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["variantMapProperty"].toMap().size(), 2);
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["variantMapProperty"].toMap()["key1"].toString(), "value1");
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["variantMapProperty"].toMap()["key2"].toString(), "value2");
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["objectProperty"].toMap()["boolProperty"].toBool(), true);
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["objectProperty"].toMap()["intProperty"].toInt(), 45);
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["objectProperty"].toMap()["stringProperty"].toString(),
|
||||||
|
"stringVal");
|
||||||
|
QCOMPARE(snapshotObject->snapshot().toMap()["objectProperty"].toMap()["variantProperty"].toString(),
|
||||||
|
"variantVal");
|
||||||
|
|
||||||
|
auto standardItemModel = snapshotObject->snapshot().toMap()["standardItemModel"].value<QAbstractItemModel*>();
|
||||||
|
QSignalSpy modelDestroyedSpy(standardItemModel, &QObject::destroyed);
|
||||||
|
|
||||||
|
QCOMPARE(standardItemModel->rowCount(), 2);
|
||||||
|
QCOMPARE(standardItemModel->data(standardItemModel->index(0, 0)), "item1");
|
||||||
|
QCOMPARE(standardItemModel->data(standardItemModel->index(1, 0)).toString(), "item2");
|
||||||
|
|
||||||
|
QVERIFY(snapshotObject->available());
|
||||||
|
|
||||||
|
snapshotObject->grabSnapshot(nullptr);
|
||||||
|
|
||||||
|
QCOMPARE(snapshotChangedSpy.count(), 3);
|
||||||
|
QCOMPARE(availableChangedSpy.count(), 2);
|
||||||
|
QVERIFY(snapshotObject->snapshot().isNull());
|
||||||
|
QVERIFY(!snapshotObject->available());
|
||||||
|
// check if the memory is released after grabbing another snapshot
|
||||||
|
QTRY_COMPARE(modelDestroyedSpy.count(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void snapshotModelTest()
|
||||||
|
{
|
||||||
|
QScopedPointer<SnapshotObject> snapshotObject {new SnapshotObject()};
|
||||||
|
|
||||||
|
{
|
||||||
|
QScopedPointer<QStandardItemModel> model {new QStandardItemModel()};
|
||||||
|
model->insertRow(0, new QStandardItem("item1"));
|
||||||
|
model->insertRow(1, new QStandardItem("item2"));
|
||||||
|
|
||||||
|
snapshotObject->grabSnapshot(model.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto snapshot = snapshotObject->snapshot();
|
||||||
|
auto snapshotModel = snapshot.value<QAbstractItemModel*>();
|
||||||
|
|
||||||
|
QSignalSpy modelDestroyedSpy(snapshotModel, &QObject::destroyed);
|
||||||
|
|
||||||
|
QVERIFY(snapshotModel);
|
||||||
|
QCOMPARE(snapshotModel->rowCount(), 2);
|
||||||
|
QCOMPARE(snapshotModel->data(snapshotModel->index(0, 0)).toString(), "item1");
|
||||||
|
QCOMPARE(snapshotModel->data(snapshotModel->index(1, 0)).toString(), "item2");
|
||||||
|
QVERIFY(snapshotObject->available());
|
||||||
|
|
||||||
|
snapshotObject->grabSnapshot(nullptr);
|
||||||
|
QTRY_COMPARE(modelDestroyedSpy.count(), 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QTEST_MAIN(SnapshotObjectTest)
|
||||||
|
#include "tst_SnapshotObject.moc"
|
|
@ -1,17 +1,19 @@
|
||||||
#include <QAbstractItemModelTester>
|
#include <QAbstractItemModelTester>
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QTest>
|
|
||||||
#include <QSignalSpy>
|
#include <QSignalSpy>
|
||||||
|
#include <QTest>
|
||||||
|
|
||||||
|
#include "StatusQ/snapshotmodel.h"
|
||||||
#include "StatusQ/writableproxymodel.h"
|
#include "StatusQ/writableproxymodel.h"
|
||||||
|
|
||||||
#include <TestHelpers/persistentindexestester.h>
|
|
||||||
#include <TestHelpers/snapshotmodel.h>
|
|
||||||
#include <TestHelpers/modeltestutils.h>
|
#include <TestHelpers/modeltestutils.h>
|
||||||
|
#include <TestHelpers/persistentindexestester.h>
|
||||||
|
|
||||||
namespace {
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
class TestSourceModel : public QAbstractListModel {
|
class TestSourceModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit TestSourceModel(QList<QPair<QString, QVariantList>> data)
|
explicit TestSourceModel(QList<QPair<QString, QVariantList>> data)
|
||||||
|
|
Loading…
Reference in New Issue