feat(StatusQ): GroupingModel for grouping rows with the same key into

submodels

Closes: #12683
This commit is contained in:
Michał Cieślak 2023-11-28 15:32:48 +01:00
parent 42533b8c61
commit e09d0b090b
8 changed files with 3435 additions and 3 deletions

View File

@ -0,0 +1,143 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ 0.1
import SortFilterProxyModel 0.2
Pane {
id: root
ListModel {
id: collectiblesModel
Component.onCompleted: {
const randomInt = max => Math.floor(Math.random() * max)
const data = []
for (let i = 0; i < 1000; i++) {
const collectionName = "collection_" + i
const tokensCount = randomInt(10) + 1
for (let j = 0; j < tokensCount; j++) {
data.push({
collectionName,
tokenName: "token_" + j,
tokenValue: randomInt(200)
})
}
}
append(data)
}
}
SortFilterProxyModel {
id: sfpm
sourceModel: collectiblesModel
filters: RangeFilter {
roleName: "tokenValue"
minimumValue: slider.value
}
}
GroupingModel {
id: groupingModel
sourceModel: sfpm
groupingRoleName: "collectionName"
submodelRoleName: "collectibles"
}
ColumnLayout {
anchors.fill: parent
Label {
Layout.fillWidth: true
Layout.bottomMargin: 10
wrapMode: Text.Wrap
text: "<b>Description</b>: flat model with roles 'collectionName',"
+ " 'tokenName' and 'tokenValue' is filtered by token value"
+ " and grouped by collection name"
}
Label {
text: "source model count: " + collectiblesModel.count
}
Label {
text: "filtered model count: " + sfpm.count
}
Label {
text: "grouped model count: " + listView.count
}
RowLayout {
Layout.fillHeight: false
Label {
text: "Token value threshold"
}
Slider {
id: slider
stepSize: 1
value: 100
from: 0
to: 200
}
Label {
text: slider.value
}
}
ListView {
id: listView
Layout.fillWidth: true
Layout.fillHeight: true
model: groupingModel
spacing: 5
clip: true
ScrollBar.vertical: ScrollBar {}
delegate: RowLayout {
id: delegateRoot
width: ListView.view.width
readonly property var collectibles: model.collectibles
Label {
text: model.collectionName
}
ListView {
clip: true
orientation: ListView.Horizontal
Layout.fillWidth: true
Layout.fillHeight: true
model: delegateRoot.collectibles
delegate: Label {
text: `${model.tokenName} (val: ${model.tokenValue})`
color: "darkred"
}
}
}
}
}
}
// category: Models

View File

@ -100,6 +100,7 @@ add_library(StatusQ SHARED
include/StatusQ/fastexpressionsorter.h
include/StatusQ/formatteddoubleproperty.h
include/StatusQ/functionaggregator.h
include/StatusQ/groupingmodel.h
include/StatusQ/leftjoinmodel.h
include/StatusQ/modelentry.h
include/StatusQ/modelsyncedcontainer.h
@ -109,9 +110,9 @@ add_library(StatusQ SHARED
include/StatusQ/permissionutilsinternal.h
include/StatusQ/rolesrenamingmodel.h
include/StatusQ/rxvalidator.h
include/StatusQ/singleroleaggregator.h
include/StatusQ/snapshotmodel.h
include/StatusQ/snapshotobject.h
include/StatusQ/singleroleaggregator.h
include/StatusQ/statussyntaxhighlighter.h
include/StatusQ/statuswindow.h
include/StatusQ/stringutilsinternal.h
@ -127,6 +128,7 @@ add_library(StatusQ SHARED
src/fastexpressionsorter.cpp
src/formatteddoubleproperty.cpp
src/functionaggregator.cpp
src/groupingmodel.cpp
src/leftjoinmodel.cpp
src/modelentry.cpp
src/modelutilsinternal.cpp

View File

@ -0,0 +1,81 @@
#pragma once
#include <QAbstractProxyModel>
#include <optional>
#include <memory>
class RangeModel;
class GroupingModel : public QAbstractProxyModel
{
Q_OBJECT
Q_PROPERTY(QString groupingRoleName READ groupingRoleName
WRITE setGroupingRoleName NOTIFY groupingRoleNameChanged)
Q_PROPERTY(QString submodelRoleName READ submodelRoleName
WRITE setSubmodelRoleName NOTIFY submodelRoleNameChanged)
public:
explicit GroupingModel(QObject* parent = nullptr);
~GroupingModel();
void setSourceModel(QAbstractItemModel* sourceModel) override;
QModelIndex mapToSource(const QModelIndex& proxyIndex) const override;
QModelIndex mapFromSource(const QModelIndex& sourceIndex) const override;
void setGroupingRoleName(const QString& groupingRoleName);
const QString& groupingRoleName() const;
void setSubmodelRoleName(const QString& submodelRoleName);
const QString& submodelRoleName() const;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QModelIndex index(int row, int column = 0, const QModelIndex& parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex& child) const override;
signals:
void groupingRoleNameChanged();
void submodelRoleNameChanged();
protected slots:
void resetInternalData();
private:
static constexpr auto s_defaultSubmodelRoleName = "submodel";
struct Entry {
int submodel = 0; // index of submodel
int submodelIndex = 0; // index within submodel
int sourceIndex = 0; // index within source model
};
void init();
void initRoles();
void initSubmodelRole();
void connectSignals(QAbstractItemModel* model);
std::vector<Entry> m_entries;
std::vector<std::unique_ptr<RangeModel>> m_submodels;
RangeModel* m_pendingMergeSubmodel = nullptr;
RangeModel* m_pendingRemovalSubmodel = nullptr;
QHash<int, QByteArray> m_roleNames;
QString m_groupingRoleName;
QString m_submodelRoleName = s_defaultSubmodelRoleName;
std::optional<int> m_groupingRole;
int m_submodelRole = -1;
bool m_rolesInitialized = false;
friend class RangeModel;
};

View File

@ -0,0 +1,684 @@
#include "StatusQ/groupingmodel.h"
#include <iterator>
#include <QDebug>
namespace {
QVariant data(QAbstractItemModel* model, int row, int role) {
return model->data(model->index(row, 0), role);
}
} // unnamed namespace
class RangeModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit RangeModel(QAbstractItemModel* sourceModel,
const std::vector<GroupingModel::Entry>& entries, int from, int to)
: m_entries(entries), m_sourceModel(sourceModel), m_from(from), m_to(to)
{
}
using QAbstractListModel::beginInsertRows;
using QAbstractListModel::endInsertRows;
using QAbstractListModel::beginRemoveRows;
using QAbstractListModel::endRemoveRows;
QVariant data(const QModelIndex& index, int role) const override
{
if (!index.isValid())
return {};
auto sourceIndex = m_entries[index.row() + m_from].sourceIndex;
return m_sourceModel->data(m_sourceModel->index(sourceIndex,
index.column()), role);
}
QHash<int, QByteArray> roleNames() const override
{
return m_sourceModel->roleNames();
}
int rowCount(const QModelIndex& parent = {}) const override
{
return m_to - m_from + 1;
}
int& from()
{
return m_from;
}
int& to() {
return m_to;
}
void shift(int offset)
{
m_from += offset;
m_to += offset;
}
private:
QAbstractItemModel* m_sourceModel = nullptr;
const std::vector<GroupingModel::Entry>& m_entries;
int m_from = 0;
int m_to = 0;
};
GroupingModel::GroupingModel(QObject* parent)
: QAbstractProxyModel{parent}
{
}
GroupingModel::~GroupingModel() = default;
void GroupingModel::setSourceModel(QAbstractItemModel* model)
{
if (sourceModel() == model)
return;
if (sourceModel() != nullptr)
sourceModel()->disconnect(this);
beginResetModel();
QAbstractProxyModel::setSourceModel(model);
if (model != nullptr)
connectSignals(model);
endResetModel();
}
QModelIndex GroupingModel::mapToSource(const QModelIndex& proxyIndex) const
{
if (!sourceModel())
return {};
if (!proxyIndex.isValid())
return {};
if (proxyIndex.model() != sourceModel())
return {};
return index(m_submodels[proxyIndex.row()]->from());
}
QModelIndex GroupingModel::mapFromSource(const QModelIndex& sourceIndex) const
{
if (!sourceIndex.isValid())
return {};
auto& entry = m_entries[sourceIndex.row()];
auto submodel = entry.submodel;
auto s = m_submodels[submodel].get();
return s->index(entry.submodelIndex, 0);
}
void GroupingModel::setGroupingRoleName(const QString& groupingRoleName)
{
if (m_groupingRoleName == groupingRoleName)
return;
m_groupingRoleName = groupingRoleName;
initSubmodelRole();
if (m_groupingRole)
init();
emit groupingRoleNameChanged();
}
const QString &GroupingModel::groupingRoleName() const
{
return m_groupingRoleName;
}
void GroupingModel::setSubmodelRoleName(const QString& submodelRoleName)
{
if (m_submodelRoleName == submodelRoleName)
return;
if (!m_roleNames.isEmpty())
beginResetModel();
m_submodelRoleName = submodelRoleName;
if (!m_roleNames.isEmpty())
endResetModel();
emit submodelRoleNameChanged();
}
const QString& GroupingModel::submodelRoleName() const
{
return m_submodelRoleName;
}
QVariant GroupingModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid() || index.row() >= m_submodels.size())
return {};
if (role == m_submodelRole)
return QVariant::fromValue(m_submodels[index.row()].get());
auto row = index.row();
auto destRow = m_submodels[row]->from();
auto srcRow = m_entries.at(destRow).sourceIndex;
return sourceModel()->data(sourceModel()->index(srcRow, index.column()), role);
}
QHash<int, QByteArray> GroupingModel::roleNames() const
{
return m_roleNames;
}
int GroupingModel::columnCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return 1;
}
int GroupingModel::rowCount(const QModelIndex &parent) const
{
return m_submodels.size();
}
QModelIndex GroupingModel::index(int row, int column, const QModelIndex& parent) const
{
if (parent.isValid())
return {};
if (row < 0 || column < 0 || row >= rowCount(parent)
|| column >= columnCount(parent))
return {};
return createIndex(row, column);
}
QModelIndex GroupingModel::parent(const QModelIndex& child) const
{
return {};
}
void GroupingModel::resetInternalData()
{
QAbstractProxyModel::resetInternalData();
auto source = sourceModel();
m_rolesInitialized = false;
m_roleNames.clear();
m_entries.clear();
m_submodels.clear();
if (source == nullptr)
return;
if (source->rowCount() > 0) {
initRoles();
initSubmodelRole();
if (m_groupingRole)
init();
}
}
void GroupingModel::init()
{
auto count = sourceModel()->rowCount();
Q_ASSERT(m_groupingRole.has_value());
QVariant previousVal;
// from/to pair for every group
std::vector<std::pair<int, int>> pairs;
m_entries.reserve(count);
int submodel = -1;
for (int i = 0; i < count; i++) {
auto val = sourceModel()->data(sourceModel()->index(i, 0), *m_groupingRole);
if (val != previousVal || pairs.empty()) {
submodel++;
pairs.emplace_back(i, i);
} else {
pairs.back().second++;
}
m_entries.push_back({submodel, pairs.back().second - pairs.back().first, i});
previousVal = val;
}
m_submodels.reserve(pairs.size());
std::transform(pairs.cbegin(), pairs.cend(), std::back_inserter(m_submodels),
[this] (auto& entry) {
return std::make_unique<RangeModel>(sourceModel(), m_entries, entry.first, entry.second);
});
}
void GroupingModel::initRoles()
{
auto roleNames = sourceModel()->roleNames();
auto roles = roleNames.keys();
auto maxIt = std::max_element(roles.cbegin(), roles.cend());
m_submodelRole = maxIt == roles.cend() ? 0 : *maxIt + 1;
roleNames.insert(m_submodelRole, m_submodelRoleName.toUtf8());
m_roleNames = std::move(roleNames);
m_rolesInitialized = true;
}
void GroupingModel::initSubmodelRole()
{
auto groupingRole = m_roleNames.keys(m_groupingRoleName.toUtf8());
if (groupingRole.size())
m_groupingRole = groupingRole.first();
else
m_groupingRole.reset();
}
void GroupingModel::connectSignals(QAbstractItemModel* model)
{
connect(model, &QAbstractItemModel::rowsInserted, this,
[this, model](const QModelIndex &parent, int first, int last) {
if (!m_rolesInitialized) {
initRoles();
initSubmodelRole(); // check order
}
auto insertCount = last - first + 1;
std::optional<QVariant> previousGroupingValue;
if (first - 1 >= 0)
previousGroupingValue = ::data(model, first - 1, *m_groupingRole);
std::optional<QVariant> nextGroupingValue;
if (last + 1 < m_entries.size() + insertCount)
nextGroupingValue = ::data(model, last + 1, *m_groupingRole);
int currentFirst = first;
int currentLast = last;
int appendToPrevious = 0;
int appendToNext = 0;
// count data belonging to the previous group
while (previousGroupingValue
&& currentFirst <= currentLast
&& ::data(model, currentFirst, *m_groupingRole) == *previousGroupingValue) {
currentFirst++;
appendToPrevious++;
}
// count data belonging to the following group
while (nextGroupingValue
&& currentLast >= currentFirst
&& ::data(model, currentLast, *m_groupingRole) == *nextGroupingValue) {
currentLast--;
appendToNext++;
}
int toNewGroups = insertCount - appendToPrevious - appendToNext;
int toRemove = 0;
// shift indexes to indicate old items for rowsAboutToBe* signals
for (auto i = first; i < m_entries.size(); i++)
m_entries[i].sourceIndex += last - first + 1;
if (toNewGroups > 0 && previousGroupingValue && nextGroupingValue
&& previousGroupingValue == nextGroupingValue) {
int submodelIndex = m_entries[first - 1].submodel;
RangeModel* submodel = m_submodels[submodelIndex].get();
toRemove = submodel->rowCount() - m_entries[first - 1].submodelIndex - 1;
submodel->beginRemoveRows({}, m_entries[first - 1].submodelIndex + 1,
submodel->rowCount() - 1);
submodel->to() -= toRemove;
submodel->endRemoveRows();
toNewGroups += toRemove + appendToNext;
appendToNext = 0;
}
RangeModel* previousModel = nullptr;
RangeModel* nextModel = nullptr;
if (appendToPrevious) {
const Entry& entry = m_entries[first - 1];
int submodel = entry.submodel;
int offset = entry.submodelIndex + 1;
previousModel = m_submodels[submodel].get();
previousModel->beginInsertRows({}, offset, appendToPrevious + offset - 1);
}
// prepare new entries
QVariant previousVal;
std::vector<std::pair<int, int>> pairs;
int baseline = first + appendToPrevious;
int submodel = -1;
for (int i = baseline; i < baseline + toNewGroups; i++) {
Q_ASSERT(m_groupingRole);
auto val = sourceModel()->data(sourceModel()->index(i, 0), *m_groupingRole);
if (val != previousVal || pairs.empty()) {
submodel++;
pairs.emplace_back(i, i);
} else {
pairs.back().second++;
}
previousVal = val;
}
std::vector<std::unique_ptr<RangeModel>> newSubmodels;
std::transform(pairs.cbegin(), pairs.cend(), std::back_inserter(newSubmodels),
[this] (auto& entry) {
return std::make_unique<RangeModel>(sourceModel(), m_entries, entry.first, entry.second);
});
if (newSubmodels.size()) {
int offset = first == 0 ? 0 : m_entries[first - 1].submodel + 1;
beginInsertRows({}, offset, offset + newSubmodels.size() - 1);
}
if (appendToNext) {
Q_ASSERT(first < m_entries.size());
int submodel = m_entries[first].submodel;
Q_ASSERT(submodel < m_submodels.size());
nextModel = m_submodels[submodel].get();
nextModel->beginInsertRows({}, 0, appendToNext - 1);
}
// ADJUST STRUCTURES
if (appendToPrevious) {
previousModel->to() += appendToPrevious;
const Entry& entry = m_entries[first - 1];
int submodel = entry.submodel;
for (int i = submodel + 1; i < m_submodels.size(); i++)
m_submodels[i]->shift(appendToPrevious);
}
if (newSubmodels.size()) {
int offset = first == 0 ? 0 : m_entries[first - 1].submodel + 1;
m_submodels.insert(m_submodels.begin() + offset,
std::make_move_iterator(newSubmodels.begin()),
std::make_move_iterator(newSubmodels.end()));
for (int i = offset + newSubmodels.size(); i < m_submodels.size(); i++)
m_submodels[i]->shift(toNewGroups - toRemove);
}
if (appendToNext) {
nextModel->to() += appendToNext;
int submodel = m_entries[first].submodel + newSubmodels.size();
for (int i = submodel + 1; i < m_submodels.size(); i++)
m_submodels[i]->shift(appendToNext);
}
m_entries.resize(m_entries.size() + insertCount);
int totalCounter = 0;
for (std::size_t i = 0; i < m_submodels.size(); i++) {
RangeModel* model = m_submodels[i].get();
int count = model->rowCount();
for (std::size_t j = 0; j < count; j++) {
Entry& entry = m_entries[totalCounter];
entry.submodel = i;
entry.submodelIndex = j;
entry.sourceIndex = totalCounter;
totalCounter++;
}
}
// EMIT SIGNALS
if (previousModel)
previousModel->endInsertRows();
if (newSubmodels.size())
endInsertRows();
if (nextModel) {
nextModel->endInsertRows();
auto dataChangedIdx = index(m_entries[last].submodel, 0);
auto roles = m_roleNames.keys();
roles.removeOne(m_submodelRole);
roles.removeOne(*m_groupingRole);
emit dataChanged(dataChangedIdx, dataChangedIdx, roles.toVector());
}
});
connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, this,
[this, model](const QModelIndex &parent, int first, int last) {
int firstSubmodelToBeRemoved = -1;
int lastSubmodelToBeRemoved = -1;
bool mergeRequired = first > 0
&& last < m_entries.size() - 1
&& m_entries[first - 1].submodel != m_entries[last + 1].submodel
&& ::data(model, first - 1, *m_groupingRole)
== ::data(model, last + 1, *m_groupingRole);
std::vector<std::tuple<RangeModel*, int, int>> removals;
for (int i = first; i <= last;) {
Entry& entry = m_entries[i];
auto submodel = entry.submodel;
auto submodelIndex = entry.submodelIndex;
RangeModel* s = m_submodels[submodel].get();
auto submodelCount = s->rowCount();
int remaining = last - i + 1;
if (submodelIndex == 0 && remaining >= submodelCount) {
if (firstSubmodelToBeRemoved == -1) {
firstSubmodelToBeRemoved = submodel;
lastSubmodelToBeRemoved = submodel;
} else {
lastSubmodelToBeRemoved++;
}
} else {
int removeFrom = submodelIndex;
int removeTo = std::min(removeFrom + remaining, submodelCount) - 1;
int countToRemove = removeTo - removeFrom + 1;
if (removeFrom == 0) {
if (!mergeRequired) {
s->beginRemoveRows({}, removeFrom, removeTo);
s->from() = s->from() + countToRemove;
s->endRemoveRows();
}
} else {
s->beginRemoveRows({}, removeFrom, removeTo);
s->to() = s->to() - countToRemove;
Q_ASSERT(m_pendingRemovalSubmodel == nullptr);
// removing tail of the submodel
if (removeTo == submodelCount - 1)
s->endRemoveRows();
// removing from the middle, must be deferred to keep correct
// intermediate state
else
m_pendingRemovalSubmodel = s;
}
}
i += submodelCount - submodelIndex;
}
auto mergeCount = 0;
if (mergeRequired) {
lastSubmodelToBeRemoved++;
auto submodel = m_entries[first - 1].submodel;
auto submodelToBeMerged = m_entries[last + 1].submodel;
mergeCount = m_submodels[submodelToBeMerged]->rowCount()
- m_entries[last + 1].submodelIndex;
}
if (firstSubmodelToBeRemoved != -1) {
beginRemoveRows({}, firstSubmodelToBeRemoved, lastSubmodelToBeRemoved);
m_submodels.erase(m_submodels.begin() + firstSubmodelToBeRemoved,
m_submodels.begin() + lastSubmodelToBeRemoved + 1);
endRemoveRows();
}
if (mergeRequired) {
auto submodel = m_entries[first - 1].submodel;
RangeModel* s = m_submodels[submodel].get();
s->beginInsertRows({}, s->rowCount(), s->rowCount() + mergeCount - 1);
s->to() = s->to() + mergeCount;
Q_ASSERT(m_pendingMergeSubmodel == nullptr);
m_pendingMergeSubmodel = s;
}
});
connect(model, &QAbstractItemModel::rowsRemoved, this,
[this, model](const QModelIndex &parent, int first, int last) {
int newSize = m_entries.size() - (last - first + 1);
m_entries.clear();
m_entries.reserve(newSize);
int sourceIndex = 0;
for (int i = 0; i < m_submodels.size(); i++) {
auto s = m_submodels[i].get();
auto count = s->rowCount();
s->from() = sourceIndex;
s->to() = sourceIndex + count - 1;
for (int j = 0; j < count; j++)
m_entries.push_back({i, j, sourceIndex++});
}
if (m_pendingMergeSubmodel) {
m_pendingMergeSubmodel->endInsertRows();
m_pendingMergeSubmodel = nullptr;
}
if (m_pendingRemovalSubmodel) {
m_pendingRemovalSubmodel->endRemoveRows();
m_pendingRemovalSubmodel = nullptr;
}
Q_ASSERT(m_entries.size() == newSize);
});
connect(model, &QAbstractItemModel::dataChanged, this, [this, model] (
const QModelIndex& topLeft, const QModelIndex& bottomRight,
const QVector<int>& roles) {
if (!topLeft.isValid() || !bottomRight.isValid())
return;
auto sourceFirst = topLeft.row();
auto sourceLast = bottomRight.row();
auto destFirst = m_entries.at(sourceFirst).submodel;
auto destLast = m_entries.at(sourceLast).submodel;
// internal models
int changeSize = sourceLast - sourceFirst + 1;
int offset = m_entries.at(sourceFirst).submodelIndex;
for (auto i = destFirst; i <= destLast; i++) {
auto submodel = m_submodels[i].get();
auto sumodelChangeSize = std::min(changeSize,
submodel->rowCount() - offset);
emit submodel->dataChanged(submodel->index(offset),
submodel->index(offset + sumodelChangeSize - 1),
roles);
changeSize -= sumodelChangeSize;
offset = 0;
}
// external model
if (m_entries.at(sourceFirst).submodelIndex > 0)
destFirst++;
if (destLast < destFirst)
return;
const QVector<int>& rolesAligned = roles.isEmpty()
? model->roleNames().keys().toVector()
: roles;
emit this->dataChanged(this->index(destFirst), this->index(destLast),
rolesAligned);
});
connect(model, &QAbstractItemModel::modelAboutToBeReset, this, [this] {
this->beginResetModel();
});
connect(model, &QAbstractItemModel::modelReset, this, [this] {
this->endResetModel();
});
connect(model, &QAbstractItemModel::layoutAboutToBeChanged, this, [this] {
this->beginResetModel();
});
connect(model, &QAbstractItemModel::layoutChanged, this, [this] {
this->endResetModel();
});
}
#include "groupingmodel.moc"

View File

@ -10,14 +10,15 @@
#include "StatusQ/fastexpressionsorter.h"
#include "StatusQ/formatteddoubleproperty.h"
#include "StatusQ/functionaggregator.h"
#include "StatusQ/groupingmodel.h"
#include "StatusQ/leftjoinmodel.h"
#include "StatusQ/modelentry.h"
#include "StatusQ/modelutilsinternal.h"
#include "StatusQ/movablemodel.h"
#include "StatusQ/objectproxymodel.h"
#include "StatusQ/permissionutilsinternal.h"
#include "StatusQ/rolesrenamingmodel.h"
#include "StatusQ/rxvalidator.h"
#include "StatusQ/modelentry.h"
#include "StatusQ/snapshotobject.h"
#include "StatusQ/statussyntaxhighlighter.h"
#include "StatusQ/statuswindow.h"
@ -46,6 +47,7 @@ public:
qmlRegisterType<ManageTokensController>("StatusQ.Models", 0, 1, "ManageTokensController");
qmlRegisterType<ManageTokensModel>("StatusQ.Models", 0, 1, "ManageTokensModel");
qmlRegisterType<GroupingModel>("StatusQ", 0, 1, "GroupingModel");
qmlRegisterType<SourceModel>("StatusQ", 0, 1, "SourceModel");
qmlRegisterType<ConcatModel>("StatusQ", 0, 1, "ConcatModel");
qmlRegisterType<MovableModel>("StatusQ", 0, 1, "MovableModel");

View File

@ -110,4 +110,8 @@ add_test(NAME ModelEntryTest COMMAND ModelEntryTest)
add_executable(SnapshotObjectTest tst_SnapshotObject.cpp)
target_link_libraries(SnapshotObjectTest PRIVATE StatusQ StatusQTestLib)
add_test(NAME SnapshotObjectTest COMMAND SnapshotObjectTest)
add_test(NAME SnapshotObjectTest COMMAND SnapshotObjectTest)
add_executable(GroupingModelTest tst_GroupingModel.cpp)
target_link_libraries(GroupingModelTest PRIVATE Qt5::Qml StatusQ StatusQTestLib)
add_test(NAME GroupingModelTest COMMAND GroupingModelTest)

View File

@ -29,6 +29,8 @@ ListModelWrapper::ListModelWrapper(QQmlEngine& engine, const QString& content)
m_model.reset(qobject_cast<QAbstractItemModel*>(
component.create(engine.rootContext())));
Q_ASSERT_X(m_model, "ListModelWrapper", "creating model failed!");
}
ListModelWrapper::ListModelWrapper(QQmlEngine& engine, const QJsonArray& content)

File diff suppressed because it is too large Load Diff