summaryrefslogtreecommitdiffstats
path: root/src/widgets
diff options
context:
space:
mode:
authorTor Arne Vestbø <tor.arne.vestbo@qt.io>2024-03-02 16:55:43 +0100
committerVolker Hilsheimer <volker.hilsheimer@qt.io>2024-03-14 13:50:36 +0000
commitc956eb8eddb1b3608d7e3d332fbe55df5ec41578 (patch)
treedc6de1d52fa3f0ea9ba1a650be899ea27c8147b0 /src/widgets
parentb30121041c07b1b8613eaf624c9aa55a51001aef (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.cpp130
-rw-r--r--src/widgets/kernel/qwidget_p.h2
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();