parent
7c24d19241
commit
9c4e1a8fe9
|
@ -1,346 +0,0 @@
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Controls 2.15
|
|
||||||
import QtQuick.Layouts 1.15
|
|
||||||
|
|
||||||
import StatusQ 0.1
|
|
||||||
import Storybook 1.0
|
|
||||||
|
|
||||||
import SortFilterProxyModel 0.2
|
|
||||||
|
|
||||||
import StatusQ.Core.Utils 0.1
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
readonly property string intro:
|
|
||||||
"<b>THIS MODEL IS DEPRECTAED. Use ObjectProxyModel instead</b><br>" +
|
|
||||||
"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: [
|
|
||||||
"purple", "lightgreen", "red", "blue", "darkgreen"
|
|
||||||
]
|
|
||||||
|
|
||||||
function getRandomInt(max) {
|
|
||||||
return Math.floor(Math.random() * max);
|
|
||||||
}
|
|
||||||
|
|
||||||
ListModel {
|
|
||||||
id: networksModel
|
|
||||||
|
|
||||||
ListElement {
|
|
||||||
chainId: "1"
|
|
||||||
name: "Mainnet"
|
|
||||||
color: "purple"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
chainId: "2"
|
|
||||||
name: "Optimism"
|
|
||||||
color: "lightgreen"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
chainId: "3"
|
|
||||||
name: "Status"
|
|
||||||
color: "red"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
chainId: "4"
|
|
||||||
name: "Abitrum"
|
|
||||||
color: "blue"
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
chainId: "5"
|
|
||||||
name: "Sepolia"
|
|
||||||
color: "darkgreen"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ListModel {
|
|
||||||
id: tokensModel
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
// Populate model with given number of tokens containing random
|
|
||||||
// balances
|
|
||||||
const numberOfTokens = root.numberOfTokens
|
|
||||||
const tokens = []
|
|
||||||
|
|
||||||
const chainIds = []
|
|
||||||
|
|
||||||
for (let n = 0; n < networksModel.count; n++)
|
|
||||||
chainIds.push(networksModel.get(n).chainId)
|
|
||||||
|
|
||||||
for (let i = 0; i < numberOfTokens; i++) {
|
|
||||||
const balances = []
|
|
||||||
const numberOfBalances = 1 + getRandomInt(networksModel.count)
|
|
||||||
const chainIdsCpy = [...chainIds]
|
|
||||||
|
|
||||||
for (let i = 0; i < numberOfBalances; i++) {
|
|
||||||
const chainId = chainIdsCpy.splice(
|
|
||||||
getRandomInt(chainIdsCpy.length), 1)[0]
|
|
||||||
|
|
||||||
balances.push({
|
|
||||||
chainId: chainId,
|
|
||||||
balance: 1 + getRandomInt(200)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens.push({ name: `Token ${i + 1}`, balances })
|
|
||||||
}
|
|
||||||
|
|
||||||
append(tokens)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proxy model joining networksModel to submodels under "balances" role.
|
|
||||||
// Additionally submodel is filtered and sorted via SFPM. Submodel is
|
|
||||||
// accessible via "submodel" context property.
|
|
||||||
SubmodelProxyModel {
|
|
||||||
id: submodelProxyModel
|
|
||||||
|
|
||||||
sourceModel: tokensModel
|
|
||||||
|
|
||||||
delegateModel: SortFilterProxyModel {
|
|
||||||
id: delegateRoot
|
|
||||||
|
|
||||||
// properties exposed as roles to the top-level model
|
|
||||||
readonly property var balancesCountRole: submodel.count
|
|
||||||
readonly property int sumRole: aggregator.value
|
|
||||||
|
|
||||||
sourceModel: joinModel
|
|
||||||
|
|
||||||
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.
|
|
||||||
ListView {
|
|
||||||
id: listView
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
reuseItems: true
|
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {}
|
|
||||||
|
|
||||||
clip: true
|
|
||||||
spacing: 18
|
|
||||||
|
|
||||||
model: sortBySumProxy
|
|
||||||
|
|
||||||
delegate: ColumnLayout {
|
|
||||||
id: delegateRoot
|
|
||||||
|
|
||||||
width: ListView.view.width
|
|
||||||
height: 46
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
readonly property var balances: model.balances
|
|
||||||
|
|
||||||
Label {
|
|
||||||
id: tokenLabel
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: model.name
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
spacing: 14
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: delegateRoot.balances
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: label.implicitWidth * 1.5
|
|
||||||
height: label.implicitHeight * 2
|
|
||||||
|
|
||||||
color: "transparent"
|
|
||||||
border.width: 2
|
|
||||||
border.color: model.color
|
|
||||||
|
|
||||||
Label {
|
|
||||||
id: label
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
|
|
||||||
text: `${model.name} (${model.balance})`
|
|
||||||
font.pixelSize: 10
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
const item = ModelUtils.getByKey(
|
|
||||||
tokensModel, "name", tokenLabel.text)
|
|
||||||
const index = ModelUtils.indexOf(
|
|
||||||
item.balances, "chainId", model.chainId)
|
|
||||||
|
|
||||||
item.balances.setProperty(
|
|
||||||
index, "balance",
|
|
||||||
item.balances.get(index).balance + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: model.balancesCount + " / " + model.sum
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuSeparator {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Label {
|
|
||||||
text: `Number of tokens: ${listView.count}, minimum balance:`
|
|
||||||
}
|
|
||||||
|
|
||||||
Slider {
|
|
||||||
id: thresholdSlider
|
|
||||||
|
|
||||||
from: 0
|
|
||||||
to: 201
|
|
||||||
stepSize: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: thresholdSlider.value
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckBox {
|
|
||||||
id: sortCheckBox
|
|
||||||
|
|
||||||
text: "sort networks by name"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// category: Models
|
|
|
@ -119,7 +119,6 @@ add_library(StatusQ SHARED
|
||||||
include/StatusQ/statussyntaxhighlighter.h
|
include/StatusQ/statussyntaxhighlighter.h
|
||||||
include/StatusQ/statuswindow.h
|
include/StatusQ/statuswindow.h
|
||||||
include/StatusQ/stringutilsinternal.h
|
include/StatusQ/stringutilsinternal.h
|
||||||
include/StatusQ/submodelproxymodel.h
|
|
||||||
include/StatusQ/sumaggregator.h
|
include/StatusQ/sumaggregator.h
|
||||||
include/StatusQ/systemutilsinternal.h
|
include/StatusQ/systemutilsinternal.h
|
||||||
include/StatusQ/undefinedfilter.h
|
include/StatusQ/undefinedfilter.h
|
||||||
|
@ -152,7 +151,6 @@ add_library(StatusQ SHARED
|
||||||
src/statussyntaxhighlighter.cpp
|
src/statussyntaxhighlighter.cpp
|
||||||
src/statuswindow.cpp
|
src/statuswindow.cpp
|
||||||
src/stringutilsinternal.cpp
|
src/stringutilsinternal.cpp
|
||||||
src/submodelproxymodel.cpp
|
|
||||||
src/sumaggregator.cpp
|
src/sumaggregator.cpp
|
||||||
src/systemutilsinternal.cpp
|
src/systemutilsinternal.cpp
|
||||||
src/undefinedfilter.cpp
|
src/undefinedfilter.cpp
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QIdentityProxyModel>
|
|
||||||
#include <QPointer>
|
|
||||||
|
|
||||||
#include <limits>
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
#include "modelsyncedcontainer.h"
|
|
||||||
|
|
||||||
class QQmlComponent;
|
|
||||||
class QQmlEngine;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* NOTE: This proxy model is deprecated and will be removed soon. Use
|
|
||||||
* ObjectProxyModel instead.
|
|
||||||
*/
|
|
||||||
class QT_DEPRECATED_X("Use ObjectProxyModel instead") SubmodelProxyModel
|
|
||||||
: public QIdentityProxyModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
Q_PROPERTY(QQmlComponent* delegateModel READ delegateModel
|
|
||||||
WRITE setDelegateModel NOTIFY delegateModelChanged)
|
|
||||||
|
|
||||||
Q_PROPERTY(QString submodelRoleName READ submodelRoleName
|
|
||||||
WRITE setSubmodelRoleName NOTIFY submodelRoleNameChanged)
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit SubmodelProxyModel(QObject* parent = nullptr);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
const QString& submodelRoleName() const;
|
|
||||||
void setSubmodelRoleName(const QString& sumodelRoleName);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void delegateModelChanged();
|
|
||||||
void submodelRoleNameChanged();
|
|
||||||
|
|
||||||
protected slots:
|
|
||||||
void resetInternalData();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onCustomRoleChanged(QObject* source, int role);
|
|
||||||
void emitAllDataChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void initRoles();
|
|
||||||
void updateRoleNames();
|
|
||||||
|
|
||||||
QStringList fetchAdditionalRoles(QQmlComponent* delegateComponent);
|
|
||||||
QQmlComponent* buildConnectorComponent(
|
|
||||||
const QHash<QString, int>& additionalRoles,
|
|
||||||
QQmlEngine* engine, QObject* parent);
|
|
||||||
|
|
||||||
std::optional<int> findSubmodelRole(const QHash<int, QByteArray>& roleNames,
|
|
||||||
const QString& submodelRoleName);
|
|
||||||
|
|
||||||
QPointer<QQmlComponent> m_delegateModel;
|
|
||||||
QPointer<QQmlComponent> m_connector;
|
|
||||||
|
|
||||||
QString m_submodelRoleName;
|
|
||||||
|
|
||||||
bool m_sourceModelDeleted = false;
|
|
||||||
bool m_dataChangedQueued = false;
|
|
||||||
|
|
||||||
std::optional<int> m_submodelRole = 0;
|
|
||||||
|
|
||||||
QStringList m_additionalRoles;
|
|
||||||
QHash<int, QByteArray> m_roleNames;
|
|
||||||
QHash<QString, int> m_additionalRolesMap;
|
|
||||||
int m_additionalRolesOffset = std::numeric_limits<int>::max();
|
|
||||||
|
|
||||||
mutable ModelSyncedContainer<std::unique_ptr<QObject>> m_container;
|
|
||||||
};
|
|
|
@ -26,7 +26,6 @@
|
||||||
#include "StatusQ/statussyntaxhighlighter.h"
|
#include "StatusQ/statussyntaxhighlighter.h"
|
||||||
#include "StatusQ/statuswindow.h"
|
#include "StatusQ/statuswindow.h"
|
||||||
#include "StatusQ/stringutilsinternal.h"
|
#include "StatusQ/stringutilsinternal.h"
|
||||||
#include "StatusQ/submodelproxymodel.h"
|
|
||||||
#include "StatusQ/sumaggregator.h"
|
#include "StatusQ/sumaggregator.h"
|
||||||
#include "StatusQ/systemutilsinternal.h"
|
#include "StatusQ/systemutilsinternal.h"
|
||||||
#include "StatusQ/undefinedfilter.h"
|
#include "StatusQ/undefinedfilter.h"
|
||||||
|
@ -70,7 +69,6 @@ public:
|
||||||
|
|
||||||
qmlRegisterType<ObjectProxyModel>("StatusQ", 0, 1, "ObjectProxyModel");
|
qmlRegisterType<ObjectProxyModel>("StatusQ", 0, 1, "ObjectProxyModel");
|
||||||
qmlRegisterType<LeftJoinModel>("StatusQ", 0, 1, "LeftJoinModel");
|
qmlRegisterType<LeftJoinModel>("StatusQ", 0, 1, "LeftJoinModel");
|
||||||
qmlRegisterType<SubmodelProxyModel>("StatusQ", 0, 1, "SubmodelProxyModel");
|
|
||||||
qmlRegisterType<RoleRename>("StatusQ", 0, 1, "RoleRename");
|
qmlRegisterType<RoleRename>("StatusQ", 0, 1, "RoleRename");
|
||||||
qmlRegisterType<RolesRenamingModel>("StatusQ", 0, 1, "RolesRenamingModel");
|
qmlRegisterType<RolesRenamingModel>("StatusQ", 0, 1, "RolesRenamingModel");
|
||||||
qmlRegisterType<SumAggregator>("StatusQ", 0, 1, "SumAggregator");
|
qmlRegisterType<SumAggregator>("StatusQ", 0, 1, "SumAggregator");
|
||||||
|
|
|
@ -1,366 +0,0 @@
|
||||||
#include "StatusQ/submodelproxymodel.h"
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QQmlComponent>
|
|
||||||
#include <QQmlContext>
|
|
||||||
#include <QQmlEngine>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr const auto roleSuffix = "Role";
|
|
||||||
|
|
||||||
void emptyMessageHandler(QtMsgType type, const QMessageLogContext& context,
|
|
||||||
const QString& msg)
|
|
||||||
{
|
|
||||||
Q_UNUSED(type)
|
|
||||||
Q_UNUSED(context)
|
|
||||||
Q_UNUSED(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SubmodelProxyModel::SubmodelProxyModel(QObject* parent)
|
|
||||||
: QIdentityProxyModel{parent}
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant SubmodelProxyModel::data(const QModelIndex& index, int role) const
|
|
||||||
{
|
|
||||||
if (!checkIndex(index, CheckIndexOption::IndexIsValid))
|
|
||||||
return {};
|
|
||||||
|
|
||||||
if (m_delegateModel && m_submodelRole && role == m_submodelRole) {
|
|
||||||
auto submodel = QIdentityProxyModel::data(index, role);
|
|
||||||
|
|
||||||
QObject* submodelObj = submodel.value<QObject*>();
|
|
||||||
|
|
||||||
if (submodelObj == nullptr) {
|
|
||||||
qWarning("Submodel must be a QObject-based type!");
|
|
||||||
return submodel;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant proxyVariant;
|
|
||||||
|
|
||||||
auto& entry = m_container[index.row()];
|
|
||||||
|
|
||||||
if (entry) {
|
|
||||||
proxyVariant = QVariant::fromValue(entry.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (proxyVariant.isValid())
|
|
||||||
return proxyVariant;
|
|
||||||
|
|
||||||
auto creationContext = m_delegateModel->creationContext();
|
|
||||||
auto parentContext = creationContext
|
|
||||||
? creationContext : m_delegateModel->engine()->rootContext();
|
|
||||||
|
|
||||||
auto context = new QQmlContext(parentContext, submodelObj);
|
|
||||||
|
|
||||||
// Make sure that wrapper is destroyed before it receives signal related
|
|
||||||
// to submodel's destruction. Otherwise injected context property may
|
|
||||||
// be cleared causing warnings related to accessing null from qml.
|
|
||||||
connect(submodelObj, &QObject::destroyed, this,
|
|
||||||
[context = QPointer(context)](auto obj) {
|
|
||||||
delete context.data();
|
|
||||||
});
|
|
||||||
|
|
||||||
context->setContextProperty(QStringLiteral("submodel"), submodel);
|
|
||||||
|
|
||||||
QObject* instance = m_delegateModel->create(context);
|
|
||||||
QVariant wrappedInstance = QVariant::fromValue(instance);
|
|
||||||
|
|
||||||
if (m_additionalRolesMap.size()) {
|
|
||||||
QObject* connector = m_connector->createWithInitialProperties(
|
|
||||||
{ { "target", QVariant::fromValue(instance) } });
|
|
||||||
connector->setParent(instance);
|
|
||||||
|
|
||||||
connect(connector, SIGNAL(customRoleChanged(QObject*,int)),
|
|
||||||
this, SLOT(onCustomRoleChanged(QObject*,int)));
|
|
||||||
}
|
|
||||||
|
|
||||||
m_container[index.row()].reset(instance);
|
|
||||||
return wrappedInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_submodelRole && role >= m_additionalRolesOffset
|
|
||||||
&& role < m_additionalRolesOffset + m_additionalRolesMap.size())
|
|
||||||
{
|
|
||||||
auto submodel = data(index, *m_submodelRole);
|
|
||||||
|
|
||||||
auto submodelObj = submodel.value<QObject*>();
|
|
||||||
|
|
||||||
if (submodelObj == nullptr) {
|
|
||||||
qWarning("Submodel must be a QObject-based type!");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return submodelObj->property(m_roleNames[role] + roleSuffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
return QIdentityProxyModel::data(index, role);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SubmodelProxyModel::setSourceModel(QAbstractItemModel* model)
|
|
||||||
{
|
|
||||||
if (sourceModel() != nullptr || m_sourceModelDeleted) {
|
|
||||||
qWarning("Changing source model is not supported!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sourceModel() == model)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_container.setModel(model);
|
|
||||||
|
|
||||||
// Workaround for QTBUG-57971
|
|
||||||
if (model->roleNames().isEmpty())
|
|
||||||
connect(model, &QAbstractItemModel::rowsInserted,
|
|
||||||
this, &SubmodelProxyModel::initRoles);
|
|
||||||
|
|
||||||
connect(model, &QObject::destroyed, this, [this] {
|
|
||||||
this->m_sourceModelDeleted = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (m_delegateModel)
|
|
||||||
m_additionalRoles = fetchAdditionalRoles(m_delegateModel);
|
|
||||||
|
|
||||||
QIdentityProxyModel::setSourceModel(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> SubmodelProxyModel::roleNames() const
|
|
||||||
{
|
|
||||||
return m_roleNames.isEmpty() && sourceModel()
|
|
||||||
? sourceModel()->roleNames() : m_roleNames;;
|
|
||||||
}
|
|
||||||
|
|
||||||
QQmlComponent* SubmodelProxyModel::delegateModel() const
|
|
||||||
{
|
|
||||||
return m_delegateModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SubmodelProxyModel::setDelegateModel(QQmlComponent* delegateModel)
|
|
||||||
{
|
|
||||||
if (m_delegateModel != nullptr) {
|
|
||||||
qWarning("Changing delegate model is not supported!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_delegateModel == delegateModel)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (sourceModel() != nullptr) {
|
|
||||||
QStringList additionalRoles = fetchAdditionalRoles(delegateModel);
|
|
||||||
|
|
||||||
if (m_additionalRoles == additionalRoles) {
|
|
||||||
m_delegateModel = delegateModel;
|
|
||||||
|
|
||||||
if (m_submodelRole && rowCount() && columnCount()) {
|
|
||||||
emit dataChanged(index(0, 0),
|
|
||||||
index(rowCount() - 1, columnCount() - 1),
|
|
||||||
{ *m_submodelRole });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
beginResetModel();
|
|
||||||
m_delegateModel = delegateModel;
|
|
||||||
m_additionalRoles = additionalRoles;
|
|
||||||
endResetModel();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
m_delegateModel = delegateModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit delegateModelChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString& SubmodelProxyModel::submodelRoleName() const
|
|
||||||
{
|
|
||||||
return m_submodelRoleName;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SubmodelProxyModel::setSubmodelRoleName(const QString& sumodelRoleName)
|
|
||||||
{
|
|
||||||
if (m_submodelRoleName.isEmpty() && sumodelRoleName.isEmpty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!m_submodelRoleName.isEmpty()) {
|
|
||||||
qWarning("Changing submodel role name is not supported!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_submodelRoleName = sumodelRoleName;
|
|
||||||
|
|
||||||
if (sourceModel()) {
|
|
||||||
m_submodelRole = findSubmodelRole(sourceModel()->roleNames(),
|
|
||||||
sumodelRoleName);
|
|
||||||
|
|
||||||
if (rowCount() && columnCount()) {
|
|
||||||
emit dataChanged(index(0, 0),
|
|
||||||
index(rowCount() - 1, columnCount() - 1),
|
|
||||||
{ *m_submodelRole });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emit submodelRoleNameChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SubmodelProxyModel::resetInternalData()
|
|
||||||
{
|
|
||||||
QIdentityProxyModel::resetInternalData();
|
|
||||||
updateRoleNames();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SubmodelProxyModel::onCustomRoleChanged(QObject* source, int role)
|
|
||||||
{
|
|
||||||
if (!m_dataChangedQueued) {
|
|
||||||
m_dataChangedQueued = true;
|
|
||||||
QMetaObject::invokeMethod(this, "emitAllDataChanged", Qt::QueuedConnection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SubmodelProxyModel::emitAllDataChanged()
|
|
||||||
{
|
|
||||||
m_dataChangedQueued = false;
|
|
||||||
auto count = rowCount();
|
|
||||||
|
|
||||||
if (count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
QVector<int> roles(m_additionalRolesMap.cbegin(),
|
|
||||||
m_additionalRolesMap.cend());
|
|
||||||
|
|
||||||
emit this->dataChanged(index(0, 0), index(count - 1, 0), roles);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SubmodelProxyModel::initRoles()
|
|
||||||
{
|
|
||||||
disconnect(sourceModel(), &QAbstractItemModel::rowsInserted,
|
|
||||||
this, &SubmodelProxyModel::initRoles);
|
|
||||||
|
|
||||||
resetInternalData();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SubmodelProxyModel::updateRoleNames()
|
|
||||||
{
|
|
||||||
if (sourceModel() == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto roles = sourceModel()->roleNames();
|
|
||||||
|
|
||||||
if (roles.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_submodelRole = findSubmodelRole(roles, m_submodelRoleName);
|
|
||||||
|
|
||||||
const auto keys = roles.keys();
|
|
||||||
const auto maxElementIt = std::max_element(keys.begin(), keys.end());
|
|
||||||
|
|
||||||
Q_ASSERT(maxElementIt != keys.end());
|
|
||||||
|
|
||||||
auto maxRoleKey = *maxElementIt;
|
|
||||||
m_additionalRolesOffset = maxRoleKey + 1;
|
|
||||||
|
|
||||||
m_additionalRolesMap.clear();
|
|
||||||
|
|
||||||
for (auto& additionalRole : qAsConst(m_additionalRoles)) {
|
|
||||||
auto roleKey = ++maxRoleKey;
|
|
||||||
|
|
||||||
roles.insert(roleKey, additionalRole.toUtf8());
|
|
||||||
m_additionalRolesMap.insert(additionalRole, roleKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_roleNames = roles;
|
|
||||||
|
|
||||||
if (m_delegateModel == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_connector = buildConnectorComponent(m_additionalRolesMap,
|
|
||||||
m_delegateModel->engine(),
|
|
||||||
m_delegateModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList SubmodelProxyModel::fetchAdditionalRoles(
|
|
||||||
QQmlComponent* delegateComponent)
|
|
||||||
{
|
|
||||||
auto creationContext = delegateComponent->creationContext();
|
|
||||||
auto parentContext = creationContext
|
|
||||||
? creationContext : delegateComponent->engine()->rootContext();
|
|
||||||
|
|
||||||
auto context = std::make_unique<QQmlContext>(parentContext);
|
|
||||||
|
|
||||||
// The delegate object is created in order to inspect properties. It may
|
|
||||||
// be not properly initialized because of e.g. lack of context properties
|
|
||||||
// containing submodel. To avoid warnings, they are muted by setting empty
|
|
||||||
// message handler temporarily.
|
|
||||||
QtMessageHandler originalHandler = qInstallMessageHandler(
|
|
||||||
emptyMessageHandler);
|
|
||||||
std::unique_ptr<QObject> instance(delegateComponent->create(context.get()));
|
|
||||||
qInstallMessageHandler(originalHandler);
|
|
||||||
|
|
||||||
const QMetaObject* meta = instance->metaObject();
|
|
||||||
|
|
||||||
QStringList additionalRoles;
|
|
||||||
|
|
||||||
for (auto i = meta->propertyOffset(); i < meta->propertyCount(); ++i) {
|
|
||||||
const QLatin1String propertyName(meta->property(i).name());
|
|
||||||
bool isRole = propertyName.endsWith(QLatin1String(roleSuffix));
|
|
||||||
|
|
||||||
if (!isRole)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
additionalRoles << propertyName.chopped(qstrlen(roleSuffix));
|
|
||||||
}
|
|
||||||
|
|
||||||
return additionalRoles;
|
|
||||||
}
|
|
||||||
|
|
||||||
QQmlComponent* SubmodelProxyModel::buildConnectorComponent(
|
|
||||||
const QHash<QString, int>& additionalRoles, QQmlEngine* engine,
|
|
||||||
QObject* parent)
|
|
||||||
{
|
|
||||||
QString connectorCode = R"(
|
|
||||||
import QtQml 2.15
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
signal customRoleChanged(source: QtObject, role: int)
|
|
||||||
)";
|
|
||||||
|
|
||||||
for (auto i = additionalRoles.cbegin(); i != additionalRoles.cend(); ++i) {
|
|
||||||
int role = i.value();
|
|
||||||
|
|
||||||
auto upperCaseRole = i.key();
|
|
||||||
upperCaseRole[0] = upperCaseRole[0].toUpper();
|
|
||||||
|
|
||||||
connectorCode += QString(R"(
|
|
||||||
function on%1RoleChanged() { customRoleChanged(target, %2) }
|
|
||||||
)").arg(upperCaseRole).arg(role);
|
|
||||||
}
|
|
||||||
|
|
||||||
connectorCode += "}";
|
|
||||||
|
|
||||||
auto connector = new QQmlComponent(engine, parent);
|
|
||||||
connector->setData(connectorCode.toUtf8(), {});
|
|
||||||
|
|
||||||
return connector;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<int> SubmodelProxyModel::findSubmodelRole(
|
|
||||||
const QHash<int, QByteArray>& roleNames,
|
|
||||||
const QString& submodelRoleName)
|
|
||||||
{
|
|
||||||
if (roleNames.empty() || submodelRoleName.isEmpty())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
auto submodelKeys = roleNames.keys(m_submodelRoleName.toUtf8());
|
|
||||||
auto submodelKeysCount = submodelKeys.size();
|
|
||||||
|
|
||||||
if (submodelKeysCount == 1) {
|
|
||||||
return submodelKeys.first();
|
|
||||||
} else if (submodelKeysCount == 0) {
|
|
||||||
qWarning() << "Submodel role not found!";
|
|
||||||
return {};
|
|
||||||
} else {
|
|
||||||
qWarning() << "Malformed source model - multiple roles found for given "
|
|
||||||
"submodel role name!";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -64,10 +64,6 @@ add_executable(LeftJoinModelTest tst_LeftJoinModel.cpp)
|
||||||
target_link_libraries(LeftJoinModelTest PRIVATE StatusQ StatusQTestLib)
|
target_link_libraries(LeftJoinModelTest PRIVATE StatusQ StatusQTestLib)
|
||||||
add_test(NAME LeftJoinModelTest COMMAND LeftJoinModelTest)
|
add_test(NAME LeftJoinModelTest COMMAND LeftJoinModelTest)
|
||||||
|
|
||||||
add_executable(SubmodelProxyModelTest tst_SubmodelProxyModel.cpp)
|
|
||||||
target_link_libraries(SubmodelProxyModelTest PRIVATE StatusQ StatusQTestLib)
|
|
||||||
add_test(NAME SubmodelProxyModelTest COMMAND SubmodelProxyModelTest)
|
|
||||||
|
|
||||||
add_executable(ObjectProxyModelTest tst_ObjectProxyModel.cpp)
|
add_executable(ObjectProxyModelTest tst_ObjectProxyModel.cpp)
|
||||||
target_link_libraries(ObjectProxyModelTest PRIVATE StatusQ StatusQTestLib)
|
target_link_libraries(ObjectProxyModelTest PRIVATE StatusQ StatusQTestLib)
|
||||||
add_test(NAME ObjectProxyModelTest COMMAND ObjectProxyModelTest)
|
add_test(NAME ObjectProxyModelTest COMMAND ObjectProxyModelTest)
|
||||||
|
|
|
@ -1,719 +0,0 @@
|
||||||
#include <QSignalSpy>
|
|
||||||
#include <QTest>
|
|
||||||
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QQmlComponent>
|
|
||||||
#include <QQmlContext>
|
|
||||||
#include <QQmlEngine>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include <StatusQ/submodelproxymodel.h>
|
|
||||||
|
|
||||||
#include <TestHelpers/listmodelwrapper.h>
|
|
||||||
#include <TestHelpers/modelsignalsspy.h>
|
|
||||||
#include <TestHelpers/modeltestutils.h>
|
|
||||||
|
|
||||||
class TestSubmodelProxyModel: public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
int roleForName(const QHash<int, QByteArray>& roles, const QByteArray& name) const
|
|
||||||
{
|
|
||||||
auto keys = roles.keys(name);
|
|
||||||
|
|
||||||
if (keys.empty())
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return keys.first();
|
|
||||||
}
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void basicTest() {
|
|
||||||
QQmlEngine engine;
|
|
||||||
QQmlComponent delegate(&engine);
|
|
||||||
|
|
||||||
auto delegateData = R"(
|
|
||||||
import QtQml 2.15
|
|
||||||
QtObject {
|
|
||||||
property var count: submodel.count
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
delegate.setData(delegateData, QUrl());
|
|
||||||
|
|
||||||
SubmodelProxyModel model;
|
|
||||||
|
|
||||||
auto source = R"([
|
|
||||||
{ balances: [ { balance: 4 } ], name: "name 1" },
|
|
||||||
{ balances: [ { balance: 4 }, {balance: 43} ], name: "name 2" },
|
|
||||||
{ balances: [], name: "name 3" }
|
|
||||||
])";
|
|
||||||
|
|
||||||
ListModelWrapper sourceModel(engine, source);
|
|
||||||
|
|
||||||
QSignalSpy sourceModelChangedSpy(
|
|
||||||
&model, &SubmodelProxyModel::sourceModelChanged);
|
|
||||||
QSignalSpy delegateChangedSpy(
|
|
||||||
&model, &SubmodelProxyModel::delegateModelChanged);
|
|
||||||
QSignalSpy submodelRoleNameChangedSpy(
|
|
||||||
&model, &SubmodelProxyModel::submodelRoleNameChanged);
|
|
||||||
|
|
||||||
model.setSourceModel(sourceModel);
|
|
||||||
model.setDelegateModel(&delegate);
|
|
||||||
model.setSubmodelRoleName(QStringLiteral("balances"));
|
|
||||||
|
|
||||||
QCOMPARE(sourceModelChangedSpy.count(), 1);
|
|
||||||
QCOMPARE(delegateChangedSpy.count(), 1);
|
|
||||||
QCOMPARE(submodelRoleNameChangedSpy.count(), 1);
|
|
||||||
|
|
||||||
QCOMPARE(model.sourceModel(), sourceModel);
|
|
||||||
QCOMPARE(model.delegateModel(), &delegate);
|
|
||||||
QCOMPARE(model.submodelRoleName(), QStringLiteral("balances"));
|
|
||||||
|
|
||||||
QCOMPARE(model.rowCount(), 3);
|
|
||||||
|
|
||||||
QCOMPARE(model.data(model.index(0, 0), sourceModel.role("name")), "name 1");
|
|
||||||
QVERIFY(model.data(model.index(0, 0),
|
|
||||||
sourceModel.role("balances")).isValid());
|
|
||||||
|
|
||||||
auto object = model.data(model.index(0, 0),
|
|
||||||
sourceModel.role("balances")).value<QObject*>();
|
|
||||||
QVERIFY(object);
|
|
||||||
|
|
||||||
auto context = QQmlEngine::contextForObject(object);
|
|
||||||
|
|
||||||
QVERIFY(context->contextProperty("submodel").value<QObject*>() != nullptr);
|
|
||||||
QCOMPARE(object->property("count"), 1);
|
|
||||||
QCOMPARE(QQmlEngine::objectOwnership(object),
|
|
||||||
QQmlEngine::CppOwnership);
|
|
||||||
}
|
|
||||||
|
|
||||||
void submodelTypeTest() {
|
|
||||||
QQmlEngine engine;
|
|
||||||
QQmlComponent delegate(&engine);
|
|
||||||
|
|
||||||
auto delegateData = R"(
|
|
||||||
import QtQml 2.15
|
|
||||||
QtObject {
|
|
||||||
property var count: submodel.count
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
delegate.setData(delegateData, QUrl());
|
|
||||||
|
|
||||||
SubmodelProxyModel model;
|
|
||||||
|
|
||||||
auto source = R"([
|
|
||||||
{ balances: [ { balance: 4 } ], name: "name 1" }
|
|
||||||
])";
|
|
||||||
|
|
||||||
ListModelWrapper sourceModel(engine, source);
|
|
||||||
model.setSourceModel(sourceModel);
|
|
||||||
model.setDelegateModel(&delegate);
|
|
||||||
model.setSubmodelRoleName(QStringLiteral("balances"));
|
|
||||||
|
|
||||||
QCOMPARE(model.rowCount(), 1);
|
|
||||||
|
|
||||||
QVariant balances1 = model.data(model.index(0, 0),
|
|
||||||
sourceModel.role("balances"));
|
|
||||||
QVERIFY(balances1.isValid());
|
|
||||||
|
|
||||||
QVariant balances2 = model.data(model.index(0, 0),
|
|
||||||
sourceModel.role("balances"));
|
|
||||||
QVERIFY(balances2.isValid());
|
|
||||||
|
|
||||||
// SubmodelProxyModel may create proxy objects on demand, then first
|
|
||||||
// call to data(...) returns freshly created object, the next calls
|
|
||||||
// related to the same row should return cached object. It's important
|
|
||||||
// to have QVariant type identical in both cases. E.g. returning raw
|
|
||||||
// pointer in first call and pointer wrapped into QPointer in the next
|
|
||||||
// one leads to problems in UI components in some scenarios even if
|
|
||||||
// those QVariant types are automatically convertible.
|
|
||||||
QCOMPARE(balances2.type(), balances1.type());
|
|
||||||
|
|
||||||
// Check if the same instance is returned.
|
|
||||||
QCOMPARE(balances2.value<QObject*>(), balances1.value<QObject*>());
|
|
||||||
}
|
|
||||||
|
|
||||||
void usingNonObjectSubmodelRoleTest() {
|
|
||||||
QQmlEngine engine;
|
|
||||||
QQmlComponent delegate(&engine);
|
|
||||||
|
|
||||||
auto delegateData = R"(
|
|
||||||
import QtQml 2.15
|
|
||||||
QtObject {
|
|
||||||
property var count: submodel.count
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
delegate.setData(delegateData, QUrl());
|
|
||||||
|
|
||||||
SubmodelProxyModel model;
|
|
||||||
|
|
||||||
auto source = R"([
|
|
||||||
{ balances: 1, name: "name 1" },
|
|
||||||
{ balances: 2, name: "name 2" },
|
|
||||||
{ balances: 3, name: "name 3" }
|
|
||||||
])";
|
|
||||||
|
|
||||||
ListModelWrapper sourceModel(engine, source);
|
|
||||||
|
|
||||||
QTest::ignoreMessage(QtWarningMsg,
|
|
||||||
"Submodel must be a QObject-based type!");
|
|
||||||
|
|
||||||
model.setSourceModel(sourceModel);
|
|
||||||
model.setDelegateModel(&delegate);
|
|
||||||
model.setSubmodelRoleName(QStringLiteral("balances"));
|
|
||||||
|
|
||||||
QCOMPARE(model.rowCount(), 3);
|
|
||||||
|
|
||||||
QVERIFY(model.data(model.index(0, 0),
|
|
||||||
sourceModel.role("balances")).isValid());
|
|
||||||
}
|
|
||||||
|
|
||||||
void deletingDelegateTest() {
|
|
||||||
QQmlEngine engine;
|
|
||||||
auto delegate = std::make_unique<QQmlComponent>(&engine);
|
|
||||||
|
|
||||||
delegate->setData(QByteArrayLiteral(R"(
|
|
||||||
import QtQml 2.15
|
|
||||||
|
|
||||||
QtObject {
|
|
||||||
property var sub: submodel
|
|
||||||
}
|
|
||||||
)"), QUrl());
|
|
||||||
|
|
||||||
SubmodelProxyModel model;
|
|
||||||
ListModelWrapper sourceModel(engine, QJsonArray {
|
|
||||||
QJsonObject {{ "balances", 11 }, { "name", "name 1" }},
|
|
||||||
QJsonObject {{ "balances", 12 }, { "name", "name 2" }},
|
|
||||||
QJsonObject {{ "balances", 123}, { "name", "name 3" }}
|
|
||||||
});
|
|
||||||
|
|
||||||
model.setSourceModel(sourceModel);
|
|
||||||
model.setDelegateModel(delegate.get());
|
|
||||||
model.setSubmodelRoleName(QStringLiteral("balances"));
|
|
||||||
|
|
||||||
QSignalSpy delegateModelChangedSpy(
|
|
||||||
&model, &SubmodelProxyModel::delegateModelChanged);
|
|
||||||
QSignalSpy dataChangedSpy(
|
|
||||||
&model, &SubmodelProxyModel::dataChanged);
|
|
||||||
|
|
||||||
delegate.reset();
|
|
||||||
|
|
||||||
QCOMPARE(delegateModelChangedSpy.count(), 0);
|
|
||||||
QCOMPARE(dataChangedSpy.count(), 0);
|
|
||||||
|
|
||||||
QCOMPARE(model.rowCount(), 3);
|
|
||||||
QCOMPARE(model.data(model.index(0, 0),
|
|
||||||
sourceModel.role("balances")), 11);
|
|
||||||
}
|
|
||||||
|
|
||||||
void deletingSourceModelTest() {
|
|
||||||
QQmlEngine engine;
|
|
||||||
QQmlComponent delegate(&engine);
|
|
||||||
|
|
||||||
delegate.setData(QByteArrayLiteral(R"(
|
|
||||||
import QtQml 2.15
|
|
||||||
|
|
||||||
QtObject {
|
|
||||||
property var sub: submodel
|
|
||||||
}
|
|
||||||
)"), QUrl());
|
|
||||||
|
|
||||||
SubmodelProxyModel model;
|
|
||||||
|
|
||||||
auto sourceModel = std::make_unique<ListModelWrapper>(engine,
|
|
||||||
QJsonArray {
|
|
||||||
QJsonObject {{ "balances", 11 }, { "name", "name 1" }},
|
|
||||||
QJsonObject {{ "balances", 12 }, { "name", "name 2" }},
|
|
||||||
QJsonObject {{ "balances", 123}, { "name", "name 3" }}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
model.setSourceModel(sourceModel->model());
|
|
||||||
model.setDelegateModel(&delegate);
|
|
||||||
model.setSubmodelRoleName(QStringLiteral("balances"));
|
|
||||||
|
|
||||||
sourceModel.reset();
|
|
||||||
|
|
||||||
QCOMPARE(model.rowCount(), 0);
|
|
||||||
|
|
||||||
QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*"));
|
|
||||||
QCOMPARE(model.data(model.index(0, 0), 0), {});
|
|
||||||
}
|
|
||||||
|
|
||||||
void settingUndefinedSubmodelRoleNameTest() {
|
|
||||||
QQmlEngine engine;
|
|
||||||
auto delegate = std::make_unique<QQmlComponent>(&engine);
|
|
||||||
|
|
||||||
delegate->setData(QByteArrayLiteral(R"(
|
|
||||||
import QtQml 2.15
|
|
||||||
|
|
||||||
QtObject {
|
|
||||||
property var sub: submodel
|
|
||||||
}
|
|
||||||
)"), QUrl());
|
|
||||||
|
|
||||||
SubmodelProxyModel model;
|
|
||||||
ListModelWrapper sourceModel(engine, QJsonArray {
|
|
||||||
QJsonObject {{ "balances", 11 }, { "name", "name 1" }},
|
|
||||||
QJsonObject {{ "balances", 12 }, { "name", "name 2" }},
|
|
||||||
QJsonObject {{ "balances", 123}, { "name", "name 3" }}
|
|
||||||
});
|
|
||||||
|
|
||||||
model.setSourceModel(sourceModel);
|
|
||||||
model.setDelegateModel(delegate.get());
|
|
||||||
|
|
||||||
QTest::ignoreMessage(QtWarningMsg, "Submodel role not found!");
|
|
||||||
|
|
||||||
model.setSubmodelRoleName(QStringLiteral("undefined"));
|
|
||||||
|
|
||||||
QCOMPARE(model.rowCount(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addingNewRoleToTopLevelModelTest() {
|
|
||||||
QQmlEngine engine;
|
|
||||||
auto delegate = std::make_unique<QQmlComponent>(&engine);
|
|
||||||
|
|
||||||
delegate->setData(QByteArrayLiteral(R"(
|
|
||||||
import QtQml.Models 2.15
|
|
||||||
|
|
||||||
ListModel {
|
|
||||||
id: delegateRoot
|
|
||||||
|
|
||||||
property var sub: submodel
|
|
||||||
|
|
||||||
property int extraValue: submodel.rowCount()
|
|
||||||
readonly property alias extraValueRole: delegateRoot.extraValue
|
|
||||||
}
|
|
||||||
)"), QUrl());
|
|
||||||
|
|
||||||
SubmodelProxyModel model;
|
|
||||||
|
|
||||||
ListModelWrapper sourceModel(engine, R"([
|
|
||||||
{ "balances": [], "name": "name 1" },
|
|
||||||
{ "balances": [ { balance: 1 } ], "name": "name 2" },
|
|
||||||
{ "balances": [], "name": "name 3" }
|
|
||||||
])");
|
|
||||||
|
|
||||||
model.setSourceModel(sourceModel);
|
|
||||||
model.setDelegateModel(delegate.get());
|
|
||||||
|
|
||||||
model.setSubmodelRoleName(QStringLiteral("balances"));
|
|
||||||
|
|
||||||
ListModelWrapper expected(engine, R"([
|
|
||||||
{ "balances": [], "name": "name 1", "extraValue": 0 },
|
|
||||||
{ "balances": [], "name": "name 2", "extraValue": 1 },
|
|
||||||
{ "balances": [], "name": "name 3", "extraValue": 0 }
|
|
||||||
])");
|
|
||||||
|
|
||||||
QCOMPARE(model.rowCount(), 3);
|
|
||||||
|
|
||||||
auto roles = model.roleNames();
|
|
||||||
QCOMPARE(roles.size(), 3);
|
|
||||||
QVERIFY(isSame(&model, expected));
|
|
||||||
|
|
||||||
ModelSignalsSpy signalsSpy(&model);
|
|
||||||
|
|
||||||
QVariant wrapperVariant = model.data(model.index(0, 0),
|
|
||||||
roleForName(roles, "balances"));
|
|
||||||
QObject* wrapper = wrapperVariant.value<QObject*>();
|
|
||||||
QVERIFY(wrapper != nullptr);
|
|
||||||
wrapper->setProperty("extraValue", 42);
|
|
||||||
|
|
||||||
ListModelWrapper expected2(engine, R"([
|
|
||||||
{ "balances": [], "name": "name 1", "extraValue": 42 },
|
|
||||||
{ "balances": [], "name": "name 2", "extraValue": 1 },
|
|
||||||
{ "balances": [], "name": "name 3", "extraValue": 0 }
|
|
||||||
])");
|
|
||||||
|
|
||||||
// dataChanged signal emission is scheduled to event loop, not called
|
|
||||||
// immediately
|
|
||||||
QCOMPARE(signalsSpy.count(), 0);
|
|
||||||
|
|
||||||
QVERIFY(QTest::qWaitFor([&signalsSpy]() {
|
|
||||||
return signalsSpy.count() == 1;
|
|
||||||
}));
|
|
||||||
|
|
||||||
QCOMPARE(signalsSpy.count(), 1);
|
|
||||||
QCOMPARE(signalsSpy.dataChangedSpy.count(), 1);
|
|
||||||
|
|
||||||
QCOMPARE(signalsSpy.dataChangedSpy.at(0).at(0), model.index(0, 0));
|
|
||||||
QCOMPARE(signalsSpy.dataChangedSpy.at(0).at(1),
|
|
||||||
model.index(model.rowCount() - 1, 0));
|
|
||||||
|
|
||||||
QVector<int> expectedChangedRoles = { roleForName(roles, "extraValue") };
|
|
||||||
QCOMPARE(signalsSpy.dataChangedSpy.at(0).at(2).value<QVector<int>>(),
|
|
||||||
expectedChangedRoles);
|
|
||||||
|
|
||||||
QVERIFY(isSame(&model, expected2));
|
|
||||||
}
|
|
||||||
|
|
||||||
void additionalRoleDataChangedWhenEmptyTest() {
|
|
||||||
QQmlEngine engine;
|
|
||||||
auto delegate = std::make_unique<QQmlComponent>(&engine);
|
|
||||||
|
|
||||||
delegate->setData(QByteArrayLiteral(R"(
|
|
||||||
import QtQml.Models 2.15
|
|
||||||
|
|
||||||
ListModel {
|
|
||||||
property int extraValueRole: 0
|
|
||||||
}
|
|
||||||
)"), QUrl());
|
|
||||||
|
|
||||||
ListModelWrapper sourceModel(engine, R"([
|
|
||||||
{ "balances": [], "name": "name 1" }
|
|
||||||
])");
|
|
||||||
|
|
||||||
SubmodelProxyModel model;
|
|
||||||
model.setSourceModel(sourceModel);
|
|
||||||
model.setDelegateModel(delegate.get());
|
|
||||||
model.setSubmodelRoleName(QStringLiteral("balances"));
|
|
||||||
|
|
||||||
QCOMPARE(model.rowCount(), 1);
|
|
||||||
|
|
||||||
auto roles = model.roleNames();
|
|
||||||
QCOMPARE(roles.size(), 3);
|
|
||||||
|
|
||||||
ModelSignalsSpy signalsSpy(&model);
|
|
||||||
|
|
||||||
QVariant wrapperVariant = model.data(model.index(0, 0),
|
|
||||||
roleForName(roles, "balances"));
|
|
||||||
QObject* wrapper = wrapperVariant.value<QObject*>();
|
|
||||||
QVERIFY(wrapper != nullptr);
|
|
||||||
|
|
||||||
// dataChanged signal emission is scheduled to event loop, not called
|
|
||||||
// immediately. In the meantime the source may be cleared and then no
|
|
||||||
// dataChanged event should be emited.
|
|
||||||
wrapper->setProperty("extraValueRole", 42);
|
|
||||||
|
|
||||||
sourceModel.remove(0);
|
|
||||||
|
|
||||||
QCOMPARE(signalsSpy.count(), 2);
|
|
||||||
QCOMPARE(signalsSpy.rowsAboutToBeRemovedSpy.count(), 1);
|
|
||||||
QCOMPARE(signalsSpy.rowsRemovedSpy.count(), 1);
|
|
||||||
|
|
||||||
QTest::qWait(100);
|
|
||||||
QCOMPARE(signalsSpy.count(), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void modelResetWhenRoleChangedTest() {
|
|
||||||
QQmlEngine engine;
|
|
||||||
auto delegateWithRole = std::make_unique<QQmlComponent>(&engine);
|
|
||||||
|
|
||||||
delegateWithRole->setData(QByteArrayLiteral(R"(
|
|
||||||
import QtQml.Models 2.15
|
|
||||||
|
|
||||||
ListModel {
|
|
||||||
property int extraValueRole: 0
|
|
||||||
}
|
|
||||||
)"), QUrl());
|
|
||||||
|
|
||||||
auto delegateNoRole = std::make_unique<QQmlComponent>(&engine);
|
|
||||||
|
|
||||||
delegateNoRole->setData(QByteArrayLiteral(R"(
|
|
||||||
import QtQml.Models 2.15
|
|
||||||
|
|
||||||
ListModel {}
|
|
||||||
)"), QUrl());
|
|
||||||
|
|
||||||
ListModelWrapper sourceModel(engine, R"([
|
|
||||||
{ "balances": [], "name": "name 1" }
|
|
||||||
])");
|
|
||||||
|
|
||||||
// 1. set source, 2. set delegate model, 3. set submodel role name
|
|
||||||
{
|
|
||||||
SubmodelProxyModel model;
|
|
||||||
|
|
||||||
ModelSignalsSpy signalsSpy(&model);
|
|
||||||
|
|
||||||
model.setSourceModel(sourceModel);
|
|
||||||
|
|
||||||
QCOMPARE(signalsSpy.count(), 2);
|
|
||||||
QCOMPARE(signalsSpy.modelAboutToBeResetSpy.count(), 1);
|
|
||||||
QCOMPARE(signalsSpy.modelResetSpy.count(), 1);
|
|
||||||
QCOMPARE(model.roleNames().count(), 2);
|
|
||||||
|
|
||||||
model.setDelegateModel(delegateWithRole.get());
|
|
||||||
|
|
||||||
QCOMPARE(signalsSpy.count(), 4);
|
|
||||||
QCOMPARE(signalsSpy.modelAboutToBeResetSpy.count(), 2);
|
|
||||||
QCOMPARE(signalsSpy.modelResetSpy.count(), 2);
|
|
||||||
QCOMPARE(model.roleNames().count(), 3);
|
|
||||||
|
|
||||||
model.setSubmodelRoleName(QStringLiteral("balances"));
|
|
||||||
|
|
||||||
QCOMPARE(signalsSpy.count(), 5);
|
|
||||||
QCOMPARE(signalsSpy.dataChangedSpy.count(), 1);
|
|
||||||
QCOMPARE(signalsSpy.modelAboutToBeResetSpy.count(), 2);
|
|
||||||
QCOMPARE(signalsSpy.modelResetSpy.count(), 2);
|
|
||||||
QCOMPARE(model.roleNames().count(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. set delegate model, 2. set source, 3. set submodel role name
|
|
||||||
{
|
|
||||||
SubmodelProxyModel model;
|
|
||||||
|
|
||||||
ModelSignalsSpy signalsSpy(&model);
|
|
||||||
|
|
||||||
model.setDelegateModel(delegateWithRole.get());
|
|
||||||
|
|
||||||
QCOMPARE(signalsSpy.count(), 0);
|
|
||||||
QCOMPARE(model.roleNames().count(), 0);
|
|
||||||
|
|
||||||
model.setSourceModel(sourceModel);
|
|
||||||
|
|
||||||
QCOMPARE(signalsSpy.count(), 2);
|
|
||||||
QCOMPARE(signalsSpy.modelAboutToBeResetSpy.count(), 1);
|
|
||||||
QCOMPARE(signalsSpy.modelResetSpy.count(), 1);
|
|
||||||
QCOMPARE(model.roleNames().count(), 3);
|
|
||||||
|
|
||||||
model.setSubmodelRoleName(QStringLiteral("balances"));
|
|
||||||
|
|
||||||
QCOMPARE(signalsSpy.count(), 3);
|
|
||||||
QCOMPARE(signalsSpy.dataChangedSpy.count(), 1);
|
|
||||||
QCOMPARE(signalsSpy.modelAboutToBeResetSpy.count(), 1);
|
|
||||||
QCOMPARE(signalsSpy.modelResetSpy.count(), 1);
|
|
||||||
QCOMPARE(model.roleNames().count(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. set submodel role name, 2. set delegate model, 3. set source
|
|
||||||
{
|
|
||||||
SubmodelProxyModel model;
|
|
||||||
|
|
||||||
ModelSignalsSpy signalsSpy(&model);
|
|
||||||
|
|
||||||
model.setSubmodelRoleName(QStringLiteral("balances"));
|
|
||||||
model.setDelegateModel(delegateWithRole.get());
|
|
||||||
|
|
||||||
QCOMPARE(signalsSpy.count(), 0);
|
|
||||||
QCOMPARE(model.roleNames().count(), 0);
|
|
||||||
|
|
||||||
model.setSourceModel(sourceModel);
|
|
||||||
|
|
||||||
QCOMPARE(signalsSpy.count(), 2);
|
|
||||||
QCOMPARE(signalsSpy.modelAboutToBeResetSpy.count(), 1);
|
|
||||||
QCOMPARE(signalsSpy.modelResetSpy.count(), 1);
|
|
||||||
QCOMPARE(model.roleNames().count(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. set source, 2. set delegate model (no extra roles),
|
|
||||||
// 3. set submodel role name
|
|
||||||
{
|
|
||||||
SubmodelProxyModel model;
|
|
||||||
|
|
||||||
ModelSignalsSpy signalsSpy(&model);
|
|
||||||
|
|
||||||
model.setSourceModel(sourceModel);
|
|
||||||
|
|
||||||
QCOMPARE(signalsSpy.count(), 2);
|
|
||||||
QCOMPARE(signalsSpy.modelAboutToBeResetSpy.count(), 1);
|
|
||||||
QCOMPARE(signalsSpy.modelResetSpy.count(), 1);
|
|
||||||
QCOMPARE(model.roleNames().count(), 2);
|
|
||||||
|
|
||||||
model.setDelegateModel(delegateNoRole.get());
|
|
||||||
|
|
||||||
QCOMPARE(signalsSpy.count(), 2);
|
|
||||||
QCOMPARE(signalsSpy.modelAboutToBeResetSpy.count(), 1);
|
|
||||||
QCOMPARE(signalsSpy.modelResetSpy.count(), 1);
|
|
||||||
QCOMPARE(model.roleNames().count(), 2);
|
|
||||||
|
|
||||||
model.setSubmodelRoleName(QStringLiteral("balances"));
|
|
||||||
|
|
||||||
QCOMPARE(signalsSpy.count(), 3);
|
|
||||||
QCOMPARE(signalsSpy.dataChangedSpy.count(), 1);
|
|
||||||
QCOMPARE(signalsSpy.modelAboutToBeResetSpy.count(), 1);
|
|
||||||
QCOMPARE(signalsSpy.modelResetSpy.count(), 1);
|
|
||||||
QCOMPARE(model.roleNames().count(), 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubmodelProxyModel instantiates delegate model in order to inspect
|
|
||||||
// extra roles. This instantiation must be deferred until model is,
|
|
||||||
// available. Otherwise it may lead to accessing uninitialized external
|
|
||||||
// data within a delegate instance.
|
|
||||||
void deferredDelegateInstantiationTest() {
|
|
||||||
QQmlEngine engine;
|
|
||||||
|
|
||||||
QObject controlObject;
|
|
||||||
engine.rootContext()->setContextProperty("control", &controlObject);
|
|
||||||
|
|
||||||
auto delegate = std::make_unique<QQmlComponent>(&engine);
|
|
||||||
|
|
||||||
delegate->setData(QByteArrayLiteral(R"(
|
|
||||||
import QtQml.Models 2.15
|
|
||||||
import QtQml 2.15
|
|
||||||
|
|
||||||
ListModel {
|
|
||||||
property int extraValueRole: 0
|
|
||||||
|
|
||||||
Component.onCompleted: control.objectName = "instantiated"
|
|
||||||
}
|
|
||||||
)"), QUrl());
|
|
||||||
|
|
||||||
ListModelWrapper sourceModel(engine, R"([
|
|
||||||
{ "balances": [], "name": "name 1" }
|
|
||||||
])");
|
|
||||||
|
|
||||||
{
|
|
||||||
SubmodelProxyModel model;
|
|
||||||
model.setSourceModel(sourceModel);
|
|
||||||
QCOMPARE(controlObject.objectName(), "");
|
|
||||||
|
|
||||||
model.setDelegateModel(delegate.get());
|
|
||||||
QCOMPARE(controlObject.objectName(), "instantiated");
|
|
||||||
}
|
|
||||||
|
|
||||||
controlObject.setObjectName("");
|
|
||||||
|
|
||||||
{
|
|
||||||
SubmodelProxyModel model;
|
|
||||||
model.setDelegateModel(delegate.get());
|
|
||||||
QCOMPARE(controlObject.objectName(), "");
|
|
||||||
|
|
||||||
model.setSourceModel(sourceModel);
|
|
||||||
QCOMPARE(controlObject.objectName(), "instantiated");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sourceModelResetTest() {
|
|
||||||
class IdentityModel : public QIdentityProxyModel {};
|
|
||||||
|
|
||||||
QQmlEngine engine;
|
|
||||||
auto delegate = std::make_unique<QQmlComponent>(&engine);
|
|
||||||
|
|
||||||
delegate->setData(QByteArrayLiteral(R"(
|
|
||||||
import QtQml.Models 2.15
|
|
||||||
|
|
||||||
ListModel {
|
|
||||||
property int extraValueRole: 0
|
|
||||||
}
|
|
||||||
)"), QUrl());
|
|
||||||
|
|
||||||
ListModelWrapper sourceModel1(engine, R"([
|
|
||||||
{ "balances": [], "name": "name 1" }
|
|
||||||
])");
|
|
||||||
|
|
||||||
ListModelWrapper sourceModel2(engine, R"([
|
|
||||||
{ "key": "1", "balances": [], "name": "name 1", "color": "red" }
|
|
||||||
])");
|
|
||||||
|
|
||||||
IdentityModel identity;
|
|
||||||
identity.setSourceModel(sourceModel1);
|
|
||||||
|
|
||||||
SubmodelProxyModel model;
|
|
||||||
model.setSourceModel(&identity);
|
|
||||||
model.setDelegateModel(delegate.get());
|
|
||||||
model.setSubmodelRoleName(QStringLiteral("balances"));
|
|
||||||
|
|
||||||
QCOMPARE(model.rowCount(), 1);
|
|
||||||
auto roles = model.roleNames();
|
|
||||||
QCOMPARE(roles.size(), 3);
|
|
||||||
|
|
||||||
ModelSignalsSpy signalsSpy(&model);
|
|
||||||
|
|
||||||
identity.setSourceModel(sourceModel2);
|
|
||||||
|
|
||||||
QCOMPARE(signalsSpy.count(), 2);
|
|
||||||
QCOMPARE(signalsSpy.modelAboutToBeResetSpy.count(), 1);
|
|
||||||
QCOMPARE(signalsSpy.modelResetSpy.count(), 1);
|
|
||||||
|
|
||||||
QCOMPARE(model.rowCount(), 1);
|
|
||||||
roles = model.roleNames();
|
|
||||||
QCOMPARE(roles.size(), 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sourceModelLateRolesInitTest() {
|
|
||||||
QQmlEngine engine;
|
|
||||||
auto delegate = std::make_unique<QQmlComponent>(&engine);
|
|
||||||
|
|
||||||
delegate->setData(QByteArrayLiteral(R"(
|
|
||||||
import QtQml.Models 2.15
|
|
||||||
|
|
||||||
ListModel {
|
|
||||||
property int extraValueRole: 0
|
|
||||||
}
|
|
||||||
)"), QUrl());
|
|
||||||
|
|
||||||
ListModelWrapper sourceModel(engine, R"([])");
|
|
||||||
|
|
||||||
SubmodelProxyModel model;
|
|
||||||
model.setSourceModel(sourceModel);
|
|
||||||
model.setDelegateModel(delegate.get());
|
|
||||||
model.setSubmodelRoleName(QStringLiteral("balances"));
|
|
||||||
|
|
||||||
QCOMPARE(model.rowCount(), 0);
|
|
||||||
auto roles = model.roleNames();
|
|
||||||
QCOMPARE(roles.size(), 0);
|
|
||||||
|
|
||||||
ModelSignalsSpy signalsSpy(&model);
|
|
||||||
|
|
||||||
sourceModel.append(QJsonArray {
|
|
||||||
QJsonObject {{ "name", "D"}, { "balances", "d1" }},
|
|
||||||
QJsonObject {{ "name", "D"}, { "balances", "d2" }}
|
|
||||||
});
|
|
||||||
|
|
||||||
QCOMPARE(model.rowCount(), 2);
|
|
||||||
roles = model.roleNames();
|
|
||||||
QCOMPARE(roles.size(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
void multipleProxiesTest() {
|
|
||||||
QQmlEngine engine;
|
|
||||||
auto delegate1 = std::make_unique<QQmlComponent>(&engine);
|
|
||||||
|
|
||||||
delegate1->setData(QByteArrayLiteral(R"(
|
|
||||||
import QtQml.Models 2.15
|
|
||||||
|
|
||||||
ListModel {
|
|
||||||
readonly property int myProp: 42
|
|
||||||
}
|
|
||||||
)"), QUrl());
|
|
||||||
|
|
||||||
auto delegate2 = std::make_unique<QQmlComponent>(&engine);
|
|
||||||
|
|
||||||
delegate2->setData(QByteArrayLiteral(R"(
|
|
||||||
import QtQml.Models 2.15
|
|
||||||
|
|
||||||
ListModel {
|
|
||||||
readonly property int myProp: 11
|
|
||||||
}
|
|
||||||
)"), QUrl());
|
|
||||||
|
|
||||||
ListModelWrapper sourceModel(engine, R"([
|
|
||||||
{ "balances": [], "name": "name 1" },
|
|
||||||
{ "balances": [], "name": "name 2" },
|
|
||||||
{ "balances": [], "name": "name 3" }
|
|
||||||
])");
|
|
||||||
|
|
||||||
SubmodelProxyModel model1;
|
|
||||||
model1.setSourceModel(sourceModel);
|
|
||||||
model1.setDelegateModel(delegate1.get());
|
|
||||||
model1.setSubmodelRoleName(QStringLiteral("balances"));
|
|
||||||
|
|
||||||
SubmodelProxyModel model2;
|
|
||||||
model2.setSourceModel(sourceModel);
|
|
||||||
model2.setDelegateModel(delegate2.get());
|
|
||||||
model2.setSubmodelRoleName(QStringLiteral("balances"));
|
|
||||||
|
|
||||||
auto roles = model1.roleNames();
|
|
||||||
QCOMPARE(roles.size(), 2);
|
|
||||||
|
|
||||||
QVariant wrapperVariant1 = model1.data(model1.index(0, 0),
|
|
||||||
roleForName(roles, "balances"));
|
|
||||||
QObject* wrapper1 = wrapperVariant1.value<QObject*>();
|
|
||||||
QCOMPARE(wrapper1->property("myProp"), 42);
|
|
||||||
|
|
||||||
QVariant wrapperVariant2 = model2.data(model2.index(0, 0),
|
|
||||||
roleForName(roles, "balances"));
|
|
||||||
QObject* wrapper2 = wrapperVariant2.value<QObject*>();
|
|
||||||
QCOMPARE(wrapper2->property("myProp"), 11);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
QTEST_MAIN(TestSubmodelProxyModel)
|
|
||||||
#include "tst_SubmodelProxyModel.moc"
|
|
Loading…
Reference in New Issue