Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qstylekitstyle.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
7#include <QtWidgets/qstyleoption.h>
8#include <QtWidgets/qstyle.h>
9#include <QtWidgets/qapplication.h>
10#if QT_CONFIG(scrollarea)
11#include <QtWidgets/qabstractscrollarea.h>
12#endif
13#if QT_CONFIG(itemviews)
14#include <QtWidgets/qabstractitemview.h>
15#endif
16#if QT_CONFIG(lineedit)
17#include <QtWidgets/qlineedit.h>
18#endif
19#if QT_CONFIG(pushbutton)
20#include <QtWidgets/qpushbutton.h>
21#endif
22#if QT_CONFIG(checkbox)
23#include <QtWidgets/qcheckbox.h>
24#endif
25#if QT_CONFIG(radiobutton)
26#include <QtWidgets/qradiobutton.h>
27#endif
28#if QT_CONFIG(combobox)
29#include <QtWidgets/qcombobox.h>
30#endif
31#if QT_CONFIG(slider)
32#include <QtWidgets/qslider.h>
33#endif
34#if QT_CONFIG(scrollbar)
35#include <QtWidgets/qscrollbar.h>
36#endif
37#if QT_CONFIG(spinbox)
38#include <QtWidgets/qspinbox.h>
39#endif
40#if QT_CONFIG(progressbar)
41#include <QtWidgets/qprogressbar.h>
42#endif
43#if QT_CONFIG(textedit)
44#include <QtWidgets/qtextedit.h>
45#include <QtWidgets/qplaintextedit.h>
46#endif
47#if QT_CONFIG(tabbar)
48#include <QtWidgets/qtabbar.h>
49#endif
50#if QT_CONFIG(toolbar)
51#include <QtWidgets/qtoolbar.h>
52#endif
53#if QT_CONFIG(groupbox)
54#include <QtWidgets/qgroupbox.h>
55#endif
56#if QT_CONFIG(menu)
57#include <QtWidgets/qmenu.h>
58#endif
59#if QT_CONFIG(label)
60#include <QtWidgets/qlabel.h>
61#endif
62#include <QtWidgets/private/qwidget_p.h>
63#include <QtGui/qpainter.h>
64#include <QtGui/qpainterpath.h>
65#include <QtGui/qpainterstateguard.h>
66#include <QtGui/qstylehints.h>
67#include <QtCore/qloggingcategory.h>
68#include <QtQml/private/qqmlcomponent_p.h>
69#include <QtQml/qqmlengine.h>
70#include <QtLabsStyleKit/private/qqstylekit_p.h>
71#include <QtLabsStyleKit/private/qqstylekitcontrolproperties_p.h>
72#include <QtLabsStyleKit/private/qqstylekitstyle_p.h>
73
75
76Q_STATIC_LOGGING_CATEGORY(lcStyleKit, "qt.labs.stylekit")
77
78/*!
79 \class QStyleKitStyle
80 \inmodule QtLabsStyleKit
81 \ingroup appearance
82 \since 6.12
83
84 \brief The QStyleKitStyle class applies a \l {Qt Labs StyleKit} style
85 to Qt Widgets.
86
87 QStyleKitStyle is a QStyle implementation that uses a \l StyleKit \l Style
88 to style Qt Widgets. The \l Style is a QML file that declaratively describes
89 the visual design (colors, sizes, radii, borders, and other properties)
90 for each widget type and state. Those property values drive the painting,
91 which is done entirely with QPainter. Qt Quick and the scene graph play
92 no part in the rendering.
93
94 This separation means the same \l Style QML file can drive both
95 Qt Quick Controls and Qt Widgets, sharing one design definition across
96 both systems.
97
98 \note StyleKit is a Qt Labs module, and its API may change between
99 Qt releases.
100
101 \section1 Loading a Style
102
103 A style is a QML file whose root object is a \l Style. To load it,
104 pass the file path to the constructor or to \l setStylePath():
105
106 \code
107 auto *style = new QStyleKitStyle(QStringLiteral(":/styles/MyStyle.qml"));
108 QApplication::setStyle(style);
109 \endcode
110
111 The Style is loaded with an internal QQmlEngine owned by the
112 QStyleKitStyle instance. If the path is invalid or the root object is
113 not a \l Style, a warning is emitted and the style uses a default
114 fallback style until a valid \l stylePath is set.
115
116 \section1 Themes
117
118 A Style may define one or more named \l {Theme}{themes}. The active
119 theme is selected with \l setThemeName(); the list of available
120 themes is exposed through \l themeNames. The special theme name
121 \c System makes the style follow the platform color scheme: when the
122 OS color scheme changes, the active theme is recreated automatically
123 and all widgets are repolished.
124
125 \section1 Widget to StyleKit Control Mapping
126
127 Each Qt Widgets class is mapped to a StyleKit control type, which
128 determines which control entry in the \l Style applies to it. Use the
129 corresponding control entry to configure colors, sizes, and other visual
130 properties for that widget type. Properties not set in a specific control
131 entry fall back through the control type hierarchy: for example, \c button
132 falls back to \c abstractButton, which falls back to \c control.
133
134 \table
135 \header
136 \li Qt Widgets class
137 \li StyleKit control
138 \row
139 \li QPushButton (flat)
140 \li \l {AbstractStylableControls::flatButton}{flatButton}
141 \row
142 \li QPushButton
143 \li \l {AbstractStylableControls::button}{button}
144 \row
145 \li QCheckBox
146 \li \l {AbstractStylableControls::checkBox}{checkBox}
147 \row
148 \li QRadioButton
149 \li \l {AbstractStylableControls::radioButton}{radioButton}
150 \row
151 \li QComboBox
152 \li \l {AbstractStylableControls::comboBox}{comboBox}
153 \row
154 \li QSlider
155 \li \l {AbstractStylableControls::slider}{slider}
156 \row
157 \li QScrollBar
158 \li \l {AbstractStylableControls::scrollBar}{scrollBar}
159 \row
160 \li QSpinBox, QDoubleSpinBox
161 \li \l {AbstractStylableControls::spinBox}{spinBox}
162 \row
163 \li QProgressBar
164 \li \l {AbstractStylableControls::progressBar}{progressBar}
165 \row
166 \li QLineEdit
167 \li \l {AbstractStylableControls::textField}{textField}
168 \row
169 \li QTextEdit, QPlainTextEdit
170 \li \l {AbstractStylableControls::textArea}{textArea}
171 \row
172 \li QTabBar
173 \li \l {AbstractStylableControls::tabBar}{tabBar}
174 \row
175 \li QToolBar
176 \li \l {AbstractStylableControls::toolBar}{toolBar}
177 \row
178 \li QToolButton
179 \li \l {AbstractStylableControls::toolButton}{toolButton}
180 \row
181 \li QGroupBox
182 \li \l {AbstractStylableControls::groupBox}{groupBox}
183 \row
184 \li QFrame
185 \li \l {AbstractStylableControls::frame}{frame}
186 \row
187 \li Everything else
188 \li \l {AbstractStylableControls::control}{control}
189 \endtable
190
191 Widgets not listed above fall back to the
192 \l {AbstractStylableControls::control}{control} type. Notably,
193 \l QMenuBar, \l QMenu, and item view containers such as
194 \l QListView and \l QTreeView are not yet specifically mapped; only the
195 individual items inside item views are styled via the
196 \l {AbstractStylableControls::itemDelegate}{itemDelegate} control.
197 Dedicated mappings for these widgets are planned for a future release.
198 Conversely, control entries with no Qt Widgets equivalent —
199 \l {AbstractStylableControls::switchControl}{switchControl},
200 \l {AbstractStylableControls::rangeSlider}{rangeSlider},
201 \l {AbstractStylableControls::scrollIndicator}{scrollIndicator},
202 \l {AbstractStylableControls::roundButton}{roundButton}, and
203 \l {AbstractStylableControls::page}{page} — have no corresponding widget
204 type and are not applied.
205
206 \section1 Known Limitations
207
208 QStyleKitStyle is in Tech Preview. The following StyleKit features are
209 currently not supported when used with Qt Widgets:
210
211 \list
212 \li \b{Shadows} — shadows are not rendered.
213 \li \b{Variations} — setting a \l StyleVariation on a widget instance is
214 not yet supported.
215 \li \b{Custom controls} — styling custom widgets using \l CustomControl
216 is not yet supported.
217 \li \b{Custom delegates} — the \l {DelegateStyle::}{delegate} property is
218 not used; the built-in rendering is always applied.
219 \endlist
220
221 Support for these features is planned for a future release.
222
223 \sa QStyle, QCommonStyle, {Qt Labs StyleKit}, Style, Theme
224*/
225
226/*!
227 \property QStyleKitStyle::stylePath
228 \brief the path to the QML \l Style file driving this style.
229
230 The value is a path to a local file or a path to a file in the resource
231 file system (for example, \c{:/styles/MyStyle.qml}). A relative path is
232 resolved against the application's working directory. The file must
233 contain a QML component whose root object is a \l Style. Setting this
234 property reloads the style; if the new file cannot be loaded, the
235 previously loaded style is kept and a warning is emitted.
236*/
237
238/*!
239 \property QStyleKitStyle::themeName
240 \brief the name of the active theme.
241
242 The value must be one of the entries in \l themeNames, or the
243 special name \c System to follow the platform color scheme.
244 Setting this property updates all widgets to repaint with the
245 new theme.
246*/
247
248/*!
249 \property QStyleKitStyle::themeNames
250 \brief the list of theme names exposed by the loaded \l Style.
251
252 This list includes the built-in \c Light and \c Dark themes as well
253 as any custom themes defined by the style.
254*/
255
256/*!
257 \property QStyleKitStyle::customThemeNames
258 \brief the list of custom theme names defined by the loaded \l Style.
259
260 Unlike \l themeNames, this list excludes the built-in \c Light and
261 \c Dark themes and contains only the themes explicitly defined by the
262 style author. Returns an empty list when no style is loaded.
263
264 \sa themeNames, themeName
265*/
266
267static QQStyleKitReader::ControlType controlTypeForWidget(const QWidget *widget)
268{
269 if (!widget)
270 return QQStyleKitReader::Control;
271
272#if QT_CONFIG(pushbutton)
273 if (qobject_cast<const QPushButton *>(widget))
274 return QQStyleKitReader::Button;
275#endif
276#if QT_CONFIG(checkbox)
277 if (qobject_cast<const QCheckBox *>(widget))
278 return QQStyleKitReader::CheckBox;
279#endif
280#if QT_CONFIG(radiobutton)
281 if (qobject_cast<const QRadioButton *>(widget))
282 return QQStyleKitReader::RadioButton;
283#endif
284#if QT_CONFIG(combobox)
285 if (qobject_cast<const QComboBox *>(widget))
286 return QQStyleKitReader::ComboBox;
287#endif
288#if QT_CONFIG(slider)
289 if (qobject_cast<const QSlider *>(widget))
290 return QQStyleKitReader::Slider;
291#endif
292#if QT_CONFIG(scrollbar)
293 if (qobject_cast<const QScrollBar *>(widget))
294 return QQStyleKitReader::ScrollBar;
295#endif
296#if QT_CONFIG(spinbox)
297 if (qobject_cast<const QSpinBox *>(widget) || qobject_cast<const QDoubleSpinBox *>(widget))
298 return QQStyleKitReader::SpinBox;
299#endif
300#if QT_CONFIG(itemviews)
301 if (qobject_cast<const QAbstractItemView *>(widget))
302 return QQStyleKitReader::ItemDelegate;
303#endif
304#if QT_CONFIG(progressbar)
305 if (qobject_cast<const QProgressBar *>(widget))
306 return QQStyleKitReader::ProgressBar;
307#endif
308#if QT_CONFIG(lineedit)
309 if (qobject_cast<const QLineEdit *>(widget))
310 return QQStyleKitReader::TextField;
311#endif
312#if QT_CONFIG(textedit)
313 if (qobject_cast<const QTextEdit *>(widget) || qobject_cast<const QPlainTextEdit *>(widget))
314 return QQStyleKitReader::TextArea;
315#endif
316#if QT_CONFIG(tabbar)
317 if (qobject_cast<const QTabBar *>(widget))
318 return QQStyleKitReader::TabBar;
319#endif
320#if QT_CONFIG(toolbar)
321 if (qobject_cast<const QToolBar *>(widget))
322 return QQStyleKitReader::ToolBar;
323#endif
324#if QT_CONFIG(groupbox)
325 if (qobject_cast<const QGroupBox *>(widget))
326 return QQStyleKitReader::GroupBox;
327#endif
328 if (widget->windowType() & Qt::Popup)
329 return QQStyleKitReader::Popup;
330 if (widget->windowType() & Qt::Dialog)
331 return QQStyleKitReader::Dialog;
332 if (widget->windowType() & Qt::Window)
333 return QQStyleKitReader::ApplicationWindow;
334#ifndef QT_NO_FRAME
335 if (qobject_cast<const QFrame *>(widget))
336 return QQStyleKitReader::Frame;
337#endif
338
339 return QQStyleKitReader::Control;
340}
341
342// Returns true for widgets that draw themselves using their widget font and
343// palette directly, bypassing the style's drawControl path.
344static bool isSelfPaintingWidget(const QWidget *widget)
345{
346 return false
347#if QT_CONFIG(label)
348 || qobject_cast<const QLabel *>(widget)
349#endif
350#if QT_CONFIG(textedit)
351 || qobject_cast<const QTextEdit *>(widget)
352 || qobject_cast<const QPlainTextEdit *>(widget)
353#endif
354#if QT_CONFIG(lineedit)
355 || qobject_cast<const QLineEdit *>(widget)
356#endif
357 ;
358}
359
360static QWidget *managedViewport(QWidget *widget)
361{
362#if QT_CONFIG(textedit)
363 if (auto *textEdit = qobject_cast<QTextEdit *>(widget))
364 return textEdit->viewport();
365 if (auto *plainTextEdit = qobject_cast<QPlainTextEdit *>(widget))
366 return plainTextEdit->viewport();
367#endif
368#if QT_CONFIG(itemviews) && QT_CONFIG(combobox)
369 if (auto *view = qobject_cast<QAbstractItemView *>(widget)) {
370 if (auto *p = view->parentWidget(); p && p->inherits("QComboBoxPrivateContainer"))
371 return view->viewport();
372 }
373#endif
374 return nullptr;
375}
376
377static uint resolvedAlignment(uint raw, Qt::Alignment hDefault, Qt::Alignment vDefault)
378{
379 uint result = 0;
380 const uint h = raw & Qt::AlignHorizontal_Mask;
381 const uint v = raw & Qt::AlignVertical_Mask;
382 result |= h ? h : uint(hDefault);
383 result |= v ? v : uint(vDefault);
384 return result;
385}
386
387static qreal resolvedImplicitWidth(const QQStyleKitDelegateProperties *element, qreal availableW)
388{
389 return element->fillWidth() ? availableW : qMax(0.0, element->implicitWidth());
390}
391
392static qreal resolvedImplicitHeight(const QQStyleKitDelegateProperties *element, qreal avilableH)
393{
394 return element->fillHeight() ? avilableH : qMax(0.0, element->implicitHeight());
395}
396
397static QMargins elementMargins(const QQStyleKitDelegateProperties *element)
398{
399 return QMargins(element->leftMargin(), element->topMargin(),
400 element->rightMargin(), element->bottomMargin());
401}
402
403// Copied from qstylesheetstyle.cpp
404static const QWidget *containerWidget(const QWidget *w)
405{
406#if QT_CONFIG(lineedit)
407 if (qobject_cast<const QLineEdit *>(w)) {
408 //if the QLineEdit is an embeddedWidget, we need the real widget
409 QWidget *parent = w->parentWidget();
410 if (false
411#if QT_CONFIG(combobox)
412 || qobject_cast<const QComboBox *>(parent)
413#endif
414#if QT_CONFIG(spinbox)
415 || qobject_cast<const QAbstractSpinBox *>(parent)
416#endif
417 ) {
418 return parent;
419 }
420 }
421#endif
422
423#if QT_CONFIG(scrollarea)
424 if (const QAbstractScrollArea *sa = qobject_cast<const QAbstractScrollArea *>(w->parentWidget())) {
425 if (sa->viewport() == w)
426 return w->parentWidget();
427 }
428#endif
429
430 return w;
431}
432
433QStyleKitStylePrivate::QStyleKitStylePrivate()
434 : QCommonStylePrivate()
435{
436}
437
438static QUrl urlFromStylePath(const QString &filePath)
439{
440 return filePath.startsWith(QLatin1Char(':'))
441 ? QUrl(QLatin1String("qrc") + filePath)
442 : QUrl::fromLocalFile(filePath);
443}
444
445bool QStyleKitStylePrivate::loadStyle()
446{
447 Q_Q(QStyleKitStyle);
448 if (!qmlEngine)
449 qmlEngine = new QQmlEngine(q);
450
451 if (!qmlEngine) {
452 qWarning("QStyleKitStyle: No QML engine available to load style.");
453 return false;
454 }
455 const QUrl url = stylePath.isEmpty() ? QUrl() : urlFromStylePath(stylePath);
456 if (stylePath.isEmpty() || !url.isValid()) {
457 qWarning("QStyleKitStyle: No valid style path provided: %s", qPrintable(stylePath));
458 return false;
459 }
460 QQmlComponent component(qmlEngine, url);
461 if (component.isError()) {
462 qWarning("QStyleKitStyle: Failed to load style from %s: %s",
463 qPrintable(stylePath), qPrintable(component.errorString()));
464 return false;
465 }
466 // Avoid creating anything other than StyleKit Style objects
467 // by checking the metaobject of the root type before creating the object
468 QQmlComponentPrivate *componentPrivate = QQmlComponentPrivate::get(&component);
469 const auto compilationUnit = componentPrivate->compilationUnit();
470 const auto propertyCache = compilationUnit ? compilationUnit->rootPropertyCache() : nullptr;
471 const auto firstMetaObject = propertyCache ? propertyCache->firstCppMetaObject() : nullptr;
472 if (!firstMetaObject || !firstMetaObject->inherits(&QQStyleKitStyle::staticMetaObject)) {
473 qWarning("QStyleKitStyle: Failed to load style from %s: component is not a StyleKit Style.",
474 qPrintable(stylePath));
475 return false;
476 }
477 QQStyleKitStyle *styleObject = qobject_cast<QQStyleKitStyle *>(component.create());
478 if (!styleObject) {
479 qWarning("QStyleKitStyle: Failed to create style object from %s: component is not a StyleKit style.",
480 qPrintable(stylePath));
481 return false;
482 }
483 const bool isReload = style != nullptr;
484 delete style;
485 style = styleObject;
486 style->setParent(q);
487
488 if (!isReload) {
489 QObject::connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, q, [this]() {
490 if (style && style->themeName() == QLatin1String("System")) {
491 style->recreateTheme();
492 updateStyle();
493 }
494 });
495 }
496
497 if (style->loaded())
498 QQStyleKitReader::resetReadersForStyle(style);
499
500 return true;
501}
502
503/*! \internal
504 Creates and returns the empty fallback style used when no user
505 style is loaded.
506*/
507QQStyleKitStyle *QStyleKitStylePrivate::ensureDefaultStyle()
508{
509 if (defaultStyle)
510 return defaultStyle;
511
512 Q_Q(QStyleKitStyle);
513 if (!qmlEngine)
514 qmlEngine = new QQmlEngine(q);
515
516 QQmlComponent component(qmlEngine);
517 component.loadFromModule("Qt.labs.StyleKit", "Style");
518 if (component.isError()) {
519 qWarning("QStyleKitStyle: Failed to create default fallback style: %s",
520 qPrintable(component.errorString()));
521 return nullptr;
522 }
523 defaultStyle = qobject_cast<QQStyleKitStyle *>(component.create());
524 if (defaultStyle) {
525 defaultStyle->setParent(q);
526 qCDebug(lcStyleKit, "No style set; using a default fallback style. "
527 "Set stylePath to load a StyleKit Style.");
528 }
529 return defaultStyle;
530}
531
532/*! \internal
533 Returns the user style if one is loaded, otherwise the default fallback
534 style
535*/
536QQStyleKitStyle *QStyleKitStylePrivate::effectiveStyle() const
537{
538 return style ? style : defaultStyle;
539}
540
541void QStyleKitStylePrivate::updateStyle()
542{
543 clearMetricsCache();
544
545 QQStyleKitStyle *effective = effectiveStyle();
546
547 if (sharedReader && sharedReader->style() != effective)
548 sharedReader->setExplicitStyle(effective);
549
550 for (const auto &byItem : std::as_const(itemViewItemReaders)) {
551 for (auto *reader : std::as_const(byItem)) {
552 if (reader && reader->style() != effective)
553 reader->setExplicitStyle(effective);
554 }
555 }
556
557 for (auto *wr : std::as_const(widgetReaders)) {
558 if (wr->style() != effective)
559 wr->setExplicitStyle(effective);
560 }
561
562 if (!QWidgetPrivate::allWidgets)
563 return;
564
565 for (auto *widget : std::as_const(*QWidgetPrivate::allWidgets)) {
566 if (customFontWidgets.contains(widget))
567 unsetStyleFont(widget);
568 refreshStyleFont(widget);
569 if (customPaletteWidgets.contains(widget))
570 unsetStylePalette(widget);
571 refreshStylePalette(widget);
572 }
573
574 QEvent styleChange(QEvent::StyleChange);
575 for (auto *widget : std::as_const(*QWidgetPrivate::allWidgets)) {
576 QApplication::sendEvent(widget, &styleChange);
577 widget->update();
578 }
579}
580
581// Follow the approach in qstylesheetstyle.cpp
582void QStyleKitStylePrivate::unsetStyleFont(QWidget *widget)
583{
584 auto it = customFontWidgets.find(widget);
585 if (it == customFontWidgets.end())
586 return;
587
588 auto customFont = std::move(*it);
589 customFontWidgets.erase(it);
590 widget->setFont(std::move(customFont).reverted(widget->font()));
591}
592
593void QStyleKitStylePrivate::setStyleFont(QWidget *widget, const QFont &styleFont)
594{
595 if (!effectiveStyle() || !widget)
596 return;
597
598 const auto styleMask = styleFont.resolveMask();
599 auto it = customFontWidgets.find(widget);
600
601 if (it == customFontWidgets.end()) {
602 if (styleMask == 0)
603 return;
604 it = customFontWidgets.insert(widget, { QWidgetPrivate::get(widget)->localFont(), 0 });
605 QObject::connect(widget, &QObject::destroyed, widget, [this, widget]() {
606 customFontWidgets.remove(widget);
607 });
608 }
609
610 it->resolveMask = styleMask;
611
612 const QFont &baseline = it->oldWidgetValue;
613 QFont merged = styleFont.resolve(baseline);
614 merged.setResolveMask(baseline.resolveMask() | styleMask);
615 if (widget->font() != merged)
616 widget->setFont(merged);
617}
618
619void QStyleKitStylePrivate::refreshStyleFont(QWidget *widget)
620{
621 if (!effectiveStyle() || !widget)
622 return;
623
624 const QWidget *targetWidget = containerWidget(widget);
625 QQStyleKitReader::ControlType controlType = controlTypeForWidget(targetWidget);
626
627 auto *shared = ensureSharedReader();
628 if (!shared)
629 return;
630
631 QStyleOption opt;
632 opt.initFrom(targetWidget);
633 const QQSK::State currentState = resolvedStateFor(controlType, opt.state);
634
635 shared->setControlTypeAndState(controlType, currentState);
636 setStyleFont(widget, shared->font());
637}
638
639void QStyleKitStylePrivate::unsetStylePalette(QWidget *widget)
640{
641 auto it = customPaletteWidgets.find(widget);
642 if (it == customPaletteWidgets.end())
643 return;
644
645 auto customPalette = std::move(*it);
646 customPaletteWidgets.erase(it);
647 widget->setPalette(std::move(customPalette).reverted(widget->palette()));
648}
649
650void QStyleKitStylePrivate::setStylePalette(QWidget *widget, const QPalette &stylePalette) const
651{
652 if (!effectiveStyle() || !widget)
653 return;
654
655 const quint64 styleMask = stylePalette.resolveMask();
656 if (styleMask == 0)
657 return;
658
659 if (!customPaletteWidgets.contains(widget)) {
660 customPaletteWidgets.insert(widget, { widget->palette(), 0 });
661 QObject::connect(widget, &QObject::destroyed, widget, [this, widget]() {
662 customPaletteWidgets.remove(widget);
663 });
664 }
665
666 customPaletteWidgets[widget].resolveMask |= styleMask;
667 QPalette merged = stylePalette.resolve(widget->palette());
668 merged.setResolveMask(widget->palette().resolveMask() | styleMask);
669 widget->setPalette(merged);
670}
671
672// For widgets that partially or fully draw themselves (instead of delegating to the style),
673// using palette roles, we need to push the style colors to those roles
674void QStyleKitStylePrivate::refreshStylePalette(QWidget *widget)
675{
676 if (!effectiveStyle() || !widget)
677 return;
678
679 const bool isWindow = widget->windowType() & (Qt::Popup | Qt::Window | Qt::Dialog);
680 const bool isPaletteManaged = isSelfPaintingWidget(widget) || isWindow;
681 if (!isPaletteManaged)
682 return;
683
684 const QWidget *targetWidget = containerWidget(widget);
685 QQStyleKitReader::ControlType controlType = controlTypeForWidget(targetWidget);
686
687 auto *shared = ensureSharedReader();
688 if (!shared)
689 return;
690
691 QStyleOption opt;
692 opt.initFrom(targetWidget);
693 const QQSK::State currentState = resolvedStateFor(controlType, opt.state);
694
695 QPalette stylePalette;
696 if (isWindow) {
697 // Windows draw their own background using the QPalette::Window role
698 shared->setControlTypeAndState(controlType, currentState);
699 if (const auto *bg = shared->global()->background(); bg && bg->isDefined(QQSK::Property::Color))
700 stylePalette.setColor(QPalette::Window, bg->color());
701 shared->setControlTypeAndState(controlType, QQSK::StateFlag::Disabled);
702 if (const auto *dbg = shared->global()->background(); dbg && dbg->isDefined(QQSK::Property::Color))
703 stylePalette.setColor(QPalette::Disabled, QPalette::Window, dbg->color());
704 } else {
705 // The remaining text-based widgets use the QPalette::Text/WindowText roles for their foreground color
706 shared->setControlTypeAndState(controlType, currentState);
707 if (const auto *text = shared->global()->text(); text && text->isDefined(QQSK::Property::Color)) {
708 stylePalette.setColor(QPalette::Text, text->color());
709 stylePalette.setColor(QPalette::WindowText, text->color());
710 }
711 shared->setControlTypeAndState(controlType, QQSK::StateFlag::Disabled);
712 if (const auto *dt = shared->global()->text(); dt && dt->isDefined(QQSK::Property::Color)) {
713 stylePalette.setColor(QPalette::Disabled, QPalette::Text, dt->color());
714 stylePalette.setColor(QPalette::Disabled, QPalette::WindowText, dt->color());
715 }
716 }
717
718 setStylePalette(widget, stylePalette);
719}
720
721/*! \internal
722 Returns a reader for the given widget, creating and caching it if needed.
723*/
724QQStyleKitReader *QStyleKitStylePrivate::readerForWidget(const QWidget *widget) const
725{
726 Q_Q(const QStyleKitStyle);
727 QQStyleKitStyle *effective = effectiveStyle();
728 if (!effective || !widget)
729 return nullptr;
730
731 if (auto it = widgetReaders.find(widget); it != widgetReaders.end())
732 return *it;
733
734 auto *widgetReader = new QQStyleKitReader(const_cast<QStyleKitStyle *>(q));
735 widgetReader->setExplicitStyle(effective);
736 widgetReader->setTarget(const_cast<QWidget *>(widget));
737 widgetReader->setCompleted(true);
738 widgetReaders.insert(widget, widgetReader);
739 QObject::connect(widget, &QObject::destroyed, q, [this, widget]() {
740 cleanupWidgetReader(widget);
741 });
742 return widgetReader;
743}
744
745void QStyleKitStylePrivate::cleanupWidgetReader(const QWidget *widget) const
746{
747 if (auto *reader = widgetReaders.take(widget))
748 reader->deleteLater();
749 cleanupItemViewItemReaders(widget);
750}
751
752/*! \internal
753 Returns a unique key for the given item view item, or 0 if the option is not an item view item.
754 The key is used to cache a reader for the item.
755*/
756quint64 QStyleKitStylePrivate::itemViewItemKeyForOption(const QStyleOption *opt)
757{
758 if (const auto *viewOpt = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) {
759 const auto &idx = viewOpt->index;
760 if (!idx.isValid())
761 return 0;
762
763 // Generate a unique key for the item using its row, column, and internalId
764 return qHashMulti(0, idx.row(), idx.column(), quint64(idx.internalId()));
765 }
766 return 0;
767}
768
769/*! \internal
770 Returns a reader for the given item view item, creating and caching it if needed.
771 Each item gets its own reader so that transitions for different items animate independently.
772*/
773QQStyleKitReader *QStyleKitStylePrivate::readerForItemViewItem(
774 const QWidget *widget, quint64 itemKey) const
775{
776 Q_Q(const QStyleKitStyle);
777 QQStyleKitStyle *effective = effectiveStyle();
778 if (!effective)
779 return nullptr;
780
781 auto &byItem = itemViewItemReaders[widget];
782 if (auto it = byItem.find(itemKey); it != byItem.end())
783 return *it;
784
785 auto *reader = new QQStyleKitReader(const_cast<QStyleKitStyle *>(q));
786 reader->setExplicitStyle(effective);
787 reader->setTarget(const_cast<QWidget *>(widget));
788 reader->setCompleted(true);
789 byItem.insert(itemKey, reader);
790 return reader;
791}
792
793void QStyleKitStylePrivate::cleanupItemViewItemReaders(const QWidget *widget) const
794{
795 const auto readers = itemViewItemReaders.take(widget);
796 for (auto *reader : readers)
797 reader->deleteLater();
798}
799
800/*! \internal
801 Resolves the given QStyle::State into a QQSK::State based on the mapping of
802 state flags for the given control type.
803*/
804QQSK::State QStyleKitStylePrivate::resolvedStateFor(
805 QQStyleKitReader::ControlType type, QStyle::State state) const
806{
807 QQSK::State flags;
808 flags.setFlag(QQSK::StateFlag::Disabled, !(state & QStyle::State_Enabled));
809 flags.setFlag(QQSK::StateFlag::Hovered, (state & QStyle::State_MouseOver));
810 flags.setFlag(QQSK::StateFlag::Pressed, state & QStyle::State_Sunken);
811 flags.setFlag(QQSK::StateFlag::Checked, state & QStyle::State_On);
812 flags.setFlag(QQSK::StateFlag::Focused, state & QStyle::State_HasFocus);
813 flags.setFlag(QQSK::StateFlag::Highlighted, state & QStyle::State_Selected);
814 flags.setFlag(QQSK::StateFlag::Vertical, !(state & QStyle::State_Horizontal));
815
816 // ComboBox uses QStyle::State_On to indicate the popup is open, which is
817 // not a "checked" semantic.
818 if (type == QQStyleKitReader::ControlType::ComboBox)
819 flags.setFlag(QQSK::StateFlag::Checked, false);
820
821 // Popup has no hover state in the Controls style
822 if (type == QQStyleKitReader::ControlType::Popup)
823 flags.setFlag(QQSK::StateFlag::Hovered, false);
824
825 return flags;
826}
827
828/*! \internal
829 Returns the ControlMetrics for the given control type and state,
830 reading from the style if needed and caching the result.
831*/
832const QStyleKitStylePrivate::ControlMetrics &QStyleKitStylePrivate::metricsFor(
833 QQStyleKitReader::ControlType type, QQSK::State state) const
834{
835 if (auto it = metricsCache.find({type, state}); it != metricsCache.end())
836 return *it;
837
838 auto *reader = ensureSharedReader();
839 Q_ASSERT(reader);
840 reader->setControlTypeAndState(type, state);
841 return *metricsCache.insert({type, state}, metricsForReader(reader));
842}
843
844/*! \internal
845 Resolves the properties for the given widget, control type and state,
846 and returns them as a QQStyleKitResolved.
847*/
848QStyleKitStylePrivate::QQStyleKitResolved QStyleKitStylePrivate::resolve(
849 const QWidget *w, QQStyleKitReader::ControlType type, QStyle::State state) const
850{
851 QQStyleKitResolved out;
852 out.widget = w;
853
854 auto *reader = readerForWidget(w);
855 if (!reader)
856 return out;
857
858 const QQSK::State resolvedState = resolvedStateFor(type, state);
859 reader->setControlTypeAndState(type, resolvedState);
860
861 out.reader = reader;
862 out.metrics = &metricsFor(type, resolvedState);
863 return out;
864}
865
866/*! \internal
867 Resolves the properties for the given item view item, control type and state,
868 and returns them as a QQStyleKitResolved.
869 Falls back to resolve() if the option is not an item view item.
870*/
871QStyleKitStylePrivate::QQStyleKitResolved QStyleKitStylePrivate::resolveItemViewItem(
872 const QWidget *w, const QStyleOption *opt,
873 QQStyleKitReader::ControlType type, QStyle::State state) const
874{
875 // Same machinery as resolve(), but for sub-elements that have their own state
876 // (item view rows/cells). Each (widget, itemKey) gets its own reader
877 // so transitions for different items animate independently.
878 // Falls back to the widget reader
879 const quint64 itemKey = itemViewItemKeyForOption(opt);
880 if (itemKey == 0)
881 return resolve(w, type, state);
882
883 auto *reader = readerForItemViewItem(w, itemKey);
884 if (!reader)
885 return resolve(w, type, state);
886
887 const QQSK::State resolvedState = resolvedStateFor(type, state);
888 reader->setControlTypeAndState(type, resolvedState);
889
890 QQStyleKitResolved out;
891 out.widget = w;
892 out.reader = reader;
893 out.metrics = &metricsFor(type, resolvedState);
894 return out;
895}
896
897const QQStyleKitTextProperties *QStyleKitStylePrivate::QQStyleKitResolved::text() const
898{
899 return reader ? reader->text() : nullptr;
900}
901
902const QQStyleKitDelegateProperties *QStyleKitStylePrivate::QQStyleKitResolved::background() const
903{
904 return reader ? reader->background() : nullptr;
905}
906
907const QQStyleKitHandleProperties *QStyleKitStylePrivate::QQStyleKitResolved::handle() const
908{
909 return reader ? reader->handle() : nullptr;
910}
911
912const QQStyleKitIndicatorWithSubTypes *QStyleKitStylePrivate::QQStyleKitResolved::indicator() const
913{
914 return reader ? reader->indicator() : nullptr;
915}
916
917QFont QStyleKitStylePrivate::QQStyleKitResolved::font() const
918{
919 return reader ? reader->font() : QFont();
920}
921
922QQStyleKitReader *QStyleKitStylePrivate::ensureSharedReader() const
923{
924 if (sharedReader)
925 return sharedReader;
926
927 Q_Q(const QStyleKitStyle);
928 QQStyleKitStyle *effective = effectiveStyle();
929 if (!effective)
930 return nullptr;
931 sharedReader = new QQStyleKitReader(const_cast<QStyleKitStyle *>(q));
932 // Disable transitions since this reader is used for one-off metric reads in layout queries
933 sharedReader->setTransitionsEnabled(false);
934 sharedReader->setExplicitStyle(effective);
935 sharedReader->setCompleted(true);
936 return sharedReader;
937}
938
939/*! \internal
940 Resolves the properties for the given control type and state using the shared reader,
941 which disables transitions and is used for layout queries, so that layout never depends
942 on animation state.
943*/
944QStyleKitStylePrivate::QQStyleKitLayoutResolved QStyleKitStylePrivate::resolveLayout(
945 QQStyleKitReader::ControlType type, QStyle::State state) const
946{
947 QQStyleKitLayoutResolved out;
948 auto *reader = ensureSharedReader();
949 if (!reader)
950 return out;
951
952 const QQSK::State resolvedState = resolvedStateFor(type, state);
953 out.metrics = &metricsFor(type, resolvedState);
954 // metricsFor() sets the sharedReader to (type, resolvedState) on cache miss.
955 // On hit it didn't, so make sure staticProps / staticFont reflect the state
956 // and type of the caller
957 reader->setControlTypeAndState(type, resolvedState);
958 out.staticProps = reader->global();
959 out.staticFont = reader->font();
960 return out;
961}
962
963const QQStyleKitTextProperties *QStyleKitStylePrivate::QQStyleKitLayoutResolved::text() const
964{
965 return staticProps ? staticProps->text() : nullptr;
966}
967
968const QQStyleKitDelegateProperties *QStyleKitStylePrivate::QQStyleKitLayoutResolved::background() const
969{
970 return staticProps ? staticProps->background() : nullptr;
971}
972
973const QQStyleKitHandleProperties *QStyleKitStylePrivate::QQStyleKitLayoutResolved::handle() const
974{
975 return staticProps ? staticProps->handle() : nullptr;
976}
977
978const QQStyleKitIndicatorWithSubTypes *QStyleKitStylePrivate::QQStyleKitLayoutResolved::indicator() const
979{
980 return staticProps ? staticProps->indicator() : nullptr;
981}
982
983void QStyleKitStylePrivate::clearMetricsCache()
984{
985 metricsCache.clear();
986}
987
988void QStyleKitStylePrivate::drawControlIndicator(const QQStyleKitDelegateProperties *indicator, const QRectF &rect, QPainter *painter) const
989{
990 if (!indicator || !rect.isValid())
991 return;
992
993 // indicator (background)
994 QRectF indicatorRect = rect;
995 if (indicator->visible() && indicator->opacity() > 0)
996 drawStyledItemRect(indicator, indicatorRect, painter);
997
998 const QQStyleKitDelegateProperties *foreground = nullptr;
999 if (auto *indicatorWithSubTypes = qobject_cast<const QQStyleKitIndicatorWithSubTypes *>(indicator)) {
1000 foreground = indicatorWithSubTypes->foreground();
1001 } else if (auto *indicatorProps = qobject_cast<const QQStyleKitIndicatorProperties *>(indicator)) {
1002 foreground = indicatorProps->foreground();
1003 } else {
1004 return;
1005 }
1006 if (!foreground || !foreground->visible() || foreground->opacity() <= 0)
1007 return;
1008
1009 // foreground
1010 QRectF foregroundRect;
1011 const uint foregroundAlign = resolvedAlignment(foreground->alignment(), Qt::AlignHCenter, Qt::AlignVCenter);
1012 const auto foregroundW = resolvedImplicitWidth(foreground,
1013 indicatorRect.width() - foreground->leftMargin() - foreground->rightMargin());
1014 const auto foregroundH = resolvedImplicitHeight(foreground,
1015 indicatorRect.height() - foreground->topMargin() - foreground->bottomMargin());
1016 foregroundRect.setSize(QSizeF(foregroundW, foregroundH));
1017 if (foregroundAlign & Qt::AlignLeft)
1018 foregroundRect.moveLeft(indicatorRect.left() + foreground->leftMargin());
1019 else if (foregroundAlign & Qt::AlignHCenter)
1020 foregroundRect.moveLeft(indicatorRect.left() + (indicatorRect.width() - foregroundW) / 2.0);
1021 else if (foregroundAlign & Qt::AlignRight)
1022 foregroundRect.moveLeft(indicatorRect.right() - foreground->rightMargin() - foregroundW);
1023 if (foregroundAlign & Qt::AlignTop)
1024 foregroundRect.moveTop(indicatorRect.top() + foreground->topMargin());
1025 else if (foregroundAlign & Qt::AlignVCenter)
1026 foregroundRect.moveTop(indicatorRect.top() + (indicatorRect.height() - foregroundH) / 2.0);
1027 else if (foregroundAlign & Qt::AlignBottom)
1028 foregroundRect.moveTop(indicatorRect.bottom() - foreground->bottomMargin() - foregroundH);
1029 drawStyledItemRect(foreground, foregroundRect, painter);
1030}
1031
1032void QStyleKitStylePrivate::drawControlText(const QQStyleKitTextProperties *textProps,
1033 const QFont &font, const QRect &rect,
1034 const QString &text, uint textFlags,
1035 QPainter *p, Qt::Alignment defaultAlignment) const
1036{
1037 uint flags = textFlags;
1038 flags |= textProps
1039 ? resolvedAlignment(textProps->alignment(),
1040 defaultAlignment & Qt::AlignHorizontal_Mask,
1041 defaultAlignment & Qt::AlignVertical_Mask)
1042 : uint(defaultAlignment);
1043
1044 const QFont oldFont = p->font();
1045 p->setFont(font.resolve(oldFont));
1046 p->setBrush(Qt::NoBrush);
1047 p->setPen(textProps ? textProps->color() : QColor());
1048 p->drawText(rect, flags, text, nullptr);
1049}
1050
1051void QStyleKitStylePrivate::drawStyledItemRect(const QQStyleKitDelegateProperties *props, const QRectF &rect, QPainter *painter) const
1052{
1053 if (!props || !props->visible() || props->opacity() <= 0 || !rect.isValid())
1054 return;
1055
1056 QPainterStateGuard stateGuard(painter);
1057 painter->setRenderHint(QPainter::Antialiasing, true);
1058 painter->setClipping(props->clip());
1059
1060 // opacity
1061 const auto opacity = props->opacity();
1062
1063 // border
1064 const auto *border = props->border();
1065 auto borderWidth = 0;
1066 auto inset = 0.0;
1067 QColor borderColor;
1068 if (border) {
1069 borderWidth = border->width();
1070 borderColor = border->color();
1071 inset = borderWidth / 2.0;
1072 }
1073 if (borderWidth > 0 && borderColor.isValid() && borderColor.alpha() > 0) {
1074 QPen pen(borderColor, borderWidth);
1075 painter->setPen(pen);
1076 } else {
1077 painter->setPen(Qt::NoPen);
1078 }
1079
1080 // radius
1081 // TODO: support different radius for each corner
1082 const qreal minDimension = qMin(rect.width(), rect.height());
1083 const qreal xRadius = qMax(0.0, qMin(props->topLeftRadius() - inset, minDimension / 2.0));
1084 const qreal yRadius = qMax(0.0, qMin(props->bottomLeftRadius() - inset, minDimension / 2.0));
1085
1086 QRectF adjustedRect = rect.adjusted(inset, inset, -inset, -inset);
1087
1088 // rotation
1089 if (props->rotation() != 0) {
1090 const auto center = rect.center();
1091 painter->translate(center);
1092 painter->rotate(props->rotation());
1093 painter->translate(-center);
1094 }
1095
1096 // gradients/color
1097 // TODO: support palette roles for colors and gradients
1098 QColor color = props->color();
1099 QBrush colorBrush(color);
1100 painter->setBrush(colorBrush);
1101 if (color.isValid()) {
1102 painter->setOpacity(opacity);
1103 painter->drawRoundedRect(adjustedRect, xRadius, yRadius, Qt::AbsoluteSize);
1104 }
1105 if (QQuickGradient *gradient = props->gradient()) {
1106 QLinearGradient linearGradient(rect.topLeft(), rect.bottomLeft());
1107 QQmlListProperty<QQuickGradientStop> stops = gradient->stops();
1108 const int stopCount = stops.count(&stops);
1109 for (int i = 0; i < stopCount; i++) {
1110 QQuickGradientStop *stop = static_cast<QQuickGradientStop*>(stops.at(&stops, i));
1111 linearGradient.setColorAt(stop->position(), stop->color());
1112 }
1113 QBrush gradientBrush(linearGradient);
1114 painter->setBrush(gradientBrush);
1115 painter->setOpacity(opacity);
1116 painter->drawRoundedRect(adjustedRect, xRadius, yRadius, Qt::AbsoluteSize);
1117 }
1118
1119 // image
1120 drawStyledItemImage(props->image(), adjustedRect, opacity, painter);
1121}
1122
1123void QStyleKitStylePrivate::drawStyledItemImage(const QQStyleKitImageProperties *image, const QRectF &rect,
1124 qreal opacity, QPainter *painter) const
1125{
1126 if (!image || image->source().isEmpty() || image->color().alpha() <= 0)
1127 return;
1128
1129 QString imageSource = image->source().toString();
1130 if (imageSource.startsWith(QLatin1String("qrc:/")))
1131 imageSource = imageSource.mid(3);
1132 QUrl imageUrl(imageSource);
1133 QString imagePath = imageUrl.isLocalFile() ? imageUrl.toLocalFile() : imageSource;
1134 QPixmap pixmap(imagePath);
1135 if (pixmap.isNull())
1136 return;
1137
1138 if (image->color().isValid()) {
1139 QImage coloredImage = pixmap.toImage();
1140 QPainter imagePainter(&coloredImage);
1141 imagePainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
1142 imagePainter.fillRect(coloredImage.rect(), image->color());
1143 imagePainter.end();
1144 pixmap = QPixmap::fromImage(coloredImage);
1145 }
1146 painter->setOpacity(opacity);
1147 const QSizeF pixmapSize = pixmap.deviceIndependentSize();
1148
1149 switch (image->fillMode()) {
1150 case QQuickImage::PreserveAspectFit: {
1151 const QSize scaled = pixmapSize.scaled(rect.size(), Qt::KeepAspectRatio).toSize();
1152 const QPointF topLeft(
1153 rect.x() + (rect.width() - scaled.width()) / 2.0,
1154 rect.y() + (rect.height() - scaled.height()) / 2.0
1155 );
1156 painter->drawPixmap(QRectF(topLeft, scaled), pixmap, QRectF(QPointF(0, 0), pixmapSize));
1157 break;
1158 }
1159 case QQuickImage::PreserveAspectCrop: {
1160 const QSize scaled = pixmapSize.scaled(rect.size(), Qt::KeepAspectRatioByExpanding).toSize();
1161 const QPointF topLeft(
1162 rect.x() + (rect.width() - scaled.width()) / 2.0,
1163 rect.y() + (rect.height() - scaled.height()) / 2.0
1164 );
1165 QPainterStateGuard stateGuard(painter);
1166 painter->setClipRect(rect);
1167 painter->drawPixmap(QRectF(topLeft, scaled), pixmap, QRectF(QPointF(0, 0), pixmapSize));
1168 break;
1169 }
1170 // TODO: Support remaining fill modes
1171 case QQuickImage::Stretch:
1172 default:
1173 painter->drawPixmap(rect, pixmap, QRectF(QPointF(0, 0), pixmapSize));
1174 break;
1175 }
1176}
1177
1178QRect QStyleKitStylePrivate::getAlignedRectInContainer(const QRect &container, const QSize &size,
1179 uint alignment, const QMargins &padding,
1180 const QMargins &margins) const
1181{
1182 QRect r(QPoint(0, 0), size);
1183
1184 if (alignment & Qt::AlignLeft)
1185 r.moveLeft(container.x() + padding.left() + margins.left());
1186 else if (alignment & Qt::AlignHCenter)
1187 r.moveLeft(container.x() + (container.width() - size.width()) / 2);
1188 else // AlignRight
1189 r.moveLeft(container.x() + container.width() - padding.right() - margins.right() - size.width());
1190
1191 if (alignment & Qt::AlignTop)
1192 r.moveTop(container.y() + padding.top() + margins.top());
1193 else if (alignment & Qt::AlignVCenter)
1194 r.moveTop(container.y() + (container.height() - size.height()) / 2);
1195 else // AlignBottom
1196 r.moveTop(container.y() + container.height() - padding.bottom() - margins.bottom() - size.height());
1197
1198 return r;
1199}
1200
1201QStyleKitStylePrivate::ControlMetrics QStyleKitStylePrivate::metricsForReader(QQStyleKitReader *reader) const
1202{
1203 Q_ASSERT(reader);
1204 const QQStyleKitControlProperties *props = reader->global();
1205 ControlMetrics metrics;
1206 metrics.bgImplicitSize = QSize(0, 0);
1207 metrics.textPadding = QMargins(0, 0, 0, 0);
1208 metrics.padding = QMargins(props->leftPadding(), props->topPadding(),
1209 props->rightPadding(), props->bottomPadding());
1210 metrics.spacing = props->spacing();
1211 metrics.margins = QMargins(0, 0, 0, 0);
1212 metrics.indicatorImplicitSize = QSize(0, 0);
1213 metrics.indicatorMargins = QMargins(0, 0, 0, 0);
1214 metrics.foregroundImplicitSize = QSize(0, 0);
1215 metrics.foregroundMargins = QMargins(0, 0, 0, 0);
1216 const auto *background = props->background();
1217 if (background) {
1218 auto scale = background->scale();
1219 if (scale == 0)
1220 scale = 1.0;
1221 metrics.bgImplicitSize = QSize(static_cast<int>(background->implicitWidth()), static_cast<int>(background->implicitHeight())) * scale;
1222 metrics.margins = elementMargins(background);
1223 }
1224 const auto *textProps = props->text();
1225 if (textProps)
1226 metrics.textPadding = QMargins(textProps->leftPadding(), textProps->topPadding(),textProps->rightPadding(), textProps->bottomPadding());
1227 const auto *indicator = props->indicator();
1228 if (indicator) {
1229 auto scale = indicator->scale();
1230 if (scale == 0)
1231 scale = 1.0;
1232 metrics.indicatorMargins = elementMargins(indicator);
1233 metrics.indicatorImplicitSize = QSize(std::max(.0, indicator->implicitWidth()),
1234 std::max(.0, indicator->implicitHeight())) * scale;
1235
1236 const auto *foreground = indicator->foreground();
1237 if (foreground) {
1238 auto scale = foreground->scale();
1239 if (scale == 0)
1240 scale = 1.0;
1241 metrics.foregroundMargins = elementMargins(foreground);
1242 const auto foregroundW = resolvedImplicitWidth(foreground,
1243 std::max(.0, qreal(metrics.indicatorImplicitSize.width() - metrics.foregroundMargins.left() - metrics.foregroundMargins.right())));
1244 const auto foregroundH = resolvedImplicitHeight(foreground,
1245 std::max(.0, qreal(metrics.indicatorImplicitSize.height() - metrics.foregroundMargins.top() - metrics.foregroundMargins.bottom())));
1246 metrics.foregroundImplicitSize = QSize(foregroundW, foregroundH) * scale;
1247 }
1248 }
1249 const auto *handle = props->handle();
1250 if (handle) {
1251 auto scale = handle->scale();
1252 if (scale == 0)
1253 scale = 1.0;
1254 metrics.handleImplicitSize = QSize(std::max(.0, handle->implicitWidth()),
1255 std::max(.0, handle->implicitHeight())) * scale;
1256 metrics.handleMargins = elementMargins(handle);
1257 }
1258 return metrics;
1259}
1260
1261/*!
1262 Constructs a QStyleKitStyle with no style loaded.
1263
1264 Use \l setStylePath() to load a QML \l Style after construction.
1265 Until a style is loaded, the style uses a default fallback style.
1266*/
1267QStyleKitStyle::QStyleKitStyle()
1268 : QCommonStyle(*new QStyleKitStylePrivate())
1269{
1270}
1271
1272/*!
1273 Constructs a QStyleKitStyle and loads the QML \l Style at \a filePath.
1274
1275 \a filePath is a path to a local file or a path to a file in the resource
1276 file system; a relative path is resolved against the application's working
1277 directory. If the path is invalid or the root object of the loaded component
1278 is not a \l Style, a warning is emitted and the constructed style uses a
1279 default fallback style until a valid \l stylePath is set.
1280*/
1281QStyleKitStyle::QStyleKitStyle(const QString &filePath)
1282 : QCommonStyle(*new QStyleKitStylePrivate())
1283{
1284 Q_D(QStyleKitStyle);
1285 d->stylePath = filePath;
1286 if (!d->loadStyle()) {
1287 qWarning("QStyleKitStyle: Failed to load style from %s", qPrintable(filePath));
1288 }
1289}
1290
1291/*!
1292 Destroys the QStyleKitStyle.
1293*/
1294QStyleKitStyle::~QStyleKitStyle()
1295{
1296}
1297
1298/*!
1299 Returns the path of the currently loaded \l Style file.
1300
1301 \sa setStylePath()
1302*/
1303QString QStyleKitStyle::stylePath() const
1304{
1305 Q_D(const QStyleKitStyle);
1306 return d->stylePath;
1307}
1308
1309/*!
1310 Loads the QML \l Style at \a filePath and applies it to all widgets.
1311
1312 \a filePath is a path to a local file or a path to a file in the resource
1313 file system; see the \l stylePath property for the accepted forms. If it is
1314 the same as the current \l stylePath, this function does nothing. If the new
1315 style cannot be loaded, the previously loaded style remains active and a
1316 warning is emitted; \l stylePathChanged() is still emitted to reflect
1317 the changed property value.
1318
1319 \sa stylePath()
1320*/
1321void QStyleKitStyle::setStylePath(const QString &filePath)
1322{
1323 Q_D(QStyleKitStyle);
1324 if (d->stylePath == filePath)
1325 return;
1326 d->stylePath = filePath;
1327 if (d->loadStyle())
1328 d->updateStyle();
1329 emit stylePathChanged();
1330}
1331
1332/*!
1333 Returns the name of the currently active theme, or an empty string
1334 if no \l Style has been loaded.
1335
1336 \sa setThemeName(), themeNames()
1337*/
1338QString QStyleKitStyle::themeName() const
1339{
1340 Q_D(const QStyleKitStyle);
1341 return d->style ? d->style->themeName() : QString();
1342}
1343
1344/*!
1345 Activates the theme named \a themeName.
1346
1347 \a themeName must be one of the entries in \l themeNames(), or the
1348 special name \c System to follow the platform color scheme. If no
1349 \l Style has been loaded, this function emits a warning and returns
1350 without changing the active theme.
1351
1352 \sa themeName(), themeNames()
1353*/
1354void QStyleKitStyle::setThemeName(const QString &themeName)
1355{
1356 Q_D(QStyleKitStyle);
1357 if (!d->style) {
1358 qWarning("QStyleKitStyle: No style loaded, cannot set theme name.");
1359 return;
1360 }
1361 if (d->style->themeName() == themeName)
1362 return;
1363 d->style->setThemeName(themeName);
1364 d->updateStyle();
1365 emit themeNameChanged();
1366}
1367
1368/*!
1369 Returns the names of all themes exposed by the loaded \l Style,
1370 including the built-in \c Light and \c Dark themes and any custom
1371 themes defined by the style. Returns an empty list when no style
1372 is loaded.
1373
1374 \sa customThemeNames(), themeName()
1375*/
1376QStringList QStyleKitStyle::themeNames() const
1377{
1378 Q_D(const QStyleKitStyle);
1379 return d->style ? d->style->themeNames() : QStringList();
1380}
1381
1382/*!
1383 Returns the names of the custom themes defined by the loaded
1384 \l Style, excluding the built-in \c Light and \c Dark themes.
1385 Returns an empty list when no style is loaded.
1386
1387 \sa themeNames()
1388*/
1389QStringList QStyleKitStyle::customThemeNames() const
1390{
1391 Q_D(const QStyleKitStyle);
1392 return d->style ? d->style->customThemeNames() : QStringList();
1393}
1394
1395/*! \reimp */
1396void QStyleKitStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p,
1397 const QWidget *w) const
1398{
1399 Q_D(const QStyleKitStyle);
1400
1401 switch (pe) {
1402 case PE_FrameButtonBevel:
1403 if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
1404 const auto controlType = btn->features & QStyleOptionButton::Flat
1405 ? QQStyleKitReader::ControlType::FlatButton
1406 : QQStyleKitReader::ControlType::Button;
1407 const auto r = d->resolve(w, controlType, btn->state);
1408 if (!r.isValid())
1409 break;
1410 d->drawStyledItemRect(r.background(), opt->rect, p);
1411 return;
1412 }
1413 break;
1414 case PE_Frame: {
1415 QQStyleKitReader::ControlType controlType;
1416 const bool isPopup = w && (false
1417#if QT_CONFIG(menu)
1418 || qobject_cast<const QMenu *>(w)
1419#endif
1420#if QT_CONFIG(combobox)
1421 || (w && w->inherits("QComboBoxPrivateContainer"))
1422#endif
1423 );
1424 if (isPopup)
1425 controlType = QQStyleKitReader::ControlType::Popup;
1426#if QT_CONFIG(lineedit)
1427 else if (qobject_cast<const QLineEdit *>(w))
1428 controlType = QQStyleKitReader::ControlType::TextField;
1429#endif
1430 else
1431 controlType = QQStyleKitReader::ControlType::Frame;
1432 const auto r = d->resolve(w, controlType, opt->state);
1433 if (!r.isValid())
1434 break;
1435 d->drawStyledItemRect(r.background(), opt->rect, p);
1436 return;
1437 }
1438#if QT_CONFIG(lineedit)
1439 case PE_PanelLineEdit:
1440 if (const auto *lineEdit = qstyleoption_cast<const QStyleOptionFrame *>(opt)) {
1441 // LineEdit sets Sunken flag to indicate Sunken frame,
1442 // but the style uses it to indicate pressed state, so ignore it
1443 QStyleOption lineEditOpt(*lineEdit);
1444 lineEditOpt.state &= ~QStyle::State_Sunken;
1445 const auto r = d->resolve(w, QQStyleKitReader::ControlType::TextField, lineEditOpt.state);
1446 if (!r.isValid())
1447 break;
1448
1449 // LineEdit draws its own text using the Text role so update that role in the palette
1450 if (auto *le = qobject_cast<const QLineEdit *>(w)) {
1451 if (const auto *txt = r.text(); txt && txt->isDefined(QQSK::Property::Color)) {
1452 QPalette stylePalette;
1453 stylePalette.setColor(QPalette::Text, txt->color());
1454 d->setStylePalette(const_cast<QLineEdit *>(le), stylePalette);
1455 }
1456 const_cast<QStyleKitStylePrivate *>(d)->setStyleFont(const_cast<QLineEdit *>(le), r.font());
1457 }
1458
1459 const QObject *parent = w ? w->parent() : nullptr;
1460#if QT_CONFIG(spinbox)
1461 const bool isInSpinBox = qobject_cast<const QAbstractSpinBox *>(parent);
1462#else
1463 const bool isInSpinBox = false;
1464#endif
1465#if QT_CONFIG(combobox)
1466 const bool isInComboBox = qobject_cast<const QComboBox *>(parent);
1467#else
1468 const bool isInComboBox = false;
1469#endif
1470 // For spinbox and combobox, the line edit doesn't have its own background in the Controls style
1471 if (isInSpinBox || isInComboBox)
1472 return;
1473
1474 d->drawStyledItemRect(r.background(), opt->rect, p);
1475 return;
1476 }
1477 break;
1478#endif // QT_CONFIG(lineedit)
1479#if QT_CONFIG(itemviews)
1480 case PE_PanelItemViewItem: {
1481 const auto r = d->resolveItemViewItem(w, opt, QQStyleKitReader::ControlType::ItemDelegate, opt->state);
1482 if (!r.isValid())
1483 break;
1484 d->drawStyledItemRect(r.background(), opt->rect, p);
1485 return;
1486 }
1487#endif // QT_CONFIG(itemviews)
1488 case PE_IndicatorCheckBox:
1489 case PE_IndicatorRadioButton: {
1490 const auto controlType = pe == PE_IndicatorCheckBox
1491 ? QQStyleKitReader::ControlType::CheckBox
1492 : QQStyleKitReader::ControlType::RadioButton;
1493 const auto r = d->resolve(w, controlType, opt->state);
1494 if (!r.isValid())
1495 break;
1496 d->drawControlIndicator(r.indicator(), opt->rect, p);
1497 return;
1498 }
1499 case PE_IndicatorArrowDown: {
1500 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ComboBox, opt->state);
1501 if (!r.isValid())
1502 break;
1503 d->drawControlIndicator(r.indicator(), opt->rect, p);
1504 return;
1505 }
1506 case PE_IndicatorItemViewItemCheck: {
1507 const auto r = d->resolveItemViewItem(w, opt, QQStyleKitReader::ControlType::ItemDelegate, opt->state);
1508 if (!r.isValid())
1509 break;
1510 d->drawControlIndicator(r.indicator(), opt->rect, p);
1511 return;
1512 }
1513#if QT_CONFIG(spinbox)
1514 case PE_IndicatorSpinUp:
1515 case PE_IndicatorSpinDown: {
1516 const auto r = d->resolve(w, QQStyleKitReader::ControlType::SpinBox, opt->state);
1517 if (!r.isValid())
1518 break;
1519 const auto *indicator = r.indicator();
1520 const auto *upDownIndicator = indicator ? (pe == PE_IndicatorSpinUp ? indicator->first() : indicator->second()) : nullptr;
1521 d->drawControlIndicator(upDownIndicator, opt->rect, p);
1522 return;
1523 }
1524#endif // QT_CONFIG(spinbox)
1525#if QT_CONFIG(groupbox)
1526 case PE_FrameGroupBox: {
1527 const auto r = d->resolve(w, QQStyleKitReader::ControlType::GroupBox, opt->state);
1528 if (!r.isValid())
1529 break;
1530 d->drawStyledItemRect(r.background(), opt->rect, p);
1531 return;
1532 }
1533#endif // QT_CONFIG(groupbox)
1534 default:
1535 break;
1536 }
1537 QCommonStyle::drawPrimitive(pe, opt, p, w);
1538}
1539
1540/*! \reimp */
1541void QStyleKitStyle::drawControl(ControlElement element, const QStyleOption *opt, QPainter *p,
1542 const QWidget *w) const
1543{
1544 Q_D(const QStyleKitStyle);
1545
1546 switch (element) {
1547 case CE_PushButton:
1548 if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
1549 proxy()->drawControl(CE_PushButtonBevel, btn, p, w);
1550 QStyleOptionButton btnContent(*btn);
1551 btnContent.rect = subElementRect(SE_PushButtonContents, btn, w);
1552 proxy()->drawControl(CE_PushButtonLabel, &btnContent, p, w);
1553 }
1554 return;
1555 case CE_PushButtonBevel:
1556 if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
1557 QStyleOptionButton btnBg(*btn);
1558 btnBg.rect = subElementRect(SE_PushButtonBevel, btn, w);
1559 proxy()->drawPrimitive(PE_FrameButtonBevel, &btnBg, p, w);
1560 }
1561 return;
1562 case CE_PushButtonLabel:
1563 if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
1564 const auto controlType = btn->features & QStyleOptionButton::Flat ? QQStyleKitReader::ControlType::FlatButton : QQStyleKitReader::ControlType::Button;
1565 const auto r = d->resolve(w, controlType, btn->state);
1566 if (!r.isValid())
1567 break;
1568 const auto metrics = r.metrics;
1569
1570 QRect textRect = opt->rect;
1571 uint textFlags = Qt::TextShowMnemonic;
1572 if (!styleHint(SH_UnderlineShortcut, opt, w))
1573 textFlags |= Qt::TextHideMnemonic;
1574
1575 // icon
1576 const QIcon icon = btn->icon;
1577 if (!icon.isNull()) {
1578 const auto *textProps = r.text();
1579 uint iconTextFlags = textFlags;
1580 iconTextFlags |= textProps
1581 ? resolvedAlignment(textProps->alignment(), Qt::AlignHCenter, Qt::AlignVCenter)
1582 : uint(Qt::AlignHCenter | Qt::AlignVCenter);
1583 QIcon::Mode mode = btn->state & State_Enabled ? QIcon::Normal : QIcon::Disabled;
1584 if (mode == QIcon::Normal && btn->state & State_HasFocus)
1585 mode = QIcon::Active;
1586 QIcon::State iconState = btn->state & State_On ? QIcon::On : QIcon::Off;
1587 const auto paintDeviceDpr = p->device()->devicePixelRatio();
1588 QPixmap pixmap = icon.pixmap(btn->iconSize, paintDeviceDpr, mode, iconState);
1589 int pixmapWidth = pixmap.width() / pixmap.devicePixelRatio();
1590 int pixmapHeight = pixmap.height() / pixmap.devicePixelRatio();
1591 int labelWidth = pixmapWidth;
1592 int iconSpacing = metrics->spacing;
1593 int textWidth = btn->fontMetrics.boundingRect(opt->rect, iconTextFlags, btn->text).width();
1594 if (!btn->text.isEmpty())
1595 labelWidth += (textWidth + iconSpacing + textProps->leftPadding() + textProps->rightPadding());
1596
1597 QRect iconRect;
1598 if (iconTextFlags & Qt::AlignLeft) {
1599 iconRect = QRect(textRect.x(), textRect.y() + (textRect.height() - pixmapHeight) / 2,
1600 pixmapWidth, pixmapHeight);
1601 } else if (iconTextFlags & Qt::AlignHCenter) {
1602 iconRect = QRect(textRect.x() + (textRect.width() - labelWidth) / 2,
1603 textRect.y() + (textRect.height() - pixmapHeight) / 2,
1604 pixmapWidth, pixmapHeight);
1605 } else {
1606 iconRect = QRect(textRect.x() + textRect.width() - labelWidth,
1607 textRect.y() + (textRect.height() - pixmapHeight) / 2,
1608 pixmapWidth, pixmapHeight);
1609 }
1610 iconRect = visualRect(btn->direction, textRect, iconRect);
1611
1612 // After placing the icon, left-align the text relative to it
1613 textFlags &= ~Qt::AlignHorizontal_Mask;
1614 textFlags |= Qt::AlignLeft;
1615 if (btn->direction == Qt::RightToLeft)
1616 textRect.setRight(iconRect.left() - iconSpacing);
1617 else
1618 textRect.setLeft(iconRect.left() + iconRect.width() + iconSpacing);
1619
1620 p->drawPixmap(iconRect, pixmap);
1621 }
1622
1623 if (btn->features & QStyleOptionButton::HasMenu) {
1624 int indicatorSize = pixelMetric(PM_MenuButtonIndicator, btn, w);
1625 if (btn->direction == Qt::LeftToRight)
1626 textRect = textRect.adjusted(0, 0, -indicatorSize, 0);
1627 else
1628 textRect = textRect.adjusted(indicatorSize, 0, 0, 0);
1629 }
1630 d->drawControlText(r.text(), r.font(), textRect, btn->text, textFlags, p);
1631 return;
1632 }
1633 break;
1634 case CE_CheckBox:
1635 case CE_RadioButton:
1636 if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
1637 const auto controlType = element == CE_CheckBox ? QQStyleKitReader::ControlType::CheckBox
1638 : QQStyleKitReader::ControlType::RadioButton;
1639 const auto r = d->resolve(w, controlType, btn->state);
1640 if (!r.isValid())
1641 break;
1642 QRect backgroundRect = btn->rect.marginsRemoved(r.metrics->margins);
1643 // background
1644 d->drawStyledItemRect(r.background(), backgroundRect, p);
1645 // label
1646 QStyleOptionButton btnContent(*btn);
1647 const auto contentElement = element == CE_CheckBox ? SE_CheckBoxContents : SE_RadioButtonContents;
1648 btnContent.rect = subElementRect(contentElement, btn, w);
1649 const auto labelElement = element == CE_CheckBox ? CE_CheckBoxLabel : CE_RadioButtonLabel;
1650 proxy()->drawControl(labelElement, &btnContent, p, w);
1651 // indicator
1652 QStyleOptionButton indicator(*btn);
1653 const auto indicatorElement = element == CE_CheckBox ? SE_CheckBoxIndicator : SE_RadioButtonIndicator;
1654 indicator.rect = subElementRect(indicatorElement, btn, w);
1655 const auto primitiveElement = element == CE_CheckBox ? PE_IndicatorCheckBox : PE_IndicatorRadioButton;
1656 proxy()->drawPrimitive(primitiveElement, &indicator, p, w);
1657 return;
1658 }
1659 break;
1660 case CE_CheckBoxLabel:
1661 case CE_RadioButtonLabel:
1662 if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
1663 const auto controlType = element == CE_CheckBoxLabel ? QQStyleKitReader::ControlType::CheckBox
1664 : QQStyleKitReader::ControlType::RadioButton;
1665 const auto r = d->resolve(w, controlType, btn->state);
1666 if (!r.isValid())
1667 break;
1668 uint textFlags = Qt::TextShowMnemonic;
1669 if (!styleHint(SH_UnderlineShortcut, opt, w))
1670 textFlags |= Qt::TextHideMnemonic;
1671 d->drawControlText(r.text(), r.font(), opt->rect, btn->text, textFlags, p);
1672 return;
1673 }
1674 break;
1675#if QT_CONFIG(combobox)
1676 case CE_ComboBoxLabel:
1677 if (const auto *comboBox = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
1678 if (comboBox->editable)
1679 return;
1680 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ComboBox, comboBox->state);
1681 if (!r.isValid())
1682 break;
1683 const QRect textRect = subControlRect(CC_ComboBox, comboBox, SC_ComboBoxEditField, w)
1684 .marginsRemoved(r.metrics->textPadding);
1685 uint textFlags = Qt::TextShowMnemonic;
1686 if (!styleHint(SH_UnderlineShortcut, opt, w))
1687 textFlags |= Qt::TextHideMnemonic;
1688 d->drawControlText(r.text(), r.font(), textRect, comboBox->currentText, textFlags, p);
1689 return;
1690 }
1691 break;
1692#endif // QT_CONFIG(combobox)
1693#if QT_CONFIG(progressbar)
1694 case CE_ProgressBar:
1695 if (const QStyleOptionProgressBar *progressBar = qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) {
1696 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ProgressBar, progressBar->state);
1697 if (!r.isValid())
1698 break;
1699 QRect backgroundRect = progressBar->rect.marginsRemoved(r.metrics->margins);
1700 // background
1701 d->drawStyledItemRect(r.background(), backgroundRect, p);
1702 // groove
1703 QStyleOptionProgressBar contents(*progressBar);
1704 contents.rect = subElementRect(SE_ProgressBarGroove, progressBar, w);
1705 proxy()->drawControl(CE_ProgressBarGroove, &contents, p, w);
1706 // track
1707 contents.rect = subElementRect(SE_ProgressBarContents, progressBar, w);
1708 proxy()->drawControl(CE_ProgressBarContents, &contents, p, w);
1709 // We intentionally don't draw the label as it is not drawn on the Controls Style
1710 return;
1711 }
1712 break;
1713 case CE_ProgressBarGroove: {
1714 // groove = indicator background
1715 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ProgressBar, opt->state);
1716 if (!r.isValid())
1717 break;
1718 d->drawStyledItemRect(r.indicator(), opt->rect, p);
1719 return;
1720 }
1721 case CE_ProgressBarContents:
1722 // contents = indicator foreground
1723 if (const auto *progressBar = qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) {
1724 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ProgressBar, progressBar->state);
1725 if (!r.isValid())
1726 break;
1727 const auto progress = progressBar->progress;
1728 const auto ratio = progressBar->maximum > progressBar->minimum ? (progress - progressBar->minimum) / static_cast<qreal>(progressBar->maximum - progressBar->minimum) : 0;
1729 const auto width = static_cast<int>(progressBar->rect.width() * ratio);
1730 const auto x = progressBar->invertedAppearance ? progressBar->rect.right() - width : progressBar->rect.left();
1731 const auto contentsRect = QRect(x, progressBar->rect.y(), width, progressBar->rect.height());
1732 const auto *foreground = r.indicator() ? r.indicator()->foreground() : nullptr;
1733 if (foreground && foreground->visible() && foreground->opacity() > 0)
1734 d->drawStyledItemRect(foreground, contentsRect, p);
1735 return;
1736 }
1737 break;
1738#endif // QT_CONFIG(progressbar)
1739#if QT_CONFIG(itemviews)
1740 case CE_ItemViewItem:
1741 if (const auto *itemViewOption = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) {
1742 const auto r = d->resolveItemViewItem(w, opt, QQStyleKitReader::ControlType::ItemDelegate, opt->state);
1743 if (!r.isValid())
1744 break;
1745 QStyleOptionViewItem optBg(*itemViewOption);
1746 optBg.rect = optBg.rect.marginsRemoved(r.metrics->margins);
1747 QRect indicatorRect = subElementRect(SE_ItemViewItemCheckIndicator, itemViewOption, w);
1748 QRect iconRect = subElementRect(SE_ItemViewItemDecoration, itemViewOption, w);
1749 QRect textRect = subElementRect(SE_ItemViewItemText, itemViewOption, w);
1750 // Capture text properties + font now, before the indicator
1751 // drawPrimitive below potentially mutates the reader's state
1752 const auto *itemTextProps = r.text();
1753 const QFont itemFont = r.font();
1754
1755 // background
1756 proxy()->drawPrimitive(PE_PanelItemViewItem, &optBg, p, w);
1757
1758 // indicator
1759 if (itemViewOption->features & QStyleOptionViewItem::HasCheckIndicator) {
1760 QStyleOptionViewItem option(*itemViewOption);
1761 option.rect = indicatorRect;
1762 option.state = option.state & ~QStyle::State_HasFocus;
1763
1764 switch (itemViewOption->checkState) {
1765 case Qt::Unchecked:
1766 option.state |= QStyle::State_Off;
1767 break;
1768 case Qt::PartiallyChecked:
1769 option.state |= QStyle::State_NoChange;
1770 break;
1771 case Qt::Checked:
1772 option.state |= QStyle::State_On;
1773 break;
1774 }
1775 proxy()->drawPrimitive(QStyle::PE_IndicatorItemViewItemCheck, &option, p, w);
1776 }
1777
1778 // icon
1779 QIcon::Mode mode = QIcon::Normal;
1780 if (!(itemViewOption->state & QStyle::State_Enabled))
1781 mode = QIcon::Disabled;
1782 else if (itemViewOption->state & QStyle::State_Selected)
1783 mode = QIcon::Selected;
1784 QIcon::State state = itemViewOption->state & QStyle::State_Open ? QIcon::On : QIcon::Off;
1785 itemViewOption->icon.paint(p, iconRect, itemViewOption->decorationAlignment, mode, state);
1786
1787 // draw text
1788 uint textFlags = Qt::TextShowMnemonic;
1789 if (!styleHint(SH_UnderlineShortcut, opt, w))
1790 textFlags |= Qt::TextHideMnemonic;
1791 d->drawControlText(itemTextProps, itemFont, textRect, itemViewOption->text, textFlags, p,
1792 Qt::AlignLeft | Qt::AlignVCenter);
1793 return;
1794 }
1795 break;
1796#endif // QT_CONFIG(itemviews)
1797#ifndef QT_NO_FRAME
1798 case CE_ShapedFrame:
1799#if QT_CONFIG(combobox)
1800 if (w && w->inherits("QComboBoxPrivateContainer")) {
1801 const auto r = d->resolve(w, QQStyleKitReader::ControlType::Popup, opt->state);
1802 if (!r.isValid())
1803 break;
1804 QRect backgroundRect = opt->rect.marginsRemoved(r.metrics->margins);
1805 d->drawStyledItemRect(r.background(), backgroundRect, p);
1806 return;
1807 }
1808#endif // QT_CONFIG(combobox)
1809#if QT_CONFIG(textedit)
1810 if (qobject_cast<const QTextEdit *>(w)) {
1811 const auto r = d->resolve(w, QQStyleKitReader::ControlType::TextArea, opt->state);
1812 if (!r.isValid())
1813 break;
1814 QRect backgroundRect = opt->rect.marginsRemoved(r.metrics->margins);
1815 d->drawStyledItemRect(r.background(), backgroundRect, p);
1816 return;
1817 }
1818#endif // QT_CONFIG(textedit)
1819 break;
1820#if QT_CONFIG(scrollbar)
1821 case CE_ScrollBarSlider: {
1822 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ScrollBar, opt->state);
1823 if (!r.isValid())
1824 break;
1825 d->drawControlIndicator(r.indicator(), opt->rect, p);
1826 return;
1827 }
1828#endif
1829#endif // QT_NO_FRAME
1830 default:
1831 break;
1832 }
1833 QCommonStyle::drawControl(element, opt, p, w);
1834}
1835
1836/*! \reimp */
1837QRect QStyleKitStyle::subElementRect(SubElement r, const QStyleOption *opt, const QWidget *widget) const
1838{
1839 Q_D(const QStyleKitStyle);
1840
1841 switch (r) {
1842 case SE_PushButtonLayoutItem:
1843 case SE_PushButtonBevel:
1844 if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
1845 const auto controlType = btn->features & QStyleOptionButton::Flat
1846 ? QQStyleKitReader::ControlType::FlatButton
1847 : QQStyleKitReader::ControlType::Button;
1848 const auto resolved = d->resolveLayout(controlType, opt->state);
1849 if (!resolved.isValid())
1850 break;
1851 const auto &metrics = *resolved.metrics;
1852 QRect rect = opt->rect.marginsRemoved(metrics.margins);
1853 return visualRect(opt->direction, opt->rect, rect);
1854 }
1855 break;
1856 case SE_PushButtonContents:
1857 if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
1858 const auto controlType = btn->features & QStyleOptionButton::Flat
1859 ? QQStyleKitReader::ControlType::FlatButton
1860 : QQStyleKitReader::ControlType::Button;
1861 const auto resolved = d->resolveLayout(controlType, opt->state);
1862 if (!resolved.isValid())
1863 break;
1864 const auto &metrics = *resolved.metrics;
1865 QRect rect = opt->rect.marginsRemoved(metrics.margins + metrics.padding + metrics.textPadding);
1866 return visualRect(opt->direction, opt->rect, rect);
1867 }
1868 break;
1869 case SE_CheckBoxContents:
1870 case SE_RadioButtonContents: {
1871 const auto controlType = r == SE_CheckBoxContents ? QQStyleKitReader::ControlType::CheckBox
1872 : QQStyleKitReader::ControlType::RadioButton;
1873 const auto resolved = d->resolveLayout(controlType, opt->state);
1874 if (!resolved.isValid())
1875 break;
1876 const auto &metrics = *resolved.metrics;
1877 QRect contentsRect = opt->rect.marginsRemoved(metrics.margins + metrics.padding);
1878 const auto subElement = r == SE_CheckBoxContents ? SE_CheckBoxIndicator : SE_RadioButtonIndicator;
1879 QRect indicatorRect = visualRect(opt->direction, opt->rect, subElementRect(subElement, opt, widget));
1880 const int spacing = metrics.spacing;
1881 const auto *indicator = resolved.indicator();
1882 const uint alignment = indicator
1883 ? resolvedAlignment(indicator->alignment(), Qt::AlignLeft, Qt::AlignVCenter)
1884 : uint(Qt::AlignLeft | Qt::AlignVCenter);
1885 if (alignment & Qt::AlignLeft) {
1886 contentsRect.setLeft(indicatorRect.right() + spacing + metrics.textPadding.left());
1887 } else if (alignment & Qt::AlignHCenter) {
1888 contentsRect.setLeft(indicatorRect.right() + spacing + metrics.textPadding.left());
1889 contentsRect.setRight(indicatorRect.left() - spacing - metrics.textPadding.right());
1890 } else {
1891 contentsRect.setRight(indicatorRect.left() - spacing - metrics.textPadding.right());
1892 }
1893 return visualRect(opt->direction, opt->rect, contentsRect);
1894 }
1895 case SE_CheckBoxIndicator:
1896 case SE_RadioButtonIndicator: {
1897 const auto controlType = r == SE_CheckBoxIndicator
1898 ? QQStyleKitReader::ControlType::CheckBox
1899 : QQStyleKitReader::ControlType::RadioButton;
1900 const auto resolved = d->resolveLayout(controlType, opt->state);
1901 if (!resolved.isValid())
1902 break;
1903 const auto &metrics = *resolved.metrics;
1904 QRect rect = opt->rect.marginsRemoved(metrics.margins);
1905 const auto *indicator = resolved.indicator();
1906 if (!indicator || !indicator->visible() || indicator->opacity() == 0)
1907 return rect;
1908
1909 const int w = resolvedImplicitWidth(indicator,
1910 rect.width() - metrics.padding.left() - metrics.padding.right()
1911 - metrics.indicatorMargins.left() - metrics.indicatorMargins.right());
1912 const int h = resolvedImplicitHeight(indicator,
1913 rect.height() - metrics.padding.top() - metrics.padding.bottom()
1914 - metrics.indicatorMargins.top() - metrics.indicatorMargins.bottom());
1915 const uint alignment = resolvedAlignment(indicator->alignment(), Qt::AlignLeft, Qt::AlignVCenter);
1916 const QRect indicatorRect = d->getAlignedRectInContainer(
1917 rect, QSize(w, h), alignment, metrics.padding, metrics.indicatorMargins);
1918 return visualRect(opt->direction, rect, indicatorRect);
1919 }
1920#if QT_CONFIG(itemviews)
1921 case SE_ItemViewItemCheckIndicator: {
1922 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ItemDelegate, opt->state);
1923 if (!resolved.isValid())
1924 break;
1925 const auto &metrics = *resolved.metrics;
1926 QRect rect = opt->rect.marginsRemoved(metrics.margins);
1927 const auto *indicator = resolved.indicator();
1928 if (!indicator || !indicator->visible() || indicator->opacity() == 0)
1929 return rect;
1930
1931 const int w = resolvedImplicitWidth(indicator,
1932 rect.width() - metrics.padding.left() - metrics.padding.right()
1933 - metrics.indicatorMargins.left() - metrics.indicatorMargins.right());
1934 const int h = resolvedImplicitHeight(indicator,
1935 rect.height() - metrics.padding.top() - metrics.padding.bottom()
1936 - metrics.indicatorMargins.top() - metrics.indicatorMargins.bottom());
1937 const uint alignment = resolvedAlignment(indicator->alignment(), Qt::AlignLeft, Qt::AlignVCenter);
1938 const QRect indicatorRect = d->getAlignedRectInContainer(
1939 rect, QSize(w, h), alignment, metrics.padding, metrics.indicatorMargins);
1940 return visualRect(opt->direction, rect, indicatorRect);
1941 }
1942 case SE_ItemViewItemText:
1943 if (const auto *itemViewOption = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) {
1944 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ItemDelegate, opt->state);
1945 if (!resolved.isValid())
1946 break;
1947 const auto &metrics = *resolved.metrics;
1948 QRect contentsRect = opt->rect.marginsRemoved(metrics.margins + metrics.padding);
1949 QRect indicatorRect;
1950 if (itemViewOption->features & QStyleOptionViewItem::HasCheckIndicator)
1951 indicatorRect = subElementRect(SE_ItemViewItemCheckIndicator, opt, widget);
1952 const int spacing = metrics.spacing;
1953 QRect textRect = contentsRect;
1954 const auto *textProps = resolved.text();
1955 uint textAlign;
1956 if (textProps)
1957 textAlign = resolvedAlignment(textProps->alignment(), Qt::AlignLeft, Qt::AlignVCenter);
1958 else
1959 textAlign = Qt::AlignLeft | Qt::AlignVCenter;
1960 if (indicatorRect.isValid()) {
1961 if (textAlign & Qt::AlignLeft) {
1962 textRect.setLeft(indicatorRect.right() + spacing + metrics.textPadding.left());
1963 } else if (textAlign & Qt::AlignHCenter) {
1964 textRect.setLeft(indicatorRect.right() + spacing + metrics.textPadding.left());
1965 textRect.setRight(indicatorRect.left() - spacing - metrics.textPadding.right());
1966 } else {
1967 textRect.setRight(indicatorRect.left() - spacing - metrics.textPadding.right());
1968 }
1969 } else {
1970 if (textAlign & Qt::AlignLeft) {
1971 textRect.setLeft(textRect.left() + metrics.textPadding.left());
1972 } else if (textAlign & Qt::AlignHCenter) {
1973 textRect.setLeft(textRect.left() + metrics.textPadding.left() / 2);
1974 textRect.setRight(textRect.right() - metrics.textPadding.right() / 2);
1975 } else {
1976 textRect.setRight(textRect.right() - metrics.textPadding.right());
1977 }
1978 }
1979 if (textAlign & Qt::AlignTop) {
1980 textRect.setTop(textRect.top() + metrics.textPadding.top());
1981 } else if (textAlign & Qt::AlignVCenter) {
1982 textRect.setTop(textRect.top() + metrics.textPadding.top() / 2);
1983 textRect.setBottom(textRect.bottom() - metrics.textPadding.bottom() / 2);
1984 } else {
1985 textRect.setBottom(textRect.bottom() - metrics.textPadding.bottom());
1986 }
1987 return visualRect(opt->direction, opt->rect, textRect);
1988 }
1989 break;
1990#endif // QT_CONFIG(itemviews)
1991 case SE_PushButtonFocusRect:
1992 case SE_CheckBoxClickRect:
1993 case SE_RadioButtonClickRect:
1994 return opt->rect;
1995#if QT_CONFIG(progressbar)
1996 case SE_ProgressBarGroove: {
1997 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ProgressBar, opt->state);
1998 if (!resolved.isValid())
1999 break;
2000 const auto &metrics = *resolved.metrics;
2001 QRect rect = opt->rect.marginsRemoved(metrics.margins);
2002 const auto *indicator = resolved.indicator();
2003 if (!indicator || !indicator->visible() || indicator->opacity() == 0)
2004 return rect;
2005
2006 const int w = resolvedImplicitWidth(indicator,
2007 rect.width() - metrics.padding.left() - metrics.padding.right()
2008 - metrics.indicatorMargins.left() - metrics.indicatorMargins.right());
2009 const int h = resolvedImplicitHeight(indicator,
2010 rect.height() - metrics.padding.top() - metrics.padding.bottom()
2011 - metrics.indicatorMargins.top() - metrics.indicatorMargins.bottom());
2012 const uint alignment = resolvedAlignment(indicator->alignment(), Qt::AlignLeft, Qt::AlignVCenter);
2013 const QRect indicatorRect = d->getAlignedRectInContainer(
2014 rect, QSize(w, h), alignment, metrics.padding, metrics.indicatorMargins);
2015 return visualRect(opt->direction, rect, indicatorRect);
2016 }
2017 case SE_ProgressBarContents: {
2018 if (qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) {
2019 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ProgressBar, opt->state);
2020 if (!resolved.isValid())
2021 break;
2022 const auto *foreground = resolved.indicator() ? resolved.indicator()->foreground() : nullptr;
2023 if (!foreground || !foreground->visible() || foreground->opacity() == 0)
2024 return QRect();
2025
2026 QRect indicatorRect = subElementRect(SE_ProgressBarGroove, opt, widget);
2027 const auto foregroundW = resolvedImplicitWidth(foreground,
2028 indicatorRect.width() - foreground->leftMargin() - foreground->rightMargin());
2029 const auto foregroundH = resolvedImplicitHeight(foreground,
2030 indicatorRect.height() - foreground->topMargin() - foreground->bottomMargin());
2031 const uint foregroundAlign = resolvedAlignment(foreground->alignment(), Qt::AlignLeft, Qt::AlignVCenter);
2032 const QMargins foregroundMargins = elementMargins(foreground);
2033 const QRect foregroundRect = d->getAlignedRectInContainer(
2034 indicatorRect, QSize(foregroundW, foregroundH), foregroundAlign, QMargins(0, 0, 0, 0), foregroundMargins);
2035 return visualRect(opt->direction, opt->rect, foregroundRect);
2036 }
2037 break;
2038 }
2039#endif // QT_CONFIG(progressbar)
2040 case SE_LineEditContents:
2041 if (qstyleoption_cast<const QStyleOptionFrame *>(opt)) {
2042#if QT_CONFIG(spinbox)
2043 const bool isInSpinBox = widget && qobject_cast<const QSpinBox *>(widget->parentWidget());
2044#else
2045 const bool isInSpinBox = false;
2046#endif
2047 const auto controlType = isInSpinBox ? QQStyleKitReader::ControlType::SpinBox : QQStyleKitReader::ControlType::TextField;
2048 const auto resolved = d->resolveLayout(controlType, opt->state);
2049 if (!resolved.isValid())
2050 break;
2051 const auto &metrics = *resolved.metrics;
2052 QRect contentsRect = opt->rect.marginsRemoved(metrics.padding);
2053 const auto *textProps = resolved.text();
2054 const uint textAlign = resolvedAlignment(
2055 textProps ? textProps->alignment() : 0u, Qt::AlignLeft, Qt::AlignVCenter);
2056 if (textAlign & Qt::AlignLeft)
2057 contentsRect.setLeft(contentsRect.left() + metrics.textPadding.left());
2058 else if (textAlign & Qt::AlignHCenter) {
2059 contentsRect.setLeft(contentsRect.left() + metrics.textPadding.left() / 2);
2060 contentsRect.setRight(contentsRect.right() - metrics.textPadding.right() / 2);
2061 } else {
2062 contentsRect.setRight(contentsRect.right() - metrics.textPadding.right());
2063 }
2064 if (textAlign & Qt::AlignTop)
2065 contentsRect.setTop(contentsRect.top() + metrics.textPadding.top());
2066 else if (textAlign & Qt::AlignVCenter) {
2067 contentsRect.setTop(contentsRect.top() + metrics.textPadding.top() / 2);
2068 contentsRect.setBottom(contentsRect.bottom() - metrics.textPadding.bottom() / 2);
2069 } else {
2070 contentsRect.setBottom(contentsRect.bottom() - metrics.textPadding.bottom());
2071 }
2072 return visualRect(opt->direction, opt->rect, contentsRect);
2073 }
2074 break;
2075 case SE_ShapedFrameContents:
2076#if QT_CONFIG(combobox)
2077 if (widget && widget->inherits("QComboBoxPrivateContainer")) {
2078 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::Popup, opt->state);
2079 if (!resolved.isValid())
2080 break;
2081 const auto &metrics = *resolved.metrics;
2082 QRect contentsRect = opt->rect.marginsRemoved(metrics.margins + metrics.padding + metrics.textPadding);
2083 return visualRect(opt->direction, opt->rect, contentsRect);
2084 }
2085#endif
2086#if QT_CONFIG(textedit)
2087 if (qobject_cast<const QTextEdit *>(widget)) {
2088 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::TextArea, opt->state);
2089 if (!resolved.isValid())
2090 break;
2091 const auto &metrics = *resolved.metrics;
2092 QRect contentsRect = opt->rect.marginsRemoved(metrics.margins + metrics.padding + metrics.textPadding);
2093 return visualRect(opt->direction, opt->rect, contentsRect);
2094 }
2095#endif // textedit
2096 break;
2097 default:
2098 break;
2099 }
2100 return QCommonStyle::subElementRect(r, opt, widget);
2101}
2102
2103/*! \reimp */
2104void QStyleKitStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p,
2105 const QWidget *w) const
2106{
2107 Q_D(const QStyleKitStyle);
2108
2109 switch (cc) {
2110#if QT_CONFIG(slider)
2111 case CC_Slider:
2112 if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
2113 const auto r = d->resolve(w, QQStyleKitReader::ControlType::Slider, slider->state);
2114 if (!r.isValid())
2115 break;
2116 const auto &metrics = *r.metrics;
2117 QRect backgroundRect = opt->rect.marginsRemoved(metrics.margins);
2118
2119 // background
2120 d->drawStyledItemRect(r.background(), backgroundRect, p);
2121
2122 // groove
2123 if (slider->subControls & SC_SliderGroove) {
2124 // groove
2125 const auto grooveRect = proxy()->subControlRect(CC_Slider, opt, SC_SliderGroove, w);
2126 const auto *indicator = r.indicator();
2127 d->drawStyledItemRect(indicator, grooveRect, p);
2128
2129 // track
2130 const auto *foreground = indicator ? indicator->foreground() : nullptr;
2131 if (foreground && foreground->visible() && foreground->opacity() > 0) {
2132 const bool isHorizontal = slider->orientation == Qt::Horizontal;
2133 const qreal availableW = grooveRect.width() - foreground->leftMargin() - foreground->rightMargin();
2134 const qreal availableH = grooveRect.height() - foreground->topMargin() - foreground->bottomMargin();
2135
2136 const qreal range = slider->maximum - slider->minimum;
2137 const qreal ratio = range > 0 ? (slider->sliderPosition - slider->minimum) / range : 0;
2138
2139 const auto hAlign = foreground->alignment() & Qt::AlignHorizontal_Mask;
2140 const auto vAlign = foreground->alignment() & Qt::AlignVertical_Mask;
2141 const qreal fgW = resolvedImplicitWidth(foreground, availableW);
2142 const qreal fgH = resolvedImplicitHeight(foreground, availableH);
2143 const qreal fgX = hAlign & Qt::AlignRight
2144 ? grooveRect.left() + foreground->leftMargin() + availableW - fgW
2145 : hAlign & Qt::AlignHCenter
2146 ? grooveRect.left() + foreground->leftMargin() + (availableW - fgW) / 2
2147 : grooveRect.left() + foreground->leftMargin();
2148 const qreal fgY = vAlign & Qt::AlignBottom
2149 ? grooveRect.top() + foreground->topMargin() + availableH - fgH
2150 : vAlign & Qt::AlignVCenter
2151 ? grooveRect.top() + foreground->topMargin() + (availableH - fgH) / 2
2152 : grooveRect.top() + foreground->topMargin();
2153
2154 const qreal minW = foreground->minimumWidth();
2155 QRectF trackRect;
2156 if (isHorizontal) {
2157 const qreal trackW = foreground->fillWidth()
2158 ? minW + ratio * (fgW - minW)
2159 : ratio * fgW;
2160 trackRect = QRectF(fgX, fgY, trackW, fgH);
2161 } else {
2162 const qreal trackH = foreground->fillHeight()
2163 ? minW + ratio * (fgH - minW)
2164 : ratio * fgH;
2165 const qreal trackY = fgY + (1.0 - ratio) * (foreground->fillHeight() ? fgH - minW : fgH);
2166 trackRect = QRectF(fgX, trackY, fgW, trackH);
2167 }
2168 d->drawStyledItemRect(foreground, visualRect(opt->direction, grooveRect, trackRect.toAlignedRect()), p);
2169 }
2170 }
2171
2172 // handle
2173 if (slider->subControls & SC_SliderHandle) {
2174 QStyleOptionSlider handleOpt(*slider);
2175 handleOpt.rect = subControlRect(CC_Slider, opt, SC_SliderHandle, w);
2176 const auto *handle = r.handle();
2177 if (handle && handle->visible() && handle->opacity() > 0)
2178 d->drawStyledItemRect(handle, handleOpt.rect, p);
2179 }
2180 return;
2181 }
2182 break;
2183#endif // QT_CONFIG(slider)
2184#if QT_CONFIG(combobox)
2185 case CC_ComboBox:
2186 if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
2187 // background
2188 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ComboBox, combo->state);
2189 if (!r.isValid())
2190 break;
2191 QRect frameRect = subControlRect(CC_ComboBox, opt, SC_ComboBoxFrame, w);
2192 d->drawStyledItemRect(r.background(), frameRect, p);
2193
2194 // indicator
2195 if (combo->subControls & SC_ComboBoxArrow) {
2196 QStyleOptionComboBox indicatorOpt(*combo);
2197 indicatorOpt.rect = subControlRect(CC_ComboBox, opt, SC_ComboBoxArrow, w);
2198 proxy()->drawPrimitive(PE_IndicatorArrowDown, &indicatorOpt, p, w);
2199 }
2200
2201 // The editable combobox paints its own line edit using the QPalette::Text role
2202 // for the text color, so update that color in the palette
2203 if (auto *cb = qobject_cast<const QComboBox *>(w); cb && cb->isEditable()) {
2204 if (const auto *txt = r.text(); txt && txt->isDefined(QQSK::Property::Color)) {
2205 QPalette stylePalette;
2206 stylePalette.setColor(QPalette::Text, txt->color());
2207 d->setStylePalette(cb->lineEdit(), stylePalette);
2208 }
2209 }
2210 return;
2211 }
2212 break;
2213#endif // QT_CONFIG(combobox)
2214#if QT_CONFIG(spinbox)
2215 case CC_SpinBox:
2216 if (const QStyleOptionSpinBox *spin = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) {
2217 const auto r = d->resolve(w, QQStyleKitReader::ControlType::SpinBox, spin->state);
2218 if (!r.isValid())
2219 break;
2220 QRect frameRect = opt->rect.marginsRemoved(r.metrics->margins);
2221 // background
2222 d->drawStyledItemRect(r.background(), frameRect, p);
2223 // up/down buttons
2224 if (spin->subControls & SC_SpinBoxUp) {
2225 QStyleOptionSpinBox upOpt(*spin);
2226 upOpt.rect = subControlRect(CC_SpinBox, opt, SC_SpinBoxUp, w);
2227 proxy()->drawPrimitive(PE_IndicatorSpinUp, &upOpt, p, w);
2228 }
2229 if (spin->subControls & SC_SpinBoxDown) {
2230 QStyleOptionSpinBox downOpt(*spin);
2231 downOpt.rect = subControlRect(CC_SpinBox, opt, SC_SpinBoxDown, w);
2232 proxy()->drawPrimitive(PE_IndicatorSpinDown, &downOpt, p, w);
2233 }
2234 // The spinbox line edit paints its text using the QPalette::Text role,
2235 // so update that color in the palette
2236 if (auto *sb = qobject_cast<const QSpinBox *>(w)) {
2237 if (const auto *txt = r.text(); txt && txt->isDefined(QQSK::Property::Color)) {
2238 QLineEdit *lineEdit = sb->findChild<QLineEdit *>();
2239 if (lineEdit) {
2240 QPalette p = lineEdit->palette();
2241 if (p.color(QPalette::Text) != txt->color()) {
2242 p.setColor(QPalette::Text, txt->color());
2243 lineEdit->setPalette(p);
2244 }
2245 }
2246 }
2247 }
2248 return;
2249 }
2250 break;
2251#endif // QT_CONFIG(spinbox)
2252#if QT_CONFIG(groupbox)
2253 case CC_GroupBox:
2254 if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) {
2255 const auto r = d->resolve(w, QQStyleKitReader::ControlType::GroupBox, groupBox->state);
2256 if (!r.isValid())
2257 break;
2258
2259 // background/frame
2260 if (groupBox->subControls & SC_GroupBoxFrame) {
2261 QStyleOptionFrame frameOpt;
2262 frameOpt.QStyleOption::operator=(*groupBox);
2263 frameOpt.rect = subControlRect(CC_GroupBox, opt, SC_GroupBoxFrame, w);
2264 drawPrimitive(PE_FrameGroupBox, &frameOpt, p, w);
2265 }
2266
2267 // title
2268 if (groupBox->subControls & SC_GroupBoxLabel && !groupBox->text.isEmpty()) {
2269 const auto *textProps = r.text();
2270 const QFont textFont = r.font();
2271 QRect titleRect = subControlRect(CC_GroupBox, opt, SC_GroupBoxLabel, w);
2272 d->drawControlText(textProps, textFont, titleRect.marginsRemoved(r.metrics->textPadding),
2273 groupBox->text, Qt::TextShowMnemonic, p, Qt::AlignLeft | Qt::AlignVCenter);
2274 }
2275 // don't draw checkmark as the Controls style doesn't draw it
2276 // and StyleKit doesn't provide a way to style it
2277 return;
2278 }
2279 break;
2280#endif // QT_CONFIG(groupbox)
2281#if QT_CONFIG(scrollbar)
2282 case CC_ScrollBar:
2283 if (const auto *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
2284 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ScrollBar, scrollbar->state);
2285 if (!r.isValid())
2286 break;
2287
2288 // background = groove
2289 if (scrollbar->subControls & SC_ScrollBarGroove) {
2290 QRect backgroundRect = proxy()->subControlRect(CC_ScrollBar, opt, SC_ScrollBarGroove, w);
2291 d->drawStyledItemRect(r.background(), backgroundRect, p);
2292 }
2293
2294 // TODO: increase button
2295 // TODO: decrease button
2296
2297 // slider = indicator
2298 if (scrollbar->subControls & SC_ScrollBarSlider) {
2299 QStyleOptionSlider newScrollbar(*scrollbar);
2300 newScrollbar.rect = proxy()->subControlRect(CC_ScrollBar, opt, SC_ScrollBarSlider, w);
2301 proxy()->drawControl(CE_ScrollBarSlider, &newScrollbar, p, w);
2302 }
2303 return;
2304 }
2305#endif // QT_CONFIG(scrollbar)
2306 default:
2307 break;
2308 }
2309 QCommonStyle::drawComplexControl(cc, opt, p, w);
2310}
2311
2312/*! \reimp */
2313QStyle::SubControl QStyleKitStyle::hitTestComplexControl(ComplexControl cc, const QStyleOptionComplex *opt,
2314 const QPoint &pt, const QWidget *w) const
2315{
2316 return QCommonStyle::hitTestComplexControl(cc, opt, pt, w);
2317}
2318
2319/*! \reimp */
2320QRect QStyleKitStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc,
2321 const QWidget *w) const
2322{
2323 Q_D(const QStyleKitStyle);
2324
2325 switch (cc) {
2326#if QT_CONFIG(slider)
2327 case CC_Slider:
2328 if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
2329 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::Slider, opt->state);
2330 if (!resolved.isValid())
2331 break;
2332 const auto &metrics = *resolved.metrics;
2333 const bool horizontal = slider->orientation == Qt::Horizontal;
2334 QRect contentsRect = opt->rect.marginsRemoved(metrics.margins).marginsRemoved(metrics.padding);
2335
2336 const auto *indicator = resolved.indicator();
2337 if (!indicator)
2338 return contentsRect;
2339 switch (sc) {
2340 case SC_SliderGroove: {
2341 const auto availableW = contentsRect.width() - indicator->leftMargin() - indicator->rightMargin();
2342 const auto availableH = contentsRect.height() - indicator->topMargin() - indicator->bottomMargin();
2343 const qreal grooveW = resolvedImplicitWidth(indicator, availableW);
2344 const qreal grooveH = resolvedImplicitHeight(indicator, availableH);
2345
2346 QRectF grooveRect(0, 0, grooveW, grooveH);
2347 const uint rawHAlign = indicator->alignment() & Qt::AlignHorizontal_Mask;
2348 const uint rawVAlign = indicator->alignment() & Qt::AlignVertical_Mask;
2349 const uint hAlign = rawHAlign ? static_cast<Qt::Alignment>(rawHAlign) : Qt::AlignLeft;
2350 const uint vAlign = rawVAlign ? static_cast<Qt::Alignment>(rawVAlign) : Qt::AlignVCenter;
2351
2352 if (hAlign & Qt::AlignLeft) {
2353 grooveRect.moveLeft(contentsRect.x() + indicator->leftMargin());
2354 } else if (hAlign & Qt::AlignHCenter) {
2355 const int availableWidth = contentsRect.width()
2356 - indicator->leftMargin()
2357 - indicator->rightMargin();
2358 grooveRect.moveLeft(contentsRect.x() + indicator->leftMargin()
2359 + (availableWidth - grooveW) / 2.0);
2360 } else {
2361 grooveRect.moveLeft(contentsRect.x() + contentsRect.width()
2362 - indicator->rightMargin() - grooveW);
2363 }
2364
2365 if (vAlign & Qt::AlignTop) {
2366 grooveRect.moveTop(contentsRect.y() + indicator->topMargin());
2367 } else if (vAlign & Qt::AlignVCenter) {
2368 const int availableHeight = contentsRect.height()
2369 - indicator->topMargin()
2370 - indicator->bottomMargin();
2371 grooveRect.moveTop(contentsRect.y() + indicator->topMargin()
2372 + (availableHeight - grooveH) / 2.0);
2373 } else {
2374 grooveRect.moveTop(contentsRect.y() + contentsRect.height()
2375 - indicator->bottomMargin() - grooveH);
2376 }
2377 return visualRect(opt->direction, opt->rect, grooveRect.toAlignedRect());
2378 }
2379 case SC_SliderHandle: {
2380 const auto *handle = resolved.handle();
2381 if (!handle || !handle->visible() || handle->opacity() == 0)
2382 return contentsRect;
2383
2384 QRect handleRect;
2385 const auto handleW = resolvedImplicitWidth(handle,
2386 contentsRect.width() - handle->leftMargin() - handle->rightMargin());
2387 const auto handleH = resolvedImplicitHeight(handle,
2388 contentsRect.height() - handle->topMargin() - handle->bottomMargin());
2389 if (horizontal) {
2390 const int range = contentsRect.width()
2391 - handle->leftMargin() - handle->rightMargin() - handleW;
2392 const int sliderPos = QStyle::sliderPositionFromValue(
2393 slider->minimum, slider->maximum, slider->sliderPosition, range, false);
2394 handleRect = QRect(
2395 contentsRect.x() + handle->leftMargin() + sliderPos,
2396 contentsRect.y() + handle->topMargin() - handle->bottomMargin()
2397 + (contentsRect.height() - handleH) / 2,
2398 handleW, handleH);
2399 } else {
2400 const int range = contentsRect.height()
2401 - handle->topMargin() - handle->bottomMargin() - handleH;
2402 const int sliderPos = QStyle::sliderPositionFromValue(
2403 slider->minimum, slider->maximum, slider->sliderPosition, range, true);
2404 handleRect = QRect(
2405 contentsRect.x() + handle->leftMargin() - handle->rightMargin()
2406 + (contentsRect.width() - handleW) / 2,
2407 contentsRect.y() + handle->topMargin() + sliderPos,
2408 handleW, handleH);
2409 }
2410 return visualRect(opt->direction, opt->rect, handleRect);
2411 }
2412 default:
2413 break;
2414 }
2415 }
2416 break;
2417#endif // QT_CONFIG(slider)
2418#if QT_CONFIG(combobox)
2419 case CC_ComboBox:
2420 if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
2421 QRect frameRect = combo->rect;
2422 const auto r = d->resolveLayout(QQStyleKitReader::ControlType::ComboBox, combo->state);
2423 if (!r.isValid())
2424 break;
2425 const auto &metrics = *r.metrics;
2426 frameRect = frameRect.marginsRemoved(metrics.margins);
2427 switch (sc) {
2428 case SC_ComboBoxFrame:
2429 return visualRect(opt->direction, opt->rect, frameRect);
2430 case SC_ComboBoxEditField: {
2431 QRect contentsRect = frameRect.marginsRemoved(metrics.padding);
2432 QRect indicatorRect = subControlRect(CC_ComboBox, opt, SC_ComboBoxArrow, w);
2433 const int spacing = metrics.spacing;
2434 const auto *indicator = r.indicator();
2435 const uint indicatorAlign = indicator
2436 ? resolvedAlignment(indicator->alignment(), Qt::AlignRight, Qt::AlignVCenter)
2437 : uint(Qt::AlignRight | Qt::AlignVCenter);
2438 if (indicatorAlign & Qt::AlignLeft) {
2439 contentsRect.setLeft(indicatorRect.right() + spacing);
2440 } else if (indicatorAlign & Qt::AlignRight) {
2441 contentsRect.setRight(indicatorRect.left() - spacing);
2442 }
2443 return visualRect(opt->direction, opt->rect, contentsRect);
2444 }
2445 case SC_ComboBoxArrow: {
2446 QRect contentsRect = frameRect.marginsRemoved(metrics.padding);
2447 const auto *indicator = r.indicator();
2448 if (!indicator || !indicator->visible() || indicator->opacity() == 0)
2449 return contentsRect;
2450
2451 const int w = resolvedImplicitWidth(indicator,
2452 contentsRect.width() - indicator->leftMargin() - indicator->rightMargin());
2453 const int h = resolvedImplicitHeight(indicator,
2454 contentsRect.height() - indicator->topMargin() - indicator->bottomMargin());
2455 const uint indicatorAlign = resolvedAlignment(indicator->alignment(), Qt::AlignRight, Qt::AlignVCenter);
2456 const QMargins indicatorMargins = elementMargins(indicator);
2457 return visualRect(opt->direction, opt->rect,
2458 d->getAlignedRectInContainer(contentsRect, QSize(w, h), indicatorAlign, QMargins(), indicatorMargins));
2459 }
2460 case SC_ComboBoxListBoxPopup: {
2461 QRect popupRect = opt->rect;
2462 popupRect.setTop(opt->rect.bottom());
2463 return visualRect(opt->direction, opt->rect, popupRect);
2464 }
2465 default:
2466 break;
2467 }
2468 }
2469 break;
2470#endif // QT_CONFIG(combobox)
2471#if QT_CONFIG(spinbox)
2472 case CC_SpinBox:
2473 if (const QStyleOptionSpinBox *spinBox = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) {
2474 const auto r = d->resolveLayout(QQStyleKitReader::ControlType::SpinBox, spinBox->state);
2475 if (!r.isValid())
2476 break;
2477 const auto &metrics = *r.metrics;
2478 QRect frameRect = opt->rect.marginsRemoved(metrics.margins);
2479 QRect contentsRect = frameRect.marginsRemoved(metrics.padding);
2480 switch (sc) {
2481 case SC_SpinBoxFrame:
2482 return visualRect(opt->direction, opt->rect, frameRect);
2483 case SC_SpinBoxEditField: {
2484 QRect upIndicatorRect = subControlRect(CC_SpinBox, opt, SC_SpinBoxUp, w);
2485 QRect downIndicatorRect = subControlRect(CC_SpinBox, opt, SC_SpinBoxDown, w);
2486 const int spacing = metrics.spacing;
2487 const auto *upIndicator = r.indicator() ? r.indicator()->first() : nullptr;
2488 const auto *downIndicator = r.indicator() ? r.indicator()->second() : nullptr;
2489 const bool hasUpIndicator = upIndicator && upIndicator->visible() && upIndicator->opacity() > 0;
2490 const bool hasDownIndicator = downIndicator && downIndicator->visible() && downIndicator->opacity() > 0;
2491 const uint upAlign = hasUpIndicator
2492 ? resolvedAlignment(upIndicator->alignment(), Qt::AlignLeft, Qt::AlignVCenter)
2493 : uint(Qt::AlignLeft | Qt::AlignVCenter);
2494 const uint downAlign = hasDownIndicator
2495 ? resolvedAlignment(downIndicator->alignment(), Qt::AlignLeft, Qt::AlignVCenter)
2496 : uint(Qt::AlignLeft | Qt::AlignVCenter);
2497 if (hasUpIndicator && hasDownIndicator) {
2498 if ((upAlign & Qt::AlignLeft) && (downAlign & Qt::AlignRight)) {
2499 contentsRect.setLeft(upIndicatorRect.right() + spacing);
2500 contentsRect.setRight(downIndicatorRect.left() - spacing);
2501 } else if ((upAlign & Qt::AlignRight) && (downAlign & Qt::AlignLeft)) {
2502 contentsRect.setRight(upIndicatorRect.left() - spacing);
2503 contentsRect.setLeft(downIndicatorRect.right() + spacing);
2504 }
2505 } else if (hasUpIndicator) {
2506 if (upAlign & Qt::AlignLeft) {
2507 contentsRect.setLeft(upIndicatorRect.right() + spacing);
2508 } else if (upAlign & Qt::AlignRight) {
2509 contentsRect.setRight(upIndicatorRect.left() - spacing);
2510 }
2511 } else if (hasDownIndicator) {
2512 if (downAlign & Qt::AlignLeft) {
2513 contentsRect.setLeft(downIndicatorRect.right() + spacing);
2514 } else if (downAlign & Qt::AlignRight) {
2515 contentsRect.setRight(downIndicatorRect.left() - spacing);
2516 }
2517 }
2518 return visualRect(opt->direction, opt->rect, contentsRect);
2519 }
2520 case SC_SpinBoxUp: {
2521 const auto *upIndicator = r.indicator() ? r.indicator()->first() : nullptr;
2522 if (!upIndicator || !upIndicator->visible() || upIndicator->opacity() == 0)
2523 return contentsRect;
2524
2525 const int w = resolvedImplicitWidth(upIndicator,
2526 contentsRect.width() - upIndicator->leftMargin() - upIndicator->rightMargin());
2527 const int h = resolvedImplicitHeight(upIndicator,
2528 contentsRect.height() - upIndicator->topMargin() - upIndicator->bottomMargin());
2529 const uint upAlign = resolvedAlignment(upIndicator->alignment(), Qt::AlignLeft, Qt::AlignVCenter);
2530 const QMargins upMargins = elementMargins(upIndicator);
2531 return visualRect(opt->direction, opt->rect,
2532 d->getAlignedRectInContainer(contentsRect, QSize(w, h), upAlign, QMargins(), upMargins));
2533 }
2534 case SC_SpinBoxDown: {
2535 const auto *downIndicator = r.indicator() ? r.indicator()->second() : nullptr;
2536 if (!downIndicator || !downIndicator->visible() || downIndicator->opacity() == 0)
2537 return frameRect;
2538
2539 const int w = resolvedImplicitWidth(downIndicator,
2540 contentsRect.width() - downIndicator->leftMargin() - downIndicator->rightMargin());
2541 const int h = resolvedImplicitHeight(downIndicator,
2542 contentsRect.height() - downIndicator->topMargin() - downIndicator->bottomMargin());
2543 const uint downAlign = resolvedAlignment(downIndicator->alignment(), Qt::AlignLeft, Qt::AlignVCenter);
2544 const QMargins downMargins = elementMargins(downIndicator);
2545 return visualRect(opt->direction, opt->rect,
2546 d->getAlignedRectInContainer(contentsRect, QSize(w, h), downAlign, QMargins(), downMargins));
2547 }
2548 default:
2549 break;
2550 }
2551 }
2552 break;
2553#endif // QT_CONFIG(spinbox)
2554#if QT_CONFIG(groupbox)
2555 case CC_GroupBox:
2556 if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) {
2557 const auto r = d->resolveLayout(QQStyleKitReader::ControlType::GroupBox, groupBox->state);
2558 if (!r.isValid())
2559 break;
2560 const auto &metrics = *r.metrics;
2561 QRect frameRect = opt->rect.marginsRemoved(metrics.margins);
2562 switch (sc) {
2563 case SC_GroupBoxFrame:
2564 return visualRect(opt->direction, opt->rect, frameRect);
2565 case SC_GroupBoxLabel: {
2566 const int fontHeight = opt->fontMetrics.height();
2567 const int labelHeight = metrics.textPadding.top() + fontHeight
2568 + metrics.textPadding.bottom();
2569 const QRect labelContainer(opt->rect.left(), opt->rect.top(),
2570 opt->rect.width(),
2571 labelHeight);
2572 return visualRect(opt->direction, opt->rect, labelContainer);
2573 }
2574 case SC_GroupBoxCheckBox: {
2575 // The controls style doesn't draw the checkbox, so we don't return a rect for it
2576 return QRect();
2577 }
2578 case SC_GroupBoxContents: {
2579 const auto labelHeight = subControlRect(CC_GroupBox, opt, SC_GroupBoxLabel, w).height();
2580 const auto offset = metrics.padding.top() + qMax(labelHeight, metrics.margins.top() + metrics.spacing);
2581 QRect contentsRect(opt->rect.left() + metrics.padding.left(),
2582 opt->rect.top() + offset,
2583 opt->rect.width() - metrics.padding.left() - metrics.padding.right(),
2584 opt->rect.bottom() - opt->rect.top() - offset - metrics.padding.bottom());
2585 return visualRect(opt->direction, opt->rect, contentsRect);
2586 }
2587 default:
2588 break;
2589 }
2590 }
2591 break;
2592#endif // QT_CONFIG(groupbox)
2593#if QT_CONFIG(scrollbar)
2594 case CC_ScrollBar:
2595 if (const auto *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
2596 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ScrollBar, opt->state);
2597 if (!resolved.isValid())
2598 break;
2599 const auto &metrics = *resolved.metrics;
2600 const bool horizontal = scrollbar->orientation == Qt::Horizontal;
2601 const QRectF grooveRect = opt->rect.marginsRemoved(metrics.margins);
2602
2603 switch (sc) {
2604 // TODO: support up/down buttons
2605 case SC_ScrollBarAddLine:
2606 case SC_ScrollBarSubLine:
2607 case SC_ScrollBarSubPage:
2608 case SC_ScrollBarAddPage:
2609 return QRect();
2610 case SC_ScrollBarGroove:
2611 return visualRect(opt->direction, opt->rect, grooveRect.toAlignedRect());
2612 case SC_ScrollBarSlider: {
2613 const auto *indicator = resolved.indicator();
2614 if (!indicator || !indicator->visible() || indicator->opacity() == 0)
2615 return QRect();
2616 const QRectF contentsRect = grooveRect.marginsRemoved(metrics.padding);
2617
2618 const qreal availableW = contentsRect.width() - indicator->leftMargin() - indicator->rightMargin();
2619 const qreal availableH = contentsRect.height() - indicator->topMargin() - indicator->bottomMargin();
2620
2621 const int totalRange = scrollbar->maximum - scrollbar->minimum + scrollbar->pageStep;
2622 const qreal ratio = totalRange > 0 ? qreal(scrollbar->pageStep) / totalRange : 1.0;
2623
2624 QRectF sliderRect;
2625 if (horizontal) {
2626 const qreal sliderW = qMax(qreal(metrics.indicatorImplicitSize.width()), availableW * ratio);
2627 const qreal sliderH = resolvedImplicitHeight(indicator, availableH);
2628 const qreal travelRange = qMax(0.0, availableW - sliderW);
2629 const int sliderPos = QStyle::sliderPositionFromValue(
2630 scrollbar->minimum, scrollbar->maximum,
2631 scrollbar->sliderPosition, int(travelRange), scrollbar->upsideDown);
2632 sliderRect = QRectF(
2633 contentsRect.x() + indicator->leftMargin() + sliderPos,
2634 contentsRect.y() + indicator->topMargin() + (availableH - sliderH) / 2.0,
2635 sliderW, sliderH);
2636 } else {
2637 const qreal sliderH = qMax(qreal(metrics.indicatorImplicitSize.width()), availableH * ratio);
2638 const qreal sliderW = resolvedImplicitWidth(indicator, availableW);
2639 const qreal travelRange = qMax(0.0, availableH - sliderH);
2640 const int sliderPos = QStyle::sliderPositionFromValue(
2641 scrollbar->minimum, scrollbar->maximum,
2642 scrollbar->sliderPosition, int(travelRange), scrollbar->upsideDown);
2643 sliderRect = QRectF(
2644 contentsRect.x() + indicator->leftMargin() + (availableW - sliderW) / 2.0,
2645 contentsRect.y() + indicator->topMargin() + sliderPos,
2646 sliderW, sliderH);
2647 }
2648 return visualRect(opt->direction, opt->rect, sliderRect.toAlignedRect());
2649 }
2650 default:
2651 break;
2652 }
2653 }
2654 break;
2655#endif // QT_CONFIG(scrollbar)
2656 default:
2657 break;
2658 }
2659 return QCommonStyle::subControlRect(cc, opt, sc, w);
2660}
2661
2662/*! \reimp */
2663QSize QStyleKitStyle::sizeFromContents(ContentsType ct, const QStyleOption *opt,
2664 const QSize &contentsSize, const QWidget *widget) const
2665{
2666 Q_D(const QStyleKitStyle);
2667
2668 switch (ct) {
2669 case CT_PushButton:
2670 if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
2671 const auto controlType = btn->features & QStyleOptionButton::Flat
2672 ? QQStyleKitReader::ControlType::FlatButton
2673 : QQStyleKitReader::ControlType::Button;
2674 const auto resolved = d->resolveLayout(controlType, opt->state);
2675 if (!resolved.isValid())
2676 break;
2677 const auto &metrics = *resolved.metrics;
2678 const QSize textSize = opt->fontMetrics.size(Qt::TextShowMnemonic, btn->text);
2679 const QSize contentSizeWithPadding = textSize.expandedTo(contentsSize)
2680 + QSize(metrics.padding.left() + metrics.padding.right(),
2681 metrics.padding.top() + metrics.padding.bottom())
2682 + QSize(metrics.textPadding.left() + metrics.textPadding.right(),
2683 metrics.textPadding.top() + metrics.textPadding.bottom());
2684 const QSize bgSizeWithMargins = metrics.bgImplicitSize + QSize(metrics.margins.left() + metrics.margins.right(),
2685 metrics.margins.top() + metrics.margins.bottom());
2686 return contentSizeWithPadding.expandedTo(bgSizeWithMargins);
2687 }
2688 break;
2689 case CT_CheckBox:
2690 case CT_RadioButton:
2691 if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
2692 const auto controlType = ct == CT_CheckBox ? QQStyleKitReader::ControlType::CheckBox
2693 : QQStyleKitReader::ControlType::RadioButton;
2694 const auto resolved = d->resolveLayout(controlType, opt->state);
2695 if (!resolved.isValid())
2696 break;
2697 const auto &metrics = *resolved.metrics;
2698 const QSize textSize = opt->fontMetrics.size(Qt::TextShowMnemonic, btn->text);
2699 const int bgWidth = metrics.bgImplicitSize.width() + metrics.margins.left() + metrics.margins.right();
2700 const int bgHeight = metrics.bgImplicitSize.height() + metrics.margins.top() + metrics.margins.bottom();
2701 const int contentWidth = std::max(textSize.width(), contentsSize.width()) + metrics.padding.left() + metrics.padding.right()
2702 + metrics.textPadding.left() + metrics.textPadding.right();
2703 const int contentHeight = std::max(textSize.height(), contentsSize.height()) + metrics.padding.top() + metrics.padding.bottom()
2704 + metrics.textPadding.top() + metrics.textPadding.bottom();
2705 const int indicatorWidth = metrics.indicatorImplicitSize.width() + metrics.indicatorMargins.left()
2706 + metrics.indicatorMargins.right();
2707 const int indicatorHeight = metrics.indicatorImplicitSize.height() + metrics.indicatorMargins.top()
2708 + metrics.indicatorMargins.bottom();
2709 return QSize(std::max({contentWidth + indicatorWidth + metrics.spacing, bgWidth}),
2710 std::max({contentHeight, indicatorHeight, bgHeight}));
2711 }
2712 break;
2713#if QT_CONFIG(itemviews)
2714 case CT_ItemViewItem:
2715 if (const auto *item = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) {
2716 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ItemDelegate, opt->state);
2717 if (!resolved.isValid())
2718 break;
2719 const auto &metrics = *resolved.metrics;
2720 const QSize textSize = opt->fontMetrics.size(Qt::TextShowMnemonic, item->text);
2721 const int bgWidth = metrics.bgImplicitSize.width() + metrics.margins.left() + metrics.margins.right();
2722 const int bgHeight = metrics.bgImplicitSize.height() + metrics.margins.top() + metrics.margins.bottom();
2723 const int contentWidth = std::max(textSize.width(), contentsSize.width()) + metrics.padding.left() + metrics.padding.right()
2724 + metrics.textPadding.left() + metrics.textPadding.right();
2725 const int contentHeight = std::max(textSize.height(), contentsSize.height()) + metrics.padding.top() + metrics.padding.bottom()
2726 + metrics.textPadding.top() + metrics.textPadding.bottom();
2727 const int indicatorWidth = metrics.indicatorImplicitSize.width() + metrics.indicatorMargins.left()
2728 + metrics.indicatorMargins.right();
2729 const int indicatorHeight = metrics.indicatorImplicitSize.height() + metrics.indicatorMargins.top()
2730 + metrics.indicatorMargins.bottom();
2731 return QSize(std::max({contentWidth + indicatorWidth + metrics.spacing, bgWidth}),
2732 std::max({contentHeight, indicatorHeight, bgHeight}));
2733 }
2734 break;
2735#endif // QT_CONFIG(itemviews)
2736 case CT_ProgressBar:
2737 if (qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) {
2738 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ProgressBar, opt->state);
2739 if (!resolved.isValid())
2740 break;
2741 const auto &metrics = *resolved.metrics;
2742 const auto indicatorW = std::max(metrics.indicatorImplicitSize.width()
2743 + metrics.indicatorMargins.left()
2744 + metrics.indicatorMargins.right(),
2745 metrics.foregroundImplicitSize.width()
2746 + metrics.foregroundMargins.left()
2747 + metrics.foregroundMargins.right());
2748 const auto indicatorH = std::max(metrics.indicatorImplicitSize.height()
2749 + metrics.indicatorMargins.top()
2750 + metrics.indicatorMargins.bottom(),
2751 metrics.foregroundImplicitSize.height()
2752 + metrics.foregroundMargins.top()
2753 + metrics.foregroundMargins.bottom());
2754 const int bgW = metrics.bgImplicitSize.width() + metrics.margins.left() + metrics.margins.right();
2755 const int bgH = metrics.bgImplicitSize.height() + metrics.margins.top() + metrics.margins.bottom();
2756 // For progress bar in Controls, the content size is based on the indicator size
2757 const int contentW = indicatorW + metrics.padding.left() + metrics.padding.right();
2758 const int contentH = indicatorH + metrics.padding.top() + metrics.padding.bottom();
2759 return QSize(std::max(contentW, bgW), std::max(contentH, bgH));
2760 }
2761 break;
2762 case CT_Slider:
2763 if (qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
2764 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::Slider, opt->state);
2765 if (!resolved.isValid())
2766 break;
2767 const auto &metrics = *resolved.metrics;
2768 // background = indicator = groove + track
2769 const int bgW = std::max(
2770 metrics.padding.left() + metrics.padding.right()
2771 + metrics.indicatorImplicitSize.width()
2772 + metrics.indicatorMargins.left() + metrics.indicatorMargins.right(),
2773 metrics.bgImplicitSize.width() + metrics.margins.left() + metrics.margins.right());
2774 const int bgH = std::max(
2775 metrics.padding.top() + metrics.padding.bottom()
2776 + metrics.indicatorImplicitSize.height()
2777 + metrics.indicatorMargins.top() + metrics.indicatorMargins.bottom(),
2778 metrics.bgImplicitSize.height() + metrics.margins.top() + metrics.margins.bottom());
2779 const int handleW = metrics.handleImplicitSize.width() + metrics.padding.left() + metrics.padding.right();
2780 const int handleH = metrics.handleImplicitSize.height() + metrics.padding.top() + metrics.padding.bottom();
2781 return QSize(std::max(handleW, bgW), std::max(handleH, bgH));
2782 }
2783 break;
2784#if QT_CONFIG(lineedit)
2785 case CT_LineEdit:
2786 if (const auto *lineEdit = qstyleoption_cast<const QStyleOptionFrame *>(opt)) {
2787 QStyleOption lineEditOpt(*lineEdit);
2788 lineEditOpt.state &= ~QStyle::State_Sunken;
2789#if QT_CONFIG(spinbox)
2790 const bool isInSpinBox = widget && qobject_cast<const QSpinBox *>(widget->parent());
2791#else
2792 const bool isInSpinBox = false;
2793#endif
2794 auto controlType = isInSpinBox ? QQStyleKitReader::ControlType::SpinBox : QQStyleKitReader::ControlType::TextField;
2795 const auto resolved = d->resolveLayout(controlType, lineEditOpt.state);
2796 if (!resolved.isValid())
2797 break;
2798 const auto &metrics = *resolved.metrics;
2799 QSize bgSize(0, 0);
2800 // For spinbox, the line edit doesn't have its own background in the Controls style
2801 if (!isInSpinBox)
2802 bgSize = metrics.bgImplicitSize.grownBy(metrics.margins);
2803 const QSize contentSizeWithPadding = contentsSize.grownBy(metrics.textPadding).grownBy(metrics.padding);
2804 return contentSizeWithPadding.expandedTo(bgSize);
2805 }
2806 break;
2807#endif // QT_CONFIG(lineedit)
2808#if QT_CONFIG(combobox)
2809 case CT_ComboBox:
2810 if (const auto *comboBox = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
2811 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ComboBox, opt->state);
2812 if (!resolved.isValid())
2813 break;
2814 const auto &metrics = *resolved.metrics;
2815 const QSize textSize = opt->fontMetrics.size(Qt::TextShowMnemonic, comboBox->currentText);
2816 const int bgW = metrics.bgImplicitSize.width() + metrics.margins.left() + metrics.margins.right();
2817 const int bgH = metrics.bgImplicitSize.height() + metrics.margins.top() + metrics.margins.bottom();
2818 const int contentW = textSize.width() + metrics.padding.left() + metrics.padding.right()
2819 + metrics.textPadding.left() + metrics.textPadding.right();
2820 const int contentH = textSize.height() + metrics.padding.top() + metrics.padding.bottom()
2821 + metrics.textPadding.top() + metrics.textPadding.bottom();
2822 const int indicatorW = metrics.indicatorImplicitSize.width() + metrics.indicatorMargins.left()
2823 + metrics.indicatorMargins.right();
2824 const int indicatorH = metrics.indicatorImplicitSize.height() + metrics.indicatorMargins.top()
2825 + metrics.indicatorMargins.bottom();
2826 return QSize(std::max({contentW + indicatorW + metrics.spacing, bgW}),
2827 std::max({contentH, indicatorH, bgH}));
2828 }
2829 break;
2830#endif // QT_CONFIG(combobox)
2831#if QT_CONFIG(spinbox)
2832 case CT_SpinBox: {
2833 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::SpinBox, opt->state);
2834 if (!resolved.isValid())
2835 break;
2836 const auto &metrics = *resolved.metrics;
2837 const int bgW = metrics.bgImplicitSize.width() + metrics.margins.left() + metrics.margins.right();
2838 const int bgH = metrics.bgImplicitSize.height() + metrics.margins.top() + metrics.margins.bottom();
2839 const int contentW = contentsSize.width() + metrics.padding.left() + metrics.padding.right()
2840 + metrics.textPadding.left() + metrics.textPadding.right();
2841 const int contentH = contentsSize.height() + metrics.padding.top() + metrics.padding.bottom()
2842 + metrics.textPadding.top() + metrics.textPadding.bottom();
2843 // TODO: Support vertical layout for spinbox, currently we assume horizontal layout
2844 // TODO: Calculate each indicator (up/down) size separately if they have different sizes in the style
2845 const int indicatorW = (metrics.indicatorImplicitSize.width() + metrics.indicatorMargins.left()
2846 + metrics.indicatorMargins.right()) * 2;
2847 const int indicatorH = metrics.indicatorImplicitSize.height() + metrics.indicatorMargins.top()
2848 + metrics.indicatorMargins.bottom();
2849 return QSize(std::max({contentW + indicatorW + metrics.spacing, bgW}),
2850 std::max({contentH, indicatorH, bgH}));
2851 }
2852#endif // QT_CONFIG(spinbox)
2853#if QT_CONFIG(groupbox)
2854 case CT_GroupBox:
2855 if (const auto *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) {
2856 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::GroupBox, opt->state);
2857 if (!resolved.isValid())
2858 break;
2859 const auto &metrics = *resolved.metrics;
2860 const QSize bgSize = metrics.bgImplicitSize + QSize(metrics.margins.left() + metrics.margins.right(),
2861 metrics.margins.top() + metrics.margins.bottom());
2862 const QSize contentSize = contentsSize.grownBy(metrics.padding);
2863 const QSize textSize = opt->fontMetrics.size(Qt::TextShowMnemonic, groupBox->text).grownBy(metrics.textPadding);
2864 return textSize.expandedTo(contentSize).expandedTo(bgSize);
2865 }
2866 break;
2867#endif // QT_CONFIG(groupbox)
2868#if QT_CONFIG(scrollbar)
2869 case CT_ScrollBar:
2870 if (qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
2871 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ScrollBar, opt->state);
2872 if (!resolved.isValid())
2873 break;
2874 const auto &metrics = *resolved.metrics;
2875
2876 const int iH = metrics.indicatorImplicitSize.height()
2877 + metrics.padding.top() + metrics.padding.bottom()
2878 + metrics.indicatorMargins.top() + metrics.indicatorMargins.bottom();
2879 const int iW = metrics.indicatorImplicitSize.width()
2880 + metrics.padding.left() + metrics.padding.right()
2881 + metrics.indicatorMargins.left() + metrics.indicatorMargins.right();
2882 const int bgH = metrics.bgImplicitSize.height()
2883 + metrics.margins.top() + metrics.margins.bottom();
2884 const int bgW = metrics.bgImplicitSize.width()
2885 + metrics.margins.left() + metrics.margins.right();
2886 return QSize(std::max(iW, bgW), std::max(iH, bgH));
2887 }
2888 break;
2889#endif // QT_CONFIG(scrollbar)
2890 default:
2891 break;
2892 }
2893 return QCommonStyle::sizeFromContents(ct, opt, contentsSize, widget);
2894}
2895
2896/*! \reimp */
2897int QStyleKitStyle::pixelMetric(PixelMetric m, const QStyleOption *opt, const QWidget *widget) const
2898{
2899 // case PM_LayoutBottomMargin:
2900 // case PM_LayoutTopMargin:
2901 // case PM_LayoutLeftMargin:
2902 // case PM_LayoutRightMargin:
2903 // return -100.0;
2904 return QCommonStyle::pixelMetric(m, opt, widget);
2905}
2906
2907/*! \reimp */
2908int QStyleKitStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget *w,
2909 QStyleHintReturn *shret) const
2910{
2911 switch (sh) {
2912 case SH_SpinBox_SelectOnStep:
2913 return 0;
2914 default:
2915 break;
2916 }
2917 return QCommonStyle::styleHint(sh, opt, w, shret);
2918}
2919
2920/*! \reimp */
2921QPalette QStyleKitStyle::standardPalette() const
2922{
2923 return QCommonStyle::standardPalette();
2924}
2925
2926/*! \reimp */
2927void QStyleKitStyle::polish(QWidget *widget)
2928{
2929 if (!widget)
2930 return;
2931
2932 Q_D(QStyleKitStyle);
2933
2934 // When no user style is loaded, create the empty fallback style now,
2935 // before any reader is created, so that all widgets are styled consistently
2936 if (!d->style)
2937 d->ensureDefaultStyle();
2938
2939 widget->setAttribute(Qt::WA_Hover);
2940
2941#if QT_CONFIG(scrollbar)
2942 // QScrollBar sets WA_OpaquePaintEvent in its constructor, which skips
2943 // background erase before paint. This can leave artifacts when the
2944 // style's background is invisible, so disable it
2945 if (qobject_cast<QScrollBar *>(widget))
2946 widget->setAttribute(Qt::WA_OpaquePaintEvent, false);
2947#endif
2948
2949 // Create per-widget reader for interactive controls (transitions)
2950 const bool isInteractiveControl = false
2951#if QT_CONFIG(pushbutton)
2952 || qobject_cast<const QPushButton *>(widget)
2953#endif
2954#if QT_CONFIG(checkbox)
2955 || qobject_cast<const QCheckBox *>(widget)
2956#endif
2957#if QT_CONFIG(radiobutton)
2958 || qobject_cast<const QRadioButton *>(widget)
2959#endif
2960#if QT_CONFIG(combobox)
2961 || qobject_cast<const QComboBox *>(widget)
2962 || (widget && widget->inherits("QComboBoxPrivateContainer"))
2963#endif
2964#if QT_CONFIG(lineedit)
2965 || qobject_cast<const QLineEdit *>(widget)
2966#endif
2967#if QT_CONFIG(textedit)
2968 || qobject_cast<const QTextEdit *>(widget)
2969 || qobject_cast<const QPlainTextEdit *>(widget)
2970#endif
2971#if QT_CONFIG(label)
2972 || qobject_cast<const QLabel *>(widget)
2973#endif
2974#if QT_CONFIG(progressbar)
2975 || qobject_cast<const QProgressBar *>(widget)
2976#endif
2977#if QT_CONFIG(slider)
2978 || qobject_cast<const QSlider *>(widget)
2979#endif
2980#if QT_CONFIG(scrollbar)
2981 || qobject_cast<const QScrollBar *>(widget)
2982#endif
2983#if QT_CONFIG(spinbox)
2984 || qobject_cast<const QSpinBox *>(widget)
2985#endif
2986#if QT_CONFIG(tabbar)
2987 || qobject_cast<const QTabBar *>(widget)
2988#endif
2989 ;
2990 if (isInteractiveControl)
2991 d->readerForWidget(widget);
2992
2993 // Disable the viewport's autoFillBackground so the styled background shows through.
2994 // Only flip it when it was enabled, and store the widget so unpolish() can restore it
2995 // (e.g. when the application switches to a non-StyleKit style).
2996 if (QWidget *vp = managedViewport(widget); vp && vp->autoFillBackground()) {
2997 vp->setAutoFillBackground(false);
2998 d->autoFillDisabledWidgets.insert(widget);
2999 }
3000
3001#if QT_CONFIG(lineedit)
3002 if (auto *lineEdit = qobject_cast<QLineEdit *>(widget)) {
3003 if (!lineEdit->property("_q_stylekit_alignment_set").toBool()) {
3004 QQStyleKitReader *r = d->readerForWidget(widget);
3005 if (r) {
3006 const QWidget *target = containerWidget(widget);
3007 QQStyleKitReader::ControlType ct = controlTypeForWidget(target);
3008 r->setControlType(ct);
3009 const auto *textProps = r->global()->text();
3010 if (textProps) {
3011 const uint align = resolvedAlignment(
3012 textProps->alignment(), Qt::AlignLeft, Qt::AlignVCenter);
3013 lineEdit->setAlignment(Qt::Alignment(align));
3014 }
3015 }
3016 }
3017 }
3018#endif
3019
3020 d->refreshStyleFont(widget);
3021 d->refreshStylePalette(widget);
3022
3023 if (isSelfPaintingWidget(widget))
3024 widget->installEventFilter(this);
3025
3026 QCommonStyle::polish(widget);
3027}
3028
3029/*! \reimp */
3030void QStyleKitStyle::polish(QApplication *app)
3031{
3032 QCommonStyle::polish(app);
3033}
3034
3035/*! \reimp */
3036void QStyleKitStyle::polish(QPalette &palette)
3037{
3038 QCommonStyle::polish(palette);
3039}
3040
3041/*! \reimp */
3042void QStyleKitStyle::unpolish(QWidget *widget)
3043{
3044 Q_D(QStyleKitStyle);
3045 d->unsetStylePalette(widget);
3046 d->unsetStyleFont(widget);
3047 if (isSelfPaintingWidget(widget))
3048 widget->removeEventFilter(this);
3049 if (d->autoFillDisabledWidgets.remove(widget)) {
3050 if (QWidget *vp = managedViewport(widget))
3051 vp->setAutoFillBackground(true);
3052 }
3053 d->cleanupWidgetReader(widget);
3054#if QT_CONFIG(scrollbar)
3055 if (qobject_cast<QScrollBar *>(widget))
3056 widget->setAttribute(Qt::WA_OpaquePaintEvent);
3057#endif
3058 QCommonStyle::unpolish(widget);
3059}
3060
3061/*! \reimp */
3062void QStyleKitStyle::unpolish(QApplication *app)
3063{
3064 Q_D(QStyleKitStyle);
3065 d->clearMetricsCache();
3066 QCommonStyle::unpolish(app);
3067}
3068
3069/*! \reimp */
3070bool QStyleKitStyle::eventFilter(QObject *obj, QEvent *event)
3071{
3072 Q_D(QStyleKitStyle);
3073 switch (event->type()) {
3074 case QEvent::EnabledChange:
3075 case QEvent::HoverEnter:
3076 case QEvent::HoverLeave:
3077 case QEvent::FocusIn:
3078 case QEvent::FocusOut:
3079 if (auto *w = qobject_cast<QWidget *>(obj)) {
3080 if (d->customPaletteWidgets.contains(w))
3081 d->refreshStylePalette(w);
3082 if (d->customFontWidgets.contains(w))
3083 d->refreshStyleFont(w);
3084 }
3085 break;
3086 default:
3087 break;
3088 }
3089 return QCommonStyle::eventFilter(obj, event);
3090}
3091
3092/*! \reimp */
3093bool QStyleKitStyle::event(QEvent *event)
3094{
3095 return QCommonStyle::event(event);
3096}
3097
3098QT_END_NAMESPACE
3099
3100#include "moc_qstylekitstyle.cpp"
Combined button and popup list for selecting options.
static qreal resolvedImplicitWidth(const QQStyleKitDelegateProperties *element, qreal availableW)
static QWidget * managedViewport(QWidget *widget)
static QMargins elementMargins(const QQStyleKitDelegateProperties *element)
static uint resolvedAlignment(uint raw, Qt::Alignment hDefault, Qt::Alignment vDefault)
static bool isSelfPaintingWidget(const QWidget *widget)
static qreal resolvedImplicitHeight(const QQStyleKitDelegateProperties *element, qreal avilableH)
static QUrl urlFromStylePath(const QString &filePath)
static const QWidget * containerWidget(const QWidget *w)