status-desktop/ui/StatusQ/src/groupingmodel.cpp

685 lines
19 KiB
C++

#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"