Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2026 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : #include "widgetstyle.h"
24 :
25 : #include <QAbstractItemView>
26 : #include <QApplication>
27 : #include <QGroupBox>
28 : #include <QLineEdit>
29 : #include <QMenu>
30 : #include <QPainter>
31 : #include <QPalette>
32 : #include <QStyle>
33 : #include <QStyleFactory>
34 : #include <QStyleOption>
35 : #include <QTextEdit>
36 : #include <QToolBar>
37 : #include <QVariant>
38 :
39 : #include "api/themeapi.h"
40 : #include "iconcodes.h"
41 :
42 : #include "log.h"
43 :
44 : using namespace muse::ui;
45 : using muse::api::ThemeApi;
46 :
47 : static const QPen NO_BORDER(Qt::transparent, 0);
48 : static const QBrush NO_FILL(Qt::transparent);
49 : static const int DEFAULT_RADIUS = 3;
50 :
51 : static const int GROUP_BOX_LABEL_SPACING = 2;
52 :
53 : //! In QML, a border is drawn _inside_ a rectangle.
54 : //! In C++, a border would normally be drawn half inside the rectangle, half outside.
55 : //! In this function, we exactly replicate the behaviour from QML.
56 0 : static void drawRoundedRect(QPainter* painter, const QRectF& rect, const qreal radius, const QBrush& brush = NO_FILL,
57 : const QPen& pen = NO_BORDER)
58 : {
59 0 : IF_ASSERT_FAILED(painter) {
60 0 : return;
61 : }
62 :
63 0 : const qreal bw = pen.width();
64 :
65 0 : if (bw <= 0 || pen.color().alpha() == 0) {
66 0 : if (brush == NO_FILL) {
67 0 : return;
68 : }
69 :
70 0 : painter->save();
71 0 : painter->setPen(NO_BORDER);
72 0 : painter->setBrush(brush);
73 0 : painter->drawRoundedRect(rect, radius, radius);
74 0 : painter->restore();
75 0 : return;
76 : }
77 :
78 0 : painter->save();
79 0 : painter->setPen(NO_BORDER);
80 0 : painter->setBrush(brush);
81 0 : painter->drawRoundedRect(rect.adjusted(bw, bw, -bw, -bw), radius - bw, radius - bw);
82 :
83 0 : const qreal corr = 0.5 * bw;
84 0 : painter->setPen(pen);
85 0 : painter->setBrush(NO_FILL);
86 0 : painter->drawRoundedRect(rect.adjusted(corr, corr, -corr, -corr), radius - corr, radius - corr);
87 0 : painter->restore();
88 : }
89 :
90 0 : WidgetStyle::WidgetStyle(std::shared_ptr<ThemeApi> t)
91 0 : : QProxyStyle(QStyleFactory::create("Fusion")), m_theme(t)
92 : {
93 0 : }
94 :
95 0 : void WidgetStyle::init()
96 : {
97 0 : connect(m_theme.get(), &ThemeApi::themeChanged, this, &WidgetStyle::update);
98 :
99 0 : update();
100 0 : }
101 :
102 0 : void WidgetStyle::update()
103 : {
104 0 : QColor fontPrimaryColorDisabled = m_theme->fontPrimaryColor();
105 0 : fontPrimaryColorDisabled.setAlphaF(m_theme->itemOpacityDisabled());
106 :
107 0 : QColor linkColorDisabled = m_theme->linkColor();
108 0 : linkColorDisabled.setAlphaF(m_theme->itemOpacityDisabled());
109 :
110 0 : QColor backgroundPrimaryColorDisabled = m_theme->backgroundPrimaryColor();
111 0 : backgroundPrimaryColorDisabled.setAlphaF(m_theme->itemOpacityDisabled());
112 :
113 0 : QColor backgroundSecondaryColorDisabled = m_theme->backgroundSecondaryColor();
114 0 : backgroundSecondaryColorDisabled.setAlphaF(m_theme->itemOpacityDisabled());
115 :
116 0 : QColor buttonColorDisabled = m_theme->buttonColor();
117 0 : buttonColorDisabled.setAlphaF(m_theme->itemOpacityDisabled());
118 :
119 0 : QPalette palette(QApplication::palette());
120 0 : palette.setColor(QPalette::Window, m_theme->backgroundPrimaryColor());
121 0 : palette.setColor(QPalette::Disabled, QPalette::Window, backgroundPrimaryColorDisabled);
122 0 : palette.setColor(QPalette::WindowText, m_theme->fontPrimaryColor());
123 0 : palette.setColor(QPalette::Disabled, QPalette::WindowText, fontPrimaryColorDisabled);
124 :
125 0 : palette.setColor(QPalette::Base, m_theme->backgroundSecondaryColor());
126 0 : palette.setColor(QPalette::Disabled, QPalette::Base, backgroundSecondaryColorDisabled);
127 0 : palette.setColor(QPalette::AlternateBase, m_theme->backgroundSecondaryColor());
128 0 : palette.setColor(QPalette::Disabled, QPalette::AlternateBase, backgroundSecondaryColorDisabled);
129 :
130 0 : palette.setColor(QPalette::Text, m_theme->fontPrimaryColor());
131 0 : palette.setColor(QPalette::Disabled, QPalette::Text, fontPrimaryColorDisabled);
132 :
133 0 : palette.setColor(QPalette::Link, m_theme->linkColor());
134 0 : palette.setColor(QPalette::Disabled, QPalette::Link, linkColorDisabled);
135 :
136 0 : palette.setColor(QPalette::Button, m_theme->buttonColor());
137 0 : palette.setColor(QPalette::Disabled, QPalette::Button, buttonColorDisabled);
138 0 : palette.setColor(QPalette::ButtonText, m_theme->fontPrimaryColor());
139 0 : palette.setColor(QPalette::Disabled, QPalette::ButtonText, fontPrimaryColorDisabled);
140 :
141 0 : palette.setColor(QPalette::ToolTipBase, m_theme->popupBackgroundColor());
142 0 : palette.setColor(QPalette::ToolTipText, m_theme->fontPrimaryColor());
143 :
144 0 : palette.setColor(QPalette::Highlight, m_theme->accentColor());
145 0 : palette.setColor(QPalette::HighlightedText, m_theme->fontPrimaryColor());
146 :
147 0 : palette.setColor(QPalette::PlaceholderText, m_theme->fontPrimaryColor());
148 0 : palette.setColor(QPalette::Disabled, QPalette::PlaceholderText, fontPrimaryColorDisabled);
149 :
150 0 : QApplication::setStyle(this);
151 0 : QApplication::setPalette(palette);
152 :
153 0 : QFont font = m_theme->bodyFont();
154 0 : QString styleSheet = QString("* { font: %1px \"%2\" } ")
155 0 : .arg(QString::number(font.pixelSize()), font.family());
156 0 : qApp->setStyleSheet(styleSheet);
157 0 : }
158 :
159 0 : void WidgetStyle::polish(QWidget* widget)
160 : {
161 0 : QProxyStyle::polish(widget);
162 :
163 0 : if (qobject_cast<QAbstractItemView*>(widget)
164 0 : || qobject_cast<QGroupBox*>(widget)
165 0 : || qobject_cast<QLineEdit*>(widget)) {
166 : // Make hovering work
167 0 : widget->setMouseTracking(true);
168 0 : widget->setAttribute(Qt::WA_Hover, true);
169 : }
170 0 : }
171 :
172 0 : void WidgetStyle::unpolish(QWidget* widget)
173 : {
174 0 : QProxyStyle::unpolish(widget);
175 :
176 0 : if (qobject_cast<QAbstractItemView*>(widget)
177 0 : || qobject_cast<QGroupBox*>(widget)
178 0 : || qobject_cast<QLineEdit*>(widget)) {
179 0 : widget->setMouseTracking(false);
180 0 : widget->setAttribute(Qt::WA_Hover, false);
181 : }
182 0 : }
183 :
184 0 : void WidgetStyle::drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption* option, QPainter* painter,
185 : const QWidget* widget) const
186 : {
187 0 : StyleState styleState;
188 0 : styleState.enabled = option->state & State_Enabled;
189 0 : styleState.hovered = option->state & State_MouseOver;
190 0 : styleState.pressed = option->state & State_Sunken;
191 0 : styleState.focused = (option->state & State_KeyboardFocusChange) && (option->state & State_HasFocus);
192 :
193 0 : painter->save();
194 0 : painter->setRenderHint(QPainter::Antialiasing, true);
195 :
196 : //! NOTE This drawing code is based on the implementation in QML.
197 0 : switch (element) {
198 : // Buttons (and ComboBoxes)
199 0 : case QStyle::PE_PanelButtonCommand: {
200 0 : auto buttonOption = qstyleoption_cast<const QStyleOptionButton*>(option);
201 0 : const bool accentButton = (buttonOption && buttonOption->features & QStyleOptionButton::DefaultButton)
202 0 : || option->state & State_On;
203 0 : const bool flat = (buttonOption && buttonOption->features & QStyleOptionButton::Flat)
204 0 : && !(option->state & State_On);
205 :
206 0 : QColor paletteColor = widget ? widget->palette().color(QPalette::Button) : QColor();
207 0 : const QColor background = paletteColor.isValid() ? paletteColor : m_theme->buttonColor();
208 :
209 0 : drawButtonBackground(painter, option->rect, styleState, accentButton, flat, background);
210 0 : } break;
211 0 : case QStyle::PE_FrameDefaultButton: {
212 0 : auto buttonOption = qstyleoption_cast<const QStyleOptionButton*>(option);
213 0 : const bool flat = (buttonOption && buttonOption->features & QStyleOptionButton::Flat)
214 0 : && !(option->state & State_On);
215 0 : if (flat && styleState.focused) {
216 : // For other buttons, this is done in `drawButtonBackground`, but that is not called for flat buttons
217 0 : drawRoundedRect(painter, option->rect, DEFAULT_RADIUS, NO_FILL,
218 0 : QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
219 : }
220 0 : } break;
221 :
222 : // Checkboxes
223 0 : case QStyle::PE_IndicatorCheckBox: {
224 0 : const bool indeterminate = option->state & State_NoChange;
225 0 : const bool checked = option->state & State_On;
226 0 : const bool inMenu = qobject_cast<const QMenu*>(widget);
227 :
228 0 : drawCheckboxIndicator(painter, option->rect, styleState, checked, indeterminate, inMenu);
229 0 : } break;
230 :
231 : // Radio buttons
232 0 : case QStyle::PE_IndicatorRadioButton: {
233 0 : const bool selected = option->state & State_On;
234 :
235 0 : drawRadioButtonIndicator(painter, option->rect, styleState, selected);
236 0 : } break;
237 :
238 0 : case QStyle::PE_FrameLineEdit: {
239 0 : const bool editing = option->state & State_HasFocus;
240 :
241 0 : drawLineEditBackground(painter, option->rect, styleState, editing);
242 0 : } break;
243 :
244 0 : case QStyle::PE_FrameFocusRect: {
245 0 : bool isTreeWidget = option->styleObject && option->styleObject->inherits("QTreeWidget");
246 0 : if (isTreeWidget) {
247 0 : drawRoundedRect(painter, option->rect, 1, NO_FILL, QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
248 : }
249 :
250 : //! NOTE: need for removing frame focus rectangle
251 0 : } break;
252 :
253 : // Indicator icons
254 0 : case QStyle::PE_IndicatorSpinUp:
255 : case QStyle::PE_IndicatorSpinDown:
256 : case QStyle::PE_IndicatorSpinPlus:
257 : case QStyle::PE_IndicatorSpinMinus: {
258 0 : drawIndicatorIcon(painter, option->rect, styleState, element);
259 0 : } break;
260 :
261 : // ViewItem
262 0 : case QStyle::PE_PanelItemViewItem: {
263 0 : bool selected = option->state & State_Selected;
264 :
265 0 : drawViewItemBackground(painter, option->rect, styleState, selected);
266 0 : } break;
267 :
268 : // Toolbar
269 0 : case QStyle::PE_PanelToolBar: {
270 0 : painter->fillRect(option->rect, m_theme->backgroundPrimaryColor());
271 0 : } break;
272 0 : case QStyle::PE_IndicatorToolBarHandle: {
273 0 : drawToolbarGrip(painter, option->rect, option->state & State_Horizontal);
274 0 : } break;
275 :
276 : // GroupBox
277 0 : case QStyle::PE_FrameGroupBox: {
278 0 : drawRoundedRect(painter, option->rect, DEFAULT_RADIUS, QBrush(QColor::fromRgba(0x03000000)),
279 0 : QPen(m_theme->strokeColor(), fmax(m_theme->borderWidth(), 1.0)));
280 0 : } break;
281 :
282 : // Menu
283 0 : case QStyle::PE_PanelMenu: {
284 0 : drawRoundedRect(painter, option->rect, 1, m_theme->backgroundPrimaryColor(), QPen(m_theme->strokeColor(), m_theme->borderWidth()));
285 0 : } break;
286 0 : case QStyle::PE_FrameMenu: {
287 0 : drawRoundedRect(painter, option->rect, 1, NO_FILL, QPen(m_theme->strokeColor(), m_theme->borderWidth()));
288 0 : } break;
289 :
290 0 : case QStyle::PE_Frame: {
291 0 : if (qobject_cast<const QTextEdit*>(widget) != nullptr) {
292 0 : if (styleState.enabled) {
293 0 : drawRoundedRect(painter, option->rect, DEFAULT_RADIUS, NO_FILL, QPen(m_theme->strokeColor(), m_theme->borderWidth()));
294 : } else {
295 0 : QColor penBorderColor = m_theme->strokeColor();
296 0 : penBorderColor.setAlphaF(m_theme->itemOpacityDisabled());
297 0 : drawRoundedRect(painter, option->rect, DEFAULT_RADIUS, NO_FILL, QPen(penBorderColor, m_theme->borderWidth()));
298 : }
299 : }
300 0 : } break;
301 :
302 0 : default:
303 0 : QProxyStyle::drawPrimitive(element, option, painter, widget);
304 0 : break;
305 : }
306 :
307 0 : painter->restore();
308 0 : }
309 :
310 0 : void WidgetStyle::drawComplexControl(QStyle::ComplexControl control, const QStyleOptionComplex* option, QPainter* painter,
311 : const QWidget* widget) const
312 : {
313 0 : StyleState styleState;
314 0 : styleState.enabled = option->state & State_Enabled;
315 0 : styleState.hovered = option->state & State_MouseOver;
316 0 : styleState.pressed = option->state & State_Sunken;
317 0 : styleState.focused = (option->state & State_KeyboardFocusChange) && (option->state & State_HasFocus);
318 :
319 0 : switch (control) {
320 0 : case CC_ScrollBar: {
321 0 : QProxyStyle::drawComplexControl(control, option, painter, widget);
322 :
323 0 : if (m_theme->configuration()->isHighContrast()) {
324 0 : QRect scrollBarHandle = QProxyStyle::subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget);
325 :
326 0 : QColor handleColor = m_theme->fontPrimaryColor();
327 0 : handleColor.setAlphaF(
328 0 : !styleState.enabled ? m_theme->buttonOpacityNormal()
329 0 : * m_theme->itemOpacityDisabled() : styleState.pressed ? m_theme->buttonOpacityHit() : styleState.hovered ? m_theme->buttonOpacityHover() : m_theme->buttonOpacityNormal());
330 :
331 0 : drawRoundedRect(painter, option->rect, 1, NO_FILL, QPen(m_theme->strokeColor(), m_theme->borderWidth()));
332 0 : drawRoundedRect(painter, scrollBarHandle, 1, handleColor, NO_BORDER);
333 : }
334 0 : } break;
335 :
336 0 : case CC_SpinBox: {
337 0 : QProxyStyle::drawComplexControl(control, option, painter, widget);
338 :
339 0 : QRect spinBoxFrame = QProxyStyle::subControlRect(CC_SpinBox, option, SC_SpinBoxFrame, widget);
340 0 : if (styleState.focused) {
341 0 : drawRoundedRect(painter, spinBoxFrame, DEFAULT_RADIUS, NO_FILL,
342 0 : QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
343 : }
344 0 : } break;
345 :
346 0 : default: QProxyStyle::drawComplexControl(control, option, painter, widget);
347 : }
348 0 : }
349 :
350 0 : QRect WidgetStyle::subControlRect(QStyle::ComplexControl control, const QStyleOptionComplex* option, QStyle::SubControl subControl,
351 : const QWidget* widget) const
352 : {
353 : //QRect commonStyleRect = QCommonStyle::subControlRect(control, option, subControl, widget);
354 0 : QRect proxyStyleRect = QProxyStyle::subControlRect(control, option, subControl, widget);
355 :
356 0 : switch (control) {
357 0 : case QStyle::CC_GroupBox:
358 0 : if (const QStyleOptionGroupBox* optionGroupBox = qstyleoption_cast<const QStyleOptionGroupBox*>(option)) {
359 0 : int indicatorWidth = 0;
360 0 : int indicatorHeight = 0;
361 0 : int indicatorSpacing = 0;
362 0 : const QSize textSize = option->fontMetrics.boundingRect(optionGroupBox->text).size() + QSize(2, 2);
363 :
364 0 : const bool checkable = option->subControls & QStyle::SC_GroupBoxCheckBox;
365 :
366 0 : if (checkable) {
367 : const bool isRadioButtonGroupBox
368 0 : =widget && strcmp(widget->metaObject()->className(), "muse::uicomponents::RadioButtonGroupBox") == 0;
369 :
370 0 : indicatorWidth = pixelMetric(isRadioButtonGroupBox ? PM_ExclusiveIndicatorWidth : PM_IndicatorWidth, option, widget);
371 0 : indicatorHeight = pixelMetric(isRadioButtonGroupBox ? PM_ExclusiveIndicatorHeight : PM_IndicatorHeight, option, widget);
372 : indicatorSpacing
373 0 : = pixelMetric(isRadioButtonGroupBox ? PM_RadioButtonLabelSpacing : PM_CheckBoxLabelSpacing, option, widget);
374 : }
375 :
376 0 : if (subControl == SC_GroupBoxFrame) {
377 0 : int topMargin = std::max(indicatorHeight, textSize.height()) + GROUP_BOX_LABEL_SPACING;
378 0 : return option->rect.adjusted(0, topMargin, 0, 0);
379 : }
380 :
381 0 : if (subControl == SC_GroupBoxContents) {
382 0 : int margin = 3;
383 0 : int topMargin = margin + std::max(indicatorHeight, textSize.height()) + GROUP_BOX_LABEL_SPACING;
384 0 : return option->rect.adjusted(margin, topMargin, -margin, -margin);
385 : }
386 :
387 0 : const int width = textSize.width() + (checkable ? indicatorWidth + indicatorSpacing : 0);
388 0 : QRect rect;
389 :
390 0 : if (option->rect.width() > width) {
391 0 : switch (optionGroupBox->textAlignment & Qt::AlignHorizontal_Mask) {
392 0 : case Qt::AlignHCenter:
393 0 : rect.moveLeft((option->rect.width() - width) / 2);
394 0 : break;
395 0 : case Qt::AlignRight:
396 0 : rect.moveLeft(option->rect.width() - width);
397 0 : break;
398 : }
399 : }
400 :
401 0 : if (subControl == SC_GroupBoxCheckBox) {
402 0 : rect.setWidth(indicatorWidth);
403 0 : rect.setHeight(indicatorHeight);
404 0 : rect.moveTop(textSize.height() > indicatorHeight ? (textSize.height() - indicatorHeight) / 2 : 0);
405 0 : } else if (subControl == SC_GroupBoxLabel) {
406 0 : rect.setSize(textSize);
407 0 : if (checkable) {
408 0 : rect.moveTop(textSize.height() < indicatorHeight ? (indicatorHeight - textSize.height()) / 2 : 0);
409 0 : rect.translate(indicatorWidth + indicatorSpacing, 0);
410 : }
411 : }
412 :
413 0 : return visualRect(option->direction, option->rect, rect);
414 : }
415 0 : break;
416 0 : default:
417 0 : break;
418 : }
419 :
420 0 : return proxyStyleRect;
421 : }
422 :
423 0 : int WidgetStyle::pixelMetric(QStyle::PixelMetric metric, const QStyleOption* option, const QWidget* widget) const
424 : {
425 : //! NOTE These metrics are based on the implementation in QML.
426 0 : switch (metric) {
427 0 : case PM_IndicatorWidth: // Checkbox
428 : case PM_IndicatorHeight:
429 : case PM_ExclusiveIndicatorWidth: // Radio button
430 : case PM_ExclusiveIndicatorHeight:
431 0 : return 20;
432 0 : case PM_CheckBoxLabelSpacing: // Checkbox
433 : case PM_RadioButtonLabelSpacing: // Radio button
434 0 : return 6;
435 0 : case PM_ToolBarHandleExtent: // Toolbars
436 0 : return 32;
437 0 : default:
438 0 : break;
439 : }
440 :
441 0 : return QProxyStyle::pixelMetric(metric, option, widget);
442 : }
443 :
444 0 : QSize WidgetStyle::sizeFromContents(QStyle::ContentsType type, const QStyleOption* option, const QSize& contentsSize,
445 : const QWidget* widget) const
446 : {
447 0 : QSize commonStyleSize = QCommonStyle::sizeFromContents(type, option, contentsSize, widget);
448 0 : QSize proxyStyleSize = QProxyStyle::sizeFromContents(type, option, contentsSize, widget);
449 :
450 : //! NOTE These calculations are based on the implementation in QML.
451 0 : switch (type) {
452 0 : case CT_PushButton:
453 0 : return QSize(std::max(contentsSize.width() + 32, 132),
454 0 : contentsSize.height() + 14);
455 0 : case CT_ToolButton:
456 0 : return contentsSize.expandedTo(QSize(30, 30));
457 0 : case CT_ComboBox:
458 : case CT_LineEdit:
459 0 : return proxyStyleSize.expandedTo(QSize(30, 30));
460 0 : case CT_SpinBox:
461 0 : return QSize(proxyStyleSize.width(), 26); // results in a height of 30
462 0 : case CT_GroupBox: {
463 0 : const QGroupBox* groupBox = qobject_cast<const QGroupBox*>(widget);
464 0 : const bool checkable = groupBox && groupBox->isCheckable();
465 :
466 0 : if (checkable) {
467 : const bool isRadioButtonGroupBox
468 0 : =widget && strcmp(widget->metaObject()->className(), "muse::uicomponents::RadioButtonGroupBox") == 0;
469 :
470 0 : int pm = pixelMetric(isRadioButtonGroupBox ? PM_ExclusiveIndicatorHeight : PM_IndicatorHeight, option, widget);
471 0 : return commonStyleSize + QSize(0, std::max(pm, option->fontMetrics.height()));
472 : }
473 :
474 0 : return commonStyleSize + QSize(0, option->fontMetrics.height());
475 : } break;
476 0 : case CT_ItemViewItem:
477 0 : return commonStyleSize.expandedTo(QSize(20, 20));
478 0 : case CT_MenuItem:
479 0 : if (const QStyleOptionMenuItem* optionMenuItem = qstyleoption_cast<const QStyleOptionMenuItem*>(option)) {
480 0 : if (optionMenuItem->menuItemType == QStyleOptionMenuItem::Separator) {
481 0 : return proxyStyleSize;
482 : }
483 : }
484 0 : return proxyStyleSize.expandedTo(QSize(30, 30));
485 0 : default:
486 0 : break;
487 : }
488 :
489 0 : return proxyStyleSize;
490 : }
491 :
492 0 : QIcon WidgetStyle::standardIcon(QStyle::StandardPixmap standardIcon, const QStyleOption* option, const QWidget* widget) const
493 : {
494 0 : switch (standardIcon) {
495 0 : case SP_DialogOkButton:
496 : case SP_DialogCancelButton:
497 : case SP_DialogHelpButton:
498 : case SP_DialogOpenButton:
499 : case SP_DialogSaveButton:
500 : case SP_DialogCloseButton:
501 : case SP_DialogApplyButton:
502 : case SP_DialogResetButton:
503 : case SP_DialogDiscardButton:
504 : case SP_DialogYesButton:
505 : case SP_DialogNoButton:
506 : case SP_DialogYesToAllButton:
507 : case SP_DialogNoToAllButton:
508 : case SP_DialogSaveAllButton:
509 : case SP_DialogAbortButton:
510 : case SP_DialogRetryButton:
511 : case SP_DialogIgnoreButton:
512 0 : return {};
513 0 : default:
514 0 : return QProxyStyle::standardIcon(standardIcon, option, widget);
515 : }
516 : }
517 :
518 0 : int WidgetStyle::styleHint(QStyle::StyleHint hint, const QStyleOption* option, const QWidget* widget, QStyleHintReturn* returnData) const
519 : {
520 0 : switch (hint) {
521 0 : case SH_DitherDisabledText:
522 : case SH_EtchDisabledText:
523 0 : return false;
524 0 : case SH_ItemView_ScrollMode:
525 0 : return QAbstractItemView::ScrollPerPixel;
526 0 : default:
527 0 : break;
528 : }
529 :
530 0 : return QProxyStyle::styleHint(hint, option, widget, returnData);
531 : }
532 :
533 : // ====================================================
534 : // QStyle elements drawing
535 : // ====================================================
536 :
537 0 : void WidgetStyle::drawButtonBackground(QPainter* painter, const QRect& rect, const StyleState& styleState, bool accentButton, bool flat,
538 : const QColor& defaultBackground) const
539 : {
540 0 : QColor backgroundColor(accentButton ? m_theme->accentColor() : defaultBackground);
541 :
542 0 : if (styleState.enabled) {
543 0 : backgroundColor.setAlphaF(styleState.pressed ? m_theme->buttonOpacityHit()
544 0 : : styleState.hovered ? m_theme->buttonOpacityHover()
545 0 : : !flat ? m_theme->buttonOpacityNormal()
546 : : 0.0);
547 :
548 0 : if (m_theme->configuration()->isHighContrast()) {
549 0 : QColor penBorderColor(m_theme->strokeColor());
550 0 : penBorderColor.setAlphaF(styleState.pressed ? m_theme->buttonOpacityHit()
551 0 : : styleState.hovered ? m_theme->buttonOpacityHover()
552 0 : : m_theme->buttonOpacityNormal());
553 :
554 0 : drawRoundedRect(painter, rect, DEFAULT_RADIUS, backgroundColor, QPen(penBorderColor, m_theme->borderWidth()));
555 : } else {
556 0 : drawRoundedRect(painter, rect, DEFAULT_RADIUS, backgroundColor, NO_BORDER);
557 : }
558 : } else {
559 0 : backgroundColor.setAlphaF(flat ? 0.0 : m_theme->buttonOpacityNormal() * m_theme->itemOpacityDisabled());
560 :
561 0 : drawRoundedRect(painter, rect, DEFAULT_RADIUS, backgroundColor, NO_BORDER);
562 : }
563 :
564 0 : if (styleState.focused) {
565 0 : drawRoundedRect(painter, rect, DEFAULT_RADIUS, NO_FILL, QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
566 : }
567 0 : }
568 :
569 0 : void WidgetStyle::drawCheckboxIndicator(QPainter* painter, const QRect& rect, const StyleState& styleState, bool checked,
570 : bool indeterminate,
571 : bool inMenu) const
572 : {
573 0 : QColor backgroundColor = m_theme->buttonColor();
574 0 : const qreal borderRadius = 2;
575 :
576 0 : if (!inMenu) {
577 0 : backgroundColor.setAlphaF(!styleState.enabled ? m_theme->buttonOpacityNormal() * m_theme->itemOpacityDisabled()
578 0 : : styleState.pressed ? m_theme->buttonOpacityHit()
579 0 : : styleState.hovered ? m_theme->buttonOpacityHover()
580 0 : : m_theme->buttonOpacityNormal());
581 :
582 0 : QColor penBorderColor(Qt::transparent);
583 0 : int penBorderWidth = 0;
584 0 : if (m_theme->configuration()->isHighContrast()) {
585 0 : penBorderColor = m_theme->strokeColor();
586 0 : penBorderWidth = styleState.enabled ? m_theme->borderWidth() : 0;
587 : } else {
588 0 : penBorderWidth = styleState.enabled && (styleState.hovered || styleState.pressed) ? m_theme->borderWidth() : 0;
589 : }
590 :
591 0 : penBorderColor.setAlphaF(
592 0 : styleState.pressed ? m_theme->buttonOpacityHit() : styleState.hovered ? m_theme->buttonOpacityHover() : m_theme->buttonOpacityNormal());
593 0 : drawRoundedRect(painter, rect, borderRadius, backgroundColor, QPen(penBorderColor, penBorderWidth));
594 : }
595 :
596 0 : if (styleState.focused) {
597 0 : drawRoundedRect(painter, rect, borderRadius, NO_FILL, QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
598 : }
599 :
600 0 : if (checked || indeterminate) {
601 0 : QColor tickColor = m_theme->fontPrimaryColor();
602 0 : if (!styleState.enabled) {
603 0 : tickColor.setAlphaF(m_theme->itemOpacityDisabled());
604 : }
605 0 : painter->setPen(tickColor);
606 0 : painter->setFont(m_theme->iconsFont());
607 0 : painter->drawText(rect, Qt::AlignCenter,
608 : iconCodeToChar(indeterminate ? IconCode::Code::MINUS : IconCode::Code::TICK_RIGHT_ANGLE));
609 : }
610 0 : }
611 :
612 0 : void WidgetStyle::drawRadioButtonIndicator(QPainter* painter, const QRect& rect, const StyleState& styleState, bool selected) const
613 : {
614 0 : QColor borderColor = m_theme->fontPrimaryColor();
615 0 : QColor backgroundColor = m_theme->textFieldColor();
616 :
617 0 : if (!styleState.enabled) {
618 0 : borderColor.setAlphaF(m_theme->itemOpacityDisabled());
619 0 : } else if (styleState.pressed) {
620 0 : borderColor.setAlphaF(m_theme->buttonOpacityHit());
621 0 : } else if (styleState.hovered) {
622 0 : borderColor.setAlphaF(m_theme->buttonOpacityHover());
623 : } else {
624 0 : borderColor.setAlphaF(m_theme->buttonOpacityNormal());
625 : }
626 :
627 0 : const int borderWidth = 1;
628 0 : qreal outerCircleRadius = 10; // diameter = 20
629 :
630 0 : if (styleState.focused) {
631 0 : const qreal focusCircleRadius = outerCircleRadius;
632 0 : const QRect focusCircleRect(rect.center() + QPoint(1, 1) - QPoint(focusCircleRadius, focusCircleRadius),
633 0 : QSize(focusCircleRadius, focusCircleRadius) * 2);
634 0 : drawRoundedRect(painter, focusCircleRect, focusCircleRadius, NO_FILL,
635 0 : QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
636 :
637 0 : outerCircleRadius -= m_theme->navCtrlBorderWidth();
638 : }
639 :
640 0 : const QRect outerCircleRect(rect.center() + QPoint(1, 1) - QPoint(outerCircleRadius, outerCircleRadius),
641 0 : QSize(outerCircleRadius, outerCircleRadius) * 2);
642 0 : drawRoundedRect(painter, outerCircleRect, outerCircleRadius, backgroundColor, QPen(borderColor, borderWidth));
643 :
644 0 : if (selected || styleState.pressed) {
645 0 : QColor centerColor = m_theme->accentColor();
646 0 : const int innerCircleRadius = 5; // diameter = 10
647 0 : const QRect innerCircleRect(rect.center() + QPoint(1, 1) - QPoint(innerCircleRadius, innerCircleRadius),
648 0 : QSize(innerCircleRadius, innerCircleRadius) * 2);
649 0 : drawRoundedRect(painter, innerCircleRect, innerCircleRadius, centerColor);
650 : }
651 0 : }
652 :
653 0 : void WidgetStyle::drawLineEditBackground(QPainter* painter, const QRect& rect, const StyleState& styleState, bool editing) const
654 : {
655 0 : QColor backgroundColor = m_theme->textFieldColor();
656 0 : backgroundColor.setAlphaF(!styleState.enabled ? m_theme->itemOpacityDisabled() : (editing ? 1 : (styleState.hovered ? 0.6 : 1)));
657 :
658 0 : if (styleState.focused) {
659 0 : drawRoundedRect(painter, rect, DEFAULT_RADIUS, NO_FILL, QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
660 : }
661 :
662 0 : QRect editRect = rect.adjusted(m_theme->navCtrlBorderWidth(), m_theme->navCtrlBorderWidth(),
663 0 : -m_theme->navCtrlBorderWidth(), -m_theme->navCtrlBorderWidth());
664 0 : QColor penBorderColor = editing ? m_theme->accentColor() : m_theme->strokeColor();
665 0 : penBorderColor.setAlphaF(editing ? 1 : (styleState.hovered ? 0.6 : 1));
666 0 : int borderWidth = m_theme->configuration()->isHighContrast() ? m_theme->borderWidth() : 1;
667 :
668 0 : if (styleState.enabled) {
669 0 : drawRoundedRect(painter, editRect, DEFAULT_RADIUS, backgroundColor, QPen(penBorderColor, borderWidth));
670 : } else {
671 0 : drawRoundedRect(painter, editRect, DEFAULT_RADIUS, backgroundColor, QPen(penBorderColor, 1));
672 : }
673 0 : }
674 :
675 0 : void WidgetStyle::drawIndicatorIcon(QPainter* painter, const QRect& rect, const StyleState& styleState,
676 : QStyle::PrimitiveElement element) const
677 : {
678 0 : QColor color = m_theme->fontPrimaryColor();
679 0 : if (!styleState.enabled) {
680 0 : color.setAlphaF(m_theme->itemOpacityDisabled());
681 : }
682 0 : painter->setPen(color);
683 0 : painter->setFont(m_theme->iconsFont());
684 :
685 : IconCode::Code code;
686 0 : switch (element) {
687 0 : case QStyle::PE_IndicatorSpinPlus:
688 0 : code = IconCode::Code::PLUS;
689 0 : break;
690 0 : case QStyle::PE_IndicatorSpinMinus:
691 0 : code = IconCode::Code::MINUS;
692 0 : break;
693 0 : case QStyle::PE_IndicatorSpinUp:
694 0 : code = IconCode::Code::SMALL_ARROW_UP;
695 0 : break;
696 0 : case QStyle::PE_IndicatorSpinDown:
697 0 : code = IconCode::Code::SMALL_ARROW_DOWN;
698 0 : break;
699 0 : default:
700 0 : return;
701 : }
702 :
703 0 : painter->drawText(rect, Qt::AlignCenter, iconCodeToChar(code));
704 0 : drawRoundedRect(painter, rect, 1, NO_FILL, QPen(m_theme->strokeColor(), m_theme->borderWidth())); //does nothing apparently
705 : }
706 :
707 0 : void WidgetStyle::drawViewItemBackground(QPainter* painter, const QRect& rect, const StyleState& styleState, bool selected) const
708 : {
709 0 : QColor backgroundColor(Qt::transparent);
710 0 : if (selected) {
711 0 : backgroundColor = m_theme->accentColor();
712 0 : backgroundColor.setAlphaF(
713 0 : styleState.enabled ? m_theme->accentOpacityHit() : m_theme->accentOpacityHit() * m_theme->itemOpacityDisabled());
714 0 : } else if (styleState.enabled && styleState.pressed) {
715 0 : backgroundColor = m_theme->buttonColor();
716 0 : backgroundColor.setAlphaF(m_theme->buttonOpacityHit());
717 0 : } else if (styleState.enabled && styleState.hovered) {
718 0 : backgroundColor = m_theme->buttonColor();
719 0 : backgroundColor.setAlphaF(m_theme->buttonOpacityHover());
720 : }
721 :
722 0 : painter->fillRect(rect, backgroundColor);
723 :
724 0 : if (m_theme->configuration()->isHighContrast()) {
725 0 : drawRoundedRect(painter, rect, 1, NO_FILL, QPen(m_theme->strokeColor(), m_theme->borderWidth()));
726 : }
727 :
728 0 : if (styleState.focused) {
729 0 : drawRoundedRect(painter, rect, 1, NO_FILL, QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
730 : }
731 0 : }
732 :
733 0 : void WidgetStyle::drawToolbarGrip(QPainter* painter, const QRect& rect, bool horizontal) const
734 : {
735 0 : QColor gripColor(m_theme->fontPrimaryColor());
736 0 : gripColor.setAlphaF(0.5);
737 0 : painter->setPen(gripColor);
738 0 : painter->setFont(m_theme->iconsFont());
739 :
740 0 : int rotation = horizontal ? 0 : 90;
741 0 : QRect r = horizontal ? rect : QRect(rect.topLeft() + QPoint(0, -rect.width()),
742 0 : rect.size().transposed());
743 0 : painter->rotate(rotation);
744 0 : painter->drawText(r, Qt::AlignCenter, iconCodeToChar(IconCode::Code::TOOLBAR_GRIP));
745 0 : painter->rotate(-rotation);
746 0 : }
|