File indexing completed on 2024-04-28 16:55:25

0001 /*
0002  * kwin_smaragd.cpp - Emerald window decoration for KDE
0003  *
0004  * Copyright (c) 2010 Christoph Feck <christoph@maxiom.de>
0005  * Copyright (c) 2006 Novell, Inc.
0006  *
0007  * This program is free software; you can redistribute it and/or modify
0008  * it under the terms of the GNU General Public License as published by
0009  * the Free Software Foundation; either version 2 of the License, or
0010  * (at your option) any later version.
0011  *
0012  * This program is distributed in the hope that it will be useful,
0013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0015  * GNU General Public License for more details.
0016  *
0017  * You should have received a copy of the GNU General Public License
0018  * along with this program; if not, write to the Free Software
0019  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
0020  *
0021  */
0022 
0023 #include "kwin_smaragd.h"
0024 
0025 #include <KDecoration2/DecoratedClient>
0026 #include <KDecoration2/DecorationButtonGroup>
0027 #include <KDecoration2/DecorationSettings>
0028 #include <KDecoration2/DecorationShadow>
0029 
0030 #include <KConfig>
0031 #include <KConfigGroup>
0032 #include <KPluginFactory>
0033 
0034 #include <QDebug>
0035 #include <QPaintEngine>
0036 
0037 #include <QBitmap>
0038 #include <QPainter>
0039 #include <QPropertyAnimation>
0040 
0041 #include <cairo.h>
0042 
0043 extern "C"
0044 {
0045 
0046 #include <engine.h>
0047 
0048 void draw_button_with_glow_alpha_bstate(gint b_t, decor_t * d, cairo_t * cr,
0049                                         gint y1, gdouble button_alpha,
0050                                         gdouble glow_alpha, int b_state);
0051 
0052 void pango_layout_get_pixel_size(PangoLayout *layout, int *pwidth, int *pheight)
0053 {
0054     if (pwidth) {
0055         *pwidth = layout->bounding_width;
0056     }
0057     if (pheight) {
0058         *pheight = layout->bounding_height;
0059     }
0060 }
0061 
0062 void gdk_color_parse(gchar *s, GdkColor *c)
0063 {
0064     QString string = QString::fromLocal8Bit(s);
0065     QColor color(string);
0066     c->red = qRound(color.redF() * 65535);
0067     c->green = qRound(color.greenF() * 65535);
0068     c->blue = qRound(color.blueF() * 65535);
0069 }
0070 
0071 void gdk_drawable_get_size(GdkPixmap *pixmap, int *width, int *height)
0072 {
0073     *width = pixmap->width;
0074     *height = pixmap->height;
0075 }
0076 
0077 extern window_settings *create_settings();
0078 extern void update_settings(window_settings *ws);
0079 
0080 extern void legacy_load_engine_settings(GKeyFile *f, window_settings *ws);
0081 extern void line_load_engine_settings(GKeyFile *f, window_settings *ws);
0082 extern void oxygen_load_engine_settings(GKeyFile *f, window_settings *ws);
0083 extern void pixmap_load_engine_settings(GKeyFile *f, window_settings *ws);
0084 extern void truglass_load_engine_settings(GKeyFile *f, window_settings *ws);
0085 extern void vrunner_load_engine_settings(GKeyFile *f, window_settings *ws);
0086 extern void zootreeves_load_engine_settings(GKeyFile *f, window_settings *ws);
0087 
0088 extern void legacy_init_engine(window_settings *ws);
0089 extern void line_init_engine(window_settings *ws);
0090 extern void oxygen_init_engine(window_settings *ws);
0091 extern void pixmap_init_engine(window_settings *ws);
0092 extern void truglass_init_engine(window_settings *ws);
0093 extern void vrunner_init_engine(window_settings *ws);
0094 extern void zootreeves_init_engine(window_settings *ws);
0095 
0096 extern void legacy_engine_draw_frame(decor_t * d, cairo_t * cr);
0097 extern void line_engine_draw_frame(decor_t * d, cairo_t * cr);
0098 extern void oxygen_engine_draw_frame(decor_t * d, cairo_t * cr);
0099 extern void pixmap_engine_draw_frame(decor_t * d, cairo_t * cr);
0100 extern void truglass_engine_draw_frame(decor_t * d, cairo_t * cr);
0101 extern void vrunner_engine_draw_frame(decor_t * d, cairo_t * cr);
0102 extern void zootreeves_engine_draw_frame(decor_t * d, cairo_t * cr);
0103 
0104 static init_engine_proc init_engine;
0105 static draw_frame_proc draw_frame;
0106 static load_settings_proc load_settings;
0107 
0108 gboolean load_engine(gchar *engine, window_settings *ws)
0109 {
0110     if (!engine || !strcmp(engine, "legacy")) {
0111         init_engine = legacy_init_engine;
0112         draw_frame = legacy_engine_draw_frame;
0113         load_settings = legacy_load_engine_settings;
0114     } else if (!strcmp(engine, "line")) {
0115         init_engine = line_init_engine;
0116         draw_frame = line_engine_draw_frame;
0117         load_settings = line_load_engine_settings;
0118     } else if (!strcmp(engine, "oxygen")) {
0119         init_engine = oxygen_init_engine;
0120         draw_frame = oxygen_engine_draw_frame;
0121         load_settings = oxygen_load_engine_settings;
0122     } else if (!strcmp(engine, "pixmap")) {
0123         init_engine = pixmap_init_engine;
0124         draw_frame = pixmap_engine_draw_frame;
0125         load_settings = pixmap_load_engine_settings;
0126     } else if (!strcmp(engine, "truglass")) {
0127         init_engine = truglass_init_engine;
0128         draw_frame = truglass_engine_draw_frame;
0129         load_settings = truglass_load_engine_settings;
0130     } else if (!strcmp(engine, "vrunner")) {
0131         init_engine = vrunner_init_engine;
0132         draw_frame = vrunner_engine_draw_frame;
0133         load_settings = vrunner_load_engine_settings;
0134     } else if (!strcmp(engine, "zootreeves")) {
0135         init_engine = zootreeves_init_engine;
0136         draw_frame = zootreeves_engine_draw_frame;
0137         load_settings = zootreeves_load_engine_settings;
0138     } else {
0139         return false;
0140     }
0141     init_engine(ws);
0142     return true;
0143 }
0144 
0145 
0146 void load_engine_settings(GKeyFile *f, window_settings *ws)
0147 {
0148     load_settings(f, ws);
0149 }
0150 
0151 #define CORNER_REDUCTION 3
0152 
0153 int update_shadow(frame_settings * fs)
0154 {
0155     window_settings *ws = fs->ws;
0156 
0157     int size = ws->shadow_radius * 2 + 2;
0158 
0159     ws->shadow_offset_x = ws->shadow_offset_y = size = 0;
0160 
0161     if (ws->shadow_radius <= 0.0 && ws->shadow_offset_x == 0 &&
0162         ws->shadow_offset_y == 0)
0163         size = 0;
0164 
0165     size = size / 2;
0166 
0167     ws->left_space = ws->win_extents.left + size - ws->shadow_offset_x;
0168     ws->right_space = ws->win_extents.right + size + ws->shadow_offset_x;
0169     ws->top_space = ws->win_extents.top + size - ws->shadow_offset_y;
0170     ws->bottom_space = ws->win_extents.bottom + size + ws->shadow_offset_y;
0171 
0172 
0173     ws->left_space = MAX(ws->win_extents.left, ws->left_space);
0174     ws->right_space = MAX(ws->win_extents.right, ws->right_space);
0175     ws->top_space = MAX(ws->win_extents.top, ws->top_space);
0176     ws->bottom_space = MAX(ws->win_extents.bottom, ws->bottom_space);
0177 
0178     ws->shadow_left_space = MAX(0, size - ws->shadow_offset_x);
0179     ws->shadow_right_space = MAX(0, size + ws->shadow_offset_x);
0180     ws->shadow_top_space = MAX(0, size - ws->shadow_offset_y);
0181     ws->shadow_bottom_space = MAX(0, size + ws->shadow_offset_y);
0182 
0183     ws->shadow_left_corner_space = MAX(0, size + ws->shadow_offset_x);
0184     ws->shadow_right_corner_space = MAX(0, size - ws->shadow_offset_x);
0185     ws->shadow_top_corner_space = MAX(0, size + ws->shadow_offset_y);
0186     ws->shadow_bottom_corner_space = MAX(0, size - ws->shadow_offset_y);
0187 
0188     ws->left_corner_space =
0189         MAX(0, ws->shadow_left_corner_space - CORNER_REDUCTION);
0190     ws->right_corner_space =
0191         MAX(0, ws->shadow_right_corner_space - CORNER_REDUCTION);
0192     ws->top_corner_space =
0193         MAX(0, ws->shadow_top_corner_space - CORNER_REDUCTION);
0194     ws->bottom_corner_space =
0195         MAX(0, ws->shadow_bottom_corner_space - CORNER_REDUCTION);
0196 
0197     ws->normal_top_corner_space =
0198         MAX(0, ws->top_corner_space - ws->titlebar_height);
0199 /*
0200     d.width =
0201         ws->left_space + ws->left_corner_space + 1 +
0202         ws->right_corner_space + ws->right_space;
0203     d.height =
0204         ws->top_space + ws->titlebar_height +
0205         ws->normal_top_corner_space + 2 + ws->bottom_corner_space +
0206         ws->bottom_space;
0207 */
0208     return 1;
0209 }
0210 
0211 struct _GdkPixbuf
0212 {
0213     QImage image;
0214 };
0215 
0216 void g_object_unref(void *x)
0217 {
0218     delete (_GdkPixbuf *) x;
0219 }
0220 
0221 int gdk_pixbuf_get_width(GdkPixbuf *pixbuf)
0222 {
0223     return pixbuf->image.width();
0224 }
0225 
0226 int gdk_pixbuf_get_height(GdkPixbuf *pixbuf)
0227 {
0228     return pixbuf->image.height();
0229 }
0230 
0231 GdkPixbuf *gdk_pixbuf_new(GdkColorspace colorspace, gboolean has_alpha, int bits_per_sample, int w, int h)
0232 {
0233     Q_ASSERT(colorspace == GDK_COLORSPACE_RGB);
0234     Q_ASSERT(bits_per_sample == 8);
0235 
0236     _GdkPixbuf *pixbuf = new _GdkPixbuf;
0237     pixbuf->image = QImage(w, h, has_alpha ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32);
0238     pixbuf->image.fill(qRgba(0, 0, 0, 0));
0239     return pixbuf;
0240 }
0241 
0242 GdkPixbuf *gdk_pixbuf_new_from_file(gchar *file, GError **/*error*/)
0243 {
0244     QImage image = QImage(QString::fromLocal8Bit(file));
0245 
0246     if (image.isNull()) {
0247         return 0;
0248     }
0249     _GdkPixbuf *pixbuf = new _GdkPixbuf;
0250     pixbuf->image = image;
0251     return pixbuf;
0252 }
0253 
0254 GdkPixbuf *gdk_pixbuf_new_subpixbuf(GdkPixbuf *source, int x, int y, int w, int h)
0255 {
0256     _GdkPixbuf *pixbuf = new _GdkPixbuf;
0257     pixbuf->image = source->image.copy(x, y, w, h);
0258     return pixbuf;
0259 }
0260 
0261 void gdk_pixbuf_scale(GdkPixbuf *source, GdkPixbuf *dest, int x, int y, int w, int h, double source_x, double source_y, double scale_x, double scale_y, int interp)
0262 {
0263     QPainter p(&dest->image);
0264     if (interp == GDK_INTERP_BILINEAR) {
0265         p.setRenderHint(QPainter::SmoothPixmapTransform, true);
0266     }
0267     p.setCompositionMode(QPainter::CompositionMode_Source);
0268     p.drawImage(QRect(x, y, w, h), source->image, QRect(qRound(source_x), qRound(source_y), qRound(w / scale_x), qRound(h / scale_y)));
0269     p.end();
0270 }
0271 
0272 int gdk_pixbuf_get_colorspace(GdkPixbuf */*pixbuf*/)
0273 {
0274     return GDK_COLORSPACE_RGB;
0275 }
0276 
0277 int gdk_pixbuf_get_bits_per_sample(GdkPixbuf */*pixbuf*/)
0278 {
0279     return 8;
0280 }
0281 
0282 }
0283 
0284 K_PLUGIN_FACTORY_WITH_JSON(SmaragdDecorationFactory,
0285     "smaragd.json",
0286     registerPlugin<Smaragd::Decoration>();
0287 )
0288 
0289 namespace Smaragd
0290 {
0291 
0292 static QRegion findCornerShape(const QImage &image, int corner, const QSize &maxSize)
0293 {
0294     QSize cornerSize = maxSize.boundedTo(image.size());
0295     QImage cornerImage(cornerSize, QImage::Format_MonoLSB);
0296     cornerImage.fill(1);
0297 
0298     int xd = 1, yd = 1; // scanning direction
0299     int sx = 0, sy = 0;
0300     int cx = 0, cy = 0;
0301     if (corner & 1) {
0302         xd = -1;
0303         sx = image.width() - 1;
0304         cx = cornerImage.width() - 1;
0305     }
0306     if (corner & 2) {
0307         yd = -1;
0308         sy = image.height() - 1;
0309         cy = cornerImage.height() - 1;
0310     }
0311 
0312     int threshold = qAlpha(QRgb(image.pixel(sx + (cornerSize.width() - 1) * xd, sy))) >> 1;
0313     for (int y = 0, ys = sy, yc = cy; y < cornerSize.height(); ++y, ys += yd, yc += yd) {
0314         for (int x = 0, xs = sx, xc = cx; x < cornerSize.width(); ++x, xs += xd, xc += xd) {
0315             QRgb pixel = QRgb(image.pixel(xs, ys));
0316             if (qAlpha(pixel) >= threshold) {
0317                 break;
0318             }
0319             cornerImage.setPixel(xc, yc, 0);
0320         }
0321     }
0322     return QRegion(QBitmap::fromImage(cornerImage));
0323 }
0324 
0325 
0326 DecorationFactory::DecorationFactory()
0327 {
0328     ws = create_settings();
0329 }
0330 
0331 DecorationFactory::~DecorationFactory()
0332 {
0333     free(ws);
0334 }
0335 
0336 void DecorationFactory::setFontHeight(int fontHeight)
0337 {
0338     ws->text_height = fontHeight;
0339     update_settings(ws);
0340 
0341     QImage decoImage = decorationImage(QSize(96, 64), true, 0, QRect(32, 8, 32, 8));
0342     QPainter p(&decoImage);
0343     QRect rect(0, 0, 96, 64);
0344     rect.adjust(ws->left_space + ws->left_corner_space, ws->top_space + ws->normal_top_corner_space + ws->titlebar_height,
0345         -(ws->right_space + ws->right_corner_space), -(ws->bottom_space + ws->bottom_corner_space));
0346     p.fillRect(rect, Qt::black);
0347     p.end();
0348     for (int corner = 0; corner < 4; ++corner) {
0349         cornerRegion[corner] = findCornerShape(decoImage, corner, QSize(32, 32));
0350     }
0351 
0352     KConfig configFile(QLatin1String("kwinsmaragdrc"));
0353     KConfigGroup configGroup(&configFile, "General");
0354 
0355     m_config.useKWinTextColors = configGroup.readEntry("UseKWinTextColors", false);
0356     m_config.useKWinShadows = configGroup.readEntry("UseKWinShadows", false);
0357     m_config.hoverDuration = configGroup.readEntry("HoverDuration", 200);
0358 
0359     if (!m_config.useKWinShadows) {
0360         m_config.shadowSettings.radius = configGroup.readEntry("ShadowRadius", 5);
0361         m_config.shadowSettings.color = configGroup.readEntry("ShadowColor", QColor(0, 0, 0));
0362         m_config.shadowSettings.color.setAlpha(configGroup.readEntry("ShadowAlpha", 180));
0363         m_config.shadowSettings.offsetX = configGroup.readEntry("ShadowOffsetX", 0);
0364         m_config.shadowSettings.offsetY = configGroup.readEntry("ShadowOffsetY", 0);
0365         m_config.shadowSettings.size = configGroup.readEntry("ShadowSize", -3);
0366         m_config.shadowSettings.linearDecay = configGroup.readEntry("ShadowLinearDecay", 1.0);
0367         m_config.shadowSettings.exponentialDecay = configGroup.readEntry("ShadowExponentialDecay", 6.0);
0368 
0369         m_config.shadowImage = createShadowImage(m_config.shadowSettings);
0370     }
0371 }
0372 
0373 QRegion DecorationFactory::cornerShape(int corner) const
0374 {
0375     return cornerRegion[corner];
0376 }
0377 
0378 Decoration::Decoration(QObject *parent, const QVariantList &args)
0379     : KDecoration2::Decoration(parent, args)
0380     , m_titleLeft(0)
0381     , m_titleRight(0)
0382 {
0383 }
0384 
0385 Decoration::~Decoration()
0386 {
0387 }
0388 
0389 void Decoration::init()
0390 {
0391     connect(client().data(), &KDecoration2::DecoratedClient::widthChanged, this, &Decoration::updateLayout);
0392     connect(client().data(), &KDecoration2::DecoratedClient::heightChanged, this, &Decoration::updateLayout);
0393     connect(client().data(), &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::updateLayout);
0394     connect(client().data(), &KDecoration2::DecoratedClient::shadedChanged, this, &Decoration::updateLayout);
0395 
0396     connect(client().data(), &KDecoration2::DecoratedClient::paletteChanged, this, [this]() { update(); });
0397     connect(client().data(), &KDecoration2::DecoratedClient::iconChanged, this, [this]() { update(); });
0398     connect(client().data(), &KDecoration2::DecoratedClient::captionChanged, this, [this]() { update(); });
0399     connect(client().data(), &KDecoration2::DecoratedClient::activeChanged, this, [this]() { update(); });
0400 
0401     window_settings *ws = factory()->windowSettings();
0402     factory()->setFontHeight(settings()->fontMetrics().height());
0403     parseButtonLayout(ws->tobj_layout ? ws->tobj_layout : (char *) "I:T:NXC");
0404 
0405     QVector<QPointer<KDecoration2::DecorationButton>> buttons;
0406     buttons = m_buttonGroup[0]->buttons();
0407     if (!buttons.isEmpty()) {
0408         KDecoration2::DecorationButton *button = buttons.at(0);
0409         if (button->type() == KDecoration2::DecorationButtonType::Custom && button->geometry().width() < 0) {
0410             m_titleLeft = button->geometry().width();
0411             m_buttonGroup[0]->removeButton(button);
0412         }
0413     }
0414     buttons = m_buttonGroup[2]->buttons();
0415     if (!buttons.isEmpty()) {
0416         KDecoration2::DecorationButton *button = buttons.at(buttons.count() - 1);
0417         if (button->type() == KDecoration2::DecorationButtonType::Custom && button->geometry().width() < 0) {
0418             m_titleRight = button->geometry().width();
0419             m_buttonGroup[2]->removeButton(button);
0420         }
0421     }
0422 
0423     KDecoration2::DecorationShadow *shadow = new KDecoration2::DecorationShadow();
0424     const Config *config = factory()->config();
0425     QImage image = config->shadowImage;
0426     shadow->setShadow(image);
0427     shadow->setInnerShadowRect(QRect(image.width() / 2, image.height() / 2, 1, 1));
0428     int p = 32 + config->shadowSettings.size;
0429     shadow->setPadding(QMargins(p, p, p, p));
0430     setShadow(QSharedPointer<KDecoration2::DecorationShadow>(shadow));
0431 
0432     updateLayout();
0433 }
0434 
0435 void Decoration::updateLayout()
0436 {
0437     window_settings *ws = factory()->windowSettings();
0438     bool horizontalBorders = !client().data()->isMaximizedHorizontally();
0439     bool verticalBorders = !client().data()->isMaximizedVertically();
0440     factory()->setFontHeight(settings()->fontMetrics().height());
0441     setBorders(QMargins(
0442         horizontalBorders ? ws->left_space + ws->left_corner_space : 0,
0443         ws->top_space + ws->normal_top_corner_space + ws->titlebar_height,
0444         horizontalBorders ? ws->right_space + ws->right_corner_space : 0,
0445         verticalBorders ? ws->bottom_space + ws->bottom_corner_space : 0
0446     ));
0447     setTitleBar(QRect(2, 4, size().width() - 2 * 2, borderTop() - 4));
0448     int titleEdgeLeft = horizontalBorders ? ws->left_space + ws->button_hoffset + m_titleLeft : 0;
0449     int titleEdgeRight = horizontalBorders ? ws->right_space + ws->button_hoffset + m_titleRight : 0;
0450 
0451     m_buttonGroup[0]->setPos(QPointF(titleEdgeLeft, 0));
0452     m_buttonGroup[2]->setPos(QPointF(size().width() - qRound(m_buttonGroup[2]->geometry().width()) - titleEdgeRight, 0));
0453 }
0454 
0455 int Decoration::buttonGlyph(KDecoration2::DecorationButtonType type) const
0456 {
0457     switch (type) {
0458     case KDecoration2::DecorationButtonType::ContextHelp:
0459         return B_HELP;
0460     case KDecoration2::DecorationButtonType::Maximize:
0461         return client().data()->isMaximized() ? B_RESTORE : B_MAXIMIZE;
0462     case KDecoration2::DecorationButtonType::Minimize:
0463         return B_MINIMIZE;
0464     case KDecoration2::DecorationButtonType::Close:
0465         return B_CLOSE;
0466     case KDecoration2::DecorationButtonType::Menu:
0467     case KDecoration2::DecorationButtonType::ApplicationMenu:
0468         return B_MENU;
0469     case KDecoration2::DecorationButtonType::OnAllDesktops:
0470         return client().data()->isOnAllDesktops() ? B_UNSTICK : B_STICK;
0471     case KDecoration2::DecorationButtonType::KeepAbove:
0472         return client().data()->isKeepAbove() ? B_UNABOVE : B_ABOVE;
0473     case KDecoration2::DecorationButtonType::KeepBelow:
0474         return client().data()->isKeepBelow() ? B_UNABOVE : B_ABOVE;
0475     case KDecoration2::DecorationButtonType::Shade:
0476         return client().data()->isShaded() ? B_UNSHADE : B_SHADE;
0477     case KDecoration2::DecorationButtonType::Custom:
0478         break;
0479     }
0480     return -1; // spacer
0481 }
0482 
0483 static inline KDecoration2::DecorationButtonType parseButtonCode(char c)
0484 {
0485     switch (c) {
0486     case 'H': // B_HELP
0487         return KDecoration2::DecorationButtonType::ContextHelp;
0488     case 'M':  // B_MENU
0489         return KDecoration2::DecorationButtonType::ApplicationMenu;
0490     case 'I':
0491         return KDecoration2::DecorationButtonType::Menu;
0492     case 'N': // B_MINIMIZE
0493         return KDecoration2::DecorationButtonType::Minimize;
0494     case 'R':
0495     case 'X': // B_MAXIMIZE
0496         return KDecoration2::DecorationButtonType::Maximize;
0497     case 'C': // B_CLOSE
0498         return KDecoration2::DecorationButtonType::Close;
0499     case 'U':
0500     case 'A': // B_ABOVE
0501         return KDecoration2::DecorationButtonType::KeepAbove;
0502     case 'D': // B_BELOW
0503         return KDecoration2::DecorationButtonType::KeepBelow;
0504     case 'S': // B_SHADE
0505         return KDecoration2::DecorationButtonType::Shade;
0506     case 'Y': // B_STICK
0507         return KDecoration2::DecorationButtonType::OnAllDesktops;
0508     default:
0509         return KDecoration2::DecorationButtonType::Custom;
0510     }
0511 }
0512 
0513 static Qt::Alignment parseTitleAlignment(char *p)
0514 {
0515     char c;
0516 
0517     while ((c = *p++) && c != ':') {
0518         if (c == 'T') {
0519             return Qt::AlignLeft;
0520         }
0521     }
0522     while ((c = *p++) && c != ':') {
0523         if (c == 'T') {
0524             return Qt::AlignHCenter;
0525         }
0526     }
0527     return Qt::AlignRight;
0528 }
0529 
0530 void Decoration::parseButtonLayout(char *p)
0531 {
0532     for (int group = 0; group < 3; ++group) {
0533         m_buttonGroup[group] = new KDecoration2::DecorationButtonGroup(this);
0534     }
0535 
0536     KDecoration2::DecorationButtonType type;
0537     bool negative;
0538     int s;
0539     char c;
0540 
0541     int group = 0;
0542 
0543     while ((c = *p++)) {
0544         switch (c) {
0545         case ':':
0546             ++group;
0547             if (!(group < 3)) {
0548                 return;
0549             }
0550             break;
0551         case '(':
0552             negative = false;
0553             s = 0;
0554             if (*p == '-') {
0555                 negative = true;
0556                 ++p;
0557             }
0558             while (c = *p, c >= '0' && c <= '9') {
0559                 s = s * 10 + c - '0';
0560                 ++p;
0561             }
0562             if (c == ')') {
0563                 ++p;
0564             }
0565             if (s > 99) {
0566                 s = 99;
0567             }
0568             if (negative) {
0569                 s = -s;
0570             }
0571             if (s != 0) {
0572                 DecorationButton *button = new DecorationButton(KDecoration2::DecorationButtonType::Custom, this);
0573                 button->setAcceptedButtons(Qt::NoButton);
0574                 button->setGeometry(QRect(0, 0, s, 0));
0575                 m_buttonGroup[group]->addButton(button);
0576             }
0577             break;
0578         default:
0579             type = parseButtonCode(c);
0580             if (type != KDecoration2::DecorationButtonType::Custom) {
0581                 DecorationButton *button = new DecorationButton(type, this);
0582                 window_settings *ws = factory()->windowSettings();
0583                 int width;
0584                 int height;
0585                 if (type == KDecoration2::DecorationButtonType::Menu || !ws->use_pixmap_buttons) {
0586                     width = 16;
0587                     height = ws->top_space + ws->normal_top_corner_space + ws->titlebar_height;
0588                 } else {
0589                     GdkPixbuf *pixbuf = ws->ButtonPix[buttonGlyph(type) * S_COUNT];
0590                     if (pixbuf) {
0591                         width = gdk_pixbuf_get_width(pixbuf);
0592                         height = gdk_pixbuf_get_height(pixbuf) + ws->button_offset;
0593                     }
0594                 }
0595                 button->setGeometry(QRect(0, 0, width, height));
0596                 m_buttonGroup[group]->addButton(button);
0597             }
0598             break;
0599         }
0600     }
0601 }
0602 
0603 QImage DecorationFactory::buttonImage(const QSize &size, bool active, int button, int state) const
0604 {
0605     decor_t deco, *d = &deco;
0606     bzero(d, sizeof(decor_t));
0607 
0608     d->decorated = true;
0609     d->active = active;
0610 
0611     d->fs = active ? ws->fs_act : ws->fs_inact;
0612 
0613     QSize allocSize(cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, size.width()) / 4, size.height());
0614 
0615     QImage image(allocSize, QImage::Format_ARGB32_Premultiplied);
0616     image.fill(0);
0617 
0618     cairo_surface_t *surface;
0619     cairo_t *cr;
0620 
0621     surface = cairo_image_surface_create_for_data(image.bits(), CAIRO_FORMAT_ARGB32, size.width(), image.height(), image.bytesPerLine());
0622     cr = cairo_create(surface);
0623     cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
0624     draw_button_with_glow_alpha_bstate(button, d, cr, 0, 1.0, 1.0, state);
0625     cairo_destroy(cr);
0626     cairo_surface_destroy(surface);
0627 
0628     return image;
0629 }
0630 
0631 QImage DecorationFactory::decorationImage(const QSize &size, bool active, int state, const QRect &titleRect) const
0632 {
0633     decor_t deco, *d = &deco;
0634     bzero(d, sizeof(decor_t));
0635 
0636     // ### Title objects position and sizes
0637     d->tobj_pos[0] = 0; // left
0638     d->tobj_pos[1] = 0; // mid
0639     d->tobj_pos[2] = 0; // right
0640     d->tobj_size[0] = 0;
0641     d->tobj_size[1] = 0;
0642     d->tobj_size[2] = 0;
0643 
0644     // ### Buttons
0645     for (int i = 0; i < 11; ++i) {
0646         d->tobj_item_pos[i] = 0;
0647         d->tobj_item_width[i] = 0;
0648         d->tobj_item_state[i] = 3;
0649     }
0650 
0651     d->width = size.width();
0652     d->height = size.height();
0653 
0654     const int left = ws->left_space + ws->left_corner_space;
0655     const int top = ws->top_space + ws->titlebar_height + ws->normal_top_corner_space;
0656     const int right = ws->right_corner_space + ws->right_space;
0657     const int bottom = ws->bottom_corner_space + ws->bottom_space;
0658 
0659     d->client_width = d->width - (left + right);
0660     d->client_height = d->height - (top + bottom);
0661 
0662     d->tobj_item_state[TBT_TITLE] = 0;
0663     d->tobj_item_pos[TBT_TITLE] = titleRect.left() - ws->left_space;
0664     PangoLayout pangoLayout;
0665     pangoLayout.bounding_width = titleRect.width();
0666     pangoLayout.bounding_height = titleRect.height();
0667     d->layout = &pangoLayout;
0668 
0669     d->state = WnckWindowState(state);
0670 
0671     d->decorated = true;
0672     d->active = active;
0673 
0674     d->fs = active ? ws->fs_act : ws->fs_inact;
0675 
0676     QSize allocSize(cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, size.width()) / 4, size.height());
0677 
0678     QImage image(allocSize, QImage::Format_ARGB32_Premultiplied);
0679     QPainter painter(&image);
0680     painter.setCompositionMode(QPainter::CompositionMode_Source);
0681     painter.fillRect(QRect(0, 0, d->width, top), Qt::transparent);
0682     painter.fillRect(QRect(0, top, left, d->client_height), Qt::transparent);
0683     painter.fillRect(QRect(d->width - right, top, right, d->client_height), Qt::transparent);
0684     painter.fillRect(QRect(0, d->height - bottom, d->width, bottom), Qt::transparent);
0685     painter.end();
0686 
0687     cairo_surface_t *surface;
0688     cairo_t *cr;
0689 
0690     surface = cairo_image_surface_create_for_data(image.bits(), CAIRO_FORMAT_ARGB32, size.width(), image.height(), image.bytesPerLine());
0691     cr = cairo_create(surface);
0692     cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
0693     cairo_set_line_width(cr, 1.0);
0694     draw_frame(d, cr);
0695     cairo_destroy(cr);
0696     cairo_surface_destroy(surface);
0697 
0698     return image;
0699 }
0700 
0701 static QImage hoverImage(const QImage &image, const QImage &hoverImage, qreal hoverProgress)
0702 {
0703     if (hoverProgress <= 0.5 / 256) {
0704         return image;
0705     }
0706     if (hoverProgress >= 1.0 - 0.5 / 256) {
0707         return hoverImage;
0708     }
0709     QImage result = image;
0710     QImage over = hoverImage;
0711     QColor alpha = Qt::black;
0712     alpha.setAlphaF(hoverProgress);
0713     QPainter p;
0714     p.begin(&over);
0715     p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
0716     p.fillRect(image.rect(), alpha);
0717     p.end();
0718     p.begin(&result);
0719     p.setCompositionMode(QPainter::CompositionMode_DestinationOut);
0720     p.fillRect(image.rect(), alpha);
0721     p.setCompositionMode(QPainter::CompositionMode_Plus);
0722     p.drawImage(0, 0, over);
0723     p.end();
0724     return result;
0725 }
0726 
0727 void Decoration::paint(QPainter *painter, const QRect &repaintArea)
0728 {
0729     painter->save();
0730     painter->setClipRect(repaintArea, Qt::IntersectClip);
0731 
0732     const bool horizontalBorders = !client().data()->isMaximizedHorizontally();
0733     const bool verticalBorders = !client().data()->isMaximizedVertically();
0734     const bool active = client().data()->isActive();
0735     QSize decoSize = size();
0736 
0737     QRect captionRect(m_buttonGroup[0]->geometry().right() + 2, 0, m_buttonGroup[2]->geometry().left() - m_buttonGroup[0]->geometry().right() - 4, borderTop());
0738     QImage decoImage = factory()->decorationImage(size(), active, 0, captionRect);
0739     window_settings *ws = factory()->windowSettings();
0740     const Config *config = factory()->config();
0741 
0742     painter->drawImage(0, 0, decoImage);
0743 
0744     frame_settings *fs = active ? ws->fs_act : ws->fs_inact;
0745 
0746     QColor shadowColor = QColor(0, 0, 0, 255);
0747     QColor textColor = client().data()->color(active ? KDecoration2::ColorGroup::Active : KDecoration2::ColorGroup::Inactive, KDecoration2::ColorRole::Foreground);
0748     int textHaloXOffset = 1;
0749     int textHaloYOffset = 1;
0750     int textHaloSize = 2;
0751     if (!config->useKWinTextColors) {
0752         alpha_color &c = fs->text_halo;
0753         shadowColor = QColor::fromRgbF(c.color.r, c.color.g, c.color.b, c.alpha);
0754         c = fs->text;
0755         textColor = QColor::fromRgbF(c.color.r, c.color.g, c.color.b, c.alpha);
0756     }
0757     QString caption = settings()->fontMetrics().elidedText(client().data()->caption(), Qt::ElideMiddle, captionRect.width());
0758     captionRect.setHeight(captionRect.height() & -2);
0759     painter->setFont(settings()->font());
0760     painter->setPen(shadowColor);
0761 //    painter->drawText(captionRect.adjusted(1, 1, 1, 1), Qt::AlignVCenter, caption);
0762     painter->setPen(textColor);
0763     Qt::Alignment alignment = Qt::AlignHCenter;
0764     if (ws->tobj_layout) {
0765         alignment = parseTitleAlignment(ws->tobj_layout);
0766     }
0767     painter->drawText(captionRect, alignment | Qt::AlignVCenter | Qt::TextSingleLine, caption);
0768 
0769     m_buttonGroup[0]->paint(painter, repaintArea);
0770     m_buttonGroup[2]->paint(painter, repaintArea);
0771 
0772     foreach (QPointer<KDecoration2::DecorationButton> button, m_buttonGroup[0]->buttons()) {
0773         static_cast<DecorationButton *>(button.data())->paintGlow(painter, repaintArea);
0774     }
0775     foreach (QPointer<KDecoration2::DecorationButton> button, m_buttonGroup[2]->buttons()) {
0776         static_cast<DecorationButton *>(button.data())->paintGlow(painter, repaintArea);
0777     }
0778     painter->restore();
0779 }
0780 
0781 #if 0
0782 void Decoration::paintEvent(QPaintEvent */*event */)
0783 {
0784     DecorationFactory *decorationFactory = static_cast<DecorationFactory *>(factory());
0785     window_settings *ws = decorationFactory->windowSettings();
0786     const Config *config = decorationFactory->config();
0787     bool border = !(maximizeMode() == MaximizeFull && !options()->moveResizeMaximizedWindows());
0788     bool active = isActive();
0789     QPainter painter(widget());
0790 
0791     QSize size(widget()->size());
0792 #if KDE_IS_VERSION(4,3,0)
0793     size -= QSize(layoutMetric(LM_OuterPaddingLeft, true) + layoutMetric(LM_OuterPaddingRight, true),
0794                   layoutMetric(LM_OuterPaddingTop, true) + layoutMetric(LM_OuterPaddingBottom, true));
0795 #endif
0796 
0797     painter.setFont(options()->font(active));
0798     Qt::Alignment alignment = Qt::AlignHCenter;
0799     if (ws->tobj_layout) {
0800         alignment = parseTitleAlignment(ws->tobj_layout);
0801     }
0802     QRect labelRect = titleRect().adjusted(0, 0, 1, 1);
0803 
0804     QString text = painter.fontMetrics().elidedText(caption(), Qt::ElideMiddle, labelRect.width());
0805     int state = 0;
0806 #if 0
0807     if (maximizeMode() & MaximizeHorizontal) state |= WNCK_WINDOW_STATE_MAXIMIZED_HORIZONTALLY;
0808     if (maximizeMode() & MaximizeVertical) state |= WNCK_WINDOW_STATE_MAXIMIZED_VERTICALLY;
0809 #else
0810     if (!border) state |= WNCK_WINDOW_STATE_MAXIMIZED_HORIZONTALLY | WNCK_WINDOW_STATE_MAXIMIZED_VERTICALLY;
0811 #endif
0812     if (isShade()) state |= WNCK_WINDOW_STATE_SHADED;
0813     if (isOnAllDesktops()) state |= WNCK_WINDOW_STATE_STICKY;
0814     if (keepAbove()) state |= WNCK_WINDOW_STATE_ABOVE;
0815     if (keepBelow()) state |= WNCK_WINDOW_STATE_BELOW;
0816 
0817     if (!border) {
0818         size += QSize(layoutMetric(LM_TitleEdgeLeft, false) + layoutMetric(LM_TitleEdgeRight, false), 0);
0819     }
0820     QRect titleRect = painter.boundingRect(labelRect, alignment | Qt::AlignVCenter | Qt::TextSingleLine, text);
0821 #if KDE_IS_VERSION(4,3,0)
0822     titleRect.adjust(-layoutMetric(LM_OuterPaddingLeft, true), 0, -layoutMetric(LM_OuterPaddingLeft, true), 0);
0823 #endif
0824     if (!border) {
0825         titleRect.adjust(layoutMetric(LM_TitleEdgeLeft, false), 0, layoutMetric(LM_TitleEdgeLeft, false), 0);
0826     }
0827     QImage decoImage = static_cast<DecorationFactory *>(factory())->decorationImage(size, active, state, titleRect);
0828 
0829 #if KDE_IS_VERSION(4,3,0)
0830     const int paddingLeft = layoutMetric(LM_OuterPaddingLeft, true);
0831     const int paddingTop = layoutMetric(LM_OuterPaddingTop, true);
0832 #else
0833     const int paddingLeft = 0;
0834     const int paddingTop = 0;
0835 #endif
0836     QRect outerRect(paddingLeft, paddingTop, size.width(), size.height());
0837     QRect innerRect = outerRect.adjusted(layoutMetric(LM_BorderLeft, true), layoutMetric(LM_TitleHeight, true),
0838         -layoutMetric(LM_BorderRight, true), -layoutMetric(LM_BorderBottom, true));
0839 
0840 #if KDE_IS_VERSION(4,3,0)
0841     if (border && !config->useKWinShadows) {
0842         paintShadow(&painter, outerRect, config->shadowSettings, config->shadowImage);
0843     }
0844 #endif
0845 
0846     if (border) {
0847         painter.drawImage(outerRect.x(), outerRect.y(), decoImage,
0848             0, 0, outerRect.width(), innerRect.y() - outerRect.y());
0849         painter.drawImage(outerRect.x(), innerRect.y() + innerRect.height(), decoImage,
0850             0, outerRect.height() - (outerRect.bottom() - innerRect.bottom()),
0851             outerRect.width(), outerRect.bottom() - innerRect.bottom());
0852         painter.drawImage(outerRect.x(), innerRect.y(), decoImage,
0853             0, innerRect.y() - outerRect.y(),
0854             innerRect.x() - outerRect.x(), innerRect.height());
0855         painter.drawImage(innerRect.x() + innerRect.width(), innerRect.y(), decoImage,
0856             outerRect.width() - (outerRect.right() - innerRect.right()), innerRect.y() - outerRect.y(),
0857             outerRect.right() - innerRect.right(), innerRect.height());
0858     } else {
0859         painter.drawImage(outerRect.x(), outerRect.y(), decoImage,
0860             layoutMetric(LM_TitleEdgeLeft, false), 0, outerRect.width(), innerRect.y() - outerRect.y());
0861     }
0862 
0863     frame_settings *fs = active ? ws->fs_act : ws->fs_inact;
0864 
0865     QColor shadowColor = QColor(0, 0, 0, 255);
0866     QColor textColor = options()->color(ColorFont, active);
0867     int textHaloXOffset = 1;
0868     int textHaloYOffset = 1;
0869     int textHaloSize = 2;
0870     if (!config->useKWinTextColors) {
0871         alpha_color &c = fs->text_halo;
0872         shadowColor = QColor::fromRgbF(c.color.r, c.color.g, c.color.b, c.alpha);
0873         c = fs->text;
0874         textColor = QColor::fromRgbF(c.color.r, c.color.g, c.color.b, c.alpha);
0875     }
0876     QPixmap shadowText = Plasma::PaintUtils::shadowText(text, painter.font(), textColor, shadowColor, QPoint(0, 0), 2);
0877 //    widget()->style()->drawItemPixmap(&painter, labelRect.adjusted(-2, -2, 2, 2), alignment | Qt::AlignVCenter, shadowText);
0878 }
0879 #endif
0880 
0881 DecorationButton::DecorationButton(KDecoration2::DecorationButtonType type, Decoration *parent)
0882     : KDecoration2::DecorationButton(type, parent)
0883     , m_hoverProgress(0.0)
0884 {
0885     /* */
0886 }
0887 
0888 DecorationButton::~DecorationButton()
0889 {
0890     /* */
0891 }
0892 
0893 void DecorationButton::paint(QPainter *painter, const QRect &repaintArea)
0894 {
0895     Decoration *decoration = static_cast<Decoration *>(KDecoration2::DecorationButton::decoration().data());
0896     KDecoration2::DecoratedClient *client = decoration->client().data();
0897     DecorationFactory *decorationFactory =decoration->factory();
0898     window_settings *ws = decorationFactory->windowSettings();
0899     const bool active = client->isActive();
0900     const bool down = isPressed();
0901     QRect rect = geometry().toRect();
0902 
0903     int state = 0;
0904     if (down) {
0905         state = 2;
0906     }
0907     if (!active) {
0908         state += 3;
0909     }
0910 
0911     if (type() == KDecoration2::DecorationButtonType::Menu) {
0912         client->icon().paint(painter, rect);
0913     } else {
0914         int glyph = decoration->buttonGlyph(type());
0915         if (glyph == -1) {
0916             return;
0917         }
0918         if (ws->use_pixmap_buttons) {
0919             QImage image = ws->ButtonPix[state + glyph * S_COUNT]->image;
0920             if (!down) {
0921                 image = hoverImage(image, ws->ButtonPix[state + 1 + glyph * S_COUNT]->image, m_hoverProgress);
0922             }
0923             painter->drawImage(rect.x(), rect.y() + ws->button_offset, image);
0924         } else {
0925             state = 0;
0926             if (down) state |= PRESSED_EVENT_WINDOW;
0927             if (isHovered()) state |= IN_EVENT_WINDOW;
0928             QImage buttonImage = decorationFactory->buttonImage(QSize(16, 16), active, glyph, state);
0929 
0930             painter->drawImage(rect.x(), rect.y() + ws->button_offset, buttonImage);
0931         }
0932     }
0933 }
0934 
0935 void DecorationButton::paintGlow(QPainter *painter, const QRect &repaintArea)
0936 {
0937     if (m_hoverProgress > 0.0 && isVisible() && type() != KDecoration2::DecorationButtonType::Menu) {
0938         Decoration *decoration = static_cast<Decoration *>(KDecoration2::DecorationButton::decoration().data());
0939         KDecoration2::DecoratedClient *client = decoration->client().data();
0940         DecorationFactory *decorationFactory =decoration->factory();
0941         window_settings *ws = decorationFactory->windowSettings();
0942         const bool active = client->isActive();
0943 
0944         QRect rect = geometry().toRect();
0945         int glyph = decoration->buttonGlyph(type());
0946         if (glyph == -1) {
0947             return;
0948         }
0949         QImage image;
0950 
0951         if (active && ws->use_button_glow) {
0952             image = ws->ButtonGlowPix[glyph]->image;
0953         } else if (!active && ws->use_button_inactive_glow) {
0954             image = ws->ButtonInactiveGlowPix[glyph]->image;
0955         }
0956         if (!image.isNull() && ws->use_pixmap_buttons) {
0957             QImage buttonImage = ws->ButtonPix[glyph * S_COUNT]->image;
0958             painter->setOpacity(m_hoverProgress);
0959             const int xp = rect.x() + (buttonImage.width() - ws->c_glow_size.w) / 2;
0960             const int yp = rect.y() + (buttonImage.height() - ws->c_glow_size.h) / 2;
0961             painter->drawImage(xp, yp + ws->button_offset, image);
0962         }
0963     }
0964 }
0965 
0966 void DecorationButton::hoverEnterEvent(QHoverEvent *event)
0967 {
0968     KDecoration2::DecorationButton::hoverEnterEvent(event);
0969     if (isHovered()) {
0970         startHoverAnimation(1.0);
0971     }
0972 }
0973 
0974 void DecorationButton::hoverLeaveEvent(QHoverEvent *event)
0975 {
0976     KDecoration2::DecorationButton::hoverLeaveEvent(event);
0977     if (!isHovered()) {
0978         startHoverAnimation(0.0);
0979     }
0980 }
0981 
0982 qreal DecorationButton::hoverProgress() const
0983 {
0984     return m_hoverProgress;
0985 }
0986 
0987 void DecorationButton::setHoverProgress(qreal hoverProgress)
0988 {
0989     if (m_hoverProgress != hoverProgress) {
0990         m_hoverProgress = hoverProgress;
0991         if (static_cast<Decoration *>(decoration().data())) {
0992             update(geometry().adjusted(-32, -32, 32, 32));
0993         }
0994     }
0995 }
0996 
0997 void DecorationButton::startHoverAnimation(qreal endValue)
0998 {
0999     DecorationFactory *decorationFactory = static_cast<Decoration *>(decoration().data())->factory();
1000     const Config *config = decorationFactory->config();
1001     QPropertyAnimation *hoverAnimation = m_hoverAnimation.data();
1002 
1003     if (hoverAnimation) {
1004         if (hoverAnimation->endValue() == endValue) {
1005             return;
1006         }
1007         hoverAnimation->stop();
1008     } else if (m_hoverProgress != endValue) {
1009         if (config->hoverDuration < 10) {
1010             setHoverProgress(endValue);
1011             return;
1012         }
1013         hoverAnimation = new QPropertyAnimation(this, "hoverProgress");
1014         m_hoverAnimation = hoverAnimation;
1015     } else {
1016         return;
1017     }
1018     hoverAnimation->setEasingCurve(QEasingCurve::OutQuad);
1019     hoverAnimation->setStartValue(m_hoverProgress);
1020     hoverAnimation->setEndValue(endValue);
1021     hoverAnimation->setDuration(1 + qRound(config->hoverDuration * qAbs(m_hoverProgress - endValue)));
1022     hoverAnimation->start();
1023 }
1024 
1025 }; // namespace Smaragd
1026 
1027 #include "kwin_smaragd.moc"