diff options
author | Tor Arne Vestbø <tor.arne.vestbo@qt.io> | 2024-03-02 16:55:43 +0100 |
---|---|---|
committer | Volker Hilsheimer <volker.hilsheimer@qt.io> | 2024-03-14 13:50:36 +0000 |
commit | c956eb8eddb1b3608d7e3d332fbe55df5ec41578 (patch) | |
tree | dc6de1d52fa3f0ea9ba1a650be899ea27c8147b0 /src/widgets | |
parent | b30121041c07b1b8613eaf624c9aa55a51001aef (diff) |
Reparent QWindow children when reparenting QWidget
When a QWidget was reparented, we would take care to reparent its
backing QWidgetWindow as well, into the nearest QWindow of the
new QWidget parent.
However we would only do this for the reparented widget itself,
and not any of its child widgets. In the case where the widget
has native children with their own QWindows, the widget itself
may not (yet) be native, e.g. if it hasn't been shown yet, or
if the user has set Qt::WA_DontCreateNativeAncestors.
In these scenarios, we would be left with dangling QWindows,
still hanging off their original QWindow parents, which
would eventually lead to crashes.
We now reparent both the QWindow of the reparented widget (as
long as it's not about to be destroyed), and any QQWindow
children we can reach. For each child hierarchy we can stop
once we reach a QWindow, as the QWindow children of that
window will follow along once we reparent the QWindow.
QWindowContainer widgets don't usually have their own
windowHandle(), but still manage a QWindow inside their
parent widget hierarchy. These will not be reparented
during QWidgetPrivate::setParent_sys(), but instead
do their own reparenting later in QWidget::setParent
via QWindowContainer::parentWasChanged(). The only
exception to this is when the top level is about to
be destroyed, in which case we let the window container
know during QWidgetPrivate::setParent_sys().
Finally, although there should not be any leftover
QWindows in the reparented widget once we have done
the QWidgetWindow and QWindowContainer reparenting,
we still do a pass over any remaining QWindows and
reparent those too, since the original code included
this as a possibility.
We could make further improvements in this areas, such
as moving the QWindowContainer::parentWasChanged() call,
but the goal was to keep this change as minimal as possible
so we can back-port it.
Fixes: QTBUG-122747
Pick-to: 6.7.0 6.7 6.6 6.5
Change-Id: I4d1217fce4c3c48cf5f7bfbe9d561ab408ceebb2
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Diffstat (limited to 'src/widgets')
-rw-r--r-- | src/widgets/kernel/qwidget.cpp | 130 | ||||
-rw-r--r-- | src/widgets/kernel/qwidget_p.h | 2 |
2 files changed, 93 insertions, 39 deletions
diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index ec7b5ce69a..30daee1b79 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp @@ -83,6 +83,7 @@ using namespace Qt::StringLiterals; Q_LOGGING_CATEGORY(lcWidgetPainting, "qt.widgets.painting", QtWarningMsg); Q_LOGGING_CATEGORY(lcWidgetShowHide, "qt.widgets.showhide", QtWarningMsg); +Q_LOGGING_CATEGORY(lcWidgetWindow, "qt.widgets.window", QtWarningMsg); #ifndef QT_NO_DEBUG_STREAM namespace { @@ -10953,57 +10954,61 @@ void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f) setWinId(0); - if (parent != newparent) { - QObjectPrivate::setParent_helper(newparent); //### why does this have to be done in the _sys function??? - if (q->windowHandle()) { - q->windowHandle()->setFlags(f); - QWidget *parentWithWindow = - newparent ? (newparent->windowHandle() ? newparent : newparent->nativeParentWidget()) : nullptr; - if (parentWithWindow) { - QWidget *topLevel = parentWithWindow->window(); - if ((f & Qt::Window) && topLevel && topLevel->windowHandle()) { - q->windowHandle()->setTransientParent(topLevel->windowHandle()); - q->windowHandle()->setParent(nullptr); - } else { - q->windowHandle()->setTransientParent(nullptr); - q->windowHandle()->setParent(parentWithWindow->windowHandle()); - } - } else { - q->windowHandle()->setTransientParent(nullptr); - q->windowHandle()->setParent(nullptr); - } - } - } - if (!newparent) { f |= Qt::Window; if (parent) targetScreen = q->parentWidget()->window()->screen(); } + const bool destroyWindow = ( + // Reparenting top level to child + (oldFlags & Qt::Window) && !(f & Qt::Window) + // And we can dispose of the window + && wasCreated && !q->testAttribute(Qt::WA_NativeWindow) + ); + + if (parent != newparent) { + // Update object parent now, so we can resolve new parent window below + QObjectPrivate::setParent_helper(newparent); + + if (q->windowHandle()) + q->windowHandle()->setFlags(f); + + // If the widget itself or any of its children have been created, + // we need to reparent their QWindows as well. + QWidget *parentWithWindow = closestParentWidgetWithWindowHandle(); + // But if the widget is about to be destroyed we must skip the + // widget itself, and only reparent children. + if (destroyWindow) + reparentWidgetWindowChildren(parentWithWindow); + else + reparentWidgetWindows(parentWithWindow, f); + } + bool explicitlyHidden = isExplicitlyHidden(); - // Reparenting toplevel to child - if (wasCreated && !(f & Qt::Window) && (oldFlags & Qt::Window) && !q->testAttribute(Qt::WA_NativeWindow)) { + if (destroyWindow) { if (extra && extra->hasWindowContainer) QWindowContainer::toplevelAboutToBeDestroyed(q); - QWindow *newParentWindow = newparent->windowHandle(); - if (!newParentWindow) - if (QWidget *npw = newparent->nativeParentWidget()) - newParentWindow = npw->windowHandle(); - - for (QObject *child : q->windowHandle()->children()) { - QWindow *childWindow = qobject_cast<QWindow *>(child); - if (!childWindow) - continue; - - QWidgetWindow *childWW = qobject_cast<QWidgetWindow *>(childWindow); - QWidget *childWidget = childWW ? childWW->widget() : nullptr; - if (!childWW || (childWidget && childWidget->testAttribute(Qt::WA_NativeWindow))) - childWindow->setParent(newParentWindow); + // There shouldn't be any QWindow children left, but if there + // are, re-parent them now, before we destroy. + if (!q->windowHandle()->children().isEmpty()) { + QWidget *parentWithWindow = closestParentWidgetWithWindowHandle(); + QWindow *newParentWindow = parentWithWindow ? parentWithWindow->windowHandle() : nullptr; + for (QObject *child : q->windowHandle()->children()) { + if (QWindow *childWindow = qobject_cast<QWindow *>(child)) { + qCWarning(lcWidgetWindow) << "Reparenting" << childWindow + << "before destroying" << this; + childWindow->setParent(newParentWindow); + } + } } - q->destroy(); + + // We have reparented any child windows of the widget we are + // about to destroy to the new parent window handle, so we can + // safely destroy this widget without destroying sub windows. + q->destroy(true, false); } adjustFlags(f, q); @@ -11029,6 +11034,53 @@ void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f) } } +void QWidgetPrivate::reparentWidgetWindows(QWidget *parentWithWindow, Qt::WindowFlags windowFlags) +{ + if (QWindow *window = windowHandle()) { + // Reparent this QWindow, and all QWindow children will follow + if (parentWithWindow) { + // The reparented widget has not updated its window flags yet, + // so we can't ask the widget directly. And we can't use the + // QWindow flags, as unlike QWidgets the QWindow flags always + // reflect Qt::Window, even for child windows. And we can't use + // QWindow::isTopLevel() either, as that depends on the parent, + // which we are in the process of updating. So we propagate the + // new flags of the reparented window from setParent_sys(). + if (windowFlags & Qt::Window) { + // Top level windows can only have transient parents, + // and the transient parent must be another top level. + QWidget *topLevel = parentWithWindow->window(); + auto *transientParent = topLevel->windowHandle(); + Q_ASSERT(transientParent); + qCDebug(lcWidgetWindow) << "Setting" << window << "transient parent to" << transientParent; + window->setTransientParent(transientParent); + window->setParent(nullptr); + } else { + auto *parentWindow = parentWithWindow->windowHandle(); + qCDebug(lcWidgetWindow) << "Reparenting" << window << "into" << parentWindow; + window->setTransientParent(nullptr); + window->setParent(parentWindow); + } + } else { + qCDebug(lcWidgetWindow) << "Making" << window << "top level window"; + window->setTransientParent(nullptr); + window->setParent(nullptr); + } + } else { + reparentWidgetWindowChildren(parentWithWindow); + } +} + +void QWidgetPrivate::reparentWidgetWindowChildren(QWidget *parentWithWindow) +{ + for (auto *child : std::as_const(children)) { + if (auto *childWidget = qobject_cast<QWidget*>(child)) { + auto *childPrivate = QWidgetPrivate::get(childWidget); + childPrivate->reparentWidgetWindows(parentWithWindow); + } + } +} + /*! Scrolls the widget including its children \a dx pixels to the right and \a dy downward. Both \a dx and \a dy may be negative. diff --git a/src/widgets/kernel/qwidget_p.h b/src/widgets/kernel/qwidget_p.h index e2e39630de..d16d0f438c 100644 --- a/src/widgets/kernel/qwidget_p.h +++ b/src/widgets/kernel/qwidget_p.h @@ -370,6 +370,8 @@ public: void showChildren(bool spontaneous); void hideChildren(bool spontaneous); void setParent_sys(QWidget *parent, Qt::WindowFlags); + void reparentWidgetWindows(QWidget *parentWithWindow, Qt::WindowFlags windowFlags = {}); + void reparentWidgetWindowChildren(QWidget *parentWithWindow); void scroll_sys(int dx, int dy); void scroll_sys(int dx, int dy, const QRect &r); void deactivateWidgetCleanup(); |