diff options
-rw-r--r-- | src/widgets/widgets/qdockarealayout.cpp | 72 | ||||
-rw-r--r-- | src/widgets/widgets/qdockarealayout_p.h | 3 | ||||
-rw-r--r-- | src/widgets/widgets/qmainwindowlayout.cpp | 73 | ||||
-rw-r--r-- | src/widgets/widgets/qmainwindowlayout_p.h | 6 | ||||
-rw-r--r-- | tests/auto/widgets/widgets/qmainwindow/tst_qmainwindow.cpp | 65 |
5 files changed, 169 insertions, 50 deletions
diff --git a/src/widgets/widgets/qdockarealayout.cpp b/src/widgets/widgets/qdockarealayout.cpp index 5a144224db..626710cd54 100644 --- a/src/widgets/widgets/qdockarealayout.cpp +++ b/src/widgets/widgets/qdockarealayout.cpp @@ -2914,7 +2914,8 @@ void QDockAreaLayout::clear() centralWidgetRect = QRect(); } -QSize QDockAreaLayout::sizeHint() const +template<typename SizePMF, typename CenterPMF> +QSize QDockAreaLayout::size_helper(SizePMF sizeFn, CenterPMF centerFn) const { int left_sep = 0; int right_sep = 0; @@ -2928,11 +2929,12 @@ QSize QDockAreaLayout::sizeHint() const bottom_sep = docks[QInternal::BottomDock].isEmpty() ? 0 : sep; } - QSize left = docks[QInternal::LeftDock].sizeHint() + QSize(left_sep, 0); - QSize right = docks[QInternal::RightDock].sizeHint() + QSize(right_sep, 0); - QSize top = docks[QInternal::TopDock].sizeHint() + QSize(0, top_sep); - QSize bottom = docks[QInternal::BottomDock].sizeHint() + QSize(0, bottom_sep); - QSize center = centralWidgetItem == nullptr ? QSize(0, 0) : centralWidgetItem->sizeHint(); + const QSize left = (docks[QInternal::LeftDock].*sizeFn)() + QSize(left_sep, 0); + const QSize right = (docks[QInternal::RightDock].*sizeFn)() + QSize(right_sep, 0); + const QSize top = (docks[QInternal::TopDock].*sizeFn)() + QSize(0, top_sep); + const QSize bottom = (docks[QInternal::BottomDock].*sizeFn)() + QSize(0, bottom_sep); + const QSize center = centralWidgetItem == nullptr + ? QSize(0, 0) : (centralWidgetItem->*centerFn)(); int row1 = top.width(); int row2 = left.width() + center.width() + right.width(); @@ -2964,54 +2966,24 @@ QSize QDockAreaLayout::sizeHint() const return QSize(qMax(row1, row2, row3), qMax(col1, col2, col3)); } -QSize QDockAreaLayout::minimumSize() const +QSize QDockAreaLayout::sizeHint() const { - int left_sep = 0; - int right_sep = 0; - int top_sep = 0; - int bottom_sep = 0; - - if (centralWidgetItem != nullptr) { - left_sep = docks[QInternal::LeftDock].isEmpty() ? 0 : sep; - right_sep = docks[QInternal::RightDock].isEmpty() ? 0 : sep; - top_sep = docks[QInternal::TopDock].isEmpty() ? 0 : sep; - bottom_sep = docks[QInternal::BottomDock].isEmpty() ? 0 : sep; - } - - QSize left = docks[QInternal::LeftDock].minimumSize() + QSize(left_sep, 0); - QSize right = docks[QInternal::RightDock].minimumSize() + QSize(right_sep, 0); - QSize top = docks[QInternal::TopDock].minimumSize() + QSize(0, top_sep); - QSize bottom = docks[QInternal::BottomDock].minimumSize() + QSize(0, bottom_sep); - QSize center = centralWidgetItem == nullptr ? QSize(0, 0) : centralWidgetItem->minimumSize(); - - int row1 = top.width(); - int row2 = left.width() + center.width() + right.width(); - int row3 = bottom.width(); - int col1 = left.height(); - int col2 = top.height() + center.height() + bottom.height(); - int col3 = right.height(); - - if (corners[Qt::TopLeftCorner] == Qt::LeftDockWidgetArea) - row1 += left.width(); - else - col1 += top.height(); - - if (corners[Qt::TopRightCorner] == Qt::RightDockWidgetArea) - row1 += right.width(); - else - col3 += top.height(); + return size_helper(&QDockAreaLayoutInfo::sizeHint, &QLayoutItem::sizeHint); +} - if (corners[Qt::BottomLeftCorner] == Qt::LeftDockWidgetArea) - row3 += left.width(); - else - col1 += bottom.height(); +QSize QDockAreaLayout::minimumSize() const +{ + return size_helper(&QDockAreaLayoutInfo::minimumSize, &QLayoutItem::minimumSize); +} - if (corners[Qt::BottomRightCorner] == Qt::RightDockWidgetArea) - row3 += right.width(); - else - col3 += bottom.height(); +/*! + \internal - return QSize(qMax(row1, row2, row3), qMax(col1, col2, col3)); + Returns the smallest size that doesn't change the size of any of the dock areas. +*/ +QSize QDockAreaLayout::minimumStableSize() const +{ + return size_helper(&QDockAreaLayoutInfo::size, &QLayoutItem::minimumSize); } /*! \internal diff --git a/src/widgets/widgets/qdockarealayout_p.h b/src/widgets/widgets/qdockarealayout_p.h index 171147c254..57572bcfa1 100644 --- a/src/widgets/widgets/qdockarealayout_p.h +++ b/src/widgets/widgets/qdockarealayout_p.h @@ -271,6 +271,9 @@ public: QSize sizeHint() const; QSize minimumSize() const; + QSize minimumStableSize() const; + template<typename SizePMF, typename CenterPMF> + QSize size_helper(SizePMF sizeFn, CenterPMF centerFn) const; void addDockWidget(QInternal::DockPosition pos, QDockWidget *dockWidget, Qt::Orientation orientation); bool restoreDockWidget(QDockWidget *dockWidget); diff --git a/src/widgets/widgets/qmainwindowlayout.cpp b/src/widgets/widgets/qmainwindowlayout.cpp index 18dd05e63b..60db982cb7 100644 --- a/src/widgets/widgets/qmainwindowlayout.cpp +++ b/src/widgets/widgets/qmainwindowlayout.cpp @@ -675,6 +675,31 @@ QSize QMainWindowLayoutState::minimumSize() const return result; } +/*! + \internal + + Returns whether the layout fits into the main window. +*/ +bool QMainWindowLayoutState::fits() const +{ + Q_ASSERT(mainWindow); + + QSize size; + +#if QT_CONFIG(dockwidget) + size = dockAreaLayout.minimumStableSize(); +#endif + +#if QT_CONFIG(toolbar) + size.rwidth() += toolBarAreaLayout.docks[QInternal::LeftDock].rect.width(); + size.rwidth() += toolBarAreaLayout.docks[QInternal::RightDock].rect.width(); + size.rheight() += toolBarAreaLayout.docks[QInternal::TopDock].rect.height(); + size.rheight() += toolBarAreaLayout.docks[QInternal::BottomDock].rect.height(); +#endif + + return size.width() <= mainWindow->width() && size.height() <= mainWindow->height(); +} + void QMainWindowLayoutState::apply(bool animated) { #if QT_CONFIG(toolbar) @@ -1974,11 +1999,47 @@ void QMainWindowLayout::setGeometry(const QRect &_r) r.setBottom(sbr.top() - 1); } + if (restoredState) { + /* + The main window was hidden and was going to be maximized or full-screened when + the state was restored. The state might have been for a larger window size than + the current size (in _r), and the window might still be in the process of being + shown and transitioning to the final size (there's no reliable way of knowing + this across different platforms). Try again with the restored state. + */ + layoutState = *restoredState; + if (restoredState->fits()) { + restoredState.reset(); + discardRestoredStateTimer.stop(); + } else { + /* + Try again in the next setGeometry call, but discard the restored state + after 150ms without any further tries. That's a reasonably short amount of + time during which we can expect the windowing system to either have completed + showing the window, or resized the window once more (which then restarts the + timer in timerEvent). + If the windowing system is done, then the user won't have had a chance to + change the layout interactively AND trigger another resize. + */ + discardRestoredStateTimer.start(150, this); + } + } + layoutState.rect = r; + layoutState.fitLayout(); applyState(layoutState, false); } +void QMainWindowLayout::timerEvent(QTimerEvent *e) +{ + if (e->timerId() == discardRestoredStateTimer.timerId()) { + discardRestoredStateTimer.stop(); + restoredState.reset(); + } + QLayout::timerEvent(e); +} + void QMainWindowLayout::addItem(QLayoutItem *) { qWarning("QMainWindowLayout::addItem: Please use the public QMainWindow API instead"); } @@ -2781,6 +2842,18 @@ bool QMainWindowLayout::restoreState(QDataStream &stream) if (parentWidget()->isVisible()) { layoutState.fitLayout(); applyState(layoutState, false); + } else { + /* + The state might not fit into the size of the widget as it gets shown, but + if the window is expected to be maximized or full screened, then we might + get several resizes as part of that transition, at the end of which the + state might fit. So keep the restored state around for now and try again + later in setGeometry. + */ + if ((parentWidget()->windowState() & (Qt::WindowFullScreen | Qt::WindowMaximized)) + && !layoutState.fits()) { + restoredState.reset(new QMainWindowLayoutState(layoutState)); + } } savedState.deleteAllLayoutItems(); diff --git a/src/widgets/widgets/qmainwindowlayout_p.h b/src/widgets/widgets/qmainwindowlayout_p.h index 20518bc2c9..55385cc164 100644 --- a/src/widgets/widgets/qmainwindowlayout_p.h +++ b/src/widgets/widgets/qmainwindowlayout_p.h @@ -414,6 +414,7 @@ public: QSize sizeHint() const; QSize minimumSize() const; + bool fits() const; void fitLayout(); QLayoutItem *itemAt(int index, int *x) const; @@ -451,6 +452,7 @@ class Q_AUTOTEST_EXPORT QMainWindowLayout public: QMainWindowLayoutState layoutState, savedState; + std::unique_ptr<QMainWindowLayoutState> restoredState; QMainWindowLayout(QMainWindow *mainwindow, QLayout *parentLayout); ~QMainWindowLayout(); @@ -546,6 +548,7 @@ public: }; void saveState(QDataStream &stream) const; bool restoreState(QDataStream &stream); + QBasicTimer discardRestoredStateTimer; // QLayout interface @@ -584,6 +587,9 @@ public: void restore(bool keepSavedState = false); void animationFinished(QWidget *widget); +protected: + void timerEvent(QTimerEvent *e) override; + private Q_SLOTS: void updateGapIndicator(); #if QT_CONFIG(dockwidget) diff --git a/tests/auto/widgets/widgets/qmainwindow/tst_qmainwindow.cpp b/tests/auto/widgets/widgets/qmainwindow/tst_qmainwindow.cpp index b31acb2b7a..e30df32d5d 100644 --- a/tests/auto/widgets/widgets/qmainwindow/tst_qmainwindow.cpp +++ b/tests/auto/widgets/widgets/qmainwindow/tst_qmainwindow.cpp @@ -126,6 +126,8 @@ private slots: void dockWidgetArea(); void restoreState(); void restoreStateFromPreviousVersion(); + void restoreStateSizeChanged_data(); + void restoreStateSizeChanged(); void createPopupMenu(); void hideBeforeLayout(); #ifdef QT_BUILD_INTERNAL @@ -1391,6 +1393,67 @@ void tst_QMainWindow::restoreStateFromPreviousVersion() } +void tst_QMainWindow::restoreStateSizeChanged_data() +{ + QTest::addColumn<Qt::WindowState>("saveState"); + QTest::addColumn<Qt::WindowState>("showState"); + QTest::addColumn<bool>("sameSize"); + + QTest::addRow("fullscreen") << Qt::WindowFullScreen << Qt::WindowFullScreen << true; + QTest::addRow("maximized") << Qt::WindowMaximized << Qt::WindowMaximized << true; + QTest::addRow("maximized->normal") << Qt::WindowMaximized << Qt::WindowNoState << false; + QTest::addRow("fullscreen->normal") << Qt::WindowFullScreen << Qt::WindowNoState << false; + QTest::addRow("fullscreen->maximized") << Qt::WindowFullScreen << Qt::WindowMaximized << false; + QTest::addRow("maximized->fullscreen") << Qt::WindowMaximized << Qt::WindowFullScreen << true; +} + +void tst_QMainWindow::restoreStateSizeChanged() +{ + QFETCH(Qt::WindowState, saveState); + QFETCH(Qt::WindowState, showState); + QFETCH(bool, sameSize); + + auto createMainWindow = []{ + QMainWindow *mainWindow = new QMainWindow; + mainWindow->move(QGuiApplication::primaryScreen()->availableGeometry().topLeft()); + mainWindow->setCentralWidget(new QLabel("X")); + QDockWidget *dockWidget = new QDockWidget; + dockWidget->setObjectName("Dock Widget"); + mainWindow->addDockWidget(Qt::LeftDockWidgetArea, dockWidget); + return mainWindow; + }; + + QByteArray geometryData; + QByteArray stateData; + int dockWidgetWidth = 0; + QRect normalGeometry; + + { + auto mainWindow = QScopedPointer<QMainWindow>(createMainWindow()); + mainWindow->setWindowState(saveState); + mainWindow->show(); + QVERIFY(QTest::qWaitForWindowExposed(mainWindow.data())); + dockWidgetWidth = mainWindow->width() - 100; + QDockWidget *dockWidget = mainWindow->findChild<QDockWidget*>("Dock Widget"); + mainWindow->resizeDocks({dockWidget}, {dockWidgetWidth}, Qt::Horizontal); + geometryData = mainWindow->saveGeometry(); + stateData = mainWindow->saveState(); + normalGeometry = mainWindow->normalGeometry(); + } + + auto mainWindow = QScopedPointer<QMainWindow>(createMainWindow()); + mainWindow->restoreGeometry(geometryData); + mainWindow->restoreState(stateData); + mainWindow->setWindowState(showState); + mainWindow->show(); + QVERIFY(QTest::qWaitForWindowExposed(mainWindow.data())); + + QDockWidget *dockWidget = mainWindow->findChild<QDockWidget*>("Dock Widget"); + QVERIFY(dockWidget); + QCOMPARE(mainWindow->normalGeometry().size(), normalGeometry.size()); + if (sameSize) + QTRY_COMPARE(dockWidget->width(), dockWidgetWidth); +} void tst_QMainWindow::createPopupMenu() { @@ -1692,6 +1755,7 @@ void tst_QMainWindow::saveRestore() adw.apply(&mainWindow); mainWindow.show(); + mainWindow.restoreState(stateData); COMPARE_DOCK_WIDGET_GEOS(dockWidgetGeos, dockWidgetGeometries(&mainWindow)); @@ -1710,6 +1774,7 @@ void tst_QMainWindow::saveRestore() mainWindow.restoreState(stateData); mainWindow.show(); + QVERIFY(QTest::qWaitForWindowExposed(&mainWindow)); COMPARE_DOCK_WIDGET_GEOS(dockWidgetGeos, dockWidgetGeometries(&mainWindow)); } } |