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