summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorten Johan Sørvig <morten.sorvig@qt.io>2020-02-04 17:47:23 +0100
committerMorten Johan Sørvig <morten.sorvig@qt.io>2020-10-20 07:26:57 +0200
commitd023da86399b99d365f1e745fb0d9424afc8194b (patch)
tree95e7da87a357997ccc82631003cc39b5a6d20a18
parentf4a66e5c4c4be97a7511b372930e20f330ea0813 (diff)
Add high-dpi auto test
Use the new screen config feature of the offscreen platform plugin to run tests on virtual screen setup. This has the benefit that we can auto-test the QHighDpiScaling implementation (as well as its usage in QtGui) on any platform with a fixed mock screen setup which does not rely on physical screen configuration. Test the following configurations: - three screens: 96 DPI. (reference) - three screens: 192 DPI - three screens: mixed (high) DPI Change-Id: I2fac889d896cf30ab2a79c306cee22177ad8f4ac Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
-rw-r--r--tests/auto/gui/kernel/CMakeLists.txt1
-rw-r--r--tests/auto/gui/kernel/kernel.pro1
-rw-r--r--tests/auto/gui/kernel/qhighdpi/CMakeLists.txt14
-rw-r--r--tests/auto/gui/kernel/qhighdpi/qhighdpi.pro6
-rw-r--r--tests/auto/gui/kernel/qhighdpi/tst_qhighdpi.cpp433
5 files changed, 455 insertions, 0 deletions
diff --git a/tests/auto/gui/kernel/CMakeLists.txt b/tests/auto/gui/kernel/CMakeLists.txt
index 690a679833..2753007a95 100644
--- a/tests/auto/gui/kernel/CMakeLists.txt
+++ b/tests/auto/gui/kernel/CMakeLists.txt
@@ -36,6 +36,7 @@ if(QT_FEATURE_shortcut)
add_subdirectory(qshortcut)
endif()
if(QT_FEATURE_highdpiscaling)
+ add_subdirectory(qhighdpi)
add_subdirectory(qhighdpiscaling)
endif()
if(TARGET Qt::Widgets)
diff --git a/tests/auto/gui/kernel/kernel.pro b/tests/auto/gui/kernel/kernel.pro
index c11c0ad26b..346c7fb3c4 100644
--- a/tests/auto/gui/kernel/kernel.pro
+++ b/tests/auto/gui/kernel/kernel.pro
@@ -13,6 +13,7 @@ SUBDIRS=\
qguimetatype \
qguitimer \
qguivariant \
+ qhighdpi\
qhighdpiscaling \
qinputdevice \
qinputmethod \
diff --git a/tests/auto/gui/kernel/qhighdpi/CMakeLists.txt b/tests/auto/gui/kernel/qhighdpi/CMakeLists.txt
new file mode 100644
index 0000000000..bdd9a5e17f
--- /dev/null
+++ b/tests/auto/gui/kernel/qhighdpi/CMakeLists.txt
@@ -0,0 +1,14 @@
+# Generated from qhighdpi.pro.
+
+#####################################################################
+## tst_qhighdpi Test:
+#####################################################################
+
+qt_internal_add_test(tst_qhighdpi
+ SOURCES
+ tst_qhighdpi.cpp
+ PUBLIC_LIBRARIES
+ Qt::CorePrivate
+ Qt::Gui
+ Qt::GuiPrivate
+)
diff --git a/tests/auto/gui/kernel/qhighdpi/qhighdpi.pro b/tests/auto/gui/kernel/qhighdpi/qhighdpi.pro
new file mode 100644
index 0000000000..41e73848f3
--- /dev/null
+++ b/tests/auto/gui/kernel/qhighdpi/qhighdpi.pro
@@ -0,0 +1,6 @@
+CONFIG += testcase
+TARGET = tst_qhighdpi
+
+QT += core-private gui-private testlib
+
+SOURCES += tst_qhighdpi.cpp
diff --git a/tests/auto/gui/kernel/qhighdpi/tst_qhighdpi.cpp b/tests/auto/gui/kernel/qhighdpi/tst_qhighdpi.cpp
new file mode 100644
index 0000000000..a7b18167ba
--- /dev/null
+++ b/tests/auto/gui/kernel/qhighdpi/tst_qhighdpi.cpp
@@ -0,0 +1,433 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <private/qhighdpiscaling_p.h>
+#include <qpa/qplatformscreen.h>
+
+#include <QtTest/QtTest>
+
+class tst_QHighDpi: public QObject
+{
+ Q_OBJECT
+private: // helpers
+ QJsonArray createStandardScreens(const QList<qreal> &dpiValues);
+ QGuiApplication *createOffscreenApplication(const QByteArray &jsonConfig);
+ QGuiApplication *createStandardOffscreenApp(const QList<qreal> &dpiValues);
+ static void standardScreenDpiTestData();
+private slots:
+ void initTestCase();
+ void qhighdpiscaling_data();
+ void qhighdpiscaling();
+ void screenDpiAndDpr_data();
+ void screenDpiAndDpr();
+ void screenAt_data();
+ void screenAt();
+ void screenGeometry_data();
+ void screenGeometry();
+ void windowGeometry_data();
+ void windowGeometry();
+ void spanningWindows_data();
+ void spanningWindows();
+ void mouseEvents_data();
+ void mouseEvents();
+};
+
+/// Offscreen platform plugin test setup
+const int standardScreenWidth = 640;
+const int standardScreenHeight = 480;
+const int standardBaseDpi = 96;
+const int standardScreenCount = 3;
+
+QJsonArray tst_QHighDpi::createStandardScreens(const QList<qreal> &dpiValues)
+{
+ Q_ASSERT(dpiValues.count() == standardScreenCount);
+
+ // Create row of three screens: screen#0 screen#1 screen#2
+ return QJsonArray {
+ QJsonObject {
+ {"name", "screen#0"},
+ {"x", -standardScreenWidth},
+ {"y", -10},
+ {"width", standardScreenWidth},
+ {"height", standardScreenHeight},
+ {"logicalDpi", dpiValues[0]},
+ {"logicalBaseDpi", standardBaseDpi},
+ {"dpr", 1}
+ },
+ QJsonObject {
+ {"name", "screen#1"},
+ {"x", 0},
+ {"y", 0},
+ {"width", standardScreenWidth},
+ {"height", standardScreenHeight},
+ {"logicalDpi", dpiValues[1]},
+ {"logicalBaseDpi", standardBaseDpi},
+ {"dpr", 1}
+ },
+ QJsonObject {
+ {"name", "screen#2"},
+ {"x", standardScreenWidth},
+ {"y", 10},
+ {"width", standardScreenWidth},
+ {"height", standardScreenHeight},
+ {"logicalDpi", dpiValues[2]},
+ {"logicalBaseDpi", standardBaseDpi},
+ {"dpr", 1}
+ }
+ };
+}
+
+QGuiApplication *tst_QHighDpi::createOffscreenApplication(const QByteArray &jsonConfig)
+{
+ // Write offscreen platform config file
+ QFile configFile(QLatin1String("qt-offscreen-test-config.json"));
+ if (!configFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
+ qFatal("Failed to open test config file: %s", qPrintable(configFile.errorString()));
+ configFile.resize(0); // truncate
+ if (configFile.write(jsonConfig) == -1)
+ qFatal("Could not write config file: %s", qPrintable(configFile.errorString()));
+ configFile.close();
+
+ // Create QGuiApplication which loads the offscreen platform plugin
+ // Note that argc and argv need to stay valid for the duration of the app lifetime,
+ // and may be used at any point. The config file used at app startup only.
+ static int argc;
+ argc = 3;
+ static char *argv[3];
+ static QByteArray binaryNameArg = QByteArray("tst_qguiapplication");
+ argv[0] = binaryNameArg.data();
+ static QByteArray platformArg = QByteArray("-platform");
+ argv[1] = platformArg.data();
+ static QByteArray offscreenAndFileArg;
+ offscreenAndFileArg = QByteArray("offscreen:configfile=") + configFile.fileName().toUtf8();
+ argv[2] = offscreenAndFileArg.data();
+
+ QGuiApplication *app = new QGuiApplication(argc, argv);
+ configFile.remove(); // config file is needed during QGuiApplication construction only.
+ return app;
+}
+
+QGuiApplication *tst_QHighDpi::createStandardOffscreenApp(const QList<qreal> &dpiValues)
+{
+ QJsonArray screens = createStandardScreens(dpiValues);
+ QJsonObject config {
+ {"synchronousWindowSystemEvents", true},
+ {"windowFrameMargins", false},
+ {"screens" , screens},
+ };
+ return createOffscreenApplication(QJsonDocument(config).toJson());
+}
+
+/// Auto test begins
+
+void tst_QHighDpi::standardScreenDpiTestData()
+{
+ // We run each test under three screen configurations (each with three screens):
+ QTest::addColumn<QList<qreal>>("dpiValues");
+ QTest::newRow("96") << QList<qreal> { 96, 96, 96 }; // standard-dpi sanity check
+ QTest::newRow("192") << QList<qreal> { 192, 192, 192 }; // 2x high dpi
+ QTest::newRow("144-168-192") << QList<qreal> { 144, 168, 192 }; // mixed dpi (1.5x, 1.75x, 2x)
+}
+
+void tst_QHighDpi::initTestCase()
+{
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ // Run tests on the (proposed) Qt 6 default configuration
+ QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+ QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
+#endif
+}
+
+void tst_QHighDpi::screenDpiAndDpr_data()
+{
+ standardScreenDpiTestData();
+}
+
+void tst_QHighDpi::screenDpiAndDpr()
+{
+ QFETCH(QList<qreal>, dpiValues);
+
+ std::unique_ptr<QGuiApplication> app(createStandardOffscreenApp(dpiValues));
+ int i = 0;
+ for (QScreen *screen : app->screens()) {
+ qreal dpi = dpiValues[i++];
+
+ // verify that the devicePixelRatio equation holds: DPR = DPI / BaseDPI
+ QCOMPARE(screen->devicePixelRatio(), dpi / standardBaseDpi);
+ QCOMPARE(screen->logicalDotsPerInch(), dpi / screen->devicePixelRatio());
+
+ QWindow window(screen);
+ QCOMPARE(window.devicePixelRatio(), screen->devicePixelRatio());
+ }
+}
+
+void tst_QHighDpi::screenAt_data()
+{
+ standardScreenDpiTestData();
+}
+
+void tst_QHighDpi::screenAt()
+{
+ QFETCH(QList<qreal>, dpiValues);
+ std::unique_ptr<QGuiApplication> app(createStandardOffscreenApp(dpiValues));
+
+ QCOMPARE(app->screens().count(), standardScreenCount); // standard setup
+
+ // Verify that screenAt() returns the correct or no screen for various points,
+ // for all screens.
+ int i = 0;
+ for (QScreen *screen : app->screens()) {
+ qreal dpi = dpiValues[i++];
+
+ // veryfy virtualSiblings and that AA_EnableHighDpiScaling is active
+ QCOMPARE(screen->virtualSiblings().count(), standardScreenCount);
+ QCOMPARE(screen->geometry().size(), QSize(standardScreenWidth, standardScreenHeight) * (96.0 / dpi));
+
+ // test points on screen
+ QCOMPARE(app->screenAt(screen->geometry().center()), screen);
+ QCOMPARE(app->screenAt(screen->geometry().topLeft()), screen);
+ QCOMPARE(app->screenAt(screen->geometry().bottomRight()), screen);
+
+ // test points off screen
+ QCOMPARE(app->screenAt(screen->geometry().center() + QPoint(0, -1000)), nullptr);
+ QCOMPARE(app->screenAt(screen->geometry().topLeft() + QPoint(0, -1)), nullptr);
+ QCOMPARE(app->screenAt(screen->geometry().bottomRight() + QPoint(0, +1)), nullptr);
+
+ // check the "gaps" created by Qt::AA_EnableHighDpiScaling: no screen there
+ if (dpi > 96) {
+ QCOMPARE(app->screenAt(screen->geometry().topLeft() + QPoint(-1, 0)), nullptr);
+ QCOMPARE(app->screenAt(screen->geometry().bottomRight() + QPoint(1, 0)), nullptr);
+ }
+ }
+}
+
+void tst_QHighDpi::screenGeometry_data()
+{
+ standardScreenDpiTestData();
+}
+
+void tst_QHighDpi::screenGeometry()
+{
+ QFETCH(QList<qreal>, dpiValues);
+ std::unique_ptr<QGuiApplication> app(createStandardOffscreenApp(dpiValues));
+
+ for (QScreen *screen : app->screens()) {
+ QRect geometry = screen->geometry();
+ QPoint onScreen = geometry.topLeft() + QPoint(10, 10);
+ QPoint onScreenNative = QHighDpi::toNativePixels(onScreen, screen);
+ QPoint onScreenBack = QHighDpi::fromNativePixels(onScreenNative, screen);
+
+ QCOMPARE(onScreen, onScreenBack);
+
+ QPoint offScreen = geometry.topLeft() - QPoint(10, 10);
+ QPoint offScreenNative = QHighDpi::toNativePixels(offScreen, screen);
+ QPoint offScreenBack = QHighDpi::fromNativePixels(offScreenNative, screen);
+ QCOMPARE(offScreenBack, offScreenBack);
+ }
+}
+
+void tst_QHighDpi::windowGeometry_data()
+{
+ standardScreenDpiTestData();
+}
+
+void tst_QHighDpi::windowGeometry()
+{
+ QFETCH(QList<qreal>, dpiValues);
+ std::unique_ptr<QGuiApplication> app(createStandardOffscreenApp(dpiValues));
+
+ auto testWindow = [&app](QWindow *window, QScreen *expectedScreen, QPoint expectedPosition, QSize expectedSize) {
+
+ // Is the window correctly sized and on the correct screen?
+ QCOMPARE(window->size(), expectedSize);
+ QCOMPARE(window->position(), expectedPosition);
+ QCOMPARE(window->screen(), expectedScreen);
+ QCOMPARE(app->screenAt(window->mapToGlobal(QPoint(0, 0))), expectedScreen);
+
+ // Round-trip coordinates local->global->local, which should return the starting
+ // coordinates, also for coordinates outside the window (and screen)
+ auto globalRoundtrip = [](QWindow *window, QPoint pos) {
+ QCOMPARE(window->mapFromGlobal(window->mapToGlobal(pos)), pos);
+ };
+ globalRoundtrip(window, QPoint(10, 10)); // window-interior
+ globalRoundtrip(window, QPoint(-5, -5)); // outside window, on same screen
+ globalRoundtrip(window, QPoint(standardScreenWidth *2, standardScreenHeight * 2)); // Outside window, outside all screens
+ globalRoundtrip(window, QPoint(0, -standardScreenWidth)); // Outside window, on neighbor screen
+
+ // Round-trip float coordinates
+ auto globalRoundtripF = [](QWindow *window, QPointF pos) {
+ QCOMPARE(window->mapFromGlobal(window->mapToGlobal(pos)), pos);
+ };
+
+ globalRoundtripF(window, QPointF(10, 10)); // window-interior
+ globalRoundtripF(window, QPointF(10.1, 10.1));
+ globalRoundtripF(window, QPointF(10.5, 10.5));
+ globalRoundtripF(window, QPointF(10.9, 10.9));
+ globalRoundtripF(window, QPointF(-5.5, -5.5)); // outside window, on same screen
+ globalRoundtripF(window, QPointF(standardScreenWidth * 2.1, standardScreenHeight * 2.1)); // Outside window, outside all screens
+ globalRoundtripF(window, QPointF(0.5, -standardScreenWidth)); // Outside window, on neighbor screen
+ };
+
+ // verify window geometry for top-level and child windows on all screens
+ for (QScreen *screen : app->screens()) {
+ QWindow topLevelWindow;
+ QSize topLevelSize(40, 40);
+ QPoint topLevelPosition(screen->geometry().center());
+ topLevelWindow.resize(topLevelSize);
+ topLevelWindow.setPosition(topLevelPosition);
+ topLevelWindow.show();
+ testWindow(&topLevelWindow, screen, topLevelPosition, topLevelSize);
+
+ QWindow childWindow(&topLevelWindow);
+ QSize childSize(20, 20);
+ QPoint childPosition(10, 10);
+ childWindow.resize(childSize);
+ childWindow.setPosition(childPosition);
+ childWindow.show();
+ testWindow(&childWindow, screen, childPosition, childSize);
+ }
+}
+
+void tst_QHighDpi::spanningWindows_data()
+{
+ standardScreenDpiTestData();
+}
+
+void tst_QHighDpi::spanningWindows()
+{
+ QFETCH(QList<qreal>, dpiValues);
+ std::unique_ptr<QGuiApplication> app(createStandardOffscreenApp(dpiValues));
+
+ QPoint screen0Center = app->screens()[0]->geometry().center();
+ int screenWidth = app->screens()[0]->geometry().width();
+
+ // Create window spanning screen 0 and screen 1
+ QWindow window;
+ QRect windowGeometry = QRect(screen0Center, QSize(screenWidth - 10, 20));
+ windowGeometry.adjust(0, 0, -10, 0); // Make sure the center point is on screen 0
+ window.setGeometry(windowGeometry);
+ window.show();
+ QCOMPARE(window.geometry(), windowGeometry);
+
+ // Device independent screen space may be non-contiguous, in which case global
+ // window geometry behaves non-intuitivly when a window spans multiple screens:
+ // - The main screen for the window is defined by the windowing system
+ // (usually the screen with most window coverage), and is reflected
+ // by QWindow::screen()
+ // - screen coordinate linear math does not work for points on the window
+ // extending beyond the main screen - these may be on a different screen
+ // with a non-linear coordinate offset.
+ //
+ // Local window geometry works (mostly) as before:
+ // - QWindow::mapToGlobal() can map any window-local coordinate to the correct
+ // global coordinate and screen, as long as the coordinate is on the window.
+ // - QWindow::mapFromGlobal() can map any global coordinate to the correct
+ // local coordinate, as long as the coordinate is on screen and on the window.
+ //
+ // Open issue:
+ // - Mapping coordinates which are outside of the window is iffy; we might
+ // fall back to using/assuming the coordinate system for the main screen
+ // in this case.
+ QPoint globalTopLeft = window.mapToGlobal(QPoint(0, 0));
+ QSize foo = window.geometry().size() - QSize(1, 1);
+ QPoint globalBottomRight = window.mapToGlobal(QPoint(foo.width(), foo.height()));
+
+ QCOMPARE(app->screenAt(globalTopLeft), app->screens()[0]);
+ QCOMPARE(app->screenAt(globalBottomRight), app->screens()[1]);
+}
+
+void tst_QHighDpi::mouseEvents_data()
+{
+ standardScreenDpiTestData();
+}
+
+void tst_QHighDpi::mouseEvents()
+{
+ QFETCH(QList<qreal>, dpiValues);
+ std::unique_ptr<QGuiApplication> app(createStandardOffscreenApp(dpiValues));
+
+ class MousePressTestWindow : public QWindow {
+ public:
+ QPoint m_mouseTestPoint;
+
+ MousePressTestWindow(QWindow *parent = nullptr)
+ :QWindow(parent)
+ {
+
+ }
+
+ void mousePressEvent(QMouseEvent *ev) override
+ {
+ QCOMPARE(ev->position(), m_mouseTestPoint);
+ if (devicePixelRatio() == 1 || devicePixelRatio() == 2) // ### off-by-one error on non-integer dpr
+ QCOMPARE(mapFromGlobal(ev->globalPosition()), m_mouseTestPoint);
+ }
+
+ };
+
+ // Verify mouse event coordinates for top-level and chlid windows on each screen
+ for (QScreen *screen : app->screens()) {
+ QPoint mouseTestPoint(10, 10);
+ MousePressTestWindow topLevelWindow;
+ topLevelWindow.m_mouseTestPoint = mouseTestPoint;
+ topLevelWindow.resize(QSize(40, 40));
+ topLevelWindow.setPosition(screen->geometry().center());
+ topLevelWindow.show();
+
+ QTest::mouseClick(&topLevelWindow, Qt::LeftButton, Qt::KeyboardModifiers(), mouseTestPoint);
+ MousePressTestWindow childWindow(&topLevelWindow);
+ childWindow.m_mouseTestPoint = mouseTestPoint;
+ childWindow.resize(QSize(20, 20));
+ childWindow.setPosition(QPoint(15, 15));
+ childWindow.show();
+ QTest::mouseClick(&childWindow, Qt::LeftButton, Qt::KeyboardModifiers(), mouseTestPoint);
+ }
+
+ // Verify mouse event coordinates for a window spanning screen 0 and screen 1
+ QPoint screen0Center = app->screens()[0]->geometry().center();
+ int screenWidth = app->screens()[0]->geometry().width();
+ QSize windowSize = QSize(screenWidth - 10, 20);
+ QRect windowGeometry = QRect(screen0Center, windowSize);
+ windowGeometry.adjust(0, 0, -10, 0); // Make sure the center point is on screen 0
+ MousePressTestWindow window;
+ window.setGeometry(windowGeometry);
+ window.show();
+
+ QPoint screen0Point(QPoint(10,10));
+ QPoint screen1Point(QPoint(windowSize.width() - 20,10));
+ QCOMPARE(app->screenAt(window.mapToGlobal(screen0Point)), app->screens()[0]);
+ QCOMPARE(app->screenAt(window.mapToGlobal(screen1Point)), app->screens()[1]);
+
+ window.m_mouseTestPoint = screen0Point;
+ QTest::mouseClick(&window, Qt::LeftButton, Qt::KeyboardModifiers(), screen0Point);
+ window.m_mouseTestPoint = screen1Point;
+ QTest::mouseClick(&window, Qt::LeftButton, Qt::KeyboardModifiers(), screen1Point);
+}
+
+#include "tst_qhighdpi.moc"
+QTEST_APPLESS_MAIN(tst_QHighDpi);