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

KDE's Doxygen guidelines are available online.