diff options
Diffstat (limited to 'src/ui/theme.c')
-rw-r--r-- | src/ui/theme.c | 6652 |
1 files changed, 6652 insertions, 0 deletions
diff --git a/src/ui/theme.c b/src/ui/theme.c new file mode 100644 index 00000000..63103960 --- /dev/null +++ b/src/ui/theme.c @@ -0,0 +1,6652 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco Theme Rendering */ + +/* + * Copyright (C) 2001 Havoc Pennington + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +/** + * \file theme.c Making Marco look pretty + * + * The window decorations drawn by Marco are described by files on disk + * known internally as "themes" (externally as "window border themes" on + * http://art.gnome.org/themes/marco/ or "Marco themes"). This file + * contains most of the code necessary to support themes; it does not + * contain the XML parser, which is in theme-parser.c. + * + * \bug This is a big file with lots of different subsystems, which might + * be better split out into separate files. + */ + +/** + * \defgroup tokenizer The theme expression tokenizer + * + * Themes can use a simple expression language to represent the values of + * things. This is the tokeniser used for that language. + * + * \bug We could remove almost all this code by using GScanner instead, + * but we would also have to find every expression in every existing theme + * we could and make sure the parse trees were the same. + */ + +/** + * \defgroup parser The theme expression parser + * + * Themes can use a simple expression language to represent the values of + * things. This is the parser used for that language. + */ + +#include <config.h> +#include "theme.h" +#include "theme-parser.h" +#include "util.h" +#include "gradient.h" +#include <gtk/gtk.h> +#include <string.h> +#include <stdlib.h> +#define __USE_XOPEN +#include <math.h> + +#define GDK_COLOR_RGBA(color) \ + ((guint32) (0xff | \ + (((color).red / 256) << 24) | \ + (((color).green / 256) << 16) | \ + (((color).blue / 256) << 8))) + +#define GDK_COLOR_RGB(color) \ + ((guint32) ((((color).red / 256) << 16) | \ + (((color).green / 256) << 8) | \ + (((color).blue / 256)))) + +#define ALPHA_TO_UCHAR(d) ((unsigned char) ((d) * 255)) + +#define DEBUG_FILL_STRUCT(s) memset ((s), 0xef, sizeof (*(s))) +#define CLAMP_UCHAR(v) ((guchar) (CLAMP (((int)v), (int)0, (int)255))) +#define INTENSITY(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11) + +static void gtk_style_shade (GdkColor *a, + GdkColor *b, + gdouble k); +static void rgb_to_hls (gdouble *r, + gdouble *g, + gdouble *b); +static void hls_to_rgb (gdouble *h, + gdouble *l, + gdouble *s); + +/** + * The current theme. (Themes are singleton.) + */ +static MetaTheme *meta_current_theme = NULL; + +static GdkPixbuf * +colorize_pixbuf (GdkPixbuf *orig, + GdkColor *new_color) +{ + GdkPixbuf *pixbuf; + double intensity; + int x, y; + const guchar *src; + guchar *dest; + int orig_rowstride; + int dest_rowstride; + int width, height; + gboolean has_alpha; + const guchar *src_pixels; + guchar *dest_pixels; + + pixbuf = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (orig), gdk_pixbuf_get_has_alpha (orig), + gdk_pixbuf_get_bits_per_sample (orig), + gdk_pixbuf_get_width (orig), gdk_pixbuf_get_height (orig)); + + if (pixbuf == NULL) + return NULL; + + orig_rowstride = gdk_pixbuf_get_rowstride (orig); + dest_rowstride = gdk_pixbuf_get_rowstride (pixbuf); + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + has_alpha = gdk_pixbuf_get_has_alpha (orig); + src_pixels = gdk_pixbuf_get_pixels (orig); + dest_pixels = gdk_pixbuf_get_pixels (pixbuf); + + for (y = 0; y < height; y++) + { + src = src_pixels + y * orig_rowstride; + dest = dest_pixels + y * dest_rowstride; + + for (x = 0; x < width; x++) + { + double dr, dg, db; + + intensity = INTENSITY (src[0], src[1], src[2]) / 255.0; + + if (intensity <= 0.5) + { + /* Go from black at intensity = 0.0 to new_color at intensity = 0.5 */ + dr = (new_color->red * intensity * 2.0) / 65535.0; + dg = (new_color->green * intensity * 2.0) / 65535.0; + db = (new_color->blue * intensity * 2.0) / 65535.0; + } + else + { + /* Go from new_color at intensity = 0.5 to white at intensity = 1.0 */ + dr = (new_color->red + (65535 - new_color->red) * (intensity - 0.5) * 2.0) / 65535.0; + dg = (new_color->green + (65535 - new_color->green) * (intensity - 0.5) * 2.0) / 65535.0; + db = (new_color->blue + (65535 - new_color->blue) * (intensity - 0.5) * 2.0) / 65535.0; + } + + dest[0] = CLAMP_UCHAR (255 * dr); + dest[1] = CLAMP_UCHAR (255 * dg); + dest[2] = CLAMP_UCHAR (255 * db); + + if (has_alpha) + { + dest[3] = src[3]; + src += 4; + dest += 4; + } + else + { + src += 3; + dest += 3; + } + } + } + + return pixbuf; +} + +static void +color_composite (const GdkColor *bg, + const GdkColor *fg, + double alpha_d, + GdkColor *color) +{ + guint16 alpha; + + *color = *bg; + alpha = alpha_d * 0xffff; + color->red = color->red + (((fg->red - color->red) * alpha + 0x8000) >> 16); + color->green = color->green + (((fg->green - color->green) * alpha + 0x8000) >> 16); + color->blue = color->blue + (((fg->blue - color->blue) * alpha + 0x8000) >> 16); +} + +/** + * Sets all the fields of a border to dummy values. + * + * \param border The border whose fields should be reset. + */ +static void +init_border (GtkBorder *border) +{ + border->top = -1; + border->bottom = -1; + border->left = -1; + border->right = -1; +} + +/** + * Creates a new, empty MetaFrameLayout. The fields will be set to dummy + * values. + * + * \return The newly created MetaFrameLayout. + */ +MetaFrameLayout* +meta_frame_layout_new (void) +{ + MetaFrameLayout *layout; + + layout = g_new0 (MetaFrameLayout, 1); + + layout->refcount = 1; + + /* Fill with -1 values to detect invalid themes */ + layout->left_width = -1; + layout->right_width = -1; + layout->bottom_height = -1; + + init_border (&layout->title_border); + + layout->title_vertical_pad = -1; + + layout->right_titlebar_edge = -1; + layout->left_titlebar_edge = -1; + + layout->button_sizing = META_BUTTON_SIZING_LAST; + layout->button_aspect = 1.0; + layout->button_width = -1; + layout->button_height = -1; + + layout->has_title = TRUE; + layout->title_scale = 1.0; + + init_border (&layout->button_border); + + return layout; +} + +/** + * + */ +static gboolean +validate_border (const GtkBorder *border, + const char **bad) +{ + *bad = NULL; + + if (border->top < 0) + *bad = _("top"); + else if (border->bottom < 0) + *bad = _("bottom"); + else if (border->left < 0) + *bad = _("left"); + else if (border->right < 0) + *bad = _("right"); + + return *bad == NULL; +} + +/** + * Ensures that the theme supplied a particular dimension. When a + * MetaFrameLayout is created, all its integer fields are set to -1 + * by meta_frame_layout_new(). After an instance of this type + * should have been initialised, this function checks that + * a given field is not still at -1. It is never called directly, but + * rather via the CHECK_GEOMETRY_VALUE and CHECK_GEOMETRY_BORDER + * macros. + * + * \param val The value to check + * \param name The name to use in the error message + * \param[out] error Set to an error if val was not initialised + */ +static gboolean +validate_geometry_value (int val, + const char *name, + GError **error) +{ + if (val < 0) + { + g_set_error (error, META_THEME_ERROR, + META_THEME_ERROR_FRAME_GEOMETRY, + _("frame geometry does not specify \"%s\" dimension"), + name); + return FALSE; + } + else + return TRUE; +} + +static gboolean +validate_geometry_border (const GtkBorder *border, + const char *name, + GError **error) +{ + const char *bad; + + if (!validate_border (border, &bad)) + { + g_set_error (error, META_THEME_ERROR, + META_THEME_ERROR_FRAME_GEOMETRY, + _("frame geometry does not specify dimension \"%s\" for border \"%s\""), + bad, name); + return FALSE; + } + else + return TRUE; +} + +gboolean +meta_frame_layout_validate (const MetaFrameLayout *layout, + GError **error) +{ + g_return_val_if_fail (layout != NULL, FALSE); + +#define CHECK_GEOMETRY_VALUE(vname) if (!validate_geometry_value (layout->vname, #vname, error)) return FALSE + +#define CHECK_GEOMETRY_BORDER(bname) if (!validate_geometry_border (&layout->bname, #bname, error)) return FALSE + + CHECK_GEOMETRY_VALUE (left_width); + CHECK_GEOMETRY_VALUE (right_width); + CHECK_GEOMETRY_VALUE (bottom_height); + + CHECK_GEOMETRY_BORDER (title_border); + + CHECK_GEOMETRY_VALUE (title_vertical_pad); + + CHECK_GEOMETRY_VALUE (right_titlebar_edge); + CHECK_GEOMETRY_VALUE (left_titlebar_edge); + + switch (layout->button_sizing) + { + case META_BUTTON_SIZING_ASPECT: + if (layout->button_aspect < (0.1) || + layout->button_aspect > (15.0)) + { + g_set_error (error, META_THEME_ERROR, + META_THEME_ERROR_FRAME_GEOMETRY, + _("Button aspect ratio %g is not reasonable"), + layout->button_aspect); + return FALSE; + } + break; + case META_BUTTON_SIZING_FIXED: + CHECK_GEOMETRY_VALUE (button_width); + CHECK_GEOMETRY_VALUE (button_height); + break; + case META_BUTTON_SIZING_LAST: + g_set_error (error, META_THEME_ERROR, + META_THEME_ERROR_FRAME_GEOMETRY, + _("Frame geometry does not specify size of buttons")); + return FALSE; + } + + CHECK_GEOMETRY_BORDER (button_border); + + return TRUE; +} + +MetaFrameLayout* +meta_frame_layout_copy (const MetaFrameLayout *src) +{ + MetaFrameLayout *layout; + + layout = g_new0 (MetaFrameLayout, 1); + + *layout = *src; + + layout->refcount = 1; + + return layout; +} + +void +meta_frame_layout_ref (MetaFrameLayout *layout) +{ + g_return_if_fail (layout != NULL); + + layout->refcount += 1; +} + +void +meta_frame_layout_unref (MetaFrameLayout *layout) +{ + g_return_if_fail (layout != NULL); + g_return_if_fail (layout->refcount > 0); + + layout->refcount -= 1; + + if (layout->refcount == 0) + { + DEBUG_FILL_STRUCT (layout); + g_free (layout); + } +} + +void +meta_frame_layout_get_borders (const MetaFrameLayout *layout, + int text_height, + MetaFrameFlags flags, + int *top_height, + int *bottom_height, + int *left_width, + int *right_width) +{ + int buttons_height, title_height; + + g_return_if_fail (top_height != NULL); + g_return_if_fail (bottom_height != NULL); + g_return_if_fail (left_width != NULL); + g_return_if_fail (right_width != NULL); + + if (!layout->has_title) + text_height = 0; + + buttons_height = layout->button_height + + layout->button_border.top + layout->button_border.bottom; + title_height = text_height + + layout->title_vertical_pad + + layout->title_border.top + layout->title_border.bottom; + + if (top_height) + { + *top_height = MAX (buttons_height, title_height); + } + + if (left_width) + *left_width = layout->left_width; + if (right_width) + *right_width = layout->right_width; + + if (bottom_height) + { + if (flags & META_FRAME_SHADED) + *bottom_height = 0; + else + *bottom_height = layout->bottom_height; + } + + if (flags & META_FRAME_FULLSCREEN) + { + if (top_height) + *top_height = 0; + if (bottom_height) + *bottom_height = 0; + if (left_width) + *left_width = 0; + if (right_width) + *right_width = 0; + } +} + +static MetaButtonSpace* +rect_for_function (MetaFrameGeometry *fgeom, + MetaFrameFlags flags, + MetaButtonFunction function, + MetaTheme *theme) +{ + + /* Firstly, check version-specific things. */ + + if (META_THEME_ALLOWS(theme, META_THEME_SHADE_STICK_ABOVE_BUTTONS)) + { + switch (function) + { + case META_BUTTON_FUNCTION_SHADE: + if ((flags & META_FRAME_ALLOWS_SHADE) && !(flags & META_FRAME_SHADED)) + return &fgeom->shade_rect; + else + return NULL; + case META_BUTTON_FUNCTION_ABOVE: + if (!(flags & META_FRAME_ABOVE)) + return &fgeom->above_rect; + else + return NULL; + case META_BUTTON_FUNCTION_STICK: + if (!(flags & META_FRAME_STUCK)) + return &fgeom->stick_rect; + else + return NULL; + case META_BUTTON_FUNCTION_UNSHADE: + if ((flags & META_FRAME_ALLOWS_SHADE) && (flags & META_FRAME_SHADED)) + return &fgeom->unshade_rect; + else + return NULL; + case META_BUTTON_FUNCTION_UNABOVE: + if (flags & META_FRAME_ABOVE) + return &fgeom->unabove_rect; + else + return NULL; + case META_BUTTON_FUNCTION_UNSTICK: + if (flags & META_FRAME_STUCK) + return &fgeom->unstick_rect; + default: + /* just go on to the next switch block */; + } + } + + /* now consider the buttons which exist in all versions */ + + switch (function) + { + case META_BUTTON_FUNCTION_MENU: + if (flags & META_FRAME_ALLOWS_MENU) + return &fgeom->menu_rect; + else + return NULL; + case META_BUTTON_FUNCTION_MINIMIZE: + if (flags & META_FRAME_ALLOWS_MINIMIZE) + return &fgeom->min_rect; + else + return NULL; + case META_BUTTON_FUNCTION_MAXIMIZE: + if (flags & META_FRAME_ALLOWS_MAXIMIZE) + return &fgeom->max_rect; + else + return NULL; + case META_BUTTON_FUNCTION_CLOSE: + if (flags & META_FRAME_ALLOWS_DELETE) + return &fgeom->close_rect; + else + return NULL; + case META_BUTTON_FUNCTION_STICK: + case META_BUTTON_FUNCTION_SHADE: + case META_BUTTON_FUNCTION_ABOVE: + case META_BUTTON_FUNCTION_UNSTICK: + case META_BUTTON_FUNCTION_UNSHADE: + case META_BUTTON_FUNCTION_UNABOVE: + /* we are being asked for a >v1 button which hasn't been handled yet, + * so obviously we're not in a theme which supports that version. + * therefore, we don't show the button. return NULL and all will + * be well. + */ + return NULL; + + case META_BUTTON_FUNCTION_LAST: + return NULL; + } + + return NULL; +} + +static gboolean +strip_button (MetaButtonSpace *func_rects[MAX_BUTTONS_PER_CORNER], + GdkRectangle *bg_rects[MAX_BUTTONS_PER_CORNER], + int *n_rects, + MetaButtonSpace *to_strip) +{ + int i; + + i = 0; + while (i < *n_rects) + { + if (func_rects[i] == to_strip) + { + *n_rects -= 1; + + /* shift the other rects back in the array */ + while (i < *n_rects) + { + func_rects[i] = func_rects[i+1]; + bg_rects[i] = bg_rects[i+1]; + + ++i; + } + + func_rects[i] = NULL; + bg_rects[i] = NULL; + + return TRUE; + } + + ++i; + } + + return FALSE; /* did not strip anything */ +} + +void +meta_frame_layout_calc_geometry (const MetaFrameLayout *layout, + int text_height, + MetaFrameFlags flags, + int client_width, + int client_height, + const MetaButtonLayout *button_layout, + MetaFrameGeometry *fgeom, + MetaTheme *theme) +{ + int i, n_left, n_right, n_left_spacers, n_right_spacers; + int x; + int button_y; + int title_right_edge; + int width, height; + int button_width, button_height; + int min_size_for_rounding; + + /* the left/right rects in order; the max # of rects + * is the number of button functions + */ + MetaButtonSpace *left_func_rects[MAX_BUTTONS_PER_CORNER]; + MetaButtonSpace *right_func_rects[MAX_BUTTONS_PER_CORNER]; + GdkRectangle *left_bg_rects[MAX_BUTTONS_PER_CORNER]; + gboolean left_buttons_has_spacer[MAX_BUTTONS_PER_CORNER]; + GdkRectangle *right_bg_rects[MAX_BUTTONS_PER_CORNER]; + gboolean right_buttons_has_spacer[MAX_BUTTONS_PER_CORNER]; + + meta_frame_layout_get_borders (layout, text_height, + flags, + &fgeom->top_height, + &fgeom->bottom_height, + &fgeom->left_width, + &fgeom->right_width); + + width = client_width + fgeom->left_width + fgeom->right_width; + + height = ((flags & META_FRAME_SHADED) ? 0: client_height) + + fgeom->top_height + fgeom->bottom_height; + + fgeom->width = width; + fgeom->height = height; + + fgeom->top_titlebar_edge = layout->title_border.top; + fgeom->bottom_titlebar_edge = layout->title_border.bottom; + fgeom->left_titlebar_edge = layout->left_titlebar_edge; + fgeom->right_titlebar_edge = layout->right_titlebar_edge; + + /* gcc warnings */ + button_width = -1; + button_height = -1; + + switch (layout->button_sizing) + { + case META_BUTTON_SIZING_ASPECT: + button_height = fgeom->top_height - layout->button_border.top - layout->button_border.bottom; + button_width = button_height / layout->button_aspect; + break; + case META_BUTTON_SIZING_FIXED: + button_width = layout->button_width; + button_height = layout->button_height; + break; + case META_BUTTON_SIZING_LAST: + g_assert_not_reached (); + break; + } + + /* FIXME all this code sort of pretends that duplicate buttons + * with the same function are allowed, but that breaks the + * code in frames.c, so isn't really allowed right now. + * Would need left_close_rect, right_close_rect, etc. + */ + + /* Init all button rects to 0, lame hack */ + memset (ADDRESS_OF_BUTTON_RECTS (fgeom), '\0', + LENGTH_OF_BUTTON_RECTS); + + n_left = 0; + n_right = 0; + n_left_spacers = 0; + n_right_spacers = 0; + + if (!layout->hide_buttons) + { + /* Try to fill in rects */ + for (i = 0; i < MAX_BUTTONS_PER_CORNER && button_layout->left_buttons[i] != META_BUTTON_FUNCTION_LAST; i++) + { + left_func_rects[n_left] = rect_for_function (fgeom, flags, + button_layout->left_buttons[i], + theme); + if (left_func_rects[n_left] != NULL) + { + left_buttons_has_spacer[n_left] = button_layout->left_buttons_has_spacer[i]; + if (button_layout->left_buttons_has_spacer[i]) + ++n_left_spacers; + + ++n_left; + } + } + + for (i = 0; i < MAX_BUTTONS_PER_CORNER && button_layout->right_buttons[i] != META_BUTTON_FUNCTION_LAST; i++) + { + right_func_rects[n_right] = rect_for_function (fgeom, flags, + button_layout->right_buttons[i], + theme); + if (right_func_rects[n_right] != NULL) + { + right_buttons_has_spacer[n_right] = button_layout->right_buttons_has_spacer[i]; + if (button_layout->right_buttons_has_spacer[i]) + ++n_right_spacers; + + ++n_right; + } + } + } + + for (i = 0; i < MAX_BUTTONS_PER_CORNER; i++) + { + left_bg_rects[i] = NULL; + right_bg_rects[i] = NULL; + } + + for (i = 0; i < n_left; i++) + { + if (i == 0) /* prefer left background if only one button */ + left_bg_rects[i] = &fgeom->left_left_background; + else if (i == (n_left - 1)) + left_bg_rects[i] = &fgeom->left_right_background; + else + left_bg_rects[i] = &fgeom->left_middle_backgrounds[i - 1]; + } + + for (i = 0; i < n_right; i++) + { + /* prefer right background if only one button */ + if (i == (n_right - 1)) + right_bg_rects[i] = &fgeom->right_right_background; + else if (i == 0) + right_bg_rects[i] = &fgeom->right_left_background; + else + right_bg_rects[i] = &fgeom->right_middle_backgrounds[i - 1]; + } + + /* Be sure buttons fit */ + while (n_left > 0 || n_right > 0) + { + int space_used_by_buttons; + int space_available; + + space_available = fgeom->width - layout->left_titlebar_edge - layout->right_titlebar_edge; + + space_used_by_buttons = 0; + + space_used_by_buttons += button_width * n_left; + space_used_by_buttons += (button_width * 0.75) * n_left_spacers; + space_used_by_buttons += layout->button_border.left * n_left; + space_used_by_buttons += layout->button_border.right * n_left; + + space_used_by_buttons += button_width * n_right; + space_used_by_buttons += (button_width * 0.75) * n_right_spacers; + space_used_by_buttons += layout->button_border.left * n_right; + space_used_by_buttons += layout->button_border.right * n_right; + + if (space_used_by_buttons <= space_available) + break; /* Everything fits, bail out */ + + /* First try to remove separators */ + if (n_left_spacers > 0) + { + left_buttons_has_spacer[--n_left_spacers] = FALSE; + continue; + } + else if (n_right_spacers > 0) + { + right_buttons_has_spacer[--n_right_spacers] = FALSE; + continue; + } + + /* Otherwise we need to shave out a button. Shave + * above, stick, shade, min, max, close, then menu (menu is most useful); + * prefer the default button locations. + */ + if (strip_button (left_func_rects, left_bg_rects, + &n_left, &fgeom->above_rect)) + continue; + else if (strip_button (right_func_rects, right_bg_rects, + &n_right, &fgeom->above_rect)) + continue; + else if (strip_button (left_func_rects, left_bg_rects, + &n_left, &fgeom->stick_rect)) + continue; + else if (strip_button (right_func_rects, right_bg_rects, + &n_right, &fgeom->stick_rect)) + continue; + else if (strip_button (left_func_rects, left_bg_rects, + &n_left, &fgeom->shade_rect)) + continue; + else if (strip_button (right_func_rects, right_bg_rects, + &n_right, &fgeom->shade_rect)) + continue; + else if (strip_button (left_func_rects, left_bg_rects, + &n_left, &fgeom->min_rect)) + continue; + else if (strip_button (right_func_rects, right_bg_rects, + &n_right, &fgeom->min_rect)) + continue; + else if (strip_button (left_func_rects, left_bg_rects, + &n_left, &fgeom->max_rect)) + continue; + else if (strip_button (right_func_rects, right_bg_rects, + &n_right, &fgeom->max_rect)) + continue; + else if (strip_button (left_func_rects, left_bg_rects, + &n_left, &fgeom->close_rect)) + continue; + else if (strip_button (right_func_rects, right_bg_rects, + &n_right, &fgeom->close_rect)) + continue; + else if (strip_button (right_func_rects, right_bg_rects, + &n_right, &fgeom->menu_rect)) + continue; + else if (strip_button (left_func_rects, left_bg_rects, + &n_left, &fgeom->menu_rect)) + continue; + else + { + meta_bug ("Could not find a button to strip. n_left = %d n_right = %d\n", + n_left, n_right); + } + } + + /* center buttons vertically */ + button_y = (fgeom->top_height - + (button_height + layout->button_border.top + layout->button_border.bottom)) / 2 + layout->button_border.top; + + /* right edge of farthest-right button */ + x = width - layout->right_titlebar_edge; + + i = n_right - 1; + while (i >= 0) + { + MetaButtonSpace *rect; + + if (x < 0) /* if we go negative, leave the buttons we don't get to as 0-width */ + break; + + rect = right_func_rects[i]; + rect->visible.x = x - layout->button_border.right - button_width; + if (right_buttons_has_spacer[i]) + rect->visible.x -= (button_width * 0.75); + + rect->visible.y = button_y; + rect->visible.width = button_width; + rect->visible.height = button_height; + + if (flags & META_FRAME_MAXIMIZED) + { + rect->clickable.x = rect->visible.x; + rect->clickable.y = 0; + rect->clickable.width = rect->visible.width; + rect->clickable.height = button_height + button_y; + + if (i == n_right - 1) + rect->clickable.width += layout->right_titlebar_edge + layout->right_width + layout->button_border.right; + + } + else + g_memmove (&(rect->clickable), &(rect->visible), sizeof(rect->clickable)); + + *(right_bg_rects[i]) = rect->visible; + + x = rect->visible.x - layout->button_border.left; + + --i; + } + + /* save right edge of titlebar for later use */ + title_right_edge = x - layout->title_border.right; + + /* Now x changes to be position from the left and we go through + * the left-side buttons + */ + x = layout->left_titlebar_edge; + for (i = 0; i < n_left; i++) + { + MetaButtonSpace *rect; + + rect = left_func_rects[i]; + + rect->visible.x = x + layout->button_border.left; + rect->visible.y = button_y; + rect->visible.width = button_width; + rect->visible.height = button_height; + + if (flags & META_FRAME_MAXIMIZED) + { + if (i==0) + { + rect->clickable.x = 0; + rect->clickable.width = button_width + x; + } + else + { + rect->clickable.x = rect->visible.x; + rect->clickable.width = button_width; + } + + rect->clickable.y = 0; + rect->clickable.height = button_height + button_y; + } + else + g_memmove (&(rect->clickable), &(rect->visible), sizeof(rect->clickable)); + + + x = rect->visible.x + rect->visible.width + layout->button_border.right; + if (left_buttons_has_spacer[i]) + x += (button_width * 0.75); + + *(left_bg_rects[i]) = rect->visible; + } + + /* We always fill as much vertical space as possible with title rect, + * rather than centering it like the buttons + */ + fgeom->title_rect.x = x + layout->title_border.left; + fgeom->title_rect.y = layout->title_border.top; + fgeom->title_rect.width = title_right_edge - fgeom->title_rect.x; + fgeom->title_rect.height = fgeom->top_height - layout->title_border.top - layout->title_border.bottom; + + /* Nuke title if it won't fit */ + if (fgeom->title_rect.width < 0 || + fgeom->title_rect.height < 0) + { + fgeom->title_rect.width = 0; + fgeom->title_rect.height = 0; + } + + if (flags & META_FRAME_SHADED) + min_size_for_rounding = 0; + else + min_size_for_rounding = 5; + + fgeom->top_left_corner_rounded_radius = 0; + fgeom->top_right_corner_rounded_radius = 0; + fgeom->bottom_left_corner_rounded_radius = 0; + fgeom->bottom_right_corner_rounded_radius = 0; + + if (fgeom->top_height + fgeom->left_width >= min_size_for_rounding) + fgeom->top_left_corner_rounded_radius = layout->top_left_corner_rounded_radius; + if (fgeom->top_height + fgeom->right_width >= min_size_for_rounding) + fgeom->top_right_corner_rounded_radius = layout->top_right_corner_rounded_radius; + + if (fgeom->bottom_height + fgeom->left_width >= min_size_for_rounding) + fgeom->bottom_left_corner_rounded_radius = layout->bottom_left_corner_rounded_radius; + if (fgeom->bottom_height + fgeom->right_width >= min_size_for_rounding) + fgeom->bottom_right_corner_rounded_radius = layout->bottom_right_corner_rounded_radius; +} + +MetaGradientSpec* +meta_gradient_spec_new (MetaGradientType type) +{ + MetaGradientSpec *spec; + + spec = g_new (MetaGradientSpec, 1); + + spec->type = type; + spec->color_specs = NULL; + + return spec; +} + +static void +free_color_spec (gpointer spec, gpointer user_data) +{ + meta_color_spec_free (spec); +} + +void +meta_gradient_spec_free (MetaGradientSpec *spec) +{ + g_return_if_fail (spec != NULL); + + g_slist_foreach (spec->color_specs, free_color_spec, NULL); + g_slist_free (spec->color_specs); + + DEBUG_FILL_STRUCT (spec); + g_free (spec); +} + +GdkPixbuf* +meta_gradient_spec_render (const MetaGradientSpec *spec, + GtkWidget *widget, + int width, + int height) +{ + int n_colors; + GdkColor *colors; + GSList *tmp; + int i; + GdkPixbuf *pixbuf; + + n_colors = g_slist_length (spec->color_specs); + + if (n_colors == 0) + return NULL; + + colors = g_new (GdkColor, n_colors); + + i = 0; + tmp = spec->color_specs; + while (tmp != NULL) + { + meta_color_spec_render (tmp->data, widget, &colors[i]); + + tmp = tmp->next; + ++i; + } + + pixbuf = meta_gradient_create_multi (width, height, + colors, n_colors, + spec->type); + + g_free (colors); + + return pixbuf; +} + +gboolean +meta_gradient_spec_validate (MetaGradientSpec *spec, + GError **error) +{ + g_return_val_if_fail (spec != NULL, FALSE); + + if (g_slist_length (spec->color_specs) < 2) + { + g_set_error (error, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Gradients should have at least two colors")); + return FALSE; + } + + return TRUE; +} + +MetaAlphaGradientSpec* +meta_alpha_gradient_spec_new (MetaGradientType type, + int n_alphas) +{ + MetaAlphaGradientSpec *spec; + + g_return_val_if_fail (n_alphas > 0, NULL); + + spec = g_new0 (MetaAlphaGradientSpec, 1); + + spec->type = type; + spec->alphas = g_new0 (unsigned char, n_alphas); + spec->n_alphas = n_alphas; + + return spec; +} + +void +meta_alpha_gradient_spec_free (MetaAlphaGradientSpec *spec) +{ + g_return_if_fail (spec != NULL); + + g_free (spec->alphas); + g_free (spec); +} + +MetaColorSpec* +meta_color_spec_new (MetaColorSpecType type) +{ + MetaColorSpec *spec; + MetaColorSpec dummy; + int size; + + size = G_STRUCT_OFFSET (MetaColorSpec, data); + + switch (type) + { + case META_COLOR_SPEC_BASIC: + size += sizeof (dummy.data.basic); + break; + + case META_COLOR_SPEC_GTK: + size += sizeof (dummy.data.gtk); + break; + + case META_COLOR_SPEC_BLEND: + size += sizeof (dummy.data.blend); + break; + + case META_COLOR_SPEC_SHADE: + size += sizeof (dummy.data.shade); + break; + } + + spec = g_malloc0 (size); + + spec->type = type; + + return spec; +} + +void +meta_color_spec_free (MetaColorSpec *spec) +{ + g_return_if_fail (spec != NULL); + + switch (spec->type) + { + case META_COLOR_SPEC_BASIC: + DEBUG_FILL_STRUCT (&spec->data.basic); + break; + + case META_COLOR_SPEC_GTK: + DEBUG_FILL_STRUCT (&spec->data.gtk); + break; + + case META_COLOR_SPEC_BLEND: + if (spec->data.blend.foreground) + meta_color_spec_free (spec->data.blend.foreground); + if (spec->data.blend.background) + meta_color_spec_free (spec->data.blend.background); + DEBUG_FILL_STRUCT (&spec->data.blend); + break; + + case META_COLOR_SPEC_SHADE: + if (spec->data.shade.base) + meta_color_spec_free (spec->data.shade.base); + DEBUG_FILL_STRUCT (&spec->data.shade); + break; + } + + g_free (spec); +} + +MetaColorSpec* +meta_color_spec_new_from_string (const char *str, + GError **err) +{ + MetaColorSpec *spec; + + spec = NULL; + + if (str[0] == 'g' && str[1] == 't' && str[2] == 'k' && str[3] == ':') + { + /* GTK color */ + const char *bracket; + const char *end_bracket; + char *tmp; + GtkStateType state; + MetaGtkColorComponent component; + + bracket = str; + while (*bracket && *bracket != '[') + ++bracket; + + if (*bracket == '\0') + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("GTK color specification must have the state in brackets, e.g. gtk:fg[NORMAL] where NORMAL is the state; could not parse \"%s\""), + str); + return NULL; + } + + end_bracket = bracket; + ++end_bracket; + while (*end_bracket && *end_bracket != ']') + ++end_bracket; + + if (*end_bracket == '\0') + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("GTK color specification must have a close bracket after the state, e.g. gtk:fg[NORMAL] where NORMAL is the state; could not parse \"%s\""), + str); + return NULL; + } + + tmp = g_strndup (bracket + 1, end_bracket - bracket - 1); + state = meta_gtk_state_from_string (tmp); + if (((int) state) == -1) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Did not understand state \"%s\" in color specification"), + tmp); + g_free (tmp); + return NULL; + } + g_free (tmp); + + tmp = g_strndup (str + 4, bracket - str - 4); + component = meta_color_component_from_string (tmp); + if (component == META_GTK_COLOR_LAST) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Did not understand color component \"%s\" in color specification"), + tmp); + g_free (tmp); + return NULL; + } + g_free (tmp); + + spec = meta_color_spec_new (META_COLOR_SPEC_GTK); + spec->data.gtk.state = state; + spec->data.gtk.component = component; + g_assert (spec->data.gtk.state < N_GTK_STATES); + g_assert (spec->data.gtk.component < META_GTK_COLOR_LAST); + } + else if (str[0] == 'b' && str[1] == 'l' && str[2] == 'e' && str[3] == 'n' && + str[4] == 'd' && str[5] == '/') + { + /* blend */ + char **split; + double alpha; + char *end; + MetaColorSpec *fg; + MetaColorSpec *bg; + + split = g_strsplit (str, "/", 4); + + if (split[0] == NULL || split[1] == NULL || + split[2] == NULL || split[3] == NULL) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Blend format is \"blend/bg_color/fg_color/alpha\", \"%s\" does not fit the format"), + str); + g_strfreev (split); + return NULL; + } + + alpha = g_ascii_strtod (split[3], &end); + if (end == split[3]) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Could not parse alpha value \"%s\" in blended color"), + split[3]); + g_strfreev (split); + return NULL; + } + + if (alpha < (0.0 - 1e6) || alpha > (1.0 + 1e6)) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Alpha value \"%s\" in blended color is not between 0.0 and 1.0"), + split[3]); + g_strfreev (split); + return NULL; + } + + fg = NULL; + bg = NULL; + + bg = meta_color_spec_new_from_string (split[1], err); + if (bg == NULL) + { + g_strfreev (split); + return NULL; + } + + fg = meta_color_spec_new_from_string (split[2], err); + if (fg == NULL) + { + meta_color_spec_free (bg); + g_strfreev (split); + return NULL; + } + + g_strfreev (split); + + spec = meta_color_spec_new (META_COLOR_SPEC_BLEND); + spec->data.blend.alpha = alpha; + spec->data.blend.background = bg; + spec->data.blend.foreground = fg; + } + else if (str[0] == 's' && str[1] == 'h' && str[2] == 'a' && str[3] == 'd' && + str[4] == 'e' && str[5] == '/') + { + /* shade */ + char **split; + double factor; + char *end; + MetaColorSpec *base; + + split = g_strsplit (str, "/", 3); + + if (split[0] == NULL || split[1] == NULL || + split[2] == NULL) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Shade format is \"shade/base_color/factor\", \"%s\" does not fit the format"), + str); + g_strfreev (split); + return NULL; + } + + factor = g_ascii_strtod (split[2], &end); + if (end == split[2]) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Could not parse shade factor \"%s\" in shaded color"), + split[2]); + g_strfreev (split); + return NULL; + } + + if (factor < (0.0 - 1e6)) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Shade factor \"%s\" in shaded color is negative"), + split[2]); + g_strfreev (split); + return NULL; + } + + base = NULL; + + base = meta_color_spec_new_from_string (split[1], err); + if (base == NULL) + { + g_strfreev (split); + return NULL; + } + + g_strfreev (split); + + spec = meta_color_spec_new (META_COLOR_SPEC_SHADE); + spec->data.shade.factor = factor; + spec->data.shade.base = base; + } + else + { + spec = meta_color_spec_new (META_COLOR_SPEC_BASIC); + + if (!gdk_color_parse (str, &spec->data.basic.color)) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Could not parse color \"%s\""), + str); + meta_color_spec_free (spec); + return NULL; + } + } + + g_assert (spec); + + return spec; +} + +MetaColorSpec* +meta_color_spec_new_gtk (MetaGtkColorComponent component, + GtkStateType state) +{ + MetaColorSpec *spec; + + spec = meta_color_spec_new (META_COLOR_SPEC_GTK); + + spec->data.gtk.component = component; + spec->data.gtk.state = state; + + return spec; +} + +void +meta_color_spec_render (MetaColorSpec *spec, + GtkWidget *widget, + GdkColor *color) +{ + GtkStyle *widget_style; + + g_return_if_fail (spec != NULL); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + widget_style = gtk_widget_get_style (widget); + g_return_if_fail (widget_style != NULL); + + switch (spec->type) + { + case META_COLOR_SPEC_BASIC: + *color = spec->data.basic.color; + break; + + case META_COLOR_SPEC_GTK: + switch (spec->data.gtk.component) + { + case META_GTK_COLOR_BG: + *color = widget_style->bg[spec->data.gtk.state]; + break; + case META_GTK_COLOR_FG: + *color = widget_style->fg[spec->data.gtk.state]; + break; + case META_GTK_COLOR_BASE: + *color = widget_style->base[spec->data.gtk.state]; + break; + case META_GTK_COLOR_TEXT: + *color = widget_style->text[spec->data.gtk.state]; + break; + case META_GTK_COLOR_LIGHT: + *color = widget_style->light[spec->data.gtk.state]; + break; + case META_GTK_COLOR_DARK: + *color = widget_style->dark[spec->data.gtk.state]; + break; + case META_GTK_COLOR_MID: + *color = widget_style->mid[spec->data.gtk.state]; + break; + case META_GTK_COLOR_TEXT_AA: + *color = widget_style->text_aa[spec->data.gtk.state]; + break; + case META_GTK_COLOR_LAST: + g_assert_not_reached (); + break; + } + break; + + case META_COLOR_SPEC_BLEND: + { + GdkColor bg, fg; + + meta_color_spec_render (spec->data.blend.background, widget, &bg); + meta_color_spec_render (spec->data.blend.foreground, widget, &fg); + + color_composite (&bg, &fg, spec->data.blend.alpha, + &spec->data.blend.color); + + *color = spec->data.blend.color; + } + break; + + case META_COLOR_SPEC_SHADE: + { + meta_color_spec_render (spec->data.shade.base, widget, + &spec->data.shade.color); + + gtk_style_shade (&spec->data.shade.color, + &spec->data.shade.color, spec->data.shade.factor); + + *color = spec->data.shade.color; + } + break; + } +} + +/** + * Represents an operation as a string. + * + * \param type an operation, such as addition + * \return a string, such as "+" + */ +static const char* +op_name (PosOperatorType type) +{ + switch (type) + { + case POS_OP_ADD: + return "+"; + case POS_OP_SUBTRACT: + return "-"; + case POS_OP_MULTIPLY: + return "*"; + case POS_OP_DIVIDE: + return "/"; + case POS_OP_MOD: + return "%"; + case POS_OP_MAX: + return "`max`"; + case POS_OP_MIN: + return "`min`"; + case POS_OP_NONE: + break; + } + + return "<unknown>"; +} + +/** + * Parses a string and returns an operation. + * + * \param p a pointer into a string representing an operation; part of an + * expression somewhere, so not null-terminated + * \param len set to the length of the string found. Set to 0 if none is. + * \return the operation found. If none was, returns POS_OP_NONE. + */ +static PosOperatorType +op_from_string (const char *p, + int *len) +{ + *len = 0; + + switch (*p) + { + case '+': + *len = 1; + return POS_OP_ADD; + case '-': + *len = 1; + return POS_OP_SUBTRACT; + case '*': + *len = 1; + return POS_OP_MULTIPLY; + case '/': + *len = 1; + return POS_OP_DIVIDE; + case '%': + *len = 1; + return POS_OP_MOD; + + case '`': + if (p[0] == '`' && + p[1] == 'm' && + p[2] == 'a' && + p[3] == 'x' && + p[4] == '`') + { + *len = 5; + return POS_OP_MAX; + } + else if (p[0] == '`' && + p[1] == 'm' && + p[2] == 'i' && + p[3] == 'n' && + p[4] == '`') + { + *len = 5; + return POS_OP_MIN; + } + } + + return POS_OP_NONE; +} + +/** + * Frees an array of tokens. All the tokens and their associated memory + * will be freed. + * + * \param tokens an array of tokens to be freed + * \param n_tokens how many tokens are in the array. + */ +static void +free_tokens (PosToken *tokens, + int n_tokens) +{ + int i; + + /* n_tokens can be 0 since tokens may have been allocated more than + * it was initialized + */ + + for (i = 0; i < n_tokens; i++) + if (tokens[i].type == POS_TOKEN_VARIABLE) + g_free (tokens[i].d.v.name); + + g_free (tokens); +} + +/** + * Tokenises a number in an expression. + * + * \param p a pointer into a string representing an operation; part of an + * expression somewhere, so not null-terminated + * \param end_return set to a pointer to the end of the number found; but + * not updated if no number was found at all + * \param next set to either an integer or a float token + * \param[out] err set to the problem if there was a problem + * \return TRUE if a valid number was found, FALSE otherwise (and "err" will + * have been set) + * + * \bug The "while (*start)..." part: what's wrong with strchr-ish things? + * \bug The name is wrong: it doesn't parse anything. + * \ingroup tokenizer + */ +static gboolean +parse_number (const char *p, + const char **end_return, + PosToken *next, + GError **err) +{ + const char *start = p; + char *end; + gboolean is_float; + char *num_str; + + while (*p && (*p == '.' || g_ascii_isdigit (*p))) + ++p; + + if (p == start) + { + char buf[7] = { '\0' }; + buf[g_unichar_to_utf8 (g_utf8_get_char (p), buf)] = '\0'; + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_BAD_CHARACTER, + _("Coordinate expression contains character '%s' which is not allowed"), + buf); + return FALSE; + } + + *end_return = p; + + /* we need this to exclude floats like "1e6" */ + num_str = g_strndup (start, p - start); + start = num_str; + is_float = FALSE; + while (*start) + { + if (*start == '.') + is_float = TRUE; + ++start; + } + + if (is_float) + { + next->type = POS_TOKEN_DOUBLE; + next->d.d.val = g_ascii_strtod (num_str, &end); + + if (end == num_str) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression contains floating point number '%s' which could not be parsed"), + num_str); + g_free (num_str); + return FALSE; + } + } + else + { + next->type = POS_TOKEN_INT; + next->d.i.val = strtol (num_str, &end, 10); + if (end == num_str) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression contains integer '%s' which could not be parsed"), + num_str); + g_free (num_str); + return FALSE; + } + } + + g_free (num_str); + + g_assert (next->type == POS_TOKEN_INT || next->type == POS_TOKEN_DOUBLE); + + return TRUE; +} + +/** + * Whether a variable can validly appear as part of the name of a variable. + */ +#define IS_VARIABLE_CHAR(c) (g_ascii_isalpha ((c)) || (c) == '_') + +#if 0 +static void +debug_print_tokens (PosToken *tokens, + int n_tokens) +{ + int i; + + for (i = 0; i < n_tokens; i++) + { + PosToken *t = &tokens[i]; + + g_print (" "); + + switch (t->type) + { + case POS_TOKEN_INT: + g_print ("\"%d\"", t->d.i.val); + break; + case POS_TOKEN_DOUBLE: + g_print ("\"%g\"", t->d.d.val); + break; + case POS_TOKEN_OPEN_PAREN: + g_print ("\"(\""); + break; + case POS_TOKEN_CLOSE_PAREN: + g_print ("\")\""); + break; + case POS_TOKEN_VARIABLE: + g_print ("\"%s\"", t->d.v.name); + break; + case POS_TOKEN_OPERATOR: + g_print ("\"%s\"", op_name (t->d.o.op)); + break; + } + } + + g_print ("\n"); +} +#endif + +/** + * Tokenises an expression. + * + * \param expr The expression + * \param[out] tokens_p The resulting tokens + * \param[out] n_tokens_p The number of resulting tokens + * \param[out] err set to the problem if there was a problem + * + * \return True if the expression was successfully tokenised; false otherwise. + * + * \ingroup tokenizer + */ +static gboolean +pos_tokenize (const char *expr, + PosToken **tokens_p, + int *n_tokens_p, + GError **err) +{ + PosToken *tokens; + int n_tokens; + int allocated; + const char *p; + + *tokens_p = NULL; + *n_tokens_p = 0; + + allocated = 3; + n_tokens = 0; + tokens = g_new (PosToken, allocated); + + p = expr; + while (*p) + { + PosToken *next; + int len; + + if (n_tokens == allocated) + { + allocated *= 2; + tokens = g_renew (PosToken, tokens, allocated); + } + + next = &tokens[n_tokens]; + + switch (*p) + { + case '*': + case '/': + case '+': + case '-': /* negative numbers aren't allowed so this is easy */ + case '%': + case '`': + next->type = POS_TOKEN_OPERATOR; + next->d.o.op = op_from_string (p, &len); + if (next->d.o.op != POS_OP_NONE) + { + ++n_tokens; + p = p + (len - 1); /* -1 since we ++p later */ + } + else + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression contained unknown operator at the start of this text: \"%s\""), + p); + + goto error; + } + break; + + case '(': + next->type = POS_TOKEN_OPEN_PAREN; + ++n_tokens; + break; + + case ')': + next->type = POS_TOKEN_CLOSE_PAREN; + ++n_tokens; + break; + + case ' ': + case '\t': + case '\n': + break; + + default: + if (IS_VARIABLE_CHAR (*p)) + { + /* Assume variable */ + const char *start = p; + while (*p && IS_VARIABLE_CHAR (*p)) + ++p; + g_assert (p != start); + next->type = POS_TOKEN_VARIABLE; + next->d.v.name = g_strndup (start, p - start); + ++n_tokens; + --p; /* since we ++p again at the end of while loop */ + } + else + { + /* Assume number */ + const char *end; + + if (!parse_number (p, &end, next, err)) + goto error; + + ++n_tokens; + p = end - 1; /* -1 since we ++p again at the end of while loop */ + } + + break; + } + + ++p; + } + + if (n_tokens == 0) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression was empty or not understood")); + + goto error; + } + + *tokens_p = tokens; + *n_tokens_p = n_tokens; + + return TRUE; + + error: + g_assert (err == NULL || *err != NULL); + + free_tokens (tokens, n_tokens); + return FALSE; +} + +/** + * The type of a PosExpr: either integer, double, or an operation. + * \ingroup parser + */ +typedef enum +{ + POS_EXPR_INT, + POS_EXPR_DOUBLE, + POS_EXPR_OPERATOR +} PosExprType; + +/** + * Type and value of an expression in a parsed sequence. We don't + * keep expressions in a tree; if this is of type POS_EXPR_OPERATOR, + * the arguments of the operator will be in the array positions + * immediately preceding and following this operator; they cannot + * themselves be operators. + * + * \bug operator is char; it should really be of PosOperatorType. + * \ingroup parser + */ +typedef struct +{ + PosExprType type; + union + { + double double_val; + int int_val; + char operator; + } d; +} PosExpr; + +#if 0 +static void +debug_print_exprs (PosExpr *exprs, + int n_exprs) +{ + int i; + + for (i = 0; i < n_exprs; i++) + { + switch (exprs[i].type) + { + case POS_EXPR_INT: + g_print (" %d", exprs[i].d.int_val); + break; + case POS_EXPR_DOUBLE: + g_print (" %g", exprs[i].d.double_val); + break; + case POS_EXPR_OPERATOR: + g_print (" %s", op_name (exprs[i].d.operator)); + break; + } + } + g_print ("\n"); +} +#endif + +static gboolean +do_operation (PosExpr *a, + PosExpr *b, + PosOperatorType op, + GError **err) +{ + /* Promote types to double if required */ + if (a->type == POS_EXPR_DOUBLE || + b->type == POS_EXPR_DOUBLE) + { + if (a->type != POS_EXPR_DOUBLE) + { + a->type = POS_EXPR_DOUBLE; + a->d.double_val = a->d.int_val; + } + if (b->type != POS_EXPR_DOUBLE) + { + b->type = POS_EXPR_DOUBLE; + b->d.double_val = b->d.int_val; + } + } + + g_assert (a->type == b->type); + + if (a->type == POS_EXPR_INT) + { + switch (op) + { + case POS_OP_MULTIPLY: + a->d.int_val = a->d.int_val * b->d.int_val; + break; + case POS_OP_DIVIDE: + if (b->d.int_val == 0) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_DIVIDE_BY_ZERO, + _("Coordinate expression results in division by zero")); + return FALSE; + } + a->d.int_val = a->d.int_val / b->d.int_val; + break; + case POS_OP_MOD: + if (b->d.int_val == 0) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_DIVIDE_BY_ZERO, + _("Coordinate expression results in division by zero")); + return FALSE; + } + a->d.int_val = a->d.int_val % b->d.int_val; + break; + case POS_OP_ADD: + a->d.int_val = a->d.int_val + b->d.int_val; + break; + case POS_OP_SUBTRACT: + a->d.int_val = a->d.int_val - b->d.int_val; + break; + case POS_OP_MAX: + a->d.int_val = MAX (a->d.int_val, b->d.int_val); + break; + case POS_OP_MIN: + a->d.int_val = MIN (a->d.int_val, b->d.int_val); + break; + case POS_OP_NONE: + g_assert_not_reached (); + break; + } + } + else if (a->type == POS_EXPR_DOUBLE) + { + switch (op) + { + case POS_OP_MULTIPLY: + a->d.double_val = a->d.double_val * b->d.double_val; + break; + case POS_OP_DIVIDE: + if (b->d.double_val == 0.0) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_DIVIDE_BY_ZERO, + _("Coordinate expression results in division by zero")); + return FALSE; + } + a->d.double_val = a->d.double_val / b->d.double_val; + break; + case POS_OP_MOD: + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_MOD_ON_FLOAT, + _("Coordinate expression tries to use mod operator on a floating-point number")); + return FALSE; + case POS_OP_ADD: + a->d.double_val = a->d.double_val + b->d.double_val; + break; + case POS_OP_SUBTRACT: + a->d.double_val = a->d.double_val - b->d.double_val; + break; + case POS_OP_MAX: + a->d.double_val = MAX (a->d.double_val, b->d.double_val); + break; + case POS_OP_MIN: + a->d.double_val = MIN (a->d.double_val, b->d.double_val); + break; + case POS_OP_NONE: + g_assert_not_reached (); + break; + } + } + else + g_assert_not_reached (); + + return TRUE; +} + +static gboolean +do_operations (PosExpr *exprs, + int *n_exprs, + int precedence, + GError **err) +{ + int i; + +#if 0 + g_print ("Doing prec %d ops on %d exprs\n", precedence, *n_exprs); + debug_print_exprs (exprs, *n_exprs); +#endif + + i = 1; + while (i < *n_exprs) + { + gboolean compress; + + /* exprs[i-1] first operand + * exprs[i] operator + * exprs[i+1] second operand + * + * we replace first operand with result of mul/div/mod, + * or skip over operator and second operand if we have + * an add/subtract + */ + + if (exprs[i-1].type == POS_EXPR_OPERATOR) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression has an operator \"%s\" where an operand was expected"), + op_name (exprs[i-1].d.operator)); + return FALSE; + } + + if (exprs[i].type != POS_EXPR_OPERATOR) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression had an operand where an operator was expected")); + return FALSE; + } + + if (i == (*n_exprs - 1)) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression ended with an operator instead of an operand")); + return FALSE; + } + + g_assert ((i+1) < *n_exprs); + + if (exprs[i+1].type == POS_EXPR_OPERATOR) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression has operator \"%c\" following operator \"%c\" with no operand in between"), + exprs[i+1].d.operator, + exprs[i].d.operator); + return FALSE; + } + + compress = FALSE; + + switch (precedence) + { + case 2: + switch (exprs[i].d.operator) + { + case POS_OP_DIVIDE: + case POS_OP_MOD: + case POS_OP_MULTIPLY: + compress = TRUE; + if (!do_operation (&exprs[i-1], &exprs[i+1], + exprs[i].d.operator, + err)) + return FALSE; + break; + } + break; + case 1: + switch (exprs[i].d.operator) + { + case POS_OP_ADD: + case POS_OP_SUBTRACT: + compress = TRUE; + if (!do_operation (&exprs[i-1], &exprs[i+1], + exprs[i].d.operator, + err)) + return FALSE; + break; + } + break; + /* I have no rationale at all for making these low-precedence */ + case 0: + switch (exprs[i].d.operator) + { + case POS_OP_MAX: + case POS_OP_MIN: + compress = TRUE; + if (!do_operation (&exprs[i-1], &exprs[i+1], + exprs[i].d.operator, + err)) + return FALSE; + break; + } + break; + } + + if (compress) + { + /* exprs[i-1] first operand (now result) + * exprs[i] operator + * exprs[i+1] second operand + * exprs[i+2] new operator + * + * we move new operator just after first operand + */ + if ((i+2) < *n_exprs) + { + g_memmove (&exprs[i], &exprs[i+2], + sizeof (PosExpr) * (*n_exprs - i - 2)); + } + + *n_exprs -= 2; + } + else + { + /* Skip operator and next operand */ + i += 2; + } + } + + return TRUE; +} + +/** + * There is a predefined set of variables which can appear in an expression. + * Here we take a token representing a variable, and return the current value + * of that variable in a particular environment. + * (The value is always an integer.) + * + * There are supposedly some circumstances in which this function can be + * called from outside Marco, in which case env->theme will be NULL, and + * therefore we can't use it to find out quark values, so we do the comparison + * using strcmp, which is slower. + * + * \param t The token representing a variable + * \param[out] result The value of that variable; not set if the token did + * not represent a known variable + * \param env The environment within which t should be evaluated + * \param[out] err set to the problem if there was a problem + * + * \return true if we found the variable asked for, false if we didn't + * + * \bug shouldn't t be const? + * \bug we should perhaps consider some sort of lookup arrangement into an + * array; also, the duplication of code is unlovely; perhaps using glib + * string hashes instead of quarks would fix both problems? + * \ingroup parser + */ +static gboolean +pos_eval_get_variable (PosToken *t, + int *result, + const MetaPositionExprEnv *env, + GError **err) +{ + if (env->theme) + { + if (t->d.v.name_quark == env->theme->quark_width) + *result = env->rect.width; + else if (t->d.v.name_quark == env->theme->quark_height) + *result = env->rect.height; + else if (env->object_width >= 0 && + t->d.v.name_quark == env->theme->quark_object_width) + *result = env->object_width; + else if (env->object_height >= 0 && + t->d.v.name_quark == env->theme->quark_object_height) + *result = env->object_height; + else if (t->d.v.name_quark == env->theme->quark_left_width) + *result = env->left_width; + else if (t->d.v.name_quark == env->theme->quark_right_width) + *result = env->right_width; + else if (t->d.v.name_quark == env->theme->quark_top_height) + *result = env->top_height; + else if (t->d.v.name_quark == env->theme->quark_bottom_height) + *result = env->bottom_height; + else if (t->d.v.name_quark == env->theme->quark_mini_icon_width) + *result = env->mini_icon_width; + else if (t->d.v.name_quark == env->theme->quark_mini_icon_height) + *result = env->mini_icon_height; + else if (t->d.v.name_quark == env->theme->quark_icon_width) + *result = env->icon_width; + else if (t->d.v.name_quark == env->theme->quark_icon_height) + *result = env->icon_height; + else if (t->d.v.name_quark == env->theme->quark_title_width) + *result = env->title_width; + else if (t->d.v.name_quark == env->theme->quark_title_height) + *result = env->title_height; + else + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_UNKNOWN_VARIABLE, + _("Coordinate expression had unknown variable or constant \"%s\""), + t->d.v.name); + return FALSE; + } + } + else + { + if (strcmp (t->d.v.name, "width") == 0) + *result = env->rect.width; + else if (strcmp (t->d.v.name, "height") == 0) + *result = env->rect.height; + else if (env->object_width >= 0 && + strcmp (t->d.v.name, "object_width") == 0) + *result = env->object_width; + else if (env->object_height >= 0 && + strcmp (t->d.v.name, "object_height") == 0) + *result = env->object_height; + else if (strcmp (t->d.v.name, "left_width") == 0) + *result = env->left_width; + else if (strcmp (t->d.v.name, "right_width") == 0) + *result = env->right_width; + else if (strcmp (t->d.v.name, "top_height") == 0) + *result = env->top_height; + else if (strcmp (t->d.v.name, "bottom_height") == 0) + *result = env->bottom_height; + else if (strcmp (t->d.v.name, "mini_icon_width") == 0) + *result = env->mini_icon_width; + else if (strcmp (t->d.v.name, "mini_icon_height") == 0) + *result = env->mini_icon_height; + else if (strcmp (t->d.v.name, "icon_width") == 0) + *result = env->icon_width; + else if (strcmp (t->d.v.name, "icon_height") == 0) + *result = env->icon_height; + else if (strcmp (t->d.v.name, "title_width") == 0) + *result = env->title_width; + else if (strcmp (t->d.v.name, "title_height") == 0) + *result = env->title_height; + else + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_UNKNOWN_VARIABLE, + _("Coordinate expression had unknown variable or constant \"%s\""), + t->d.v.name); + return FALSE; + } + } + + return TRUE; +} + +/** + * Evaluates a sequence of tokens within a particular environment context, + * and returns the current value. May recur if parantheses are found. + * + * \param tokens A list of tokens to evaluate. + * \param n_tokens How many tokens are in the list. + * \param env The environment context in which to evaluate the expression. + * \param[out] result The current value of the expression + * + * \bug Yes, we really do reparse the expression every time it's evaluated. + * We should keep the parse tree around all the time and just + * run the new values through it. + * \ingroup parser + */ +static gboolean +pos_eval_helper (PosToken *tokens, + int n_tokens, + const MetaPositionExprEnv *env, + PosExpr *result, + GError **err) +{ + /* Lazy-ass hardcoded limit on number of terms in expression */ +#define MAX_EXPRS 32 + int paren_level; + int first_paren; + int i; + PosExpr exprs[MAX_EXPRS]; + int n_exprs; + int precedence; + + /* Our first goal is to get a list of PosExpr, essentially + * substituting variables and handling parentheses. + */ + + first_paren = 0; + paren_level = 0; + n_exprs = 0; + for (i = 0; i < n_tokens; i++) + { + PosToken *t = &tokens[i]; + + if (n_exprs >= MAX_EXPRS) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression parser overflowed its buffer.")); + return FALSE; + } + + if (paren_level == 0) + { + switch (t->type) + { + case POS_TOKEN_INT: + exprs[n_exprs].type = POS_EXPR_INT; + exprs[n_exprs].d.int_val = t->d.i.val; + ++n_exprs; + break; + + case POS_TOKEN_DOUBLE: + exprs[n_exprs].type = POS_EXPR_DOUBLE; + exprs[n_exprs].d.double_val = t->d.d.val; + ++n_exprs; + break; + + case POS_TOKEN_OPEN_PAREN: + ++paren_level; + if (paren_level == 1) + first_paren = i; + break; + + case POS_TOKEN_CLOSE_PAREN: + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_BAD_PARENS, + _("Coordinate expression had a close parenthesis with no open parenthesis")); + return FALSE; + + case POS_TOKEN_VARIABLE: + exprs[n_exprs].type = POS_EXPR_INT; + + /* FIXME we should just dump all this crap + * in a hash, maybe keep width/height out + * for optimization purposes + */ + if (!pos_eval_get_variable (t, &exprs[n_exprs].d.int_val, env, err)) + return FALSE; + + ++n_exprs; + break; + + case POS_TOKEN_OPERATOR: + exprs[n_exprs].type = POS_EXPR_OPERATOR; + exprs[n_exprs].d.operator = t->d.o.op; + ++n_exprs; + break; + } + } + else + { + g_assert (paren_level > 0); + + switch (t->type) + { + case POS_TOKEN_INT: + case POS_TOKEN_DOUBLE: + case POS_TOKEN_VARIABLE: + case POS_TOKEN_OPERATOR: + break; + + case POS_TOKEN_OPEN_PAREN: + ++paren_level; + break; + + case POS_TOKEN_CLOSE_PAREN: + if (paren_level == 1) + { + /* We closed a toplevel paren group, so recurse */ + if (!pos_eval_helper (&tokens[first_paren+1], + i - first_paren - 1, + env, + &exprs[n_exprs], + err)) + return FALSE; + + ++n_exprs; + } + + --paren_level; + break; + + } + } + } + + if (paren_level > 0) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_BAD_PARENS, + _("Coordinate expression had an open parenthesis with no close parenthesis")); + return FALSE; + } + + /* Now we have no parens and no vars; so we just do all the multiplies + * and divides, then all the add and subtract. + */ + if (n_exprs == 0) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression doesn't seem to have any operators or operands")); + return FALSE; + } + + /* precedence 1 ops */ + precedence = 2; + while (precedence >= 0) + { + if (!do_operations (exprs, &n_exprs, precedence, err)) + return FALSE; + --precedence; + } + + g_assert (n_exprs == 1); + + *result = *exprs; + + return TRUE; +} + +/* + * expr = int | double | expr * expr | expr / expr | + * expr + expr | expr - expr | (expr) + * + * so very not worth fooling with bison, yet so very painful by hand. + */ +/** + * Evaluates an expression. + * + * \param spec The expression to evaluate. + * \param env The environment context to evaluate the expression in. + * \param[out] val_p The integer value of the expression; if the expression + * is of type float, this will be rounded. If we return + * FALSE because the expression is invalid, this will be + * zero. + * \param[out] err The error, if anything went wrong. + * + * \return True if we evaluated the expression successfully; false otherwise. + * + * \bug Shouldn't spec be const? + * \ingroup parser + */ +static gboolean +pos_eval (MetaDrawSpec *spec, + const MetaPositionExprEnv *env, + int *val_p, + GError **err) +{ + PosExpr expr; + + *val_p = 0; + + if (pos_eval_helper (spec->tokens, spec->n_tokens, env, &expr, err)) + { + switch (expr.type) + { + case POS_EXPR_INT: + *val_p = expr.d.int_val; + break; + case POS_EXPR_DOUBLE: + *val_p = expr.d.double_val; + break; + case POS_EXPR_OPERATOR: + g_assert_not_reached (); + break; + } + return TRUE; + } + else + { + return FALSE; + } +} + +/* We always return both X and Y, but only one will be meaningful in + * most contexts. + */ + +gboolean +meta_parse_position_expression (MetaDrawSpec *spec, + const MetaPositionExprEnv *env, + int *x_return, + int *y_return, + GError **err) +{ + /* All positions are in a coordinate system with x, y at the origin. + * The expression can have -, +, *, / as operators, floating point + * or integer constants, and the variables "width" and "height" and + * optionally "object_width" and object_height". Negative numbers + * aren't allowed. + */ + int val; + + if (spec->constant) + val = spec->value; + else + { + if (pos_eval (spec, env, &spec->value, err) == FALSE) + { + g_assert (err == NULL || *err != NULL); + return FALSE; + } + + val = spec->value; + } + + if (x_return) + *x_return = env->rect.x + val; + if (y_return) + *y_return = env->rect.y + val; + + return TRUE; +} + + +gboolean +meta_parse_size_expression (MetaDrawSpec *spec, + const MetaPositionExprEnv *env, + int *val_return, + GError **err) +{ + int val; + + if (spec->constant) + val = spec->value; + else + { + if (pos_eval (spec, env, &spec->value, err) == FALSE) + { + g_assert (err == NULL || *err != NULL); + return FALSE; + } + + val = spec->value; + } + + if (val_return) + *val_return = MAX (val, 1); /* require that sizes be at least 1x1 */ + + return TRUE; +} + +/* To do this we tokenize, replace variable tokens + * that are constants, then reassemble. The purpose + * here is to optimize expressions so we don't do hash + * lookups to eval them. Obviously it's a tradeoff that + * slows down theme load times. + */ +gboolean +meta_theme_replace_constants (MetaTheme *theme, + PosToken *tokens, + int n_tokens, + GError **err) +{ + int i; + double dval; + int ival; + gboolean is_constant = TRUE; + + /* Loop through tokenized string looking for variables to replace */ + for (i = 0; i < n_tokens; i++) + { + PosToken *t = &tokens[i]; + + if (t->type == POS_TOKEN_VARIABLE) + { + if (meta_theme_lookup_int_constant (theme, t->d.v.name, &ival)) + { + g_free (t->d.v.name); + t->type = POS_TOKEN_INT; + t->d.i.val = ival; + } + else if (meta_theme_lookup_float_constant (theme, t->d.v.name, &dval)) + { + g_free (t->d.v.name); + t->type = POS_TOKEN_DOUBLE; + t->d.d.val = dval; + } + else + { + /* If we've found a variable that cannot be replaced then the + expression is not a constant expression and we want to + replace it with a GQuark */ + + t->d.v.name_quark = g_quark_from_string (t->d.v.name); + is_constant = FALSE; + } + } + } + + return is_constant; +} + +static int +parse_x_position_unchecked (MetaDrawSpec *spec, + const MetaPositionExprEnv *env) +{ + int retval; + GError *error; + + retval = 0; + error = NULL; + if (!meta_parse_position_expression (spec, env, &retval, NULL, &error)) + { + meta_warning (_("Theme contained an expression that resulted in an error: %s\n"), + error->message); + + g_error_free (error); + } + + return retval; +} + +static int +parse_y_position_unchecked (MetaDrawSpec *spec, + const MetaPositionExprEnv *env) +{ + int retval; + GError *error; + + retval = 0; + error = NULL; + if (!meta_parse_position_expression (spec, env, NULL, &retval, &error)) + { + meta_warning (_("Theme contained an expression that resulted in an error: %s\n"), + error->message); + + g_error_free (error); + } + + return retval; +} + +static int +parse_size_unchecked (MetaDrawSpec *spec, + MetaPositionExprEnv *env) +{ + int retval; + GError *error; + + retval = 0; + error = NULL; + if (!meta_parse_size_expression (spec, env, &retval, &error)) + { + meta_warning (_("Theme contained an expression that resulted in an error: %s\n"), + error->message); + + g_error_free (error); + } + + return retval; +} + +void +meta_draw_spec_free (MetaDrawSpec *spec) +{ + if (!spec) return; + free_tokens (spec->tokens, spec->n_tokens); + g_slice_free (MetaDrawSpec, spec); +} + +MetaDrawSpec * +meta_draw_spec_new (MetaTheme *theme, + const char *expr, + GError **error) +{ + MetaDrawSpec *spec; + + spec = g_slice_new0 (MetaDrawSpec); + + pos_tokenize (expr, &spec->tokens, &spec->n_tokens, NULL); + + spec->constant = meta_theme_replace_constants (theme, spec->tokens, + spec->n_tokens, NULL); + if (spec->constant) + { + gboolean result; + + result = pos_eval (spec, NULL, &spec->value, error); + if (result == FALSE) + { + meta_draw_spec_free (spec); + return NULL; + } + } + + return spec; +} + +MetaDrawOp* +meta_draw_op_new (MetaDrawType type) +{ + MetaDrawOp *op; + MetaDrawOp dummy; + int size; + + size = G_STRUCT_OFFSET (MetaDrawOp, data); + + switch (type) + { + case META_DRAW_LINE: + size += sizeof (dummy.data.line); + break; + + case META_DRAW_RECTANGLE: + size += sizeof (dummy.data.rectangle); + break; + + case META_DRAW_ARC: + size += sizeof (dummy.data.arc); + break; + + case META_DRAW_CLIP: + size += sizeof (dummy.data.clip); + break; + + case META_DRAW_TINT: + size += sizeof (dummy.data.tint); + break; + + case META_DRAW_GRADIENT: + size += sizeof (dummy.data.gradient); + break; + + case META_DRAW_IMAGE: + size += sizeof (dummy.data.image); + break; + + case META_DRAW_GTK_ARROW: + size += sizeof (dummy.data.gtk_arrow); + break; + + case META_DRAW_GTK_BOX: + size += sizeof (dummy.data.gtk_box); + break; + + case META_DRAW_GTK_VLINE: + size += sizeof (dummy.data.gtk_vline); + break; + + case META_DRAW_ICON: + size += sizeof (dummy.data.icon); + break; + + case META_DRAW_TITLE: + size += sizeof (dummy.data.title); + break; + case META_DRAW_OP_LIST: + size += sizeof (dummy.data.op_list); + break; + case META_DRAW_TILE: + size += sizeof (dummy.data.tile); + break; + } + + op = g_malloc0 (size); + + op->type = type; + + return op; +} + +void +meta_draw_op_free (MetaDrawOp *op) +{ + g_return_if_fail (op != NULL); + + switch (op->type) + { + case META_DRAW_LINE: + if (op->data.line.color_spec) + meta_color_spec_free (op->data.line.color_spec); + + meta_draw_spec_free (op->data.line.x1); + meta_draw_spec_free (op->data.line.y1); + meta_draw_spec_free (op->data.line.x2); + meta_draw_spec_free (op->data.line.y2); + break; + + case META_DRAW_RECTANGLE: + if (op->data.rectangle.color_spec) + g_free (op->data.rectangle.color_spec); + + meta_draw_spec_free (op->data.rectangle.x); + meta_draw_spec_free (op->data.rectangle.y); + meta_draw_spec_free (op->data.rectangle.width); + meta_draw_spec_free (op->data.rectangle.height); + break; + + case META_DRAW_ARC: + if (op->data.arc.color_spec) + g_free (op->data.arc.color_spec); + + meta_draw_spec_free (op->data.arc.x); + meta_draw_spec_free (op->data.arc.y); + meta_draw_spec_free (op->data.arc.width); + meta_draw_spec_free (op->data.arc.height); + break; + + case META_DRAW_CLIP: + meta_draw_spec_free (op->data.clip.x); + meta_draw_spec_free (op->data.clip.y); + meta_draw_spec_free (op->data.clip.width); + meta_draw_spec_free (op->data.clip.height); + break; + + case META_DRAW_TINT: + if (op->data.tint.color_spec) + meta_color_spec_free (op->data.tint.color_spec); + + if (op->data.tint.alpha_spec) + meta_alpha_gradient_spec_free (op->data.tint.alpha_spec); + + meta_draw_spec_free (op->data.tint.x); + meta_draw_spec_free (op->data.tint.y); + meta_draw_spec_free (op->data.tint.width); + meta_draw_spec_free (op->data.tint.height); + break; + + case META_DRAW_GRADIENT: + if (op->data.gradient.gradient_spec) + meta_gradient_spec_free (op->data.gradient.gradient_spec); + + if (op->data.gradient.alpha_spec) + meta_alpha_gradient_spec_free (op->data.gradient.alpha_spec); + + meta_draw_spec_free (op->data.gradient.x); + meta_draw_spec_free (op->data.gradient.y); + meta_draw_spec_free (op->data.gradient.width); + meta_draw_spec_free (op->data.gradient.height); + break; + + case META_DRAW_IMAGE: + if (op->data.image.alpha_spec) + meta_alpha_gradient_spec_free (op->data.image.alpha_spec); + + if (op->data.image.pixbuf) + g_object_unref (G_OBJECT (op->data.image.pixbuf)); + + if (op->data.image.colorize_spec) + meta_color_spec_free (op->data.image.colorize_spec); + + if (op->data.image.colorize_cache_pixbuf) + g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf)); + + meta_draw_spec_free (op->data.image.x); + meta_draw_spec_free (op->data.image.y); + meta_draw_spec_free (op->data.image.width); + meta_draw_spec_free (op->data.image.height); + break; + + case META_DRAW_GTK_ARROW: + meta_draw_spec_free (op->data.gtk_arrow.x); + meta_draw_spec_free (op->data.gtk_arrow.y); + meta_draw_spec_free (op->data.gtk_arrow.width); + meta_draw_spec_free (op->data.gtk_arrow.height); + break; + + case META_DRAW_GTK_BOX: + meta_draw_spec_free (op->data.gtk_box.x); + meta_draw_spec_free (op->data.gtk_box.y); + meta_draw_spec_free (op->data.gtk_box.width); + meta_draw_spec_free (op->data.gtk_box.height); + break; + + case META_DRAW_GTK_VLINE: + meta_draw_spec_free (op->data.gtk_vline.x); + meta_draw_spec_free (op->data.gtk_vline.y1); + meta_draw_spec_free (op->data.gtk_vline.y2); + break; + + case META_DRAW_ICON: + if (op->data.icon.alpha_spec) + meta_alpha_gradient_spec_free (op->data.icon.alpha_spec); + + meta_draw_spec_free (op->data.icon.x); + meta_draw_spec_free (op->data.icon.y); + meta_draw_spec_free (op->data.icon.width); + meta_draw_spec_free (op->data.icon.height); + break; + + case META_DRAW_TITLE: + if (op->data.title.color_spec) + meta_color_spec_free (op->data.title.color_spec); + + meta_draw_spec_free (op->data.title.x); + meta_draw_spec_free (op->data.title.y); + break; + + case META_DRAW_OP_LIST: + if (op->data.op_list.op_list) + meta_draw_op_list_unref (op->data.op_list.op_list); + + meta_draw_spec_free (op->data.op_list.x); + meta_draw_spec_free (op->data.op_list.y); + meta_draw_spec_free (op->data.op_list.width); + meta_draw_spec_free (op->data.op_list.height); + break; + + case META_DRAW_TILE: + if (op->data.tile.op_list) + meta_draw_op_list_unref (op->data.tile.op_list); + + meta_draw_spec_free (op->data.tile.x); + meta_draw_spec_free (op->data.tile.y); + meta_draw_spec_free (op->data.tile.width); + meta_draw_spec_free (op->data.tile.height); + meta_draw_spec_free (op->data.tile.tile_xoffset); + meta_draw_spec_free (op->data.tile.tile_yoffset); + meta_draw_spec_free (op->data.tile.tile_width); + meta_draw_spec_free (op->data.tile.tile_height); + break; + } + + g_free (op); +} + +static GdkPixbuf* +apply_alpha (GdkPixbuf *pixbuf, + MetaAlphaGradientSpec *spec, + gboolean force_copy) +{ + GdkPixbuf *new_pixbuf; + gboolean needs_alpha; + + g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL); + + needs_alpha = spec && (spec->n_alphas > 1 || + spec->alphas[0] != 0xff); + + if (!needs_alpha) + return pixbuf; + + if (!gdk_pixbuf_get_has_alpha (pixbuf)) + { + new_pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0); + g_object_unref (G_OBJECT (pixbuf)); + pixbuf = new_pixbuf; + } + else if (force_copy) + { + new_pixbuf = gdk_pixbuf_copy (pixbuf); + g_object_unref (G_OBJECT (pixbuf)); + pixbuf = new_pixbuf; + } + + g_assert (gdk_pixbuf_get_has_alpha (pixbuf)); + + meta_gradient_add_alpha (pixbuf, spec->alphas, spec->n_alphas, spec->type); + + return pixbuf; +} + +static GdkPixbuf* +pixbuf_tile (GdkPixbuf *tile, + int width, + int height) +{ + GdkPixbuf *pixbuf; + int tile_width; + int tile_height; + int i, j; + + tile_width = gdk_pixbuf_get_width (tile); + tile_height = gdk_pixbuf_get_height (tile); + + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + gdk_pixbuf_get_has_alpha (tile), + 8, width, height); + + i = 0; + while (i < width) + { + j = 0; + while (j < height) + { + int w, h; + + w = MIN (tile_width, width - i); + h = MIN (tile_height, height - j); + + gdk_pixbuf_copy_area (tile, + 0, 0, + w, h, + pixbuf, + i, j); + + j += tile_height; + } + + i += tile_width; + } + + return pixbuf; +} + +static GdkPixbuf * +replicate_rows (GdkPixbuf *src, + int src_x, + int src_y, + int width, + int height) +{ + unsigned int n_channels = gdk_pixbuf_get_n_channels (src); + unsigned int src_rowstride = gdk_pixbuf_get_rowstride (src); + unsigned char *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x + * n_channels); + unsigned char *dest_pixels; + GdkPixbuf *result; + unsigned int dest_rowstride; + int i; + + result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8, + width, height); + dest_rowstride = gdk_pixbuf_get_rowstride (result); + dest_pixels = gdk_pixbuf_get_pixels (result); + + for (i = 0; i < height; i++) + memcpy (dest_pixels + dest_rowstride * i, pixels, n_channels * width); + + return result; +} + +static GdkPixbuf * +replicate_cols (GdkPixbuf *src, + int src_x, + int src_y, + int width, + int height) +{ + unsigned int n_channels = gdk_pixbuf_get_n_channels (src); + unsigned int src_rowstride = gdk_pixbuf_get_rowstride (src); + unsigned char *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x + * n_channels); + unsigned char *dest_pixels; + GdkPixbuf *result; + unsigned int dest_rowstride; + int i, j; + + result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8, + width, height); + dest_rowstride = gdk_pixbuf_get_rowstride (result); + dest_pixels = gdk_pixbuf_get_pixels (result); + + for (i = 0; i < height; i++) + { + unsigned char *p = dest_pixels + dest_rowstride * i; + unsigned char *q = pixels + src_rowstride * i; + + unsigned char r = *(q++); + unsigned char g = *(q++); + unsigned char b = *(q++); + + if (n_channels == 4) + { + unsigned char a; + + a = *(q++); + + for (j = 0; j < width; j++) + { + *(p++) = r; + *(p++) = g; + *(p++) = b; + *(p++) = a; + } + } + else + { + for (j = 0; j < width; j++) + { + *(p++) = r; + *(p++) = g; + *(p++) = b; + } + } + } + + return result; +} + +static GdkPixbuf* +scale_and_alpha_pixbuf (GdkPixbuf *src, + MetaAlphaGradientSpec *alpha_spec, + MetaImageFillType fill_type, + int width, + int height, + gboolean vertical_stripes, + gboolean horizontal_stripes) +{ + GdkPixbuf *pixbuf; + GdkPixbuf *temp_pixbuf; + + pixbuf = NULL; + + pixbuf = src; + + if (gdk_pixbuf_get_width (pixbuf) == width && + gdk_pixbuf_get_height (pixbuf) == height) + { + g_object_ref (G_OBJECT (pixbuf)); + } + else + { + if (fill_type == META_IMAGE_FILL_TILE) + { + pixbuf = pixbuf_tile (pixbuf, width, height); + } + else + { + int src_h, src_w, dest_h, dest_w; + src_h = gdk_pixbuf_get_height (src); + src_w = gdk_pixbuf_get_width (src); + + /* prefer to replicate_cols if possible, as that + * is faster (no memory reads) + */ + if (horizontal_stripes) + { + dest_w = gdk_pixbuf_get_width (src); + dest_h = height; + } + else if (vertical_stripes) + { + dest_w = width; + dest_h = gdk_pixbuf_get_height (src); + } + + else + { + dest_w = width; + dest_h = height; + } + + if (dest_w == src_w && dest_h == src_h) + { + temp_pixbuf = src; + g_object_ref (G_OBJECT (temp_pixbuf)); + } + else + { + temp_pixbuf = gdk_pixbuf_scale_simple (src, + dest_w, dest_h, + GDK_INTERP_BILINEAR); + } + + /* prefer to replicate_cols if possible, as that + * is faster (no memory reads) + */ + if (horizontal_stripes) + { + pixbuf = replicate_cols (temp_pixbuf, 0, 0, width, height); + g_object_unref (G_OBJECT (temp_pixbuf)); + } + else if (vertical_stripes) + { + pixbuf = replicate_rows (temp_pixbuf, 0, 0, width, height); + g_object_unref (G_OBJECT (temp_pixbuf)); + } + else + { + pixbuf = temp_pixbuf; + } + } + } + + if (pixbuf) + pixbuf = apply_alpha (pixbuf, alpha_spec, pixbuf == src); + + return pixbuf; +} + +static GdkPixbuf* +draw_op_as_pixbuf (const MetaDrawOp *op, + GtkWidget *widget, + const MetaDrawInfo *info, + int width, + int height) +{ + /* Try to get the op as a pixbuf, assuming w/h in the op + * matches the width/height passed in. return NULL + * if the op can't be converted to an equivalent pixbuf. + */ + GdkPixbuf *pixbuf; + + pixbuf = NULL; + + switch (op->type) + { + case META_DRAW_LINE: + break; + + case META_DRAW_RECTANGLE: + if (op->data.rectangle.filled) + { + GdkColor color; + + meta_color_spec_render (op->data.rectangle.color_spec, + widget, + &color); + + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + FALSE, + 8, width, height); + + gdk_pixbuf_fill (pixbuf, GDK_COLOR_RGBA (color)); + } + break; + + case META_DRAW_ARC: + break; + + case META_DRAW_CLIP: + break; + + case META_DRAW_TINT: + { + GdkColor color; + guint32 rgba; + gboolean has_alpha; + + meta_color_spec_render (op->data.rectangle.color_spec, + widget, + &color); + + has_alpha = + op->data.tint.alpha_spec && + (op->data.tint.alpha_spec->n_alphas > 1 || + op->data.tint.alpha_spec->alphas[0] != 0xff); + + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + has_alpha, + 8, width, height); + + if (!has_alpha) + { + rgba = GDK_COLOR_RGBA (color); + + gdk_pixbuf_fill (pixbuf, rgba); + } + else if (op->data.tint.alpha_spec->n_alphas == 1) + { + rgba = GDK_COLOR_RGBA (color); + rgba &= ~0xff; + rgba |= op->data.tint.alpha_spec->alphas[0]; + + gdk_pixbuf_fill (pixbuf, rgba); + } + else + { + rgba = GDK_COLOR_RGBA (color); + + gdk_pixbuf_fill (pixbuf, rgba); + + meta_gradient_add_alpha (pixbuf, + op->data.tint.alpha_spec->alphas, + op->data.tint.alpha_spec->n_alphas, + op->data.tint.alpha_spec->type); + } + } + break; + + case META_DRAW_GRADIENT: + { + pixbuf = meta_gradient_spec_render (op->data.gradient.gradient_spec, + widget, width, height); + + pixbuf = apply_alpha (pixbuf, + op->data.gradient.alpha_spec, + FALSE); + } + break; + + + case META_DRAW_IMAGE: + { + if (op->data.image.colorize_spec) + { + GdkColor color; + + meta_color_spec_render (op->data.image.colorize_spec, + widget, &color); + + if (op->data.image.colorize_cache_pixbuf == NULL || + op->data.image.colorize_cache_pixel != GDK_COLOR_RGB (color)) + { + if (op->data.image.colorize_cache_pixbuf) + g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf)); + + /* const cast here */ + ((MetaDrawOp*)op)->data.image.colorize_cache_pixbuf = + colorize_pixbuf (op->data.image.pixbuf, + &color); + ((MetaDrawOp*)op)->data.image.colorize_cache_pixel = + GDK_COLOR_RGB (color); + } + + if (op->data.image.colorize_cache_pixbuf) + { + pixbuf = scale_and_alpha_pixbuf (op->data.image.colorize_cache_pixbuf, + op->data.image.alpha_spec, + op->data.image.fill_type, + width, height, + op->data.image.vertical_stripes, + op->data.image.horizontal_stripes); + } + } + else + { + pixbuf = scale_and_alpha_pixbuf (op->data.image.pixbuf, + op->data.image.alpha_spec, + op->data.image.fill_type, + width, height, + op->data.image.vertical_stripes, + op->data.image.horizontal_stripes); + } + break; + } + + case META_DRAW_GTK_ARROW: + case META_DRAW_GTK_BOX: + case META_DRAW_GTK_VLINE: + break; + + case META_DRAW_ICON: + if (info->mini_icon && + width <= gdk_pixbuf_get_width (info->mini_icon) && + height <= gdk_pixbuf_get_height (info->mini_icon)) + pixbuf = scale_and_alpha_pixbuf (info->mini_icon, + op->data.icon.alpha_spec, + op->data.icon.fill_type, + width, height, + FALSE, FALSE); + else if (info->icon) + pixbuf = scale_and_alpha_pixbuf (info->icon, + op->data.icon.alpha_spec, + op->data.icon.fill_type, + width, height, + FALSE, FALSE); + break; + + case META_DRAW_TITLE: + break; + + case META_DRAW_OP_LIST: + break; + + case META_DRAW_TILE: + break; + } + + return pixbuf; +} + +static void +fill_env (MetaPositionExprEnv *env, + const MetaDrawInfo *info, + MetaRectangle logical_region) +{ + /* FIXME this stuff could be raised into draw_op_list_draw() probably + */ + env->rect = logical_region; + env->object_width = -1; + env->object_height = -1; + if (info->fgeom) + { + env->left_width = info->fgeom->left_width; + env->right_width = info->fgeom->right_width; + env->top_height = info->fgeom->top_height; + env->bottom_height = info->fgeom->bottom_height; + } + else + { + env->left_width = 0; + env->right_width = 0; + env->top_height = 0; + env->bottom_height = 0; + } + + env->mini_icon_width = info->mini_icon ? gdk_pixbuf_get_width (info->mini_icon) : 0; + env->mini_icon_height = info->mini_icon ? gdk_pixbuf_get_height (info->mini_icon) : 0; + env->icon_width = info->icon ? gdk_pixbuf_get_width (info->icon) : 0; + env->icon_height = info->icon ? gdk_pixbuf_get_height (info->icon) : 0; + + env->title_width = info->title_layout_width; + env->title_height = info->title_layout_height; + env->theme = meta_current_theme; +} + +/* This code was originally rendering anti-aliased using X primitives, and + * now has been switched to draw anti-aliased using cairo. In general, the + * closest correspondence between X rendering and cairo rendering is given + * by offsetting the geometry by 0.5 pixels in both directions before rendering + * with cairo. This is because X samples at the upper left corner of the + * pixel while cairo averages over the entire pixel. However, in the cases + * where the X rendering was an exact rectangle with no "jaggies" + * we need to be a bit careful about applying the offset. We want to produce + * the exact same pixel-aligned rectangle, rather than a rectangle with + * fuzz around the edges. + */ +static void +meta_draw_op_draw_with_env (const MetaDrawOp *op, + GtkStyle *style_gtk, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + const MetaDrawInfo *info, + MetaRectangle rect, + MetaPositionExprEnv *env) +{ + GdkColor color; + cairo_t *cr; + + cr = gdk_cairo_create (drawable); + + cairo_set_line_width (cr, 1.0); + + if (clip) + { + gdk_cairo_rectangle (cr, clip); + cairo_clip (cr); + } + + switch (op->type) + { + case META_DRAW_LINE: + { + int x1, x2, y1, y2; + + meta_color_spec_render (op->data.line.color_spec, widget, &color); + gdk_cairo_set_source_color (cr, &color); + + if (op->data.line.width > 0) + cairo_set_line_width (cr, op->data.line.width); + + if (op->data.line.dash_on_length > 0 && + op->data.line.dash_off_length > 0) + { + double dash_list[2]; + dash_list[0] = op->data.line.dash_on_length; + dash_list[1] = op->data.line.dash_off_length; + cairo_set_dash (cr, dash_list, 2, 0); + } + + x1 = parse_x_position_unchecked (op->data.line.x1, env); + y1 = parse_y_position_unchecked (op->data.line.y1, env); + + if (!op->data.line.x2 && + !op->data.line.y2 && + op->data.line.width==0) + { + cairo_rectangle (cr, x1, y1, 1, 1); + cairo_fill (cr); + } + else + { + if (op->data.line.x2) + x2 = parse_x_position_unchecked (op->data.line.x2, env); + else + x2 = x1; + + if (op->data.line.y2) + y2 = parse_y_position_unchecked (op->data.line.y2, env); + else + y2 = y1; + + /* This is one of the cases where we are matching the exact + * pixel aligned rectangle produced by X. + */ + if (y1 == y2 || x1 == x2) + { + double offset = (op->data.line.width == 0 || + op->data.line.width % 2) ? .5 : 0; + /* X includes end points for lines of width 0 */ + double line_extend = op->data.line.width == 0 ? 1. : 0.; + + if (y1 == y2) + { + if (x2 < x1) + { + x1 ^= x2; + x2 ^= x1; + x1 ^= x2; + } + cairo_move_to (cr, x1, y1 + offset); + cairo_line_to (cr, x2 + line_extend, y2 + offset); + } + else + { + if (y2 < y1) + { + y1 ^= y2; + y2 ^= y1; + y1 ^= y2; + } + cairo_move_to (cr, x1 + offset, y1); + cairo_line_to (cr, x2 + offset, y2 + line_extend); + } + } + else + { + if (op->data.line.width <= 0) + { + cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE); + } + cairo_move_to (cr, x1 + .5, y1 + .5); + cairo_line_to (cr, x2 + .5, y2 + .5); + } + cairo_stroke (cr); + } + } + break; + + case META_DRAW_RECTANGLE: + { + int rx, ry, rwidth, rheight; + + meta_color_spec_render (op->data.rectangle.color_spec, widget, &color); + gdk_cairo_set_source_color (cr, &color); + + rx = parse_x_position_unchecked (op->data.rectangle.x, env); + ry = parse_y_position_unchecked (op->data.rectangle.y, env); + rwidth = parse_size_unchecked (op->data.rectangle.width, env); + rheight = parse_size_unchecked (op->data.rectangle.height, env); + + /* Filled and stroked rectangles are the other cases + * we pixel-align to X rasterization + */ + if (op->data.rectangle.filled) + { + cairo_rectangle (cr, rx, ry, rwidth, rheight); + cairo_fill (cr); + } + else + { + cairo_rectangle (cr, rx + .5, ry + .5, rwidth, rheight); + cairo_stroke (cr); + } + } + break; + + case META_DRAW_ARC: + { + int rx, ry, rwidth, rheight; + double start_angle, end_angle; + double center_x, center_y; + + meta_color_spec_render (op->data.arc.color_spec, widget, &color); + gdk_cairo_set_source_color (cr, &color); + + rx = parse_x_position_unchecked (op->data.arc.x, env); + ry = parse_y_position_unchecked (op->data.arc.y, env); + rwidth = parse_size_unchecked (op->data.arc.width, env); + rheight = parse_size_unchecked (op->data.arc.height, env); + + start_angle = op->data.arc.start_angle * (M_PI / 180.) + - (.25 * M_PI); /* start at 12 instead of 3 oclock */ + end_angle = start_angle + op->data.arc.extent_angle * (M_PI / 180.); + center_x = rx + (double)rwidth / 2. + .5; + center_y = ry + (double)rheight / 2. + .5; + + cairo_save (cr); + + cairo_translate (cr, center_x, center_y); + cairo_scale (cr, (double)rwidth / 2., (double)rheight / 2.); + + if (op->data.arc.extent_angle >= 0) + cairo_arc (cr, 0, 0, 1, start_angle, end_angle); + else + cairo_arc_negative (cr, 0, 0, 1, start_angle, end_angle); + + cairo_restore (cr); + + if (op->data.arc.filled) + { + cairo_line_to (cr, center_x, center_y); + cairo_fill (cr); + } + else + cairo_stroke (cr); + } + break; + + case META_DRAW_CLIP: + break; + + case META_DRAW_TINT: + { + int rx, ry, rwidth, rheight; + gboolean needs_alpha; + + needs_alpha = op->data.tint.alpha_spec && + (op->data.tint.alpha_spec->n_alphas > 1 || + op->data.tint.alpha_spec->alphas[0] != 0xff); + + rx = parse_x_position_unchecked (op->data.tint.x, env); + ry = parse_y_position_unchecked (op->data.tint.y, env); + rwidth = parse_size_unchecked (op->data.tint.width, env); + rheight = parse_size_unchecked (op->data.tint.height, env); + + if (!needs_alpha) + { + meta_color_spec_render (op->data.tint.color_spec, widget, &color); + gdk_cairo_set_source_color (cr, &color); + + cairo_rectangle (cr, rx, ry, rwidth, rheight); + cairo_fill (cr); + } + else + { + GdkPixbuf *pixbuf; + + pixbuf = draw_op_as_pixbuf (op, widget, info, + rwidth, rheight); + + if (pixbuf) + { + gdk_cairo_set_source_pixbuf (cr, pixbuf, rx, ry); + cairo_paint (cr); + + g_object_unref (G_OBJECT (pixbuf)); + } + } + } + break; + + case META_DRAW_GRADIENT: + { + int rx, ry, rwidth, rheight; + GdkPixbuf *pixbuf; + + rx = parse_x_position_unchecked (op->data.gradient.x, env); + ry = parse_y_position_unchecked (op->data.gradient.y, env); + rwidth = parse_size_unchecked (op->data.gradient.width, env); + rheight = parse_size_unchecked (op->data.gradient.height, env); + + pixbuf = draw_op_as_pixbuf (op, widget, info, + rwidth, rheight); + + if (pixbuf) + { + gdk_cairo_set_source_pixbuf (cr, pixbuf, rx, ry); + cairo_paint (cr); + + g_object_unref (G_OBJECT (pixbuf)); + } + } + break; + + case META_DRAW_IMAGE: + { + int rx, ry, rwidth, rheight; + GdkPixbuf *pixbuf; + + if (op->data.image.pixbuf) + { + env->object_width = gdk_pixbuf_get_width (op->data.image.pixbuf); + env->object_height = gdk_pixbuf_get_height (op->data.image.pixbuf); + } + + rwidth = parse_size_unchecked (op->data.image.width, env); + rheight = parse_size_unchecked (op->data.image.height, env); + + pixbuf = draw_op_as_pixbuf (op, widget, info, + rwidth, rheight); + + if (pixbuf) + { + rx = parse_x_position_unchecked (op->data.image.x, env); + ry = parse_y_position_unchecked (op->data.image.y, env); + + gdk_cairo_set_source_pixbuf (cr, pixbuf, rx, ry); + cairo_paint (cr); + + g_object_unref (G_OBJECT (pixbuf)); + } + } + break; + + case META_DRAW_GTK_ARROW: + { + int rx, ry, rwidth, rheight; + + rx = parse_x_position_unchecked (op->data.gtk_arrow.x, env); + ry = parse_y_position_unchecked (op->data.gtk_arrow.y, env); + rwidth = parse_size_unchecked (op->data.gtk_arrow.width, env); + rheight = parse_size_unchecked (op->data.gtk_arrow.height, env); + + gtk_paint_arrow (style_gtk, + drawable, + op->data.gtk_arrow.state, + op->data.gtk_arrow.shadow, + (GdkRectangle*) clip, + widget, + "marco", + op->data.gtk_arrow.arrow, + op->data.gtk_arrow.filled, + rx, ry, rwidth, rheight); + } + break; + + case META_DRAW_GTK_BOX: + { + int rx, ry, rwidth, rheight; + + rx = parse_x_position_unchecked (op->data.gtk_box.x, env); + ry = parse_y_position_unchecked (op->data.gtk_box.y, env); + rwidth = parse_size_unchecked (op->data.gtk_box.width, env); + rheight = parse_size_unchecked (op->data.gtk_box.height, env); + + gtk_paint_box (style_gtk, + drawable, + op->data.gtk_box.state, + op->data.gtk_box.shadow, + (GdkRectangle*) clip, + widget, + "marco", + rx, ry, rwidth, rheight); + } + break; + + case META_DRAW_GTK_VLINE: + { + int rx, ry1, ry2; + + rx = parse_x_position_unchecked (op->data.gtk_vline.x, env); + ry1 = parse_y_position_unchecked (op->data.gtk_vline.y1, env); + ry2 = parse_y_position_unchecked (op->data.gtk_vline.y2, env); + + gtk_paint_vline (style_gtk, + drawable, + op->data.gtk_vline.state, + (GdkRectangle*) clip, + widget, + "marco", + ry1, ry2, rx); + } + break; + + case META_DRAW_ICON: + { + int rx, ry, rwidth, rheight; + GdkPixbuf *pixbuf; + + rwidth = parse_size_unchecked (op->data.icon.width, env); + rheight = parse_size_unchecked (op->data.icon.height, env); + + pixbuf = draw_op_as_pixbuf (op, widget, info, + rwidth, rheight); + + if (pixbuf) + { + rx = parse_x_position_unchecked (op->data.icon.x, env); + ry = parse_y_position_unchecked (op->data.icon.y, env); + + gdk_cairo_set_source_pixbuf (cr, pixbuf, rx, ry); + cairo_paint (cr); + + g_object_unref (G_OBJECT (pixbuf)); + } + } + break; + + case META_DRAW_TITLE: + if (info->title_layout) + { + int rx, ry; + + meta_color_spec_render (op->data.title.color_spec, widget, &color); + gdk_cairo_set_source_color (cr, &color); + + rx = parse_x_position_unchecked (op->data.title.x, env); + ry = parse_y_position_unchecked (op->data.title.y, env); + + cairo_move_to (cr, rx, ry); + pango_cairo_show_layout (cr, info->title_layout); + } + break; + + case META_DRAW_OP_LIST: + { + MetaRectangle d_rect; + + d_rect.x = parse_x_position_unchecked (op->data.op_list.x, env); + d_rect.y = parse_y_position_unchecked (op->data.op_list.y, env); + d_rect.width = parse_size_unchecked (op->data.op_list.width, env); + d_rect.height = parse_size_unchecked (op->data.op_list.height, env); + + meta_draw_op_list_draw_with_style (op->data.op_list.op_list, + style_gtk, widget, drawable, clip, info, + d_rect); + } + break; + + case META_DRAW_TILE: + { + int rx, ry, rwidth, rheight; + int tile_xoffset, tile_yoffset; + GdkRectangle new_clip; + MetaRectangle tile; + + rx = parse_x_position_unchecked (op->data.tile.x, env); + ry = parse_y_position_unchecked (op->data.tile.y, env); + rwidth = parse_size_unchecked (op->data.tile.width, env); + rheight = parse_size_unchecked (op->data.tile.height, env); + + new_clip.x = rx; + new_clip.y = ry; + new_clip.width = rwidth; + new_clip.height = rheight; + + if (clip == NULL || gdk_rectangle_intersect ((GdkRectangle*)clip, &new_clip, + &new_clip)) + { + tile_xoffset = parse_x_position_unchecked (op->data.tile.tile_xoffset, env); + tile_yoffset = parse_y_position_unchecked (op->data.tile.tile_yoffset, env); + /* tile offset should not include x/y */ + tile_xoffset -= rect.x; + tile_yoffset -= rect.y; + + tile.width = parse_size_unchecked (op->data.tile.tile_width, env); + tile.height = parse_size_unchecked (op->data.tile.tile_height, env); + + tile.x = rx - tile_xoffset; + + while (tile.x < (rx + rwidth)) + { + tile.y = ry - tile_yoffset; + while (tile.y < (ry + rheight)) + { + meta_draw_op_list_draw_with_style (op->data.tile.op_list, + style_gtk, widget, drawable, &new_clip, info, + tile); + + tile.y += tile.height; + } + + tile.x += tile.width; + } + } + } + break; + } + + cairo_destroy (cr); +} + +void +meta_draw_op_draw_with_style (const MetaDrawOp *op, + GtkStyle *style_gtk, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + const MetaDrawInfo *info, + MetaRectangle logical_region) +{ + MetaPositionExprEnv env; + + g_return_if_fail (style_gtk->colormap == gdk_drawable_get_colormap (drawable)); + + fill_env (&env, info, logical_region); + + meta_draw_op_draw_with_env (op, style_gtk, widget, drawable, clip, + info, logical_region, + &env); + +} + +void +meta_draw_op_draw (const MetaDrawOp *op, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + const MetaDrawInfo *info, + MetaRectangle logical_region) +{ + meta_draw_op_draw_with_style (op, gtk_widget_get_style (widget), widget, + drawable, clip, info, logical_region); +} + +MetaDrawOpList* +meta_draw_op_list_new (int n_preallocs) +{ + MetaDrawOpList *op_list; + + g_return_val_if_fail (n_preallocs >= 0, NULL); + + op_list = g_new (MetaDrawOpList, 1); + + op_list->refcount = 1; + op_list->n_allocated = n_preallocs; + op_list->ops = g_new (MetaDrawOp*, op_list->n_allocated); + op_list->n_ops = 0; + + return op_list; +} + +void +meta_draw_op_list_ref (MetaDrawOpList *op_list) +{ + g_return_if_fail (op_list != NULL); + + op_list->refcount += 1; +} + +void +meta_draw_op_list_unref (MetaDrawOpList *op_list) +{ + g_return_if_fail (op_list != NULL); + g_return_if_fail (op_list->refcount > 0); + + op_list->refcount -= 1; + + if (op_list->refcount == 0) + { + int i; + + for (i = 0; i < op_list->n_ops; i++) + meta_draw_op_free (op_list->ops[i]); + + g_free (op_list->ops); + + DEBUG_FILL_STRUCT (op_list); + g_free (op_list); + } +} + +void +meta_draw_op_list_draw_with_style (const MetaDrawOpList *op_list, + GtkStyle *style_gtk, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + const MetaDrawInfo *info, + MetaRectangle rect) +{ + int i; + GdkRectangle active_clip; + GdkRectangle orig_clip; + MetaPositionExprEnv env; + + g_return_if_fail (style_gtk->colormap == gdk_drawable_get_colormap (drawable)); + + if (op_list->n_ops == 0) + return; + + fill_env (&env, info, rect); + + /* FIXME this can be optimized, potentially a lot, by + * compressing multiple ops when possible. For example, + * anything convertible to a pixbuf can be composited + * client-side, and putting a color tint over a pixbuf + * can be done without creating the solid-color pixbuf. + * + * To implement this my plan is to have the idea of a + * compiled draw op (with the string expressions already + * evaluated), we make an array of those, and then fold + * adjacent items when possible. + */ + if (clip) + { + orig_clip = *clip; + } + else + { + orig_clip.x = rect.x; + orig_clip.y = rect.y; + orig_clip.width = rect.width; + orig_clip.height = rect.height; + } + + active_clip = orig_clip; + + for (i = 0; i < op_list->n_ops; i++) + { + MetaDrawOp *op = op_list->ops[i]; + + if (op->type == META_DRAW_CLIP) + { + active_clip.x = parse_x_position_unchecked (op->data.clip.x, &env); + active_clip.y = parse_y_position_unchecked (op->data.clip.y, &env); + active_clip.width = parse_size_unchecked (op->data.clip.width, &env); + active_clip.height = parse_size_unchecked (op->data.clip.height, &env); + + gdk_rectangle_intersect (&orig_clip, &active_clip, &active_clip); + } + else if (active_clip.width > 0 && + active_clip.height > 0) + { + meta_draw_op_draw_with_env (op, + style_gtk, widget, drawable, &active_clip, info, + rect, + &env); + } + } +} + +void +meta_draw_op_list_draw (const MetaDrawOpList *op_list, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + const MetaDrawInfo *info, + MetaRectangle rect) + +{ + meta_draw_op_list_draw_with_style (op_list, gtk_widget_get_style (widget), widget, + drawable, clip, info, rect); +} + +void +meta_draw_op_list_append (MetaDrawOpList *op_list, + MetaDrawOp *op) +{ + if (op_list->n_ops == op_list->n_allocated) + { + op_list->n_allocated *= 2; + op_list->ops = g_renew (MetaDrawOp*, op_list->ops, op_list->n_allocated); + } + + op_list->ops[op_list->n_ops] = op; + op_list->n_ops += 1; +} + +gboolean +meta_draw_op_list_validate (MetaDrawOpList *op_list, + GError **error) +{ + g_return_val_if_fail (op_list != NULL, FALSE); + + /* empty lists are OK, nothing else to check really */ + + return TRUE; +} + +/* This is not done in validate, since we wouldn't know the name + * of the list to report the error. It might be nice to + * store names inside the list sometime. + */ +gboolean +meta_draw_op_list_contains (MetaDrawOpList *op_list, + MetaDrawOpList *child) +{ + int i; + + /* mmm, huge tree recursion */ + + for (i = 0; i < op_list->n_ops; i++) + { + if (op_list->ops[i]->type == META_DRAW_OP_LIST) + { + if (op_list->ops[i]->data.op_list.op_list == child) + return TRUE; + + if (meta_draw_op_list_contains (op_list->ops[i]->data.op_list.op_list, + child)) + return TRUE; + } + else if (op_list->ops[i]->type == META_DRAW_TILE) + { + if (op_list->ops[i]->data.tile.op_list == child) + return TRUE; + + if (meta_draw_op_list_contains (op_list->ops[i]->data.tile.op_list, + child)) + return TRUE; + } + } + + return FALSE; +} + +/** + * Constructor for a MetaFrameStyle. + * + * \param parent The parent style. Data not filled in here will be + * looked for in the parent style, and in its parent + * style, and so on. + * + * \return The newly-constructed style. + */ +MetaFrameStyle* +meta_frame_style_new (MetaFrameStyle *parent) +{ + MetaFrameStyle *style; + + style = g_new0 (MetaFrameStyle, 1); + + style->refcount = 1; + + /* Default alpha is fully opaque */ + style->window_background_alpha = 255; + + style->parent = parent; + if (parent) + meta_frame_style_ref (parent); + + return style; +} + +/** + * Increases the reference count of a frame style. + * If the style is NULL, this is a no-op. + * + * \param style The style. + */ +void +meta_frame_style_ref (MetaFrameStyle *style) +{ + g_return_if_fail (style != NULL); + + style->refcount += 1; +} + +static void +free_button_ops (MetaDrawOpList *op_lists[META_BUTTON_TYPE_LAST][META_BUTTON_STATE_LAST]) +{ + int i, j; + + for (i = 0; i < META_BUTTON_TYPE_LAST; i++) + for (j = 0; j < META_BUTTON_STATE_LAST; j++) + if (op_lists[i][j]) + meta_draw_op_list_unref (op_lists[i][j]); +} + +void +meta_frame_style_unref (MetaFrameStyle *style) +{ + g_return_if_fail (style != NULL); + g_return_if_fail (style->refcount > 0); + + style->refcount -= 1; + + if (style->refcount == 0) + { + int i; + + free_button_ops (style->buttons); + + for (i = 0; i < META_FRAME_PIECE_LAST; i++) + if (style->pieces[i]) + meta_draw_op_list_unref (style->pieces[i]); + + if (style->layout) + meta_frame_layout_unref (style->layout); + + if (style->window_background_color) + meta_color_spec_free (style->window_background_color); + + /* we hold a reference to any parent style */ + if (style->parent) + meta_frame_style_unref (style->parent); + + DEBUG_FILL_STRUCT (style); + g_free (style); + } +} + +static MetaDrawOpList* +get_button (MetaFrameStyle *style, + MetaButtonType type, + MetaButtonState state) +{ + MetaDrawOpList *op_list; + MetaFrameStyle *parent; + + parent = style; + op_list = NULL; + while (parent && op_list == NULL) + { + op_list = parent->buttons[type][state]; + parent = parent->parent; + } + + /* We fall back to middle button backgrounds if we don't + * have the ones on the sides + */ + + if (op_list == NULL && + (type == META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND || + type == META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND)) + return get_button (style, META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND, + state); + + if (op_list == NULL && + (type == META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND || + type == META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND)) + return get_button (style, META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND, + state); + + /* We fall back to normal if no prelight */ + if (op_list == NULL && + state == META_BUTTON_STATE_PRELIGHT) + return get_button (style, type, META_BUTTON_STATE_NORMAL); + + return op_list; +} + +gboolean +meta_frame_style_validate (MetaFrameStyle *style, + guint current_theme_version, + GError **error) +{ + int i, j; + + g_return_val_if_fail (style != NULL, FALSE); + g_return_val_if_fail (style->layout != NULL, FALSE); + + for (i = 0; i < META_BUTTON_TYPE_LAST; i++) + { + /* for now the "positional" buttons are optional */ + if (i >= META_BUTTON_TYPE_CLOSE) + { + for (j = 0; j < META_BUTTON_STATE_LAST; j++) + { + if (get_button (style, i, j) == NULL && + meta_theme_earliest_version_with_button (i) <= current_theme_version + ) + { + g_set_error (error, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("<button function=\"%s\" state=\"%s\" draw_ops=\"whatever\"/> must be specified for this frame style"), + meta_button_type_to_string (i), + meta_button_state_to_string (j)); + return FALSE; + } + } + } + } + + return TRUE; +} + +static void +button_rect (MetaButtonType type, + const MetaFrameGeometry *fgeom, + int middle_background_offset, + GdkRectangle *rect) +{ + switch (type) + { + case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND: + *rect = fgeom->left_left_background; + break; + + case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND: + *rect = fgeom->left_middle_backgrounds[middle_background_offset]; + break; + + case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND: + *rect = fgeom->left_right_background; + break; + + case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND: + *rect = fgeom->right_left_background; + break; + + case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND: + *rect = fgeom->right_middle_backgrounds[middle_background_offset]; + break; + + case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND: + *rect = fgeom->right_right_background; + break; + + case META_BUTTON_TYPE_CLOSE: + *rect = fgeom->close_rect.visible; + break; + + case META_BUTTON_TYPE_SHADE: + *rect = fgeom->shade_rect.visible; + break; + + case META_BUTTON_TYPE_UNSHADE: + *rect = fgeom->unshade_rect.visible; + break; + + case META_BUTTON_TYPE_ABOVE: + *rect = fgeom->above_rect.visible; + break; + + case META_BUTTON_TYPE_UNABOVE: + *rect = fgeom->unabove_rect.visible; + break; + + case META_BUTTON_TYPE_STICK: + *rect = fgeom->stick_rect.visible; + break; + + case META_BUTTON_TYPE_UNSTICK: + *rect = fgeom->unstick_rect.visible; + break; + + case META_BUTTON_TYPE_MAXIMIZE: + *rect = fgeom->max_rect.visible; + break; + + case META_BUTTON_TYPE_MINIMIZE: + *rect = fgeom->min_rect.visible; + break; + + case META_BUTTON_TYPE_MENU: + *rect = fgeom->menu_rect.visible; + break; + + case META_BUTTON_TYPE_LAST: + g_assert_not_reached (); + break; + } +} + +void +meta_frame_style_draw_with_style (MetaFrameStyle *style, + GtkStyle *style_gtk, + GtkWidget *widget, + GdkDrawable *drawable, + int x_offset, + int y_offset, + const GdkRectangle *clip, + const MetaFrameGeometry *fgeom, + int client_width, + int client_height, + PangoLayout *title_layout, + int text_height, + MetaButtonState button_states[META_BUTTON_TYPE_LAST], + GdkPixbuf *mini_icon, + GdkPixbuf *icon) +{ + int i, j; + GdkRectangle titlebar_rect; + GdkRectangle left_titlebar_edge; + GdkRectangle right_titlebar_edge; + GdkRectangle bottom_titlebar_edge; + GdkRectangle top_titlebar_edge; + GdkRectangle left_edge, right_edge, bottom_edge; + PangoRectangle extents; + MetaDrawInfo draw_info; + + g_return_if_fail (style_gtk->colormap == gdk_drawable_get_colormap (drawable)); + + titlebar_rect.x = 0; + titlebar_rect.y = 0; + titlebar_rect.width = fgeom->width; + titlebar_rect.height = fgeom->top_height; + + left_titlebar_edge.x = titlebar_rect.x; + left_titlebar_edge.y = titlebar_rect.y + fgeom->top_titlebar_edge; + left_titlebar_edge.width = fgeom->left_titlebar_edge; + left_titlebar_edge.height = titlebar_rect.height - fgeom->top_titlebar_edge - fgeom->bottom_titlebar_edge; + + right_titlebar_edge.y = left_titlebar_edge.y; + right_titlebar_edge.height = left_titlebar_edge.height; + right_titlebar_edge.width = fgeom->right_titlebar_edge; + right_titlebar_edge.x = titlebar_rect.x + titlebar_rect.width - right_titlebar_edge.width; + + top_titlebar_edge.x = titlebar_rect.x; + top_titlebar_edge.y = titlebar_rect.y; + top_titlebar_edge.width = titlebar_rect.width; + top_titlebar_edge.height = fgeom->top_titlebar_edge; + + bottom_titlebar_edge.x = titlebar_rect.x; + bottom_titlebar_edge.width = titlebar_rect.width; + bottom_titlebar_edge.height = fgeom->bottom_titlebar_edge; + bottom_titlebar_edge.y = titlebar_rect.y + titlebar_rect.height - bottom_titlebar_edge.height; + + left_edge.x = 0; + left_edge.y = fgeom->top_height; + left_edge.width = fgeom->left_width; + left_edge.height = fgeom->height - fgeom->top_height - fgeom->bottom_height; + + right_edge.x = fgeom->width - fgeom->right_width; + right_edge.y = fgeom->top_height; + right_edge.width = fgeom->right_width; + right_edge.height = fgeom->height - fgeom->top_height - fgeom->bottom_height; + + bottom_edge.x = 0; + bottom_edge.y = fgeom->height - fgeom->bottom_height; + bottom_edge.width = fgeom->width; + bottom_edge.height = fgeom->bottom_height; + + if (title_layout) + pango_layout_get_pixel_extents (title_layout, + NULL, &extents); + + draw_info.mini_icon = mini_icon; + draw_info.icon = icon; + draw_info.title_layout = title_layout; + draw_info.title_layout_width = title_layout ? extents.width : 0; + draw_info.title_layout_height = title_layout ? extents.height : 0; + draw_info.fgeom = fgeom; + + /* The enum is in the order the pieces should be rendered. */ + i = 0; + while (i < META_FRAME_PIECE_LAST) + { + GdkRectangle rect; + GdkRectangle combined_clip; + + switch ((MetaFramePiece) i) + { + case META_FRAME_PIECE_ENTIRE_BACKGROUND: + rect.x = 0; + rect.y = 0; + rect.width = fgeom->width; + rect.height = fgeom->height; + break; + + case META_FRAME_PIECE_TITLEBAR: + rect = titlebar_rect; + break; + + case META_FRAME_PIECE_LEFT_TITLEBAR_EDGE: + rect = left_titlebar_edge; + break; + + case META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE: + rect = right_titlebar_edge; + break; + + case META_FRAME_PIECE_TOP_TITLEBAR_EDGE: + rect = top_titlebar_edge; + break; + + case META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE: + rect = bottom_titlebar_edge; + break; + + case META_FRAME_PIECE_TITLEBAR_MIDDLE: + rect.x = left_titlebar_edge.x + left_titlebar_edge.width; + rect.y = top_titlebar_edge.y + top_titlebar_edge.height; + rect.width = titlebar_rect.width - left_titlebar_edge.width - + right_titlebar_edge.width; + rect.height = titlebar_rect.height - top_titlebar_edge.height - bottom_titlebar_edge.height; + break; + + case META_FRAME_PIECE_TITLE: + rect = fgeom->title_rect; + break; + + case META_FRAME_PIECE_LEFT_EDGE: + rect = left_edge; + break; + + case META_FRAME_PIECE_RIGHT_EDGE: + rect = right_edge; + break; + + case META_FRAME_PIECE_BOTTOM_EDGE: + rect = bottom_edge; + break; + + case META_FRAME_PIECE_OVERLAY: + rect.x = 0; + rect.y = 0; + rect.width = fgeom->width; + rect.height = fgeom->height; + break; + + case META_FRAME_PIECE_LAST: + g_assert_not_reached (); + break; + } + + rect.x += x_offset; + rect.y += y_offset; + + if (clip == NULL) + combined_clip = rect; + else + gdk_rectangle_intersect ((GdkRectangle*) clip, /* const cast */ + &rect, + &combined_clip); + + if (combined_clip.width > 0 && combined_clip.height > 0) + { + MetaDrawOpList *op_list; + MetaFrameStyle *parent; + + parent = style; + op_list = NULL; + while (parent && op_list == NULL) + { + op_list = parent->pieces[i]; + parent = parent->parent; + } + + if (op_list) + { + MetaRectangle m_rect; + m_rect = meta_rect (rect.x, rect.y, rect.width, rect.height); + meta_draw_op_list_draw_with_style (op_list, + style_gtk, + widget, + drawable, + &combined_clip, + &draw_info, + m_rect); + } + } + + + /* Draw buttons just before overlay */ + if ((i + 1) == META_FRAME_PIECE_OVERLAY) + { + int middle_bg_offset; + + middle_bg_offset = 0; + j = 0; + while (j < META_BUTTON_TYPE_LAST) + { + button_rect (j, fgeom, middle_bg_offset, &rect); + + rect.x += x_offset; + rect.y += y_offset; + + if (clip == NULL) + combined_clip = rect; + else + gdk_rectangle_intersect ((GdkRectangle*) clip, /* const cast */ + &rect, + &combined_clip); + + if (combined_clip.width > 0 && combined_clip.height > 0) + { + MetaDrawOpList *op_list; + + op_list = get_button (style, j, button_states[j]); + + if (op_list) + { + MetaRectangle m_rect; + m_rect = meta_rect (rect.x, rect.y, + rect.width, rect.height); + meta_draw_op_list_draw_with_style (op_list, + style_gtk, + widget, + drawable, + &combined_clip, + &draw_info, + m_rect); + } + } + + /* MIDDLE_BACKGROUND type may get drawn more than once */ + if ((j == META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND || + j == META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND) && + middle_bg_offset < MAX_MIDDLE_BACKGROUNDS) + { + ++middle_bg_offset; + } + else + { + middle_bg_offset = 0; + ++j; + } + } + } + + ++i; + } +} + +void +meta_frame_style_draw (MetaFrameStyle *style, + GtkWidget *widget, + GdkDrawable *drawable, + int x_offset, + int y_offset, + const GdkRectangle *clip, + const MetaFrameGeometry *fgeom, + int client_width, + int client_height, + PangoLayout *title_layout, + int text_height, + MetaButtonState button_states[META_BUTTON_TYPE_LAST], + GdkPixbuf *mini_icon, + GdkPixbuf *icon) +{ + meta_frame_style_draw_with_style (style, gtk_widget_get_style (widget), widget, + drawable, x_offset, y_offset, + clip, fgeom, client_width, client_height, + title_layout, text_height, + button_states, mini_icon, icon); +} + +MetaFrameStyleSet* +meta_frame_style_set_new (MetaFrameStyleSet *parent) +{ + MetaFrameStyleSet *style_set; + + style_set = g_new0 (MetaFrameStyleSet, 1); + + style_set->parent = parent; + if (parent) + meta_frame_style_set_ref (parent); + + style_set->refcount = 1; + + return style_set; +} + +static void +free_focus_styles (MetaFrameStyle *focus_styles[META_FRAME_FOCUS_LAST]) +{ + int i; + + for (i = 0; i < META_FRAME_FOCUS_LAST; i++) + if (focus_styles[i]) + meta_frame_style_unref (focus_styles[i]); +} + +void +meta_frame_style_set_ref (MetaFrameStyleSet *style_set) +{ + g_return_if_fail (style_set != NULL); + + style_set->refcount += 1; +} + +void +meta_frame_style_set_unref (MetaFrameStyleSet *style_set) +{ + g_return_if_fail (style_set != NULL); + g_return_if_fail (style_set->refcount > 0); + + style_set->refcount -= 1; + + if (style_set->refcount == 0) + { + int i; + + for (i = 0; i < META_FRAME_RESIZE_LAST; i++) + { + free_focus_styles (style_set->normal_styles[i]); + free_focus_styles (style_set->shaded_styles[i]); + } + + free_focus_styles (style_set->maximized_styles); + free_focus_styles (style_set->maximized_and_shaded_styles); + + if (style_set->parent) + meta_frame_style_set_unref (style_set->parent); + + DEBUG_FILL_STRUCT (style_set); + g_free (style_set); + } +} + + +static MetaFrameStyle* +get_style (MetaFrameStyleSet *style_set, + MetaFrameState state, + MetaFrameResize resize, + MetaFrameFocus focus) +{ + MetaFrameStyle *style; + + style = NULL; + + switch (state) + { + case META_FRAME_STATE_NORMAL: + case META_FRAME_STATE_SHADED: + { + if (state == META_FRAME_STATE_SHADED) + style = style_set->shaded_styles[resize][focus]; + else + style = style_set->normal_styles[resize][focus]; + + /* Try parent if we failed here */ + if (style == NULL && style_set->parent) + style = get_style (style_set->parent, state, resize, focus); + + /* Allow people to omit the vert/horz/none resize modes */ + if (style == NULL && + resize != META_FRAME_RESIZE_BOTH) + style = get_style (style_set, state, META_FRAME_RESIZE_BOTH, focus); + } + break; + default: + { + MetaFrameStyle **styles; + + styles = NULL; + + switch (state) + { + case META_FRAME_STATE_MAXIMIZED: + styles = style_set->maximized_styles; + break; + case META_FRAME_STATE_MAXIMIZED_AND_SHADED: + styles = style_set->maximized_and_shaded_styles; + break; + case META_FRAME_STATE_NORMAL: + case META_FRAME_STATE_SHADED: + case META_FRAME_STATE_LAST: + g_assert_not_reached (); + break; + } + + style = styles[focus]; + + /* Try parent if we failed here */ + if (style == NULL && style_set->parent) + style = get_style (style_set->parent, state, resize, focus); + } + } + + return style; +} + +static gboolean +check_state (MetaFrameStyleSet *style_set, + MetaFrameState state, + GError **error) +{ + int i; + + for (i = 0; i < META_FRAME_FOCUS_LAST; i++) + { + if (get_style (style_set, state, + META_FRAME_RESIZE_NONE, i) == NULL) + { + /* Translators: This error occurs when a <frame> tag is missing + * in theme XML. The "<frame ...>" is intended as a noun phrase, + * and the "missing" qualifies it. You should translate "whatever". + */ + g_set_error (error, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Missing <frame state=\"%s\" resize=\"%s\" focus=\"%s\" style=\"whatever\"/>"), + meta_frame_state_to_string (state), + meta_frame_resize_to_string (META_FRAME_RESIZE_NONE), + meta_frame_focus_to_string (i)); + return FALSE; + } + } + + return TRUE; +} + +gboolean +meta_frame_style_set_validate (MetaFrameStyleSet *style_set, + GError **error) +{ + int i, j; + + g_return_val_if_fail (style_set != NULL, FALSE); + + for (i = 0; i < META_FRAME_RESIZE_LAST; i++) + for (j = 0; j < META_FRAME_FOCUS_LAST; j++) + if (get_style (style_set, META_FRAME_STATE_NORMAL, i, j) == NULL) + { + g_set_error (error, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Missing <frame state=\"%s\" resize=\"%s\" focus=\"%s\" style=\"whatever\"/>"), + meta_frame_state_to_string (META_FRAME_STATE_NORMAL), + meta_frame_resize_to_string (i), + meta_frame_focus_to_string (j)); + return FALSE; + } + + if (!check_state (style_set, META_FRAME_STATE_SHADED, error)) + return FALSE; + + if (!check_state (style_set, META_FRAME_STATE_MAXIMIZED, error)) + return FALSE; + + if (!check_state (style_set, META_FRAME_STATE_MAXIMIZED_AND_SHADED, error)) + return FALSE; + + return TRUE; +} + +MetaTheme* +meta_theme_get_current (void) +{ + return meta_current_theme; +} + +void +meta_theme_set_current (const char *name, + gboolean force_reload) +{ + MetaTheme *new_theme; + GError *err; + + meta_topic (META_DEBUG_THEMES, "Setting current theme to \"%s\"\n", name); + + if (!force_reload && + meta_current_theme && + strcmp (name, meta_current_theme->name) == 0) + return; + + err = NULL; + new_theme = meta_theme_load (name, &err); + + if (new_theme == NULL) + { + meta_warning (_("Failed to load theme \"%s\": %s\n"), + name, err->message); + g_error_free (err); + } + else + { + if (meta_current_theme) + meta_theme_free (meta_current_theme); + + meta_current_theme = new_theme; + + meta_topic (META_DEBUG_THEMES, "New theme is \"%s\"\n", meta_current_theme->name); + } +} + +MetaTheme* +meta_theme_new (void) +{ + MetaTheme *theme; + + theme = g_new0 (MetaTheme, 1); + + theme->images_by_filename = + g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) g_object_unref); + + theme->layouts_by_name = + g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) meta_frame_layout_unref); + + theme->draw_op_lists_by_name = + g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) meta_draw_op_list_unref); + + theme->styles_by_name = + g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) meta_frame_style_unref); + + theme->style_sets_by_name = + g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) meta_frame_style_set_unref); + + /* Create our variable quarks so we can look up variables without + having to strcmp for the names */ + theme->quark_width = g_quark_from_static_string ("width"); + theme->quark_height = g_quark_from_static_string ("height"); + theme->quark_object_width = g_quark_from_static_string ("object_width"); + theme->quark_object_height = g_quark_from_static_string ("object_height"); + theme->quark_left_width = g_quark_from_static_string ("left_width"); + theme->quark_right_width = g_quark_from_static_string ("right_width"); + theme->quark_top_height = g_quark_from_static_string ("top_height"); + theme->quark_bottom_height = g_quark_from_static_string ("bottom_height"); + theme->quark_mini_icon_width = g_quark_from_static_string ("mini_icon_width"); + theme->quark_mini_icon_height = g_quark_from_static_string ("mini_icon_height"); + theme->quark_icon_width = g_quark_from_static_string ("icon_width"); + theme->quark_icon_height = g_quark_from_static_string ("icon_height"); + theme->quark_title_width = g_quark_from_static_string ("title_width"); + theme->quark_title_height = g_quark_from_static_string ("title_height"); + return theme; +} + + +void +meta_theme_free (MetaTheme *theme) +{ + int i; + + g_return_if_fail (theme != NULL); + + g_free (theme->name); + g_free (theme->dirname); + g_free (theme->filename); + g_free (theme->readable_name); + g_free (theme->date); + g_free (theme->description); + g_free (theme->author); + g_free (theme->copyright); + + /* be more careful when destroying the theme hash tables, + since they are only constructed as needed, and may be NULL. */ + if (theme->integer_constants) + g_hash_table_destroy (theme->integer_constants); + if (theme->images_by_filename) + g_hash_table_destroy (theme->images_by_filename); + if (theme->layouts_by_name) + g_hash_table_destroy (theme->layouts_by_name); + if (theme->draw_op_lists_by_name) + g_hash_table_destroy (theme->draw_op_lists_by_name); + if (theme->styles_by_name) + g_hash_table_destroy (theme->styles_by_name); + if (theme->style_sets_by_name) + g_hash_table_destroy (theme->style_sets_by_name); + + for (i = 0; i < META_FRAME_TYPE_LAST; i++) + if (theme->style_sets_by_type[i]) + meta_frame_style_set_unref (theme->style_sets_by_type[i]); + + DEBUG_FILL_STRUCT (theme); + g_free (theme); +} + +gboolean +meta_theme_validate (MetaTheme *theme, + GError **error) +{ + int i; + + g_return_val_if_fail (theme != NULL, FALSE); + + /* FIXME what else should be checked? */ + + g_assert (theme->name); + + if (theme->readable_name == NULL) + { + /* Translators: This error means that a necessary XML tag (whose name + * is given in angle brackets) was not found in a given theme (whose + * name is given second, in quotation marks). + */ + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <%s> set for theme \"%s\""), "name", theme->name); + return FALSE; + } + + if (theme->author == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <%s> set for theme \"%s\""), "author", theme->name); + return FALSE; + } + + if (theme->date == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <%s> set for theme \"%s\""), "date", theme->name); + return FALSE; + } + + if (theme->description == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <%s> set for theme \"%s\""), "description", theme->name); + return FALSE; + } + + if (theme->copyright == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <%s> set for theme \"%s\""), "copyright", theme->name); + return FALSE; + } + + for (i = 0; i < (int)META_FRAME_TYPE_LAST; i++) + if (theme->style_sets_by_type[i] == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No frame style set for window type \"%s\" in theme \"%s\", add a <window type=\"%s\" style_set=\"whatever\"/> element"), + meta_frame_type_to_string (i), + theme->name, + meta_frame_type_to_string (i)); + + return FALSE; + } + + return TRUE; +} + +GdkPixbuf* +meta_theme_load_image (MetaTheme *theme, + const char *filename, + guint size_of_theme_icons, + GError **error) +{ + GdkPixbuf *pixbuf; + + pixbuf = g_hash_table_lookup (theme->images_by_filename, + filename); + + if (pixbuf == NULL) + { + + if (g_str_has_prefix (filename, "theme:") && + META_THEME_ALLOWS (theme, META_THEME_IMAGES_FROM_ICON_THEMES)) + { + pixbuf = gtk_icon_theme_load_icon ( + gtk_icon_theme_get_default (), + filename+6, + size_of_theme_icons, + 0, + error); + if (pixbuf == NULL) return NULL; + } + else + { + char *full_path; + full_path = g_build_filename (theme->dirname, filename, NULL); + + pixbuf = gdk_pixbuf_new_from_file (full_path, error); + if (pixbuf == NULL) + { + g_free (full_path); + return NULL; + } + + g_free (full_path); + } + g_hash_table_replace (theme->images_by_filename, + g_strdup (filename), + pixbuf); + } + + g_assert (pixbuf); + + g_object_ref (G_OBJECT (pixbuf)); + + return pixbuf; +} + +static MetaFrameStyle* +theme_get_style (MetaTheme *theme, + MetaFrameType type, + MetaFrameFlags flags) +{ + MetaFrameState state; + MetaFrameResize resize; + MetaFrameFocus focus; + MetaFrameStyle *style; + MetaFrameStyleSet *style_set; + + style_set = theme->style_sets_by_type[type]; + + /* Right now the parser forces a style set for all types, + * but this fallback code is here in case I take that out. + */ + if (style_set == NULL) + style_set = theme->style_sets_by_type[META_FRAME_TYPE_NORMAL]; + if (style_set == NULL) + return NULL; + + switch (flags & (META_FRAME_MAXIMIZED | META_FRAME_SHADED)) + { + case 0: + state = META_FRAME_STATE_NORMAL; + break; + case META_FRAME_MAXIMIZED: + state = META_FRAME_STATE_MAXIMIZED; + break; + case META_FRAME_SHADED: + state = META_FRAME_STATE_SHADED; + break; + case (META_FRAME_MAXIMIZED | META_FRAME_SHADED): + state = META_FRAME_STATE_MAXIMIZED_AND_SHADED; + break; + default: + g_assert_not_reached (); + state = META_FRAME_STATE_LAST; /* compiler */ + break; + } + + switch (flags & (META_FRAME_ALLOWS_VERTICAL_RESIZE | META_FRAME_ALLOWS_HORIZONTAL_RESIZE)) + { + case 0: + resize = META_FRAME_RESIZE_NONE; + break; + case META_FRAME_ALLOWS_VERTICAL_RESIZE: + resize = META_FRAME_RESIZE_VERTICAL; + break; + case META_FRAME_ALLOWS_HORIZONTAL_RESIZE: + resize = META_FRAME_RESIZE_HORIZONTAL; + break; + case (META_FRAME_ALLOWS_VERTICAL_RESIZE | META_FRAME_ALLOWS_HORIZONTAL_RESIZE): + resize = META_FRAME_RESIZE_BOTH; + break; + default: + g_assert_not_reached (); + resize = META_FRAME_RESIZE_LAST; /* compiler */ + break; + } + + /* re invert the styles used for focus/unfocussed while flashing a frame */ + if (((flags & META_FRAME_HAS_FOCUS) && !(flags & META_FRAME_IS_FLASHING)) + || (!(flags & META_FRAME_HAS_FOCUS) && (flags & META_FRAME_IS_FLASHING))) + focus = META_FRAME_FOCUS_YES; + else + focus = META_FRAME_FOCUS_NO; + + style = get_style (style_set, state, resize, focus); + + return style; +} + +MetaFrameStyle* +meta_theme_get_frame_style (MetaTheme *theme, + MetaFrameType type, + MetaFrameFlags flags) +{ + MetaFrameStyle *style; + + g_return_val_if_fail (type < META_FRAME_TYPE_LAST, NULL); + + style = theme_get_style (theme, type, flags); + + return style; +} + +double +meta_theme_get_title_scale (MetaTheme *theme, + MetaFrameType type, + MetaFrameFlags flags) +{ + MetaFrameStyle *style; + + g_return_val_if_fail (type < META_FRAME_TYPE_LAST, 1.0); + + style = theme_get_style (theme, type, flags); + + /* Parser is not supposed to allow this currently */ + if (style == NULL) + return 1.0; + + return style->layout->title_scale; +} + +void +meta_theme_draw_frame_with_style (MetaTheme *theme, + GtkStyle *style_gtk, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + int x_offset, + int y_offset, + MetaFrameType type, + MetaFrameFlags flags, + int client_width, + int client_height, + PangoLayout *title_layout, + int text_height, + const MetaButtonLayout *button_layout, + MetaButtonState button_states[META_BUTTON_TYPE_LAST], + GdkPixbuf *mini_icon, + GdkPixbuf *icon) +{ + MetaFrameGeometry fgeom; + MetaFrameStyle *style; + + g_return_if_fail (type < META_FRAME_TYPE_LAST); + + style = theme_get_style (theme, type, flags); + + /* Parser is not supposed to allow this currently */ + if (style == NULL) + return; + + meta_frame_layout_calc_geometry (style->layout, + text_height, + flags, + client_width, client_height, + button_layout, + &fgeom, + theme); + + meta_frame_style_draw_with_style (style, + style_gtk, + widget, + drawable, + x_offset, y_offset, + clip, + &fgeom, + client_width, client_height, + title_layout, + text_height, + button_states, + mini_icon, icon); +} + +void +meta_theme_draw_frame (MetaTheme *theme, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + int x_offset, + int y_offset, + MetaFrameType type, + MetaFrameFlags flags, + int client_width, + int client_height, + PangoLayout *title_layout, + int text_height, + const MetaButtonLayout *button_layout, + MetaButtonState button_states[META_BUTTON_TYPE_LAST], + GdkPixbuf *mini_icon, + GdkPixbuf *icon) +{ + meta_theme_draw_frame_with_style (theme, gtk_widget_get_style (widget), widget, + drawable, clip, x_offset, y_offset, type,flags, + client_width, client_height, + title_layout, text_height, + button_layout, button_states, + mini_icon, icon); +} + +void +meta_theme_draw_frame_by_name (MetaTheme *theme, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + int x_offset, + int y_offset, + const gchar *style_name, + MetaFrameFlags flags, + int client_width, + int client_height, + PangoLayout *title_layout, + int text_height, + const MetaButtonLayout *button_layout, + MetaButtonState button_states[META_BUTTON_TYPE_LAST], + GdkPixbuf *mini_icon, + GdkPixbuf *icon) +{ + MetaFrameGeometry fgeom; + MetaFrameStyle *style; + + style = meta_theme_lookup_style (theme, style_name); + + /* Parser is not supposed to allow this currently */ + if (style == NULL) + return; + + meta_frame_layout_calc_geometry (style->layout, + text_height, + flags, + client_width, client_height, + button_layout, + &fgeom, + theme); + + meta_frame_style_draw (style, + widget, + drawable, + x_offset, y_offset, + clip, + &fgeom, + client_width, client_height, + title_layout, + text_height, + button_states, + mini_icon, icon); +} + +void +meta_theme_get_frame_borders (MetaTheme *theme, + MetaFrameType type, + int text_height, + MetaFrameFlags flags, + int *top_height, + int *bottom_height, + int *left_width, + int *right_width) +{ + MetaFrameStyle *style; + + g_return_if_fail (type < META_FRAME_TYPE_LAST); + + if (top_height) + *top_height = 0; + if (bottom_height) + *bottom_height = 0; + if (left_width) + *left_width = 0; + if (right_width) + *right_width = 0; + + style = theme_get_style (theme, type, flags); + + /* Parser is not supposed to allow this currently */ + if (style == NULL) + return; + + meta_frame_layout_get_borders (style->layout, + text_height, + flags, + top_height, bottom_height, + left_width, right_width); +} + +void +meta_theme_calc_geometry (MetaTheme *theme, + MetaFrameType type, + int text_height, + MetaFrameFlags flags, + int client_width, + int client_height, + const MetaButtonLayout *button_layout, + MetaFrameGeometry *fgeom) +{ + MetaFrameStyle *style; + + g_return_if_fail (type < META_FRAME_TYPE_LAST); + + style = theme_get_style (theme, type, flags); + + /* Parser is not supposed to allow this currently */ + if (style == NULL) + return; + + meta_frame_layout_calc_geometry (style->layout, + text_height, + flags, + client_width, client_height, + button_layout, + fgeom, + theme); +} + +MetaFrameLayout* +meta_theme_lookup_layout (MetaTheme *theme, + const char *name) +{ + return g_hash_table_lookup (theme->layouts_by_name, name); +} + +void +meta_theme_insert_layout (MetaTheme *theme, + const char *name, + MetaFrameLayout *layout) +{ + meta_frame_layout_ref (layout); + g_hash_table_replace (theme->layouts_by_name, g_strdup (name), layout); +} + +MetaDrawOpList* +meta_theme_lookup_draw_op_list (MetaTheme *theme, + const char *name) +{ + return g_hash_table_lookup (theme->draw_op_lists_by_name, name); +} + +void +meta_theme_insert_draw_op_list (MetaTheme *theme, + const char *name, + MetaDrawOpList *op_list) +{ + meta_draw_op_list_ref (op_list); + g_hash_table_replace (theme->draw_op_lists_by_name, g_strdup (name), op_list); +} + +MetaFrameStyle* +meta_theme_lookup_style (MetaTheme *theme, + const char *name) +{ + return g_hash_table_lookup (theme->styles_by_name, name); +} + +void +meta_theme_insert_style (MetaTheme *theme, + const char *name, + MetaFrameStyle *style) +{ + meta_frame_style_ref (style); + g_hash_table_replace (theme->styles_by_name, g_strdup (name), style); +} + +MetaFrameStyleSet* +meta_theme_lookup_style_set (MetaTheme *theme, + const char *name) +{ + return g_hash_table_lookup (theme->style_sets_by_name, name); +} + +void +meta_theme_insert_style_set (MetaTheme *theme, + const char *name, + MetaFrameStyleSet *style_set) +{ + meta_frame_style_set_ref (style_set); + g_hash_table_replace (theme->style_sets_by_name, g_strdup (name), style_set); +} + +static gboolean +first_uppercase (const char *str) +{ + return g_ascii_isupper (*str); +} + +gboolean +meta_theme_define_int_constant (MetaTheme *theme, + const char *name, + int value, + GError **error) +{ + if (theme->integer_constants == NULL) + theme->integer_constants = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); + + if (!first_uppercase (name)) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("User-defined constants must begin with a capital letter; \"%s\" does not"), + name); + return FALSE; + } + + if (g_hash_table_lookup_extended (theme->integer_constants, name, NULL, NULL)) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("Constant \"%s\" has already been defined"), + name); + + return FALSE; + } + + g_hash_table_insert (theme->integer_constants, + g_strdup (name), + GINT_TO_POINTER (value)); + + return TRUE; +} + +gboolean +meta_theme_lookup_int_constant (MetaTheme *theme, + const char *name, + int *value) +{ + gpointer old_value; + + *value = 0; + + if (theme->integer_constants == NULL) + return FALSE; + + if (g_hash_table_lookup_extended (theme->integer_constants, + name, NULL, &old_value)) + { + *value = GPOINTER_TO_INT (old_value); + return TRUE; + } + else + { + return FALSE; + } +} + +gboolean +meta_theme_define_float_constant (MetaTheme *theme, + const char *name, + double value, + GError **error) +{ + double *d; + + if (theme->float_constants == NULL) + theme->float_constants = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_free); + + if (!first_uppercase (name)) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("User-defined constants must begin with a capital letter; \"%s\" does not"), + name); + return FALSE; + } + + if (g_hash_table_lookup_extended (theme->float_constants, name, NULL, NULL)) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("Constant \"%s\" has already been defined"), + name); + + return FALSE; + } + + d = g_new (double, 1); + *d = value; + + g_hash_table_insert (theme->float_constants, + g_strdup (name), d); + + return TRUE; +} + +gboolean +meta_theme_lookup_float_constant (MetaTheme *theme, + const char *name, + double *value) +{ + double *d; + + *value = 0.0; + + if (theme->float_constants == NULL) + return FALSE; + + d = g_hash_table_lookup (theme->float_constants, name); + + if (d) + { + *value = *d; + return TRUE; + } + else + { + return FALSE; + } +} + +gboolean +meta_theme_define_color_constant (MetaTheme *theme, + const char *name, + const char *value, + GError **error) +{ + if (theme->color_constants == NULL) + theme->color_constants = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); + + if (!first_uppercase (name)) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("User-defined constants must begin with a capital letter; \"%s\" does not"), + name); + return FALSE; + } + + if (g_hash_table_lookup_extended (theme->color_constants, name, NULL, NULL)) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("Constant \"%s\" has already been defined"), + name); + + return FALSE; + } + + g_hash_table_insert (theme->color_constants, + g_strdup (name), + g_strdup (value)); + + return TRUE; +} + +/** + * Looks up a colour constant. + * + * \param theme the theme containing the constant + * \param name the name of the constant + * \param value [out] the string representation of the colour, or NULL if it + * doesn't exist + * \return TRUE if it exists, FALSE otherwise + */ +gboolean +meta_theme_lookup_color_constant (MetaTheme *theme, + const char *name, + char **value) +{ + char *result; + + *value = NULL; + + if (theme->color_constants == NULL) + return FALSE; + + result = g_hash_table_lookup (theme->color_constants, name); + + if (result) + { + *value = result; + return TRUE; + } + else + { + return FALSE; + } +} + + +PangoFontDescription* +meta_gtk_widget_get_font_desc (GtkWidget *widget, + double scale, + const PangoFontDescription *override) +{ + PangoFontDescription *font_desc; + + g_return_val_if_fail (gtk_widget_get_realized (widget), NULL); + + font_desc = pango_font_description_copy (gtk_widget_get_style (widget)->font_desc); + + if (override) + pango_font_description_merge (font_desc, override, TRUE); + + pango_font_description_set_size (font_desc, + MAX (pango_font_description_get_size (font_desc) * scale, 1)); + + return font_desc; +} + +/** + * Returns the height of the letters in a particular font. + * + * \param font_desc the font + * \param context the context of the font + * \return the height of the letters + */ +int +meta_pango_font_desc_get_text_height (const PangoFontDescription *font_desc, + PangoContext *context) +{ + PangoFontMetrics *metrics; + PangoLanguage *lang; + int retval; + + lang = pango_context_get_language (context); + metrics = pango_context_get_metrics (context, font_desc, lang); + + retval = PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) + + pango_font_metrics_get_descent (metrics)); + + pango_font_metrics_unref (metrics); + + return retval; +} + +MetaGtkColorComponent +meta_color_component_from_string (const char *str) +{ + if (strcmp ("fg", str) == 0) + return META_GTK_COLOR_FG; + else if (strcmp ("bg", str) == 0) + return META_GTK_COLOR_BG; + else if (strcmp ("light", str) == 0) + return META_GTK_COLOR_LIGHT; + else if (strcmp ("dark", str) == 0) + return META_GTK_COLOR_DARK; + else if (strcmp ("mid", str) == 0) + return META_GTK_COLOR_MID; + else if (strcmp ("text", str) == 0) + return META_GTK_COLOR_TEXT; + else if (strcmp ("base", str) == 0) + return META_GTK_COLOR_BASE; + else if (strcmp ("text_aa", str) == 0) + return META_GTK_COLOR_TEXT_AA; + else + return META_GTK_COLOR_LAST; +} + +const char* +meta_color_component_to_string (MetaGtkColorComponent component) +{ + switch (component) + { + case META_GTK_COLOR_FG: + return "fg"; + case META_GTK_COLOR_BG: + return "bg"; + case META_GTK_COLOR_LIGHT: + return "light"; + case META_GTK_COLOR_DARK: + return "dark"; + case META_GTK_COLOR_MID: + return "mid"; + case META_GTK_COLOR_TEXT: + return "text"; + case META_GTK_COLOR_BASE: + return "base"; + case META_GTK_COLOR_TEXT_AA: + return "text_aa"; + case META_GTK_COLOR_LAST: + break; + } + + return "<unknown>"; +} + +MetaButtonState +meta_button_state_from_string (const char *str) +{ + if (strcmp ("normal", str) == 0) + return META_BUTTON_STATE_NORMAL; + else if (strcmp ("pressed", str) == 0) + return META_BUTTON_STATE_PRESSED; + else if (strcmp ("prelight", str) == 0) + return META_BUTTON_STATE_PRELIGHT; + else + return META_BUTTON_STATE_LAST; +} + +const char* +meta_button_state_to_string (MetaButtonState state) +{ + switch (state) + { + case META_BUTTON_STATE_NORMAL: + return "normal"; + case META_BUTTON_STATE_PRESSED: + return "pressed"; + case META_BUTTON_STATE_PRELIGHT: + return "prelight"; + case META_BUTTON_STATE_LAST: + break; + } + + return "<unknown>"; +} + +MetaButtonType +meta_button_type_from_string (const char *str, MetaTheme *theme) +{ + if (META_THEME_ALLOWS(theme, META_THEME_SHADE_STICK_ABOVE_BUTTONS)) + { + if (strcmp ("shade", str) == 0) + return META_BUTTON_TYPE_SHADE; + else if (strcmp ("above", str) == 0) + return META_BUTTON_TYPE_ABOVE; + else if (strcmp ("stick", str) == 0) + return META_BUTTON_TYPE_STICK; + else if (strcmp ("unshade", str) == 0) + return META_BUTTON_TYPE_UNSHADE; + else if (strcmp ("unabove", str) == 0) + return META_BUTTON_TYPE_UNABOVE; + else if (strcmp ("unstick", str) == 0) + return META_BUTTON_TYPE_UNSTICK; + } + + if (strcmp ("close", str) == 0) + return META_BUTTON_TYPE_CLOSE; + else if (strcmp ("maximize", str) == 0) + return META_BUTTON_TYPE_MAXIMIZE; + else if (strcmp ("minimize", str) == 0) + return META_BUTTON_TYPE_MINIMIZE; + else if (strcmp ("menu", str) == 0) + return META_BUTTON_TYPE_MENU; + else if (strcmp ("left_left_background", str) == 0) + return META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND; + else if (strcmp ("left_middle_background", str) == 0) + return META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND; + else if (strcmp ("left_right_background", str) == 0) + return META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND; + else if (strcmp ("right_left_background", str) == 0) + return META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND; + else if (strcmp ("right_middle_background", str) == 0) + return META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND; + else if (strcmp ("right_right_background", str) == 0) + return META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND; + else + return META_BUTTON_TYPE_LAST; +} + +const char* +meta_button_type_to_string (MetaButtonType type) +{ + switch (type) + { + case META_BUTTON_TYPE_CLOSE: + return "close"; + case META_BUTTON_TYPE_MAXIMIZE: + return "maximize"; + case META_BUTTON_TYPE_MINIMIZE: + return "minimize"; + case META_BUTTON_TYPE_SHADE: + return "shade"; + case META_BUTTON_TYPE_ABOVE: + return "above"; + case META_BUTTON_TYPE_STICK: + return "stick"; + case META_BUTTON_TYPE_UNSHADE: + return "unshade"; + case META_BUTTON_TYPE_UNABOVE: + return "unabove"; + case META_BUTTON_TYPE_UNSTICK: + return "unstick"; + case META_BUTTON_TYPE_MENU: + return "menu"; + case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND: + return "left_left_background"; + case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND: + return "left_middle_background"; + case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND: + return "left_right_background"; + case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND: + return "right_left_background"; + case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND: + return "right_middle_background"; + case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND: + return "right_right_background"; + case META_BUTTON_TYPE_LAST: + break; + } + + return "<unknown>"; +} + +MetaFramePiece +meta_frame_piece_from_string (const char *str) +{ + if (strcmp ("entire_background", str) == 0) + return META_FRAME_PIECE_ENTIRE_BACKGROUND; + else if (strcmp ("titlebar", str) == 0) + return META_FRAME_PIECE_TITLEBAR; + else if (strcmp ("titlebar_middle", str) == 0) + return META_FRAME_PIECE_TITLEBAR_MIDDLE; + else if (strcmp ("left_titlebar_edge", str) == 0) + return META_FRAME_PIECE_LEFT_TITLEBAR_EDGE; + else if (strcmp ("right_titlebar_edge", str) == 0) + return META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE; + else if (strcmp ("top_titlebar_edge", str) == 0) + return META_FRAME_PIECE_TOP_TITLEBAR_EDGE; + else if (strcmp ("bottom_titlebar_edge", str) == 0) + return META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE; + else if (strcmp ("title", str) == 0) + return META_FRAME_PIECE_TITLE; + else if (strcmp ("left_edge", str) == 0) + return META_FRAME_PIECE_LEFT_EDGE; + else if (strcmp ("right_edge", str) == 0) + return META_FRAME_PIECE_RIGHT_EDGE; + else if (strcmp ("bottom_edge", str) == 0) + return META_FRAME_PIECE_BOTTOM_EDGE; + else if (strcmp ("overlay", str) == 0) + return META_FRAME_PIECE_OVERLAY; + else + return META_FRAME_PIECE_LAST; +} + +const char* +meta_frame_piece_to_string (MetaFramePiece piece) +{ + switch (piece) + { + case META_FRAME_PIECE_ENTIRE_BACKGROUND: + return "entire_background"; + case META_FRAME_PIECE_TITLEBAR: + return "titlebar"; + case META_FRAME_PIECE_TITLEBAR_MIDDLE: + return "titlebar_middle"; + case META_FRAME_PIECE_LEFT_TITLEBAR_EDGE: + return "left_titlebar_edge"; + case META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE: + return "right_titlebar_edge"; + case META_FRAME_PIECE_TOP_TITLEBAR_EDGE: + return "top_titlebar_edge"; + case META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE: + return "bottom_titlebar_edge"; + case META_FRAME_PIECE_TITLE: + return "title"; + case META_FRAME_PIECE_LEFT_EDGE: + return "left_edge"; + case META_FRAME_PIECE_RIGHT_EDGE: + return "right_edge"; + case META_FRAME_PIECE_BOTTOM_EDGE: + return "bottom_edge"; + case META_FRAME_PIECE_OVERLAY: + return "overlay"; + case META_FRAME_PIECE_LAST: + break; + } + + return "<unknown>"; +} + +MetaFrameState +meta_frame_state_from_string (const char *str) +{ + if (strcmp ("normal", str) == 0) + return META_FRAME_STATE_NORMAL; + else if (strcmp ("maximized", str) == 0) + return META_FRAME_STATE_MAXIMIZED; + else if (strcmp ("shaded", str) == 0) + return META_FRAME_STATE_SHADED; + else if (strcmp ("maximized_and_shaded", str) == 0) + return META_FRAME_STATE_MAXIMIZED_AND_SHADED; + else + return META_FRAME_STATE_LAST; +} + +const char* +meta_frame_state_to_string (MetaFrameState state) +{ + switch (state) + { + case META_FRAME_STATE_NORMAL: + return "normal"; + case META_FRAME_STATE_MAXIMIZED: + return "maximized"; + case META_FRAME_STATE_SHADED: + return "shaded"; + case META_FRAME_STATE_MAXIMIZED_AND_SHADED: + return "maximized_and_shaded"; + case META_FRAME_STATE_LAST: + break; + } + + return "<unknown>"; +} + +MetaFrameResize +meta_frame_resize_from_string (const char *str) +{ + if (strcmp ("none", str) == 0) + return META_FRAME_RESIZE_NONE; + else if (strcmp ("vertical", str) == 0) + return META_FRAME_RESIZE_VERTICAL; + else if (strcmp ("horizontal", str) == 0) + return META_FRAME_RESIZE_HORIZONTAL; + else if (strcmp ("both", str) == 0) + return META_FRAME_RESIZE_BOTH; + else + return META_FRAME_RESIZE_LAST; +} + +const char* +meta_frame_resize_to_string (MetaFrameResize resize) +{ + switch (resize) + { + case META_FRAME_RESIZE_NONE: + return "none"; + case META_FRAME_RESIZE_VERTICAL: + return "vertical"; + case META_FRAME_RESIZE_HORIZONTAL: + return "horizontal"; + case META_FRAME_RESIZE_BOTH: + return "both"; + case META_FRAME_RESIZE_LAST: + break; + } + + return "<unknown>"; +} + +MetaFrameFocus +meta_frame_focus_from_string (const char *str) +{ + if (strcmp ("no", str) == 0) + return META_FRAME_FOCUS_NO; + else if (strcmp ("yes", str) == 0) + return META_FRAME_FOCUS_YES; + else + return META_FRAME_FOCUS_LAST; +} + +const char* +meta_frame_focus_to_string (MetaFrameFocus focus) +{ + switch (focus) + { + case META_FRAME_FOCUS_NO: + return "no"; + case META_FRAME_FOCUS_YES: + return "yes"; + case META_FRAME_FOCUS_LAST: + break; + } + + return "<unknown>"; +} + +MetaFrameType +meta_frame_type_from_string (const char *str) +{ + if (strcmp ("normal", str) == 0) + return META_FRAME_TYPE_NORMAL; + else if (strcmp ("dialog", str) == 0) + return META_FRAME_TYPE_DIALOG; + else if (strcmp ("modal_dialog", str) == 0) + return META_FRAME_TYPE_MODAL_DIALOG; + else if (strcmp ("utility", str) == 0) + return META_FRAME_TYPE_UTILITY; + else if (strcmp ("menu", str) == 0) + return META_FRAME_TYPE_MENU; + else if (strcmp ("border", str) == 0) + return META_FRAME_TYPE_BORDER; +#if 0 + else if (strcmp ("toolbar", str) == 0) + return META_FRAME_TYPE_TOOLBAR; +#endif + else + return META_FRAME_TYPE_LAST; +} + +const char* +meta_frame_type_to_string (MetaFrameType type) +{ + switch (type) + { + case META_FRAME_TYPE_NORMAL: + return "normal"; + case META_FRAME_TYPE_DIALOG: + return "dialog"; + case META_FRAME_TYPE_MODAL_DIALOG: + return "modal_dialog"; + case META_FRAME_TYPE_UTILITY: + return "utility"; + case META_FRAME_TYPE_MENU: + return "menu"; + case META_FRAME_TYPE_BORDER: + return "border"; +#if 0 + case META_FRAME_TYPE_TOOLBAR: + return "toolbar"; +#endif + case META_FRAME_TYPE_LAST: + break; + } + + return "<unknown>"; +} + +MetaGradientType +meta_gradient_type_from_string (const char *str) +{ + if (strcmp ("vertical", str) == 0) + return META_GRADIENT_VERTICAL; + else if (strcmp ("horizontal", str) == 0) + return META_GRADIENT_HORIZONTAL; + else if (strcmp ("diagonal", str) == 0) + return META_GRADIENT_DIAGONAL; + else + return META_GRADIENT_LAST; +} + +const char* +meta_gradient_type_to_string (MetaGradientType type) +{ + switch (type) + { + case META_GRADIENT_VERTICAL: + return "vertical"; + case META_GRADIENT_HORIZONTAL: + return "horizontal"; + case META_GRADIENT_DIAGONAL: + return "diagonal"; + case META_GRADIENT_LAST: + break; + } + + return "<unknown>"; +} + +GtkStateType +meta_gtk_state_from_string (const char *str) +{ + if (strcmp ("normal", str) == 0 || strcmp ("NORMAL", str) == 0) + return GTK_STATE_NORMAL; + else if (strcmp ("prelight", str) == 0 || strcmp ("PRELIGHT", str) == 0) + return GTK_STATE_PRELIGHT; + else if (strcmp ("active", str) == 0 || strcmp ("ACTIVE", str) == 0) + return GTK_STATE_ACTIVE; + else if (strcmp ("selected", str) == 0 || strcmp ("SELECTED", str) == 0) + return GTK_STATE_SELECTED; + else if (strcmp ("insensitive", str) == 0 || strcmp ("INSENSITIVE", str) == 0) + return GTK_STATE_INSENSITIVE; + else + return -1; /* hack */ +} + +const char* +meta_gtk_state_to_string (GtkStateType state) +{ + switch (state) + { + case GTK_STATE_NORMAL: + return "NORMAL"; + case GTK_STATE_PRELIGHT: + return "PRELIGHT"; + case GTK_STATE_ACTIVE: + return "ACTIVE"; + case GTK_STATE_SELECTED: + return "SELECTED"; + case GTK_STATE_INSENSITIVE: + return "INSENSITIVE"; + } + + return "<unknown>"; +} + +GtkShadowType +meta_gtk_shadow_from_string (const char *str) +{ + if (strcmp ("none", str) == 0) + return GTK_SHADOW_NONE; + else if (strcmp ("in", str) == 0) + return GTK_SHADOW_IN; + else if (strcmp ("out", str) == 0) + return GTK_SHADOW_OUT; + else if (strcmp ("etched_in", str) == 0) + return GTK_SHADOW_ETCHED_IN; + else if (strcmp ("etched_out", str) == 0) + return GTK_SHADOW_ETCHED_OUT; + else + return -1; +} + +const char* +meta_gtk_shadow_to_string (GtkShadowType shadow) +{ + switch (shadow) + { + case GTK_SHADOW_NONE: + return "none"; + case GTK_SHADOW_IN: + return "in"; + case GTK_SHADOW_OUT: + return "out"; + case GTK_SHADOW_ETCHED_IN: + return "etched_in"; + case GTK_SHADOW_ETCHED_OUT: + return "etched_out"; + } + + return "<unknown>"; +} + +GtkArrowType +meta_gtk_arrow_from_string (const char *str) +{ + if (strcmp ("up", str) == 0) + return GTK_ARROW_UP; + else if (strcmp ("down", str) == 0) + return GTK_ARROW_DOWN; + else if (strcmp ("left", str) == 0) + return GTK_ARROW_LEFT; + else if (strcmp ("right", str) == 0) + return GTK_ARROW_RIGHT; + else if (strcmp ("none", str) == 0) + return GTK_ARROW_NONE; + else + return -1; +} + +const char* +meta_gtk_arrow_to_string (GtkArrowType arrow) +{ + switch (arrow) + { + case GTK_ARROW_UP: + return "up"; + case GTK_ARROW_DOWN: + return "down"; + case GTK_ARROW_LEFT: + return "left"; + case GTK_ARROW_RIGHT: + return "right"; + case GTK_ARROW_NONE: + return "none"; + } + + return "<unknown>"; +} + +/** + * Returns a fill_type from a string. The inverse of + * meta_image_fill_type_to_string(). + * + * \param str a string representing a fill_type + * \result the fill_type, or -1 if it represents no fill_type. + */ +MetaImageFillType +meta_image_fill_type_from_string (const char *str) +{ + if (strcmp ("tile", str) == 0) + return META_IMAGE_FILL_TILE; + else if (strcmp ("scale", str) == 0) + return META_IMAGE_FILL_SCALE; + else + return -1; +} + +/** + * Returns a string representation of a fill_type. The inverse of + * meta_image_fill_type_from_string(). + * + * \param fill_type the fill type + * \result a string representing that type + */ +const char* +meta_image_fill_type_to_string (MetaImageFillType fill_type) +{ + switch (fill_type) + { + case META_IMAGE_FILL_TILE: + return "tile"; + case META_IMAGE_FILL_SCALE: + return "scale"; + } + + return "<unknown>"; +} + +/** + * Takes a colour "a", scales the lightness and saturation by a certain amount, + * and sets "b" to the resulting colour. + * gtkstyle.c cut-and-pastage. + * + * \param a the starting colour + * \param b [out] the resulting colour + * \param k amount to scale lightness and saturation by + */ +static void +gtk_style_shade (GdkColor *a, + GdkColor *b, + gdouble k) +{ + gdouble red; + gdouble green; + gdouble blue; + + red = (gdouble) a->red / 65535.0; + green = (gdouble) a->green / 65535.0; + blue = (gdouble) a->blue / 65535.0; + + rgb_to_hls (&red, &green, &blue); + + green *= k; + if (green > 1.0) + green = 1.0; + else if (green < 0.0) + green = 0.0; + + blue *= k; + if (blue > 1.0) + blue = 1.0; + else if (blue < 0.0) + blue = 0.0; + + hls_to_rgb (&red, &green, &blue); + + b->red = red * 65535.0; + b->green = green * 65535.0; + b->blue = blue * 65535.0; +} + +/** + * Converts a red/green/blue triplet to a hue/lightness/saturation triplet. + * + * \param r on input, red; on output, hue + * \param g on input, green; on output, lightness + * \param b on input, blue; on output, saturation + */ +static void +rgb_to_hls (gdouble *r, + gdouble *g, + gdouble *b) +{ + gdouble min; + gdouble max; + gdouble red; + gdouble green; + gdouble blue; + gdouble h, l, s; + gdouble delta; + + red = *r; + green = *g; + blue = *b; + + if (red > green) + { + if (red > blue) + max = red; + else + max = blue; + + if (green < blue) + min = green; + else + min = blue; + } + else + { + if (green > blue) + max = green; + else + max = blue; + + if (red < blue) + min = red; + else + min = blue; + } + + l = (max + min) / 2; + s = 0; + h = 0; + + if (max != min) + { + if (l <= 0.5) + s = (max - min) / (max + min); + else + s = (max - min) / (2 - max - min); + + delta = max -min; + if (red == max) + h = (green - blue) / delta; + else if (green == max) + h = 2 + (blue - red) / delta; + else if (blue == max) + h = 4 + (red - green) / delta; + + h *= 60; + if (h < 0.0) + h += 360; + } + + *r = h; + *g = l; + *b = s; +} + +/** + * Converts a hue/lightness/saturation triplet to a red/green/blue triplet. + * + * \param h on input, hue; on output, red + * \param l on input, lightness; on output, green + * \param s on input, saturation; on output, blue + */ +static void +hls_to_rgb (gdouble *h, + gdouble *l, + gdouble *s) +{ + gdouble hue; + gdouble lightness; + gdouble saturation; + gdouble m1, m2; + gdouble r, g, b; + + lightness = *l; + saturation = *s; + + if (lightness <= 0.5) + m2 = lightness * (1 + saturation); + else + m2 = lightness + saturation - lightness * saturation; + m1 = 2 * lightness - m2; + + if (saturation == 0) + { + *h = lightness; + *l = lightness; + *s = lightness; + } + else + { + hue = *h + 120; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + r = m1 + (m2 - m1) * hue / 60; + else if (hue < 180) + r = m2; + else if (hue < 240) + r = m1 + (m2 - m1) * (240 - hue) / 60; + else + r = m1; + + hue = *h; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + g = m1 + (m2 - m1) * hue / 60; + else if (hue < 180) + g = m2; + else if (hue < 240) + g = m1 + (m2 - m1) * (240 - hue) / 60; + else + g = m1; + + hue = *h - 120; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + b = m1 + (m2 - m1) * hue / 60; + else if (hue < 180) + b = m2; + else if (hue < 240) + b = m1 + (m2 - m1) * (240 - hue) / 60; + else + b = m1; + + *h = r; + *l = g; + *s = b; + } +} + +#if 0 +/* These are some functions I'm saving to use in optimizing + * MetaDrawOpList, namely to pre-composite pixbufs on client side + * prior to rendering to the server + */ +static void +draw_bg_solid_composite (const MetaTextureSpec *bg, + const MetaTextureSpec *fg, + double alpha, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + MetaTextureDrawMode mode, + double xalign, + double yalign, + int x, + int y, + int width, + int height) +{ + GdkColor bg_color; + + g_assert (bg->type == META_TEXTURE_SOLID); + g_assert (fg->type != META_TEXTURE_COMPOSITE); + g_assert (fg->type != META_TEXTURE_SHAPE_LIST); + + meta_color_spec_render (bg->data.solid.color_spec, + widget, + &bg_color); + + switch (fg->type) + { + case META_TEXTURE_SOLID: + { + GdkColor fg_color; + + meta_color_spec_render (fg->data.solid.color_spec, + widget, + &fg_color); + + color_composite (&bg_color, &fg_color, + alpha, &fg_color); + + draw_color_rectangle (widget, drawable, &fg_color, clip, + x, y, width, height); + } + break; + + case META_TEXTURE_GRADIENT: + /* FIXME I think we could just composite all the colors in + * the gradient prior to generating the gradient? + */ + /* FALL THRU */ + case META_TEXTURE_IMAGE: + { + GdkPixbuf *pixbuf; + GdkPixbuf *composited; + + pixbuf = meta_texture_spec_render (fg, widget, mode, 255, + width, height); + + if (pixbuf == NULL) + return; + + composited = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + gdk_pixbuf_get_has_alpha (pixbuf), 8, + gdk_pixbuf_get_width (pixbuf), + gdk_pixbuf_get_height (pixbuf)); + + if (composited == NULL) + { + g_object_unref (G_OBJECT (pixbuf)); + return; + } + + gdk_pixbuf_composite_color (pixbuf, + composited, + 0, 0, + gdk_pixbuf_get_width (pixbuf), + gdk_pixbuf_get_height (pixbuf), + 0.0, 0.0, /* offsets */ + 1.0, 1.0, /* scale */ + GDK_INTERP_BILINEAR, + 255 * alpha, + 0, 0, /* check offsets */ + 0, /* check size */ + GDK_COLOR_RGB (bg_color), + GDK_COLOR_RGB (bg_color)); + + /* Need to draw background since pixbuf is not + * necessarily covering the whole thing + */ + draw_color_rectangle (widget, drawable, &bg_color, clip, + x, y, width, height); + + render_pixbuf_aligned (drawable, clip, composited, + xalign, yalign, + x, y, width, height); + + g_object_unref (G_OBJECT (pixbuf)); + g_object_unref (G_OBJECT (composited)); + } + break; + + case META_TEXTURE_BLANK: + case META_TEXTURE_COMPOSITE: + case META_TEXTURE_SHAPE_LIST: + g_assert_not_reached (); + break; + } +} + +static void +draw_bg_gradient_composite (const MetaTextureSpec *bg, + const MetaTextureSpec *fg, + double alpha, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + MetaTextureDrawMode mode, + double xalign, + double yalign, + int x, + int y, + int width, + int height) +{ + g_assert (bg->type == META_TEXTURE_GRADIENT); + g_assert (fg->type != META_TEXTURE_COMPOSITE); + g_assert (fg->type != META_TEXTURE_SHAPE_LIST); + + switch (fg->type) + { + case META_TEXTURE_SOLID: + case META_TEXTURE_GRADIENT: + case META_TEXTURE_IMAGE: + { + GdkPixbuf *bg_pixbuf; + GdkPixbuf *fg_pixbuf; + GdkPixbuf *composited; + int fg_width, fg_height; + + bg_pixbuf = meta_texture_spec_render (bg, widget, mode, 255, + width, height); + + if (bg_pixbuf == NULL) + return; + + fg_pixbuf = meta_texture_spec_render (fg, widget, mode, 255, + width, height); + + if (fg_pixbuf == NULL) + { + g_object_unref (G_OBJECT (bg_pixbuf)); + return; + } + + /* gradients always fill the entire target area */ + g_assert (gdk_pixbuf_get_width (bg_pixbuf) == width); + g_assert (gdk_pixbuf_get_height (bg_pixbuf) == height); + + composited = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + gdk_pixbuf_get_has_alpha (bg_pixbuf), 8, + gdk_pixbuf_get_width (bg_pixbuf), + gdk_pixbuf_get_height (bg_pixbuf)); + + if (composited == NULL) + { + g_object_unref (G_OBJECT (bg_pixbuf)); + g_object_unref (G_OBJECT (fg_pixbuf)); + return; + } + + fg_width = gdk_pixbuf_get_width (fg_pixbuf); + fg_height = gdk_pixbuf_get_height (fg_pixbuf); + + /* If we wanted to be all cool we could deal with the + * offsets and try to composite only in the clip rectangle, + * but I just don't care enough to figure it out. + */ + + gdk_pixbuf_composite (fg_pixbuf, + composited, + x + (width - fg_width) * xalign, + y + (height - fg_height) * yalign, + gdk_pixbuf_get_width (fg_pixbuf), + gdk_pixbuf_get_height (fg_pixbuf), + 0.0, 0.0, /* offsets */ + 1.0, 1.0, /* scale */ + GDK_INTERP_BILINEAR, + 255 * alpha); + + gdk_cairo_set_source_pixbuf (cr, composited, x, y); + cairo_paint (cr); + + g_object_unref (G_OBJECT (bg_pixbuf)); + g_object_unref (G_OBJECT (fg_pixbuf)); + g_object_unref (G_OBJECT (composited)); + } + break; + + case META_TEXTURE_BLANK: + case META_TEXTURE_SHAPE_LIST: + case META_TEXTURE_COMPOSITE: + g_assert_not_reached (); + break; + } +} +#endif + +/** + * Returns the earliest version of the theme format which required support + * for a particular button. (For example, "shade" first appeared in v2, and + * "close" in v1.) + * + * \param type the button type + * \return the number of the theme format + */ +guint +meta_theme_earliest_version_with_button (MetaButtonType type) +{ + switch (type) + { + case META_BUTTON_TYPE_CLOSE: + case META_BUTTON_TYPE_MAXIMIZE: + case META_BUTTON_TYPE_MINIMIZE: + case META_BUTTON_TYPE_MENU: + case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND: + case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND: + case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND: + case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND: + case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND: + case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND: + return 1; + + case META_BUTTON_TYPE_SHADE: + case META_BUTTON_TYPE_ABOVE: + case META_BUTTON_TYPE_STICK: + case META_BUTTON_TYPE_UNSHADE: + case META_BUTTON_TYPE_UNABOVE: + case META_BUTTON_TYPE_UNSTICK: + return 2; + + default: + meta_warning("Unknown button %d\n", type); + return 1; + } +} |