Skip to content

Commit aaca5d7

Browse files
senthilauto2023torarnv
authored and
GitHub Enterprise
committed
MAYA-132611: Crash when docking the Select a light source (qt#102)
* Add QWidgetPrivate::closestParentWidgetWithWindowHandle helper method In contrast to nativeParentWidget(), we return the closest widget with a QWindow, even if this window has not been created yet. Pick-to: 6.7 6.6 6.5 Change-Id: Icac46297a6052a7a5698d752d4aa871bd5c2bdd8 Reviewed-by: Axel Spoerl <[email protected]> (cherry picked from commit b571634) * 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.5 Change-Id: I4d1217fce4c3c48cf5f7bfbe9d561ab408ceebb2 Reviewed-by: Volker Hilsheimer <[email protected]> (cherry picked from commit c956eb8) Reviewed-by: Tor Arne Vestbø <[email protected]> (cherry picked from commit 8ee25c6) (cherry picked from commit 2c0a4d6) --------- Co-authored-by: Tor Arne Vestbø <[email protected]>
1 parent 43996d7 commit aaca5d7

File tree

3 files changed

+293
-39
lines changed

3 files changed

+293
-39
lines changed

src/widgets/kernel/qwidget.cpp

Lines changed: 109 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ using namespace QNativeInterface::Private;
8282
using namespace Qt::StringLiterals;
8383

8484
Q_LOGGING_CATEGORY(lcWidgetPainting, "qt.widgets.painting", QtWarningMsg);
85+
Q_LOGGING_CATEGORY(lcWidgetWindow, "qt.widgets.window", QtWarningMsg);
8586

8687
static inline bool qRectIntersects(const QRect &r1, const QRect &r2)
8788
{
@@ -1026,6 +1027,23 @@ void QWidgetPrivate::createRecursively()
10261027
}
10271028
}
10281029

1030+
/*!
1031+
\internal
1032+
Returns the closest parent widget that has a QWindow window handle
1033+
1034+
\note This behavior is different from nativeParentWidget(), which
1035+
returns the closest parent that has a QWindow window handle with
1036+
a created QPlatformWindow, and hence native window (winId).
1037+
*/
1038+
QWidget *QWidgetPrivate::closestParentWidgetWithWindowHandle() const
1039+
{
1040+
Q_Q(const QWidget);
1041+
QWidget *parent = q->parentWidget();
1042+
while (parent && !parent->windowHandle())
1043+
parent = parent->parentWidget();
1044+
return parent;
1045+
}
1046+
10291047
QWindow *QWidgetPrivate::windowHandle(WindowHandleMode mode) const
10301048
{
10311049
if (mode == WindowHandleMode::Direct || mode == WindowHandleMode::Closest) {
@@ -1035,6 +1053,7 @@ QWindow *QWidgetPrivate::windowHandle(WindowHandleMode mode) const
10351053
}
10361054
}
10371055
if (mode == WindowHandleMode::Closest) {
1056+
// FIXME: Use closestParentWidgetWithWindowHandle instead
10381057
if (auto nativeParent = q_func()->nativeParentWidget()) {
10391058
if (auto window = nativeParent->windowHandle())
10401059
return window;
@@ -10855,57 +10874,61 @@ void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f)
1085510874

1085610875
setWinId(0);
1085710876

10858-
if (parent != newparent) {
10859-
QObjectPrivate::setParent_helper(newparent); //### why does this have to be done in the _sys function???
10860-
if (q->windowHandle()) {
10861-
q->windowHandle()->setFlags(f);
10862-
QWidget *parentWithWindow =
10863-
newparent ? (newparent->windowHandle() ? newparent : newparent->nativeParentWidget()) : nullptr;
10864-
if (parentWithWindow) {
10865-
QWidget *topLevel = parentWithWindow->window();
10866-
if ((f & Qt::Window) && topLevel && topLevel->windowHandle()) {
10867-
q->windowHandle()->setTransientParent(topLevel->windowHandle());
10868-
q->windowHandle()->setParent(nullptr);
10869-
} else {
10870-
q->windowHandle()->setTransientParent(nullptr);
10871-
q->windowHandle()->setParent(parentWithWindow->windowHandle());
10872-
}
10873-
} else {
10874-
q->windowHandle()->setTransientParent(nullptr);
10875-
q->windowHandle()->setParent(nullptr);
10876-
}
10877-
}
10878-
}
10879-
1088010877
if (!newparent) {
1088110878
f |= Qt::Window;
1088210879
if (parent)
1088310880
targetScreen = q->parentWidget()->window()->screen();
1088410881
}
1088510882

10883+
const bool destroyWindow = (
10884+
// Reparenting top level to child
10885+
(oldFlags & Qt::Window) && !(f & Qt::Window)
10886+
// And we can dispose of the window
10887+
&& wasCreated && !q->testAttribute(Qt::WA_NativeWindow)
10888+
);
10889+
10890+
if (parent != newparent) {
10891+
// Update object parent now, so we can resolve new parent window below
10892+
QObjectPrivate::setParent_helper(newparent);
10893+
10894+
if (q->windowHandle())
10895+
q->windowHandle()->setFlags(f);
10896+
10897+
// If the widget itself or any of its children have been created,
10898+
// we need to reparent their QWindows as well.
10899+
QWidget *parentWithWindow = closestParentWidgetWithWindowHandle();
10900+
// But if the widget is about to be destroyed we must skip the
10901+
// widget itself, and only reparent children.
10902+
if (destroyWindow)
10903+
reparentWidgetWindowChildren(parentWithWindow);
10904+
else
10905+
reparentWidgetWindows(parentWithWindow, f);
10906+
}
10907+
1088610908
bool explicitlyHidden = q->testAttribute(Qt::WA_WState_Hidden) && q->testAttribute(Qt::WA_WState_ExplicitShowHide);
1088710909

10888-
// Reparenting toplevel to child
10889-
if (wasCreated && !(f & Qt::Window) && (oldFlags & Qt::Window) && !q->testAttribute(Qt::WA_NativeWindow)) {
10910+
if (destroyWindow) {
1089010911
if (extra && extra->hasWindowContainer)
1089110912
QWindowContainer::toplevelAboutToBeDestroyed(q);
1089210913

10893-
QWindow *newParentWindow = newparent->windowHandle();
10894-
if (!newParentWindow)
10895-
if (QWidget *npw = newparent->nativeParentWidget())
10896-
newParentWindow = npw->windowHandle();
10897-
10898-
for (QObject *child : q->windowHandle()->children()) {
10899-
QWindow *childWindow = qobject_cast<QWindow *>(child);
10900-
if (!childWindow)
10901-
continue;
10902-
10903-
QWidgetWindow *childWW = qobject_cast<QWidgetWindow *>(childWindow);
10904-
QWidget *childWidget = childWW ? childWW->widget() : nullptr;
10905-
if (!childWW || (childWidget && childWidget->testAttribute(Qt::WA_NativeWindow)))
10906-
childWindow->setParent(newParentWindow);
10914+
// There shouldn't be any QWindow children left, but if there
10915+
// are, re-parent them now, before we destroy.
10916+
if (!q->windowHandle()->children().isEmpty()) {
10917+
QWidget *parentWithWindow = closestParentWidgetWithWindowHandle();
10918+
QWindow *newParentWindow = parentWithWindow ? parentWithWindow->windowHandle() : nullptr;
10919+
for (QObject *child : q->windowHandle()->children()) {
10920+
if (QWindow *childWindow = qobject_cast<QWindow *>(child)) {
10921+
qCWarning(lcWidgetWindow) << "Reparenting" << childWindow
10922+
<< "before destroying" << this;
10923+
childWindow->setParent(newParentWindow);
10924+
}
10925+
}
1090710926
}
10908-
q->destroy();
10927+
10928+
// We have reparented any child windows of the widget we are
10929+
// about to destroy to the new parent window handle, so we can
10930+
// safely destroy this widget without destroying sub windows.
10931+
q->destroy(true, false);
1090910932
}
1091010933

1091110934
adjustFlags(f, q);
@@ -10931,6 +10954,53 @@ void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f)
1093110954
}
1093210955
}
1093310956

10957+
void QWidgetPrivate::reparentWidgetWindows(QWidget *parentWithWindow, Qt::WindowFlags windowFlags)
10958+
{
10959+
if (QWindow *window = windowHandle()) {
10960+
// Reparent this QWindow, and all QWindow children will follow
10961+
if (parentWithWindow) {
10962+
// The reparented widget has not updated its window flags yet,
10963+
// so we can't ask the widget directly. And we can't use the
10964+
// QWindow flags, as unlike QWidgets the QWindow flags always
10965+
// reflect Qt::Window, even for child windows. And we can't use
10966+
// QWindow::isTopLevel() either, as that depends on the parent,
10967+
// which we are in the process of updating. So we propagate the
10968+
// new flags of the reparented window from setParent_sys().
10969+
if (windowFlags & Qt::Window) {
10970+
// Top level windows can only have transient parents,
10971+
// and the transient parent must be another top level.
10972+
QWidget *topLevel = parentWithWindow->window();
10973+
auto *transientParent = topLevel->windowHandle();
10974+
Q_ASSERT(transientParent);
10975+
qCDebug(lcWidgetWindow) << "Setting" << window << "transient parent to" << transientParent;
10976+
window->setTransientParent(transientParent);
10977+
window->setParent(nullptr);
10978+
} else {
10979+
auto *parentWindow = parentWithWindow->windowHandle();
10980+
qCDebug(lcWidgetWindow) << "Reparenting" << window << "into" << parentWindow;
10981+
window->setTransientParent(nullptr);
10982+
window->setParent(parentWindow);
10983+
}
10984+
} else {
10985+
qCDebug(lcWidgetWindow) << "Making" << window << "top level window";
10986+
window->setTransientParent(nullptr);
10987+
window->setParent(nullptr);
10988+
}
10989+
} else {
10990+
reparentWidgetWindowChildren(parentWithWindow);
10991+
}
10992+
}
10993+
10994+
void QWidgetPrivate::reparentWidgetWindowChildren(QWidget *parentWithWindow)
10995+
{
10996+
for (auto *child : std::as_const(children)) {
10997+
if (auto *childWidget = qobject_cast<QWidget*>(child)) {
10998+
auto *childPrivate = QWidgetPrivate::get(childWidget);
10999+
childPrivate->reparentWidgetWindows(parentWithWindow);
11000+
}
11001+
}
11002+
}
11003+
1093411004
/*!
1093511005
Scrolls the widget including its children \a dx pixels to the
1093611006
right and \a dy downward. Both \a dx and \a dy may be negative.

src/widgets/kernel/qwidget_p.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,8 @@ class Q_WIDGETS_EXPORT QWidgetPrivate : public QObjectPrivate
367367
void showChildren(bool spontaneous);
368368
void hideChildren(bool spontaneous);
369369
void setParent_sys(QWidget *parent, Qt::WindowFlags);
370+
void reparentWidgetWindows(QWidget *parentWithWindow, Qt::WindowFlags windowFlags = {});
371+
void reparentWidgetWindowChildren(QWidget *parentWithWindow);
370372
void scroll_sys(int dx, int dy);
371373
void scroll_sys(int dx, int dy, const QRect &r);
372374
void deactivateWidgetCleanup();
@@ -633,6 +635,8 @@ class Q_WIDGETS_EXPORT QWidgetPrivate : public QObjectPrivate
633635

634636
std::string flagsForDumping() const override;
635637

638+
QWidget *closestParentWidgetWithWindowHandle() const;
639+
636640
// Variables.
637641
// Regular pointers (keep them together to avoid gaps on 64 bit architectures).
638642
std::unique_ptr<QWExtra> extra;

0 commit comments

Comments
 (0)