Skip to content

Commit c956eb8

Browse files
torarnvvohi
authored andcommitted
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 <[email protected]>
1 parent b301210 commit c956eb8

File tree

3 files changed

+245
-39
lines changed

3 files changed

+245
-39
lines changed

src/widgets/kernel/qwidget.cpp

Lines changed: 91 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ using namespace Qt::StringLiterals;
8383

8484
Q_LOGGING_CATEGORY(lcWidgetPainting, "qt.widgets.painting", QtWarningMsg);
8585
Q_LOGGING_CATEGORY(lcWidgetShowHide, "qt.widgets.showhide", QtWarningMsg);
86+
Q_LOGGING_CATEGORY(lcWidgetWindow, "qt.widgets.window", QtWarningMsg);
8687

8788
#ifndef QT_NO_DEBUG_STREAM
8889
namespace {
@@ -10953,57 +10954,61 @@ void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f)
1095310954

1095410955
setWinId(0);
1095510956

10956-
if (parent != newparent) {
10957-
QObjectPrivate::setParent_helper(newparent); //### why does this have to be done in the _sys function???
10958-
if (q->windowHandle()) {
10959-
q->windowHandle()->setFlags(f);
10960-
QWidget *parentWithWindow =
10961-
newparent ? (newparent->windowHandle() ? newparent : newparent->nativeParentWidget()) : nullptr;
10962-
if (parentWithWindow) {
10963-
QWidget *topLevel = parentWithWindow->window();
10964-
if ((f & Qt::Window) && topLevel && topLevel->windowHandle()) {
10965-
q->windowHandle()->setTransientParent(topLevel->windowHandle());
10966-
q->windowHandle()->setParent(nullptr);
10967-
} else {
10968-
q->windowHandle()->setTransientParent(nullptr);
10969-
q->windowHandle()->setParent(parentWithWindow->windowHandle());
10970-
}
10971-
} else {
10972-
q->windowHandle()->setTransientParent(nullptr);
10973-
q->windowHandle()->setParent(nullptr);
10974-
}
10975-
}
10976-
}
10977-
1097810957
if (!newparent) {
1097910958
f |= Qt::Window;
1098010959
if (parent)
1098110960
targetScreen = q->parentWidget()->window()->screen();
1098210961
}
1098310962

10963+
const bool destroyWindow = (
10964+
// Reparenting top level to child
10965+
(oldFlags & Qt::Window) && !(f & Qt::Window)
10966+
// And we can dispose of the window
10967+
&& wasCreated && !q->testAttribute(Qt::WA_NativeWindow)
10968+
);
10969+
10970+
if (parent != newparent) {
10971+
// Update object parent now, so we can resolve new parent window below
10972+
QObjectPrivate::setParent_helper(newparent);
10973+
10974+
if (q->windowHandle())
10975+
q->windowHandle()->setFlags(f);
10976+
10977+
// If the widget itself or any of its children have been created,
10978+
// we need to reparent their QWindows as well.
10979+
QWidget *parentWithWindow = closestParentWidgetWithWindowHandle();
10980+
// But if the widget is about to be destroyed we must skip the
10981+
// widget itself, and only reparent children.
10982+
if (destroyWindow)
10983+
reparentWidgetWindowChildren(parentWithWindow);
10984+
else
10985+
reparentWidgetWindows(parentWithWindow, f);
10986+
}
10987+
1098410988
bool explicitlyHidden = isExplicitlyHidden();
1098510989

10986-
// Reparenting toplevel to child
10987-
if (wasCreated && !(f & Qt::Window) && (oldFlags & Qt::Window) && !q->testAttribute(Qt::WA_NativeWindow)) {
10990+
if (destroyWindow) {
1098810991
if (extra && extra->hasWindowContainer)
1098910992
QWindowContainer::toplevelAboutToBeDestroyed(q);
1099010993

10991-
QWindow *newParentWindow = newparent->windowHandle();
10992-
if (!newParentWindow)
10993-
if (QWidget *npw = newparent->nativeParentWidget())
10994-
newParentWindow = npw->windowHandle();
10995-
10996-
for (QObject *child : q->windowHandle()->children()) {
10997-
QWindow *childWindow = qobject_cast<QWindow *>(child);
10998-
if (!childWindow)
10999-
continue;
11000-
11001-
QWidgetWindow *childWW = qobject_cast<QWidgetWindow *>(childWindow);
11002-
QWidget *childWidget = childWW ? childWW->widget() : nullptr;
11003-
if (!childWW || (childWidget && childWidget->testAttribute(Qt::WA_NativeWindow)))
11004-
childWindow->setParent(newParentWindow);
10994+
// There shouldn't be any QWindow children left, but if there
10995+
// are, re-parent them now, before we destroy.
10996+
if (!q->windowHandle()->children().isEmpty()) {
10997+
QWidget *parentWithWindow = closestParentWidgetWithWindowHandle();
10998+
QWindow *newParentWindow = parentWithWindow ? parentWithWindow->windowHandle() : nullptr;
10999+
for (QObject *child : q->windowHandle()->children()) {
11000+
if (QWindow *childWindow = qobject_cast<QWindow *>(child)) {
11001+
qCWarning(lcWidgetWindow) << "Reparenting" << childWindow
11002+
<< "before destroying" << this;
11003+
childWindow->setParent(newParentWindow);
11004+
}
11005+
}
1100511006
}
11006-
q->destroy();
11007+
11008+
// We have reparented any child windows of the widget we are
11009+
// about to destroy to the new parent window handle, so we can
11010+
// safely destroy this widget without destroying sub windows.
11011+
q->destroy(true, false);
1100711012
}
1100811013

1100911014
adjustFlags(f, q);
@@ -11029,6 +11034,53 @@ void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f)
1102911034
}
1103011035
}
1103111036

11037+
void QWidgetPrivate::reparentWidgetWindows(QWidget *parentWithWindow, Qt::WindowFlags windowFlags)
11038+
{
11039+
if (QWindow *window = windowHandle()) {
11040+
// Reparent this QWindow, and all QWindow children will follow
11041+
if (parentWithWindow) {
11042+
// The reparented widget has not updated its window flags yet,
11043+
// so we can't ask the widget directly. And we can't use the
11044+
// QWindow flags, as unlike QWidgets the QWindow flags always
11045+
// reflect Qt::Window, even for child windows. And we can't use
11046+
// QWindow::isTopLevel() either, as that depends on the parent,
11047+
// which we are in the process of updating. So we propagate the
11048+
// new flags of the reparented window from setParent_sys().
11049+
if (windowFlags & Qt::Window) {
11050+
// Top level windows can only have transient parents,
11051+
// and the transient parent must be another top level.
11052+
QWidget *topLevel = parentWithWindow->window();
11053+
auto *transientParent = topLevel->windowHandle();
11054+
Q_ASSERT(transientParent);
11055+
qCDebug(lcWidgetWindow) << "Setting" << window << "transient parent to" << transientParent;
11056+
window->setTransientParent(transientParent);
11057+
window->setParent(nullptr);
11058+
} else {
11059+
auto *parentWindow = parentWithWindow->windowHandle();
11060+
qCDebug(lcWidgetWindow) << "Reparenting" << window << "into" << parentWindow;
11061+
window->setTransientParent(nullptr);
11062+
window->setParent(parentWindow);
11063+
}
11064+
} else {
11065+
qCDebug(lcWidgetWindow) << "Making" << window << "top level window";
11066+
window->setTransientParent(nullptr);
11067+
window->setParent(nullptr);
11068+
}
11069+
} else {
11070+
reparentWidgetWindowChildren(parentWithWindow);
11071+
}
11072+
}
11073+
11074+
void QWidgetPrivate::reparentWidgetWindowChildren(QWidget *parentWithWindow)
11075+
{
11076+
for (auto *child : std::as_const(children)) {
11077+
if (auto *childWidget = qobject_cast<QWidget*>(child)) {
11078+
auto *childPrivate = QWidgetPrivate::get(childWidget);
11079+
childPrivate->reparentWidgetWindows(parentWithWindow);
11080+
}
11081+
}
11082+
}
11083+
1103211084
/*!
1103311085
Scrolls the widget including its children \a dx pixels to the
1103411086
right and \a dy downward. Both \a dx and \a dy may be negative.

src/widgets/kernel/qwidget_p.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,8 @@ class Q_WIDGETS_EXPORT QWidgetPrivate : public QObjectPrivate
370370
void showChildren(bool spontaneous);
371371
void hideChildren(bool spontaneous);
372372
void setParent_sys(QWidget *parent, Qt::WindowFlags);
373+
void reparentWidgetWindows(QWidget *parentWithWindow, Qt::WindowFlags windowFlags = {});
374+
void reparentWidgetWindowChildren(QWidget *parentWithWindow);
373375
void scroll_sys(int dx, int dy);
374376
void scroll_sys(int dx, int dy, const QRect &r);
375377
void deactivateWidgetCleanup();

tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,9 @@ private slots:
472472

473473
void dragEnterLeaveSymmetry();
474474

475+
void reparentWindowHandles_data();
476+
void reparentWindowHandles();
477+
475478
private:
476479
const QString m_platform;
477480
QSize m_testWidgetSize;
@@ -13771,5 +13774,154 @@ void tst_QWidget::dragEnterLeaveSymmetry()
1377113774
QVERIFY(widget.underMouse());
1377213775
}
1377313776

13777+
void tst_QWidget::reparentWindowHandles_data()
13778+
{
13779+
QTest::addColumn<int>("stage");
13780+
QTest::addRow("reparent child") << 1;
13781+
QTest::addRow("top level to child") << 2;
13782+
QTest::addRow("transient parent") << 3;
13783+
QTest::addRow("window container") << 4;
13784+
}
13785+
13786+
void tst_QWidget::reparentWindowHandles()
13787+
{
13788+
const bool nativeSiblingsOriginal = qApp->testAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
13789+
qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true);
13790+
auto nativeSiblingGuard = qScopeGuard([&]{
13791+
qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, nativeSiblingsOriginal);
13792+
});
13793+
13794+
QFETCH(int, stage);
13795+
13796+
switch (stage) {
13797+
case 1: {
13798+
// Reparent child widget
13799+
13800+
QWidget topLevel;
13801+
topLevel.setAttribute(Qt::WA_NativeWindow);
13802+
QVERIFY(topLevel.windowHandle());
13803+
QPointer<QWidget> child = new QWidget(&topLevel);
13804+
child->setAttribute(Qt::WA_DontCreateNativeAncestors);
13805+
child->setAttribute(Qt::WA_NativeWindow);
13806+
QVERIFY(child->windowHandle());
13807+
13808+
QWidget anotherTopLevel;
13809+
anotherTopLevel.setAttribute(Qt::WA_NativeWindow);
13810+
QVERIFY(anotherTopLevel.windowHandle());
13811+
QPointer<QWidget> intermediate = new QWidget(&anotherTopLevel);
13812+
QPointer<QWidget> leaf = new QWidget(intermediate);
13813+
leaf->setAttribute(Qt::WA_DontCreateNativeAncestors);
13814+
leaf->setAttribute(Qt::WA_NativeWindow);
13815+
QVERIFY(leaf->windowHandle());
13816+
QVERIFY(!intermediate->windowHandle());
13817+
13818+
// Reparenting a native widget should reparent the QWindow
13819+
child->setParent(leaf);
13820+
QCOMPARE(child->windowHandle()->parent(), leaf->windowHandle());
13821+
QCOMPARE(child->windowHandle()->transientParent(), nullptr);
13822+
QVERIFY(!intermediate->windowHandle());
13823+
13824+
// So should reparenting a non-native widget with native children
13825+
intermediate->setParent(&topLevel);
13826+
QVERIFY(!intermediate->windowHandle());
13827+
QCOMPARE(leaf->windowHandle()->parent(), topLevel.windowHandle());
13828+
QCOMPARE(leaf->windowHandle()->transientParent(), nullptr);
13829+
QCOMPARE(child->windowHandle()->parent(), leaf->windowHandle());
13830+
QCOMPARE(child->windowHandle()->transientParent(), nullptr);
13831+
}
13832+
break;
13833+
case 2: {
13834+
// Top level to child
13835+
13836+
QWidget topLevel;
13837+
topLevel.setAttribute(Qt::WA_NativeWindow);
13838+
13839+
// A regular top level loses its nativeness
13840+
QPointer<QWidget> regularToplevel = new QWidget;
13841+
regularToplevel->show();
13842+
QVERIFY(QTest::qWaitForWindowExposed(regularToplevel));
13843+
QVERIFY(regularToplevel->windowHandle());
13844+
regularToplevel->setParent(&topLevel);
13845+
QVERIFY(!regularToplevel->windowHandle());
13846+
13847+
// A regular top level loses its nativeness
13848+
QPointer<QWidget> regularToplevelWithNativeChildren = new QWidget;
13849+
QPointer<QWidget> nativeChild = new QWidget(regularToplevelWithNativeChildren);
13850+
nativeChild->setAttribute(Qt::WA_DontCreateNativeAncestors);
13851+
nativeChild->setAttribute(Qt::WA_NativeWindow);
13852+
QVERIFY(nativeChild->windowHandle());
13853+
regularToplevelWithNativeChildren->show();
13854+
QVERIFY(QTest::qWaitForWindowExposed(regularToplevelWithNativeChildren));
13855+
QVERIFY(regularToplevelWithNativeChildren->windowHandle());
13856+
regularToplevelWithNativeChildren->setParent(&topLevel);
13857+
QVERIFY(!regularToplevelWithNativeChildren->windowHandle());
13858+
// But the native child does not
13859+
QVERIFY(nativeChild->windowHandle());
13860+
QCOMPARE(nativeChild->windowHandle()->parent(), topLevel.windowHandle());
13861+
13862+
// An explicitly native top level keeps its nativeness, and the window handle moves
13863+
QPointer<QWidget> nativeTopLevel = new QWidget;
13864+
nativeTopLevel->setAttribute(Qt::WA_NativeWindow);
13865+
QVERIFY(nativeTopLevel->windowHandle());
13866+
nativeTopLevel->setParent(&topLevel);
13867+
QVERIFY(nativeTopLevel->windowHandle());
13868+
QCOMPARE(nativeTopLevel->windowHandle()->parent(), topLevel.windowHandle());
13869+
}
13870+
break;
13871+
case 3: {
13872+
// Transient parent
13873+
13874+
QWidget topLevel;
13875+
topLevel.setAttribute(Qt::WA_NativeWindow);
13876+
QVERIFY(topLevel.windowHandle());
13877+
QPointer<QWidget> child = new QWidget(&topLevel);
13878+
child->setAttribute(Qt::WA_NativeWindow);
13879+
QVERIFY(child->windowHandle());
13880+
13881+
QWidget anotherTopLevel;
13882+
anotherTopLevel.setAttribute(Qt::WA_NativeWindow);
13883+
QVERIFY(anotherTopLevel.windowHandle());
13884+
13885+
// Make transient child of top level
13886+
anotherTopLevel.setParent(&topLevel, Qt::Window);
13887+
QCOMPARE(anotherTopLevel.windowHandle()->parent(), nullptr);
13888+
QCOMPARE(anotherTopLevel.windowHandle()->transientParent(), topLevel.windowHandle());
13889+
13890+
// Make transient child of child
13891+
anotherTopLevel.setParent(child, Qt::Window);
13892+
QCOMPARE(anotherTopLevel.windowHandle()->parent(), nullptr);
13893+
QCOMPARE(anotherTopLevel.windowHandle()->transientParent(), topLevel.windowHandle());
13894+
}
13895+
break;
13896+
case 4: {
13897+
// Window container
13898+
13899+
QWidget topLevel;
13900+
topLevel.setAttribute(Qt::WA_NativeWindow);
13901+
QVERIFY(topLevel.windowHandle());
13902+
13903+
QPointer<QWidget> child = new QWidget(&topLevel);
13904+
QVERIFY(!child->windowHandle());
13905+
13906+
QWindow *window = new QWindow;
13907+
QWidget *container = QWidget::createWindowContainer(window);
13908+
container->setParent(child);
13909+
topLevel.show();
13910+
QVERIFY(QTest::qWaitForWindowExposed(&topLevel));
13911+
QCOMPARE(window->parent(), topLevel.windowHandle());
13912+
13913+
QWidget anotherTopLevel;
13914+
anotherTopLevel.setAttribute(Qt::WA_NativeWindow);
13915+
QVERIFY(anotherTopLevel.windowHandle());
13916+
13917+
child->setParent(&anotherTopLevel);
13918+
QCOMPARE(window->parent(), anotherTopLevel.windowHandle());
13919+
}
13920+
break;
13921+
default:
13922+
Q_UNREACHABLE();
13923+
}
13924+
}
13925+
1377413926
QTEST_MAIN(tst_QWidget)
1377513927
#include "tst_qwidget.moc"

0 commit comments

Comments
 (0)