diff --git a/src/widgets/widgets/qmainwindowlayout.cpp b/src/widgets/widgets/qmainwindowlayout.cpp index 52dec2efdfb..33dda248c42 100644 --- a/src/widgets/widgets/qmainwindowlayout.cpp +++ b/src/widgets/widgets/qmainwindowlayout.cpp @@ -1741,6 +1741,7 @@ bool QMainWindowLayout::restoreDockWidget(QDockWidget *dockwidget) #if QT_CONFIG(tabbar) void QMainWindowLayout::tabifyDockWidget(QDockWidget *first, QDockWidget *second) { + applyRestoredState(); addChildWidget(second); layoutState.dockAreaLayout.tabifyDockWidget(first, second); emit second->dockLocationChanged(dockWidgetArea(first)); @@ -1862,6 +1863,7 @@ void QMainWindowLayout::splitDockWidget(QDockWidget *after, QDockWidget *dockwidget, Qt::Orientation orientation) { + applyRestoredState(); addChildWidget(dockwidget); layoutState.dockAreaLayout.splitDockWidget(after, dockwidget, orientation); emit dockwidget->dockLocationChanged(dockWidgetArea(after)); @@ -2211,6 +2213,32 @@ QLayoutItem *QMainWindowLayout::takeAt(int index) return nullptr; } + +/*! + \internal + + restoredState stores what we earlier read from storage, but it couldn't + be applied as the mainwindow wasn't large enough (yet) to fit the state. + Usually, the restored state would be applied lazily in setGeometry below. + However, if the mainwindow's layout is modified (e.g. by a call to tabify or + splitDockWidgets), then we have to forget the restored state as it might contain + dangling pointers (QDockWidgetLayoutItem has a copy constructor that copies the + layout item pointer, and splitting or tabify might have to delete some of those + layout structures). + + Functions that might result in the QMainWindowLayoutState storing dangling pointers + have to call this function first, so that the restoredState becomes the actual state + first, and is forgotten afterwards. +*/ +void QMainWindowLayout::applyRestoredState() +{ + if (restoredState) { + layoutState = *restoredState; + restoredState.reset(); + discardRestoredStateTimer.stop(); + } +} + void QMainWindowLayout::setGeometry(const QRect &_r) { if (savedState.isValid()) diff --git a/src/widgets/widgets/qmainwindowlayout_p.h b/src/widgets/widgets/qmainwindowlayout_p.h index 72bb9f23846..83d41fa5814 100644 --- a/src/widgets/widgets/qmainwindowlayout_p.h +++ b/src/widgets/widgets/qmainwindowlayout_p.h @@ -587,6 +587,7 @@ public: QLayoutItem *unplug(QWidget *widget, QDockWidgetPrivate::DragScope scope); void revert(QLayoutItem *widgetItem); void applyState(QMainWindowLayoutState &newState, bool animate = true); + void applyRestoredState(); void restore(bool keepSavedState = false); void animationFinished(QWidget *widget); diff --git a/tests/auto/widgets/widgets/qmainwindow/tst_qmainwindow.cpp b/tests/auto/widgets/widgets/qmainwindow/tst_qmainwindow.cpp index d2e5e86dea2..b5457e5e722 100644 --- a/tests/auto/widgets/widgets/qmainwindow/tst_qmainwindow.cpp +++ b/tests/auto/widgets/widgets/qmainwindow/tst_qmainwindow.cpp @@ -104,6 +104,7 @@ private slots: void restoreStateFromPreviousVersion(); void restoreStateSizeChanged_data(); void restoreStateSizeChanged(); + void restoreAndModify(); void createPopupMenu(); void hideBeforeLayout(); #ifdef QT_BUILD_INTERNAL @@ -1476,6 +1477,70 @@ void tst_QMainWindow::restoreStateSizeChanged() } } +/*! + If a main window's state is restored but also modified, then we + might have to forget the restored state to avoid dangling pointers. + See comment in QMainWindowLayout::applyRestoredState() and QTBUG-120025. +*/ +void tst_QMainWindow::restoreAndModify() +{ + class MainWindow : public QMainWindow + { + public: + MainWindow() + { + setCentralWidget(new QTextEdit); + + customers = new QDockWidget(tr("Customers"), this); + customers->setObjectName("Customers"); + customers->setAllowedAreas(Qt::LeftDockWidgetArea | + Qt::RightDockWidgetArea); + customers->setWidget(new QTextEdit); + addDockWidget(Qt::RightDockWidgetArea, customers); + + paragraphs = new QDockWidget(tr("Paragraphs"), this); + paragraphs->setObjectName("Paragraphs"); + paragraphs->setWidget(new QTextEdit); + addDockWidget(Qt::RightDockWidgetArea, paragraphs); + } + + void restore() + { + if (!savedGeometry.isEmpty()) + restoreGeometry(savedGeometry); + setWindowState(Qt::WindowMaximized); + if (!savedState.isEmpty()) + restoreState(savedState); + + tabifyDockWidget(customers, paragraphs); + } + protected: + void closeEvent(QCloseEvent *event) override + { + savedGeometry = saveGeometry(); + savedState = saveState(); + + return QMainWindow::closeEvent(event); + } + private: + QByteArray savedGeometry; + QByteArray savedState; + + QDockWidget *customers; + QDockWidget *paragraphs; + + } mainWindow; + + mainWindow.restore(); + mainWindow.show(); + QVERIFY(QTest::qWaitForWindowExposed(&mainWindow)); + mainWindow.close(); + + mainWindow.restore(); + mainWindow.show(); + QVERIFY(QTest::qWaitForWindowExposed(&mainWindow)); +} + void tst_QMainWindow::createPopupMenu() { {