Kirigami2

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

KDE's Doxygen guidelines are available online.