1 | /* |
2 | * Copyright 2006-2007 Aaron Seigo <aseigo@kde.org> |
3 | * |
4 | * This program is free software; you can redistribute it and/or modify |
5 | * it under the terms of the GNU Library General Public License as |
6 | * published by the Free Software Foundation; either version 2, or |
7 | * (at your option) any later version. |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details |
13 | * |
14 | * You should have received a copy of the GNU Library General Public |
15 | * License along with this program; if not, write to the |
16 | * Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | */ |
19 | |
20 | #include "theme.h" |
21 | |
22 | #include <QApplication> |
23 | #include <QFile> |
24 | #include <QFileInfo> |
25 | #include <QMutableListIterator> |
26 | #include <QPair> |
27 | #include <QStringBuilder> |
28 | #include <QTimer> |
29 | #ifdef Q_WS_X11 |
30 | #include <QX11Info> |
31 | #include "private/effectwatcher_p.h" |
32 | #endif |
33 | |
34 | #include <kcolorscheme.h> |
35 | #include <kcomponentdata.h> |
36 | #include <kconfiggroup.h> |
37 | #include <kdebug.h> |
38 | #include <kdirwatch.h> |
39 | #include <kglobal.h> |
40 | #include <kglobalsettings.h> |
41 | #include <kmanagerselection.h> |
42 | #include <kimagecache.h> |
43 | #include <ksharedconfig.h> |
44 | #include <kstandarddirs.h> |
45 | #include <kwindowsystem.h> |
46 | |
47 | |
48 | #include "animations/animationscriptengine_p.h" |
49 | #include "libplasma-theme-global.h" |
50 | #include "private/packages_p.h" |
51 | #include "windoweffects.h" |
52 | |
53 | namespace Plasma |
54 | { |
55 | |
56 | //NOTE: Default wallpaper can be set from the theme configuration |
57 | #define DEFAULT_WALLPAPER_THEME "default" |
58 | #define DEFAULT_WALLPAPER_SUFFIX ".png" |
59 | static const int DEFAULT_WALLPAPER_WIDTH = 1920; |
60 | static const int DEFAULT_WALLPAPER_HEIGHT = 1200; |
61 | |
62 | enum styles { |
63 | DEFAULTSTYLE, |
64 | SVGSTYLE |
65 | }; |
66 | |
67 | enum CacheType { |
68 | NoCache = 0, |
69 | PixmapCache = 1, |
70 | SvgElementsCache = 2 |
71 | }; |
72 | Q_DECLARE_FLAGS(CacheTypes, CacheType) |
73 | Q_DECLARE_OPERATORS_FOR_FLAGS(CacheTypes) |
74 | |
75 | class ThemePrivate |
76 | { |
77 | public: |
78 | ThemePrivate(Theme *theme) |
79 | : q(theme), |
80 | colorScheme(QPalette::Active, KColorScheme::Window, KSharedConfigPtr(0)), |
81 | buttonColorScheme(QPalette::Active, KColorScheme::Button, KSharedConfigPtr(0)), |
82 | viewColorScheme(QPalette::Active, KColorScheme::View, KSharedConfigPtr(0)), |
83 | defaultWallpaperTheme(DEFAULT_WALLPAPER_THEME), |
84 | defaultWallpaperSuffix(DEFAULT_WALLPAPER_SUFFIX), |
85 | defaultWallpaperWidth(DEFAULT_WALLPAPER_WIDTH), |
86 | defaultWallpaperHeight(DEFAULT_WALLPAPER_HEIGHT), |
87 | pixmapCache(0), |
88 | cachesToDiscard(NoCache), |
89 | locolor(false), |
90 | compositingActive(KWindowSystem::self()->compositingActive()), |
91 | blurActive(false), |
92 | isDefault(false), |
93 | useGlobal(true), |
94 | hasWallpapers(false), |
95 | useNativeWidgetStyle(false) |
96 | { |
97 | generalFont = QApplication::font(); |
98 | ThemeConfig config; |
99 | cacheTheme = config.cacheTheme(); |
100 | |
101 | saveTimer = new QTimer(q); |
102 | saveTimer->setSingleShot(true); |
103 | saveTimer->setInterval(600); |
104 | QObject::connect(saveTimer, SIGNAL(timeout()), q, SLOT(scheduledCacheUpdate())); |
105 | |
106 | updateNotificationTimer = new QTimer(q); |
107 | updateNotificationTimer->setSingleShot(true); |
108 | updateNotificationTimer->setInterval(500); |
109 | QObject::connect(updateNotificationTimer, SIGNAL(timeout()), q, SLOT(notifyOfChanged())); |
110 | |
111 | if (QPixmap::defaultDepth() > 8) { |
112 | QObject::connect(KWindowSystem::self(), SIGNAL(compositingChanged(bool)), q, SLOT(compositingChanged(bool))); |
113 | #ifdef Q_WS_X11 |
114 | //watch for blur effect property changes as well |
115 | if (!s_blurEffectWatcher) { |
116 | s_blurEffectWatcher = new EffectWatcher("_KDE_NET_WM_BLUR_BEHIND_REGION" ); |
117 | } |
118 | |
119 | QObject::connect(s_blurEffectWatcher, SIGNAL(effectChanged(bool)), q, SLOT(blurBehindChanged(bool))); |
120 | #endif |
121 | } |
122 | } |
123 | |
124 | ~ThemePrivate() |
125 | { |
126 | delete pixmapCache; |
127 | } |
128 | |
129 | KConfigGroup &config() |
130 | { |
131 | if (!cfg.isValid()) { |
132 | QString groupName = "Theme" ; |
133 | |
134 | if (!useGlobal) { |
135 | QString app = KGlobal::mainComponent().componentName(); |
136 | |
137 | if (!app.isEmpty()) { |
138 | kDebug() << "using theme for app" << app; |
139 | groupName.append("-" ).append(app); |
140 | } |
141 | } |
142 | |
143 | cfg = KConfigGroup(KSharedConfig::openConfig(themeRcFile), groupName); |
144 | } |
145 | |
146 | return cfg; |
147 | } |
148 | |
149 | QString findInTheme(const QString &image, const QString &theme, bool cache = true); |
150 | void compositingChanged(bool active); |
151 | void discardCache(CacheTypes caches); |
152 | void scheduledCacheUpdate(); |
153 | void scheduleThemeChangeNotification(CacheTypes caches); |
154 | void notifyOfChanged(); |
155 | void colorsChanged(); |
156 | void blurBehindChanged(bool blur); |
157 | bool useCache(); |
158 | void settingsFileChanged(const QString &); |
159 | void setThemeName(const QString &themeName, bool writeSettings); |
160 | void onAppExitCleanup(); |
161 | void processWallpaperSettings(KConfigBase *metadata); |
162 | void processAnimationSettings(const QString &theme, KConfigBase *metadata); |
163 | |
164 | const QString processStyleSheet(const QString &css); |
165 | |
166 | static const char *defaultTheme; |
167 | static const char *systemColorsTheme; |
168 | static const char *themeRcFile; |
169 | static PackageStructure::Ptr packageStructure; |
170 | #ifdef Q_WS_X11 |
171 | static EffectWatcher *s_blurEffectWatcher; |
172 | #endif |
173 | |
174 | Theme *q; |
175 | QString themeName; |
176 | QList<QString> fallbackThemes; |
177 | KSharedConfigPtr colors; |
178 | KColorScheme colorScheme; |
179 | KColorScheme buttonColorScheme; |
180 | KColorScheme viewColorScheme; |
181 | KConfigGroup cfg; |
182 | QFont generalFont; |
183 | QString defaultWallpaperTheme; |
184 | QString defaultWallpaperSuffix; |
185 | int defaultWallpaperWidth; |
186 | int defaultWallpaperHeight; |
187 | KImageCache *pixmapCache; |
188 | KSharedConfigPtr svgElementsCache; |
189 | QHash<QString, QSet<QString> > invalidElements; |
190 | QHash<QString, QPixmap> pixmapsToCache; |
191 | QHash<QString, QString> keysToCache; |
192 | QHash<QString, QString> idsToCache; |
193 | QHash<QString, QString> animationMapping; |
194 | QHash<styles, QString> cachedStyleSheets; |
195 | QHash<QString, QString> discoveries; |
196 | QTimer *saveTimer; |
197 | QTimer *updateNotificationTimer; |
198 | int toolTipDelay; |
199 | CacheTypes cachesToDiscard; |
200 | QString themeVersion; |
201 | QString themeMetadataPath; |
202 | |
203 | bool locolor : 1; |
204 | bool compositingActive : 1; |
205 | bool blurActive : 1; |
206 | bool isDefault : 1; |
207 | bool useGlobal : 1; |
208 | bool hasWallpapers : 1; |
209 | bool cacheTheme : 1; |
210 | bool useNativeWidgetStyle :1; |
211 | }; |
212 | |
213 | PackageStructure::Ptr ThemePrivate::packageStructure(0); |
214 | const char *ThemePrivate::defaultTheme = "default" ; |
215 | |
216 | const char *ThemePrivate::themeRcFile = "plasmarc" ; |
217 | // the system colors theme is used to cache unthemed svgs with colorization needs |
218 | // these svgs do not follow the theme's colors, but rather the system colors |
219 | const char *ThemePrivate::systemColorsTheme = "internal-system-colors" ; |
220 | #ifdef Q_WS_X11 |
221 | EffectWatcher *ThemePrivate::s_blurEffectWatcher = 0; |
222 | #endif |
223 | |
224 | bool ThemePrivate::useCache() |
225 | { |
226 | bool cachesTooOld = false; |
227 | |
228 | if (cacheTheme && !pixmapCache) { |
229 | const bool isRegularTheme = themeName != systemColorsTheme; |
230 | QString cacheFile = "plasma_theme_" + themeName; |
231 | |
232 | // clear any cached values from the previous theme cache |
233 | themeVersion.clear(); |
234 | |
235 | if (!themeMetadataPath.isEmpty()) { |
236 | KDirWatch::self()->removeFile(themeMetadataPath); |
237 | } |
238 | themeMetadataPath = KStandardDirs::locate("data" , "desktoptheme/" + themeName + "/metadata.desktop" ); |
239 | |
240 | if (isRegularTheme) { |
241 | const QString cacheFileBase = cacheFile + "*.kcache" ; |
242 | |
243 | // if the path is empty, then we haven't found the theme and so |
244 | // we will leave currentCacheFileName empty, resulting in the deletion of |
245 | // *all* matching cache files |
246 | QString currentCacheFileName; |
247 | if (!themeMetadataPath.isEmpty()) { |
248 | // now we record the theme version, if we can |
249 | const KPluginInfo pluginInfo(themeMetadataPath); |
250 | themeVersion = pluginInfo.version(); |
251 | if (!themeVersion.isEmpty()) { |
252 | cacheFile += "_v" + themeVersion; |
253 | currentCacheFileName = cacheFile + ".kcache" ; |
254 | } |
255 | |
256 | // watch the metadata file for changes at runtime |
257 | KDirWatch::self()->addFile(themeMetadataPath); |
258 | QObject::connect(KDirWatch::self(), SIGNAL(created(QString)), |
259 | q, SLOT(settingsFileChanged(QString)), |
260 | Qt::UniqueConnection); |
261 | QObject::connect(KDirWatch::self(), SIGNAL(dirty(QString)), |
262 | q, SLOT(settingsFileChanged(QString)), |
263 | Qt::UniqueConnection); |
264 | } |
265 | |
266 | // now we check for (and remove) old caches |
267 | foreach (const QString &file, KGlobal::dirs()->findAllResources("cache" , cacheFileBase)) { |
268 | if (currentCacheFileName.isEmpty() || |
269 | !file.endsWith(currentCacheFileName)) { |
270 | QFile::remove(file); |
271 | } |
272 | } |
273 | } |
274 | |
275 | // now we do a sanity check: if the metadata.desktop file is newer than the cache, drop |
276 | // the cache |
277 | if (isRegularTheme && !themeMetadataPath.isEmpty()) { |
278 | // now we check to see if the theme metadata file itself is newer than the pixmap cache |
279 | // this is done before creating the pixmapCache object since that can change the mtime |
280 | // on the cache file |
281 | |
282 | // FIXME: when using the system colors, if they change while the application is not running |
283 | // the cache should be dropped; we need a way to detect system color change when the |
284 | // application is not running. |
285 | // check for expired cache |
286 | const QString cacheFilePath = KStandardDirs::locateLocal("cache" , cacheFile); |
287 | if (!cacheFilePath.isEmpty()) { |
288 | const QFileInfo cacheFileInfo(cacheFilePath); |
289 | const QFileInfo metadataFileInfo(themeMetadataPath); |
290 | cachesTooOld = cacheFileInfo.lastModified().toTime_t() > metadataFileInfo.lastModified().toTime_t(); |
291 | } |
292 | } |
293 | |
294 | ThemeConfig config; |
295 | pixmapCache = new KImageCache(cacheFile, config.themeCacheKb() * 1024); |
296 | |
297 | if (cachesTooOld) { |
298 | discardCache(PixmapCache | SvgElementsCache); |
299 | } |
300 | } |
301 | |
302 | if (cacheTheme && !svgElementsCache) { |
303 | const QString svgElementsFileNameBase = "plasma-svgelements-" + themeName; |
304 | QString svgElementsFileName = svgElementsFileNameBase; |
305 | if (!themeVersion.isEmpty()) { |
306 | svgElementsFileName += "_v" + themeVersion; |
307 | } |
308 | |
309 | // now we check for (and remove) old caches |
310 | foreach (const QString &file, KGlobal::dirs()->findAllResources("cache" , svgElementsFileNameBase + "*" )) { |
311 | if (cachesTooOld || !file.endsWith(svgElementsFileName)) { |
312 | QFile::remove(file); |
313 | } |
314 | } |
315 | |
316 | const QString svgElementsFile = KStandardDirs::locateLocal("cache" , svgElementsFileName); |
317 | svgElementsCache = KSharedConfig::openConfig(svgElementsFile); |
318 | } |
319 | |
320 | return cacheTheme; |
321 | } |
322 | |
323 | void ThemePrivate::onAppExitCleanup() |
324 | { |
325 | pixmapsToCache.clear(); |
326 | delete pixmapCache; |
327 | pixmapCache = 0; |
328 | cacheTheme = false; |
329 | } |
330 | |
331 | QString ThemePrivate::findInTheme(const QString &image, const QString &theme, bool cache) |
332 | { |
333 | if (cache && discoveries.contains(image)) { |
334 | return discoveries[image]; |
335 | } |
336 | |
337 | QString search; |
338 | |
339 | if (locolor) { |
340 | search = QLatin1Literal("desktoptheme/" ) % theme % QLatin1Literal("/locolor/" ) % image; |
341 | search = KStandardDirs::locate("data" , search); |
342 | } else if (!compositingActive) { |
343 | search = QLatin1Literal("desktoptheme/" ) % theme % QLatin1Literal("/opaque/" ) % image; |
344 | search = KStandardDirs::locate("data" , search); |
345 | } else if (WindowEffects::isEffectAvailable(WindowEffects::BlurBehind)) { |
346 | search = QLatin1Literal("desktoptheme/" ) % theme % QLatin1Literal("/translucent/" ) % image; |
347 | search = KStandardDirs::locate("data" , search); |
348 | } |
349 | |
350 | //not found or compositing enabled |
351 | if (search.isEmpty()) { |
352 | search = QLatin1Literal("desktoptheme/" ) % theme % QLatin1Char('/') % image; |
353 | search = KStandardDirs::locate("data" , search); |
354 | } |
355 | |
356 | if (cache && !search.isEmpty()) { |
357 | discoveries.insert(image, search); |
358 | } |
359 | |
360 | return search; |
361 | } |
362 | |
363 | void ThemePrivate::compositingChanged(bool active) |
364 | { |
365 | #ifdef Q_WS_X11 |
366 | if (compositingActive != active) { |
367 | compositingActive = active; |
368 | //kDebug() << QTime::currentTime(); |
369 | scheduleThemeChangeNotification(PixmapCache | SvgElementsCache); |
370 | } |
371 | #endif |
372 | } |
373 | |
374 | void ThemePrivate::discardCache(CacheTypes caches) |
375 | { |
376 | if (caches & PixmapCache) { |
377 | pixmapsToCache.clear(); |
378 | saveTimer->stop(); |
379 | if (pixmapCache) { |
380 | pixmapCache->clear(); |
381 | } |
382 | } else { |
383 | // This deletes the object but keeps the on-disk cache for later use |
384 | delete pixmapCache; |
385 | pixmapCache = 0; |
386 | } |
387 | |
388 | cachedStyleSheets.clear(); |
389 | |
390 | if (caches & SvgElementsCache) { |
391 | discoveries.clear(); |
392 | invalidElements.clear(); |
393 | svgElementsCache = 0; |
394 | } |
395 | } |
396 | |
397 | void ThemePrivate::scheduledCacheUpdate() |
398 | { |
399 | if (useCache()) { |
400 | QHashIterator<QString, QPixmap> it(pixmapsToCache); |
401 | while (it.hasNext()) { |
402 | it.next(); |
403 | pixmapCache->insertPixmap(idsToCache[it.key()], it.value()); |
404 | } |
405 | } |
406 | |
407 | pixmapsToCache.clear(); |
408 | keysToCache.clear(); |
409 | idsToCache.clear(); |
410 | } |
411 | |
412 | void ThemePrivate::colorsChanged() |
413 | { |
414 | colorScheme = KColorScheme(QPalette::Active, KColorScheme::Window, colors); |
415 | buttonColorScheme = KColorScheme(QPalette::Active, KColorScheme::Button, colors); |
416 | viewColorScheme = KColorScheme(QPalette::Active, KColorScheme::View, colors); |
417 | scheduleThemeChangeNotification(PixmapCache); |
418 | } |
419 | |
420 | void ThemePrivate::blurBehindChanged(bool blur) |
421 | { |
422 | if (blurActive != blur) { |
423 | blurActive = blur; |
424 | scheduleThemeChangeNotification(PixmapCache | SvgElementsCache); |
425 | } |
426 | } |
427 | |
428 | void ThemePrivate::scheduleThemeChangeNotification(CacheTypes caches) |
429 | { |
430 | cachesToDiscard |= caches; |
431 | updateNotificationTimer->start(); |
432 | } |
433 | |
434 | void ThemePrivate::notifyOfChanged() |
435 | { |
436 | //kDebug() << cachesToDiscard; |
437 | discardCache(cachesToDiscard); |
438 | cachesToDiscard = NoCache; |
439 | emit q->themeChanged(); |
440 | } |
441 | |
442 | const QString ThemePrivate::processStyleSheet(const QString &css) |
443 | { |
444 | QString stylesheet; |
445 | if (css.isEmpty()) { |
446 | stylesheet = cachedStyleSheets.value(DEFAULTSTYLE); |
447 | if (stylesheet.isEmpty()) { |
448 | stylesheet = QString("\n\ |
449 | body {\n\ |
450 | color: %textcolor;\n\ |
451 | font-size: %fontsize;\n\ |
452 | font-family: %fontfamily;\n\ |
453 | }\n\ |
454 | a:active { color: %activatedlink; }\n\ |
455 | a:link { color: %link; }\n\ |
456 | a:visited { color: %visitedlink; }\n\ |
457 | a:hover { color: %hoveredlink; text-decoration: none; }\n\ |
458 | " ); |
459 | stylesheet = processStyleSheet(stylesheet); |
460 | cachedStyleSheets.insert(DEFAULTSTYLE, stylesheet); |
461 | } |
462 | |
463 | return stylesheet; |
464 | } else if (css == "SVG" ) { |
465 | stylesheet = cachedStyleSheets.value(SVGSTYLE); |
466 | if (stylesheet.isEmpty()) { |
467 | QString skel = ".ColorScheme-%1{color:%2;}" ; |
468 | |
469 | stylesheet += skel.arg("Text" ,"%textcolor" ); |
470 | stylesheet += skel.arg("Background" ,"%backgroundcolor" ); |
471 | |
472 | stylesheet += skel.arg("ButtonText" ,"%buttontextcolor" ); |
473 | stylesheet += skel.arg("ButtonBackground" ,"%buttonbackgroundcolor" ); |
474 | stylesheet += skel.arg("ButtonHover" ,"%buttonhovercolor" ); |
475 | stylesheet += skel.arg("ButtonFocus" ,"%buttonfocuscolor" ); |
476 | |
477 | stylesheet += skel.arg("ViewText" ,"%viewtextcolor" ); |
478 | stylesheet += skel.arg("ViewBackground" ,"%viewbackgroundcolor" ); |
479 | stylesheet += skel.arg("ViewHover" ,"%viewhovercolor" ); |
480 | stylesheet += skel.arg("ViewFocus" ,"%viewfocuscolor" ); |
481 | |
482 | stylesheet = processStyleSheet(stylesheet); |
483 | cachedStyleSheets.insert(SVGSTYLE, stylesheet); |
484 | } |
485 | |
486 | return stylesheet; |
487 | } else { |
488 | stylesheet = css; |
489 | } |
490 | |
491 | QHash<QString, QString> elements; |
492 | // If you add elements here, make sure their names are sufficiently unique to not cause |
493 | // clashes between element keys |
494 | elements["%textcolor" ] = q->color(Theme::TextColor).name(); |
495 | elements["%backgroundcolor" ] = q->color(Theme::BackgroundColor).name(); |
496 | elements["%visitedlink" ] = q->color(Theme::VisitedLinkColor).name(); |
497 | elements["%activatedlink" ] = q->color(Theme::HighlightColor).name(); |
498 | elements["%hoveredlink" ] = q->color(Theme::HighlightColor).name(); |
499 | elements["%link" ] = q->color(Theme::LinkColor).name(); |
500 | elements["%buttontextcolor" ] = q->color(Theme::ButtonTextColor).name(); |
501 | elements["%buttonbackgroundcolor" ] = q->color(Theme::ButtonBackgroundColor).name(); |
502 | elements["%buttonhovercolor" ] = q->color(Theme::ButtonHoverColor).name(); |
503 | elements["%buttonfocuscolor" ] = q->color(Theme::ButtonFocusColor).name(); |
504 | elements["%viewtextcolor" ] = q->color(Theme::ViewTextColor).name(); |
505 | elements["%viewbackgroundcolor" ] = q->color(Theme::ViewBackgroundColor).name(); |
506 | elements["%viewhovercolor" ] = q->color(Theme::ViewHoverColor).name(); |
507 | elements["%viewfocuscolor" ] = q->color(Theme::ViewFocusColor).name(); |
508 | |
509 | QFont font = q->font(Theme::DefaultFont); |
510 | elements["%fontsize" ] = QString("%1pt" ).arg(font.pointSize()); |
511 | elements["%fontfamily" ] = font.family().split('[').first(); |
512 | elements["%smallfontsize" ] = QString("%1pt" ).arg(KGlobalSettings::smallestReadableFont().pointSize()); |
513 | |
514 | QHash<QString, QString>::const_iterator it = elements.constBegin(); |
515 | QHash<QString, QString>::const_iterator itEnd = elements.constEnd(); |
516 | for ( ; it != itEnd; ++it) { |
517 | stylesheet.replace(it.key(), it.value()); |
518 | } |
519 | return stylesheet; |
520 | } |
521 | |
522 | class ThemeSingleton |
523 | { |
524 | public: |
525 | ThemeSingleton() |
526 | { |
527 | self.d->isDefault = true; |
528 | |
529 | //FIXME: if/when kconfig gets change notification, this will be unnecessary |
530 | KDirWatch::self()->addFile(KStandardDirs::locateLocal("config" , ThemePrivate::themeRcFile)); |
531 | QObject::connect(KDirWatch::self(), SIGNAL(created(QString)), |
532 | &self, SLOT(settingsFileChanged(QString)), |
533 | Qt::UniqueConnection); |
534 | QObject::connect(KDirWatch::self(), SIGNAL(dirty(QString)), |
535 | &self, SLOT(settingsFileChanged(QString)), |
536 | Qt::UniqueConnection); |
537 | } |
538 | |
539 | Theme self; |
540 | }; |
541 | |
542 | K_GLOBAL_STATIC(ThemeSingleton, privateThemeSelf) |
543 | |
544 | Theme *Theme::defaultTheme() |
545 | { |
546 | return &privateThemeSelf->self; |
547 | } |
548 | |
549 | Theme::Theme(QObject *parent) |
550 | : QObject(parent), |
551 | d(new ThemePrivate(this)) |
552 | { |
553 | settingsChanged(); |
554 | if (QCoreApplication::instance()) { |
555 | connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), |
556 | this, SLOT(onAppExitCleanup())); |
557 | } |
558 | } |
559 | |
560 | Theme::Theme(const QString &themeName, QObject *parent) |
561 | : QObject(parent), |
562 | d(new ThemePrivate(this)) |
563 | { |
564 | // turn off caching so we don't accidently trigger unnecessary disk activity at this point |
565 | bool useCache = d->cacheTheme; |
566 | d->cacheTheme = false; |
567 | setThemeName(themeName); |
568 | d->cacheTheme = useCache; |
569 | if (QCoreApplication::instance()) { |
570 | connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), |
571 | this, SLOT(onAppExitCleanup())); |
572 | } |
573 | } |
574 | |
575 | Theme::~Theme() |
576 | { |
577 | if (d->svgElementsCache) { |
578 | QHashIterator<QString, QSet<QString> > it(d->invalidElements); |
579 | while (it.hasNext()) { |
580 | it.next(); |
581 | KConfigGroup imageGroup(d->svgElementsCache, it.key()); |
582 | imageGroup.writeEntry("invalidElements" , it.value().toList()); //FIXME: add QSet support to KConfig |
583 | } |
584 | } |
585 | |
586 | d->onAppExitCleanup(); |
587 | delete d; |
588 | } |
589 | |
590 | PackageStructure::Ptr Theme::packageStructure() |
591 | { |
592 | if (!ThemePrivate::packageStructure) { |
593 | ThemePrivate::packageStructure = new ThemePackage(); |
594 | } |
595 | |
596 | return ThemePrivate::packageStructure; |
597 | } |
598 | |
599 | KPluginInfo::List Theme::listThemeInfo() |
600 | { |
601 | const QStringList themes = KGlobal::dirs()->findAllResources("data" , "desktoptheme/*/metadata.desktop" , |
602 | KStandardDirs::NoDuplicates); |
603 | return KPluginInfo::fromFiles(themes); |
604 | } |
605 | |
606 | void ThemePrivate::settingsFileChanged(const QString &file) |
607 | { |
608 | if (file == themeMetadataPath) { |
609 | const KPluginInfo pluginInfo(themeMetadataPath); |
610 | if (themeVersion != pluginInfo.version()) { |
611 | scheduleThemeChangeNotification(SvgElementsCache); |
612 | } |
613 | } else if (file.endsWith(themeRcFile)) { |
614 | config().config()->reparseConfiguration(); |
615 | q->settingsChanged(); |
616 | } |
617 | } |
618 | |
619 | void Theme::settingsChanged() |
620 | { |
621 | KConfigGroup cg = d->config(); |
622 | d->setThemeName(cg.readEntry("name" , ThemePrivate::defaultTheme), false); |
623 | cg = KConfigGroup(cg.config(), "PlasmaToolTips" ); |
624 | d->toolTipDelay = cg.readEntry("Delay" , 700); |
625 | } |
626 | |
627 | void Theme::setThemeName(const QString &themeName) |
628 | { |
629 | d->setThemeName(themeName, true); |
630 | } |
631 | |
632 | void ThemePrivate::processWallpaperSettings(KConfigBase *metadata) |
633 | { |
634 | if (!defaultWallpaperTheme.isEmpty() && defaultWallpaperTheme != DEFAULT_WALLPAPER_THEME) { |
635 | return; |
636 | } |
637 | |
638 | KConfigGroup cg; |
639 | if (metadata->hasGroup("Wallpaper" )) { |
640 | // we have a theme color config, so let's also check to see if |
641 | // there is a wallpaper defined in there. |
642 | cg = KConfigGroup(metadata, "Wallpaper" ); |
643 | } else { |
644 | // since we didn't find an entry in the theme, let's look in the main |
645 | // theme config |
646 | cg = config(); |
647 | } |
648 | |
649 | defaultWallpaperTheme = cg.readEntry("defaultWallpaperTheme" , DEFAULT_WALLPAPER_THEME); |
650 | defaultWallpaperSuffix = cg.readEntry("defaultFileSuffix" , DEFAULT_WALLPAPER_SUFFIX); |
651 | defaultWallpaperWidth = cg.readEntry("defaultWidth" , DEFAULT_WALLPAPER_WIDTH); |
652 | defaultWallpaperHeight = cg.readEntry("defaultHeight" , DEFAULT_WALLPAPER_HEIGHT); |
653 | } |
654 | |
655 | void ThemePrivate::processAnimationSettings(const QString &theme, KConfigBase *metadata) |
656 | { |
657 | KConfigGroup cg(metadata, "Animations" ); |
658 | const QString animDir = QLatin1Literal("desktoptheme/" ) % theme % QLatin1Literal("/animations/" ); |
659 | foreach (const QString &path, cg.keyList()) { |
660 | const QStringList anims = cg.readEntry(path, QStringList()); |
661 | foreach (const QString &anim, anims) { |
662 | if (!animationMapping.contains(anim)) { |
663 | kDebug() << "Registering animation. animDir: " << animDir |
664 | << "\tanim: " << anim |
665 | << "\tpath: " << path << "\t*******\n\n\n" ; |
666 | //key: desktoptheme/default/animations/+ all.js |
667 | //value: ZoomAnimation |
668 | animationMapping.insert(anim, animDir % path); |
669 | } else { |
670 | kDebug() << "************Animation already registered!\n\n\n" ; |
671 | } |
672 | } |
673 | } |
674 | |
675 | } |
676 | |
677 | void ThemePrivate::setThemeName(const QString &tempThemeName, bool writeSettings) |
678 | { |
679 | //kDebug() << tempThemeName; |
680 | QString theme = tempThemeName; |
681 | if (theme.isEmpty() || theme == themeName) { |
682 | // let's try and get the default theme at least |
683 | if (themeName.isEmpty()) { |
684 | theme = ThemePrivate::defaultTheme; |
685 | } else { |
686 | return; |
687 | } |
688 | } |
689 | |
690 | // we have one special theme: essentially a dummy theme used to cache things with |
691 | // the system colors. |
692 | bool realTheme = theme != systemColorsTheme; |
693 | if (realTheme) { |
694 | QString themePath = KStandardDirs::locate("data" , QLatin1Literal("desktoptheme/" ) % theme % QLatin1Char('/')); |
695 | if (themePath.isEmpty() && themeName.isEmpty()) { |
696 | themePath = KStandardDirs::locate("data" , "desktoptheme/default/" ); |
697 | |
698 | if (themePath.isEmpty()) { |
699 | return; |
700 | } |
701 | |
702 | theme = ThemePrivate::defaultTheme; |
703 | } |
704 | } |
705 | |
706 | // check again as ThemePrivate::defaultTheme might be empty |
707 | if (themeName == theme) { |
708 | return; |
709 | } |
710 | |
711 | themeName = theme; |
712 | |
713 | // load the color scheme config |
714 | const QString colorsFile = realTheme ? KStandardDirs::locate("data" , QLatin1Literal("desktoptheme/" ) % theme % QLatin1Literal("/colors" )) |
715 | : QString(); |
716 | |
717 | //kDebug() << "we're going for..." << colorsFile << "*******************"; |
718 | |
719 | // load the wallpaper settings, if any |
720 | if (realTheme) { |
721 | const QString metadataPath(KStandardDirs::locate("data" , QLatin1Literal("desktoptheme/" ) % theme % QLatin1Literal("/metadata.desktop" ))); |
722 | KConfig metadata(metadataPath); |
723 | |
724 | processWallpaperSettings(&metadata); |
725 | |
726 | AnimationScriptEngine::clearAnimations(); |
727 | animationMapping.clear(); |
728 | processAnimationSettings(themeName, &metadata); |
729 | |
730 | KConfigGroup cg(&metadata, "Settings" ); |
731 | useNativeWidgetStyle = cg.readEntry("UseNativeWidgetStyle" , false); |
732 | QString fallback = cg.readEntry("FallbackTheme" , QString()); |
733 | |
734 | fallbackThemes.clear(); |
735 | while (!fallback.isEmpty() && !fallbackThemes.contains(fallback)) { |
736 | fallbackThemes.append(fallback); |
737 | |
738 | QString metadataPath(KStandardDirs::locate("data" , QLatin1Literal("desktoptheme/" ) % theme % QLatin1Literal("/metadata.desktop" ))); |
739 | KConfig metadata(metadataPath); |
740 | KConfigGroup cg(&metadata, "Settings" ); |
741 | fallback = cg.readEntry("FallbackTheme" , QString()); |
742 | } |
743 | |
744 | if (!fallbackThemes.contains("oxygen" )) { |
745 | fallbackThemes.append("oxygen" ); |
746 | } |
747 | |
748 | if (!fallbackThemes.contains(ThemePrivate::defaultTheme)) { |
749 | fallbackThemes.append(ThemePrivate::defaultTheme); |
750 | } |
751 | |
752 | foreach (const QString &theme, fallbackThemes) { |
753 | QString metadataPath(KStandardDirs::locate("data" , QLatin1Literal("desktoptheme/" ) % theme % QLatin1Literal("/metadata.desktop" ))); |
754 | KConfig metadata(metadataPath); |
755 | processAnimationSettings(theme, &metadata); |
756 | processWallpaperSettings(&metadata); |
757 | } |
758 | } |
759 | |
760 | if (colorsFile.isEmpty()) { |
761 | colors = 0; |
762 | QObject::connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), |
763 | q, SLOT(colorsChanged()), Qt::UniqueConnection); |
764 | } else { |
765 | QObject::disconnect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), |
766 | q, SLOT(colorsChanged())); |
767 | colors = KSharedConfig::openConfig(colorsFile); |
768 | } |
769 | |
770 | colorScheme = KColorScheme(QPalette::Active, KColorScheme::Window, colors); |
771 | buttonColorScheme = KColorScheme(QPalette::Active, KColorScheme::Button, colors); |
772 | viewColorScheme = KColorScheme(QPalette::Active, KColorScheme::View, colors); |
773 | hasWallpapers = KStandardDirs::exists(KStandardDirs::locateLocal("data" , QLatin1Literal("desktoptheme/" ) % theme % QLatin1Literal("/wallpapers/" ))); |
774 | |
775 | if (realTheme && isDefault && writeSettings) { |
776 | // we're the default theme, let's save our state |
777 | KConfigGroup &cg = config(); |
778 | if (ThemePrivate::defaultTheme == themeName) { |
779 | cg.deleteEntry("name" ); |
780 | } else { |
781 | cg.writeEntry("name" , themeName); |
782 | } |
783 | cg.sync(); |
784 | } |
785 | |
786 | scheduleThemeChangeNotification(SvgElementsCache); |
787 | } |
788 | |
789 | QString Theme::themeName() const |
790 | { |
791 | return d->themeName; |
792 | } |
793 | |
794 | QString Theme::imagePath(const QString &name) const |
795 | { |
796 | // look for a compressed svg file in the theme |
797 | if (name.contains("../" ) || name.isEmpty()) { |
798 | // we don't support relative paths |
799 | //kDebug() << "Theme says: bad image path " << name; |
800 | return QString(); |
801 | } |
802 | |
803 | const QString svgzName = name % QLatin1Literal(".svgz" ); |
804 | QString path = d->findInTheme(svgzName, d->themeName); |
805 | |
806 | if (path.isEmpty()) { |
807 | // try for an uncompressed svg file |
808 | const QString svgName = name % QLatin1Literal(".svg" ); |
809 | path = d->findInTheme(svgName, d->themeName); |
810 | |
811 | // search in fallback themes if necessary |
812 | for (int i = 0; path.isEmpty() && i < d->fallbackThemes.count(); ++i) { |
813 | if (d->themeName == d->fallbackThemes[i]) { |
814 | continue; |
815 | } |
816 | |
817 | // try a compressed svg file in the fallback theme |
818 | path = d->findInTheme(svgzName, d->fallbackThemes[i]); |
819 | |
820 | if (path.isEmpty()) { |
821 | // try an uncompressed svg file in the fallback theme |
822 | path = d->findInTheme(svgName, d->fallbackThemes[i]); |
823 | } |
824 | } |
825 | } |
826 | |
827 | /* |
828 | if (path.isEmpty()) { |
829 | kDebug() << "Theme says: bad image path " << name; |
830 | } |
831 | */ |
832 | |
833 | return path; |
834 | } |
835 | |
836 | QString Theme::styleSheet(const QString &css) const |
837 | { |
838 | return d->processStyleSheet(css); |
839 | } |
840 | |
841 | QString Theme::animationPath(const QString &name) const |
842 | { |
843 | const QString path = d->animationMapping.value(name); |
844 | if (path.isEmpty()) { |
845 | //kError() << "****** FAILED TO FIND IN MAPPING!"; |
846 | return path; |
847 | } |
848 | |
849 | return KStandardDirs::locate("data" , path); |
850 | } |
851 | |
852 | QString Theme::wallpaperPath(const QSize &size) const |
853 | { |
854 | QString fullPath; |
855 | QString image = d->defaultWallpaperTheme; |
856 | |
857 | image.append("/contents/images/%1x%2" ).append(d->defaultWallpaperSuffix); |
858 | QString defaultImage = image.arg(d->defaultWallpaperWidth).arg(d->defaultWallpaperHeight); |
859 | |
860 | if (size.isValid()) { |
861 | // try to customize the paper to the size requested |
862 | //TODO: this should do better than just fallback to the default size. |
863 | // a "best fit" matching would be far better, so we don't end |
864 | // up returning a 1920x1200 wallpaper for a 640x480 request ;) |
865 | image = image.arg(size.width()).arg(size.height()); |
866 | } else { |
867 | image = defaultImage; |
868 | } |
869 | |
870 | //TODO: the theme's wallpaper overrides regularly installed wallpapers. |
871 | // should it be possible for user installed (e.g. locateLocal) wallpapers |
872 | // to override the theme? |
873 | if (d->hasWallpapers) { |
874 | // check in the theme first |
875 | fullPath = d->findInTheme(QLatin1Literal("wallpapers/" ) % image, d->themeName); |
876 | |
877 | if (fullPath.isEmpty()) { |
878 | fullPath = d->findInTheme(QLatin1Literal("wallpapers/" ) % defaultImage, d->themeName); |
879 | } |
880 | } |
881 | |
882 | if (fullPath.isEmpty()) { |
883 | // we failed to find it in the theme, so look in the standard directories |
884 | //kDebug() << "looking for" << image; |
885 | fullPath = KStandardDirs::locate("wallpaper" , image); |
886 | } |
887 | |
888 | if (fullPath.isEmpty()) { |
889 | // we still failed to find it in the theme, so look for the default in |
890 | // the standard directories |
891 | //kDebug() << "looking for" << defaultImage; |
892 | fullPath = KStandardDirs::locate("wallpaper" , defaultImage); |
893 | |
894 | if (fullPath.isEmpty()) { |
895 | kDebug() << "exhausted every effort to find a wallpaper." ; |
896 | } |
897 | } |
898 | |
899 | return fullPath; |
900 | } |
901 | |
902 | bool Theme::currentThemeHasImage(const QString &name) const |
903 | { |
904 | if (name.contains("../" )) { |
905 | // we don't support relative paths |
906 | return false; |
907 | } |
908 | |
909 | return !(d->findInTheme(name % QLatin1Literal(".svgz" ), d->themeName, false).isEmpty()) || |
910 | !(d->findInTheme(name % QLatin1Literal(".svg" ), d->themeName, false).isEmpty()); |
911 | } |
912 | |
913 | KSharedConfigPtr Theme::colorScheme() const |
914 | { |
915 | return d->colors; |
916 | } |
917 | |
918 | QColor Theme::color(ColorRole role) const |
919 | { |
920 | switch (role) { |
921 | case TextColor: |
922 | return d->colorScheme.foreground(KColorScheme::NormalText).color(); |
923 | |
924 | case HighlightColor: |
925 | return d->colorScheme.decoration(KColorScheme::HoverColor).color(); |
926 | |
927 | case BackgroundColor: |
928 | return d->colorScheme.background(KColorScheme::NormalBackground).color(); |
929 | |
930 | case ButtonTextColor: |
931 | return d->buttonColorScheme.foreground(KColorScheme::NormalText).color(); |
932 | |
933 | case ButtonBackgroundColor: |
934 | return d->buttonColorScheme.background(KColorScheme::NormalBackground).color(); |
935 | |
936 | case ButtonHoverColor: |
937 | return d->buttonColorScheme.decoration(KColorScheme::HoverColor).color(); |
938 | |
939 | case ButtonFocusColor: |
940 | return d->buttonColorScheme.decoration(KColorScheme::FocusColor).color(); |
941 | |
942 | case ViewTextColor: |
943 | return d->viewColorScheme.foreground(KColorScheme::NormalText).color(); |
944 | |
945 | case ViewBackgroundColor: |
946 | return d->viewColorScheme.background(KColorScheme::NormalBackground).color(); |
947 | |
948 | case ViewHoverColor: |
949 | return d->viewColorScheme.decoration(KColorScheme::HoverColor).color(); |
950 | |
951 | case ViewFocusColor: |
952 | return d->viewColorScheme.decoration(KColorScheme::FocusColor).color(); |
953 | |
954 | case LinkColor: |
955 | return d->viewColorScheme.foreground(KColorScheme::LinkText).color(); |
956 | |
957 | case VisitedLinkColor: |
958 | return d->viewColorScheme.foreground(KColorScheme::VisitedText).color(); |
959 | } |
960 | |
961 | return QColor(); |
962 | } |
963 | |
964 | void Theme::setFont(const QFont &font, FontRole role) |
965 | { |
966 | Q_UNUSED(role) |
967 | d->generalFont = font; |
968 | } |
969 | |
970 | QFont Theme::font(FontRole role) const |
971 | { |
972 | switch (role) { |
973 | case DesktopFont: { |
974 | KConfigGroup cg(KGlobal::config(), "General" ); |
975 | return cg.readEntry("desktopFont" , d->generalFont); |
976 | } |
977 | break; |
978 | |
979 | case DefaultFont: |
980 | default: |
981 | return d->generalFont; |
982 | break; |
983 | |
984 | case SmallestFont: |
985 | return KGlobalSettings::smallestReadableFont(); |
986 | break; |
987 | } |
988 | |
989 | return d->generalFont; |
990 | } |
991 | |
992 | QFontMetrics Theme::fontMetrics() const |
993 | { |
994 | //TODO: allow this to be overridden with a plasma specific font? |
995 | return QFontMetrics(d->generalFont); |
996 | } |
997 | |
998 | bool Theme::windowTranslucencyEnabled() const |
999 | { |
1000 | return d->compositingActive; |
1001 | } |
1002 | |
1003 | void Theme::setUseGlobalSettings(bool useGlobal) |
1004 | { |
1005 | if (d->useGlobal == useGlobal) { |
1006 | return; |
1007 | } |
1008 | |
1009 | d->useGlobal = useGlobal; |
1010 | d->cfg = KConfigGroup(); |
1011 | d->themeName.clear(); |
1012 | settingsChanged(); |
1013 | } |
1014 | |
1015 | bool Theme::useGlobalSettings() const |
1016 | { |
1017 | return d->useGlobal; |
1018 | } |
1019 | |
1020 | bool Theme::useNativeWidgetStyle() const |
1021 | { |
1022 | return d->useNativeWidgetStyle; |
1023 | } |
1024 | |
1025 | bool Theme::findInCache(const QString &key, QPixmap &pix) |
1026 | { |
1027 | if (d->useCache()) { |
1028 | const QString id = d->keysToCache.value(key); |
1029 | if (d->pixmapsToCache.contains(id)) { |
1030 | pix = d->pixmapsToCache.value(id); |
1031 | return !pix.isNull(); |
1032 | } |
1033 | |
1034 | QPixmap temp; |
1035 | if (d->pixmapCache->findPixmap(key, &temp) && !temp.isNull()) { |
1036 | pix = temp; |
1037 | return true; |
1038 | } |
1039 | } |
1040 | |
1041 | return false; |
1042 | } |
1043 | |
1044 | // BIC FIXME: Should be merged with the other findInCache method above when we break BC |
1045 | bool Theme::findInCache(const QString &key, QPixmap &pix, unsigned int lastModified) |
1046 | { |
1047 | if (d->useCache() && lastModified > uint(d->pixmapCache->lastModifiedTime())) { |
1048 | return false; |
1049 | } |
1050 | |
1051 | return findInCache(key, pix); |
1052 | } |
1053 | |
1054 | void Theme::insertIntoCache(const QString& key, const QPixmap& pix) |
1055 | { |
1056 | if (d->useCache()) { |
1057 | d->pixmapCache->insertPixmap(key, pix); |
1058 | } |
1059 | } |
1060 | |
1061 | void Theme::insertIntoCache(const QString& key, const QPixmap& pix, const QString& id) |
1062 | { |
1063 | if (d->useCache()) { |
1064 | d->pixmapsToCache.insert(id, pix); |
1065 | |
1066 | if (d->idsToCache.contains(id)) { |
1067 | d->keysToCache.remove(d->idsToCache[id]); |
1068 | } |
1069 | |
1070 | d->keysToCache.insert(key, id); |
1071 | d->idsToCache.insert(id, key); |
1072 | d->saveTimer->start(); |
1073 | } |
1074 | } |
1075 | |
1076 | bool Theme::findInRectsCache(const QString &image, const QString &element, QRectF &rect) const |
1077 | { |
1078 | if (!d->useCache()) { |
1079 | return false; |
1080 | } |
1081 | |
1082 | KConfigGroup imageGroup(d->svgElementsCache, image); |
1083 | rect = imageGroup.readEntry(element % QLatin1Literal("Size" ), QRectF()); |
1084 | |
1085 | if (rect.isValid()) { |
1086 | return true; |
1087 | } |
1088 | |
1089 | //Name starting by _ means the element is empty and we're asked for the size of |
1090 | //the whole image, so the whole image is never invalid |
1091 | if (element.indexOf('_') <= 0) { |
1092 | return false; |
1093 | } |
1094 | |
1095 | bool invalid = false; |
1096 | |
1097 | QHash<QString, QSet<QString> >::iterator it = d->invalidElements.find(image); |
1098 | if (it == d->invalidElements.end()) { |
1099 | QSet<QString> elements = imageGroup.readEntry("invalidElements" , QStringList()).toSet(); |
1100 | d->invalidElements.insert(image, elements); |
1101 | invalid = elements.contains(element); |
1102 | } else { |
1103 | invalid = it.value().contains(element); |
1104 | } |
1105 | |
1106 | return invalid; |
1107 | } |
1108 | |
1109 | QStringList Theme::listCachedRectKeys(const QString &image) const |
1110 | { |
1111 | if (!d->useCache()) { |
1112 | return QStringList(); |
1113 | } |
1114 | |
1115 | KConfigGroup imageGroup(d->svgElementsCache, image); |
1116 | QStringList keys = imageGroup.keyList(); |
1117 | |
1118 | QMutableListIterator<QString> i(keys); |
1119 | while (i.hasNext()) { |
1120 | QString key = i.next(); |
1121 | if (key.endsWith("Size" )) { |
1122 | // The actual cache id used from outside doesn't end on "Size". |
1123 | key.resize(key.size() - 4); |
1124 | i.setValue(key); |
1125 | } else { |
1126 | i.remove(); |
1127 | } |
1128 | } |
1129 | return keys; |
1130 | } |
1131 | |
1132 | void Theme::insertIntoRectsCache(const QString& image, const QString &element, const QRectF &rect) |
1133 | { |
1134 | if (!d->useCache()) { |
1135 | return; |
1136 | } |
1137 | |
1138 | if (rect.isValid()) { |
1139 | KConfigGroup imageGroup(d->svgElementsCache, image); |
1140 | imageGroup.writeEntry(element % QLatin1Literal("Size" ), rect); |
1141 | } else { |
1142 | QHash<QString, QSet<QString> >::iterator it = d->invalidElements.find(image); |
1143 | if (it == d->invalidElements.end()) { |
1144 | d->invalidElements[image].insert(element); |
1145 | } else if (!it.value().contains(element)) { |
1146 | if (it.value().count() > 1000) { |
1147 | it.value().erase(it.value().begin()); |
1148 | } |
1149 | |
1150 | it.value().insert(element); |
1151 | } |
1152 | } |
1153 | } |
1154 | |
1155 | void Theme::invalidateRectsCache(const QString& image) |
1156 | { |
1157 | if (d->useCache()) { |
1158 | KConfigGroup imageGroup(d->svgElementsCache, image); |
1159 | imageGroup.deleteGroup(); |
1160 | } |
1161 | |
1162 | d->invalidElements.remove(image); |
1163 | } |
1164 | |
1165 | void Theme::releaseRectsCache(const QString &image) |
1166 | { |
1167 | QHash<QString, QSet<QString> >::iterator it = d->invalidElements.find(image); |
1168 | if (it != d->invalidElements.end()) { |
1169 | if (d->useCache()) { |
1170 | KConfigGroup imageGroup(d->svgElementsCache, it.key()); |
1171 | imageGroup.writeEntry("invalidElements" , it.value().toList()); |
1172 | } |
1173 | |
1174 | d->invalidElements.erase(it); |
1175 | } |
1176 | } |
1177 | |
1178 | void Theme::setCacheLimit(int kbytes) |
1179 | { |
1180 | Q_UNUSED(kbytes) |
1181 | if (d->useCache()) { |
1182 | ; |
1183 | // Too late for you bub. |
1184 | // d->pixmapCache->setCacheLimit(kbytes); |
1185 | } |
1186 | } |
1187 | |
1188 | KUrl Theme::homepage() const |
1189 | { |
1190 | const QString metadataPath(KStandardDirs::locate("data" , QLatin1Literal("desktoptheme/" ) % d->themeName % QLatin1Literal("/metadata.desktop" ))); |
1191 | KConfig metadata(metadataPath); |
1192 | KConfigGroup brandConfig(&metadata, "Branding" ); |
1193 | return brandConfig.readEntry("homepage" , KUrl("http://www.kde.org" )); |
1194 | } |
1195 | |
1196 | int Theme::toolTipDelay() const |
1197 | { |
1198 | return d->toolTipDelay; |
1199 | } |
1200 | |
1201 | } |
1202 | |
1203 | #include <theme.moc> |
1204 | |