Skip to content

Conversation

@BLumia
Copy link
Member

@BLumia BLumia commented Nov 4, 2025

简单而言,对于程序A,打开a1,打开a2,关闭a1后会观察到任务栏指示此图标无窗口.原因是 DockGroupModel 的 rowsRemoved 发了 dataChanged,会触发 all(), 然后因为其是继承的 RoleGroupModel,它的rowsRemoved 会被它本 体和 DockGroupModel 都接到,但 DockGroupModel 比 RoleGroupModel先收 到了信号,导致了时序问题,使后续 all() 调用时其中获取的数据已经不再正确.

此处通过使相关信号排队处理的方式,使 DockGroupModel 的事件响应晚于 RoleGroupModel 的移除事件响应.

Summary by Sourcery

Fix incorrect window state updates in DockGroupModel by deferring its signal handling to avoid ordering conflicts with its base class.

Bug Fixes:

  • Ensure DockGroupModel updates active window state correctly when windows are removed to prevent taskbar indicators from showing no windows

Enhancements:

  • Change connections for rowsInserted, rowsRemoved, and dataChanged to use Qt::QueuedConnection, delaying DockGroupModel’s response until after RoleGroupModel’s events

简单而言,对于程序A,打开a1,打开a2,关闭a1后会观察到任务栏指示此图标无
窗口.原因是 DockGroupModel 的 rowsRemoved 发了 dataChanged,会触发
all(), 然后因为其是继承的 RoleGroupModel,它的rowsRemoved 会被它本
体和 DockGroupModel 都接到,但 DockGroupModel 比 RoleGroupModel先收
到了信号,导致了时序问题,使后续 all() 调用时其中获取的数据已经不再正
确.

此处通过使相关信号排队处理的方式,使 DockGroupModel 的事件响应晚于
RoleGroupModel 的移除事件响应.

PMS: BUG-338573
Log:
@sourcery-ai
Copy link

sourcery-ai bot commented Nov 4, 2025

Reviewer's Guide

Switches DockGroupModel's rowsInserted, rowsRemoved, and dataChanged signal-slot connections to use queued connections, ensuring DockGroupModel processes removals after RoleGroupModel and thus fixing incorrect window state updates.

Sequence diagram for queued signal processing in DockGroupModel

sequenceDiagram
    participant RoleGroupModel
    participant DockGroupModel
    participant QtEventLoop
    Note over RoleGroupModel, DockGroupModel: rowsRemoved signal emitted
    RoleGroupModel->RoleGroupModel: Handle rowsRemoved synchronously
    RoleGroupModel->QtEventLoop: Emit rowsRemoved signal
    QtEventLoop->DockGroupModel: Deliver rowsRemoved signal (queued)
    DockGroupModel->DockGroupModel: Handle rowsRemoved after RoleGroupModel
    DockGroupModel->DockGroupModel: Update m_currentActiveWindow
    DockGroupModel->DockGroupModel: Emit dataChanged
Loading

Class diagram for updated DockGroupModel signal connections

classDiagram
    class DockGroupModel {
        +DockGroupModel(QAbstractItemModel *sourceModel, int role, QObject *parent)
        +data(const QModelIndex &index, int role) const
        -m_roleForDeduplication
        -m_currentActiveWindow
        +resetActiveWindow(parentRow)
        <<signal>> rowsInserted (queued)
        <<signal>> rowsRemoved (queued)
        <<signal>> dataChanged (queued)
    }
    DockGroupModel --|> RoleGroupModel
    DockGroupModel --|> AbstractTaskManagerInterface
    class RoleGroupModel {
        +rowCount(const QModelIndex &parent) const
    }
    class AbstractTaskManagerInterface {
    }
    DockGroupModel o-- RoleGroupModel
    DockGroupModel o-- AbstractTaskManagerInterface
Loading

File-Level Changes

Change Details Files
Use queued connections for DockGroupModel signal handlers to fix race condition
  • rowsInserted connection updated to use Qt::QueuedConnection
  • rowsRemoved connection updated to use Qt::QueuedConnection
  • dataChanged connection updated to use Qt::QueuedConnection
panels/dock/taskmanager/dockgroupmodel.cpp

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@deepin-ci-robot
Copy link

deepin pr auto review

这个代码变更主要是在连接信号槽时添加了 Qt::QueuedConnection 连接类型。我来分析一下这个变更:

  1. 代码质量改进:
  • 代码格式变得更加清晰,使用了多行格式来组织长函数调用
  • 每个参数单独一行,提高了可读性
  • 缩进保持一致,符合Qt的编码规范
  1. 性能和安全改进:
  • 添加了 Qt::QueuedConnection 是一个重要的改进。这意味着:
    • 信号槽的调用将被放入事件队列中异步执行
    • 避免了直接调用可能导致的递归问题
    • 防止在信号处理过程中修改模型数据时可能出现的崩溃
    • 提高了程序的响应性,特别是在处理大量数据时
  1. 潜在影响:
  • 使用 QueuedConnection 会增加一些延迟,因为信号槽调用是异步的
  • 但在这个场景下,这种延迟是可以接受的,而且带来的稳定性提升更重要
  1. 建议:
  • 这个改动是合理的,特别是在处理模型数据变更时
  • 建议在类似的模型操作中都考虑使用 QueuedConnection
  • 可以考虑在其他信号槽连接中也应用这个模式,特别是那些可能导致递归调用的场景

总的来说,这是一个很好的改进,提高了代码的稳定性和可维护性,同时保持了良好的性能特征。

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • Add a brief code comment above the queued connections explaining the signal ordering issue and why Qt::QueuedConnection is needed.
  • Extract the duplicated lambda logic for rowsInserted, rowsRemoved, and dataChanged into helper methods to reduce duplication and improve maintainability.
  • Review potential side effects of queuing all dataChanged and row‐change signals; ensure this deferred delivery won’t introduce latency or ordering issues elsewhere.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Add a brief code comment above the queued connections explaining the signal ordering issue and why Qt::QueuedConnection is needed.
- Extract the duplicated lambda logic for rowsInserted, rowsRemoved, and dataChanged into helper methods to reduce duplication and improve maintainability.
- Review potential side effects of queuing all dataChanged and row‐change signals; ensure this deferred delivery won’t introduce latency or ordering issues elsewhere.

## Individual Comments

### Comment 1
<location> `panels/dock/taskmanager/dockgroupmodel.cpp:26` </location>
<code_context>
-        Q_UNUSED(last)
-        if (!parent.isValid())
-            return;
-        Q_EMIT dataChanged(index(parent.row(), 0), index(parent.row(), 0), {TaskManager::WindowsRole});
-    });
-    connect(this, &QAbstractItemModel::rowsRemoved, this, [this](const QModelIndex &parent, int first, int last) {
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Emitting dataChanged for only the first column may miss updates for other columns.

If your model has multiple columns, limiting dataChanged to column 0 may cause the UI to miss updates for other columns. Please confirm whether the model is single-column or update the signal to cover all relevant columns.

Suggested implementation:

```cpp
        int colCount = columnCount(parent);
        if (colCount > 0) {
            Q_EMIT dataChanged(index(parent.row(), 0), index(parent.row(), colCount - 1), {TaskManager::WindowsRole});
        }

```

If `columnCount()` is not accessible in this context, you may need to use `this->columnCount(parent)` or ensure you have a valid model pointer. Also, if the model is guaranteed to be single-column, you can leave the code as-is, but this change will ensure all columns are updated if there are multiple.
</issue_to_address>

### Comment 2
<location> `panels/dock/taskmanager/dockgroupmodel.cpp:55` </location>
<code_context>
-
-    connect(this, &QAbstractItemModel::dataChanged, this, [this](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles) {
-        Q_UNUSED(bottomRight)
-        if (!topLeft.parent().isValid())
-            return;
-        auto parentRow = topLeft.parent().row();
</code_context>

<issue_to_address>
**question (bug_risk):** Guard clause may skip legitimate updates for top-level items.

This guard prevents updates to top-level items. If updates to these items are required, consider revising this condition.
</issue_to_address>

### Comment 3
<location> `panels/dock/taskmanager/dockgroupmodel.cpp:21` </location>
<code_context>
-                // Current active window is after the removed range, shift it back
-                int removedCount = last - first + 1;
-                m_currentActiveWindow[parentRow] = currentActive - removedCount;
+    connect(
+        this,
+        &QAbstractItemModel::rowsInserted,
</code_context>

<issue_to_address>
**issue (complexity):** Consider introducing a queuedConnect helper to eliminate repetitive connect boilerplate and simplify the constructor.

Here’s one way to collapse the repeated boiler-plate while keeping all behavior intact:

1) Add a small `queuedConnect()` helper in your `.cpp` (above your ctor):

```cpp
namespace {
template<typename Sender, typename Signal, typename Receiver, typename Slot>
QMetaObject::Connection queuedConnect(Sender* s,
                                      Signal   sig,
                                      Receiver* r,
                                      Slot     slot)
{
    return QObject::connect(s, sig, r, slot, Qt::QueuedConnection);
}
}
```

2) Replace each `connect(..., Qt::QueuedConnection)` with:

```cpp
queuedConnect(this, &QAbstractItemModel::rowsInserted, this,
    [this](const QModelIndex &parent, int /*first*/, int /*last*/) {
        if (!parent.isValid()) return;
        Q_EMIT dataChanged(
            index(parent.row(), 0),
            index(parent.row(), 0),
            { TaskManager::WindowsRole }
        );
    }
);

queuedConnect(this, &QAbstractItemModel::rowsRemoved, this,
    [this](const QModelIndex &parent, int first, int last) {
        if (!parent.isValid()) return;
        const int row = parent.row();
        if (m_currentActiveWindow.contains(row)) {
            int cur = m_currentActiveWindow[row];
            if (cur >= first && cur <= last)
                resetActiveWindow(row);
            else if (cur > last)
                m_currentActiveWindow[row] = cur - (last - first + 1);
        }
        Q_EMIT dataChanged(
            index(row, 0),
            index(row, 0),
            { TaskManager::WindowsRole }
        );
    }
);

queuedConnect(this, &QAbstractItemModel::dataChanged, this,
    [this](const QModelIndex &topLeft, const QModelIndex&, const QList<int> &roles){
        auto p = topLeft.parent();
        if (!p.isValid()) return;
        int row = p.row();
        Q_EMIT dataChanged(index(row, 0), index(row, 0), roles);
        if (roles.contains(TaskManager::ActiveRole))
            m_currentActiveWindow.insert(row, topLeft.row());
    }
);
```

This:

- Removes the repeated `, Qt::QueuedConnection` overload on each call  
- Keeps each lambda exactly as-is  
- Makes your constructor much shorter and easier to scan
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

&QAbstractItemModel::rowsInserted,
this,
[this](const QModelIndex &parent, int first, int last) {
Q_UNUSED(first)
Copy link

Choose a reason for hiding this comment

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

suggestion (bug_risk): Emitting dataChanged for only the first column may miss updates for other columns.

If your model has multiple columns, limiting dataChanged to column 0 may cause the UI to miss updates for other columns. Please confirm whether the model is single-column or update the signal to cover all relevant columns.

Suggested implementation:

        int colCount = columnCount(parent);
        if (colCount > 0) {
            Q_EMIT dataChanged(index(parent.row(), 0), index(parent.row(), colCount - 1), {TaskManager::WindowsRole});
        }

If columnCount() is not accessible in this context, you may need to use this->columnCount(parent) or ensure you have a valid model pointer. Also, if the model is guaranteed to be single-column, you can leave the code as-is, but this change will ensure all columns are updated if there are multiple.

// Current active window is after the removed range, shift it back
int removedCount = last - first + 1;
m_currentActiveWindow[parentRow] = currentActive - removedCount;
}
Copy link

Choose a reason for hiding this comment

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

question (bug_risk): Guard clause may skip legitimate updates for top-level items.

This guard prevents updates to top-level items. If updates to these items are required, consider revising this condition.

// Current active window is after the removed range, shift it back
int removedCount = last - first + 1;
m_currentActiveWindow[parentRow] = currentActive - removedCount;
connect(
Copy link

Choose a reason for hiding this comment

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

issue (complexity): Consider introducing a queuedConnect helper to eliminate repetitive connect boilerplate and simplify the constructor.

Here’s one way to collapse the repeated boiler-plate while keeping all behavior intact:

  1. Add a small queuedConnect() helper in your .cpp (above your ctor):
namespace {
template<typename Sender, typename Signal, typename Receiver, typename Slot>
QMetaObject::Connection queuedConnect(Sender* s,
                                      Signal   sig,
                                      Receiver* r,
                                      Slot     slot)
{
    return QObject::connect(s, sig, r, slot, Qt::QueuedConnection);
}
}
  1. Replace each connect(..., Qt::QueuedConnection) with:
queuedConnect(this, &QAbstractItemModel::rowsInserted, this,
    [this](const QModelIndex &parent, int /*first*/, int /*last*/) {
        if (!parent.isValid()) return;
        Q_EMIT dataChanged(
            index(parent.row(), 0),
            index(parent.row(), 0),
            { TaskManager::WindowsRole }
        );
    }
);

queuedConnect(this, &QAbstractItemModel::rowsRemoved, this,
    [this](const QModelIndex &parent, int first, int last) {
        if (!parent.isValid()) return;
        const int row = parent.row();
        if (m_currentActiveWindow.contains(row)) {
            int cur = m_currentActiveWindow[row];
            if (cur >= first && cur <= last)
                resetActiveWindow(row);
            else if (cur > last)
                m_currentActiveWindow[row] = cur - (last - first + 1);
        }
        Q_EMIT dataChanged(
            index(row, 0),
            index(row, 0),
            { TaskManager::WindowsRole }
        );
    }
);

queuedConnect(this, &QAbstractItemModel::dataChanged, this,
    [this](const QModelIndex &topLeft, const QModelIndex&, const QList<int> &roles){
        auto p = topLeft.parent();
        if (!p.isValid()) return;
        int row = p.row();
        Q_EMIT dataChanged(index(row, 0), index(row, 0), roles);
        if (roles.contains(TaskManager::ActiveRole))
            m_currentActiveWindow.insert(row, topLeft.row());
    }
);

This:

  • Removes the repeated , Qt::QueuedConnection overload on each call
  • Keeps each lambda exactly as-is
  • Makes your constructor much shorter and easier to scan

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 18202781743, BLumia, tsic404

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@BLumia BLumia merged commit 03c7157 into linuxdeepin:master Nov 5, 2025
10 of 11 checks passed
@BLumia BLumia deleted the pms-338573 branch November 5, 2025 03:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants