From 77de4a9bb4e15d19bd3f87f03f4b2d06a68277b5 Mon Sep 17 00:00:00 2001 From: Volker Hilsheimer Date: Thu, 18 Nov 2021 16:58:01 +0100 Subject: [PATCH] Fix and complete style sheet support for QToolButton Amends 2b2e7b2ac50e5b4f6e1888e594f6e32338dd2a80, which rewrote the rendering to remove the conflation of menu arrows and arrow icons, but introduced double rendering of the arrow icons if only the border was styled. Add a baseline test for style sheets, with a test function for QToolButton configured in various ways and styled with different style sheets. The new test case includes a Qt 5 build system so that we can compare Qt 5.15 with Qt 6. Fixes: QTBUG-98286 Pick-to: 6.2 6.2.2 Change-Id: I09cdc829c1a7e7913df4c3768dbe44b6dba4778b Reviewed-by: Richard Moe Gustavsen Reviewed-by: Eirik Aavitsland --- src/widgets/styles/qstylesheetstyle.cpp | 90 +++--- tests/baseline/CMakeLists.txt | 1 + tests/baseline/stylesheet/CMakeLists.txt | 28 ++ tests/baseline/stylesheet/icons.qrc | 9 + .../stylesheet/icons/align-center.png | Bin 0 -> 1495 bytes .../baseline/stylesheet/icons/align-left.png | Bin 0 -> 1496 bytes .../baseline/stylesheet/icons/align-right.png | Bin 0 -> 1595 bytes tests/baseline/stylesheet/icons/arrow-up.png | Bin 0 -> 5935 bytes tests/baseline/stylesheet/qss/default.qss | 0 .../stylesheet/qss/qtoolbutton/no_border.qss | 1 + .../stylesheet/qss/qtoolbutton/styled.qss | 38 +++ .../qss/qtoolbutton/styled_no_border.qss | 42 +++ tests/baseline/stylesheet/stylesheet.pro | 10 + .../stylesheet/tst_baseline_stylesheet.cpp | 261 ++++++++++++++++++ 14 files changed, 441 insertions(+), 39 deletions(-) create mode 100644 tests/baseline/stylesheet/CMakeLists.txt create mode 100644 tests/baseline/stylesheet/icons.qrc create mode 100644 tests/baseline/stylesheet/icons/align-center.png create mode 100644 tests/baseline/stylesheet/icons/align-left.png create mode 100644 tests/baseline/stylesheet/icons/align-right.png create mode 100644 tests/baseline/stylesheet/icons/arrow-up.png create mode 100644 tests/baseline/stylesheet/qss/default.qss create mode 100644 tests/baseline/stylesheet/qss/qtoolbutton/no_border.qss create mode 100644 tests/baseline/stylesheet/qss/qtoolbutton/styled.qss create mode 100644 tests/baseline/stylesheet/qss/qtoolbutton/styled_no_border.qss create mode 100644 tests/baseline/stylesheet/stylesheet.pro create mode 100644 tests/baseline/stylesheet/tst_baseline_stylesheet.cpp diff --git a/src/widgets/styles/qstylesheetstyle.cpp b/src/widgets/styles/qstylesheetstyle.cpp index 1697832d7f..0784f61747 100644 --- a/src/widgets/styles/qstylesheetstyle.cpp +++ b/src/widgets/styles/qstylesheetstyle.cpp @@ -3274,7 +3274,6 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC rule.configurePalette(&toolOpt.palette, QPalette::ButtonText, QPalette::Button); toolOpt.font = rule.font.resolve(toolOpt.font); toolOpt.rect = rule.borderRect(opt->rect); - bool customArrow = tool->features & QStyleOptionToolButton::Arrow; const auto customArrowElement = [tool]{ switch (tool->arrowType) { case Qt::DownArrow: return PseudoElement_DownArrow; @@ -3285,9 +3284,29 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC } return PseudoElement_None; }; - bool customDropDown = tool->features & QStyleOptionToolButton::MenuButtonPopup; + // if arrow/menu/indicators are requested, either draw them using the available rule, + // or let the base style draw them; but not both + const bool drawArrow = tool->features & QStyleOptionToolButton::Arrow; + bool customArrow = drawArrow && hasStyleRule(w, customArrowElement()); + if (customArrow) { + toolOpt.features &= ~QStyleOptionToolButton::Arrow; + toolOpt.text = QString(); // we need to draw the arrow and the text ourselves + } + const bool drawDropDown = tool->features & QStyleOptionToolButton::MenuButtonPopup; + bool customDropDown = drawDropDown && hasStyleRule(w, PseudoElement_ToolButtonMenu); bool customDropDownArrow = false; - bool customMenuIndicator = !customDropDown && (tool->features & QStyleOptionToolButton::HasMenu); + const bool drawMenuIndicator = tool->features & QStyleOptionToolButton::HasMenu; + if (customDropDown) { + toolOpt.subControls &= ~QStyle::SC_ToolButtonMenu; + customDropDownArrow = hasStyleRule(w, PseudoElement_ToolButtonMenuArrow); + if (customDropDownArrow) + toolOpt.features &= ~(QStyleOptionToolButton::Menu | QStyleOptionToolButton::HasMenu); + } + bool customMenuIndicator = (!customDropDown && drawMenuIndicator) + && hasStyleRule(w, PseudoElement_ToolButtonMenuIndicator); + if (customMenuIndicator) + toolOpt.features &= ~QStyleOptionToolButton::HasMenu; + if (rule.hasNativeBorder()) { if (tool->subControls & SC_ToolButton) { //in some case (eg. the button is "auto raised") the style doesn't draw the background @@ -3301,28 +3320,12 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC if (!(bflags & (State_Sunken | State_On | State_Raised))) rule.drawBackground(p, toolOpt.rect); } - customArrow = customArrow && hasStyleRule(w, customArrowElement()); - if (customArrow) - toolOpt.features &= ~QStyleOptionToolButton::Arrow; - customDropDown = customDropDown && hasStyleRule(w, PseudoElement_ToolButtonMenu); - if (customDropDown) { - toolOpt.subControls &= ~QStyle::SC_ToolButtonMenu; - customDropDownArrow = hasStyleRule(w, PseudoElement_ToolButtonMenuArrow); - if (customDropDownArrow) - toolOpt.features &= ~(QStyleOptionToolButton::Menu | QStyleOptionToolButton::HasMenu); - } - customMenuIndicator = customMenuIndicator && hasStyleRule(w, PseudoElement_ToolButtonMenuIndicator); - if (customMenuIndicator) - toolOpt.features &= ~QStyleOptionToolButton::HasMenu; if (rule.baseStyleCanDraw() && !(tool->features & QStyleOptionToolButton::Arrow)) { baseStyle()->drawComplexControl(cc, &toolOpt, p, w); } else { QWindowsStyle::drawComplexControl(cc, &toolOpt, p, w); } - - if (!customArrow && !customDropDown && !customMenuIndicator) - return; } else { rule.drawRule(p, opt->rect); toolOpt.rect = rule.contentsRect(opt->rect); @@ -3332,7 +3335,7 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC } const QRect cr = toolOpt.rect; - if (customDropDown) { + if (drawDropDown) { if (opt->subControls & QStyle::SC_ToolButtonMenu) { QRenderRule subRule = renderRule(w, opt, PseudoElement_ToolButtonMenu); QRect menuButtonRect = subControlRect(CC_ToolButton, opt, QStyle::SC_ToolButtonMenu, w); @@ -3343,7 +3346,7 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC baseStyle()->drawPrimitive(PE_IndicatorButtonDropDown, &toolOpt, p, w); } - if (customDropDownArrow) { + if (customDropDownArrow || drawMenuIndicator) { QRenderRule arrowRule = renderRule(w, opt, PseudoElement_ToolButtonMenuArrow); QRect arrowRect = arrowRule.hasGeometry() ? positionRect(w, arrowRule, PseudoElement_ToolButtonMenuArrow, menuButtonRect, toolOpt.direction) @@ -3356,7 +3359,7 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC } } } - } else if (customMenuIndicator) { + } else if (drawMenuIndicator) { QRenderRule subRule = renderRule(w, opt, PseudoElement_ToolButtonMenuIndicator); QRect r = subRule.hasGeometry() ? positionRect(w, subRule, PseudoElement_ToolButtonMenuIndicator, toolOpt.rect, toolOpt.direction) @@ -3370,27 +3373,36 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC } toolOpt.rect = cr; + // If we don't have a custom arrow, then the arrow will have been rendered + // already by the base style when drawing the label. if (customArrow) { const auto arrowElement = customArrowElement(); QRenderRule subRule = renderRule(w, opt, arrowElement); - QRect r = subRule.hasGeometry() ? positionRect(w, subRule, arrowElement, toolOpt.rect, toolOpt.direction) - : subRule.contentsRect(toolOpt.rect); - if (subRule.hasDrawable()) { - subRule.drawRule(p, r); - } else { - toolOpt.rect = r; - const auto arrowElement = [&toolOpt] { - switch (toolOpt.arrowType) { - case Qt::DownArrow: return QStyle::PE_IndicatorArrowDown; - case Qt::UpArrow: return QStyle::PE_IndicatorArrowUp; - case Qt::LeftArrow: return QStyle::PE_IndicatorArrowLeft; - case Qt::RightArrow: return QStyle::PE_IndicatorArrowRight; - case Qt::NoArrow: break; - } - return QStyle::PE_IndicatorArrowDown; // never happens - }; - baseStyle()->drawPrimitive(arrowElement(), &toolOpt, p, w); + QRect arrowRect = subRule.hasGeometry() ? positionRect(w, subRule, arrowElement, toolOpt.rect, toolOpt.direction) + : subRule.contentsRect(toolOpt.rect); + + switch (toolOpt.toolButtonStyle) { + case Qt::ToolButtonIconOnly: + break; + case Qt::ToolButtonTextOnly: + case Qt::ToolButtonTextBesideIcon: + case Qt::ToolButtonTextUnderIcon: { + // The base style needs to lay out the contents and will render the styled + // arrow icons, unless the geometry is defined in the style sheet. + toolOpt.text = tool->text; + if (!subRule.hasGeometry()) + toolOpt.features |= QStyleOptionToolButton::Arrow; + drawControl(CE_ToolButtonLabel, &toolOpt, p, w); + if (!subRule.hasGeometry()) + return; + break; } + case Qt::ToolButtonFollowStyle: + // QToolButton handles this, so must never happen + Q_ASSERT(false); + break; + } + subRule.drawRule(p, arrowRect); } return; diff --git a/tests/baseline/CMakeLists.txt b/tests/baseline/CMakeLists.txt index 28a1910fd2..dadfa1ba7d 100644 --- a/tests/baseline/CMakeLists.txt +++ b/tests/baseline/CMakeLists.txt @@ -3,4 +3,5 @@ if(TARGET Qt::Network) endif() if(TARGET Qt::Network AND TARGET Qt::Widgets) add_subdirectory(widgets) + add_subdirectory(stylesheet) endif() diff --git a/tests/baseline/stylesheet/CMakeLists.txt b/tests/baseline/stylesheet/CMakeLists.txt new file mode 100644 index 0000000000..5129f1691c --- /dev/null +++ b/tests/baseline/stylesheet/CMakeLists.txt @@ -0,0 +1,28 @@ +file(GLOB_RECURSE test_data_glob + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + qss/*) +list(APPEND test_data ${test_data_glob}) + +qt_internal_add_test(tst_baseline_stylesheet + SOURCES + ../shared/baselineprotocol.cpp ../shared/baselineprotocol.h ../shared/lookup3.cpp + ../shared/qbaselinetest.cpp ../shared/qbaselinetest.h + tst_baseline_stylesheet.cpp + INCLUDE_DIRECTORIES + ../shared + PUBLIC_LIBRARIES + Qt::Gui + Qt::Widgets + Qt::Network + TESTDATA ${test_data} +) + +qt6_add_resources(tst_baseline_stylesheet "tst_baseline_stylesheet" + PREFIX + "/" + FILES + "icons/align-center.png" + "icons/align-left.png" + "icons/align-right.png" + "icons/arrow-up.png" +) diff --git a/tests/baseline/stylesheet/icons.qrc b/tests/baseline/stylesheet/icons.qrc new file mode 100644 index 0000000000..8b3e7ca6cc --- /dev/null +++ b/tests/baseline/stylesheet/icons.qrc @@ -0,0 +1,9 @@ + + + + icons/align-center.png + icons/align-left.png + icons/align-right.png + icons/arrow-up.png + + diff --git a/tests/baseline/stylesheet/icons/align-center.png b/tests/baseline/stylesheet/icons/align-center.png new file mode 100644 index 0000000000000000000000000000000000000000..a7ce2fad52253d94e8380f1fef2fdb9cf517baf5 GIT binary patch literal 1495 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&zE~)R&4YzkrlviEBhjaDG}zd16s2gJVj5 zQmTSyZen_BP-3NZkcwMxt{voUFyLW1c;oN)oFl>?dsth}lpKgu zvIdz)DVX!<`TcM|o`&xq_3lbAd{bs%Z~z(!bg%#eLj#b-!oZ*aq!<|(I0#9Rs2s@> zJf;KfrXQ$k<(w9%#;`}X#8a45-xKE@JU#_V;SYBDg#*y01iu*#?Nd1q*w6gFlyi`! z31CRk6&R?S{Ww;X#;|8%_{)6^8{`Sb6rQ|8LViL`4tTO3T?6BSw$FwhBgO~03%zHN sR6G#v9Xvh-O5q7xpcLhx=CI-chJx8_`}~C>FVdQ&MBb@02tn7+5i9m literal 0 HcmV?d00001 diff --git a/tests/baseline/stylesheet/icons/align-left.png b/tests/baseline/stylesheet/icons/align-left.png new file mode 100644 index 0000000000000000000000000000000000000000..ad5b975480f5ab09a1d807cc3cf5ec3278f61586 GIT binary patch literal 1496 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&zE~)R&4YzkrlviEBhjaDG}zd16s2gJVj5 zQmTSyZen_BP-3kcwMxt~v4@P~c!VcK9mcd=&QrX+?x{j0YmGt6*jU}ylE$il#&0Hhch7&sUh9DppK4+IEGk*FMD z2_DmdcGC~c5k31{Tbpgq@eN+UfWYG_{89*ykrH}9UHFyLFC2h2HQ0jFg+%rLM*f!^ z3=Tjc{GP^An?bcqT|z?tp}X1JKpf4oLb1 z1jwdz*Uvvsbuqp-Tysf~p#dmBi|ECZ6Yz%~$b}SvhWZ_>5)5-ru~4t%m^dvKb=WA$!*hAYYp3=Tl! zfxZx6U}ylcSQr=-fE3VL4nk5SDo3&ehv@~gC;s`lO_=$?Zz}j2=n0}gfbV7|zFCG0 z+ix3Y@|r&@V9?2CkP~6dEd9>fPz4MPg28~~FO{(qsGDXESE*bF@`&Nz=G$_jKiR`QxU+j}Y$^qy;vO=fFVdQ I&MBb@0Az-tu>b%7 literal 0 HcmV?d00001 diff --git a/tests/baseline/stylesheet/icons/arrow-up.png b/tests/baseline/stylesheet/icons/arrow-up.png new file mode 100644 index 0000000000000000000000000000000000000000..70c43a7c621ce6615fa7972e1a0d33ce7ed52c01 GIT binary patch literal 5935 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 literal 0 HcmV?d00001 diff --git a/tests/baseline/stylesheet/qss/default.qss b/tests/baseline/stylesheet/qss/default.qss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/baseline/stylesheet/qss/qtoolbutton/no_border.qss b/tests/baseline/stylesheet/qss/qtoolbutton/no_border.qss new file mode 100644 index 0000000000..0c9744de7c --- /dev/null +++ b/tests/baseline/stylesheet/qss/qtoolbutton/no_border.qss @@ -0,0 +1 @@ +border: none diff --git a/tests/baseline/stylesheet/qss/qtoolbutton/styled.qss b/tests/baseline/stylesheet/qss/qtoolbutton/styled.qss new file mode 100644 index 0000000000..799be9bf31 --- /dev/null +++ b/tests/baseline/stylesheet/qss/qtoolbutton/styled.qss @@ -0,0 +1,38 @@ +QToolButton::menu-button { + border: 2px solid gray; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + /* 16px width + 4px for border = 20px allocated above */ + width: 16px; +} + +QToolButton::menu-indicator { + image: url(:/icons/arrow-up.png); + width: 16px; + height: 16px; + subcontrol-position: right bottom; +} + +QToolButton::menu-arrow { + subcontrol-position: bottom right; + image: url(:/icons/arrow-up.png); +} +QToolButton::down-arrow { + image: url(:/icons/arrow-up.png); + background-color: blue +} +QToolButton::up-arrow { + image: url(:/icons/arrow-up.png); + background-color: green +} +QToolButton::left-arrow { + image: url(:/icons/arrow-up.png); + background-color: red +} +QToolButton::right-arrow { + image: url(:/icons/arrow-up.png); + background-color: cyan; + width: 15px; + height: 15px; + subcontrol-position: right bottom; +} diff --git a/tests/baseline/stylesheet/qss/qtoolbutton/styled_no_border.qss b/tests/baseline/stylesheet/qss/qtoolbutton/styled_no_border.qss new file mode 100644 index 0000000000..7cb753120f --- /dev/null +++ b/tests/baseline/stylesheet/qss/qtoolbutton/styled_no_border.qss @@ -0,0 +1,42 @@ +QToolButton { + border: none +} + +QToolButton::menu-button { + border: 2px solid gray; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + /* 16px width + 4px for border = 20px allocated above */ + width: 16px; +} + +QToolButton::menu-indicator { + image: url(:/icons/arrow-up.png); + width: 16px; + height: 16px; + subcontrol-position: right bottom; +} + +QToolButton::menu-arrow { + subcontrol-position: bottom right; + image: url(:/icons/arrow-up.png); +} +QToolButton::down-arrow { + image: url(:/icons/arrow-up.png); + background-color: blue +} +QToolButton::up-arrow { + image: url(:/icons/arrow-up.png); + background-color: green +} +QToolButton::left-arrow { + image: url(:/icons/arrow-up.png); + background-color: red +} +QToolButton::right-arrow { + image: url(:/icons/arrow-up.png); + background-color: cyan; + width: 15px; + height: 15px; + subcontrol-position: right bottom; +} diff --git a/tests/baseline/stylesheet/stylesheet.pro b/tests/baseline/stylesheet/stylesheet.pro new file mode 100644 index 0000000000..8c6d79124c --- /dev/null +++ b/tests/baseline/stylesheet/stylesheet.pro @@ -0,0 +1,10 @@ +CONFIG += testcase +TARGET = tst_baseline_stylesheet +QT += widgets testlib gui-private + +SOURCES += tst_baseline_stylesheet.cpp +RESOURCES += icons.qrc + +include($$PWD/../shared/qbaselinetest.pri) + +TESTDATA += qss/* diff --git a/tests/baseline/stylesheet/tst_baseline_stylesheet.cpp b/tests/baseline/stylesheet/tst_baseline_stylesheet.cpp new file mode 100644 index 0000000000..b755237b30 --- /dev/null +++ b/tests/baseline/stylesheet/tst_baseline_stylesheet.cpp @@ -0,0 +1,261 @@ +/**************************************************************************** +** +** 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 + +class tst_Stylesheet : public QObject +{ + Q_OBJECT + +public: + tst_Stylesheet(); + + QWidget *testWindow() const { return window; } + void loadTestFiles(); + +private slots: + void initTestCase(); + void init(); + void cleanup(); + + void tst_QToolButton_data(); + void tst_QToolButton(); + +private: + void makeVisible(); + QImage takeSnapshot(); + QDir styleSheetDir; + + QWidget *window = nullptr; +}; + +tst_Stylesheet::tst_Stylesheet() +{ + QBaselineTest::addClientProperty("Project", "Widgets"); + + // Set key platform properties that are relevant for the appearance of widgets + const QString platformName = QGuiApplication::platformName() + "-" + QSysInfo::productType(); + QBaselineTest::addClientProperty("PlatformName", platformName); + QBaselineTest::addClientProperty("OSVersion", QSysInfo::productVersion()); + + // Encode a number of parameters that impact the UI + QPalette palette; + QFont font; + QByteArray appearanceBytes; + { + QDataStream appearanceStream(&appearanceBytes, QIODevice::WriteOnly); + appearanceStream << palette << font << +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + QApplication::style()->metaObject()->className(); +#else + QApplication::style()->name(); +#endif + const qreal screenDpr = QApplication::primaryScreen()->devicePixelRatio(); + if (screenDpr != 1.0) + qWarning() << "DPR is" << screenDpr << "- images will be scaled"; + } +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + const quint16 appearanceId = qChecksum(appearanceBytes, appearanceBytes.size()); +#else + const quint16 appearanceId = qChecksum(appearanceBytes); +#endif + + // Assume that text that's darker than the background means we run in light mode + // This results in a more meaningful appearance ID between different runs than + // just the checksum of the various attributes. + const QColor windowColor = palette.window().color(); + const QColor textColor = palette.text().color(); + const QString appearanceIdString = (windowColor.value() > textColor.value() + ? QString("light-%1") : QString("dark-%1")) + .arg(appearanceId, 0, 16); + QBaselineTest::addClientProperty("AppearanceID", appearanceIdString); + + // let users know where they can find the results + qDebug() << "PlatformName computed to be:" << platformName; + qDebug() << "Appearance ID computed as:" << appearanceIdString; +} + +void tst_Stylesheet::initTestCase() +{ + QString baseDir = QFINDTESTDATA("qss/default.qss"); + styleSheetDir = QDir(QFileInfo(baseDir).path()); + + // Check and setup the environment. Failure to do so skips the test. + QByteArray msg; + if (!QBaselineTest::connectToBaselineServer(&msg)) + QSKIP(msg); +} + +void tst_Stylesheet::init() +{ + QFETCH(QString, styleSheet); + + QVERIFY(!window); + window = new QWidget; + window->setWindowTitle(QTest::currentDataTag()); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + window->setScreen(QGuiApplication::primaryScreen()); +#endif + window->move(QGuiApplication::primaryScreen()->availableGeometry().topLeft()); + window->setStyleSheet(styleSheet); +} + +void tst_Stylesheet::loadTestFiles() +{ + QTest::addColumn("styleSheet"); + + QStringList qssFiles; + // first add generic test files + for (const auto &qssFile : styleSheetDir.entryList({QStringLiteral("*.qss")}, QDir::Files | QDir::Readable)) + qssFiles << styleSheetDir.absoluteFilePath(qssFile); + + // then test-function specific files + const QString testFunction = QString(QTest::currentTestFunction()).remove("tst_").toLower(); + if (styleSheetDir.cd(testFunction)) { + for (const auto &qssFile : styleSheetDir.entryList({QStringLiteral("*.qss")}, QDir::Files | QDir::Readable)) + qssFiles << styleSheetDir.absoluteFilePath(qssFile); + styleSheetDir.cdUp(); + } + + for (const auto &qssFile : qssFiles) { + QFileInfo fileInfo(qssFile); + QFile file(qssFile); + file.open(QFile::ReadOnly); + QString styleSheet = QString::fromUtf8(file.readAll()); + QBaselineTest::newRow(fileInfo.baseName().toUtf8()) << styleSheet; + } +} + +void tst_Stylesheet::makeVisible() +{ + window->show(); + window->window()->windowHandle()->requestActivate(); + // explicitly unset focus, the test needs to control when focus is shown + if (window->focusWidget()) + window->focusWidget()->clearFocus(); + QVERIFY(QTest::qWaitForWindowActive(window)); +} + +/* + Always return images scaled to a DPR of 1.0. + + This might produce some fuzzy differences, but lets us + compare those. +*/ +QImage tst_Stylesheet::takeSnapshot() +{ + QGuiApplication::processEvents(); + QPixmap pm = window->grab(); + QTransform scaleTransform = QTransform::fromScale(1.0 / pm.devicePixelRatioF(), 1.0 / pm.devicePixelRatioF()); + return pm.toImage().transformed(scaleTransform, Qt::SmoothTransformation); +} + +void tst_Stylesheet::cleanup() +{ + delete window; + window = nullptr; +} + +void tst_Stylesheet::tst_QToolButton_data() +{ + loadTestFiles(); +} + +void tst_Stylesheet::tst_QToolButton() +{ + const QIcon fileIcon = QApplication::style()->standardIcon(QStyle::SP_FileIcon); + + QVBoxLayout *vbox = new QVBoxLayout; + + QHBoxLayout *normalButtons = new QHBoxLayout; + for (const auto &buttonStyle : {Qt::ToolButtonIconOnly, Qt::ToolButtonTextOnly, + Qt::ToolButtonTextUnderIcon, Qt::ToolButtonTextBesideIcon}) { + QToolButton *normal = new QToolButton; + normal->setToolButtonStyle(buttonStyle); + normal->setText("Text"); + normal->setIcon(fileIcon); + normalButtons->addWidget(normal); + } + vbox->addLayout(normalButtons); + + QHBoxLayout *arrowButtons = new QHBoxLayout; + for (const auto &arrowType : {Qt::LeftArrow, Qt::RightArrow, Qt::UpArrow, Qt::DownArrow}) { + QToolButton *arrow = new QToolButton; + arrow->setText("Text"); + arrow->setArrowType(arrowType); + arrowButtons->addWidget(arrow); + } + vbox->addLayout(arrowButtons); + + QHBoxLayout *arrowWithTextButtons = new QHBoxLayout; + for (const auto &buttonStyle : {Qt::ToolButtonTextOnly, + Qt::ToolButtonTextUnderIcon, Qt::ToolButtonTextBesideIcon}) { + QToolButton *arrow = new QToolButton; + arrow->setText("Text"); + arrow->setArrowType(Qt::UpArrow); + arrow->setToolButtonStyle(buttonStyle); + arrowWithTextButtons->addWidget(arrow); + } + vbox->addLayout(arrowWithTextButtons); + + QHBoxLayout *menuButtons = new QHBoxLayout; + for (const auto &popupMode : {QToolButton::InstantPopup, QToolButton::MenuButtonPopup, + QToolButton::DelayedPopup}) { + QToolButton *menuButton = new QToolButton; + menuButton->setText("Text"); + menuButton->setIcon(fileIcon); + QMenu *menuButtonMenu = new QMenu; + menuButtonMenu->addAction(QIcon(":/icons/align-left.png"), "Left"); + menuButtonMenu->addAction(QIcon(":/icons/align-right.png"), "Right"); + menuButtonMenu->addAction(QIcon(":/icons/align-center.png"), "Center"); + menuButton->setMenu(menuButtonMenu); + menuButton->setPopupMode(popupMode); + menuButtons->addWidget(menuButton); + } + vbox->addLayout(menuButtons); + testWindow()->setLayout(vbox); + + makeVisible(); + QBASELINE_TEST(takeSnapshot()); +} + +#define main _realmain +QTEST_MAIN(tst_Stylesheet) +#undef main + +int main(int argc, char *argv[]) +{ + qSetGlobalQHashSeed(0); // Avoid rendering variations caused by QHash randomization + + QBaselineTest::handleCmdLineArgs(&argc, &argv); + return _realmain(argc, argv); +} + +#include "tst_baseline_stylesheet.moc"