1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2019 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | |
30 | #include <QtTest/QtTest> |
31 | #include <qlayout.h> |
32 | #include "qstyle.h" |
33 | #include <qevent.h> |
34 | #include <qpainter.h> |
35 | #include <qpixmap.h> |
36 | #include <qapplication.h> |
37 | #include <qwidget.h> |
38 | #include <qlabel.h> |
39 | #include <qstyleoption.h> |
40 | #include <qscrollbar.h> |
41 | #include <qprogressbar.h> |
42 | #include <qtoolbutton.h> |
43 | #include <qtoolbar.h> |
44 | |
45 | #include <qcommonstyle.h> |
46 | #include <qproxystyle.h> |
47 | #include <qstylefactory.h> |
48 | |
49 | #include <qimagereader.h> |
50 | #include <qimagewriter.h> |
51 | #include <qmenu.h> |
52 | #include <qpushbutton.h> |
53 | #include <qspinbox.h> |
54 | #include <qcombobox.h> |
55 | #include <qradiobutton.h> |
56 | #include <qlineedit.h> |
57 | #include <qmdiarea.h> |
58 | #include <qscrollarea.h> |
59 | #include <qwidget.h> |
60 | |
61 | #include <algorithm> |
62 | |
63 | #include <QtTest/private/qtesthelpers_p.h> |
64 | |
65 | using namespace QTestPrivate; |
66 | |
67 | class tst_QStyle : public QObject |
68 | { |
69 | Q_OBJECT |
70 | |
71 | private slots: |
72 | void drawItemPixmap(); |
73 | void cleanup(); |
74 | #ifndef QT_NO_STYLE_FUSION |
75 | void testFusionStyle(); |
76 | #endif |
77 | void testWindowsStyle(); |
78 | #if defined(Q_OS_WIN) && !defined(QT_NO_STYLE_WINDOWSVISTA) && !defined(Q_OS_WINRT) |
79 | void testWindowsVistaStyle(); |
80 | #endif |
81 | #ifdef Q_OS_MAC |
82 | void testMacStyle(); |
83 | #endif |
84 | void testStyleFactory(); |
85 | void testProxyStyle(); |
86 | void pixelMetric(); |
87 | #if !defined(QT_NO_STYLE_WINDOWS) && !defined(QT_NO_STYLE_FUSION) |
88 | void progressBarChangeStyle(); |
89 | #endif |
90 | void defaultFont(); |
91 | void testDrawingShortcuts(); |
92 | void testFrameOnlyAroundContents(); |
93 | |
94 | void testProxyCalled(); |
95 | void testStyleOptionInit(); |
96 | private: |
97 | bool testAllFunctions(QStyle *); |
98 | bool testScrollBarSubControls(const QStyle *style); |
99 | void testPainting(QStyle *style, const QString &platform); |
100 | void lineUpLayoutTest(QStyle *); |
101 | }; |
102 | |
103 | class MyWidget : public QWidget |
104 | { |
105 | public: |
106 | using QWidget::QWidget; |
107 | |
108 | protected: |
109 | void paintEvent(QPaintEvent *) override; |
110 | }; |
111 | |
112 | void tst_QStyle::cleanup() |
113 | { |
114 | QVERIFY(QApplication::topLevelWidgets().isEmpty()); |
115 | } |
116 | |
117 | void tst_QStyle::testStyleFactory() |
118 | { |
119 | const QStringList keys = QStyleFactory::keys(); |
120 | #ifndef QT_NO_STYLE_FUSION |
121 | QVERIFY(keys.contains("Fusion" )); |
122 | #endif |
123 | #ifndef QT_NO_STYLE_WINDOWS |
124 | QVERIFY(keys.contains("Windows" )); |
125 | #endif |
126 | |
127 | for (const QString &styleName : keys) { |
128 | QScopedPointer<QStyle> style(QStyleFactory::create(styleName)); |
129 | QVERIFY2(!style.isNull(), |
130 | qPrintable(QString::fromLatin1("Fail to load style '%1'" ).arg(styleName))); |
131 | } |
132 | } |
133 | |
134 | class CustomProxy : public QProxyStyle |
135 | { |
136 | int pixelMetric(PixelMetric metric, const QStyleOption *option = nullptr, |
137 | const QWidget *widget = nullptr) const override |
138 | { |
139 | if (metric == QStyle::PM_ButtonIconSize) |
140 | return 13; |
141 | return QProxyStyle::pixelMetric(metric, option, widget); |
142 | } |
143 | }; |
144 | |
145 | void tst_QStyle::testProxyStyle() |
146 | { |
147 | QProxyStyle *proxyStyle = new QProxyStyle(); |
148 | QVERIFY(proxyStyle->baseStyle()); |
149 | QStyle *style = QStyleFactory::create("Windows" ); |
150 | QCOMPARE(style->proxy(), style); |
151 | |
152 | proxyStyle->setBaseStyle(style); |
153 | QCOMPARE(style->proxy(), proxyStyle); |
154 | QCOMPARE(style->parent(), proxyStyle); |
155 | QCOMPARE(proxyStyle->baseStyle(), style); |
156 | |
157 | QVERIFY(testAllFunctions(proxyStyle)); |
158 | proxyStyle->setBaseStyle(nullptr); |
159 | QVERIFY(proxyStyle->baseStyle()); |
160 | QApplication::setStyle(proxyStyle); |
161 | |
162 | QProxyStyle* baseStyle = new QProxyStyle("Windows" ); |
163 | QCOMPARE(baseStyle->baseStyle()->objectName(), style->objectName()); |
164 | |
165 | QProxyStyle doubleProxy(baseStyle); |
166 | QVERIFY(testAllFunctions(&doubleProxy)); |
167 | |
168 | CustomProxy customStyle; |
169 | QLineEdit edit; |
170 | edit.setStyle(&customStyle); |
171 | QVERIFY(!customStyle.parent()); |
172 | QCOMPARE(edit.style()->pixelMetric(QStyle::PM_ButtonIconSize), 13); |
173 | } |
174 | |
175 | void tst_QStyle::drawItemPixmap() |
176 | { |
177 | MyWidget testWidget; |
178 | testWidget.setObjectName("testObject" ); |
179 | testWidget.resize(w: 300, h: 300); |
180 | testWidget.showNormal(); |
181 | |
182 | QImage image = testWidget.grab().toImage(); |
183 | const QRgb green = QColor(Qt::green).rgb(); |
184 | QVERIFY(image.reinterpretAsFormat(QImage::Format_RGB32)); |
185 | const QRgb *bits = reinterpret_cast<const QRgb *>(image.constBits()); |
186 | const QRgb *end = bits + image.sizeInBytes() / sizeof(QRgb); |
187 | #ifdef Q_OS_WINRT |
188 | QEXPECT_FAIL("" , "QWidget::resize does not work on WinRT" , Continue); |
189 | #endif |
190 | QVERIFY(std::all_of(bits, end, [green] (QRgb r) { return r == green; })); |
191 | } |
192 | |
193 | bool tst_QStyle::testAllFunctions(QStyle *style) |
194 | { |
195 | QStyleOption opt; |
196 | QWidget testWidget; |
197 | opt.init(w: &testWidget); |
198 | |
199 | testWidget.setStyle(style); |
200 | |
201 | //Tests styleHint with default arguments for potential crashes |
202 | for ( int hint = 0 ; hint < int(QStyle::SH_Menu_Mask); ++hint) { |
203 | style->styleHint(stylehint: QStyle::StyleHint(hint)); |
204 | style->styleHint(stylehint: QStyle::StyleHint(hint), opt: &opt, widget: &testWidget); |
205 | } |
206 | |
207 | //Tests pixelMetric with default arguments for potential crashes |
208 | for ( int pm = 0 ; pm < int(QStyle::PM_LayoutVerticalSpacing); ++pm) { |
209 | style->pixelMetric(metric: QStyle::PixelMetric(pm)); |
210 | style->pixelMetric(metric: QStyle::PixelMetric(pm), option: &opt, widget: &testWidget); |
211 | } |
212 | |
213 | //Tests drawControl with default arguments for potential crashes |
214 | for ( int control = 0 ; control < int(QStyle::CE_ColumnViewGrip); ++control) { |
215 | QPixmap surface(QSize(200, 200)); |
216 | QPainter painter(&surface); |
217 | style->drawControl(element: QStyle::ControlElement(control), opt: &opt, p: &painter, w: nullptr); |
218 | } |
219 | |
220 | //Tests drawComplexControl with default arguments for potential crashes |
221 | { |
222 | QPixmap surface(QSize(200, 200)); |
223 | QPainter painter(&surface); |
224 | QStyleOptionComboBox copt1; |
225 | copt1.init(w: &testWidget); |
226 | |
227 | QStyleOptionGroupBox copt2; |
228 | copt2.init(w: &testWidget); |
229 | QStyleOptionSizeGrip copt3; |
230 | copt3.init(w: &testWidget); |
231 | QStyleOptionSlider copt4; |
232 | copt4.init(w: &testWidget); |
233 | copt4.minimum = 0; |
234 | copt4.maximum = 100; |
235 | copt4.tickInterval = 25; |
236 | copt4.sliderValue = 50; |
237 | QStyleOptionSpinBox copt5; |
238 | copt5.init(w: &testWidget); |
239 | QStyleOptionTitleBar copt6; |
240 | copt6.init(w: &testWidget); |
241 | QStyleOptionToolButton copt7; |
242 | copt7.init(w: &testWidget); |
243 | QStyleOptionComplex copt9; |
244 | copt9.initFrom(w: &testWidget); |
245 | |
246 | style->drawComplexControl(cc: QStyle::CC_SpinBox, opt: &copt5, p: &painter, widget: nullptr); |
247 | style->drawComplexControl(cc: QStyle::CC_ComboBox, opt: &copt1, p: &painter, widget: nullptr); |
248 | style->drawComplexControl(cc: QStyle::CC_ScrollBar, opt: &copt4, p: &painter, widget: nullptr); |
249 | style->drawComplexControl(cc: QStyle::CC_Slider, opt: &copt4, p: &painter, widget: nullptr); |
250 | style->drawComplexControl(cc: QStyle::CC_ToolButton, opt: &copt7, p: &painter, widget: nullptr); |
251 | style->drawComplexControl(cc: QStyle::CC_TitleBar, opt: &copt6, p: &painter, widget: nullptr); |
252 | style->drawComplexControl(cc: QStyle::CC_GroupBox, opt: &copt2, p: &painter, widget: nullptr); |
253 | style->drawComplexControl(cc: QStyle::CC_Dial, opt: &copt4, p: &painter, widget: nullptr); |
254 | } |
255 | |
256 | //Check standard pixmaps/icons |
257 | for ( int i = 0 ; i < int(QStyle::SP_ToolBarVerticalExtensionButton); ++i) { |
258 | QPixmap pixmap = style->standardPixmap(standardPixmap: QStyle::StandardPixmap(i)); |
259 | if (pixmap.isNull()) { |
260 | qWarning(msg: "missing StandardPixmap: %d" , i); |
261 | } |
262 | QIcon icn = style->standardIcon(standardIcon: QStyle::StandardPixmap(i)); |
263 | if (icn.isNull()) { |
264 | qWarning(msg: "missing StandardIcon: %d" , i); |
265 | } |
266 | } |
267 | |
268 | style->itemPixmapRect(r: QRect(0, 0, 100, 100), flags: Qt::AlignHCenter, pixmap: QPixmap(200, 200)); |
269 | style->itemTextRect(fm: QFontMetrics(QApplication::font()), r: QRect(0, 0, 100, 100), |
270 | flags: Qt::AlignHCenter, enabled: true, text: QLatin1String("Test" )); |
271 | |
272 | return testScrollBarSubControls(style); |
273 | } |
274 | |
275 | bool tst_QStyle::testScrollBarSubControls(const QStyle *style) |
276 | { |
277 | const bool isMacStyle = style->objectName().compare(other: QLatin1String("macintosh" ), |
278 | cs: Qt::CaseInsensitive) == 0; |
279 | QScrollBar scrollBar; |
280 | setFrameless(&scrollBar); |
281 | scrollBar.show(); |
282 | const QStyleOptionSlider opt = qt_qscrollbarStyleOption(scrollBar: &scrollBar); |
283 | for (int sc : {1, 2, 4, 8}) { |
284 | const auto subControl = static_cast<QStyle::SubControl>(sc); |
285 | const QRect sr = style->subControlRect(cc: QStyle::CC_ScrollBar, opt: &opt, sc: subControl, widget: &scrollBar); |
286 | if (sr.isNull()) { |
287 | // macOS scrollbars no longer have these, so there's no reason to fail |
288 | if (!(isMacStyle && (subControl == QStyle::SC_ScrollBarAddLine || |
289 | subControl == QStyle::SC_ScrollBarSubLine))) { |
290 | qWarning() << "Unexpected null rect for subcontrol" << subControl; |
291 | return false; |
292 | } |
293 | } |
294 | } |
295 | return true; |
296 | } |
297 | |
298 | #ifndef QT_NO_STYLE_FUSION |
299 | void tst_QStyle::testFusionStyle() |
300 | { |
301 | QScopedPointer<QStyle> fstyle(QStyleFactory::create("Fusion" )); |
302 | QVERIFY(!fstyle.isNull()); |
303 | QVERIFY(testAllFunctions(fstyle.data())); |
304 | lineUpLayoutTest(fstyle.data()); |
305 | } |
306 | #endif |
307 | |
308 | void tst_QStyle::testWindowsStyle() |
309 | { |
310 | QScopedPointer<QStyle> wstyle(QStyleFactory::create("Windows" )); |
311 | QVERIFY(!wstyle.isNull()); |
312 | QVERIFY(testAllFunctions(wstyle.data())); |
313 | lineUpLayoutTest(wstyle.data()); |
314 | |
315 | // Tests drawing indeterminate progress with 0 size: QTBUG-15973 |
316 | QStyleOptionProgressBar pb; |
317 | pb.rect = QRect(0,0,-9,0); |
318 | QPixmap surface(QSize(200, 200)); |
319 | QPainter painter(&surface); |
320 | wstyle->drawControl(element: QStyle::CE_ProgressBar, opt: &pb, p: &painter, w: nullptr); |
321 | } |
322 | |
323 | #if defined(Q_OS_WIN) && !defined(QT_NO_STYLE_WINDOWSVISTA) && !defined(Q_OS_WINRT) |
324 | void tst_QStyle::testWindowsVistaStyle() |
325 | { |
326 | QScopedPointer<QStyle> vistastyle(QStyleFactory::create("WindowsVista" )); |
327 | QVERIFY(!vistastyle.isNull()); |
328 | QVERIFY(testAllFunctions(vistastyle.data())); |
329 | } |
330 | #endif |
331 | |
332 | #ifdef Q_OS_MAC |
333 | void tst_QStyle::testMacStyle() |
334 | { |
335 | QStyle *mstyle = QStyleFactory::create("Macintosh" ); |
336 | QVERIFY(testAllFunctions(mstyle)); |
337 | delete mstyle; |
338 | } |
339 | #endif |
340 | |
341 | // Helper class... |
342 | void MyWidget::paintEvent(QPaintEvent *) |
343 | { |
344 | QPainter p(this); |
345 | QPixmap big(400,400); |
346 | big.fill(fillColor: Qt::green); |
347 | style()->drawItemPixmap(painter: &p, rect: rect(), alignment: Qt::AlignCenter, pixmap: big); |
348 | } |
349 | |
350 | |
351 | class Qt42Style : public QCommonStyle |
352 | { |
353 | Q_OBJECT |
354 | public: |
355 | int pixelMetric(PixelMetric metric, const QStyleOption *option = nullptr, |
356 | const QWidget *widget = nullptr) const override; |
357 | |
358 | int margin_toplevel = 10; |
359 | int margin = 5; |
360 | int spacing = 0; |
361 | }; |
362 | |
363 | int Qt42Style::pixelMetric(PixelMetric metric, const QStyleOption * /* option = 0*/, |
364 | const QWidget * /* widget = 0*/ ) const |
365 | { |
366 | switch (metric) { |
367 | case QStyle::PM_DefaultTopLevelMargin: |
368 | return margin_toplevel; |
369 | case QStyle::PM_DefaultChildMargin: |
370 | return margin; |
371 | case QStyle::PM_DefaultLayoutSpacing: |
372 | return spacing; |
373 | default: |
374 | break; |
375 | } |
376 | return -1; |
377 | } |
378 | |
379 | |
380 | void tst_QStyle::pixelMetric() |
381 | { |
382 | QScopedPointer<Qt42Style> style(new Qt42Style); |
383 | QCOMPARE(style->pixelMetric(QStyle::PM_DefaultTopLevelMargin), 10); |
384 | QCOMPARE(style->pixelMetric(QStyle::PM_DefaultChildMargin), 5); |
385 | QCOMPARE(style->pixelMetric(QStyle::PM_DefaultLayoutSpacing), 0); |
386 | |
387 | style->margin_toplevel = 0; |
388 | style->margin = 0; |
389 | style->spacing = 0; |
390 | QCOMPARE(style->pixelMetric(QStyle::PM_DefaultTopLevelMargin), 0); |
391 | QCOMPARE(style->pixelMetric(QStyle::PM_DefaultChildMargin), 0); |
392 | QCOMPARE(style->pixelMetric(QStyle::PM_DefaultLayoutSpacing), 0); |
393 | |
394 | style->margin_toplevel = -1; |
395 | style->margin = -1; |
396 | style->spacing = -1; |
397 | QCOMPARE(style->pixelMetric(QStyle::PM_DefaultTopLevelMargin), -1); |
398 | QCOMPARE(style->pixelMetric(QStyle::PM_DefaultChildMargin), -1); |
399 | QCOMPARE(style->pixelMetric(QStyle::PM_DefaultLayoutSpacing), -1); |
400 | } |
401 | |
402 | #if !defined(QT_NO_STYLE_WINDOWS) && !defined(QT_NO_STYLE_FUSION) |
403 | void tst_QStyle::progressBarChangeStyle() |
404 | { |
405 | //test a crashing situation (task 143530) |
406 | //where changing the styles and deleting a progressbar would crash |
407 | |
408 | QStyle *style1 = QStyleFactory::create("Windows" ); |
409 | QStyle *style2 = QStyleFactory::create("Fusion" ); |
410 | |
411 | QProgressBar *progress=new QProgressBar; |
412 | progress->setStyle(style1); |
413 | |
414 | progress->show(); |
415 | |
416 | progress->setStyle(style2); |
417 | |
418 | QTest::qWait(ms: 100); |
419 | delete progress; |
420 | |
421 | QTest::qWait(ms: 100); |
422 | |
423 | //before the correction, there would be a crash here |
424 | delete style1; |
425 | delete style2; |
426 | } |
427 | #endif |
428 | |
429 | void tst_QStyle::lineUpLayoutTest(QStyle *style) |
430 | { |
431 | QWidget widget; |
432 | setFrameless(&widget); |
433 | QHBoxLayout layout; |
434 | QFont font; |
435 | font.setPointSize(9); //Plastique is lined up for odd numbers... |
436 | widget.setFont(font); |
437 | QSpinBox spinbox(&widget); |
438 | QLineEdit lineedit(&widget); |
439 | QComboBox combo(&widget); |
440 | combo.setEditable(true); |
441 | layout.addWidget(&spinbox); |
442 | layout.addWidget(&lineedit); |
443 | layout.addWidget(&combo); |
444 | widget.setLayout(&layout); |
445 | widget.setStyle(style); |
446 | // propagate the style. |
447 | const auto children = widget.findChildren<QWidget *>(); |
448 | for (QWidget *w : children) |
449 | w->setStyle(style); |
450 | widget.show(); |
451 | QVERIFY(QTest::qWaitForWindowExposed(&widget)); |
452 | |
453 | #ifdef Q_OS_WIN |
454 | const int limit = 2; // Aero style has larger margins |
455 | #else |
456 | const int limit = 1; |
457 | #endif |
458 | const int slDiff = qAbs(t: spinbox.height() - lineedit.height()); |
459 | const int scDiff = qAbs(t: spinbox.height() - combo.height()); |
460 | QVERIFY2(slDiff <= limit, |
461 | qPrintable(QString::fromLatin1("%1 exceeds %2 for %3" ) |
462 | .arg(slDiff).arg(limit).arg(style->objectName()))); |
463 | QVERIFY2(scDiff <= limit, |
464 | qPrintable(QString::fromLatin1("%1 exceeds %2 for %3" ) |
465 | .arg(scDiff).arg(limit).arg(style->objectName()))); |
466 | } |
467 | |
468 | void tst_QStyle::defaultFont() |
469 | { |
470 | QFont defaultFont = QApplication::font(); |
471 | QFont pointFont = defaultFont; |
472 | pointFont.setPixelSize(9); |
473 | QApplication::setFont(pointFont); |
474 | QPushButton button; |
475 | setFrameless(&button); |
476 | button.show(); |
477 | QCoreApplication::processEvents(); |
478 | QApplication::setFont(defaultFont); |
479 | } |
480 | |
481 | class DrawTextStyle : public QProxyStyle |
482 | { |
483 | Q_OBJECT |
484 | public: |
485 | using QProxyStyle::QProxyStyle; |
486 | |
487 | void drawItemText(QPainter *painter, const QRect &rect, |
488 | int flags, const QPalette &pal, bool enabled, |
489 | const QString &text, QPalette::ColorRole textRole = QPalette::NoRole) const override |
490 | { |
491 | alignment = flags; |
492 | QProxyStyle::drawItemText(painter, rect, flags, pal, enabled, text, textRole); |
493 | } |
494 | |
495 | mutable int alignment = 0; |
496 | }; |
497 | |
498 | |
499 | void tst_QStyle::testDrawingShortcuts() |
500 | { |
501 | { |
502 | QWidget w; |
503 | setFrameless(&w); |
504 | QToolButton *tb = new QToolButton(&w); |
505 | tb->setText("&abc" ); |
506 | QScopedPointer<DrawTextStyle> dts(new DrawTextStyle); |
507 | w.show(); |
508 | tb->setStyle(dts.data()); |
509 | tb->grab(); |
510 | QStyleOptionToolButton sotb; |
511 | sotb.initFrom(w: tb); |
512 | bool showMnemonic = dts->styleHint(hint: QStyle::SH_UnderlineShortcut, option: &sotb, widget: tb); |
513 | QVERIFY(dts->alignment & (showMnemonic ? Qt::TextShowMnemonic : Qt::TextHideMnemonic)); |
514 | } |
515 | { |
516 | QToolBar w; |
517 | setFrameless(&w); |
518 | QToolButton *tb = new QToolButton(&w); |
519 | tb->setText("&abc" ); |
520 | QScopedPointer<DrawTextStyle> dts(new DrawTextStyle); |
521 | w.addWidget(widget: tb); |
522 | w.show(); |
523 | tb->setStyle(dts.data()); |
524 | tb->grab(); |
525 | QStyleOptionToolButton sotb; |
526 | sotb.initFrom(w: tb); |
527 | bool showMnemonic = dts->styleHint(hint: QStyle::SH_UnderlineShortcut, option: &sotb, widget: tb); |
528 | QVERIFY(dts->alignment & (showMnemonic ? Qt::TextShowMnemonic : Qt::TextHideMnemonic)); |
529 | } |
530 | } |
531 | |
532 | static const int SCROLLBAR_SPACING = 33; |
533 | |
534 | class FrameTestStyle : public QProxyStyle { |
535 | public: |
536 | FrameTestStyle() : QProxyStyle("Windows" ) { } |
537 | |
538 | int styleHint(StyleHint hint, const QStyleOption *opt, const QWidget *widget, |
539 | QStyleHintReturn *returnData) const override |
540 | { |
541 | if (hint == QStyle::SH_ScrollView_FrameOnlyAroundContents) |
542 | return 1; |
543 | return QProxyStyle ::styleHint(hint, option: opt, widget, returnData); |
544 | } |
545 | |
546 | int pixelMetric(PixelMetric pm, const QStyleOption *option, const QWidget *widget) const override |
547 | { |
548 | if (pm == QStyle::PM_ScrollView_ScrollBarSpacing) |
549 | return SCROLLBAR_SPACING; |
550 | return QProxyStyle ::pixelMetric(metric: pm, option ,widget); |
551 | } |
552 | }; |
553 | |
554 | void tst_QStyle::testFrameOnlyAroundContents() |
555 | { |
556 | QScrollArea area; |
557 | area.setGeometry(ax: 0, ay: 0, aw: 200, ah: 200); |
558 | QScopedPointer<QStyle> winStyle(QStyleFactory::create("Windows" )); |
559 | FrameTestStyle frameStyle; |
560 | QWidget *widget = new QWidget(&area); |
561 | widget->setGeometry(ax: 0, ay: 0, aw: 400, ah: 400); |
562 | area.setStyle(winStyle.data()); |
563 | area.verticalScrollBar()->setStyle(winStyle.data()); |
564 | area.setWidget(widget); |
565 | area.setVisible(true); |
566 | int viewPortWidth = area.viewport()->width(); |
567 | area.verticalScrollBar()->setStyle(&frameStyle); |
568 | area.setStyle(&frameStyle); |
569 | // Test that we reserve space for scrollbar spacing |
570 | #ifdef Q_OS_WINRT |
571 | QEXPECT_FAIL("" , "QWidget::setGeometry does not work on WinRT" , Continue); |
572 | #endif |
573 | QCOMPARE(viewPortWidth, area.viewport()->width() + SCROLLBAR_SPACING); |
574 | } |
575 | |
576 | |
577 | class ProxyTest: public QProxyStyle |
578 | { |
579 | Q_OBJECT |
580 | public: |
581 | using QProxyStyle::QProxyStyle; |
582 | |
583 | void drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, |
584 | const QWidget *w) const override |
585 | { |
586 | called = true; |
587 | return QProxyStyle::drawPrimitive(element: pe, option: opt, painter: p, widget: w); |
588 | } |
589 | |
590 | mutable bool called = false; |
591 | }; |
592 | |
593 | |
594 | void tst_QStyle::testProxyCalled() |
595 | { |
596 | QToolButton b; |
597 | b.setArrowType(Qt::DownArrow); |
598 | QStyleOptionToolButton opt; |
599 | opt.init(w: &b); |
600 | opt.features |= QStyleOptionToolButton::Arrow; |
601 | QPixmap surface(QSize(200, 200)); |
602 | QPainter painter(&surface); |
603 | |
604 | const QStringList keys = QStyleFactory::keys(); |
605 | QVector<QStyle*> styles; |
606 | styles.reserve(size: keys.size() + 1); |
607 | |
608 | styles << new QCommonStyle(); |
609 | |
610 | for (const QString &key : keys) |
611 | styles << QStyleFactory::create(key); |
612 | |
613 | for (QStyle *style : styles) { |
614 | ProxyTest testStyle; |
615 | testStyle.setBaseStyle(style); |
616 | style->drawControl(element: QStyle::CE_ToolButtonLabel, opt: &opt, p: &painter, w: &b); |
617 | QVERIFY(testStyle.called); |
618 | delete style; |
619 | } |
620 | } |
621 | |
622 | |
623 | class TestStyleOptionInitProxy: public QProxyStyle |
624 | { |
625 | Q_OBJECT |
626 | public: |
627 | mutable bool invalidOptionsDetected = false; |
628 | |
629 | using QProxyStyle::QProxyStyle; |
630 | |
631 | void drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w) const override { |
632 | checkStyleEnum<QStyle::PrimitiveElement>(element: pe, opt); |
633 | return QProxyStyle::drawPrimitive(element: pe, option: opt, painter: p, widget: w); |
634 | } |
635 | |
636 | void drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w) const override { |
637 | checkStyleEnum<QStyle::ControlElement>(element, opt); |
638 | return QProxyStyle::drawControl(element, option: opt, painter: p, widget: w); |
639 | } |
640 | |
641 | QRect subElementRect(SubElement subElement, const QStyleOption *option, const QWidget *widget) const override { |
642 | checkStyleEnum<QStyle::SubElement>(element: subElement, opt: option); |
643 | return QProxyStyle::subElementRect(element: subElement, option, widget); |
644 | } |
645 | |
646 | void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, const QWidget *widget) const override { |
647 | checkStyleEnum<QStyle::ComplexControl>(element: cc, opt); |
648 | return QProxyStyle::drawComplexControl(control: cc, option: opt, painter: p, widget); |
649 | } |
650 | |
651 | QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, const QWidget *widget) const override { |
652 | checkStyleEnum<QStyle::ComplexControl>(element: cc, opt); |
653 | return QProxyStyle::subControlRect(cc, opt, sc, widget); |
654 | } |
655 | |
656 | int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const override { |
657 | checkStyleEnum<QStyle::PixelMetric>(element: metric, opt: option); |
658 | return QProxyStyle::pixelMetric(metric, option, widget); |
659 | } |
660 | |
661 | QSize sizeFromContents(ContentsType ct, const QStyleOption *opt, const QSize &contentsSize, const QWidget *w) const override { |
662 | checkStyleEnum<QStyle::ContentsType>(element: ct, opt); |
663 | return QProxyStyle::sizeFromContents(type: ct, option: opt, size: contentsSize, widget: w); |
664 | } |
665 | |
666 | int styleHint(StyleHint stylehint, const QStyleOption *opt, const QWidget *widget, QStyleHintReturn *returnData) const override { |
667 | checkStyleEnum<QStyle::StyleHint>(element: stylehint, opt); |
668 | return QProxyStyle::styleHint(hint: stylehint, option: opt, widget, returnData); |
669 | } |
670 | |
671 | QPixmap standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, const QWidget *widget) const override { |
672 | checkStyleEnum<QStyle::StandardPixmap>(element: standardPixmap, opt); |
673 | return QProxyStyle::standardPixmap(standardPixmap, opt, widget); |
674 | } |
675 | |
676 | QIcon standardIcon(StandardPixmap standardIcon, const QStyleOption *option, const QWidget *widget) const override { |
677 | checkStyleEnum<QStyle::StandardPixmap>(element: standardIcon, opt: option); |
678 | return QProxyStyle::standardIcon(standardIcon, option, widget); |
679 | } |
680 | |
681 | QPixmap generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, const QStyleOption *opt) const override { |
682 | checkStyle(info: QString::asprintf(format: "QIcon::Mode(%i)" , iconMode).toLatin1(), opt); |
683 | return QProxyStyle::generatedIconPixmap(iconMode, pixmap, opt); |
684 | } |
685 | |
686 | int layoutSpacing(QSizePolicy::ControlType control1, QSizePolicy::ControlType control2, Qt::Orientation orientation, const QStyleOption *option, const QWidget *widget) const override { |
687 | checkStyle(info: QString::asprintf(format: "QSizePolicy::ControlType(%i), QSizePolicy::ControlType(%i)" , control1, control2).toLatin1(), opt: option); |
688 | return QProxyStyle::layoutSpacing(control1, control2, orientation, option, widget); |
689 | } |
690 | |
691 | private: |
692 | void checkStyle(const QByteArray &info, const QStyleOption *opt) const { |
693 | if (opt && (opt->version == 0 || opt->styleObject == nullptr) ) { |
694 | invalidOptionsDetected = true; |
695 | qWarning() << baseStyle()->metaObject()->className() |
696 | << "Invalid QStyleOption found for" |
697 | << info; |
698 | qWarning() << "Version:" << opt->version << "StyleObject:" << opt->styleObject; |
699 | } |
700 | } |
701 | |
702 | template<typename MEnum> |
703 | void checkStyleEnum(MEnum element, const QStyleOption *opt) const { |
704 | static QMetaEnum _enum = QMetaEnum::fromType<MEnum>(); |
705 | checkStyle(info: _enum.valueToKey(value: element), opt); |
706 | } |
707 | }; |
708 | |
709 | void tst_QStyle::testStyleOptionInit() |
710 | { |
711 | QStringList keys = QStyleFactory::keys(); |
712 | keys.prepend(t: QString()); // QCommonStyle marker |
713 | |
714 | for (const QString &key : qAsConst(t&: keys)) { |
715 | QStyle* style = key.isEmpty() ? new QCommonStyle : QStyleFactory::create(key); |
716 | TestStyleOptionInitProxy testStyle; |
717 | testStyle.setBaseStyle(style); |
718 | testAllFunctions(style); |
719 | QVERIFY(!testStyle.invalidOptionsDetected); |
720 | } |
721 | } |
722 | |
723 | QTEST_MAIN(tst_QStyle) |
724 | #include "tst_qstyle.moc" |
725 | |