From 0531e444a78330eb9ce22afcf9b4d2e4a8b66105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Tue, 6 Feb 2024 16:36:17 +0100 Subject: [PATCH] Ensure QWidget::destroy() events and attributes match hiding the widget When destroying a widget via QWidget::destroy(), we clear WA_WState_Created, and then delete the QWidgetWindow, which in turn hides the widget. But QWidgetPrivate::setVisible(false) skips hide_helper() if the widget has not been created, which leaves out important events such as the hide event, and even more important, fails to clear WA_WState_Visible. As a result, the widget is left visible (and mapped), even if it has been destroyed. This is normally not a big issue for the main use of destroy(), namely destructing the widget, but for cases where destroy() and create() is used to recreate the widget this is important. We now unconditionally call hide_helper() if the widget is not already hidden. As a result, the widget will correctly be visible=false after a destroy(). This in turn means we need to re-apply the visible state after recreating the widget when we detect a mismatch in RHI configuration. Due to our meddling of the Hidden and ExplicitShowHide attributes in QWidgetWindow private, a QWidet::show() will not have any effect after being destroy(). This is okey for now, as destroy() is internal to a widget, and we make sure to either update WA_WState_Visible and WA_WState_Hidden (in QWidgetPrivate::setParent_sys), or use the QWidgetPrivate::setVisible() code path directly, which doesn't have that issue. The root problem will be fixed in a follow up. Change-Id: I77cb88d75e57f0d9a31741161fb14d618a653291 Reviewed-by: Volker Hilsheimer Reviewed-by: Qt CI Bot Reviewed-by: Richard Moe Gustavsen (cherry picked from commit 371d7ea19a8075a1ad2c2011433b40e8eb1f6816) Reviewed-by: Qt Cherry-pick Bot --- src/widgets/kernel/qwidget.cpp | 5 +- .../widgets/kernel/qwidget/tst_qwidget.cpp | 135 ++++++++++++++---- 2 files changed, 110 insertions(+), 30 deletions(-) diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index 2734d598517..694fcf59c22 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp @@ -8430,8 +8430,7 @@ void QWidgetPrivate::setVisible(bool visible) if (!q->testAttribute(Qt::WA_WState_Hidden)) { q->setAttribute(Qt::WA_WState_Hidden); - if (q->testAttribute(Qt::WA_WState_Created)) - hide_helper(); + hide_helper(); } // invalidate layout similar to updateGeometry() @@ -10899,10 +10898,12 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f) } if (recreate) { const auto windowStateBeforeDestroy = newtlw->windowState(); + const auto visibilityBeforeDestroy = newtlw->isVisible(); newtlw->destroy(); newtlw->create(); Q_ASSERT(newtlw->windowHandle()); newtlw->windowHandle()->setWindowStates(windowStateBeforeDestroy); + QWidgetPrivate::get(newtlw)->setVisible(visibilityBeforeDestroy); } } } diff --git a/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp b/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp index 6b8fd503d43..fe84d6e8e30 100644 --- a/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp +++ b/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp @@ -122,6 +122,34 @@ static QByteArray msgComparisonFailed(T v1, const char *op, T v2) return s.toLocal8Bit(); } +template class EventSpy : public QObject +{ +public: + EventSpy(T *widget, QEvent::Type event) + : m_widget(widget), eventToSpy(event) + { + if (m_widget) + m_widget->installEventFilter(this); + } + + T *widget() const { return m_widget; } + int count() const { return m_count; } + void clear() { m_count = 0; } + +protected: + bool eventFilter(QObject *object, QEvent *event) override + { + if (event->type() == eventToSpy) + ++m_count; + return QObject::eventFilter(object, event); + } + +private: + T *m_widget; + const QEvent::Type eventToSpy; + int m_count = 0; +}; + Q_LOGGING_CATEGORY(lcTests, "qt.widgets.tests") class tst_QWidget : public QObject @@ -227,6 +255,7 @@ private slots: void ensureCreated(); void createAndDestroy(); + void eventsAndAttributesOnDestroy(); void winIdChangeEvent(); void persistentWinId(); void showNativeChild(); @@ -5262,6 +5291,84 @@ void tst_QWidget::createAndDestroy() QVERIFY(widget.internalWinId()); } +void tst_QWidget::eventsAndAttributesOnDestroy() +{ + // The events and attributes when destroying a widget should + // include those of hiding the widget. + + CreateDestroyWidget widget; + EventSpy showEventSpy(&widget, QEvent::Show); + EventSpy hideEventSpy(&widget, QEvent::Hide); + + QCOMPARE(widget.testAttribute(Qt::WA_WState_Created), false); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Visible), false); + QCOMPARE(widget.testAttribute(Qt::WA_Mapped), false); + + widget.show(); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Created), true); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Visible), true); + QTRY_COMPARE(widget.testAttribute(Qt::WA_Mapped), true); + QCOMPARE(showEventSpy.count(), 1); + QCOMPARE(hideEventSpy.count(), 0); + + widget.hide(); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Created), true); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Visible), false); + QCOMPARE(widget.testAttribute(Qt::WA_Mapped), false); + QCOMPARE(showEventSpy.count(), 1); + QCOMPARE(hideEventSpy.count(), 1); + + widget.show(); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Created), true); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Visible), true); + QTRY_COMPARE(widget.testAttribute(Qt::WA_Mapped), true); + QCOMPARE(showEventSpy.count(), 2); + QCOMPARE(hideEventSpy.count(), 1); + + widget.destroy(); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Created), false); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Visible), false); + QCOMPARE(widget.testAttribute(Qt::WA_Mapped), false); + QCOMPARE(showEventSpy.count(), 2); + QCOMPARE(hideEventSpy.count(), 2); + + const int hideEventsAfterDestroy = hideEventSpy.count(); + + widget.create(); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Created), true); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Visible), false); + QCOMPARE(widget.testAttribute(Qt::WA_Mapped), false); + QCOMPARE(showEventSpy.count(), 2); + QCOMPARE(hideEventSpy.count(), hideEventsAfterDestroy); + + QWidgetPrivate::get(&widget)->setVisible(true); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Created), true); + QCOMPARE(widget.testAttribute(Qt::WA_WState_Visible), true); + QTRY_COMPARE(widget.testAttribute(Qt::WA_Mapped), true); + QCOMPARE(showEventSpy.count(), 3); + QCOMPARE(hideEventSpy.count(), hideEventsAfterDestroy); + + // Make sure the destroy that happens when a top level + // is moved to being a child does not prevent the child + // being shown again. + + QWidget parent; + QWidget child; + parent.show(); + QVERIFY(QTest::qWaitForWindowExposed(&parent)); + child.show(); + QVERIFY(QTest::qWaitForWindowExposed(&child)); + + child.setParent(&parent); + QCOMPARE(child.testAttribute(Qt::WA_WState_Created), false); + QCOMPARE(child.testAttribute(Qt::WA_WState_Visible), false); + + child.show(); + QCOMPARE(child.testAttribute(Qt::WA_WState_Created), true); + QCOMPARE(child.testAttribute(Qt::WA_WState_Visible), true); + QVERIFY(QTest::qWaitForWindowExposed(&child)); +} + void tst_QWidget::winIdChangeEvent() { { @@ -7060,34 +7167,6 @@ void tst_QWidget::setFocus() } } -template class EventSpy : public QObject -{ -public: - EventSpy(T *widget, QEvent::Type event) - : m_widget(widget), eventToSpy(event) - { - if (m_widget) - m_widget->installEventFilter(this); - } - - T *widget() const { return m_widget; } - int count() const { return m_count; } - void clear() { m_count = 0; } - -protected: - bool eventFilter(QObject *object, QEvent *event) override - { - if (event->type() == eventToSpy) - ++m_count; - return QObject::eventFilter(object, event); - } - -private: - T *m_widget; - const QEvent::Type eventToSpy; - int m_count = 0; -}; - #ifndef QT_NO_CURSOR void tst_QWidget::setCursor() {