Plasma
theme.cpp
Go to the documentation of this file.
00001 /* 00002 * Copyright 2006-2007 Aaron Seigo <aseigo@kde.org> 00003 * 00004 * This program is free software; you can redistribute it and/or modify 00005 * it under the terms of the GNU Library General Public License as 00006 * published by the Free Software Foundation; either version 2, or 00007 * (at your option) any later version. 00008 * 00009 * This program is distributed in the hope that it will be useful, 00010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 * GNU General Public License for more details 00013 * 00014 * You should have received a copy of the GNU Library General Public 00015 * License along with this program; if not, write to the 00016 * Free Software Foundation, Inc., 00017 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00018 */ 00019 00020 #include "theme.h" 00021 00022 #include <QApplication> 00023 #include <QFile> 00024 #include <QFileInfo> 00025 #include <QMutableListIterator> 00026 #include <QPair> 00027 #include <QStringBuilder> 00028 #include <QTimer> 00029 #ifdef Q_WS_X11 00030 #include <QX11Info> 00031 #include "private/effectwatcher_p.h" 00032 #endif 00033 00034 #include <kcolorscheme.h> 00035 #include <kcomponentdata.h> 00036 #include <kconfiggroup.h> 00037 #include <kdebug.h> 00038 #include <kdirwatch.h> 00039 #include <kglobal.h> 00040 #include <kglobalsettings.h> 00041 #include <kmanagerselection.h> 00042 #include <kimagecache.h> 00043 #include <ksharedconfig.h> 00044 #include <kstandarddirs.h> 00045 #include <kwindowsystem.h> 00046 00047 00048 #include "animations/animationscriptengine_p.h" 00049 #include "libplasma-theme-global.h" 00050 #include "private/packages_p.h" 00051 #include "windoweffects.h" 00052 00053 namespace Plasma 00054 { 00055 00056 //NOTE: Default wallpaper can be set from the theme configuration 00057 #define DEFAULT_WALLPAPER_THEME "default" 00058 #define DEFAULT_WALLPAPER_SUFFIX ".png" 00059 static const int DEFAULT_WALLPAPER_WIDTH = 1920; 00060 static const int DEFAULT_WALLPAPER_HEIGHT = 1200; 00061 00062 enum styles { 00063 DEFAULTSTYLE, 00064 SVGSTYLE 00065 }; 00066 00067 enum CacheType { 00068 NoCache = 0, 00069 PixmapCache = 1, 00070 SvgElementsCache = 2 00071 }; 00072 Q_DECLARE_FLAGS(CacheTypes, CacheType) 00073 Q_DECLARE_OPERATORS_FOR_FLAGS(CacheTypes) 00074 00075 class ThemePrivate 00076 { 00077 public: 00078 ThemePrivate(Theme *theme) 00079 : q(theme), 00080 colorScheme(QPalette::Active, KColorScheme::Window, KSharedConfigPtr(0)), 00081 buttonColorScheme(QPalette::Active, KColorScheme::Button, KSharedConfigPtr(0)), 00082 viewColorScheme(QPalette::Active, KColorScheme::View, KSharedConfigPtr(0)), 00083 defaultWallpaperTheme(DEFAULT_WALLPAPER_THEME), 00084 defaultWallpaperSuffix(DEFAULT_WALLPAPER_SUFFIX), 00085 defaultWallpaperWidth(DEFAULT_WALLPAPER_WIDTH), 00086 defaultWallpaperHeight(DEFAULT_WALLPAPER_HEIGHT), 00087 pixmapCache(0), 00088 locolor(false), 00089 compositingActive(KWindowSystem::compositingActive()), 00090 blurActive(false), 00091 isDefault(false), 00092 useGlobal(true), 00093 hasWallpapers(false), 00094 useNativeWidgetStyle(false) 00095 { 00096 generalFont = QApplication::font(); 00097 ThemeConfig config; 00098 cacheTheme = config.cacheTheme(); 00099 00100 if (QPixmap::defaultDepth() > 8) { 00101 QObject::connect(KWindowSystem::self(), SIGNAL(compositingChanged(bool)), q, SLOT(compositingChanged(bool))); 00102 #ifdef Q_WS_X11 00103 //watch for blur effect property changes as well 00104 effectWatcher = 0; 00105 effectWatcher = new EffectWatcher("_KDE_NET_WM_BLUR_BEHIND_REGION"); 00106 QObject::connect(effectWatcher, SIGNAL(blurBehindChanged(bool)), q, SLOT(blurBehindChanged(bool))); 00107 #endif 00108 } 00109 00110 saveTimer = new QTimer(q); 00111 saveTimer->setSingleShot(true); 00112 QObject::connect(saveTimer, SIGNAL(timeout()), q, SLOT(scheduledCacheUpdate())); 00113 } 00114 00115 ~ThemePrivate() 00116 { 00117 delete pixmapCache; 00118 #ifdef Q_WS_X11 00119 delete effectWatcher; 00120 #endif 00121 } 00122 00123 KConfigGroup &config() 00124 { 00125 if (!cfg.isValid()) { 00126 QString groupName = "Theme"; 00127 00128 if (!useGlobal) { 00129 QString app = KGlobal::mainComponent().componentName(); 00130 00131 if (!app.isEmpty()) { 00132 kDebug() << "using theme for app" << app; 00133 groupName.append("-").append(app); 00134 } 00135 } 00136 00137 cfg = KConfigGroup(KSharedConfig::openConfig(themeRcFile), groupName); 00138 } 00139 00140 return cfg; 00141 } 00142 00143 QString findInTheme(const QString &image, const QString &theme, bool cache = true); 00144 void compositingChanged(bool active); 00145 void discardCache(CacheTypes caches); 00146 void scheduledCacheUpdate(); 00147 void colorsChanged(); 00148 void blurBehindChanged(bool blur); 00149 bool useCache(); 00150 void settingsFileChanged(const QString &); 00151 void setThemeName(const QString &themeName, bool writeSettings); 00152 void onAppExitCleanup(); 00153 void processWallpaperSettings(KConfigBase *metadata); 00154 void processAnimationSettings(const QString &theme, KConfigBase *metadata); 00155 00156 const QString processStyleSheet(const QString &css); 00157 00158 static const char *defaultTheme; 00159 static const char *systemColorsTheme; 00160 static const char *themeRcFile; 00161 static PackageStructure::Ptr packageStructure; 00162 00163 Theme *q; 00164 QString themeName; 00165 QList<QString> fallbackThemes; 00166 KSharedConfigPtr colors; 00167 KColorScheme colorScheme; 00168 KColorScheme buttonColorScheme; 00169 KColorScheme viewColorScheme; 00170 KConfigGroup cfg; 00171 QFont generalFont; 00172 QString defaultWallpaperTheme; 00173 QString defaultWallpaperSuffix; 00174 int defaultWallpaperWidth; 00175 int defaultWallpaperHeight; 00176 KImageCache *pixmapCache; 00177 KSharedConfigPtr svgElementsCache; 00178 QHash<QString, QSet<QString> > invalidElements; 00179 QHash<QString, QPixmap> pixmapsToCache; 00180 QHash<QString, QString> keysToCache; 00181 QHash<QString, QString> idsToCache; 00182 QHash<QString, QString> animationMapping; 00183 QHash<styles, QString> cachedStyleSheets; 00184 QHash<QString, QString> discoveries; 00185 QTimer *saveTimer; 00186 00187 #ifdef Q_WS_X11 00188 EffectWatcher *effectWatcher; 00189 #endif 00190 bool locolor : 1; 00191 bool compositingActive : 1; 00192 bool blurActive : 1; 00193 bool isDefault : 1; 00194 bool useGlobal : 1; 00195 bool hasWallpapers : 1; 00196 bool cacheTheme : 1; 00197 bool useNativeWidgetStyle :1; 00198 }; 00199 00200 PackageStructure::Ptr ThemePrivate::packageStructure(0); 00201 const char *ThemePrivate::defaultTheme = "default"; 00202 const char *ThemePrivate::themeRcFile = "plasmarc"; 00203 // the system colors theme is used to cache unthemed svgs with colorization needs 00204 // these svgs do not follow the theme's colors, but rather the system colors 00205 const char *ThemePrivate::systemColorsTheme = "internal-system-colors"; 00206 00207 bool ThemePrivate::useCache() 00208 { 00209 if (cacheTheme && !pixmapCache) { 00210 ThemeConfig config; 00211 pixmapCache = new KImageCache("plasma_theme_" + themeName, config.themeCacheKb() * 1024); 00212 if (themeName != systemColorsTheme) { 00213 //check for expired cache 00214 // FIXME: when using the system colors, if they change while the application is not running 00215 // the cache should be dropped; we need a way to detect system color change when the 00216 // application is not running. 00217 QFile f(KStandardDirs::locate("data", "desktoptheme/" + themeName + "/metadata.desktop")); 00218 QFileInfo info(f); 00219 if (info.lastModified().toTime_t() > uint(pixmapCache->lastModifiedTime())) { 00220 pixmapCache->clear(); 00221 } 00222 } 00223 } 00224 00225 return cacheTheme; 00226 } 00227 00228 void ThemePrivate::onAppExitCleanup() 00229 { 00230 pixmapsToCache.clear(); 00231 delete pixmapCache; 00232 pixmapCache = 0; 00233 cacheTheme = false; 00234 } 00235 00236 QString ThemePrivate::findInTheme(const QString &image, const QString &theme, bool cache) 00237 { 00238 if (cache && discoveries.contains(image)) { 00239 return discoveries[image]; 00240 } 00241 00242 QString search; 00243 00244 if (locolor) { 00245 search = QLatin1Literal("desktoptheme/") % theme % QLatin1Literal("/locolor/") % image; 00246 search = KStandardDirs::locate("data", search); 00247 } else if (!compositingActive) { 00248 search = QLatin1Literal("desktoptheme/") % theme % QLatin1Literal("/opaque/") % image; 00249 search = KStandardDirs::locate("data", search); 00250 } else if (WindowEffects::isEffectAvailable(WindowEffects::BlurBehind)) { 00251 search = QLatin1Literal("desktoptheme/") % theme % QLatin1Literal("/translucent/") % image; 00252 search = KStandardDirs::locate("data", search); 00253 } 00254 00255 //not found or compositing enabled 00256 if (search.isEmpty()) { 00257 search = QLatin1Literal("desktoptheme/") % theme % QLatin1Char('/') % image; 00258 search = KStandardDirs::locate("data", search); 00259 } 00260 00261 if (cache && !search.isEmpty()) { 00262 discoveries.insert(image, search); 00263 } 00264 00265 return search; 00266 } 00267 00268 void ThemePrivate::compositingChanged(bool active) 00269 { 00270 #ifdef Q_WS_X11 00271 if (compositingActive != active) { 00272 compositingActive = active; 00273 discardCache(PixmapCache | SvgElementsCache); 00274 emit q->themeChanged(); 00275 } 00276 #endif 00277 } 00278 00279 void ThemePrivate::discardCache(CacheTypes caches) 00280 { 00281 if (caches & PixmapCache) { 00282 pixmapsToCache.clear(); 00283 saveTimer->stop(); 00284 if (pixmapCache) { 00285 pixmapCache->clear(); 00286 } 00287 } else { 00288 // This deletes the object but keeps the on-disk cache for later use 00289 delete pixmapCache; 00290 pixmapCache = 0; 00291 } 00292 00293 cachedStyleSheets.clear(); 00294 00295 if (caches & SvgElementsCache) { 00296 discoveries.clear(); 00297 invalidElements.clear(); 00298 00299 if (svgElementsCache) { 00300 QFile f(svgElementsCache->name()); 00301 svgElementsCache = 0; 00302 f.remove(); 00303 } 00304 00305 const QString svgElementsFile = KStandardDirs::locateLocal("cache", "plasma-svgelements-" + themeName); 00306 svgElementsCache = KSharedConfig::openConfig(svgElementsFile); 00307 } 00308 } 00309 00310 void ThemePrivate::scheduledCacheUpdate() 00311 { 00312 if (useCache()) { 00313 QHashIterator<QString, QPixmap> it(pixmapsToCache); 00314 while (it.hasNext()) { 00315 it.next(); 00316 pixmapCache->insertPixmap(idsToCache[it.key()], it.value()); 00317 } 00318 } 00319 00320 pixmapsToCache.clear(); 00321 keysToCache.clear(); 00322 idsToCache.clear(); 00323 } 00324 00325 void ThemePrivate::colorsChanged() 00326 { 00327 discardCache(PixmapCache); 00328 colorScheme = KColorScheme(QPalette::Active, KColorScheme::Window, colors); 00329 buttonColorScheme = KColorScheme(QPalette::Active, KColorScheme::Button, colors); 00330 viewColorScheme = KColorScheme(QPalette::Active, KColorScheme::View, colors); 00331 emit q->themeChanged(); 00332 } 00333 00334 void ThemePrivate::blurBehindChanged(bool blur) 00335 { 00336 blurActive = blur; 00337 discardCache(PixmapCache | SvgElementsCache); 00338 emit q->themeChanged(); 00339 } 00340 00341 const QString ThemePrivate::processStyleSheet(const QString &css) 00342 { 00343 QString stylesheet; 00344 if (css.isEmpty()) { 00345 stylesheet = cachedStyleSheets.value(DEFAULTSTYLE); 00346 if (stylesheet.isEmpty()) { 00347 stylesheet = QString("\n\ 00348 body {\n\ 00349 color: %textcolor;\n\ 00350 font-size: %fontsize;\n\ 00351 font-family: %fontfamily;\n\ 00352 }\n\ 00353 a:active { color: %activatedlink; }\n\ 00354 a:link { color: %link; }\n\ 00355 a:visited { color: %visitedlink; }\n\ 00356 a:hover { color: %hoveredlink; text-decoration: none; }\n\ 00357 "); 00358 stylesheet = processStyleSheet(stylesheet); 00359 cachedStyleSheets.insert(DEFAULTSTYLE, stylesheet); 00360 } 00361 00362 return stylesheet; 00363 } else if (css == "SVG") { 00364 stylesheet = cachedStyleSheets.value(SVGSTYLE); 00365 if (stylesheet.isEmpty()) { 00366 QString skel = ".ColorScheme-%1{color:%2;}"; 00367 00368 stylesheet += skel.arg("Text","%textcolor"); 00369 stylesheet += skel.arg("Background","%backgroundcolor"); 00370 00371 stylesheet += skel.arg("ButtonText","%buttontextcolor"); 00372 stylesheet += skel.arg("ButtonBackground","%buttonbackgroundcolor"); 00373 stylesheet += skel.arg("ButtonHover","%buttonhovercolor"); 00374 stylesheet += skel.arg("ButtonFocus","%buttonfocuscolor"); 00375 00376 stylesheet += skel.arg("ViewText","%viewtextcolor"); 00377 stylesheet += skel.arg("ViewBackground","%viewbackgroundcolor"); 00378 stylesheet += skel.arg("ViewHover","%viewhovercolor"); 00379 stylesheet += skel.arg("ViewFocus","%viewfocuscolor"); 00380 00381 stylesheet = processStyleSheet(stylesheet); 00382 cachedStyleSheets.insert(SVGSTYLE, stylesheet); 00383 } 00384 00385 return stylesheet; 00386 } else { 00387 stylesheet = css; 00388 } 00389 00390 QHash<QString, QString> elements; 00391 // If you add elements here, make sure their names are sufficiently unique to not cause 00392 // clashes between element keys 00393 elements["%textcolor"] = q->color(Theme::TextColor).name(); 00394 elements["%backgroundcolor"] = q->color(Theme::BackgroundColor).name(); 00395 elements["%visitedlink"] = q->color(Theme::VisitedLinkColor).name(); 00396 elements["%activatedlink"] = q->color(Theme::HighlightColor).name(); 00397 elements["%hoveredlink"] = q->color(Theme::HighlightColor).name(); 00398 elements["%link"] = q->color(Theme::LinkColor).name(); 00399 elements["%buttontextcolor"] = q->color(Theme::ButtonTextColor).name(); 00400 elements["%buttonbackgroundcolor"] = q->color(Theme::ButtonBackgroundColor).name(); 00401 elements["%buttonhovercolor"] = q->color(Theme::ButtonHoverColor).name(); 00402 elements["%buttonfocuscolor"] = q->color(Theme::ButtonFocusColor).name(); 00403 elements["%viewtextcolor"] = q->color(Theme::ViewTextColor).name(); 00404 elements["%viewbackgroundcolor"] = q->color(Theme::ViewBackgroundColor).name(); 00405 elements["%viewhovercolor"] = q->color(Theme::ViewHoverColor).name(); 00406 elements["%viewfocuscolor"] = q->color(Theme::ViewFocusColor).name(); 00407 00408 QFont font = q->font(Theme::DefaultFont); 00409 elements["%fontsize"] = QString("%1pt").arg(font.pointSize()); 00410 elements["%fontfamily"] = font.family().split('[').first(); 00411 elements["%smallfontsize"] = QString("%1pt").arg(KGlobalSettings::smallestReadableFont().pointSize()); 00412 00413 QHash<QString, QString>::const_iterator it = elements.constBegin(); 00414 QHash<QString, QString>::const_iterator itEnd = elements.constEnd(); 00415 for ( ; it != itEnd; ++it) { 00416 stylesheet.replace(it.key(), it.value()); 00417 } 00418 return stylesheet; 00419 } 00420 00421 class ThemeSingleton 00422 { 00423 public: 00424 ThemeSingleton() 00425 { 00426 self.d->isDefault = true; 00427 00428 //FIXME: if/when kconfig gets change notification, this will be unnecessary 00429 KDirWatch::self()->addFile(KStandardDirs::locateLocal("config", ThemePrivate::themeRcFile)); 00430 QObject::connect(KDirWatch::self(), SIGNAL(created(QString)), &self, SLOT(settingsFileChanged(QString))); 00431 QObject::connect(KDirWatch::self(), SIGNAL(dirty(QString)), &self, SLOT(settingsFileChanged(QString))); 00432 } 00433 00434 Theme self; 00435 }; 00436 00437 K_GLOBAL_STATIC(ThemeSingleton, privateThemeSelf) 00438 00439 Theme *Theme::defaultTheme() 00440 { 00441 return &privateThemeSelf->self; 00442 } 00443 00444 Theme::Theme(QObject *parent) 00445 : QObject(parent), 00446 d(new ThemePrivate(this)) 00447 { 00448 settingsChanged(); 00449 if (QCoreApplication::instance()) { 00450 connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), 00451 this, SLOT(onAppExitCleanup())); 00452 } 00453 } 00454 00455 Theme::Theme(const QString &themeName, QObject *parent) 00456 : QObject(parent), 00457 d(new ThemePrivate(this)) 00458 { 00459 // turn off caching so we don't accidently trigger unnecessary disk activity at this point 00460 bool useCache = d->cacheTheme; 00461 d->cacheTheme = false; 00462 setThemeName(themeName); 00463 d->cacheTheme = useCache; 00464 if (QCoreApplication::instance()) { 00465 connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), 00466 this, SLOT(onAppExitCleanup())); 00467 } 00468 } 00469 00470 Theme::~Theme() 00471 { 00472 if (d->svgElementsCache) { 00473 QHashIterator<QString, QSet<QString> > it(d->invalidElements); 00474 while (it.hasNext()) { 00475 it.next(); 00476 KConfigGroup imageGroup(d->svgElementsCache, it.key()); 00477 imageGroup.writeEntry("invalidElements", it.value().toList()); //FIXME: add QSet support to KConfig 00478 } 00479 } 00480 00481 d->onAppExitCleanup(); 00482 delete d; 00483 } 00484 00485 PackageStructure::Ptr Theme::packageStructure() 00486 { 00487 if (!ThemePrivate::packageStructure) { 00488 ThemePrivate::packageStructure = new ThemePackage(); 00489 } 00490 00491 return ThemePrivate::packageStructure; 00492 } 00493 00494 KPluginInfo::List Theme::listThemeInfo() 00495 { 00496 const QStringList themes = KGlobal::dirs()->findAllResources("data", "desktoptheme/*/metadata.desktop", 00497 KStandardDirs::NoDuplicates); 00498 return KPluginInfo::fromFiles(themes); 00499 } 00500 00501 void ThemePrivate::settingsFileChanged(const QString &file) 00502 { 00503 if (file.endsWith(themeRcFile)) { 00504 config().config()->reparseConfiguration(); 00505 q->settingsChanged(); 00506 } 00507 } 00508 00509 void Theme::settingsChanged() 00510 { 00511 d->setThemeName(d->config().readEntry("name", ThemePrivate::defaultTheme), false); 00512 } 00513 00514 void Theme::setThemeName(const QString &themeName) 00515 { 00516 d->setThemeName(themeName, true); 00517 } 00518 00519 void ThemePrivate::processWallpaperSettings(KConfigBase *metadata) 00520 { 00521 if (!defaultWallpaperTheme.isEmpty() && defaultWallpaperTheme != DEFAULT_WALLPAPER_THEME) { 00522 return; 00523 } 00524 00525 KConfigGroup cg; 00526 if (metadata->hasGroup("Wallpaper")) { 00527 // we have a theme color config, so let's also check to see if 00528 // there is a wallpaper defined in there. 00529 cg = KConfigGroup(metadata, "Wallpaper"); 00530 } else { 00531 // since we didn't find an entry in the theme, let's look in the main 00532 // theme config 00533 cg = config(); 00534 } 00535 00536 defaultWallpaperTheme = cg.readEntry("defaultWallpaperTheme", DEFAULT_WALLPAPER_THEME); 00537 defaultWallpaperSuffix = cg.readEntry("defaultFileSuffix", DEFAULT_WALLPAPER_SUFFIX); 00538 defaultWallpaperWidth = cg.readEntry("defaultWidth", DEFAULT_WALLPAPER_WIDTH); 00539 defaultWallpaperHeight = cg.readEntry("defaultHeight", DEFAULT_WALLPAPER_HEIGHT); 00540 } 00541 00542 void ThemePrivate::processAnimationSettings(const QString &theme, KConfigBase *metadata) 00543 { 00544 KConfigGroup cg(metadata, "Animations"); 00545 const QString animDir = QLatin1Literal("desktoptheme/") % theme % QLatin1Literal("/animations/"); 00546 foreach (const QString &path, cg.keyList()) { 00547 const QStringList anims = cg.readEntry(path, QStringList()); 00548 foreach (const QString &anim, anims) { 00549 if (!animationMapping.contains(anim)) { 00550 kDebug() << "Registering animation. animDir: " << animDir 00551 << "\tanim: " << anim 00552 << "\tpath: " << path << "\t*******\n\n\n"; 00553 //key: desktoptheme/default/animations/+ all.js 00554 //value: ZoomAnimation 00555 animationMapping.insert(anim, animDir % path); 00556 } else { 00557 kDebug() << "************Animation already registered!\n\n\n"; 00558 } 00559 } 00560 } 00561 00562 } 00563 00564 void ThemePrivate::setThemeName(const QString &tempThemeName, bool writeSettings) 00565 { 00566 //kDebug() << tempThemeName; 00567 QString theme = tempThemeName; 00568 if (theme.isEmpty() || theme == themeName) { 00569 // let's try and get the default theme at least 00570 if (themeName.isEmpty()) { 00571 theme = ThemePrivate::defaultTheme; 00572 } else { 00573 return; 00574 } 00575 } 00576 00577 // we have one special theme: essentially a dummy theme used to cache things with 00578 // the system colors. 00579 bool realTheme = theme != systemColorsTheme; 00580 if (realTheme) { 00581 QString themePath = KStandardDirs::locate("data", QLatin1Literal("desktoptheme/") % theme % QLatin1Char('/')); 00582 if (themePath.isEmpty() && themeName.isEmpty()) { 00583 themePath = KStandardDirs::locate("data", "desktoptheme/default/"); 00584 00585 if (themePath.isEmpty()) { 00586 return; 00587 } 00588 00589 theme = ThemePrivate::defaultTheme; 00590 } 00591 } 00592 00593 // check again as ThemePrivate::defaultTheme might be empty 00594 if (themeName == theme) { 00595 return; 00596 } 00597 00598 themeName = theme; 00599 00600 // load the color scheme config 00601 const QString colorsFile = realTheme ? KStandardDirs::locate("data", QLatin1Literal("desktoptheme/") % theme % QLatin1Literal("/colors")) 00602 : QString(); 00603 00604 //kDebug() << "we're going for..." << colorsFile << "*******************"; 00605 00606 // load the wallpaper settings, if any 00607 if (realTheme) { 00608 const QString metadataPath(KStandardDirs::locate("data", QLatin1Literal("desktoptheme/") % theme % QLatin1Literal("/metadata.desktop"))); 00609 KConfig metadata(metadataPath); 00610 00611 processWallpaperSettings(&metadata); 00612 00613 AnimationScriptEngine::clearAnimations(); 00614 animationMapping.clear(); 00615 processAnimationSettings(themeName, &metadata); 00616 00617 KConfigGroup cg(&metadata, "Settings"); 00618 useNativeWidgetStyle = cg.readEntry("UseNativeWidgetStyle", false); 00619 QString fallback = cg.readEntry("FallbackTheme", QString()); 00620 00621 fallbackThemes.clear(); 00622 while (!fallback.isEmpty() && !fallbackThemes.contains(fallback)) { 00623 fallbackThemes.append(fallback); 00624 00625 QString metadataPath(KStandardDirs::locate("data", QLatin1Literal("desktoptheme/") % theme % QLatin1Literal("/metadata.desktop"))); 00626 KConfig metadata(metadataPath); 00627 KConfigGroup cg(&metadata, "Settings"); 00628 fallback = cg.readEntry("FallbackTheme", QString()); 00629 } 00630 00631 if (!fallbackThemes.contains("oxygen")) { 00632 fallbackThemes.append("oxygen"); 00633 } 00634 00635 if (!fallbackThemes.contains(ThemePrivate::defaultTheme)) { 00636 fallbackThemes.append(ThemePrivate::defaultTheme); 00637 } 00638 00639 foreach (const QString &theme, fallbackThemes) { 00640 QString metadataPath(KStandardDirs::locate("data", QLatin1Literal("desktoptheme/") % theme % QLatin1Literal("/metadata.desktop"))); 00641 KConfig metadata(metadataPath); 00642 processAnimationSettings(theme, &metadata); 00643 processWallpaperSettings(&metadata); 00644 } 00645 } 00646 00647 if (colorsFile.isEmpty()) { 00648 colors = 0; 00649 QObject::connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), 00650 q, SLOT(colorsChanged()), Qt::UniqueConnection); 00651 } else { 00652 QObject::disconnect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), 00653 q, SLOT(colorsChanged())); 00654 colors = KSharedConfig::openConfig(colorsFile); 00655 } 00656 00657 colorScheme = KColorScheme(QPalette::Active, KColorScheme::Window, colors); 00658 buttonColorScheme = KColorScheme(QPalette::Active, KColorScheme::Button, colors); 00659 viewColorScheme = KColorScheme(QPalette::Active, KColorScheme::View, colors); 00660 hasWallpapers = KStandardDirs::exists(KStandardDirs::locateLocal("data", QLatin1Literal("desktoptheme/") % theme % QLatin1Literal("/wallpapers/"))); 00661 00662 if (realTheme && isDefault && writeSettings) { 00663 // we're the default theme, let's save our state 00664 KConfigGroup &cg = config(); 00665 if (ThemePrivate::defaultTheme == themeName) { 00666 cg.deleteEntry("name"); 00667 } else { 00668 cg.writeEntry("name", themeName); 00669 } 00670 cg.sync(); 00671 } 00672 00673 discardCache(SvgElementsCache); 00674 00675 emit q->themeChanged(); 00676 } 00677 00678 QString Theme::themeName() const 00679 { 00680 return d->themeName; 00681 } 00682 00683 QString Theme::imagePath(const QString &name) const 00684 { 00685 // look for a compressed svg file in the theme 00686 if (name.contains("../") || name.isEmpty()) { 00687 // we don't support relative paths 00688 kDebug() << "Theme says: bad image path " << name; 00689 return QString(); 00690 } 00691 00692 const QString svgzName = name % QLatin1Literal(".svgz"); 00693 QString path = d->findInTheme(svgzName, d->themeName); 00694 00695 if (path.isEmpty()) { 00696 // try for an uncompressed svg file 00697 const QString svgName = name % QLatin1Literal(".svg"); 00698 path = d->findInTheme(svgName, d->themeName); 00699 00700 // search in fallback themes if necessary 00701 for (int i = 0; path.isEmpty() && i < d->fallbackThemes.count(); ++i) { 00702 if (d->themeName == d->fallbackThemes[i]) { 00703 continue; 00704 } 00705 00706 // try a compressed svg file in the fallback theme 00707 path = d->findInTheme(svgzName, d->fallbackThemes[i]); 00708 00709 if (path.isEmpty()) { 00710 // try an uncompressed svg file in the fallback theme 00711 path = d->findInTheme(svgName, d->fallbackThemes[i]); 00712 } 00713 } 00714 } 00715 00716 /* 00717 if (path.isEmpty()) { 00718 kDebug() << "Theme says: bad image path " << name; 00719 } 00720 */ 00721 00722 return path; 00723 } 00724 00725 QString Theme::styleSheet(const QString &css) const 00726 { 00727 return d->processStyleSheet(css); 00728 } 00729 00730 QString Theme::animationPath(const QString &name) const 00731 { 00732 const QString path = d->animationMapping.value(name); 00733 if (path.isEmpty()) { 00734 //kError() << "****** FAILED TO FIND IN MAPPING!"; 00735 return path; 00736 } 00737 00738 return KStandardDirs::locate("data", path); 00739 } 00740 00741 QString Theme::wallpaperPath(const QSize &size) const 00742 { 00743 QString fullPath; 00744 QString image = d->defaultWallpaperTheme; 00745 00746 image.append("/contents/images/%1x%2").append(d->defaultWallpaperSuffix); 00747 QString defaultImage = image.arg(d->defaultWallpaperWidth).arg(d->defaultWallpaperHeight); 00748 00749 if (size.isValid()) { 00750 // try to customize the paper to the size requested 00751 //TODO: this should do better than just fallback to the default size. 00752 // a "best fit" matching would be far better, so we don't end 00753 // up returning a 1920x1200 wallpaper for a 640x480 request ;) 00754 image = image.arg(size.width()).arg(size.height()); 00755 } else { 00756 image = defaultImage; 00757 } 00758 00759 //TODO: the theme's wallpaper overrides regularly installed wallpapers. 00760 // should it be possible for user installed (e.g. locateLocal) wallpapers 00761 // to override the theme? 00762 if (d->hasWallpapers) { 00763 // check in the theme first 00764 fullPath = d->findInTheme(QLatin1Literal("wallpapers/") % image, d->themeName); 00765 00766 if (fullPath.isEmpty()) { 00767 fullPath = d->findInTheme(QLatin1Literal("wallpapers/") % defaultImage, d->themeName); 00768 } 00769 } 00770 00771 if (fullPath.isEmpty()) { 00772 // we failed to find it in the theme, so look in the standard directories 00773 //kDebug() << "looking for" << image; 00774 fullPath = KStandardDirs::locate("wallpaper", image); 00775 } 00776 00777 if (fullPath.isEmpty()) { 00778 // we still failed to find it in the theme, so look for the default in 00779 // the standard directories 00780 //kDebug() << "looking for" << defaultImage; 00781 fullPath = KStandardDirs::locate("wallpaper", defaultImage); 00782 00783 if (fullPath.isEmpty()) { 00784 kDebug() << "exhausted every effort to find a wallpaper."; 00785 } 00786 } 00787 00788 return fullPath; 00789 } 00790 00791 bool Theme::currentThemeHasImage(const QString &name) const 00792 { 00793 if (name.contains("../")) { 00794 // we don't support relative paths 00795 return false; 00796 } 00797 00798 return !(d->findInTheme(name % QLatin1Literal(".svgz"), d->themeName, false).isEmpty()) || 00799 !(d->findInTheme(name % QLatin1Literal(".svg"), d->themeName, false).isEmpty()); 00800 } 00801 00802 KSharedConfigPtr Theme::colorScheme() const 00803 { 00804 return d->colors; 00805 } 00806 00807 QColor Theme::color(ColorRole role) const 00808 { 00809 switch (role) { 00810 case TextColor: 00811 return d->colorScheme.foreground(KColorScheme::NormalText).color(); 00812 00813 case HighlightColor: 00814 return d->colorScheme.decoration(KColorScheme::HoverColor).color(); 00815 00816 case BackgroundColor: 00817 return d->colorScheme.background(KColorScheme::NormalBackground).color(); 00818 00819 case ButtonTextColor: 00820 return d->buttonColorScheme.foreground(KColorScheme::NormalText).color(); 00821 00822 case ButtonBackgroundColor: 00823 return d->buttonColorScheme.background(KColorScheme::NormalBackground).color(); 00824 00825 case ButtonHoverColor: 00826 return d->buttonColorScheme.decoration(KColorScheme::HoverColor).color(); 00827 00828 case ButtonFocusColor: 00829 return d->buttonColorScheme.decoration(KColorScheme::FocusColor).color(); 00830 00831 case ViewTextColor: 00832 return d->viewColorScheme.foreground(KColorScheme::NormalText).color(); 00833 00834 case ViewBackgroundColor: 00835 return d->viewColorScheme.background(KColorScheme::NormalBackground).color(); 00836 00837 case ViewHoverColor: 00838 return d->viewColorScheme.decoration(KColorScheme::HoverColor).color(); 00839 00840 case ViewFocusColor: 00841 return d->viewColorScheme.decoration(KColorScheme::FocusColor).color(); 00842 00843 case LinkColor: 00844 return d->viewColorScheme.foreground(KColorScheme::LinkText).color(); 00845 00846 case VisitedLinkColor: 00847 return d->viewColorScheme.foreground(KColorScheme::VisitedText).color(); 00848 } 00849 00850 return QColor(); 00851 } 00852 00853 void Theme::setFont(const QFont &font, FontRole role) 00854 { 00855 Q_UNUSED(role) 00856 d->generalFont = font; 00857 } 00858 00859 QFont Theme::font(FontRole role) const 00860 { 00861 switch (role) { 00862 case DesktopFont: { 00863 KConfigGroup cg(KGlobal::config(), "General"); 00864 return cg.readEntry("desktopFont", d->generalFont); 00865 } 00866 break; 00867 00868 case DefaultFont: 00869 default: 00870 return d->generalFont; 00871 break; 00872 00873 case SmallestFont: 00874 return KGlobalSettings::smallestReadableFont(); 00875 break; 00876 } 00877 00878 return d->generalFont; 00879 } 00880 00881 QFontMetrics Theme::fontMetrics() const 00882 { 00883 //TODO: allow this to be overridden with a plasma specific font? 00884 return QFontMetrics(d->generalFont); 00885 } 00886 00887 bool Theme::windowTranslucencyEnabled() const 00888 { 00889 return d->compositingActive; 00890 } 00891 00892 void Theme::setUseGlobalSettings(bool useGlobal) 00893 { 00894 if (d->useGlobal == useGlobal) { 00895 return; 00896 } 00897 00898 d->useGlobal = useGlobal; 00899 d->cfg = KConfigGroup(); 00900 d->themeName.clear(); 00901 settingsChanged(); 00902 } 00903 00904 bool Theme::useGlobalSettings() const 00905 { 00906 return d->useGlobal; 00907 } 00908 00909 bool Theme::useNativeWidgetStyle() const 00910 { 00911 return d->useNativeWidgetStyle; 00912 } 00913 00914 bool Theme::findInCache(const QString &key, QPixmap &pix) 00915 { 00916 if (d->useCache()) { 00917 const QString id = d->keysToCache.value(key); 00918 if (d->pixmapsToCache.contains(id)) { 00919 pix = d->pixmapsToCache.value(id); 00920 return !pix.isNull(); 00921 } 00922 00923 QPixmap temp; 00924 if (d->pixmapCache->findPixmap(key, &temp) && !temp.isNull()) { 00925 pix = temp; 00926 return true; 00927 } 00928 } 00929 00930 return false; 00931 } 00932 00933 // BIC FIXME: Should be merged with the other findInCache method above when we break BC 00934 bool Theme::findInCache(const QString &key, QPixmap &pix, unsigned int lastModified) 00935 { 00936 if (d->useCache() && lastModified > uint(d->pixmapCache->lastModifiedTime())) { 00937 return false; 00938 } 00939 00940 return findInCache(key, pix); 00941 } 00942 00943 void Theme::insertIntoCache(const QString& key, const QPixmap& pix) 00944 { 00945 if (d->useCache()) { 00946 d->pixmapCache->insertPixmap(key, pix); 00947 } 00948 } 00949 00950 void Theme::insertIntoCache(const QString& key, const QPixmap& pix, const QString& id) 00951 { 00952 if (d->useCache()) { 00953 d->pixmapsToCache.insert(id, pix); 00954 00955 if (d->idsToCache.contains(id)) { 00956 d->keysToCache.remove(d->idsToCache[id]); 00957 } 00958 00959 d->keysToCache.insert(key, id); 00960 d->idsToCache.insert(id, key); 00961 d->saveTimer->start(600); 00962 } 00963 } 00964 00965 bool Theme::findInRectsCache(const QString &image, const QString &element, QRectF &rect) const 00966 { 00967 if (!d->svgElementsCache) { 00968 return false; 00969 } 00970 00971 KConfigGroup imageGroup(d->svgElementsCache, image); 00972 rect = imageGroup.readEntry(element % QLatin1Literal("Size"), QRectF()); 00973 00974 if (rect.isValid()) { 00975 return true; 00976 } 00977 00978 //Name starting by _ means the element is empty and we're asked for the size of 00979 //the whole image, so the whole image is never invalid 00980 if (element.indexOf('_') <= 0) { 00981 return false; 00982 } 00983 00984 bool invalid = false; 00985 00986 QHash<QString, QSet<QString> >::iterator it = d->invalidElements.find(image); 00987 if (it == d->invalidElements.end()) { 00988 QSet<QString> elements = imageGroup.readEntry("invalidElements", QStringList()).toSet(); 00989 d->invalidElements.insert(image, elements); 00990 invalid = elements.contains(element); 00991 } else { 00992 invalid = it.value().contains(element); 00993 } 00994 00995 return invalid; 00996 } 00997 00998 QStringList Theme::listCachedRectKeys(const QString &image) const 00999 { 01000 if (!d->svgElementsCache) { 01001 return QStringList(); 01002 } 01003 01004 KConfigGroup imageGroup(d->svgElementsCache, image); 01005 QStringList keys = imageGroup.keyList(); 01006 01007 QMutableListIterator<QString> i(keys); 01008 while (i.hasNext()) { 01009 QString key = i.next(); 01010 if (key.endsWith("Size")) { 01011 // The actual cache id used from outside doesn't end on "Size". 01012 key.resize(key.size() - 4); 01013 i.setValue(key); 01014 } else { 01015 i.remove(); 01016 } 01017 } 01018 return keys; 01019 } 01020 01021 void Theme::insertIntoRectsCache(const QString& image, const QString &element, const QRectF &rect) 01022 { 01023 if (!d->svgElementsCache) { 01024 return; 01025 } 01026 01027 if (rect.isValid()) { 01028 KConfigGroup imageGroup(d->svgElementsCache, image); 01029 imageGroup.writeEntry(element % QLatin1Literal("Size"), rect); 01030 } else { 01031 QHash<QString, QSet<QString> >::iterator it = d->invalidElements.find(image); 01032 if (it == d->invalidElements.end()) { 01033 d->invalidElements[image].insert(element); 01034 } else if (!it.value().contains(element)) { 01035 if (it.value().count() > 1000) { 01036 it.value().erase(it.value().begin()); 01037 } 01038 01039 it.value().insert(element); 01040 } 01041 } 01042 } 01043 01044 void Theme::invalidateRectsCache(const QString& image) 01045 { 01046 if (d->svgElementsCache) { 01047 KConfigGroup imageGroup(d->svgElementsCache, image); 01048 imageGroup.deleteGroup(); 01049 } 01050 01051 d->invalidElements.remove(image); 01052 } 01053 01054 void Theme::releaseRectsCache(const QString &image) 01055 { 01056 QHash<QString, QSet<QString> >::iterator it = d->invalidElements.find(image); 01057 if (it != d->invalidElements.end()) { 01058 if (!d->svgElementsCache) { 01059 KConfigGroup imageGroup(d->svgElementsCache, it.key()); 01060 imageGroup.writeEntry("invalidElements", it.value().toList()); 01061 } 01062 01063 d->invalidElements.erase(it); 01064 } 01065 } 01066 01067 void Theme::setCacheLimit(int kbytes) 01068 { 01069 Q_UNUSED(kbytes) 01070 if (d->useCache()) { 01071 ; 01072 // Too late for you bub. 01073 // d->pixmapCache->setCacheLimit(kbytes); 01074 } 01075 } 01076 01077 KUrl Theme::homepage() const 01078 { 01079 const QString metadataPath(KStandardDirs::locate("data", QLatin1Literal("desktoptheme/") % d->themeName % QLatin1Literal("/metadata.desktop"))); 01080 KConfig metadata(metadataPath); 01081 KConfigGroup brandConfig(&metadata, "Branding"); 01082 return brandConfig.readEntry("homepage", KUrl("http://www.kde.org")); 01083 } 01084 01085 } 01086 01087 #include <theme.moc>
KDE 4.7 API Reference