00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
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
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
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());
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
00287 if (d->themeName.isEmpty()) {
00288 theme = ThemePrivate::defaultTheme;
00289 } else {
00290 return;
00291 }
00292 }
00293
00294
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
00311 if (!d->themeName.isEmpty() && d->pixmapCache) {
00312 d->discardCache(false);
00313 }
00314
00315 d->themeName = theme;
00316
00317
00318 QString colorsFile = KStandardDirs::locate("data", "desktoptheme/" + theme + "/colors");
00319
00320
00321
00322 QString metadataPath(KStandardDirs::locate("data", "desktoptheme/" + theme + "/metadata.desktop"));
00323 KConfig metadata(metadataPath);
00324 KConfigGroup cg;
00325 if (metadata.hasGroup("Wallpaper")) {
00326
00327
00328 cg = KConfigGroup(&metadata, "Wallpaper");
00329 } else {
00330
00331
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
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
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
00389 if (name.contains("../")) {
00390
00391 return QString();
00392 }
00393
00394 QString path = d->findInTheme(name + ".svgz", d->themeName);
00395
00396 if (path.isEmpty()) {
00397
00398 path = d->findInTheme(name + ".svg", d->themeName);
00399
00400 if (path.isEmpty() && d->themeName != ThemePrivate::defaultTheme) {
00401
00402 path = d->findInTheme(name + ".svgz", ThemePrivate::defaultTheme);
00403
00404 if (path.isEmpty()) {
00405
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
00429
00430
00431
00432 image = image.arg(size.width()).arg(size.height());
00433 } else {
00434 image = defaultImage;
00435 }
00436
00437
00438
00439
00440 if (d->hasWallpapers) {
00441
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
00451
00452 fullPath = KStandardDirs::locate("wallpaper", image);
00453 }
00454
00455 if (fullPath.isEmpty()) {
00456
00457
00458
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
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
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>