From fea54241aaefcfce6c0d19a8f22f922b83318f81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= Date: Fri, 16 Apr 2021 12:04:49 +0200 Subject: Add ScreenGadget utility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ScreenGadget visualizes virtual desktop screen layout, in device independent and native pixels. This can be used to debug the (sometimes surprising) device independent screen geometry resulting from Qt applying a scale factor. Change-Id: I5b18e0fc9a54ba3e14d648794429b2eeadd25748 Reviewed-by: Morten Johan Sørvig --- tests/manual/highdpi/screengadget/CMakeLists.txt | 20 ++ tests/manual/highdpi/screengadget/main.cpp | 244 +++++++++++++++++++++ tests/manual/highdpi/screengadget/screengadget.pro | 2 + 3 files changed, 266 insertions(+) create mode 100644 tests/manual/highdpi/screengadget/CMakeLists.txt create mode 100644 tests/manual/highdpi/screengadget/main.cpp create mode 100644 tests/manual/highdpi/screengadget/screengadget.pro (limited to 'tests/manual/highdpi') diff --git a/tests/manual/highdpi/screengadget/CMakeLists.txt b/tests/manual/highdpi/screengadget/CMakeLists.txt new file mode 100644 index 0000000000..cbccd5de69 --- /dev/null +++ b/tests/manual/highdpi/screengadget/CMakeLists.txt @@ -0,0 +1,20 @@ +# special case skip regeneration +cmake_minimum_required(VERSION 3.14) +project(screengadget LANGUAGES CXX) +set(CMAKE_AUTOMOC ON) + +find_package(Qt6 COMPONENTS Core) +find_package(Qt6 COMPONENTS Gui) +find_package(Qt6 COMPONENTS Widgets) + +qt_add_executable(screengadget + main.cpp +) + +target_link_libraries(screengadget PUBLIC + Qt::Core + Qt::Gui + Qt::GuiPrivate + Qt::Widgets +) + diff --git a/tests/manual/highdpi/screengadget/main.cpp b/tests/manual/highdpi/screengadget/main.cpp new file mode 100644 index 0000000000..762981c395 --- /dev/null +++ b/tests/manual/highdpi/screengadget/main.cpp @@ -0,0 +1,244 @@ +/**************************************************************************** + ** + ** Copyright (C) 2021 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 +#include + +#include +#include +#include + +// This test app optionally doubles as a manual test for QScreen::mapToNative() +// #define TEST_MAP_TO_NATIVE + +// ScreenDisplayer based on original impl from qtbase/tests/manual/highdpi +class ScreenDisplayer : public QWidget +{ +public: + ScreenDisplayer() = default; + + void setTitle() { + + QPoint deviceIndependentPos = pos(); + QPlatformWindow *platformWindow = windowHandle()->handle(); + QPoint nativePos = platformWindow->geometry().topLeft(); + + QString windowDebug = QString("pos ") + + QString("device independent %1 %2 ").arg(deviceIndependentPos.x()).arg(deviceIndependentPos.y()) + + QString("native %1 %2 ").arg(nativePos.x()).arg(nativePos.y()) + ; + + setWindowTitle(windowDebug); + } + + void updateMapToNative(QPointF pos) + { +#ifdef TEST_MAP_TO_NATIVE + mappedToNative = QGuiApplication::mapToNative(QRectF(pos, QPointF(1,1))).topLeft(); // there + mappedFromNative = QGuiApplication::mapFromNative(QRectF(mappedToNative, QPointF(1,1))).topLeft(); // and back again +#endif + } + + void timerEvent(QTimerEvent *) override + { + update(); + setTitle(); + } + + void mousePressEvent(QMouseEvent *) override + { + displayMappedToNativeCursor = true; + } + + void mouseMoveEvent(QMouseEvent *e) override + { + setTitle(); + updateMapToNative(e->globalPosition()); + } + + void mouseReleaseEvent(QMouseEvent *) override + { + } + + void showEvent(QShowEvent *) override + { + refreshTimer.start(60, this); + } + + void hideEvent(QHideEvent *) override + { + refreshTimer.stop(); + } + + void paintScreensInRect(QPainter &p, QRect rect, bool native) { + p.save(); + + QRectF total; + const auto screens = QGuiApplication::screens(); + for (const QScreen *screen : screens) + total |= native ? screen->handle()->geometry() : screen->geometry(); + if (total.isEmpty()) + return; + + qreal scaleMargin = 0.9; + scaleFactor = scaleMargin * qMin(rect.width()/total.width(), rect.height()/total.height()); + + // p.fillRect(rect, Qt::black); + int margin = 20; + p.translate(margin, margin + rect.y()); + p.scale(scaleFactor, scaleFactor); + p.translate(-total.topLeft()); + p.setPen(QPen(Qt::white, 10)); + p.setBrush(Qt::gray); + + for (const QScreen *screen : screens) { + QRect geometry = native ? screen->handle()->geometry() : screen->geometry(); + p.drawRect(geometry); + QFont f("Courier New"); + f.setPixelSize(geometry.height() / 16); + p.setFont(f); + + if (displayInfo) { + QString text = "Name: " + screen->name() + "\n"; + text += QString("\nGeometry: %1 %2 %3 %4 \n ").arg(geometry.x()).arg(geometry.y()).arg(geometry.width()).arg(geometry.height()); + p.save(); + p.translate(20, 0); + p.drawText(geometry, Qt::AlignLeft | Qt::AlignVCenter, text); + p.restore(); + } + } + p.setBrush(QColor(200,220,255,127)); + + const auto topLevels = QApplication::topLevelWidgets(); + for (QWidget *widget : topLevels) { + if (!widget->isHidden()) + p.drawRect(native ? widget->windowHandle()->handle()->geometry() : widget->geometry()); + } + + QPolygon cursorShape; + cursorShape << QPoint(0,0) << QPoint(20, 60) + << QPoint(30, 50) << QPoint(60, 80) + << QPoint(80, 60) << QPoint(50, 30) + << QPoint(60, 20); + + p.save(); + p.translate(native ? qApp->primaryScreen()->handle()->cursor()->pos() : QCursor::pos()); + p.drawPolygon(cursorShape); + p.restore(); + +#ifdef TEST_MAP_TO_NATIVE + // Draw red "mapped to native" cursor. We expect this + // cursor to track the normal blue cursor if everything + // works out ok. + if (displayMappedToNativeCursor) { + p.save(); + p.setBrush(QColor(230,120,155,127)); + p.translate(native ? mappedToNative: mappedFromNative); + p.drawPolygon(cursorShape); + p.restore(); + } +#endif + + p.restore(); + } + + void paintEvent(QPaintEvent *) override + { + QPainter p(this); + + QRect g = geometry(); + int halfHeight = g.height() / 2; + QRect topHalf = QRect(0, 0, g.width(), halfHeight); + QRect bottomHalf = QRect(0, halfHeight, g.width(), halfHeight); + + if (displayDeviceIndependentGeometry) + paintScreensInRect(p, topHalf, false); + if (displayNativeGeometry) + paintScreensInRect(p, bottomHalf, true); + } + + bool displayInfo = false; + bool displayDeviceIndependentGeometry = true; + bool displayNativeGeometry = false; +private: + bool displayMappedToNativeCursor = false; + QPointF mappedToNative; + QPointF mappedFromNative; + qreal scaleFactor = 1; + QBasicTimer refreshTimer; +}; + +class Controller : public QWidget { +public: + Controller(ScreenDisplayer *displayer) { + setWindowTitle("Controller"); + + QVBoxLayout *layout = new QVBoxLayout(); + setLayout(layout); + + layout->addWidget(new QLabel("Coordinate System:")); + + QCheckBox *deviceIndpendentGeometry = new QCheckBox("Device Independent"); + deviceIndpendentGeometry->setChecked(true); + connect(deviceIndpendentGeometry, &QCheckBox::stateChanged, [displayer](int checked){ displayer->displayDeviceIndependentGeometry = checked > 0; }); + layout->addWidget(deviceIndpendentGeometry); + + QCheckBox *nativeGeometry = new QCheckBox("Native"); + nativeGeometry->setChecked(false); + connect(nativeGeometry, &QCheckBox::stateChanged, [displayer](int checked){ displayer->displayNativeGeometry = checked > 0; }); + layout->addWidget(nativeGeometry); + + layout->addSpacing(40); + + QCheckBox *debug = new QCheckBox("Debug!"); + debug->setChecked(false); + connect(debug, &QCheckBox::stateChanged, [displayer](int checked){ displayer->displayInfo = checked > 0; }); + layout->addWidget(debug); + + layout->addStretch(); + } +}; + +int main(int argc, char **argv) { + + QApplication app(argc, argv); + + ScreenDisplayer displayer; + displayer.resize(300, 200); + displayer.show(); + + Controller controller(&displayer); + controller.resize(100, 200); + + QTimer::singleShot(300, [&controller, &displayer](){ + controller.move(displayer.pos() + QPoint(displayer.width(), 0) + QPoint(50, 0)); + controller.show(); + }); + + return app.exec(); +} diff --git a/tests/manual/highdpi/screengadget/screengadget.pro b/tests/manual/highdpi/screengadget/screengadget.pro new file mode 100644 index 0000000000..cdbaa8c2a1 --- /dev/null +++ b/tests/manual/highdpi/screengadget/screengadget.pro @@ -0,0 +1,2 @@ +QT += widgets gui_private +SOURCES += main.cpp -- cgit v1.2.3