• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

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 #ifdef Q_WS_X11
00026 #include <QX11Info>
00027 #endif
00028 
00029 #include <kcolorscheme.h>
00030 #include <kcomponentdata.h>
00031 #include <kconfiggroup.h>
00032 #include <kdebug.h>
00033 #include <kglobal.h>
00034 #include <kglobalsettings.h>
00035 #include <kmanagerselection.h>
00036 #include <kpixmapcache.h>
00037 #include <ksharedconfig.h>
00038 #include <kstandarddirs.h>
00039 #include <kwindowsystem.h>
00040 
00041 #include "private/packages_p.h"
00042 
00043 namespace Plasma
00044 {
00045 
00046 #define DEFAULT_WALLPAPER_THEME "Air"
00047 #define DEFAULT_WALLPAPER_SUFFIX ".jpg"
00048 static const int DEFAULT_WALLPAPER_WIDTH = 1920;
00049 static const int DEFAULT_WALLPAPER_HEIGHT = 1200;
00050 
00051 class ThemePrivate
00052 {
00053 public:
00054     ThemePrivate(Theme *theme)
00055         : q(theme),
00056           colorScheme(QPalette::Active, KColorScheme::Window, KSharedConfigPtr(0)),
00057           buttonColorScheme(QPalette::Active, KColorScheme::Button, KSharedConfigPtr(0)),
00058           defaultWallpaperTheme(DEFAULT_WALLPAPER_THEME),
00059           defaultWallpaperSuffix(DEFAULT_WALLPAPER_SUFFIX),
00060           defaultWallpaperWidth(DEFAULT_WALLPAPER_WIDTH),
00061           defaultWallpaperHeight(DEFAULT_WALLPAPER_HEIGHT),
00062           pixmapCache(0),
00063           locolor(false),
00064           compositingActive(KWindowSystem::compositingActive()),
00065           isDefault(false),
00066           useGlobal(true),
00067           hasWallpapers(false)
00068     {
00069         generalFont = QApplication::font();
00070         KConfigGroup cg(KGlobal::config(), "CachePolicies");
00071         cacheTheme = cg.readEntry("CacheTheme", true);
00072     }
00073 
00074     ~ThemePrivate()
00075     {
00076        delete pixmapCache;
00077     }
00078 
00079     KConfigGroup &config()
00080     {
00081         if (!cfg.isValid()) {
00082             QString groupName = "Theme";
00083 
00084             if (!useGlobal) {
00085                 QString app = KGlobal::mainComponent().componentName();
00086 
00087                 if (!app.isEmpty() && app != "plasma") {
00088                     kDebug() << "using theme for app" << app;
00089                     groupName.append("-").append(app);
00090                 }
00091             }
00092 
00093             cfg = KConfigGroup(KSharedConfig::openConfig("plasmarc"), groupName);
00094         }
00095 
00096         return cfg;
00097     }
00098 
00099     QString findInTheme(const QString &image, const QString &theme) const;
00100     void compositingChanged();
00101     void discardCache();
00102     void discardCache(bool recreateElementsCache);
00103     void colorsChanged();
00104     bool useCache();
00105 
00106     static const char *defaultTheme;
00107     static PackageStructure::Ptr packageStructure;
00108 
00109     Theme *q;
00110     QString themeName;
00111     KSharedConfigPtr colors;
00112     KColorScheme colorScheme;
00113     KColorScheme buttonColorScheme;
00114     KConfigGroup cfg;
00115     QFont generalFont;
00116     QString defaultWallpaperTheme;
00117     QString defaultWallpaperSuffix;
00118     int defaultWallpaperWidth;
00119     int defaultWallpaperHeight;
00120     KPixmapCache *pixmapCache;
00121     KSharedConfigPtr svgElementsCache;
00122     QHash<QString, QSet<QString> > invalidElements;
00123 
00124 #ifdef Q_WS_X11
00125     KSelectionWatcher *compositeWatch;
00126 #endif
00127     bool locolor : 1;
00128     bool compositingActive : 1;
00129     bool isDefault : 1;
00130     bool useGlobal : 1;
00131     bool hasWallpapers : 1;
00132     bool cacheTheme : 1;
00133 };
00134 
00135 PackageStructure::Ptr ThemePrivate::packageStructure(0);
00136 const char *ThemePrivate::defaultTheme = "default";
00137 
00138 bool ThemePrivate::useCache()
00139 {
00140     if (cacheTheme && !pixmapCache) {
00141         KConfigGroup cg(KGlobal::config(), "CachePolicies");
00142         pixmapCache = new KPixmapCache("plasma_theme_" + themeName);
00143         pixmapCache->setCacheLimit(cg.readEntry("ThemeCacheKb", 80 * 1024));
00144     }
00145 
00146     return cacheTheme;
00147 }
00148 
00149 QString ThemePrivate::findInTheme(const QString &image, const QString &theme) const
00150 {
00151     //TODO: this should be using Package
00152     QString search;
00153 
00154     if (locolor) {
00155         search = "desktoptheme/" + theme + "/locolor/" + image;
00156         search =  KStandardDirs::locate("data", search);
00157     } else if (!compositingActive) {
00158         search = "desktoptheme/" + theme + "/opaque/" + image;
00159         search =  KStandardDirs::locate("data", search);
00160     }
00161 
00162     //not found or compositing enabled
00163     if (search.isEmpty()) {
00164         search = "desktoptheme/" + theme + '/' + image;
00165         search =  KStandardDirs::locate("data", search);
00166     }
00167 
00168     return search;
00169 }
00170 
00171 void ThemePrivate::compositingChanged()
00172 {
00173 #ifdef Q_WS_X11
00174     bool nowCompositingActive = compositeWatch->owner() != None;
00175 
00176     if (compositingActive != nowCompositingActive) {
00177         compositingActive = nowCompositingActive;
00178         discardCache(true);
00179         emit q->themeChanged();
00180     }
00181 #endif
00182 }
00183 
00184 void ThemePrivate::discardCache()
00185 {
00186     discardCache(true);
00187 }
00188 
00189 void ThemePrivate::discardCache(bool recreateElementsCache)
00190 {
00191     KPixmapCache::deleteCache("plasma_theme_" + themeName);
00192     delete pixmapCache;
00193     pixmapCache = 0;
00194     invalidElements.clear();
00195 
00196     svgElementsCache = 0;
00197 
00198     QString svgElementsFile = KStandardDirs::locateLocal("cache", "plasma-svgelements-" + themeName);
00199     if (!svgElementsFile.isEmpty()) {
00200         QFile f(svgElementsFile);
00201         f.remove();
00202     }
00203 
00204     if (recreateElementsCache) {
00205         svgElementsCache = KSharedConfig::openConfig(svgElementsFile);
00206     }
00207 }
00208 
00209 void ThemePrivate::colorsChanged()
00210 {
00211     discardCache(true);
00212     colorScheme = KColorScheme(QPalette::Active, KColorScheme::Window, colors);
00213     buttonColorScheme = KColorScheme(QPalette::Active, KColorScheme::Button, colors);
00214     emit q->themeChanged();
00215 }
00216 
00217 class ThemeSingleton
00218 {
00219 public:
00220     ThemeSingleton()
00221     {
00222         self.d->isDefault = true;
00223     }
00224 
00225    Theme self;
00226 };
00227 
00228 K_GLOBAL_STATIC(ThemeSingleton, privateThemeSelf)
00229 
00230 Theme *Theme::defaultTheme()
00231 {
00232     return &privateThemeSelf->self;
00233 }
00234 
00235 Theme::Theme(QObject *parent)
00236     : QObject(parent),
00237       d(new ThemePrivate(this))
00238 {
00239     settingsChanged();
00240 
00241 #ifdef Q_WS_X11
00242     Display *dpy = QX11Info::display();
00243     int screen = DefaultScreen(dpy);
00244     d->locolor = DefaultDepth(dpy, screen) < 16;
00245 
00246     if (!d->locolor) {
00247         char net_wm_cm_name[100];
00248         sprintf(net_wm_cm_name, "_NET_WM_CM_S%d", screen);
00249         d->compositeWatch = new KSelectionWatcher(net_wm_cm_name, -1, this);
00250         connect(d->compositeWatch, SIGNAL(newOwner(Window)), this, SLOT(compositingChanged()));
00251         connect(d->compositeWatch, SIGNAL(lostOwner()), this, SLOT(compositingChanged()));
00252     }
00253 #endif
00254 }
00255 
00256 Theme::~Theme()
00257 {
00258     QHashIterator<QString, QSet<QString> > it(d->invalidElements);
00259     while (it.hasNext()) {
00260         it.next();
00261         KConfigGroup imageGroup(d->svgElementsCache, it.key());
00262         imageGroup.writeEntry("invalidElements", it.value().toList()); //FIXME: add QSet support to KConfig
00263     }
00264 
00265     delete d;
00266 }
00267 
00268 PackageStructure::Ptr Theme::packageStructure()
00269 {
00270     if (!ThemePrivate::packageStructure) {
00271         ThemePrivate::packageStructure = new ThemePackage();
00272     }
00273 
00274     return ThemePrivate::packageStructure;
00275 }
00276 
00277 void Theme::settingsChanged()
00278 {
00279     setThemeName(d->config().readEntry("name", ThemePrivate::defaultTheme));
00280 }
00281 
00282 void Theme::setThemeName(const QString &themeName)
00283 {
00284     QString theme = themeName;
00285     if (theme.isEmpty() || theme == d->themeName) {
00286         // let's try and get the default theme at least
00287         if (d->themeName.isEmpty()) {
00288             theme = ThemePrivate::defaultTheme;
00289         } else {
00290             return;
00291         }
00292     }
00293 
00294     //TODO: should we care about names with relative paths in them?
00295     QString themePath = KStandardDirs::locate("data", "desktoptheme/" + theme + '/');
00296     if (themePath.isEmpty() && d->themeName.isEmpty()) {
00297         themePath = KStandardDirs::locate("data", "desktoptheme/default/");
00298 
00299         if (themePath.isEmpty()) {
00300             return;
00301         }
00302 
00303         theme = ThemePrivate::defaultTheme;
00304     }
00305 
00306     if (d->themeName == theme) {
00307         return;
00308     }
00309 
00310     //discard the old theme cache
00311     if (!d->themeName.isEmpty() && d->pixmapCache) {
00312         d->discardCache(false);
00313     }
00314 
00315     d->themeName = theme;
00316 
00317     // load the color scheme config
00318     QString colorsFile = KStandardDirs::locate("data", "desktoptheme/" + theme + "/colors");
00319     //kDebug() << "we're going for..." << colorsFile << "*******************";
00320 
00321     // load the wallpaper settings, if any
00322     QString metadataPath(KStandardDirs::locate("data", "desktoptheme/" + theme + "/metadata.desktop"));
00323     KConfig metadata(metadataPath);
00324     KConfigGroup cg;
00325     if (metadata.hasGroup("Wallpaper")) {
00326         // we have a theme color config, so let's also check to see if
00327         // there is a wallpaper defined in there.
00328         cg = KConfigGroup(&metadata, "Wallpaper");
00329     } else {
00330         // since we didn't find an entry in the theme, let's look in the main
00331         // theme config
00332         cg = d->config();
00333     }
00334 
00335     d->defaultWallpaperTheme = cg.readEntry("defaultWallpaperTheme", DEFAULT_WALLPAPER_THEME);
00336     d->defaultWallpaperSuffix = cg.readEntry("defaultFileSuffix", DEFAULT_WALLPAPER_SUFFIX);
00337     d->defaultWallpaperWidth = cg.readEntry("defaultWidth", DEFAULT_WALLPAPER_WIDTH);
00338     d->defaultWallpaperHeight = cg.readEntry("defaultHeight", DEFAULT_WALLPAPER_HEIGHT);
00339 
00340     disconnect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()),
00341                 this, SLOT(colorsChanged()));
00342 
00343     if (colorsFile.isEmpty()) {
00344         d->colors = 0;
00345         connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()),
00346                 this, SLOT(colorsChanged()));
00347     } else {
00348         d->colors = KSharedConfig::openConfig(colorsFile);
00349     }
00350 
00351     d->colorScheme = KColorScheme(QPalette::Active, KColorScheme::Window, d->colors);
00352     d->buttonColorScheme = KColorScheme(QPalette::Active, KColorScheme::Button, d->colors);
00353     d->hasWallpapers =
00354         KStandardDirs::exists(KStandardDirs::locateLocal("data", "desktoptheme/" + theme + "/wallpapers/"));
00355 
00356     if (d->isDefault) {
00357         // we're the default theme, let's save our state
00358         KConfigGroup &cg = d->config();
00359         if (ThemePrivate::defaultTheme == d->themeName) {
00360             cg.deleteEntry("name");
00361         } else {
00362             cg.writeEntry("name", d->themeName);
00363         }
00364     }
00365 
00366     //check for expired cache
00367     QFile f(metadataPath);
00368     QFileInfo info(f);
00369 
00370     if (d->useCache() && info.lastModified().toTime_t() > d->pixmapCache->timestamp()) {
00371         d->discardCache(false);
00372     }
00373 
00374     d->invalidElements.clear();
00375     QString svgElementsFile = KStandardDirs::locateLocal("cache", "plasma-svgelements-" + themeName);
00376     d->svgElementsCache = KSharedConfig::openConfig(svgElementsFile);
00377 
00378     emit themeChanged();
00379 }
00380 
00381 QString Theme::themeName() const
00382 {
00383     return d->themeName;
00384 }
00385 
00386 QString Theme::imagePath(const QString &name) const
00387 {
00388     // look for a compressed svg file in the theme
00389     if (name.contains("../")) {
00390         // we don't support relative paths
00391         return QString();
00392     }
00393 
00394     QString path = d->findInTheme(name + ".svgz", d->themeName);
00395 
00396     if (path.isEmpty()) {
00397         // try for an uncompressed svg file
00398         path = d->findInTheme(name + ".svg", d->themeName);
00399 
00400         if (path.isEmpty() && d->themeName != ThemePrivate::defaultTheme) {
00401             // try a compressed svg file in the default theme
00402             path = d->findInTheme(name + ".svgz", ThemePrivate::defaultTheme);
00403 
00404             if (path.isEmpty()) {
00405                 // try an uncompressed svg file in the default theme
00406                 path = d->findInTheme(name + ".svg", ThemePrivate::defaultTheme);
00407             }
00408         }
00409 
00410     }
00411 
00412     if (path.isEmpty()) {
00413         kDebug() << "Theme says: bad image path " << name;
00414     }
00415 
00416     return path;
00417 }
00418 
00419 QString Theme::wallpaperPath(const QSize &size) const
00420 {
00421     QString fullPath;
00422     QString image = d->defaultWallpaperTheme;
00423 
00424     image.append("/contents/images/%1x%2").append(d->defaultWallpaperSuffix);
00425     QString defaultImage = image.arg(d->defaultWallpaperWidth).arg(d->defaultWallpaperHeight);
00426 
00427     if (size.isValid()) {
00428         // try to customize the paper to the size requested
00429         //TODO: this should do better than just fallback to the default size.
00430         //      a "best fit" matching would be far better, so we don't end
00431         //      up returning a 1920x1200 wallpaper for a 640x480 request ;)
00432         image = image.arg(size.width()).arg(size.height());
00433     } else {
00434         image = defaultImage;
00435     }
00436 
00437     //TODO: the theme's wallpaper overrides regularly installed wallpapers.
00438     //      should it be possible for user installed (e.g. locateLocal) wallpapers
00439     //      to override the theme?
00440     if (d->hasWallpapers) {
00441         // check in the theme first
00442         fullPath = d->findInTheme("wallpapers/" + image, d->themeName);
00443 
00444         if (fullPath.isEmpty()) {
00445             fullPath = d->findInTheme("wallpapers/" + defaultImage, d->themeName);
00446         }
00447     }
00448 
00449     if (fullPath.isEmpty()) {
00450         // we failed to find it in the theme, so look in the standard directories
00451         //kDebug() << "looking for" << image;
00452         fullPath = KStandardDirs::locate("wallpaper", image);
00453     }
00454 
00455     if (fullPath.isEmpty()) {
00456         // we still failed to find it in the theme, so look for the default in
00457         // the standard directories
00458         //kDebug() << "looking for" << defaultImage;
00459         fullPath = KStandardDirs::locate("wallpaper", defaultImage);
00460 
00461         if (fullPath.isEmpty()) {
00462             kDebug() << "exhausted every effort to find a wallpaper.";
00463         }
00464     }
00465 
00466     return fullPath;
00467 }
00468 
00469 bool Theme::currentThemeHasImage(const QString &name) const
00470 {
00471     if (name.contains("../")) {
00472         // we don't support relative paths
00473         return false;
00474     }
00475 
00476     return !(d->findInTheme(name + ".svgz", d->themeName).isEmpty()) ||
00477            !(d->findInTheme(name + ".svg", d->themeName).isEmpty());
00478 }
00479 
00480 KSharedConfigPtr Theme::colorScheme() const
00481 {
00482     return d->colors;
00483 }
00484 
00485 QColor Theme::color(ColorRole role) const
00486 {
00487     switch (role) {
00488         case TextColor:
00489             return d->colorScheme.foreground(KColorScheme::NormalText).color();
00490             break;
00491 
00492         case HighlightColor:
00493             return d->colorScheme.background(KColorScheme::ActiveBackground).color();
00494             break;
00495 
00496         case BackgroundColor:
00497             return d->colorScheme.background().color();
00498             break;
00499 
00500         case ButtonTextColor:
00501             return d->buttonColorScheme.foreground(KColorScheme::NormalText).color();
00502             break;
00503 
00504         case ButtonBackgroundColor:
00505             return d->buttonColorScheme.background(KColorScheme::ActiveBackground).color();
00506             break;
00507     }
00508 
00509     return QColor();
00510 }
00511 
00512 void Theme::setFont(const QFont &font, FontRole role)
00513 {
00514     Q_UNUSED(role)
00515     d->generalFont = font;
00516 }
00517 
00518 QFont Theme::font(FontRole role) const
00519 {
00520     Q_UNUSED(role)
00521     switch (role) {
00522     case DesktopFont:
00523     {
00524         KConfigGroup cg(KGlobal::config(), "General");
00525         return cg.readEntry("desktopFont", QFont("Sans Serif", 10));
00526     }
00527     break;
00528     case DefaultFont:
00529     default:
00530         return d->generalFont;
00531         break;
00532     }
00533 }
00534 
00535 QFontMetrics Theme::fontMetrics() const
00536 {
00537     //TODO: allow this to be overridden with a plasma specific font?
00538     return QFontMetrics(d->generalFont);
00539 }
00540 
00541 bool Theme::windowTranslucencyEnabled() const
00542 {
00543     return d->compositingActive;
00544 }
00545 
00546 void Theme::setUseGlobalSettings(bool useGlobal)
00547 {
00548     if (d->useGlobal == useGlobal) {
00549         return;
00550     }
00551 
00552     d->useGlobal = useGlobal;
00553     d->cfg = KConfigGroup();
00554     d->themeName.clear();
00555     settingsChanged();
00556 }
00557 
00558 bool Theme::useGlobalSettings() const
00559 {
00560     return d->useGlobal;
00561 }
00562 
00563 bool Theme::findInCache(const QString &key, QPixmap &pix)
00564 {
00565     return d->useCache() && d->pixmapCache->find(key, pix);
00566 }
00567 
00568 void Theme::insertIntoCache(const QString& key, const QPixmap& pix)
00569 {
00570     if (d->useCache()) {
00571         d->pixmapCache->insert(key, pix);
00572     }
00573 }
00574 
00575 bool Theme::findInRectsCache(const QString &image, const QString &element, QRectF &rect) const
00576 {
00577     if (!d->pixmapCache) {
00578         return false;
00579     }
00580 
00581     KConfigGroup imageGroup(d->svgElementsCache, image);
00582     rect = imageGroup.readEntry(element + "Size", QRectF());
00583 
00584     if (rect.isValid()) {
00585         return true;
00586     }
00587 
00588     bool invalid = false;
00589 
00590     QHash<QString, QSet<QString> >::iterator it = d->invalidElements.find(image);
00591     if (it == d->invalidElements.end()) {
00592         QSet<QString> elements = imageGroup.readEntry("invalidElements", QStringList()).toSet();
00593         d->invalidElements.insert(image, elements);
00594         invalid = elements.contains(element);
00595     } else {
00596         invalid = it.value().contains(element);
00597     }
00598 
00599     return invalid;
00600 }
00601 
00602 void Theme::insertIntoRectsCache(const QString& image, const QString &element, const QRectF &rect)
00603 {
00604     if (!d->pixmapCache) {
00605         return;
00606     }
00607 
00608     if (rect.isValid()) {
00609         KConfigGroup imageGroup(d->svgElementsCache, image);
00610         imageGroup.writeEntry(element + "Size", rect);
00611     } else {
00612         QHash<QString, QSet<QString> >::iterator it = d->invalidElements.find(image);
00613         if (it == d->invalidElements.end()) {
00614             d->invalidElements[image].insert(element);
00615         } else if (!it.value().contains(element)) {
00616             if (it.value().count() > 1000) {
00617                 it.value().erase(it.value().begin());
00618             }
00619 
00620             it.value().insert(element);
00621         }
00622     }
00623 }
00624 
00625 void Theme::invalidateRectsCache(const QString& image)
00626 {
00627     KConfigGroup imageGroup(d->svgElementsCache, image);
00628     imageGroup.deleteGroup();
00629 
00630     releaseRectsCache(image);
00631 }
00632 
00633 void Theme::releaseRectsCache(const QString &image)
00634 {
00635     QHash<QString, QSet<QString> >::iterator it = d->invalidElements.find(image);
00636     if (it != d->invalidElements.end()) {
00637         d->invalidElements.erase(it);
00638     }
00639 }
00640 
00641 void Theme::setCacheLimit(int kbytes)
00642 {
00643     if (d->useCache()) {
00644         d->pixmapCache->setCacheLimit(kbytes);
00645     }
00646 }
00647 
00648 }
00649 
00650 #include <theme.moc>

Plasma

Skip menu "Plasma"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.7
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal