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 "imageset_p.h"
9#include "debug_p.h"
10#include "framesvg.h"
11#include "framesvg_p.h"
12#include "imageset.h"
13#include "svg_p.h"
14
15#include <QDir>
16#include <QFile>
17#include <QFileInfo>
18#include <QFontDatabase>
19#include <QGuiApplication>
20#include <QMetaEnum>
21
22#include <KDirWatch>
23#include <KSharedConfig>
24#include <kpluginmetadata.h>
25
26#if defined(Q_OS_LINUX)
27#include <sys/sysinfo.h>
28#endif
29
30#define DEFAULT_CACHE_SIZE 16384 // value is from the old kconfigxt default value
31
32namespace KSvg
33{
34const char ImageSetPrivate::defaultImageSet[] = "default";
35
36ImageSetPrivate *ImageSetPrivate::globalImageSet = nullptr;
37QHash<QString, ImageSetPrivate *> ImageSetPrivate::themes = QHash<QString, ImageSetPrivate *>();
38using QSP = QStandardPaths;
39
40KSharedConfig::Ptr configForImageSet(const QString &basePath, const QString &theme)
41{
42 const QString baseName = basePath % theme;
43 QString configPath = QSP::locate(type: QSP::GenericDataLocation, fileName: baseName + QLatin1String("/config"));
44 if (!configPath.isEmpty()) {
45 return KSharedConfig::openConfig(fileName: configPath, mode: KConfig::SimpleConfig);
46 }
47 QString metadataPath = QSP::locate(type: QSP::GenericDataLocation, fileName: baseName + QLatin1String("/metadata.desktop"));
48 return KSharedConfig::openConfig(fileName: metadataPath, mode: KConfig::SimpleConfig);
49}
50
51KPluginMetaData metaDataForImageSet(const QString &basePath, const QString &theme)
52{
53 QString packageBasePath = basePath % theme;
54 QDir dir;
55 if (!dir.exists(name: packageBasePath)) {
56 packageBasePath = QSP::locate(type: QSP::GenericDataLocation, fileName: basePath % theme, options: QSP::LocateDirectory);
57 }
58 if (packageBasePath.isEmpty()) {
59 qWarning(catFunc: LOG_KSVG) << "Could not locate KSvg image set" << theme << "in" << basePath << "using search path"
60 << QSP::standardLocations(type: QSP::GenericDataLocation);
61 return {};
62 }
63 if (QFileInfo::exists(file: packageBasePath + QLatin1String("/metadata.json"))) {
64 return KPluginMetaData::fromJsonFile(jsonFile: packageBasePath + QLatin1String("/metadata.json"));
65 } else if (QFileInfo::exists(file: packageBasePath + QLatin1String("/metadata.desktop"))) {
66 QString metadataPath = packageBasePath + QLatin1String("/metadata.desktop");
67 KConfigGroup cg(KSharedConfig::openConfig(fileName: packageBasePath + QLatin1String("/metadata.desktop"), mode: KConfig::SimpleConfig),
68 QStringLiteral("Desktop Entry"));
69 QJsonObject obj = {};
70 for (const QString &key : cg.keyList()) {
71 obj[key] = cg.readEntry(key);
72 }
73 qWarning(catFunc: LOG_KSVG) << "The theme" << theme << "uses the legacy metadata.desktop. Consider contacting the author and asking them update it to use the newer JSON format.";
74 return KPluginMetaData(obj, packageBasePath + QLatin1String("/metadata.desktop"));
75 } else {
76 qCWarning(LOG_KSVG) << "Could not locate metadata for theme" << theme;
77 return {};
78 }
79}
80
81ImageSetPrivate::ImageSetPrivate(QObject *parent)
82 : QObject(parent)
83 , colorScheme(QPalette::Active, KColorScheme::Window, KSharedConfigPtr(nullptr))
84 , selectionColorScheme(QPalette::Active, KColorScheme::Selection, KSharedConfigPtr(nullptr))
85 , buttonColorScheme(QPalette::Active, KColorScheme::Button, KSharedConfigPtr(nullptr))
86 , viewColorScheme(QPalette::Active, KColorScheme::View, KSharedConfigPtr(nullptr))
87 , complementaryColorScheme(QPalette::Active, KColorScheme::Complementary, KSharedConfigPtr(nullptr))
88 , headerColorScheme(QPalette::Active, KColorScheme::Header, KSharedConfigPtr(nullptr))
89 , tooltipColorScheme(QPalette::Active, KColorScheme::Tooltip, KSharedConfigPtr(nullptr))
90 , pixmapCache(nullptr)
91 , cacheSize(DEFAULT_CACHE_SIZE)
92 , cachesToDiscard(NoCache)
93 , isDefault(true)
94 , useGlobal(true)
95 , cacheImageSet(true)
96 , fixedName(false)
97 , apiMajor(1)
98 , apiMinor(0)
99 , apiRevision(0)
100{
101 const QString org = QCoreApplication::organizationName();
102 if (!org.isEmpty()) {
103 basePath += u'/' + org;
104 }
105 const QString appName = QCoreApplication::applicationName();
106 if (!appName.isEmpty()) {
107 basePath += u'/' + appName;
108 }
109 if (basePath.isEmpty()) {
110 basePath = QStringLiteral("ksvg");
111 }
112 basePath += u"/svgtheme/";
113 pixmapSaveTimer = new QTimer(this);
114 pixmapSaveTimer->setSingleShot(true);
115 pixmapSaveTimer->setInterval(600);
116 QObject::connect(sender: pixmapSaveTimer, signal: &QTimer::timeout, context: this, slot: &ImageSetPrivate::scheduledCacheUpdate);
117
118 updateNotificationTimer = new QTimer(this);
119 updateNotificationTimer->setSingleShot(true);
120 updateNotificationTimer->setInterval(100);
121 QObject::connect(sender: updateNotificationTimer, signal: &QTimer::timeout, context: this, slot: &ImageSetPrivate::notifyOfChanged);
122
123 QCoreApplication::instance()->installEventFilter(filterObj: this);
124
125 #if defined(Q_OS_LINUX)
126 struct sysinfo x;
127 if (sysinfo(info: &x) == 0) {
128 bootTime = QDateTime::currentSecsSinceEpoch() - x.uptime;
129 qCDebug(LOG_KSVG) << "ImageSetPrivate: Using boot time value" << bootTime;
130 } else {
131 // Should never happen, but just in case, fallback to a sane value
132 bootTime = QDateTime::currentSecsSinceEpoch();
133 qCWarning(LOG_KSVG) << "ImageSetPrivate: Failed to get uptime from sysinfo. Using current time as boot time" << bootTime;
134 }
135 #endif
136}
137
138ImageSetPrivate::~ImageSetPrivate()
139{
140 FrameSvgPrivate::s_sharedFrames.remove(key: this);
141 delete pixmapCache;
142}
143
144bool ImageSetPrivate::useCache()
145{
146 bool cachesTooOld = false;
147
148 if (cacheImageSet && !pixmapCache) {
149 if (cacheSize == 0) {
150 cacheSize = DEFAULT_CACHE_SIZE;
151 }
152 QString cacheFile = QLatin1String("plasma_theme_") + imageSetName;
153
154 // clear any cached values from the previous theme cache
155 themeVersion.clear();
156
157 if (!themeMetadataPath.isEmpty()) {
158 KDirWatch::self()->removeFile(file: themeMetadataPath);
159 }
160
161 themeMetadataPath = configForImageSet(basePath, theme: imageSetName)->name();
162 const QString cacheFileBase = cacheFile + QLatin1String("*.kcache");
163
164 QString currentCacheFileName;
165 if (!themeMetadataPath.isEmpty()) {
166 // now we record the theme version, if we can
167 const KPluginMetaData data = metaDataForImageSet(basePath, theme: imageSetName);
168 if (data.isValid()) {
169 themeVersion = data.version();
170 }
171 if (!themeVersion.isEmpty()) {
172 cacheFile += QLatin1String("_v") + themeVersion;
173 currentCacheFileName = cacheFile + QLatin1String(".kcache");
174 }
175 }
176
177 // now we check for, and remove if necessary, old caches
178 QDir cacheDir(QStandardPaths::writableLocation(type: QStandardPaths::GenericCacheLocation));
179 cacheDir.setNameFilters(QStringList({cacheFileBase}));
180
181 const auto files = cacheDir.entryInfoList();
182 for (const QFileInfo &file : files) {
183 if (currentCacheFileName.isEmpty() //
184 || !file.absoluteFilePath().endsWith(s: currentCacheFileName)) {
185 QFile::remove(fileName: file.absoluteFilePath());
186 }
187 }
188
189 // now we do a sanity check: if the metadata.desktop file is newer than the cache, drop the cache
190 if (!themeMetadataPath.isEmpty()) {
191 // now we check to see if the theme metadata file itself is newer than the pixmap cache
192 // this is done before creating the pixmapCache object since that can change the mtime
193 // on the cache file
194
195 // FIXME: when using the system colors, if they change while the application is not running
196 // the cache should be dropped; we need a way to detect system color change when the
197 // application is not running.
198 // check for expired cache
199 const QString cacheFilePath =
200 QStandardPaths::writableLocation(type: QStandardPaths::GenericCacheLocation) + QLatin1Char('/') + cacheFile + QLatin1String(".kcache");
201 if (!cacheFilePath.isEmpty()) {
202 const QFileInfo cacheFileInfo(cacheFilePath);
203 const QFileInfo metadataFileInfo(themeMetadataPath);
204 const QFileInfo iconImageSetMetadataFileInfo(iconImageSetMetadataPath);
205
206 cachesTooOld = (cacheFileInfo.lastModified().toSecsSinceEpoch() < metadataFileInfo.lastModified().toSecsSinceEpoch())
207 || (cacheFileInfo.lastModified().toSecsSinceEpoch() < iconImageSetMetadataFileInfo.lastModified().toSecsSinceEpoch());
208 }
209 }
210
211 pixmapCache = new KImageCache(cacheFile, cacheSize * 1024);
212 pixmapCache->setEvictionPolicy(KSharedDataCache::EvictLeastRecentlyUsed);
213
214 if (cachesTooOld) {
215 discardCache(caches: PixmapCache | SvgElementsCache);
216 }
217 }
218
219 return cacheImageSet;
220}
221
222void ImageSetPrivate::onAppExitCleanup()
223{
224 pixmapsToCache.clear();
225 delete pixmapCache;
226 pixmapCache = nullptr;
227 cacheImageSet = false;
228}
229
230QString ImageSetPrivate::imagePath(const QString &theme, const QString &type, const QString &image)
231{
232 QString subdir = basePath % theme % type % image;
233
234 if (QFileInfo::exists(file: subdir)) {
235 return subdir;
236 } else {
237 return QStandardPaths::locate(type: QStandardPaths::GenericDataLocation, fileName: subdir);
238 }
239}
240
241QString ImageSetPrivate::findInImageSet(const QString &image, const QString &theme, bool cache)
242{
243 if (cache) {
244 auto it = discoveries.constFind(key: image);
245 if (it != discoveries.constEnd()) {
246 return it.value();
247 }
248 }
249
250 QString search;
251
252 // TODO: use also QFileSelector::allSelectors?
253 // TODO: check if the theme supports selectors starting with +
254 for (const QString &type : std::as_const(t&: selectors)) {
255 search = imagePath(theme, type: QLatin1Char('/') % type % QLatin1Char('/'), image);
256 if (!search.isEmpty()) {
257 break;
258 }
259 }
260
261 // not found in selectors
262 if (search.isEmpty()) {
263 search = imagePath(theme, QStringLiteral("/"), image);
264 }
265
266 if (cache && !search.isEmpty()) {
267 discoveries.insert(key: image, value: search);
268 }
269
270 return search;
271}
272
273void ImageSetPrivate::discardCache(CacheTypes caches)
274{
275 if (caches & PixmapCache) {
276 pixmapsToCache.clear();
277 pixmapSaveTimer->stop();
278 if (pixmapCache) {
279 pixmapCache->clear();
280 }
281 } else {
282 // This deletes the object but keeps the on-disk cache for later use
283 delete pixmapCache;
284 pixmapCache = nullptr;
285 }
286
287 cachedSvgStyleSheets.clear();
288 cachedSelectedSvgStyleSheets.clear();
289 cachedInactiveSvgStyleSheets.clear();
290
291 if (caches & SvgElementsCache) {
292 discoveries.clear();
293 }
294}
295
296void ImageSetPrivate::scheduledCacheUpdate()
297{
298 if (useCache()) {
299 QHashIterator<QString, QPixmap> it(pixmapsToCache);
300 while (it.hasNext()) {
301 it.next();
302 pixmapCache->insertPixmap(key: idsToCache[it.key()], pixmap: it.value());
303 }
304 }
305
306 pixmapsToCache.clear();
307 keysToCache.clear();
308 idsToCache.clear();
309}
310
311void ImageSetPrivate::colorsChanged()
312{
313 // in the case the theme follows the desktop settings, refetch the colorschemes
314 // and discard the svg pixmap cache
315 if (!colors) {
316 KSharedConfig::openConfig()->reparseConfiguration();
317 }
318 colorScheme = KColorScheme(QPalette::Active, KColorScheme::Window, colors);
319 buttonColorScheme = KColorScheme(QPalette::Active, KColorScheme::Button, colors);
320 viewColorScheme = KColorScheme(QPalette::Active, KColorScheme::View, colors);
321 selectionColorScheme = KColorScheme(QPalette::Active, KColorScheme::Selection, colors);
322 complementaryColorScheme = KColorScheme(QPalette::Active, KColorScheme::Complementary, colors);
323 headerColorScheme = KColorScheme(QPalette::Active, KColorScheme::Header, colors);
324 tooltipColorScheme = KColorScheme(QPalette::Active, KColorScheme::Tooltip, colors);
325 scheduleImageSetChangeNotification(caches: PixmapCache | SvgElementsCache);
326 Q_EMIT applicationPaletteChange();
327}
328
329void ImageSetPrivate::scheduleImageSetChangeNotification(CacheTypes caches)
330{
331 cachesToDiscard |= caches;
332 updateNotificationTimer->start();
333}
334
335void ImageSetPrivate::notifyOfChanged()
336{
337 // qCDebug(LOG_KSVG) << cachesToDiscard;
338 discardCache(caches: cachesToDiscard);
339 cachesToDiscard = NoCache;
340 Q_EMIT imageSetChanged(imageSetName);
341}
342
343QColor ImageSetPrivate::namedColor(Svg::StyleSheetColor colorName, const KSvg::Svg *svg)
344{
345 const KSvg::Svg::Status status = svg->status();
346 KColorScheme *currentScheme = nullptr;
347
348 switch (KColorScheme::ColorSet(svg->colorSet())) {
349 case KColorScheme::Button:
350 currentScheme = &buttonColorScheme;
351 break;
352 case KColorScheme::View:
353 currentScheme = &viewColorScheme;
354 break;
355 case KColorScheme::Complementary:
356 currentScheme = &complementaryColorScheme;
357 break;
358 case KColorScheme::Header:
359 currentScheme = &headerColorScheme;
360 break;
361 case KColorScheme::Tooltip:
362 currentScheme = &tooltipColorScheme;
363 break;
364 default:
365 currentScheme = &colorScheme;
366 }
367
368 switch (colorName) {
369 case Svg::Text:
370 switch (status) {
371 case Svg::Status::Selected:
372 return selectionColorScheme.foreground(KColorScheme::NormalText).color();
373 case Svg::Status::Inactive:
374 return currentScheme->foreground(KColorScheme::InactiveText).color();
375 default:
376 return currentScheme->foreground(KColorScheme::NormalText).color();
377 }
378 case Svg::Background:
379 if (status == Svg::Status::Selected) {
380 return selectionColorScheme.background(KColorScheme::NormalBackground).color();
381 } else {
382 return colorScheme.background(KColorScheme::NormalBackground).color();
383 }
384 case Svg::Highlight:
385 return selectionColorScheme.background(KColorScheme::NormalBackground).color();
386 case Svg::HighlightedText:
387 return selectionColorScheme.foreground(KColorScheme::NormalText).color();
388 case Svg::PositiveText:
389 return currentScheme->foreground(KColorScheme::PositiveText).color();
390 case Svg::NeutralText:
391 return currentScheme->foreground(KColorScheme::NeutralText).color();
392 case Svg::NegativeText:
393 return currentScheme->foreground(KColorScheme::NegativeText).color();
394
395 case Svg::ButtonText:
396 if (status == Svg::Status::Selected) {
397 return selectionColorScheme.foreground(KColorScheme::NormalText).color();
398 } else {
399 return buttonColorScheme.foreground(KColorScheme::NormalText).color();
400 }
401 case Svg::ButtonBackground:
402 if (status == Svg::Status::Selected) {
403 return selectionColorScheme.background(KColorScheme::NormalBackground).color();
404 } else {
405 return buttonColorScheme.background(KColorScheme::NormalBackground).color();
406 }
407 case Svg::ButtonHover:
408 return buttonColorScheme.decoration(KColorScheme::HoverColor).color();
409 case Svg::ButtonFocus:
410 return buttonColorScheme.decoration(KColorScheme::FocusColor).color();
411 case Svg::ButtonHighlightedText:
412 return selectionColorScheme.foreground(KColorScheme::NormalText).color();
413 case Svg::ButtonPositiveText:
414 return buttonColorScheme.foreground(KColorScheme::PositiveText).color();
415 case Svg::ButtonNeutralText:
416 return buttonColorScheme.foreground(KColorScheme::NeutralText).color();
417 case Svg::ButtonNegativeText:
418 return buttonColorScheme.foreground(KColorScheme::NegativeText).color();
419
420 case Svg::ViewText:
421 if (status == Svg::Status::Selected) {
422 return selectionColorScheme.foreground(KColorScheme::NormalText).color();
423 } else {
424 return viewColorScheme.foreground(KColorScheme::NormalText).color();
425 }
426 case Svg::ViewBackground:
427 if (status == Svg::Status::Selected) {
428 return selectionColorScheme.background(KColorScheme::NormalBackground).color();
429 } else {
430 return viewColorScheme.background(KColorScheme::NormalBackground).color();
431 }
432 case Svg::ViewHover:
433 return viewColorScheme.decoration(KColorScheme::HoverColor).color();
434 case Svg::ViewFocus:
435 return viewColorScheme.decoration(KColorScheme::FocusColor).color();
436 case Svg::ViewHighlightedText:
437 return selectionColorScheme.foreground(KColorScheme::NormalText).color();
438 case Svg::ViewPositiveText:
439 return viewColorScheme.foreground(KColorScheme::PositiveText).color();
440 case Svg::ViewNeutralText:
441 return viewColorScheme.foreground(KColorScheme::NeutralText).color();
442 case Svg::ViewNegativeText:
443 return viewColorScheme.foreground(KColorScheme::NegativeText).color();
444
445 case Svg::TooltipText:
446 if (status == Svg::Status::Selected) {
447 return selectionColorScheme.foreground(KColorScheme::NormalText).color();
448 } else {
449 return tooltipColorScheme.foreground(KColorScheme::NormalText).color();
450 }
451 case Svg::TooltipBackground:
452 if (status == Svg::Status::Selected) {
453 return selectionColorScheme.background(KColorScheme::NormalBackground).color();
454 } else {
455 return tooltipColorScheme.background(KColorScheme::NormalBackground).color();
456 }
457 case Svg::TooltipHover:
458 return tooltipColorScheme.decoration(KColorScheme::HoverColor).color();
459 case Svg::TooltipFocus:
460 return tooltipColorScheme.decoration(KColorScheme::FocusColor).color();
461 case Svg::TooltipHighlightedText:
462 return selectionColorScheme.foreground(KColorScheme::NormalText).color();
463 case Svg::TooltipPositiveText:
464 return tooltipColorScheme.foreground(KColorScheme::PositiveText).color();
465 case Svg::TooltipNeutralText:
466 return tooltipColorScheme.foreground(KColorScheme::NeutralText).color();
467 case Svg::TooltipNegativeText:
468 return tooltipColorScheme.foreground(KColorScheme::NegativeText).color();
469
470 case Svg::ComplementaryText:
471 if (status == Svg::Status::Selected) {
472 return selectionColorScheme.foreground(KColorScheme::NormalText).color();
473 } else {
474 return complementaryColorScheme.foreground(KColorScheme::NormalText).color();
475 }
476 case Svg::ComplementaryBackground:
477 if (status == Svg::Status::Selected) {
478 return selectionColorScheme.background(KColorScheme::NormalBackground).color();
479 } else {
480 return complementaryColorScheme.background(KColorScheme::NormalBackground).color();
481 }
482 case Svg::ComplementaryHover:
483 return complementaryColorScheme.decoration(KColorScheme::HoverColor).color();
484 case Svg::ComplementaryFocus:
485 return complementaryColorScheme.decoration(KColorScheme::FocusColor).color();
486 case Svg::ComplementaryHighlightedText:
487 return selectionColorScheme.foreground(KColorScheme::NormalText).color();
488 case Svg::ComplementaryPositiveText:
489 return complementaryColorScheme.foreground(KColorScheme::PositiveText).color();
490 case Svg::ComplementaryNeutralText:
491 return complementaryColorScheme.foreground(KColorScheme::NeutralText).color();
492 case Svg::ComplementaryNegativeText:
493 return complementaryColorScheme.foreground(KColorScheme::NegativeText).color();
494
495 case Svg::HeaderText:
496 if (status == Svg::Status::Selected) {
497 return selectionColorScheme.foreground(KColorScheme::NormalText).color();
498 } else {
499 return headerColorScheme.foreground(KColorScheme::NormalText).color();
500 }
501 case Svg::HeaderBackground:
502 if (status == Svg::Status::Selected) {
503 return selectionColorScheme.background(KColorScheme::NormalBackground).color();
504 } else {
505 return headerColorScheme.background(KColorScheme::NormalBackground).color();
506 }
507 case Svg::HeaderHover:
508 return headerColorScheme.decoration(KColorScheme::HoverColor).color();
509 case Svg::HeaderFocus:
510 return headerColorScheme.decoration(KColorScheme::FocusColor).color();
511 case Svg::HeaderHighlightedText:
512 return selectionColorScheme.foreground(KColorScheme::NormalText).color();
513 case Svg::HeaderPositiveText:
514 return headerColorScheme.foreground(KColorScheme::PositiveText).color();
515 case Svg::HeaderNeutralText:
516 return headerColorScheme.foreground(KColorScheme::NeutralText).color();
517 case Svg::HeaderNegativeText:
518 return headerColorScheme.foreground(KColorScheme::NegativeText).color();
519 default:
520 return {};
521 }
522}
523
524const QString ImageSetPrivate::svgStyleSheet(KSvg::Svg *svg)
525{
526 const KSvg::Svg::Status status = svg->status();
527 const KColorScheme::ColorSet colorSet = KColorScheme::ColorSet(svg->colorSet());
528 const bool useCache = svg->d->colorOverrides.isEmpty();
529 QString stylesheet;
530 if (useCache) {
531 stylesheet = (status == Svg::Status::Selected)
532 ? cachedSelectedSvgStyleSheets.value(key: colorSet)
533 : (status == Svg::Status::Inactive ? cachedInactiveSvgStyleSheets.value(key: colorSet) : cachedSvgStyleSheets.value(key: colorSet));
534 }
535
536 if (stylesheet.isEmpty()) {
537 const QList<Svg::StyleSheetColor> namedColors({Svg::Text,
538 Svg::Background,
539 Svg::Highlight,
540 Svg::HighlightedText,
541 Svg::PositiveText,
542 Svg::NeutralText,
543 Svg::NegativeText,
544
545 Svg::ButtonText,
546 Svg::ButtonBackground,
547 Svg::ButtonHover,
548 Svg::ButtonFocus,
549 Svg::ButtonHighlightedText,
550 Svg::ButtonPositiveText,
551 Svg::ButtonNeutralText,
552 Svg::ButtonNegativeText,
553
554 Svg::ViewText,
555 Svg::ViewBackground,
556 Svg::ViewHover,
557 Svg::ViewFocus,
558 Svg::ViewHighlightedText,
559 Svg::ViewPositiveText,
560 Svg::ViewNeutralText,
561 Svg::ViewNegativeText,
562
563 Svg::TooltipText,
564 Svg::TooltipBackground,
565 Svg::TooltipHover,
566 Svg::TooltipFocus,
567 Svg::TooltipHighlightedText,
568 Svg::TooltipPositiveText,
569 Svg::TooltipNeutralText,
570 Svg::TooltipNegativeText,
571
572 Svg::ComplementaryText,
573 Svg::ComplementaryBackground,
574 Svg::ComplementaryHover,
575 Svg::ComplementaryFocus,
576 Svg::ComplementaryHighlightedText,
577 Svg::ComplementaryPositiveText,
578 Svg::ComplementaryNeutralText,
579 Svg::ComplementaryNegativeText,
580
581 Svg::HeaderText,
582 Svg::HeaderBackground,
583 Svg::HeaderHover,
584 Svg::HeaderFocus,
585 Svg::HeaderHighlightedText,
586 Svg::HeaderPositiveText,
587 Svg::HeaderNeutralText,
588 Svg::HeaderNegativeText});
589 const QString skel = QStringLiteral(".ColorScheme-%1{color:%2;}");
590 const QMetaEnum metaEnum = QMetaEnum::fromType<Svg::StyleSheetColor>();
591
592 for (const auto colorName : std::as_const(t: namedColors)) {
593 stylesheet += skel.arg(args: QString::fromUtf8(utf8: metaEnum.valueToKey(value: colorName)), args: svg->color(colorName).name());
594 }
595
596 if (status == Svg::Status::Selected) {
597 cachedSelectedSvgStyleSheets.insert(key: colorSet, value: stylesheet);
598 } else if (status == Svg::Status::Inactive) {
599 cachedInactiveSvgStyleSheets.insert(key: colorSet, value: stylesheet);
600 } else {
601 cachedSvgStyleSheets.insert(key: colorSet, value: stylesheet);
602 }
603 }
604
605 return stylesheet;
606}
607
608bool ImageSetPrivate::findInCache(const QString &key, QPixmap &pix, unsigned int lastModified)
609{
610 if (!useCache()) {
611 return false;
612 }
613
614 qint64 cacheLastModifiedTime = uint(pixmapCache->lastModifiedTime().toSecsSinceEpoch());
615 if (lastModified > cacheLastModifiedTime) {
616 qCDebug(LOG_KSVG) << "ImageSetPrivate::findInCache: lastModified > cacheLastModifiedTime for" << key;
617 return false;
618 }
619 #if defined(Q_OS_LINUX)
620 // If the timestamp is the UNIX epoch (0) then we compare against the boot time instead.
621 // This is notably the case on ostree based systems such as Fedora Kinoite.
622 if (lastModified == 0 && bootTime > cacheLastModifiedTime) {
623 qCDebug(LOG_KSVG) << "ImageSetPrivate::findInCache: lastModified == 0 && bootTime > cacheLastModifiedTime for" << key;
624 return false;
625 }
626 #else
627 if (lastModified == 0) {
628 qCWarning(LOG_KSVG) << "findInCache with a lastModified timestamp of 0 is deprecated";
629 return false;
630 }
631 #endif
632
633 qCDebug(LOG_KSVG) << "ImageSetPrivate::findInCache: using cache for" << key;
634 const QString id = keysToCache.value(key);
635 const auto it = pixmapsToCache.constFind(key: id);
636 if (it != pixmapsToCache.constEnd()) {
637 pix = *it;
638 return !pix.isNull();
639 }
640
641 QPixmap temp;
642 if (pixmapCache->findPixmap(key, destination: &temp) && !temp.isNull()) {
643 pix = temp;
644 return true;
645 }
646
647 return false;
648}
649
650void ImageSetPrivate::insertIntoCache(const QString &key, const QPixmap &pix)
651{
652 if (useCache()) {
653 pixmapCache->insertPixmap(key, pixmap: pix);
654 }
655}
656
657void ImageSetPrivate::insertIntoCache(const QString &key, const QPixmap &pix, const QString &id)
658{
659 if (useCache()) {
660 // Remove old key -> id mapping first
661 if (auto key = idsToCache.find(key: id); key != idsToCache.end()) {
662 keysToCache.remove(key: *key);
663 }
664 pixmapsToCache[id] = pix;
665 keysToCache[key] = id;
666 idsToCache[id] = key;
667
668 // always start timer in pixmapSaveTimer's thread
669 QMetaObject::invokeMethod(obj: pixmapSaveTimer, member: "start", c: Qt::QueuedConnection);
670 }
671}
672
673void ImageSetPrivate::setImageSetName(const QString &tempImageSetName, bool emitChanged)
674{
675 QString theme = tempImageSetName;
676 if (theme.isEmpty() || theme == imageSetName) {
677 // let's try and get the default theme at least
678 if (imageSetName.isEmpty()) {
679 theme = QLatin1String(ImageSetPrivate::defaultImageSet);
680 } else {
681 return;
682 }
683 }
684
685 KPluginMetaData data = metaDataForImageSet(basePath, theme);
686 if (!data.isValid()) {
687 data = metaDataForImageSet(basePath, QStringLiteral("default"));
688 if (!data.isValid()) {
689 return;
690 }
691
692 theme = QLatin1String(ImageSetPrivate::defaultImageSet);
693 }
694
695 // check again as ImageSetPrivate::defaultImageSet might be empty
696 if (imageSetName == theme) {
697 return;
698 }
699
700 imageSetName = theme;
701
702 // load the color scheme config
703 const QString colorsFile = QStandardPaths::locate(type: QStandardPaths::GenericDataLocation, fileName: basePath % theme % QLatin1String("/colors"));
704 // qCDebug(LOG_KSVG) << "we're going for..." << colorsFile << "*******************";
705
706 if (colorsFile.isEmpty()) {
707 colors = nullptr;
708 } else {
709 colors = KSharedConfig::openConfig(fileName: colorsFile);
710 }
711
712 colorScheme = KColorScheme(QPalette::Active, KColorScheme::Window, colors);
713 selectionColorScheme = KColorScheme(QPalette::Active, KColorScheme::Selection, colors);
714 buttonColorScheme = KColorScheme(QPalette::Active, KColorScheme::Button, colors);
715 viewColorScheme = KColorScheme(QPalette::Active, KColorScheme::View, colors);
716 complementaryColorScheme = KColorScheme(QPalette::Active, KColorScheme::Complementary, colors);
717 headerColorScheme = KColorScheme(QPalette::Active, KColorScheme::Header, colors);
718 tooltipColorScheme = KColorScheme(QPalette::Active, KColorScheme::Tooltip, colors);
719
720 pluginMetaData = metaDataForImageSet(basePath, theme);
721 KSharedConfigPtr metadata = configForImageSet(basePath, theme);
722
723 KConfigGroup cg(metadata, QStringLiteral("Settings"));
724 QString fallback = cg.readEntry(key: "FallbackImageSet", aDefault: QString());
725
726 fallbackImageSets.clear();
727 while (!fallback.isEmpty() && !fallbackImageSets.contains(str: fallback)) {
728 fallbackImageSets.append(t: fallback);
729
730 KSharedConfigPtr metadata = configForImageSet(basePath, theme: fallback);
731 KConfigGroup cg(metadata, QStringLiteral("Settings"));
732 fallback = cg.readEntry(key: "FallbackImageSet", aDefault: QString());
733 }
734
735 if (!fallbackImageSets.contains(str: QLatin1String(ImageSetPrivate::defaultImageSet))) {
736 fallbackImageSets.append(t: QLatin1String(ImageSetPrivate::defaultImageSet));
737 }
738
739 // Check for what Plasma version the theme has been done
740 // There are some behavioral differences between KDE4 Plasma and Plasma 5
741 const QString apiVersion = pluginMetaData.value(QStringLiteral("X-Plasma-API"));
742 apiMajor = 1;
743 apiMinor = 0;
744 apiRevision = 0;
745 if (!apiVersion.isEmpty()) {
746 const QList<QStringView> parts = QStringView(apiVersion).split(sep: QLatin1Char('.'));
747 if (!parts.isEmpty()) {
748 apiMajor = parts.value(i: 0).toInt();
749 }
750 if (parts.count() > 1) {
751 apiMinor = parts.value(i: 1).toInt();
752 }
753 if (parts.count() > 2) {
754 apiRevision = parts.value(i: 2).toInt();
755 }
756 }
757
758 if (emitChanged) {
759 scheduleImageSetChangeNotification(caches: PixmapCache | SvgElementsCache);
760 }
761}
762
763bool ImageSetPrivate::eventFilter(QObject *watched, QEvent *event)
764{
765 if (watched == QCoreApplication::instance()) {
766 if (event->type() == QEvent::ApplicationPaletteChange) {
767 colorsChanged();
768 }
769 }
770 return QObject::eventFilter(watched, event);
771}
772}
773
774#include "moc_imageset_p.cpp"
775

source code of ksvg/src/ksvg/private/imageset_p.cpp