Skip to content

Commit

Permalink
MAYA-132611: Crash when docking the Select a light source (qt#102)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
2 people authored and GitHub Enterprise committed May 16, 2024
1 parent 43996d7 commit aaca5d7
Show file tree
Hide file tree
Showing 3 changed files with 293 additions and 39 deletions.
148 changes: 109 additions & 39 deletions src/widgets/kernel/qwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ using namespace QNativeInterface::Private;
using namespace Qt::StringLiterals;

Q_LOGGING_CATEGORY(lcWidgetPainting, "qt.widgets.painting", QtWarningMsg);
Q_LOGGING_CATEGORY(lcWidgetWindow, "qt.widgets.window", QtWarningMsg);

static inline bool qRectIntersects(const QRect &r1, const QRect &r2)
{
Expand Down Expand Up @@ -1026,6 +1027,23 @@ void QWidgetPrivate::createRecursively()
}
}

/*!
\internal
Returns the closest parent widget that has a QWindow window handle
\note This behavior is different from nativeParentWidget(), which
returns the closest parent that has a QWindow window handle with
a created QPlatformWindow, and hence native window (winId).
*/
QWidget *QWidgetPrivate::closestParentWidgetWithWindowHandle() const
{
Q_Q(const QWidget);
QWidget *parent = q->parentWidget();
while (parent && !parent->windowHandle())
parent = parent->parentWidget();
return parent;
}

QWindow *QWidgetPrivate::windowHandle(WindowHandleMode mode) const
{
if (mode == WindowHandleMode::Direct || mode == WindowHandleMode::Closest) {
Expand All @@ -1035,6 +1053,7 @@ QWindow *QWidgetPrivate::windowHandle(WindowHandleMode mode) const
}
}
if (mode == WindowHandleMode::Closest) {
// FIXME: Use closestParentWidgetWithWindowHandle instead
if (auto nativeParent = q_func()->nativeParentWidget()) {
if (auto window = nativeParent->windowHandle())
return window;
Expand Down Expand Up @@ -10855,57 +10874,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 = q->testAttribute(Qt::WA_WState_Hidden) && q->testAttribute(Qt::WA_WState_ExplicitShowHide);

// 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);
Expand All @@ -10931,6 +10954,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.
Expand Down
4 changes: 4 additions & 0 deletions src/widgets/kernel/qwidget_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,8 @@ class Q_WIDGETS_EXPORT QWidgetPrivate : public QObjectPrivate
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();
Expand Down Expand Up @@ -633,6 +635,8 @@ class Q_WIDGETS_EXPORT QWidgetPrivate : public QObjectPrivate

std::string flagsForDumping() const override;

QWidget *closestParentWidgetWithWindowHandle() const;

// Variables.
// Regular pointers (keep them together to avoid gaps on 64 bit architectures).
std::unique_ptr<QWExtra> extra;
Expand Down
Loading

0 comments on commit aaca5d7

Please sign in to comment.