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