diff --git a/panels/dock/taskmanager/CMakeLists.txt b/panels/dock/taskmanager/CMakeLists.txt index 6cabad4ab..3c3d291a3 100644 --- a/panels/dock/taskmanager/CMakeLists.txt +++ b/panels/dock/taskmanager/CMakeLists.txt @@ -57,6 +57,8 @@ add_library(dock-taskmanager SHARED ${DBUS_INTERFACES} appitem.h rolecombinemodel.cpp rolecombinemodel.h + rolegroupmodel.cpp + rolegroupmodel.h itemmodel.cpp itemmodel.h desktopfileabstractparser.cpp diff --git a/panels/dock/taskmanager/rolegroupmodel.cpp b/panels/dock/taskmanager/rolegroupmodel.cpp new file mode 100644 index 000000000..5bbc390f1 --- /dev/null +++ b/panels/dock/taskmanager/rolegroupmodel.cpp @@ -0,0 +1,255 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rolegroupmodel.h" + +#include + +RoleGroupModel::RoleGroupModel(QAbstractItemModel *sourceModel, int role, QObject *parent) + : QAbstractProxyModel(parent) + , m_roleForDeduplication(role) +{ + RoleGroupModel::setSourceModel(sourceModel); +} + +void RoleGroupModel::setDeduplicationRole(const int &role) +{ + if (role != m_roleForDeduplication) { + m_roleForDeduplication = role; + rebuildTreeSource(); + } +} + +int RoleGroupModel::deduplicationRole() const +{ + return m_roleForDeduplication; +} + +void RoleGroupModel::setSourceModel(QAbstractItemModel *model) +{ + if (sourceModel()) { + sourceModel()->disconnect(this); + } + + QAbstractProxyModel::setSourceModel(model); + + rebuildTreeSource(); + if (sourceModel() == nullptr) + return; + + connect(sourceModel(), &QAbstractItemModel::rowsInserted, this, [this](const QModelIndex &parent, int first, int last) { + adjustMap(first, (last - first) + 1); + + for (int i = first; i <= last; i++) { + auto sourceIndex = sourceModel()->index(i, 0); + auto data = sourceIndex.data(m_roleForDeduplication).toString(); + if (data.isEmpty()) { + continue; + } + + auto list = m_map.value(data, nullptr); + if (nullptr == list) { + beginInsertRows(QModelIndex(), m_rowMap.size(), m_rowMap.size()); + list = new QList(); + m_map.insert(data, list); + m_rowMap.append(list); + endInsertRows(); + } + beginInsertRows(index(m_rowMap.indexOf(list), 0), list->size(), list->size()); + list->append(i); + endInsertRows(); + } + }); + + connect(sourceModel(), &QAbstractItemModel::rowsRemoved, this, [this](const QModelIndex &parent, int first, int last) { + for (int i = 0; i < m_rowMap.count(); ++i) { + auto sourceRows = m_rowMap.value(i); + for (int j = 0; j < sourceRows->size(); ++j) { + if (first <= sourceRows->value(j) && last >= sourceRows->value(j)) { + beginRemoveRows(index(m_rowMap.indexOf(sourceRows), 0), j, j); + sourceRows->removeAt(j); + endRemoveRows(); + --j; + } + } + if (sourceRows->size() == 0) { + beginRemoveRows(QModelIndex(), m_rowMap.indexOf(sourceRows), m_rowMap.indexOf(sourceRows)); + m_map.remove(m_map.key(sourceRows)); + m_rowMap.removeOne(sourceRows); + delete sourceRows; + endRemoveRows(); + } + } + adjustMap(first, -((last - first) + 1)); + }); + + connect(sourceModel(), &QAbstractItemModel::dataChanged, this, [this](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList &roles) { + // TODO: if roles contains m_roleForDeduplication, need update from topLeft 2 bottomRight or just send dataChanged + if (roles.contains(m_roleForDeduplication)) { + rebuildTreeSource(); + return; + } + + for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { + auto data = sourceModel()->index(i, 0).data(m_roleForDeduplication).toString(); + auto list = m_map.value(data, nullptr); + if (list == nullptr) + continue; + + auto index = createIndex(list->indexOf(i), 0, m_rowMap.indexOf(list)); + Q_EMIT dataChanged(index, index, roles); + } + }); + + connect(sourceModel(), &QAbstractItemModel::modelReset, this, [this]() { + rebuildTreeSource(); + }); +} + +int RoleGroupModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + auto list = m_rowMap.value(parent.row(), nullptr); + return nullptr == list ? 0 : list->size(); + } + + return m_rowMap.size(); +} + +int RoleGroupModel::columnCount(const QModelIndex &parent) const +{ + if (sourceModel()) { + return sourceModel()->columnCount(parent); + } + return 0; +} + +QHash RoleGroupModel::roleNames() const +{ + if (sourceModel()) { + return sourceModel()->roleNames(); + } + return QAbstractProxyModel::roleNames(); +} + +QVariant RoleGroupModel::data(const QModelIndex &index, int role) const +{ + auto parentPos = static_cast(index.internalId()); + auto list = m_rowMap.value(index.row(), nullptr); + if (parentPos == -1) { + if (nullptr == list || list->size() == 0) { + return QVariant(); + } + return sourceModel()->index(list->first(), 0).data(role); + } + + list = m_rowMap.value(parentPos); + if (list == nullptr) { + return QVariant(); + } + + return sourceModel()->index(list->value(index.row()), 0).data(role); +} + +QModelIndex RoleGroupModel::index(int row, int column, const QModelIndex &parent) const +{ + auto list = m_map.value(parent.data(m_roleForDeduplication).toString(), nullptr); + if (parent.isValid() && nullptr != list) { + return createIndex(row, column, m_rowMap.indexOf(list)); + } + + return createIndex(row, column, -1); +} + +QModelIndex RoleGroupModel::parent(const QModelIndex &child) const +{ + auto pos = static_cast(child.internalId()); + if (pos == -1) + return QModelIndex(); + + return createIndex(pos, 0); +} + +QModelIndex RoleGroupModel::mapToSource(const QModelIndex &proxyIndex) const +{ + auto parentIndex = proxyIndex.parent(); + QList *list = nullptr; + + if (parentIndex.isValid()) { + list = m_rowMap.value(parentIndex.row()); + } else { + list = m_map.value(proxyIndex.data(m_roleForDeduplication).toString()); + } + + if (nullptr == list) + return QModelIndex(); + + if (parentIndex.isValid()) { + return sourceModel()->index(list->value(proxyIndex.row()), 0); + } + + return sourceModel()->index(list->value(0), 0); +} + +QModelIndex RoleGroupModel::mapFromSource(const QModelIndex &sourceIndex) const +{ + auto data = sourceIndex.data(m_roleForDeduplication).toString(); + if (data.isEmpty()) { + return QModelIndex(); + } + + auto list = m_map.value(data, nullptr); + if (nullptr == list || 0 == list->size()) { + return {}; + } + + if (sourceIndex.row() == list->first()) { + return createIndex(m_rowMap.indexOf(list), 0, -1); + } + + auto pos = list->indexOf(sourceIndex.row()); + return createIndex(pos, 0, m_rowMap.indexOf(list)); +} + +void RoleGroupModel::rebuildTreeSource() +{ + beginResetModel(); + qDeleteAll(m_rowMap); + m_map.clear(); + m_rowMap.clear(); + + if (sourceModel() == nullptr) { + endResetModel(); + return; + } + + for (int i = 0; i < sourceModel()->rowCount(); i++) { + auto index = sourceModel()->index(i, 0); + auto data = index.data(m_roleForDeduplication).toString(); + if (data.isEmpty()) { + continue; + } + + auto list = m_map.value(data, nullptr); + if (nullptr == list) { + list = new QList(); + m_map.insert(data, list); + m_rowMap.append(list); + } + list->append(i); + } + endResetModel(); +} + +void RoleGroupModel::adjustMap(int base, int offset) +{ + for (int i = 0; i < m_rowMap.count(); ++i) { + auto sourceRows = m_rowMap.value(i); + for (int j = 0; j < sourceRows->size(); ++j) { + if (sourceRows->value(j) < base) + continue; + (*sourceRows)[j] += offset; + } + } +} diff --git a/panels/dock/taskmanager/rolegroupmodel.h b/panels/dock/taskmanager/rolegroupmodel.h new file mode 100644 index 000000000..a1cca23eb --- /dev/null +++ b/panels/dock/taskmanager/rolegroupmodel.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +class RoleGroupModel : public QAbstractProxyModel +{ + Q_OBJECT + +public: + explicit RoleGroupModel(QAbstractItemModel *sourceModel, int role, QObject *parent = nullptr); + void setSourceModel(QAbstractItemModel *sourceModel) override; + + void setDeduplicationRole(const int &role); + int deduplicationRole() const; + + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + bool hasIndex(int row, int column, const QModelIndex &parent = QModelIndex()) const; + Q_INVOKABLE virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + Q_INVOKABLE virtual QModelIndex parent(const QModelIndex &child) const override; + + QModelIndex mapToSource(const QModelIndex &proxyIndex) const override; + QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override; + +Q_SIGNALS: + void deduplicationRoleChanged(int role); + +private: + void rebuildTreeSource(); + void adjustMap(int base, int offset); + +private: + int m_roleForDeduplication; + + // for order + QList *> m_rowMap; + + // data 2 source row + QHash *> m_map; +}; diff --git a/tests/panels/dock/taskmanager/CMakeLists.txt b/tests/panels/dock/taskmanager/CMakeLists.txt index b66a395fc..73f60a3e1 100644 --- a/tests/panels/dock/taskmanager/CMakeLists.txt +++ b/tests/panels/dock/taskmanager/CMakeLists.txt @@ -25,3 +25,21 @@ target_include_directories(rolecombinemodel_tests PRIVATE ) add_test(NAME rolecombinemodel COMMAND rolecombinemodel_tests) + +add_executable(rolegroupmodel_tests + rolegroupmodeltests.cpp +) + +target_link_libraries(rolegroupmodel_tests + GTest::GTest + GTest::Main + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Test + dock-taskmanager +) +target_include_directories(rolegroupmodel_tests PRIVATE + ${CMAKE_SOURCE_DIR}/panels/dock/taskmanager/ +) + +add_test(NAME rolegroupmodel COMMAND rolegroupmodel_tests) + diff --git a/tests/panels/dock/taskmanager/rolegroupmodeltests.cpp b/tests/panels/dock/taskmanager/rolegroupmodeltests.cpp new file mode 100644 index 000000000..5719b16f3 --- /dev/null +++ b/tests/panels/dock/taskmanager/rolegroupmodeltests.cpp @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rolegroupmodel.h" +#include + +#include + +#include +#include +#include + +#include "rolegroupmodel.h" + +TEST(RoleGroupModel, RowCountTest) +{ + QStandardItemModel model; + QStandardItem *item1 = new QStandardItem; + QStandardItem *item2 = new QStandardItem; + QStandardItem *item3 = new QStandardItem; + QStandardItem *item4 = new QStandardItem; + QStandardItem *item5 = new QStandardItem; + + auto role = Qt::UserRole + 1; + + RoleGroupModel groupModel(&model, role); + model.setItemRoleNames({{role, "data"}}); + + item1->setData(QString("data"), role); + model.appendRow(item1); + + item2->setData(QString("data"), role); + model.appendRow(item2); + + model.appendRow(item3); + model.appendRow(item4); + model.appendRow(item5); + QSignalSpy spy(&model, &QAbstractItemModel::rowsRemoved); + QSignalSpy spys(&model, &QAbstractItemModel::rowsInserted); + + item3->setData(QString("data3"), role); + item4->setData(QString("data4"), role); + item5->setData(QString("data4"), role); + + auto index1 = groupModel.index(0, 0); + auto index11 = groupModel.index(0, 0, index1); + auto index2 = groupModel.index(1, 0, index1); + auto index3 = groupModel.index(1, 0); + auto index4 = groupModel.index(2, 0); + + EXPECT_EQ(groupModel.rowCount(), 3); + EXPECT_EQ(groupModel.rowCount(index1), 2); + EXPECT_EQ(groupModel.rowCount(index3), 1); + EXPECT_EQ(groupModel.rowCount(index4), 2); + + EXPECT_EQ(index11.data(role), index1.data(role)); + EXPECT_EQ(index1.data(role), "data"); + EXPECT_EQ(index2.data(role), "data"); + EXPECT_EQ(index3.data(role), "data3"); + EXPECT_EQ(index4.data(role), "data4"); + + // 移除了item3 + model.removeRows(2, 1); + EXPECT_EQ(groupModel.rowCount(), 2); + + // 移除了item4 + model.removeRows(2, 1); + EXPECT_EQ(groupModel.rowCount(), 2); + + index4 = groupModel.index(1, 0); + EXPECT_EQ(groupModel.rowCount(index1), 2); + // item5 的内容为data4 + EXPECT_EQ(groupModel.rowCount(index4), 1); +}