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

KDE's Doxygen guidelines are available online.