Skip to content

Conversation

@wyu71
Copy link
Contributor

@wyu71 wyu71 commented Dec 17, 2025

  • Implemented manual focus control for Tab/Shift+Tab between header and notify items
  • Added focus cycling signals and helper functions for proper keyboard navigation
  • Disabled default activeFocusOnTab to prevent focus jumping issues

Log: resolve Tab key focus navigation issues in notification center
pms: BUG-339893
pms: BUG-339891

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.

Sorry @wyu71, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

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 Tab key focus navigation issues in the notification center by implementing manual keyboard focus control. The changes replace the default QML focus behavior with custom Tab/Shift+Tab handling to prevent focus jumping and ensure proper cycling between header buttons and notification items.

Key changes include:

  • Added focus cycling signals (gotoNextItem, gotoPrevItem, gotoFirstNotify, gotoLastNotify) throughout the component hierarchy
  • Implemented helper functions (focusFirstButton, focusLastButton, focusItemAtIndex) for programmatic focus management
  • Disabled activeFocusOnTab on interactive elements and added custom Keys.onTabPressed and Keys.onBacktabPressed handlers

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
NotifyItemContent.qml Adds focus navigation signals, helper functions for focusing first/last buttons, custom Tab handlers for close button, and integrates action button navigation
NotifyAction.qml Implements focus helpers and custom Tab/Shift+Tab handlers for action buttons with proper navigation between buttons and signals to parent
OverlapNotify.qml Forwards focus navigation signals between NotifyItemContent and parent, exposes focusFirstButton helper
NotifyViewDelegate.qml Implements custom Tab/Shift+Tab handlers for all three delegate types (group, normal, overlap) with navigation logic and focus state management
NotifyView.qml Adds signal forwarding functions and focusItemAtIndex/focusLastItem helpers with retry logic for delegate creation
NotifyHeader.qml Implements focus scope with Tab/Shift+Tab handlers for header buttons (collapse, more, clear all) and cycling to/from notify items
NotifyCenter.qml Connects header and view focus navigation signals to enable cycling between components
NormalNotify.qml Forwards focus navigation signals from NotifyItemContent to parent and exposes focusFirstButton helper
GroupNotify.qml Adds focus navigation signals and Tab/Shift+Tab handlers for group buttons (fold, more, clear all)

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

focus: true
anchors.right: parent.right
active: !(root.strongInteractive && root.actions.length > 0) && (root.closeVisible || closePlaceHolder.hovered || activeFocus)
// 在鼠标悬停或通知项获得焦点时显示
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

Chinese comment should be replaced with English for consistency. The comment "在鼠标悬停或通知项获得焦点时显示" should be translated to English, such as "Show when mouse hovers or notification item has focus".

Suggested change
// 在鼠标悬停或通知项获得焦点时显示
// Show when mouse hovers or notification item has focus

Copilot uses AI. Check for mistakes.
event.accepted = true
return
}
Qt.callLater(function() {
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

Inconsistent indentation: this line has extra leading spaces compared to the surrounding code. It should align with the if statement on line 213.

Suggested change
Qt.callLater(function() {
Qt.callLater(function() {

Copilot uses AI. Check for mistakes.
id: groupClearBtn
Layout.alignment: Qt.AlignRight
activeFocusOnTab: false
forcusBorderVisible: activeFocus
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

Typo in property name: "forcusBorderVisible" should be "focusBorderVisible" (missing 'o').

Copilot uses AI. Check for mistakes.
Qt.callLater(function() { tryFocusClear(retries - 1) })
}
}
Qt.callLater(function() { tryFocusClear(5) })
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The magic number 5 is used for retry attempts without explanation. Consider defining this as a named constant at the component or module level (e.g., readonly property int maxFocusRetries: 5) to improve code readability and maintainability.

Copilot uses AI. Check for mistakes.
padding: 2
forcusBorderVisible: visualFocus
activeFocusOnTab: false
forcusBorderVisible: activeFocus
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

Typo in property name: "forcusBorderVisible" should be "focusBorderVisible" (missing 'o').

Suggested change
forcusBorderVisible: activeFocus
focusBorderVisible: activeFocus

Copilot uses AI. Check for mistakes.
visible: !notifyModel.collapse
Layout.alignment: Qt.AlignRight
activeFocusOnTab: false
forcusBorderVisible: activeFocus
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

Typo in property name: "forcusBorderVisible" should be "focusBorderVisible" (missing 'o').

Suggested change
forcusBorderVisible: activeFocus
focusBorderVisible: activeFocus

Copilot uses AI. Check for mistakes.
id: clearAllBtn
objectName: "closeAllNotify"
activeFocusOnTab: false
forcusBorderVisible: activeFocus
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

Typo in property name: "forcusBorderVisible" should be "focusBorderVisible" (missing 'o').

Copilot uses AI. Check for mistakes.
icon.name: "clean-alone"
text: qsTr("Clean All")
activeFocusOnTab: false
forcusBorderVisible: activeFocus
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

Typo in property name: "forcusBorderVisible" should be "focusBorderVisible" (missing 'o').

Suggested change
forcusBorderVisible: activeFocus
focusBorderVisible: activeFocus

Copilot uses AI. Check for mistakes.
Comment on lines 35 to 66
Keys.onBacktabPressed: function(event) {
if (index > 0) {
root.view.currentIndex = index - 1
root.view.positionViewAtIndex(index - 1, ListView.Contain)
Qt.callLater(function() {
let prevItem = root.view.itemAtIndex(index - 1)
if (prevItem) prevItem.forceActiveFocus()
})
event.accepted = true
} else {
root.view.gotoHeaderLast()
event.accepted = true
}
}
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

This navigation logic is duplicated across groupNotify (lines 36-43), normalNotify (lines 121-127), and overlapNotify (lines 231-237). Consider extracting this into a shared helper function to reduce code duplication and improve maintainability.

Copilot uses AI. Check for mistakes.
Qt.callLater(function() { tryFocus(retries - 1) })
}
}
Qt.callLater(function() { tryFocus(5) })
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The magic number 5 is used for retry attempts without explanation. Consider defining this as a named constant at the component or module level (e.g., readonly property int maxFocusRetries: 5) to improve code readability and maintainability.

Copilot uses AI. Check for mistakes.
@wyu71 wyu71 force-pushed the master branch 2 times, most recently from 039ab54 to 31d98d4 Compare December 17, 2025 03:00
@deepin-ci-robot
Copy link

deepin pr auto review

我来对这个代码diff进行审查。这个改动主要涉及通知中心中焦点导航和Tab键循环的实现。让我从几个方面进行分析:

  1. 代码质量改进:
  • 优点:

    • 添加了清晰的信号命名和函数命名,如gotoNextItem、gotoPrevItem等
    • 统一了activeFocusOnTab的处理方式,都设置为false
    • 添加了适当的注释说明关键功能
    • 统一了focusBorderVisible的使用
  • 需要改进:

    • 在NotifyView.qml中,maxFocusRetries定义为5,但没有解释为什么选择这个值
    • 部分重复代码可以抽取为公共函数,如focusFirstButton和focusLastButton的实现
  1. 性能考虑:
  • 优点:

    • 使用Qt.callLater进行异步处理,避免同步操作可能导致的性能问题
    • 使用Loader进行延迟加载,提高初始化性能
  • 需要改进:

    • focusItemAtIndex函数中的重试机制可能导致性能开销,建议添加重试间隔
    • 频繁的forceActiveFocus调用可能影响性能,建议添加状态检查
  1. 安全性考虑:
  • 优点:

    • 在NotifyAction.qml中添加了actionData的空值检查
    • 在焦点切换时正确处理event.accepted
  • 需要改进:

    • 建议在forceActiveFocus调用前添加组件有效性检查
    • focusFirstButton和focusLastButton函数应该添加返回值说明
  1. 具体改进建议:
// 在NotifyView.qml中
readonly property int maxFocusRetries: 5  // 添加注释说明重试次数的考虑

// 改进focusItemAtIndex函数
function focusItemAtIndex(idx) {
    if (idx < 0 || idx >= view.count) return false
    view.currentIndex = idx
    view.positionViewAtIndex(idx, ListView.Contain)
    
    function tryFocus(retries) {
        let item = view.itemAtIndex(idx)
        if (item && item.enabled) {  // 添加enabled检查
            item.forceActiveFocus()
        } else if (retries > 0) {
            Qt.callLater(function() { 
                tryFocus(retries - 1) 
            }, 100)  // 添加重试间隔
        }
    }
    Qt.callLater(function() { tryFocus(root.maxFocusRetries) })
    return true
}

// 在NotifyItemContent.qml中
function focusFirstButton() {
    // 添加组件有效性检查
    if (actionLoader.item && actionLoader.item.enabled) {
        actionLoader.item.focusFirstButton()
        return true
    }
    // ... 其余代码
}

// 添加公共焦点管理组件
component FocusManager : QtObject {
    function navigateToNextItem(currentIndex, itemCount, view) {
        if (currentIndex < itemCount - 1) {
            view.currentIndex = currentIndex + 1
            view.positionViewAtIndex(currentIndex + 1, ListView.Contain)
            Qt.callLater(function() {
                let nextItem = view.itemAtIndex(currentIndex + 1)
                if (nextItem && nextItem.enabled) {
                    nextItem.forceActiveFocus()
                }
            })
            return true
        }
        return false
    }
}
  1. 其他建议:
  • 考虑添加焦点导航的单元测试
  • 建议添加键盘快捷键的文档说明
  • 可以考虑添加焦点导航的动画效果,提升用户体验
  • 建议添加对高对比度模式的支持

总的来说,这个改动实现了良好的焦点导航功能,但在代码复用、错误处理和性能优化方面还有改进空间。建议进行上述改进以提高代码质量和可维护性。

- Implemented manual focus control for Tab/Shift+Tab between header and notify items
- Added focus cycling signals and helper functions for proper keyboard navigation
- Disabled default activeFocusOnTab to prevent focus jumping issues

Log: resolve Tab key focus navigation issues in notification center
pms: BUG-339893
pms: BUG-339891
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

Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.


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

}

onGotoNextItem: root.navigateToNextItem(index)
onGotoPrevItem: groupNotify.focusFirstButton()
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

This creates a focus loop. When Shift+Tab is pressed on the first button (foldBtn) in GroupNotify, it emits gotoPrevItem signal (line 56 in GroupNotify.qml). This handler then calls focusFirstButton which focuses foldBtn again, preventing navigation to the previous item. This should be consistent with NormalNotify and OverlapNotify which use forceActiveFocus on the item itself, allowing the delegate's Keys.onBacktabPressed handler to properly navigate to the previous item.

Copilot uses AI. Check for mistakes.
Comment on lines +230 to +232
Keys.onBacktabPressed: function(event) {
overlapNotify.gotoPrevItem()
event.accepted = true
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The Shift+Tab handler for the overlap notification's "Clean All" button doesn't check for action buttons before calling gotoPrevItem. This is inconsistent with the standard close button behavior in NotifyItemContent.qml (lines 116-123) which checks if actionLoader.item exists and focuses the last action button before navigating to the previous item. This could cause the action buttons to be skipped during keyboard navigation.

Copilot uses AI. Check for mistakes.
icon.name: "clean-group"
text: qsTr("Clear All")
Keys.onTabPressed: function(event) {
groupClearBtn.focus = false // Clear focus before signal to prevent focus state residue
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

Setting focus to false directly on a control before triggering navigation may not properly clear the focus state. Consider using Qt.callLater to ensure the focus state is cleared after the event is processed, similar to how other navigation code uses Qt.callLater for focus operations.

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +44
// Focus first interactive button (action buttons first, then X button)
function focusFirstButton() {
if (actionLoader.item && actionLoader.item.enabled) {
actionLoader.item.focusFirstButton()
return true
}
if (clearLoader.item && clearLoader.item.enabled) {
clearLoader.item.forceActiveFocus()
return true
}
// Retry if clearLoader not yet created
function tryFocusClear(retries) {
if (clearLoader.item && clearLoader.item.enabled) {
clearLoader.item.forceActiveFocus()
} else if (retries > 0) {
Qt.callLater(function() { tryFocusClear(retries - 1) })
}
}
Qt.callLater(function() { tryFocusClear(root.maxFocusRetries) })
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The focusFirstButton function always returns true even when no button can be focused. This could cause issues for the caller who relies on the return value to determine if focus was successfully set. Consider returning false when neither actionLoader nor clearLoader have items and the retry logic is triggered.

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +46
// Focus notify item at specified index with retry logic for delegate creation
function focusItemAtIndex(idx) {
if (idx < 0 || idx >= view.count) return false
view.currentIndex = idx
view.positionViewAtIndex(idx, ListView.Contain)
function tryFocus(retries) {
let item = view.itemAtIndex(idx)
if (item && item.enabled) {
item.forceActiveFocus()
} else if (retries > 0) {
Qt.callLater(function() { tryFocus(retries - 1) })
}
}
Qt.callLater(function() { tryFocus(root.maxFocusRetries) })
return true
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The focusItemAtIndex function returns true even when the item cannot be found and retry logic is triggered. This doesn't accurately reflect whether focus was successfully set. Consider tracking whether the retry succeeded and updating the return value accordingly, or document that the return value only indicates whether the attempt was initiated.

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +43
view.positionViewAtIndex(idx, ListView.Contain)
function tryFocus(retries) {
let item = view.itemAtIndex(idx)
if (item && item.enabled) {
item.forceActiveFocus()
} else if (retries > 0) {
Qt.callLater(function() { tryFocus(retries - 1) })
}
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The nested function tryFocus is defined inside focusItemAtIndex but may cause confusion about scope. Consider extracting this as a separate function or using a more conventional pattern to make the retry logic clearer and more maintainable.

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +40
onGotoFirstNotify: {
if (view.viewCount === 0 || !view.focusItemAtIndex(0)) header.focusFirstButton()
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The condition relies on focusItemAtIndex returning false when it can't focus, but that function always returns true when the retry logic is initiated. This means if view.viewCount is greater than 0 but the item at index 0 doesn't exist yet (delegate not created), the header won't be focused even though the retry might fail. Consider checking view.viewCount first and only calling focusItemAtIndex if count is greater than 0, or fix focusItemAtIndex to return a meaningful value.

Copilot uses AI. Check for mistakes.
@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

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

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

@wyu71
Copy link
Contributor Author

wyu71 commented Dec 17, 2025

/forcemerge

@deepin-bot
Copy link

deepin-bot bot commented Dec 17, 2025

This pr force merged! (status: unstable)

@deepin-bot deepin-bot bot merged commit 0f31aa1 into linuxdeepin:master Dec 17, 2025
9 of 10 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