Skip to content

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 84
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

@deepin-ci-robot
Copy link
Contributor

deepin pr auto review

这段代码主要对 DialogWindow.qml 进行了修改,增加了关于 transientParent(父窗口)的逻辑处理,旨在更好地控制模态对话框的层级关系和激活行为。

以下是对该代码的详细审查意见,分为语法逻辑、代码质量、代码性能和代码安全四个方面:

1. 语法逻辑

  • transientParent 的赋值逻辑

    • 问题transientParent: transientParentWindow 这种写法存在潜在的逻辑陷阱。在 QML 中,属性绑定是动态的。如果在 onVisibleChanged 或其他地方修改了 transientParentWindowtransientParent 会随之改变。虽然这可能是预期行为,但在窗口生命周期中动态改变父窗口可能会导致平台插件层面的未定义行为(例如窗口句柄的突然变更)。
    • 改进建议:确认是否需要在窗口显示后动态修改父窗口。如果只需要初始化一次,建议仅在初始化时赋值,或者确保动态修改是安全的。
  • Qt.application.activeWindow 的不确定性

    • 问题:代码依赖 Qt.application.activeWindow 来寻找候选父窗口。在某些边缘情况下(例如应用启动初期、多个窗口快速切换时),activeWindow 可能是 null 或者指向了不正确的窗口。
    • 逻辑冲突:在 onVisibleChanged 中,如果 transientParentWindow 已经被设置(例如在 Component.onCompleted 中),代码会跳过重新赋值。但是,如果用户在对话框关闭后再次打开,且此时 activeWindow 已经切换到了其他窗口,对话框仍然会依附于旧的 transientParentWindow,这可能不符合"当前活动窗口的模态对话框"这一直觉。
    • 改进建议:重新评估业务逻辑。如果对话框总是应该属于当前活动窗口,那么每次显示时都应该尝试更新 transientParentWindow,而不是仅在它为空时更新。
  • 递归引用检查

    • 代码transientParentWindow !== control 检查是好的,防止了窗口将自身设为父窗口。
    • 建议:虽然检查了 candidate !== control,但没有检查 candidate 是否已经是 control 的子窗口,或者是否存在循环引用(虽然 QML 引擎通常会处理,但显式检查更稳健)。

2. 代码质量

  • 类型定义

    • 问题property var transientParentWindow: null 使用了 var 类型。
    • 改进建议:明确指定类型可以提高代码的可读性和运行时效率。应改为 property QWindow transientParentWindow: null(如果是在 C++ 端关联)或 property Item transientParentWindow: null(如果是 QML Item)。考虑到 transientParent 通常期望的是 QWindow,最好明确类型。
  • 代码重复

    • 问题:获取 Qt.application.activeWindow 的逻辑在 Component.onCompletedonVisibleChanged 中重复出现。
    • 改进建议:提取一个辅助函数 function updateTransientParent() { ... } 来统一处理这个逻辑,减少代码冗余。
  • 魔法值

    • 问题Qt.WindowModal 是硬编码的。
    • 改进建议:虽然这是 Qt 标准枚举,但在复杂的对话框逻辑中,建议将其提取为可读性更好的属性或常量,或者如果这是一个通用组件,确保文档中说明了模态行为。

3. 代码性能

  • Qt.callLater 的使用
    • 优点:使用 Qt.callLater 来执行 raise()requestActivate() 是很好的实践。这确保了这些操作在当前事件循环处理完毕后执行,避免了与窗口显示事件处理的冲突,保证了窗口能正确置顶和获取焦点。
    • 注意:确保 Qt.callLater 中的闭包不会捕获大量不必要的数据,虽然在这个简单的例子中看起来没问题。

4. 代码安全

  • 空指针检查

    • 现状:代码对 candidate 进行了 if (candidate) 检查,这是很好的。
    • 建议:在访问 candidate 的属性或将其赋值给 transientParent 之前,确保其类型确实是预期的窗口类型。
  • 异常处理

    • 建议Qt.application.activeWindow 在某些嵌入式平台或特定窗口管理器下可能表现不一致。虽然无法直接 try-catch QML 属性绑定,但在逻辑上应考虑到获取不到父窗口时的降级处理(例如:如果没有父窗口,是否应该保持非模态或记录日志)。

改进后的代码示例

基于以上意见,建议修改如下:

Window {
    id: control
    // ... 其他属性 ...

    // 明确类型,建议使用 QWindow 或者具体的窗口类型
    property Item transientParentWindow: null

    // 仅在初始化时绑定一次,避免动态修改带来的副作用
    // 如果确实需要动态修改,请确保平台支持
    transientParent: transientParentWindow

    Item {
        id: content
        // ...
    }

    // 提取公共逻辑
    function updateTransientParent() {
        if (control.modality !== Qt.WindowModal) {
            return;
        }
        
        // 如果已有父窗口且不是自身,通常不需要更改,除非业务逻辑要求总是跟随活动窗口
        // 这里假设逻辑是:如果没有父窗口,则尝试获取活动窗口
        if (!transientParentWindow || transientParentWindow === control) {
            var candidate = Qt.application.activeWindow;
            // 增加更严格的检查,确保 candidate 有效且不是自身
            if (candidate && candidate !== control && candidate.visible) {
                transientParentWindow = candidate;
            }
        }
    }

    Component.onCompleted: {
        updateTransientParent();
    }

    onVisibleChanged: {
        if (!control.visible) {
            return;
        }
        
        // 每次显示时更新父窗口逻辑(根据实际需求决定是否总是更新)
        updateTransientParent();

        Qt.callLater(function () {
            control.raise();
            control.requestActivate();
        });
    }

    onClosing: function(close) {
        // ...
    }
}

总结

原代码主要解决了模态对话框找不到父窗口时的回退逻辑,并优化了显示时的激活行为。主要改进点在于:明确属性类型、消除重复逻辑、优化类型检查,以及根据实际业务需求确认 transientParent 的更新策略。

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.

2 participants