Skip to content

fix: fix warning with closebtn and binding loop.#1517

Merged
18202781743 merged 1 commit intolinuxdeepin:masterfrom
wjyrich:fix-waring
Mar 20, 2026
Merged

fix: fix warning with closebtn and binding loop.#1517
18202781743 merged 1 commit intolinuxdeepin:masterfrom
wjyrich:fix-waring

Conversation

@wjyrich
Copy link
Contributor

@wjyrich wjyrich commented Mar 20, 2026

更新了 closebtn的未定义问题,修复了hover通知,循环依赖问题。

更新通知焦点循环一次后,第二次循环无法经过overlapnotify,从而无法给到焦点到后面其他的通知上。 以及修复 shift+tab 保证跟tab的逻辑一致,能够正常响应焦点处理。
还需修复: 带有action的通知焦点处理。

Summary by Sourcery

Improve keyboard focus and close-button behavior in notification items to prevent focus loops and ensure consistent navigation.

Bug Fixes:

  • Ensure notification close buttons are shown before focusing new items when navigating between notifications.
  • Prevent focus loops involving clear buttons by focusing only action buttons first during Tab navigation.
  • Fix Shift+Tab (Backtab) handling so focus moves to the last button when available before wrapping to the previous notification item.
  • Avoid relying on the clear button retaining focus to keep it loaded, preventing binding loops and hover/focus glitches.
  • Adjust initial focus in the notification list to prefer action buttons when available, falling back to the item itself.
  • Remove redundant focus resets on group and header clear-all buttons to avoid inconsistent focus state residue.

Enhancements:

  • Expose the instantiated clear button item via a readonly alias and wire it directly into overlapping notifications for clearer ownership.

@wjyrich wjyrich requested a review from 18202781743 March 20, 2026 09:22
@sourcery-ai
Copy link

sourcery-ai bot commented Mar 20, 2026

Reviewer's Guide

Adjusts notification focus and close-button behavior to eliminate binding loops and ensure predictable Tab/Shift+Tab navigation across notification items, including overlap and normal notifications, while simplifying how clear buttons are referenced and activated.

Sequence diagram for Tab navigation within a notification item

sequenceDiagram
    actor User
    participant NotifyItem as NotifyItem(OverlapOrNormal)
    participant NotifyContent as NotifyItemContent
    participant Buttons as ActionButtons
    participant NextItem as NextNotifyItem

    User->>NotifyItem: Press Tab
    activate NotifyItem
    NotifyItem->>NotifyContent: Keys.onTabPressed
    activate NotifyContent
    NotifyContent->>Buttons: focusFirstActionOnly()
    alt Has_action_buttons
        Buttons-->>NotifyContent: true (focus moved to first action)
        NotifyContent-->>NotifyItem: event.accepted = true
    else No_action_buttons
        Buttons-->>NotifyContent: false
        NotifyContent-->>NotifyItem: delegate.gotoNextItem()
        deactivate NotifyContent
        NotifyItem->>NextItem: gotoNextItem signal
        activate NextItem
        NextItem->>NextItem: shouldShowClose = true (if defined)
        NextItem->>NextItem: forceActiveFocus()
        deactivate NextItem
    end
    deactivate NotifyItem
Loading

Sequence diagram for Shift+Tab navigation across notification items

sequenceDiagram
    actor User
    participant NotifyItem as NotifyItem(OverlapOrNormal)
    participant NotifyContent as NotifyItemContent
    participant Buttons as Buttons
    participant PrevItem as PrevNotifyItem

    User->>NotifyItem: Press Shift+Tab
    activate NotifyItem
    NotifyItem->>NotifyItem: root.shouldShowClose = true
    NotifyItem->>NotifyContent: Keys.onBacktabPressed
    activate NotifyContent
    NotifyContent->>Buttons: focusLastButton()
    alt Has_focusable_button
        Buttons-->>NotifyContent: true (focus moved to last button)
        NotifyContent-->>NotifyItem: event.accepted = true
    else No_focusable_button
        Buttons-->>NotifyContent: false
        NotifyContent-->>NotifyItem: delegate.gotoPrevItem()
        deactivate NotifyContent
        NotifyItem->>PrevItem: gotoPrevItem signal
        activate PrevItem
        PrevItem->>PrevItem: shouldShowClose = true (if defined)
        PrevItem->>PrevItem: forceActiveFocus()
        deactivate PrevItem
    end
    deactivate NotifyItem
Loading

Sequence diagram for initial focus when opening notification center

sequenceDiagram
    participant NotifyView as NotifyView
    participant ViewList as NotificationListView
    participant Item as NotifyItemDelegate
    participant Buttons as Buttons

    NotifyView->>NotifyView: tryFocus(retries)
    NotifyView->>ViewList: itemAtIndex(idx)
    ViewList-->>NotifyView: Item
    alt Item_exists_and_enabled
        NotifyView->>Item: check focusFirstButton
        alt Item_has_focusFirstButton_and_succeeds
            Item->>Buttons: focusFirstButton()
            Buttons-->>Item: true (button focused)
        else No_focusFirstButton_or_failed
            Item-->>NotifyView: false
            NotifyView->>Item: forceActiveFocus()
        end
    else Item_missing_or_disabled
        NotifyView->>NotifyView: if retries > 0 callLater(tryFocus)
    end
Loading

Class diagram for updated notification focus components

classDiagram
    class NotifyView {
        +function tryFocus(retries)
        -int idx
        -NotificationListView view
    }

    class NotifyViewDelegate {
        +bool shouldShowClose
        +function gotoNextItem()
        +function gotoPrevItem()
    }

    class NotifyItemContent {
        +readonly int maxFocusRetries
        +bool parentHovered
        +bool closeVisible
        +int miniContentHeight
        +bool enableDismissed
        +alias clearButton
        +readonly alias clearButtonItem
        +signal gotoNextItem()
        +signal gotoPrevItem()
        +function focusFirstActionOnly()
        +function focusLastButton()
    }

    class OverlapNotify {
        +signal gotoNextItem()
        +signal gotoPrevItem()
        +var clearButton
        +bool shouldShowClose
        +function focusFirstButton()
    }

    class NormalNotify {
        +signal gotoNextItem()
        +signal gotoPrevItem()
        +bool shouldShowClose
        +function focusFirstButton()
    }

    class GroupNotify {
        +signal gotoNextItem()
        +Button groupClearBtn
    }

    class NotifyHeader {
        +signal gotoFirstNotify()
        +Button clearAllBtn
    }

    class ActionButtons {
        +function focusFirstActionOnly()
        +function focusLastButton()
    }

    NotifyView --> NotifyViewDelegate : uses
    NotifyViewDelegate --> OverlapNotify : delegates_to
    NotifyViewDelegate --> NormalNotify : delegates_to
    OverlapNotify o-- NotifyItemContent : contains
    NormalNotify o-- NotifyItemContent : contains
    NotifyItemContent --> ActionButtons : manages_focus_for
    OverlapNotify --> ActionButtons : focusFirstButton()
    NormalNotify --> ActionButtons : focusFirstButton()
    OverlapNotify --> NotifyViewDelegate : gotoNextItem/gotoPrevItem
    NormalNotify --> NotifyViewDelegate : gotoNextItem/gotoPrevItem
    GroupNotify --> NotifyViewDelegate : gotoNextItem
    NotifyHeader --> NotifyViewDelegate : gotoFirstNotify
    OverlapNotify --> NotifyItemContent : clearButton = clearButtonItem
Loading

File-Level Changes

Change Details Files
Ensure next/previous notification items explicitly show their close button when navigated via custom gotoNextItem/gotoPrevItem logic.
  • When moving to the next item, set shouldShowClose to true if the target item exposes that property before forcing focus.
  • When moving to the previous item, set shouldShowClose to true on the target item if available before forcing focus.
panels/notification/center/NotifyViewDelegate.qml
Update focus movement from the clear button to only consider action buttons and remove explicit clear button wiring to OverlapNotify.
  • Change Tab handling on the clear button to call notifyContent.focusFirstActionOnly() so the clear button is skipped to avoid focus loops.
  • Remove the Component.onCompleted hook that manually passes the clear button reference into overlapNotify.
panels/notification/center/NotifyViewDelegate.qml
Simplify clear button reference and refine Shift+Tab (Backtab) behavior for overlap notifications.
  • Replace the generic clearButton var with a direct alias to notifyContent.clearButtonItem to avoid manual wiring and potential binding loops.
  • On Backtab, first attempt notifyContent.focusLastButton(); if it succeeds, consume the event, otherwise fall back to gotoPrevItem().
panels/notification/center/OverlapNotify.qml
Align normal notification Backtab (Shift+Tab) behavior with overlap notifications to correctly traverse buttons before moving to the previous item.
  • On Backtab, first attempt notifyContent.focusLastButton(); if successful, accept the event; otherwise, go to the previous notification item and accept the event.
panels/notification/center/NormalNotify.qml
Adjust close button visibility and loader activation logic to remove dependency on the internal clear button focus and expose the instantiated button item.
  • Remove clearLoader.item.activeFocus from the closeVisible condition so visibility depends only on item focus, hover, or parent hover.
  • Expose the instantiated clear button as clearButtonItem via a readonly property alias.
  • Stop keeping the clear button loader active based on the button’s own focus, relying instead on closeVisible or hover to control loader activation.
panels/notification/plugin/NotifyItemContent.qml
Improve initial focus behavior in the notification view to prefer in-item buttons when available.
  • When trying to focus an item, first call item.focusFirstButton() if it exists; only fall back to forceActiveFocus() when that returns false or is unavailable.
  • Retain retry logic when items are not yet instantiated.
panels/notification/center/NotifyView.qml
Simplify Tab behavior on group and header "Clear All" buttons to rely on navigation signals without manually clearing focus state.
  • Remove explicit focus reset on groupClearBtn before emitting gotoNextItem on Tab.
  • Remove explicit focus reset on clearAllBtn before emitting gotoFirstNotify on Tab.
panels/notification/center/GroupNotify.qml
panels/notification/center/NotifyHeader.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:

  • The repeated Backtab handling logic in NormalNotify and OverlapNotify (toggling shouldShowClose and preferring notifyContent.focusLastButton before gotoPrevItem) could be extracted into a shared helper to keep the focus behavior consistent and easier to maintain.
  • The pattern if (nextItem.shouldShowClose !== undefined) / if (prevItem.shouldShowClose !== undefined) is a bit brittle; consider exposing a small method (e.g. ensureCloseVisible()) on the delegate instead of reaching into an optional property from the outside.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The repeated Backtab handling logic in NormalNotify and OverlapNotify (toggling shouldShowClose and preferring notifyContent.focusLastButton before gotoPrevItem) could be extracted into a shared helper to keep the focus behavior consistent and easier to maintain.
- The pattern `if (nextItem.shouldShowClose !== undefined)` / `if (prevItem.shouldShowClose !== undefined)` is a bit brittle; consider exposing a small method (e.g. `ensureCloseVisible()`) on the delegate instead of reaching into an optional property from the outside.

## Individual Comments

### Comment 1
<location path="panels/notification/plugin/NotifyItemContent.qml" line_range="19-26" />
<code_context>
     readonly property int maxFocusRetries: 5
     property bool parentHovered: false  // External hover state from parent component
-    property bool closeVisible: activeFocus || impl.hovered || parentHovered || (clearLoader.item && clearLoader.item.activeFocus)
+    property bool closeVisible: activeFocus || impl.hovered || parentHovered
     property int miniContentHeight: NotifyStyle.contentItem.miniHeight
     property bool enableDismissed: true
</code_context>
<issue_to_address>
**issue (bug_risk):** Re-evaluate visibility/activation conditions now that clear button focus is no longer considered explicitly.

Previously, `closeVisible` and `clearLoader.active` stayed true while the clear button itself had focus by checking `clearLoader.item && clearLoader.item.activeFocus`. Now `closeVisible` relies only on `root.activeFocus` and hover. In many QML hierarchies only the focused child has `activeFocus`, so when the close button gains focus, `root.activeFocus` may become false, hiding/unloading the button while it is still focused. Please verify whether `root` keeps `activeFocus` when the close button is focused; if not, consider reintroducing a focus check tied directly to the clear/close button (e.g., its `activeFocus`) in these conditions.
</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.

Comment on lines +19 to 26
property bool closeVisible: activeFocus || impl.hovered || parentHovered
property int miniContentHeight: NotifyStyle.contentItem.miniHeight
property bool enableDismissed: true
property alias clearButton: clearLoader.sourceComponent
readonly property alias clearButtonItem: clearLoader.item

signal gotoNextItem() // Signal to navigate to next notify item
signal gotoPrevItem() // Signal to navigate to previous notify item
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): Re-evaluate visibility/activation conditions now that clear button focus is no longer considered explicitly.

Previously, closeVisible and clearLoader.active stayed true while the clear button itself had focus by checking clearLoader.item && clearLoader.item.activeFocus. Now closeVisible relies only on root.activeFocus and hover. In many QML hierarchies only the focused child has activeFocus, so when the close button gains focus, root.activeFocus may become false, hiding/unloading the button while it is still focused. Please verify whether root keeps activeFocus when the close button is focused; if not, consider reintroducing a focus check tied directly to the clear/close button (e.g., its activeFocus) in these conditions.

@wjyrich wjyrich force-pushed the fix-waring branch 4 times, most recently from b66d661 to bb6de1b Compare March 20, 2026 09:58
@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 18202781743, wjyrich

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

更新了 closebtn的未定义问题,修复了hover通知,循环依赖问题。

更新通知焦点循环一次后,第二次循环无法经过overlapnotify,从而无法给到焦点到后面其他的通知上。
以及修复 shift+tab 保证跟tab的逻辑一致,能够正常响应焦点处理。
还需修复: 带有action的通知焦点处理。
@deepin-ci-robot
Copy link

deepin pr auto review

代码审查报告

本次代码变更主要集中在通知中心(NotificationCenter)的焦点管理和键盘导航逻辑上,涉及多个QML文件的修改。以下是详细的审查意见:

1. 整体评估

这次提交主要优化了键盘导航体验,改进了焦点管理逻辑,移除了不必要的焦点状态标记,使代码更加简洁和可维护。整体方向是正确的,但存在一些潜在问题需要注意。

2. 语法逻辑分析

2.1 正确的改进

  • 移除了shouldShowClose属性,直接使用activeFocus来判断是否显示关闭按钮,简化了逻辑。
  • 统一了Keys.onTabPressedKeys.onBacktabPressed的处理方式,使代码更加一致。
  • NormalNotify.qmlOverlapNotify.qml中,优化了焦点切换逻辑,先尝试聚焦子元素,失败后再切换到下一个/上一个通知项。

2.2 潜在问题

  1. 焦点管理不一致

    • NotifyView.qml中,tryFocus函数的逻辑与NormalNotify.qmlOverlapNotify.qml中的处理不一致:
      if (!item.focusFirstButton()) {
          item.forceActiveFocus()
      }
      这可能导致在某些情况下焦点行为不一致。
  2. 未定义的方法

    • OverlapNotify.qml中,引用了notifyContent.focusFirstActionOnly()方法,但该方法的定义在diff中未显示,需要确认其实现是否正确。
  3. 焦点循环风险

    • NotifyViewDelegate.qml中,移除了对清除按钮的特殊处理注释,这可能导致焦点在清除按钮和通知项之间循环。

3. 代码质量

3.1 优点

  • 代码更加简洁,移除了不必要的属性和注释。
  • 统一了焦点管理逻辑,提高了代码可维护性。
  • 更新了版权年份范围(2024-2026),符合项目规范。

3.2 改进建议

  1. 添加文档注释

    • 对于focusFirstButton()focusFirstActionOnly()等关键方法,建议添加文档注释,说明其功能和预期行为。
  2. 统一焦点处理策略

    • 建议在整个通知中心模块中统一焦点处理策略,避免不同组件中的不一致行为。

4. 代码性能

  1. 性能影响

    • 移除shouldShowClose属性减少了状态管理,理论上可以提高性能。
    • 使用activeFocus直接判断焦点状态比维护额外属性更高效。
  2. 潜在性能问题

    • NotifyView.qml中,tryFocus函数使用Qt.callLater延迟重试,这是好的做法,但需要确保重试次数合理。

5. 代码安全

  1. 焦点状态残留

    • 原代码中groupClearBtn.focus = falseclearAllBtn.focus = false被移除,注释说明是为了"防止焦点状态残留"。需要确认移除这些代码后是否会有焦点状态残留问题。
  2. 焦点管理安全性

    • 新的焦点管理逻辑依赖于focusFirstButton()focusLastButton()方法的正确实现,需要确保这些方法在各种边界条件下都能安全工作。

6. 具体建议

  1. 添加单元测试

    • 建议为新的焦点管理逻辑添加单元测试,特别是测试键盘导航的各种场景。
  2. 焦点管理文档

    • 建议创建一份焦点管理策略文档,明确说明通知中心的焦点导航规则,帮助后续维护。
  3. 边界条件测试

    • 特别测试以下场景:
      • 通知列表为空时的焦点行为
      • 通知项没有可聚焦按钮时的焦点行为
      • 快速连续按Tab键时的焦点行为
  4. 代码审查建议

    • 在合并前,建议进行一次完整的键盘导航测试,确保所有场景下的焦点行为符合预期。

7. 总结

这次提交改进了通知中心的焦点管理逻辑,使代码更加简洁和一致。主要关注点是确保焦点管理的一致性和边界条件的正确处理。建议在合并前进行全面的键盘导航测试,并考虑添加单元测试以验证焦点管理逻辑的正确性。

@18202781743 18202781743 merged commit 2eb7560 into linuxdeepin:master Mar 20, 2026
11 of 12 checks passed
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.

3 participants