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(dial)
35# include <QtWidgets/qdial.h>
36#endif
37#if QT_CONFIG(scrollbar)
38# include <QtWidgets/qscrollbar.h>
39#endif
40#if QT_CONFIG(spinbox)
41# include <QtWidgets/qspinbox.h>
42#endif
43#if QT_CONFIG(progressbar)
44# include <QtWidgets/qprogressbar.h>
45#endif
46#if QT_CONFIG(textedit)
47# include <QtWidgets/qtextedit.h>
48# include <QtWidgets/qplaintextedit.h>
49#endif
50#if QT_CONFIG(tabbar)
51# include <QtWidgets/qtabbar.h>
52#endif
53#if QT_CONFIG(tabwidget)
54# include <QtWidgets/qtabwidget.h>
55#endif
56#if QT_CONFIG(toolbar)
57# include <QtWidgets/qtoolbar.h>
58#endif
59#if QT_CONFIG(toolbutton)
60# include <QtWidgets/qtoolbutton.h>
61#endif
62#if QT_CONFIG(groupbox)
63# include <QtWidgets/qgroupbox.h>
64#endif
65#if QT_CONFIG(menu)
66# include <QtWidgets/qmenu.h>
67# include <QtWidgets/qmenubar.h>
68#endif
69#if QT_CONFIG(label)
70# include <QtWidgets/qlabel.h>
71#endif
72#include <QtWidgets/private/qwidget_p.h>
73#include <QtCore/private/qobject_p.h>
74#include <QtGui/qpainter.h>
75#include <QtGui/qpainterpath.h>
76#include <QtGui/qpainterstateguard.h>
77#include <QtGui/qstylehints.h>
78#include <QtCore/qloggingcategory.h>
79#include <QtCore/qnumeric.h>
80#include <QtQml/private/qqmlcomponent_p.h>
81#include <QtQml/qqmlengine.h>
82#include <QtQuick/private/qquicktransition_p.h>
83#include <QtLabsStyleKit/private/qqstylekit_p.h>
84#include <QtLabsStyleKit/private/qqstylekitcontrolproperties_p.h>
85#include <QtLabsStyleKit/private/qqstylekitstyle_p.h>
86
88
89Q_STATIC_LOGGING_CATEGORY(lcStyleKit, "qt.labs.stylekit")
90
91/*!
92 \class QStyleKitStyle
93 \inmodule QtLabsStyleKit
94 \ingroup appearance
95 \since 6.12
96 \preliminary
97
98 \brief The QStyleKitStyle class applies a \l {Qt Labs StyleKit} style
99 to Qt Widgets.
100
101 QStyleKitStyle is a QStyle implementation that uses a \l StyleKit \l Style
102 to style Qt Widgets. The \l Style is a QML file that declaratively describes
103 the visual design (colors, sizes, radii, borders, and other properties)
104 for each widget type and state. QStyleKitStyle uses those property values
105 to paint each widget entirely with QPainter. Qt Quick and the scene graph
106 play no part in the rendering.
107
108 This separation means the same \l Style QML file can drive both
109 Qt Quick Controls and Qt Widgets, sharing one design definition across
110 both systems.
111
112 \note StyleKit is a Qt Labs module, and its API may change between
113 Qt releases.
114
115 \section1 Loading a Style
116
117 A style is a QML file whose root object is a \l Style. To load it,
118 pass the file path to the constructor or to \l setStylePath():
119
120 \if !defined(qtforpython)
121 \code
122 auto *style = new QStyleKitStyle(QStringLiteral(":/styles/MyStyle.qml"));
123 QApplication::setStyle(style);
124 \endcode
125 \else
126 \code
127 style = QStyleKitStyle(":/styles/MyStyle.qml")
128 QApplication.setStyle(style)
129 \endcode
130 \endif
131
132 QStyleKitStyle loads the Style with an internal QQmlEngine that the
133 QStyleKitStyle instance owns. If the path is invalid or the root object
134 is not a \l Style, QStyleKitStyle emits a warning and keeps the
135 previously loaded style. Until a style loads successfully, the widgets
136 use an empty fallback style.
137
138 \section1 Themes
139
140 A Style can define one or more named \l {Theme}{themes}. Call
141 \l setThemeName() to select the active theme, and read
142 \l availableThemeNames to list the themes a Style offers. The special
143 theme name \c System makes the style follow the platform color scheme:
144 when the OS color scheme changes, QStyleKitStyle recreates the active
145 theme and repolishes all widgets.
146
147 \section1 Widget to StyleKit control mapping
148
149 Each Qt Widgets class is mapped to a StyleKit control type, which
150 determines which control entry in the \l Style applies to it. Use the
151 corresponding control entry to configure visual properties for that
152 widget type, including individual parts of the widget such as its
153 background, indicator, and handle. See \l ControlStyleProperties for the
154 full list of stylable properties.
155
156 Properties not set in a specific control entry fall back through the
157 control type hierarchy: for example, \c button falls back to
158 \c abstractButton, which falls back to \c control.
159
160 \table
161 \header
162 \li Qt Widgets class
163 \li StyleKit control
164 \row
165 \li QPushButton (flat)
166 \li \l {StylableControls::flatButton}{flatButton}
167 \row
168 \li QPushButton
169 \li \l {StylableControls::button}{button}
170 \row
171 \li QCheckBox
172 \li \l {StylableControls::checkBox}{checkBox}
173 \row
174 \li QRadioButton
175 \li \l {StylableControls::radioButton}{radioButton}
176 \row
177 \li QComboBox
178 \li \l {StylableControls::comboBox}{comboBox}
179 \row
180 \li QSlider
181 \li \l {StylableControls::slider}{slider}
182 \row
183 \li QDial
184 \li \l {StylableControls::dial}{dial}
185 \row
186 \li QScrollBar
187 \li \l {StylableControls::scrollBar}{scrollBar}
188 \row
189 \li QSpinBox, QDoubleSpinBox
190 \li \l {StylableControls::spinBox}{spinBox}
191 \row
192 \li QProgressBar
193 \li \l {StylableControls::progressBar}{progressBar}
194 \row
195 \li QLineEdit
196 \li \l {StylableControls::textField}{textField}
197 \row
198 \li QTextEdit, QPlainTextEdit
199 \li \l {StylableControls::textArea}{textArea}
200 \row
201 \li QTabBar
202 \li \l {StylableControls::tabBar}{tabBar}
203 \row
204 \li QTabWidget
205 \li \l {StylableControls::page}{page}
206 \row
207 \li QToolBar
208 \li \l {StylableControls::toolBar}{toolBar}
209 \row
210 \li QToolButton
211 \li \l {StylableControls::toolButton}{toolButton}
212 \row
213 \li QGroupBox
214 \li \l {StylableControls::groupBox}{groupBox}
215 \row
216 \li QFrame
217 \li \l {StylableControls::frame}{frame}
218 \row
219 \li QLabel
220 \li \l {StylableControls::label}{label}
221 \row
222 \li QMenu
223 \li \l {StylableControls::menu}{menu}
224 \row
225 \li QMenuBar
226 \li \l {StylableControls::menuBar}{menuBar}
227 \row
228 \li Everything else
229 \li \l {StylableControls::control}{control}
230 \endtable
231
232 QStyleKitStyle does not yet support widgets outside this table;
233 \l QCommonStyle paints them instead. Conversely, some control entries in
234 \l StylableControls have no Qt Widgets equivalent, and
235 QStyleKitStyle ignores them when styling widgets.
236
237 \section2 Sub-controls within a widget
238
239 Each sub-control of a widget maps to its own control entry in the
240 \l Style, which lets you style the sub-controls individually.
241
242 \table
243 \header
244 \li Sub-control
245 \li StyleKit control
246 \row
247 \li \l QStyledItemDelegate items -- the default delegate for all Qt item views,
248 including the \l QComboBox popup list
249 \li \l {StylableControls::itemDelegate}{itemDelegate}
250 \row
251 \li The same items, when user-checkable (that is, when they show
252 a check indicator)
253 \li \l {StylableControls::checkDelegate}{checkDelegate};
254 falls back to \c itemDelegate for anything not set explicitly
255 \row
256 \li Individual tabs in a \l QTabBar
257 \li \l {StylableControls::tabButton}{tabButton}
258 \row
259 \li \l QMenu items
260 \li \l {StylableControls::menuItem}{menuItem}
261 \row
262 \li Separators in a \l QMenu
263 \li \l {StylableControls::menuSeparator}{menuSeparator}
264 \row
265 \li \l QMenuBar items
266 \li \l {StylableControls::menuBarItem}{menuBarItem}
267 \row
268 \li Separators in a \l QToolBar
269 \li \l {StylableControls::toolSeparator}{toolSeparator}
270 \row
271 \li The \l QComboBox popup list container
272 \li \l {StylableControls::popup}{popup}
273 \endtable
274
275 \section1 Known limitations
276
277 QStyleKitStyle does not support the following StyleKit features:
278
279 \list
280 \li \b{Shadows} -- QStyleKitStyle ignores shadow definitions.
281 \li \b{Delegate scale above 1.0 on a control's background} -- a
282 widget cannot paint outside its own rectangle, so QStyleKitStyle
283 clips the scaled background at the widget edge. Use
284 \l {DelegateStyle::}{margins} to inset the background and reserve
285 room for it to grow. Scaling indicators, handles, and foregrounds
286 still works.
287 \li \b{Variations} -- QStyleKitStyle does not yet apply a
288 \l StyleVariation set on a widget instance.
289 \li \b{Custom controls} -- QStyleKitStyle does not yet style custom
290 widgets declared with \l CustomControl.
291 \li \b{Custom delegates} -- QStyleKitStyle ignores the
292 \l {DelegateStyle::}{delegate} property and always uses its
293 built-in rendering.
294 \endlist
295
296 \sa QStyle, QCommonStyle, {Qt Labs StyleKit}, Style, Theme
297*/
298
299/*!
300 \property QStyleKitStyle::stylePath
301 \brief the path to the QML \l Style file driving this style.
302
303 The value is a path to a local file or to a file in the resource file
304 system, (for example, \c{:/styles/MyStyle.qml} or \c{qrc:/qt/qml/MyQmlModule/Style.qml}).
305 QStyleKitStyle resolves a relative path against the application's working directory.
306 The file must contain a QML component whose root object is a \l Style.
307 Setting this property reloads the style. If the new file does not load,
308 QStyleKitStyle emits a warning and keeps the previously loaded style
309 and style path.
310*/
311
312/*!
313 \property QStyleKitStyle::themeName
314 \brief the name of the active theme.
315
316 The value must be one of the entries in \l availableThemeNames. That
317 list includes the special name \c System, which makes the style follow
318 the platform color scheme. The comparison ignores case.
319
320 Setting this property repolishes all widgets so that they repaint with
321 the new theme. If the name matches no theme, the \l Style emits a
322 warning and applies no theme.
323*/
324
325/*!
326 \property QStyleKitStyle::availableThemeNames
327 \brief the list of theme names exposed by the loaded \l Style.
328
329 This list always includes \c System, which follows the platform color
330 scheme. It also includes \c Light and \c Dark when the \l Style defines
331 those themes, together with any custom themes that the style author
332 defines. The list is empty when no style is loaded.
333*/
334
335/*!
336 \property QStyleKitStyle::customThemeNames
337 \brief the list of custom theme names defined by the loaded \l Style.
338
339 Unlike \l availableThemeNames, this list excludes \c System, \c Light,
340 and \c Dark, and contains only the themes that the style author
341 defines explicitly. The list is empty when no style is loaded.
342
343 \sa availableThemeNames, themeName
344*/
345
346static QQStyleKitReader::ControlType controlTypeForWidget(const QWidget *widget)
347{
348 if (!widget)
349 return QQStyleKitReader::Control;
350
351#if QT_CONFIG(pushbutton)
352 if (qobject_cast<const QPushButton *>(widget))
353 return QQStyleKitReader::Button;
354#endif
355#if QT_CONFIG(checkbox)
356 if (qobject_cast<const QCheckBox *>(widget))
357 return QQStyleKitReader::CheckBox;
358#endif
359#if QT_CONFIG(radiobutton)
360 if (qobject_cast<const QRadioButton *>(widget))
361 return QQStyleKitReader::RadioButton;
362#endif
363#if QT_CONFIG(combobox)
364 if (qobject_cast<const QComboBox *>(widget))
365 return QQStyleKitReader::ComboBox;
366#endif
367#if QT_CONFIG(slider)
368 if (qobject_cast<const QSlider *>(widget))
369 return QQStyleKitReader::Slider;
370#endif
371#if QT_CONFIG(scrollbar)
372 if (qobject_cast<const QScrollBar *>(widget))
373 return QQStyleKitReader::ScrollBar;
374#endif
375#if QT_CONFIG(spinbox)
376 if (qobject_cast<const QSpinBox *>(widget) || qobject_cast<const QDoubleSpinBox *>(widget))
377 return QQStyleKitReader::SpinBox;
378#endif
379#if QT_CONFIG(itemviews)
380 if (qobject_cast<const QAbstractItemView *>(widget))
381 return QQStyleKitReader::ItemDelegate;
382#endif
383#if QT_CONFIG(progressbar)
384 if (qobject_cast<const QProgressBar *>(widget))
385 return QQStyleKitReader::ProgressBar;
386#endif
387#if QT_CONFIG(lineedit)
388 if (qobject_cast<const QLineEdit *>(widget))
389 return QQStyleKitReader::TextField;
390#endif
391#if QT_CONFIG(textedit)
392 if (qobject_cast<const QTextEdit *>(widget) || qobject_cast<const QPlainTextEdit *>(widget))
393 return QQStyleKitReader::TextArea;
394#endif
395#if QT_CONFIG(tabbar)
396 if (qobject_cast<const QTabBar *>(widget))
397 return QQStyleKitReader::TabBar;
398#endif
399#if QT_CONFIG(tabwidget)
400 if (qobject_cast<const QTabWidget *>(widget))
401 return QQStyleKitReader::Page;
402#endif
403#if QT_CONFIG(toolbar)
404 if (qobject_cast<const QToolBar *>(widget))
405 return QQStyleKitReader::ToolBar;
406#endif
407#if QT_CONFIG(toolbutton)
408 if (qobject_cast<const QToolButton *>(widget))
409 return QQStyleKitReader::ToolButton;
410#endif
411#if QT_CONFIG(groupbox)
412 if (qobject_cast<const QGroupBox *>(widget))
413 return QQStyleKitReader::GroupBox;
414#endif
415#if QT_CONFIG(menu)
416 if (qobject_cast<const QMenuBar *>(widget))
417 return QQStyleKitReader::MenuBar;
418 if (qobject_cast<const QMenu *>(widget))
419 return QQStyleKitReader::Menu;
420#endif
421#if QT_CONFIG(tooltip)
422 if (widget->inherits("QTipLabel"))
423 return QQStyleKitReader::ToolTip;
424#endif
425 if (widget->windowType() == Qt::Popup)
426 return QQStyleKitReader::Popup;
427 if (widget->windowType() == Qt::Dialog)
428 return QQStyleKitReader::Dialog;
429 if (widget->windowType() == Qt::Window)
430 return QQStyleKitReader::ApplicationWindow;
431#ifndef QT_NO_FRAME
432 if (qobject_cast<const QFrame *>(widget))
433 return QQStyleKitReader::Frame;
434#endif
435#if QT_CONFIG(dial)
436 if (qobject_cast<const QDial *>(widget))
437 return QQStyleKitReader::Dial;
438#endif
439 return QQStyleKitReader::Control;
440}
441
442#if QT_CONFIG(itemviews)
443static QQStyleKitReader::ControlType itemViewControlType(const QStyleOption *opt)
444{
445 const auto *itemViewOption = qstyleoption_cast<const QStyleOptionViewItem *>(opt);
446 return itemViewOption && (itemViewOption->features & QStyleOptionViewItem::HasCheckIndicator)
447 ? QQStyleKitReader::ControlType::CheckDelegate
448 : QQStyleKitReader::ControlType::ItemDelegate;
449}
450#endif
451
452// Some widgets (like QAbstractScrollArea) paint on a child widget (the viewport)
453// instead of on themselves. This function returns the widget that is actually painted on,
454// which is the one that should be updated when the style values change.
455static QWidget *paintTarget(const QWidget *widget)
456{
457#if QT_CONFIG(scrollarea)
458 if (const auto *area = qobject_cast<const QAbstractScrollArea *>(widget))
459 return area->viewport();
460#endif
461 return const_cast<QWidget *>(widget);
462}
463
464// Returns true for widgets that draw themselves using their widget font and
465// palette directly, bypassing the style's drawControl path.
466static bool isSelfPaintingWidget(const QWidget *widget)
467{
468 return false
469#if QT_CONFIG(label)
470 || qobject_cast<const QLabel *>(widget)
471#endif
472#if QT_CONFIG(textedit)
473 || qobject_cast<const QTextEdit *>(widget)
474 || qobject_cast<const QPlainTextEdit *>(widget)
475#endif
476#if QT_CONFIG(lineedit)
477 || qobject_cast<const QLineEdit *>(widget)
478#endif
479 ;
480}
481
482static QWidget *managedViewport(QWidget *widget)
483{
484#if QT_CONFIG(textedit)
485 if (auto *textEdit = qobject_cast<QTextEdit *>(widget))
486 return textEdit->viewport();
487 if (auto *plainTextEdit = qobject_cast<QPlainTextEdit *>(widget))
488 return plainTextEdit->viewport();
489#endif
490#if QT_CONFIG(itemviews) && QT_CONFIG(combobox)
491 if (auto *view = qobject_cast<QAbstractItemView *>(widget)) {
492 if (auto *p = view->parentWidget(); p && p->inherits("QComboBoxPrivateContainer"))
493 return view->viewport();
494 }
495#endif
496 return nullptr;
497}
498
499static uint resolvedAlignment(uint raw, Qt::Alignment hDefault, Qt::Alignment vDefault)
500{
501 uint result = 0;
502 const uint h = raw & Qt::AlignHorizontal_Mask;
503 const uint v = raw & Qt::AlignVertical_Mask;
504 result |= h ? h : uint(hDefault);
505 result |= v ? v : uint(vDefault);
506 return result;
507}
508
509static qreal resolvedWidth(const QQStyleKitDelegateProperties *element, qreal availableW)
510{
511 return qMax(0.0, element->fillWidth() ? availableW : element->width());
512}
513
514static qreal resolvedHeight(const QQStyleKitDelegateProperties *element, qreal availableH)
515{
516 return qMax(0.0, element->fillHeight() ? availableH : element->height());
517}
518
519static QMargins elementMargins(const QQStyleKitDelegateProperties *element)
520{
521 using QtPrivate::qSaturateRound;
522 return QMargins(qSaturateRound(element->leftMargin()),
523 qSaturateRound(element->topMargin()),
524 qSaturateRound(element->rightMargin()),
525 qSaturateRound(element->bottomMargin()));
526}
527
528static QRect alignedRectInContainer(const QRect &container, const QSize &size, uint alignment,
529 const QMargins &padding, const QMargins &margins)
530{
531 QRect r(QPoint(0, 0), size);
532
533 if (alignment & Qt::AlignLeft)
534 r.moveLeft(container.x() + padding.left() + margins.left());
535 else if (alignment & Qt::AlignHCenter)
536 r.moveLeft(container.x() + (container.width() - size.width()) / 2);
537 else // AlignRight
538 r.moveLeft(container.right() + 1 - padding.right() - margins.right() - size.width());
539
540 if (alignment & Qt::AlignTop)
541 r.moveTop(container.y() + padding.top() + margins.top());
542 else if (alignment & Qt::AlignVCenter)
543 r.moveTop(container.y() + (container.height() - size.height()) / 2);
544 else // AlignBottom
545 r.moveTop(container.bottom() + 1 - padding.bottom() - margins.bottom() - size.height());
546
547 return r;
548}
549
550/*! \internal
551 Returns the rect of \a delegate inside \a container: sized from the width/height or fill
552 properties after deducting \a padding and the delegate's margins, then aligned (falling back
553 to \a hDefault and \a vDefault). Returns a null rect if \a delegate is null or not visible.
554*/
555static QRect delegateRect(const QQStyleKitDelegateProperties *delegate, const QRect &container,
556 Qt::Alignment hDefault, Qt::Alignment vDefault,
557 const QMargins &padding = QMargins())
558{
559 if (!delegate || !delegate->visible())
560 return QRect();
561 using QtPrivate::qSaturateRound;
562 const int w = qSaturateRound(resolvedWidth(
563 delegate, container.width() - padding.left() - padding.right()
564 - delegate->leftMargin() - delegate->rightMargin()));
565 const int h = qSaturateRound(resolvedHeight(
566 delegate, container.height() - padding.top() - padding.bottom()
567 - delegate->topMargin() - delegate->bottomMargin()));
568 return alignedRectInContainer(container, QSize(w, h),
569 resolvedAlignment(delegate->alignment(), hDefault, vDefault),
570 padding, elementMargins(delegate));
571}
572
573/*! \internal
574 Applies \a element's rotation and scale to \a painter about the centre of \a box.
575 Returns \c false if the scale is 0 or if \a element is null, otherwise returns \c true.
576*/
577static bool applyDelegateTransform(QPainter *painter,
578 const QQStyleKitDelegateProperties *element,
579 const QRectF &box)
580{
581 if (!element)
582 return false;
583
584 const qreal scale = element->scale();
585 if (qFuzzyIsNull(scale))
586 return false;
587
588 const qreal rotation = element->rotation();
589 if (qFuzzyIsNull(rotation) && qFuzzyCompare(scale, 1.0))
590 return true;
591
592 const QPointF center = box.center();
593 painter->translate(center);
594 if (!qFuzzyIsNull(rotation))
595 painter->rotate(rotation);
596 if (!qFuzzyCompare(scale, 1.0))
597 painter->scale(scale, scale);
598 painter->translate(-center);
599 return true;
600}
601
602static void drawStyledItemImage(const QQStyleKitImageProperties *image, const QRectF &rect,
603 qreal opacity, QPainter *painter)
604{
605 if (!image || image->source().isEmpty() || image->color().alpha() <= 0)
606 return;
607
608 QString imageSource = image->source().toString();
609 if (imageSource.startsWith(QLatin1String("qrc:/")))
610 imageSource = imageSource.mid(3);
611 QUrl imageUrl(imageSource);
612 QString imagePath = imageUrl.isLocalFile() ? imageUrl.toLocalFile() : imageSource;
613 QPixmap pixmap(imagePath);
614 if (pixmap.isNull())
615 return;
616
617 if (image->color().isValid()) {
618 QImage coloredImage = pixmap.toImage();
619 QPainter imagePainter(&coloredImage);
620 imagePainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
621 imagePainter.fillRect(coloredImage.rect(), image->color());
622 imagePainter.end();
623 pixmap = QPixmap::fromImage(coloredImage);
624 }
625 painter->setOpacity(opacity);
626 const QSizeF pixmapSize = pixmap.deviceIndependentSize();
627
628 switch (image->fillMode()) {
629 case QQuickImage::PreserveAspectFit: {
630 const QSize scaled = pixmapSize.scaled(rect.size(), Qt::KeepAspectRatio).toSize();
631 const QPointF topLeft(rect.x() + (rect.width() - scaled.width()) / 2.0,
632 rect.y() + (rect.height() - scaled.height()) / 2.0);
633 painter->drawPixmap(QRectF(topLeft, scaled), pixmap, QRectF(QPointF(0, 0), pixmapSize));
634 break;
635 }
636 case QQuickImage::PreserveAspectCrop: {
637 const QSize scaled = pixmapSize
638 .scaled(rect.size(), Qt::KeepAspectRatioByExpanding)
639 .toSize();
640 const QPointF topLeft(rect.x() + (rect.width() - scaled.width()) / 2.0,
641 rect.y() + (rect.height() - scaled.height()) / 2.0);
642 QPainterStateGuard stateGuard(painter);
643 painter->setClipRect(rect);
644 painter->drawPixmap(QRectF(topLeft, scaled), pixmap, QRectF(QPointF(0, 0), pixmapSize));
645 break;
646 }
647 // TODO: Support remaining fill modes
648 case QQuickImage::Stretch:
649 default:
650 painter->drawPixmap(rect, pixmap, QRectF(QPointF(0, 0), pixmapSize));
651 break;
652 }
653}
654
655static void drawStyledItemContents(const QQStyleKitDelegateProperties *props, const QRectF &rect,
656 QPainter *painter)
657{
658 if (!props || !props->visible() || props->opacity() <= 0 || !rect.isValid())
659 return;
660
661 QPainterStateGuard stateGuard(painter);
662 painter->setRenderHint(QPainter::Antialiasing, true);
663 if (props->clip())
664 painter->setClipping(true);
665
666 // opacity
667 const auto opacity = props->opacity();
668
669 // border
670 const auto *border = props->border();
671 auto borderWidth = 0;
672 auto inset = 0.0;
673 QColor borderColor;
674 if (border) {
675 borderWidth = border->width();
676 borderColor = border->color();
677 inset = borderWidth / 2.0;
678 }
679 if (borderWidth > 0 && borderColor.isValid() && borderColor.alpha() > 0) {
680 QPen pen(borderColor, borderWidth);
681 painter->setPen(pen);
682 } else {
683 painter->setPen(Qt::NoPen);
684 }
685
686 // radius
687 // TODO: support different radius for each corner
688 const qreal minDimension = qMin(rect.width(), rect.height());
689 const qreal xRadius = qMax(0.0, qMin(props->topLeftRadius() - inset, minDimension / 2.0));
690 const qreal yRadius = qMax(0.0, qMin(props->bottomLeftRadius() - inset, minDimension / 2.0));
691
692 QRectF adjustedRect = rect.adjusted(inset, inset, -inset, -inset);
693
694 // gradients/color
695 // TODO: support palette roles for colors and gradients
696 QColor color = props->color();
697 QBrush colorBrush(color);
698 painter->setBrush(colorBrush);
699 if (color.isValid()) {
700 painter->setOpacity(opacity);
701 painter->drawRoundedRect(adjustedRect, xRadius, yRadius, Qt::AbsoluteSize);
702 }
703 if (QQuickGradient *gradient = props->gradient()) {
704 QLinearGradient linearGradient(rect.topLeft(), rect.bottomLeft());
705 QQmlListProperty<QQuickGradientStop> stops = gradient->stops();
706 const int stopCount = stops.count(&stops);
707 for (int i = 0; i < stopCount; i++) {
708 QQuickGradientStop *stop = static_cast<QQuickGradientStop *>(stops.at(&stops, i));
709 linearGradient.setColorAt(stop->position(), stop->color());
710 }
711 QBrush gradientBrush(linearGradient);
712 painter->setBrush(gradientBrush);
713 painter->setOpacity(opacity);
714 painter->drawRoundedRect(adjustedRect, xRadius, yRadius, Qt::AbsoluteSize);
715 }
716
717 // image
718 drawStyledItemImage(props->image(), adjustedRect, opacity, painter);
719}
720
721static void drawStyledItemRect(const QQStyleKitDelegateProperties *props, const QRectF &rect,
722 QPainter *painter)
723{
724 if (!props || !props->visible() || props->opacity() <= 0 || !rect.isValid())
725 return;
726
727 QPainterStateGuard stateGuard(painter);
728 if (!applyDelegateTransform(painter, props, rect))
729 return;
730 drawStyledItemContents(props, rect, painter);
731}
732
733static void drawControlIndicator(const QQStyleKitDelegateProperties *indicator,
734 const QRectF &rect, QPainter *painter)
735{
736 if (!indicator || !indicator->visible() || indicator->opacity() <= 0 || !rect.isValid())
737 return;
738
739 // The foreground is a "child" of the indicator container so it
740 // inherits the indicator's transform. Apply it here and let the foreground stack its
741 // own on top.
742 QPainterStateGuard stateGuard(painter);
743 if (!applyDelegateTransform(painter, indicator, rect))
744 return;
745
746 // indicator (background)
747 QRectF indicatorRect = rect;
748 drawStyledItemContents(indicator, indicatorRect, painter);
749
750 const QQStyleKitDelegateProperties *foreground = nullptr;
751 if (auto *indicatorWithSubTypes =
752 qobject_cast<const QQStyleKitIndicatorWithSubTypes *>(indicator)) {
753 foreground = indicatorWithSubTypes->foreground();
754 } else if (auto *indicatorProps =
755 qobject_cast<const QQStyleKitIndicatorProperties *>(indicator)) {
756 foreground = indicatorProps->foreground();
757 } else {
758 return;
759 }
760 if (!foreground || !foreground->visible() || foreground->opacity() <= 0)
761 return;
762
763 // foreground
764 QRectF foregroundRect;
765 const uint foregroundAlign = resolvedAlignment(foreground->alignment(), Qt::AlignHCenter,
766 Qt::AlignVCenter);
767 const auto foregroundW = resolvedWidth(
768 foreground, indicatorRect.width() - foreground->leftMargin() - foreground->rightMargin());
769 const auto foregroundH = resolvedHeight(
770 foreground, indicatorRect.height() - foreground->topMargin() - foreground->bottomMargin());
771 foregroundRect.setSize(QSizeF(foregroundW, foregroundH));
772 if (foregroundAlign & Qt::AlignLeft)
773 foregroundRect.moveLeft(indicatorRect.left() + foreground->leftMargin());
774 else if (foregroundAlign & Qt::AlignHCenter)
775 foregroundRect.moveLeft(indicatorRect.left() + (indicatorRect.width() - foregroundW) / 2.0);
776 else if (foregroundAlign & Qt::AlignRight)
777 foregroundRect.moveLeft(indicatorRect.right() - foreground->rightMargin() - foregroundW);
778 if (foregroundAlign & Qt::AlignTop)
779 foregroundRect.moveTop(indicatorRect.top() + foreground->topMargin());
780 else if (foregroundAlign & Qt::AlignVCenter)
781 foregroundRect.moveTop(indicatorRect.top() + (indicatorRect.height() - foregroundH) / 2.0);
782 else if (foregroundAlign & Qt::AlignBottom)
783 foregroundRect.moveTop(indicatorRect.bottom() - foreground->bottomMargin() - foregroundH);
784 drawStyledItemRect(foreground, foregroundRect, painter);
785}
786
787static void drawControlText(const QQStyleKitTextProperties *textProps, const QFont &font,
788 const QRect &rect, const QString &text, uint textFlags, QPainter *p,
789 Qt::Alignment defaultAlignment = Qt::AlignHCenter | Qt::AlignVCenter)
790{
791 uint flags = textFlags;
792 flags |= textProps
793 ? resolvedAlignment(textProps->alignment(), defaultAlignment & Qt::AlignHorizontal_Mask,
794 defaultAlignment & Qt::AlignVertical_Mask)
795 : uint(defaultAlignment);
796
797 const QFont oldFont = p->font();
798 p->setFont(font.resolve(oldFont));
799 p->setBrush(Qt::NoBrush);
800 p->setPen(textProps ? textProps->color() : QColor());
801 p->drawText(rect, flags, text, nullptr);
802}
803
804#if QT_CONFIG(dial)
805static qreal dialAngle(const QStyleOptionSlider *dial)
806{
807 const qreal range = dial->maximum - dial->minimum;
808 qreal position = range > 0 ? (dial->sliderPosition - dial->minimum) / range : 0;
809 if (!dial->upsideDown)
810 position = 1 - position;
811 const qreal start = dial->dialWrapping ? -180 : -150;
812 return start + position * -2 * start;
813}
814
815static QPointF dialHandleOffset(const QRectF &backgroundRect, const QQStyleKitDelegateProperties *handle)
816{
817 // mirrors Dial.qml
818 return QPointF(handle->leftMargin() - handle->rightMargin(),
819 -qMin(backgroundRect.width(), backgroundRect.height()) / 2 + handle->height() / 2
820 + handle->topMargin() - handle->bottomMargin());
821}
822#endif
823
824// Copied from qstylesheetstyle.cpp
825static const QWidget *containerWidget(const QWidget *w)
826{
827#if QT_CONFIG(lineedit)
828 if (qobject_cast<const QLineEdit *>(w)) {
829 // if the QLineEdit is an embeddedWidget, we need the real widget
830 QWidget *parent = w->parentWidget();
831 if (false
832# if QT_CONFIG(combobox)
833 || qobject_cast<const QComboBox *>(parent)
834# endif
835# if QT_CONFIG(spinbox)
836 || qobject_cast<const QAbstractSpinBox *>(parent)
837# endif
838 ) {
839 return parent;
840 }
841 }
842#endif
843
844#if QT_CONFIG(scrollarea)
845 if (const QAbstractScrollArea *sa =
846 qobject_cast<const QAbstractScrollArea *>(w->parentWidget())) {
847 if (sa->viewport() == w)
848 return w->parentWidget();
849 }
850#endif
851
852 return w;
853}
854
855QStyleKitStylePrivate::QStyleKitStylePrivate()
856 : QCommonStylePrivate()
857{
858}
859
860static QUrl urlFromStylePath(const QString &filePath)
861{
862 if (filePath.startsWith(QLatin1Char(':')))
863 return QUrl(QLatin1String("qrc") + filePath);
864 if (filePath.startsWith(QLatin1String("qrc:")) || filePath.startsWith(QLatin1String("file:")))
865 return QUrl(filePath);
866 return QUrl::fromLocalFile(filePath);
867}
868
869bool QStyleKitStylePrivate::loadStyle()
870{
871 Q_Q(QStyleKitStyle);
872 if (!qmlEngine)
873 qmlEngine = new QQmlEngine(q);
874
875 if (!qmlEngine) {
876 qWarning("QStyleKitStyle: No QML engine available to load style.");
877 return false;
878 }
879 const QUrl url = stylePath.isEmpty() ? QUrl() : urlFromStylePath(stylePath);
880 if (stylePath.isEmpty() || !url.isValid()) {
881 qWarning("QStyleKitStyle: No valid style path provided: %s", qPrintable(stylePath));
882 return false;
883 }
884 QQmlComponent component(qmlEngine, url);
885 if (component.isError()) {
886 qWarning("QStyleKitStyle: Failed to load style from %s: %s", qPrintable(stylePath),
887 qPrintable(component.errorString()));
888 return false;
889 }
890 // Avoid creating anything other than StyleKit Style objects
891 // by checking the metaobject of the root type before creating the object
892 QQmlComponentPrivate *componentPrivate = QQmlComponentPrivate::get(&component);
893 const auto compilationUnit = componentPrivate->compilationUnit();
894 const auto propertyCache = compilationUnit ? compilationUnit->rootPropertyCache() : nullptr;
895 const auto firstMetaObject = propertyCache ? propertyCache->firstCppMetaObject() : nullptr;
896 if (!firstMetaObject || !firstMetaObject->inherits(&QQStyleKitStyle::staticMetaObject)) {
897 qWarning("QStyleKitStyle: Failed to load style from %s: component is not a StyleKit Style.",
898 qPrintable(stylePath));
899 return false;
900 }
901 QQStyleKitStyle *styleObject = qobject_cast<QQStyleKitStyle *>(component.create());
902 if (!styleObject) {
903 qWarning("QStyleKitStyle: Failed to create style object from %s: "
904 "component is not a StyleKit style.",
905 qPrintable(stylePath));
906 return false;
907 }
908 const bool isReload = style != nullptr;
909 delete style;
910 style = styleObject;
911 style->setParent(q);
912
913 if (!isReload) {
914 QObject::connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, q,
915 [this]() {
916 if (style && style->themeName() == QLatin1String("System")) {
917 style->recreateTheme();
918 updateStyle();
919 }
920 });
921 }
922
923 if (style->loaded())
924 QQStyleKitReader::resetReadersForStyle(style);
925
926 return true;
927}
928
929/*! \internal
930 Creates and returns the empty fallback style used when no user
931 style is loaded.
932*/
933QQStyleKitStyle *QStyleKitStylePrivate::ensureDefaultStyle()
934{
935 if (defaultStyle)
936 return defaultStyle;
937
938 Q_Q(QStyleKitStyle);
939 if (!qmlEngine)
940 qmlEngine = new QQmlEngine(q);
941
942 QQmlComponent component(qmlEngine);
943 component.loadFromModule("Qt.labs.StyleKit", "Style");
944 if (component.isError()) {
945 qWarning("QStyleKitStyle: Failed to create default fallback style: %s",
946 qPrintable(component.errorString()));
947 return nullptr;
948 }
949 defaultStyle = qobject_cast<QQStyleKitStyle *>(component.create());
950 if (defaultStyle) {
951 defaultStyle->setParent(q);
952 qCDebug(lcStyleKit,
953 "No style set; using a default fallback style. "
954 "Set stylePath to load a StyleKit Style.");
955 }
956 return defaultStyle;
957}
958
959/*! \internal
960 Returns the user style if one is loaded, otherwise the default fallback
961 style
962*/
963QQStyleKitStyle *QStyleKitStylePrivate::effectiveStyle() const
964{
965 return style ? style : defaultStyle;
966}
967
968void QStyleKitStylePrivate::updateStyle()
969{
970 clearMetricsCache();
971
972 QQStyleKitStyle *effective = effectiveStyle();
973
974 if (sharedReader && sharedReader->style() != effective)
975 sharedReader->setExplicitStyle(effective);
976
977 if (subElementReader && subElementReader->style() != effective)
978 subElementReader->setExplicitStyle(effective);
979
980 clearAllSubElements();
981
982 for (auto *wr : std::as_const(widgetReaders)) {
983 if (wr->style() != effective)
984 wr->setExplicitStyle(effective);
985 }
986
987 if (!QWidgetPrivate::allWidgets)
988 return;
989
990 for (auto *widget : std::as_const(*QWidgetPrivate::allWidgets)) {
991 if (customFontWidgets.contains(widget))
992 unsetStyleFont(widget);
993 refreshStyleFont(widget);
994 if (customPaletteWidgets.contains(widget))
995 unsetStylePalette(widget);
996 refreshStylePalette(widget);
997 refreshStyleMinimumSize(widget);
998 }
999
1000 QEvent styleChange(QEvent::StyleChange);
1001 for (auto *widget : std::as_const(*QWidgetPrivate::allWidgets)) {
1002 QApplication::sendEvent(widget, &styleChange);
1003 widget->update();
1004 }
1005}
1006
1007void QStyleKitStylePrivate::refreshStyleMinimumSize(QWidget *widget)
1008{
1009 // QDial::sizeHint() is fixed and never asks the style, so enforce
1010 // the minimum size from the StyleKit style.
1011#if QT_CONFIG(dial)
1012 if (!qobject_cast<QDial *>(widget))
1013 return;
1014 const auto resolved = resolveLayout(QQStyleKitReader::ControlType::Dial, QStyle::State_Enabled);
1015 if (!resolved.isValid())
1016 return;
1017 const auto &m = *resolved.metrics;
1018 const QSize styleSize(m.bgImplicitSize.width() + m.margins.left() + m.margins.right(),
1019 m.bgImplicitSize.height() + m.margins.top() + m.margins.bottom());
1020
1021 auto it = customMinimumSizeWidgets.find(widget);
1022 if (it == customMinimumSizeWidgets.end()) {
1023 it = customMinimumSizeWidgets.insert(widget, widget->minimumSize());
1024 QObject::connect(widget, &QObject::destroyed, widget,
1025 [this, widget]() { customMinimumSizeWidgets.remove(widget); });
1026 }
1027 widget->setMinimumSize(it.value().expandedTo(styleSize));
1028#else
1029 Q_UNUSED(widget);
1030#endif
1031}
1032
1033void QStyleKitStylePrivate::unsetStyleMinimumSize(QWidget *widget)
1034{
1035 const auto it = customMinimumSizeWidgets.constFind(widget);
1036 if (it == customMinimumSizeWidgets.cend())
1037 return;
1038 const QSize original = it.value();
1039 customMinimumSizeWidgets.erase(it);
1040 widget->setMinimumSize(original);
1041}
1042
1043void QStyleKitStylePrivate::setStyleAutoFill(QWidget *widget)
1044{
1045 // Disable the viewport's autoFillBackground
1046 // so the styled background shows through
1047 if (QWidget *vp = managedViewport(widget); vp && vp->autoFillBackground()) {
1048 vp->setAutoFillBackground(false);
1049 autoFillDisabledWidgets.insert(widget);
1050 }
1051}
1052
1053void QStyleKitStylePrivate::unsetStyleAutoFill(QWidget *widget)
1054{
1055 if (!autoFillDisabledWidgets.remove(widget))
1056 return;
1057 if (QWidget *vp = managedViewport(widget))
1058 vp->setAutoFillBackground(true);
1059}
1060
1061void QStyleKitStylePrivate::setStyleTranslucency(QWidget *widget)
1062{
1063 const bool isPopup = false
1064#if QT_CONFIG(menu)
1065 || qobject_cast<QMenu *>(widget)
1066#endif
1067#if QT_CONFIG(combobox)
1068 || widget->inherits("QComboBoxPrivateContainer")
1069#endif
1070#if QT_CONFIG(tooltip)
1071 || widget->inherits("QTipLabel")
1072#endif
1073 ;
1074 if (!isPopup || widget->testAttribute(Qt::WA_TranslucentBackground))
1075 return;
1076 // Enable translucency for popups so that we can draw rounded corners
1077 widget->setAttribute(Qt::WA_TranslucentBackground, true);
1078 widget->setWindowFlag(Qt::FramelessWindowHint, true);
1079 translucentWidgets.insert(widget);
1080}
1081
1082void QStyleKitStylePrivate::unsetStyleTranslucency(QWidget *widget)
1083{
1084 if (!translucentWidgets.remove(widget))
1085 return;
1086 widget->setAttribute(Qt::WA_TranslucentBackground, false);
1087 widget->setWindowFlag(Qt::FramelessWindowHint, false);
1088}
1089
1090// Follow the approach in qstylesheetstyle.cpp
1091void QStyleKitStylePrivate::unsetStyleFont(QWidget *widget)
1092{
1093 auto it = customFontWidgets.find(widget);
1094 if (it == customFontWidgets.end())
1095 return;
1096
1097 auto customFont = std::move(*it);
1098 customFontWidgets.erase(it);
1099 widget->setFont(std::move(customFont).reverted(QWidgetPrivate::get(widget)->localFont()));
1100}
1101
1102void QStyleKitStylePrivate::setStyleFont(QWidget *widget, const QFont &styleFont)
1103{
1104 if (!effectiveStyle() || !widget)
1105 return;
1106
1107 const auto styleMask = styleFont.resolveMask();
1108 auto it = customFontWidgets.find(widget);
1109
1110 if (it == customFontWidgets.end()) {
1111 if (styleMask == 0)
1112 return;
1113 it = customFontWidgets.insert(widget, { QWidgetPrivate::get(widget)->localFont(), 0 });
1114 QObject::connect(widget, &QObject::destroyed, widget,
1115 [this, widget]() { customFontWidgets.remove(widget); });
1116 }
1117
1118 it->resolveMask = styleMask;
1119
1120 const QFont &baseline = it->oldWidgetValue;
1121 QFont merged = styleFont.resolve(baseline);
1122 merged.setResolveMask(baseline.resolveMask() | styleMask);
1123 widget->setFont(merged);
1124}
1125
1126void QStyleKitStylePrivate::refreshStyleFont(QWidget *widget)
1127{
1128 if (!effectiveStyle() || !widget)
1129 return;
1130
1131 const QWidget *targetWidget = containerWidget(widget);
1132 QQStyleKitReader::ControlType controlType = controlTypeForWidget(targetWidget);
1133
1134 auto *shared = ensureSharedReader();
1135 if (!shared)
1136 return;
1137
1138 QStyleOption opt;
1139 opt.initFrom(targetWidget);
1140 const QQSK::State currentState = resolvedStateFor(controlType, opt.state, targetWidget);
1141
1142 shared->setControlTypeAndState(controlType, currentState, false);
1143 setStyleFont(widget, shared->font());
1144}
1145
1146void QStyleKitStylePrivate::unsetStylePalette(QWidget *widget)
1147{
1148 auto it = customPaletteWidgets.find(widget);
1149 if (it == customPaletteWidgets.end())
1150 return;
1151
1152 auto customPalette = std::move(*it);
1153 customPaletteWidgets.erase(it);
1154 widget->setPalette(std::move(customPalette).reverted(widget->palette()));
1155}
1156
1157void QStyleKitStylePrivate::setStylePalette(QWidget *widget, const QPalette &stylePalette) const
1158{
1159 if (!effectiveStyle() || !widget)
1160 return;
1161
1162 const quint64 styleMask = stylePalette.resolveMask();
1163 if (styleMask == 0)
1164 return;
1165
1166 if (!customPaletteWidgets.contains(widget)) {
1167 customPaletteWidgets.insert(widget, { widget->palette(), 0 });
1168 QObject::connect(widget, &QObject::destroyed, widget,
1169 [this, widget]() { customPaletteWidgets.remove(widget); });
1170 }
1171
1172 customPaletteWidgets[widget].resolveMask |= styleMask;
1173 QPalette merged = stylePalette.resolve(widget->palette());
1174 merged.setResolveMask(widget->palette().resolveMask() | styleMask);
1175 widget->setPalette(merged);
1176}
1177
1178// For widgets that partially or fully draw themselves (instead of delegating to the style),
1179// using palette roles, we need to push the style colors to those roles
1180void QStyleKitStylePrivate::refreshStylePalette(QWidget *widget)
1181{
1182 if (!effectiveStyle() || !widget)
1183 return;
1184
1185 const bool isWindow = widget->isWindow();
1186 const bool isPaletteManaged = isSelfPaintingWidget(widget) || isWindow;
1187 if (!isPaletteManaged)
1188 return;
1189
1190 const QWidget *targetWidget = containerWidget(widget);
1191 QQStyleKitReader::ControlType controlType = controlTypeForWidget(targetWidget);
1192
1193 auto *shared = ensureSharedReader();
1194 if (!shared)
1195 return;
1196
1197 QStyleOption opt;
1198 opt.initFrom(targetWidget);
1199 const QQSK::State currentState = resolvedStateFor(controlType, opt.state, targetWidget);
1200
1201 QPalette stylePalette;
1202 shared->setControlTypeAndState(controlType, currentState, false);
1203 if (isWindow) {
1204 // Tooltips draw their text using QPalette::ToolTipText role
1205 if (controlType == QQStyleKitReader::ControlType::ToolTip) {
1206 if (const auto *text = shared->global()->text();
1207 text && text->isDefined(QQSK::Property::Color)) {
1208 stylePalette.setColor(QPalette::ToolTipText, text->color());
1209 }
1210 } else {
1211 // Windows draw their own background using the QPalette::Window role
1212 if (const auto *bg = shared->global()->background();
1213 bg && bg->isDefined(QQSK::Property::Color)) {
1214 stylePalette.setColor(QPalette::Window, bg->color());
1215 }
1216 shared->setControlTypeAndState(controlType, QQSK::StateFlag::Disabled, false);
1217 if (const auto *dbg = shared->global()->background();
1218 dbg && dbg->isDefined(QQSK::Property::Color)) {
1219 stylePalette.setColor(QPalette::Disabled, QPalette::Window, dbg->color());
1220 }
1221 }
1222 } else {
1223 // The remaining text-based widgets use the QPalette::Text/WindowText roles for their
1224 // foreground color
1225 shared->setControlTypeAndState(controlType, currentState, false);
1226 if (const auto *text = shared->global()->text();
1227 text && text->isDefined(QQSK::Property::Color)) {
1228 stylePalette.setColor(QPalette::Text, text->color());
1229 stylePalette.setColor(QPalette::WindowText, text->color());
1230 }
1231 shared->setControlTypeAndState(controlType, QQSK::StateFlag::Disabled, false);
1232 if (const auto *dt = shared->global()->text();
1233 dt && dt->isDefined(QQSK::Property::Color)) {
1234 stylePalette.setColor(QPalette::Disabled, QPalette::Text, dt->color());
1235 stylePalette.setColor(QPalette::Disabled, QPalette::WindowText, dt->color());
1236 }
1237 }
1238
1239 setStylePalette(widget, stylePalette);
1240}
1241
1242/*! \internal
1243 Returns a reader for the given widget, creating and caching it if needed.
1244*/
1245QQStyleKitReader *QStyleKitStylePrivate::readerForWidget(const QWidget *widget) const
1246{
1247 Q_Q(const QStyleKitStyle);
1248 QQStyleKitStyle *effective = effectiveStyle();
1249 if (!effective || !widget)
1250 return nullptr;
1251
1252 if (auto it = widgetReaders.find(widget); it != widgetReaders.end())
1253 return *it;
1254
1255 auto *widgetReader = new QQStyleKitReader(const_cast<QStyleKitStyle *>(q));
1256 widgetReader->setExplicitStyle(effective);
1257 widgetReader->setTarget(paintTarget(widget));
1258 widgetReader->setControlType(controlTypeForWidget(widget));
1259 widgetReader->setCompleted(true);
1260 widgetReaders.insert(widget, widgetReader);
1261 QObjectPrivate::connect(widget, &QObject::destroyed, this,
1262 &QStyleKitStylePrivate::onWidgetDestroyed, Qt::UniqueConnection);
1263 return widgetReader;
1264}
1265
1266void QStyleKitStylePrivate::cleanupWidgetReader(const QWidget *widget) const
1267{
1268 if (auto *reader = widgetReaders.take(widget))
1269 reader->deleteLater();
1270 cleanupSubElements(widget);
1271}
1272
1273void QStyleKitStylePrivate::onWidgetDestroyed(QObject *w) const
1274{
1275 cleanupWidgetReader(static_cast<QWidget *>(w));
1276}
1277
1278/*! \internal
1279 Returns the key identifying the sub-element (item-view cell, menu item or
1280 menubar item) that \a opt refers to, or an invalid key if the option does
1281 not identify one.
1282*/
1283QStyleKitStylePrivate::SubElementKey
1284QStyleKitStylePrivate::subElementKeyForOption(const QStyleOption *opt, const QWidget *widget)
1285{
1286 SubElementKey key;
1287 if (!widget)
1288 return key;
1289
1290#if QT_CONFIG(itemviews)
1291 if (const auto *viewOpt = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) {
1292 const QModelIndex &idx = viewOpt->index;
1293 if (!idx.isValid())
1294 return key;
1295 key.widget = widget;
1296 key.id = SubElementKey::ItemViewCell{ idx.model(), idx.internalId(), idx.row(),
1297 idx.column() };
1298 return key;
1299 }
1300#endif
1301#if QT_CONFIG(menu)
1302 if (const auto *menuOpt = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) {
1303 if (menuOpt->menuItemType == QStyleOptionMenuItem::Separator)
1304 return key;
1305 QAction *action = nullptr;
1306 if (const auto *menu = qobject_cast<const QMenu *>(widget))
1307 action = menu->actionAt(menuOpt->rect.center());
1308 else if (const auto *menuBar = qobject_cast<const QMenuBar *>(widget))
1309 action = menuBar->actionAt(menuOpt->rect.center());
1310 if (action) {
1311 key.widget = widget;
1312 key.id = SubElementKey::Action{ action };
1313 }
1314 return key;
1315 }
1316#endif
1317#if QT_CONFIG(tabbar)
1318 if (const auto *tabOpt = qstyleoption_cast<const QStyleOptionTab *>(opt)) {
1319 if (tabOpt->tabIndex < 0)
1320 return key;
1321 key.widget = widget;
1322 key.id = SubElementKey::Tab{ tabOpt->tabIndex };
1323 return key;
1324 }
1325#endif
1326 return key;
1327}
1328
1329/*! \internal
1330 Creates and returns a transitions-enabled reader for the given sub-element
1331 that animates from \a fromState to \a toState
1332
1333*/
1334QQStyleKitReader *QStyleKitStylePrivate::startSubElementTransition(
1335 const SubElementKey &key, QQStyleKitReader::ControlType type, QQSK::State fromState,
1336 QQSK::State toState, QQuickTransition *transition) const
1337{
1338 Q_Q(const QStyleKitStyle);
1339 QQStyleKitStyle *effective = effectiveStyle();
1340 if (!effective)
1341 return nullptr;
1342
1343 // Reclaim finished readers. running() is per (shared) transition object, so
1344 // it turns false only once no instance of that transition animates anywhere
1345 for (auto it = subElementAnimationReaders.begin(); it != subElementAnimationReaders.end();) {
1346 const QQuickTransition *t = it.value()->transition();
1347 if (!t || !t->running()) {
1348 it.value()->deleteLater();
1349 it = subElementAnimationReaders.erase(it);
1350 } else {
1351 ++it;
1352 }
1353 }
1354
1355
1356 if (subElementAnimationReaders.size() >= kMaxSubElementAnimationReaders) {
1357 const auto it = subElementAnimationReaders.begin();
1358 if (const QWidget *widget = it.key().widget)
1359 paintTarget(widget)->update(); // snap to end state
1360 it.value()->deleteLater();
1361 subElementAnimationReaders.erase(it);
1362 }
1363
1364 auto *reader = new QQStyleKitReader(const_cast<QStyleKitStyle *>(q));
1365 reader->setExplicitStyle(effective);
1366 reader->setTarget(paintTarget(key.widget));
1367 // just stores the fromState, doesn't start the animation yet
1368 reader->setControlTypeAndState(type, fromState);
1369 reader->setCompleted(true);
1370 // As delegates are created lazily, a fresh reader has none,
1371 // so we need to create them now so delegates can already start animating changes
1372 // Only create delegates actually used by subelements, ie: background, indicators
1373 reader->background();
1374 auto *indicator = reader->indicator();
1375 indicator->first()->foreground();
1376 indicator->second()->foreground();
1377 // Start the transition to the new state
1378 reader->setControlTypeAndState(type, toState);
1379 if (!transition || !transition->running()) {
1380 reader->deleteLater();
1381 return nullptr;
1382 }
1383 subElementAnimationReaders.insert(key, reader);
1384 return reader;
1385}
1386
1387/*! \internal
1388 Drops all sub-element state tracking and animation readers for \a widget.
1389*/
1390void QStyleKitStylePrivate::cleanupSubElements(const QWidget *widget) const
1391{
1392 for (auto it = subElementStates.begin(); it != subElementStates.end();) {
1393 if (it.key().widget == widget)
1394 it = subElementStates.erase(it);
1395 else
1396 ++it;
1397 }
1398 for (auto it = subElementAnimationReaders.begin(); it != subElementAnimationReaders.end();) {
1399 if (it.key().widget == widget) {
1400 it.value()->deleteLater();
1401 it = subElementAnimationReaders.erase(it);
1402 } else {
1403 ++it;
1404 }
1405 }
1406}
1407
1408/*! \internal
1409 Drops all sub-element state tracking and animation readers.
1410*/
1411void QStyleKitStylePrivate::clearAllSubElements() const
1412{
1413 subElementStates.clear();
1414 for (QQStyleKitReader *reader : std::as_const(subElementAnimationReaders))
1415 reader->deleteLater();
1416 subElementAnimationReaders.clear();
1417}
1418
1419/*! \internal
1420 Resolves the given QStyle::State into a QQSK::State based on the mapping of
1421 state flags for the given control type.
1422*/
1423QQSK::State QStyleKitStylePrivate::resolvedStateFor(QQStyleKitReader::ControlType type,
1424 QStyle::State state,
1425 const QWidget *widget) const
1426{
1427 QQSK::State flags;
1428 flags.setFlag(QQSK::StateFlag::Hovered, (state & QStyle::State_MouseOver));
1429 flags.setFlag(QQSK::StateFlag::Pressed, state & QStyle::State_Sunken);
1430 flags.setFlag(QQSK::StateFlag::Checked, state & QStyle::State_On);
1431 flags.setFlag(QQSK::StateFlag::Focused, state & QStyle::State_HasFocus);
1432 flags.setFlag(QQSK::StateFlag::Highlighted, state & QStyle::State_Selected);
1433
1434 bool hasOrientation = false;
1435 switch (type) {
1436 case QQStyleKitReader::ProgressBar:
1437 case QQStyleKitReader::ScrollBar:
1438 case QQStyleKitReader::ScrollIndicator:
1439 case QQStyleKitReader::Slider:
1440 case QQStyleKitReader::ToolBar:
1441 case QQStyleKitReader::ToolSeparator:
1442 hasOrientation = true;
1443 break;
1444 default:
1445 break;
1446 }
1447 if (hasOrientation)
1448 flags.setFlag(QQSK::StateFlag::Vertical, !(state & QStyle::State_Horizontal));
1449 // Some widgets don't set State_Enabled in their
1450 // style option even when the widget is enabled.
1451 // Fall back to widget->isEnabled() in that case
1452 const bool enabled = (state & QStyle::State_Enabled) || (widget && widget->isEnabled());
1453 flags.setFlag(QQSK::StateFlag::Disabled, !enabled);
1454
1455 // ComboBox uses QStyle::State_On to indicate the popup is open, which is
1456 // not a "checked" semantic.
1457 if (type == QQStyleKitReader::ControlType::ComboBox)
1458 flags.setFlag(QQSK::StateFlag::Checked, false);
1459
1460 // Popup has no hover state in the Controls style
1461 if (type == QQStyleKitReader::ControlType::Popup)
1462 flags.setFlag(QQSK::StateFlag::Hovered, false);
1463
1464 // QTabBar sets State_Selected (not State_On) on the current tab
1465 if (type == QQStyleKitReader::ControlType::TabButton) {
1466 flags.setFlag(QQSK::StateFlag::Checked, state & QStyle::State_Selected);
1467 flags.setFlag(QQSK::StateFlag::Highlighted, false);
1468 }
1469
1470 return flags;
1471}
1472
1473/*! \internal
1474 Returns the ControlMetrics for the given control type and state,
1475 reading from the style if needed and caching the result.
1476*/
1477const QStyleKitStylePrivate::ControlMetrics &
1478QStyleKitStylePrivate::metricsFor(QQStyleKitReader::ControlType type, QQSK::State state) const
1479{
1480 if (auto it = metricsCache.find({ type, state }); it != metricsCache.end())
1481 return *it;
1482
1483 auto *reader = ensureSharedReader();
1484 Q_ASSERT(reader);
1485 reader->setControlTypeAndState(type, state, false);
1486 return *metricsCache.insert({ type, state }, metricsForReader(reader));
1487}
1488
1489/*! \internal
1490 Resolves the properties for the given widget, control type and state,
1491 and returns them as a QQStyleKitResolved.
1492*/
1493QStyleKitStylePrivate::QQStyleKitResolved
1494QStyleKitStylePrivate::resolve(const QWidget *w, QQStyleKitReader::ControlType type,
1495 QStyle::State state) const
1496{
1497 QQStyleKitResolved out;
1498 out.widget = w;
1499
1500 auto *reader = readerForWidget(w);
1501 if (!reader)
1502 return out;
1503
1504 const QQSK::State resolvedState = resolvedStateFor(type, state, w);
1505 reader->setControlTypeAndState(type, resolvedState);
1506
1507 out.reader = reader;
1508 out.metrics = &metricsFor(type, resolvedState);
1509 return out;
1510}
1511
1512/*! \internal
1513 Resolves the properties for a sub-element of \a w (an item-view item, menu/
1514 menuBar item), and returns them as a QQStyleKitResolved.
1515
1516 Sub-elements in a steady-state use the shared, transitions-disabled
1517 subElementReader. When a sub-element is observed to change state and
1518 the style defines a transition for the new state, a transient per-item
1519 reader is created to run that transition
1520
1521 Pass \a track = false for synthetic resolves that should not register state changes
1522 or retarget a running animation.
1523*/
1524QStyleKitStylePrivate::QQStyleKitResolved
1525QStyleKitStylePrivate::resolveSubElement(const QWidget *w, const QStyleOption *opt,
1526 QQStyleKitReader::ControlType type, QStyle::State state,
1527 bool track) const
1528{
1529 QQStyleKitResolved out;
1530 out.widget = w;
1531
1532 auto *steadyReader = ensureSubElementReader();
1533 if (!steadyReader)
1534 return out;
1535
1536 const QQSK::State newState = resolvedStateFor(type, state, w);
1537 const SubElementKey key = subElementKeyForOption(opt, w);
1538
1539 QQStyleKitReader *reader = nullptr;
1540 if (track && key.isValid()) {
1541 QQStyleKitReader *animReader = subElementAnimationReaders.value(key);
1542 const auto it = subElementStates.constFind(key);
1543 const bool seen = it != subElementStates.cend();
1544
1545 // Fallback to a reasonable base state to transition from by stripping the
1546 // interactive/pointer-driven flags from the new state.
1547 constexpr QQSK::State InteractiveStates = QQSK::State(QQSK::StateFlag::Hovered)
1548 | QQSK::StateFlag::Pressed | QQSK::StateFlag::Checked
1549 | QQSK::StateFlag::Focused | QQSK::StateFlag::Highlighted;
1550 const auto baseState = newState & ~InteractiveStates;
1551 const QQSK::State prevState = seen ? *it : baseState;
1552 if (prevState != newState) {
1553 if (animReader) {
1554 // State changed before transition finished
1555 // Retarget to new state, the transition continues from the current interpolated
1556 // values
1557 animReader->setControlTypeAndState(type, newState);
1558 } else {
1559 // Only pointer-driven states should animate
1560 // If an item first comes into view checked/highlighted, etc.,
1561 // it should not animate
1562 const bool shouldAnimate = seen || newState.testFlag(QQSK::StateFlag::Hovered)
1563 || newState.testFlag(QQSK::StateFlag::Pressed);
1564 if (shouldAnimate) {
1565 steadyReader->setControlTypeAndState(type, newState);
1566 if (QQuickTransition *transition = steadyReader->transition())
1567 animReader = startSubElementTransition(key, type, prevState,
1568 newState, transition);
1569 }
1570 }
1571 }
1572 if (!seen || prevState != newState) {
1573 QObjectPrivate::connect(w, &QObject::destroyed, this,
1574 &QStyleKitStylePrivate::onWidgetDestroyed,
1575 Qt::UniqueConnection);
1576 if (subElementStates.size() >= kMaxSubElementStates)
1577 subElementStates.clear();
1578 subElementStates.insert(key, newState);
1579 }
1580 reader = animReader;
1581 }
1582
1583 if (!reader)
1584 reader = steadyReader;
1585 reader->setControlTypeAndState(type, newState);
1586
1587 out.reader = reader;
1588 out.metrics = &metricsFor(type, newState);
1589 return out;
1590}
1591
1592const QQStyleKitTextProperties *QStyleKitStylePrivate::QQStyleKitResolved::text() const
1593{
1594 return reader ? reader->text() : nullptr;
1595}
1596
1597const QQStyleKitDelegateProperties *QStyleKitStylePrivate::QQStyleKitResolved::background() const
1598{
1599 return reader ? reader->background() : nullptr;
1600}
1601
1602const QQStyleKitHandleProperties *QStyleKitStylePrivate::QQStyleKitResolved::handle() const
1603{
1604 return reader ? reader->handle() : nullptr;
1605}
1606
1607const QQStyleKitIndicatorWithSubTypes *QStyleKitStylePrivate::QQStyleKitResolved::indicator() const
1608{
1609 return reader ? reader->indicator() : nullptr;
1610}
1611
1612QFont QStyleKitStylePrivate::QQStyleKitResolved::font() const
1613{
1614 return reader ? reader->font() : QFont();
1615}
1616
1617void QStyleKitStylePrivate::QQStyleKitResolved::drawBackground(const QRectF &rect,
1618 QPainter *p) const
1619{
1620 drawStyledItemRect(background(), rect, p);
1621}
1622
1623void QStyleKitStylePrivate::QQStyleKitResolved::drawIndicator(const QRectF &rect,
1624 QPainter *p) const
1625{
1626 drawControlIndicator(indicator(), rect, p);
1627}
1628
1629void QStyleKitStylePrivate::QQStyleKitResolved::drawSubIndicator(bool first, const QRectF &rect,
1630 QPainter *p) const
1631{
1632 const auto *ind = indicator();
1633 if (ind)
1634 drawControlIndicator(first ? ind->first() : ind->second(), rect, p);
1635}
1636
1637void QStyleKitStylePrivate::QQStyleKitResolved::drawHandle(const QRectF &rect, QPainter *p) const
1638{
1639 drawStyledItemRect(handle(), rect, p);
1640}
1641
1642void QStyleKitStylePrivate::QQStyleKitResolved::drawText(const QRect &rect, const QString &text,
1643 uint textFlags, QPainter *p,
1644 Qt::Alignment defaultAlignment) const
1645{
1646 drawControlText(this->text(), font(), rect, text, textFlags, p, defaultAlignment);
1647}
1648
1649QQStyleKitReader *QStyleKitStylePrivate::ensureSharedReader() const
1650{
1651 if (sharedReader)
1652 return sharedReader;
1653
1654 Q_Q(const QStyleKitStyle);
1655 QQStyleKitStyle *effective = effectiveStyle();
1656 if (!effective)
1657 return nullptr;
1658 sharedReader = new QQStyleKitReader(const_cast<QStyleKitStyle *>(q));
1659 // Disable transitions since this reader is used for one-off metric reads in layout queries
1660 sharedReader->setTransitionsEnabled(false);
1661 sharedReader->setExplicitStyle(effective);
1662 sharedReader->setCompleted(true);
1663 return sharedReader;
1664}
1665
1666QQStyleKitReader *QStyleKitStylePrivate::ensureSubElementReader() const
1667{
1668 if (subElementReader)
1669 return subElementReader;
1670
1671 Q_Q(const QStyleKitStyle);
1672 QQStyleKitStyle *effective = effectiveStyle();
1673 if (!effective)
1674 return nullptr;
1675 subElementReader = new QQStyleKitReader(const_cast<QStyleKitStyle *>(q));
1676 // Disable transitions: this reader is retargeted across many sub-elements
1677 // per paint pass and must never animate between their states
1678 subElementReader->setTransitionsEnabled(false);
1679 subElementReader->setExplicitStyle(effective);
1680 subElementReader->setCompleted(true);
1681 return subElementReader;
1682}
1683
1684/*! \internal
1685 Resolves the properties for the given control type and state using the shared reader,
1686 which disables transitions and is used for layout queries, so that layout never depends
1687 on animation state.
1688*/
1689QStyleKitStylePrivate::QQStyleKitLayoutResolved
1690QStyleKitStylePrivate::resolveLayout(QQStyleKitReader::ControlType type, QStyle::State state) const
1691{
1692 QQStyleKitLayoutResolved out;
1693 auto *reader = ensureSharedReader();
1694 if (!reader)
1695 return out;
1696
1697 const QQSK::State resolvedState = resolvedStateFor(type, state);
1698 out.metrics = &metricsFor(type, resolvedState);
1699 // metricsFor() sets the sharedReader to (type, resolvedState) on cache miss.
1700 // On hit it didn't, so make sure staticProps / staticFont reflect the state
1701 // and type of the caller
1702 reader->setControlTypeAndState(type, resolvedState, false);
1703 out.staticProps = reader->global();
1704 out.staticFont = reader->font();
1705 return out;
1706}
1707
1708const QQStyleKitTextProperties *QStyleKitStylePrivate::QQStyleKitLayoutResolved::text() const
1709{
1710 return staticProps ? staticProps->text() : nullptr;
1711}
1712
1713const QQStyleKitDelegateProperties *
1714QStyleKitStylePrivate::QQStyleKitLayoutResolved::background() const
1715{
1716 return staticProps ? staticProps->background() : nullptr;
1717}
1718
1719const QQStyleKitHandleProperties *QStyleKitStylePrivate::QQStyleKitLayoutResolved::handle() const
1720{
1721 return staticProps ? staticProps->handle() : nullptr;
1722}
1723
1725QStyleKitStylePrivate::QQStyleKitLayoutResolved::indicator() const
1726{
1727 return staticProps ? staticProps->indicator() : nullptr;
1728}
1729
1730void QStyleKitStylePrivate::clearMetricsCache()
1731{
1732 metricsCache.clear();
1733}
1734
1735QStyleKitStylePrivate::ControlMetrics
1736QStyleKitStylePrivate::metricsForReader(QQStyleKitReader *reader) const
1737{
1738 Q_ASSERT(reader);
1739 using QtPrivate::qSaturateRound;
1740 const QQStyleKitControlProperties *props = reader->global();
1741 ControlMetrics metrics;
1742 metrics.bgImplicitSize = QSize(0, 0);
1743 metrics.textPadding = QMargins(0, 0, 0, 0);
1744 metrics.padding = QMargins(qSaturateRound(props->leftPadding()),
1745 qSaturateRound(props->topPadding()),
1746 qSaturateRound(props->rightPadding()),
1747 qSaturateRound(props->bottomPadding()));
1748 metrics.spacing = qSaturateRound(props->spacing());
1749 metrics.margins = QMargins(0, 0, 0, 0);
1750 metrics.indicatorImplicitSize = QSize(0, 0);
1751 metrics.indicatorMargins = QMargins(0, 0, 0, 0);
1752 const auto *background = props->background();
1753
1754 // Note: scale is deliberately absent here as it should not affect layout
1755 const auto elementSize = [](qreal w, qreal h) {
1756 constexpr qreal zero(.0);
1757 return QSizeF(std::max(zero, w), std::max(zero, h)).toSize();
1758 };
1759
1760 if (background) {
1761 metrics.bgImplicitSize = elementSize(background->width(), background->height());
1762 metrics.margins = elementMargins(background);
1763 }
1764 const auto *textProps = props->text();
1765 if (textProps) {
1766 metrics.textPadding = QMargins(qSaturateRound(textProps->leftPadding()),
1767 qSaturateRound(textProps->topPadding()),
1768 qSaturateRound(textProps->rightPadding()),
1769 qSaturateRound(textProps->bottomPadding()));
1770 }
1771 const auto *indicator = props->indicator();
1772 if (indicator) {
1773 metrics.indicatorMargins = elementMargins(indicator);
1774 metrics.indicatorImplicitSize = elementSize(indicator->width(), indicator->height());
1775 }
1776 const auto *handle = props->handle();
1777 if (handle) {
1778 metrics.handleImplicitSize = elementSize(handle->width(), handle->height());
1779 metrics.handleMargins = elementMargins(handle);
1780 }
1781 return metrics;
1782}
1783
1784/*!
1785 Constructs a QStyleKitStyle with no style loaded.
1786
1787 Use \l setStylePath() to load a QML \l Style after construction. Until a
1788 Style loads successfully, the widgets use an empty fallback style.
1789*/
1790QStyleKitStyle::QStyleKitStyle()
1791 : QCommonStyle(*new QStyleKitStylePrivate())
1792{
1793}
1794
1795/*!
1796 Constructs a QStyleKitStyle and loads the QML \l Style at \a filePath.
1797 See the \l stylePath property for the accepted path forms.
1798
1799 If the path is invalid or the root object of the loaded component is not
1800 a \l Style, QStyleKitStyle emits a warning and the widgets use an empty
1801 fallback style until a valid \l stylePath is set.
1802*/
1803QStyleKitStyle::QStyleKitStyle(const QString &filePath)
1804 : QCommonStyle(*new QStyleKitStylePrivate())
1805{
1806 Q_D(QStyleKitStyle);
1807 d->stylePath = filePath;
1808 if (!d->loadStyle()) {
1809 d->stylePath.clear();
1810 qWarning("QStyleKitStyle: Failed to load style from %s", qPrintable(filePath));
1811 }
1812}
1813
1814/*!
1815 Destroys the QStyleKitStyle.
1816*/
1817QStyleKitStyle::~QStyleKitStyle()
1818{
1819}
1820
1821/*!
1822 Returns the path most recently set for the QML \l Style file.
1823
1824 \sa setStylePath()
1825*/
1826QString QStyleKitStyle::stylePath() const
1827{
1828 Q_D(const QStyleKitStyle);
1829 return d->stylePath;
1830}
1831
1832/*!
1833 Loads the QML \l Style at \a filePath and applies it to all widgets.
1834
1835 \a filePath is a path to a local file or a path to a file in the resource
1836 file system; see the \l stylePath property for the accepted forms. If it is
1837 the same as the current \l stylePath, this function does nothing.
1838
1839 If the new style does not load, QStyleKitStyle emits a warning, keeps
1840 the previously loaded style active, and does not emit \l stylePathChanged().
1841
1842 \sa stylePath()
1843*/
1844void QStyleKitStyle::setStylePath(const QString &filePath)
1845{
1846 Q_D(QStyleKitStyle);
1847 if (d->stylePath == filePath)
1848 return;
1849
1850 const QString oldThemeName = themeName();
1851 const QStringList oldThemeNames = availableThemeNames();
1852 const QStringList oldCustomThemeNames = customThemeNames();
1853
1854 const QString previousPath = d->stylePath;
1855 d->stylePath = filePath;
1856 if (!d->loadStyle()) {
1857 d->stylePath = previousPath;
1858 return;
1859 }
1860 d->updateStyle();
1861 if (const QStringList names = availableThemeNames(); names != oldThemeNames)
1862 emit availableThemeNamesChanged(names);
1863 if (const QStringList names = customThemeNames(); names != oldCustomThemeNames)
1864 emit customThemeNamesChanged(names);
1865 emit stylePathChanged(d->stylePath);
1866}
1867
1868/*!
1869 Returns the name of the requested theme, or an empty string when no
1870 \l Style is loaded.
1871
1872 When the theme name is \c System, this function returns \c System rather
1873 than the \c Light or \c Dark theme that the platform color scheme
1874 resolves to.
1875
1876 \sa setThemeName(), availableThemeNames()
1877*/
1878QString QStyleKitStyle::themeName() const
1879{
1880 Q_D(const QStyleKitStyle);
1881 return d->style ? d->style->themeName() : QString();
1882}
1883
1884/*!
1885 Activates the theme named \a themeName.
1886
1887 \a themeName must be one of the entries in \l availableThemeNames(),
1888 which includes the special name \c System for following the platform
1889 color scheme. The comparison ignores case.
1890
1891 If no \l Style is loaded, this function emits a warning and returns
1892 without changing the active theme. If a Style is loaded but \a themeName
1893 matches none of its themes, the Style emits a warning and applies no
1894 theme.
1895
1896 \sa themeName(), availableThemeNames()
1897*/
1898void QStyleKitStyle::setThemeName(const QString &themeName)
1899{
1900 Q_D(QStyleKitStyle);
1901 if (!d->style) {
1902 qWarning("QStyleKitStyle: No style loaded, cannot set theme name.");
1903 return;
1904 }
1905 if (d->style->themeName() == themeName)
1906 return;
1907 d->style->setThemeName(themeName);
1908 d->updateStyle();
1909 emit themeNameChanged(d->style->themeName());
1910}
1911
1912/*!
1913 Returns the names of all themes that the loaded \l Style exposes. The
1914 list always includes \c System, and includes \c Light and \c Dark when
1915 the Style defines those themes, together with any custom themes that
1916 the style author defines. Returns an empty list when no style is
1917 loaded.
1918
1919 \sa customThemeNames(), themeName()
1920*/
1921QStringList QStyleKitStyle::availableThemeNames() const
1922{
1923 Q_D(const QStyleKitStyle);
1924 return d->style ? d->style->availableThemeNames() : QStringList();
1925}
1926
1927/*!
1928 Returns the names of the custom themes defined by the loaded
1929 \l Style, excluding the built-in \c Light and \c Dark themes.
1930 Returns an empty list when no style is loaded.
1931
1932 \sa availableThemeNames()
1933*/
1934QStringList QStyleKitStyle::customThemeNames() const
1935{
1936 Q_D(const QStyleKitStyle);
1937 return d->style ? d->style->customThemeNames() : QStringList();
1938}
1939
1940/*! \reimp */
1941void QStyleKitStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p,
1942 const QWidget *w) const
1943{
1944 Q_D(const QStyleKitStyle);
1945
1946 switch (pe) {
1947#if QT_CONFIG(tooltip)
1948 case PE_PanelTipLabel: {
1949 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ToolTip, opt->state);
1950 if (!r.isValid())
1951 break;
1952 r.drawBackground(opt->rect.marginsRemoved(r.metrics->margins), p);
1953 return;
1954 }
1955#endif
1956#if QT_CONFIG(menu)
1957 case PE_PanelMenu: {
1958 const auto controlType = w && w->inherits("QComboBoxPrivateContainer")
1959 ? QQStyleKitReader::ControlType::Popup
1960 : QQStyleKitReader::ControlType::Menu;
1961 const auto r = d->resolve(w, controlType, opt->state);
1962 if (!r.isValid())
1963 break;
1964 r.drawBackground(opt->rect.marginsRemoved(r.metrics->margins), p);
1965 return;
1966 }
1967 case PE_FrameMenu:
1968 // already drawn in PE_PanelMenu
1969 return;
1970#endif // QT_CONFIG(menu)
1971#if QT_CONFIG(menubar)
1972 case PE_PanelMenuBar: {
1973 const auto r = d->resolve(w, QQStyleKitReader::ControlType::MenuBar, opt->state);
1974 if (!r.isValid())
1975 break;
1976 r.drawBackground(opt->rect.marginsRemoved(r.metrics->margins), p);
1977 return;
1978 }
1979#endif // QT_CONFIG(menubar)
1980 case PE_FrameButtonBevel:
1981 if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
1982 const auto controlType = btn->features & QStyleOptionButton::Flat
1983 ? QQStyleKitReader::ControlType::FlatButton
1984 : QQStyleKitReader::ControlType::Button;
1985 const auto r = d->resolve(w, controlType, btn->state);
1986 if (!r.isValid())
1987 break;
1988 r.drawBackground(opt->rect, p);
1989 return;
1990 }
1991 break;
1992 case PE_Frame: {
1993 QQStyleKitReader::ControlType controlType;
1994 const bool isPopup = w
1995 && (false
1996#if QT_CONFIG(combobox)
1997 || w->inherits("QComboBoxPrivateContainer")
1998#endif
1999 );
2000 if (isPopup)
2001 controlType = QQStyleKitReader::ControlType::Popup;
2002#if QT_CONFIG(lineedit)
2003 else if (qobject_cast<const QLineEdit *>(w))
2004 controlType = QQStyleKitReader::ControlType::TextField;
2005#endif
2006 else
2007 controlType = QQStyleKitReader::ControlType::Frame;
2008 const auto r = d->resolve(w, controlType, opt->state);
2009 if (!r.isValid())
2010 break;
2011 r.drawBackground(opt->rect, p);
2012 return;
2013 }
2014#if QT_CONFIG(lineedit)
2015 case PE_PanelLineEdit:
2016 if (const auto *lineEdit = qstyleoption_cast<const QStyleOptionFrame *>(opt)) {
2017 // LineEdit sets Sunken flag to indicate Sunken frame,
2018 // but the style uses it to indicate pressed state, so ignore it
2019 QStyleOption lineEditOpt(*lineEdit);
2020 lineEditOpt.state &= ~QStyle::State_Sunken;
2021 const auto r = d->resolve(w, QQStyleKitReader::ControlType::TextField,
2022 lineEditOpt.state);
2023 if (!r.isValid())
2024 break;
2025
2026 // LineEdit draws its own text using the Text role so update that role in the palette
2027 if (auto *le = qobject_cast<const QLineEdit *>(w)) {
2028 if (const auto *txt = r.text(); txt && txt->isDefined(QQSK::Property::Color)) {
2029 QPalette stylePalette;
2030 stylePalette.setColor(QPalette::Text, txt->color());
2031 d->setStylePalette(const_cast<QLineEdit *>(le), stylePalette);
2032 }
2033 const_cast<QStyleKitStylePrivate *>(d)->setStyleFont(const_cast<QLineEdit *>(le),
2034 r.font());
2035 }
2036
2037 const QObject *parent = w ? w->parent() : nullptr;
2038# if QT_CONFIG(spinbox)
2039 const bool isInSpinBox = qobject_cast<const QAbstractSpinBox *>(parent);
2040# else
2041 const bool isInSpinBox = false;
2042# endif
2043# if QT_CONFIG(combobox)
2044 const bool isInComboBox = qobject_cast<const QComboBox *>(parent);
2045# else
2046 const bool isInComboBox = false;
2047# endif
2048 // For spinbox and combobox, the line edit doesn't have its own background in the Controls style
2049 if (isInSpinBox || isInComboBox)
2050 return;
2051
2052 r.drawBackground(opt->rect, p);
2053 return;
2054 }
2055 break;
2056#endif // QT_CONFIG(lineedit)
2057#if QT_CONFIG(itemviews)
2058 case PE_PanelItemViewItem: {
2059 const auto r = d->resolveSubElement(w, opt, itemViewControlType(opt), opt->state);
2060 if (!r.isValid())
2061 break;
2062 r.drawBackground(opt->rect, p);
2063 return;
2064 }
2065#endif // QT_CONFIG(itemviews)
2066 case PE_IndicatorCheckBox:
2067 case PE_IndicatorRadioButton: {
2068 const auto controlType = pe == PE_IndicatorCheckBox
2069 ? QQStyleKitReader::ControlType::CheckBox
2070 : QQStyleKitReader::ControlType::RadioButton;
2071 const auto r = d->resolve(w, controlType, opt->state);
2072 if (!r.isValid())
2073 break;
2074 r.drawIndicator(opt->rect, p);
2075 return;
2076 }
2077 case PE_IndicatorArrowDown: {
2078 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ComboBox, opt->state);
2079 if (!r.isValid())
2080 break;
2081 r.drawIndicator(opt->rect, p);
2082 return;
2083 }
2084 case PE_IndicatorItemViewItemCheck: {
2085 // track = false: Checked state is synthetic
2086 const auto r = d->resolveSubElement(w, opt, itemViewControlType(opt), opt->state, false);
2087 if (!r.isValid())
2088 break;
2089 r.drawIndicator(opt->rect, p);
2090 return;
2091 }
2092#if QT_CONFIG(spinbox)
2093 case PE_IndicatorSpinUp:
2094 case PE_IndicatorSpinDown: {
2095 const auto r = d->resolve(w, QQStyleKitReader::ControlType::SpinBox, opt->state);
2096 if (!r.isValid())
2097 break;
2098 r.drawSubIndicator(pe == PE_IndicatorSpinUp, opt->rect, p);
2099 return;
2100 }
2101#endif // QT_CONFIG(spinbox)
2102#if QT_CONFIG(groupbox)
2103 case PE_FrameGroupBox: {
2104 const auto r = d->resolve(w, QQStyleKitReader::ControlType::GroupBox, opt->state);
2105 if (!r.isValid())
2106 break;
2107 r.drawBackground(opt->rect, p);
2108 return;
2109 }
2110#endif // QT_CONFIG(groupbox)
2111#if QT_CONFIG(tabwidget)
2112 case PE_FrameTabWidget: {
2113 const auto r = d->resolve(w, QQStyleKitReader::ControlType::Page, opt->state);
2114 if (!r.isValid())
2115 break;
2116 r.drawBackground(opt->rect, p);
2117 return;
2118 }
2119#endif // QT_CONFIG(tabwidget)
2120#if QT_CONFIG(tabbar)
2121 // Not (yet) supported in StyleKit
2122 case PE_IndicatorTabClose:
2123 case PE_IndicatorTabTearRight:
2124 case PE_IndicatorTabTearLeft:
2125 return;
2126#endif // QT_CONFIG(tabbar)
2127#if QT_CONFIG(toolbutton)
2128 case PE_FrameButtonTool:
2129 case PE_PanelButtonTool: {
2130 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ToolButton, opt->state);
2131 if (!r.isValid())
2132 break;
2133 r.drawBackground(opt->rect, p);
2134 return;
2135 }
2136#endif // QT_CONFIG(toolbutton)
2137#if QT_CONFIG(toolbar)
2138 case PE_IndicatorToolBarHandle:
2139 return; // no handle in Controls style
2140 case PE_IndicatorToolBarSeparator: {
2141 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ToolSeparator, opt->state);
2142 if (!r.isValid())
2143 break;
2144 const auto &metrics = *r.metrics;
2145 // background
2146 QRect frameRect = opt->rect.marginsRemoved(metrics.margins);
2147 r.drawBackground(frameRect, p);
2148 // indicator
2149 const auto *indicator = r.indicator();
2150 QRect contentRect = frameRect.marginsRemoved(metrics.padding);
2151 const QRect indicatorRect = delegateRect(indicator, contentRect, Qt::AlignHCenter,
2152 Qt::AlignVCenter);
2153 r.drawIndicator(visualRect(opt->direction, contentRect, indicatorRect), p);
2154 return;
2155 }
2156#endif
2157 default:
2158 break;
2159 }
2160 QCommonStyle::drawPrimitive(pe, opt, p, w);
2161}
2162
2163/*! \reimp */
2164void QStyleKitStyle::drawControl(ControlElement element, const QStyleOption *opt, QPainter *p,
2165 const QWidget *w) const
2166{
2167 Q_D(const QStyleKitStyle);
2168
2169 switch (element) {
2170 case CE_PushButton:
2171 if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
2172 proxy()->drawControl(CE_PushButtonBevel, btn, p, w);
2173 QStyleOptionButton btnContent(*btn);
2174 btnContent.rect = subElementRect(SE_PushButtonContents, btn, w);
2175 proxy()->drawControl(CE_PushButtonLabel, &btnContent, p, w);
2176 }
2177 return;
2178 case CE_PushButtonBevel:
2179 if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
2180 QStyleOptionButton btnBg(*btn);
2181 btnBg.rect = subElementRect(SE_PushButtonBevel, btn, w);
2182 proxy()->drawPrimitive(PE_FrameButtonBevel, &btnBg, p, w);
2183 }
2184 return;
2185 case CE_PushButtonLabel:
2186 if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
2187 const auto controlType = btn->features & QStyleOptionButton::Flat
2188 ? QQStyleKitReader::ControlType::FlatButton
2189 : QQStyleKitReader::ControlType::Button;
2190 const auto r = d->resolve(w, controlType, btn->state);
2191 if (!r.isValid())
2192 break;
2193 const auto metrics = r.metrics;
2194
2195 QRect textRect = opt->rect;
2196 uint textFlags = Qt::TextShowMnemonic;
2197 if (!styleHint(SH_UnderlineShortcut, opt, w))
2198 textFlags |= Qt::TextHideMnemonic;
2199
2200 if (btn->features & QStyleOptionButton::HasMenu) {
2201 int indicatorSize = pixelMetric(PM_MenuButtonIndicator, btn, w);
2202 if (btn->direction == Qt::LeftToRight)
2203 textRect = textRect.adjusted(0, 0, -indicatorSize, 0);
2204 else
2205 textRect = textRect.adjusted(indicatorSize, 0, 0, 0);
2206 }
2207
2208 // icon
2209 const QIcon icon = btn->icon;
2210 if (!icon.isNull()) {
2211 const auto *textProps = r.text();
2212 uint iconTextFlags = textFlags;
2213 iconTextFlags |= textProps
2214 ? resolvedAlignment(textProps->alignment(), Qt::AlignHCenter, Qt::AlignVCenter)
2215 : uint(Qt::AlignHCenter | Qt::AlignVCenter);
2216 QIcon::Mode mode = btn->state & State_Enabled ? QIcon::Normal : QIcon::Disabled;
2217 if (mode == QIcon::Normal && btn->state & State_HasFocus)
2218 mode = QIcon::Active;
2219 QIcon::State iconState = btn->state & State_On ? QIcon::On : QIcon::Off;
2220 const auto paintDeviceDpr = p->device()->devicePixelRatio();
2221 QPixmap pixmap = icon.pixmap(btn->iconSize, paintDeviceDpr, mode, iconState);
2222 int pixmapWidth = pixmap.width() / pixmap.devicePixelRatio();
2223 int pixmapHeight = pixmap.height() / pixmap.devicePixelRatio();
2224 int labelWidth = pixmapWidth;
2225 int iconSpacing = metrics->spacing;
2226 int textWidth = QFontMetrics(r.font().resolve(p->font()))
2227 .boundingRect(opt->rect, iconTextFlags, btn->text)
2228 .width();
2229 if (!btn->text.isEmpty())
2230 labelWidth += textWidth + iconSpacing;
2231
2232 QRect iconRect;
2233 if (iconTextFlags & Qt::AlignLeft) {
2234 iconRect = QRect(textRect.x(),
2235 textRect.y() + (textRect.height() - pixmapHeight) / 2,
2236 pixmapWidth, pixmapHeight);
2237 } else if (iconTextFlags & Qt::AlignHCenter) {
2238 iconRect = QRect(textRect.x() + (textRect.width() - labelWidth) / 2,
2239 textRect.y() + (textRect.height() - pixmapHeight) / 2,
2240 pixmapWidth, pixmapHeight);
2241 } else {
2242 iconRect = QRect(textRect.x() + textRect.width() - labelWidth,
2243 textRect.y() + (textRect.height() - pixmapHeight) / 2,
2244 pixmapWidth, pixmapHeight);
2245 }
2246 iconRect = visualRect(btn->direction, textRect, iconRect);
2247
2248 // After placing the icon, fit the text rect to the text so it follows the icon
2249 if (btn->direction == Qt::RightToLeft) {
2250 textRect.setRight(iconRect.left() - iconSpacing);
2251 textRect.setLeft(textRect.right() - textWidth + 1);
2252 } else {
2253 textRect.setLeft(iconRect.left() + iconRect.width() + iconSpacing);
2254 textRect.setWidth(textWidth);
2255 }
2256
2257 p->drawPixmap(iconRect, pixmap);
2258 }
2259
2260 r.drawText(textRect, btn->text, textFlags, p);
2261 return;
2262 }
2263 break;
2264 case CE_CheckBox:
2265 case CE_RadioButton:
2266 if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
2267 const auto controlType = element == CE_CheckBox
2268 ? QQStyleKitReader::ControlType::CheckBox
2269 : QQStyleKitReader::ControlType::RadioButton;
2270 const auto r = d->resolve(w, controlType, btn->state);
2271 if (!r.isValid())
2272 break;
2273 QRect backgroundRect = btn->rect.marginsRemoved(r.metrics->margins);
2274 // background
2275 r.drawBackground(backgroundRect, p);
2276 // label
2277 QStyleOptionButton btnContent(*btn);
2278 const auto contentElement = element == CE_CheckBox
2279 ? SE_CheckBoxContents
2280 : SE_RadioButtonContents;
2281 btnContent.rect = subElementRect(contentElement, btn, w);
2282 const auto labelElement = element == CE_CheckBox
2283 ? CE_CheckBoxLabel
2284 : CE_RadioButtonLabel;
2285 proxy()->drawControl(labelElement, &btnContent, p, w);
2286 // indicator
2287 QStyleOptionButton indicator(*btn);
2288 const auto indicatorElement = element == CE_CheckBox
2289 ? SE_CheckBoxIndicator
2290 : SE_RadioButtonIndicator;
2291 indicator.rect = subElementRect(indicatorElement, btn, w);
2292 const auto primitiveElement = element == CE_CheckBox
2293 ? PE_IndicatorCheckBox
2294 : PE_IndicatorRadioButton;
2295 proxy()->drawPrimitive(primitiveElement, &indicator, p, w);
2296 return;
2297 }
2298 break;
2299 case CE_CheckBoxLabel:
2300 case CE_RadioButtonLabel:
2301 if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
2302 const auto controlType = element == CE_CheckBoxLabel
2303 ? QQStyleKitReader::ControlType::CheckBox
2304 : QQStyleKitReader::ControlType::RadioButton;
2305 const auto r = d->resolve(w, controlType, btn->state);
2306 if (!r.isValid())
2307 break;
2308 uint textFlags = Qt::TextShowMnemonic;
2309 if (!styleHint(SH_UnderlineShortcut, opt, w))
2310 textFlags |= Qt::TextHideMnemonic;
2311 if (btn->icon.isNull()) {
2312 r.drawText(opt->rect, btn->text, textFlags, p);
2313 return;
2314 }
2315 const QIcon::Mode mode = btn->state & State_Enabled ? QIcon::Normal : QIcon::Disabled;
2316 const QIcon::State iconState = btn->state & State_On ? QIcon::On : QIcon::Off;
2317 const QPixmap pm = btn->icon.pixmap(btn->iconSize, p->device()->devicePixelRatio(),
2318 mode, iconState);
2319 const QSize pmSize = pm.size() / pm.devicePixelRatio();
2320 // Align icon + text as one group, like IconLabel
2321 const auto *textProps = r.text();
2322 const auto alignment = Qt::Alignment(resolvedAlignment(
2323 textProps ? textProps->alignment() : 0u, Qt::AlignHCenter, Qt::AlignVCenter));
2324 const QSize textSize = QFontMetrics(r.font().resolve(p->font()))
2325 .size(textFlags, btn->text);
2326 const int spacing = btn->text.isEmpty() ? 0 : r.metrics->spacing;
2327 const QSize labelSize(pmSize.width() + spacing + textSize.width(),
2328 qMax(pmSize.height(), textSize.height()));
2329 const QRect labelRect = alignedRect(btn->direction, alignment, labelSize, opt->rect);
2330 p->drawPixmap(alignedRect(btn->direction, Qt::AlignLeft | Qt::AlignVCenter, pmSize,
2331 labelRect),
2332 pm);
2333 r.drawText(alignedRect(btn->direction, Qt::AlignRight | Qt::AlignVCenter, textSize,
2334 labelRect),
2335 btn->text, textFlags, p);
2336 return;
2337 }
2338 break;
2339#if QT_CONFIG(combobox)
2340 case CE_ComboBoxLabel:
2341 if (const auto *comboBox = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
2342 if (comboBox->editable && comboBox->currentIcon.isNull())
2343 return;
2344 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ComboBox, comboBox->state);
2345 if (!r.isValid())
2346 break;
2347 const QRect editRect = subControlRect(CC_ComboBox, comboBox, SC_ComboBoxEditField, w);
2348 const QRect textRect = editRect.marginsRemoved(r.metrics->textPadding);
2349 uint textFlags = Qt::TextShowMnemonic;
2350 if (!styleHint(SH_UnderlineShortcut, opt, w))
2351 textFlags |= Qt::TextHideMnemonic;
2352 if (comboBox->currentIcon.isNull()) {
2353 r.drawText(textRect, comboBox->currentText, textFlags, p);
2354 return;
2355 }
2356 const QIcon::Mode mode = comboBox->state & State_Enabled
2357 ? QIcon::Normal
2358 : QIcon::Disabled;
2359 const QPixmap pm = comboBox->currentIcon.pixmap(
2360 comboBox->iconSize, p->device()->devicePixelRatio(), mode);
2361 const QSize pmSize = pm.size() / pm.devicePixelRatio();
2362 if (comboBox->editable) {
2363 // QComboBox leaves iconSize + 4 free for the icon, in front of the line edit
2364 const QRect iconSlot = alignedRect(
2365 comboBox->direction, Qt::AlignLeft | Qt::AlignVCenter,
2366 QSize(comboBox->iconSize.width() + 4, editRect.height()), editRect);
2367 p->drawPixmap(alignedRect(comboBox->direction, Qt::AlignCenter, pmSize, iconSlot),
2368 pm);
2369 return;
2370 }
2371 // Align icon + text as one group, like IconLabel
2372 const auto *textProps = r.text();
2373 const auto alignment = Qt::Alignment(resolvedAlignment(
2374 textProps ? textProps->alignment() : 0u, Qt::AlignHCenter, Qt::AlignVCenter));
2375 const QSize textSize = QFontMetrics(r.font().resolve(p->font()))
2376 .size(textFlags, comboBox->currentText);
2377 const int spacing = comboBox->currentText.isEmpty() ? 0 : r.metrics->spacing;
2378 const QSize labelSize(pmSize.width() + spacing + textSize.width(),
2379 qMax(pmSize.height(), textSize.height()));
2380 const QRect labelRect = alignedRect(comboBox->direction, alignment, labelSize,
2381 textRect);
2382 p->drawPixmap(alignedRect(comboBox->direction, Qt::AlignLeft | Qt::AlignVCenter,
2383 pmSize, labelRect),
2384 pm);
2385 r.drawText(alignedRect(comboBox->direction, Qt::AlignRight | Qt::AlignVCenter,
2386 textSize, labelRect),
2387 comboBox->currentText, textFlags, p);
2388 return;
2389 }
2390 break;
2391#endif // QT_CONFIG(combobox)
2392#if QT_CONFIG(progressbar)
2393 case CE_ProgressBar:
2394 if (const QStyleOptionProgressBar *progressBar =
2395 qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) {
2396 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ProgressBar,
2397 progressBar->state);
2398 if (!r.isValid())
2399 break;
2400 QRect backgroundRect = progressBar->rect.marginsRemoved(r.metrics->margins);
2401 // background
2402 r.drawBackground(backgroundRect, p);
2403 // groove
2404 QStyleOptionProgressBar contents(*progressBar);
2405 contents.rect = subElementRect(SE_ProgressBarGroove, progressBar, w);
2406 const QRect grooveRect = contents.rect;
2407 proxy()->drawControl(CE_ProgressBarGroove, &contents, p, w);
2408 // track
2409 contents.rect = subElementRect(SE_ProgressBarContents, progressBar, w);
2410 // The track is the groove's foreground child, so it inherits the indicator's
2411 // transform
2412 QPainterStateGuard stateGuard(p);
2413 if (applyDelegateTransform(p, r.indicator(), grooveRect))
2414 proxy()->drawControl(CE_ProgressBarContents, &contents, p, w);
2415 // We intentionally don't draw the label as it is not drawn on the Controls Style
2416 return;
2417 }
2418 break;
2419 case CE_ProgressBarGroove: {
2420 // groove = indicator background
2421 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ProgressBar, opt->state);
2422 if (!r.isValid())
2423 break;
2424 drawStyledItemRect(r.indicator(), opt->rect, p);
2425 return;
2426 }
2427 case CE_ProgressBarContents:
2428 // contents = indicator foreground
2429 if (const auto *progressBar = qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) {
2430 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ProgressBar,
2431 progressBar->state);
2432 if (!r.isValid())
2433 break;
2434 const auto progress = progressBar->progress;
2435 const auto ratio = progressBar->maximum > progressBar->minimum
2436 ? (progress - progressBar->minimum)
2437 / static_cast<qreal>(progressBar->maximum - progressBar->minimum)
2438 : 0;
2439 const auto width = static_cast<int>(progressBar->rect.width() * ratio);
2440 const auto x = progressBar->invertedAppearance
2441 ? progressBar->rect.right() - width
2442 : progressBar->rect.left();
2443 const auto contentsRect = QRect(x, progressBar->rect.y(),
2444 width, progressBar->rect.height());
2445 const auto *foreground = r.indicator() ? r.indicator()->foreground() : nullptr;
2446 if (foreground && foreground->visible() && foreground->opacity() > 0) {
2447 QPainterStateGuard stateGuard(p);
2448 if (applyDelegateTransform(p, foreground, progressBar->rect))
2449 drawStyledItemContents(foreground, contentsRect, p);
2450 }
2451 return;
2452 }
2453 break;
2454#endif // QT_CONFIG(progressbar)
2455#if QT_CONFIG(itemviews)
2456 case CE_ItemViewItem:
2457 if (const auto *itemViewOption = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) {
2458 const auto r = d->resolveSubElement(w, opt, itemViewControlType(opt), opt->state);
2459 if (!r.isValid())
2460 break;
2461 QStyleOptionViewItem optBg(*itemViewOption);
2462 optBg.rect = optBg.rect.marginsRemoved(r.metrics->margins);
2463 QRect indicatorRect = subElementRect(SE_ItemViewItemCheckIndicator, itemViewOption, w);
2464 QRect iconRect = subElementRect(SE_ItemViewItemDecoration, itemViewOption, w);
2465 QRect textRect = subElementRect(SE_ItemViewItemText, itemViewOption, w);
2466 // Capture text properties + font now, before the indicator
2467 // drawPrimitive below potentially mutates the reader's state
2468 const auto *itemTextProps = r.text();
2469 const QFont itemFont = r.font();
2470
2471 // background
2472 proxy()->drawPrimitive(PE_PanelItemViewItem, &optBg, p, w);
2473
2474 // indicator
2475 if (itemViewOption->features & QStyleOptionViewItem::HasCheckIndicator) {
2476 QStyleOptionViewItem option(*itemViewOption);
2477 option.rect = indicatorRect;
2478 option.state = option.state & ~QStyle::State_HasFocus;
2479
2480 switch (itemViewOption->checkState) {
2481 case Qt::Unchecked:
2482 option.state |= QStyle::State_Off;
2483 break;
2484 case Qt::PartiallyChecked:
2485 option.state |= QStyle::State_NoChange;
2486 break;
2487 case Qt::Checked:
2488 option.state |= QStyle::State_On;
2489 break;
2490 }
2491 proxy()->drawPrimitive(QStyle::PE_IndicatorItemViewItemCheck, &option, p, w);
2492 }
2493
2494 // icon
2495 QIcon::Mode mode = QIcon::Normal;
2496 if (!(itemViewOption->state & QStyle::State_Enabled))
2497 mode = QIcon::Disabled;
2498 else if (itemViewOption->state & QStyle::State_Selected)
2499 mode = QIcon::Selected;
2500 QIcon::State state = itemViewOption->state & QStyle::State_Open
2501 ? QIcon::On
2502 : QIcon::Off;
2503 itemViewOption->icon.paint(p, iconRect, itemViewOption->decorationAlignment, mode,
2504 state);
2505
2506 // draw text
2507 uint textFlags = Qt::TextShowMnemonic;
2508 if (!styleHint(SH_UnderlineShortcut, opt, w))
2509 textFlags |= Qt::TextHideMnemonic;
2510 drawControlText(itemTextProps, itemFont, textRect, itemViewOption->text, textFlags,
2511 p, Qt::AlignLeft | Qt::AlignVCenter);
2512 return;
2513 }
2514 break;
2515#endif // QT_CONFIG(itemviews)
2516#ifndef QT_NO_FRAME
2517 case CE_ShapedFrame: {
2518# if QT_CONFIG(scrollarea)
2519 if (qobject_cast<const QAbstractScrollArea *>(w))
2520 // ScrollView has no styling in Controls, keep consistent
2521 return;
2522# endif
2523 auto controlType = QQStyleKitReader::ControlType::Frame;
2524# if QT_CONFIG(combobox)
2525 if (w && w->inherits("QComboBoxPrivateContainer"))
2526 controlType = QQStyleKitReader::ControlType::Popup;
2527# endif
2528# if QT_CONFIG(textedit)
2529 else if (w && qobject_cast<const QTextEdit *>(w))
2530 controlType = QQStyleKitReader::ControlType::TextArea;
2531# endif
2532# if QT_CONFIG(label)
2533 else if (w && qobject_cast<const QLabel *>(w))
2534 controlType = QQStyleKitReader::ControlType::Label;
2535# endif
2536 // Fallback to Frame
2537 if (qstyleoption_cast<const QStyleOptionFrame *>(opt)) {
2538 const auto r = d->resolve(w, controlType, opt->state);
2539 if (!r.isValid())
2540 break;
2541 QRect backgroundRect = opt->rect.marginsRemoved(r.metrics->margins);
2542 r.drawBackground(backgroundRect, p);
2543 return;
2544 }
2545 break;
2546 }
2547#endif // QT_NO_FRAME
2548#if QT_CONFIG(scrollbar)
2549 case CE_ScrollBarSlider: {
2550 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ScrollBar, opt->state);
2551 if (!r.isValid())
2552 break;
2553 r.drawIndicator(opt->rect, p);
2554 return;
2555 }
2556#endif // QT_CONFIG(scrollbar)
2557#if QT_CONFIG(toolbutton)
2558 case CE_ToolButtonLabel:
2559 if (const auto *tool = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) {
2560 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ToolButton, tool->state);
2561 if (!r.isValid())
2562 break;
2563 const auto *metrics = r.metrics;
2564 QRect rect = tool->rect.marginsRemoved(metrics->textPadding);
2565
2566 uint textFlags = Qt::TextShowMnemonic;
2567 if (!styleHint(SH_UnderlineShortcut, opt, w))
2568 textFlags |= Qt::TextHideMnemonic;
2569
2570 const bool hasArrow = tool->features & QStyleOptionToolButton::Arrow;
2571 if ((!hasArrow && tool->icon.isNull() && !tool->text.isEmpty())
2572 || tool->toolButtonStyle == Qt::ToolButtonTextOnly) {
2573 r.drawText(rect, tool->text, textFlags, p);
2574 return;
2575 }
2576
2577 QPixmap pm;
2578 QSize pmSize;
2579 if (!hasArrow && !tool->icon.isNull()) {
2580 const QIcon::State iconState = tool->state & State_On ? QIcon::On : QIcon::Off;
2581 QIcon::Mode mode;
2582 if (!(tool->state & State_Enabled))
2583 mode = QIcon::Disabled;
2584 else if ((tool->state & State_MouseOver) && (tool->state & State_AutoRaise))
2585 mode = QIcon::Active;
2586 else
2587 mode = QIcon::Normal;
2588 pm = tool->icon.pixmap(tool->iconSize, p->device()->devicePixelRatio(), mode,
2589 iconState);
2590 pmSize = pm.size() / pm.devicePixelRatio();
2591 }
2592
2593 const auto *textProps = r.text();
2594 const auto alignment = Qt::Alignment(resolvedAlignment(
2595 textProps ? textProps->alignment() : 0u, Qt::AlignHCenter, Qt::AlignVCenter));
2596
2597 if (tool->toolButtonStyle == Qt::ToolButtonIconOnly || tool->text.isEmpty()) {
2598 if (!pm.isNull())
2599 p->drawPixmap(alignedRect(tool->direction, alignment, pmSize, rect), pm);
2600 return;
2601 }
2602
2603 // Align icon + text as one group, like IconLabel
2604 const QSize textSize = QFontMetrics(r.font().resolve(p->font()))
2605 .size(textFlags, tool->text);
2606
2607 if (tool->toolButtonStyle == Qt::ToolButtonTextUnderIcon) {
2608 if (!pm.isNull()) {
2609 const QSize labelSize(qMax(pmSize.width(), textSize.width()),
2610 pmSize.height() + metrics->spacing + textSize.height());
2611 const QRect labelRect = alignedRect(tool->direction, alignment, labelSize,
2612 rect);
2613 p->drawPixmap(alignedRect(tool->direction, Qt::AlignHCenter | Qt::AlignTop,
2614 pmSize, labelRect),
2615 pm);
2616 r.drawText(alignedRect(tool->direction, Qt::AlignHCenter | Qt::AlignBottom,
2617 textSize, labelRect),
2618 tool->text, textFlags, p, Qt::AlignCenter);
2619 } else {
2620 r.drawText(rect, tool->text, textFlags, p, Qt::AlignCenter);
2621 }
2622 } else {
2623 if (!pm.isNull()) {
2624 const QSize labelSize(pmSize.width() + metrics->spacing + textSize.width(),
2625 qMax(pmSize.height(), textSize.height()));
2626 const QRect labelRect = alignedRect(tool->direction, alignment, labelSize,
2627 rect);
2628 p->drawPixmap(alignedRect(tool->direction, Qt::AlignLeft | Qt::AlignVCenter,
2629 pmSize, labelRect),
2630 pm);
2631 r.drawText(alignedRect(tool->direction, Qt::AlignRight | Qt::AlignVCenter,
2632 textSize, labelRect),
2633 tool->text, textFlags, p, Qt::AlignLeft | Qt::AlignVCenter);
2634 } else {
2635 r.drawText(rect, tool->text, textFlags, p, Qt::AlignLeft | Qt::AlignVCenter);
2636 }
2637 }
2638 return;
2639 }
2640 break;
2641#endif // QT_CONFIG(toolbutton)
2642#if QT_CONFIG(menu)
2643 case CE_MenuItem:
2644 if (const auto *menuItem = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) {
2645 using QtPrivate::qSaturateRound;
2646 // MenuItemSeperator
2647 if (menuItem->menuItemType == QStyleOptionMenuItem::Separator) {
2648 const auto r = d->resolve(w, QQStyleKitReader::ControlType::MenuSeparator,
2649 menuItem->state);
2650 if (!r.isValid())
2651 break;
2652 const auto metrics = r.metrics;
2653 QRect contentRect = opt->rect
2654 .marginsRemoved(metrics->margins)
2655 .marginsRemoved(metrics->padding);
2656 const auto *indicator = r.indicator();
2657 const QRect indicatorRect = delegateRect(
2658 indicator, contentRect, Qt::AlignHCenter, Qt::AlignVCenter);
2659 r.drawIndicator(visualRect(opt->direction, contentRect, indicatorRect), p);
2660 return;
2661 }
2662 // MenuItem
2663 QStyle::State adjustedState = menuItem->state;
2664 if (adjustedState & State_Selected)
2665 adjustedState |= State_MouseOver;
2666
2667 const auto r = d->resolveSubElement(w, opt, QQStyleKitReader::ControlType::MenuItem,
2668 adjustedState);
2669 if (!r.isValid())
2670 break;
2671
2672 // background
2673 r.drawBackground(opt->rect.marginsRemoved(r.metrics->margins), p);
2674
2675 // indicators
2676 const QRect contentRect = opt->rect.marginsRemoved(r.metrics->padding);
2677 const auto *indicator = r.indicator();
2678 const auto *first = indicator ? indicator->first() : nullptr;
2679 const auto *second = indicator ? indicator->second() : nullptr;
2680 int leftOffset = 0, rightOffset = 0;
2681 const int spacing = r.metrics->spacing;
2682 QRect firstDrawRect, secondDrawRect;
2683 auto placeSubIndicator = [&](const QQStyleKitIndicatorProperties *sub,
2684 QRect &drawRect) {
2685 if (!sub)
2686 return;
2687 const QSize sz(qSaturateRound(sub->width()), qSaturateRound(sub->height()));
2688 const QMargins subMargins = elementMargins(sub);
2689 const int slotW = qSaturateRound(sub->leftMargin() + sub->width() + sub->rightMargin());
2690 if (sub->alignment() & Qt::AlignRight) {
2691 const QRect slot(contentRect.right() - rightOffset - slotW, contentRect.top(),
2692 slotW, contentRect.height());
2693 drawRect = alignedRectInContainer(
2694 slot, sz, Qt::AlignRight | Qt::AlignVCenter, { }, subMargins);
2695 rightOffset += slotW + spacing;
2696 } else {
2697 const QRect slot(contentRect.left() + leftOffset, contentRect.top(), slotW,
2698 contentRect.height());
2699 drawRect = alignedRectInContainer(
2700 slot, sz, Qt::AlignLeft | Qt::AlignVCenter, { }, subMargins);
2701 leftOffset += slotW + spacing;
2702 }
2703 };
2704 if (menuItem->menuHasCheckableItems)
2705 placeSubIndicator(first, firstDrawRect);
2706 if (menuItem->menuItemType == QStyleOptionMenuItem::SubMenu)
2707 placeSubIndicator(second, secondDrawRect);
2708
2709 if (first && menuItem->checkType != QStyleOptionMenuItem::NotCheckable
2710 && menuItem->checked) {
2711 r.drawSubIndicator(
2712 true, visualRect(menuItem->direction, contentRect, firstDrawRect), p);
2713 }
2714
2715 if (second && menuItem->menuItemType == QStyleOptionMenuItem::SubMenu) {
2716 r.drawSubIndicator(
2717 false, visualRect(menuItem->direction, contentRect, secondDrawRect), p);
2718 }
2719
2720 // text
2721 QRect textRect = contentRect
2722 .adjusted(leftOffset, 0, -rightOffset, 0)
2723 .marginsRemoved(r.metrics->textPadding);
2724 // Reserve the icon column in all items if any item in the menu has an icon
2725 if (menuItem->maxIconWidth > 0) {
2726 const int iconExtent = proxy()->pixelMetric(PM_SmallIconSize, opt, w);
2727 if (!menuItem->icon.isNull()) {
2728 const QIcon::Mode mode = (menuItem->state & State_Enabled)
2729 ? QIcon::Normal
2730 : QIcon::Disabled;
2731 const QPixmap pm = menuItem->icon.pixmap(
2732 QSize(iconExtent, iconExtent), p->device()->devicePixelRatio(), mode);
2733 const QSize pmSize = pm.size() / pm.devicePixelRatio();
2734 const QRect iconColumn(textRect.left(), textRect.top(), iconExtent,
2735 textRect.height());
2736 p->drawPixmap(visualRect(menuItem->direction, textRect,
2737 alignedRect(Qt::LeftToRight, Qt::AlignCenter, pmSize,
2738 iconColumn)),
2739 pm);
2740 }
2741 textRect.setLeft(textRect.left() + iconExtent + r.metrics->spacing);
2742 }
2743
2744 const int tabIdx = menuItem->text.indexOf(QLatin1Char('\t'));
2745 const QString label = tabIdx >= 0 ? menuItem->text.left(tabIdx) : menuItem->text;
2746 const QString shortcut = tabIdx >= 0 ? menuItem->text.mid(tabIdx + 1) : QString();
2747 uint textFlags = Qt::TextShowMnemonic;
2748 if (!styleHint(SH_UnderlineShortcut, opt, w))
2749 textFlags |= Qt::TextHideMnemonic;
2750 QRect labelRect = textRect;
2751 if (!shortcut.isEmpty() && menuItem->reservedShortcutWidth > 0) {
2752 const QRect shortcutRect(textRect.right() - menuItem->reservedShortcutWidth + 1,
2753 textRect.top(), menuItem->reservedShortcutWidth,
2754 textRect.height());
2755 labelRect.setRight(shortcutRect.left() - r.metrics->spacing - 1);
2756 const int shortcutFlags =
2757 int(QStyle::visualAlignment(menuItem->direction,
2758 Qt::AlignRight | Qt::AlignVCenter))
2759 | int(textFlags);
2760 p->save();
2761 p->setFont(r.font().resolve(p->font()));
2762 p->setBrush(Qt::NoBrush);
2763 if (const auto *shortcutText = r.text())
2764 p->setPen(shortcutText->color());
2765 p->drawText(visualRect(menuItem->direction, textRect, shortcutRect), shortcutFlags,
2766 shortcut);
2767 p->restore();
2768 }
2769 r.drawText(visualRect(menuItem->direction, textRect, labelRect), label, textFlags, p,
2770 Qt::AlignLeft | Qt::AlignVCenter);
2771 return;
2772 }
2773 break;
2774 // no tearoff or bottom scroller support in Controls style,
2775 // so make it consistent and don't draw them
2776 case CE_MenuScroller:
2777 case CE_MenuTearoff:
2778 return;
2779 case CE_MenuEmptyArea:
2780 return;
2781#endif // QT_CONFIG(menu)
2782#if QT_CONFIG(menubar)
2783 case CE_MenuBarEmptyArea:
2784 return;
2785 case CE_MenuBarItem:
2786 if (const auto *menuItem = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) {
2787 QStyle::State adjustedState = menuItem->state;
2788 if (adjustedState & State_Selected)
2789 adjustedState |= State_MouseOver;
2790 const auto r = d->resolveSubElement(w, opt, QQStyleKitReader::ControlType::MenuBarItem,
2791 adjustedState);
2792 if (!r.isValid())
2793 break;
2794 r.drawBackground(opt->rect.marginsRemoved(r.metrics->margins), p);
2795 const QRect textRect = opt->rect.marginsRemoved(
2796 r.metrics->margins + r.metrics->padding + r.metrics->textPadding);
2797 uint textFlags = Qt::TextShowMnemonic;
2798 if (!styleHint(SH_UnderlineShortcut, opt, w))
2799 textFlags |= Qt::TextHideMnemonic;
2800 if (menuItem->icon.isNull()) {
2801 r.drawText(textRect, menuItem->text, textFlags, p);
2802 return;
2803 }
2804 // QMenuBar sizes items with an icon to show only the icon
2805 const QIcon::Mode mode = menuItem->state & State_Enabled
2806 ? QIcon::Normal
2807 : QIcon::Disabled;
2808 const int iconExtent = proxy()->pixelMetric(PM_SmallIconSize, opt, w);
2809 const QPixmap pm = menuItem->icon.pixmap(QSize(iconExtent, iconExtent),
2810 p->device()->devicePixelRatio(), mode);
2811 const auto *textProps = r.text();
2812 const auto alignment = Qt::Alignment(resolvedAlignment(
2813 textProps ? textProps->alignment() : 0u, Qt::AlignHCenter, Qt::AlignVCenter));
2814 p->drawPixmap(alignedRect(menuItem->direction, alignment,
2815 pm.size() / pm.devicePixelRatio(), textRect),
2816 pm);
2817 return;
2818 }
2819 break;
2820#endif // QT_CONFIG(menubar)
2821#if QT_CONFIG(tabbar)
2822 case CE_TabBarTab:
2823 if (const auto *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) {
2824 const auto r = d->resolveSubElement(w, opt, QQStyleKitReader::ControlType::TabButton,
2825 tab->state);
2826 if (!r.isValid())
2827 break;
2828 const auto &m = *r.metrics;
2829
2830 const QRect backgroundRect = opt->rect.marginsRemoved(m.margins);
2831 r.drawBackground(backgroundRect, p);
2832
2833 const QRect contentRect = backgroundRect.marginsRemoved(m.padding + m.textPadding);
2834 uint textFlags = Qt::TextShowMnemonic;
2835 if (!styleHint(SH_UnderlineShortcut, opt, w))
2836 textFlags |= Qt::TextHideMnemonic;
2837
2838 if (!tab->icon.isNull()) {
2839 const QIcon::Mode mode = (tab->state & State_Enabled)
2840 ? QIcon::Normal
2841 : QIcon::Disabled;
2842 const QIcon::State iconState = (tab->state & State_Selected)
2843 ? QIcon::On
2844 : QIcon::Off;
2845 const QPixmap pm = tab->icon.pixmap(tab->iconSize, p->device()->devicePixelRatio(),
2846 mode, iconState);
2847 const QSize pmSize = pm.size() / pm.devicePixelRatio();
2848 // Align icon + text as one group, like IconLabel
2849 const auto *textProps = r.text();
2850 const auto alignment = Qt::Alignment(resolvedAlignment(
2851 textProps ? textProps->alignment() : 0u, Qt::AlignHCenter, Qt::AlignVCenter));
2852 const QSize textSize = QFontMetrics(r.font().resolve(p->font()))
2853 .size(textFlags, tab->text);
2854 const int spacing = tab->text.isEmpty() ? 0 : m.spacing;
2855 const QSize labelSize(pmSize.width() + spacing + textSize.width(),
2856 qMax(pmSize.height(), textSize.height()));
2857 const QRect labelRect = alignedRect(tab->direction, alignment, labelSize,
2858 contentRect);
2859 p->drawPixmap(alignedRect(tab->direction, Qt::AlignLeft | Qt::AlignVCenter,
2860 pmSize, labelRect),
2861 pm);
2862 r.drawText(alignedRect(tab->direction, Qt::AlignRight | Qt::AlignVCenter,
2863 textSize, labelRect),
2864 tab->text, textFlags, p, Qt::AlignCenter);
2865 } else {
2866 r.drawText(contentRect, tab->text, textFlags, p, Qt::AlignCenter);
2867 }
2868 return;
2869 }
2870 break;
2871#endif // QT_CONFIG(tabbar)
2872#if QT_CONFIG(toolbar)
2873 case CE_ToolBar:
2874 if (const auto *toolBar = qstyleoption_cast<const QStyleOptionToolBar *>(opt)) {
2875 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ToolBar, toolBar->state);
2876 if (!r.isValid())
2877 break;
2878 r.drawBackground(opt->rect.marginsRemoved(r.metrics->margins), p);
2879 return;
2880 }
2881 break;
2882#endif // QT_CONFIG(toolbar)
2883 default:
2884 break;
2885 }
2886 QCommonStyle::drawControl(element, opt, p, w);
2887}
2888
2889/*! \reimp */
2890QRect QStyleKitStyle::subElementRect(SubElement r, const QStyleOption *opt,
2891 const QWidget *widget) const
2892{
2893 using QtPrivate::qSaturateRound;
2894 Q_D(const QStyleKitStyle);
2895
2896 switch (r) {
2897 case SE_PushButtonBevel:
2898 if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
2899 const auto controlType = btn->features & QStyleOptionButton::Flat
2900 ? QQStyleKitReader::ControlType::FlatButton
2901 : QQStyleKitReader::ControlType::Button;
2902 const auto resolved = d->resolveLayout(controlType, opt->state);
2903 if (!resolved.isValid())
2904 break;
2905 const auto &metrics = *resolved.metrics;
2906 QRect rect = opt->rect.marginsRemoved(metrics.margins);
2907 return visualRect(opt->direction, opt->rect, rect);
2908 }
2909 break;
2910 case SE_PushButtonContents:
2911 if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
2912 const auto controlType = btn->features & QStyleOptionButton::Flat
2913 ? QQStyleKitReader::ControlType::FlatButton
2914 : QQStyleKitReader::ControlType::Button;
2915 const auto resolved = d->resolveLayout(controlType, opt->state);
2916 if (!resolved.isValid())
2917 break;
2918 const auto &metrics = *resolved.metrics;
2919 QRect rect = opt->rect.marginsRemoved(
2920 metrics.margins + metrics.padding + metrics.textPadding);
2921 return visualRect(opt->direction, opt->rect, rect);
2922 }
2923 break;
2924 case SE_CheckBoxContents:
2925 case SE_RadioButtonContents: {
2926 const auto controlType = r == SE_CheckBoxContents
2927 ? QQStyleKitReader::ControlType::CheckBox
2928 : QQStyleKitReader::ControlType::RadioButton;
2929 const auto resolved = d->resolveLayout(controlType, opt->state);
2930 if (!resolved.isValid())
2931 break;
2932 const auto &metrics = *resolved.metrics;
2933 QRect contentsRect = opt->rect.marginsRemoved(metrics.margins + metrics.padding);
2934 const auto subElement = r == SE_CheckBoxContents
2935 ? SE_CheckBoxIndicator
2936 : SE_RadioButtonIndicator;
2937 QRect indicatorRect = visualRect(opt->direction, opt->rect,
2938 subElementRect(subElement, opt, widget));
2939 const int spacing = metrics.spacing;
2940 const auto *indicator = resolved.indicator();
2941 const uint alignment = indicator
2942 ? resolvedAlignment(indicator->alignment(), Qt::AlignLeft, Qt::AlignVCenter)
2943 : uint(Qt::AlignLeft | Qt::AlignVCenter);
2944 if (alignment & Qt::AlignLeft) {
2945 contentsRect.setLeft(indicatorRect.right() + spacing + metrics.textPadding.left());
2946 } else if (alignment & Qt::AlignHCenter) {
2947 contentsRect.setLeft(indicatorRect.right() + spacing + metrics.textPadding.left());
2948 contentsRect.setRight(indicatorRect.left() - spacing - metrics.textPadding.right());
2949 } else {
2950 contentsRect.setRight(indicatorRect.left() - spacing - metrics.textPadding.right());
2951 }
2952 return visualRect(opt->direction, opt->rect, contentsRect);
2953 }
2954 case SE_CheckBoxIndicator:
2955 case SE_RadioButtonIndicator: {
2956 const auto controlType = r == SE_CheckBoxIndicator
2957 ? QQStyleKitReader::ControlType::CheckBox
2958 : QQStyleKitReader::ControlType::RadioButton;
2959 const auto resolved = d->resolveLayout(controlType, opt->state);
2960 if (!resolved.isValid())
2961 break;
2962 const auto &metrics = *resolved.metrics;
2963 QRect rect = opt->rect.marginsRemoved(metrics.margins);
2964 const auto *indicator = resolved.indicator();
2965 if (!indicator || !indicator->visible())
2966 return rect;
2967
2968 return visualRect(opt->direction, rect,
2969 delegateRect(indicator, rect, Qt::AlignLeft, Qt::AlignVCenter,
2970 metrics.padding));
2971 }
2972#if QT_CONFIG(itemviews)
2973 case SE_ItemViewItemCheckIndicator: {
2974 const auto resolved = d->resolveLayout(itemViewControlType(opt), opt->state);
2975 if (!resolved.isValid())
2976 break;
2977 const auto &metrics = *resolved.metrics;
2978 QRect rect = opt->rect.marginsRemoved(metrics.margins);
2979 const auto *indicator = resolved.indicator();
2980 if (!indicator || !indicator->visible())
2981 return rect;
2982
2983 return visualRect(opt->direction, rect,
2984 delegateRect(indicator, rect, Qt::AlignLeft, Qt::AlignVCenter,
2985 metrics.padding));
2986 }
2987 case SE_ItemViewItemDecoration:
2988 if (const auto *itemViewOption = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) {
2989 if (!(itemViewOption->features & QStyleOptionViewItem::HasDecoration))
2990 return QRect();
2991 const auto resolved = d->resolveLayout(itemViewControlType(opt), opt->state);
2992 if (!resolved.isValid())
2993 break;
2994 const auto &metrics = *resolved.metrics;
2995 QRect contentsRect = opt->rect.marginsRemoved(metrics.margins + metrics.padding);
2996 if (itemViewOption->features & QStyleOptionViewItem::HasCheckIndicator) {
2997 const QRect indicatorRect = subElementRect(
2998 SE_ItemViewItemCheckIndicator, opt, widget);
2999 const auto *checkIndicator = resolved.indicator();
3000 const uint checkAlign = checkIndicator
3001 ? resolvedAlignment(checkIndicator->alignment(), Qt::AlignLeft,
3002 Qt::AlignVCenter)
3003 : uint(Qt::AlignLeft | Qt::AlignVCenter);
3004 if (checkAlign & Qt::AlignLeft) {
3005 contentsRect.setLeft(indicatorRect.right()
3006 + metrics.indicatorMargins.right() + metrics.spacing);
3007 } else if (checkAlign & Qt::AlignRight) {
3008 contentsRect.setRight(indicatorRect.left()
3009 - metrics.indicatorMargins.left() - metrics.spacing);
3010 }
3011 }
3012
3013 const QSize decorationSize = itemViewOption->decorationSize
3014 .boundedTo(contentsRect.size());
3015 QRect decorationRect;
3016 switch (itemViewOption->decorationPosition) {
3017 case QStyleOptionViewItem::Top:
3018 decorationRect = QRect(contentsRect.left(), contentsRect.top(),
3019 contentsRect.width(), decorationSize.height());
3020 break;
3021 case QStyleOptionViewItem::Bottom:
3022 decorationRect = QRect(contentsRect.left(),
3023 contentsRect.bottom() - decorationSize.height() + 1,
3024 contentsRect.width(), decorationSize.height());
3025 break;
3026 case QStyleOptionViewItem::Right:
3027 decorationRect = QRect(contentsRect.right() - decorationSize.width() + 1,
3028 contentsRect.top(), decorationSize.width(),
3029 contentsRect.height());
3030 break;
3031 case QStyleOptionViewItem::Left:
3032 default:
3033 decorationRect = QRect(contentsRect.left(), contentsRect.top(),
3034 decorationSize.width(), contentsRect.height());
3035 break;
3036 }
3037 const uint decorationAlign = resolvedAlignment(itemViewOption->decorationAlignment,
3038 Qt::AlignLeft, Qt::AlignVCenter);
3039 decorationRect = alignedRectInContainer(
3040 decorationRect, decorationSize, decorationAlign, QMargins(0, 0, 0, 0),
3041 QMargins(0, 0, 0, 0));
3042 return visualRect(opt->direction, opt->rect, decorationRect);
3043 }
3044 break;
3045 case SE_ItemViewItemText:
3046 if (const auto *itemViewOption = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) {
3047 const auto resolved = d->resolveLayout(itemViewControlType(opt), opt->state);
3048 if (!resolved.isValid())
3049 break;
3050 const auto &metrics = *resolved.metrics;
3051 QRect contentsRect = opt->rect.marginsRemoved(metrics.margins + metrics.padding);
3052 const int spacing = metrics.spacing;
3053
3054 if (itemViewOption->features & QStyleOptionViewItem::HasCheckIndicator) {
3055 const QRect indicatorRect = subElementRect(
3056 SE_ItemViewItemCheckIndicator, opt, widget);
3057 const auto *checkIndicator = resolved.indicator();
3058 const uint checkAlign = checkIndicator
3059 ? resolvedAlignment(checkIndicator->alignment(), Qt::AlignLeft,
3060 Qt::AlignVCenter)
3061 : uint(Qt::AlignLeft | Qt::AlignVCenter);
3062 if (checkAlign & Qt::AlignLeft) {
3063 contentsRect.setLeft(
3064 indicatorRect.right() + metrics.indicatorMargins.right() + spacing);
3065 } else if (checkAlign & Qt::AlignRight) {
3066 contentsRect.setRight(
3067 indicatorRect.left() - metrics.indicatorMargins.left() - spacing);
3068 }
3069 }
3070
3071 QRect textRect = contentsRect;
3072 if (itemViewOption->features & QStyleOptionViewItem::HasDecoration) {
3073 const QRect decorationRect = subElementRect(
3074 SE_ItemViewItemDecoration, opt, widget);
3075 switch (itemViewOption->decorationPosition) {
3076 case QStyleOptionViewItem::Top:
3077 textRect.setTop(decorationRect.bottom() + spacing);
3078 break;
3079 case QStyleOptionViewItem::Bottom:
3080 textRect.setBottom(decorationRect.top() - spacing);
3081 break;
3082 case QStyleOptionViewItem::Right:
3083 textRect.setRight(decorationRect.left() - spacing);
3084 break;
3085 case QStyleOptionViewItem::Left:
3086 default:
3087 textRect.setLeft(decorationRect.right() + spacing);
3088 break;
3089 }
3090 }
3091
3092 const auto *textProps = resolved.text();
3093 const uint textAlign = textProps
3094 ? resolvedAlignment(textProps->alignment(), Qt::AlignLeft, Qt::AlignVCenter)
3095 : uint(Qt::AlignLeft | Qt::AlignVCenter);
3096 if (textAlign & Qt::AlignLeft) {
3097 textRect.setLeft(textRect.left() + metrics.textPadding.left());
3098 } else if (textAlign & Qt::AlignHCenter) {
3099 textRect.setLeft(textRect.left() + metrics.textPadding.left() / 2);
3100 textRect.setRight(textRect.right() - metrics.textPadding.right() / 2);
3101 } else {
3102 textRect.setRight(textRect.right() - metrics.textPadding.right());
3103 }
3104 if (textAlign & Qt::AlignTop) {
3105 textRect.setTop(textRect.top() + metrics.textPadding.top());
3106 } else if (textAlign & Qt::AlignVCenter) {
3107 textRect.setTop(textRect.top() + metrics.textPadding.top() / 2);
3108 textRect.setBottom(textRect.bottom() - metrics.textPadding.bottom() / 2);
3109 } else {
3110 textRect.setBottom(textRect.bottom() - metrics.textPadding.bottom());
3111 }
3112 return visualRect(opt->direction, opt->rect, textRect);
3113 }
3114 break;
3115#endif // QT_CONFIG(itemviews)
3116 case SE_PushButtonFocusRect:
3117 case SE_CheckBoxClickRect:
3118 case SE_RadioButtonClickRect:
3119 return opt->rect;
3120#if QT_CONFIG(progressbar)
3121 case SE_ProgressBarGroove: {
3122 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ProgressBar,
3123 opt->state);
3124 if (!resolved.isValid())
3125 break;
3126 const auto &metrics = *resolved.metrics;
3127 QRect rect = opt->rect.marginsRemoved(metrics.margins);
3128 const auto *indicator = resolved.indicator();
3129 if (!indicator || !indicator->visible())
3130 return rect;
3131
3132 return visualRect(opt->direction, rect,
3133 delegateRect(indicator, rect, Qt::AlignLeft, Qt::AlignVCenter,
3134 metrics.padding));
3135 }
3136 case SE_ProgressBarContents: {
3137 if (qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) {
3138 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ProgressBar,
3139 opt->state);
3140 if (!resolved.isValid())
3141 break;
3142 const auto *foreground = resolved.indicator()
3143 ? resolved.indicator()->foreground()
3144 : nullptr;
3145 if (!foreground || !foreground->visible())
3146 return QRect();
3147
3148 QRect indicatorRect = subElementRect(SE_ProgressBarGroove, opt, widget);
3149 const QRect foregroundRect = delegateRect(
3150 foreground, indicatorRect, Qt::AlignLeft, Qt::AlignVCenter);
3151 return visualRect(opt->direction, opt->rect, foregroundRect);
3152 }
3153 break;
3154 }
3155#endif // QT_CONFIG(progressbar)
3156 case SE_LineEditContents:
3157 if (qstyleoption_cast<const QStyleOptionFrame *>(opt)) {
3158#if QT_CONFIG(spinbox)
3159 const bool isInSpinBox = widget && qobject_cast<const QSpinBox *>(widget->parentWidget());
3160#else
3161 const bool isInSpinBox = false;
3162#endif
3163 const auto controlType = isInSpinBox ? QQStyleKitReader::ControlType::SpinBox
3164 : QQStyleKitReader::ControlType::TextField;
3165 const auto resolved = d->resolveLayout(controlType, opt->state);
3166 if (!resolved.isValid())
3167 break;
3168 const auto &metrics = *resolved.metrics;
3169 QRect contentsRect = opt->rect.marginsRemoved(metrics.padding);
3170 const auto *textProps = resolved.text();
3171 const uint textAlign = resolvedAlignment(textProps ? textProps->alignment() : 0u,
3172 Qt::AlignLeft, Qt::AlignVCenter);
3173 if (textAlign & Qt::AlignLeft)
3174 contentsRect.setLeft(contentsRect.left() + metrics.textPadding.left());
3175 else if (textAlign & Qt::AlignHCenter) {
3176 contentsRect.setLeft(contentsRect.left() + metrics.textPadding.left() / 2);
3177 contentsRect.setRight(contentsRect.right() - metrics.textPadding.right() / 2);
3178 } else {
3179 contentsRect.setRight(contentsRect.right() - metrics.textPadding.right());
3180 }
3181 if (textAlign & Qt::AlignTop)
3182 contentsRect.setTop(contentsRect.top() + metrics.textPadding.top());
3183 else if (textAlign & Qt::AlignVCenter) {
3184 contentsRect.setTop(contentsRect.top() + metrics.textPadding.top() / 2);
3185 contentsRect.setBottom(contentsRect.bottom() - metrics.textPadding.bottom() / 2);
3186 } else {
3187 contentsRect.setBottom(contentsRect.bottom() - metrics.textPadding.bottom());
3188 }
3189 return visualRect(opt->direction, opt->rect, contentsRect);
3190 }
3191 break;
3192 case SE_ShapedFrameContents:
3193#if QT_CONFIG(combobox)
3194 if (widget && widget->inherits("QComboBoxPrivateContainer")) {
3195 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::Popup,
3196 opt->state);
3197 if (!resolved.isValid())
3198 break;
3199 const auto &metrics = *resolved.metrics;
3200 QRect contentsRect = opt->rect.marginsRemoved(
3201 metrics.margins + metrics.padding + metrics.textPadding);
3202 return visualRect(opt->direction, opt->rect, contentsRect);
3203 }
3204#endif
3205#if QT_CONFIG(textedit)
3206 if (qobject_cast<const QTextEdit *>(widget)) {
3207 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::TextArea,
3208 opt->state);
3209 if (!resolved.isValid())
3210 break;
3211 const auto &metrics = *resolved.metrics;
3212 QRect contentsRect = opt->rect.marginsRemoved(
3213 metrics.margins + metrics.padding + metrics.textPadding);
3214 return visualRect(opt->direction, opt->rect, contentsRect);
3215 }
3216#endif // textedit
3217 break;
3218#if QT_CONFIG(tabbar)
3219 case SE_TabBarTabText:
3220 if (const auto *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) {
3221 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::TabButton,
3222 opt->state);
3223 if (!resolved.isValid())
3224 break;
3225 const auto &metrics = *resolved.metrics;
3226 QRect rect = tab->rect.marginsRemoved(
3227 metrics.margins + metrics.padding + metrics.textPadding);
3228 if (!tab->icon.isNull())
3229 rect.setLeft(rect.left() + tab->iconSize.width() + metrics.spacing);
3230 return visualRect(tab->direction, tab->rect, rect);
3231 }
3232 break;
3233 case SE_TabBarTearIndicatorRight:
3234 case SE_TabBarTearIndicatorLeft:
3235 case SE_TabBarScrollLeftButton:
3236 case SE_TabBarScrollRightButton:
3237 // Not supported
3238 return QRect();
3239#endif // QT_CONFIG(tabbar)
3240#if QT_CONFIG(tabwidget)
3241 case SE_TabWidgetTabContents:
3242 if (qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) {
3243 // TabWidget corresponds to Page
3244 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::Page, opt->state);
3245 if (!resolved.isValid())
3246 break;
3247 const QRect paneRect = subElementRect(SE_TabWidgetTabPane, opt, widget);
3248 const QRect rect = paneRect.marginsRemoved(resolved.metrics->padding);
3249 return visualRect(opt->direction, opt->rect, rect);
3250 }
3251 break;
3252#endif // QT_CONFIG(tabwidget)
3253#if QT_CONFIG(toolbar)
3254 case SE_ToolBarHandle:
3255 // Not supported
3256 return QRect();
3257#endif // QT_CONFIG(toolbar)
3258 default:
3259 break;
3260 }
3261 return QCommonStyle::subElementRect(r, opt, widget);
3262}
3263
3264/*! \reimp */
3265void QStyleKitStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt,
3266 QPainter *p, const QWidget *w) const
3267{
3268 Q_D(const QStyleKitStyle);
3269
3270 switch (cc) {
3271#if QT_CONFIG(slider)
3272 case CC_Slider:
3273 if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
3274 const auto r = d->resolve(w, QQStyleKitReader::ControlType::Slider, slider->state);
3275 if (!r.isValid())
3276 break;
3277 const auto &metrics = *r.metrics;
3278 QRect backgroundRect = opt->rect.marginsRemoved(metrics.margins);
3279
3280 // background
3281 r.drawBackground(backgroundRect, p);
3282
3283 // groove
3284 if (slider->subControls & SC_SliderGroove) {
3285 // groove
3286 const auto grooveRect = proxy()->subControlRect(CC_Slider, opt, SC_SliderGroove, w);
3287 const auto *indicator = r.indicator();
3288 drawStyledItemRect(indicator, grooveRect, p);
3289
3290 // track
3291 const auto *foreground = indicator ? indicator->foreground() : nullptr;
3292 if (foreground && foreground->visible()) {
3293 const bool isHorizontal = slider->orientation == Qt::Horizontal;
3294 const qreal availableW = grooveRect.width()
3295 - foreground->leftMargin() - foreground->rightMargin();
3296 const qreal availableH = grooveRect.height()
3297 - foreground->topMargin() - foreground->bottomMargin();
3298
3299 const qreal range = slider->maximum - slider->minimum;
3300 const qreal ratio = range > 0
3301 ? (slider->sliderPosition - slider->minimum) / range
3302 : 0;
3303
3304 const auto hAlign = foreground->alignment() & Qt::AlignHorizontal_Mask;
3305 const auto vAlign = foreground->alignment() & Qt::AlignVertical_Mask;
3306 const qreal fgW = resolvedWidth(foreground, availableW);
3307 const qreal fgH = resolvedHeight(foreground, availableH);
3308 const qreal fgX = hAlign & Qt::AlignRight
3309 ? grooveRect.left() + foreground->leftMargin() + availableW - fgW
3310 : hAlign & Qt::AlignHCenter
3311 ? grooveRect.left() + foreground->leftMargin() + (availableW - fgW) / 2
3312 : grooveRect.left() + foreground->leftMargin();
3313 const qreal fgY = vAlign & Qt::AlignBottom
3314 ? grooveRect.top() + foreground->topMargin() + availableH - fgH
3315 : vAlign & Qt::AlignVCenter
3316 ? grooveRect.top() + foreground->topMargin() + (availableH - fgH) / 2
3317 : grooveRect.top() + foreground->topMargin();
3318
3319 const qreal minW = foreground->minimumWidth();
3320 QRectF trackRect;
3321 if (isHorizontal) {
3322 const qreal trackW = foreground->fillWidth()
3323 ? minW + ratio * (fgW - minW)
3324 : ratio * fgW;
3325 trackRect = QRectF(fgX, fgY, trackW, fgH);
3326 } else {
3327 const qreal trackH = foreground->fillHeight()
3328 ? minW + ratio * (fgH - minW)
3329 : ratio * fgH;
3330 const qreal trackY = fgY
3331 + (1.0 - ratio) * (foreground->fillHeight() ? fgH - minW : fgH);
3332 trackRect = QRectF(fgX, trackY, fgW, trackH);
3333 }
3334 // The track is the groove's foreground child, so it inherits the
3335 // indicator's transform.
3336 const QRect foregroundBox = visualRect(
3337 opt->direction, grooveRect, QRectF(fgX, fgY, fgW, fgH).toAlignedRect());
3338 QPainterStateGuard stateGuard(p);
3339 if (applyDelegateTransform(p, indicator, grooveRect)
3340 && applyDelegateTransform(p, foreground, foregroundBox)) {
3341 drawStyledItemContents(
3342 foreground,
3343 visualRect(opt->direction, grooveRect, trackRect.toAlignedRect()), p);
3344 }
3345 }
3346 }
3347
3348 // handle
3349 if (slider->subControls & SC_SliderHandle) {
3350 QStyleOptionSlider handleOpt(*slider);
3351 handleOpt.rect = subControlRect(CC_Slider, opt, SC_SliderHandle, w);
3352 r.drawHandle(handleOpt.rect, p);
3353 }
3354 return;
3355 }
3356 break;
3357#endif // QT_CONFIG(slider)
3358#if QT_CONFIG(dial)
3359 case CC_Dial:
3360 if (const QStyleOptionSlider *dial = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
3361 const auto r = d->resolve(w, QQStyleKitReader::ControlType::Dial, dial->state);
3362 if (!r.isValid())
3363 break;
3364 const QRectF backgroundRect = QRectF(opt->rect).marginsRemoved(r.metrics->margins);
3365 r.drawBackground(backgroundRect, p);
3366
3367 const auto *handle = r.handle();
3368 if (handle && handle->visible() && handle->opacity() > 0) {
3369 QPainterStateGuard stateGuard(p);
3370 p->translate(backgroundRect.center());
3371 p->rotate(dialAngle(dial));
3372 p->translate(dialHandleOffset(backgroundRect, handle));
3373 drawStyledItemRect(handle, QRectF(-handle->width() / 2, -handle->height() / 2,
3374 handle->width(), handle->height()), p);
3375 }
3376 return;
3377 }
3378 break;
3379#endif // QT_CONFIG(dial)
3380#if QT_CONFIG(combobox)
3381 case CC_ComboBox:
3382 if (const QStyleOptionComboBox *combo =
3383 qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
3384 // background
3385 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ComboBox, combo->state);
3386 if (!r.isValid())
3387 break;
3388 QRect frameRect = subControlRect(CC_ComboBox, opt, SC_ComboBoxFrame, w);
3389 r.drawBackground(frameRect, p);
3390
3391 // indicator
3392 if (combo->subControls & SC_ComboBoxArrow) {
3393 QStyleOptionComboBox indicatorOpt(*combo);
3394 indicatorOpt.rect = subControlRect(CC_ComboBox, opt, SC_ComboBoxArrow, w);
3395 proxy()->drawPrimitive(PE_IndicatorArrowDown, &indicatorOpt, p, w);
3396 }
3397
3398 // The editable combobox paints its own line edit using the QPalette::Text role
3399 // for the text color, so update that color in the palette
3400 if (auto *cb = qobject_cast<const QComboBox *>(w); cb && cb->isEditable()) {
3401 if (const auto *txt = r.text(); txt && txt->isDefined(QQSK::Property::Color)) {
3402 QPalette stylePalette;
3403 stylePalette.setColor(QPalette::Text, txt->color());
3404 d->setStylePalette(cb->lineEdit(), stylePalette);
3405 }
3406 }
3407 return;
3408 }
3409 break;
3410#endif // QT_CONFIG(combobox)
3411#if QT_CONFIG(spinbox)
3412 case CC_SpinBox:
3413 if (const QStyleOptionSpinBox *spin = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) {
3414 const auto r = d->resolve(w, QQStyleKitReader::ControlType::SpinBox, spin->state);
3415 if (!r.isValid())
3416 break;
3417 QRect frameRect = opt->rect.marginsRemoved(r.metrics->margins);
3418 // background
3419 r.drawBackground(frameRect, p);
3420 // up/down buttons
3421 if (spin->subControls & SC_SpinBoxUp) {
3422 QStyleOptionSpinBox upOpt(*spin);
3423 upOpt.rect = subControlRect(CC_SpinBox, opt, SC_SpinBoxUp, w);
3424 proxy()->drawPrimitive(PE_IndicatorSpinUp, &upOpt, p, w);
3425 }
3426 if (spin->subControls & SC_SpinBoxDown) {
3427 QStyleOptionSpinBox downOpt(*spin);
3428 downOpt.rect = subControlRect(CC_SpinBox, opt, SC_SpinBoxDown, w);
3429 proxy()->drawPrimitive(PE_IndicatorSpinDown, &downOpt, p, w);
3430 }
3431 // The spinbox line edit paints its text using the QPalette::Text role,
3432 // so update that color in the palette
3433 if (auto *sb = qobject_cast<const QSpinBox *>(w)) {
3434 if (const auto *txt = r.text(); txt && txt->isDefined(QQSK::Property::Color)) {
3435 QLineEdit *lineEdit = sb->findChild<QLineEdit *>();
3436 if (lineEdit) {
3437 QPalette p = lineEdit->palette();
3438 if (p.color(QPalette::Text) != txt->color()) {
3439 p.setColor(QPalette::Text, txt->color());
3440 lineEdit->setPalette(p);
3441 }
3442 }
3443 }
3444 }
3445 return;
3446 }
3447 break;
3448#endif // QT_CONFIG(spinbox)
3449#if QT_CONFIG(groupbox)
3450 case CC_GroupBox:
3451 if (const QStyleOptionGroupBox *groupBox =
3452 qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) {
3453 const auto r = d->resolve(w, QQStyleKitReader::ControlType::GroupBox, groupBox->state);
3454 if (!r.isValid())
3455 break;
3456
3457 // background/frame
3458 if (groupBox->subControls & SC_GroupBoxFrame) {
3459 QStyleOptionFrame frameOpt;
3460 frameOpt.QStyleOption::operator=(*groupBox);
3461 frameOpt.rect = subControlRect(CC_GroupBox, opt, SC_GroupBoxFrame, w);
3462 drawPrimitive(PE_FrameGroupBox, &frameOpt, p, w);
3463 }
3464
3465 // title
3466 if (groupBox->subControls & SC_GroupBoxLabel && !groupBox->text.isEmpty()) {
3467 const auto *textProps = r.text();
3468 const QFont textFont = r.font();
3469 QRect titleRect = subControlRect(CC_GroupBox, opt, SC_GroupBoxLabel, w);
3470 drawControlText(textProps, textFont,
3471 titleRect.marginsRemoved(r.metrics->textPadding), groupBox->text,
3472 Qt::TextShowMnemonic, p, Qt::AlignLeft | Qt::AlignVCenter);
3473 }
3474 // don't draw checkmark as the Controls style doesn't draw it
3475 // and StyleKit doesn't provide a way to style it
3476 return;
3477 }
3478 break;
3479#endif // QT_CONFIG(groupbox)
3480#if QT_CONFIG(scrollbar)
3481 case CC_ScrollBar:
3482 if (const auto *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
3483 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ScrollBar,
3484 scrollbar->state);
3485 if (!r.isValid())
3486 break;
3487
3488 // background = groove
3489 if (scrollbar->subControls & SC_ScrollBarGroove) {
3490 QRect backgroundRect = proxy()->subControlRect(
3491 CC_ScrollBar, opt, SC_ScrollBarGroove, w);
3492 r.drawBackground(backgroundRect, p);
3493 }
3494
3495 // TODO: increase button
3496 // TODO: decrease button
3497
3498 // slider = indicator
3499 if (scrollbar->subControls & SC_ScrollBarSlider) {
3500 QStyleOptionSlider newScrollbar(*scrollbar);
3501 newScrollbar.rect = proxy()->subControlRect(
3502 CC_ScrollBar, opt, SC_ScrollBarSlider, w);
3503 proxy()->drawControl(CE_ScrollBarSlider, &newScrollbar, p, w);
3504 }
3505 return;
3506 }
3507 break;
3508#endif // QT_CONFIG(scrollbar)
3509#if QT_CONFIG(toolbutton)
3510 case CC_ToolButton:
3511 if (const QStyleOptionToolButton *tool =
3512 qstyleoption_cast<const QStyleOptionToolButton *>(opt)) {
3513 const auto r = d->resolve(w, QQStyleKitReader::ControlType::ToolButton, tool->state);
3514 if (!r.isValid())
3515 break;
3516
3517 // background
3518 r.drawBackground(opt->rect, p);
3519
3520 // menu arrow indicator
3521 if ((tool->features
3522 & (QStyleOptionToolButton::MenuButtonPopup | QStyleOptionToolButton::PopupDelay))
3523 == QStyleOptionToolButton::MenuButtonPopup
3524 && (tool->subControls & SC_ToolButtonMenu)) {
3525 const QRect menuRect = subControlRect(CC_ToolButton, opt, SC_ToolButtonMenu, w);
3526 r.drawIndicator(menuRect, p);
3527 }
3528
3529 // label
3530 State bflags = tool->state & ~State_Sunken;
3531 if (bflags & State_AutoRaise) {
3532 if (!(bflags & State_MouseOver) || !(bflags & State_Enabled))
3533 bflags &= ~State_Raised;
3534 }
3535 if (tool->state & State_Sunken && tool->activeSubControls & SC_ToolButton)
3536 bflags |= State_Sunken;
3537 QStyleOptionToolButton label = *tool;
3538 label.state = bflags;
3539 label.rect = subControlRect(CC_ToolButton, opt, SC_ToolButton, w);
3540 proxy()->drawControl(CE_ToolButtonLabel, &label, p, w);
3541 return;
3542 }
3543 break;
3544#endif // QT_CONFIG(toolbutton)
3545 default:
3546 break;
3547 }
3548 QCommonStyle::drawComplexControl(cc, opt, p, w);
3549}
3550
3551/*! \reimp */
3552QStyle::SubControl QStyleKitStyle::hitTestComplexControl(ComplexControl cc,
3553 const QStyleOptionComplex *opt,
3554 const QPoint &pt, const QWidget *w) const
3555{
3556 return QCommonStyle::hitTestComplexControl(cc, opt, pt, w);
3557}
3558
3559/*! \reimp */
3560QRect QStyleKitStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *opt,
3561 SubControl sc, const QWidget *w) const
3562{
3563 using QtPrivate::qSaturateRound;
3564 Q_D(const QStyleKitStyle);
3565
3566 switch (cc) {
3567#if QT_CONFIG(slider)
3568 case CC_Slider:
3569 if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
3570 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::Slider,
3571 opt->state);
3572 if (!resolved.isValid())
3573 break;
3574 const auto &metrics = *resolved.metrics;
3575 const bool horizontal = slider->orientation == Qt::Horizontal;
3576 QRect contentsRect = opt->rect
3577 .marginsRemoved(metrics.margins)
3578 .marginsRemoved(metrics.padding);
3579
3580 const auto *indicator = resolved.indicator();
3581 if (!indicator)
3582 return contentsRect;
3583 switch (sc) {
3584 case SC_SliderGroove: {
3585 const auto availableW = contentsRect.width()
3586 - indicator->leftMargin() - indicator->rightMargin();
3587 const auto availableH = contentsRect.height()
3588 - indicator->topMargin() - indicator->bottomMargin();
3589 const qreal grooveW = resolvedWidth(indicator, availableW);
3590 const qreal grooveH = resolvedHeight(indicator, availableH);
3591
3592 QRectF grooveRect(0, 0, grooveW, grooveH);
3593 const uint rawHAlign = indicator->alignment() & Qt::AlignHorizontal_Mask;
3594 const uint rawVAlign = indicator->alignment() & Qt::AlignVertical_Mask;
3595 const uint hAlign = rawHAlign
3596 ? static_cast<Qt::Alignment>(rawHAlign)
3597 : Qt::AlignLeft;
3598 const uint vAlign = rawVAlign
3599 ? static_cast<Qt::Alignment>(rawVAlign)
3600 : Qt::AlignVCenter;
3601
3602 if (hAlign & Qt::AlignLeft) {
3603 grooveRect.moveLeft(contentsRect.x() + indicator->leftMargin());
3604 } else if (hAlign & Qt::AlignHCenter) {
3605 const qreal availableWidth = contentsRect.width()
3606 - indicator->leftMargin() - indicator->rightMargin();
3607 grooveRect.moveLeft(contentsRect.x() + indicator->leftMargin()
3608 + (availableWidth - grooveW) / 2.0);
3609 } else {
3610 grooveRect.moveLeft(contentsRect.x() + contentsRect.width()
3611 - indicator->rightMargin() - grooveW);
3612 }
3613
3614 if (vAlign & Qt::AlignTop) {
3615 grooveRect.moveTop(contentsRect.y() + indicator->topMargin());
3616 } else if (vAlign & Qt::AlignVCenter) {
3617 const qreal availableHeight = contentsRect.height()
3618 - indicator->topMargin() - indicator->bottomMargin();
3619 grooveRect.moveTop(contentsRect.y()
3620 + indicator->topMargin()
3621 + (availableHeight - grooveH) / 2.0);
3622 } else {
3623 grooveRect.moveTop(contentsRect.y()
3624 + contentsRect.height()
3625 - indicator->bottomMargin() - grooveH);
3626 }
3627 return visualRect(opt->direction, opt->rect, grooveRect.toAlignedRect());
3628 }
3629 case SC_SliderHandle: {
3630 const auto *handle = resolved.handle();
3631 if (!handle || !handle->visible())
3632 return contentsRect;
3633
3634 const qreal handleW = resolvedWidth(
3635 handle, contentsRect.width() - handle->leftMargin() - handle->rightMargin());
3636 const qreal handleH = resolvedHeight(
3637 handle, contentsRect.height() - handle->topMargin() - handle->bottomMargin());
3638 QRectF handleRect(0, 0, handleW, handleH);
3639 if (horizontal) {
3640 const int range = qSaturateRound(contentsRect.width()
3641 - handle->leftMargin() - handle->rightMargin() - handleW);
3642 const int sliderPos = QStyle::sliderPositionFromValue(
3643 slider->minimum, slider->maximum, slider->sliderPosition, range, false);
3644 handleRect.moveLeft(contentsRect.x() + handle->leftMargin() + sliderPos);
3645 handleRect.moveTop(contentsRect.y()
3646 + handle->topMargin() - handle->bottomMargin()
3647 + (contentsRect.height() - handleH) / 2.0);
3648 } else {
3649 const int range = qSaturateRound(contentsRect.height()
3650 - handle->topMargin() - handle->bottomMargin() - handleH);
3651 const int sliderPos = QStyle::sliderPositionFromValue(
3652 slider->minimum, slider->maximum, slider->sliderPosition, range, true);
3653 handleRect.moveLeft(contentsRect.x()
3654 + handle->leftMargin() - handle->rightMargin()
3655 + (contentsRect.width() - handleW) / 2.0);
3656 handleRect.moveTop(contentsRect.y() + handle->topMargin() + sliderPos);
3657 }
3658 return visualRect(opt->direction, opt->rect, handleRect.toAlignedRect());
3659 }
3660 default:
3661 break;
3662 }
3663 }
3664 break;
3665#endif // QT_CONFIG(slider)
3666#if QT_CONFIG(dial)
3667 case CC_Dial:
3668 if (const QStyleOptionSlider *dial = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
3669 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::Dial, opt->state);
3670 if (!resolved.isValid())
3671 break;
3672 const auto &metrics = *resolved.metrics;
3673 const QRectF backgroundRect(opt->rect.marginsRemoved(metrics.margins));
3674 if (sc == SC_DialGroove)
3675 return visualRect(opt->direction, opt->rect, backgroundRect.toAlignedRect());
3676 if (sc != SC_DialHandle) {
3677 return sc == SC_DialTickmarks
3678 ? QRect() : QRectF(opt->rect.marginsRemoved(metrics.padding)).toAlignedRect();
3679 }
3680
3681 const auto *handle = resolved.handle();
3682 if (!handle)
3683 return QRect();
3684 const QPointF offset = dialHandleOffset(backgroundRect, handle);
3685 const QPointF center = backgroundRect.center()
3686 + QTransform().rotate(dialAngle(dial)).map(offset);
3687 const QRectF handleRect(center.x() - handle->width() / 2,
3688 center.y() - handle->height() / 2,
3689 handle->width(), handle->height());
3690 return visualRect(opt->direction, opt->rect, handleRect.toAlignedRect());
3691 }
3692 break;
3693#endif // QT_CONFIG(dial)
3694#if QT_CONFIG(combobox)
3695 case CC_ComboBox:
3696 if (const QStyleOptionComboBox *combo =
3697 qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
3698 const auto r = d->resolveLayout(QQStyleKitReader::ControlType::ComboBox, combo->state);
3699 if (!r.isValid())
3700 break;
3701 const auto &metrics = *r.metrics;
3702 QRect frameRect = opt->rect.marginsRemoved(metrics.margins);
3703 switch (sc) {
3704 case SC_ComboBoxFrame:
3705 return visualRect(opt->direction, opt->rect, frameRect);
3706 case SC_ComboBoxEditField: {
3707 QRect contentsRect = frameRect.marginsRemoved(metrics.padding);
3708 QRect indicatorRect = subControlRect(CC_ComboBox, opt, SC_ComboBoxArrow, w);
3709 const int spacing = metrics.spacing;
3710 const auto *indicator = r.indicator();
3711 const uint indicatorAlign = indicator
3712 ? resolvedAlignment(indicator->alignment(), Qt::AlignRight, Qt::AlignVCenter)
3713 : uint(Qt::AlignRight | Qt::AlignVCenter);
3714 if (indicatorAlign & Qt::AlignLeft) {
3715 contentsRect.setLeft(indicatorRect.right() + spacing);
3716 } else if (indicatorAlign & Qt::AlignRight) {
3717 contentsRect.setRight(indicatorRect.left() - spacing);
3718 }
3719 return visualRect(opt->direction, opt->rect, contentsRect);
3720 }
3721 case SC_ComboBoxArrow: {
3722 QRect contentsRect = frameRect.marginsRemoved(metrics.padding);
3723 const auto *indicator = r.indicator();
3724 if (!indicator || !indicator->visible())
3725 return QRect();
3726
3727 return visualRect(opt->direction, opt->rect,
3728 delegateRect(indicator, contentsRect, Qt::AlignRight,
3729 Qt::AlignVCenter));
3730 }
3731 case SC_ComboBoxListBoxPopup: {
3732 QRect popupRect = opt->rect;
3733 popupRect.setTop(opt->rect.bottom());
3734 return visualRect(opt->direction, opt->rect, popupRect);
3735 }
3736 default:
3737 break;
3738 }
3739 }
3740 break;
3741#endif // QT_CONFIG(combobox)
3742#if QT_CONFIG(spinbox)
3743 case CC_SpinBox:
3744 if (const QStyleOptionSpinBox *spinBox =
3745 qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) {
3746 const auto r = d->resolveLayout(QQStyleKitReader::ControlType::SpinBox, spinBox->state);
3747 if (!r.isValid())
3748 break;
3749 const auto &metrics = *r.metrics;
3750 QRect frameRect = opt->rect.marginsRemoved(metrics.margins);
3751 QRect contentsRect = frameRect.marginsRemoved(metrics.padding);
3752 switch (sc) {
3753 case SC_SpinBoxFrame:
3754 return visualRect(opt->direction, opt->rect, frameRect);
3755 case SC_SpinBoxEditField: {
3756 QRect upIndicatorRect = subControlRect(CC_SpinBox, opt, SC_SpinBoxUp, w);
3757 QRect downIndicatorRect = subControlRect(CC_SpinBox, opt, SC_SpinBoxDown, w);
3758 const int spacing = metrics.spacing;
3759 const auto *upIndicator = r.indicator() ? r.indicator()->first() : nullptr;
3760 const auto *downIndicator = r.indicator() ? r.indicator()->second() : nullptr;
3761 const bool hasUpIndicator = upIndicator && upIndicator->visible();
3762 const bool hasDownIndicator = downIndicator && downIndicator->visible();
3763 const uint upAlign = hasUpIndicator
3764 ? resolvedAlignment(upIndicator->alignment(), Qt::AlignLeft, Qt::AlignVCenter)
3765 : uint(Qt::AlignLeft | Qt::AlignVCenter);
3766 const uint downAlign = hasDownIndicator
3767 ? resolvedAlignment(downIndicator->alignment(), Qt::AlignLeft, Qt::AlignVCenter)
3768 : uint(Qt::AlignLeft | Qt::AlignVCenter);
3769 if (hasUpIndicator && hasDownIndicator) {
3770 if ((upAlign & Qt::AlignLeft) && (downAlign & Qt::AlignRight)) {
3771 contentsRect.setLeft(upIndicatorRect.right() + spacing);
3772 contentsRect.setRight(downIndicatorRect.left() - spacing);
3773 } else if ((upAlign & Qt::AlignRight) && (downAlign & Qt::AlignLeft)) {
3774 contentsRect.setRight(upIndicatorRect.left() - spacing);
3775 contentsRect.setLeft(downIndicatorRect.right() + spacing);
3776 }
3777 } else if (hasUpIndicator) {
3778 if (upAlign & Qt::AlignLeft) {
3779 contentsRect.setLeft(upIndicatorRect.right() + spacing);
3780 } else if (upAlign & Qt::AlignRight) {
3781 contentsRect.setRight(upIndicatorRect.left() - spacing);
3782 }
3783 } else if (hasDownIndicator) {
3784 if (downAlign & Qt::AlignLeft) {
3785 contentsRect.setLeft(downIndicatorRect.right() + spacing);
3786 } else if (downAlign & Qt::AlignRight) {
3787 contentsRect.setRight(downIndicatorRect.left() - spacing);
3788 }
3789 }
3790 return visualRect(opt->direction, opt->rect, contentsRect);
3791 }
3792 case SC_SpinBoxUp: {
3793 const auto *upIndicator = r.indicator() ? r.indicator()->first() : nullptr;
3794 if (!upIndicator || !upIndicator->visible())
3795 return contentsRect;
3796
3797 return visualRect(opt->direction, opt->rect,
3798 delegateRect(upIndicator, contentsRect, Qt::AlignLeft,
3799 Qt::AlignVCenter));
3800 }
3801 case SC_SpinBoxDown: {
3802 const auto *downIndicator = r.indicator() ? r.indicator()->second() : nullptr;
3803 if (!downIndicator || !downIndicator->visible())
3804 return frameRect;
3805
3806 return visualRect(opt->direction, opt->rect,
3807 delegateRect(downIndicator, contentsRect, Qt::AlignLeft,
3808 Qt::AlignVCenter));
3809 }
3810 default:
3811 break;
3812 }
3813 }
3814 break;
3815#endif // QT_CONFIG(spinbox)
3816#if QT_CONFIG(groupbox)
3817 case CC_GroupBox:
3818 if (const QStyleOptionGroupBox *groupBox =
3819 qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) {
3820 const auto r = d->resolveLayout(QQStyleKitReader::ControlType::GroupBox,
3821 groupBox->state);
3822 if (!r.isValid())
3823 break;
3824 const auto &metrics = *r.metrics;
3825 QRect frameRect = opt->rect.marginsRemoved(metrics.margins);
3826 switch (sc) {
3827 case SC_GroupBoxFrame:
3828 return visualRect(opt->direction, opt->rect, frameRect);
3829 case SC_GroupBoxLabel: {
3830 const int fontHeight = opt->fontMetrics.height();
3831 const int labelHeight = metrics.textPadding.top()
3832 + fontHeight + metrics.textPadding.bottom();
3833 const QRect labelContainer(opt->rect.left(), opt->rect.top(), opt->rect.width(),
3834 labelHeight);
3835 return visualRect(opt->direction, opt->rect, labelContainer);
3836 }
3837 case SC_GroupBoxCheckBox: {
3838 // The controls style doesn't draw the checkbox, so we don't return a rect for it
3839 return QRect();
3840 }
3841 case SC_GroupBoxContents: {
3842 const auto labelHeight = subControlRect(CC_GroupBox, opt, SC_GroupBoxLabel, w)
3843 .height();
3844 const auto offset = metrics.padding.top()
3845 + qMax(metrics.margins.top(), labelHeight + metrics.spacing);
3846 QRect contentsRect(
3847 opt->rect.left() + metrics.padding.left(), opt->rect.top() + offset,
3848 opt->rect.width() - metrics.padding.left() - metrics.padding.right(),
3849 opt->rect.bottom() - opt->rect.top() - offset - metrics.padding.bottom());
3850 return visualRect(opt->direction, opt->rect, contentsRect);
3851 }
3852 default:
3853 break;
3854 }
3855 }
3856 break;
3857#endif // QT_CONFIG(groupbox)
3858#if QT_CONFIG(scrollbar)
3859 case CC_ScrollBar:
3860 if (const auto *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
3861 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ScrollBar,
3862 opt->state);
3863 if (!resolved.isValid())
3864 break;
3865 const auto &metrics = *resolved.metrics;
3866 const bool horizontal = scrollbar->orientation == Qt::Horizontal;
3867 const QRectF grooveRect = opt->rect.marginsRemoved(metrics.margins);
3868
3869 switch (sc) {
3870 // TODO: support up/down buttons
3871 case SC_ScrollBarAddLine:
3872 case SC_ScrollBarSubLine:
3873 case SC_ScrollBarSubPage:
3874 case SC_ScrollBarAddPage:
3875 return QRect();
3876 case SC_ScrollBarGroove:
3877 return visualRect(opt->direction, opt->rect, grooveRect.toAlignedRect());
3878 case SC_ScrollBarSlider: {
3879 const auto *indicator = resolved.indicator();
3880 if (!indicator || !indicator->visible())
3881 return QRect();
3882 const QRectF contentsRect = grooveRect.marginsRemoved(metrics.padding);
3883
3884 const qreal availableW = contentsRect.width()
3885 - indicator->leftMargin() - indicator->rightMargin();
3886 const qreal availableH = contentsRect.height()
3887 - indicator->topMargin() - indicator->bottomMargin();
3888
3889 const int totalRange = scrollbar->maximum
3890 - scrollbar->minimum + scrollbar->pageStep;
3891 const qreal ratio = totalRange > 0 ? qreal(scrollbar->pageStep) / totalRange : 1.0;
3892
3893 QRectF sliderRect;
3894 if (horizontal) {
3895 const qreal sliderW = qMax(
3896 qreal(metrics.indicatorImplicitSize.width()), availableW * ratio);
3897 const qreal sliderH = resolvedHeight(indicator, availableH);
3898 const qreal travelRange = qMax(0.0, availableW - sliderW);
3899 const int sliderPos = QStyle::sliderPositionFromValue(
3900 scrollbar->minimum, scrollbar->maximum, scrollbar->sliderPosition,
3901 int(travelRange), scrollbar->upsideDown);
3902 sliderRect = QRectF(
3903 contentsRect.x() + indicator->leftMargin() + sliderPos,
3904 contentsRect.y() + indicator->topMargin() + (availableH - sliderH) / 2.0,
3905 sliderW, sliderH);
3906 } else {
3907 const qreal sliderH = qMax(
3908 qreal(metrics.indicatorImplicitSize.width()), availableH * ratio);
3909 const qreal sliderW = resolvedWidth(indicator, availableW);
3910 const qreal travelRange = qMax(0.0, availableH - sliderH);
3911 const int sliderPos = QStyle::sliderPositionFromValue(
3912 scrollbar->minimum, scrollbar->maximum, scrollbar->sliderPosition,
3913 int(travelRange), scrollbar->upsideDown);
3914 sliderRect = QRectF(
3915 contentsRect.x() + indicator->leftMargin() + (availableW - sliderW) / 2.0,
3916 contentsRect.y() + indicator->topMargin() + sliderPos, sliderW, sliderH);
3917 }
3918 return visualRect(opt->direction, opt->rect, sliderRect.toAlignedRect());
3919 }
3920 default:
3921 break;
3922 }
3923 }
3924 break;
3925#endif // QT_CONFIG(scrollbar)
3926#if QT_CONFIG(toolbutton)
3927 case CC_ToolButton:
3928 if (const auto *tool = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) {
3929 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ToolButton,
3930 opt->state);
3931 if (!resolved.isValid())
3932 break;
3933 const auto &metrics = *resolved.metrics;
3934 QRect frameRect = opt->rect.marginsRemoved(metrics.margins);
3935 switch (sc) {
3936 case SC_ToolButton: {
3937 QRect contentsRect = frameRect.marginsRemoved(metrics.padding);
3938 const bool hasMenuButton = (tool->features
3939 & (QStyleOptionToolButton::MenuButtonPopup | QStyleOptionToolButton::HasMenu))
3940 != 0;
3941 const auto *indicator = resolved.indicator();
3942 if (hasMenuButton && indicator && indicator->visible()) {
3943 const QRect menuRect = subControlRect(CC_ToolButton, opt, SC_ToolButtonMenu, w);
3944 const int spacing = metrics.spacing;
3945 const uint indicatorAlign = resolvedAlignment(indicator->alignment(),
3946 Qt::AlignRight, Qt::AlignVCenter);
3947 if (indicatorAlign & Qt::AlignLeft)
3948 contentsRect.setLeft(menuRect.right() + spacing);
3949 else if (indicatorAlign & Qt::AlignRight)
3950 contentsRect.setRight(menuRect.left() - spacing);
3951 }
3952 return visualRect(opt->direction, opt->rect, contentsRect);
3953 }
3954 case SC_ToolButtonMenu: {
3955 QRect contentsRect = frameRect.marginsRemoved(metrics.padding);
3956 if ((tool->features
3957 & (QStyleOptionToolButton::MenuButtonPopup
3958 | QStyleOptionToolButton::PopupDelay))
3959 != QStyleOptionToolButton::MenuButtonPopup) {
3960 break;
3961 }
3962
3963 const auto *indicator = resolved.indicator();
3964 if (!indicator || !indicator->visible())
3965 return QRect();
3966
3967 return visualRect(opt->direction, opt->rect,
3968 delegateRect(indicator, contentsRect, Qt::AlignRight,
3969 Qt::AlignVCenter));
3970 }
3971 default:
3972 break;
3973 }
3974 }
3975 break;
3976#endif // QT_CONFIG(toolbutton)
3977 default:
3978 break;
3979 }
3980 return QCommonStyle::subControlRect(cc, opt, sc, w);
3981}
3982
3983/*! \reimp */
3984QSize QStyleKitStyle::sizeFromContents(ContentsType ct, const QStyleOption *opt,
3985 const QSize &contentsSize, const QWidget *widget) const
3986{
3987 Q_D(const QStyleKitStyle);
3988
3989 switch (ct) {
3990#if QT_CONFIG(pushbutton)
3991 case CT_PushButton:
3992#endif
3993#if QT_CONFIG(toolbutton)
3994 case CT_ToolButton: {
3995 auto controlType = QQStyleKitReader::ControlType::ToolButton;
3996 if (const auto *buttonOpt = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
3997 controlType = (buttonOpt->features & QStyleOptionButton::Flat)
3998 ? QQStyleKitReader::ControlType::FlatButton
3999 : QQStyleKitReader::ControlType::Button;
4000 }
4001 const auto resolved = d->resolveLayout(controlType, opt->state);
4002 if (!resolved.isValid())
4003 break;
4004
4005 const auto &metrics = *resolved.metrics;
4006 const QSize contentSize = contentsSize
4007 + QSize(metrics.padding.left() + metrics.padding.right(),
4008 metrics.padding.top() + metrics.padding.bottom())
4009 + QSize(metrics.textPadding.left() + metrics.textPadding.right(),
4010 metrics.textPadding.top() + metrics.textPadding.bottom());
4011 const QSize bgSize = metrics.bgImplicitSize
4012 + QSize(metrics.margins.left() + metrics.margins.right(),
4013 metrics.margins.top() + metrics.margins.bottom());
4014 return contentSize.expandedTo(bgSize);
4015 }
4016#endif
4017 case CT_CheckBox:
4018 case CT_RadioButton:
4019 if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
4020 const auto controlType = ct == CT_CheckBox ? QQStyleKitReader::ControlType::CheckBox
4021 : QQStyleKitReader::ControlType::RadioButton;
4022 const auto resolved = d->resolveLayout(controlType, opt->state);
4023 if (!resolved.isValid())
4024 break;
4025 const auto &metrics = *resolved.metrics;
4026 const QSize textSize = opt->fontMetrics.size(Qt::TextShowMnemonic, btn->text);
4027 const int bgWidth = metrics.bgImplicitSize.width()
4028 + metrics.margins.left() + metrics.margins.right();
4029 const int bgHeight = metrics.bgImplicitSize.height()
4030 + metrics.margins.top() + metrics.margins.bottom();
4031 const int contentWidth = std::max(textSize.width(), contentsSize.width())
4032 + metrics.padding.left() + metrics.padding.right() + metrics.textPadding.left()
4033 + metrics.textPadding.right();
4034 const int contentHeight = std::max(textSize.height(), contentsSize.height())
4035 + metrics.padding.top() + metrics.padding.bottom() + metrics.textPadding.top()
4036 + metrics.textPadding.bottom();
4037 const int indicatorWidth = metrics.indicatorImplicitSize.width()
4038 + metrics.indicatorMargins.left() + metrics.indicatorMargins.right();
4039 const int indicatorHeight = metrics.indicatorImplicitSize.height()
4040 + metrics.indicatorMargins.top() + metrics.indicatorMargins.bottom();
4041 return QSize(std::max({ contentWidth + indicatorWidth + metrics.spacing, bgWidth }),
4042 std::max({ contentHeight, indicatorHeight, bgHeight }));
4043 }
4044 break;
4045#if QT_CONFIG(itemviews)
4046 case CT_ItemViewItem:
4047 if (const auto *item = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) {
4048 const auto resolved = d->resolveLayout(itemViewControlType(opt), opt->state);
4049 if (!resolved.isValid())
4050 break;
4051 const auto &metrics = *resolved.metrics;
4052 const QSize textSize = opt->fontMetrics.size(Qt::TextShowMnemonic, item->text);
4053 const int bgWidth = metrics.bgImplicitSize.width()
4054 + metrics.margins.left() + metrics.margins.right();
4055 const int bgHeight = metrics.bgImplicitSize.height()
4056 + metrics.margins.top() + metrics.margins.bottom();
4057 const int contentWidth = std::max(textSize.width(), contentsSize.width())
4058 + metrics.padding.left() + metrics.padding.right() + metrics.textPadding.left()
4059 + metrics.textPadding.right();
4060 const int contentHeight = std::max(textSize.height(), contentsSize.height())
4061 + metrics.padding.top() + metrics.padding.bottom() + metrics.textPadding.top()
4062 + metrics.textPadding.bottom();
4063 const int indicatorWidth = metrics.indicatorImplicitSize.width()
4064 + metrics.indicatorMargins.left() + metrics.indicatorMargins.right();
4065 const int indicatorHeight = metrics.indicatorImplicitSize.height()
4066 + metrics.indicatorMargins.top() + metrics.indicatorMargins.bottom();
4067 return QSize(std::max({ contentWidth + indicatorWidth + metrics.spacing, bgWidth }),
4068 std::max({ contentHeight, indicatorHeight, bgHeight }));
4069 }
4070 break;
4071#endif // QT_CONFIG(itemviews)
4072 case CT_ProgressBar:
4073 if (qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) {
4074 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ProgressBar,
4075 opt->state);
4076 if (!resolved.isValid())
4077 break;
4078 const auto &metrics = *resolved.metrics;
4079 const auto indicatorW = metrics.indicatorImplicitSize.width()
4080 + metrics.indicatorMargins.left()
4081 + metrics.indicatorMargins.right();
4082 const auto indicatorH = metrics.indicatorImplicitSize.height()
4083 + metrics.indicatorMargins.top()
4084 + metrics.indicatorMargins.bottom();
4085 const int bgW = metrics.bgImplicitSize.width()
4086 + metrics.margins.left() + metrics.margins.right();
4087 const int bgH = metrics.bgImplicitSize.height()
4088 + metrics.margins.top() + metrics.margins.bottom();
4089 // For progress bar in Controls, the content size is based on the indicator size
4090 const int contentW = indicatorW + metrics.padding.left() + metrics.padding.right();
4091 const int contentH = indicatorH + metrics.padding.top() + metrics.padding.bottom();
4092 return QSize(std::max(contentW, bgW), std::max(contentH, bgH));
4093 }
4094 break;
4095 case CT_Slider:
4096 if (qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
4097 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::Slider,
4098 opt->state);
4099 if (!resolved.isValid())
4100 break;
4101 const auto &metrics = *resolved.metrics;
4102 // background = indicator = groove + track
4103 const int bgW = std::max(
4104 metrics.padding.left() + metrics.padding.right()
4105 + metrics.indicatorImplicitSize.width()
4106 + metrics.indicatorMargins.left() + metrics.indicatorMargins.right(),
4107 metrics.bgImplicitSize.width() + metrics.margins.left() + metrics.margins.right());
4108 const int bgH = std::max(
4109 metrics.padding.top() + metrics.padding.bottom()
4110 + metrics.indicatorImplicitSize.height()
4111 + metrics.indicatorMargins.top() + metrics.indicatorMargins.bottom(),
4112 metrics.bgImplicitSize.height() + metrics.margins.top() + metrics.margins.bottom());
4113 const int handleW = metrics.handleImplicitSize.width()
4114 + metrics.padding.left()
4115 + metrics.padding.right();
4116 const int handleH = metrics.handleImplicitSize.height()
4117 + metrics.padding.top()
4118 + metrics.padding.bottom();
4119 return QSize(std::max(handleW, bgW), std::max(handleH, bgH));
4120 }
4121 break;
4122#if QT_CONFIG(lineedit)
4123 case CT_LineEdit:
4124 if (const auto *lineEdit = qstyleoption_cast<const QStyleOptionFrame *>(opt)) {
4125 QStyleOption lineEditOpt(*lineEdit);
4126 lineEditOpt.state &= ~QStyle::State_Sunken;
4127# if QT_CONFIG(spinbox)
4128 const bool isInSpinBox = widget && qobject_cast<const QSpinBox *>(widget->parent());
4129# else
4130 const bool isInSpinBox = false;
4131# endif
4132 auto controlType = isInSpinBox ? QQStyleKitReader::ControlType::SpinBox
4133 : QQStyleKitReader::ControlType::TextField;
4134 const auto resolved = d->resolveLayout(controlType, lineEditOpt.state);
4135 if (!resolved.isValid())
4136 break;
4137 const auto &metrics = *resolved.metrics;
4138 QSize bgSize(0, 0);
4139 // For spinbox, the line edit doesn't have its own background in the Controls style
4140 if (!isInSpinBox)
4141 bgSize = metrics.bgImplicitSize.grownBy(metrics.margins);
4142 const QSize contentSizeWithPadding = contentsSize
4143 .grownBy(metrics.textPadding)
4144 .grownBy(metrics.padding);
4145 return contentSizeWithPadding.expandedTo(bgSize);
4146 }
4147 break;
4148#endif // QT_CONFIG(lineedit)
4149#if QT_CONFIG(combobox)
4150 case CT_ComboBox:
4151 if (const auto *comboBox = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
4152 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ComboBox,
4153 opt->state);
4154 if (!resolved.isValid())
4155 break;
4156 const auto &metrics = *resolved.metrics;
4157 QSize textSize = opt->fontMetrics.size(
4158 Qt::TextShowMnemonic, comboBox->currentText);
4159 if (!comboBox->currentIcon.isNull()) {
4160 textSize.rwidth() += comboBox->iconSize.width() + metrics.spacing;
4161 textSize.setHeight(qMax(textSize.height(), comboBox->iconSize.height()));
4162 }
4163 const int bgW = metrics.bgImplicitSize.width()
4164 + metrics.margins.left() + metrics.margins.right();
4165 const int bgH = metrics.bgImplicitSize.height()
4166 + metrics.margins.top() + metrics.margins.bottom();
4167 const int contentW = textSize.width()
4168 + metrics.padding.left() + metrics.padding.right()
4169 + metrics.textPadding.left() + metrics.textPadding.right();
4170 const int contentH = textSize.height()
4171 + metrics.padding.top() + metrics.padding.bottom()
4172 + metrics.textPadding.top() + metrics.textPadding.bottom();
4173 const int indicatorW = metrics.indicatorImplicitSize.width()
4174 + metrics.indicatorMargins.left() + metrics.indicatorMargins.right();
4175 const int indicatorH = metrics.indicatorImplicitSize.height()
4176 + metrics.indicatorMargins.top() + metrics.indicatorMargins.bottom();
4177 return QSize(std::max({ contentW + indicatorW + metrics.spacing, bgW }),
4178 std::max({ contentH, indicatorH, bgH }));
4179 }
4180 break;
4181#endif // QT_CONFIG(combobox)
4182#if QT_CONFIG(spinbox)
4183 case CT_SpinBox: {
4184 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::SpinBox, opt->state);
4185 if (!resolved.isValid())
4186 break;
4187 const auto &metrics = *resolved.metrics;
4188 const int bgW = metrics.bgImplicitSize.width()
4189 + metrics.margins.left() + metrics.margins.right();
4190 const int bgH = metrics.bgImplicitSize.height()
4191 + metrics.margins.top() + metrics.margins.bottom();
4192 const int contentW = contentsSize.width()
4193 + metrics.padding.left() + metrics.padding.right()
4194 + metrics.textPadding.left() + metrics.textPadding.right();
4195 const int contentH = contentsSize.height()
4196 + metrics.padding.top() + metrics.padding.bottom()
4197 + metrics.textPadding.top() + metrics.textPadding.bottom();
4198 // TODO: Support vertical layout for spinbox, currently we assume horizontal layout
4199 // TODO: Calculate each indicator (up/down) size separately if they have different sizes in
4200 // the style
4201 const int indicatorW = (metrics.indicatorImplicitSize.width()
4202 + metrics.indicatorMargins.left() + metrics.indicatorMargins.right()) * 2;
4203 const int indicatorH = metrics.indicatorImplicitSize.height()
4204 + metrics.indicatorMargins.top() + metrics.indicatorMargins.bottom();
4205 return QSize(std::max({ contentW + indicatorW + metrics.spacing, bgW }),
4206 std::max({ contentH, indicatorH, bgH }));
4207 }
4208#endif // QT_CONFIG(spinbox)
4209#if QT_CONFIG(tabbar)
4210 case CT_TabBarTab:
4211 if (qstyleoption_cast<const QStyleOptionTab *>(opt)) {
4212 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::TabButton,
4213 opt->state);
4214 if (!resolved.isValid())
4215 break;
4216 const auto &m = *resolved.metrics;
4217 const auto bgSize = m.bgImplicitSize.grownBy(m.margins);
4218 const auto contentSizeWithPadding = contentsSize.grownBy(m.padding + m.textPadding);
4219 return contentSizeWithPadding.expandedTo(bgSize);
4220 }
4221 break;
4222#endif // QT_CONFIG(tabbar)
4223#if QT_CONFIG(tabwidget)
4224 case CT_TabWidget:
4225 if (qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) {
4226 // TabWidget corresponds to Page
4227 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::Page, opt->state);
4228 if (!resolved.isValid())
4229 break;
4230 const auto &m = *resolved.metrics;
4231 const auto bgSize = m.bgImplicitSize.grownBy(m.margins);
4232 const auto contentSizeWithPadding = contentsSize.grownBy(m.padding);
4233 return contentSizeWithPadding.expandedTo(bgSize);
4234 }
4235 break;
4236#endif // QT_CONFIG(tabwidget)
4237#if QT_CONFIG(groupbox)
4238 case CT_GroupBox:
4239 if (const auto *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) {
4240 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::GroupBox,
4241 opt->state);
4242 if (!resolved.isValid())
4243 break;
4244 const auto &metrics = *resolved.metrics;
4245 const QSize bgSize = metrics.bgImplicitSize
4246 + QSize(metrics.margins.left() + metrics.margins.right(),
4247 metrics.margins.top() + metrics.margins.bottom());
4248 const QSize contentSize = contentsSize.grownBy(metrics.padding);
4249 const QSize textSize = opt->fontMetrics
4250 .size(Qt::TextShowMnemonic, groupBox->text)
4251 .grownBy(metrics.textPadding);
4252 return textSize.expandedTo(contentSize).expandedTo(bgSize);
4253 }
4254 break;
4255#endif // QT_CONFIG(groupbox)
4256#if QT_CONFIG(scrollbar)
4257 case CT_ScrollBar:
4258 if (qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
4259 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ScrollBar, opt->state);
4260 if (!resolved.isValid())
4261 break;
4262 const auto &metrics = *resolved.metrics;
4263
4264 const int iH = metrics.indicatorImplicitSize.height()
4265 + metrics.padding.top() + metrics.padding.bottom()
4266 + metrics.indicatorMargins.top() + metrics.indicatorMargins.bottom();
4267 const int iW = metrics.indicatorImplicitSize.width()
4268 + metrics.padding.left() + metrics.padding.right()
4269 + metrics.indicatorMargins.left() + metrics.indicatorMargins.right();
4270 const int bgH = metrics.bgImplicitSize.height()
4271 + metrics.margins.top() + metrics.margins.bottom();
4272 const int bgW = metrics.bgImplicitSize.width()
4273 + metrics.margins.left() + metrics.margins.right();
4274 return QSize(std::max(iW, bgW), std::max(iH, bgH));
4275 }
4276 break;
4277#endif // QT_CONFIG(scrollbar)
4278#if QT_CONFIG(menu)
4279 case CT_MenuItem:
4280 if (const auto *menuItem = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) {
4281 // The menu dictates the item width
4282 // QMenu uses for PM_MenuHMargin for both left/right paddings,
4283 // so the available width is bgImplicitWidth + margins - 2 * padding.left.
4284 const auto menuType = widget && widget->inherits("QComboBoxPrivateContainer")
4285 ? QQStyleKitReader::ControlType::Popup
4286 : QQStyleKitReader::ControlType::Menu;
4287 const auto menuResolved = d->resolveLayout(menuType, opt->state);
4288 int minMenuW = 0;
4289 if (menuResolved.isValid()) {
4290 const auto &mm = *menuResolved.metrics;
4291 minMenuW = mm.bgImplicitSize.width()
4292 + mm.margins.left() + mm.margins.right() - 2 * mm.padding.left();
4293 }
4294
4295 if (menuItem->menuItemType == QStyleOptionMenuItem::Separator) {
4296 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::MenuSeparator,
4297 opt->state);
4298 if (!resolved.isValid())
4299 break;
4300 const auto &m = *resolved.metrics;
4301 const int bgWidth = m.bgImplicitSize.width()
4302 + m.margins.left() + m.margins.right();
4303 const int bgHeight = m.bgImplicitSize.height()
4304 + m.margins.top() + m.margins.bottom();
4305 return QSize(std::max(bgWidth, minMenuW), bgHeight);
4306 }
4307
4308 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::MenuItem, opt->state);
4309 if (!resolved.isValid())
4310 break;
4311 const auto &m = *resolved.metrics;
4312 const auto *indicator = resolved.indicator();
4313 const auto *first = indicator ? indicator->first() : nullptr;
4314 const auto *second = indicator ? indicator->second() : nullptr;
4315 int indicatorW = 0;
4316 int indicatorH = 0;
4317 using QtPrivate::qSaturateRound;
4318 if (first && menuItem->menuHasCheckableItems) {
4319 indicatorW += qSaturateRound(
4320 first->leftMargin() + first->width() + first->rightMargin()) + m.spacing;
4321 indicatorH = std::max(
4322 qSaturateRound(first->topMargin() + first->bottomMargin() + first->height()),
4323 indicatorH);
4324 }
4325 if (second && menuItem->menuItemType == QStyleOptionMenuItem::SubMenu) {
4326 indicatorW += qSaturateRound(
4327 second->leftMargin() + second->width() + second->rightMargin()) + m.spacing;
4328 indicatorH = std::max(
4329 qSaturateRound(second->topMargin() + second->bottomMargin() + second->height()),
4330 indicatorH);
4331 }
4332 const int bgH = m.bgImplicitSize.height() + m.margins.top() + m.margins.bottom();
4333 const int iconReserved = menuItem->maxIconWidth > 0
4334 ? proxy()->pixelMetric(PM_SmallIconSize, opt, widget) + m.spacing
4335 : 0;
4336 const int contentW = contentsSize.width()
4337 + m.padding.left() + m.padding.right()
4338 + m.textPadding.left() + m.textPadding.right()
4339 + indicatorW + iconReserved;
4340 const int contentH = contentsSize.height()
4341 + m.padding.top() + m.padding.bottom()
4342 + m.textPadding.top() + m.textPadding.bottom();
4343 return QSize(std::max(contentW, minMenuW), std::max({ contentH, indicatorH, bgH }));
4344 }
4345 break;
4346 case CT_Menu: {
4347 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::Menu, opt->state);
4348 if (!resolved.isValid())
4349 break;
4350 const auto &m = *resolved.metrics;
4351 const int height = std::max(contentsSize.height(), m.bgImplicitSize.height())
4352 + m.margins.top() + m.margins.bottom();
4353 return QSize(contentsSize.width(), height);
4354 }
4355#endif // QT_CONFIG(menu)
4356#if QT_CONFIG(menubar)
4357 case CT_MenuBar: {
4358 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::MenuBar, opt->state);
4359 if (!resolved.isValid())
4360 break;
4361 const auto &m = *resolved.metrics;
4362 const int bgWidth = m.bgImplicitSize.width() + m.margins.left() + m.margins.right();
4363 const int bgHeight = m.bgImplicitSize.height() + m.margins.top() + m.margins.bottom();
4364 const int contentW = contentsSize.width() + m.padding.left() + m.padding.right();
4365 const int contentH = contentsSize.height() + m.padding.top() + m.padding.bottom();
4366 return QSize(std::max(contentW, bgWidth), std::max(contentH, bgHeight));
4367 }
4368 case CT_MenuBarItem: {
4369 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::MenuBarItem,
4370 opt->state);
4371 if (!resolved.isValid())
4372 break;
4373 const auto &m = *resolved.metrics;
4374 const int bgWidth = m.bgImplicitSize.width() + m.margins.left() + m.margins.right();
4375 const int bgHeight = m.bgImplicitSize.height() + m.margins.top() + m.margins.bottom();
4376 const int contentW = contentsSize.width()
4377 + m.padding.left() + m.padding.right()
4378 + m.textPadding.left() + m.textPadding.right();
4379 const int contentH = contentsSize.height()
4380 + m.padding.top() + m.padding.bottom()
4381 + m.textPadding.top() + m.textPadding.bottom();
4382 return QSize(std::max(contentW, bgWidth), std::max(contentH, bgHeight));
4383 }
4384#endif // QT_CONFIG(menubar)
4385 default:
4386 break;
4387 }
4388 return QCommonStyle::sizeFromContents(ct, opt, contentsSize, widget);
4389}
4390
4391/*! \reimp */
4392int QStyleKitStyle::pixelMetric(PixelMetric m, const QStyleOption *opt, const QWidget *widget) const
4393{
4394 Q_D(const QStyleKitStyle);
4395 switch (m) {
4396#if QT_CONFIG(menu)
4397 case PM_MenuPanelWidth:
4398 // The panel border is drawn as part of the resolved background (see PE_PanelMenu);
4399 return 0;
4400 case PM_MenuTearoffHeight:
4401 case PM_MenuScrollerHeight:
4402 return 0;
4403 case PM_MenuHMargin:
4404 case PM_MenuVMargin: {
4405 const auto controlType = widget && widget->inherits("QComboBoxPrivateContainer")
4406 ? QQStyleKitReader::ControlType::Popup
4407 : QQStyleKitReader::ControlType::Menu;
4408 const auto resolved = d->resolveLayout(controlType, opt ? opt->state : QStyle::State_None);
4409 if (!resolved.isValid())
4410 break;
4411 return m == PM_MenuHMargin ? resolved.metrics->padding.left()
4412 : resolved.metrics->padding.top();
4413 }
4414#endif
4415#if QT_CONFIG(menubar)
4416 case PM_MenuBarItemSpacing: {
4417 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::MenuBarItem,
4418 opt ? opt->state : QStyle::State_None);
4419 if (resolved.isValid())
4420 return resolved.metrics->spacing;
4421 break;
4422 }
4423#endif
4424#if QT_CONFIG(tooltip)
4425 case PM_ToolTipLabelFrameWidth: {
4426 // QTipLabel adds 1 to this and applies it uniformly on all sides
4427 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ToolTip,
4428 opt ? opt->state : QStyle::State_None);
4429 if (!resolved.isValid())
4430 break;
4431 const auto *m = resolved.metrics;
4432 return qMax(0, m->margins.left() + m->padding.left() + m->textPadding.left() - 1);
4433 }
4434#endif
4435#if QT_CONFIG(toolbar)
4436 case PM_ToolBarFrameWidth:
4437 // The frame is folded into the resolved background/margins (see CE_ToolBar);
4438 return 0;
4439 case PM_ToolBarItemMargin: {
4440 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ToolBar,
4441 opt ? opt->state : QStyle::State_None);
4442 if (!resolved.isValid())
4443 break;
4444 return resolved.metrics->padding.left();
4445 }
4446 case PM_ToolBarItemSpacing: {
4447 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ToolBar,
4448 opt ? opt->state : QStyle::State_None);
4449 if (resolved.isValid())
4450 return resolved.metrics->spacing;
4451 break;
4452 }
4453 case PM_ToolBarHandleExtent: {
4454 // The only lever available to honor the style's background width/height
4455 // QToolBarLayout uses this to calculate the minimum size of the toolbar
4456 const auto resolved = d->resolveLayout(QQStyleKitReader::ControlType::ToolBar,
4457 opt ? opt->state : QStyle::State_None);
4458 if (!resolved.isValid())
4459 break;
4460 const auto &metrics = *resolved.metrics;
4461 const bool horizontal = !opt || (opt->state & State_Horizontal);
4462 const int crossImplicit = horizontal
4463 ? metrics.bgImplicitSize.height()
4464 : metrics.bgImplicitSize.width();
4465 const int crossMargins = horizontal
4466 ? metrics.margins.top() + metrics.margins.bottom()
4467 : metrics.margins.left() + metrics.margins.right();
4468 const int crossPadding = horizontal
4469 ? metrics.padding.top() + metrics.padding.bottom()
4470 : metrics.padding.left() + metrics.padding.right();
4471 return qMax(0, crossImplicit + crossMargins - crossPadding);
4472 }
4473#endif // QT_CONFIG(toolbar)
4474#if QT_CONFIG(tabbar)
4475 case PM_TabBarTabHSpace:
4476 case PM_TabBarTabVSpace:
4477 case PM_TabBarTabOverlap:
4478 // Not supported in StyleKit
4479 case PM_TabCloseIndicatorWidth:
4480 case PM_TabCloseIndicatorHeight:
4481 return 0;
4482#endif // QT_CONFIG(tabbar)
4483 default:
4484 break;
4485 }
4486 return QCommonStyle::pixelMetric(m, opt, widget);
4487}
4488
4489/*! \reimp */
4490int QStyleKitStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget *w,
4491 QStyleHintReturn *shret) const
4492{
4493 switch (sh) {
4494 // keep consistent with the Controls style behavior
4495 case SH_SpinBox_SelectOnStep:
4496 case SH_ToolBar_Movable:
4497 case SH_TabBar_PreferNoArrows:
4498 return 0;
4499 case SH_Menu_MouseTracking:
4500 case SH_MenuBar_MouseTracking:
4501 case SH_Menu_Scrollable:
4502 return 1;
4503 default:
4504 break;
4505 }
4506 return QCommonStyle::styleHint(sh, opt, w, shret);
4507}
4508
4509/*! \reimp */
4510QPalette QStyleKitStyle::standardPalette() const
4511{
4512 return QCommonStyle::standardPalette();
4513}
4514
4515/*! \reimp */
4516void QStyleKitStyle::polish(QWidget *widget)
4517{
4518 if (!widget)
4519 return;
4520
4521 Q_D(QStyleKitStyle);
4522
4523 // When no user style is loaded, create the empty fallback style now,
4524 // before any reader is created, so that all widgets are styled consistently
4525 if (!d->style)
4526 d->ensureDefaultStyle();
4527
4528 widget->setAttribute(Qt::WA_Hover);
4529
4530#if QT_CONFIG(scrollbar)
4531 // QScrollBar sets WA_OpaquePaintEvent in its constructor, which skips
4532 // background erase before paint. This can leave artifacts when the
4533 // style's background is invisible, so disable it
4534 if (qobject_cast<QScrollBar *>(widget))
4535 widget->setAttribute(Qt::WA_OpaquePaintEvent, false);
4536#endif
4537
4538 // Create per-widget reader for interactive controls (transitions)
4539 const bool isInteractiveControl = false
4540#if QT_CONFIG(pushbutton)
4541 || qobject_cast<const QPushButton *>(widget)
4542#endif
4543#if QT_CONFIG(checkbox)
4544 || qobject_cast<const QCheckBox *>(widget)
4545#endif
4546#if QT_CONFIG(radiobutton)
4547 || qobject_cast<const QRadioButton *>(widget)
4548#endif
4549#if QT_CONFIG(combobox)
4550 || qobject_cast<const QComboBox *>(widget)
4551 || (widget && widget->inherits("QComboBoxPrivateContainer"))
4552#endif
4553#if QT_CONFIG(lineedit)
4554 || qobject_cast<const QLineEdit *>(widget)
4555#endif
4556#if QT_CONFIG(textedit)
4557 || qobject_cast<const QTextEdit *>(widget)
4558 || qobject_cast<const QPlainTextEdit *>(widget)
4559#endif
4560#if QT_CONFIG(label)
4561 || qobject_cast<const QLabel *>(widget)
4562#endif
4563#if QT_CONFIG(progressbar)
4564 || qobject_cast<const QProgressBar *>(widget)
4565#endif
4566#if QT_CONFIG(slider)
4567 || qobject_cast<const QSlider *>(widget)
4568#endif
4569#if QT_CONFIG(dial)
4570 || qobject_cast<const QDial *>(widget)
4571#endif
4572#if QT_CONFIG(scrollbar)
4573 || qobject_cast<const QScrollBar *>(widget)
4574#endif
4575#if QT_CONFIG(spinbox)
4576 || qobject_cast<const QSpinBox *>(widget)
4577#endif
4578#if QT_CONFIG(tabbar)
4579 || qobject_cast<const QTabBar *>(widget)
4580#endif
4581#if QT_CONFIG(tabwidget)
4582 || qobject_cast<const QTabWidget *>(widget)
4583#endif
4584#if QT_CONFIG(menu)
4585 || qobject_cast<const QMenu *>(widget)
4586 || qobject_cast<const QMenuBar *>(widget)
4587#endif
4588 ;
4589 if (isInteractiveControl)
4590 d->readerForWidget(widget);
4591
4592 d->setStyleAutoFill(widget);
4593 d->setStyleTranslucency(widget);
4594
4595#if QT_CONFIG(lineedit)
4596 if (auto *lineEdit = qobject_cast<QLineEdit *>(widget)) {
4597 if (!lineEdit->property("_q_stylekit_alignment_set").toBool()) {
4598 QQStyleKitReader *r = d->readerForWidget(widget);
4599 if (r) {
4600 const QWidget *target = containerWidget(widget);
4601 QQStyleKitReader::ControlType ct = controlTypeForWidget(target);
4602 r->setControlType(ct);
4603 const auto *textProps = r->global()->text();
4604 if (textProps) {
4605 const uint align = resolvedAlignment(
4606 textProps->alignment(), Qt::AlignLeft, Qt::AlignVCenter);
4607 lineEdit->setAlignment(Qt::Alignment(align));
4608 }
4609 }
4610 }
4611 }
4612#endif
4613
4614 d->refreshStyleFont(widget);
4615 d->refreshStylePalette(widget);
4616 d->refreshStyleMinimumSize(widget);
4617
4618 bool needsEventFilter = isSelfPaintingWidget(widget);
4619#if QT_CONFIG(menu)
4620 // Menus are transient: drop their sub-element tracking when they hide
4621 needsEventFilter = needsEventFilter || qobject_cast<QMenu *>(widget);
4622#endif
4623 if (needsEventFilter)
4624 widget->installEventFilter(this);
4625
4626 QCommonStyle::polish(widget);
4627}
4628
4629/*! \reimp */
4630void QStyleKitStyle::polish(QApplication *app)
4631{
4632 QCommonStyle::polish(app);
4633}
4634
4635/*! \reimp */
4636void QStyleKitStyle::polish(QPalette &palette)
4637{
4638 QCommonStyle::polish(palette);
4639}
4640
4641/*! \reimp */
4642void QStyleKitStyle::unpolish(QWidget *widget)
4643{
4644 Q_D(QStyleKitStyle);
4645 d->unsetStylePalette(widget);
4646 d->unsetStyleFont(widget);
4647 d->unsetStyleMinimumSize(widget);
4648 bool hasEventFilter = isSelfPaintingWidget(widget);
4649#if QT_CONFIG(menu)
4650 hasEventFilter = hasEventFilter || qobject_cast<QMenu *>(widget);
4651#endif
4652 if (hasEventFilter)
4653 widget->removeEventFilter(this);
4654 d->unsetStyleAutoFill(widget);
4655 d->unsetStyleTranslucency(widget);
4656 d->cleanupWidgetReader(widget);
4657#if QT_CONFIG(scrollbar)
4658 if (qobject_cast<QScrollBar *>(widget))
4659 widget->setAttribute(Qt::WA_OpaquePaintEvent);
4660#endif
4661 QCommonStyle::unpolish(widget);
4662}
4663
4664/*! \reimp */
4665void QStyleKitStyle::unpolish(QApplication *app)
4666{
4667 Q_D(QStyleKitStyle);
4668 d->clearMetricsCache();
4669 QCommonStyle::unpolish(app);
4670}
4671
4672/*! \reimp */
4673bool QStyleKitStyle::eventFilter(QObject *obj, QEvent *event)
4674{
4675 Q_D(QStyleKitStyle);
4676 switch (event->type()) {
4677 case QEvent::EnabledChange:
4678 case QEvent::HoverEnter:
4679 case QEvent::HoverLeave:
4680 case QEvent::FocusIn:
4681 case QEvent::FocusOut:
4682 if (auto *w = qobject_cast<QWidget *>(obj)) {
4683 if (d->customPaletteWidgets.contains(w))
4684 d->refreshStylePalette(w);
4685 if (d->customFontWidgets.contains(w))
4686 d->refreshStyleFont(w);
4687 }
4688 break;
4689#if QT_CONFIG(menu)
4690 case QEvent::Hide:
4691 if (auto *menu = qobject_cast<QMenu *>(obj))
4692 d->cleanupSubElements(menu);
4693 break;
4694#endif
4695 default:
4696 break;
4697 }
4698 return QCommonStyle::eventFilter(obj, event);
4699}
4700
4701/*! \reimp */
4702bool QStyleKitStyle::event(QEvent *event)
4703{
4704 return QCommonStyle::event(event);
4705}
4706
4707QT_END_NAMESPACE
4708
4709#include "moc_qstylekitstyle.cpp"
Combined button and popup list for selecting options.
static QWidget * paintTarget(const QWidget *widget)
static bool applyDelegateTransform(QPainter *painter, const QQStyleKitDelegateProperties *element, const QRectF &box)
static void drawStyledItemImage(const QQStyleKitImageProperties *image, const QRectF &rect, qreal opacity, QPainter *painter)
static void drawStyledItemContents(const QQStyleKitDelegateProperties *props, const QRectF &rect, QPainter *painter)
static QWidget * managedViewport(QWidget *widget)
static QMargins elementMargins(const QQStyleKitDelegateProperties *element)
static uint resolvedAlignment(uint raw, Qt::Alignment hDefault, Qt::Alignment vDefault)
static qreal resolvedWidth(const QQStyleKitDelegateProperties *element, qreal availableW)
static bool isSelfPaintingWidget(const QWidget *widget)
static QRect delegateRect(const QQStyleKitDelegateProperties *delegate, const QRect &container, Qt::Alignment hDefault, Qt::Alignment vDefault, const QMargins &padding=QMargins())
static qreal resolvedHeight(const QQStyleKitDelegateProperties *element, qreal availableH)
static QUrl urlFromStylePath(const QString &filePath)
static const QWidget * containerWidget(const QWidget *w)
static QRect alignedRectInContainer(const QRect &container, const QSize &size, uint alignment, const QMargins &padding, const QMargins &margins)
static void drawControlIndicator(const QQStyleKitDelegateProperties *indicator, const QRectF &rect, QPainter *painter)
static void drawControlText(const QQStyleKitTextProperties *textProps, const QFont &font, const QRect &rect, const QString &text, uint textFlags, QPainter *p, Qt::Alignment defaultAlignment=Qt::AlignHCenter|Qt::AlignVCenter)
static void drawStyledItemRect(const QQStyleKitDelegateProperties *props, const QRectF &rect, QPainter *painter)