summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAxel Spoerl <axel.spoerl@qt.io>2022-12-13 10:43:26 +0100
committerAxel Spoerl <axel.spoerl@qt.io>2022-12-16 22:58:11 +0100
commit5edb71c6d4cb0051d27d023ddcd180c5f59f2725 (patch)
tree0da5eda94d2e30808e3945e067237b6351b4cde5
parent4ccb10dc918c3d4d01afa1ad718a24eb5fa49a3c (diff)
Fix QWidget::restoreGeometry when restored geometry is off screen
If a widget's geometry is restored to a screen, which is smaller than the one it was saved from, - the widget could appear (partly) off screen - the widget's title bar and resize handles could be inaccessible This patch refactors and documents checkRestoredGeometry. In a first step, the restored geometry's size is checked against a given screen size. It is corrected if necessary. In a second step, the restored geometry is moved inside the screen, if necessary. It makes the function a static member of QWidgetPrivate in order to expose it for auto testing and adds a respective test function to tst_QWidget. Fixes: QTBUG-77385 Fixes: QTBUG-4397 Task-number: QTBUG-69104 Pick-to: 6.5 6.4 Change-Id: I7172e27bfef86d82cd51de70b40de42e8895bae6 Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
-rw-r--r--src/widgets/kernel/qwidget.cpp74
-rw-r--r--src/widgets/kernel/qwidget_p.h3
-rw-r--r--tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp73
3 files changed, 140 insertions, 10 deletions
diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp
index 0b3333a718..3ad88957ba 100644
--- a/src/widgets/kernel/qwidget.cpp
+++ b/src/widgets/kernel/qwidget.cpp
@@ -7382,15 +7382,65 @@ QByteArray QWidget::saveGeometry() const
return array;
}
-static void checkRestoredGeometry(const QRect &availableGeometry, QRect *restoredGeometry,
+/*!
+ \internal Check a if \a restoredGeometry fits into \a availableGeometry
+ This method is used to verify that a widget is restored to a geometry, which
+ fits into the target screen.
+
+ \param frameHeight represents the height of the widget's title bar, which is expected
+ to be on its top.
+
+ If the size of \a restoredGeometry exceeds \a availableGeometry, its height and width
+ will be resized to be two pixels smaller than \a availableGeometry. An exact match would
+ be full screen.
+
+ If at least one edge of \a restoredGeometry is outside \a availableGeometry,
+ \a restoredGeometry will be moved
+ \list
+ \li down if its top is off screen
+ \li up if its bottom is off screen
+ \li right if its left edge is off screen
+ \li left if its right edge is off screen
+ \endlist
+ */
+void QWidgetPrivate::checkRestoredGeometry(const QRect &availableGeometry, QRect *restoredGeometry,
int frameHeight)
{
- if (!restoredGeometry->intersects(availableGeometry)) {
- restoredGeometry->moveBottom(qMin(restoredGeometry->bottom(), availableGeometry.bottom()));
- restoredGeometry->moveLeft(qMax(restoredGeometry->left(), availableGeometry.left()));
- restoredGeometry->moveRight(qMin(restoredGeometry->right(), availableGeometry.right()));
+ // compare with restored geometry's height increased by frameHeight
+ const int height = restoredGeometry->height() + frameHeight;
+
+ // Step 1: Resize if necessary:
+ // make height / width 2px smaller than screen, because an exact match would be fullscreen
+ if (availableGeometry.height() <= height)
+ restoredGeometry->setHeight(availableGeometry.height() - 2 - frameHeight);
+ if (availableGeometry.width() <= restoredGeometry->width())
+ restoredGeometry->setWidth(availableGeometry.width() - 2);
+
+ // Step 2: Move if necessary:
+ // Construct a rectangle from restored Geometry adjusted by frameHeight
+ const QRect restored = restoredGeometry->adjusted(0, -frameHeight, 0, 0);
+
+ // Return if restoredGeometry (including frame) fits into screen
+ if (availableGeometry.contains(restored))
+ return;
+
+ // (size is correct, but at least one edge is off screen)
+
+ // Top out of bounds => move down
+ if (restored.top() <= availableGeometry.top()) {
+ restoredGeometry->moveTop(availableGeometry.top() + 1 + frameHeight);
+ } else if (restored.bottom() >= availableGeometry.bottom()) {
+ // Bottom out of bounds => move up
+ restoredGeometry->moveBottom(availableGeometry.bottom() - 1);
+ }
+
+ // Left edge out of bounds => move right
+ if (restored.left() <= availableGeometry.left()) {
+ restoredGeometry->moveLeft(availableGeometry.left() + 1);
+ } else if (restored.right() >= availableGeometry.right()) {
+ // Right edge out of bounds => move left
+ restoredGeometry->moveRight(availableGeometry.right() - 1);
}
- restoredGeometry->moveTop(qMax(restoredGeometry->top(), availableGeometry.top() + frameHeight));
}
/*!
@@ -7477,7 +7527,9 @@ bool QWidget::restoreGeometry(const QByteArray &geometry)
return false;
}
- const int frameHeight = 20;
+ const int frameHeight = QApplication::style()
+ ? QApplication::style()->pixelMetric(QStyle::PM_TitleBarHeight)
+ : 20;
if (!restoredNormalGeometry.isValid())
restoredNormalGeometry = QRect(QPoint(0, frameHeight), sizeHint());
@@ -7493,11 +7545,11 @@ bool QWidget::restoreGeometry(const QByteArray &geometry)
// Modify the restored geometry if we are about to restore to coordinates
// that would make the window "lost". This happens if:
- // - The restored geometry is completely oustside the available geometry
+ // - The restored geometry is completely or partly oustside the available geometry
// - The title bar is outside the available geometry.
- checkRestoredGeometry(availableGeometry, &restoredGeometry, frameHeight);
- checkRestoredGeometry(availableGeometry, &restoredNormalGeometry, frameHeight);
+ QWidgetPrivate::checkRestoredGeometry(availableGeometry, &restoredGeometry, frameHeight);
+ QWidgetPrivate::checkRestoredGeometry(availableGeometry, &restoredNormalGeometry, frameHeight);
if (maximized || fullScreen) {
// set geometry before setting the window state to make
@@ -7529,6 +7581,8 @@ bool QWidget::restoreGeometry(const QByteArray &geometry)
d_func()->topData()->normalGeometry = restoredNormalGeometry;
} else {
setWindowState(windowState() & ~(Qt::WindowMaximized | Qt::WindowFullScreen));
+
+ // FIXME: Why fall back to restoredNormalGeometry if majorVersion <= 2?
if (majorVersion > 2)
setGeometry(restoredGeometry);
else
diff --git a/src/widgets/kernel/qwidget_p.h b/src/widgets/kernel/qwidget_p.h
index 25a9c75701..d9d9825b6f 100644
--- a/src/widgets/kernel/qwidget_p.h
+++ b/src/widgets/kernel/qwidget_p.h
@@ -210,6 +210,9 @@ public:
static QWidgetPrivate *get(QWidget *w) { return w->d_func(); }
static const QWidgetPrivate *get(const QWidget *w) { return w->d_func(); }
+ static void checkRestoredGeometry(const QRect &availableGeometry, QRect *restoredGeometry,
+ int frameHeight);
+
QWExtra *extraData() const;
QTLWExtra *topData() const;
QTLWExtra *maybeTopData() const;
diff --git a/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp b/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp
index 0b495b2379..7e7cc8da7c 100644
--- a/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp
+++ b/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp
@@ -195,6 +195,8 @@ private slots:
void saveRestoreGeometry();
void restoreVersion1Geometry_data();
void restoreVersion1Geometry();
+ void restoreGeometryAfterScreenChange_data();
+ void restoreGeometryAfterScreenChange();
void widgetAt();
#ifdef Q_OS_MACOS
@@ -434,6 +436,15 @@ private:
QPointingDevice *m_touchScreen;
const int m_fuzz;
QPalette simplePalette();
+
+private:
+ enum class ScreenPosition {
+ OffAbove,
+ OffLeft,
+ OffBelow,
+ OffRight,
+ Contained
+ };
};
// Testing get/set functions
@@ -4245,6 +4256,68 @@ void tst_QWidget::restoreVersion1Geometry()
#endif
}
+void tst_QWidget::restoreGeometryAfterScreenChange_data()
+{
+ QTest::addColumn<ScreenPosition>("screenPosition");
+ QTest::addColumn<int>("deltaWidth");
+ QTest::addColumn<int>("deltaHeight");
+ QTest::addColumn<int>("frameMargin");
+ QTest::addColumn<bool>("outside");
+
+ QTest::newRow("offAboveLarge") << ScreenPosition::OffAbove << 200 << 250 << 20 << true;
+ QTest::newRow("fitting") << ScreenPosition::Contained << 80 << 80 << 20 << false;
+ QTest::newRow("offRightWide") << ScreenPosition::OffRight << 150 << 80 << 20 << false;
+ QTest::newRow("offLeftFitting") << ScreenPosition::OffLeft << 70 << 70 << 20 << true;
+ QTest::newRow("offBelowHigh") << ScreenPosition::OffBelow << 80 << 200 << 20 << false;
+}
+
+void tst_QWidget::restoreGeometryAfterScreenChange()
+{
+ const QList<QScreen *> &screens = QApplication::screens();
+ QVERIFY2(!screens.isEmpty(), "No screens found.");
+ const QRect screenGeometry = screens.at(0)->geometry();
+
+ QFETCH(ScreenPosition, screenPosition);
+ QFETCH(int, deltaWidth);
+ QFETCH(int, deltaHeight);
+ QFETCH(int, frameMargin);
+ QFETCH(bool, outside);
+
+ QRect restoredGeometry = screenGeometry;
+ restoredGeometry.setHeight(screenGeometry.height() * deltaHeight / 100);
+ restoredGeometry.setWidth(screenGeometry.width() * deltaWidth / 100);
+ const float moveMargin = outside ? 1.2 : 0.75;
+
+ switch (screenPosition) {
+ case ScreenPosition::OffLeft:
+ restoredGeometry.setLeft(restoredGeometry.width() * (-moveMargin));
+ break;
+ case ScreenPosition::OffAbove:
+ restoredGeometry.setTop(restoredGeometry.height() * (-moveMargin));
+ break;
+ case ScreenPosition::OffRight:
+ restoredGeometry.setRight(restoredGeometry.width() * moveMargin);
+ break;
+ case ScreenPosition::OffBelow:
+ restoredGeometry.setBottom(restoredGeometry.height() * moveMargin);
+ break;
+ case ScreenPosition::Contained:
+ break;
+ }
+
+ // If restored geometry fits into screen and has not been moved,
+ // it is changed only by frame margin plus one pixel at each edge
+ const QRect originalGeometry = restoredGeometry.adjusted(1, frameMargin + 1, 1, frameMargin + 1);
+
+ QWidgetPrivate::checkRestoredGeometry(screenGeometry, &restoredGeometry, frameMargin);
+
+ if (deltaHeight < 100 && deltaWidth < 100 && screenPosition == ScreenPosition::Contained)
+ QCOMPARE(originalGeometry, restoredGeometry);
+
+ // new geometry has to fit on the screen
+ QVERIFY(screenGeometry.contains(restoredGeometry));
+}
+
void tst_QWidget::widgetAt()
{
#ifdef Q_OS_MACOS