Skip to content

Conversation

@Ivy233
Copy link
Contributor

@Ivy233 Ivy233 commented Jan 14, 2026

在 dock 空白区域右键时,启动器应该随其他弹窗一起关闭。
此修改为 dock 的 onRequestClosePopup 处理器添加了可选的启动器支持。

修改内容:

  • 添加 launcherControllerWrapper 属性以条件性支持启动器
  • 在 Component.onCompleted 中使用 try-catch 初始化包装器以保证兼容性
  • 在 onRequestClosePopup 中关闭启动器(如果包装器可用)
  • 移除延迟菜单定时器,关闭弹窗后直接打开菜单
  • 修复 MenuHelper 正确断开 aboutToHide 处理器

PMS: BUG-323289

Summary by Sourcery

Ensure the dock closes the launcher along with other popups and improve menu cleanup handling.

Bug Fixes:

  • Close the dde-launchpad launcher when dock popups are dismissed, such as when right-clicking the dock background.
  • Prevent stale aboutToHide signal handlers in MenuHelper by tracking and disconnecting per-menu connections.

Enhancements:

  • Add an optional LauncherController wrapper to support launchpad control when available and remain compatible when it is not.

在 dock 空白区域右键时,启动器应该随其他弹窗一起关闭。
此修改为 dock 的 onRequestClosePopup 处理器添加了可选的启动器支持。

修改内容:
- 添加 launcherControllerWrapper 属性以条件性支持启动器
- 在 Component.onCompleted 中使用 try-catch 初始化包装器以保证兼容性
- 在 onRequestClosePopup 中关闭启动器(如果包装器可用)
- 移除延迟菜单定时器,关闭弹窗后直接打开菜单
- 修复 MenuHelper 正确断开 aboutToHide 处理器

PMS: BUG-323289
@sourcery-ai
Copy link

sourcery-ai bot commented Jan 14, 2026

Reviewer's Guide

Adds optional dde-launchpad integration so the launcher closes together with other dock popups, and fixes menu cleanup by correctly managing aboutToHide handlers while simplifying popup/menu timing.

Sequence diagram for closing dock popups and optional launcher

sequenceDiagram
    actor User
    participant Dock as DockWindow
    participant Popup as DockPopup
    participant MenuHelper
    participant LauncherWrapper as LauncherControllerWrapper
    participant LauncherController
    participant Menu

    User->>Dock: rightClickBlankArea()
    Dock->>Dock: onRequestClosePopup()
    Dock->>Popup: DS.closeChildrenWindows(popup)
    alt popup visible
        Dock->>Popup: close()
    end
    alt launcherControllerWrapper exists
        Dock->>LauncherWrapper: hide()
        LauncherWrapper->>LauncherController: set visible = false
    end
    Dock->>MenuHelper: openMenu(contextMenu)
    alt activeMenu exists
        MenuHelper->>MenuHelper: activeMenu.close()
    end
    MenuHelper->>MenuHelper: disconnect previous aboutToHide handler for menu
    MenuHelper->>MenuHelper: handler = function() { activeMenu = null }
    MenuHelper->>Menu: menu.open()
    MenuHelper->>Menu: menu.aboutToHide.connect(handler)
    MenuHelper->>MenuHelper: activeMenu = menu
Loading

Updated class diagram for dock main window and MenuHelper

classDiagram
    class DockWindow {
        int dockRemainingSpaceForCenter
        int dockPartSpacing
        real dockItemIconSize
        var launcherControllerWrapper
        onRequestClosePopup()
        onDockScreenChanged()
        Component_onCompleted()
    }

    class LauncherControllerWrapper {
        hide()
    }

    class LauncherController {
        bool visible
    }

    class MenuHelper {
        Menu activeMenu
        var aboutToHideConnections
        openMenu(menu)
        closeMenu(menu)
    }

    class Menu {
        open()
        close()
        aboutToHide
    }

    DockWindow --> LauncherControllerWrapper : optionally creates
    LauncherControllerWrapper --> LauncherController : controls visibility
    MenuHelper --> Menu : manages
Loading

File-Level Changes

Change Details Files
Add optional launcher controller wrapper and close the launcher when dock popups are closed.
  • Introduce launcherControllerWrapper property on the main dock Window to encapsulate optional launchpad access.
  • Initialize launcherControllerWrapper in Component.onCompleted using Qt.createQmlObject with a try/catch so docks work even if org.deepin.launchpad is unavailable.
  • Extend onRequestClosePopup to invoke launcherControllerWrapper.hide() so dde-launchpad is hidden when popups are closed.
  • Minor whitespace cleanups in main.qml around layout and connections code.
panels/dock/package/main.qml
Fix menu state cleanup by tracking and properly disconnecting aboutToHide handlers per menu instance.
  • Add aboutToHideConnections map property to store aboutToHide handlers by menu instance.
  • In openMenu, disconnect any previously stored aboutToHide handler for the same menu before reconnecting, wrapped in try/catch for safety.
  • Replace inline arrow connection with a named handler function that sets activeMenu to null and store/connect that per menu.
  • Keep activeMenu semantics the same while ensuring no duplicate or leaked aboutToHide connections.
panels/dock/MenuHelper.qml

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

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 - I've found 1 issue, and left some high level feedback:

  • Using a plain JS object for aboutToHideConnections with menu objects as keys will coerce the keys to strings (e.g. [object Object]), so different menu instances can collide; consider using a Map or attaching the handler directly as a property on the menu instance to avoid incorrect disconnect behavior.
  • aboutToHideConnections never clears entries when a menu is closed or destroyed, so over time this can accumulate stale references and handlers; consider deleting the entry in the aboutToHide handler or in closeMenu to keep the mapping in sync with live menus.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Using a plain JS object for `aboutToHideConnections` with `menu` objects as keys will coerce the keys to strings (e.g. `[object Object]`), so different menu instances can collide; consider using a `Map` or attaching the handler directly as a property on the `menu` instance to avoid incorrect disconnect behavior.
- `aboutToHideConnections` never clears entries when a menu is closed or destroyed, so over time this can accumulate stale references and handlers; consider deleting the entry in the `aboutToHide` handler or in `closeMenu` to keep the mapping in sync with live menus.

## Individual Comments

### Comment 1
<location> `panels/dock/MenuHelper.qml:11` </location>
<code_context>

 Item {
     property Menu activeMenu: null
+    property var aboutToHideConnections: ({})  // Store connections by menu object
+
     function openMenu(menu: Menu) {
</code_context>

<issue_to_address>
**issue (bug_risk):** Using a plain JS object keyed by menu instances is unreliable because keys are coerced to strings.

Because QML/JS object keys are always strings, indexing with `aboutToHideConnections[menu]` will stringify `menu` (e.g. to `"[object Object]"`), so different `Menu` instances can collide and overwrite each other’s handlers. Instead, store the handler directly on the `menu` instance (e.g. `menu._aboutToHideHandler`) or use a small helper component that keeps the connection alongside the specific `Menu` object.
</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.


Item {
property Menu activeMenu: null
property var aboutToHideConnections: ({}) // Store connections by menu object
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): Using a plain JS object keyed by menu instances is unreliable because keys are coerced to strings.

Because QML/JS object keys are always strings, indexing with aboutToHideConnections[menu] will stringify menu (e.g. to "[object Object]"), so different Menu instances can collide and overwrite each other’s handlers. Instead, store the handler directly on the menu instance (e.g. menu._aboutToHideHandler) or use a small helper component that keeps the connection alongside the specific Menu object.

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: Ivy233

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

Comment on lines +758 to +759
'import QtQuick 2.15; import org.deepin.launchpad 1.0; QtObject { function hide() { LauncherController.visible = false } }',
dock, "LauncherControllerWrapper")
Copy link
Member

Choose a reason for hiding this comment

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

啊?这里的目的仅仅是为了获取LauncherController以便你隐藏launchpad是么?

Copy link
Member

Choose a reason for hiding this comment

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

onRequestClosePopup 的时候关掉启动器的话,下面那一堆仍然是需要的吗?

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes an issue where the dde-launchpad launcher doesn't close when right-clicking on the dock's empty area. The fix adds optional launcher support to the dock's popup close handler while maintaining compatibility when the launcher module is unavailable. Additionally, it improves MenuHelper's signal handler management to prevent stale connections.

Changes:

  • Added optional LauncherController wrapper to conditionally close the launcher when dock popups are dismissed
  • Fixed MenuHelper to properly track and disconnect aboutToHide signal handlers per menu instance
  • Cleaned up trailing whitespace in main.qml

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
panels/dock/package/main.qml Adds launcherControllerWrapper property with try-catch initialization and integrates launcher hiding into onRequestClosePopup handler; removes trailing whitespace
panels/dock/MenuHelper.qml Implements per-menu connection tracking to prevent handler accumulation and properly disconnect previous handlers

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


Item {
property Menu activeMenu: null
property var aboutToHideConnections: ({}) // Store connections by menu object
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

Using a Menu object as a JavaScript object key can lead to unexpected behavior because QML objects don't have stable string representations. This could cause memory leaks as old menu instances won't be properly cleaned up from the dictionary. Consider using a WeakMap-like pattern or implementing cleanup logic in a Component.onDestruction handler.

Copilot uses AI. Check for mistakes.
try {
menu.aboutToHide.disconnect(aboutToHideConnections[menu])
} catch (e) {
// Silently ignore disconnect errors
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

The try-catch block silently suppresses disconnect errors without logging or clearing the stale entry from aboutToHideConnections. If disconnection fails, the stale handler remains in the dictionary, potentially causing memory leaks. Consider logging the error or removing the entry from aboutToHideConnections after the disconnect attempt.

Suggested change
// Silently ignore disconnect errors
console.warn("Failed to disconnect aboutToHide handler for menu:", menu, "error:", e)
} finally {
delete aboutToHideConnections[menu]

Copilot uses AI. Check for mistakes.
Comment on lines +757 to +761
launcherControllerWrapper = Qt.createQmlObject(
'import QtQuick 2.15; import org.deepin.launchpad 1.0; QtObject { function hide() { LauncherController.visible = false } }',
dock, "LauncherControllerWrapper")
} catch (e) {
// Launchpad module not available, launcherControllerWrapper remains null
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

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

The inline QML string creates a tight coupling and makes the code harder to maintain. Consider extracting this to a separate QML file (e.g., LauncherControllerWrapper.qml) that can be loaded conditionally using a Loader or Qt.createComponent, which would improve readability and make the wrapper logic easier to test and modify.

Suggested change
launcherControllerWrapper = Qt.createQmlObject(
'import QtQuick 2.15; import org.deepin.launchpad 1.0; QtObject { function hide() { LauncherController.visible = false } }',
dock, "LauncherControllerWrapper")
} catch (e) {
// Launchpad module not available, launcherControllerWrapper remains null
var launcherComponent = Qt.createComponent("LauncherControllerWrapper.qml");
if (launcherComponent.status === Component.Ready) {
launcherControllerWrapper = launcherComponent.createObject(dock);
}
} catch (e) {
// Launchpad module or wrapper component not available, launcherControllerWrapper remains null

Copilot uses AI. Check for mistakes.

// Try to create LauncherController wrapper for optional launchpad support
try {
launcherControllerWrapper = Qt.createQmlObject(
Copy link
Contributor

Choose a reason for hiding this comment

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

这种不应该在dde-shell中依赖launchpad,

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