1 /*
2     SPDX-FileCopyrightText: 2006-2007 Aaron Seigo <aseigo@kde.org>
3     SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
4 
5     SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "theme_p.h"
9 #include "debug_p.h"
10 #include "framesvg.h"
11 #include "framesvg_p.h"
12 #include "svg_p.h"
13 
14 #include <QDir>
15 #include <QFile>
16 #include <QFileInfo>
17 #include <QFontDatabase>
18 #include <QGuiApplication>
19 
20 #include <KDirWatch>
21 #include <KIconLoader>
22 #include <KIconTheme>
23 #include <KWindowEffects>
24 
25 namespace Plasma
26 {
27 const char ThemePrivate::defaultTheme[] = "default";
28 const char ThemePrivate::themeRcFile[] = "plasmarc";
29 // the system colors theme is used to cache unthemed svgs with colorization needs
30 // these svgs do not follow the theme's colors, but rather the system colors
31 const char ThemePrivate::systemColorsTheme[] = "internal-system-colors";
32 #if HAVE_X11
33 EffectWatcher *ThemePrivate::s_backgroundContrastEffectWatcher = nullptr;
34 #endif
35 
36 ThemePrivate *ThemePrivate::globalTheme = nullptr;
37 QHash<QString, ThemePrivate *> ThemePrivate::themes = QHash<QString, ThemePrivate *>();
38 
ThemePrivate(QObject * parent)39 ThemePrivate::ThemePrivate(QObject *parent)
40     : QObject(parent)
41     , colorScheme(QPalette::Active, KColorScheme::Window, KSharedConfigPtr(nullptr))
42     , selectionColorScheme(QPalette::Active, KColorScheme::Selection, KSharedConfigPtr(nullptr))
43     , buttonColorScheme(QPalette::Active, KColorScheme::Button, KSharedConfigPtr(nullptr))
44     , viewColorScheme(QPalette::Active, KColorScheme::View, KSharedConfigPtr(nullptr))
45     , complementaryColorScheme(QPalette::Active, KColorScheme::Complementary, KSharedConfigPtr(nullptr))
46     , headerColorScheme(QPalette::Active, KColorScheme::Header, KSharedConfigPtr(nullptr))
47     , tooltipColorScheme(QPalette::Active, KColorScheme::Tooltip, KSharedConfigPtr(nullptr))
48     , defaultWallpaperTheme(QStringLiteral(DEFAULT_WALLPAPER_THEME))
49     , defaultWallpaperSuffix(QStringLiteral(DEFAULT_WALLPAPER_SUFFIX))
50     , defaultWallpaperWidth(DEFAULT_WALLPAPER_WIDTH)
51     , defaultWallpaperHeight(DEFAULT_WALLPAPER_HEIGHT)
52     , pixmapCache(nullptr)
53     , cacheSize(0)
54     , cachesToDiscard(NoCache)
55     , compositingActive(KWindowSystem::self()->compositingActive())
56     , backgroundContrastActive(KWindowEffects::isEffectAvailable(KWindowEffects::BackgroundContrast))
57     , isDefault(true)
58     , useGlobal(true)
59     , hasWallpapers(false)
60     , fixedName(false)
61     , backgroundContrast(qQNaN())
62     , backgroundIntensity(qQNaN())
63     , backgroundSaturation(qQNaN())
64     , backgroundContrastEnabled(true)
65     , adaptiveTransparencyEnabled(false)
66     , blurBehindEnabled(true)
67     , apiMajor(1)
68     , apiMinor(0)
69     , apiRevision(0)
70 {
71     ThemeConfig config;
72     cacheTheme = config.cacheTheme();
73 
74     pixmapSaveTimer = new QTimer(this);
75     pixmapSaveTimer->setSingleShot(true);
76     pixmapSaveTimer->setInterval(600);
77     QObject::connect(pixmapSaveTimer, &QTimer::timeout, this, &ThemePrivate::scheduledCacheUpdate);
78 
79     updateNotificationTimer = new QTimer(this);
80     updateNotificationTimer->setSingleShot(true);
81     updateNotificationTimer->setInterval(100);
82     QObject::connect(updateNotificationTimer, &QTimer::timeout, this, &ThemePrivate::notifyOfChanged);
83 
84     if (QPixmap::defaultDepth() > 8) {
85 #if HAVE_X11
86         // watch for background contrast effect property changes as well
87         if (!s_backgroundContrastEffectWatcher) {
88             s_backgroundContrastEffectWatcher = new EffectWatcher(QStringLiteral("_KDE_NET_WM_BACKGROUND_CONTRAST_REGION"));
89         }
90 
91         QObject::connect(s_backgroundContrastEffectWatcher, &EffectWatcher::effectChanged, this, [this](bool active) {
92             if (backgroundContrastActive != active) {
93                 backgroundContrastActive = active;
94                 scheduleThemeChangeNotification(PixmapCache | SvgElementsCache);
95             }
96         });
97 #endif
98     }
99     QCoreApplication::instance()->installEventFilter(this);
100 
101     const QString configFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + QLatin1String(themeRcFile);
102     KDirWatch::self()->addFile(configFile);
103 
104     // Catch both, direct changes to the config file ...
105     connect(KDirWatch::self(), &KDirWatch::dirty, this, &ThemePrivate::settingsFileChanged);
106     // ... but also remove/recreate cycles, like KConfig does it
107     connect(KDirWatch::self(), &KDirWatch::created, this, &ThemePrivate::settingsFileChanged);
108 
109     QObject::connect(KIconLoader::global(), &KIconLoader::iconChanged, this, [this]() {
110         scheduleThemeChangeNotification(PixmapCache | SvgElementsCache);
111     });
112 
113     connect(KWindowSystem::self(), &KWindowSystem::compositingChanged, this, &ThemePrivate::compositingChanged);
114 }
115 
~ThemePrivate()116 ThemePrivate::~ThemePrivate()
117 {
118     FrameSvgPrivate::s_sharedFrames.remove(this);
119     delete pixmapCache;
120 }
121 
config()122 KConfigGroup &ThemePrivate::config()
123 {
124     if (!cfg.isValid()) {
125         QString groupName = QStringLiteral("Theme");
126 
127         if (!useGlobal) {
128             QString app = QCoreApplication::applicationName();
129 
130             if (!app.isEmpty()) {
131 #ifndef NDEBUG
132                 // qCDebug(LOG_PLASMA) << "using theme for app" << app;
133 #endif
134                 groupName.append(QLatin1Char('-')).append(app);
135             }
136         }
137         cfg = KConfigGroup(KSharedConfig::openConfig(QFile::decodeName(themeRcFile)), groupName);
138     }
139 
140     return cfg;
141 }
142 
useCache()143 bool ThemePrivate::useCache()
144 {
145     bool cachesTooOld = false;
146 
147     if (cacheTheme && !pixmapCache) {
148         if (cacheSize == 0) {
149             ThemeConfig config;
150             cacheSize = config.themeCacheKb();
151         }
152         const bool isRegularTheme = themeName != QLatin1String(systemColorsTheme);
153         QString cacheFile = QLatin1String("plasma_theme_") + themeName;
154 
155         // clear any cached values from the previous theme cache
156         themeVersion.clear();
157 
158         if (!themeMetadataPath.isEmpty()) {
159             KDirWatch::self()->removeFile(themeMetadataPath);
160         }
161         if (isRegularTheme) {
162             themeMetadataPath =
163                 QStandardPaths::locate(QStandardPaths::GenericDataLocation,
164                                        QStringLiteral(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/") % themeName % QStringLiteral("/metadata.desktop"));
165             const auto *iconTheme = KIconLoader::global()->theme();
166             if (iconTheme) {
167                 iconThemeMetadataPath = iconTheme->dir() + QStringLiteral("index.theme");
168             }
169 
170             Q_ASSERT(!themeMetadataPath.isEmpty() || themeName.isEmpty());
171             const QString cacheFileBase = cacheFile + QLatin1String("*.kcache");
172 
173             QString currentCacheFileName;
174             if (!themeMetadataPath.isEmpty()) {
175                 // now we record the theme version, if we can
176                 const KPluginInfo pluginInfo(themeMetadataPath);
177                 if (pluginInfo.isValid()) {
178                     themeVersion = pluginInfo.version();
179                 }
180                 if (!themeVersion.isEmpty()) {
181                     cacheFile += QLatin1String("_v") + themeVersion;
182                     currentCacheFileName = cacheFile + QLatin1String(".kcache");
183                 }
184 
185                 // watch the metadata file for changes at runtime
186                 KDirWatch::self()->addFile(themeMetadataPath);
187                 QObject::connect(KDirWatch::self(), &KDirWatch::created, this, &ThemePrivate::settingsFileChanged, Qt::UniqueConnection);
188                 QObject::connect(KDirWatch::self(), &KDirWatch::dirty, this, &ThemePrivate::settingsFileChanged, Qt::UniqueConnection);
189 
190                 if (!iconThemeMetadataPath.isEmpty()) {
191                     KDirWatch::self()->addFile(iconThemeMetadataPath);
192                 }
193             }
194 
195             // now we check for, and remove if necessary, old caches
196             QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation));
197             cacheDir.setNameFilters(QStringList({cacheFileBase}));
198 
199             const auto files = cacheDir.entryInfoList();
200             for (const QFileInfo &file : files) {
201                 if (currentCacheFileName.isEmpty() //
202                     || !file.absoluteFilePath().endsWith(currentCacheFileName)) {
203                     QFile::remove(file.absoluteFilePath());
204                 }
205             }
206         }
207 
208         // now we do a sanity check: if the metadata.desktop file is newer than the cache, drop the cache
209         if (isRegularTheme && !themeMetadataPath.isEmpty()) {
210             // now we check to see if the theme metadata file itself is newer than the pixmap cache
211             // this is done before creating the pixmapCache object since that can change the mtime
212             // on the cache file
213 
214             // FIXME: when using the system colors, if they change while the application is not running
215             // the cache should be dropped; we need a way to detect system color change when the
216             // application is not running.
217             // check for expired cache
218             const QString cacheFilePath =
219                 QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1Char('/') + cacheFile + QLatin1String(".kcache");
220             if (!cacheFilePath.isEmpty()) {
221                 const QFileInfo cacheFileInfo(cacheFilePath);
222                 const QFileInfo metadataFileInfo(themeMetadataPath);
223                 const QFileInfo iconThemeMetadataFileInfo(iconThemeMetadataPath);
224 
225                 cachesTooOld = (cacheFileInfo.lastModified().toSecsSinceEpoch() < metadataFileInfo.lastModified().toSecsSinceEpoch())
226                     || (cacheFileInfo.lastModified().toSecsSinceEpoch() < iconThemeMetadataFileInfo.lastModified().toSecsSinceEpoch());
227             }
228         }
229 
230         ThemeConfig config;
231         pixmapCache = new KImageCache(cacheFile, config.themeCacheKb() * 1024);
232         pixmapCache->setEvictionPolicy(KSharedDataCache::EvictLeastRecentlyUsed);
233 
234         if (cachesTooOld) {
235             discardCache(PixmapCache | SvgElementsCache);
236         }
237     }
238 
239     if (cacheTheme) {
240         QString currentIconThemePath;
241         const auto *iconTheme = KIconLoader::global()->theme();
242         if (iconTheme) {
243             currentIconThemePath = iconTheme->dir();
244         }
245 
246         const QString oldIconThemePath = SvgRectsCache::instance()->iconThemePath();
247         if (oldIconThemePath != currentIconThemePath) {
248             discardCache(PixmapCache | SvgElementsCache);
249             SvgRectsCache::instance()->setIconThemePath(currentIconThemePath);
250         }
251     }
252 
253     return cacheTheme;
254 }
255 
onAppExitCleanup()256 void ThemePrivate::onAppExitCleanup()
257 {
258     pixmapsToCache.clear();
259     delete pixmapCache;
260     pixmapCache = nullptr;
261     cacheTheme = false;
262 }
263 
imagePath(const QString & theme,const QString & type,const QString & image)264 QString ThemePrivate::imagePath(const QString &theme, const QString &type, const QString &image)
265 {
266     QString subdir = QLatin1String(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/") % theme % type % image;
267     return QStandardPaths::locate(QStandardPaths::GenericDataLocation, subdir);
268 }
269 
findInTheme(const QString & image,const QString & theme,bool cache)270 QString ThemePrivate::findInTheme(const QString &image, const QString &theme, bool cache)
271 {
272     if (cache) {
273         auto it = discoveries.constFind(image);
274         if (it != discoveries.constEnd()) {
275             return it.value();
276         }
277     }
278 
279     QString type = QStringLiteral("/");
280     if (!compositingActive) {
281         type = QStringLiteral("/opaque/");
282     } else if (backgroundContrastActive) {
283         type = QStringLiteral("/translucent/");
284     }
285 
286     QString search = imagePath(theme, type, image);
287 
288     // not found or compositing enabled
289     if (search.isEmpty()) {
290         search = imagePath(theme, QStringLiteral("/"), image);
291     }
292 
293     if (cache && !search.isEmpty()) {
294         discoveries.insert(image, search);
295     }
296 
297     return search;
298 }
299 
compositingChanged(bool active)300 void ThemePrivate::compositingChanged(bool active)
301 {
302 #if HAVE_X11
303     if (compositingActive != active) {
304         compositingActive = active;
305         // qCDebug(LOG_PLASMA) << QTime::currentTime();
306         scheduleThemeChangeNotification(PixmapCache | SvgElementsCache);
307     }
308 #endif
309 }
310 
discardCache(CacheTypes caches)311 void ThemePrivate::discardCache(CacheTypes caches)
312 {
313     if (caches & PixmapCache) {
314         pixmapsToCache.clear();
315         pixmapSaveTimer->stop();
316         if (pixmapCache) {
317             pixmapCache->clear();
318         }
319     } else {
320         // This deletes the object but keeps the on-disk cache for later use
321         delete pixmapCache;
322         pixmapCache = nullptr;
323     }
324 
325     cachedDefaultStyleSheet = QString();
326     cachedSvgStyleSheets.clear();
327     cachedSelectedSvgStyleSheets.clear();
328 
329     if (caches & SvgElementsCache) {
330         discoveries.clear();
331     }
332 }
333 
scheduledCacheUpdate()334 void ThemePrivate::scheduledCacheUpdate()
335 {
336     if (useCache()) {
337         QHashIterator<QString, QPixmap> it(pixmapsToCache);
338         while (it.hasNext()) {
339             it.next();
340             pixmapCache->insertPixmap(idsToCache[it.key()], it.value());
341         }
342     }
343 
344     pixmapsToCache.clear();
345     keysToCache.clear();
346     idsToCache.clear();
347 }
348 
colorsChanged()349 void ThemePrivate::colorsChanged()
350 {
351     // in the case the theme follows the desktop settings, refetch the colorschemes
352     // and discard the svg pixmap cache
353     if (!colors) {
354         KSharedConfig::openConfig()->reparseConfiguration();
355     }
356     colorScheme = KColorScheme(QPalette::Active, KColorScheme::Window, colors);
357     buttonColorScheme = KColorScheme(QPalette::Active, KColorScheme::Button, colors);
358     viewColorScheme = KColorScheme(QPalette::Active, KColorScheme::View, colors);
359     selectionColorScheme = KColorScheme(QPalette::Active, KColorScheme::Selection, colors);
360     complementaryColorScheme = KColorScheme(QPalette::Active, KColorScheme::Complementary, colors);
361     headerColorScheme = KColorScheme(QPalette::Active, KColorScheme::Header, colors);
362     tooltipColorScheme = KColorScheme(QPalette::Active, KColorScheme::Tooltip, colors);
363     palette = KColorScheme::createApplicationPalette(colors);
364     scheduleThemeChangeNotification(PixmapCache | SvgElementsCache);
365     Q_EMIT applicationPaletteChange();
366 }
367 
scheduleThemeChangeNotification(CacheTypes caches)368 void ThemePrivate::scheduleThemeChangeNotification(CacheTypes caches)
369 {
370     cachesToDiscard |= caches;
371     updateNotificationTimer->start();
372 }
373 
notifyOfChanged()374 void ThemePrivate::notifyOfChanged()
375 {
376     // qCDebug(LOG_PLASMA) << cachesToDiscard;
377     discardCache(cachesToDiscard);
378     cachesToDiscard = NoCache;
379     Q_EMIT themeChanged();
380 }
381 
processStyleSheet(const QString & css,Plasma::Svg::Status status)382 const QString ThemePrivate::processStyleSheet(const QString &css, Plasma::Svg::Status status)
383 {
384     QString stylesheet;
385     if (css.isEmpty()) {
386         stylesheet = cachedDefaultStyleSheet;
387         if (stylesheet.isEmpty()) {
388             /* clang-format off */
389             stylesheet = QStringLiteral(
390                         "\n\
391                         body {\n\
392                             color: %textcolor;\n\
393                             generalfont-size: %fontsize;\n\
394                             font-family: %fontfamily;\n\
395                         }\n\
396                         a:active  { color: %activatedlink; }\n\
397                         a:link    { color: %link; }\n\
398                         a:visited { color: %visitedlink; }\n\
399                         a:hover   { color: %hoveredlink; text-decoration: none; }\n\
400                         ");
401             /* clang-format on */
402             stylesheet = cachedDefaultStyleSheet = processStyleSheet(stylesheet, status);
403         }
404 
405         return stylesheet;
406     } else {
407         stylesheet = css;
408     }
409 
410     QHash<QString, QString> elements;
411     // If you add elements here, make sure their names are sufficiently unique to not cause
412     // clashes between element keys
413     elements[QStringLiteral("%textcolor")] =
414         color(status == Svg::Status::Selected ? Theme::HighlightedTextColor : Theme::TextColor, Theme::NormalColorGroup).name();
415     elements[QStringLiteral("%backgroundcolor")] =
416         color(status == Svg::Status::Selected ? Theme::HighlightColor : Theme::BackgroundColor, Theme::NormalColorGroup).name();
417     elements[QStringLiteral("%highlightcolor")] = color(Theme::HighlightColor, Theme::NormalColorGroup).name();
418     elements[QStringLiteral("%highlightedtextcolor")] = color(Theme::HighlightedTextColor, Theme::NormalColorGroup).name();
419     elements[QStringLiteral("%visitedlink")] = color(Theme::VisitedLinkColor, Theme::NormalColorGroup).name();
420     elements[QStringLiteral("%activatedlink")] = color(Theme::HighlightColor, Theme::NormalColorGroup).name();
421     elements[QStringLiteral("%hoveredlink")] = color(Theme::HighlightColor, Theme::NormalColorGroup).name();
422     elements[QStringLiteral("%link")] = color(Theme::LinkColor, Theme::NormalColorGroup).name();
423     elements[QStringLiteral("%positivetextcolor")] = color(Theme::PositiveTextColor, Theme::NormalColorGroup).name();
424     elements[QStringLiteral("%neutraltextcolor")] = color(Theme::NeutralTextColor, Theme::NormalColorGroup).name();
425     elements[QStringLiteral("%negativetextcolor")] = color(Theme::NegativeTextColor, Theme::NormalColorGroup).name();
426 
427     elements[QStringLiteral("%buttontextcolor")] =
428         color(status == Svg::Status::Selected ? Theme::HighlightedTextColor : Theme::TextColor, Theme::ButtonColorGroup).name();
429     elements[QStringLiteral("%buttonbackgroundcolor")] =
430         color(status == Svg::Status::Selected ? Theme::HighlightColor : Theme::BackgroundColor, Theme::ButtonColorGroup).name();
431     elements[QStringLiteral("%buttonhovercolor")] = color(Theme::HoverColor, Theme::ButtonColorGroup).name();
432     elements[QStringLiteral("%buttonfocuscolor")] = color(Theme::FocusColor, Theme::ButtonColorGroup).name();
433     elements[QStringLiteral("%buttonhighlightedtextcolor")] = color(Theme::HighlightedTextColor, Theme::ButtonColorGroup).name();
434     elements[QStringLiteral("%buttonpositivetextcolor")] = color(Theme::PositiveTextColor, Theme::ButtonColorGroup).name();
435     elements[QStringLiteral("%buttonneutraltextcolor")] = color(Theme::NeutralTextColor, Theme::ButtonColorGroup).name();
436     elements[QStringLiteral("%buttonnegativetextcolor")] = color(Theme::NegativeTextColor, Theme::ButtonColorGroup).name();
437 
438     elements[QStringLiteral("%viewtextcolor")] =
439         color(status == Svg::Status::Selected ? Theme::HighlightedTextColor : Theme::TextColor, Theme::ViewColorGroup).name();
440     elements[QStringLiteral("%viewbackgroundcolor")] =
441         color(status == Svg::Status::Selected ? Theme::HighlightColor : Theme::BackgroundColor, Theme::ViewColorGroup).name();
442     elements[QStringLiteral("%viewhovercolor")] = color(Theme::HoverColor, Theme::ViewColorGroup).name();
443     elements[QStringLiteral("%viewfocuscolor")] = color(Theme::FocusColor, Theme::ViewColorGroup).name();
444     elements[QStringLiteral("%viewhighlightedtextcolor")] = color(Theme::HighlightedTextColor, Theme::ViewColorGroup).name();
445     elements[QStringLiteral("%viewpositivetextcolor")] = color(Theme::PositiveTextColor, Theme::ViewColorGroup).name();
446     elements[QStringLiteral("%viewneutraltextcolor")] = color(Theme::NeutralTextColor, Theme::ViewColorGroup).name();
447     elements[QStringLiteral("%viewnegativetextcolor")] = color(Theme::NegativeTextColor, Theme::ViewColorGroup).name();
448 
449     elements[QStringLiteral("%tooltiptextcolor")] =
450         color(status == Svg::Status::Selected ? Theme::HighlightedTextColor : Theme::TextColor, Theme::ToolTipColorGroup).name();
451     elements[QStringLiteral("%tooltipbackgroundcolor")] =
452         color(status == Svg::Status::Selected ? Theme::HighlightColor : Theme::BackgroundColor, Theme::ToolTipColorGroup).name();
453     elements[QStringLiteral("%tooltiphovercolor")] = color(Theme::HoverColor, Theme::ToolTipColorGroup).name();
454     elements[QStringLiteral("%tooltipfocuscolor")] = color(Theme::FocusColor, Theme::ToolTipColorGroup).name();
455     elements[QStringLiteral("%tooltiphighlightedtextcolor")] = color(Theme::HighlightedTextColor, Theme::ToolTipColorGroup).name();
456     elements[QStringLiteral("%tooltippositivetextcolor")] = color(Theme::PositiveTextColor, Theme::ToolTipColorGroup).name();
457     elements[QStringLiteral("%tooltipneutraltextcolor")] = color(Theme::NeutralTextColor, Theme::ToolTipColorGroup).name();
458     elements[QStringLiteral("%tooltipnegativetextcolor")] = color(Theme::NegativeTextColor, Theme::ToolTipColorGroup).name();
459 
460     elements[QStringLiteral("%complementarytextcolor")] =
461         color(status == Svg::Status::Selected ? Theme::HighlightedTextColor : Theme::TextColor, Theme::ComplementaryColorGroup).name();
462     elements[QStringLiteral("%complementarybackgroundcolor")] =
463         color(status == Svg::Status::Selected ? Theme::HighlightColor : Theme::BackgroundColor, Theme::ComplementaryColorGroup).name();
464     elements[QStringLiteral("%complementaryhovercolor")] = color(Theme::HoverColor, Theme::ComplementaryColorGroup).name();
465     elements[QStringLiteral("%complementaryfocuscolor")] = color(Theme::FocusColor, Theme::ComplementaryColorGroup).name();
466     elements[QStringLiteral("%complementaryhighlightedtextcolor")] = color(Theme::HighlightedTextColor, Theme::ComplementaryColorGroup).name();
467     elements[QStringLiteral("%complementarypositivetextcolor")] = color(Theme::PositiveTextColor, Theme::ComplementaryColorGroup).name();
468     elements[QStringLiteral("%complementaryneutraltextcolor")] = color(Theme::NeutralTextColor, Theme::ComplementaryColorGroup).name();
469     elements[QStringLiteral("%complementarynegativetextcolor")] = color(Theme::NegativeTextColor, Theme::ComplementaryColorGroup).name();
470 
471     elements[QStringLiteral("%headertextcolor")] =
472         color(status == Svg::Status::Selected ? Theme::HighlightedTextColor : Theme::TextColor, Theme::HeaderColorGroup).name();
473     elements[QStringLiteral("%headerbackgroundcolor")] =
474         color(status == Svg::Status::Selected ? Theme::HighlightColor : Theme::BackgroundColor, Theme::HeaderColorGroup).name();
475     elements[QStringLiteral("%headerhovercolor")] = color(Theme::HoverColor, Theme::HeaderColorGroup).name();
476     elements[QStringLiteral("%headerfocuscolor")] = color(Theme::FocusColor, Theme::HeaderColorGroup).name();
477     elements[QStringLiteral("%headerhighlightedtextcolor")] = color(Theme::HighlightedTextColor, Theme::HeaderColorGroup).name();
478     elements[QStringLiteral("%headerpositivetextcolor")] = color(Theme::PositiveTextColor, Theme::HeaderColorGroup).name();
479     elements[QStringLiteral("%headerneutraltextcolor")] = color(Theme::NeutralTextColor, Theme::HeaderColorGroup).name();
480     elements[QStringLiteral("%headernegativetextcolor")] = color(Theme::NegativeTextColor, Theme::HeaderColorGroup).name();
481 
482     QFont font = QGuiApplication::font();
483     elements[QStringLiteral("%fontsize")] = QStringLiteral("%1pt").arg(font.pointSize());
484     QString family{font.family()};
485     family.truncate(family.indexOf(QLatin1Char('[')));
486     elements[QStringLiteral("%fontfamily")] = family;
487     elements[QStringLiteral("%smallfontsize")] = QStringLiteral("%1pt").arg(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont).pointSize());
488 
489     QHash<QString, QString>::const_iterator it = elements.constBegin();
490     QHash<QString, QString>::const_iterator itEnd = elements.constEnd();
491     for (; it != itEnd; ++it) {
492         stylesheet.replace(it.key(), it.value());
493     }
494     return stylesheet;
495 }
496 
svgStyleSheet(Plasma::Theme::ColorGroup group,Plasma::Svg::Status status)497 const QString ThemePrivate::svgStyleSheet(Plasma::Theme::ColorGroup group, Plasma::Svg::Status status)
498 {
499     QString stylesheet = (status == Svg::Status::Selected) ? cachedSelectedSvgStyleSheets.value(group) : cachedSvgStyleSheets.value(group);
500     if (stylesheet.isEmpty()) {
501         QString skel = QStringLiteral(".ColorScheme-%1{color:%2;}");
502 
503         switch (group) {
504         case Theme::ButtonColorGroup:
505             stylesheet += skel.arg(QStringLiteral("Text"), QStringLiteral("%buttontextcolor"));
506             stylesheet += skel.arg(QStringLiteral("Background"), QStringLiteral("%buttonbackgroundcolor"));
507 
508             stylesheet += skel.arg(QStringLiteral("Highlight"), QStringLiteral("%buttonhovercolor"));
509             stylesheet += skel.arg(QStringLiteral("HighlightedText"), QStringLiteral("%buttonhighlightedtextcolor"));
510             stylesheet += skel.arg(QStringLiteral("PositiveText"), QStringLiteral("%buttonpositivetextcolor"));
511             stylesheet += skel.arg(QStringLiteral("NeutralText"), QStringLiteral("%buttonneutraltextcolor"));
512             stylesheet += skel.arg(QStringLiteral("NegativeText"), QStringLiteral("%buttonnegativetextcolor"));
513             break;
514         case Theme::ViewColorGroup:
515             stylesheet += skel.arg(QStringLiteral("Text"), QStringLiteral("%viewtextcolor"));
516             stylesheet += skel.arg(QStringLiteral("Background"), QStringLiteral("%viewbackgroundcolor"));
517 
518             stylesheet += skel.arg(QStringLiteral("Highlight"), QStringLiteral("%viewhovercolor"));
519             stylesheet += skel.arg(QStringLiteral("HighlightedText"), QStringLiteral("%viewhighlightedtextcolor"));
520             stylesheet += skel.arg(QStringLiteral("PositiveText"), QStringLiteral("%viewpositivetextcolor"));
521             stylesheet += skel.arg(QStringLiteral("NeutralText"), QStringLiteral("%viewneutraltextcolor"));
522             stylesheet += skel.arg(QStringLiteral("NegativeText"), QStringLiteral("%viewnegativetextcolor"));
523             break;
524         case Theme::ComplementaryColorGroup:
525             stylesheet += skel.arg(QStringLiteral("Text"), QStringLiteral("%complementarytextcolor"));
526             stylesheet += skel.arg(QStringLiteral("Background"), QStringLiteral("%complementarybackgroundcolor"));
527 
528             stylesheet += skel.arg(QStringLiteral("Highlight"), QStringLiteral("%complementaryhovercolor"));
529             stylesheet += skel.arg(QStringLiteral("HighlightedText"), QStringLiteral("%complementaryhighlightedtextcolor"));
530             stylesheet += skel.arg(QStringLiteral("PositiveText"), QStringLiteral("%complementarypositivetextcolor"));
531             stylesheet += skel.arg(QStringLiteral("NeutralText"), QStringLiteral("%complementaryneutraltextcolor"));
532             stylesheet += skel.arg(QStringLiteral("NegativeText"), QStringLiteral("%complementarynegativetextcolor"));
533             break;
534         case Theme::HeaderColorGroup:
535             stylesheet += skel.arg(QStringLiteral("Text"), QStringLiteral("%headertextcolor"));
536             stylesheet += skel.arg(QStringLiteral("Background"), QStringLiteral("%headerbackgroundcolor"));
537 
538             stylesheet += skel.arg(QStringLiteral("Highlight"), QStringLiteral("%headerhovercolor"));
539             stylesheet += skel.arg(QStringLiteral("HighlightedText"), QStringLiteral("%headerhighlightedtextcolor"));
540             stylesheet += skel.arg(QStringLiteral("PositiveText"), QStringLiteral("%headerpositivetextcolor"));
541             stylesheet += skel.arg(QStringLiteral("NeutralText"), QStringLiteral("%headerneutraltextcolor"));
542             stylesheet += skel.arg(QStringLiteral("NegativeText"), QStringLiteral("%headernegativetextcolor"));
543             break;
544         case Theme::ToolTipColorGroup:
545             stylesheet += skel.arg(QStringLiteral("Text"), QStringLiteral("%tooltiptextcolor"));
546             stylesheet += skel.arg(QStringLiteral("Background"), QStringLiteral("%tooltipbackgroundcolor"));
547 
548             stylesheet += skel.arg(QStringLiteral("Highlight"), QStringLiteral("%tooltiphovercolor"));
549             stylesheet += skel.arg(QStringLiteral("HighlightedText"), QStringLiteral("%tooltiphighlightedtextcolor"));
550             stylesheet += skel.arg(QStringLiteral("PositiveText"), QStringLiteral("%tooltippositivetextcolor"));
551             stylesheet += skel.arg(QStringLiteral("NeutralText"), QStringLiteral("%tooltipneutraltextcolor"));
552             stylesheet += skel.arg(QStringLiteral("NegativeText"), QStringLiteral("%tooltipnegativetextcolor"));
553             break;
554         default:
555             stylesheet += skel.arg(QStringLiteral("Text"), QStringLiteral("%textcolor"));
556             stylesheet += skel.arg(QStringLiteral("Background"), QStringLiteral("%backgroundcolor"));
557 
558             stylesheet += skel.arg(QStringLiteral("Highlight"), QStringLiteral("%highlightcolor"));
559             stylesheet += skel.arg(QStringLiteral("HighlightedText"), QStringLiteral("%highlightedtextcolor"));
560             stylesheet += skel.arg(QStringLiteral("PositiveText"), QStringLiteral("%positivetextcolor"));
561             stylesheet += skel.arg(QStringLiteral("NeutralText"), QStringLiteral("%neutraltextcolor"));
562             stylesheet += skel.arg(QStringLiteral("NegativeText"), QStringLiteral("%negativetextcolor"));
563         }
564 
565         stylesheet += skel.arg(QStringLiteral("ButtonText"), QStringLiteral("%buttontextcolor"));
566         stylesheet += skel.arg(QStringLiteral("ButtonBackground"), QStringLiteral("%buttonbackgroundcolor"));
567         stylesheet += skel.arg(QStringLiteral("ButtonHover"), QStringLiteral("%buttonhovercolor"));
568         stylesheet += skel.arg(QStringLiteral("ButtonFocus"), QStringLiteral("%buttonfocuscolor"));
569         stylesheet += skel.arg(QStringLiteral("ButtonHighlightedText"), QStringLiteral("%buttonhighlightedtextcolor"));
570         stylesheet += skel.arg(QStringLiteral("ButtonPositiveText"), QStringLiteral("%buttonpositivetextcolor"));
571         stylesheet += skel.arg(QStringLiteral("ButtonNeutralText"), QStringLiteral("%buttonneutraltextcolor"));
572         stylesheet += skel.arg(QStringLiteral("ButtonNegativeText"), QStringLiteral("%buttonnegativetextcolor"));
573 
574         stylesheet += skel.arg(QStringLiteral("ViewText"), QStringLiteral("%viewtextcolor"));
575         stylesheet += skel.arg(QStringLiteral("ViewBackground"), QStringLiteral("%viewbackgroundcolor"));
576         stylesheet += skel.arg(QStringLiteral("ViewHover"), QStringLiteral("%viewhovercolor"));
577         stylesheet += skel.arg(QStringLiteral("ViewFocus"), QStringLiteral("%viewfocuscolor"));
578         stylesheet += skel.arg(QStringLiteral("ViewHighlightedText"), QStringLiteral("%viewhighlightedtextcolor"));
579         stylesheet += skel.arg(QStringLiteral("ViewPositiveText"), QStringLiteral("%viewpositivetextcolor"));
580         stylesheet += skel.arg(QStringLiteral("ViewNeutralText"), QStringLiteral("%viewneutraltextcolor"));
581         stylesheet += skel.arg(QStringLiteral("ViewNegativeText"), QStringLiteral("%viewnegativetextcolor"));
582 
583         stylesheet += skel.arg(QStringLiteral("ComplementaryText"), QStringLiteral("%complementarytextcolor"));
584         stylesheet += skel.arg(QStringLiteral("ComplementaryBackground"), QStringLiteral("%complementarybackgroundcolor"));
585         stylesheet += skel.arg(QStringLiteral("ComplementaryHover"), QStringLiteral("%complementaryhovercolor"));
586         stylesheet += skel.arg(QStringLiteral("ComplementaryFocus"), QStringLiteral("%complementaryfocuscolor"));
587         stylesheet += skel.arg(QStringLiteral("ComplementaryHighlightedText"), QStringLiteral("%complementaryhighlightedtextcolor"));
588         stylesheet += skel.arg(QStringLiteral("ComplementaryPositiveText"), QStringLiteral("%complementarypositivetextcolor"));
589         stylesheet += skel.arg(QStringLiteral("ComplementaryNeutralText"), QStringLiteral("%complementaryneutraltextcolor"));
590         stylesheet += skel.arg(QStringLiteral("ComplementaryNegativeText"), QStringLiteral("%complementarynegativetextcolor"));
591 
592         stylesheet += skel.arg(QStringLiteral("HeaderText"), QStringLiteral("%headertextcolor"));
593         stylesheet += skel.arg(QStringLiteral("HeaderBackground"), QStringLiteral("%headerbackgroundcolor"));
594         stylesheet += skel.arg(QStringLiteral("HeaderHover"), QStringLiteral("%headerhovercolor"));
595         stylesheet += skel.arg(QStringLiteral("HeaderFocus"), QStringLiteral("%headerfocuscolor"));
596         stylesheet += skel.arg(QStringLiteral("HeaderHighlightedText"), QStringLiteral("%headerhighlightedtextcolor"));
597         stylesheet += skel.arg(QStringLiteral("HeaderPositiveText"), QStringLiteral("%headerpositivetextcolor"));
598         stylesheet += skel.arg(QStringLiteral("HeaderNeutralText"), QStringLiteral("%headerneutraltextcolor"));
599         stylesheet += skel.arg(QStringLiteral("HeaderNegativeText"), QStringLiteral("%headernegativetextcolor"));
600 
601         stylesheet += skel.arg(QStringLiteral("TootipText"), QStringLiteral("%tooltiptextcolor"));
602         stylesheet += skel.arg(QStringLiteral("TootipBackground"), QStringLiteral("%tooltipbackgroundcolor"));
603         stylesheet += skel.arg(QStringLiteral("TootipHover"), QStringLiteral("%tooltiphovercolor"));
604         stylesheet += skel.arg(QStringLiteral("TootipFocus"), QStringLiteral("%tooltipfocuscolor"));
605         stylesheet += skel.arg(QStringLiteral("TootipHighlightedText"), QStringLiteral("%tooltiphighlightedtextcolor"));
606         stylesheet += skel.arg(QStringLiteral("TootipPositiveText"), QStringLiteral("%tooltippositivetextcolor"));
607         stylesheet += skel.arg(QStringLiteral("TootipNeutralText"), QStringLiteral("%tooltipneutraltextcolor"));
608         stylesheet += skel.arg(QStringLiteral("TootipNegativeText"), QStringLiteral("%tooltipnegativetextcolor"));
609 
610         stylesheet = processStyleSheet(stylesheet, status);
611         if (status == Svg::Status::Selected) {
612             cachedSelectedSvgStyleSheets.insert(group, stylesheet);
613         } else {
614             cachedSvgStyleSheets.insert(group, stylesheet);
615         }
616     }
617 
618     return stylesheet;
619 }
620 
settingsFileChanged(const QString & file)621 void ThemePrivate::settingsFileChanged(const QString &file)
622 {
623     qCDebug(LOG_PLASMA) << "settingsFile: " << file;
624     if (file == themeMetadataPath) {
625         const KPluginInfo pluginInfo(themeMetadataPath);
626         if (!pluginInfo.isValid() || themeVersion != pluginInfo.version()) {
627             scheduleThemeChangeNotification(SvgElementsCache);
628         }
629     } else if (file.endsWith(QLatin1String(themeRcFile))) {
630         config().config()->reparseConfiguration();
631         settingsChanged(true);
632     }
633 }
634 
settingsChanged(bool emitChanges)635 void ThemePrivate::settingsChanged(bool emitChanges)
636 {
637     if (fixedName) {
638         return;
639     }
640     // qCDebug(LOG_PLASMA) << "Settings Changed!";
641     KConfigGroup cg = config();
642     setThemeName(cg.readEntry("name", ThemePrivate::defaultTheme), false, emitChanges);
643 }
644 
color(Theme::ColorRole role,Theme::ColorGroup group) const645 QColor ThemePrivate::color(Theme::ColorRole role, Theme::ColorGroup group) const
646 {
647     const KColorScheme *scheme = nullptr;
648 
649     // Before 5.0 Plasma theme really only used Normal and Button
650     // many old themes are built on this assumption and will break
651     // otherwise
652     if (apiMajor < 5 && group != Theme::NormalColorGroup) {
653         group = Theme::ButtonColorGroup;
654     }
655 
656     switch (group) {
657     case Theme::ButtonColorGroup: {
658         scheme = &buttonColorScheme;
659         break;
660     }
661 
662     case Theme::ViewColorGroup: {
663         scheme = &viewColorScheme;
664         break;
665     }
666 
667     // this doesn't have a real kcolorscheme
668     case Theme::ComplementaryColorGroup: {
669         scheme = &complementaryColorScheme;
670         break;
671     }
672 
673     case Theme::HeaderColorGroup: {
674         scheme = &headerColorScheme;
675         break;
676     }
677 
678     case Theme::ToolTipColorGroup: {
679         scheme = &tooltipColorScheme;
680         break;
681     }
682 
683     case Theme::NormalColorGroup:
684     default: {
685         scheme = &colorScheme;
686         break;
687     }
688     }
689 
690     switch (role) {
691     case Theme::TextColor:
692         return scheme->foreground(KColorScheme::NormalText).color();
693 
694     case Theme::BackgroundColor:
695         return scheme->background(KColorScheme::NormalBackground).color();
696 
697     case Theme::HoverColor:
698         return scheme->decoration(KColorScheme::HoverColor).color();
699 
700     case Theme::HighlightColor:
701         return selectionColorScheme.background(KColorScheme::NormalBackground).color();
702 
703     case Theme::FocusColor:
704         return scheme->decoration(KColorScheme::FocusColor).color();
705 
706     case Theme::LinkColor:
707         return scheme->foreground(KColorScheme::LinkText).color();
708 
709     case Theme::VisitedLinkColor:
710         return scheme->foreground(KColorScheme::VisitedText).color();
711 
712     case Theme::HighlightedTextColor:
713         return selectionColorScheme.foreground(KColorScheme::NormalText).color();
714 
715     case Theme::PositiveTextColor:
716         return scheme->foreground(KColorScheme::PositiveText).color();
717     case Theme::NeutralTextColor:
718         return scheme->foreground(KColorScheme::NeutralText).color();
719     case Theme::NegativeTextColor:
720         return scheme->foreground(KColorScheme::NegativeText).color();
721     case Theme::DisabledTextColor:
722         return scheme->foreground(KColorScheme::InactiveText).color();
723     }
724 
725     return QColor();
726 }
727 
processWallpaperSettings(KConfigBase * metadata)728 void ThemePrivate::processWallpaperSettings(KConfigBase *metadata)
729 {
730     if (!defaultWallpaperTheme.isEmpty() && defaultWallpaperTheme != QLatin1String(DEFAULT_WALLPAPER_THEME)) {
731         return;
732     }
733 
734     KConfigGroup cg;
735     if (metadata->hasGroup("Wallpaper")) {
736         // we have a theme color config, so let's also check to see if
737         // there is a wallpaper defined in there.
738         cg = KConfigGroup(metadata, "Wallpaper");
739     } else {
740         // since we didn't find an entry in the theme, let's look in the main
741         // theme config
742         cg = config();
743     }
744 
745     defaultWallpaperTheme = cg.readEntry("defaultWallpaperTheme", DEFAULT_WALLPAPER_THEME);
746     defaultWallpaperSuffix = cg.readEntry("defaultFileSuffix", DEFAULT_WALLPAPER_SUFFIX);
747     defaultWallpaperWidth = cg.readEntry("defaultWidth", DEFAULT_WALLPAPER_WIDTH);
748     defaultWallpaperHeight = cg.readEntry("defaultHeight", DEFAULT_WALLPAPER_HEIGHT);
749 }
750 
processContrastSettings(KConfigBase * metadata)751 void ThemePrivate::processContrastSettings(KConfigBase *metadata)
752 {
753     KConfigGroup cg;
754     if (metadata->hasGroup("ContrastEffect")) {
755         cg = KConfigGroup(metadata, "ContrastEffect");
756         backgroundContrastEnabled = cg.readEntry("enabled", false);
757 
758         backgroundContrast = cg.readEntry("contrast", qQNaN());
759         backgroundIntensity = cg.readEntry("intensity", qQNaN());
760         backgroundSaturation = cg.readEntry("saturation", qQNaN());
761     } else {
762         backgroundContrastEnabled = false;
763     }
764 }
765 
processAdaptiveTransparencySettings(KConfigBase * metadata)766 void ThemePrivate::processAdaptiveTransparencySettings(KConfigBase *metadata)
767 {
768     KConfigGroup cg;
769     if (metadata->hasGroup("AdaptiveTransparency")) {
770         cg = KConfigGroup(metadata, "AdaptiveTransparency");
771         adaptiveTransparencyEnabled = cg.readEntry("enabled", false);
772     } else {
773         adaptiveTransparencyEnabled = false;
774     }
775 }
776 
processBlurBehindSettings(KConfigBase * metadata)777 void ThemePrivate::processBlurBehindSettings(KConfigBase *metadata)
778 {
779     KConfigGroup cg;
780     if (metadata->hasGroup("BlurBehindEffect")) {
781         cg = KConfigGroup(metadata, "BlurBehindEffect");
782         blurBehindEnabled = cg.readEntry("enabled", true);
783     } else {
784         blurBehindEnabled = true;
785     }
786 }
787 
setThemeName(const QString & tempThemeName,bool writeSettings,bool emitChanged)788 void ThemePrivate::setThemeName(const QString &tempThemeName, bool writeSettings, bool emitChanged)
789 {
790     QString theme = tempThemeName;
791     if (theme.isEmpty() || theme == themeName) {
792         // let's try and get the default theme at least
793         if (themeName.isEmpty()) {
794             theme = QLatin1String(ThemePrivate::defaultTheme);
795         } else {
796             return;
797         }
798     }
799 
800     // we have one special theme: essentially a dummy theme used to cache things with
801     // the system colors.
802     bool realTheme = theme != QLatin1String(systemColorsTheme);
803     if (realTheme) {
804         QString themePath =
805             QStandardPaths::locate(QStandardPaths::GenericDataLocation,
806                                    QLatin1String(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/") % theme % QLatin1String("/metadata.desktop"));
807 
808         if (themePath.isEmpty() && themeName.isEmpty()) {
809             // note: can't use QStringLiteral("foo" "bar") on Windows
810             themePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
811                                                QStringLiteral(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/default"),
812                                                QStandardPaths::LocateDirectory);
813 
814             if (themePath.isEmpty()) {
815                 return;
816             }
817 
818             theme = QLatin1String(ThemePrivate::defaultTheme);
819         }
820     }
821 
822     // check again as ThemePrivate::defaultTheme might be empty
823     if (themeName == theme) {
824         return;
825     }
826 
827     themeName = theme;
828 
829     // load the color scheme config
830     const QString colorsFile = realTheme
831         ? QStandardPaths::locate(QStandardPaths::GenericDataLocation,
832                                  QLatin1String(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/") % theme % QLatin1String("/colors"))
833         : QString();
834 
835     // qCDebug(LOG_PLASMA) << "we're going for..." << colorsFile << "*******************";
836 
837     if (colorsFile.isEmpty()) {
838         colors = nullptr;
839     } else {
840         colors = KSharedConfig::openConfig(colorsFile);
841     }
842 
843     colorScheme = KColorScheme(QPalette::Active, KColorScheme::Window, colors);
844     selectionColorScheme = KColorScheme(QPalette::Active, KColorScheme::Selection, colors);
845     buttonColorScheme = KColorScheme(QPalette::Active, KColorScheme::Button, colors);
846     viewColorScheme = KColorScheme(QPalette::Active, KColorScheme::View, colors);
847     complementaryColorScheme = KColorScheme(QPalette::Active, KColorScheme::Complementary, colors);
848     headerColorScheme = KColorScheme(QPalette::Active, KColorScheme::Header, colors);
849     tooltipColorScheme = KColorScheme(QPalette::Active, KColorScheme::Tooltip, colors);
850     palette = KColorScheme::createApplicationPalette(colors);
851     const QString wallpaperPath = QLatin1String(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/") % theme % QLatin1String("/wallpapers/");
852     hasWallpapers = !QStandardPaths::locate(QStandardPaths::GenericDataLocation, wallpaperPath, QStandardPaths::LocateDirectory).isEmpty();
853 
854     // load the wallpaper settings, if any
855     if (realTheme) {
856         const QString metadataPath(
857             QStandardPaths::locate(QStandardPaths::GenericDataLocation,
858                                    QLatin1String(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/") % theme % QLatin1String("/metadata.desktop")));
859         KConfig metadata(metadataPath, KConfig::SimpleConfig);
860         pluginMetaData = KPluginMetaData(metadataPath);
861 
862         processContrastSettings(&metadata);
863         processBlurBehindSettings(&metadata);
864         processAdaptiveTransparencySettings(&metadata);
865 
866         processWallpaperSettings(&metadata);
867 
868         KConfigGroup cg(&metadata, "Settings");
869         QString fallback = cg.readEntry("FallbackTheme", QString());
870 
871         fallbackThemes.clear();
872         while (!fallback.isEmpty() && !fallbackThemes.contains(fallback)) {
873             fallbackThemes.append(fallback);
874 
875             QString metadataPath(
876                 QStandardPaths::locate(QStandardPaths::GenericDataLocation,
877                                        QLatin1String(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/") % fallback % QStringLiteral("/metadata.desktop")));
878             KConfig metadata(metadataPath, KConfig::SimpleConfig);
879             KConfigGroup cg(&metadata, "Settings");
880             fallback = cg.readEntry("FallbackTheme", QString());
881         }
882 
883         if (!fallbackThemes.contains(QLatin1String(ThemePrivate::defaultTheme))) {
884             fallbackThemes.append(QLatin1String(ThemePrivate::defaultTheme));
885         }
886 
887         for (const QString &theme : std::as_const(fallbackThemes)) {
888             QString metadataPath(
889                 QStandardPaths::locate(QStandardPaths::GenericDataLocation,
890                                        QStringLiteral(PLASMA_RELATIVE_DATA_INSTALL_DIR "/desktoptheme/") % theme % QStringLiteral("/metadata.desktop")));
891             KConfig metadata(metadataPath, KConfig::SimpleConfig);
892             processWallpaperSettings(&metadata);
893         }
894 
895         // Check for what Plasma version the theme has been done
896         // There are some behavioral differences between KDE4 Plasma and Plasma 5
897         cg = KConfigGroup(&metadata, "Desktop Entry");
898         const QString apiVersion = cg.readEntry("X-Plasma-API", QString());
899         apiMajor = 1;
900         apiMinor = 0;
901         apiRevision = 0;
902         if (!apiVersion.isEmpty()) {
903 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
904             const QVector<QStringView> parts = QStringView(apiVersion).split(QLatin1Char('.'));
905 #else
906             const QVector<QStringRef> parts = apiVersion.splitRef(QLatin1Char('.'));
907 #endif
908             if (!parts.isEmpty()) {
909                 apiMajor = parts.value(0).toInt();
910             }
911             if (parts.count() > 1) {
912                 apiMinor = parts.value(1).toInt();
913             }
914             if (parts.count() > 2) {
915                 apiRevision = parts.value(2).toInt();
916             }
917         }
918     }
919 
920     if (realTheme && isDefault && writeSettings) {
921         // we're the default theme, let's save our status
922         KConfigGroup &cg = config();
923         cg.writeEntry("name", themeName);
924         cg.sync();
925     }
926 
927     if (emitChanged) {
928         scheduleThemeChangeNotification(PixmapCache | SvgElementsCache);
929     }
930 }
931 
eventFilter(QObject * watched,QEvent * event)932 bool ThemePrivate::eventFilter(QObject *watched, QEvent *event)
933 {
934     if (watched == QCoreApplication::instance()) {
935         if (event->type() == QEvent::ApplicationPaletteChange) {
936             colorsChanged();
937         }
938         if (event->type() == QEvent::ApplicationFontChange || event->type() == QEvent::FontChange) {
939             Q_EMIT defaultFontChanged();
940             Q_EMIT smallestFontChanged();
941         }
942     }
943     return QObject::eventFilter(watched, event);
944 }
945 
946 }
947 
948 #include "moc_theme_p.cpp"
949