Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions panels/dock/taskmanager/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
255 changes: 255 additions & 0 deletions panels/dock/taskmanager/rolegroupmodel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include "rolegroupmodel.h"

#include <QList>

Check warning on line 7 in panels/dock/taskmanager/rolegroupmodel.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QList> not found. Please note: Cppcheck does not need standard library headers to get proper results.

RoleGroupModel::RoleGroupModel(QAbstractItemModel *sourceModel, int role, QObject *parent)
: QAbstractProxyModel(parent)
, m_roleForDeduplication(role)
{
RoleGroupModel::setSourceModel(sourceModel);
}

void RoleGroupModel::setDeduplicationRole(const int &role)

Check warning on line 16 in panels/dock/taskmanager/rolegroupmodel.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

The function 'setDeduplicationRole' is never used.
{
if (role != m_roleForDeduplication) {
m_roleForDeduplication = role;
rebuildTreeSource();
}
}

int RoleGroupModel::deduplicationRole() const

Check warning on line 24 in panels/dock/taskmanager/rolegroupmodel.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

The function 'deduplicationRole' is never used.
{
return m_roleForDeduplication;
}

void RoleGroupModel::setSourceModel(QAbstractItemModel *model)
{
if (sourceModel()) {
sourceModel()->disconnect(this);
Comment on lines +31 to +32
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Disconnecting all signals from the previous source model may unintentionally break other connections.

disconnect(this) removes all sourceModel→this connections, risking other slots. Instead, disconnect only the signals you connected or store QMetaObject::Connection objects to manage them.

}

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<int>();
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<int> &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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): columnCount() should check for a valid sourceModel pointer.

Add a null check for sourceModel() and return 0 if it’s unset to prevent a crash.

{
if (sourceModel()) {
return sourceModel()->columnCount(parent);
}
return 0;
}

QHash<int, QByteArray> RoleGroupModel::roleNames() const
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): roleNames() should check for a valid sourceModel pointer.

Add a null check and return an empty QHash if sourceModel() is null.

{
if (sourceModel()) {
return sourceModel()->roleNames();
}
return QAbstractProxyModel::roleNames();
}

QVariant RoleGroupModel::data(const QModelIndex &index, int role) const
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: data() should validate the index and sourceModel pointer.

Return a default QVariant when the index is invalid or sourceModel() is null to prevent crashes.

{
auto parentPos = static_cast<int>(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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): index() should validate row and column bounds.

Check that row and column fall within valid ranges to avoid out-of-bounds indexes.

{
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<int>(child.internalId());
if (pos == -1)
return QModelIndex();

return createIndex(pos, 0);
}

QModelIndex RoleGroupModel::mapToSource(const QModelIndex &proxyIndex) const
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: mapToSource() should validate proxyIndex and sourceModel pointer.

Return a default QModelIndex when proxyIndex is invalid or sourceModel() is null to prevent crashes.

{
auto parentIndex = proxyIndex.parent();
QList<int> *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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): mapFromSource() should validate sourceIndex and sourceModel pointer.

Return a default QModelIndex if sourceIndex is invalid or sourceModel() is null to avoid crashes.

{
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<int>();
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;
}
}
}
47 changes: 47 additions & 0 deletions panels/dock/taskmanager/rolegroupmodel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <QAbstractProxyModel>

Check warning on line 7 in panels/dock/taskmanager/rolegroupmodel.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QAbstractProxyModel> not found. Please note: Cppcheck does not need standard library headers to get proper results.

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<int, QByteArray> 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;

Check warning on line 23 in panels/dock/taskmanager/rolegroupmodel.h

View workflow job for this annotation

GitHub Actions / cppcheck

Local variable 'data' shadows outer function

Check warning on line 23 in panels/dock/taskmanager/rolegroupmodel.h

View workflow job for this annotation

GitHub Actions / cppcheck

Local variable 'data' shadows outer function

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;

Check warning on line 26 in panels/dock/taskmanager/rolegroupmodel.h

View workflow job for this annotation

GitHub Actions / cppcheck

Local variable 'index' shadows outer function
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<QList<int> *> m_rowMap;

// data 2 source row
QHash<QString, QList<int> *> m_map;
};
18 changes: 18 additions & 0 deletions tests/panels/dock/taskmanager/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Loading