diff options
-rw-r--r-- | src/widgets/kernel/qwidget.cpp | 130 | ||||
-rw-r--r-- | src/widgets/kernel/qwidget_p.h | 2 | ||||
-rw-r--r-- | tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp | 152 |
3 files changed, 245 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(); diff --git a/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp b/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp index 0b0c1a9391..dde7054467 100644 --- a/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp +++ b/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp @@ -472,6 +472,9 @@ private slots: void dragEnterLeaveSymmetry(); + void reparentWindowHandles_data(); + void reparentWindowHandles(); + private: const QString m_platform; QSize m_testWidgetSize; @@ -13771,5 +13774,154 @@ void tst_QWidget::dragEnterLeaveSymmetry() QVERIFY(widget.underMouse()); } +void tst_QWidget::reparentWindowHandles_data() +{ + QTest::addColumn<int>("stage"); + QTest::addRow("reparent child") << 1; + QTest::addRow("top level to child") << 2; + QTest::addRow("transient parent") << 3; + QTest::addRow("window container") << 4; +} + +void tst_QWidget::reparentWindowHandles() +{ + const bool nativeSiblingsOriginal = qApp->testAttribute(Qt::AA_DontCreateNativeWidgetSiblings); + qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); + auto nativeSiblingGuard = qScopeGuard([&]{ + qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, nativeSiblingsOriginal); + }); + + QFETCH(int, stage); + + switch (stage) { + case 1: { + // Reparent child widget + + QWidget topLevel; + topLevel.setAttribute(Qt::WA_NativeWindow); + QVERIFY(topLevel.windowHandle()); + QPointer<QWidget> child = new QWidget(&topLevel); + child->setAttribute(Qt::WA_DontCreateNativeAncestors); + child->setAttribute(Qt::WA_NativeWindow); + QVERIFY(child->windowHandle()); + + QWidget anotherTopLevel; + anotherTopLevel.setAttribute(Qt::WA_NativeWindow); + QVERIFY(anotherTopLevel.windowHandle()); + QPointer<QWidget> intermediate = new QWidget(&anotherTopLevel); + QPointer<QWidget> leaf = new QWidget(intermediate); + leaf->setAttribute(Qt::WA_DontCreateNativeAncestors); + leaf->setAttribute(Qt::WA_NativeWindow); + QVERIFY(leaf->windowHandle()); + QVERIFY(!intermediate->windowHandle()); + + // Reparenting a native widget should reparent the QWindow + child->setParent(leaf); + QCOMPARE(child->windowHandle()->parent(), leaf->windowHandle()); + QCOMPARE(child->windowHandle()->transientParent(), nullptr); + QVERIFY(!intermediate->windowHandle()); + + // So should reparenting a non-native widget with native children + intermediate->setParent(&topLevel); + QVERIFY(!intermediate->windowHandle()); + QCOMPARE(leaf->windowHandle()->parent(), topLevel.windowHandle()); + QCOMPARE(leaf->windowHandle()->transientParent(), nullptr); + QCOMPARE(child->windowHandle()->parent(), leaf->windowHandle()); + QCOMPARE(child->windowHandle()->transientParent(), nullptr); + } + break; + case 2: { + // Top level to child + + QWidget topLevel; + topLevel.setAttribute(Qt::WA_NativeWindow); + + // A regular top level loses its nativeness + QPointer<QWidget> regularToplevel = new QWidget; + regularToplevel->show(); + QVERIFY(QTest::qWaitForWindowExposed(regularToplevel)); + QVERIFY(regularToplevel->windowHandle()); + regularToplevel->setParent(&topLevel); + QVERIFY(!regularToplevel->windowHandle()); + + // A regular top level loses its nativeness + QPointer<QWidget> regularToplevelWithNativeChildren = new QWidget; + QPointer<QWidget> nativeChild = new QWidget(regularToplevelWithNativeChildren); + nativeChild->setAttribute(Qt::WA_DontCreateNativeAncestors); + nativeChild->setAttribute(Qt::WA_NativeWindow); + QVERIFY(nativeChild->windowHandle()); + regularToplevelWithNativeChildren->show(); + QVERIFY(QTest::qWaitForWindowExposed(regularToplevelWithNativeChildren)); + QVERIFY(regularToplevelWithNativeChildren->windowHandle()); + regularToplevelWithNativeChildren->setParent(&topLevel); + QVERIFY(!regularToplevelWithNativeChildren->windowHandle()); + // But the native child does not + QVERIFY(nativeChild->windowHandle()); + QCOMPARE(nativeChild->windowHandle()->parent(), topLevel.windowHandle()); + + // An explicitly native top level keeps its nativeness, and the window handle moves + QPointer<QWidget> nativeTopLevel = new QWidget; + nativeTopLevel->setAttribute(Qt::WA_NativeWindow); + QVERIFY(nativeTopLevel->windowHandle()); + nativeTopLevel->setParent(&topLevel); + QVERIFY(nativeTopLevel->windowHandle()); + QCOMPARE(nativeTopLevel->windowHandle()->parent(), topLevel.windowHandle()); + } + break; + case 3: { + // Transient parent + + QWidget topLevel; + topLevel.setAttribute(Qt::WA_NativeWindow); + QVERIFY(topLevel.windowHandle()); + QPointer<QWidget> child = new QWidget(&topLevel); + child->setAttribute(Qt::WA_NativeWindow); + QVERIFY(child->windowHandle()); + + QWidget anotherTopLevel; + anotherTopLevel.setAttribute(Qt::WA_NativeWindow); + QVERIFY(anotherTopLevel.windowHandle()); + + // Make transient child of top level + anotherTopLevel.setParent(&topLevel, Qt::Window); + QCOMPARE(anotherTopLevel.windowHandle()->parent(), nullptr); + QCOMPARE(anotherTopLevel.windowHandle()->transientParent(), topLevel.windowHandle()); + + // Make transient child of child + anotherTopLevel.setParent(child, Qt::Window); + QCOMPARE(anotherTopLevel.windowHandle()->parent(), nullptr); + QCOMPARE(anotherTopLevel.windowHandle()->transientParent(), topLevel.windowHandle()); + } + break; + case 4: { + // Window container + + QWidget topLevel; + topLevel.setAttribute(Qt::WA_NativeWindow); + QVERIFY(topLevel.windowHandle()); + + QPointer<QWidget> child = new QWidget(&topLevel); + QVERIFY(!child->windowHandle()); + + QWindow *window = new QWindow; + QWidget *container = QWidget::createWindowContainer(window); + container->setParent(child); + topLevel.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevel)); + QCOMPARE(window->parent(), topLevel.windowHandle()); + + QWidget anotherTopLevel; + anotherTopLevel.setAttribute(Qt::WA_NativeWindow); + QVERIFY(anotherTopLevel.windowHandle()); + + child->setParent(&anotherTopLevel); + QCOMPARE(window->parent(), anotherTopLevel.windowHandle()); + } + break; + default: + Q_UNREACHABLE(); + } +} + QTEST_MAIN(tst_QWidget) #include "tst_qwidget.moc" |