feat(StatusQ): Add cpp model utils to allow handling ListModel and generic QAbstractItemModel uniformly

Closes: #9562
This commit is contained in:
Michał Cieślak 2023-02-17 14:36:58 +01:00 committed by Michał
parent 89b767cf2a
commit 03e1636cff
15 changed files with 277 additions and 145 deletions

View File

@ -30,6 +30,7 @@ SplitView {
store: CommunitiesStore { store: CommunitiesStore {
id: mockedCommunity id: mockedCommunity
permissionsModel: PermissionsModel.permissionsModel permissionsModel: PermissionsModel.permissionsModel
readonly property var assetsModel: AssetsModel { readonly property var assetsModel: AssetsModel {
@ -83,10 +84,8 @@ SplitView {
anchors.fill: parent anchors.fill: parent
model: mockedCommunity.permissionsModel model: mockedCommunity.permissionsModel
assetKeys: ModelUtils.modelToArray( assetKeys: assetsModel.data.map(asset => asset.key)
assetsModel, ["key"]).map(asset => asset.key) collectibleKeys: collectiblesModel.data.map(collectible => collectible.key)
collectibleKeys: ModelUtils.modelToArray(
collectiblesModel, ["key"]).map(collectible => collectible.key)
} }
} }
} }

View File

@ -3,42 +3,43 @@ import QtQuick 2.15
import AppLayouts.Chat.controls.community 1.0 import AppLayouts.Chat.controls.community 1.0
ListModel { ListModel {
Component.onCompleted: readonly property var data: [
append([ {
{ key: "socks",
key: "socks", iconSource: ModelsData.assets.socks,
iconSource: ModelsData.assets.socks, name: "Unisocks",
name: "Unisocks", shortName: "SOCKS",
shortName: "SOCKS", category: TokenCategories.Category.Community
category: TokenCategories.Category.Community },
}, {
{ key: "zrx",
key: "zrx", iconSource: ModelsData.assets.zrx,
iconSource: ModelsData.assets.zrx, name: "Ox",
name: "Ox", shortName: "ZRX",
shortName: "ZRX", category: TokenCategories.Category.Own
category: TokenCategories.Category.Own },
}, {
{ key: "1inch",
key: "1inch", iconSource: ModelsData.assets.inch,
iconSource: ModelsData.assets.inch, name: "1inch",
name: "1inch", shortName: "1INCH",
shortName: "1INCH", category: TokenCategories.Category.Own
category: TokenCategories.Category.Own },
}, {
{ key: "Aave",
key: "Aave", iconSource: ModelsData.assets.aave,
iconSource: ModelsData.assets.aave, name: "Aave",
name: "Aave", shortName: "AAVE",
shortName: "AAVE", category: TokenCategories.Category.Own
category: TokenCategories.Category.Own },
}, {
{ key: "Amp",
key: "Amp", iconSource: ModelsData.assets.amp,
iconSource: ModelsData.assets.amp, name: "Amp",
name: "Amp", shortName: "AMP",
shortName: "AMP", category: TokenCategories.Category.Own
category: TokenCategories.Category.Own }
} ]
])
Component.onCompleted: append(data)
} }

View File

@ -3,75 +3,76 @@ import QtQuick 2.15
import AppLayouts.Chat.controls.community 1.0 import AppLayouts.Chat.controls.community 1.0
ListModel { ListModel {
Component.onCompleted: readonly property var data: [
append([ {
{ key: "Anniversary",
key: "Anniversary", iconSource: ModelsData.collectibles.anniversary,
iconSource: ModelsData.collectibles.anniversary, name: "Anniversary",
name: "Anniversary", category: TokenCategories.Category.Community
category: TokenCategories.Category.Community },
}, {
{ key: "CryptoKitties",
key: "CryptoKitties", iconSource: ModelsData.collectibles.cryptoKitties,
iconSource: ModelsData.collectibles.cryptoKitties, name: "CryptoKitties",
name: "CryptoKitties", category: TokenCategories.Category.Own,
category: TokenCategories.Category.Own, subItems: [
subItems: [ {
{ key: "Kitty1",
key: "Kitty1", iconSource: ModelsData.collectibles.kitty1,
iconSource: ModelsData.collectibles.kitty1, imageSource: ModelsData.collectibles.kitty1Big,
imageSource: ModelsData.collectibles.kitty1Big, name: "Furbeard"
name: "Furbeard" },
}, {
{ key: "Kitty2",
key: "Kitty2", iconSource: ModelsData.collectibles.kitty2,
iconSource: ModelsData.collectibles.kitty2, imageSource: ModelsData.collectibles.kitty2Big,
imageSource: ModelsData.collectibles.kitty2Big, name: "Magicat"
name: "Magicat" },
}, {
{ key: "Kitty3",
key: "Kitty3", iconSource: ModelsData.collectibles.kitty3,
iconSource: ModelsData.collectibles.kitty3, imageSource: ModelsData.collectibles.kitty3Big,
imageSource: ModelsData.collectibles.kitty3Big, name: "Happy Meow"
name: "Happy Meow" },
}, {
{ key: "Kitty4",
key: "Kitty4", iconSource: ModelsData.collectibles.kitty4,
iconSource: ModelsData.collectibles.kitty4, imageSource: ModelsData.collectibles.kitty4Big,
imageSource: ModelsData.collectibles.kitty4Big, name: "Furbeard-2"
name: "Furbeard-2" },
}, {
{ key: "Kitty5",
key: "Kitty5", iconSource: ModelsData.collectibles.kitty5,
iconSource: ModelsData.collectibles.kitty5, imageSource: ModelsData.collectibles.kitty5Big,
imageSource: ModelsData.collectibles.kitty5Big, name: "Magicat-3"
name: "Magicat-3" },
}, {
{ key: "Kitty5",
key: "Kitty5", iconSource: ModelsData.collectibles.kitty4,
iconSource: ModelsData.collectibles.kitty4, imageSource: ModelsData.collectibles.kitty4Big,
imageSource: ModelsData.collectibles.kitty4Big, name: "Furbeard-3"
name: "Furbeard-3" },
}, {
{ key: "Kitty6",
key: "Kitty6", iconSource: ModelsData.collectibles.kitty5,
iconSource: ModelsData.collectibles.kitty5, imageSource: ModelsData.collectibles.kitty5Big,
imageSource: ModelsData.collectibles.kitty5Big, name: "Magicat-4"
name: "Magicat-4" }
} ]
] },
}, {
{ key: "SuperRare",
key: "SuperRare", iconSource: ModelsData.collectibles.superRare,
iconSource: ModelsData.collectibles.superRare, name: "SuperRare",
name: "SuperRare", category: TokenCategories.Category.Own
category: TokenCategories.Category.Own },
}, {
{ key: "Custom",
key: "Custom", iconSource: ModelsData.collectibles.custom,
iconSource: ModelsData.collectibles.custom, name: "Custom Collectible",
name: "Custom Collectible", category: TokenCategories.Category.General
category: TokenCategories.Category.General }
} ]
])
Component.onCompleted: append(data)
} }

View File

@ -1,19 +1,21 @@
set(STATUSQ_DIR ${CMAKE_CURRENT_LIST_DIR}) set(STATUSQ_DIR ${CMAKE_CURRENT_LIST_DIR})
set(STATUSQ_HEADERS set(STATUSQ_HEADERS
${STATUSQ_DIR}/include/StatusQ/QClipboardProxy.h
${STATUSQ_DIR}/include/StatusQ/modelutilsinternal.h
${STATUSQ_DIR}/include/StatusQ/rxvalidator.h
${STATUSQ_DIR}/include/StatusQ/statussyntaxhighlighter.h
${STATUSQ_DIR}/include/StatusQ/statuswindow.h ${STATUSQ_DIR}/include/StatusQ/statuswindow.h
${STATUSQ_DIR}/include/StatusQ/typesregistration.h ${STATUSQ_DIR}/include/StatusQ/typesregistration.h
${STATUSQ_DIR}/include/StatusQ/QClipboardProxy.h
${STATUSQ_DIR}/include/StatusQ/statussyntaxhighlighter.h
${STATUSQ_DIR}/include/StatusQ/rxvalidator.h
) )
set(STATUSQ_SOURCES set(STATUSQ_SOURCES
${STATUSQ_DIR}/src/QClipboardProxy.cpp
${STATUSQ_DIR}/src/modelutilsinternal.cpp
${STATUSQ_DIR}/src/rxvalidator.cpp
${STATUSQ_DIR}/src/statussyntaxhighlighter.cpp
${STATUSQ_DIR}/src/statuswindow.cpp ${STATUSQ_DIR}/src/statuswindow.cpp
${STATUSQ_DIR}/src/typesregistration.cpp ${STATUSQ_DIR}/src/typesregistration.cpp
${STATUSQ_DIR}/src/QClipboardProxy.cpp
${STATUSQ_DIR}/src/statussyntaxhighlighter.cpp
${STATUSQ_DIR}/src/rxvalidator.cpp
) )
if(APPLE) if(APPLE)

View File

@ -0,0 +1,34 @@
#pragma once
#include <QObject>
#include <QString>
#include <QVariant>
class QAbstractItemModel;
class QJSEngine;
class QQmlEngine;
class ModelUtilsInternal : public QObject
{
Q_OBJECT
public:
explicit ModelUtilsInternal(QObject* parent = nullptr);
Q_INVOKABLE int roleByName(QAbstractItemModel *model,
const QString &roleName) const;
Q_INVOKABLE QStringList roleNames(QAbstractItemModel *model) const;
Q_INVOKABLE QVariantMap get(QAbstractItemModel *model, int row) const;
Q_INVOKABLE QVariant get(QAbstractItemModel *model, int row,
const QString &roleName) const;
static QObject* qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine);
Q_UNUSED(scriptEngine);
return new ModelUtilsInternal;
}
};

View File

@ -2,16 +2,25 @@ pragma Singleton
import QtQuick 2.14 import QtQuick 2.14
import StatusQ.Internal 0.1 as Internal
QtObject { QtObject {
function get(model, index, role = "") {
if (role)
return Internal.ModelUtils.get(model, index, role)
else
return Internal.ModelUtils.get(model, index)
}
function modelToArray(model, roles) { function modelToArray(model, roles) {
if (!model) if (!model)
return [] return []
const count = model.count const count = model.rowCount()
const array = [] const array = []
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const modelItem = model.get(i) const modelItem = Internal.ModelUtils.get(model, i)
const arrayItem = {} const arrayItem = {}
roles.forEach(role => { roles.forEach(role => {
@ -28,10 +37,10 @@ QtObject {
} }
function indexOf(model, role, key) { function indexOf(model, role, key) {
const count = model.count const count = model.rowCount()
for (let i = 0; i < count; i++) for (let i = 0; i < count; i++)
if (model.get(i)[role] === key) if (Internal.ModelUtils.get(model, i, role) === key)
return i return i
return -1 return -1
@ -55,8 +64,8 @@ QtObject {
return true return true
for (let i = 0; i < countA; i++) { for (let i = 0; i < countA; i++) {
const itemA = modelA.get(i) const itemA = Internal.ModelUtils.get(modelA, i)
const itemB = modelB.get(i) const itemB = Internal.ModelUtils.get(modelB, i)
if (!checkItemsEquality(itemA, itemB, roles)) if (!checkItemsEquality(itemA, itemB, roles))
return false return false
@ -79,11 +88,11 @@ QtObject {
return true return true
for (let i = 0; i < countA; i++) { for (let i = 0; i < countA; i++) {
const itemA = modelA.get(i) const itemA = Internal.ModelUtils.get(modelA, i)
let found = false let found = false
for (let j = 0; j < countB; j++) { for (let j = 0; j < countB; j++) {
const itemB = modelB.get(j) const itemB = Internal.ModelUtils.get(modelB, j)
if (checkItemsEquality(itemA, itemB, roles)) if (checkItemsEquality(itemA, itemB, roles))
found = true found = true

View File

@ -0,0 +1,56 @@
#include "StatusQ/modelutilsinternal.h"
#include <QAbstractItemModel>
ModelUtilsInternal::ModelUtilsInternal(QObject* parent)
: QObject(parent)
{
}
QStringList ModelUtilsInternal::roleNames(QAbstractItemModel *model) const
{
if (model == nullptr)
return {};
QHash<int, QByteArray> roles = model->roleNames();
QStringList strings;
strings.reserve(roles.size());
for (auto it = roles.begin(); it != roles.end(); ++it)
strings << QString::fromUtf8(it.value());
return strings;
}
int ModelUtilsInternal::roleByName(QAbstractItemModel* model,
const QString &roleName) const
{
if (model == nullptr)
return -1;
return model->roleNames().key(roleName.toUtf8(), -1);
}
QVariantMap ModelUtilsInternal::get(QAbstractItemModel *model, int row) const
{
QVariantMap map;
if (model == nullptr)
return map;
QModelIndex modelIndex = model->index(row, 0);
QHash<int, QByteArray> roles = model->roleNames();
for (auto it = roles.begin(); it != roles.end(); ++it)
map.insert(it.value(), model->data(modelIndex, it.key()));
return map;
}
QVariant ModelUtilsInternal::get(QAbstractItemModel *model,
int row, const QString &roleName) const
{
return model->data(model->index(row, 0), roleByName(model, roleName));
}

View File

@ -1,17 +1,21 @@
#include "StatusQ/typesregistration.h" #include "StatusQ/typesregistration.h"
#include "StatusQ/QClipboardProxy.h" #include "StatusQ/QClipboardProxy.h"
#include "StatusQ/modelutilsinternal.h"
#include "StatusQ/rxvalidator.h"
#include "StatusQ/statussyntaxhighlighter.h" #include "StatusQ/statussyntaxhighlighter.h"
#include "StatusQ/statuswindow.h" #include "StatusQ/statuswindow.h"
#include "StatusQ/rxvalidator.h"
#include <QQmlEngine> #include <QQmlEngine>
void registerStatusQTypes() void registerStatusQTypes()
{ {
qmlRegisterType<StatusWindow>("StatusQ", 0 , 1, "StatusWindow"); qmlRegisterType<StatusWindow>("StatusQ", 0 , 1, "StatusWindow");
qmlRegisterSingletonType<QClipboardProxy>("StatusQ", 0 , 1, "QClipboardProxy", qmlRegisterSingletonType<QClipboardProxy>("StatusQ", 0 , 1, "QClipboardProxy",
&QClipboardProxy::qmlInstance); &QClipboardProxy::qmlInstance);
qmlRegisterType<StatusSyntaxHighlighter>("StatusQ", 0 , 1, "StatusSyntaxHighlighter"); qmlRegisterType<StatusSyntaxHighlighter>("StatusQ", 0 , 1, "StatusSyntaxHighlighter");
qmlRegisterType<RXValidator>("StatusQ", 0 , 1, "RXValidator"); qmlRegisterType<RXValidator>("StatusQ", 0 , 1, "RXValidator");
qmlRegisterSingletonType<ModelUtilsInternal>("StatusQ.Internal", 0 , 1, "ModelUtils",
&ModelUtilsInternal::qmlInstance);
} }

View File

@ -125,7 +125,7 @@ StatusListView {
Item { Item {
id: sectionDelegateRoot id: sectionDelegateRoot
property string section: root.model && root.model.count ? property string section: root.count ?
qsTr("Search result") : qsTr("Search result") :
qsTr("No results") qsTr("No results")

View File

@ -37,7 +37,7 @@ StatusScrollView {
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.topMargin: 8 Layout.topMargin: 8
visible: !!model ? model.count === 0 : false visible: repeater.count === 0
Layout.fillWidth: true Layout.fillWidth: true
@ -56,6 +56,8 @@ StatusScrollView {
columns: d.columns columns: d.columns
Repeater { Repeater {
id: repeater
model: root.model model: root.model
delegate: ColumnLayout { delegate: ColumnLayout {
spacing: 4 spacing: 4

View File

@ -3,28 +3,31 @@ pragma Singleton
import QtQml 2.14 import QtQml 2.14
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1
import utils 1.0
import AppLayouts.Chat.controls.community 1.0 import AppLayouts.Chat.controls.community 1.0
QtObject { QtObject {
function getTokenByKey(model, key) { function getTokenByKey(model, key) {
if (!model) if (!model)
return null return null
for (let i = 0; i < model.count; i++) { const count = model.rowCount()
const item = model.get(i)
for (let i = 0; i < count; i++) {
const item = ModelUtils.get(model, i)
if (item.key === key) if (item.key === key)
return item return item
if (item.subItems) { if (item.subItems) {
const subitem = getTokenByKey(item.subItems, key) const subitem = getTokenByKey(item.subItems, key)
if (subitem !== null) if (subitem !== null)
return subitem return subitem
} }
} }
return null return null
} }

View File

@ -170,13 +170,14 @@ SettingsPageLayout {
communityNewPermissionView.dirtyValues.isPrivate communityNewPermissionView.dirtyValues.isPrivate
const model = root.store.permissionsModel const model = root.store.permissionsModel
const count = model.rowCount()
for (let i = 0; i < model.count; i++) { for (let i = 0; i < count; i++) {
if (root.state === d.editPermissionViewState if (root.state === d.editPermissionViewState
&& d.permissionIndexToEdit === i) && d.permissionIndexToEdit === i)
continue continue
const item = model.get(i) const item = ModelUtils.get(model, i)
const holdings = item.holdingsListModel const holdings = item.holdingsListModel
const channels = item.channelsListModel const channels = item.channelsListModel
@ -255,7 +256,7 @@ SettingsPageLayout {
store: root.store store: root.store
function setInitialValuesFromIndex(index) { function setInitialValuesFromIndex(index) {
const item = root.store.permissionsModel.get(index) const item = ModelUtils.get(root.store.permissionsModel, index)
d.holdingsToEditModel = item.holdingsListModel d.holdingsToEditModel = item.holdingsListModel
d.channelsToEditModel = item.channelsListModel d.channelsToEditModel = item.channelsListModel

View File

@ -49,6 +49,8 @@ Control {
spacing: d.defaultHoldingsSpacing spacing: d.defaultHoldingsSpacing
Repeater { Repeater {
id: repeater
model: root.model model: root.model
ColumnLayout { ColumnLayout {
@ -88,7 +90,7 @@ Control {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("or") text: qsTr("or")
textFormat: Text.StyledText textFormat: Text.StyledText
visible: (index !== root.model.count - 1) visible: (index !== repeater.count - 1)
} }
} }
} }

View File

@ -12,6 +12,15 @@ SortFilterProxyModel {
property var assetsModel property var assetsModel
property var collectiblesModel property var collectiblesModel
readonly property ModelChangeTracker _assetsChanges: ModelChangeTracker {
model: assetsModel
}
readonly property ModelChangeTracker _collectiblesChanges: ModelChangeTracker {
model: collectiblesModel
}
proxyRoles: [ proxyRoles: [
ExpressionRole { ExpressionRole {
name: "text" name: "text"
@ -23,10 +32,9 @@ SortFilterProxyModel {
const model = type === HoldingTypes.Type.Asset const model = type === HoldingTypes.Type.Asset
? assetsModel ? assetsModel
: collectiblesModel : collectiblesModel
const item = CommunityPermissionsHelpers.getTokenByKey(model, key) const item = CommunityPermissionsHelpers.getTokenByKey(model, key)
return item ? item.name : "" return item ? item.shortName || item.name : ""
} }
function getText(type, key, amount) { function getText(type, key, amount) {
@ -38,7 +46,11 @@ SortFilterProxyModel {
// Direct call for singleton function is not handled properly by // Direct call for singleton function is not handled properly by
// SortFilterProxyModel that's why helper function is used instead. // SortFilterProxyModel that's why helper function is used instead.
expression: getText(model.type, model.key, model.amount) expression: {
_assetsChanges.revision
_collectiblesChanges.revision
getText(model.type, model.key, model.amount)
}
}, },
ExpressionRole { ExpressionRole {
name: "imageSource" name: "imageSource"
@ -53,7 +65,11 @@ SortFilterProxyModel {
return CommunityPermissionsHelpers.getTokenIconByKey(model, key) return CommunityPermissionsHelpers.getTokenIconByKey(model, key)
} }
expression: getIcon(model.type, model.key) expression: {
_assetsChanges.revision
_collectiblesChanges.revision
getIcon(model.type, model.key)
}
}, },
ExpressionRole { ExpressionRole {
name: "operator" name: "operator"

View File

@ -73,6 +73,7 @@
#include "DOtherSide/Status/SoundManager.h" #include "DOtherSide/Status/SoundManager.h"
#include "StatusQ/QClipboardProxy.h" #include "StatusQ/QClipboardProxy.h"
#include "StatusQ/modelutilsinternal.h"
#include "StatusQ/rxvalidator.h" #include "StatusQ/rxvalidator.h"
#include "StatusQ/statussyntaxhighlighter.h" #include "StatusQ/statussyntaxhighlighter.h"
#include "StatusQ/statuswindow.h" #include "StatusQ/statuswindow.h"
@ -94,7 +95,8 @@ void register_meta_types()
qmlRegisterType<StatusSyntaxHighlighter>("StatusQ", 0 , 1, "StatusSyntaxHighlighter"); qmlRegisterType<StatusSyntaxHighlighter>("StatusQ", 0 , 1, "StatusSyntaxHighlighter");
qmlRegisterType<RXValidator>("StatusQ", 0, 1, "RXValidator"); qmlRegisterType<RXValidator>("StatusQ", 0, 1, "RXValidator");
qmlRegisterSingletonType<QClipboardProxy>("StatusQ", 0 , 1, "QClipboardProxy", &QClipboardProxy::qmlInstance); qmlRegisterSingletonType<QClipboardProxy>("StatusQ", 0 , 1, "QClipboardProxy", &QClipboardProxy::qmlInstance);
qmlRegisterSingletonType<ModelUtilsInternal>("StatusQ.Internal", 0 , 1, "ModelUtils",
&ModelUtilsInternal::qmlInstance);
#ifdef MONITORING #ifdef MONITORING
qmlRegisterSingletonType<Monitor>("Monitoring", 1 , 0, "Monitor", &Monitor::qmlInstance); qmlRegisterSingletonType<Monitor>("Monitoring", 1 , 0, "Monitor", &Monitor::qmlInstance);
#endif #endif