summaryrefslogtreecommitdiffstats
path: root/src/widgets
diff options
context:
space:
mode:
authorVolker Hilsheimer <volker.hilsheimer@qt.io>2021-10-04 21:39:31 +0200
committerVolker Hilsheimer <volker.hilsheimer@qt.io>2021-10-16 09:46:22 +0200
commit32edae5e268b968aff82f0713612eff2feffb4e1 (patch)
treefe97e538232807fdd5e87a0312f7cf94ffa81087 /src/widgets
parent553a1c48fd7a9078c1a33ad0829eaa84f0afb670 (diff)
Fix restoring main window state for maximized/fullscreen windows
On systems that asynchronously resize the window to maximized or full screen state, the window will become visible in its normal geometry before it gets the final size by the windowing system. This might cause multiple resize events, to each of which the widget's layout responds with a call to its setGeometry implementation. The QMainWindowLayout is special in that it will shrink dock widgets if there is not enough space for them, but it doesn't grow them back once there is. With the initial resize event being for a smaller size than what was restored, the state is not restored correctly, but remains in the state that fit into the smallest size with which setGeometry got called. To fix this, we have to keep the restored state around until the window either gets a size that is large enough for it to fit, or until we can be reasonably certain that the windowing system is done resizing the window while transitioning it to the maximized or full screen state. Since across the various platforms and windowing systems there is no reliable way to know when the window reaches its final size, we have to use a timer that we (re)start for each call to setGeometry with a size that's not large enough. Once the timer times out, we have to give up; then the last layout state calculated is the final state. To calculate the size of the layout, introduce a function to the QDockAreaLayout that returns the size required for the current sizes of the docks. Refactor sizeHint and minimumSize (which were identical) into a helper template that takes member-function pointers to call the respective method from the dock area layout's content items. Add a test case for various permutations of the scenario. The timeout of 150ms is based on running this test case repeatedly on various desktop platforms and X11 window managers. Fixes: QTBUG-46620 Change-Id: I489675c2c40d3308ac8194aeb4267172b2fb38be Reviewed-by: Albert Astals Cid <albert.astals.cid@kdab.com> Reviewed-by: Lars Knoll <lars.knoll@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Diffstat (limited to 'src/widgets')
-rw-r--r--src/widgets/widgets/qdockarealayout.cpp72
-rw-r--r--src/widgets/widgets/qdockarealayout_p.h3
-rw-r--r--src/widgets/widgets/qmainwindowlayout.cpp73
-rw-r--r--src/widgets/widgets/qmainwindowlayout_p.h6
4 files changed, 104 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)