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