diff options
Diffstat (limited to 'src/ui/theme-parser.c')
-rw-r--r-- | src/ui/theme-parser.c | 4212 |
1 files changed, 4212 insertions, 0 deletions
diff --git a/src/ui/theme-parser.c b/src/ui/theme-parser.c new file mode 100644 index 00000000..12f01061 --- /dev/null +++ b/src/ui/theme-parser.c @@ -0,0 +1,4212 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco theme parsing */ + +/* + * 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. + */ + +#include <config.h> +#include "theme-parser.h" +#include "util.h" +#include <string.h> +#include <stdlib.h> + +/* This is a hack for Ubuntu's metacity themes + * Its safe for unable or not. + * I am using this define to know what code was added */ +#define USE_UBUNTU_CODE 1 + +typedef enum +{ + STATE_START, + STATE_THEME, + /* info section */ + STATE_INFO, + STATE_NAME, + STATE_AUTHOR, + STATE_COPYRIGHT, + STATE_DATE, + STATE_DESCRIPTION, + /* constants */ + STATE_CONSTANT, + /* geometry */ + STATE_FRAME_GEOMETRY, + STATE_DISTANCE, + STATE_BORDER, + STATE_ASPECT_RATIO, + /* draw ops */ + STATE_DRAW_OPS, + STATE_LINE, + STATE_RECTANGLE, + STATE_ARC, + STATE_CLIP, + STATE_TINT, + STATE_GRADIENT, + STATE_IMAGE, + STATE_GTK_ARROW, + STATE_GTK_BOX, + STATE_GTK_VLINE, + STATE_ICON, + STATE_TITLE, + STATE_INCLUDE, /* include another draw op list */ + STATE_TILE, /* tile another draw op list */ + /* sub-parts of gradient */ + STATE_COLOR, + /* frame style */ + STATE_FRAME_STYLE, + STATE_PIECE, + STATE_BUTTON, +#ifdef USE_UBUNTU_CODE + STATE_SHADOW, + STATE_PADDING, +#endif + /* style set */ + STATE_FRAME_STYLE_SET, + STATE_FRAME, + /* assigning style sets to windows */ + STATE_WINDOW, + /* things we don't use any more but we can still parse: */ + STATE_MENU_ICON, + STATE_FALLBACK +} ParseState; + +typedef struct +{ + GSList *states; + + const char *theme_name; /* name of theme (directory it's in) */ + char *theme_file; /* theme filename */ + char *theme_dir; /* dir the theme is inside */ + MetaTheme *theme; /* theme being parsed */ + guint format_version; /* version of format of theme file */ + char *name; /* name of named thing being parsed */ + MetaFrameLayout *layout; /* layout being parsed if any */ + MetaDrawOpList *op_list; /* op list being parsed if any */ + MetaDrawOp *op; /* op being parsed if any */ + MetaFrameStyle *style; /* frame style being parsed if any */ + MetaFrameStyleSet *style_set; /* frame style set being parsed if any */ + MetaFramePiece piece; /* position of piece being parsed */ + MetaButtonType button_type; /* type of button/menuitem being parsed */ + MetaButtonState button_state; /* state of button being parsed */ +} ParseInfo; + +static void set_error (GError **err, + GMarkupParseContext *context, + int error_domain, + int error_code, + const char *format, + ...) G_GNUC_PRINTF (5, 6); + +static void add_context_to_error (GError **err, + GMarkupParseContext *context); + +static void parse_info_init (ParseInfo *info); +static void parse_info_free (ParseInfo *info); + +static void push_state (ParseInfo *info, + ParseState state); +static void pop_state (ParseInfo *info); +static ParseState peek_state (ParseInfo *info); + + +static void parse_toplevel_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +static void parse_info_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +static void parse_geometry_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +static void parse_draw_op_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +static void parse_gradient_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +static void parse_style_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +static void parse_style_set_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); + +static void parse_piece_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); + +static void parse_button_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); + +#ifdef USE_UBUNTU_CODE +static void parse_shadow_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); + +static void parse_padding_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +#endif +static void parse_menu_icon_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); + +static void start_element_handler (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error); +static void end_element_handler (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error); +static void text_handler (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error); + +/* Translators: This means that an attribute which should have been found + * on an XML element was not in fact found. + */ +#define ATTRIBUTE_NOT_FOUND _("No \"%s\" attribute on element <%s>") + +static GMarkupParser marco_theme_parser = { + start_element_handler, + end_element_handler, + text_handler, + NULL, + NULL +}; + +static void +set_error (GError **err, + GMarkupParseContext *context, + int error_domain, + int error_code, + const char *format, + ...) +{ + int line, ch; + va_list args; + char *str; + + g_markup_parse_context_get_position (context, &line, &ch); + + va_start (args, format); + str = g_strdup_vprintf (format, args); + va_end (args); + + g_set_error (err, error_domain, error_code, + _("Line %d character %d: %s"), + line, ch, str); + + g_free (str); +} + +static void +add_context_to_error (GError **err, + GMarkupParseContext *context) +{ + int line, ch; + char *str; + + if (err == NULL || *err == NULL) + return; + + g_markup_parse_context_get_position (context, &line, &ch); + + str = g_strdup_printf (_("Line %d character %d: %s"), + line, ch, (*err)->message); + g_free ((*err)->message); + (*err)->message = str; +} + +static void +parse_info_init (ParseInfo *info) +{ + info->theme_file = NULL; + info->states = g_slist_prepend (NULL, GINT_TO_POINTER (STATE_START)); + info->theme = NULL; + info->name = NULL; + info->layout = NULL; + info->op_list = NULL; + info->op = NULL; + info->style = NULL; + info->style_set = NULL; + info->piece = META_FRAME_PIECE_LAST; + info->button_type = META_BUTTON_TYPE_LAST; + info->button_state = META_BUTTON_STATE_LAST; +} + +static void +parse_info_free (ParseInfo *info) +{ + g_free (info->theme_file); + g_free (info->theme_dir); + + g_slist_free (info->states); + + if (info->theme) + meta_theme_free (info->theme); + + if (info->layout) + meta_frame_layout_unref (info->layout); + + if (info->op_list) + meta_draw_op_list_unref (info->op_list); + + if (info->op) + meta_draw_op_free (info->op); + + if (info->style) + meta_frame_style_unref (info->style); + + if (info->style_set) + meta_frame_style_set_unref (info->style_set); +} + +static void +push_state (ParseInfo *info, + ParseState state) +{ + info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state)); +} + +static void +pop_state (ParseInfo *info) +{ + g_return_if_fail (info->states != NULL); + + info->states = g_slist_remove (info->states, info->states->data); +} + +static ParseState +peek_state (ParseInfo *info) +{ + g_return_val_if_fail (info->states != NULL, STATE_START); + + return GPOINTER_TO_INT (info->states->data); +} + +#define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0) + +typedef struct +{ + const char *name; + const char **retloc; + gboolean required; +} LocateAttr; + +/* Attribute names can have a leading '!' to indicate that they are + * required. + */ +static gboolean +locate_attributes (GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + GError **error, + const char *first_attribute_name, + const char **first_attribute_retloc, + ...) +{ + va_list args; + const char *name; + const char **retloc; + int n_attrs; +#define MAX_ATTRS 24 + LocateAttr attrs[MAX_ATTRS]; + gboolean retval; + int i; + + g_return_val_if_fail (first_attribute_name != NULL, FALSE); + g_return_val_if_fail (first_attribute_retloc != NULL, FALSE); + + retval = TRUE; + + /* FIXME: duplicated code; refactor loop */ + n_attrs = 1; + attrs[0].name = first_attribute_name; + attrs[0].retloc = first_attribute_retloc; + attrs[0].required = attrs[0].name[0]=='!'; + if (attrs[0].required) + attrs[0].name++; /* skip past it */ + *first_attribute_retloc = NULL; + + va_start (args, first_attribute_retloc); + + name = va_arg (args, const char*); + retloc = va_arg (args, const char**); + + while (name != NULL) + { + g_return_val_if_fail (retloc != NULL, FALSE); + + g_assert (n_attrs < MAX_ATTRS); + + attrs[n_attrs].name = name; + attrs[n_attrs].retloc = retloc; + attrs[n_attrs].required = attrs[n_attrs].name[0]=='!'; + if (attrs[n_attrs].required) + attrs[n_attrs].name++; /* skip past it */ + + n_attrs += 1; + *retloc = NULL; + + name = va_arg (args, const char*); + retloc = va_arg (args, const char**); + } + + va_end (args); + + i = 0; + while (attribute_names[i]) + { + int j; + gboolean found; + + found = FALSE; + j = 0; + while (j < n_attrs) + { + if (strcmp (attrs[j].name, attribute_names[i]) == 0) + { + retloc = attrs[j].retloc; + + if (*retloc != NULL) + { + + set_error (error, context, + G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Attribute \"%s\" repeated twice on the same <%s> element"), + attrs[j].name, element_name); + retval = FALSE; + goto out; + } + + *retloc = attribute_values[i]; + found = TRUE; + } + + ++j; + } + + if (!found) + { + j = 0; + while (j < n_attrs) + { + g_warning ("It could have been %s.\n", attrs[j++].name); + } + + set_error (error, context, + G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Attribute \"%s\" is invalid on <%s> element in this context"), + attribute_names[i], element_name); + retval = FALSE; + goto out; + } + + ++i; + } + + /* Did we catch them all? */ + i = 0; + while (i < n_attrs) + { + if (attrs[i].required && *(attrs[i].retloc)==NULL) + { + set_error (error, context, + G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + ATTRIBUTE_NOT_FOUND, + attrs[i].name, element_name); + retval = FALSE; + goto out; + } + + ++i; + } + + out: + return retval; +} + +static gboolean +check_no_attributes (GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + GError **error) +{ + if (attribute_names[0] != NULL) + { + set_error (error, context, + G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Attribute \"%s\" is invalid on <%s> element in this context"), + attribute_names[0], element_name); + return FALSE; + } + + return TRUE; +} + +#define MAX_REASONABLE 4096 +static gboolean +parse_positive_integer (const char *str, + int *val, + GMarkupParseContext *context, + MetaTheme *theme, + GError **error) +{ + char *end; + long l; + int j; + + *val = 0; + + end = NULL; + + /* Is str a constant? */ + + if (META_THEME_ALLOWS (theme, META_THEME_UBIQUITOUS_CONSTANTS) && + meta_theme_lookup_int_constant (theme, str, &j)) + { + /* Yes. */ + l = j; + } + else + { + /* No. Let's try parsing it instead. */ + + l = strtol (str, &end, 10); + + if (end == NULL || end == str) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Could not parse \"%s\" as an integer"), + str); + return FALSE; + } + + if (*end != '\0') + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand trailing characters \"%s\" in string \"%s\""), + end, str); + return FALSE; + } + } + + if (l < 0) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Integer %ld must be positive"), l); + return FALSE; + } + + if (l > MAX_REASONABLE) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Integer %ld is too large, current max is %d"), + l, MAX_REASONABLE); + return FALSE; + } + + *val = (int) l; + + return TRUE; +} + +static gboolean +parse_double (const char *str, + double *val, + GMarkupParseContext *context, + GError **error) +{ + char *end; + + *val = 0; + + end = NULL; + + *val = g_ascii_strtod (str, &end); + + if (end == NULL || end == str) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Could not parse \"%s\" as a floating point number"), + str); + return FALSE; + } + + if (*end != '\0') + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand trailing characters \"%s\" in string \"%s\""), + end, str); + return FALSE; + } + + return TRUE; +} + +static gboolean +parse_boolean (const char *str, + gboolean *val, + GMarkupParseContext *context, + GError **error) +{ + if (strcmp ("true", str) == 0) + *val = TRUE; + else if (strcmp ("false", str) == 0) + *val = FALSE; + else + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Boolean values must be \"true\" or \"false\" not \"%s\""), + str); + return FALSE; + } + + return TRUE; +} + +static gboolean +parse_rounding (const char *str, + guint *val, + GMarkupParseContext *context, + MetaTheme *theme, + GError **error) +{ + if (strcmp ("true", str) == 0) + *val = 5; /* historical "true" value */ + else if (strcmp ("false", str) == 0) + *val = 0; + else + { + int tmp; + gboolean result; + if (!META_THEME_ALLOWS (theme, META_THEME_VARIED_ROUND_CORNERS)) + { + /* Not known in this version, so bail. */ + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Boolean values must be \"true\" or \"false\" not \"%s\""), + str); + return FALSE; + } + + result = parse_positive_integer (str, &tmp, context, theme, error); + + *val = tmp; + + return result; + } + + return TRUE; +} + +static gboolean +parse_angle (const char *str, + double *val, + GMarkupParseContext *context, + GError **error) +{ + if (!parse_double (str, val, context, error)) + return FALSE; + + if (*val < (0.0 - 1e6) || *val > (360.0 + 1e6)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Angle must be between 0.0 and 360.0, was %g\n"), + *val); + return FALSE; + } + + return TRUE; +} + +static gboolean +parse_alpha (const char *str, + MetaAlphaGradientSpec **spec_ret, + GMarkupParseContext *context, + GError **error) +{ + char **split; + int i; + int n_alphas; + MetaAlphaGradientSpec *spec; + + *spec_ret = NULL; + + split = g_strsplit (str, ":", -1); + + i = 0; + while (split[i]) + ++i; + + if (i == 0) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Could not parse \"%s\" as a floating point number"), + str); + + g_strfreev (split); + + return FALSE; + } + + n_alphas = i; + + /* FIXME allow specifying horizontal/vertical/diagonal in theme format, + * once we implement vertical/diagonal in gradient.c + */ + spec = meta_alpha_gradient_spec_new (META_GRADIENT_HORIZONTAL, + n_alphas); + + i = 0; + while (i < n_alphas) + { + double v; + + if (!parse_double (split[i], &v, context, error)) + { + /* clear up, but don't set error: it was set by parse_double */ + g_strfreev (split); + meta_alpha_gradient_spec_free (spec); + + return FALSE; + } + + if (v < (0.0 - 1e-6) || v > (1.0 + 1e-6)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Alpha must be between 0.0 (invisible) and 1.0 (fully opaque), was %g\n"), + v); + + g_strfreev (split); + meta_alpha_gradient_spec_free (spec); + + return FALSE; + } + + spec->alphas[i] = (unsigned char) (v * 255); + + ++i; + } + + g_strfreev (split); + + *spec_ret = spec; + + return TRUE; +} + +static MetaColorSpec* +parse_color (MetaTheme *theme, + const char *str, + GError **err) +{ + char* referent; + + if (META_THEME_ALLOWS (theme, META_THEME_COLOR_CONSTANTS) && + meta_theme_lookup_color_constant (theme, str, &referent)) + { + if (referent) + return meta_color_spec_new_from_string (referent, err); + + /* no need to free referent: it's a pointer into the actual hash table */ + } + + return meta_color_spec_new_from_string (str, err); +} + +static gboolean +parse_title_scale (const char *str, + double *val, + GMarkupParseContext *context, + GError **error) +{ + double factor; + + if (strcmp (str, "xx-small") == 0) + factor = PANGO_SCALE_XX_SMALL; + else if (strcmp (str, "x-small") == 0) + factor = PANGO_SCALE_X_SMALL; + else if (strcmp (str, "small") == 0) + factor = PANGO_SCALE_SMALL; + else if (strcmp (str, "medium") == 0) + factor = PANGO_SCALE_MEDIUM; + else if (strcmp (str, "large") == 0) + factor = PANGO_SCALE_LARGE; + else if (strcmp (str, "x-large") == 0) + factor = PANGO_SCALE_X_LARGE; + else if (strcmp (str, "xx-large") == 0) + factor = PANGO_SCALE_XX_LARGE; + else + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Invalid title scale \"%s\" (must be one of xx-small,x-small,small,medium,large,x-large,xx-large)\n"), + str); + return FALSE; + } + + *val = factor; + + return TRUE; +} + +static void +parse_toplevel_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_THEME); + + if (ELEMENT_IS ("info")) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_INFO); + } + else if (ELEMENT_IS ("constant")) + { + const char *name; + const char *value; + int ival = 0; + double dval = 0.0; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, "!value", &value, + NULL)) + return; + + /* We don't know how a a constant is going to be used, so we have guess its + * type from its contents: + * + * - Starts like a number and contains a '.': float constant + * - Starts like a number and doesn't contain a '.': int constant + * - Starts with anything else: a color constant. + * (colors always start with # or a letter) + */ + if (value[0] == '.' || value[0] == '+' || value[0] == '-' || (value[0] >= '0' && value[0] <= '9')) + { + if (strchr (value, '.')) + { + if (!parse_double (value, &dval, context, error)) + return; + + if (!meta_theme_define_float_constant (info->theme, + name, + dval, + error)) + { + add_context_to_error (error, context); + return; + } + } + else + { + if (!parse_positive_integer (value, &ival, context, info->theme, error)) + return; + + if (!meta_theme_define_int_constant (info->theme, + name, + ival, + error)) + { + add_context_to_error (error, context); + return; + } + } + } + else + { + if (!meta_theme_define_color_constant (info->theme, + name, + value, + error)) + { + add_context_to_error (error, context); + return; + } + } + + push_state (info, STATE_CONSTANT); + } + else if (ELEMENT_IS ("frame_geometry")) + { + const char *name = NULL; + const char *parent = NULL; + const char *has_title = NULL; + const char *title_scale = NULL; + const char *rounded_top_left = NULL; + const char *rounded_top_right = NULL; + const char *rounded_bottom_left = NULL; + const char *rounded_bottom_right = NULL; + const char *hide_buttons = NULL; + gboolean has_title_val; + guint rounded_top_left_val; + guint rounded_top_right_val; + guint rounded_bottom_left_val; + guint rounded_bottom_right_val; + gboolean hide_buttons_val; + double title_scale_val; + MetaFrameLayout *parent_layout; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, "parent", &parent, + "has_title", &has_title, "title_scale", &title_scale, + "rounded_top_left", &rounded_top_left, + "rounded_top_right", &rounded_top_right, + "rounded_bottom_left", &rounded_bottom_left, + "rounded_bottom_right", &rounded_bottom_right, + "hide_buttons", &hide_buttons, + NULL)) + return; + + has_title_val = TRUE; + if (has_title && !parse_boolean (has_title, &has_title_val, context, error)) + return; + + hide_buttons_val = FALSE; + if (hide_buttons && !parse_boolean (hide_buttons, &hide_buttons_val, context, error)) + return; + + rounded_top_left_val = 0; + rounded_top_right_val = 0; + rounded_bottom_left_val = 0; + rounded_bottom_right_val = 0; + + if (rounded_top_left && !parse_rounding (rounded_top_left, &rounded_top_left_val, context, info->theme, error)) + return; + if (rounded_top_right && !parse_rounding (rounded_top_right, &rounded_top_right_val, context, info->theme, error)) + return; + if (rounded_bottom_left && !parse_rounding (rounded_bottom_left, &rounded_bottom_left_val, context, info->theme, error)) + return; + if (rounded_bottom_right && !parse_rounding (rounded_bottom_right, &rounded_bottom_right_val, context, info->theme, error)) + return; + + title_scale_val = 1.0; + if (title_scale && !parse_title_scale (title_scale, &title_scale_val, context, error)) + return; + + if (meta_theme_lookup_layout (info->theme, name)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> name \"%s\" used a second time"), + element_name, name); + return; + } + + parent_layout = NULL; + if (parent) + { + parent_layout = meta_theme_lookup_layout (info->theme, parent); + if (parent_layout == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> parent \"%s\" has not been defined"), + element_name, parent); + return; + } + } + + g_assert (info->layout == NULL); + + if (parent_layout) + info->layout = meta_frame_layout_copy (parent_layout); + else + info->layout = meta_frame_layout_new (); + + if (has_title) /* only if explicit, otherwise inherit */ + info->layout->has_title = has_title_val; + + if (META_THEME_ALLOWS (info->theme, META_THEME_HIDDEN_BUTTONS) && hide_buttons_val) + info->layout->hide_buttons = hide_buttons_val; + + if (title_scale) + info->layout->title_scale = title_scale_val; + + if (rounded_top_left) + info->layout->top_left_corner_rounded_radius = rounded_top_left_val; + + if (rounded_top_right) + info->layout->top_right_corner_rounded_radius = rounded_top_right_val; + + if (rounded_bottom_left) + info->layout->bottom_left_corner_rounded_radius = rounded_bottom_left_val; + + if (rounded_bottom_right) + info->layout->bottom_right_corner_rounded_radius = rounded_bottom_right_val; + + meta_theme_insert_layout (info->theme, name, info->layout); + + push_state (info, STATE_FRAME_GEOMETRY); + } + else if (ELEMENT_IS ("draw_ops")) + { + const char *name = NULL; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, + NULL)) + return; + + if (meta_theme_lookup_draw_op_list (info->theme, name)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> name \"%s\" used a second time"), + element_name, name); + return; + } + + g_assert (info->op_list == NULL); + info->op_list = meta_draw_op_list_new (2); + + meta_theme_insert_draw_op_list (info->theme, name, info->op_list); + + push_state (info, STATE_DRAW_OPS); + } + else if (ELEMENT_IS ("frame_style")) + { + const char *name = NULL; + const char *parent = NULL; + const char *geometry = NULL; + const char *background = NULL; + const char *alpha = NULL; + MetaFrameStyle *parent_style; + MetaFrameLayout *layout; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, "parent", &parent, + "geometry", &geometry, + "background", &background, + "alpha", &alpha, + NULL)) + return; + + if (meta_theme_lookup_style (info->theme, name)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> name \"%s\" used a second time"), + element_name, name); + return; + } + + parent_style = NULL; + if (parent) + { + parent_style = meta_theme_lookup_style (info->theme, parent); + if (parent_style == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> parent \"%s\" has not been defined"), + element_name, parent); + return; + } + } + + layout = NULL; + if (geometry) + { + layout = meta_theme_lookup_layout (info->theme, geometry); + if (layout == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> geometry \"%s\" has not been defined"), + element_name, geometry); + return; + } + } + else if (parent_style) + { + layout = parent_style->layout; + } + + if (layout == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> must specify either a geometry or a parent that has a geometry"), + element_name); + return; + } + + g_assert (info->style == NULL); + + info->style = meta_frame_style_new (parent_style); + g_assert (info->style->layout == NULL); + meta_frame_layout_ref (layout); + info->style->layout = layout; + + if (background != NULL && META_THEME_ALLOWS (info->theme, META_THEME_FRAME_BACKGROUNDS)) + { + info->style->window_background_color = meta_color_spec_new_from_string (background, error); + if (!info->style->window_background_color) + return; + + if (alpha != NULL) + { + + gboolean success; + MetaAlphaGradientSpec *alpha_vector; + + g_clear_error (error); + /* fortunately, we already have a routine to parse alpha values, + * though it produces a vector of them, which is a superset of + * what we want. + */ + success = parse_alpha (alpha, &alpha_vector, context, error); + if (!success) + return; + + /* alpha_vector->alphas must contain at least one element */ + info->style->window_background_alpha = alpha_vector->alphas[0]; + + meta_alpha_gradient_spec_free (alpha_vector); + } + } + else if (alpha != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("You must specify a background for an alpha value to be meaningful")); + return; + } + + meta_theme_insert_style (info->theme, name, info->style); + + push_state (info, STATE_FRAME_STYLE); + } + else if (ELEMENT_IS ("frame_style_set")) + { + const char *name = NULL; + const char *parent = NULL; + MetaFrameStyleSet *parent_set; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, "parent", &parent, + NULL)) + return; + + if (meta_theme_lookup_style_set (info->theme, name)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> name \"%s\" used a second time"), + element_name, name); + return; + } + + parent_set = NULL; + if (parent) + { + parent_set = meta_theme_lookup_style_set (info->theme, parent); + if (parent_set == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> parent \"%s\" has not been defined"), + element_name, parent); + return; + } + } + + g_assert (info->style_set == NULL); + + info->style_set = meta_frame_style_set_new (parent_set); + + meta_theme_insert_style_set (info->theme, name, info->style_set); + + push_state (info, STATE_FRAME_STYLE_SET); + } + else if (ELEMENT_IS ("window")) + { + const char *type_name = NULL; + const char *style_set_name = NULL; + MetaFrameStyleSet *style_set; + MetaFrameType type; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!type", &type_name, "!style_set", &style_set_name, + NULL)) + return; + + type = meta_frame_type_from_string (type_name); + + if (type == META_FRAME_TYPE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Unknown type \"%s\" on <%s> element"), + type_name, element_name); + return; + } + + style_set = meta_theme_lookup_style_set (info->theme, + style_set_name); + + if (style_set == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Unknown style_set \"%s\" on <%s> element"), + style_set_name, element_name); + return; + } + + if (info->theme->style_sets_by_type[type] != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Window type \"%s\" has already been assigned a style set"), + type_name); + return; + } + + meta_frame_style_set_ref (style_set); + info->theme->style_sets_by_type[type] = style_set; + + push_state (info, STATE_WINDOW); + } + else if (ELEMENT_IS ("menu_icon")) + { + /* Not supported any more, but we have to parse it if they include it, + * for backwards compatibility. + */ + g_assert (info->op_list == NULL); + + push_state (info, STATE_MENU_ICON); + } + else if (ELEMENT_IS ("fallback")) + { + /* Not supported any more, but we have to parse it if they include it, + * for backwards compatibility. + */ + push_state (info, STATE_FALLBACK); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "metacity_theme"); + } +} + +static void +parse_info_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_INFO); + + if (ELEMENT_IS ("name")) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_NAME); + } + else if (ELEMENT_IS ("author")) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_AUTHOR); + } + else if (ELEMENT_IS ("copyright")) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_COPYRIGHT); + } + else if (ELEMENT_IS ("description")) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_DESCRIPTION); + } + else if (ELEMENT_IS ("date")) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_DATE); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "info"); + } +} + +static void +parse_distance (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + const char *name; + const char *value; + int val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, "!value", &value, + NULL)) + return; + + val = 0; + if (!parse_positive_integer (value, &val, context, info->theme, error)) + return; + + g_assert (val >= 0); /* yeah, "non-negative" not "positive" get over it */ + g_assert (info->layout); + + if (strcmp (name, "left_width") == 0) + info->layout->left_width = val; + else if (strcmp (name, "right_width") == 0) + info->layout->right_width = val; + else if (strcmp (name, "bottom_height") == 0) + info->layout->bottom_height = val; + else if (strcmp (name, "title_vertical_pad") == 0) + info->layout->title_vertical_pad = val; + else if (strcmp (name, "right_titlebar_edge") == 0) + info->layout->right_titlebar_edge = val; + else if (strcmp (name, "left_titlebar_edge") == 0) + info->layout->left_titlebar_edge = val; + else if (strcmp (name, "button_width") == 0) + { + info->layout->button_width = val; + + if (!(info->layout->button_sizing == META_BUTTON_SIZING_LAST || + info->layout->button_sizing == META_BUTTON_SIZING_FIXED)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Cannot specify both \"button_width\"/\"button_height\" and \"aspect_ratio\" for buttons")); + return; + } + + info->layout->button_sizing = META_BUTTON_SIZING_FIXED; + } + else if (strcmp (name, "button_height") == 0) + { + info->layout->button_height = val; + + if (!(info->layout->button_sizing == META_BUTTON_SIZING_LAST || + info->layout->button_sizing == META_BUTTON_SIZING_FIXED)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Cannot specify both \"button_width\"/\"button_height\" and \"aspect_ratio\" for buttons")); + return; + } + + info->layout->button_sizing = META_BUTTON_SIZING_FIXED; + } + else + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Distance \"%s\" is unknown"), name); + return; + } +} + +static void +parse_aspect_ratio (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + const char *name; + const char *value; + double val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, "!value", &value, + NULL)) + return; + + val = 0; + if (!parse_double (value, &val, context, error)) + return; + + g_assert (info->layout); + + if (strcmp (name, "button") == 0) + { + info->layout->button_aspect = val; + + if (info->layout->button_sizing != META_BUTTON_SIZING_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Cannot specify both \"button_width\"/\"button_height\" and \"aspect_ratio\" for buttons")); + return; + } + + info->layout->button_sizing = META_BUTTON_SIZING_ASPECT; + } + else + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Aspect ratio \"%s\" is unknown"), name); + return; + } +} + +static void +parse_border (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + const char *name; + const char *top; + const char *bottom; + const char *left; + const char *right; + int top_val; + int bottom_val; + int left_val; + int right_val; + GtkBorder *border; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, + "!top", &top, + "!bottom", &bottom, + "!left", &left, + "!right", &right, + NULL)) + return; + + top_val = 0; + if (!parse_positive_integer (top, &top_val, context, info->theme, error)) + return; + + bottom_val = 0; + if (!parse_positive_integer (bottom, &bottom_val, context, info->theme, error)) + return; + + left_val = 0; + if (!parse_positive_integer (left, &left_val, context, info->theme, error)) + return; + + right_val = 0; + if (!parse_positive_integer (right, &right_val, context, info->theme, error)) + return; + + g_assert (info->layout); + + border = NULL; + + if (strcmp (name, "title_border") == 0) + border = &info->layout->title_border; + else if (strcmp (name, "button_border") == 0) + border = &info->layout->button_border; + + if (border == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Border \"%s\" is unknown"), name); + return; + } + + border->top = top_val; + border->bottom = bottom_val; + border->left = left_val; + border->right = right_val; +} + +static void +parse_geometry_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_FRAME_GEOMETRY); + + if (ELEMENT_IS ("distance")) + { + parse_distance (context, element_name, + attribute_names, attribute_values, + info, error); + push_state (info, STATE_DISTANCE); + } + else if (ELEMENT_IS ("border")) + { + parse_border (context, element_name, + attribute_names, attribute_values, + info, error); + push_state (info, STATE_BORDER); + } + else if (ELEMENT_IS ("aspect_ratio")) + { + parse_aspect_ratio (context, element_name, + attribute_names, attribute_values, + info, error); + + push_state (info, STATE_ASPECT_RATIO); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "frame_geometry"); + } +} + +#if 0 +static gboolean +check_expression (PosToken *tokens, + int n_tokens, + gboolean has_object, + MetaTheme *theme, + GMarkupParseContext *context, + GError **error) +{ + MetaPositionExprEnv env; + int x, y; + + /* We set it all to 0 to try and catch divide-by-zero screwups. + * it's possible we should instead guarantee that widths and heights + * are at least 1. + */ + + env.rect = meta_rect (0, 0, 0, 0); + if (has_object) + { + env.object_width = 0; + env.object_height = 0; + } + else + { + env.object_width = -1; + env.object_height = -1; + } + + env.left_width = 0; + env.right_width = 0; + env.top_height = 0; + env.bottom_height = 0; + env.title_width = 0; + env.title_height = 0; + + env.icon_width = 0; + env.icon_height = 0; + env.mini_icon_width = 0; + env.mini_icon_height = 0; + env.theme = theme; + + if (!meta_parse_position_expression (tokens, n_tokens, + &env, + &x, &y, + error)) + { + add_context_to_error (error, context); + return FALSE; + } + + return TRUE; +} +#endif + +static void +parse_draw_op_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_DRAW_OPS); + + if (ELEMENT_IS ("line")) + { + MetaDrawOp *op; + const char *color; + const char *x1; + const char *y1; + const char *x2; + const char *y2; + const char *dash_on_length; + const char *dash_off_length; + const char *width; + MetaColorSpec *color_spec; + int dash_on_val; + int dash_off_val; + int width_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!color", &color, + "!x1", &x1, "!y1", &y1, + "!x2", &x2, "!y2", &y2, + "dash_on_length", &dash_on_length, + "dash_off_length", &dash_off_length, + "width", &width, + NULL)) + return; + +#if 0 + if (!check_expression (x1, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y1, FALSE, info->theme, context, error)) + return; + + if (!check_expression (x2, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y2, FALSE, info->theme, context, error)) + return; +#endif + + dash_on_val = 0; + if (dash_on_length && + !parse_positive_integer (dash_on_length, &dash_on_val, context, info->theme, error)) + return; + + dash_off_val = 0; + if (dash_off_length && + !parse_positive_integer (dash_off_length, &dash_off_val, context, info->theme, error)) + return; + + width_val = 0; + if (width && + !parse_positive_integer (width, &width_val, context, info->theme, error)) + return; + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = parse_color (info->theme, color, error); + if (color_spec == NULL) + { + add_context_to_error (error, context); + return; + } + + op = meta_draw_op_new (META_DRAW_LINE); + + op->data.line.color_spec = color_spec; + + op->data.line.x1 = meta_draw_spec_new (info->theme, x1, NULL); + op->data.line.y1 = meta_draw_spec_new (info->theme, y1, NULL); + + if (strcmp(x1, x2)==0) + op->data.line.x2 = NULL; + else + op->data.line.x2 = meta_draw_spec_new (info->theme, x2, NULL); + + if (strcmp(y1, y2)==0) + op->data.line.y2 = NULL; + else + op->data.line.y2 = meta_draw_spec_new (info->theme, y2, NULL); + + op->data.line.width = width_val; + op->data.line.dash_on_length = dash_on_val; + op->data.line.dash_off_length = dash_off_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_LINE); + } + else if (ELEMENT_IS ("rectangle")) + { + MetaDrawOp *op; + const char *color; + const char *x; + const char *y; + const char *width; + const char *height; + const char *filled; + gboolean filled_val; + MetaColorSpec *color_spec; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!color", &color, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + "filled", &filled, + NULL)) + return; + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; +#endif + + filled_val = FALSE; + if (filled && !parse_boolean (filled, &filled_val, context, error)) + return; + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = parse_color (info->theme, color, error); + if (color_spec == NULL) + { + add_context_to_error (error, context); + return; + } + + op = meta_draw_op_new (META_DRAW_RECTANGLE); + + op->data.rectangle.color_spec = color_spec; + op->data.rectangle.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.rectangle.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.rectangle.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.rectangle.height = meta_draw_spec_new (info->theme, + height, NULL); + + op->data.rectangle.filled = filled_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_RECTANGLE); + } + else if (ELEMENT_IS ("arc")) + { + MetaDrawOp *op; + const char *color; + const char *x; + const char *y; + const char *width; + const char *height; + const char *filled; + const char *start_angle; + const char *extent_angle; + const char *from; + const char *to; + gboolean filled_val; + double start_angle_val; + double extent_angle_val; + MetaColorSpec *color_spec; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!color", &color, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + "filled", &filled, + "start_angle", &start_angle, + "extent_angle", &extent_angle, + "from", &from, + "to", &to, + NULL)) + return; + + if (META_THEME_ALLOWS (info->theme, META_THEME_DEGREES_IN_ARCS) ) + { + if (start_angle == NULL && from == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"start_angle\" or \"from\" attribute on element <%s>"), element_name); + return; + } + + if (extent_angle == NULL && to == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"extent_angle\" or \"to\" attribute on element <%s>"), element_name); + return; + } + } + else + { + if (start_angle == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + ATTRIBUTE_NOT_FOUND, "start_angle", element_name); + return; + } + + if (extent_angle == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + ATTRIBUTE_NOT_FOUND, "extent_angle", element_name); + return; + } + } + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; +#endif + + if (start_angle == NULL) + { + if (!parse_angle (from, &start_angle_val, context, error)) + return; + + start_angle_val = (180-start_angle_val)/360.0; + } + else + { + if (!parse_angle (start_angle, &start_angle_val, context, error)) + return; + } + + if (extent_angle == NULL) + { + if (!parse_angle (to, &extent_angle_val, context, error)) + return; + + extent_angle_val = ((180-extent_angle_val)/360.0) - start_angle_val; + } + else + { + if (!parse_angle (extent_angle, &extent_angle_val, context, error)) + return; + } + + filled_val = FALSE; + if (filled && !parse_boolean (filled, &filled_val, context, error)) + return; + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = parse_color (info->theme, color, error); + if (color_spec == NULL) + { + add_context_to_error (error, context); + return; + } + + op = meta_draw_op_new (META_DRAW_ARC); + + op->data.arc.color_spec = color_spec; + + op->data.arc.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.arc.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.arc.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.arc.height = meta_draw_spec_new (info->theme, height, NULL); + + op->data.arc.filled = filled_val; + op->data.arc.start_angle = start_angle_val; + op->data.arc.extent_angle = extent_angle_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_ARC); + } + else if (ELEMENT_IS ("clip")) + { + MetaDrawOp *op; + const char *x; + const char *y; + const char *width; + const char *height; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + NULL)) + return; + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; +#endif + op = meta_draw_op_new (META_DRAW_CLIP); + + op->data.clip.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.clip.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.clip.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.clip.height = meta_draw_spec_new (info->theme, height, NULL); + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_CLIP); + } + else if (ELEMENT_IS ("tint")) + { + MetaDrawOp *op; + const char *color; + const char *x; + const char *y; + const char *width; + const char *height; + const char *alpha; + MetaAlphaGradientSpec *alpha_spec; + MetaColorSpec *color_spec; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!color", &color, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + "!alpha", &alpha, + NULL)) + return; + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; +#endif + alpha_spec = NULL; + if (!parse_alpha (alpha, &alpha_spec, context, error)) + return; + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = parse_color (info->theme, color, error); + if (color_spec == NULL) + { + if (alpha_spec) + meta_alpha_gradient_spec_free (alpha_spec); + + add_context_to_error (error, context); + return; + } + + op = meta_draw_op_new (META_DRAW_TINT); + + op->data.tint.color_spec = color_spec; + op->data.tint.alpha_spec = alpha_spec; + + op->data.tint.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.tint.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.tint.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.tint.height = meta_draw_spec_new (info->theme, height, NULL); + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_TINT); + } + else if (ELEMENT_IS ("gradient")) + { + const char *x; + const char *y; + const char *width; + const char *height; + const char *type; + const char *alpha; + MetaAlphaGradientSpec *alpha_spec; + MetaGradientType type_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!type", &type, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + "alpha", &alpha, + NULL)) + return; + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; +#endif + + type_val = meta_gradient_type_from_string (type); + if (type_val == META_GRADIENT_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Did not understand value \"%s\" for type of gradient"), + type); + return; + } + + alpha_spec = NULL; + if (alpha && !parse_alpha (alpha, &alpha_spec, context, error)) + return; + + g_assert (info->op == NULL); + info->op = meta_draw_op_new (META_DRAW_GRADIENT); + + info->op->data.gradient.x = meta_draw_spec_new (info->theme, x, NULL); + info->op->data.gradient.y = meta_draw_spec_new (info->theme, y, NULL); + info->op->data.gradient.width = meta_draw_spec_new (info->theme, + width, NULL); + info->op->data.gradient.height = meta_draw_spec_new (info->theme, + height, NULL); + + info->op->data.gradient.gradient_spec = meta_gradient_spec_new (type_val); + + info->op->data.gradient.alpha_spec = alpha_spec; + + push_state (info, STATE_GRADIENT); + + /* op gets appended on close tag */ + } + else if (ELEMENT_IS ("image")) + { + MetaDrawOp *op; + const char *filename; + const char *x; + const char *y; + const char *width; + const char *height; + const char *alpha; + const char *colorize; + const char *fill_type; + MetaAlphaGradientSpec *alpha_spec; + GdkPixbuf *pixbuf; + MetaColorSpec *colorize_spec = NULL; + MetaImageFillType fill_type_val; + int h, w, c; + int pixbuf_width, pixbuf_height, pixbuf_n_channels, pixbuf_rowstride; + guchar *pixbuf_pixels; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + "alpha", &alpha, "!filename", &filename, + "colorize", &colorize, + "fill_type", &fill_type, + NULL)) + return; + +#if 0 + if (!check_expression (x, TRUE, info->theme, context, error)) + return; + + if (!check_expression (y, TRUE, info->theme, context, error)) + return; + + if (!check_expression (width, TRUE, info->theme, context, error)) + return; + + if (!check_expression (height, TRUE, info->theme, context, error)) + return; +#endif + fill_type_val = META_IMAGE_FILL_SCALE; + if (fill_type) + { + fill_type_val = meta_image_fill_type_from_string (fill_type); + + if (((int) fill_type_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand fill type \"%s\" for <%s> element"), + fill_type, element_name); + } + } + + /* Check last so we don't have to free it when other + * stuff fails. + * + * If it's a theme image, ask for it at 64px, which is + * the largest possible. We scale it anyway. + */ + pixbuf = meta_theme_load_image (info->theme, filename, 64, error); + + if (pixbuf == NULL) + { + add_context_to_error (error, context); + return; + } + + if (colorize) + { + colorize_spec = parse_color (info->theme, colorize, error); + + if (colorize_spec == NULL) + { + add_context_to_error (error, context); + g_object_unref (G_OBJECT (pixbuf)); + return; + } + } + + alpha_spec = NULL; + if (alpha && !parse_alpha (alpha, &alpha_spec, context, error)) + { + g_object_unref (G_OBJECT (pixbuf)); + return; + } + + op = meta_draw_op_new (META_DRAW_IMAGE); + + op->data.image.pixbuf = pixbuf; + op->data.image.colorize_spec = colorize_spec; + + op->data.image.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.image.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.image.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.image.height = meta_draw_spec_new (info->theme, height, NULL); + + op->data.image.alpha_spec = alpha_spec; + op->data.image.fill_type = fill_type_val; + + /* Check for vertical & horizontal stripes */ + pixbuf_n_channels = gdk_pixbuf_get_n_channels(pixbuf); + pixbuf_width = gdk_pixbuf_get_width(pixbuf); + pixbuf_height = gdk_pixbuf_get_height(pixbuf); + pixbuf_rowstride = gdk_pixbuf_get_rowstride(pixbuf); + pixbuf_pixels = gdk_pixbuf_get_pixels(pixbuf); + + /* Check for horizontal stripes */ + for (h = 0; h < pixbuf_height; h++) + { + for (w = 1; w < pixbuf_width; w++) + { + for (c = 0; c < pixbuf_n_channels; c++) + { + if (pixbuf_pixels[(h * pixbuf_rowstride) + c] != + pixbuf_pixels[(h * pixbuf_rowstride) + w + c]) + break; + } + if (c < pixbuf_n_channels) + break; + } + if (w < pixbuf_width) + break; + } + + if (h >= pixbuf_height) + { + op->data.image.horizontal_stripes = TRUE; + } + else + { + op->data.image.horizontal_stripes = FALSE; + } + + /* Check for vertical stripes */ + for (w = 0; w < pixbuf_width; w++) + { + for (h = 1; h < pixbuf_height; h++) + { + for (c = 0; c < pixbuf_n_channels; c++) + { + if (pixbuf_pixels[w + c] != + pixbuf_pixels[(h * pixbuf_rowstride) + w + c]) + break; + } + if (c < pixbuf_n_channels) + break; + } + if (h < pixbuf_height) + break; + } + + if (w >= pixbuf_width) + { + op->data.image.vertical_stripes = TRUE; + } + else + { + op->data.image.vertical_stripes = FALSE; + } + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_IMAGE); + } + else if (ELEMENT_IS ("gtk_arrow")) + { + MetaDrawOp *op; + const char *state; + const char *shadow; + const char *arrow; + const char *x; + const char *y; + const char *width; + const char *height; + const char *filled; + gboolean filled_val; + GtkStateType state_val; + GtkShadowType shadow_val; + GtkArrowType arrow_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!state", &state, + "!shadow", &shadow, + "!arrow", &arrow, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + "filled", &filled, + NULL)) + return; + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; +#endif + filled_val = TRUE; + if (filled && !parse_boolean (filled, &filled_val, context, error)) + return; + + state_val = meta_gtk_state_from_string (state); + if (((int) state_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand state \"%s\" for <%s> element"), + state, element_name); + return; + } + + shadow_val = meta_gtk_shadow_from_string (shadow); + if (((int) shadow_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand shadow \"%s\" for <%s> element"), + shadow, element_name); + return; + } + + arrow_val = meta_gtk_arrow_from_string (arrow); + if (((int) arrow_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand arrow \"%s\" for <%s> element"), + arrow, element_name); + return; + } + + op = meta_draw_op_new (META_DRAW_GTK_ARROW); + + op->data.gtk_arrow.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.gtk_arrow.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.gtk_arrow.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.gtk_arrow.height = meta_draw_spec_new (info->theme, + height, NULL); + + op->data.gtk_arrow.filled = filled_val; + op->data.gtk_arrow.state = state_val; + op->data.gtk_arrow.shadow = shadow_val; + op->data.gtk_arrow.arrow = arrow_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_GTK_ARROW); + } + else if (ELEMENT_IS ("gtk_box")) + { + MetaDrawOp *op; + const char *state; + const char *shadow; + const char *x; + const char *y; + const char *width; + const char *height; + GtkStateType state_val; + GtkShadowType shadow_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!state", &state, + "!shadow", &shadow, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + NULL)) + return; + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; +#endif + state_val = meta_gtk_state_from_string (state); + if (((int) state_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand state \"%s\" for <%s> element"), + state, element_name); + return; + } + + shadow_val = meta_gtk_shadow_from_string (shadow); + if (((int) shadow_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand shadow \"%s\" for <%s> element"), + shadow, element_name); + return; + } + + op = meta_draw_op_new (META_DRAW_GTK_BOX); + + op->data.gtk_box.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.gtk_box.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.gtk_box.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.gtk_box.height = meta_draw_spec_new (info->theme, height, NULL); + + op->data.gtk_box.state = state_val; + op->data.gtk_box.shadow = shadow_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_GTK_BOX); + } + else if (ELEMENT_IS ("gtk_vline")) + { + MetaDrawOp *op; + const char *state; + const char *x; + const char *y1; + const char *y2; + GtkStateType state_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!state", &state, + "!x", &x, "!y1", &y1, "!y2", &y2, + NULL)) + return; + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y1, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y2, FALSE, info->theme, context, error)) + return; +#endif + + state_val = meta_gtk_state_from_string (state); + if (((int) state_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand state \"%s\" for <%s> element"), + state, element_name); + return; + } + + op = meta_draw_op_new (META_DRAW_GTK_VLINE); + + op->data.gtk_vline.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.gtk_vline.y1 = meta_draw_spec_new (info->theme, y1, NULL); + op->data.gtk_vline.y2 = meta_draw_spec_new (info->theme, y2, NULL); + + op->data.gtk_vline.state = state_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_GTK_VLINE); + } + else if (ELEMENT_IS ("icon")) + { + MetaDrawOp *op; + const char *x; + const char *y; + const char *width; + const char *height; + const char *alpha; + const char *fill_type; + MetaAlphaGradientSpec *alpha_spec; + MetaImageFillType fill_type_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + "alpha", &alpha, + "fill_type", &fill_type, + NULL)) + return; + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; +#endif + fill_type_val = META_IMAGE_FILL_SCALE; + if (fill_type) + { + fill_type_val = meta_image_fill_type_from_string (fill_type); + + if (((int) fill_type_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand fill type \"%s\" for <%s> element"), + fill_type, element_name); + } + } + + alpha_spec = NULL; + if (alpha && !parse_alpha (alpha, &alpha_spec, context, error)) + return; + + op = meta_draw_op_new (META_DRAW_ICON); + + op->data.icon.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.icon.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.icon.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.icon.height = meta_draw_spec_new (info->theme, height, NULL); + + op->data.icon.alpha_spec = alpha_spec; + op->data.icon.fill_type = fill_type_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_ICON); + } + else if (ELEMENT_IS ("title")) + { + MetaDrawOp *op; + const char *color; + const char *x; + const char *y; + MetaColorSpec *color_spec; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!color", &color, + "!x", &x, "!y", &y, + NULL)) + return; + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; +#endif + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = parse_color (info->theme, color, error); + if (color_spec == NULL) + { + add_context_to_error (error, context); + return; + } + + op = meta_draw_op_new (META_DRAW_TITLE); + + op->data.title.color_spec = color_spec; + + op->data.title.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.title.y = meta_draw_spec_new (info->theme, y, NULL); + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_TITLE); + } + else if (ELEMENT_IS ("include")) + { + MetaDrawOp *op; + const char *name; + const char *x; + const char *y; + const char *width; + const char *height; + MetaDrawOpList *op_list; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "x", &x, "y", &y, + "width", &width, "height", &height, + "!name", &name, + NULL)) + return; + + /* x/y/width/height default to 0,0,width,height - should + * probably do this for all the draw ops + */ +#if 0 + if (x && !check_expression (x, FALSE, info->theme, context, error)) + return; + + if (y && !check_expression (y, FALSE, info->theme, context, error)) + return; + + if (width && !check_expression (width, FALSE, info->theme, context, error)) + return; + + if (height && !check_expression (height, FALSE, info->theme, context, error)) + return; +#endif + + op_list = meta_theme_lookup_draw_op_list (info->theme, + name); + if (op_list == NULL) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("No <draw_ops> called \"%s\" has been defined"), + name); + return; + } + + g_assert (info->op_list); + + if (op_list == info->op_list || + meta_draw_op_list_contains (op_list, info->op_list)) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Including draw_ops \"%s\" here would create a circular reference"), + name); + return; + } + + op = meta_draw_op_new (META_DRAW_OP_LIST); + + meta_draw_op_list_ref (op_list); + op->data.op_list.op_list = op_list; + + op->data.op_list.x = meta_draw_spec_new (info->theme, x ? x : "0", NULL); + op->data.op_list.y = meta_draw_spec_new (info->theme, y ? y : "0", NULL); + op->data.op_list.width = meta_draw_spec_new (info->theme, + width ? width : "width", + NULL); + op->data.op_list.height = meta_draw_spec_new (info->theme, + height ? height : "height", + NULL); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_INCLUDE); + } + else if (ELEMENT_IS ("tile")) + { + MetaDrawOp *op; + const char *name; + const char *x; + const char *y; + const char *width; + const char *height; + const char *tile_xoffset; + const char *tile_yoffset; + const char *tile_width; + const char *tile_height; + MetaDrawOpList *op_list; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "x", &x, "y", &y, + "width", &width, "height", &height, + "!name", &name, + "tile_xoffset", &tile_xoffset, + "tile_yoffset", &tile_yoffset, + "!tile_width", &tile_width, + "!tile_height", &tile_height, + NULL)) + return; + + /* These default to 0 */ +#if 0 + if (tile_xoffset && !check_expression (tile_xoffset, FALSE, info->theme, context, error)) + return; + + if (tile_yoffset && !check_expression (tile_yoffset, FALSE, info->theme, context, error)) + return; + + /* x/y/width/height default to 0,0,width,height - should + * probably do this for all the draw ops + */ + if (x && !check_expression (x, FALSE, info->theme, context, error)) + return; + + if (y && !check_expression (y, FALSE, info->theme, context, error)) + return; + + if (width && !check_expression (width, FALSE, info->theme, context, error)) + return; + + if (height && !check_expression (height, FALSE, info->theme, context, error)) + return; + + if (!check_expression (tile_width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (tile_height, FALSE, info->theme, context, error)) + return; +#endif + op_list = meta_theme_lookup_draw_op_list (info->theme, + name); + if (op_list == NULL) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("No <draw_ops> called \"%s\" has been defined"), + name); + return; + } + + g_assert (info->op_list); + + if (op_list == info->op_list || + meta_draw_op_list_contains (op_list, info->op_list)) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Including draw_ops \"%s\" here would create a circular reference"), + name); + return; + } + + op = meta_draw_op_new (META_DRAW_TILE); + + meta_draw_op_list_ref (op_list); + + op->data.tile.x = meta_draw_spec_new (info->theme, x ? x : "0", NULL); + op->data.tile.y = meta_draw_spec_new (info->theme, y ? y : "0", NULL); + op->data.tile.width = meta_draw_spec_new (info->theme, + width ? width : "width", + NULL); + op->data.tile.height = meta_draw_spec_new (info->theme, + height ? height : "height", + NULL); + op->data.tile.tile_xoffset = meta_draw_spec_new (info->theme, + tile_xoffset ? tile_xoffset : "0", + NULL); + op->data.tile.tile_yoffset = meta_draw_spec_new (info->theme, + tile_yoffset ? tile_yoffset : "0", + NULL); + op->data.tile.tile_width = meta_draw_spec_new (info->theme, tile_width, NULL); + op->data.tile.tile_height = meta_draw_spec_new (info->theme, tile_height, NULL); + + op->data.tile.op_list = op_list; + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_TILE); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "draw_ops"); + } +} + +static void +parse_gradient_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_GRADIENT); + + if (ELEMENT_IS ("color")) + { + const char *value = NULL; + MetaColorSpec *color_spec; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!value", &value, + NULL)) + return; + + color_spec = parse_color (info->theme, value, error); + if (color_spec == NULL) + { + add_context_to_error (error, context); + return; + } + + g_assert (info->op); + g_assert (info->op->type == META_DRAW_GRADIENT); + g_assert (info->op->data.gradient.gradient_spec != NULL); + info->op->data.gradient.gradient_spec->color_specs = + g_slist_append (info->op->data.gradient.gradient_spec->color_specs, + color_spec); + + push_state (info, STATE_COLOR); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "gradient"); + } +} + +static void +parse_style_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_FRAME_STYLE); + + g_assert (info->style); + + if (ELEMENT_IS ("piece")) + { + const char *position = NULL; + const char *draw_ops = NULL; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!position", &position, + "draw_ops", &draw_ops, + NULL)) + return; + + info->piece = meta_frame_piece_from_string (position); + if (info->piece == META_FRAME_PIECE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Unknown position \"%s\" for frame piece"), + position); + return; + } + + if (info->style->pieces[info->piece] != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Frame style already has a piece at position %s"), + position); + return; + } + + g_assert (info->op_list == NULL); + + if (draw_ops) + { + MetaDrawOpList *op_list; + + op_list = meta_theme_lookup_draw_op_list (info->theme, + draw_ops); + + if (op_list == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No <draw_ops> with the name \"%s\" has been defined"), + draw_ops); + return; + } + + meta_draw_op_list_ref (op_list); + info->op_list = op_list; + } + + push_state (info, STATE_PIECE); + } + else if (ELEMENT_IS ("button")) + { + const char *function = NULL; + const char *state = NULL; + const char *draw_ops = NULL; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!function", &function, + "!state", &state, + "draw_ops", &draw_ops, + NULL)) + return; + + info->button_type = meta_button_type_from_string (function, info->theme); + if (info->button_type == META_BUTTON_TYPE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Unknown function \"%s\" for button"), + function); + return; + } + + if (meta_theme_earliest_version_with_button (info->button_type) > + info->theme->format_version) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Button function \"%s\" does not exist in this version (%d, need %d)"), + function, + info->theme->format_version, + meta_theme_earliest_version_with_button (info->button_type) + ); + return; + } + + info->button_state = meta_button_state_from_string (state); + if (info->button_state == META_BUTTON_STATE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Unknown state \"%s\" for button"), + state); + return; + } + + if (info->style->buttons[info->button_type][info->button_state] != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Frame style already has a button for function %s state %s"), + function, state); + return; + } + + g_assert (info->op_list == NULL); + + if (draw_ops) + { + MetaDrawOpList *op_list; + + op_list = meta_theme_lookup_draw_op_list (info->theme, + draw_ops); + + if (op_list == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No <draw_ops> with the name \"%s\" has been defined"), + draw_ops); + return; + } + + meta_draw_op_list_ref (op_list); + info->op_list = op_list; + } + + push_state (info, STATE_BUTTON); + } +#ifdef USE_UBUNTU_CODE + else if (ELEMENT_IS ("shadow")) + { + push_state (info, STATE_SHADOW); + } + else if (ELEMENT_IS ("padding")) + { + push_state (info, STATE_PADDING); + } +#endif + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "frame_style"); + } +} + +static void +parse_style_set_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_FRAME_STYLE_SET); + + if (ELEMENT_IS ("frame")) + { + const char *focus = NULL; + const char *state = NULL; + const char *resize = NULL; + const char *style = NULL; + MetaFrameFocus frame_focus; + MetaFrameState frame_state; + MetaFrameResize frame_resize; + MetaFrameStyle *frame_style; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!focus", &focus, + "!state", &state, + "resize", &resize, + "!style", &style, + NULL)) + return; + + frame_focus = meta_frame_focus_from_string (focus); + if (frame_focus == META_FRAME_FOCUS_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("\"%s\" is not a valid value for focus attribute"), + focus); + return; + } + + frame_state = meta_frame_state_from_string (state); + if (frame_state == META_FRAME_STATE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("\"%s\" is not a valid value for state attribute"), + focus); + return; + } + + frame_style = meta_theme_lookup_style (info->theme, style); + + if (frame_style == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("A style called \"%s\" has not been defined"), + style); + return; + } + + switch (frame_state) + { + case META_FRAME_STATE_NORMAL: + if (resize == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + ATTRIBUTE_NOT_FOUND, + "resize", element_name); + return; + } + + + frame_resize = meta_frame_resize_from_string (resize); + if (frame_resize == META_FRAME_RESIZE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("\"%s\" is not a valid value for resize attribute"), + focus); + return; + } + + break; + + case META_FRAME_STATE_SHADED: + if (META_THEME_ALLOWS (info->theme, META_THEME_UNRESIZABLE_SHADED_STYLES)) + { + if (resize == NULL) + /* In state="normal" we would complain here. But instead we accept + * not having a resize attribute and default to resize="both", since + * that most closely mimics what we did in v1, and thus people can + * upgrade a theme to v2 without as much hassle. + */ + frame_resize = META_FRAME_RESIZE_BOTH; + else + { + frame_resize = meta_frame_resize_from_string (resize); + if (frame_resize == META_FRAME_RESIZE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("\"%s\" is not a valid value for resize attribute"), + focus); + return; + } + } + } + else /* v1 theme */ + { + if (resize != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Should not have \"resize\" attribute on <%s> element for maximized/shaded states"), + element_name); + return; + } + + /* resize="both" is equivalent to the old behaviour */ + frame_resize = META_FRAME_RESIZE_BOTH; + } + break; + + default: + if (resize != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Should not have \"resize\" attribute on <%s> element for maximized states"), + element_name); + return; + } + + frame_resize = META_FRAME_RESIZE_LAST; + } + + switch (frame_state) + { + case META_FRAME_STATE_NORMAL: + if (info->style_set->normal_styles[frame_resize][frame_focus]) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Style has already been specified for state %s resize %s focus %s"), + state, resize, focus); + return; + } + meta_frame_style_ref (frame_style); + info->style_set->normal_styles[frame_resize][frame_focus] = frame_style; + break; + case META_FRAME_STATE_MAXIMIZED: + if (info->style_set->maximized_styles[frame_focus]) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Style has already been specified for state %s focus %s"), + state, focus); + return; + } + meta_frame_style_ref (frame_style); + info->style_set->maximized_styles[frame_focus] = frame_style; + break; + case META_FRAME_STATE_SHADED: + if (info->style_set->shaded_styles[frame_resize][frame_focus]) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Style has already been specified for state %s resize %s focus %s"), + state, resize, focus); + return; + } + meta_frame_style_ref (frame_style); + info->style_set->shaded_styles[frame_resize][frame_focus] = frame_style; + break; + case META_FRAME_STATE_MAXIMIZED_AND_SHADED: + if (info->style_set->maximized_and_shaded_styles[frame_focus]) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Style has already been specified for state %s focus %s"), + state, focus); + return; + } + meta_frame_style_ref (frame_style); + info->style_set->maximized_and_shaded_styles[frame_focus] = frame_style; + break; + case META_FRAME_STATE_LAST: + g_assert_not_reached (); + break; + } + + push_state (info, STATE_FRAME); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "frame_style_set"); + } +} + +static void +parse_piece_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_PIECE); + + if (ELEMENT_IS ("draw_ops")) + { + if (info->op_list) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Can't have a two draw_ops for a <piece> element (theme specified a draw_ops attribute and also a <draw_ops> element, or specified two elements)")); + return; + } + + if (!check_no_attributes (context, element_name, attribute_names, attribute_values, + error)) + return; + + g_assert (info->op_list == NULL); + info->op_list = meta_draw_op_list_new (2); + + push_state (info, STATE_DRAW_OPS); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "piece"); + } +} + +static void +parse_button_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_BUTTON); + + if (ELEMENT_IS ("draw_ops")) + { + if (info->op_list) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Can't have a two draw_ops for a <button> element (theme specified a draw_ops attribute and also a <draw_ops> element, or specified two elements)")); + return; + } + + if (!check_no_attributes (context, element_name, attribute_names, attribute_values, + error)) + return; + + g_assert (info->op_list == NULL); + info->op_list = meta_draw_op_list_new (2); + + push_state (info, STATE_DRAW_OPS); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "button"); + } +} + +#ifdef USE_UBUNTU_CODE +static void +parse_shadow_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_SHADOW); + + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "shadow"); +} + +static void +parse_padding_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_PADDING); + + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "padding"); +} +#endif + +static void +parse_menu_icon_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_MENU_ICON); + + if (ELEMENT_IS ("draw_ops")) + { + if (info->op_list) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Can't have a two draw_ops for a <menu_icon> element (theme specified a draw_ops attribute and also a <draw_ops> element, or specified two elements)")); + return; + } + + if (!check_no_attributes (context, element_name, attribute_names, attribute_values, + error)) + return; + + g_assert (info->op_list == NULL); + info->op_list = meta_draw_op_list_new (2); + + push_state (info, STATE_DRAW_OPS); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "menu_icon"); + } +} + + +static void +start_element_handler (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + ParseInfo *info = user_data; + + switch (peek_state (info)) + { + case STATE_START: + if (strcmp (element_name, "metacity_theme") == 0) + { + info->theme = meta_theme_new (); + info->theme->name = g_strdup (info->theme_name); + info->theme->filename = g_strdup (info->theme_file); + info->theme->dirname = g_strdup (info->theme_dir); + info->theme->format_version = info->format_version; + + push_state (info, STATE_THEME); + } + else + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Outermost element in theme must be <metacity_theme> not <%s>"), + element_name); + break; + + case STATE_THEME: + parse_toplevel_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_INFO: + parse_info_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_NAME: + case STATE_AUTHOR: + case STATE_COPYRIGHT: + case STATE_DATE: + case STATE_DESCRIPTION: + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed inside a name/author/date/description element"), + element_name); + break; + case STATE_CONSTANT: + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed inside a <constant> element"), + element_name); + break; + case STATE_FRAME_GEOMETRY: + parse_geometry_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_DISTANCE: + case STATE_BORDER: + case STATE_ASPECT_RATIO: + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed inside a distance/border/aspect_ratio element"), + element_name); + break; + case STATE_DRAW_OPS: + parse_draw_op_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_LINE: + case STATE_RECTANGLE: + case STATE_ARC: + case STATE_CLIP: + case STATE_TINT: + case STATE_IMAGE: + case STATE_GTK_ARROW: + case STATE_GTK_BOX: + case STATE_GTK_VLINE: + case STATE_ICON: + case STATE_TITLE: + case STATE_INCLUDE: + case STATE_TILE: + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed inside a draw operation element"), + element_name); + break; + case STATE_GRADIENT: + parse_gradient_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_COLOR: + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed inside a <%s> element"), + element_name, "color"); + break; + case STATE_FRAME_STYLE: + parse_style_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_PIECE: + parse_piece_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_BUTTON: + parse_button_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; +#ifdef USE_UBUNTU_CODE + case STATE_SHADOW: + parse_shadow_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_PADDING: + parse_padding_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; +#endif + case STATE_MENU_ICON: + parse_menu_icon_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_FRAME_STYLE_SET: + parse_style_set_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_FRAME: + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed inside a <%s> element"), + element_name, "frame"); + break; + case STATE_WINDOW: + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed inside a <%s> element"), + element_name, "window"); + break; + case STATE_FALLBACK: + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed inside a <%s> element"), + element_name, "fallback"); + break; + } +} + +static void +end_element_handler (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + ParseInfo *info = user_data; + + switch (peek_state (info)) + { + case STATE_START: + break; + case STATE_THEME: + g_assert (info->theme); + + if (!meta_theme_validate (info->theme, error)) + { + add_context_to_error (error, context); + meta_theme_free (info->theme); + info->theme = NULL; + } + + pop_state (info); + g_assert (peek_state (info) == STATE_START); + break; + case STATE_INFO: + pop_state (info); + g_assert (peek_state (info) == STATE_THEME); + break; + case STATE_NAME: + pop_state (info); + g_assert (peek_state (info) == STATE_INFO); + break; + case STATE_AUTHOR: + pop_state (info); + g_assert (peek_state (info) == STATE_INFO); + break; + case STATE_COPYRIGHT: + pop_state (info); + g_assert (peek_state (info) == STATE_INFO); + break; + case STATE_DATE: + pop_state (info); + g_assert (peek_state (info) == STATE_INFO); + break; + case STATE_DESCRIPTION: + pop_state (info); + g_assert (peek_state (info) == STATE_INFO); + break; + case STATE_CONSTANT: + pop_state (info); + g_assert (peek_state (info) == STATE_THEME); + break; + case STATE_FRAME_GEOMETRY: + g_assert (info->layout); + + if (!meta_frame_layout_validate (info->layout, + error)) + { + add_context_to_error (error, context); + } + + /* layout will already be stored in the theme under + * its name + */ + meta_frame_layout_unref (info->layout); + info->layout = NULL; + pop_state (info); + g_assert (peek_state (info) == STATE_THEME); + break; + case STATE_DISTANCE: + pop_state (info); + g_assert (peek_state (info) == STATE_FRAME_GEOMETRY); + break; + case STATE_BORDER: + pop_state (info); + g_assert (peek_state (info) == STATE_FRAME_GEOMETRY); + break; + case STATE_ASPECT_RATIO: + pop_state (info); + g_assert (peek_state (info) == STATE_FRAME_GEOMETRY); + break; + case STATE_DRAW_OPS: + { + g_assert (info->op_list); + + if (!meta_draw_op_list_validate (info->op_list, + error)) + { + add_context_to_error (error, context); + meta_draw_op_list_unref (info->op_list); + info->op_list = NULL; + } + + pop_state (info); + + switch (peek_state (info)) + { + case STATE_BUTTON: + case STATE_PIECE: + case STATE_MENU_ICON: + /* Leave info->op_list to be picked up + * when these elements are closed + */ + g_assert (info->op_list); + break; + case STATE_THEME: + g_assert (info->op_list); + meta_draw_op_list_unref (info->op_list); + info->op_list = NULL; + break; + default: + /* Op list can't occur in other contexts */ + g_assert_not_reached (); + break; + } + } + break; + case STATE_LINE: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_RECTANGLE: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_ARC: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_CLIP: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_TINT: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_GRADIENT: + g_assert (info->op); + g_assert (info->op->type == META_DRAW_GRADIENT); + if (!meta_gradient_spec_validate (info->op->data.gradient.gradient_spec, + error)) + { + add_context_to_error (error, context); + meta_draw_op_free (info->op); + info->op = NULL; + } + else + { + g_assert (info->op_list); + meta_draw_op_list_append (info->op_list, info->op); + info->op = NULL; + } + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_IMAGE: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_GTK_ARROW: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_GTK_BOX: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_GTK_VLINE: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_ICON: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_TITLE: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_INCLUDE: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_TILE: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_COLOR: + pop_state (info); + g_assert (peek_state (info) == STATE_GRADIENT); + break; + case STATE_FRAME_STYLE: + g_assert (info->style); + + if (!meta_frame_style_validate (info->style, + info->theme->format_version, + error)) + { + add_context_to_error (error, context); + } + + /* Frame style is in the theme hash table and a ref + * is held there + */ + meta_frame_style_unref (info->style); + info->style = NULL; + pop_state (info); + g_assert (peek_state (info) == STATE_THEME); + break; + case STATE_PIECE: + g_assert (info->style); + if (info->op_list == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No draw_ops provided for frame piece")); + } + else + { + info->style->pieces[info->piece] = info->op_list; + info->op_list = NULL; + } + pop_state (info); + g_assert (peek_state (info) == STATE_FRAME_STYLE); + break; + case STATE_BUTTON: + g_assert (info->style); + if (info->op_list == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No draw_ops provided for button")); + } + else + { + info->style->buttons[info->button_type][info->button_state] = + info->op_list; + info->op_list = NULL; + } + pop_state (info); + break; +#ifdef USE_UBUNTU_CODE + case STATE_SHADOW: + g_assert (info->style); + pop_state (info); + break; + case STATE_PADDING: + g_assert (info->style); + pop_state (info); + break; +#endif + case STATE_MENU_ICON: + g_assert (info->theme); + if (info->op_list != NULL) + { + meta_draw_op_list_unref (info->op_list); + info->op_list = NULL; + } + pop_state (info); + g_assert (peek_state (info) == STATE_THEME); + break; + case STATE_FRAME_STYLE_SET: + g_assert (info->style_set); + + if (!meta_frame_style_set_validate (info->style_set, + error)) + { + add_context_to_error (error, context); + } + + /* Style set is in the theme hash table and a reference + * is held there. + */ + meta_frame_style_set_unref (info->style_set); + info->style_set = NULL; + pop_state (info); + g_assert (peek_state (info) == STATE_THEME); + break; + case STATE_FRAME: + pop_state (info); + g_assert (peek_state (info) == STATE_FRAME_STYLE_SET); + break; + case STATE_WINDOW: + pop_state (info); + g_assert (peek_state (info) == STATE_THEME); + break; + case STATE_FALLBACK: + pop_state (info); + g_assert (peek_state (info) == STATE_THEME); + break; + } +} + +#define NO_TEXT(element_name) set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, _("No text is allowed inside element <%s>"), element_name) + +static gboolean +all_whitespace (const char *text, + int text_len) +{ + const char *p; + const char *end; + + p = text; + end = text + text_len; + + while (p != end) + { + if (!g_ascii_isspace (*p)) + return FALSE; + + p = g_utf8_next_char (p); + } + + return TRUE; +} + +static void +text_handler (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + ParseInfo *info = user_data; + + if (all_whitespace (text, text_len)) + return; + + /* FIXME http://bugzilla.gnome.org/show_bug.cgi?id=70448 would + * allow a nice cleanup here. + */ + + switch (peek_state (info)) + { + case STATE_START: + g_assert_not_reached (); /* gmarkup shouldn't do this */ + break; + case STATE_THEME: + NO_TEXT ("metacity_theme"); + break; + case STATE_INFO: + NO_TEXT ("info"); + break; + case STATE_NAME: + if (info->theme->readable_name != NULL) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("<%s> specified twice for this theme"), + "name"); + return; + } + + info->theme->readable_name = g_strndup (text, text_len); + break; + case STATE_AUTHOR: + if (info->theme->author != NULL) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("<%s> specified twice for this theme"), + "author"); + return; + } + + info->theme->author = g_strndup (text, text_len); + break; + case STATE_COPYRIGHT: + if (info->theme->copyright != NULL) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("<%s> specified twice for this theme"), + "copyright"); + return; + } + + info->theme->copyright = g_strndup (text, text_len); + break; + case STATE_DATE: + if (info->theme->date != NULL) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("<%s> specified twice for this theme"), + "date"); + return; + } + + info->theme->date = g_strndup (text, text_len); + break; + case STATE_DESCRIPTION: + if (info->theme->description != NULL) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("<%s> specified twice for this theme"), + "description"); + return; + } + + info->theme->description = g_strndup (text, text_len); + break; + case STATE_CONSTANT: + NO_TEXT ("constant"); + break; + case STATE_FRAME_GEOMETRY: + NO_TEXT ("frame_geometry"); + break; + case STATE_DISTANCE: + NO_TEXT ("distance"); + break; + case STATE_BORDER: + NO_TEXT ("border"); + break; + case STATE_ASPECT_RATIO: + NO_TEXT ("aspect_ratio"); + break; + case STATE_DRAW_OPS: + NO_TEXT ("draw_ops"); + break; + case STATE_LINE: + NO_TEXT ("line"); + break; + case STATE_RECTANGLE: + NO_TEXT ("rectangle"); + break; + case STATE_ARC: + NO_TEXT ("arc"); + break; + case STATE_CLIP: + NO_TEXT ("clip"); + break; + case STATE_TINT: + NO_TEXT ("tint"); + break; + case STATE_GRADIENT: + NO_TEXT ("gradient"); + break; + case STATE_IMAGE: + NO_TEXT ("image"); + break; + case STATE_GTK_ARROW: + NO_TEXT ("gtk_arrow"); + break; + case STATE_GTK_BOX: + NO_TEXT ("gtk_box"); + break; + case STATE_GTK_VLINE: + NO_TEXT ("gtk_vline"); + break; + case STATE_ICON: + NO_TEXT ("icon"); + break; + case STATE_TITLE: + NO_TEXT ("title"); + break; + case STATE_INCLUDE: + NO_TEXT ("include"); + break; + case STATE_TILE: + NO_TEXT ("tile"); + break; + case STATE_COLOR: + NO_TEXT ("color"); + break; + case STATE_FRAME_STYLE: + NO_TEXT ("frame_style"); + break; + case STATE_PIECE: + NO_TEXT ("piece"); + break; + case STATE_BUTTON: + NO_TEXT ("button"); + break; +#ifdef USE_UBUNTU_CODE + case STATE_SHADOW: + NO_TEXT ("shadow"); + break; + case STATE_PADDING: + NO_TEXT ("padding"); + break; +#endif + case STATE_MENU_ICON: + NO_TEXT ("menu_icon"); + break; + case STATE_FRAME_STYLE_SET: + NO_TEXT ("frame_style_set"); + break; + case STATE_FRAME: + NO_TEXT ("frame"); + break; + case STATE_WINDOW: + NO_TEXT ("window"); + break; + case STATE_FALLBACK: + NO_TEXT ("fallback"); + break; + } +} + +/* We were intending to put the version number + * in the subdirectory name, but we ended up + * using the filename instead. The "-1" survives + * as a fossil. + */ +#define THEME_SUBDIR "metacity-1" + +/* Highest version of the theme format to + * look out for. + */ +#define THEME_VERSION 2 + +#define MARCO_THEME_FILENAME_FORMAT "metacity-theme-%d.xml" + +MetaTheme* +meta_theme_load (const char *theme_name, + GError **err) +{ + GMarkupParseContext *context; + GError *error; + ParseInfo info; + char *text; + gsize length; + char *theme_file; + char *theme_dir; + MetaTheme *retval; + guint version; + const gchar* const* xdg_data_dirs; + int i; + + text = NULL; + length = 0; + retval = NULL; + context = NULL; + + theme_dir = NULL; + theme_file = NULL; + + if (meta_is_debugging ()) + { + gchar *theme_filename = g_strdup_printf (MARCO_THEME_FILENAME_FORMAT, + THEME_VERSION); + + /* Try in themes in our source tree */ + theme_dir = g_build_filename ("./themes", theme_name, NULL); + + theme_file = g_build_filename (theme_dir, + theme_filename, + NULL); + + error = NULL; + if (!g_file_get_contents (theme_file, + &text, + &length, + &error)) + { + meta_topic (META_DEBUG_THEMES, "Failed to read theme from file %s: %s\n", + theme_file, error->message); + g_error_free (error); + g_free (theme_dir); + g_free (theme_file); + theme_file = NULL; + } + version = THEME_VERSION; + + g_free (theme_filename); + } + + /* We try all supported versions from current to oldest */ + for (version = THEME_VERSION; (version > 0) && (text == NULL); version--) + { + gchar *theme_filename = g_strdup_printf (MARCO_THEME_FILENAME_FORMAT, + version); + + /* We try first in home dir, XDG_DATA_DIRS, then system dir for themes */ + + /* Try home dir for themes */ + theme_dir = g_build_filename (g_get_home_dir (), + ".themes", + theme_name, + THEME_SUBDIR, + NULL); + + theme_file = g_build_filename (theme_dir, + theme_filename, + NULL); + + error = NULL; + if (!g_file_get_contents (theme_file, + &text, + &length, + &error)) + { + meta_topic (META_DEBUG_THEMES, "Failed to read theme from file %s: %s\n", + theme_file, error->message); + g_error_free (error); + g_free (theme_dir); + g_free (theme_file); + theme_file = NULL; + } + + /* Try each XDG_DATA_DIRS for theme */ + xdg_data_dirs = g_get_system_data_dirs(); + for(i = 0; xdg_data_dirs[i] != NULL; i++) + { + if (text == NULL) + { + theme_dir = g_build_filename (xdg_data_dirs[i], + "themes", + theme_name, + THEME_SUBDIR, + NULL); + + theme_file = g_build_filename (theme_dir, + theme_filename, + NULL); + + error = NULL; + if (!g_file_get_contents (theme_file, + &text, + &length, + &error)) + { + meta_topic (META_DEBUG_THEMES, "Failed to read theme from file %s: %s\n", + theme_file, error->message); + g_error_free (error); + g_free (theme_dir); + g_free (theme_file); + theme_file = NULL; + } + else + { + break; + } + } + } + + /* Look for themes in MARCO_DATADIR */ + if (text == NULL) + { + theme_dir = g_build_filename (MARCO_DATADIR, + "themes", + theme_name, + THEME_SUBDIR, + NULL); + + theme_file = g_build_filename (theme_dir, + theme_filename, + NULL); + + error = NULL; + if (!g_file_get_contents (theme_file, + &text, + &length, + &error)) + { + meta_topic (META_DEBUG_THEMES, "Failed to read theme from file %s: %s\n", + theme_file, error->message); + g_error_free (error); + g_free (theme_dir); + g_free (theme_file); + theme_file = NULL; + } + } + + g_free (theme_filename); + } + + if (text == NULL) + { + g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("Failed to find a valid file for theme %s\n"), + theme_name); + + return NULL; /* all fallbacks failed */ + } + + meta_topic (META_DEBUG_THEMES, "Parsing theme file %s\n", theme_file); + + + parse_info_init (&info); + info.theme_name = theme_name; + + /* pass ownership to info so we free it with the info */ + info.theme_file = theme_file; + info.theme_dir = theme_dir; + + info.format_version = version + 1; + + context = g_markup_parse_context_new (&marco_theme_parser, + 0, &info, NULL); + + error = NULL; + if (!g_markup_parse_context_parse (context, + text, + length, + &error)) + goto out; + + error = NULL; + if (!g_markup_parse_context_end_parse (context, &error)) + goto out; + + goto out; + + out: + + if (context) + g_markup_parse_context_free (context); + g_free (text); + + if (info.theme) + info.theme->format_version = info.format_version; + + if (error) + { + g_propagate_error (err, error); + } + else if (info.theme) + { + /* Steal theme from info */ + retval = info.theme; + info.theme = NULL; + } + else + { + g_set_error (err, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Theme file %s did not contain a root <metacity_theme> element"), + info.theme_file); + } + + parse_info_free (&info); + + return retval; +} |