From 62bd4f5852137276e354746737a88c06168c4b8b Mon Sep 17 00:00:00 2001 From: Paul Olav Tvete Date: Thu, 18 Jun 2015 17:00:27 +0200 Subject: Extend high-DPI manual test Add several new tests to verify that Qt behaves properly when high-DPI scaling is enabled. Add a generic framework for manual tests for good measure. This could be refactored and used for other manual tests later. Includes tests written by Morten and Friedemann. Task-number: QTBUG-46615 Change-Id: Ib6762ec1454711e71f0c094b19015932b99e8d6d Reviewed-by: Friedemann Kleint --- tests/manual/highdpi/main.cpp | 725 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 653 insertions(+), 72 deletions(-) (limited to 'tests/manual/highdpi/main.cpp') diff --git a/tests/manual/highdpi/main.cpp b/tests/manual/highdpi/main.cpp index fd14523a97..692a60d511 100644 --- a/tests/manual/highdpi/main.cpp +++ b/tests/manual/highdpi/main.cpp @@ -32,6 +32,7 @@ ****************************************************************************/ #include +#include #include #include #include @@ -39,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -49,10 +51,219 @@ #include #include #include +#include #include +#include #include #include +#include +#include +#include "dragwidget.h" + +class DemoContainerBase +{ +public: + DemoContainerBase() : m_widget(0) {} + virtual ~DemoContainerBase() {} + QString name() { return option().names().first(); } + virtual QCommandLineOption &option() = 0; + virtual void makeVisible(bool visible, QWidget *parent) = 0; + QWidget *widget() { return m_widget; } +protected: + QWidget *m_widget; +}; + +typedef QList DemoContainerList ; + + +template +class DemoContainer : public DemoContainerBase +{ +public: + DemoContainer(const QString &optionName, const QString &description) + : m_option(optionName, description) + { + } + ~DemoContainer() { delete m_widget; } + + QCommandLineOption &option() { return m_option; } + + void makeVisible(bool visible, QWidget *parent) { + if (visible && !m_widget) { + m_widget = new T; + 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(".")) + 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 = 0; + return QHighDpiScaling::factor(noScreen); +} + +class DemoController : public QWidget +{ +Q_OBJECT +public: + DemoController(DemoContainerList *demos, QCommandLineParser *parser); + ~DemoController(); +protected: + bool eventFilter(QObject *object, QEvent *event); + void closeEvent(QCloseEvent *) { qApp->quit(); } +private slots: + void handleButton(int id, bool toggled); +private: + DemoContainerList *m_demos; + QButtonGroup *m_group; +}; + +DemoController::DemoController(DemoContainerList *demos, QCommandLineParser *parser) + : m_demos(demos) +{ + setWindowTitle("screen scale factors"); + setObjectName("controller"); // make WindowScaleFactorSetter skip this window + + QGridLayout *layout = new QGridLayout; + setLayout(layout); + + int layoutRow = 0; + LabelSlider *globalScaleSlider = new LabelSlider(this, "Global scale factor", layout, 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 + QList screens = QGuiApplication::screens(); + foreach (QScreen *screen, screens) { + // create scale control line + QSize screenSize = screen->geometry().size(); + QString screenId = screen->name() + " " + QString::number(screenSize.width()) + + " " + QString::number(screenSize.height()); + LabelSlider *slider = new LabelSlider(this, screenId, layout, 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; + }); + } + + m_group = new QButtonGroup(this); + m_group->setExclusive(false); + + for (int i = 0; i < m_demos->size(); ++i) { + DemoContainerBase *demo = m_demos->at(i); + QPushButton *button = new QPushButton(demo->name()); + button->setToolTip(demo->option().description()); + button->setCheckable(true); + layout->addWidget(button, layoutRow++, 0, 1, -1); + m_group->addButton(button, i); + + if (parser->isSet(demo->option())) { + demo->makeVisible(true, this); + button->setChecked(true); + } + } + connect(m_group, SIGNAL(buttonToggled(int, bool)), this, SLOT(handleButton(int, bool))); +} + +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 { @@ -69,7 +280,6 @@ public: QIcon qtIcon; }; - PixmapPainter::PixmapPainter() { pixmap1X = QPixmap(":/qticon32.png"); @@ -172,15 +382,18 @@ class MainWindow : public QMainWindow { public: MainWindow(); + QMenu *addNewMenu(const QString &title, int itemCount = 5); QIcon qtIcon; QIcon qtIcon1x; QIcon qtIcon2x; QToolBar *fileToolBar; + int menuCount; }; MainWindow::MainWindow() + :menuCount(0) { // beware that QIcon auto-loads the @2x versions. qtIcon1x.addFile(":/qticon16.png"); @@ -192,8 +405,33 @@ MainWindow::MainWindow() // 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); + addNewMenu("&Transmogrify", 7); + 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; +} + + class StandardIcons : public QWidget { public: @@ -205,7 +443,7 @@ public: int dy = 50; int maxX = 500; - for (int iconIndex = QStyle::SP_TitleBarMenuButton; iconIndex < QStyle::SP_MediaVolumeMuted; ++iconIndex) { + 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)); @@ -295,14 +533,27 @@ public: void paintEvent(QPaintEvent *) { QPainter painter(this); - int y = 40; - for (int fontSize = 2; fontSize < 18; fontSize += 2) { + + // Points + int y = 10; + for (int fontSize = 6; fontSize < 18; fontSize += 2) { QFont font; font.setPointSize(fontSize); - QString string = QString(QStringLiteral("%1 The quick brown fox jumped over the lazy Doug.")).arg(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); - y += (fontSize * 2.5); } } }; @@ -461,97 +712,427 @@ public: } }; +class LinePainter : public QWidget +{ +public: + void paintEvent(QPaintEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + + QPoint lastMousePoint; + QVector linePoints; +}; -int main(int argc, char **argv) +void LinePainter::paintEvent(QPaintEvent *) { - QApplication app(argc, argv); - QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); - QCoreApplication::setApplicationVersion(QT_VERSION_STR); + QPainter p(this); + p.fillRect(QRect(QPoint(0, 0), size()), QBrush(Qt::gray)); - QCommandLineParser parser; - parser.setApplicationDescription("High DPI tester"); - parser.addHelpOption(); - parser.addVersionOption(); - QCommandLineOption pixmapPainterOption("pixmap", "Test pixmap painter"); - parser.addOption(pixmapPainterOption); - QCommandLineOption labelOption("label", "Test Labels"); - parser.addOption(labelOption); - QCommandLineOption mainWindowOption("mainwindow", "Test QMainWindow"); - parser.addOption(mainWindowOption); - QCommandLineOption standardIconsOption("standard-icons", "Test standard icons"); - parser.addOption(standardIconsOption); - QCommandLineOption cachingOption("caching", "Test caching"); - parser.addOption(cachingOption); - QCommandLineOption styleOption("styles", "Test style"); - parser.addOption(styleOption); - QCommandLineOption fontsOption("fonts", "Test fonts"); - parser.addOption(fontsOption); - QCommandLineOption iconDrawingOption("icondrawing", "Test icon drawing"); - parser.addOption(iconDrawingOption); - QCommandLineOption buttonsOption("buttons", "Test buttons"); - parser.addOption(buttonsOption); + // 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); +} - parser.process(app); +void LinePainter::mousePressEvent(QMouseEvent *event) +{ + lastMousePoint = event->pos(); +} - QScopedPointer pixmapPainter; - if (parser.isSet(pixmapPainterOption)) { - pixmapPainter.reset(new PixmapPainter); - pixmapPainter->show(); +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() + :moveLabel(0), moving(false) + { } - QScopedPointer label; - if (parser.isSet(labelOption)) { - label.reset(new Labels); - label->resize(200, 200); - label->show(); + inline QRect getRect(int idx) const + { + int h = height() / 2; + return QRect(10, 10 + h * (idx - 1), width() - 20, h - 20); + } + void paintEvent(QPaintEvent *) + { + 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"); + } } - QScopedPointer mainWindow; - if (parser.isSet(mainWindowOption)) { - mainWindow.reset(new MainWindow); - mainWindow->show(); + void mousePressEvent(QMouseEvent *e) + { + 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(); } - QScopedPointer icons; - if (parser.isSet(standardIconsOption)) { - icons.reset(new StandardIcons); - icons->resize(510, 510); - icons->show(); + void mouseReleaseEvent(QMouseEvent *) + { + if (moveLabel) + moveLabel->hide(); + update(); + moving = false; } - QScopedPointer caching; - if (parser.isSet(cachingOption)) { - caching.reset(new Caching); - caching->resize(300, 300); - caching->show(); + void mouseMoveEvent(QMouseEvent *e) + { + if (!moving) + return; + QPoint pos = useCursorPos ? QCursor::pos() : e->globalPos(); + pos -= moveLabel->rect().center(); + moveLabel->move(pos); + mousePos = e->pos(); + update(); } - QScopedPointer