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

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