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

KDE's Doxygen guidelines are available online.