From 347c0caac4d8d11b63e986b49873b4e56aeba105 Mon Sep 17 00:00:00 2001 From: David Nolden Date: Fri, 1 May 2009 00:41:41 +0000 Subject: [PATCH] - Allow painting a background color, or a background pattern behind freely floating elements of the theme, to provide a better fallback mechanism for transparent themes when composition is not available. The color/pattern is given through the configuration file, and updating due to changes of the configuration file works perfectly. A simple user-interface to use this will be added to the panel configuration. If you want to try this out, put into the [Theme] section of your plasmarc file: frameBackgroundColor=#aacc00 (Your picked color) frameBackgroundColorAlpha=120 (Alpha value for the color, between 0 and 255, 0=invisible, 255=opaque) frameBackgroundPattern=/path/to/image frameBackgroundPatternAlpha=255 (Alpha value for the pattern, as above) The color is painted first, then the pattern. They are painted into the mask defined by the theme, so this only works nicely with themes that supply proper masks. svn path=/trunk/KDE/kdelibs/; revision=961915 --- CMakeLists.txt | 2 + framebackgroundprovider.cpp | 27 +++++++ framebackgroundprovider.h | 56 +++++++++++++ framesvg.cpp | 29 +++++-- private/framesvg_p.h | 2 + svg.cpp | 46 ++++++----- theme.cpp | 153 +++++++++++++++++++++++++++++++++++- theme.h | 14 +++- 8 files changed, 294 insertions(+), 35 deletions(-) create mode 100644 framebackgroundprovider.cpp create mode 100644 framebackgroundprovider.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c8c6bf70d..f34aefce4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,6 +72,7 @@ set(plasma_LIB_SRCS framesvg.cpp plasma.cpp popupapplet.cpp + framebackgroundprovider.cpp private/applethandle.cpp private/datacontainer_p.cpp private/desktoptoolbox.cpp @@ -223,6 +224,7 @@ set(plasma_LIB_INCLUDES servicejob.h svg.h theme.h + framebackgroundprovider.h tooltipcontent.h tooltipmanager.h tooltipmanager.h diff --git a/framebackgroundprovider.cpp b/framebackgroundprovider.cpp new file mode 100644 index 000000000..ec9eaa166 --- /dev/null +++ b/framebackgroundprovider.cpp @@ -0,0 +1,27 @@ +/* + * Copyright 2009 David Nolden + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "framebackgroundprovider.h" + +namespace Plasma { + +FrameBackgroundProvider::~FrameBackgroundProvider() { +} + +} \ No newline at end of file diff --git a/framebackgroundprovider.h b/framebackgroundprovider.h new file mode 100644 index 000000000..6d0b7d3e6 --- /dev/null +++ b/framebackgroundprovider.h @@ -0,0 +1,56 @@ +/* + * Copyright 2009 David Nolden + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef PLASMA_PANELBACKGROUNDPROVIDER_H +#define PLASMA_PANELBACKGROUNDPROVIDER_H + +#include +#include + +class QPainter; +class QRegion; + +namespace Plasma { + +/** + * Abstract class to provide additional panel backgrounds behind translucent panels. + */ +class PLASMA_EXPORT FrameBackgroundProvider +{ +public: + virtual ~FrameBackgroundProvider(); + /** + * Returns an identity that can be used for caching the result of the background rendering. + * @return The identity string + */ + virtual QString identity() = 0; + + /** + * Applies the background to the given target. The target must have correct alpha-values, + * so the background can be painted under it. Also the clip-region must be already set correctly + * to restrict the area where the background is painted. + * @param target The target where the background should be painted + * @param offset Additional offset for the rendering: The render-source is translated by this offset + */ + virtual void apply(QPainter& target, QPoint offset = QPoint()) = 0; +}; + +} + +#endif // PLASMA_PANELBACKGROUNDPROVIDER_H diff --git a/framesvg.cpp b/framesvg.cpp index 3d4d62412..cab91ffe2 100644 --- a/framesvg.cpp +++ b/framesvg.cpp @@ -32,6 +32,7 @@ #include #include +#include "framebackgroundprovider.h" namespace Plasma { @@ -384,20 +385,28 @@ void FrameSvg::paintFrame(QPainter *painter, const QPointF &pos) painter->drawPixmap(pos, frame->cachedBackground); } +QString FrameSvgPrivate::cacheId(const FrameData* frame) const +{ + Theme *theme = Theme::defaultTheme(); + FrameBackgroundProvider* backgroundProvider = theme->frameBackgroundProvider(q->imagePath()); + + + return QString::fromLatin1("%6_%5_%4_%3_%2_%1_"). + arg(frame->enabledBorders).arg(frame->frameSize.width()).arg(frame->frameSize.height()).arg(prefix) + .arg(q->imagePath()).arg(backgroundProvider ? backgroundProvider->identity() : QString()); +} + void FrameSvgPrivate::generateBackground(FrameData *frame) { if (!frame->cachedBackground.isNull()) { return; } - - QString id = QString::fromLatin1("%5_%4_%3_%2_%1_"). - arg(frame->enabledBorders).arg(frame->frameSize.width()).arg(frame->frameSize.height()).arg(prefix).arg(q->imagePath()); + QString id = cacheId(frame); Theme *theme = Theme::defaultTheme(); - if (theme->findInCache(id, frame->cachedBackground) && !frame->cachedBackground.isNull()) { + if (theme->findInCache(id, frame->cachedBackground) && !frame->cachedBackground.isNull()) return; - } //kDebug() << "generating background"; const int topWidth = q->elementSize(prefix + "top").width(); @@ -602,6 +611,13 @@ void FrameSvgPrivate::generateBackground(FrameData *frame) p.drawPixmap(overlayPos, overlay, QRect(overlayPos, overlaySize)); } + if(!prefix.startsWith("mask-")) { + if(FrameBackgroundProvider* backgroundProvider = theme->frameBackgroundProvider(q->imagePath())) { + p.setClipRegion(q->mask() ); + backgroundProvider->apply(p); + } + } + if (!framesToSave.contains(prefix)) { framesToSave.append(prefix); } @@ -617,8 +633,7 @@ void FrameSvgPrivate::scheduledCacheUpdate() FrameData *frame = frames[prefix]; framesToSave.removeAll(prefixToSave); - QString id = QString::fromLatin1("%5_%4_%3_%2_%1_"). - arg(frame->enabledBorders).arg(frame->frameSize.width()).arg(frame->frameSize.height()).arg(prefix).arg(q->imagePath()); + QString id = cacheId(frame); //kDebug()<<"Saving to cache frame"<imagePath(themePath); - if (path == newPath) { - return; + if (path != newPath) { + path = newPath; + //delete d->renderer; we're a KSharedPtr + eraseRenderer(); + + // check if new theme svg wants colorscheme applied + bool wasApplyColors = applyColors; + checkApplyColorHint(); + if (applyColors && !Theme::defaultTheme()->colorScheme()) { + if (!wasApplyColors) { + QObject::connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), + q, SLOT(colorsChanged())); + } + } else { + QObject::disconnect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), + q, SLOT(colorsChanged())); + } + localRectCache.clear(); + itemsToSave.clear(); + saveTimer->stop(); } - - path = newPath; - //delete d->renderer; we're a KSharedPtr - eraseRenderer(); - - // check if new theme svg wants colorscheme applied - bool wasApplyColors = applyColors; - checkApplyColorHint(); - if (applyColors && !Theme::defaultTheme()->colorScheme()) { - if (!wasApplyColors) { - QObject::connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), - q, SLOT(colorsChanged())); - } - } else { - QObject::disconnect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), - q, SLOT(colorsChanged())); - } - - localRectCache.clear(); - itemsToSave.clear(); - saveTimer->stop(); - + //Even trigger a repaint when the theme path has not changed. + //It may be other aspects of the theme rendering that changed (Composition type, background color, ...) //kDebug() << themePath << ">>>>>>>>>>>>>>>>>> theme changed"; emit q->repaintNeeded(); } diff --git a/theme.cpp b/theme.cpp index 4915c7342..10108bb5a 100644 --- a/theme.cpp +++ b/theme.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #ifdef Q_WS_X11 #include #endif @@ -40,6 +41,7 @@ #include #include "private/packages_p.h" +#include "framebackgroundprovider.h" namespace Plasma { @@ -49,6 +51,32 @@ namespace Plasma static const int DEFAULT_WALLPAPER_WIDTH = 1920; static const int DEFAULT_WALLPAPER_HEIGHT = 1200; +class StandardThemeBackgroundProvider : public FrameBackgroundProvider { + public: + StandardThemeBackgroundProvider(); + virtual void apply(QPainter& target, QPoint offset); + virtual QString identity(); + QColor m_color; + QString m_pattern; + int m_patternAlpha; + int m_offsetX; + int m_offsetY; + + void clearCache() { + m_cachedPatterns.clear(); + } + + private: + //Maps file-name to (image, alpha) + typedef QPair PatternAlphaPair; //The alpha value is statically applied to the pattern + QMap m_cachedPatterns; +}; + +static StandardThemeBackgroundProvider& standardThemeBackgroundProvider() { + static StandardThemeBackgroundProvider ret; + return ret; +} + class ThemePrivate { public: @@ -82,6 +110,10 @@ public: return KConfigGroup(KSharedConfig::openConfig(themeRcFile), "CachePolicies"); } + const KConfigGroup& config() const { + return const_cast(this)->config(); + } + KConfigGroup &config() { if (!cfg.isValid()) { @@ -102,6 +134,28 @@ public: return cfg; } + /** + * Reads optional configuration, that is specific to the current composite mode: + * When composition is active, the configuration entry is prefixed with "composite_". + * Optionally, the configuration can also be specific to the specified image path: + * Then the image path has to be appended to the configuration name + */ + template + T readOptionalConfig(QString configName, T _default, QString imagePath) { + if(compositingActive) + configName = "composite_" + configName; + + T ret = config().readEntry(configName, _default); + return config().readEntry(configName + "_" + imagePath, ret); + } + + bool hasOptionalConfig(QString configName, QString imagePath = QString()) { + if(compositingActive) + configName = "composite_" + configName; + + return config().hasKey(configName) || config().hasKey(configName + "_" + imagePath); + } + QString findInTheme(const QString &image, const QString &theme) const; void compositingChanged(); void discardCache(); @@ -164,7 +218,7 @@ QString ThemePrivate::findInTheme(const QString &image, const QString &theme) co if (locolor) { search = "desktoptheme/" + theme + "/locolor/" + image; search = KStandardDirs::locate("data", search); - } else if (!compositingActive) { + } else if (!compositingActive && !config().readEntry("forceTransparentTheme", false)) { search = "desktoptheme/" + theme + "/opaque/" + image; search = KStandardDirs::locate("data", search); } @@ -292,13 +346,26 @@ PackageStructure::Ptr Theme::packageStructure() void ThemePrivate::settingsFileChanged(const QString &file) { kDebug() << file; + QMap< QString, QString > oldEntries = config().entryMap(); + config().config()->reparseConfiguration(); - q->settingsChanged(); + + if(oldEntries != config().entryMap()) + q->settingsChanged(); } void Theme::settingsChanged() { - d->setThemeName(d->config().readEntry("name", ThemePrivate::defaultTheme), false); + standardThemeBackgroundProvider().clearCache(); //So we don't waste memory with background images that are not used + + QString newThemeName = d->config().readEntry("name", ThemePrivate::defaultTheme); + if(newThemeName != d->themeName) { + d->setThemeName(newThemeName, false); + }else{ + ///@todo More precise monitoring of attributes + d->discardCache(true); + emit themeChanged(); + } } void Theme::setThemeName(const QString &themeName) @@ -697,6 +764,86 @@ void Theme::setCacheLimit(int kbytes) } } +StandardThemeBackgroundProvider::StandardThemeBackgroundProvider() : m_color(Qt::black), m_patternAlpha(0) { +} + +void StandardThemeBackgroundProvider::apply(QPainter& target, QPoint offset) { + target.setCompositionMode(QPainter::CompositionMode_DestinationOver); + + //Apply color + if(m_color.alpha()) + target.fillRect(target.clipRegion().boundingRect(), m_color); + + //Apply pattern + if(m_patternAlpha && !m_pattern.isEmpty()) { + if(!m_cachedPatterns.contains(m_pattern) || m_cachedPatterns[m_pattern].second != m_patternAlpha) { + m_cachedPatterns.remove(m_pattern); + m_cachedPatterns.insert(m_pattern, PatternAlphaPair(QImage(m_pattern), m_patternAlpha)); + if(m_patternAlpha != 255) { + PatternAlphaPair& cached(m_cachedPatterns[m_pattern]); + //Apply lower alpha value to the pattern + QImage alpha(cached.first.size(), QImage::Format_ARGB32); + alpha.fill(QColor(cached.second, cached.second, cached.second).rgb()); + cached.first.setAlphaChannel(alpha); + } + } + + PatternAlphaPair& cached(m_cachedPatterns[m_pattern]); + + if(!cached.first.isNull()) { + QBrush brush; + QColor col(Qt::white); + col.setAlpha(m_patternAlpha); + brush.setColor(col); + brush.setTextureImage(cached.first); + target.setBrushOrigin(-(m_offsetX + offset.x()), -(m_offsetY + offset.y())); + target.fillRect(target.clipRegion().boundingRect(), brush); + }else{ + kDebug() << "failed to load pattern" << m_pattern; + } + } +} + +QString StandardThemeBackgroundProvider::identity() { + return QString("bgcolor_%1=").arg(m_color.alpha()) + m_color.name()+QString("_pattern_%1=").arg(m_patternAlpha)+m_pattern + QString("_offsets_%1_%2__").arg(m_offsetX).arg(m_offsetY); +} + +FrameBackgroundProvider* Theme::frameBackgroundProvider(QString imagePath) const { + if(d->locolor) + return 0; + + if((imagePath.startsWith("widgets/panel-") || imagePath.startsWith("dialogs/")) && + (d->hasOptionalConfig("frameBackgroundColor") || d->hasOptionalConfig("frameBackgroundPattern"))) + { + StandardThemeBackgroundProvider& provider(standardThemeBackgroundProvider());; + + provider.m_color = d->readOptionalConfig("frameBackgroundColor", Qt::black, imagePath); + if(d->hasOptionalConfig("frameBackgroundColor")) + provider.m_color.setAlpha(d->readOptionalConfig("frameBackgroundColorAlpha", 255, imagePath)); + else + provider.m_color.setAlpha(0); + + provider.m_pattern = d->readOptionalConfig("frameBackgroundPattern", QString(), imagePath); + provider.m_patternAlpha = d->readOptionalConfig("frameBackgroundPatternAlpha", 255, imagePath); + provider.m_offsetX = d->readOptionalConfig("frameBackgroundPatternOffsetX", 0, imagePath); + provider.m_offsetY = d->readOptionalConfig("frameBackgroundPatternOffsetY", 0, imagePath); + int randomX = d->readOptionalConfig("frameBackgroundPatternOffsetRandomX", 0, imagePath); + int randomY = d->readOptionalConfig("frameBackgroundPatternOffsetRandomY", 0, imagePath); + + if(randomX || randomY) { + //Add "this" so the offsets are different after every startup, but stay same for the same image path + qsrand(qHash(imagePath) + ((size_t)this) + randomX + 11 * randomY); + if(randomX) + provider.m_offsetX += qrand() % randomX; + if(randomY) + provider.m_offsetY += qrand() % randomY; + } + + return &provider; + }else + return 0; +} + } #include diff --git a/theme.h b/theme.h index 0d15f94d3..ce5583c60 100644 --- a/theme.h +++ b/theme.h @@ -31,7 +31,7 @@ namespace Plasma { - +class FrameBackgroundProvider; class ThemePrivate; /** @@ -265,6 +265,18 @@ class PLASMA_EXPORT Theme : public QObject */ void releaseRectsCache(const QString &image); + /** + * Returns a frame background provider, that allows intelligently filling the + * background of the frame represented by the given image. + * + * The ownership stays with the theme object, and the background provider + * is only for immediate usage. + * + * @param imagePath image path identifying the frame + * @return the backgrond-provider or zero, depending on the image, settings, and composition mode + */ + FrameBackgroundProvider* frameBackgroundProvider(QString imagePath) const; + Q_SIGNALS: /** * Emitted when the user changes the theme. SVGs should be reloaded at