qt5base-lts/tests/manual/highdpi/main.cpp
Friedemann Kleint 43c8596be5 More polish of the manual High DPI test
The aim is to make it suitable to test for High DPI
bugs, ideally removing the need to provide bug report
examples.

- Add descriptive window titles/output
- Add options to force scaling on/off
- Change the updating of the text to be done in screenChanged()
  and log the signal.
- Rearrange the layout and show the descriptions as labels

Task-number: QTBUG-80323
Change-Id: Ia44c184c2b38cb18045c40b440fe785c6c17925f
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
2019-11-28 14:26:25 +01:00

1408 lines
42 KiB
C++

/****************************************************************************
**
** Copyright (C) 2016 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 <QMainWindow>
#include <QMenuBar>
#include <QLabel>
#include <QHBoxLayout>
#include <QFormLayout>
#include <QApplication>
#include <QAction>
#include <QStyle>
#include <QToolBar>
#include <QPushButton>
#include <QButtonGroup>
#include <QLineEdit>
#include <QPlainTextEdit>
#include <QScrollBar>
#include <QSlider>
#include <QSpinBox>
#include <QTabBar>
#include <QTextBrowser>
#include <QIcon>
#include <QPainter>
#include <QWindow>
#include <QScreen>
#include <QGraphicsView>
#include <QGraphicsTextItem>
#include <QFile>
#include <QMouseEvent>
#include <QTemporaryDir>
#include <QTimer>
#include <QCommandLineParser>
#include <QCommandLineOption>
#include <QDebug>
#include <private/qhighdpiscaling_p.h>
#include <qpa/qplatformscreen.h>
#include "dragwidget.h"
#include <utility>
static QTextStream &operator<<(QTextStream &str, const QRect &r)
{
str << r.width() << 'x' << r.height() << forcesign << r.x() << r.y() << noforcesign;
return str;
}
static QString formatWindowTitle(const QString &title)
{
QString result;
QTextStream(&result) << title << ' ' << QT_VERSION_STR << " ("
<< QGuiApplication::platformName()
<< '/' << QApplication::style()->objectName() << ')';
return result;
}
class DemoContainerBase
{
public:
DemoContainerBase() = default;
virtual ~DemoContainerBase() = default;
QString name() { return option().names().constFirst(); }
virtual QCommandLineOption &option() = 0;
virtual void makeVisible(bool visible, QWidget *parent) = 0;
QWidget *widget() const { return m_widget; }
protected:
QWidget *m_widget = nullptr;
};
using DemoContainerList = QVector<DemoContainerBase*>;
template <class T>
class DemoContainer : public DemoContainerBase
{
public:
DemoContainer(const QString &optionName, const QString &description)
: m_option(optionName, description)
{
}
~DemoContainer() { delete m_widget; }
QCommandLineOption &option() override { return m_option; }
void makeVisible(bool visible, QWidget *parent) override
{
if (visible && !m_widget) {
m_widget = new T;
if (m_widget->windowTitle().isEmpty()) {
QString title = m_option.description();
if (title.startsWith("Test ", Qt::CaseInsensitive))
title.remove(0, 5);
title[0] = title.at(0).toUpper();
m_widget->setWindowTitle(formatWindowTitle(title));
}
m_widget->installEventFilter(parent);
}
if (m_widget)
m_widget->setVisible(visible);
}
private:
QCommandLineOption m_option;
};
class LabelSlider : public QObject
{
Q_OBJECT
public:
LabelSlider(QObject *parent, const QString &text, QGridLayout *layout, int row)
: QObject(parent)
{
QLabel *textLabel = new QLabel(text);
m_slider = new QSlider();
m_slider->setOrientation(Qt::Horizontal);
m_slider->setMinimum(1);
m_slider->setMaximum(40);
m_slider->setValue(10);
m_slider->setTracking(false);
m_slider->setTickInterval(5);
m_slider->setTickPosition(QSlider::TicksBelow);
m_label = new QLabel("1.0");
// set up layouts
layout->addWidget(textLabel, row, 0);
layout->addWidget(m_slider, row, 1);
layout->addWidget(m_label, row, 2);
// handle slider position change
connect(m_slider, &QSlider::sliderMoved, this, &LabelSlider::updateLabel);
connect(m_slider, &QSlider::valueChanged, this, &LabelSlider::valueChanged);
}
void setValue(int scaleFactor)
{
m_slider->setValue(scaleFactor);
updateLabel(scaleFactor);
}
private slots:
void updateLabel(int scaleFactor)
{
// slider value is scale factor times ten;
qreal scalefactorF = qreal(scaleFactor) / 10.0;
// update label, add ".0" if needed.
QString number = QString::number(scalefactorF);
if (!number.contains(QLatin1Char('.')))
number.append(".0");
m_label->setText(number);
}
signals:
void valueChanged(int scaleFactor);
private:
QSlider *m_slider;
QLabel *m_label;
};
static qreal getScreenFactorWithoutPixelDensity(const QScreen *screen)
{
// this is a hack that relies on knowing the internals of QHighDpiScaling
static const char *scaleFactorProperty = "_q_scaleFactor";
QVariant screenFactor = screen->property(scaleFactorProperty);
return screenFactor.isValid() ? screenFactor.toReal() : 1.0;
}
static inline qreal getGlobalScaleFactor()
{
QScreen *noScreen = nullptr;
return QHighDpiScaling::factor(noScreen);
}
class DemoController : public QWidget
{
Q_OBJECT
public:
DemoController(DemoContainerList demos, QCommandLineParser *parser);
~DemoController();
protected:
bool eventFilter(QObject *object, QEvent *event) override;
void closeEvent(QCloseEvent *) override { QCoreApplication::quit(); }
private slots:
void handleButton(int id, bool toggled);
private:
DemoContainerList m_demos;
QButtonGroup *m_group;
};
DemoController::DemoController(DemoContainerList demos, QCommandLineParser *parser)
: m_demos(std::move(demos))
{
setWindowTitle(formatWindowTitle("Screen Scale Factors"));
setObjectName("controller"); // make WindowScaleFactorSetter skip this window
auto mainLayout = new QVBoxLayout(this);
auto scaleLayout = new QGridLayout;
mainLayout->addLayout(scaleLayout);
int layoutRow = 0;
LabelSlider *globalScaleSlider = new LabelSlider(this, "Global scale factor", scaleLayout, layoutRow++);
globalScaleSlider->setValue(int(getGlobalScaleFactor() * 10));
connect(globalScaleSlider, &LabelSlider::valueChanged, [](int scaleFactor){
// slider value is scale factor times ten;
qreal scalefactorF = qreal(scaleFactor) / 10.0;
QHighDpiScaling::setGlobalFactor(scalefactorF);
});
// set up one scale control line per screen
const auto screens = QGuiApplication::screens();
for (QScreen *screen : screens) {
// create scale control line
QSize screenSize = screen->geometry().size();
QString screenId = screen->name() + QLatin1Char(' ') + QString::number(screenSize.width())
+ QLatin1Char(' ') + QString::number(screenSize.height());
LabelSlider *slider = new LabelSlider(this, screenId, scaleLayout, layoutRow++);
slider->setValue(getScreenFactorWithoutPixelDensity(screen) * 10);
// handle slider value change
connect(slider, &LabelSlider::valueChanged, [screen](int scaleFactor){
// slider value is scale factor times ten;
qreal scalefactorF = qreal(scaleFactor) / 10.0;
// set scale factor for screen
qreal oldFactor = QHighDpiScaling::factor(screen);
QHighDpiScaling::setScreenFactor(screen, scalefactorF);
qreal newFactor = QHighDpiScaling::factor(screen);
qDebug() << "factor was / is" << oldFactor << newFactor;
});
}
auto demoLayout = new QFormLayout;
mainLayout->addLayout(demoLayout);
m_group = new QButtonGroup(this);
m_group->setExclusive(false);
for (int i = 0; i < m_demos.size(); ++i) {
DemoContainerBase *demo = m_demos.at(i);
QString name = demo->name();
name[0] = name.at(0).toUpper();
auto button = new QPushButton(name);
button->setCheckable(true);
demoLayout->addRow(demo->option().description(), button);
m_group->addButton(button, i);
if (parser->isSet(demo->option())) {
demo->makeVisible(true, this);
button->setChecked(true);
}
}
connect(m_group, QOverload<int,bool>::of(&QButtonGroup::buttonToggled),
this, &DemoController::handleButton);
}
DemoController::~DemoController()
{
qDeleteAll(m_demos);
}
bool DemoController::eventFilter(QObject *object, QEvent *event)
{
if (event->type() == QEvent::Close) {
for (int i = 0; i < m_demos.size(); ++i) {
DemoContainerBase *demo = m_demos.at(i);
if (demo->widget() == object) {
m_group->button(i)->setChecked(false);
break;
}
}
}
return false;
}
void DemoController::handleButton(int id, bool toggled)
{
m_demos.at(id)->makeVisible(toggled, this);
}
class PixmapPainter : public QWidget
{
public:
PixmapPainter();
void paintEvent(QPaintEvent *event) override;
private:
QPixmap pixmap1X;
QPixmap pixmap2X;
QPixmap pixmapLarge;
QImage image1X;
QImage image2X;
QImage imageLarge;
QIcon qtIcon;
};
PixmapPainter::PixmapPainter()
{
pixmap1X = QPixmap(":/qticon32.png");
pixmap2X = QPixmap(":/qticon32@2x.png");
pixmapLarge = QPixmap(":/qticon64.png");
image1X = QImage(":/qticon32.png");
image2X = QImage(":/qticon32@2x.png");
imageLarge = QImage(":/qticon64.png");
qtIcon.addFile(":/qticon32.png");
qtIcon.addFile(":/qticon32@2x.png");
}
void PixmapPainter::paintEvent(QPaintEvent *)
{
QPainter p(this);
p.fillRect(QRect(QPoint(0, 0), size()), QBrush(Qt::gray));
int pixmapPointSize = 32;
int y = 30;
int dy = 90;
int x = 10;
int dx = 40;
// draw at point
// qDebug() << "paint pixmap" << pixmap1X.devicePixelRatio();
p.drawPixmap(x, y, pixmap1X);
x+=dx;p.drawPixmap(x, y, pixmap2X);
x+=dx;p.drawPixmap(x, y, pixmapLarge);
x+=dx*2;p.drawPixmap(x, y, qtIcon.pixmap(QSize(pixmapPointSize, pixmapPointSize)));
x+=dx;p.drawImage(x, y, image1X);
x+=dx;p.drawImage(x, y, image2X);
x+=dx;p.drawImage(x, y, imageLarge);
// draw at 32x32 rect
y+=dy;
x = 10;
p.drawPixmap(QRect(x, y, pixmapPointSize, pixmapPointSize), pixmap1X);
x+=dx;p.drawPixmap(QRect(x, y, pixmapPointSize, pixmapPointSize), pixmap2X);
x+=dx;p.drawPixmap(QRect(x, y, pixmapPointSize, pixmapPointSize), pixmapLarge);
x+=dx;p.drawPixmap(QRect(x, y, pixmapPointSize, pixmapPointSize), qtIcon.pixmap(QSize(pixmapPointSize, pixmapPointSize)));
x+=dx;p.drawImage(QRect(x, y, pixmapPointSize, pixmapPointSize), image1X);
x+=dx;p.drawImage(QRect(x, y, pixmapPointSize, pixmapPointSize), image2X);
x+=dx;p.drawImage(QRect(x, y, pixmapPointSize, pixmapPointSize), imageLarge);
// draw at 64x64 rect
y+=dy - 50;
x = 10;
p.drawPixmap(QRect(x, y, pixmapPointSize * 2, pixmapPointSize * 2), pixmap1X);
x+=dx * 2; p.drawPixmap(QRect(x, y, pixmapPointSize * 2, pixmapPointSize * 2), pixmap2X);
x+=dx * 2; p.drawPixmap(QRect(x, y, pixmapPointSize * 2, pixmapPointSize * 2), pixmapLarge);
x+=dx * 2; p.drawPixmap(QRect(x, y, pixmapPointSize * 2, pixmapPointSize * 2), qtIcon.pixmap(QSize(pixmapPointSize, pixmapPointSize)));
x+=dx * 2; p.drawImage(QRect(x, y, pixmapPointSize * 2, pixmapPointSize * 2), image1X);
x+=dx * 2; p.drawImage(QRect(x, y, pixmapPointSize * 2, pixmapPointSize * 2), image2X);
x+=dx * 2; p.drawImage(QRect(x, y, pixmapPointSize * 2, pixmapPointSize * 2), imageLarge);
}
class TiledPixmapPainter : public QWidget
{
public:
TiledPixmapPainter();
void paintEvent(QPaintEvent *event) override;
private:
QPixmap pixmap1X;
QPixmap pixmap2X;
QPixmap pixmapLarge;
};
TiledPixmapPainter::TiledPixmapPainter()
{
pixmap1X = QPixmap(":/qticon32.png");
pixmap2X = QPixmap(":/qticon32@2x.png");
pixmapLarge = QPixmap(":/qticon64.png");
}
void TiledPixmapPainter::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter p(this);
int xoff = 10;
int yoff = 10;
int tiles = 4;
int pixmapEdge = 32;
int tileAreaEdge = pixmapEdge * tiles;
// Expected behavior for both 1x and 2x dislays:
// 1x pixmap : 4 x 4 tiles
// large pixmap: 2 x 2 tiles
// 2x pixmap : 4 x 4 tiles
//
// On a 2x display the 2x pixmap tiles
// will be drawn in high resolution.
p.drawTiledPixmap(QRect(xoff, yoff, tileAreaEdge, tileAreaEdge), pixmap1X);
yoff += tiles * pixmapEdge + 10;
p.drawTiledPixmap(QRect(xoff, yoff, tileAreaEdge, tileAreaEdge), pixmapLarge);
yoff += tiles * pixmapEdge + 10;
p.drawTiledPixmap(QRect(xoff, yoff, tileAreaEdge, tileAreaEdge), pixmap2X);
// Again, with an offset. The offset is in
// device-independent pixels.
QPoint offset(40, 40); // larger than the pixmap edge size to exercise that code path
yoff = 10;
xoff = 20 + tiles * pixmapEdge ;
p.drawTiledPixmap(QRect(xoff, yoff, tileAreaEdge, tileAreaEdge), pixmap1X, offset);
yoff += tiles * pixmapEdge + 10;
p.drawTiledPixmap(QRect(xoff, yoff, tileAreaEdge, tileAreaEdge), pixmapLarge, offset);
yoff += tiles * pixmapEdge + 10;
p.drawTiledPixmap(QRect(xoff, yoff, tileAreaEdge, tileAreaEdge), pixmap2X, offset);
}
class Labels : public QWidget
{
public:
Labels();
private:
QPixmap pixmap1X;
QPixmap pixmap2X;
QPixmap pixmapLarge;
QIcon qtIcon;
};
Labels::Labels()
{
pixmap1X = QPixmap(":/qticon32.png");
pixmap2X = QPixmap(":/qticon32@2x.png");
pixmapLarge = QPixmap(":/qticon64.png");
qtIcon.addFile(":/qticon32.png");
qtIcon.addFile(":/qticon32@2x.png");
setWindowIcon(qtIcon);
setWindowTitle(formatWindowTitle("Labels"));
QLabel *label1x = new QLabel();
label1x->setPixmap(pixmap1X);
QLabel *label2x = new QLabel();
label2x->setPixmap(pixmap2X);
QLabel *labelIcon = new QLabel();
labelIcon->setPixmap(qtIcon.pixmap(QSize(32,32)));
QLabel *labelLarge = new QLabel();
labelLarge->setPixmap(pixmapLarge);
QHBoxLayout *layout = new QHBoxLayout(this);
layout->addWidget(label1x); //expected low-res on high-dpi displays
layout->addWidget(label2x); //expected high-res on high-dpi displays
layout->addWidget(labelIcon); //expected high-res on high-dpi displays
layout->addWidget(labelLarge); // expected large size and low-res
setLayout(layout);
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
QMenu *addNewMenu(const QString &title, int itemCount = 5);
private slots:
void maskActionToggled(bool t);
private:
QIcon qtIcon;
QIcon qtIcon1x;
QIcon qtIcon2x;
QToolBar *fileToolBar;
QAction *m_maskAction;
int menuCount = 0;
};
MainWindow::MainWindow()
{
// beware that QIcon auto-loads the @2x versions.
qtIcon1x.addFile(":/qticon16.png");
qtIcon2x.addFile(":/qticon32.png");
setWindowIcon(qtIcon);
setWindowTitle(formatWindowTitle("MainWindow"));
fileToolBar = addToolBar(tr("File"));
// fileToolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
fileToolBar->addAction(new QAction(qtIcon1x, QString("1x"), this));
fileToolBar->addAction(new QAction(qtIcon2x, QString("2x"), this));
addNewMenu("&Edit");
addNewMenu("&Build");
addNewMenu("&Debug", 4);
QMenu *menu = addNewMenu("&Transmogrify", 7);
menu->addSeparator();
m_maskAction = menu->addAction("Mask");
m_maskAction->setCheckable(true);
connect(m_maskAction, &QAction::toggled, this, &MainWindow::maskActionToggled);
fileToolBar->addAction(m_maskAction);
addNewMenu("T&ools");
addNewMenu("&Help", 2);
}
QMenu *MainWindow::addNewMenu(const QString &title, int itemCount)
{
QMenu *menu = menuBar()->addMenu(title);
for (int i = 0; i < itemCount; i++) {
menuCount++;
QString s = "Menu item " + QString::number(menuCount);
if (i == 3) {
QMenu *subMenu = menu->addMenu(s);
for (int j = 1; j < 4; j++)
subMenu->addAction(QString::fromLatin1("SubMenu item %1.%2").arg(menuCount).arg(j));
} else {
menu->addAction(s);
}
}
return menu;
}
void MainWindow::maskActionToggled(bool t)
{
if (t) {
QVector<QPoint> upperLeftTriangle;
upperLeftTriangle << QPoint(0, 0) << QPoint(width(), 0) << QPoint(0, height());
setMask(QRegion(QPolygon(upperLeftTriangle)));
} else {
clearMask();
}
}
class StandardIcons : public QWidget
{
public:
void paintEvent(QPaintEvent *) override
{
int x = 10;
int y = 10;
int dx = 50;
int dy = 50;
int maxX = 500;
for (uint iconIndex = QStyle::SP_TitleBarMenuButton; iconIndex < QStyle::SP_MediaVolumeMuted; ++iconIndex) {
QIcon icon = qApp->style()->standardIcon(QStyle::StandardPixmap(iconIndex));
QPainter p(this);
p.drawPixmap(x, y, icon.pixmap(dx - 5, dy - 5));
if (x + dx > maxX)
y+=dy;
x = ((x + dx) % maxX);
}
}
};
class Caching : public QWidget
{
public:
void paintEvent(QPaintEvent *) override
{
QSize layoutSize(75, 75);
QPainter widgetPainter(this);
widgetPainter.fillRect(QRect(QPoint(0, 0), this->size()), Qt::gray);
{
const qreal devicePixelRatio = this->windowHandle()->devicePixelRatio();
QPixmap cache(layoutSize * devicePixelRatio);
cache.setDevicePixelRatio(devicePixelRatio);
QPainter cachedPainter(&cache);
cachedPainter.fillRect(QRect(0,0, 75, 75), Qt::blue);
cachedPainter.fillRect(QRect(10,10, 55, 55), Qt::red);
cachedPainter.drawEllipse(QRect(10,10, 55, 55));
QPainter widgetPainter(this);
widgetPainter.drawPixmap(QPoint(10, 10), cache);
}
{
const qreal devicePixelRatio = this->windowHandle()->devicePixelRatio();
QImage cache = QImage(layoutSize * devicePixelRatio, QImage::Format_ARGB32_Premultiplied);
cache.setDevicePixelRatio(devicePixelRatio);
QPainter cachedPainter(&cache);
cachedPainter.fillRect(QRect(0,0, 75, 75), Qt::blue);
cachedPainter.fillRect(QRect(10,10, 55, 55), Qt::red);
cachedPainter.drawEllipse(QRect(10,10, 55, 55));
QPainter widgetPainter(this);
widgetPainter.drawImage(QPoint(95, 10), cache);
}
}
};
class Style : public QWidget
{
public:
Style()
{
row1 = new QHBoxLayout(this);
button = new QPushButton();
button->setText("Test Button");
row1->addWidget(button);
lineEdit = new QLineEdit();
lineEdit->setText("Test Lineedit");
row1->addWidget(lineEdit);
slider = new QSlider();
row1->addWidget(slider);
row1->addWidget(new QSpinBox);
row1->addWidget(new QScrollBar);
auto tab = new QTabBar();
tab->addTab("Foo");
tab->addTab("Bar");
row1->addWidget(tab);
}
private:
QPushButton *button;
QLineEdit *lineEdit;
QSlider *slider;
QHBoxLayout *row1;
};
class Fonts : public QWidget
{
public:
void paintEvent(QPaintEvent *) override
{
QPainter painter(this);
// Points
int y = 10;
for (int fontSize = 6; fontSize < 18; fontSize += 2) {
QFont font;
font.setPointSize(fontSize);
QString string = QString(QStringLiteral("This text is in point size %1")).arg(fontSize);
painter.setFont(font);
y += (painter.fontMetrics().lineSpacing());
painter.drawText(10, y, string);
}
// Pixels
y += painter.fontMetrics().lineSpacing();
for (int fontSize = 6; fontSize < 18; fontSize += 2) {
QFont font;
font.setPixelSize(fontSize);
QString string = QString(QStringLiteral("This text is in pixel size %1")).arg(fontSize);
painter.setFont(font);
y += (painter.fontMetrics().lineSpacing());
painter.drawText(10, y, string);
}
}
};
template <typename T>
void apiTestdevicePixelRatioGetter()
{
if (0) {
T *t = nullptr;
t->devicePixelRatio();
}
}
template <typename T>
void apiTestdevicePixelRatioSetter()
{
if (0) {
T *t = nullptr;
t->setDevicePixelRatio(2.0);
}
}
void apiTest()
{
// compile call to devicePixelRatio getter and setter (verify spelling)
apiTestdevicePixelRatioGetter<QWindow>();
apiTestdevicePixelRatioGetter<QScreen>();
apiTestdevicePixelRatioGetter<QGuiApplication>();
apiTestdevicePixelRatioGetter<QImage>();
apiTestdevicePixelRatioSetter<QImage>();
apiTestdevicePixelRatioGetter<QPixmap>();
apiTestdevicePixelRatioSetter<QPixmap>();
}
// Request and draw an icon at different sizes
class IconDrawing : public QWidget
{
public:
IconDrawing()
{
const QString tempPath = m_temporaryDir.path();
const QString path32 = tempPath + "/qticon32.png";
const QString path32_2 = tempPath + "/qticon32-2.png";
const QString path32_2x = tempPath + "/qticon32@2x.png";
QFile::copy(":/qticon32.png", path32_2);
QFile::copy(":/qticon32.png", path32);
QFile::copy(":/qticon32@2x.png", path32_2x);
iconHighDPI.reset(new QIcon(path32)); // will auto-load @2x version.
iconNormalDpi.reset(new QIcon(path32_2)); // does not have a 2x version.
}
void paintEvent(QPaintEvent *) override
{
int x = 10;
int y = 10;
int dx = 50;
int dy = 50;
int maxX = 600;
int minSize = 5;
int maxSize = 64;
int sizeIncrement = 5;
// Disable high-dpi icons
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, false);
// normal icon
for (int size = minSize; size < maxSize; size += sizeIncrement) {
QPainter p(this);
p.drawPixmap(x, y, iconNormalDpi->pixmap(size, size));
if (x + dx > maxX)
y+=dy;
x = ((x + dx) % maxX);
}
x = 10;
y+=dy;
// high-dpi icon
for (int size = minSize; size < maxSize; size += sizeIncrement) {
QPainter p(this);
p.drawPixmap(x, y, iconHighDPI->pixmap(size, size));
if (x + dx > maxX)
y+=dy;
x = ((x + dx) % maxX);
}
x = 10;
y+=dy;
// Enable high-dpi icons
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true);
// normal icon
for (int size = minSize; size < maxSize; size += sizeIncrement) {
QPainter p(this);
p.drawPixmap(x, y, iconNormalDpi->pixmap(size, size));
if (x + dx > maxX)
y+=dy;
x = ((x + dx) % maxX);
}
x = 10;
y+=dy;
// high-dpi icon (draw point)
for (int size = minSize; size < maxSize; size += sizeIncrement) {
QPainter p(this);
p.drawPixmap(x, y, iconHighDPI->pixmap(size, size));
if (x + dx > maxX)
y+=dy;
x = ((x + dx) % maxX);
}
x = 10;
y+=dy;
}
private:
QTemporaryDir m_temporaryDir;
QScopedPointer<QIcon> iconHighDPI;
QScopedPointer<QIcon> iconNormalDpi;
};
// Icons on buttons
class Buttons : public QWidget
{
public:
Buttons()
{
QIcon icon;
icon.addFile(":/qticon16@2x.png");
QPushButton *button = new QPushButton(this);
button->setIcon(icon);
button->setText("16@2x");
QTabBar *tab = new QTabBar(this);
tab->addTab(QIcon(":/qticon16.png"), "16@1x");
tab->addTab(QIcon(":/qticon16@2x.png"), "16@2x");
tab->addTab(QIcon(":/qticon16.png"), "");
tab->addTab(QIcon(":/qticon16@2x.png"), "");
tab->move(10, 100);
tab->show();
auto toolBar = new QToolBar(this);
toolBar->addAction(QIcon(":/qticon16.png"), "16");
toolBar->addAction(QIcon(":/qticon16@2x.png"), "16@2x");
toolBar->addAction(QIcon(":/qticon32.png"), "32");
toolBar->addAction(QIcon(":/qticon32@2x.png"), "32@2x");
toolBar->move(10, 200);
toolBar->show();
}
};
class LinePainter : public QWidget
{
public:
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
private:
QPoint lastMousePoint;
QVector<QPoint> linePoints;
};
void LinePainter::paintEvent(QPaintEvent *)
{
QPainter p(this);
p.fillRect(QRect(QPoint(0, 0), size()), QBrush(Qt::gray));
// Default antialiased line
p.setRenderHint(QPainter::Antialiasing);
p.drawLines(linePoints);
// Cosmetic 1 antialiased line
QPen pen;
pen.setCosmetic(true);
pen.setWidth(1);
p.setPen(pen);
p.translate(3, 3);
p.drawLines(linePoints);
// Aliased cosmetic 1 line
p.setRenderHint(QPainter::Antialiasing, false);
p.translate(3, 3);
p.drawLines(linePoints);
}
void LinePainter::mousePressEvent(QMouseEvent *event)
{
lastMousePoint = event->pos();
}
void LinePainter::mouseReleaseEvent(QMouseEvent *)
{
lastMousePoint = QPoint();
}
void LinePainter::mouseMoveEvent(QMouseEvent *event)
{
if (lastMousePoint.isNull())
return;
QPoint newMousePoint = event->pos();
if (lastMousePoint == newMousePoint)
return;
linePoints.append(lastMousePoint);
linePoints.append(newMousePoint);
lastMousePoint = newMousePoint;
update();
}
class CursorTester : public QWidget
{
public:
CursorTester() = default;
inline QRect getRect(int idx) const
{
int h = height() / 2;
return QRect(10, 10 + h * (idx - 1), width() - 20, h - 20);
}
void paintEvent(QPaintEvent *) override
{
QPainter p(this);
QRect r1 = getRect(1);
QRect r2 = getRect(2);
p.fillRect(r1, QColor(200, 200, 250));
p.drawText(r1, "Drag from here to move a window based on QCursor::pos()");
p.fillRect(r2, QColor(250, 200, 200));
p.drawText(r2, "Drag from here to move a window based on mouse event position");
if (moving) {
p.setPen(Qt::darkGray);
QFont f = font();
f.setPointSize(8);
p.setFont(f);
p.drawEllipse(mousePos, 30,60);
QPoint pt = mousePos - QPoint(0, 60);
QPoint pt2 = pt - QPoint(30,10);
QPoint offs(30, 0);
p.drawLine(pt, pt2);
p.drawLine(pt2 - offs, pt2 + offs);
p.drawText(pt2 - offs, "mouse pos");
p.setPen(QColor(50,130,70));
QPoint cursorPos = mapFromGlobal(QCursor::pos());
pt = cursorPos - QPoint(0, 30);
pt2 = pt + QPoint(60, -20);
p.drawEllipse(cursorPos, 60, 30);
p.drawLine(pt, pt2);
p.drawLine(pt2 - offs, pt2 + offs);
p.drawText(pt2 - offs, "cursor pos");
}
}
void mousePressEvent(QMouseEvent *e) override
{
if (moving)
return;
QRect r1 = getRect(1);
QRect r2 = getRect(2);
moving = r1.contains(e->pos()) || r2.contains(e->pos());
if (!moving)
return;
useCursorPos = r1.contains(e->pos());
if (!moveLabel)
moveLabel = new QLabel(this,Qt::BypassWindowManagerHint|Qt::FramelessWindowHint|Qt::Window );
if (useCursorPos)
moveLabel->setText("I'm following QCursor::pos()");
else
moveLabel->setText("I'm following QMouseEvent::globalPos()");
moveLabel->adjustSize();
mouseMoveEvent(e);
moveLabel->show();
}
void mouseReleaseEvent(QMouseEvent *) override
{
if (moveLabel)
moveLabel->hide();
update();
moving = false;
}
void mouseMoveEvent(QMouseEvent *e) override
{
if (!moving)
return;
QPoint pos = useCursorPos ? QCursor::pos() : e->globalPos();
pos -= moveLabel->rect().center();
moveLabel->move(pos);
mousePos = e->pos();
update();
}
private:
QLabel *moveLabel = nullptr;
QPoint mousePos;
bool useCursorPos = false;
bool moving = false;
};
class ScreenDisplayer : public QWidget
{
public:
ScreenDisplayer() = default;
void timerEvent(QTimerEvent *) override
{
update();
}
void mousePressEvent(QMouseEvent *) override
{
if (!moveLabel)
moveLabel = new QLabel(this,Qt::BypassWindowManagerHint|Qt::FramelessWindowHint|Qt::Window );
moveLabel->setText("Hello, Qt this is a label\nwith some text");
moveLabel->show();
}
void mouseMoveEvent(QMouseEvent *e) override
{
if (!moveLabel)
return;
moveLabel->move(e->pos() / scaleFactor);
QString str;
QDebug dbg(&str);
dbg.setAutoInsertSpaces(false);
dbg << moveLabel->geometry();
moveLabel->setText(str);
}
void mouseReleaseEvent(QMouseEvent *) override
{
if (moveLabel)
moveLabel->hide();
}
void showEvent(QShowEvent *) override
{
refreshTimer.start(300, this);
}
void hideEvent(QHideEvent *) override
{
refreshTimer.stop();
}
void paintEvent(QPaintEvent *) override
{
QPainter p(this);
QRectF total;
const auto screens = QGuiApplication::screens();
for (const QScreen *screen : screens)
total |= screen->geometry();
if (total.isEmpty())
return;
scaleFactor = qMin(width()/total.width(), height()/total.height());
p.fillRect(rect(), Qt::black);
p.scale(scaleFactor, scaleFactor);
p.translate(-total.topLeft());
p.setPen(QPen(Qt::white, 10));
p.setBrush(Qt::gray);
for (const QScreen *screen : screens) {
p.drawRect(screen->geometry());
QFont f = font();
f.setPixelSize(screen->geometry().height() / 8);
p.setFont(f);
p.drawText(screen->geometry(), Qt::AlignCenter, screen->name());
}
p.setBrush(QColor(200,220,255,127));
const auto topLevels = QApplication::topLevelWidgets();
for (QWidget *widget : topLevels) {
if (!widget->isHidden())
p.drawRect(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);
cursorShape.translate(QCursor::pos());
p.drawPolygon(cursorShape);
}
private:
QLabel *moveLabel = nullptr;
qreal scaleFactor = 1;
QBasicTimer refreshTimer;
};
class PhysicalSizeTest : public QWidget
{
Q_OBJECT
public:
PhysicalSizeTest() = default;
void paintEvent(QPaintEvent *event) override;
void resizeEvent(QResizeEvent *) override
{
qreal ppi = window()->windowHandle()->screen()->physicalDotsPerInchX();
QSizeF s = size();
if (!m_ignoreResize)
m_physicalSize = s / ppi;
}
bool event(QEvent *event) override
{
if (event->type() == QEvent::ScreenChangeInternal) {
// we will get resize events when the scale factor changes
m_ignoreResize = true;
QTimer::singleShot(100, this, &PhysicalSizeTest::handleScreenChange);
}
return QWidget::event(event);
}
public slots:
void handleScreenChange()
{
qreal ppi = window()->windowHandle()->screen()->physicalDotsPerInchX();
QSizeF newSize = m_physicalSize * ppi;
resize(newSize.toSize());
m_ignoreResize = false;
}
private:
QSizeF m_physicalSize;
bool m_ignoreResize = false;
};
void PhysicalSizeTest::paintEvent(QPaintEvent *)
{
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
qreal ppi = window()->windowHandle()->screen()->physicalDotsPerInchX();
qreal ppmm = ppi / 25.4;
qreal h = 15 * ppmm;
QRectF rulerRect(0,0, width(), h);
rulerRect.moveCenter(rect().center());
QFont f = font();
f.setPixelSize(18);
p.setFont(f);
// draw a rectangle in (Qt) pixel coordinates, for comparison
QRect pixelRect(0, 0, 300, 50);
pixelRect.moveTopLeft(QPoint(5 * ppmm, rulerRect.bottom() + 5 * ppmm));
p.fillRect(pixelRect, QColor(199,222,255));
p.drawText(pixelRect, "This rectangle is 300x50 pixels");
f.setPixelSize(4 * ppmm);
p.setFont(f);
QRectF topRect(0, 0, width(), rulerRect.top());
p.drawText(topRect, Qt::AlignCenter, "The ruler is drawn in physical units.\nThis window tries to keep its physical size\nwhen moved between screens.");
// draw a ruler in real physical coordinates
p.fillRect(rulerRect, QColor(255, 222, 111));
QPen linePen(Qt::black, 0.3 * ppmm);
p.setPen(linePen);
f.setBold(true);
p.setFont(f);
qreal vCenter = rulerRect.center().y();
p.drawLine(0, vCenter, width(), vCenter);
// cm
for (int i = 0;;) {
i++;
qreal x = i * ppmm;
if (x > width())
break;
qreal y = rulerRect.bottom();
qreal len;
if (i % 5)
len = 2 * ppmm;
else if (i % 10)
len = 3 * ppmm;
else
len = h / 2;
p.drawLine(QPointF(x, y), QPointF(x, y - len));
if (i % 10 == 5) {
QRectF textR(0, 0, 5 * ppmm, h / 2 - 2 * ppmm);
textR.moveTopLeft(QPointF(x, vCenter));
int n = i / 10 + 1;
if (n % 10 == 0)
p.setPen(Qt::red);
p.drawText(textR, Qt::AlignCenter, QString::number(n));
p.setPen(linePen);
}
}
//inches
for (int i = 0;;) {
i++;
qreal x = i * ppi / 16;
if (x > width())
break;
qreal y = rulerRect.top();
qreal d = h / 10;
qreal len;
if (i % 2)
len = 1 * d;
else if (i % 4)
len = 2 * d;
else if (i % 8)
len = 3 * d;
else if (i % 16)
len = 4 * d;
else
len = h / 2;
p.drawLine(QPointF(x, y), QPointF(x, y + len));
if (i % 16 == 12) {
QRectF textR(0, 0, 0.25 * ppi, h / 2 - 2 * d);
textR.moveBottomLeft(QPointF(x, vCenter));
p.drawText(textR, Qt::AlignCenter, QString::number(1 + i/16));
}
}
}
class GraphicsViewCaching : public QGraphicsView
{
public:
GraphicsViewCaching()
{
auto scene = new QGraphicsScene(0, 0, 400, 400);
QGraphicsTextItem *item = scene->addText("NoCache");
item->setCacheMode(QGraphicsItem::NoCache);
item->setPos(10, 10);
item = scene->addText("ItemCoordinateCache");
item->setCacheMode(QGraphicsItem::ItemCoordinateCache);
item->setPos(10, 30);
item = scene->addText("DeviceCoordinateCache");
item->setCacheMode(QGraphicsItem::DeviceCoordinateCache);
item->setPos(10, 50);
setScene(scene);
}
};
class MetricsTest : public QWidget
{
Q_OBJECT
public:
MetricsTest()
{
qDebug().noquote().nospace() << "MetricsTest " << QT_VERSION_STR
<< ' ' << QGuiApplication::platformName() << '\n'
<< R"(Relevant environment variables are:
QT_FONT_DPI=N
QT_SCALE_FACTOR=n
QT_ENABLE_HIGHDPI_SCALING=0|1
QT_USE_PHYSICAL_DPI=0|1
QT_SCREEN_SCALE_FACTORS=N;N;N or QT_SCREEN_SCALE_FACTORS=name:N
QT_SCALE_FACTOR_ROUNDING_POLICY=Round|Ceil|Floor|RoundPreferFloor|PassThrough
QT_DPI_ADJUSTMENT_POLICY=AdjustDpi|DontAdjustDpi|AdjustUpOnly)";
resize(480, 360);
auto layout = new QVBoxLayout(this);
m_textEdit = new QPlainTextEdit;
m_textEdit->setReadOnly(true);
layout->addWidget(m_textEdit);
setWindowTitle(formatWindowTitle("Screens"));
}
void setVisible(bool visible) override
{
QWidget::setVisible(visible);
if (visible && !m_screenChangedConnected) {
m_screenChangedConnected = true;
QObject::connect(windowHandle(), &QWindow::screenChanged,
this, &MetricsTest::screenChanged);
updateMetrics();
}
}
void updateMetrics()
{
QString text;
QTextStream str(&text);
auto currentScreen = windowHandle()->screen();
const auto screens = QGuiApplication::screens();
for (int i = 0, size = screens.size(); i < size; ++i) {
auto screen = screens.at(i);
auto platformScreen = screen->handle();
str << "Screen #" << i << " \"" << screen->name() << '"';
if (screen == currentScreen)
str << " [current]";
if (screen == QGuiApplication::primaryScreen())
str << " [primary]";
str << "\n screen geometry: " << screen->geometry()
<< "\n platform screen geometry: " << platformScreen->geometry()
<< "\n platform screen logicalDpi: " << platformScreen->logicalDpi().first;
#ifdef HAVE_SCREEN_BASE_DPI
str << "\n platform screen logicalBaseDpi: " << platformScreen->logicalBaseDpi().first;
#endif
str << "\n platform screen devicePixelRatio: " <<platformScreen->devicePixelRatio()
<< "\n platform screen physicalDpi: " << screen->physicalDotsPerInch()
<< "\n\n";
}
str << "widget devicePixelRatio: " << this->devicePixelRatioF()
<< "\nwidget logicalDpi: " << this->logicalDpiX()
<< "\n\nQT_FONT_DPI: " << qgetenv("QT_FONT_DPI")
<< "\nQT_SCALE_FACTOR: " << qgetenv("QT_SCALE_FACTOR")
<< "\nQT_ENABLE_HIGHDPI_SCALING: " << qgetenv("QT_ENABLE_HIGHDPI_SCALING")
<< "\nQT_SCREEN_SCALE_FACTORS: " << qgetenv("QT_SCREEN_SCALE_FACTORS")
<< "\nQT_USE_PHYSICAL_DPI: " << qgetenv("QT_USE_PHYSICAL_DPI")
<< "\nQT_SCALE_FACTOR_ROUNDING_POLICY: " << qgetenv("QT_SCALE_FACTOR_ROUNDING_POLICY")
<< "\nQT_DPI_ADJUSTMENT_POLICY: " << qgetenv("QT_DPI_ADJUSTMENT_POLICY")
<< '\n';
m_textEdit->setPlainText(text);
}
private slots:
void screenChanged()
{
qDebug().noquote() << __FUNCTION__ << windowHandle()->screen()->name();
updateMetrics();
}
private:
QPlainTextEdit *m_textEdit;
bool m_screenChangedConnected = false;
};
int main(int argc, char **argv)
{
#define NOSCALINGOPTION "noscaling"
#define SCALINGOPTION "scaling"
qInfo("High DPI tester %s", QT_VERSION_STR);
int preAppOptionCount = 0;
for (int a = 1; a < argc; ++a) {
if (qstrcmp(argv[a], "--" NOSCALINGOPTION) == 0) {
QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
preAppOptionCount++;
qInfo("AA_DisableHighDpiScaling");
} else if (qstrcmp(argv[a], "--" SCALINGOPTION) == 0) {
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
preAppOptionCount++;
qInfo("AA_EnableHighDpiScaling");
}
}
QApplication app(argc, argv);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
QCoreApplication::setApplicationVersion(QT_VERSION_STR);
QCommandLineParser parser;
parser.setApplicationDescription("High DPI tester. Pass one or more of the options to\n"
"test various high-dpi aspects. \n"
"--interactive is a special option and opens a configuration"
" window.");
parser.addHelpOption();
parser.addVersionOption();
QCommandLineOption controllerOption("interactive", "Show configuration window.");
parser.addOption(controllerOption);
parser.addOption(QCommandLineOption(NOSCALINGOPTION, "Set AA_DisableHighDpiScaling"));
parser.addOption(QCommandLineOption(SCALINGOPTION, "Set AA_EnableHighDpiScaling"));
DemoContainerList demoList;
demoList << new DemoContainer<PixmapPainter>("pixmap", "Test pixmap painter");
demoList << new DemoContainer<TiledPixmapPainter>("tiledpixmap", "Test tiled pixmap painter");
demoList << new DemoContainer<Labels>("label", "Test Labels");
demoList << new DemoContainer<MainWindow>("mainwindow", "Test QMainWindow");
demoList << new DemoContainer<StandardIcons>("standard-icons", "Test standard icons");
demoList << new DemoContainer<Caching>("caching", "Test caching");
demoList << new DemoContainer<Style>("styles", "Test style");
demoList << new DemoContainer<Fonts>("fonts", "Test fonts");
demoList << new DemoContainer<IconDrawing>("icondrawing", "Test icon drawing");
demoList << new DemoContainer<Buttons>("buttons", "Test buttons");
demoList << new DemoContainer<LinePainter>("linepainter", "Test line painting");
demoList << new DemoContainer<DragWidget>("draganddrop", "Test drag and drop");
demoList << new DemoContainer<CursorTester>("cursorpos", "Test cursor and window positioning");
demoList << new DemoContainer<ScreenDisplayer>("screens", "Test screen and window positioning");
demoList << new DemoContainer<PhysicalSizeTest>("physicalsize", "Test manual highdpi support using physicalDotsPerInch");
demoList << new DemoContainer<GraphicsViewCaching>("graphicsview", "Test QGraphicsView caching");
demoList << new DemoContainer<MetricsTest>("metrics", "Show screen metrics");
for (DemoContainerBase *demo : qAsConst(demoList))
parser.addOption(demo->option());
parser.process(app);
//controller takes ownership of all demos
DemoController controller(demoList, &parser);
if (parser.isSet(controllerOption) || (QCoreApplication::arguments().count() - preAppOptionCount) <= 1)
controller.show();
if (QApplication::topLevelWidgets().isEmpty())
parser.showHelp(0);
return app.exec();
}
#include "main.moc"