Skip to content

fix: Main interface does not gray out when losing focus#570

Closed
JWWTSL wants to merge 2 commits intolinuxdeepin:masterfrom
JWWTSL:master
Closed

fix: Main interface does not gray out when losing focus#570
JWWTSL wants to merge 2 commits intolinuxdeepin:masterfrom
JWWTSL:master

Conversation

@JWWTSL
Copy link
Contributor

@JWWTSL JWWTSL commented Feb 7, 2026

log: In the DialogWindow, when set to WindowModal, the transientParent is automatically set to Qt.application.activeWindow. Upon display, raise() and requestActivate() are called to ensure the main window correctly enters a grayed-out state.

pms: bug-336201

Summary by Sourcery

Bug Fixes:

  • Automatically associate window-modal dialogs with the current active window as their transient parent to restore expected grayed-out background behavior when dialogs appear.

@sourcery-ai
Copy link

sourcery-ai bot commented Feb 7, 2026

Reviewer's Guide

Ensures WindowModal DialogWindow instances correctly set and maintain their transient parent and, when shown, raise and activate themselves so the main window enters the expected grayed-out (modal) state.

Sequence diagram for WindowModal DialogWindow activation and main window graying

sequenceDiagram
    actor User
    participant MainWindow
    participant DialogWindow
    participant QtApplication

    User->>MainWindow: openDialog()
    MainWindow->>DialogWindow: create DialogWindow (modality = Qt.WindowModal)

    DialogWindow->>DialogWindow: Component.onCompleted
    alt modality is WindowModal and transientParentWindow is null
        DialogWindow->>QtApplication: get activeWindow
        QtApplication-->>DialogWindow: activeWindow
        DialogWindow->>DialogWindow: set transientParentWindow
        DialogWindow->>DialogWindow: bind transientParent to transientParentWindow
    end

    User->>DialogWindow: set visible = true
    DialogWindow->>DialogWindow: onVisibleChanged (visible = true)
    alt modality is WindowModal
        alt transientParentWindow is null or equals control
            DialogWindow->>QtApplication: get activeWindow
            QtApplication-->>DialogWindow: activeWindow
            DialogWindow->>DialogWindow: set transientParentWindow
        end
        DialogWindow->>DialogWindow: Qt.callLater
        DialogWindow->>DialogWindow: raise()
        DialogWindow->>DialogWindow: requestActivate()
        DialogWindow->>MainWindow: enforce modal relationship (main window grayed)
    end
Loading

Updated class diagram for DialogWindow QML component

classDiagram
    class DialogWindow {
        <<Window>>
        +real leftPadding
        +real rightPadding
        +var transientParentWindow
        +Window transientParent
        +onCompleted()
        +onVisibleChanged(visible)
        +onClosing(close)
    }

    class QtApplication {
        +Window activeWindow
        +callLater(callback)
    }

    class Window {
        +bool visible
        +int modality
        +void raise()
        +void requestActivate()
    }

    DialogWindow --|> Window
    DialogWindow --> QtApplication : uses
    QtApplication --> Window : returns activeWindow
Loading

File-Level Changes

Change Details Files
Wire DialogWindow.transientParent through a dedicated property to allow dynamic assignment while keeping QML bindings clean.
  • Introduce a transientParentWindow var property initialized to null.
  • Bind the Window.transientParent to transientParentWindow so it can be updated programmatically.
qt6/src/qml/DialogWindow.qml
Automatically select an appropriate transient parent for WindowModal dialogs when they are created.
  • On Component.onCompleted, if the dialog is WindowModal and no transient parent has been set, assign Qt.application.activeWindow as the transient parent window.
qt6/src/qml/DialogWindow.qml
Ensure WindowModal dialogs properly gray out and block their parent window when they become visible.
  • Handle onVisibleChanged to early-return for non-visible or non-WindowModal dialogs.
  • Re-evaluate and, if needed, reset transientParentWindow to the current active window when the dialog is shown, avoiding self-parenting.
  • Use Qt.callLater to asynchronously call raise() and requestActivate() on the dialog so that modality and z-order are correctly applied after it becomes visible.
qt6/src/qml/DialogWindow.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 left some high level feedback:

  • When the dialog becomes visible, Qt.application.activeWindow may already be the dialog itself, so the transientParentWindow === control guard and re-read of activeWindow will still not yield the main window; consider capturing the previously active window before showing or using a more reliable source for the parent.
  • Overwriting transientParentWindow in onVisibleChanged can break any external binding or explicit assignment to this property; if it is meant to be user-configurable, consider only setting a default once (e.g., via a binding or when the property is still null) rather than reassigning on every visibility change.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- When the dialog becomes visible, `Qt.application.activeWindow` may already be the dialog itself, so the `transientParentWindow === control` guard and re-read of `activeWindow` will still not yield the main window; consider capturing the previously active window before showing or using a more reliable source for the parent.
- Overwriting `transientParentWindow` in `onVisibleChanged` can break any external binding or explicit assignment to this property; if it is meant to be user-configurable, consider only setting a default once (e.g., via a binding or when the property is still `null`) rather than reassigning on every visibility change.

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.

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 a focus/modality behavior issue where DialogWindow instances shown as Qt.WindowModal don’t correctly gray out the main window by ensuring a transient parent is set and activating the dialog when it becomes visible.

Changes:

  • Introduce a transientParentWindow property and bind transientParent to it.
  • Auto-assign transientParentWindow to Qt.application.activeWindow for Qt.WindowModal dialogs.
  • On show, raise and request activation for window-modal dialogs to trigger expected grayed-out behavior.

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

Comment on lines +66 to +81
Component.onCompleted: {
if (control.modality === Qt.WindowModal && !transientParentWindow)
transientParentWindow = Qt.application.activeWindow
}

onVisibleChanged: {
if (!control.visible)
return
if (control.modality !== Qt.WindowModal)
return
if (!transientParentWindow || transientParentWindow === control)
transientParentWindow = Qt.application.activeWindow
Qt.callLater(function () {
control.raise()
control.requestActivate()
})
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

transientParentWindow is only set when it is null (or equals control). Once it has been auto-assigned the first time, it will not update on subsequent shows. If a DialogWindow instance is reused across multiple top-level windows, the dialog can remain transient for the original window, causing the wrong window to gray out (or none) when shown from another active window. Consider recalculating the transient parent each time the dialog becomes visible (or clearing the auto-assigned parent when the dialog is hidden), while still respecting an explicitly provided transient parent when callers set one.

Copilot uses AI. Check for mistakes.
log: In the DialogWindow, when set to WindowModal, the transientParent is automatically set to Qt.application.activeWindow. Upon display, raise() and requestActivate() are called to ensure the main window correctly enters a grayed-out state.

pms: bug-336201
@deepin-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: JWWTSL

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

}

onVisibleChanged: {
if (!control.visible)
Copy link
Contributor

Choose a reason for hiding this comment

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

更新transientParentWindow的代码封装一下,逻辑在omCompleted和visibleChanged的时候应该是一样的,

@deepin-bot
Copy link
Contributor

deepin-bot bot commented Feb 27, 2026

TAG Bot

New tag: 6.7.34
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #576

@deepin-ci-robot
Copy link
Contributor

deepin pr auto review

这是一个关于Qt/QML代码的审查,针对你提供的 git diff,我将对新增的代码(关于 transientParent 的处理逻辑)进行详细的审查。

1. 语法与逻辑审查

  • 变量声明与绑定

    • property var transientParentWindow: null:语法正确。使用 var 是灵活的做法,因为它可以持有 Window 对象或 null
    • transientParent: transientParentWindow:逻辑正确。将自定义属性绑定到 Window 的 transientParent 属性上,实现了动态设置父窗口的功能。
  • 初始化逻辑 (Component.onCompleted)

    • 逻辑:当组件完成初始化时,如果是模态窗口(Qt.WindowModal)且尚未指定父窗口,则自动将当前应用的活动窗口(Qt.application.activeWindow)设为父窗口。
    • 潜在问题:在 Component.onCompleted 阶段,Qt.application.activeWindow 可能尚未完全就绪,或者如果这个窗口是在应用启动初期创建的,activeWindow 可能还是 null 或不是预期的窗口。
  • 显示逻辑 (onVisibleChanged)

    • 逻辑:当窗口显示时,再次检查并修正 transientParentWindow
    • 逻辑漏洞
      • 代码中有 if (!transientParentWindow || transientParentWindow === control)
      • transientParentWindow === control 的检查是必要的,防止窗口将自己设为父窗口(这在某些平台上可能导致问题或无意义)。
      • 问题:如果用户在代码外部显式设置了 transientParentWindow 为某个特定窗口(例如 windowA),但 windowA 此时不是 activeWindow,这段代码的逻辑是:如果 transientParentWindow 已存在(即 !transientParentWindow 为 false),则不会进入 if 块。
      • 这意味着如果用户手动设置了一个非活动窗口作为父窗口,代码会尊重这个设置(这是好的)。
      • 但是,如果 transientParentWindownull,代码会尝试获取 activeWindow。如果此时 activeWindow 恰好是 control 自己(这在极少数快速切换场景下可能发生),candidate !== control 会阻止赋值,导致父窗口依然为空,模态行为可能失效。
  • 激活逻辑 (Qt.callLater)

    • Qt.callLater 的使用非常好。它确保 raise()requestActivate() 在当前事件循环处理完毕后执行,避免了与窗口显示事件处理的竞争条件,保证了窗口能够正确置顶并获得焦点。

2. 代码质量建议

  • 类型安全

    • 虽然在 QML 中 property var 很方便,但为了代码清晰和类型安全,建议使用 property QQuickWindow transientParentWindow: null(如果环境支持导入 QtQuick 并访问 QQuickWindow)。或者至少在注释中说明该属性应持有 Window 对象。
    • 不过,考虑到 transientParent 属性本身接受的类型,var 在这里是可以接受的。
  • 逻辑复用

    • Component.onCompletedonVisibleChanged 中都有寻找 activeWindow 的逻辑。建议提取为一个 JavaScript 函数,例如 updateTransientParent(),在两处分别调用,减少代码重复。
  • 注释

    • 建议添加注释解释为什么需要动态设置 transientParent(例如:为了确保模态对话框在某些窗口管理器下能正确阻塞父窗口或跟随父窗口最小化)。

3. 代码性能

  • 性能影响
    • onVisibleChanged 中的逻辑只会在窗口显示时执行,且使用了 Qt.callLater,性能开销极小,可以忽略不计。
    • Qt.application.activeWindow 的访问是轻量级的。

4. 代码安全

  • 空指针检查
    • var candidate = Qt.application.activeWindow 后检查了 if (candidate ...),这是安全的。
  • 循环引用
    • 代码中 transientParentWindow === control 的检查有效地防止了窗口将自己设为父窗口,避免了潜在的循环引用或未定义行为。

5. 改进后的代码建议

基于以上分析,建议对代码进行如下重构,以提高可读性和健壮性:

Window {
    // ... 原有属性 ...

    // 建议明确类型,如果环境允许
    property var transientParentWindow: null
    // 绑定 transientParent
    transientParent: transientParentWindow

    // 提取公共逻辑函数
    function updateTransientParent() {
        // 如果不是模态窗口,或者已经明确设置了父窗口,则不处理
        // 注意:这里假设显式设置的不为 null 且不为自身
        if (control.modality !== Qt.WindowModal) {
            return;
        }
        
        // 如果当前没有父窗口,或者父窗口设置错误(指向了自己),则尝试自动寻找
        if (!transientParentWindow || transientParentWindow === control) {
            var candidate = Qt.application.activeWindow;
            // 确保候选窗口存在,且不是自己
            if (candidate && candidate !== control) {
                transientParentWindow = candidate;
            }
        }
    }

    Component.onCompleted: {
        updateTransientParent();
    }

    onVisibleChanged: {
        if (!control.visible) {
            return;
        }
        
        // 更新父窗口逻辑
        updateTransientParent();

        // 延迟激活窗口,确保置顶
        Qt.callLater(function () {
            control.raise();
            control.requestActivate();
        });
    }

    // ... 其他代码 ...
}

总结

原代码逻辑基本正确,能够解决模态对话框父窗口未指定时的默认行为问题。
主要改进点在于:

  1. 消除重复代码:将查找父窗口的逻辑提取为函数。
  2. 增强健壮性:虽然原代码有自引用检查,但通过函数封装可以更清晰地管理边界条件。
  3. 保持 Qt.callLater:这是处理窗口激活的最佳实践,请保留。

@JWWTSL JWWTSL closed this Mar 3, 2026
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