QDockWidget: don't emit visibilityChanged while in destructor

Amends 2c67d47ea15c6dc34cc20d8fbdb406efb19f11d7, after which we emitted
the signal, as the setParent(nullptr) call in the destructor would call
back into the QDockWidget::event override.

That change was correct, but we are now emitted signals while in the
destructor, after a potential subclass destructor was already completed.
This crashed applications that had slots connected to those signals.

While arguably an application problem (PMF connections need to be
disconnected explicitly), we can avoid this regression by blocking
the emission of that signal when already in the destructor.

Fixes: QTBUG-136485
Pick-to: 6.9.1 6.8 6.5
Change-Id: I6d5e98136beedc94c22287ccfd1198dd80f4f95e
Reviewed-by: Axel Spoerl <axel.spoerl@qt.io>
(cherry picked from commit 7869593119ffaea6002e6668814af159a2077398)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Volker Hilsheimer 2025-05-15 13:56:06 +03:00 committed by Qt Cherry-pick Bot
parent 19cd476da7
commit e30e358ce6
3 changed files with 50 additions and 2 deletions

View File

@ -1359,6 +1359,8 @@ QDockWidget::QDockWidget(const QString &title, QWidget *parent, Qt::WindowFlags
*/
QDockWidget::~QDockWidget()
{
Q_D(QDockWidget);
d->inDestructor = true;
// Do all the unregistering while we're still a QDockWidget. Otherwise, it
// would be ~QObject() which does that and then QDockAreaLayout::takeAt(),
// acting on QEvent::ChildRemoved, will try to access our QWidget-ness when
@ -1645,8 +1647,12 @@ bool QDockWidget::event(QEvent *event)
case QEvent::Hide:
if (layout != nullptr)
layout->keepSize(this);
d->toggleViewAction->setChecked(false);
emit visibilityChanged(false);
// If we are in the destructor, don't emit any signals, as those might
// be handled by a slot that requires this dock widget to still be alive.
if (!d->inDestructor) {
d->toggleViewAction->setChecked(false);
emit visibilityChanged(false);
}
break;
case QEvent::Show: {
d->toggleViewAction->setChecked(true);

View File

@ -94,6 +94,7 @@ public:
QRect undockedGeometry;
QString fixedWindowTitle;
QString dockedWindowTitle;
bool inDestructor = false;
bool mousePressEvent(QMouseEvent *event);
bool mouseDoubleClickEvent(QMouseEvent *event);

View File

@ -46,6 +46,8 @@ private slots:
void allowedAreas();
void toggleViewAction();
void visibilityChanged();
void visibilityChangedOnDestruction_data();
void visibilityChangedOnDestruction();
void updateTabBarOnVisibilityChanged();
void dockLocationChanged();
void setTitleBarWidget();
@ -717,6 +719,45 @@ void tst_QDockWidget::visibilityChanged()
QCOMPARE(spy.at(0).at(0).toBool(), true);
}
// QTBUG-136485 - QDockWidget didn't emit visibilityChanged when getting
// destroyed until 6.9.0; it did in 6.9.0, causing regressions in applications.
// So make sure we don't emit that signal when a QDockWidget gets destroyed.
// When implicitly destroyed as a child of a QMainWindow, it gets hidden first,
// so it emits the signal.
void tst_QDockWidget::visibilityChangedOnDestruction_data()
{
QTest::addColumn<bool>("explicitDestroy");
QTest::addColumn<bool>("floating");
QTest::addColumn<int>("signalCount");
QTest::addRow("Explicit, docked") << true << false << 0;
QTest::addRow("Explicit, floating") << true << true << 0;
QTest::addRow("Implicit, docked") << false << false << 1;
QTest::addRow("Implicit, floating") << false << true << 0;
}
void tst_QDockWidget::visibilityChangedOnDestruction()
{
QFETCH(const bool, explicitDestroy);
QFETCH(const bool, floating);
QFETCH(const int, signalCount);
std::unique_ptr<QMainWindow> mw(new QMainWindow);
QDockWidget *dw = new QDockWidget;
mw->addDockWidget(Qt::LeftDockWidgetArea, dw);
if (floating)
dw->setFloating(true);
mw->show();
QVERIFY(QTest::qWaitForWindowExposed(mw.get()));
QSignalSpy spy(dw, &QDockWidget::visibilityChanged);
if (explicitDestroy)
delete dw;
else
mw.reset();
QCOMPARE(spy.count(), signalCount);
}
void tst_QDockWidget::updateTabBarOnVisibilityChanged()
{
// QTBUG49045: Populate tabified dock area with 4 widgets, set the tab