diff --git a/clients/aurorae/AUTHORS b/clients/aurorae/AUTHORS new file mode 100644 index 0000000000..4d113408ee --- /dev/null +++ b/clients/aurorae/AUTHORS @@ -0,0 +1 @@ +Martin Gräßlin kde [at] martin [minus] graesslin [dot] com Developer and Maintainer \ No newline at end of file diff --git a/clients/aurorae/CMakeLists.txt b/clients/aurorae/CMakeLists.txt new file mode 100644 index 0000000000..d8b5ce01e6 --- /dev/null +++ b/clients/aurorae/CMakeLists.txt @@ -0,0 +1,14 @@ +project(aurorae) +set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ) + +find_package(KDE4 4.2.92 REQUIRED) +find_package(KDE4Workspace REQUIRED) +include (KDE4Defaults) +include (MacroLibrary) + +add_definitions(${QT_DEFINITIONS} ${KDE4_DEFINITIONS}) +include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDE4_INCLUDES}) + +add_subdirectory(src) +add_subdirectory(themes/example-deco) + diff --git a/clients/aurorae/README b/clients/aurorae/README new file mode 100644 index 0000000000..72e833a10b --- /dev/null +++ b/clients/aurorae/README @@ -0,0 +1,6 @@ +Aurorae is a themeable window decoration for KWin. + +It supports theme files consisting of several SVG files for decoration and buttons. Themes can be +installed and selected directly in the configuration module of KWin decorations. + +Please have a look at theme-description on how to write a theme file. \ No newline at end of file diff --git a/clients/aurorae/TODO b/clients/aurorae/TODO new file mode 100644 index 0000000000..4e30caa0dc --- /dev/null +++ b/clients/aurorae/TODO @@ -0,0 +1,3 @@ + * Button positions are not updated after theme change + * Delete themes from selection + * Get Hot New Stuff support \ No newline at end of file diff --git a/clients/aurorae/src/CMakeLists.txt b/clients/aurorae/src/CMakeLists.txt new file mode 100644 index 0000000000..6b580b96d4 --- /dev/null +++ b/clients/aurorae/src/CMakeLists.txt @@ -0,0 +1,24 @@ +########### decoration ############### + +set(kwin3_aurorae_PART_SRCS aurorae.cpp themeconfig.cpp) + +kde4_add_plugin(kwin3_aurorae ${kwin3_aurorae_PART_SRCS}) + +target_link_libraries(kwin3_aurorae ${KDE4_KDEUI_LIBS} ${KDE4_PLASMA_LIBS} ${KDE4WORKSPACE_KDECORATIONS_LIBS}) + +install(TARGETS kwin3_aurorae DESTINATION ${PLUGIN_INSTALL_DIR} ) + +########### install files ############### + +install( FILES aurorae.desktop DESTINATION ${DATA_INSTALL_DIR}/kwin ) + +########### config ############### +set(kwin_aurorae_config_PART_SRCS config/config.cpp themeconfig.cpp ) + +kde4_add_ui_files(kwin_aurorae_config_PART_SRCS config/config.ui) + +kde4_add_plugin(kwin_aurorae_config ${kwin_aurorae_config_PART_SRCS}) + +target_link_libraries(kwin_aurorae_config ${KDE4_KDEUI_LIBS} ${KDE4_KIO_LIBS} ${KDE4_PLASMA_LIBS} ${QT_QTGUI_LIBRARY}) + +install(TARGETS kwin_aurorae_config DESTINATION ${PLUGIN_INSTALL_DIR} ) diff --git a/clients/aurorae/src/aurorae.cpp b/clients/aurorae/src/aurorae.cpp new file mode 100644 index 0000000000..9b574d8d75 --- /dev/null +++ b/clients/aurorae/src/aurorae.cpp @@ -0,0 +1,714 @@ +/******************************************************************** +Copyright (C) 2009 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, 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 General Public License +along with this program. If not, see . +*********************************************************************/ + +#include "aurorae.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace Aurorae +{ + +AuroraeFactory::AuroraeFactory() + : QObject() + , KDecorationFactoryUnstable() +{ + init(); +} + +void AuroraeFactory::init() +{ + KConfig conf("auroraerc"); + KConfigGroup group(&conf, "Engine"); + + m_themeName = group.readEntry("ThemeName", "example-deco"); + + QString file("aurorae/themes/" + m_themeName + "/decoration.svg"); + QString path = KGlobal::dirs()->findResource("data", file); + if (path.isEmpty()) { + file += "z"; + path = KGlobal::dirs()->findResource("data", file); + } + if (path.isEmpty()) { + kDebug(1216) << "Could not find decoration svg: aborting"; + abort(); + } + m_frame.setImagePath(path); + m_frame.setCacheAllRenderedFrames(true); + m_frame.setEnabledBorders(Plasma::FrameSvg::AllBorders); + + // load the buttons + initButtonFrame("minimize"); + initButtonFrame("maximize"); + initButtonFrame("restore"); + initButtonFrame("close"); + initButtonFrame("alldesktops"); + initButtonFrame("keepabove"); + initButtonFrame("keepbelow"); + initButtonFrame("shade"); + initButtonFrame("help"); + + readThemeConfig(); +} + +void AuroraeFactory::readThemeConfig() +{ + // read config values + KConfig conf("aurorae/themes/" + m_themeName + "/" + m_themeName + "rc", KConfig::FullConfig, "data"); + m_themeConfig.load(&conf); +} + +AuroraeFactory::~AuroraeFactory() +{ + s_instance = NULL; +} + +AuroraeFactory *AuroraeFactory::instance() +{ + if (!s_instance) { + s_instance = new AuroraeFactory; + } + + return s_instance; +} + +bool AuroraeFactory::reset(unsigned long changed) +{ + // re-read config + m_frame.clearCache(); + m_buttons.clear(); + init(); + resetDecorations(changed); + return false; // need hard reset +} + +bool AuroraeFactory::supports(Ability ability) const +{ + switch (ability) { + case AbilityAnnounceButtons: + case AbilityUsesAlphaChannel: + case AbilityButtonMenu: + case AbilityButtonSpacer: + return true; + case AbilityButtonMinimize: + return m_buttons.contains("minimize"); + case AbilityButtonMaximize: + return m_buttons.contains("maximize") || m_buttons.contains("restore"); + case AbilityButtonClose: + return m_buttons.contains("close"); + case AbilityButtonAboveOthers: + return m_buttons.contains("keepabove"); + case AbilityButtonBelowOthers: + return m_buttons.contains("keepbelow"); + case AbilityButtonShade: + return m_buttons.contains("shade"); + case AbilityButtonOnAllDesktops: + return m_buttons.contains("alldesktops"); + case AbilityButtonHelp: + return m_buttons.contains("help"); + case AbilityProvidesShadow: + return m_themeConfig.shadow(); + default: + return false; + } +} + +KDecoration *AuroraeFactory::createDecoration(KDecorationBridge *bridge) +{ + AuroraeClient *client = new AuroraeClient(bridge, this); + return client->decoration(); +} + +void AuroraeFactory::initButtonFrame(const QString &button) +{ + QString file("aurorae/themes/" + m_themeName + "/" + button + ".svg"); + QString path = KGlobal::dirs()->findResource("data", file); + if (path.isEmpty()) { + // let's look for svgz + file.append("z"); + path = KGlobal::dirs()->findResource("data", file); + } + if (!path.isEmpty()) { + Plasma::FrameSvg *frame = new Plasma::FrameSvg(this); + frame->setImagePath(path); + frame->setCacheAllRenderedFrames(true); + frame->setEnabledBorders(Plasma::FrameSvg::NoBorder); + m_buttons[ button ] = frame; + } else { + kDebug(1216) << "No button for: " << button; + } +} + +Plasma::FrameSvg *AuroraeFactory::button(const QString &b) +{ + if (hasButton(b)) { + return m_buttons[ b ]; + } else { + return NULL; + } +} + + +AuroraeFactory *AuroraeFactory::s_instance = NULL; + +/******************************************************* +* Button +*******************************************************/ +AuroraeButton::AuroraeButton(ButtonType type, KCommonDecoration *parent) + : KCommonDecorationButton(type, parent) + , m_animationId(0) + , m_animationProgress(0.0) + , m_pressed(false) +{ + setAttribute(Qt::WA_NoSystemBackground); + connect(Plasma::Animator::self(), SIGNAL(customAnimationFinished(int)), this, SLOT(animationFinished(int))); +} + +void AuroraeButton::reset(unsigned long changed) +{ + Q_UNUSED(changed) +} + +void AuroraeButton::enterEvent(QEvent *event) +{ + Q_UNUSED(event) + if (m_animationId != 0) { + Plasma::Animator::self()->stopCustomAnimation(m_animationId); + } + m_animationProgress = 0.0; + int time = AuroraeFactory::instance()->themeConfig().animationTime(); + if (time != 0) { + m_animationId = Plasma::Animator::self()->customAnimation(40 / (1000.0 / qreal(time)), + time, Plasma::Animator::EaseInCurve, this, "animationUpdate"); + } + update(); +} + +void AuroraeButton::leaveEvent(QEvent *event) +{ + Q_UNUSED(event) + if (m_animationId != 0) { + Plasma::Animator::self()->stopCustomAnimation(m_animationId); + } + m_animationProgress = 0.0; + int time = AuroraeFactory::instance()->themeConfig().animationTime(); + if (time != 0) { + m_animationId = Plasma::Animator::self()->customAnimation(40 / (1000.0 / qreal(time)), + time, Plasma::Animator::EaseOutCurve, this, "animationUpdate"); + } + update(); +} + +void AuroraeButton::mousePressEvent(QMouseEvent *e) +{ + m_pressed = true; + update(); + KCommonDecorationButton::mousePressEvent(e); +} + +void AuroraeButton::mouseReleaseEvent(QMouseEvent *e) +{ + m_pressed = false; + update(); + KCommonDecorationButton::mouseReleaseEvent(e); +} + +void AuroraeButton::animationUpdate(double progress, int id) +{ + Q_UNUSED(id) + m_animationProgress = progress; + update(); +} + +void AuroraeButton::animationFinished(int id) +{ + if (m_animationId == id) { + m_animationId = 0; + update(); + } +} + +void AuroraeButton::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + if (decoration()->isPreview()) { + return; + } + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + bool active = decoration()->isActive(); + + if (type() == MenuButton) { + const QIcon icon = decoration()->icon(); + const QSize size = icon.actualSize(QSize(16, 16)); + QPixmap iconPix = icon.pixmap(size); + KIconEffect *effect = KIconLoader::global()->iconEffect(); + if (active) { + if (underMouse()) { + iconPix = effect->apply(iconPix, KIconLoader::Desktop, KIconLoader::ActiveState); + } + } else { + iconPix = effect->apply(iconPix, KIconLoader::Desktop, KIconLoader::DisabledState); + } + painter.drawPixmap(0, 0, iconPix); + return; + } + + ButtonStates states; + if (active) { + states |= Active; + } + if (underMouse()) { + states |= Hover; + } + if (m_pressed) { + states |= Pressed; + } + QString buttonName = ""; + switch (type()) { + case MinButton: + if (!decoration()->isMinimizable()) { + states |= Deactivated; + } + buttonName = "minimize"; + break; + case CloseButton: + if (!decoration()->isCloseable()) { + states |= Deactivated; + } + buttonName = "close"; + break; + case MaxButton: { + if (!decoration()->isMaximizable()) { + states |= Deactivated; + } + buttonName = "maximize"; + if (decoration()->maximizeMode() == KDecorationDefines::MaximizeFull && + !decoration()->options()->moveResizeMaximizedWindows()) { + buttonName = "restore"; + if (!AuroraeFactory::instance()->hasButton(buttonName)) { + buttonName = "maximize"; + } + } + break; + } + case OnAllDesktopsButton: + if (decoration()->isOnAllDesktops()) { + states |= Hover; + } + buttonName = "alldesktops"; + break; + case AboveButton: + buttonName = "keepabove"; + break; + case BelowButton: + buttonName = "keepbelow"; + break; + case ShadeButton: + if (!decoration()->isShadeable()) { + states |= Deactivated; + } + if (decoration()->isShade()) { + states |= Hover; + } + buttonName = "shade"; + break; + case HelpButton: + buttonName = "help"; + break; + default: + buttonName = QString(); + } + + if (!buttonName.isEmpty()) { + if (AuroraeFactory::instance()->hasButton(buttonName)) { + Plasma::FrameSvg *button = AuroraeFactory::instance()->button(buttonName); + paintButton(painter, button, states); + } + } +} + +void AuroraeButton::paintButton(QPainter &painter, Plasma::FrameSvg *frame, ButtonStates states) +{ + QString prefix = "active"; + QString animationPrefix = "active"; + bool hasInactive = false; + // check for inactive prefix + if (!states.testFlag(Active) && frame->hasElementPrefix("inactive")) { + // we have inactive, so we use it + hasInactive = true; + prefix = "inactive"; + animationPrefix = "inactive"; + } + + if (states.testFlag(Hover)) { + if (states.testFlag(Active)) { + if (frame->hasElementPrefix("hover")) { + prefix = "hover"; + } + } else { + if (hasInactive) { + if (frame->hasElementPrefix("hover-inactive")) { + prefix = "hover-inactive"; + } + } else { + if (frame->hasElementPrefix("hover")) { + prefix = "hover"; + } + } + } + } + if (states.testFlag(Pressed)) { + if (states.testFlag(Active)) { + if (frame->hasElementPrefix("pressed")) { + prefix = "pressed"; + } + } else { + if (hasInactive) { + if (frame->hasElementPrefix("pressed-inactive")) { + prefix = "pressed-inactive"; + } + } else { + if (frame->hasElementPrefix("pressed")) { + prefix = "pressed"; + } + } + } + } + if (states.testFlag(Deactivated)) { + if (states.testFlag(Active)) { + if (frame->hasElementPrefix("deactivated")) { + prefix = "deactivated"; + } + } else { + if (hasInactive) { + if (frame->hasElementPrefix("deactivated-inactive")) { + prefix = "deactivated-inactive"; + } + } else { + if (frame->hasElementPrefix("deactivated")) { + prefix = "deactivated"; + } + } + } + } + frame->setElementPrefix(prefix); + frame->resizeFrame(size()); + if (m_animationId != 0) { + // there is an animation so we have to use it + // the animation is definately a hover animation as currently nothing else is supported + if (!states.testFlag(Hover)) { + // only have to set for not hover state as animationPrefix is set to (in)active by default + if (states.testFlag(Active)) { + if (frame->hasElementPrefix("hover")) { + animationPrefix = "hover"; + } + } else { + if (hasInactive) { + if (frame->hasElementPrefix("hover-inactive")) { + animationPrefix = "hover-inactive"; + } + } else { + if (frame->hasElementPrefix("hover")) { + animationPrefix = "hover"; + } + } + } + } + QPixmap target = frame->framePixmap(); + frame->setElementPrefix(animationPrefix); + frame->resizeFrame(size()); + QPixmap result = Plasma::PaintUtils::transition(frame->framePixmap(), + target, m_animationProgress); + painter.drawPixmap(rect(), result); + } else { + frame->paintFrame(&painter); + } +} + +/******************************************************* +* Client +*******************************************************/ + +AuroraeClient::AuroraeClient(KDecorationBridge *bridge, KDecorationFactory *factory) + : KCommonDecorationUnstable(bridge, factory) +{ +} + +AuroraeClient::~AuroraeClient() +{ +} + +void AuroraeClient::init() +{ + KCommonDecoration::init(); +} + +void AuroraeClient::reset(unsigned long changed) +{ + widget()->update(); + + KCommonDecoration::reset(changed); +} + +QString AuroraeClient::visibleName() const +{ + return QString("Aurorae Theme Engine"); +} + +QString AuroraeClient::defaultButtonsLeft() const +{ + return AuroraeFactory::instance()->themeConfig().defaultButtonsLeft(); +} + +QString AuroraeClient::defaultButtonsRight() const +{ + return AuroraeFactory::instance()->themeConfig().defaultButtonsRight(); +} + +bool AuroraeClient::decorationBehaviour(DecorationBehaviour behavior) const +{ + switch (behavior) { + case DB_MenuClose: + return true; // Close on double click + + case DB_WindowMask: + case DB_ButtonHide: + return false; + default: + return false; + } +} + +int AuroraeClient::layoutMetric(LayoutMetric lm, bool respectWindowState, + const KCommonDecorationButton *button) const +{ + bool maximized = maximizeMode() == MaximizeFull && + !options()->moveResizeMaximizedWindows(); + const ThemeConfig &conf = AuroraeFactory::instance()->themeConfig(); + switch (lm) { + case LM_BorderLeft: + return maximized && respectWindowState ? 0 : conf.borderLeft(); + case LM_BorderRight: + return maximized && respectWindowState ? 0 : conf.borderRight(); + case LM_BorderBottom: + return maximized && respectWindowState ? 0 : conf.borderBottom(); + + case LM_OuterPaddingLeft: + return conf.paddingLeft(); + case LM_OuterPaddingRight: + return conf.paddingRight(); + case LM_OuterPaddingTop: + return conf.paddingTop(); + case LM_OuterPaddingBottom: + return conf.paddingBottom(); + + case LM_TitleEdgeLeft: + return conf.titleEdgeLeft(); + case LM_TitleEdgeRight: + return conf.titleBorderRight(); + case LM_TitleEdgeTop: + return conf.titleEdgeTop(); + case LM_TitleEdgeBottom: + return conf.titleEdgeBottom(); + + case LM_ButtonMarginTop: + return conf.buttonMarginTop(); + + case LM_TitleBorderLeft: + return conf.titleBorderLeft(); + case LM_TitleBorderRight: + return conf.titleBorderRight(); + case LM_TitleHeight: + return conf.titleHeight(); + + case LM_ButtonWidth: + return conf.buttonWidth(); + case LM_ButtonHeight: + return conf.buttonHeight(); + case LM_ButtonSpacing: + return conf.buttonSpacing(); + case LM_ExplicitButtonSpacer: + return conf.explicitButtonSpacer(); + + default: + return KCommonDecoration::layoutMetric(lm, respectWindowState, button); + } +} + +KCommonDecorationButton *AuroraeClient::createButton(ButtonType type) +{ + AuroraeFactory *factory = AuroraeFactory::instance(); + switch (type) { + case MenuButton: + return new AuroraeButton(type, this); + case MinButton: + if (factory->hasButton("minimize")) { + return new AuroraeButton(type, this); + } else { + return NULL; + } + case MaxButton: + if (factory->hasButton("maximize") || factory->hasButton("restore")) { + return new AuroraeButton(type, this); + } else { + return NULL; + } + case CloseButton: + if (factory->hasButton("close")) { + return new AuroraeButton(type, this); + } else { + return NULL; + } + case OnAllDesktopsButton: + if (factory->hasButton("alldesktops")) { + return new AuroraeButton(type, this); + } else { + return NULL; + } + case HelpButton: + if (factory->hasButton("help")) { + return new AuroraeButton(type, this); + } else { + return NULL; + } + case AboveButton: + if (factory->hasButton("keepabove")) { + return new AuroraeButton(type, this); + } else { + return NULL; + } + case BelowButton: + if (factory->hasButton("keepbelow")) { + return new AuroraeButton(type, this); + } else { + return NULL; + } + case ShadeButton: + if (factory->hasButton("shade")) { + return new AuroraeButton(type, this); + } else { + return NULL; + } + default: + return NULL; + } +} + +void AuroraeClient::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + if (isPreview()) { + return; + } + bool maximized = maximizeMode() == MaximizeFull && + !options()->moveResizeMaximizedWindows(); + + QPainter painter(widget()); + painter.setCompositionMode(QPainter::CompositionMode_Source); + + const ThemeConfig &conf = AuroraeFactory::instance()->themeConfig(); + Plasma::FrameSvg *frame = AuroraeFactory::instance()->frame(); + + frame->setElementPrefix("decoration"); + if (!isActive() && frame->hasElementPrefix("decoration-inactive")) { + frame->setElementPrefix("decoration-inactive"); + } + if (!compositingActive() && frame->hasElementPrefix("decoration-opaque")) { + frame->setElementPrefix("decoration-opaque"); + if (!isActive() && frame->hasElementPrefix("decoration-opaque-inactive")) { + frame->setElementPrefix("decoration-opaque-inactive"); + } + } + + // top + if (maximized) { + frame->setEnabledBorders(Plasma::FrameSvg::NoBorder); + } else { + frame->setEnabledBorders(Plasma::FrameSvg::AllBorders); + } + QRectF rect = QRectF(0.0, 0.0, widget()->width(), widget()->height()); + QRectF sourceRect = rect; + if (!compositingActive()) { + rect = QRectF(conf.paddingLeft(), conf.paddingTop(), + widget()->width()-conf.paddingRight()-conf.paddingLeft(), + widget()->height()-conf.paddingBottom()-conf.paddingTop()); + sourceRect = QRectF(0.0, 0.0, rect.width(), rect.height()); + } + frame->resizeFrame(rect.size()); + frame->paintFrame(&painter, rect, sourceRect); + + if (isActive()) { + painter.setPen(conf.activeTextColor()); + } else { + painter.setPen(conf.inactiveTextColor()); + } + painter.setFont(options()->font(isActive())); + painter.drawText(titleRect(), conf.alignment() | conf.verticalAlignment() | Qt::TextSingleLine, + caption()); +} + +void AuroraeClient::updateWindowShape() +{ + bool maximized = maximizeMode()==KDecorationDefines::MaximizeFull && !options()->moveResizeMaximizedWindows(); + int w=widget()->width(); + int h=widget()->height(); + + if (maximized) { + QRegion mask(0,0,w,h); + setMask(mask); + return; + } + + const ThemeConfig &conf = AuroraeFactory::instance()->themeConfig(); + Plasma::FrameSvg *deco = AuroraeFactory::instance()->frame(); + if (!deco->hasElementPrefix("decoration-opaque")) { + // opaque element is missing: set generic mask + QRegion mask(0,0,w,h); + setMask(mask); + return; + } + deco->setElementPrefix("decoration-opaque"); + deco->resizeFrame(QSize(w-conf.paddingLeft()-conf.paddingRight(), + h-conf.paddingTop()-conf.paddingBottom())); + QRegion mask = deco->mask().translated(conf.paddingLeft(), conf.paddingTop()); + setMask(mask); +} + +} // namespace Aurorae + +extern "C" +{ + KDE_EXPORT KDecorationFactory *create_factory() { + return Aurorae::AuroraeFactory::instance(); + } +} + + +#include "aurorae.moc" diff --git a/clients/aurorae/src/aurorae.desktop b/clients/aurorae/src/aurorae.desktop new file mode 100644 index 0000000000..fed257190f --- /dev/null +++ b/clients/aurorae/src/aurorae.desktop @@ -0,0 +1,3 @@ +[Desktop Entry] +Name=Aurorae Decoration Theme Engine +X-KDE-Library=kwin3_aurorae diff --git a/clients/aurorae/src/aurorae.h b/clients/aurorae/src/aurorae.h new file mode 100644 index 0000000000..e9485975c5 --- /dev/null +++ b/clients/aurorae/src/aurorae.h @@ -0,0 +1,132 @@ +/******************************************************************** +Copyright (C) 2009 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, 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 General Public License +along with this program. If not, see . +*********************************************************************/ + +#ifndef AURORAE_H +#define AURORAE_H + +#include "themeconfig.h" + +#include +#include +#include + +class KComponentData; + +namespace Aurorae +{ + +class AuroraeFactory : public QObject, public KDecorationFactoryUnstable +{ +public: + ~AuroraeFactory(); + + static AuroraeFactory* instance(); + bool reset(unsigned long changed); + KDecoration *createDecoration(KDecorationBridge*); + bool supports(Ability ability) const; + + Plasma::FrameSvg *frame() { + return &m_frame; + } + Plasma::FrameSvg *button(const QString& b); + bool hasButton(const QString& button) { + return m_buttons.contains(button); + } + ThemeConfig &themeConfig() { + return m_themeConfig; + } + +private: + AuroraeFactory(); + void init(); + void initButtonFrame(const QString& button); + void readThemeConfig(); + +private: + static AuroraeFactory *s_instance; + + // theme name + QString m_themeName; + ThemeConfig m_themeConfig; + // deco + Plasma::FrameSvg m_frame; + + // buttons + QHash< QString, Plasma::FrameSvg* > m_buttons; +}; + +class AuroraeButton : public KCommonDecorationButton +{ + Q_OBJECT + +public: + AuroraeButton(ButtonType type, KCommonDecoration *parent); + void reset(unsigned long changed); + void enterEvent(QEvent *event); + void leaveEvent(QEvent *event); + void paintEvent(QPaintEvent *event); + +public slots: + void animationUpdate(qreal progress, int id); + void animationFinished(int id); + +protected: + void mousePressEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); + +private: + enum ButtonState { + Active = 0x1, + Hover = 0x2, + Pressed = 0x4, + Deactivated = 0x8 + }; + Q_DECLARE_FLAGS(ButtonStates, ButtonState); + void paintButton(QPainter& painter, Plasma::FrameSvg* frame, ButtonStates states); + +private: + int m_animationId; + qreal m_animationProgress; + bool m_pressed; +}; + + +class AuroraeClient : public KCommonDecorationUnstable +{ +public: + AuroraeClient(KDecorationBridge *bridge, KDecorationFactory *factory); + ~AuroraeClient(); + + virtual void init(); + + virtual QString visibleName() const; + virtual QString defaultButtonsLeft() const; + virtual QString defaultButtonsRight() const; + virtual bool decorationBehaviour(DecorationBehaviour behaviour) const; + virtual int layoutMetric(LayoutMetric lm, bool respectWindowState = true, + const KCommonDecorationButton * = 0) const; + virtual KCommonDecorationButton *createButton(ButtonType type); + virtual void updateWindowShape(); + +protected: + void reset(unsigned long changed); + void paintEvent(QPaintEvent *event); +}; + +} + +#endif diff --git a/clients/aurorae/src/config/config.cpp b/clients/aurorae/src/config/config.cpp new file mode 100644 index 0000000000..293fe613ff --- /dev/null +++ b/clients/aurorae/src/config/config.cpp @@ -0,0 +1,570 @@ +/******************************************************************** +Copyright (C) 2009 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, 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 General Public License +along with this program. If not, see . +*********************************************************************/ + +#include "config.h" +#include "themeconfig.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +extern "C" +{ + KDE_EXPORT QObject *allocate_config(KConfig *conf, QWidget *parent) + { + return (new Aurorae::AuroraeConfig(conf, parent)); + } +} + +namespace Aurorae +{ + +//Theme selector code by Andre Duffeck (modified to add package description) +ThemeModel::ThemeModel(QObject *parent) + : QAbstractListModel(parent) +{ + reload(); +} + +ThemeModel::~ThemeModel() +{ + clearThemeList(); +} + +void ThemeModel::clearThemeList() +{ + foreach(const ThemeInfo &themeInfo, m_themes) { + delete themeInfo.svg; + } + m_themes.clear(); +} + +void ThemeModel::reload() +{ + reset(); + clearThemeList(); + + // get all desktop themes + QStringList themes = KGlobal::dirs()->findAllResources("data", + "aurorae/themes/*/metadata.desktop", + KStandardDirs::NoDuplicates); + foreach(const QString &theme, themes) { + int themeSepIndex = theme.lastIndexOf('/', -1); + QString themeRoot = theme.left(themeSepIndex); + int themeNameSepIndex = themeRoot.lastIndexOf('/', -1); + QString packageName = themeRoot.right(themeRoot.length() - themeNameSepIndex - 1); + + KDesktopFile df(theme); + QString name = df.readName(); + if (name.isEmpty()) { + name = packageName; + } + QString comment = df.readComment(); + QString author = df.desktopGroup().readEntry("X-KDE-PluginInfo-Author", QString()); + QString email = df.desktopGroup().readEntry("X-KDE-PluginInfo-Email", QString()); + QString version = df.desktopGroup().readEntry("X-KDE-PluginInfo-Version", QString()); + QString license = df.desktopGroup().readEntry("X-KDE-PluginInfo-License", QString()); + QString website = df.desktopGroup().readEntry("X-KDE-PluginInfo-Website", QString()); + + + Plasma::FrameSvg *svg = new Plasma::FrameSvg(this); + QString svgFile = themeRoot + "/decoration.svg"; + if (QFile::exists(svgFile)) { + svg->setImagePath(svgFile); + } else { + svg->setImagePath(svgFile + "z"); + } + svg->setEnabledBorders(Plasma::FrameSvg::AllBorders); + + ThemeConfig *config = new ThemeConfig(); + KConfig conf("aurorae/themes/" + packageName + "/" + packageName + "rc", KConfig::FullConfig, "data"); + config->load(&conf); + + // buttons + QHash *buttons = new QHash(); + initButtonFrame("minimize", packageName, buttons); + initButtonFrame("maximize", packageName, buttons); + initButtonFrame("restore", packageName, buttons); + initButtonFrame("close", packageName, buttons); + initButtonFrame("alldesktops", packageName, buttons); + initButtonFrame("keepabove", packageName, buttons); + initButtonFrame("keepbelow", packageName, buttons); + initButtonFrame("shade", packageName, buttons); + initButtonFrame("help", packageName, buttons); + + ThemeInfo info; + info.package = packageName; + info.description = comment; + info.author = author; + info.email = email; + info.version = version; + info.website = website; + info.license = license; + info.svg = svg; + info.themeRoot = themeRoot; + info.themeConfig = config; + info.buttons = buttons; + m_themes[name] = info; + } + + beginInsertRows(QModelIndex(), 0, m_themes.size()); + endInsertRows(); +} + +int ThemeModel::rowCount(const QModelIndex &) const +{ + return m_themes.size(); +} + +QVariant ThemeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + if (index.row() >= m_themes.size()) { + return QVariant(); + } + + QMap::const_iterator it = m_themes.constBegin(); + for (int i = 0; i < index.row(); ++i) { + ++it; + } + + switch (role) { + case Qt::DisplayRole: + return it.key(); + case PackageNameRole: + return (*it).package; + case SvgRole: + return qVariantFromValue((void*)(*it).svg); + case PackageDescriptionRole: + return (*it).description; + case PackageAuthorRole: + return (*it).author; + case PackageVersionRole: + return (*it).version; + case PackageEmailRole: + return (*it).email; + case PackageLicenseRole: + return (*it).license; + case PackageWebsiteRole: + return (*it).website; + case ThemeConfigRole: + return qVariantFromValue((void*)(*it).themeConfig); + case ButtonsRole: + return qVariantFromValue((void*)(*it).buttons); + default: + return QVariant(); + } +} + +int ThemeModel::indexOf(const QString &name) const +{ + QMapIterator it(m_themes); + int i = -1; + while (it.hasNext()) { + ++i; + if (it.next().value().package == name) { + return i; + } + } + + return -1; +} + +void ThemeModel::initButtonFrame(const QString &button, const QString &themeName, QHash *buttons) +{ + QString file("aurorae/themes/" + themeName + "/" + button + ".svg"); + QString path = KGlobal::dirs()->findResource("data", file); + if (path.isEmpty()) { + // let's look for svgz + file.append("z"); + path = KGlobal::dirs()->findResource("data", file); + } + if (!path.isEmpty()) { + Plasma::FrameSvg *frame = new Plasma::FrameSvg(this); + frame->setImagePath(path); + frame->setCacheAllRenderedFrames(true); + frame->setEnabledBorders(Plasma::FrameSvg::NoBorder); + buttons->insert(button, frame); + } +} + +/////////////////////////////////////////////////// +// ThemeDelegate +////////////////////////////////////////////////// +ThemeDelegate::ThemeDelegate(QObject* parent) + : QAbstractItemDelegate(parent) +{ +} + +void ThemeDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QString title = index.model()->data(index, Qt::DisplayRole).toString(); + + // highlight selected item + painter->save(); + if (option.state & QStyle::State_Selected) { + painter->setBrush(option.palette.color(QPalette::Highlight)); + } else { + painter->setBrush(Qt::gray); + } + painter->drawRect(option.rect); + painter->restore(); + + ThemeConfig *themeConfig = static_cast( + index.model()->data(index, ThemeModel::ThemeConfigRole).value()); + painter->save(); + paintDeco(painter, false, option, index, 5 + themeConfig->paddingLeft() + themeConfig->borderLeft(), + 5, 5, 5 + themeConfig->paddingBottom() + themeConfig->borderBottom() ); + painter->restore(); + painter->save(); + int activeLeft = 5; + int activeTop = 5 + themeConfig->paddingTop() + themeConfig->titleEdgeTop() + + themeConfig->titleEdgeBottom() + themeConfig->titleHeight(); + int activeRight = 5 + themeConfig->paddingRight() + themeConfig->borderRight(); + int activeBottom = 5; + paintDeco(painter, true, option, index, activeLeft, activeTop, activeRight, activeBottom); + painter->restore(); + + // paint title + painter->save(); + QFont font = painter->font(); + font.setWeight(QFont::Bold); + painter->setPen(themeConfig->activeTextColor()); + painter->setFont(font); + painter->drawText(QRect(option.rect.topLeft() + QPoint(activeLeft, activeTop), + option.rect.bottomRight() - QPoint(activeRight, activeBottom)), + Qt::AlignCenter | Qt::TextWordWrap, title); + painter->restore(); +} + +void ThemeDelegate::paintDeco(QPainter *painter, bool active, const QStyleOptionViewItem &option, const QModelIndex &index, + int leftMargin, int topMargin, + int rightMargin, int bottomMargin) const +{ + Plasma::FrameSvg *svg = static_cast( + index.model()->data(index, ThemeModel::SvgRole).value()); + svg->setElementPrefix("decoration"); + if (!active && svg->hasElementPrefix("decoration-inactive")) { + svg->setElementPrefix("decoration-inactive"); + } + svg->resizeFrame(QSize(option.rect.width() - leftMargin - rightMargin, option.rect.height() - topMargin - bottomMargin)); + svg->paintFrame(painter, option.rect.topLeft() + QPoint(leftMargin, topMargin)); + + ThemeConfig *themeConfig = static_cast( + index.model()->data(index, ThemeModel::ThemeConfigRole).value()); + + QHash *buttons = static_cast *>( + index.model()->data(index, ThemeModel::ButtonsRole).value()); + int y = option.rect.top() + topMargin + themeConfig->paddingTop() + themeConfig->titleEdgeTop() + themeConfig->buttonMarginTop(); + int x = option.rect.left() + leftMargin + themeConfig->paddingLeft() + themeConfig->titleEdgeLeft(); + int buttonWidth = themeConfig->buttonWidth(); + int buttonHeight = themeConfig->buttonHeight(); + foreach (const QChar &character, themeConfig->defaultButtonsLeft()) { + QString buttonName; + if (character == '_'){ + x += themeConfig->explicitButtonSpacer() + themeConfig->buttonSpacing(); + continue; + } + else if (character == 'M') { + KIcon icon = KIcon( "xorg" ); + QSize buttonSize(buttonWidth,buttonHeight); + painter->drawPixmap(QPoint(x,y), icon.pixmap(buttonSize)); + x += buttonWidth; + } + else if (character == 'S') { + buttonName = "alldesktops"; + } + else if (character == 'H') { + buttonName = "help"; + } + else if (character == 'I') { + buttonName = "minimize"; + } + else if (character == 'A') { + buttonName = "restore"; + if (!buttons->contains(buttonName)) { + buttonName = "maximize"; + } + } + else if (character == 'X') { + buttonName = "close"; + } + else if (character == 'F') { + buttonName = "keepabove"; + } + else if (character == 'B') { + buttonName = "keepbelow"; + } + else if (character == 'L') { + buttonName = "shade"; + } + if (!buttonName.isEmpty() && buttons->contains(buttonName)) { + Plasma::FrameSvg *frame = buttons->value(buttonName); + frame->setElementPrefix("active"); + if (!active && frame->hasElementPrefix("inactive")) { + frame->setElementPrefix("inactive"); + } + frame->resizeFrame(QSize(buttonWidth,buttonHeight)); + frame->paintFrame(painter, QPoint(x, y)); + x += buttonWidth; + } + x += themeConfig->buttonSpacing(); + } + if (!themeConfig->defaultButtonsLeft().isEmpty()) { + x -= themeConfig->buttonSpacing(); + } + int titleLeft = x; + + x = option.rect.right() - rightMargin - themeConfig->paddingRight() - themeConfig->titleEdgeRight() - buttonWidth; + QString rightButtons; + foreach (const QChar &character, themeConfig->defaultButtonsRight()) { + rightButtons.prepend(character); + } + foreach (const QChar &character, rightButtons) { + QString buttonName; + if (character == '_'){ + x -= themeConfig->explicitButtonSpacer() + themeConfig->buttonSpacing(); + continue; + } + else if (character == 'M') { + KIcon icon = KIcon( "xorg" ); + QSize buttonSize(buttonWidth,buttonHeight); + painter->drawPixmap(QPoint(x,y), icon.pixmap(buttonSize)); + x -= buttonWidth; + } + else if (character == 'S') { + buttonName = "alldesktops"; + } + else if (character == 'H') { + buttonName = "help"; + } + else if (character == 'I') { + buttonName = "minimize"; + } + else if (character == 'A') { + buttonName = "restore"; + if (!buttons->contains(buttonName)) { + buttonName = "maximize"; + } + } + else if (character == 'X') { + buttonName = "close"; + } + else if (character == 'F') { + buttonName = "keepabove"; + } + else if (character == 'B') { + buttonName = "keepbelow"; + } + else if (character == 'L') { + buttonName = "shade"; + } + if (!buttonName.isEmpty() && buttons->contains(buttonName)) { + Plasma::FrameSvg *frame = buttons->value(buttonName); + frame->setElementPrefix("active"); + if (!active && frame->hasElementPrefix("inactive")) { + frame->setElementPrefix("inactive"); + } + frame->resizeFrame(QSize(buttonWidth,buttonHeight)); + frame->paintFrame(painter, QPoint(x, y)); + x -= buttonWidth; + } + x -= themeConfig->buttonSpacing(); + } + x += buttonWidth; + if (!rightButtons.isEmpty()){ + x += themeConfig->buttonSpacing(); + } + int titleRight = x; + + // draw text + painter->save(); + if (active) { + painter->setPen(themeConfig->activeTextColor()); + } + else { + painter->setPen(themeConfig->inactiveTextColor()); + } + y = option.rect.top() + topMargin + themeConfig->paddingTop() + themeConfig->titleEdgeTop(); + QRectF titleRect(QPointF(titleLeft, y), QPointF(titleRight, y + themeConfig->titleHeight())); + QString caption = i18n("Active Window"); + if (!active) { + caption = i18n("Inactive Window"); + } + painter->drawText(titleRect, + themeConfig->alignment() | themeConfig->verticalAlignment() | Qt::TextSingleLine, + caption); + painter->restore(); +} + +QSize ThemeDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const +{ + return QSize(400, 200); +} + +/////////////////////////////////////////////////// +// AuroraeConfig +////////////////////////////////////////////////// + +AuroraeConfig::AuroraeConfig(KConfig* conf, QWidget* parent) + : QObject(parent) + , m_parent(parent) +{ + Q_UNUSED(conf) + m_ui = new AuroraeConfigUI(parent); + m_ui->aboutPushButton->setIcon(KIcon("dialog-information")); + + m_themeModel = new ThemeModel(this); + m_ui->theme->setModel(m_themeModel); + m_ui->theme->setItemDelegate(new ThemeDelegate(m_ui->theme->view())); + m_ui->theme->setMinimumSize(400, m_ui->theme->sizeHint().height()); + + m_config = new KConfig("auroraerc"); + KConfigGroup group(m_config, "Engine"); + load(group); + + connect(m_ui->theme, SIGNAL(currentIndexChanged(int)), this, SIGNAL(changed())); + connect(m_ui->installNewThemeButton, SIGNAL(clicked(bool)), this, SLOT(slotInstallNewTheme())); + connect(m_ui->aboutPushButton, SIGNAL(clicked(bool)), this, SLOT(slotAboutClicked())); + m_ui->show(); +} + +AuroraeConfig::AuroraeConfig::~AuroraeConfig() +{ + delete m_ui; + delete m_config; +} + +void AuroraeConfig::defaults() +{ + m_ui->theme->setCurrentIndex(m_themeModel->indexOf("example-deco")); +} + +void AuroraeConfig::load(const KConfigGroup &conf) +{ + QString theme = conf.readEntry("ThemeName", "example-deco"); + m_ui->theme->setCurrentIndex(m_themeModel->indexOf(theme)); +} + +void AuroraeConfig::save(KConfigGroup &conf) +{ + Q_UNUSED(conf) + KConfigGroup group(m_config, "Engine"); + int index = m_ui->theme->currentIndex(); + QString theme = m_ui->theme->itemData(index, ThemeModel::PackageNameRole).toString(); + group.writeEntry("ThemeName", theme); + group.sync(); +} + +void AuroraeConfig::slotAboutClicked() +{ + int index = m_ui->theme->currentIndex(); + const QString name = m_ui->theme->itemData(index, Qt::DisplayRole).toString(); + const QString comment = m_ui->theme->itemData(index, ThemeModel::PackageDescriptionRole).toString(); + const QString author = m_ui->theme->itemData(index, ThemeModel::PackageAuthorRole).toString(); + const QString email = m_ui->theme->itemData(index, ThemeModel::PackageEmailRole).toString(); + const QString website = m_ui->theme->itemData(index, ThemeModel::PackageWebsiteRole).toString(); + const QString version = m_ui->theme->itemData(index, ThemeModel::PackageVersionRole).toString(); + const QString license = m_ui->theme->itemData(index, ThemeModel::PackageLicenseRole).toString(); + + KAboutData aboutData(name.toUtf8(), name.toUtf8(), ki18n(name.toUtf8()), version.toUtf8(), ki18n(comment.toUtf8()), KAboutLicense::byKeyword(license).key(), ki18n(QByteArray()), ki18n(QByteArray()), website.toLatin1()); + aboutData.setProgramIconName("preferences-system-windows-action"); + const QStringList authors = author.split(','); + const QStringList emails = email.split(','); + int i = 0; + if (authors.count() == emails.count()) { + foreach(const QString &author, authors) { + if (!author.isEmpty()) { + aboutData.addAuthor(ki18n(author.toUtf8()), ki18n(QByteArray()), emails[i].toUtf8(), 0); + } + i++; + } + } + KAboutApplicationDialog aboutPlugin(&aboutData, m_parent); + aboutPlugin.exec(); +} + +void AuroraeConfig::slotInstallNewTheme() +{ + KUrl themeURL = KUrlRequesterDialog::getUrl(QString(), m_parent, + i18n("Drag or Type Theme URL")); + if (themeURL.url().isEmpty()) { + return; + } + + // themeTmpFile contains the name of the downloaded file + QString themeTmpFile; + + if (!KIO::NetAccess::download(themeURL, themeTmpFile, m_parent)) { + QString sorryText; + if (themeURL.isLocalFile()) { + sorryText = i18n("Unable to find the theme archive %1.", themeURL.prettyUrl()); + } + else { + sorryText = i18n("Unable to download theme archive;\n" + "please check that address %1 is correct.", themeURL.prettyUrl()); + } + KMessageBox::sorry(m_parent, sorryText); + return ; + } + + // TODO: check if archive contains a valid theme + // TODO: show a progress dialog + KTar archive(themeTmpFile); + archive.open(QIODevice::ReadOnly); + const KArchiveDirectory* themeDir = archive.directory(); + QString localThemesDir = KStandardDirs::locateLocal("data", "aurorae/themes/"); + foreach(const QString& entry, themeDir->entries()) { + // entry has to be a directory to contain a theme + const KArchiveEntry* possibleEntry = themeDir->entry(entry); + if (possibleEntry->isDirectory()) { + const KArchiveDirectory* dir = dynamic_cast(possibleEntry); + if (dir) { + dir->copyTo(localThemesDir + dir->name()); + } + } + } + // and reload + int index = m_ui->theme->currentIndex(); + const QString themeName = m_ui->theme->itemData(index, ThemeModel::PackageNameRole).toString(); + m_themeModel->reload(); + m_ui->theme->setCurrentIndex(m_themeModel->indexOf(themeName)); +} + +} // namespace + +#include "config.moc" diff --git a/clients/aurorae/src/config/config.h b/clients/aurorae/src/config/config.h new file mode 100644 index 0000000000..e1e3002903 --- /dev/null +++ b/clients/aurorae/src/config/config.h @@ -0,0 +1,132 @@ +/******************************************************************** +Copyright (C) 2009 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, 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 General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef AURORAE_CONFIG_H +#define AURORAE_CONFIG_H + +#include +#include +#include +#include + +#include "ui_config.h" + +namespace Plasma +{ +class FrameSvg; +} + +namespace Aurorae +{ +class ThemeConfig; + +//Theme selector code by Andre Duffeck (modified to add package description) +class ThemeInfo +{ +public: + QString package; + Plasma::FrameSvg *svg; + QString description; + QString author; + QString email; + QString version; + QString website; + QString license; + QString themeRoot; + ThemeConfig *themeConfig; + QHash *buttons; +}; + +class ThemeModel : public QAbstractListModel +{ +public: + enum { PackageNameRole = Qt::UserRole, + SvgRole = Qt::UserRole + 1, + PackageDescriptionRole = Qt::UserRole + 2, + PackageAuthorRole = Qt::UserRole + 3, + PackageVersionRole = Qt::UserRole + 4, + PackageLicenseRole = Qt::UserRole + 5, + PackageEmailRole = Qt::UserRole + 6, + PackageWebsiteRole = Qt::UserRole + 7, + ThemeConfigRole = Qt::UserRole + 8, + ButtonsRole = Qt::UserRole + 9 + }; + + ThemeModel(QObject *parent = 0); + virtual ~ThemeModel(); + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + int indexOf(const QString &name) const; + void reload(); + void clearThemeList(); + void initButtonFrame(const QString &button, const QString &path, QHash *buttons); +private: + QMap m_themes; +}; + +class ThemeDelegate : public QAbstractItemDelegate +{ +public: + ThemeDelegate(QObject *parent = 0); + + virtual void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + virtual QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const; +private: + void paintDeco(QPainter *painter, bool active, const QStyleOptionViewItem &option, const QModelIndex &index, + int leftMargin, int topMargin, + int rightMargin, int bottomMargin) const; +}; + +class AuroraeConfigUI : public QWidget, public Ui::ConfigUI +{ +public: + AuroraeConfigUI(QWidget *parent) : QWidget(parent) + { + setupUi(this); + } +}; + +class AuroraeConfig: public QObject +{ + Q_OBJECT +public: + AuroraeConfig(KConfig *conf, QWidget *parent); + ~AuroraeConfig(); + +signals: + void changed(); +public slots: + void load(const KConfigGroup &conf); + void save(KConfigGroup &conf); + void defaults(); +private slots: + void slotAboutClicked(); + void slotInstallNewTheme(); + +private: + QWidget *m_parent; + ThemeModel *m_themeModel; + KConfig *m_config; + AuroraeConfigUI *m_ui; +}; + +} // namespace + +#endif diff --git a/clients/aurorae/src/config/config.ui b/clients/aurorae/src/config/config.ui new file mode 100644 index 0000000000..5c6ec0b196 --- /dev/null +++ b/clients/aurorae/src/config/config.ui @@ -0,0 +1,98 @@ + + + ConfigUI + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + + + Theme: + + + theme + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Install New Theme + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + KPushButton + QPushButton +
kpushbutton.h
+
+
+ + +
diff --git a/clients/aurorae/src/themeconfig.cpp b/clients/aurorae/src/themeconfig.cpp new file mode 100644 index 0000000000..75b693fc28 --- /dev/null +++ b/clients/aurorae/src/themeconfig.cpp @@ -0,0 +1,85 @@ +/******************************************************************** +Copyright (C) 2009 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, 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 General Public License +along with this program. If not, see . +*********************************************************************/ +#include "themeconfig.h" + +#include +#include + +namespace Aurorae +{ + +ThemeConfig::ThemeConfig() +{ +} + +void ThemeConfig::load(KConfig *conf) +{ + KConfigGroup general(conf, "General"); + m_activeTextColor = general.readEntry("ActiveTextColor", QColor(Qt::black)); + m_inactiveTextColor = general.readEntry("InactiveTextColor", QColor(Qt::black)); + QString alignment = (general.readEntry("TitleAlignment", "Left")).toLower(); + if (alignment == "left") { + m_alignment = Qt::AlignLeft; + } + else if (alignment == "center") { + m_alignment = Qt::AlignCenter; + } + else { + m_alignment = Qt::AlignRight; + } + alignment = (general.readEntry("TitleVerticalAlignment", "Center")).toLower(); + if (alignment == "top") { + m_verticalAlignment = Qt::AlignTop; + } + else if (alignment == "center") { + m_verticalAlignment = Qt::AlignVCenter; + } + else { + m_verticalAlignment = Qt::AlignBottom; + } + m_animationTime = general.readEntry("Animation", 0); + m_defaultButtonsLeft = general.readEntry("LeftButtons", "MS"); + m_defaultButtonsRight = general.readEntry("RightButtons", "HIA__X"); + m_shadow = general.readEntry("Shadow", true); + + KConfigGroup border(conf, "Layout"); + // default values taken from KCommonDecoration::layoutMetric() in kcommondecoration.cpp + m_borderLeft = border.readEntry("BorderLeft", 5); + m_borderRight = border.readEntry("BorderRight", 5); + m_borderBottom = border.readEntry("BorderBottom", 5); + + m_titleEdgeTop = border.readEntry("TitleEdgeTop", 5); + m_titleEdgeBottom = border.readEntry("TitleEdgeBottom", 5); + m_titleEdgeLeft = border.readEntry("TitleEdgeLeft", 5); + m_titleEdgeRight = border.readEntry("TitleEdgeRight", 5); + m_titleBorderLeft = border.readEntry("TitleBorderLeft", 5); + m_titleBorderRight = border.readEntry("TitleBorderRight", 5); + m_titleHeight = border.readEntry("TitleHeight", 20); + + m_buttonWidth = border.readEntry("ButtonWidth", 20); + m_buttonHeight = border.readEntry("ButtonHeight", 20); + m_buttonSpacing = border.readEntry("ButtonSpacing", 5); + m_buttonMarginTop = border.readEntry("ButtonMarginTop", 0); + m_explicitButtonSpacer = border.readEntry("ExplicitButtonSpacer", 10); + + m_paddingLeft = border.readEntry("PaddingLeft", 0); + m_paddingRight = border.readEntry("PaddingRight", 0); + m_paddingTop = border.readEntry("PaddingTop", 0); + m_paddingBottom = border.readEntry("PaddingBottom", 0); +} + +} //namespace diff --git a/clients/aurorae/src/themeconfig.h b/clients/aurorae/src/themeconfig.h new file mode 100644 index 0000000000..1540694a85 --- /dev/null +++ b/clients/aurorae/src/themeconfig.h @@ -0,0 +1,166 @@ +/******************************************************************** +Copyright (C) 2009 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, 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 General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef THEMECONFIG_H +#define THEMECONFIG_H +// This class encapsulates all theme config values +// it's a seperate class as it's needed by both deco and config dialog + +#include +#include + +class KConfig; + +namespace Aurorae +{ +class ThemeConfig +{ +public: + ThemeConfig(); + void load(KConfig *conf); + ~ThemeConfig() {}; + // active window + QColor activeTextColor() const { + return m_activeTextColor; + } + // inactive window + QColor inactiveTextColor() const { + return m_inactiveTextColor; + } + // Alignment + Qt::Alignment alignment() const { + return m_alignment; + }; + Qt::Alignment verticalAlignment() const { + return m_verticalAlignment; + } + int animationTime() const { + return m_animationTime; + } + // Borders + int borderLeft() const { + return m_borderLeft; + } + int borderRight() const { + return m_borderRight; + } + int borderBottom() const { + return m_borderBottom; + } + + int titleEdgeTop() const { + return m_titleEdgeTop; + } + int titleEdgeBottom() const { + return m_titleEdgeBottom; + } + int titleEdgeLeft() const { + return m_titleEdgeLeft; + } + int titleEdgeRight() const { + return m_titleEdgeRight; + } + int titleBorderLeft() const { + return m_titleBorderLeft; + } + int titleBorderRight() const { + return m_titleBorderRight; + } + int titleHeight() const { + return m_titleHeight; + } + + int buttonWidth() const { + return m_buttonWidth; + } + int buttonHeight() const { + return m_buttonHeight; + } + int buttonSpacing() const { + return m_buttonSpacing; + } + int buttonMarginTop() const { + return m_buttonMarginTop; + } + int explicitButtonSpacer() const { + return m_explicitButtonSpacer; + } + + int paddingLeft() const { + return m_paddingLeft; + } + int paddingRight() const { + return m_paddingRight; + } + int paddingTop() const { + return m_paddingTop; + } + int paddingBottom() const { + return m_paddingBottom; + } + + QString defaultButtonsLeft() const { + return m_defaultButtonsLeft; + } + QString defaultButtonsRight() const { + return m_defaultButtonsRight; + } + bool shadow() const { + return m_shadow; + } + +private: + QColor m_activeTextColor; + QColor m_inactiveTextColor; + Qt::Alignment m_alignment; + Qt::Alignment m_verticalAlignment; + // borders + int m_borderLeft; + int m_borderRight; + int m_borderBottom; + + // title + int m_titleEdgeTop; + int m_titleEdgeBottom; + int m_titleEdgeLeft; + int m_titleEdgeRight; + int m_titleBorderLeft; + int m_titleBorderRight; + int m_titleHeight; + + // buttons + int m_buttonWidth; + int m_buttonHeight; + int m_buttonSpacing; + int m_buttonMarginTop; + int m_explicitButtonSpacer; + + // padding + int m_paddingLeft; + int m_paddingRight; + int m_paddingTop; + int m_paddingBottom; + + int m_animationTime; + + QString m_defaultButtonsLeft; + QString m_defaultButtonsRight; + bool m_shadow; +}; + +} + +#endif diff --git a/clients/aurorae/theme-description b/clients/aurorae/theme-description new file mode 100644 index 0000000000..54693be016 --- /dev/null +++ b/clients/aurorae/theme-description @@ -0,0 +1,122 @@ +DESCRIPTION OF AURORAE +====================== + +Aurorae is a theme engine for KWin window decorations. It is built against the unstable API of KWin +in KDE 4.3. Aurorae uses SVG to render the decoration and buttons and there is a simple config file +for configuring the theme details. + +This theme engine uses Plasma technologie to render the window decoration. Every detail can be +themed by the usage of SVG. The theme engine uses Plasma's FrameSvg, so you can provide SVG files +containing borders. This is described in more detail in techbase: +http://techbase.kde.org/Projects/Plasma/Theme#Backgrounds + +The theme consists of one folder containing svgz files for decoration and buttons, one KConfig file +for the theme details and one metadata.desktop file which you can use to name your theme, author +information, etc. + +Although the engine uses Plasma technology, it isn't Plasma. So it does not know anything about +Plasmoids and you will never be able to put Plasmoids into the decoration. That is out of scope of +this engine. + +Aurorae uses the features provided by KWin 4.3. So the themes can provide their own decoration +shadows and it is recommended that your themes provide those. The engine supports ARGB decoration +which is enabled by default. If you provide a theme using translucency, please make sure, that it +works without compositing as well. + +Window Decoration +================= +The window decoration has to be provided in file "decoration.svgz". This svg has to contain all the +elements required for a Plasma theme background. The decoration has to use the element prefix +"decoration". + +If you want to provide a different style for inactive windows you can add it to the same svg. The +inactive elements must have the element prefix "decoration-inactive". The theme engine tests for +this prefix and if not provided inactive windows will be rendered with the same style as active +windows. + +You have to provide a special decoration for opaque mode, that is when compositing is not active. +This opaque decoration is used for generating the window mask. The element prefix is +"decoration-opaque" for active and "decoration-opaque-inactive" for inactive windows. The mask is +generated from the active window. + +Buttons +======= +You have to provide a svgz file for each button your theme should contain. If you do not provide a +file for a button type the engine will not include that button, so your decoration will miss it. +There is no fallback to a default theme. The buttons are rendered using Plasma's FrameSvg as well. +So you have to provide the "center" element. Borders are not supported + +You can provide the following buttons: + * close + * minimize + * maximize + * restore + * alldesktops + * keepabove + * keepbelow + * shade + * resize + * help + +Each button can have different states. So a button could be hovered, pressed, deactivated and you +might want to provide different styles for active and inactive windows. You can use the following +element prefix to provide styles for the buttons: + * active (normal button for active window) + * inactive (normal button for inactive window) + * hover (hover state for active window) + * hover-inactive (hover state for inactive window) + * pressed (button is pressed) + * pressed-inactive (pressed inactive button) + * deactivated (button cannot be clicked, e.g. window cannot be closed) + * deactivated-inactive (same for inactive windows) + +You have at least to provide the active element. All other elements are optional and the active +element is always used as a fallback. If you provide the inactive element, this is used as a +fallback for the inactive window. That is, if you provide a hover element, but none for inactive, +the inactive window will not have a hover effect. Same is true for pressed and deactivated. +Reasonable that means if you provide a deactivated and an inactive element you want to provide a +deactivated-inactive element as well. + +Configuration file +================== +The configuration file is a normal KConfig file. You have to give it the name of your decoration +with suffix "rc". So if your theme has the name "deco", your config file will be named "decorc". +The following section shows the possible options with their default values. + +[General] +TitleAlignment=Left # vorizontal alignment of window title +TitleVerticalAlignment=Center # vertical alignment of window title +Animation=0 # animation duration in msec when hovering a button +ActiveTextColor=0,0,0,255 # title text color of active window +InactiveTextColor=0,0,0,255 # title text color of inactive window +LeftButtons=MS # buttons in left button group (see http://api.kde.org/4.x-api/kdebase-workspace-apidocs/kwin/lib/html/classKDecorationOptions.html#8ad12d76c93c5f1a12ea07b30f92d2fa) +RightButtons=HIA__X # buttons in right button group +Shadow=true # decoration provides shadows: you have to add padding + +[Layout] # uses Layout Manager (see http://api.kde.org/4.x-api/kdebase-workspace-apidocs/kwin/lib/html/classKCommonDecoration.html#7932f74c28432ad8de232f1c6e8751ce) +BorderLeft=5 +BorderRight=5 +BorderBottom=5 +TitleEdgeTop=5 +TitleEdgeBottom=5 +TitleEdgeLeft=5 +TitleEdgeRight=5 +TitleBorderLeft=5 +TitleBorderRight=5 +TitleHeight=20 +ButtonWidth=20 +ButtonHeight=20 +ButtonSpacing=5 +ButtonMarginTop=0 +ExplicitButtonSpacer=10 +PaddingTop=0 # Padding added to provide shadows +PaddingBottom=0 # Padding added to provide shadows +PaddingRight=0 # Padding added to provide shadows +PaddingLeft=0 # Padding added to provide shadows + +Packaging +========= +All theme files (decoration, buttons, metadata.desktop and configuration file) have to be stored in +one directory with the name of the theme (this has to be identical to the one used for the config +file). You have to create a tar.gz archive from that directory. This archive is the theme, which +can be installed in the kcm for window decorations. diff --git a/clients/aurorae/themes/example-deco/CMakeLists.txt b/clients/aurorae/themes/example-deco/CMakeLists.txt new file mode 100644 index 0000000000..5a738a3496 --- /dev/null +++ b/clients/aurorae/themes/example-deco/CMakeLists.txt @@ -0,0 +1,4 @@ + + +FILE(GLOB deco *.svgz) +install( FILES ${deco} example-decorc metadata.desktop DESTINATION ${DATA_INSTALL_DIR}/aurorae/themes/example-deco/ ) diff --git a/clients/aurorae/themes/example-deco/close.svgz b/clients/aurorae/themes/example-deco/close.svgz new file mode 100644 index 0000000000..91288e3dce Binary files /dev/null and b/clients/aurorae/themes/example-deco/close.svgz differ diff --git a/clients/aurorae/themes/example-deco/decoration.svgz b/clients/aurorae/themes/example-deco/decoration.svgz new file mode 100644 index 0000000000..e79bf862f9 Binary files /dev/null and b/clients/aurorae/themes/example-deco/decoration.svgz differ diff --git a/clients/aurorae/themes/example-deco/example-decorc b/clients/aurorae/themes/example-deco/example-decorc new file mode 100644 index 0000000000..f29d10f4f7 --- /dev/null +++ b/clients/aurorae/themes/example-deco/example-decorc @@ -0,0 +1,27 @@ +[General] +ActiveTextColor=0,0,0,255 +InactiveTextColor=178,178,178,178 +TitleAlignment=Center +TitleVerticalAlignment=Bottom +Animation=200 + +[Layout] +BorderLeft=5 +BorderRight=5 +BorderBottom=5 +TitleEdgeTop=5 +TitleEdgeBottom=5 +TitleEdgeLeft=5 +TitleEdgeRight=5 +TitleBorderLeft=5 +TitleBorderRight=5 +TitleHeight=20 +ButtonWidth=20 +ButtonHeight=20 +ButtonSpacing=5 +ButtonMarginTop=2 +ExplicitButtonSpacer=10 +PaddingTop=9 +PaddingBottom=9 +PaddingRight=9 +PaddingLeft=9 \ No newline at end of file diff --git a/clients/aurorae/themes/example-deco/maximize.svgz b/clients/aurorae/themes/example-deco/maximize.svgz new file mode 100644 index 0000000000..05b1c645bb Binary files /dev/null and b/clients/aurorae/themes/example-deco/maximize.svgz differ diff --git a/clients/aurorae/themes/example-deco/metadata.desktop b/clients/aurorae/themes/example-deco/metadata.desktop new file mode 100644 index 0000000000..fafee3b42c --- /dev/null +++ b/clients/aurorae/themes/example-deco/metadata.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Name=Example +Comment=An example theme based on Air desktop theme + +X-KDE-PluginInfo-Author=Martin Gräßlin +X-KDE-PluginInfo-Email=kde@martin-graesslin.com +X-KDE-PluginInfo-Name=example-deco +X-KDE-PluginInfo-Version=0.1 +X-KDE-PluginInfo-Category= +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL_V2 +X-KDE-PluginInfo-EnabledByDefault=true diff --git a/clients/aurorae/themes/example-deco/minimize.svgz b/clients/aurorae/themes/example-deco/minimize.svgz new file mode 100644 index 0000000000..2bf79b551b Binary files /dev/null and b/clients/aurorae/themes/example-deco/minimize.svgz differ diff --git a/clients/aurorae/themes/example-deco/restore.svgz b/clients/aurorae/themes/example-deco/restore.svgz new file mode 100644 index 0000000000..633de7bc3a Binary files /dev/null and b/clients/aurorae/themes/example-deco/restore.svgz differ