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;
|
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)
|
int frameHeight)
|
||||||
{
|
{
|
||||||
if (!restoredGeometry->intersects(availableGeometry)) {
|
// compare with restored geometry's height increased by frameHeight
|
||||||
restoredGeometry->moveBottom(qMin(restoredGeometry->bottom(), availableGeometry.bottom()));
|
const int height = restoredGeometry->height() + frameHeight;
|
||||||
restoredGeometry->moveLeft(qMax(restoredGeometry->left(), availableGeometry.left()));
|
|
||||||
restoredGeometry->moveRight(qMin(restoredGeometry->right(), availableGeometry.right()));
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int frameHeight = 20;
|
const int frameHeight = QApplication::style()
|
||||||
|
? QApplication::style()->pixelMetric(QStyle::PM_TitleBarHeight)
|
||||||
|
: 20;
|
||||||
|
|
||||||
if (!restoredNormalGeometry.isValid())
|
if (!restoredNormalGeometry.isValid())
|
||||||
restoredNormalGeometry = QRect(QPoint(0, frameHeight), sizeHint());
|
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
|
// Modify the restored geometry if we are about to restore to coordinates
|
||||||
// that would make the window "lost". This happens if:
|
// 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.
|
// - The title bar is outside the available geometry.
|
||||||
|
|
||||||
checkRestoredGeometry(availableGeometry, &restoredGeometry, frameHeight);
|
QWidgetPrivate::checkRestoredGeometry(availableGeometry, &restoredGeometry, frameHeight);
|
||||||
checkRestoredGeometry(availableGeometry, &restoredNormalGeometry, frameHeight);
|
QWidgetPrivate::checkRestoredGeometry(availableGeometry, &restoredNormalGeometry, frameHeight);
|
||||||
|
|
||||||
if (maximized || fullScreen) {
|
if (maximized || fullScreen) {
|
||||||
// set geometry before setting the window state to make
|
// set geometry before setting the window state to make
|
||||||
@ -7529,6 +7581,8 @@ bool QWidget::restoreGeometry(const QByteArray &geometry)
|
|||||||
d_func()->topData()->normalGeometry = restoredNormalGeometry;
|
d_func()->topData()->normalGeometry = restoredNormalGeometry;
|
||||||
} else {
|
} else {
|
||||||
setWindowState(windowState() & ~(Qt::WindowMaximized | Qt::WindowFullScreen));
|
setWindowState(windowState() & ~(Qt::WindowMaximized | Qt::WindowFullScreen));
|
||||||
|
|
||||||
|
// FIXME: Why fall back to restoredNormalGeometry if majorVersion <= 2?
|
||||||
if (majorVersion > 2)
|
if (majorVersion > 2)
|
||||||
setGeometry(restoredGeometry);
|
setGeometry(restoredGeometry);
|
||||||
else
|
else
|
||||||
|
@ -210,6 +210,9 @@ public:
|
|||||||
static QWidgetPrivate *get(QWidget *w) { return w->d_func(); }
|
static QWidgetPrivate *get(QWidget *w) { return w->d_func(); }
|
||||||
static const QWidgetPrivate *get(const 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;
|
QWExtra *extraData() const;
|
||||||
QTLWExtra *topData() const;
|
QTLWExtra *topData() const;
|
||||||
QTLWExtra *maybeTopData() const;
|
QTLWExtra *maybeTopData() const;
|
||||||
|
@ -195,6 +195,8 @@ private slots:
|
|||||||
void saveRestoreGeometry();
|
void saveRestoreGeometry();
|
||||||
void restoreVersion1Geometry_data();
|
void restoreVersion1Geometry_data();
|
||||||
void restoreVersion1Geometry();
|
void restoreVersion1Geometry();
|
||||||
|
void restoreGeometryAfterScreenChange_data();
|
||||||
|
void restoreGeometryAfterScreenChange();
|
||||||
|
|
||||||
void widgetAt();
|
void widgetAt();
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
@ -434,6 +436,15 @@ private:
|
|||||||
QPointingDevice *m_touchScreen;
|
QPointingDevice *m_touchScreen;
|
||||||
const int m_fuzz;
|
const int m_fuzz;
|
||||||
QPalette simplePalette();
|
QPalette simplePalette();
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class ScreenPosition {
|
||||||
|
OffAbove,
|
||||||
|
OffLeft,
|
||||||
|
OffBelow,
|
||||||
|
OffRight,
|
||||||
|
Contained
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Testing get/set functions
|
// Testing get/set functions
|
||||||
@ -4245,6 +4256,68 @@ void tst_QWidget::restoreVersion1Geometry()
|
|||||||
#endif
|
#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()
|
void tst_QWidget::widgetAt()
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
|
Loading…
x
Reference in New Issue
Block a user