diff options
author | Alberts Muktupāvels <alberts.muktupavels@gmail.com> | 2016-01-31 20:25:51 +0200 |
---|---|---|
committer | Alberts Muktupāvels <alberts.muktupavels@gmail.com> | 2016-01-31 21:36:03 +0200 |
commit | e420f2f7c7387d7899404e374f476b2b9023cd2f (patch) | |
tree | 65ea19f59a0c75ae6e1166b595a6ef42c9677d37 | |
parent | 80975450ff257556949b05368dfa20f199608b49 (diff) | |
download | metacity-e420f2f7c7387d7899404e374f476b2b9023cd2f.tar.gz |
theme-parser: merge with MetaThemeMetacity
-rw-r--r-- | libmetacity/Makefile.am | 1 | ||||
-rw-r--r-- | libmetacity/meta-theme-impl.c | 3 | ||||
-rw-r--r-- | libmetacity/meta-theme-metacity.c | 4668 | ||||
-rw-r--r-- | libmetacity/meta-theme-metacity.h | 42 | ||||
-rw-r--r-- | po/POTFILES.in | 1 | ||||
-rw-r--r-- | src/Makefile.am | 1 | ||||
-rw-r--r-- | src/ui/theme-parser.c | 4315 | ||||
-rw-r--r-- | src/ui/theme-private.h | 42 | ||||
-rw-r--r-- | src/ui/theme-viewer.c | 43 | ||||
-rw-r--r-- | src/ui/theme.c | 363 |
10 files changed, 4814 insertions, 4665 deletions
diff --git a/libmetacity/Makefile.am b/libmetacity/Makefile.am index cc0b7ebc..bbf8de14 100644 --- a/libmetacity/Makefile.am +++ b/libmetacity/Makefile.am @@ -44,6 +44,7 @@ libmetacity_la_SOURCES = \ $(NULL) libmetacity_la_CPPFLAGS = \ + -DDATADIR=\"$(datadir)\" \ -I$(top_srcdir) \ $(AM_CPPFLAGS) \ $(NULL) diff --git a/libmetacity/meta-theme-impl.c b/libmetacity/meta-theme-impl.c index 19d03549..a6670bc6 100644 --- a/libmetacity/meta-theme-impl.c +++ b/libmetacity/meta-theme-impl.c @@ -100,6 +100,9 @@ meta_theme_impl_add_style_set (MetaThemeImpl *impl, priv = meta_theme_impl_get_instance_private (impl); + if (priv->style_sets_by_type[type]) + meta_frame_style_set_unref (priv->style_sets_by_type[type]); + priv->style_sets_by_type[type] = style_set; } diff --git a/libmetacity/meta-theme-metacity.c b/libmetacity/meta-theme-metacity.c index 01eef6aa..3c3cbe43 100644 --- a/libmetacity/meta-theme-metacity.c +++ b/libmetacity/meta-theme-metacity.c @@ -19,6 +19,7 @@ #include "config.h" #include <glib/gi18n-lib.h> +#include <stdlib.h> #include "meta-draw-op.h" #include "meta-frame-layout.h" @@ -26,10 +27,53 @@ #include "meta-theme.h" #include "meta-theme-metacity.h" +/* 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" + +#define METACITY_THEME_FILENAME_FORMAT "metacity-theme-%d.xml" + +/* Highest version of the theme format to + * look out for. + */ +#define THEME_MAJOR_VERSION 3 +#define THEME_MINOR_VERSION 5 +#define THEME_VERSION (1000 * THEME_MAJOR_VERSION + THEME_MINOR_VERSION) + +/* 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>") + +/* What version of the theme file format were feature introduced in? */ +#define META_THEME_COLOR_CONSTANTS 2 +#define META_THEME_DEGREES_IN_ARCS 2 +#define META_THEME_FRAME_BACKGROUNDS 2 +#define META_THEME_HIDDEN_BUTTONS 2 +#define META_THEME_IMAGES_FROM_ICON_THEMES 2 +#define META_THEME_SHADE_STICK_ABOVE_BUTTONS 2 +#define META_THEME_UBIQUITOUS_CONSTANTS 2 +#define META_THEME_UNRESIZABLE_SHADED_STYLES 2 +#define META_THEME_VARIED_ROUND_CORNERS 2 + struct _MetaThemeMetacity { MetaThemeImpl parent; + gchar *name; + gchar *dirname; + + guint format_version; + + gchar *readable_name; + gchar *author; + gchar *copyright; + gchar *date; + gchar *description; + GHashTable *integers; GHashTable *floats; GHashTable *colors; @@ -38,62 +82,706 @@ struct _MetaThemeMetacity GHashTable *frame_layouts; GHashTable *styles; GHashTable *style_sets; + GHashTable *images; }; +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, + /* 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 +{ + /* This two lists contain stacks of state and required version + * (cast to pointers.) There is one list item for each currently + * open element. */ + GSList *states; + GSList *required_versions; + + MetaThemeMetacity *metacity; /* theme being parsed */ + + gchar *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 */ + + gint skip_level; /* depth of elements that we're ignoring */ +} ParseInfo; + +typedef struct +{ + const gchar *name; + const gchar **retloc; + gboolean required; +} LocateAttr; + G_DEFINE_TYPE (MetaThemeMetacity, meta_theme_metacity, META_TYPE_THEME_IMPL) static gboolean -first_uppercase (const gchar *str) +theme_allows (MetaThemeMetacity *metacity, + guint feature) { - return g_ascii_isupper (*str); + if (metacity->format_version >= feature) + return TRUE; + + return FALSE; +} + +static ParseInfo * +parse_info_new (MetaThemeMetacity *metacity) +{ + ParseInfo *info; + + info = g_new0 (ParseInfo, 1); + + info->states = g_slist_prepend (NULL, GINT_TO_POINTER (STATE_START)); + info->required_versions = NULL; + + info->metacity = metacity; + + 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; + + info->skip_level = 0; + + return info; } static void -meta_theme_metacity_dispose (GObject *object) +parse_info_free (ParseInfo *info) { - MetaThemeMetacity *metacity; + g_slist_free (info->states); + g_slist_free (info->required_versions); - metacity = META_THEME_METACITY (object); + if (info->layout) + meta_frame_layout_unref (info->layout); - g_clear_pointer (&metacity->integers, g_hash_table_destroy); - g_clear_pointer (&metacity->floats, g_hash_table_destroy); - g_clear_pointer (&metacity->colors, g_hash_table_destroy); + if (info->op_list) + meta_draw_op_list_unref (info->op_list); - g_clear_pointer (&metacity->draw_op_lists, g_hash_table_destroy); - g_clear_pointer (&metacity->frame_layouts, g_hash_table_destroy); - g_clear_pointer (&metacity->styles, g_hash_table_destroy); - g_clear_pointer (&metacity->style_sets, g_hash_table_destroy); + if (info->op) + meta_draw_op_free (info->op); - G_OBJECT_CLASS (meta_theme_metacity_parent_class)->dispose (object); + if (info->style) + meta_frame_style_unref (info->style); + + if (info->style_set) + meta_frame_style_set_unref (info->style_set); + + g_free (info); } static void -meta_theme_metacity_class_init (MetaThemeMetacityClass *metacity_class) +push_state (ParseInfo *info, + ParseState state) { - GObjectClass *object_class; + info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state)); +} - object_class = G_OBJECT_CLASS (metacity_class); +static void +pop_state (ParseInfo *info) +{ + g_return_if_fail (info->states != NULL); - object_class->dispose = meta_theme_metacity_dispose; + info->states = g_slist_remove (info->states, info->states->data); } static void -meta_theme_metacity_init (MetaThemeMetacity *metacity) +push_required_version (ParseInfo *info, + int version) { - metacity->draw_op_lists = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, - (GDestroyNotify) meta_draw_op_list_unref); + info->required_versions = g_slist_prepend (info->required_versions, + GINT_TO_POINTER (version)); +} - metacity->frame_layouts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, - (GDestroyNotify) meta_frame_layout_unref); +static void +pop_required_version (ParseInfo *info) +{ + g_return_if_fail (info->required_versions != NULL); - metacity->styles = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, - (GDestroyNotify) meta_frame_style_unref); + info->required_versions = g_slist_delete_link (info->required_versions, + info->required_versions); +} - metacity->style_sets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, - (GDestroyNotify) meta_frame_style_set_unref); +static ParseState +peek_state (ParseInfo *info) +{ + g_return_val_if_fail (info->states != NULL, STATE_START); + + return GPOINTER_TO_INT (info->states->data); } -gboolean +static int +peek_required_version (ParseInfo *info) +{ + if (info->required_versions) + return GPOINTER_TO_INT (info->required_versions->data); + else + return info->metacity->format_version; +} + +static void +set_error (GError **err, + GMarkupParseContext *context, + gint error_domain, + gint error_code, + const gchar *format, + ...) +{ + gint line; + gint ch; + va_list args; + gchar *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) +{ + gint line; + gint ch; + gchar *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; +} + +#define MAX_REASONABLE 4096 +static gboolean +parse_positive_integer (const char *str, + int *val, + GMarkupParseContext *context, + MetaThemeMetacity *metacity, + GError **error) +{ + char *end; + long l; + int j; + + *val = 0; + + end = NULL; + + /* Is str a constant? */ + + if (theme_allows (metacity, META_THEME_UBIQUITOUS_CONSTANTS) && + meta_theme_metacity_lookup_int (metacity, 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; +} + +/* Attribute names can have a leading '!' to indicate that they are + * required. + */ +static gboolean +locate_attributes (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + GError **error, + const gchar *first_attribute_name, + const gchar **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 gchar *); + retloc = va_arg (args, const gchar **); + + while (name != NULL) + { + if (retloc == NULL) + { + retval = FALSE; + goto out; + } + + 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; + + /* Can be present anywhere */ + if (strcmp (attribute_names[i], "version") == 0) + { + ++i; + continue; + } + + 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 gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + GError **error) +{ + int i = 0; + + /* Can be present anywhere */ + if (attribute_names[0] && strcmp (attribute_names[i], "version") == 0) + i++; + + if (attribute_names[i] != 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; +} + +static MetaFrameType +meta_frame_type_from_string (const char *str) +{ + if (strcmp ("normal", str) == 0) + return META_FRAME_TYPE_NORMAL; + else if (strcmp ("dialog", str) == 0) + return META_FRAME_TYPE_DIALOG; + else if (strcmp ("modal_dialog", str) == 0) + return META_FRAME_TYPE_MODAL_DIALOG; + else if (strcmp ("utility", str) == 0) + return META_FRAME_TYPE_UTILITY; + else if (strcmp ("menu", str) == 0) + return META_FRAME_TYPE_MENU; + else if (strcmp ("border", str) == 0) + return META_FRAME_TYPE_BORDER; + else if (strcmp ("attached", str) == 0) + return META_FRAME_TYPE_ATTACHED; + else + return META_FRAME_TYPE_LAST; +} + +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_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"), + v); + + g_strfreev (split); + meta_alpha_gradient_spec_free (spec); + + return FALSE; + } + + meta_alpha_gradient_spec_add_alpha (spec, i, v); + + ++i; + } + + g_strfreev (split); + + *spec_ret = spec; + + return TRUE; +} + +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 gboolean +parse_rounding (const char *str, + guint *val, + GMarkupParseContext *context, + MetaThemeMetacity *metacity, + 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 (!theme_allows (metacity, 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, metacity, error); + + *val = tmp; + + return result; + } + + 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 +first_uppercase (const gchar *str) +{ + return g_ascii_isupper (*str); +} + +static gboolean meta_theme_metacity_define_int (MetaThemeMetacity *metacity, const gchar *name, gint value, @@ -128,27 +816,7 @@ meta_theme_metacity_define_int (MetaThemeMetacity *metacity, return TRUE; } -gboolean -meta_theme_metacity_lookup_int (MetaThemeMetacity *metacity, - const gchar *name, - gint *value) -{ - gpointer tmp; - - *value = 0; - - if (metacity->integers == NULL) - return FALSE; - - if (!g_hash_table_lookup_extended (metacity->integers, name, NULL, &tmp)) - return FALSE; - - *value = GPOINTER_TO_INT (tmp); - - return TRUE; -} - -gboolean +static gboolean meta_theme_metacity_define_float (MetaThemeMetacity *metacity, const gchar *name, gdouble value, @@ -188,29 +856,7 @@ meta_theme_metacity_define_float (MetaThemeMetacity *metacity, return TRUE; } -gboolean -meta_theme_metacity_lookup_float (MetaThemeMetacity *metacity, - const gchar *name, - gdouble *value) -{ - gdouble *d; - - *value = 0.0; - - if (metacity->floats == NULL) - return FALSE; - - d = g_hash_table_lookup (metacity->floats, name); - - if (!d) - return FALSE; - - *value = *d; - - return TRUE; -} - -gboolean +static gboolean meta_theme_metacity_define_color (MetaThemeMetacity *metacity, const gchar *name, const gchar *value, @@ -244,7 +890,835 @@ meta_theme_metacity_define_color (MetaThemeMetacity *metacity, return TRUE; } -gboolean +static void +meta_theme_metacity_insert_draw_op_list (MetaThemeMetacity *metacity, + const gchar *name, + MetaDrawOpList *op_list) +{ + meta_draw_op_list_ref (op_list); + g_hash_table_replace (metacity->draw_op_lists, g_strdup (name), op_list); +} + +static void +meta_theme_metacity_insert_layout (MetaThemeMetacity *metacity, + const gchar *name, + MetaFrameLayout *layout) +{ + meta_frame_layout_ref (layout); + g_hash_table_replace (metacity->frame_layouts, g_strdup (name), layout); +} + +static void +meta_theme_metacity_insert_style (MetaThemeMetacity *metacity, + const gchar *name, + MetaFrameStyle *style) +{ + meta_frame_style_ref (style); + g_hash_table_replace (metacity->styles, g_strdup (name), style); +} + +static void +meta_theme_metacity_insert_style_set (MetaThemeMetacity *metacity, + const gchar *name, + MetaFrameStyleSet *style_set) +{ + meta_frame_style_set_ref (style_set); + g_hash_table_replace (metacity->style_sets, g_strdup (name), style_set); +} + +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 (g_strcmp0 (element_name, "info") == 0) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_INFO); + } + else if (g_strcmp0 (element_name, "constant") == 0) + { + 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_metacity_define_float (info->metacity, + name, dval, error)) + { + add_context_to_error (error, context); + return; + } + } + else + { + if (!parse_positive_integer (value, &ival, context, info->metacity, error)) + return; + + if (!meta_theme_metacity_define_int (info->metacity, + name, ival, error)) + { + add_context_to_error (error, context); + return; + } + } + } + else + { + if (!meta_theme_metacity_define_color (info->metacity, + name, value, error)) + { + add_context_to_error (error, context); + return; + } + } + + push_state (info, STATE_CONSTANT); + } + else if (g_strcmp0 (element_name, "frame_geometry") == 0) + { + 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->metacity, error)) + return; + if (rounded_top_right && !parse_rounding (rounded_top_right, &rounded_top_right_val, context, info->metacity, error)) + return; + if (rounded_bottom_left && !parse_rounding (rounded_bottom_left, &rounded_bottom_left_val, context, info->metacity, error)) + return; + if (rounded_bottom_right && !parse_rounding (rounded_bottom_right, &rounded_bottom_right_val, context, info->metacity, error)) + return; + + title_scale_val = 1.0; + if (title_scale && !parse_title_scale (title_scale, &title_scale_val, context, error)) + return; + + if (meta_theme_metacity_lookup_layout (info->metacity, 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_metacity_lookup_layout (info->metacity, 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 (theme_allows (info->metacity, 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_metacity_insert_layout (info->metacity, name, info->layout); + + push_state (info, STATE_FRAME_GEOMETRY); + } + else if (g_strcmp0 (element_name, "draw_ops") == 0) + { + const char *name = NULL; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, + NULL)) + return; + + if (meta_theme_metacity_lookup_draw_op_list (info->metacity, 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_metacity_insert_draw_op_list (info->metacity, name, info->op_list); + + push_state (info, STATE_DRAW_OPS); + } + else if (g_strcmp0 (element_name, "frame_style") == 0) + { + 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_metacity_lookup_style (info->metacity, 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_metacity_lookup_style (info->metacity, 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_metacity_lookup_layout (info->metacity, 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 && theme_allows (info->metacity, 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 = meta_alpha_gradient_spec_get_alpha (alpha_vector, 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_metacity_insert_style (info->metacity, name, info->style); + + push_state (info, STATE_FRAME_STYLE); + } + else if (g_strcmp0 (element_name, "frame_style_set") == 0) + { + 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_metacity_lookup_style_set (info->metacity, 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_metacity_lookup_style_set (info->metacity, 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_metacity_insert_style_set (info->metacity, name, info->style_set); + + push_state (info, STATE_FRAME_STYLE_SET); + } + else if (g_strcmp0 (element_name, "window") == 0) + { + const char *type_name = NULL; + const char *style_set_name = NULL; + MetaFrameStyleSet *style_set; + MetaFrameType type; + MetaThemeImpl *impl; + + 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 || + (type == META_FRAME_TYPE_ATTACHED && peek_required_version (info) < 3002)) + { + 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_metacity_lookup_style_set (info->metacity, 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; + } + + impl = META_THEME_IMPL (info->metacity); + if (meta_theme_impl_get_style_set (impl, 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); + meta_theme_impl_add_style_set (impl, type, style_set); + + push_state (info, STATE_WINDOW); + } + else if (g_strcmp0 (element_name, "menu_icon") == 0) + { + /* 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 (g_strcmp0 (element_name, "fallback") == 0) + { + /* 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 (g_strcmp0 (element_name, "name") == 0) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_NAME); + } + else if (g_strcmp0 (element_name, "author") == 0) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_AUTHOR); + } + else if (g_strcmp0 (element_name, "copyright") == 0) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_COPYRIGHT); + } + else if (g_strcmp0 (element_name, "description") == 0) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_DESCRIPTION); + } + else if (g_strcmp0 (element_name, "date") == 0) + { + 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_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->metacity, error)) + return; + + bottom_val = 0; + if (!parse_positive_integer (bottom, &bottom_val, context, info->metacity, error)) + return; + + left_val = 0; + if (!parse_positive_integer (left, &left_val, context, info->metacity, error)) + return; + + right_val = 0; + if (!parse_positive_integer (right, &right_val, context, info->metacity, 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_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->metacity, 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_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 (g_strcmp0 (element_name, "distance") == 0) + { + parse_distance (context, element_name, + attribute_names, attribute_values, + info, error); + push_state (info, STATE_DISTANCE); + } + else if (g_strcmp0 (element_name, "border") == 0) + { + parse_border (context, element_name, + attribute_names, attribute_values, + info, error); + push_state (info, STATE_BORDER); + } + else if (g_strcmp0 (element_name, "aspect_ratio") == 0) + { + 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"); + } +} + +static GtkArrowType +meta_gtk_arrow_from_string (const char *str) +{ + if (strcmp ("up", str) == 0) + return GTK_ARROW_UP; + else if (strcmp ("down", str) == 0) + return GTK_ARROW_DOWN; + else if (strcmp ("left", str) == 0) + return GTK_ARROW_LEFT; + else if (strcmp ("right", str) == 0) + return GTK_ARROW_RIGHT; + else if (strcmp ("none", str) == 0) + return GTK_ARROW_NONE; + else + return -1; +} + +static GtkShadowType +meta_gtk_shadow_from_string (const char *str) +{ + if (strcmp ("none", str) == 0) + return GTK_SHADOW_NONE; + else if (strcmp ("in", str) == 0) + return GTK_SHADOW_IN; + else if (strcmp ("out", str) == 0) + return GTK_SHADOW_OUT; + else if (strcmp ("etched_in", str) == 0) + return GTK_SHADOW_ETCHED_IN; + else if (strcmp ("etched_out", str) == 0) + return GTK_SHADOW_ETCHED_OUT; + else + return -1; +} + +static MetaGradientType +meta_gradient_type_from_string (const char *str) +{ + if (strcmp ("vertical", str) == 0) + return META_GRADIENT_VERTICAL; + else if (strcmp ("horizontal", str) == 0) + return META_GRADIENT_HORIZONTAL; + else if (strcmp ("diagonal", str) == 0) + return META_GRADIENT_DIAGONAL; + else + return META_GRADIENT_LAST; +} + +/** + * Returns a fill_type from a string. The inverse of + * meta_image_fill_type_to_string(). + * + * \param str a string representing a fill_type + * \result the fill_type, or -1 if it represents no fill_type. + */ +static MetaImageFillType +meta_image_fill_type_from_string (const char *str) +{ + if (strcmp ("tile", str) == 0) + return META_IMAGE_FILL_TILE; + else if (strcmp ("scale", str) == 0) + return META_IMAGE_FILL_SCALE; + else + return -1; +} + +static gboolean meta_theme_metacity_lookup_color (MetaThemeMetacity *metacity, const gchar *name, gchar **value) @@ -266,6 +1740,2959 @@ meta_theme_metacity_lookup_color (MetaThemeMetacity *metacity, return TRUE; } +static MetaColorSpec* +parse_color (MetaThemeMetacity *metacity, + const gchar *str, + GError **err) +{ + gchar* referent; + + if (theme_allows (metacity, META_THEME_COLOR_CONSTANTS) && + meta_theme_metacity_lookup_color (metacity, 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 GdkPixbuf * +meta_theme_load_image (MetaThemeMetacity *metacity, + const gchar *filename, + guint size_of_theme_icons, + GError **error) +{ + GdkPixbuf *pixbuf; + + pixbuf = g_hash_table_lookup (metacity->images, filename); + + if (pixbuf == NULL) + { + if (g_str_has_prefix (filename, "theme:") && + theme_allows (metacity, META_THEME_IMAGES_FROM_ICON_THEMES)) + { + pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + filename + 6, size_of_theme_icons, + 0, error); + + if (pixbuf == NULL) + return NULL; + } + else + { + char *full_path; + full_path = g_build_filename (metacity->dirname, filename, NULL); + + pixbuf = gdk_pixbuf_new_from_file (full_path, error); + if (pixbuf == NULL) + { + g_free (full_path); + return NULL; + } + + g_free (full_path); + } + + g_hash_table_replace (metacity->images, g_strdup (filename), pixbuf); + } + + g_assert (pixbuf); + + g_object_ref (G_OBJECT (pixbuf)); + + return pixbuf; +} + +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 void +parse_draw_op_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + MetaThemeMetacity *metacity; + + g_return_if_fail (peek_state (info) == STATE_DRAW_OPS); + + metacity = info->metacity; + + if (g_strcmp0 (element_name, "line") == 0) + { + 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; + + dash_on_val = 0; + if (dash_on_length && + !parse_positive_integer (dash_on_length, &dash_on_val, context, info->metacity, error)) + return; + + dash_off_val = 0; + if (dash_off_length && + !parse_positive_integer (dash_off_length, &dash_off_val, context, info->metacity, error)) + return; + + width_val = 0; + if (width && + !parse_positive_integer (width, &width_val, context, info->metacity, error)) + return; + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = parse_color (info->metacity, 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 (metacity, x1, NULL); + op->data.line.y1 = meta_draw_spec_new (metacity, y1, NULL); + + if (strcmp(x1, x2)==0) + op->data.line.x2 = NULL; + else + op->data.line.x2 = meta_draw_spec_new (metacity, x2, NULL); + + if (strcmp(y1, y2)==0) + op->data.line.y2 = NULL; + else + op->data.line.y2 = meta_draw_spec_new (metacity, 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 (g_strcmp0 (element_name, "rectangle") == 0) + { + 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; + + 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->metacity, 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 (metacity, x, NULL); + op->data.rectangle.y = meta_draw_spec_new (metacity, y, NULL); + op->data.rectangle.width = meta_draw_spec_new (metacity, width, NULL); + op->data.rectangle.height = meta_draw_spec_new (metacity, + 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 (g_strcmp0 (element_name, "arc") == 0) + { + 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 (theme_allows (info->metacity, 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 (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->metacity, 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 (metacity, x, NULL); + op->data.arc.y = meta_draw_spec_new (metacity, y, NULL); + op->data.arc.width = meta_draw_spec_new (metacity, width, NULL); + op->data.arc.height = meta_draw_spec_new (metacity, 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 (g_strcmp0 (element_name, "clip") == 0) + { + 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; + + op = meta_draw_op_new (META_DRAW_CLIP); + + op->data.clip.x = meta_draw_spec_new (metacity, x, NULL); + op->data.clip.y = meta_draw_spec_new (metacity, y, NULL); + op->data.clip.width = meta_draw_spec_new (metacity, width, NULL); + op->data.clip.height = meta_draw_spec_new (metacity, height, NULL); + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_CLIP); + } + else if (g_strcmp0 (element_name, "tint") == 0) + { + 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; + + 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->metacity, 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 (metacity, x, NULL); + op->data.tint.y = meta_draw_spec_new (metacity, y, NULL); + op->data.tint.width = meta_draw_spec_new (metacity, width, NULL); + op->data.tint.height = meta_draw_spec_new (metacity, height, NULL); + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_TINT); + } + else if (g_strcmp0 (element_name, "gradient") == 0) + { + 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; + + 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 (metacity, x, NULL); + info->op->data.gradient.y = meta_draw_spec_new (metacity, y, NULL); + info->op->data.gradient.width = meta_draw_spec_new (metacity, + width, NULL); + info->op->data.gradient.height = meta_draw_spec_new (metacity, + 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 (g_strcmp0 (element_name, "image") == 0) + { + 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; + + 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->metacity, filename, 64, error); + + if (pixbuf == NULL) + { + add_context_to_error (error, context); + return; + } + + if (colorize) + { + colorize_spec = parse_color (info->metacity, 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 (metacity, x, NULL); + op->data.image.y = meta_draw_spec_new (metacity, y, NULL); + op->data.image.width = meta_draw_spec_new (metacity, width, NULL); + op->data.image.height = meta_draw_spec_new (metacity, 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 (g_strcmp0 (element_name, "gtk_arrow") == 0) + { + 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; + GtkStateFlags 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; + + filled_val = TRUE; + if (filled && !parse_boolean (filled, &filled_val, context, error)) + return; + + if (!meta_gtk_state_from_string (state, &state_val)) + { + 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 (metacity, x, NULL); + op->data.gtk_arrow.y = meta_draw_spec_new (metacity, y, NULL); + op->data.gtk_arrow.width = meta_draw_spec_new (metacity, width, NULL); + op->data.gtk_arrow.height = meta_draw_spec_new (metacity, + 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 (g_strcmp0 (element_name, "gtk_box") == 0) + { + MetaDrawOp *op; + const char *state; + const char *shadow; + const char *x; + const char *y; + const char *width; + const char *height; + GtkStateFlags 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 (!meta_gtk_state_from_string (state, &state_val)) + { + 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 (metacity, x, NULL); + op->data.gtk_box.y = meta_draw_spec_new (metacity, y, NULL); + op->data.gtk_box.width = meta_draw_spec_new (metacity, width, NULL); + op->data.gtk_box.height = meta_draw_spec_new (metacity, 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 (g_strcmp0 (element_name, "gtk_vline") == 0) + { + MetaDrawOp *op; + const char *state; + const char *x; + const char *y1; + const char *y2; + GtkStateFlags state_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!state", &state, + "!x", &x, "!y1", &y1, "!y2", &y2, + NULL)) + return; + + if (!meta_gtk_state_from_string (state, &state_val)) + { + 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 (metacity, x, NULL); + op->data.gtk_vline.y1 = meta_draw_spec_new (metacity, y1, NULL); + op->data.gtk_vline.y2 = meta_draw_spec_new (metacity, 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 (g_strcmp0 (element_name, "icon") == 0) + { + 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; + + 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 (metacity, x, NULL); + op->data.icon.y = meta_draw_spec_new (metacity, y, NULL); + op->data.icon.width = meta_draw_spec_new (metacity, width, NULL); + op->data.icon.height = meta_draw_spec_new (metacity, 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 (g_strcmp0 (element_name, "title") == 0) + { + MetaDrawOp *op; + const char *color; + const char *x; + const char *y; + const char *ellipsize_width; + MetaColorSpec *color_spec; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!color", &color, + "!x", &x, "!y", &y, + "ellipsize_width", &ellipsize_width, + NULL)) + return; + + if (ellipsize_width && peek_required_version (info) < 3001) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + ATTRIBUTE_NOT_FOUND, "ellipsize_width", element_name); + return; + } + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = parse_color (info->metacity, 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 (metacity, x, NULL); + op->data.title.y = meta_draw_spec_new (metacity, y, NULL); + if (ellipsize_width) + op->data.title.ellipsize_width = meta_draw_spec_new (metacity, ellipsize_width, NULL); + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_TITLE); + } + else if (g_strcmp0 (element_name, "include") == 0) + { + 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 + */ + op_list = meta_theme_metacity_lookup_draw_op_list (info->metacity, 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 (metacity, x ? x : "0", NULL); + op->data.op_list.y = meta_draw_spec_new (metacity, y ? y : "0", NULL); + op->data.op_list.width = meta_draw_spec_new (metacity, + width ? width : "width", + NULL); + op->data.op_list.height = meta_draw_spec_new (metacity, + height ? height : "height", + NULL); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_INCLUDE); + } + else if (g_strcmp0 (element_name, "tile") == 0) + { + 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 */ + op_list = meta_theme_metacity_lookup_draw_op_list (info->metacity, 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 (metacity, x ? x : "0", NULL); + op->data.tile.y = meta_draw_spec_new (metacity, y ? y : "0", NULL); + op->data.tile.width = meta_draw_spec_new (metacity, + width ? width : "width", + NULL); + op->data.tile.height = meta_draw_spec_new (metacity, + height ? height : "height", + NULL); + op->data.tile.tile_xoffset = meta_draw_spec_new (metacity, + tile_xoffset ? tile_xoffset : "0", + NULL); + op->data.tile.tile_yoffset = meta_draw_spec_new (metacity, + tile_yoffset ? tile_yoffset : "0", + NULL); + op->data.tile.tile_width = meta_draw_spec_new (metacity, tile_width, NULL); + op->data.tile.tile_height = meta_draw_spec_new (metacity, 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 (g_strcmp0 (element_name, "color") == 0) + { + 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->metacity, 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); + + meta_gradient_spec_add_color_spec (info->op->data.gradient.gradient_spec, + 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 MetaButtonState +meta_button_state_from_string (const char *str) +{ + if (strcmp ("normal", str) == 0) + return META_BUTTON_STATE_NORMAL; + else if (strcmp ("pressed", str) == 0) + return META_BUTTON_STATE_PRESSED; + else if (strcmp ("prelight", str) == 0) + return META_BUTTON_STATE_PRELIGHT; + else + return META_BUTTON_STATE_LAST; +} + +static MetaButtonType +meta_button_type_from_string (MetaThemeMetacity *metacity, + const gchar *str) +{ + if (theme_allows (metacity, META_THEME_SHADE_STICK_ABOVE_BUTTONS)) + { + if (strcmp ("shade", str) == 0) + return META_BUTTON_TYPE_SHADE; + else if (strcmp ("above", str) == 0) + return META_BUTTON_TYPE_ABOVE; + else if (strcmp ("stick", str) == 0) + return META_BUTTON_TYPE_STICK; + else if (strcmp ("unshade", str) == 0) + return META_BUTTON_TYPE_UNSHADE; + else if (strcmp ("unabove", str) == 0) + return META_BUTTON_TYPE_UNABOVE; + else if (strcmp ("unstick", str) == 0) + return META_BUTTON_TYPE_UNSTICK; + } + + if (strcmp ("close", str) == 0) + return META_BUTTON_TYPE_CLOSE; + else if (strcmp ("maximize", str) == 0) + return META_BUTTON_TYPE_MAXIMIZE; + else if (strcmp ("minimize", str) == 0) + return META_BUTTON_TYPE_MINIMIZE; + else if (strcmp ("menu", str) == 0) + return META_BUTTON_TYPE_MENU; + else if (strcmp ("appmenu", str) == 0) + return META_BUTTON_TYPE_APPMENU; + else if (strcmp ("left_left_background", str) == 0) + return META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND; + else if (strcmp ("left_middle_background", str) == 0) + return META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND; + else if (strcmp ("left_right_background", str) == 0) + return META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND; + else if (strcmp ("left_single_background", str) == 0) + return META_BUTTON_TYPE_LEFT_SINGLE_BACKGROUND; + else if (strcmp ("right_left_background", str) == 0) + return META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND; + else if (strcmp ("right_middle_background", str) == 0) + return META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND; + else if (strcmp ("right_right_background", str) == 0) + return META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND; + else if (strcmp ("right_single_background", str) == 0) + return META_BUTTON_TYPE_RIGHT_SINGLE_BACKGROUND; + else + return META_BUTTON_TYPE_LAST; +} + +static MetaFramePiece +meta_frame_piece_from_string (const char *str) +{ + if (strcmp ("entire_background", str) == 0) + return META_FRAME_PIECE_ENTIRE_BACKGROUND; + else if (strcmp ("titlebar", str) == 0) + return META_FRAME_PIECE_TITLEBAR; + else if (strcmp ("titlebar_middle", str) == 0) + return META_FRAME_PIECE_TITLEBAR_MIDDLE; + else if (strcmp ("left_titlebar_edge", str) == 0) + return META_FRAME_PIECE_LEFT_TITLEBAR_EDGE; + else if (strcmp ("right_titlebar_edge", str) == 0) + return META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE; + else if (strcmp ("top_titlebar_edge", str) == 0) + return META_FRAME_PIECE_TOP_TITLEBAR_EDGE; + else if (strcmp ("bottom_titlebar_edge", str) == 0) + return META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE; + else if (strcmp ("title", str) == 0) + return META_FRAME_PIECE_TITLE; + else if (strcmp ("left_edge", str) == 0) + return META_FRAME_PIECE_LEFT_EDGE; + else if (strcmp ("right_edge", str) == 0) + return META_FRAME_PIECE_RIGHT_EDGE; + else if (strcmp ("bottom_edge", str) == 0) + return META_FRAME_PIECE_BOTTOM_EDGE; + else if (strcmp ("overlay", str) == 0) + return META_FRAME_PIECE_OVERLAY; + else + return META_FRAME_PIECE_LAST; +} + +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 (g_strcmp0 (element_name, "piece") == 0) + { + 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_metacity_lookup_draw_op_list (info->metacity, 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 (g_strcmp0 (element_name, "button") == 0) + { + const char *function = NULL; + const char *state = NULL; + const char *draw_ops = NULL; + guint earliest_version; + gint required_version; + + 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 (info->metacity, function); + 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; + } + + earliest_version = meta_theme_metacity_earliest_version_with_button (info->button_type); + required_version = peek_required_version (info); + + if (earliest_version > (guint) required_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, required_version, earliest_version); + 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_metacity_lookup_draw_op_list (info->metacity, + 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); + } + 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_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 (g_strcmp0 (element_name, "draw_ops") == 0) + { + 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 (g_strcmp0 (element_name, "draw_ops") == 0) + { + 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"); + } +} + +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 (g_strcmp0 (element_name, "draw_ops") == 0) + { + 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 MetaFrameFocus +meta_frame_focus_from_string (const char *str) +{ + if (strcmp ("no", str) == 0) + return META_FRAME_FOCUS_NO; + else if (strcmp ("yes", str) == 0) + return META_FRAME_FOCUS_YES; + else + return META_FRAME_FOCUS_LAST; +} + +static MetaFrameResize +meta_frame_resize_from_string (const char *str) +{ + if (strcmp ("none", str) == 0) + return META_FRAME_RESIZE_NONE; + else if (strcmp ("vertical", str) == 0) + return META_FRAME_RESIZE_VERTICAL; + else if (strcmp ("horizontal", str) == 0) + return META_FRAME_RESIZE_HORIZONTAL; + else if (strcmp ("both", str) == 0) + return META_FRAME_RESIZE_BOTH; + else + return META_FRAME_RESIZE_LAST; +} + +static MetaFrameState +meta_frame_state_from_string (const char *str) +{ + if (strcmp ("normal", str) == 0) + return META_FRAME_STATE_NORMAL; + else if (strcmp ("maximized", str) == 0) + return META_FRAME_STATE_MAXIMIZED; + else if (strcmp ("tiled_left", str) == 0) + return META_FRAME_STATE_TILED_LEFT; + else if (strcmp ("tiled_right", str) == 0) + return META_FRAME_STATE_TILED_RIGHT; + else if (strcmp ("shaded", str) == 0) + return META_FRAME_STATE_SHADED; + else if (strcmp ("maximized_and_shaded", str) == 0) + return META_FRAME_STATE_MAXIMIZED_AND_SHADED; + else if (strcmp ("tiled_left_and_shaded", str) == 0) + return META_FRAME_STATE_TILED_LEFT_AND_SHADED; + else if (strcmp ("tiled_right_and_shaded", str) == 0) + return META_FRAME_STATE_TILED_RIGHT_AND_SHADED; + else + return META_FRAME_STATE_LAST; +} + +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 (g_strcmp0 (element_name, "frame") == 0) + { + 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_metacity_lookup_style (info->metacity, 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 (theme_allows (info->metacity, 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; + + case META_FRAME_STATE_MAXIMIZED: + case META_FRAME_STATE_TILED_LEFT: + case META_FRAME_STATE_TILED_RIGHT: + case META_FRAME_STATE_MAXIMIZED_AND_SHADED: + case META_FRAME_STATE_TILED_LEFT_AND_SHADED: + case META_FRAME_STATE_TILED_RIGHT_AND_SHADED: + case META_FRAME_STATE_LAST: + 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; + break; + } + + 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_TILED_LEFT: + if (info->style_set->tiled_left_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->tiled_left_styles[frame_focus] = frame_style; + break; + + case META_FRAME_STATE_TILED_RIGHT: + if (info->style_set->tiled_right_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->tiled_right_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_TILED_LEFT_AND_SHADED: + if (info->style_set->tiled_left_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->tiled_left_and_shaded_styles[frame_focus] = frame_style; + break; + + case META_FRAME_STATE_TILED_RIGHT_AND_SHADED: + if (info->style_set->tiled_right_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->tiled_right_and_shaded_styles[frame_focus] = frame_style; + break; + + case META_FRAME_STATE_LAST: + default: + 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 const gchar * +find_version (const gchar **attribute_names, + const gchar **attribute_values) +{ + int i; + + for (i = 0; attribute_names[i]; i++) + { + if (strcmp (attribute_names[i], "version") == 0) + return attribute_values[i]; + } + + return NULL; +} + +/* Returns whether the version element was successfully parsed. + * If successfully parsed, then two additional items are returned: + * + * satisfied: whether this version of Mutter meets the version check + * minimum_required: minimum version of theme format required by version check + */ +static gboolean +check_version (GMarkupParseContext *context, + const char *version_str, + gboolean *satisfied, + guint *minimum_required, + GError **error) +{ + static GRegex *version_regex; + GMatchInfo *info; + char *comparison_str, *major_str, *minor_str; + guint version; + + *minimum_required = 0; + + if (!version_regex) + version_regex = g_regex_new ("^\\s*([<>]=?)\\s*(\\d+)(\\.\\d+)?\\s*$", 0, 0, NULL); + + if (!g_regex_match (version_regex, version_str, 0, &info)) + { + g_match_info_free (info); + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Bad version specification '%s'"), version_str); + return FALSE; + } + + comparison_str = g_match_info_fetch (info, 1); + major_str = g_match_info_fetch (info, 2); + minor_str = g_match_info_fetch (info, 3); + + version = 1000 * atoi (major_str); + /* might get NULL, see: https://bugzilla.gnome.org/review?bug=588217 */ + if (minor_str && minor_str[0]) + version += atoi (minor_str + 1); + + if (comparison_str[0] == '<') + { + if (comparison_str[1] == '=') + *satisfied = THEME_VERSION <= version; + else + *satisfied = THEME_VERSION < version; + } + else + { + if (comparison_str[1] == '=') + { + *satisfied = THEME_VERSION >= version; + *minimum_required = version; + } + else + { + *satisfied = THEME_VERSION > version; + *minimum_required = version + 1; + } + } + + g_free (comparison_str); + g_free (major_str); + g_free (minor_str); + g_match_info_free (info); + + return TRUE; +} + +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; + const char *version; + guint required_version = 0; + + if (info->skip_level > 0) + { + info->skip_level++; + return; + } + + required_version = peek_required_version (info); + version = find_version (attribute_names, attribute_values); + + if (version != NULL) + { + gboolean satisfied; + guint element_required; + + if (required_version < 3000) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("'version' attribute cannot be used in metacity-theme-1.xml or metacity-theme-2.xml")); + return; + } + + if (!check_version (context, version, &satisfied, &element_required, error)) + return; + + /* Two different ways of handling an unsatisfied version check: + * for the toplevel element of a file, we throw an error back so + * that the controlling code can go ahead and look for an + * alternate metacity-theme-1.xml or metacity-theme-2.xml; for + * other elements we just silently skip the element and children. + */ + if (peek_state (info) == STATE_START) + { + if (satisfied) + { + if (element_required > info->metacity->format_version) + info->metacity->format_version = element_required; + } + else + { + set_error (error, context, META_THEME_ERROR, META_THEME_ERROR_TOO_OLD, + _("Theme requires version %s but latest supported theme version is %d.%d"), + version, THEME_VERSION, THEME_MINOR_VERSION); + return; + } + } + else if (!satisfied) + { + info->skip_level = 1; + return; + } + + if (element_required > required_version) + required_version = element_required; + } + + push_required_version (info, required_version); + + switch (peek_state (info)) + { + case STATE_START: + if (strcmp (element_name, "metacity_theme") == 0) + 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; + + 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; + + default: + break; + } +} + +static const char* +meta_frame_type_to_string (MetaFrameType type) +{ + switch (type) + { + case META_FRAME_TYPE_NORMAL: + return "normal"; + case META_FRAME_TYPE_DIALOG: + return "dialog"; + case META_FRAME_TYPE_MODAL_DIALOG: + return "modal_dialog"; + case META_FRAME_TYPE_UTILITY: + return "utility"; + case META_FRAME_TYPE_MENU: + return "menu"; + case META_FRAME_TYPE_BORDER: + return "border"; + case META_FRAME_TYPE_ATTACHED: + return "attached"; + case META_FRAME_TYPE_LAST: + break; + default: + break; + } + + return "<unknown>"; +} + +static gboolean +theme_validate (MetaThemeMetacity *metacity, + GError **error) +{ + guint i; + + g_return_val_if_fail (metacity != NULL, FALSE); + + g_assert (metacity->name); + + if (metacity->readable_name == NULL) + { + /* Translators: This error means that a necessary XML tag (whose name + * is given in angle brackets) was not found in a given theme (whose + * name is given second, in quotation marks). + */ + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <%s> set for theme '%s'"), "name", metacity->name); + + return FALSE; + } + + if (metacity->author == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <%s> set for theme '%s'"), "author", metacity->name); + + return FALSE; + } + + if (metacity->date == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <%s> set for theme '%s'"), "date", metacity->name); + + return FALSE; + } + + if (metacity->description == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <%s> set for theme '%s'"), "description", + metacity->name); + + return FALSE; + } + + if (metacity->copyright == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <%s> set for theme '%s'"), "copyright", + metacity->name); + + return FALSE; + } + + for (i = 0; i < META_FRAME_TYPE_LAST; i++) + { + MetaThemeImpl *impl; + MetaFrameStyleSet *style_set; + + impl = META_THEME_IMPL (metacity); + style_set = meta_theme_impl_get_style_set (impl, i); + + if (i != META_FRAME_TYPE_ATTACHED && style_set == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No frame style set for window type '%s' in theme '%s', add a <window type='%s' style_set='whatever' /> element"), + meta_frame_type_to_string (i), metacity->name, + meta_frame_type_to_string (i)); + + return FALSE; + } + } + + return TRUE; +} + +static void +end_element_handler (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + ParseInfo *info; + + info = (ParseInfo *) user_data; + + if (info->skip_level > 0) + { + info->skip_level--; + return; + } + + switch (peek_state (info)) + { + case STATE_START: + break; + + case STATE_THEME: + g_assert (info->metacity); + + if (!theme_validate (info->metacity, error)) + add_context_to_error (error, context); + + 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: + { + ParseState parse_state; + + 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); + + parse_state = peek_state (info); + if (parse_state == STATE_BUTTON || parse_state == STATE_PIECE || + parse_state == STATE_MENU_ICON) + { + /* Leave info->op_list to be picked up + * when these elements are closed + */ + g_assert (info->op_list); + } + else if (parse_state == STATE_THEME) + { + g_assert (info->op_list); + meta_draw_op_list_unref (info->op_list); + info->op_list = NULL; + } + else + { + /* Op list can't occur in other contexts */ + g_assert_not_reached (); + } + } + 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, + peek_required_version (info), + 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 + { + MetaButtonType type; + MetaButtonState state; + + type = info->button_type; + state = info->button_state; + + info->style->buttons[type][state] = info->op_list; + info->op_list = NULL; + } + pop_state (info); + break; + + case STATE_MENU_ICON: + g_assert (info->metacity); + + 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; + + default: + break; + } + + pop_required_version (info); +} + +static gboolean +all_whitespace (const gchar *text, + gint text_len) +{ + const gchar *p; + const gchar *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; + + info = (ParseInfo *) user_data; + + if (info->skip_level > 0) + return; + + if (all_whitespace (text, text_len)) + return; + + switch (peek_state (info)) + { + case STATE_START: + g_assert_not_reached (); /* gmarkup shouldn't do this */ + break; + + case STATE_NAME: + if (info->metacity->readable_name != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> specified twice for this theme"), "name"); + return; + } + + info->metacity->readable_name = g_strndup (text, text_len); + break; + + case STATE_AUTHOR: + if (info->metacity->author != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> specified twice for this theme"), "author"); + return; + } + + info->metacity->author = g_strndup (text, text_len); + break; + + case STATE_COPYRIGHT: + if (info->metacity->copyright != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> specified twice for this theme"), "copyright"); + return; + } + + info->metacity->copyright = g_strndup (text, text_len); + break; + + case STATE_DATE: + if (info->metacity->date != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> specified twice for this theme"), "date"); + return; + } + + info->metacity->date = g_strndup (text, text_len); + break; + + case STATE_DESCRIPTION: + if (info->metacity->description != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> specified twice for this theme"), "description"); + return; + } + + info->metacity->description = g_strndup (text, text_len); + break; + + case STATE_THEME: + case STATE_INFO: + case STATE_CONSTANT: + case STATE_FRAME_GEOMETRY: + case STATE_DISTANCE: + case STATE_BORDER: + case STATE_ASPECT_RATIO: + case STATE_DRAW_OPS: + case STATE_LINE: + case STATE_RECTANGLE: + case STATE_ARC: + case STATE_CLIP: + case STATE_TINT: + case STATE_GRADIENT: + 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: + case STATE_COLOR: + case STATE_FRAME_STYLE: + case STATE_PIECE: + case STATE_BUTTON: + case STATE_MENU_ICON: + case STATE_FRAME_STYLE_SET: + case STATE_FRAME: + case STATE_WINDOW: + case STATE_FALLBACK: + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No text is allowed inside element <%s>"), + g_markup_parse_context_get_element (context)); + break; + + default: + break; + } +} + +/* If the theme is not-corrupt, keep looking for alternate versions + * in other locations we might be compatible with + */ +static gboolean +theme_error_is_fatal (GError *error) +{ + return !(error->domain == G_FILE_ERROR || + (error->domain == META_THEME_ERROR && + error->code == META_THEME_ERROR_TOO_OLD)); +} + +static gboolean +keep_trying (GError **error) +{ + if (*error && !theme_error_is_fatal (*error)) + { + g_clear_error (error); + return TRUE; + } + + return FALSE; +} + +static void +clear_theme (MetaThemeMetacity *metacity) +{ + MetaFrameType type; + MetaThemeImpl *impl; + + impl = META_THEME_IMPL (metacity); + + g_free (metacity->name); + metacity->name = NULL; + + g_free (metacity->dirname); + metacity->dirname = NULL; + + g_free (metacity->readable_name); + metacity->readable_name = NULL; + + g_free (metacity->date); + metacity->date = NULL; + + g_free (metacity->description); + metacity->description = NULL; + + g_free (metacity->author); + metacity->author = NULL; + + g_free (metacity->copyright); + metacity->copyright = NULL; + + g_clear_pointer (&metacity->integers, g_hash_table_destroy); + g_clear_pointer (&metacity->floats, g_hash_table_destroy); + g_clear_pointer (&metacity->colors, g_hash_table_destroy); + + g_hash_table_remove_all (metacity->draw_op_lists); + g_hash_table_remove_all (metacity->frame_layouts); + g_hash_table_remove_all (metacity->styles); + g_hash_table_remove_all (metacity->style_sets); + g_hash_table_remove_all (metacity->images); + + for (type = 0; type < META_FRAME_TYPE_LAST; type++) + meta_theme_impl_add_style_set (impl, type, NULL); +} + +static GMarkupParser metacity_theme_parser = + { + start_element_handler, + end_element_handler, + text_handler, + NULL, + NULL + }; + +static gboolean +load_theme (MetaThemeMetacity *metacity, + const gchar *theme_dir, + const gchar *theme_name, + guint major_version, + GError **error) +{ + gchar *filename; + gchar *file; + gboolean retval; + gchar *text; + gsize length; + ParseInfo *info; + GMarkupParseContext *context; + + g_return_val_if_fail (error && *error == NULL, FALSE); + + clear_theme (metacity); + + metacity->name = g_strdup (theme_name); + metacity->dirname = g_strdup (theme_dir); + metacity->format_version = 1000 * major_version; + + filename = g_strdup_printf (METACITY_THEME_FILENAME_FORMAT, major_version); + file = g_build_filename (theme_dir, filename, NULL); + + retval = FALSE; + text = NULL; + info = NULL; + context = NULL; + + if (!g_file_get_contents (file, &text, &length, error)) + goto out; + + g_debug ("Parsing theme file %s", file); + + info = parse_info_new (metacity); + context = g_markup_parse_context_new (&metacity_theme_parser, 0, info, NULL); + + if (!g_markup_parse_context_parse (context, text, length, error)) + goto out; + + if (!g_markup_parse_context_end_parse (context, error)) + goto out; + + retval = TRUE; + +out: + + if (*error && !theme_error_is_fatal (*error)) + g_debug ("Failed to read theme from file %s: %s", file, (*error)->message); + + if (context) + g_markup_parse_context_free (context); + + if (info) + parse_info_free (info); + + g_free (filename); + g_free (file); + g_free (text); + + return retval; +} + +static gchar * +get_theme_dir (const gchar *dir, + const gchar *theme_name) +{ + return g_build_filename (dir, "themes", theme_name, THEME_SUBDIR, NULL); +} + +static void +meta_theme_metacity_dispose (GObject *object) +{ + MetaThemeMetacity *metacity; + + metacity = META_THEME_METACITY (object); + + g_clear_pointer (&metacity->integers, g_hash_table_destroy); + g_clear_pointer (&metacity->floats, g_hash_table_destroy); + g_clear_pointer (&metacity->colors, g_hash_table_destroy); + + g_clear_pointer (&metacity->draw_op_lists, g_hash_table_destroy); + g_clear_pointer (&metacity->frame_layouts, g_hash_table_destroy); + g_clear_pointer (&metacity->styles, g_hash_table_destroy); + g_clear_pointer (&metacity->style_sets, g_hash_table_destroy); + + G_OBJECT_CLASS (meta_theme_metacity_parent_class)->dispose (object); +} + +static void +meta_theme_metacity_finalize (GObject *object) +{ + MetaThemeMetacity *metacity; + + metacity = META_THEME_METACITY (object); + + g_free (metacity->name); + g_free (metacity->dirname); + + g_free (metacity->readable_name); + g_free (metacity->author); + g_free (metacity->copyright); + g_free (metacity->date); + g_free (metacity->description); + + G_OBJECT_CLASS (meta_theme_metacity_parent_class)->finalize (object); +} + +static gboolean +meta_theme_metacity_load (MetaThemeImpl *impl, + const gchar *name, + GError **err) +{ + MetaThemeMetacity *metacity; + gboolean retval; + GError *error; + gint version; + + g_return_val_if_fail (err == NULL || *err == NULL, FALSE); + + metacity = META_THEME_METACITY (impl); + + retval = FALSE; + error = NULL; + + /* We try all supported major versions from current to oldest */ + for (version = THEME_MAJOR_VERSION; version > 0; version--) + { + gchar *dir; + const gchar *const *xdg_data_dirs; + gint i; + + /* Try XDG_USER_DATA_DIR first */ + dir = get_theme_dir (g_get_user_data_dir(), name); + retval = load_theme (metacity, dir, name, version, &error); + g_free (dir); + + if (!keep_trying (&error)) + goto out; + + /* Try each XDG_DATA_DIRS for theme */ + xdg_data_dirs = g_get_system_data_dirs(); + + for (i = 0; xdg_data_dirs[i] != NULL; i++) + { + dir = get_theme_dir (xdg_data_dirs[i], name); + retval = load_theme (metacity, dir, name, version, &error); + g_free (dir); + + if (!keep_trying (&error)) + goto out; + } + + /* Look for themes in DATADIR */ + dir = get_theme_dir (DATADIR, name); + retval = load_theme (metacity, dir, name, version, &error); + g_free (dir); + + if (!keep_trying (&error)) + goto out; + } + +out: + + if (!error && !retval) + { + g_set_error (&error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("Failed to find a valid file for theme %s"), name); + } + + if (error) + g_propagate_error (err, error); + + return retval; +} + +static void +meta_theme_metacity_class_init (MetaThemeMetacityClass *metacity_class) +{ + GObjectClass *object_class; + MetaThemeImplClass *impl_class; + + object_class = G_OBJECT_CLASS (metacity_class); + impl_class = META_THEME_IMPL_CLASS (metacity_class); + + object_class->dispose = meta_theme_metacity_dispose; + object_class->finalize = meta_theme_metacity_finalize; + + impl_class->load = meta_theme_metacity_load; +} + +static void +meta_theme_metacity_init (MetaThemeMetacity *metacity) +{ + metacity->draw_op_lists = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + (GDestroyNotify) meta_draw_op_list_unref); + + metacity->frame_layouts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + (GDestroyNotify) meta_frame_layout_unref); + + metacity->styles = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + (GDestroyNotify) meta_frame_style_unref); + + metacity->style_sets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + (GDestroyNotify) meta_frame_style_set_unref); + + metacity->images = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + (GDestroyNotify) g_object_unref); +} + +gboolean +meta_theme_metacity_lookup_int (MetaThemeMetacity *metacity, + const gchar *name, + gint *value) +{ + gpointer tmp; + + *value = 0; + + if (metacity->integers == NULL) + return FALSE; + + if (!g_hash_table_lookup_extended (metacity->integers, name, NULL, &tmp)) + return FALSE; + + *value = GPOINTER_TO_INT (tmp); + + return TRUE; +} + +gboolean +meta_theme_metacity_lookup_float (MetaThemeMetacity *metacity, + const gchar *name, + gdouble *value) +{ + gdouble *d; + + *value = 0.0; + + if (metacity->floats == NULL) + return FALSE; + + d = g_hash_table_lookup (metacity->floats, name); + + if (!d) + return FALSE; + + *value = *d; + + return TRUE; +} + MetaDrawOpList * meta_theme_metacity_lookup_draw_op_list (MetaThemeMetacity *metacity, const gchar *name) @@ -273,15 +4700,6 @@ meta_theme_metacity_lookup_draw_op_list (MetaThemeMetacity *metacity, return g_hash_table_lookup (metacity->draw_op_lists, name); } -void -meta_theme_metacity_insert_draw_op_list (MetaThemeMetacity *metacity, - const gchar *name, - MetaDrawOpList *op_list) -{ - meta_draw_op_list_ref (op_list); - g_hash_table_replace (metacity->draw_op_lists, g_strdup (name), op_list); -} - MetaFrameLayout * meta_theme_metacity_lookup_layout (MetaThemeMetacity *metacity, const gchar *name) @@ -289,15 +4707,6 @@ meta_theme_metacity_lookup_layout (MetaThemeMetacity *metacity, return g_hash_table_lookup (metacity->frame_layouts, name); } -void -meta_theme_metacity_insert_layout (MetaThemeMetacity *metacity, - const gchar *name, - MetaFrameLayout *layout) -{ - meta_frame_layout_ref (layout); - g_hash_table_replace (metacity->frame_layouts, g_strdup (name), layout); -} - MetaFrameStyle * meta_theme_metacity_lookup_style (MetaThemeMetacity *metacity, const gchar *name) @@ -305,15 +4714,6 @@ meta_theme_metacity_lookup_style (MetaThemeMetacity *metacity, return g_hash_table_lookup (metacity->styles, name); } -void -meta_theme_metacity_insert_style (MetaThemeMetacity *metacity, - const gchar *name, - MetaFrameStyle *style) -{ - meta_frame_style_ref (style); - g_hash_table_replace (metacity->styles, g_strdup (name), style); -} - MetaFrameStyleSet * meta_theme_metacity_lookup_style_set (MetaThemeMetacity *metacity, const gchar *name) @@ -321,11 +4721,69 @@ meta_theme_metacity_lookup_style_set (MetaThemeMetacity *metacity, return g_hash_table_lookup (metacity->style_sets, name); } -void -meta_theme_metacity_insert_style_set (MetaThemeMetacity *metacity, - const gchar *name, - MetaFrameStyleSet *style_set) +const gchar * +meta_theme_metacity_get_name (MetaThemeMetacity *metacity) { - meta_frame_style_set_ref (style_set); - g_hash_table_replace (metacity->style_sets, g_strdup (name), style_set); + return metacity->name; +} + +const gchar * +meta_theme_metacity_get_readable_name (MetaThemeMetacity *metacity) +{ + return metacity->readable_name; +} + +gboolean +meta_theme_metacity_allows_shade_stick_above_buttons (MetaThemeMetacity *metacity) +{ + return theme_allows (metacity, META_THEME_SHADE_STICK_ABOVE_BUTTONS); +} + +/** + * Returns the earliest version of the theme format which required support + * for a particular button. (For example, "shade" first appeared in v2, and + * "close" in v1.) + * + * \param type the button type + * \return the number of the theme format + */ +guint +meta_theme_metacity_earliest_version_with_button (MetaButtonType type) +{ + switch (type) + { + case META_BUTTON_TYPE_CLOSE: + case META_BUTTON_TYPE_MAXIMIZE: + case META_BUTTON_TYPE_MINIMIZE: + case META_BUTTON_TYPE_MENU: + case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND: + case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND: + case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND: + case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND: + case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND: + case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND: + return 1000; + + case META_BUTTON_TYPE_SHADE: + case META_BUTTON_TYPE_ABOVE: + case META_BUTTON_TYPE_STICK: + case META_BUTTON_TYPE_UNSHADE: + case META_BUTTON_TYPE_UNABOVE: + case META_BUTTON_TYPE_UNSTICK: + return 2000; + + case META_BUTTON_TYPE_LEFT_SINGLE_BACKGROUND: + case META_BUTTON_TYPE_RIGHT_SINGLE_BACKGROUND: + return 3003; + + case META_BUTTON_TYPE_APPMENU: + return 3005; + + case META_BUTTON_TYPE_LAST: + default: + g_warning ("Unknown button %d", (gint) type); + break; + } + + return 1000; } diff --git a/libmetacity/meta-theme-metacity.h b/libmetacity/meta-theme-metacity.h index ad07d1e9..94625d8a 100644 --- a/libmetacity/meta-theme-metacity.h +++ b/libmetacity/meta-theme-metacity.h @@ -19,6 +19,7 @@ #ifndef META_THEME_METACITY_H #define META_THEME_METACITY_H +#include <libmetacity/meta-button-enums.h> #include "meta-theme-impl.h" G_BEGIN_DECLS @@ -32,60 +33,33 @@ typedef struct _MetaFrameStyleSet MetaFrameStyleSet; G_DECLARE_FINAL_TYPE (MetaThemeMetacity, meta_theme_metacity, META, THEME_METACITY, MetaThemeImpl) -gboolean meta_theme_metacity_define_int (MetaThemeMetacity *metacity, - const gchar *name, - gint value, - GError **error); - gboolean meta_theme_metacity_lookup_int (MetaThemeMetacity *metacity, const gchar *name, gint *value); -gboolean meta_theme_metacity_define_float (MetaThemeMetacity *metacity, - const gchar *name, - gdouble value, - GError **error); - gboolean meta_theme_metacity_lookup_float (MetaThemeMetacity *metacity, const gchar *name, gdouble *value); -gboolean meta_theme_metacity_define_color (MetaThemeMetacity *metacity, - const gchar *name, - const gchar *value, - GError **error); - -gboolean meta_theme_metacity_lookup_color (MetaThemeMetacity *metacity, - const gchar *name, - gchar **value); - MetaDrawOpList *meta_theme_metacity_lookup_draw_op_list (MetaThemeMetacity *metacity, const gchar *name); -void meta_theme_metacity_insert_draw_op_list (MetaThemeMetacity *metacity, - const gchar *name, - MetaDrawOpList *op_list); - MetaFrameLayout *meta_theme_metacity_lookup_layout (MetaThemeMetacity *metacity, const gchar *name); -void meta_theme_metacity_insert_layout (MetaThemeMetacity *metacity, - const gchar *name, - MetaFrameLayout *layout); - MetaFrameStyle *meta_theme_metacity_lookup_style (MetaThemeMetacity *metacity, const gchar *name); -void meta_theme_metacity_insert_style (MetaThemeMetacity *metacity, - const gchar *name, - MetaFrameStyle *style); - MetaFrameStyleSet *meta_theme_metacity_lookup_style_set (MetaThemeMetacity *metacity, const gchar *name); -void meta_theme_metacity_insert_style_set (MetaThemeMetacity *metacity, - const gchar *name, - MetaFrameStyleSet *style_set); +const gchar *meta_theme_metacity_get_name (MetaThemeMetacity *metacity); + +const gchar *meta_theme_metacity_get_readable_name (MetaThemeMetacity *metacity); + +gboolean meta_theme_metacity_allows_shade_stick_above_buttons (MetaThemeMetacity *metacity); + +guint meta_theme_metacity_earliest_version_with_button (MetaButtonType type); G_END_DECLS diff --git a/po/POTFILES.in b/po/POTFILES.in index f74a9722..fe30edc2 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -34,5 +34,4 @@ src/ui/menu.c src/ui/metaaccellabel.c src/ui/resizepopup.c src/ui/theme.c -src/ui/theme-parser.c src/ui/theme-viewer.c diff --git a/src/Makefile.am b/src/Makefile.am index 0943ef84..a3219dc5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -118,7 +118,6 @@ libmetacity_private_la_SOURCES = \ include/common.h \ ui/preview-widget.c \ ui/preview-widget.h \ - ui/theme-parser.c \ ui/theme-private.h \ ui/theme.c \ ui/theme.h \ diff --git a/src/ui/theme-parser.c b/src/ui/theme-parser.c deleted file mode 100644 index dd4b0bd8..00000000 --- a/src/ui/theme-parser.c +++ /dev/null @@ -1,4315 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ - -/* Metacity 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, see <http://www.gnu.org/licenses/>. - */ - -#include <config.h> -#include "theme-private.h" -#include "util.h" -#include <libmetacity/meta-theme-metacity.h> -#include <string.h> -#include <stdlib.h> - -/* 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_MAJOR_VERSION 3 -#define THEME_MINOR_VERSION 5 -#define THEME_VERSION (1000 * THEME_MAJOR_VERSION + THEME_MINOR_VERSION) - -#define METACITY_THEME_FILENAME_FORMAT "metacity-theme-%d.xml" - -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, - /* 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 -{ - /* This two lists contain stacks of state and required version - * (cast to pointers.) There is one list item for each currently - * open element. */ - GSList *states; - GSList *required_versions; - - const char *theme_name; /* name of theme (directory it's in) */ - const char *theme_file; /* theme filename */ - const 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 */ - int skip_level; /* depth of elements that we're ignoring */ -} ParseInfo; - -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 metacity_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->required_versions = NULL; - 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; - info->skip_level = 0; -} - -static void -parse_info_free (ParseInfo *info) -{ - g_slist_free (info->states); - g_slist_free (info->required_versions); - - 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); -} - -static void -push_required_version (ParseInfo *info, - int version) -{ - info->required_versions = g_slist_prepend (info->required_versions, - GINT_TO_POINTER (version)); -} - -static void -pop_required_version (ParseInfo *info) -{ - g_return_if_fail (info->required_versions != NULL); - - info->required_versions = g_slist_delete_link (info->required_versions, info->required_versions); -} - -static int -peek_required_version (ParseInfo *info) -{ - if (info->required_versions) - return GPOINTER_TO_INT (info->required_versions->data); - else - return info->format_version; -} - -#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) - { - if (retloc == NULL) - { - retval = FALSE; - goto out; - } - - 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; - - /* Can be present anywhere */ - if (strcmp (attribute_names[i], "version") == 0) - { - ++i; - continue; - } - - 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) -{ - int i = 0; - - /* Can be present anywhere */ - if (attribute_names[0] && strcmp (attribute_names[i], "version") == 0) - i++; - - if (attribute_names[i] != 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_metacity_lookup_int (META_THEME_METACITY (theme->impl), 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; - } - - meta_alpha_gradient_spec_add_alpha (spec, i, v); - - ++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_metacity_lookup_color (META_THEME_METACITY (theme->impl), - 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) -{ - MetaThemeMetacity *metacity; - - g_return_if_fail (peek_state (info) == STATE_THEME); - - metacity = META_THEME_METACITY (info->theme->impl); - - 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_metacity_define_float (META_THEME_METACITY (info->theme->impl), - 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_metacity_define_int (META_THEME_METACITY (info->theme->impl), - name, ival, error)) - { - add_context_to_error (error, context); - return; - } - } - } - else - { - if (!meta_theme_metacity_define_color (META_THEME_METACITY (info->theme->impl), - 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_metacity_lookup_layout (metacity, 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_metacity_lookup_layout (metacity, 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_metacity_insert_layout (metacity, 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_metacity_lookup_draw_op_list (metacity, 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_metacity_insert_draw_op_list (metacity, 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_metacity_lookup_style (metacity, 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_metacity_lookup_style (metacity, 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_metacity_lookup_layout (metacity, 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 = meta_alpha_gradient_spec_get_alpha (alpha_vector, 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_metacity_insert_style (metacity, 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_metacity_lookup_style_set (metacity, 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_metacity_lookup_style_set (metacity, 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_metacity_insert_style_set (metacity, 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 || - (type == META_FRAME_TYPE_ATTACHED && peek_required_version (info) < 3002)) - { - 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_metacity_lookup_style_set (metacity, - 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 (meta_theme_impl_get_style_set (info->theme->impl, 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); - meta_theme_impl_add_style_set (info->theme->impl, 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"); - } -} - -static MetaGradientType -meta_gradient_type_from_string (const char *str) -{ - if (strcmp ("vertical", str) == 0) - return META_GRADIENT_VERTICAL; - else if (strcmp ("horizontal", str) == 0) - return META_GRADIENT_HORIZONTAL; - else if (strcmp ("diagonal", str) == 0) - return META_GRADIENT_DIAGONAL; - else - return META_GRADIENT_LAST; -} - -static GtkShadowType -meta_gtk_shadow_from_string (const char *str) -{ - if (strcmp ("none", str) == 0) - return GTK_SHADOW_NONE; - else if (strcmp ("in", str) == 0) - return GTK_SHADOW_IN; - else if (strcmp ("out", str) == 0) - return GTK_SHADOW_OUT; - else if (strcmp ("etched_in", str) == 0) - return GTK_SHADOW_ETCHED_IN; - else if (strcmp ("etched_out", str) == 0) - return GTK_SHADOW_ETCHED_OUT; - else - return -1; -} - -static GtkArrowType -meta_gtk_arrow_from_string (const char *str) -{ - if (strcmp ("up", str) == 0) - return GTK_ARROW_UP; - else if (strcmp ("down", str) == 0) - return GTK_ARROW_DOWN; - else if (strcmp ("left", str) == 0) - return GTK_ARROW_LEFT; - else if (strcmp ("right", str) == 0) - return GTK_ARROW_RIGHT; - else if (strcmp ("none", str) == 0) - return GTK_ARROW_NONE; - else - return -1; -} - -/** - * Returns a fill_type from a string. The inverse of - * meta_image_fill_type_to_string(). - * - * \param str a string representing a fill_type - * \result the fill_type, or -1 if it represents no fill_type. - */ -static MetaImageFillType -meta_image_fill_type_from_string (const char *str) -{ - if (strcmp ("tile", str) == 0) - return META_IMAGE_FILL_TILE; - else if (strcmp ("scale", str) == 0) - return META_IMAGE_FILL_SCALE; - else - return -1; -} - -static GdkPixbuf * -meta_theme_load_image (MetaTheme *theme, - const char *filename, - guint size_of_theme_icons, - GError **error) -{ - GdkPixbuf *pixbuf; - - pixbuf = g_hash_table_lookup (theme->images_by_filename, - filename); - - if (pixbuf == NULL) - { - - if (g_str_has_prefix (filename, "theme:") && - META_THEME_ALLOWS (theme, META_THEME_IMAGES_FROM_ICON_THEMES)) - { - pixbuf = gtk_icon_theme_load_icon ( - gtk_icon_theme_get_default (), - filename+6, - size_of_theme_icons, - 0, - error); - if (pixbuf == NULL) return NULL; - } - else - { - char *full_path; - full_path = g_build_filename (theme->dirname, filename, NULL); - - pixbuf = gdk_pixbuf_new_from_file (full_path, error); - if (pixbuf == NULL) - { - g_free (full_path); - return NULL; - } - - g_free (full_path); - } - g_hash_table_replace (theme->images_by_filename, - g_strdup (filename), - pixbuf); - } - - g_assert (pixbuf); - - g_object_ref (G_OBJECT (pixbuf)); - - return pixbuf; -} - -static void -parse_draw_op_element (GMarkupParseContext *context, - const gchar *element_name, - const gchar **attribute_names, - const gchar **attribute_values, - ParseInfo *info, - GError **error) -{ - MetaThemeMetacity *metacity; - - g_return_if_fail (peek_state (info) == STATE_DRAW_OPS); - - metacity = META_THEME_METACITY (info->theme->impl); - - 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; - - 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 (metacity, x1, NULL); - op->data.line.y1 = meta_draw_spec_new (metacity, y1, NULL); - - if (strcmp(x1, x2)==0) - op->data.line.x2 = NULL; - else - op->data.line.x2 = meta_draw_spec_new (metacity, x2, NULL); - - if (strcmp(y1, y2)==0) - op->data.line.y2 = NULL; - else - op->data.line.y2 = meta_draw_spec_new (metacity, 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; - - 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 (metacity, x, NULL); - op->data.rectangle.y = meta_draw_spec_new (metacity, y, NULL); - op->data.rectangle.width = meta_draw_spec_new (metacity, width, NULL); - op->data.rectangle.height = meta_draw_spec_new (metacity, - 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 (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 (metacity, x, NULL); - op->data.arc.y = meta_draw_spec_new (metacity, y, NULL); - op->data.arc.width = meta_draw_spec_new (metacity, width, NULL); - op->data.arc.height = meta_draw_spec_new (metacity, 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; - - op = meta_draw_op_new (META_DRAW_CLIP); - - op->data.clip.x = meta_draw_spec_new (metacity, x, NULL); - op->data.clip.y = meta_draw_spec_new (metacity, y, NULL); - op->data.clip.width = meta_draw_spec_new (metacity, width, NULL); - op->data.clip.height = meta_draw_spec_new (metacity, 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; - - 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 (metacity, x, NULL); - op->data.tint.y = meta_draw_spec_new (metacity, y, NULL); - op->data.tint.width = meta_draw_spec_new (metacity, width, NULL); - op->data.tint.height = meta_draw_spec_new (metacity, 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; - - 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 (metacity, x, NULL); - info->op->data.gradient.y = meta_draw_spec_new (metacity, y, NULL); - info->op->data.gradient.width = meta_draw_spec_new (metacity, - width, NULL); - info->op->data.gradient.height = meta_draw_spec_new (metacity, - 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; - - 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 (metacity, x, NULL); - op->data.image.y = meta_draw_spec_new (metacity, y, NULL); - op->data.image.width = meta_draw_spec_new (metacity, width, NULL); - op->data.image.height = meta_draw_spec_new (metacity, 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; - GtkStateFlags 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; - - filled_val = TRUE; - if (filled && !parse_boolean (filled, &filled_val, context, error)) - return; - - if (!meta_gtk_state_from_string (state, &state_val)) - { - 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 (metacity, x, NULL); - op->data.gtk_arrow.y = meta_draw_spec_new (metacity, y, NULL); - op->data.gtk_arrow.width = meta_draw_spec_new (metacity, width, NULL); - op->data.gtk_arrow.height = meta_draw_spec_new (metacity, - 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; - GtkStateFlags 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 (!meta_gtk_state_from_string (state, &state_val)) - { - 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 (metacity, x, NULL); - op->data.gtk_box.y = meta_draw_spec_new (metacity, y, NULL); - op->data.gtk_box.width = meta_draw_spec_new (metacity, width, NULL); - op->data.gtk_box.height = meta_draw_spec_new (metacity, 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; - GtkStateFlags state_val; - - if (!locate_attributes (context, element_name, attribute_names, attribute_values, - error, - "!state", &state, - "!x", &x, "!y1", &y1, "!y2", &y2, - NULL)) - return; - - if (!meta_gtk_state_from_string (state, &state_val)) - { - 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 (metacity, x, NULL); - op->data.gtk_vline.y1 = meta_draw_spec_new (metacity, y1, NULL); - op->data.gtk_vline.y2 = meta_draw_spec_new (metacity, 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; - - 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 (metacity, x, NULL); - op->data.icon.y = meta_draw_spec_new (metacity, y, NULL); - op->data.icon.width = meta_draw_spec_new (metacity, width, NULL); - op->data.icon.height = meta_draw_spec_new (metacity, 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; - const char *ellipsize_width; - MetaColorSpec *color_spec; - - if (!locate_attributes (context, element_name, attribute_names, attribute_values, - error, - "!color", &color, - "!x", &x, "!y", &y, - "ellipsize_width", &ellipsize_width, - NULL)) - return; - - if (ellipsize_width && peek_required_version (info) < 3001) - { - set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, - ATTRIBUTE_NOT_FOUND, "ellipsize_width", element_name); - 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_TITLE); - - op->data.title.color_spec = color_spec; - - op->data.title.x = meta_draw_spec_new (metacity, x, NULL); - op->data.title.y = meta_draw_spec_new (metacity, y, NULL); - if (ellipsize_width) - op->data.title.ellipsize_width = meta_draw_spec_new (metacity, ellipsize_width, 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 - */ - op_list = meta_theme_metacity_lookup_draw_op_list (metacity, 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 (metacity, x ? x : "0", NULL); - op->data.op_list.y = meta_draw_spec_new (metacity, y ? y : "0", NULL); - op->data.op_list.width = meta_draw_spec_new (metacity, - width ? width : "width", - NULL); - op->data.op_list.height = meta_draw_spec_new (metacity, - 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 */ - op_list = meta_theme_metacity_lookup_draw_op_list (metacity, 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 (metacity, x ? x : "0", NULL); - op->data.tile.y = meta_draw_spec_new (metacity, y ? y : "0", NULL); - op->data.tile.width = meta_draw_spec_new (metacity, - width ? width : "width", - NULL); - op->data.tile.height = meta_draw_spec_new (metacity, - height ? height : "height", - NULL); - op->data.tile.tile_xoffset = meta_draw_spec_new (metacity, - tile_xoffset ? tile_xoffset : "0", - NULL); - op->data.tile.tile_yoffset = meta_draw_spec_new (metacity, - tile_yoffset ? tile_yoffset : "0", - NULL); - op->data.tile.tile_width = meta_draw_spec_new (metacity, tile_width, NULL); - op->data.tile.tile_height = meta_draw_spec_new (metacity, 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); - - meta_gradient_spec_add_color_spec (info->op->data.gradient.gradient_spec, - 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 MetaButtonState -meta_button_state_from_string (const char *str) -{ - if (strcmp ("normal", str) == 0) - return META_BUTTON_STATE_NORMAL; - else if (strcmp ("pressed", str) == 0) - return META_BUTTON_STATE_PRESSED; - else if (strcmp ("prelight", str) == 0) - return META_BUTTON_STATE_PRELIGHT; - else - return META_BUTTON_STATE_LAST; -} - -static MetaButtonType -meta_button_type_from_string (const char *str, MetaTheme *theme) -{ - if (META_THEME_ALLOWS(theme, META_THEME_SHADE_STICK_ABOVE_BUTTONS)) - { - if (strcmp ("shade", str) == 0) - return META_BUTTON_TYPE_SHADE; - else if (strcmp ("above", str) == 0) - return META_BUTTON_TYPE_ABOVE; - else if (strcmp ("stick", str) == 0) - return META_BUTTON_TYPE_STICK; - else if (strcmp ("unshade", str) == 0) - return META_BUTTON_TYPE_UNSHADE; - else if (strcmp ("unabove", str) == 0) - return META_BUTTON_TYPE_UNABOVE; - else if (strcmp ("unstick", str) == 0) - return META_BUTTON_TYPE_UNSTICK; - } - - if (strcmp ("close", str) == 0) - return META_BUTTON_TYPE_CLOSE; - else if (strcmp ("maximize", str) == 0) - return META_BUTTON_TYPE_MAXIMIZE; - else if (strcmp ("minimize", str) == 0) - return META_BUTTON_TYPE_MINIMIZE; - else if (strcmp ("menu", str) == 0) - return META_BUTTON_TYPE_MENU; - else if (strcmp ("appmenu", str) == 0) - return META_BUTTON_TYPE_APPMENU; - else if (strcmp ("left_left_background", str) == 0) - return META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND; - else if (strcmp ("left_middle_background", str) == 0) - return META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND; - else if (strcmp ("left_right_background", str) == 0) - return META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND; - else if (strcmp ("left_single_background", str) == 0) - return META_BUTTON_TYPE_LEFT_SINGLE_BACKGROUND; - else if (strcmp ("right_left_background", str) == 0) - return META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND; - else if (strcmp ("right_middle_background", str) == 0) - return META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND; - else if (strcmp ("right_right_background", str) == 0) - return META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND; - else if (strcmp ("right_single_background", str) == 0) - return META_BUTTON_TYPE_RIGHT_SINGLE_BACKGROUND; - else - return META_BUTTON_TYPE_LAST; -} - -static MetaFramePiece -meta_frame_piece_from_string (const char *str) -{ - if (strcmp ("entire_background", str) == 0) - return META_FRAME_PIECE_ENTIRE_BACKGROUND; - else if (strcmp ("titlebar", str) == 0) - return META_FRAME_PIECE_TITLEBAR; - else if (strcmp ("titlebar_middle", str) == 0) - return META_FRAME_PIECE_TITLEBAR_MIDDLE; - else if (strcmp ("left_titlebar_edge", str) == 0) - return META_FRAME_PIECE_LEFT_TITLEBAR_EDGE; - else if (strcmp ("right_titlebar_edge", str) == 0) - return META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE; - else if (strcmp ("top_titlebar_edge", str) == 0) - return META_FRAME_PIECE_TOP_TITLEBAR_EDGE; - else if (strcmp ("bottom_titlebar_edge", str) == 0) - return META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE; - else if (strcmp ("title", str) == 0) - return META_FRAME_PIECE_TITLE; - else if (strcmp ("left_edge", str) == 0) - return META_FRAME_PIECE_LEFT_EDGE; - else if (strcmp ("right_edge", str) == 0) - return META_FRAME_PIECE_RIGHT_EDGE; - else if (strcmp ("bottom_edge", str) == 0) - return META_FRAME_PIECE_BOTTOM_EDGE; - else if (strcmp ("overlay", str) == 0) - return META_FRAME_PIECE_OVERLAY; - else - return META_FRAME_PIECE_LAST; -} - -static void -parse_style_element (GMarkupParseContext *context, - const gchar *element_name, - const gchar **attribute_names, - const gchar **attribute_values, - ParseInfo *info, - GError **error) -{ - MetaThemeMetacity *metacity; - - g_return_if_fail (peek_state (info) == STATE_FRAME_STYLE); - - g_assert (info->style); - - metacity = META_THEME_METACITY (info->theme->impl); - - 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_metacity_lookup_draw_op_list (metacity, 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; - gint required_version; - - 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; - } - - required_version = peek_required_version (info); - if (meta_theme_earliest_version_with_button (info->button_type) > - (guint)required_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, - required_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_metacity_lookup_draw_op_list (metacity, 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); - } - else - { - set_error (error, context, - G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, - _("Element <%s> is not allowed below <%s>"), - element_name, "frame_style"); - } -} - -static MetaFrameState -meta_frame_state_from_string (const char *str) -{ - if (strcmp ("normal", str) == 0) - return META_FRAME_STATE_NORMAL; - else if (strcmp ("maximized", str) == 0) - return META_FRAME_STATE_MAXIMIZED; - else if (strcmp ("tiled_left", str) == 0) - return META_FRAME_STATE_TILED_LEFT; - else if (strcmp ("tiled_right", str) == 0) - return META_FRAME_STATE_TILED_RIGHT; - else if (strcmp ("shaded", str) == 0) - return META_FRAME_STATE_SHADED; - else if (strcmp ("maximized_and_shaded", str) == 0) - return META_FRAME_STATE_MAXIMIZED_AND_SHADED; - else if (strcmp ("tiled_left_and_shaded", str) == 0) - return META_FRAME_STATE_TILED_LEFT_AND_SHADED; - else if (strcmp ("tiled_right_and_shaded", str) == 0) - return META_FRAME_STATE_TILED_RIGHT_AND_SHADED; - else - return META_FRAME_STATE_LAST; -} - -static MetaFrameResize -meta_frame_resize_from_string (const char *str) -{ - if (strcmp ("none", str) == 0) - return META_FRAME_RESIZE_NONE; - else if (strcmp ("vertical", str) == 0) - return META_FRAME_RESIZE_VERTICAL; - else if (strcmp ("horizontal", str) == 0) - return META_FRAME_RESIZE_HORIZONTAL; - else if (strcmp ("both", str) == 0) - return META_FRAME_RESIZE_BOTH; - else - return META_FRAME_RESIZE_LAST; -} - -static MetaFrameFocus -meta_frame_focus_from_string (const char *str) -{ - if (strcmp ("no", str) == 0) - return META_FRAME_FOCUS_NO; - else if (strcmp ("yes", str) == 0) - return META_FRAME_FOCUS_YES; - else - return META_FRAME_FOCUS_LAST; -} - -static void -parse_style_set_element (GMarkupParseContext *context, - const gchar *element_name, - const gchar **attribute_names, - const gchar **attribute_values, - ParseInfo *info, - GError **error) -{ - MetaThemeMetacity *metacity; - - g_return_if_fail (peek_state (info) == STATE_FRAME_STYLE_SET); - - metacity = META_THEME_METACITY (info->theme->impl); - - 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_metacity_lookup_style (metacity, 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; - - case META_FRAME_STATE_MAXIMIZED: - case META_FRAME_STATE_TILED_LEFT: - case META_FRAME_STATE_TILED_RIGHT: - case META_FRAME_STATE_MAXIMIZED_AND_SHADED: - case META_FRAME_STATE_TILED_LEFT_AND_SHADED: - case META_FRAME_STATE_TILED_RIGHT_AND_SHADED: - case META_FRAME_STATE_LAST: - 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_TILED_LEFT: - if (info->style_set->tiled_left_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->tiled_left_styles[frame_focus] = frame_style; - break; - case META_FRAME_STATE_TILED_RIGHT: - if (info->style_set->tiled_right_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->tiled_right_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_TILED_LEFT_AND_SHADED: - if (info->style_set->tiled_left_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->tiled_left_and_shaded_styles[frame_focus] = frame_style; - break; - case META_FRAME_STATE_TILED_RIGHT_AND_SHADED: - if (info->style_set->tiled_right_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->tiled_right_and_shaded_styles[frame_focus] = frame_style; - break; - case META_FRAME_STATE_LAST: - g_assert_not_reached (); - break; - default: - 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"); - } -} - -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 const char * -find_version (const char **attribute_names, - const char **attribute_values) -{ - int i; - - for (i = 0; attribute_names[i]; i++) - { - if (strcmp (attribute_names[i], "version") == 0) - return attribute_values[i]; - } - - return NULL; -} - -/* Returns whether the version element was successfully parsed. - * If successfully parsed, then two additional items are returned: - * - * satisfied: whether this version of Mutter meets the version check - * minimum_required: minimum version of theme format required by version check - */ -static gboolean -check_version (GMarkupParseContext *context, - const char *version_str, - gboolean *satisfied, - guint *minimum_required, - GError **error) -{ - static GRegex *version_regex; - GMatchInfo *info; - char *comparison_str, *major_str, *minor_str; - guint version; - - *minimum_required = 0; - - if (!version_regex) - version_regex = g_regex_new ("^\\s*([<>]=?)\\s*(\\d+)(\\.\\d+)?\\s*$", 0, 0, NULL); - - if (!g_regex_match (version_regex, version_str, 0, &info)) - { - g_match_info_free (info); - set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, - _("Bad version specification '%s'"), version_str); - return FALSE; - } - - comparison_str = g_match_info_fetch (info, 1); - major_str = g_match_info_fetch (info, 2); - minor_str = g_match_info_fetch (info, 3); - - version = 1000 * atoi (major_str); - /* might get NULL, see: https://bugzilla.gnome.org/review?bug=588217 */ - if (minor_str && minor_str[0]) - version += atoi (minor_str + 1); - - if (comparison_str[0] == '<') - { - if (comparison_str[1] == '=') - *satisfied = THEME_VERSION <= version; - else - *satisfied = THEME_VERSION < version; - } - else - { - if (comparison_str[1] == '=') - { - *satisfied = THEME_VERSION >= version; - *minimum_required = version; - } - else - { - *satisfied = THEME_VERSION > version; - *minimum_required = version + 1; - } - } - - g_free (comparison_str); - g_free (major_str); - g_free (minor_str); - g_match_info_free (info); - - return TRUE; -} - -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; - const char *version; - guint required_version = 0; - - if (info->skip_level > 0) - { - info->skip_level++; - return; - } - - required_version = peek_required_version (info); - - version = find_version (attribute_names, attribute_values); - if (version != NULL) - { - gboolean satisfied; - guint element_required; - - if (required_version < 3000) - { - set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, - _("\"version\" attribute cannot be used in metacity-theme-1.xml or metacity-theme-2.xml")); - return; - } - - if (!check_version (context, version, &satisfied, &element_required, error)) - return; - - /* Two different ways of handling an unsatisfied version check: - * for the toplevel element of a file, we throw an error back so - * that the controlling code can go ahead and look for an - * alternate metacity-theme-1.xml or metacity-theme-2.xml; for - * other elements we just silently skip the element and children. - */ - if (peek_state (info) == STATE_START) - { - if (satisfied) - { - if (element_required > info->format_version) - info->format_version = element_required; - } - else - { - set_error (error, context, META_THEME_ERROR, META_THEME_ERROR_TOO_OLD, - _("Theme requires version %s but latest supported theme version is %d.%d"), - version, THEME_VERSION, THEME_MINOR_VERSION); - return; - } - } - else if (!satisfied) - { - info->skip_level = 1; - return; - } - - if (element_required > required_version) - required_version = element_required; - } - - push_required_version (info, required_version); - - switch (peek_state (info)) - { - case STATE_START: - if (strcmp (element_name, "metacity_theme") == 0) - { - 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; - 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; - default: - break; - } -} - -static const char* -meta_frame_type_to_string (MetaFrameType type) -{ - switch (type) - { - case META_FRAME_TYPE_NORMAL: - return "normal"; - case META_FRAME_TYPE_DIALOG: - return "dialog"; - case META_FRAME_TYPE_MODAL_DIALOG: - return "modal_dialog"; - case META_FRAME_TYPE_UTILITY: - return "utility"; - case META_FRAME_TYPE_MENU: - return "menu"; - case META_FRAME_TYPE_BORDER: - return "border"; - case META_FRAME_TYPE_ATTACHED: - return "attached"; - case META_FRAME_TYPE_LAST: - break; - default: - break; - } - - return "<unknown>"; -} - -static gboolean -meta_theme_validate (MetaTheme *theme, - GError **error) -{ - int i; - - g_return_val_if_fail (theme != NULL, FALSE); - - /* FIXME what else should be checked? */ - - g_assert (theme->name); - - if (theme->readable_name == NULL) - { - /* Translators: This error means that a necessary XML tag (whose name - * is given in angle brackets) was not found in a given theme (whose - * name is given second, in quotation marks). - */ - g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, - _("No <%s> set for theme \"%s\""), "name", theme->name); - return FALSE; - } - - if (theme->author == NULL) - { - g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, - _("No <%s> set for theme \"%s\""), "author", theme->name); - return FALSE; - } - - if (theme->date == NULL) - { - g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, - _("No <%s> set for theme \"%s\""), "date", theme->name); - return FALSE; - } - - if (theme->description == NULL) - { - g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, - _("No <%s> set for theme \"%s\""), "description", theme->name); - return FALSE; - } - - if (theme->copyright == NULL) - { - g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, - _("No <%s> set for theme \"%s\""), "copyright", theme->name); - return FALSE; - } - - for (i = 0; i < (int)META_FRAME_TYPE_LAST; i++) - if (i != (int)META_FRAME_TYPE_ATTACHED && - meta_theme_impl_get_style_set (theme->impl, i) == NULL) - { - g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, - _("No frame style set for window type \"%s\" in theme \"%s\", add a <window type=\"%s\" style_set=\"whatever\"/> element"), - meta_frame_type_to_string (i), - theme->name, - meta_frame_type_to_string (i)); - - return FALSE; - } - - return TRUE; -} - -static void -end_element_handler (GMarkupParseContext *context, - const gchar *element_name, - gpointer user_data, - GError **error) -{ - ParseInfo *info = user_data; - - if (info->skip_level > 0) - { - info->skip_level--; - return; - } - - 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); - - 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: - { - ParseState parse_state; - - 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); - - parse_state = peek_state (info); - if (parse_state == STATE_BUTTON || - parse_state == STATE_PIECE || - parse_state == STATE_MENU_ICON) - { - /* Leave info->op_list to be picked up - * when these elements are closed - */ - g_assert (info->op_list); - } - else if (parse_state == STATE_THEME) - { - g_assert (info->op_list); - meta_draw_op_list_unref (info->op_list); - info->op_list = NULL; - } - else - { - /* Op list can't occur in other contexts */ - g_assert_not_reached (); - } - } - 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, - peek_required_version (info), - 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; - 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; - default: - break; - } - - pop_required_version (info); -} - -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 (info->skip_level > 0) - return; - - if (all_whitespace (text, text_len)) - return; - - switch (peek_state (info)) - { - case STATE_START: - g_assert_not_reached (); /* gmarkup shouldn't do this */ - 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_THEME: - case STATE_INFO: - case STATE_CONSTANT: - case STATE_FRAME_GEOMETRY: - case STATE_DISTANCE: - case STATE_BORDER: - case STATE_ASPECT_RATIO: - case STATE_DRAW_OPS: - case STATE_LINE: - case STATE_RECTANGLE: - case STATE_ARC: - case STATE_CLIP: - case STATE_TINT: - case STATE_GRADIENT: - 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: - case STATE_COLOR: - case STATE_FRAME_STYLE: - case STATE_PIECE: - case STATE_BUTTON: - case STATE_MENU_ICON: - case STATE_FRAME_STYLE_SET: - case STATE_FRAME: - case STATE_WINDOW: - case STATE_FALLBACK: - set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, - _("No text is allowed inside element <%s>"), - g_markup_parse_context_get_element (context)); - break; - default: - break; - } -} - -/* If the theme is not-corrupt, keep looking for alternate versions - * in other locations we might be compatible with - */ -static gboolean -theme_error_is_fatal (GError *error) -{ - return !(error->domain == G_FILE_ERROR || - (error->domain == META_THEME_ERROR && - error->code == META_THEME_ERROR_TOO_OLD)); -} - -static void -clear_theme (MetaTheme *theme) -{ - g_free (theme->name); - theme->name = NULL; - - g_free (theme->dirname); - theme->dirname = NULL; - - g_free (theme->filename); - theme->filename = NULL; - - g_free (theme->readable_name); - theme->readable_name = NULL; - - g_free (theme->date); - theme->date = NULL; - - g_free (theme->description); - theme->description = NULL; - - g_free (theme->author); - theme->author = NULL; - - g_free (theme->copyright); - theme->copyright = NULL; - - if (theme->titlebar_font) - { - pango_font_description_free (theme->titlebar_font); - theme->titlebar_font = NULL; - } - - g_hash_table_remove_all (theme->images_by_filename); - - g_clear_object (&theme->impl); - theme->impl = g_object_new (META_TYPE_THEME_METACITY, NULL); -} - -static gboolean -load_theme (MetaTheme *theme, - const char *theme_dir, - const char *theme_name, - guint major_version, - GError **error) -{ - GMarkupParseContext *context; - ParseInfo info; - char *text; - gsize length; - char *theme_filename; - char *theme_file; - gboolean retval; - - g_return_val_if_fail (error && *error == NULL, FALSE); - - text = NULL; - retval = FALSE; - context = NULL; - - clear_theme (theme); - - theme_filename = g_strdup_printf (METACITY_THEME_FILENAME_FORMAT, major_version); - theme_file = g_build_filename (theme_dir, theme_filename, NULL); - - if (!g_file_get_contents (theme_file, &text, &length, error)) - goto out; - - meta_topic (META_DEBUG_THEMES, "Parsing theme file %s\n", theme_file); - - parse_info_init (&info); - - info.theme = theme; - info.theme_name = theme_name; - info.theme_file = theme_file; - info.theme_dir = theme_dir; - - info.format_version = 1000 * major_version; - - context = g_markup_parse_context_new (&metacity_theme_parser, 0, &info, NULL); - - if (!g_markup_parse_context_parse (context, text, length, error)) - goto out; - - if (!g_markup_parse_context_end_parse (context, error)) - goto out; - - retval = TRUE; - - out: - if (*error && !theme_error_is_fatal (*error)) - meta_topic (META_DEBUG_THEMES, "Failed to read theme from file %s: %s\n", - theme_file, (*error)->message); - - g_free (theme_filename); - g_free (theme_file); - g_free (text); - - if (context) - { - g_markup_parse_context_free (context); - parse_info_free (&info); - } - - return retval; -} - -static gboolean -keep_trying (GError **error) -{ - if (*error && !theme_error_is_fatal (*error)) - { - g_clear_error (error); - return TRUE; - } - - return FALSE; -} - -gboolean -meta_theme_load (MetaTheme *theme, - const gchar *theme_name, - GError **err) -{ - GError *error = NULL; - char *theme_dir; - gboolean retval; - const gchar* const* xdg_data_dirs; - int major_version; - int i; - - retval = FALSE; - - /* We try all supported major versions from current to oldest */ - for (major_version = THEME_MAJOR_VERSION; (major_version > 0); major_version--) - { - /* We try first in XDG_USER_DATA_DIR, XDG_DATA_DIRS, then system dir for themes */ - - /* Try XDG_USER_DATA_DIR first */ - theme_dir = g_build_filename (g_get_user_data_dir(), - "themes", - theme_name, - THEME_SUBDIR, - NULL); - - retval = load_theme (theme, theme_dir, theme_name, major_version, &error); - g_free (theme_dir); - if (!keep_trying (&error)) - goto out; - - /* Try each XDG_DATA_DIRS for theme */ - xdg_data_dirs = g_get_system_data_dirs(); - for(i = 0; xdg_data_dirs[i] != NULL; i++) - { - theme_dir = g_build_filename (xdg_data_dirs[i], - "themes", - theme_name, - THEME_SUBDIR, - NULL); - - retval = load_theme (theme, theme_dir, theme_name, major_version, &error); - g_free (theme_dir); - if (!keep_trying (&error)) - goto out; - } - - /* Look for themes in METACITY_DATADIR */ - theme_dir = g_build_filename (METACITY_DATADIR, - "themes", - theme_name, - THEME_SUBDIR, - NULL); - retval = load_theme (theme, theme_dir, theme_name, major_version, &error); - g_free (theme_dir); - if (!keep_trying (&error)) - goto out; - } - - out: - - if (!error && !retval) - g_set_error (&error, META_THEME_ERROR, META_THEME_ERROR_FAILED, - _("Failed to find a valid file for theme %s\n"), - theme_name); - - if (error) - { - g_propagate_error (err, error); - } - - return retval; -} diff --git a/src/ui/theme-private.h b/src/ui/theme-private.h index 9d547f88..acaa1315 100644 --- a/src/ui/theme-private.h +++ b/src/ui/theme-private.h @@ -33,39 +33,12 @@ G_BEGIN_DECLS */ struct _MetaTheme { - /** Name of the theme (on disk), e.g. "Crux" */ - char *name; - /** Path to the files associated with the theme */ - char *dirname; - /** - * Filename of the XML theme file. - * \bug Kept lying around for no discernable reason. - */ - char *filename; - /** Metadata: Human-readable name of the theme. */ - char *readable_name; - /** Metadata: Author of the theme. */ - char *author; - /** Metadata: Copyright holder. */ - char *copyright; - /** Metadata: Date of the theme. */ - char *date; - /** Metadata: Description of the theme. */ - char *description; - /** Version of the theme format. Older versions cannot use the features - * of newer versions even if they think they can (this is to allow forward - * and backward compatibility. - */ - guint format_version; - gboolean is_gtk_theme; gboolean composited; PangoFontDescription *titlebar_font; - GHashTable *images_by_filename; - MetaThemeImpl *impl; }; @@ -83,21 +56,6 @@ PangoFontDescription *meta_gtk_widget_get_font_desc (GtkWidget int meta_pango_font_desc_get_text_height (const PangoFontDescription *font_desc, PangoContext *context); -guint meta_theme_earliest_version_with_button (MetaButtonType type); - -#define META_THEME_ALLOWS(theme, feature) (theme->format_version >= feature) - -/* What version of the theme file format were various features introduced in? */ -#define META_THEME_SHADE_STICK_ABOVE_BUTTONS 2 -#define META_THEME_UBIQUITOUS_CONSTANTS 2 -#define META_THEME_VARIED_ROUND_CORNERS 2 -#define META_THEME_IMAGES_FROM_ICON_THEMES 2 -#define META_THEME_UNRESIZABLE_SHADED_STYLES 2 -#define META_THEME_DEGREES_IN_ARCS 2 -#define META_THEME_HIDDEN_BUTTONS 2 -#define META_THEME_COLOR_CONSTANTS 2 -#define META_THEME_FRAME_BACKGROUNDS 2 - G_END_DECLS #endif diff --git a/src/ui/theme-viewer.c b/src/ui/theme-viewer.c index 0716be5f..32eae2ad 100644 --- a/src/ui/theme-viewer.c +++ b/src/ui/theme-viewer.c @@ -839,6 +839,32 @@ benchmark_summary (void) return label; } +static const gchar * +theme_get_name (MetaTheme *theme) +{ + MetaThemeImpl *impl; + + impl = theme->impl; + + if (META_IS_THEME_METACITY (impl)) + return meta_theme_metacity_get_name (META_THEME_METACITY (impl)); + + return NULL; +} + +static const gchar * +theme_get_readable_name (MetaTheme *theme) +{ + MetaThemeImpl *impl; + + impl = theme->impl; + + if (META_IS_THEME_METACITY (impl)) + return meta_theme_metacity_get_readable_name (META_THEME_METACITY (impl)); + + return NULL; +} + int main (int argc, char **argv) { @@ -893,8 +919,8 @@ main (int argc, char **argv) exit (1); } - g_print (_("Loaded theme \"%s\" in %g seconds\n"), - global_theme->name, + g_print (_("Loaded theme '%s' in %g seconds\n"), + theme_get_name (global_theme), (end - start) / (double) CLOCKS_PER_SEC); run_theme_benchmark (); @@ -902,21 +928,18 @@ main (int argc, char **argv) window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_default_size (GTK_WINDOW (window), 350, 350); - if (strcmp (global_theme->name, global_theme->readable_name)==0) - gtk_window_set_title (GTK_WINDOW (window), - global_theme->readable_name); + if (g_strcmp0 (theme_get_name (global_theme), theme_get_readable_name (global_theme)) == 0) + gtk_window_set_title (GTK_WINDOW (window), theme_get_name (global_theme)); else { /* The theme directory name is different from the name the theme * gives itself within its file. Display both, directory name first. */ - gchar *title = g_strconcat (global_theme->name, " - ", - global_theme->readable_name, + gchar *title = g_strconcat (theme_get_name (global_theme), " - ", + theme_get_readable_name (global_theme), NULL); - gtk_window_set_title (GTK_WINDOW (window), - title); - + gtk_window_set_title (GTK_WINDOW (window), title); g_free (title); } diff --git a/src/ui/theme.c b/src/ui/theme.c index ede31d00..1bbc7e57 100644 --- a/src/ui/theme.c +++ b/src/ui/theme.c @@ -174,99 +174,188 @@ rect_for_function (MetaFrameGeometry *fgeom, MetaButtonFunction function, MetaTheme *theme) { + if (META_IS_THEME_METACITY (theme->impl)) + { + MetaThemeMetacity *metacity; - /* Firstly, check version-specific things. */ + metacity = META_THEME_METACITY (theme->impl); - if (META_THEME_ALLOWS(theme, META_THEME_SHADE_STICK_ABOVE_BUTTONS)) - { - switch (function) + if (meta_theme_metacity_allows_shade_stick_above_buttons (metacity)) { - case META_BUTTON_FUNCTION_SHADE: - if ((flags & META_FRAME_ALLOWS_SHADE) && !(flags & META_FRAME_SHADED)) - return &fgeom->shade_rect; - else - return NULL; - case META_BUTTON_FUNCTION_ABOVE: - if (!(flags & META_FRAME_ABOVE)) - return &fgeom->above_rect; - else - return NULL; - case META_BUTTON_FUNCTION_STICK: - if (!(flags & META_FRAME_STUCK)) - return &fgeom->stick_rect; - else - return NULL; - case META_BUTTON_FUNCTION_UNSHADE: - if ((flags & META_FRAME_ALLOWS_SHADE) && (flags & META_FRAME_SHADED)) - return &fgeom->unshade_rect; - else - return NULL; - case META_BUTTON_FUNCTION_UNABOVE: - if (flags & META_FRAME_ABOVE) - return &fgeom->unabove_rect; - else - return NULL; - case META_BUTTON_FUNCTION_UNSTICK: - if (flags & META_FRAME_STUCK) - return &fgeom->unstick_rect; - case META_BUTTON_FUNCTION_MENU: - case META_BUTTON_FUNCTION_APPMENU: - case META_BUTTON_FUNCTION_MINIMIZE: - case META_BUTTON_FUNCTION_MAXIMIZE: - case META_BUTTON_FUNCTION_CLOSE: - case META_BUTTON_FUNCTION_LAST: - default: - /* just go on to the next switch block */; - } - } + switch (function) + { + case META_BUTTON_FUNCTION_SHADE: + if ((flags & META_FRAME_ALLOWS_SHADE) && !(flags & META_FRAME_SHADED)) + return &fgeom->shade_rect; + else + return NULL; - /* now consider the buttons which exist in all versions */ + case META_BUTTON_FUNCTION_ABOVE: + if (!(flags & META_FRAME_ABOVE)) + return &fgeom->above_rect; + else + return NULL; - switch (function) - { - case META_BUTTON_FUNCTION_MENU: - if (flags & META_FRAME_ALLOWS_MENU) - return &fgeom->menu_rect; - else - return NULL; - case META_BUTTON_FUNCTION_APPMENU: - if (flags & META_FRAME_ALLOWS_APPMENU) - return &fgeom->appmenu_rect; - else - return NULL; - case META_BUTTON_FUNCTION_MINIMIZE: - if (flags & META_FRAME_ALLOWS_MINIMIZE) - return &fgeom->min_rect; - else - return NULL; - case META_BUTTON_FUNCTION_MAXIMIZE: - if (flags & META_FRAME_ALLOWS_MAXIMIZE) - return &fgeom->max_rect; - else - return NULL; - case META_BUTTON_FUNCTION_CLOSE: - if (flags & META_FRAME_ALLOWS_DELETE) - return &fgeom->close_rect; - else - return NULL; - case META_BUTTON_FUNCTION_STICK: - case META_BUTTON_FUNCTION_SHADE: - case META_BUTTON_FUNCTION_ABOVE: - case META_BUTTON_FUNCTION_UNSTICK: - case META_BUTTON_FUNCTION_UNSHADE: - case META_BUTTON_FUNCTION_UNABOVE: - /* we are being asked for a >v1 button which hasn't been handled yet, - * so obviously we're not in a theme which supports that version. - * therefore, we don't show the button. return NULL and all will - * be well. - */ - return NULL; + case META_BUTTON_FUNCTION_STICK: + if (!(flags & META_FRAME_STUCK)) + return &fgeom->stick_rect; + else + return NULL; - case META_BUTTON_FUNCTION_LAST: - return NULL; + case META_BUTTON_FUNCTION_UNSHADE: + if ((flags & META_FRAME_ALLOWS_SHADE) && (flags & META_FRAME_SHADED)) + return &fgeom->unshade_rect; + else + return NULL; - default: - break; + case META_BUTTON_FUNCTION_UNABOVE: + if (flags & META_FRAME_ABOVE) + return &fgeom->unabove_rect; + else + return NULL; + + case META_BUTTON_FUNCTION_UNSTICK: + if (flags & META_FRAME_STUCK) + return &fgeom->unstick_rect; + else + return NULL; + + case META_BUTTON_FUNCTION_MENU: + case META_BUTTON_FUNCTION_APPMENU: + case META_BUTTON_FUNCTION_MINIMIZE: + case META_BUTTON_FUNCTION_MAXIMIZE: + case META_BUTTON_FUNCTION_CLOSE: + case META_BUTTON_FUNCTION_LAST: + default: + break; + } + + /* now consider the buttons which exist in all versions */ + switch (function) + { + case META_BUTTON_FUNCTION_MENU: + if (flags & META_FRAME_ALLOWS_MENU) + return &fgeom->menu_rect; + else + return NULL; + + case META_BUTTON_FUNCTION_APPMENU: + if (flags & META_FRAME_ALLOWS_APPMENU) + return &fgeom->appmenu_rect; + else + return NULL; + + case META_BUTTON_FUNCTION_MINIMIZE: + if (flags & META_FRAME_ALLOWS_MINIMIZE) + return &fgeom->min_rect; + else + return NULL; + + case META_BUTTON_FUNCTION_MAXIMIZE: + if (flags & META_FRAME_ALLOWS_MAXIMIZE) + return &fgeom->max_rect; + else + return NULL; + + case META_BUTTON_FUNCTION_CLOSE: + if (flags & META_FRAME_ALLOWS_DELETE) + return &fgeom->close_rect; + else + return NULL; + + case META_BUTTON_FUNCTION_STICK: + case META_BUTTON_FUNCTION_SHADE: + case META_BUTTON_FUNCTION_ABOVE: + case META_BUTTON_FUNCTION_UNSTICK: + case META_BUTTON_FUNCTION_UNSHADE: + case META_BUTTON_FUNCTION_UNABOVE: + /* we are being asked for a >v1 button which hasn't been handled yet, + * so obviously we're not in a theme which supports that version. + * therefore, we don't show the button. return NULL and all will + * be well. + */ + return NULL; + + case META_BUTTON_FUNCTION_LAST: + default: + break; + } + } + } + else + { + switch (function) + { + case META_BUTTON_FUNCTION_MENU: + if (flags & META_FRAME_ALLOWS_MENU) + return &fgeom->menu_rect; + else + return NULL; + + case META_BUTTON_FUNCTION_APPMENU: + if (flags & META_FRAME_ALLOWS_APPMENU) + return &fgeom->appmenu_rect; + else + return NULL; + + case META_BUTTON_FUNCTION_MINIMIZE: + if (flags & META_FRAME_ALLOWS_MINIMIZE) + return &fgeom->min_rect; + else + return NULL; + + case META_BUTTON_FUNCTION_MAXIMIZE: + if (flags & META_FRAME_ALLOWS_MAXIMIZE) + return &fgeom->max_rect; + else + return NULL; + + case META_BUTTON_FUNCTION_CLOSE: + if (flags & META_FRAME_ALLOWS_DELETE) + return &fgeom->close_rect; + else + return NULL; + + case META_BUTTON_FUNCTION_SHADE: + if ((flags & META_FRAME_ALLOWS_SHADE) && !(flags & META_FRAME_SHADED)) + return &fgeom->shade_rect; + else + return NULL; + + case META_BUTTON_FUNCTION_ABOVE: + if (!(flags & META_FRAME_ABOVE)) + return &fgeom->above_rect; + else + return NULL; + + case META_BUTTON_FUNCTION_STICK: + if (!(flags & META_FRAME_STUCK)) + return &fgeom->stick_rect; + else + return NULL; + + case META_BUTTON_FUNCTION_UNSHADE: + if ((flags & META_FRAME_ALLOWS_SHADE) && (flags & META_FRAME_SHADED)) + return &fgeom->unshade_rect; + else + return NULL; + + case META_BUTTON_FUNCTION_UNABOVE: + if (flags & META_FRAME_ABOVE) + return &fgeom->unabove_rect; + else + return NULL; + + case META_BUTTON_FUNCTION_UNSTICK: + if (flags & META_FRAME_STUCK) + return &fgeom->unstick_rect; + else + return NULL; + + case META_BUTTON_FUNCTION_LAST: + default: + break; + } } return NULL; @@ -1043,9 +1132,12 @@ meta_frame_style_validate (MetaFrameStyle *style, { for (j = 0; j < META_BUTTON_STATE_LAST; j++) { + guint earliest_version; + + earliest_version = meta_theme_metacity_earliest_version_with_button (i); + if (get_button (style, i, j) == NULL && - meta_theme_earliest_version_with_button (i) <= current_theme_version - ) + earliest_version <= current_theme_version) { g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, @@ -1593,6 +1685,19 @@ meta_theme_get_current (void) return meta_current_theme; } +static const gchar * +theme_get_name (MetaTheme *theme) +{ + MetaThemeImpl *impl; + + impl = theme->impl; + + if (META_IS_THEME_METACITY (impl)) + return meta_theme_metacity_get_name (META_THEME_METACITY (impl)); + + return NULL; +} + static void theme_set_current_metacity (const gchar *name, gboolean force_reload, @@ -1604,9 +1709,8 @@ theme_set_current_metacity (const gchar *name, meta_topic (META_DEBUG_THEMES, "Setting current theme to \"%s\"\n", name); - if (!force_reload && - meta_current_theme && - g_strcmp0 (name, meta_current_theme->name) == 0) + if (!force_reload && meta_current_theme && + g_strcmp0 (name, theme_get_name (meta_current_theme)) == 0) return; new_theme = meta_theme_new (META_THEME_TYPE_METACITY); @@ -1630,7 +1734,8 @@ theme_set_current_metacity (const gchar *name, meta_current_theme = new_theme; - meta_topic (META_DEBUG_THEMES, "New theme is \"%s\"\n", meta_current_theme->name); + meta_topic (META_DEBUG_THEMES, "New theme is '%s'\n", + theme_get_name (meta_current_theme)); } } @@ -1654,7 +1759,7 @@ theme_set_current_gtk (const gchar *name, meta_current_theme->composited = composited; meta_current_theme->titlebar_font = pango_font_description_copy (titlebar_font); - meta_theme_impl_load (meta_current_theme->impl, name, NULL); + meta_theme_load (meta_current_theme, name, NULL); } void @@ -1683,12 +1788,6 @@ meta_theme_new (MetaThemeType type) theme->is_gtk_theme = FALSE; theme->composited = TRUE; - theme->images_by_filename = - g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - (GDestroyNotify) g_object_unref); - if (type == META_THEME_TYPE_GTK) theme->impl = g_object_new (META_TYPE_THEME_GTK, NULL); else if (type == META_THEME_TYPE_METACITY) @@ -1704,26 +1803,23 @@ meta_theme_free (MetaTheme *theme) { g_return_if_fail (theme != NULL); - g_free (theme->name); - g_free (theme->dirname); - g_free (theme->filename); - g_free (theme->readable_name); - g_free (theme->date); - g_free (theme->description); - g_free (theme->author); - g_free (theme->copyright); - if (theme->titlebar_font) pango_font_description_free (theme->titlebar_font); - g_hash_table_destroy (theme->images_by_filename); - g_clear_object (&theme->impl); DEBUG_FILL_STRUCT (theme); g_free (theme); } +gboolean +meta_theme_load (MetaTheme *theme, + const gchar *name, + GError **err) +{ + return meta_theme_impl_load (theme->impl, name, err); +} + void meta_theme_set_composited (MetaTheme *theme, gboolean composited) @@ -2076,50 +2172,3 @@ meta_frame_type_from_string (const char *str) else return META_FRAME_TYPE_LAST; } - -/** - * Returns the earliest version of the theme format which required support - * for a particular button. (For example, "shade" first appeared in v2, and - * "close" in v1.) - * - * \param type the button type - * \return the number of the theme format - */ -guint -meta_theme_earliest_version_with_button (MetaButtonType type) -{ - switch (type) - { - case META_BUTTON_TYPE_CLOSE: - case META_BUTTON_TYPE_MAXIMIZE: - case META_BUTTON_TYPE_MINIMIZE: - case META_BUTTON_TYPE_MENU: - case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND: - case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND: - case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND: - case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND: - case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND: - case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND: - return 1000; - - case META_BUTTON_TYPE_SHADE: - case META_BUTTON_TYPE_ABOVE: - case META_BUTTON_TYPE_STICK: - case META_BUTTON_TYPE_UNSHADE: - case META_BUTTON_TYPE_UNABOVE: - case META_BUTTON_TYPE_UNSTICK: - return 2000; - - case META_BUTTON_TYPE_LEFT_SINGLE_BACKGROUND: - case META_BUTTON_TYPE_RIGHT_SINGLE_BACKGROUND: - return 3003; - - case META_BUTTON_TYPE_APPMENU: - return 3005; - - case META_BUTTON_TYPE_LAST: - default: - meta_warning("Unknown button %d\n", type); - return 1000; - } -} |