Skip to content

Commit

Permalink
feat(StatusQ/SubmodelProxyModel): Exposing roles computed per submode…
Browse files Browse the repository at this point in the history
…l to the top-level model

Closes: #14390
  • Loading branch information
micieslak committed Apr 30, 2024
1 parent 792e8d7 commit 3a6a826
Show file tree
Hide file tree
Showing 7 changed files with 500 additions and 85 deletions.
180 changes: 127 additions & 53 deletions storybook/pages/SubmodelProxyModelPage.qml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,24 @@ import Storybook 1.0

import SortFilterProxyModel 0.2

import StatusQ.Core.Utils 0.1

Item {
id: root

readonly property string intro:
"The example uses two source models. The first model contains networks"
+ " (id and metadata such as name and color), visible on the left. The"
+ " second model contains tokens metadata and their balances per"
+ " network in the submodel (network id, balance).\n"
+ "The SubmodelProxyModel wrapping the tokens model joins the submodels"
+ " to the network model. It also provides filtering and sorting via"
+ " SFPM (slider and checkbox below). Additionally, SubmodelProxyModel"
+ " calculates the summary balance and issues it as a role in the"
+ " top-level model (via SumAggregator). This sum is then used to"
+ " dynamically sort the tokens model.\nClick on balances to increase"
+ " the amount."

readonly property int numberOfTokens: 2000

readonly property var colors: [
Expand Down Expand Up @@ -95,36 +110,123 @@ Item {
sourceModel: tokensModel

delegateModel: SortFilterProxyModel {
readonly property LeftJoinModel joinModel: LeftJoinModel {
leftModel: submodel
rightModel: networksModel
id: delegateRoot

joinRole: "chainId"
}
// properties exposed as roles to the top-level model
readonly property var balancesCountRole: submodel.count
readonly property int sumRole: aggregator.value

sourceModel: joinModel

filters: ExpressionFilter {
filters: FastExpressionFilter {
expression: balance >= thresholdSlider.value

expectedRoles: "balance"
}

sorters: RoleSorter {
roleName: "name"
enabled: sortCheckBox.checked
}

readonly property LeftJoinModel joinModel: LeftJoinModel {
leftModel: submodel
rightModel: networksModel

joinRole: "chainId"
}

readonly property SumAggregator aggregator: SumAggregator {
id: aggregator

model: delegateRoot
roleName: "balance"
}
}

submodelRoleName: "balances"
}

SortFilterProxyModel {
id: sortBySumProxy

sourceModel: submodelProxyModel

sorters: RoleSorter {
roleName: "sum"
ascendingOrder: false
}
}

ColumnLayout {
anchors.fill: parent
anchors.margins: 10

Label {
Layout.fillWidth: true
wrapMode: Text.Wrap
lineHeight: 1.2
text: root.intro
}

MenuSeparator {
Layout.fillWidth: true
}

RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true

ListView {
Layout.preferredWidth: 110
Layout.leftMargin: 10
Layout.fillHeight: true

spacing: 20

model: networksModel

delegate: ColumnLayout {
width: ListView.view.width

Label {
Layout.fillWidth: true
text: model.name
font.bold: true
}

Rectangle {
Layout.preferredWidth: changeColorButton.width
Layout.preferredHeight: 10

color: model.color
}

Button {
id: changeColorButton

text: "Change color"

onClicked: {
const currentIdx = root.colors.indexOf(model.color)
const numberOfColors = root.colors.length
const nextIdx = (currentIdx + 1) % numberOfColors

networksModel.setProperty(model.index, "color",
root.colors[nextIdx])
}
}
}
}

Rectangle {
Layout.preferredWidth: 1
Layout.fillHeight: true
Layout.rightMargin: 20

color: "lightgray"
}

// ListView consuming model don't have to do any transformation
// of the submodels internally because it's handled externally via
// SubmodelProxyModel.
Expand All @@ -141,7 +243,7 @@ Item {
clip: true
spacing: 18

model: submodelProxyModel
model: sortBySumProxy

delegate: ColumnLayout {
id: delegateRoot
Expand All @@ -153,6 +255,8 @@ Item {
readonly property var balances: model.balances

Label {
id: tokenLabel

Layout.fillWidth: true
text: model.name
font.bold: true
Expand Down Expand Up @@ -182,56 +286,26 @@ Item {
text: `${model.name} (${model.balance})`
font.pixelSize: 10
}
}
}
}
}
}

Rectangle {
Layout.preferredWidth: 1
Layout.fillHeight: true
Layout.rightMargin: 20

color: "lightgray"
}

ListView {
Layout.preferredWidth: 150
Layout.fillHeight: true
MouseArea {
anchors.fill: parent

spacing: 20
onClicked: {
const item = ModelUtils.getByKey(
tokensModel, "name", tokenLabel.text)
const index = ModelUtils.indexOf(
item.balances, "chainId", model.chainId)

model: networksModel

delegate: ColumnLayout {
width: ListView.view.width

Label {
Layout.fillWidth: true
text: model.name
font.bold: true
}

Rectangle {
Layout.preferredWidth: changeColorButton.width
Layout.preferredHeight: 10

color: model.color
}

Button {
id: changeColorButton

text: "Change color"

onClicked: {
const currentIdx = root.colors.indexOf(model.color)
const numberOfColors = root.colors.length
const nextIdx = (currentIdx + 1) % numberOfColors
item.balances.setProperty(
index, "balance",
item.balances.get(index).balance + 1)
}
}
}
}

networksModel.setProperty(model.index, "color",
root.colors[nextIdx])
Label {
text: model.balancesCount + " / " + model.sum
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions ui/StatusQ/include/StatusQ/submodelproxymodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <QIdentityProxyModel>
#include <QPointer>

#include <limits>

class QQmlComponent;

class SubmodelProxyModel : public QIdentityProxyModel
Expand All @@ -20,6 +22,7 @@ class SubmodelProxyModel : public QIdentityProxyModel

QVariant data(const QModelIndex& index, int role) const override;
void setSourceModel(QAbstractItemModel* sourceModel) override;
QHash<int, QByteArray> roleNames() const override;

QQmlComponent* delegateModel() const;
void setDelegateModel(QQmlComponent* delegateModel);
Expand All @@ -31,6 +34,10 @@ class SubmodelProxyModel : public QIdentityProxyModel
void delegateModelChanged();
void submodelRoleNameChanged();

private slots:
void onCustomRoleChanged(QObject* source, int role);
void emitAllDataChanged();

private:
void initializeIfReady();
void initialize();
Expand All @@ -39,9 +46,16 @@ class SubmodelProxyModel : public QIdentityProxyModel
void onDelegateChanged();

QPointer<QQmlComponent> m_delegateModel;
QPointer<QQmlComponent> m_connector;

QString m_submodelRoleName;

bool m_initialized = false;
bool m_sourceModelDeleted = false;
int m_submodelRole = 0;
bool m_dataChangedQueued = false;

QHash<int, QByteArray> m_roleNames;
QHash<QString, int> m_additionalRolesMap;
int m_additionalRolesOffset = std::numeric_limits<int>::max();
};
8 changes: 3 additions & 5 deletions ui/StatusQ/src/movablemodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,7 @@ void MovableModel::syncOrder()

void MovableModel::syncOrderInternal()
{
if (m_sourceModel)
{
if (m_sourceModel) {
auto sourceModel = m_sourceModel;

disconnect(m_sourceModel, nullptr, this, nullptr);
Expand All @@ -148,10 +147,9 @@ void MovableModel::syncOrderInternal()
}
}


m_indexes.clear();
if (!m_synced)
{

if (!m_synced) {
m_synced = true;
emit syncedChanged();
}
Expand Down

0 comments on commit 3a6a826

Please sign in to comment.