1/*
2 * SPDX-FileCopyrightText: 2017 by Marco Martin <mart@kde.org>
3 * SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
4 *
5 * SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7
8#include "platformtheme.h"
9#include "basictheme_p.h"
10#include "platformpluginfactory.h"
11#include <QDebug>
12#include <QDir>
13#include <QGuiApplication>
14#include <QPluginLoader>
15#include <QPointer>
16#include <QQmlContext>
17#include <QQmlEngine>
18#include <QQuickStyle>
19#include <QQuickWindow>
20
21#include <array>
22#include <cinttypes>
23#include <functional>
24#include <memory>
25#include <unordered_map>
26
27namespace Kirigami
28{
29namespace Platform
30{
31template<>
32KIRIGAMIPLATFORM_EXPORT QEvent::Type PlatformThemeEvents::DataChangedEvent::type = QEvent::None;
33template<>
34KIRIGAMIPLATFORM_EXPORT QEvent::Type PlatformThemeEvents::ColorSetChangedEvent::type = QEvent::None;
35template<>
36KIRIGAMIPLATFORM_EXPORT QEvent::Type PlatformThemeEvents::ColorGroupChangedEvent::type = QEvent::None;
37template<>
38KIRIGAMIPLATFORM_EXPORT QEvent::Type PlatformThemeEvents::ColorChangedEvent::type = QEvent::None;
39template<>
40KIRIGAMIPLATFORM_EXPORT QEvent::Type PlatformThemeEvents::FontChangedEvent::type = QEvent::None;
41
42// Initialize event types.
43// We want to avoid collisions with application event types so we should use
44// registerEventType for generating the event types. Unfortunately, that method
45// is not constexpr so we need to call it somewhere during application startup.
46// This struct handles that.
47struct TypeInitializer {
48 TypeInitializer()
49 {
50 PlatformThemeEvents::DataChangedEvent::type = QEvent::Type(QEvent::registerEventType());
51 PlatformThemeEvents::ColorSetChangedEvent::type = QEvent::Type(QEvent::registerEventType());
52 PlatformThemeEvents::ColorGroupChangedEvent::type = QEvent::Type(QEvent::registerEventType());
53 PlatformThemeEvents::ColorChangedEvent::type = QEvent::Type(QEvent::registerEventType());
54 PlatformThemeEvents::FontChangedEvent::type = QEvent::Type(QEvent::registerEventType());
55 }
56};
57static TypeInitializer initializer;
58
59// This class encapsulates the actual data of the Theme object. It may be shared
60// among several instances of PlatformTheme, to ensure that the memory usage of
61// PlatformTheme stays low.
62class PlatformThemeData : public QObject
63{
64 Q_OBJECT
65
66public:
67 // An enum for all colors in PlatformTheme.
68 // This is used so we can have a QHash of local overrides in the
69 // PlatformTheme, which avoids needing to store all these colors in
70 // PlatformTheme even when they're not used.
71 enum ColorRole {
72 TextColor,
73 DisabledTextColor,
74 HighlightedTextColor,
75 ActiveTextColor,
76 LinkColor,
77 VisitedLinkColor,
78 NegativeTextColor,
79 NeutralTextColor,
80 PositiveTextColor,
81 BackgroundColor,
82 AlternateBackgroundColor,
83 HighlightColor,
84 ActiveBackgroundColor,
85 LinkBackgroundColor,
86 VisitedLinkBackgroundColor,
87 NegativeBackgroundColor,
88 NeutralBackgroundColor,
89 PositiveBackgroundColor,
90 FocusColor,
91 HoverColor,
92
93 // This should always be the last item. It indicates how many items
94 // there are and is used for the storage array below.
95 ColorRoleCount,
96 };
97
98 using ColorMap = std::unordered_map<std::underlying_type<ColorRole>::type, QColor>;
99
100 // Which PlatformTheme instance "owns" this data object. Only the owner is
101 // allowed to make changes to data.
102 QPointer<PlatformTheme> owner;
103
104 PlatformTheme::ColorSet colorSet = PlatformTheme::Window;
105 PlatformTheme::ColorGroup colorGroup = PlatformTheme::Active;
106
107 std::array<QColor, ColorRoleCount> colors;
108
109 QFont defaultFont;
110 QFont smallFont;
111
112 QPalette palette;
113
114 // A list of PlatformTheme instances that want to be notified when the data
115 // changes. This is used instead of signal/slots as this way we only store
116 // a little bit of data and that data is shared among instances, whereas
117 // signal/slots turn out to have a pretty large memory overhead per instance.
118 using Watcher = PlatformTheme *;
119 QList<Watcher> watchers;
120
121 inline void setColorSet(PlatformTheme *sender, PlatformTheme::ColorSet set)
122 {
123 if (sender != owner || colorSet == set) {
124 return;
125 }
126
127 auto oldValue = colorSet;
128
129 colorSet = set;
130
131 notifyWatchers<PlatformTheme::ColorSet>(sender, oldValue, newValue: set);
132 }
133
134 inline void setColorGroup(PlatformTheme *sender, PlatformTheme::ColorGroup group)
135 {
136 if (sender != owner || colorGroup == group) {
137 return;
138 }
139
140 auto oldValue = colorGroup;
141
142 colorGroup = group;
143 palette.setCurrentColorGroup(QPalette::ColorGroup(group));
144
145 notifyWatchers<PlatformTheme::ColorGroup>(sender, oldValue, newValue: group);
146 }
147
148 inline void setColor(PlatformTheme *sender, ColorRole role, const QColor &color)
149 {
150 if (sender != owner || colors[role] == color) {
151 return;
152 }
153
154 auto oldValue = colors[role];
155
156 colors[role] = color;
157 updatePalette(palette, colors);
158
159 notifyWatchers<QColor>(sender, oldValue, newValue: colors[role]);
160 }
161
162 inline void setDefaultFont(PlatformTheme *sender, const QFont &font)
163 {
164 if (sender != owner || font == defaultFont) {
165 return;
166 }
167
168 auto oldValue = defaultFont;
169
170 defaultFont = font;
171
172 notifyWatchers<QFont>(sender, oldValue, newValue: font);
173 }
174
175 inline void setSmallFont(PlatformTheme *sender, const QFont &font)
176 {
177 if (sender != owner || font == smallFont) {
178 return;
179 }
180
181 auto oldValue = smallFont;
182
183 smallFont = font;
184
185 notifyWatchers<QFont>(sender, oldValue, newValue: smallFont);
186 }
187
188 inline void addChangeWatcher(PlatformTheme *object)
189 {
190 watchers.append(t: object);
191 }
192
193 inline void removeChangeWatcher(PlatformTheme *object)
194 {
195 watchers.removeOne(t: object);
196 }
197
198 template<typename T>
199 inline void notifyWatchers(PlatformTheme *sender, const T &oldValue, const T &newValue)
200 {
201 for (auto object : std::as_const(t&: watchers)) {
202 PlatformThemeEvents::PropertyChangedEvent<T> event(sender, oldValue, newValue);
203 QCoreApplication::sendEvent(receiver: object, event: &event);
204 }
205 }
206
207 // Update a palette from a list of colors.
208 inline static void updatePalette(QPalette &palette, const std::array<QColor, ColorRoleCount> &colors)
209 {
210 for (std::size_t i = 0; i < colors.size(); ++i) {
211 setPaletteColor(palette, role: ColorRole(i), color: colors.at(n: i));
212 }
213 }
214
215 // Update a palette from a hash of colors.
216 inline static void updatePalette(QPalette &palette, const ColorMap &colors)
217 {
218 for (auto entry : colors) {
219 setPaletteColor(palette, role: ColorRole(entry.first), color: entry.second);
220 }
221 }
222
223 inline static void setPaletteColor(QPalette &palette, ColorRole role, const QColor &color)
224 {
225 switch (role) {
226 case TextColor:
227 palette.setColor(acr: QPalette::Text, acolor: color);
228 palette.setColor(acr: QPalette::WindowText, acolor: color);
229 palette.setColor(acr: QPalette::ButtonText, acolor: color);
230 break;
231 case BackgroundColor:
232 palette.setColor(acr: QPalette::Window, acolor: color);
233 palette.setColor(acr: QPalette::Base, acolor: color);
234 palette.setColor(acr: QPalette::Button, acolor: color);
235 break;
236 case AlternateBackgroundColor:
237 palette.setColor(acr: QPalette::AlternateBase, acolor: color);
238 break;
239 case HighlightColor:
240 palette.setColor(acr: QPalette::Highlight, acolor: color);
241 break;
242 case HighlightedTextColor:
243 palette.setColor(acr: QPalette::HighlightedText, acolor: color);
244 break;
245 case LinkColor:
246 palette.setColor(acr: QPalette::Link, acolor: color);
247 break;
248 case VisitedLinkColor:
249 palette.setColor(acr: QPalette::LinkVisited, acolor: color);
250 break;
251
252 default:
253 break;
254 }
255 }
256};
257
258class PlatformThemePrivate
259{
260public:
261 PlatformThemePrivate()
262 : inherit(true)
263 , supportsIconColoring(false)
264 , pendingColorChange(false)
265 , pendingChildUpdate(false)
266 , useAlternateBackgroundColor(false)
267 , colorSet(PlatformTheme::Window)
268 , colorGroup(PlatformTheme::Active)
269 {
270 }
271
272 inline QColor color(const PlatformTheme *theme, PlatformThemeData::ColorRole color) const
273 {
274 if (!data) {
275 return QColor{};
276 }
277
278 QColor value = data->colors.at(n: color);
279
280 if (data->owner != theme && localOverrides) {
281 auto itr = localOverrides->find(x: color);
282 if (itr != localOverrides->end()) {
283 value = itr->second;
284 }
285 }
286
287 return value;
288 }
289
290 inline void setColor(PlatformTheme *theme, PlatformThemeData::ColorRole color, const QColor &value)
291 {
292 if (!localOverrides) {
293 localOverrides = std::make_unique<PlatformThemeData::ColorMap>();
294 }
295
296 if (!value.isValid()) {
297 // Invalid color, assume we are resetting the value.
298 auto itr = localOverrides->find(x: color);
299 if (itr != localOverrides->end()) {
300 localOverrides->erase(position: itr);
301
302 if (data) {
303 // TODO: Find a better way to determine "default" color.
304 // Right now this sets the color to transparent to force a
305 // color change and relies on the style-specific subclass to
306 // handle resetting the actual color.
307 data->setColor(sender: theme, role: color, color: Qt::transparent);
308 }
309
310 emitCompressedColorChanged(theme);
311 }
312
313 return;
314 }
315
316 auto itr = localOverrides->find(x: color);
317 if (itr != localOverrides->end() && itr->second == value && (data && data->owner != theme)) {
318 return;
319 }
320
321 (*localOverrides)[color] = value;
322
323 if (data) {
324 data->setColor(sender: theme, role: color, color: value);
325 }
326
327 emitCompressedColorChanged(theme);
328 }
329
330 inline void setDataColor(PlatformTheme *theme, PlatformThemeData::ColorRole color, const QColor &value)
331 {
332 // Only set color if we have no local override of the color.
333 // This is done because colorSet/colorGroup changes will trigger most
334 // subclasses to reevaluate and reset the colors, breaking any local
335 // overrides we have.
336 if (localOverrides) {
337 auto itr = localOverrides->find(x: color);
338 if (itr != localOverrides->end()) {
339 return;
340 }
341 }
342
343 if (data) {
344 data->setColor(sender: theme, role: color, color: value);
345 }
346 }
347
348 inline void emitCompressedColorChanged(PlatformTheme *theme)
349 {
350 if (pendingColorChange) {
351 return;
352 }
353
354 pendingColorChange = true;
355 QMetaObject::invokeMethod(object: theme, function: &PlatformTheme::emitColorChanged, type: Qt::QueuedConnection);
356 }
357
358 inline void queueChildUpdate(PlatformTheme *theme)
359 {
360 if (pendingChildUpdate) {
361 return;
362 }
363
364 pendingChildUpdate = true;
365 QMetaObject::invokeMethod(
366 object: theme,
367 function: [this, theme]() {
368 pendingChildUpdate = false;
369 theme->updateChildren(item: theme->parent());
370 },
371 type: Qt::QueuedConnection);
372 }
373
374 /*
375 * Please note that there is no q pointer. This is intentional, as it avoids
376 * having to store that information for each instance of PlatformTheme,
377 * saving us 8 bytes per instance. Instead, we pass the theme object as
378 * first parameter of each method. This is a little uglier but essentially
379 * works the same without needing memory.
380 */
381
382 // An instance of the data object. This is potentially shared with many
383 // instances of PlatformTheme.
384 std::shared_ptr<PlatformThemeData> data;
385 // Used to store color overrides of inherited data. This is created on
386 // demand and will only exist if we actually have local overrides.
387 std::unique_ptr<PlatformThemeData::ColorMap> localOverrides;
388
389 bool inherit : 1;
390 bool supportsIconColoring : 1; // TODO KF6: Remove in favour of virtual method
391 bool pendingColorChange : 1;
392 bool pendingChildUpdate : 1;
393 bool useAlternateBackgroundColor : 1;
394
395 // Note: We use these to store local values of PlatformTheme::ColorSet and
396 // PlatformTheme::ColorGroup. While these are standard enums and thus 32
397 // bits they only contain a few items so we store the value in only 4 bits
398 // to save space.
399 uint8_t colorSet : 4;
400 uint8_t colorGroup : 4;
401
402 // Ensure the above assumption holds. Should this static assert fail, the
403 // bit size above needs to be adjusted.
404 static_assert(PlatformTheme::ColorGroupCount <= 16, "PlatformTheme::ColorGroup contains more elements than can be stored in PlatformThemePrivate");
405 static_assert(PlatformTheme::ColorSetCount <= 16, "PlatformTheme::ColorSet contains more elements than can be stored in PlatformThemePrivate");
406
407 inline static PlatformPluginFactory *s_pluginFactory = nullptr;
408};
409
410PlatformTheme::PlatformTheme(QObject *parent)
411 : QObject(parent)
412 , d(new PlatformThemePrivate)
413{
414 if (QQuickItem *item = qobject_cast<QQuickItem *>(o: parent)) {
415 connect(sender: item, signal: &QQuickItem::windowChanged, context: this, slot: &PlatformTheme::update);
416 connect(sender: item, signal: &QQuickItem::parentChanged, context: this, slot: &PlatformTheme::update);
417 }
418
419 update();
420}
421
422PlatformTheme::~PlatformTheme()
423{
424 if (d->data) {
425 d->data->removeChangeWatcher(object: this);
426 }
427
428 delete d;
429}
430
431void PlatformTheme::setColorSet(PlatformTheme::ColorSet colorSet)
432{
433 d->colorSet = colorSet;
434
435 if (d->data) {
436 d->data->setColorSet(sender: this, set: colorSet);
437 }
438}
439
440PlatformTheme::ColorSet PlatformTheme::colorSet() const
441{
442 return d->data ? d->data->colorSet : Window;
443}
444
445void PlatformTheme::setColorGroup(PlatformTheme::ColorGroup colorGroup)
446{
447 d->colorGroup = colorGroup;
448
449 if (d->data) {
450 d->data->setColorGroup(sender: this, group: colorGroup);
451 }
452}
453
454PlatformTheme::ColorGroup PlatformTheme::colorGroup() const
455{
456 return d->data ? d->data->colorGroup : Active;
457}
458
459bool PlatformTheme::inherit() const
460{
461 return d->inherit;
462}
463
464void PlatformTheme::setInherit(bool inherit)
465{
466 if (inherit == d->inherit) {
467 return;
468 }
469
470 d->inherit = inherit;
471 update();
472
473 Q_EMIT inheritChanged(inherit);
474}
475
476QColor PlatformTheme::textColor() const
477{
478 return d->color(theme: this, color: PlatformThemeData::TextColor);
479}
480
481QColor PlatformTheme::disabledTextColor() const
482{
483 return d->color(theme: this, color: PlatformThemeData::DisabledTextColor);
484}
485
486QColor PlatformTheme::highlightColor() const
487{
488 return d->color(theme: this, color: PlatformThemeData::HighlightColor);
489}
490
491QColor PlatformTheme::highlightedTextColor() const
492{
493 return d->color(theme: this, color: PlatformThemeData::HighlightedTextColor);
494}
495
496QColor PlatformTheme::backgroundColor() const
497{
498 return d->color(theme: this, color: PlatformThemeData::BackgroundColor);
499}
500
501QColor PlatformTheme::alternateBackgroundColor() const
502{
503 return d->color(theme: this, color: PlatformThemeData::AlternateBackgroundColor);
504}
505
506QColor PlatformTheme::activeTextColor() const
507{
508 return d->color(theme: this, color: PlatformThemeData::ActiveTextColor);
509}
510
511QColor PlatformTheme::activeBackgroundColor() const
512{
513 return d->color(theme: this, color: PlatformThemeData::ActiveBackgroundColor);
514}
515
516QColor PlatformTheme::linkColor() const
517{
518 return d->color(theme: this, color: PlatformThemeData::LinkColor);
519}
520
521QColor PlatformTheme::linkBackgroundColor() const
522{
523 return d->color(theme: this, color: PlatformThemeData::LinkBackgroundColor);
524}
525
526QColor PlatformTheme::visitedLinkColor() const
527{
528 return d->color(theme: this, color: PlatformThemeData::VisitedLinkColor);
529}
530
531QColor PlatformTheme::visitedLinkBackgroundColor() const
532{
533 return d->color(theme: this, color: PlatformThemeData::VisitedLinkBackgroundColor);
534}
535
536QColor PlatformTheme::negativeTextColor() const
537{
538 return d->color(theme: this, color: PlatformThemeData::NegativeTextColor);
539}
540
541QColor PlatformTheme::negativeBackgroundColor() const
542{
543 return d->color(theme: this, color: PlatformThemeData::NegativeBackgroundColor);
544}
545
546QColor PlatformTheme::neutralTextColor() const
547{
548 return d->color(theme: this, color: PlatformThemeData::NeutralTextColor);
549}
550
551QColor PlatformTheme::neutralBackgroundColor() const
552{
553 return d->color(theme: this, color: PlatformThemeData::NeutralBackgroundColor);
554}
555
556QColor PlatformTheme::positiveTextColor() const
557{
558 return d->color(theme: this, color: PlatformThemeData::PositiveTextColor);
559}
560
561QColor PlatformTheme::positiveBackgroundColor() const
562{
563 return d->color(theme: this, color: PlatformThemeData::PositiveBackgroundColor);
564}
565
566QColor PlatformTheme::focusColor() const
567{
568 return d->color(theme: this, color: PlatformThemeData::FocusColor);
569}
570
571QColor PlatformTheme::hoverColor() const
572{
573 return d->color(theme: this, color: PlatformThemeData::HoverColor);
574}
575
576// setters for theme implementations
577void PlatformTheme::setTextColor(const QColor &color)
578{
579 d->setDataColor(theme: this, color: PlatformThemeData::TextColor, value: color);
580}
581
582void PlatformTheme::setDisabledTextColor(const QColor &color)
583{
584 d->setDataColor(theme: this, color: PlatformThemeData::DisabledTextColor, value: color);
585}
586
587void PlatformTheme::setBackgroundColor(const QColor &color)
588{
589 d->setDataColor(theme: this, color: PlatformThemeData::BackgroundColor, value: color);
590}
591
592void PlatformTheme::setAlternateBackgroundColor(const QColor &color)
593{
594 d->setDataColor(theme: this, color: PlatformThemeData::AlternateBackgroundColor, value: color);
595}
596
597void PlatformTheme::setHighlightColor(const QColor &color)
598{
599 d->setDataColor(theme: this, color: PlatformThemeData::HighlightColor, value: color);
600}
601
602void PlatformTheme::setHighlightedTextColor(const QColor &color)
603{
604 d->setDataColor(theme: this, color: PlatformThemeData::HighlightedTextColor, value: color);
605}
606
607void PlatformTheme::setActiveTextColor(const QColor &color)
608{
609 d->setDataColor(theme: this, color: PlatformThemeData::ActiveTextColor, value: color);
610}
611
612void PlatformTheme::setActiveBackgroundColor(const QColor &color)
613{
614 d->setDataColor(theme: this, color: PlatformThemeData::ActiveBackgroundColor, value: color);
615}
616
617void PlatformTheme::setLinkColor(const QColor &color)
618{
619 d->setDataColor(theme: this, color: PlatformThemeData::LinkColor, value: color);
620}
621
622void PlatformTheme::setLinkBackgroundColor(const QColor &color)
623{
624 d->setDataColor(theme: this, color: PlatformThemeData::LinkBackgroundColor, value: color);
625}
626
627void PlatformTheme::setVisitedLinkColor(const QColor &color)
628{
629 d->setDataColor(theme: this, color: PlatformThemeData::VisitedLinkColor, value: color);
630}
631
632void PlatformTheme::setVisitedLinkBackgroundColor(const QColor &color)
633{
634 d->setDataColor(theme: this, color: PlatformThemeData::VisitedLinkBackgroundColor, value: color);
635}
636
637void PlatformTheme::setNegativeTextColor(const QColor &color)
638{
639 d->setDataColor(theme: this, color: PlatformThemeData::NegativeTextColor, value: color);
640}
641
642void PlatformTheme::setNegativeBackgroundColor(const QColor &color)
643{
644 d->setDataColor(theme: this, color: PlatformThemeData::NegativeBackgroundColor, value: color);
645}
646
647void PlatformTheme::setNeutralTextColor(const QColor &color)
648{
649 d->setDataColor(theme: this, color: PlatformThemeData::NeutralTextColor, value: color);
650}
651
652void PlatformTheme::setNeutralBackgroundColor(const QColor &color)
653{
654 d->setDataColor(theme: this, color: PlatformThemeData::NeutralBackgroundColor, value: color);
655}
656
657void PlatformTheme::setPositiveTextColor(const QColor &color)
658{
659 d->setDataColor(theme: this, color: PlatformThemeData::PositiveTextColor, value: color);
660}
661
662void PlatformTheme::setPositiveBackgroundColor(const QColor &color)
663{
664 d->setDataColor(theme: this, color: PlatformThemeData::PositiveBackgroundColor, value: color);
665}
666
667void PlatformTheme::setHoverColor(const QColor &color)
668{
669 d->setDataColor(theme: this, color: PlatformThemeData::HoverColor, value: color);
670}
671
672void PlatformTheme::setFocusColor(const QColor &color)
673{
674 d->setDataColor(theme: this, color: PlatformThemeData::FocusColor, value: color);
675}
676
677QFont PlatformTheme::defaultFont() const
678{
679 return d->data ? d->data->defaultFont : QFont{};
680}
681
682void PlatformTheme::setDefaultFont(const QFont &font)
683{
684 if (d->data) {
685 d->data->setDefaultFont(sender: this, font);
686 }
687}
688
689QFont PlatformTheme::smallFont() const
690{
691 return d->data ? d->data->smallFont : QFont{};
692}
693
694void PlatformTheme::setSmallFont(const QFont &font)
695{
696 if (d->data) {
697 d->data->setSmallFont(sender: this, font);
698 }
699}
700
701qreal PlatformTheme::frameContrast() const
702{
703 // This value must be kept in sync with
704 // the value from Breeze Qt Widget theme.
705 // See: https://invent.kde.org/plasma/breeze/-/blob/master/kstyle/breezemetrics.h?ref_type=heads#L162
706 return 0.20;
707}
708
709qreal PlatformTheme::lightFrameContrast() const
710{
711 // This can be utilized to return full contrast
712 // if high contrast accessibility setting is enabled
713 return frameContrast() / 2.0;
714}
715
716// setters for QML clients
717void PlatformTheme::setCustomTextColor(const QColor &color)
718{
719 d->setColor(theme: this, color: PlatformThemeData::TextColor, value: color);
720}
721
722void PlatformTheme::setCustomDisabledTextColor(const QColor &color)
723{
724 d->setColor(theme: this, color: PlatformThemeData::DisabledTextColor, value: color);
725}
726
727void PlatformTheme::setCustomBackgroundColor(const QColor &color)
728{
729 d->setColor(theme: this, color: PlatformThemeData::BackgroundColor, value: color);
730}
731
732void PlatformTheme::setCustomAlternateBackgroundColor(const QColor &color)
733{
734 d->setColor(theme: this, color: PlatformThemeData::AlternateBackgroundColor, value: color);
735}
736
737void PlatformTheme::setCustomHighlightColor(const QColor &color)
738{
739 d->setColor(theme: this, color: PlatformThemeData::HighlightColor, value: color);
740}
741
742void PlatformTheme::setCustomHighlightedTextColor(const QColor &color)
743{
744 d->setColor(theme: this, color: PlatformThemeData::HighlightedTextColor, value: color);
745}
746
747void PlatformTheme::setCustomActiveTextColor(const QColor &color)
748{
749 d->setColor(theme: this, color: PlatformThemeData::ActiveTextColor, value: color);
750}
751
752void PlatformTheme::setCustomActiveBackgroundColor(const QColor &color)
753{
754 d->setColor(theme: this, color: PlatformThemeData::ActiveBackgroundColor, value: color);
755}
756
757void PlatformTheme::setCustomLinkColor(const QColor &color)
758{
759 d->setColor(theme: this, color: PlatformThemeData::LinkColor, value: color);
760}
761
762void PlatformTheme::setCustomLinkBackgroundColor(const QColor &color)
763{
764 d->setColor(theme: this, color: PlatformThemeData::LinkBackgroundColor, value: color);
765}
766
767void PlatformTheme::setCustomVisitedLinkColor(const QColor &color)
768{
769 d->setColor(theme: this, color: PlatformThemeData::TextColor, value: color);
770}
771
772void PlatformTheme::setCustomVisitedLinkBackgroundColor(const QColor &color)
773{
774 d->setColor(theme: this, color: PlatformThemeData::VisitedLinkBackgroundColor, value: color);
775}
776
777void PlatformTheme::setCustomNegativeTextColor(const QColor &color)
778{
779 d->setColor(theme: this, color: PlatformThemeData::NegativeTextColor, value: color);
780}
781
782void PlatformTheme::setCustomNegativeBackgroundColor(const QColor &color)
783{
784 d->setColor(theme: this, color: PlatformThemeData::NegativeBackgroundColor, value: color);
785}
786
787void PlatformTheme::setCustomNeutralTextColor(const QColor &color)
788{
789 d->setColor(theme: this, color: PlatformThemeData::NeutralTextColor, value: color);
790}
791
792void PlatformTheme::setCustomNeutralBackgroundColor(const QColor &color)
793{
794 d->setColor(theme: this, color: PlatformThemeData::NeutralBackgroundColor, value: color);
795}
796
797void PlatformTheme::setCustomPositiveTextColor(const QColor &color)
798{
799 d->setColor(theme: this, color: PlatformThemeData::PositiveTextColor, value: color);
800}
801
802void PlatformTheme::setCustomPositiveBackgroundColor(const QColor &color)
803{
804 d->setColor(theme: this, color: PlatformThemeData::PositiveBackgroundColor, value: color);
805}
806
807void PlatformTheme::setCustomHoverColor(const QColor &color)
808{
809 d->setColor(theme: this, color: PlatformThemeData::HoverColor, value: color);
810}
811
812void PlatformTheme::setCustomFocusColor(const QColor &color)
813{
814 d->setColor(theme: this, color: PlatformThemeData::FocusColor, value: color);
815}
816
817bool PlatformTheme::useAlternateBackgroundColor() const
818{
819 return d->useAlternateBackgroundColor;
820}
821
822void PlatformTheme::setUseAlternateBackgroundColor(bool alternate)
823{
824 if (alternate == d->useAlternateBackgroundColor) {
825 return;
826 }
827
828 d->useAlternateBackgroundColor = alternate;
829 Q_EMIT useAlternateBackgroundColorChanged(alternate);
830}
831
832QPalette PlatformTheme::palette() const
833{
834 if (!d->data) {
835 return QPalette{};
836 }
837
838 auto palette = d->data->palette;
839
840 if (d->localOverrides) {
841 PlatformThemeData::updatePalette(palette, colors: *d->localOverrides);
842 }
843
844 return palette;
845}
846
847QIcon PlatformTheme::iconFromTheme(const QString &name, const QColor &customColor)
848{
849 Q_UNUSED(customColor);
850 QIcon icon = QIcon::fromTheme(name);
851 return icon;
852}
853
854bool PlatformTheme::supportsIconColoring() const
855{
856 return d->supportsIconColoring;
857}
858
859void PlatformTheme::setSupportsIconColoring(bool support)
860{
861 d->supportsIconColoring = support;
862}
863
864PlatformTheme *PlatformTheme::qmlAttachedProperties(QObject *object)
865{
866 QQmlEngine *engine = qmlEngine(object);
867 QString pluginName;
868
869 if (engine) {
870 pluginName = engine->property(name: "_kirigamiTheme").toString();
871 }
872
873 auto plugin = PlatformPluginFactory::findPlugin(pluginName);
874 if (!plugin && !pluginName.isEmpty()) {
875 plugin = PlatformPluginFactory::findPlugin();
876 }
877
878 if (plugin) {
879 if (auto theme = plugin->createPlatformTheme(parent: object)) {
880 return theme;
881 }
882 }
883
884 return new BasicTheme(object);
885}
886
887bool PlatformTheme::event(QEvent *event)
888{
889 if (event->type() == PlatformThemeEvents::DataChangedEvent::type) {
890 auto changeEvent = static_cast<PlatformThemeEvents::DataChangedEvent *>(event);
891
892 if (changeEvent->sender != this) {
893 return false;
894 }
895
896 if (changeEvent->oldValue) {
897 changeEvent->oldValue->removeChangeWatcher(object: this);
898 }
899
900 if (changeEvent->newValue) {
901 auto data = changeEvent->newValue;
902 data->addChangeWatcher(object: this);
903
904 Q_EMIT colorSetChanged(colorSet: data->colorSet);
905 Q_EMIT colorGroupChanged(colorGroup: data->colorGroup);
906 Q_EMIT defaultFontChanged(font: data->defaultFont);
907 Q_EMIT smallFontChanged(font: data->smallFont);
908 d->emitCompressedColorChanged(theme: this);
909 }
910
911 return true;
912 }
913
914 if (event->type() == PlatformThemeEvents::ColorSetChangedEvent::type) {
915 if (d->data) {
916 Q_EMIT colorSetChanged(colorSet: d->data->colorSet);
917 }
918 return true;
919 }
920
921 if (event->type() == PlatformThemeEvents::ColorGroupChangedEvent::type) {
922 if (d->data) {
923 Q_EMIT colorGroupChanged(colorGroup: d->data->colorGroup);
924 }
925 return true;
926 }
927
928 if (event->type() == PlatformThemeEvents::ColorChangedEvent::type) {
929 d->emitCompressedColorChanged(theme: this);
930 return true;
931 }
932
933 if (event->type() == PlatformThemeEvents::FontChangedEvent::type) {
934 if (d->data) {
935 Q_EMIT defaultFontChanged(font: d->data->defaultFont);
936 Q_EMIT smallFontChanged(font: d->data->smallFont);
937 }
938 return true;
939 }
940
941 return QObject::event(event);
942}
943
944void PlatformTheme::update()
945{
946 d->queueChildUpdate(theme: this);
947
948 auto oldData = d->data;
949
950 if (d->inherit) {
951 QObject *candidate = parent();
952 while (true) {
953 candidate = determineParent(object: candidate);
954 if (!candidate) {
955 break;
956 }
957
958 auto t = static_cast<PlatformTheme *>(qmlAttachedPropertiesObject<PlatformTheme>(obj: candidate, create: false));
959 if (t && t->d->data && t->d->data->owner == t) {
960 if (d->data == t->d->data) {
961 // Inheritance is already correct, do nothing.
962 return;
963 }
964
965 d->data = t->d->data;
966
967 PlatformThemeEvents::DataChangedEvent event{this, oldData, t->d->data};
968 QCoreApplication::sendEvent(receiver: this, event: &event);
969
970 return;
971 }
972 }
973 } else if (d->data->owner != this) {
974 // Inherit has changed and we no longer want to inherit, clear the data
975 // so it is recreated below.
976 d->data = nullptr;
977 }
978
979 if (!d->data) {
980 d->data = std::make_shared<PlatformThemeData>();
981 d->data->owner = this;
982 d->data->setColorSet(sender: this, set: static_cast<ColorSet>(d->colorSet));
983 d->data->setColorGroup(sender: this, group: static_cast<ColorGroup>(d->colorGroup));
984 }
985
986 if (d->localOverrides) {
987 for (auto entry : *d->localOverrides) {
988 d->data->setColor(sender: this, role: PlatformThemeData::ColorRole(entry.first), color: entry.second);
989 }
990 }
991
992 PlatformThemeEvents::DataChangedEvent event{this, oldData, d->data};
993 QCoreApplication::sendEvent(receiver: this, event: &event);
994}
995
996void PlatformTheme::updateChildren(QObject *object)
997{
998 if (!object) {
999 return;
1000 }
1001
1002 const auto children = object->children();
1003 for (auto child : children) {
1004 auto t = static_cast<PlatformTheme *>(qmlAttachedPropertiesObject<PlatformTheme>(obj: child, create: false));
1005 if (t) {
1006 t->update();
1007 } else {
1008 updateChildren(object: child);
1009 }
1010 }
1011}
1012
1013void PlatformTheme::emitColorChanged()
1014{
1015 if (d->data) {
1016 Q_EMIT paletteChanged(pal: d->data->palette);
1017 }
1018
1019 Q_EMIT colorsChanged();
1020 d->pendingColorChange = false;
1021}
1022
1023// We sometimes set theme properties on non-visual objects. However, if an item
1024// has a visual and a non-visual parent that are different, we should prefer the
1025// visual parent, so we need to apply some extra logic.
1026QObject *PlatformTheme::determineParent(QObject *object)
1027{
1028 if (!object) {
1029 return nullptr;
1030 }
1031
1032 auto item = qobject_cast<QQuickItem *>(o: object);
1033 if (item) {
1034 return item->parentItem();
1035 } else {
1036 return object->parent();
1037 }
1038}
1039
1040}
1041}
1042
1043#include "moc_platformtheme.cpp"
1044#include "platformtheme.moc"
1045

source code of kirigami/src/platform/platformtheme.cpp