StatusQ: Add generic proxy model for roles renaming
This commit is contained in:
parent
a3239d9e2b
commit
628d9cdd31
|
@ -0,0 +1,156 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import StatusQ 0.1
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
RolesRenamingModel {
|
||||
id: renamedModel
|
||||
|
||||
sourceModel: sourceModel
|
||||
|
||||
mapping: [
|
||||
RoleRename {
|
||||
from: "tokenId"
|
||||
to: "id"
|
||||
},
|
||||
RoleRename {
|
||||
from: "title"
|
||||
to: "name"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: sourceModel
|
||||
|
||||
ListElement {
|
||||
tokenId: "1"
|
||||
title: "Token 1"
|
||||
communityId: "1"
|
||||
}
|
||||
ListElement {
|
||||
tokenId: "2"
|
||||
title: "Token 2"
|
||||
communityId: "1"
|
||||
}
|
||||
ListElement {
|
||||
tokenId: "3"
|
||||
title: "Token 3"
|
||||
communityId: "2"
|
||||
}
|
||||
ListElement {
|
||||
tokenId: "4"
|
||||
title: "Token 4"
|
||||
communityId: "3"
|
||||
}
|
||||
ListElement {
|
||||
tokenId: "5"
|
||||
title: "Token 5"
|
||||
communityId: ""
|
||||
}
|
||||
ListElement {
|
||||
tokenId: "6"
|
||||
title: "Token 6"
|
||||
communityId: "1"
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 40
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
border.color: "gray"
|
||||
|
||||
ListView {
|
||||
anchors.fill: parent
|
||||
|
||||
model: sourceModel
|
||||
|
||||
header: Label {
|
||||
height: implicitHeight * 2
|
||||
text: `Left model (${sourceModel.count})`
|
||||
|
||||
font.bold: true
|
||||
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
|
||||
delegate: Label {
|
||||
width: ListView.view.width
|
||||
|
||||
text: `token id: ${model.tokenId}, ${model.title}, community id: ${model.communityId || "-"}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
border.color: "gray"
|
||||
|
||||
ListView {
|
||||
id: renamedListView
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
model: renamedModel
|
||||
|
||||
header: Label {
|
||||
height: implicitHeight * 2
|
||||
text: `Renamed model (${renamedListView.count})`
|
||||
|
||||
font.bold: true
|
||||
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
|
||||
delegate: Label {
|
||||
width: ListView.view.width
|
||||
|
||||
text: `id: ${model.id}, ${model.name}, community id: ${model.communityId || "-"}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Button {
|
||||
text: "shuffle"
|
||||
|
||||
onClicked: {
|
||||
const count = sourceModel.count
|
||||
const iterations = count / 2
|
||||
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
sourceModel.move(Math.floor(Math.random() * (count - 1)),
|
||||
Math.floor(Math.random() * (count - 1)),
|
||||
Math.floor(Math.random() * 2) + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// category: Models
|
|
@ -13,8 +13,8 @@ set(CMAKE_CXX_STANDARD 17)
|
|||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Although SHARED libraries set this to ON by default,
|
||||
# all static libraries, that are built into this SHARED,
|
||||
# (which is qzxing in our case) should also be build with -fPIC.
|
||||
# all static libraries, that are built into this SHARED,
|
||||
# (which is qzxing in our case) should also be build with -fPIC.
|
||||
# This fixes it.
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
|
@ -91,6 +91,7 @@ add_library(StatusQ SHARED
|
|||
include/StatusQ/QClipboardProxy.h
|
||||
include/StatusQ/modelutilsinternal.h
|
||||
include/StatusQ/permissionutilsinternal.h
|
||||
include/StatusQ/rolesrenamingmodel.h
|
||||
include/StatusQ/rxvalidator.h
|
||||
include/StatusQ/statussyntaxhighlighter.h
|
||||
include/StatusQ/statuswindow.h
|
||||
|
@ -99,13 +100,14 @@ add_library(StatusQ SHARED
|
|||
src/modelutilsinternal.cpp
|
||||
src/permissionutilsinternal.cpp
|
||||
src/plugin.cpp
|
||||
src/rolesrenamingmodel.cpp
|
||||
src/rxvalidator.cpp
|
||||
src/statussyntaxhighlighter.cpp
|
||||
src/statuswindow.cpp
|
||||
src/stringutilsinternal.cpp
|
||||
)
|
||||
|
||||
set_target_properties(StatusQ PROPERTIES
|
||||
set_target_properties(StatusQ PROPERTIES
|
||||
ADDITIONAL_CLEAN_FILES bin/StatusQ/qmldir
|
||||
RUNTIME_OUTPUT_DIRECTORY ${STATUSQ_MODULE_PATH}
|
||||
RUNTIME_OUTPUT_DIRECTORY_DEBUG ${STATUSQ_MODULE_PATH}
|
||||
|
@ -144,7 +146,7 @@ target_link_libraries(StatusQ PRIVATE
|
|||
qzxing
|
||||
)
|
||||
|
||||
target_include_directories(StatusQ PRIVATE include)
|
||||
target_include_directories(StatusQ PUBLIC include)
|
||||
|
||||
install(TARGETS StatusQ
|
||||
RUNTIME DESTINATION StatusQ
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include <QIdentityProxyModel>
|
||||
#include <QQmlListProperty>
|
||||
|
||||
class RoleRename : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString from READ from WRITE setFrom NOTIFY fromChanged)
|
||||
Q_PROPERTY(QString to READ to WRITE setTo NOTIFY toChanged)
|
||||
|
||||
public:
|
||||
explicit RoleRename(QObject* parent = nullptr);
|
||||
|
||||
void setFrom(const QString& from);
|
||||
const QString& from() const;
|
||||
|
||||
void setTo(const QString& to);
|
||||
const QString& to() const;
|
||||
|
||||
signals:
|
||||
void fromChanged();
|
||||
void toChanged();
|
||||
|
||||
private:
|
||||
QString m_from;
|
||||
QString m_to;
|
||||
};
|
||||
|
||||
class RolesRenamingModel : public QIdentityProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QQmlListProperty<RoleRename> mapping READ mapping CONSTANT)
|
||||
|
||||
public:
|
||||
explicit RolesRenamingModel(QObject* parent = nullptr);
|
||||
|
||||
QQmlListProperty<RoleRename> mapping();
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
mutable bool m_rolesFetched = false;
|
||||
QList<RoleRename*> m_mapping;
|
||||
};
|
|
@ -6,6 +6,7 @@
|
|||
#include "StatusQ/QClipboardProxy.h"
|
||||
#include "StatusQ/modelutilsinternal.h"
|
||||
#include "StatusQ/permissionutilsinternal.h"
|
||||
#include "StatusQ/rolesrenamingmodel.h"
|
||||
#include "StatusQ/rxvalidator.h"
|
||||
#include "StatusQ/statussyntaxhighlighter.h"
|
||||
#include "StatusQ/statuswindow.h"
|
||||
|
@ -24,6 +25,9 @@ public:
|
|||
qmlRegisterType<StatusSyntaxHighlighter>("StatusQ", 0, 1, "StatusSyntaxHighlighter");
|
||||
qmlRegisterType<RXValidator>("StatusQ", 0, 1, "RXValidator");
|
||||
|
||||
qmlRegisterType<RolesRenamingModel>("StatusQ", 0, 1, "RolesRenamingModel");
|
||||
qmlRegisterType<RoleRename>("StatusQ", 0, 1, "RoleRename");
|
||||
|
||||
qmlRegisterSingletonType<QClipboardProxy>("StatusQ", 0, 1, "QClipboardProxy", &QClipboardProxy::qmlInstance);
|
||||
|
||||
qmlRegisterSingletonType<ModelUtilsInternal>(
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
#include "StatusQ/rolesrenamingmodel.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
RoleRename::RoleRename(QObject* parent)
|
||||
: QObject{parent}
|
||||
{
|
||||
}
|
||||
|
||||
void RoleRename::setFrom(const QString& from)
|
||||
{
|
||||
if (m_from == from)
|
||||
return;
|
||||
|
||||
if (!m_from.isEmpty()) {
|
||||
qWarning() << "RoleRename: property \"from\" is inteded to be initialized once and not changed!";
|
||||
return;
|
||||
}
|
||||
|
||||
m_from = from;
|
||||
emit fromChanged();
|
||||
}
|
||||
|
||||
const QString& RoleRename::from() const
|
||||
{
|
||||
return m_from;
|
||||
}
|
||||
|
||||
void RoleRename::setTo(const QString& to)
|
||||
{
|
||||
if (m_to == to)
|
||||
return;
|
||||
|
||||
if (!m_to.isEmpty()) {
|
||||
qWarning() << "RoleRename: property \"to\" is inteded to be initialized once and not changed!";
|
||||
return;
|
||||
}
|
||||
|
||||
m_to = to;
|
||||
emit toChanged();
|
||||
}
|
||||
|
||||
const QString& RoleRename::to() const
|
||||
{
|
||||
return m_to;
|
||||
}
|
||||
|
||||
RolesRenamingModel::RolesRenamingModel(QObject* parent)
|
||||
: QIdentityProxyModel{parent}
|
||||
{
|
||||
}
|
||||
|
||||
QQmlListProperty<RoleRename> RolesRenamingModel::mapping()
|
||||
{
|
||||
QQmlListProperty<RoleRename> list(this, &m_mapping);
|
||||
|
||||
list.replace = nullptr;
|
||||
list.clear = nullptr;
|
||||
list.removeLast = nullptr;
|
||||
|
||||
list.append = [](auto listProperty, auto element) {
|
||||
RolesRenamingModel* model = qobject_cast<RolesRenamingModel*>(
|
||||
listProperty->object);
|
||||
|
||||
if (model->m_rolesFetched) {
|
||||
qWarning() << "RolesRenamingModel: role names mapping cannot be modified after fetching role names!";
|
||||
return;
|
||||
}
|
||||
|
||||
model->m_mapping.append(element);
|
||||
};
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> RolesRenamingModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles = sourceModel()
|
||||
? sourceModel()->roleNames()
|
||||
: QHash<int, QByteArray>{};
|
||||
|
||||
if (roles.empty())
|
||||
return roles;
|
||||
|
||||
QHash<QString, RoleRename*> renameMap;
|
||||
|
||||
for (const auto rename : m_mapping)
|
||||
renameMap.insert(rename->from(), rename);
|
||||
|
||||
QHash<int, QByteArray> remapped;
|
||||
remapped.reserve(roles.size());
|
||||
|
||||
QSet<QByteArray> roleNamesSet;
|
||||
roleNamesSet.reserve(roles.size());
|
||||
|
||||
for (auto i = roles.cbegin(), end = roles.cend(); i != end; ++i) {
|
||||
RoleRename* rename = renameMap.take(i.value());
|
||||
QByteArray roleName = rename ? rename->to().toUtf8() : i.value();
|
||||
|
||||
remapped.insert(i.key(), roleName);
|
||||
roleNamesSet.insert(roleName);
|
||||
}
|
||||
|
||||
if (roles.size() != roleNamesSet.size()) {
|
||||
qWarning() << "RolesRenamingModel: model cannot contain duplicated role names!";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (renameMap.size()) {
|
||||
qWarning().nospace()
|
||||
<< "RolesRenamingModel: specified source roles not found: "
|
||||
<< renameMap.keys() << "!";
|
||||
}
|
||||
|
||||
m_rolesFetched = true;
|
||||
return remapped;
|
||||
}
|
|
@ -16,7 +16,9 @@ add_definitions(-DQUICK_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
|
|||
|
||||
add_executable(${PROJECT_NAME} main.cpp)
|
||||
|
||||
add_test(NAME ${PROJECT_NAME} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME} -input "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
add_test(NAME ${PROJECT_NAME} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME} -input "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
add_custom_target("Run_${PROJECT_NAME}" COMMAND ${CMAKE_CTEST_COMMAND} --test-dir "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
add_dependencies("Run_${PROJECT_NAME}" ${PROJECT_NAME})
|
||||
|
||||
|
@ -38,3 +40,7 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE
|
|||
STATUSQ_MODULE_PATH="${STATUSQ_MODULE_PATH}"
|
||||
STATUSQ_MODULE_IMPORT_PATH="${STATUSQ_MODULE_IMPORT_PATH}"
|
||||
)
|
||||
|
||||
add_executable(RolesRenamingModelTest tst_RolesRenamingModel.cpp)
|
||||
target_link_libraries(RolesRenamingModelTest PRIVATE Qt5::Qml Qt5::Test StatusQ)
|
||||
add_test(RolesRenamingModelTest COMMAND RolesRenamingModelTest)
|
||||
|
|
|
@ -0,0 +1,212 @@
|
|||
#include <QSignalSpy>
|
||||
#include <QTest>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <StatusQ/rolesrenamingmodel.h>
|
||||
|
||||
namespace {
|
||||
|
||||
class TestSourceModel : public QAbstractListModel {
|
||||
|
||||
public:
|
||||
explicit TestSourceModel(QList<QString> roles)
|
||||
: m_roles(std::move(roles))
|
||||
{
|
||||
}
|
||||
|
||||
QVariant data(const QModelIndex& index, int role) const override
|
||||
{
|
||||
if(!index.isValid() || index.row() >= capacity)
|
||||
return {};
|
||||
|
||||
return 42;
|
||||
}
|
||||
|
||||
int rowCount(const QModelIndex& parent) const override
|
||||
{
|
||||
return capacity;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles.remove(m_roles.size());
|
||||
|
||||
for (auto i = 0; i < m_roles.size(); i++)
|
||||
roles.insert(i, m_roles.at(i).toUtf8());
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr auto capacity = 5;
|
||||
QList<QString> m_roles;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
class TestRolesRenamingModel: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void initializationTest()
|
||||
{
|
||||
RolesRenamingModel model;
|
||||
|
||||
QQmlListProperty<RoleRename> mapping = model.mapping();
|
||||
|
||||
RoleRename rename;
|
||||
rename.setFrom("someIdFrom");
|
||||
rename.setTo("someIdTo");
|
||||
|
||||
mapping.append(&mapping, &rename);
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "RolesRenamingModel: specified source roles not found: (\"someIdFrom\")!");
|
||||
|
||||
QCOMPARE(model.roleNames(), {});
|
||||
}
|
||||
|
||||
void remappingTest()
|
||||
{
|
||||
TestSourceModel sourceModel({"id", "name", "color"});
|
||||
RolesRenamingModel model;
|
||||
|
||||
QQmlListProperty<RoleRename> mapping = model.mapping();
|
||||
|
||||
RoleRename rename_1;
|
||||
rename_1.setFrom("id");
|
||||
rename_1.setTo("tokenId");
|
||||
mapping.append(&mapping, &rename_1);
|
||||
|
||||
RoleRename rename_2;
|
||||
rename_2.setFrom("name");
|
||||
rename_2.setTo("tokenName");
|
||||
mapping.append(&mapping, &rename_2);
|
||||
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
QHash<int, QByteArray> expectedRoles = {{0, "tokenId"}, {1, "tokenName"}, {2, "color"}};
|
||||
QCOMPARE(model.roleNames(), expectedRoles);
|
||||
}
|
||||
|
||||
void addMappingAfterFetchingRoleNamesTest()
|
||||
{
|
||||
TestSourceModel sourceModel({"id", "name", "color"});
|
||||
RolesRenamingModel model;
|
||||
|
||||
QQmlListProperty<RoleRename> mapping = model.mapping();
|
||||
|
||||
RoleRename rename_1;
|
||||
rename_1.setFrom("id");
|
||||
rename_1.setTo("tokenId");
|
||||
mapping.append(&mapping, &rename_1);
|
||||
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
QHash<int, QByteArray> expectedRoles = {{0, "tokenId"}, {1, "name"}, {2, "color"}};
|
||||
QCOMPARE(model.roleNames(), expectedRoles);
|
||||
|
||||
RoleRename rename_2;
|
||||
rename_2.setFrom("name");
|
||||
rename_2.setTo("tokenName");
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "RolesRenamingModel: role names mapping cannot be modified after fetching role names!");
|
||||
mapping.append(&mapping, &rename_2);
|
||||
|
||||
QCOMPARE(model.roleNames(), expectedRoles);
|
||||
}
|
||||
|
||||
void duplicatedNamesTest()
|
||||
{
|
||||
TestSourceModel sourceModel({"id", "name", "color"});
|
||||
RolesRenamingModel model;
|
||||
|
||||
QQmlListProperty<RoleRename> mapping = model.mapping();
|
||||
|
||||
RoleRename rename_1;
|
||||
rename_1.setFrom("id");
|
||||
rename_1.setTo("name");
|
||||
mapping.append(&mapping, &rename_1);
|
||||
|
||||
model.setSourceModel(&sourceModel);
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "RolesRenamingModel: model cannot contain duplicated role names!");
|
||||
|
||||
QCOMPARE(model.roleNames(), {});
|
||||
}
|
||||
|
||||
void resettingFromToPropertiesTest()
|
||||
{
|
||||
RoleRename rename;
|
||||
|
||||
rename.setFrom("id");
|
||||
QCOMPARE(rename.from(), "id");
|
||||
QCOMPARE(rename.to(), "");
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg,
|
||||
"RoleRename: property \"from\" is inteded to be initialized once and not changed!");
|
||||
rename.setFrom("id2");
|
||||
QCOMPARE(rename.from(), "id");
|
||||
QCOMPARE(rename.to(), "");
|
||||
|
||||
rename.setTo("myId");
|
||||
QCOMPARE(rename.from(), "id");
|
||||
QCOMPARE(rename.to(), "myId");
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "RoleRename: property \"to\" is inteded to be initialized once and not changed!");
|
||||
rename.setTo("myId2");
|
||||
QCOMPARE(rename.from(), "id");
|
||||
QCOMPARE(rename.to(), "myId");
|
||||
}
|
||||
|
||||
void sourceModelDeletedTest()
|
||||
{
|
||||
auto sourceModel = std::make_unique<TestSourceModel>(
|
||||
QList<QString>{"id", "name", "color"});
|
||||
RolesRenamingModel model;
|
||||
|
||||
QQmlListProperty<RoleRename> mapping = model.mapping();
|
||||
|
||||
RoleRename rename_1;
|
||||
rename_1.setFrom("id");
|
||||
rename_1.setTo("tokenId");
|
||||
mapping.append(&mapping, &rename_1);
|
||||
|
||||
RoleRename rename_2;
|
||||
rename_2.setFrom("name");
|
||||
rename_2.setTo("tokenName");
|
||||
mapping.append(&mapping, &rename_2);
|
||||
|
||||
model.setSourceModel(sourceModel.get());
|
||||
|
||||
QHash<int, QByteArray> expectedRoles = {
|
||||
{0, "tokenId"}, {1, "tokenName"}, {2, "color"}
|
||||
};
|
||||
QCOMPARE(model.roleNames(), expectedRoles);
|
||||
QCOMPARE(model.rowCount(), 5);
|
||||
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), 42);
|
||||
QCOMPARE(model.data(model.index(0, 0), 1), 42);
|
||||
QCOMPARE(model.data(model.index(5, 0), 0), {});
|
||||
QCOMPARE(model.data(model.index(5, 0), 1), {});
|
||||
|
||||
QSignalSpy destroyedSpy(sourceModel.get(), &QObject::destroyed);
|
||||
sourceModel.reset();
|
||||
|
||||
QCOMPARE(destroyedSpy.size(), 1);
|
||||
|
||||
QCOMPARE(model.roleNames(), {});
|
||||
QCOMPARE(model.rowCount(), 0);
|
||||
|
||||
QCOMPARE(model.roleNames(), {});
|
||||
QCOMPARE(model.data(model.index(0, 0), 0), {});
|
||||
QCOMPARE(model.data(model.index(0, 0), 1), {});
|
||||
QCOMPARE(model.data(model.index(5, 0), 0), {});
|
||||
QCOMPARE(model.data(model.index(5, 0), 1), {});
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestRolesRenamingModel)
|
||||
#include "tst_RolesRenamingModel.moc"
|
Loading…
Reference in New Issue