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 Change-Id: I7172e27bfef86d82cd51de70b40de42e8895bae6 Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io> (cherry picked from commit 5edb71c6d4cb0051d27d023ddcd180c5f59f2725) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
parent
69c75ebbd8
commit
fe4955c2f9
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user