[gtk+/client-side-decorations] Copy the theme renderer and parser from Mutter's sources



commit fd7629677e52df94bf3dc17a7457d1805ef7aaa0
Author: Federico Mena Quintero <federico novell com>
Date:   Thu Jun 10 14:59:14 2010 -0500

    Copy the theme renderer and parser from Mutter's sources
    
    We will refactor these to work for GTK+.

 gtk/theme-parser.c | 4292 +++++++++++++++++++++++++++++++++
 gtk/theme-parser.h |   32 +
 gtk/theme.c        | 6694 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 gtk/theme.h        | 1195 ++++++++++
 4 files changed, 12213 insertions(+), 0 deletions(-)
---
diff --git a/gtk/theme-parser.c b/gtk/theme-parser.c
new file mode 100644
index 0000000..2d18eff
--- /dev/null
+++ b/gtk/theme-parser.c
@@ -0,0 +1,4292 @@
+/* -*- 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "theme-parser.h"
+#include "util.h"
+#include <string.h>
+#include <stdlib.h>
+
+/* 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 1
+#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;
+
+typedef enum {
+  THEME_PARSE_ERROR_TOO_OLD,
+  THEME_PARSE_ERROR_TOO_FAILED
+} ThemeParseError;
+
+static GQuark
+theme_parse_error_quark (void)
+{
+  return g_quark_from_static_string ("theme-parse-error-quark");
+}
+
+#define THEME_PARSE_ERROR (theme_parse_error_quark ())
+
+static void set_error (GError             **err,
+                       GMarkupParseContext *context,
+                       int                  error_domain,
+                       int                  error_code,
+                       const char          *format,
+                       ...) G_GNUC_PRINTF (5, 6);
+
+static void add_context_to_error (GError             **err,
+                                  GMarkupParseContext *context);
+
+static void       parse_info_init (ParseInfo *info);
+static void       parse_info_free (ParseInfo *info);
+
+static void       push_state (ParseInfo  *info,
+                              ParseState  state);
+static void       pop_state  (ParseInfo  *info);
+static ParseState peek_state (ParseInfo  *info);
+
+
+static void parse_toplevel_element  (GMarkupParseContext  *context,
+                                     const gchar          *element_name,
+                                     const gchar         **attribute_names,
+                                     const gchar         **attribute_values,
+                                     ParseInfo            *info,
+                                     GError              **error);
+static void parse_info_element      (GMarkupParseContext  *context,
+                                     const gchar          *element_name,
+                                     const gchar         **attribute_names,
+                                     const gchar         **attribute_values,
+                                     ParseInfo            *info,
+                                     GError              **error);
+static void parse_geometry_element  (GMarkupParseContext  *context,
+                                     const gchar          *element_name,
+                                     const gchar         **attribute_names,
+                                     const gchar         **attribute_values,
+                                     ParseInfo            *info,
+                                     GError              **error);
+static void parse_draw_op_element   (GMarkupParseContext  *context,
+                                     const gchar          *element_name,
+                                     const gchar         **attribute_names,
+                                     const gchar         **attribute_values,
+                                     ParseInfo            *info,
+                                     GError              **error);
+static void parse_gradient_element  (GMarkupParseContext  *context,
+                                     const gchar          *element_name,
+                                     const gchar         **attribute_names,
+                                     const gchar         **attribute_values,
+                                     ParseInfo            *info,
+                                     GError              **error);
+static void parse_style_element     (GMarkupParseContext  *context,
+                                     const gchar          *element_name,
+                                     const gchar         **attribute_names,
+                                     const gchar         **attribute_values,
+                                     ParseInfo            *info,
+                                     GError              **error);
+static void parse_style_set_element (GMarkupParseContext  *context,
+                                     const gchar          *element_name,
+                                     const gchar         **attribute_names,
+                                     const gchar         **attribute_values,
+                                     ParseInfo            *info,
+                                     GError              **error);
+
+static void parse_piece_element     (GMarkupParseContext  *context,
+                                     const gchar          *element_name,
+                                     const gchar         **attribute_names,
+                                     const gchar         **attribute_values,
+                                     ParseInfo            *info,
+                                     GError              **error);
+
+static void parse_button_element    (GMarkupParseContext  *context,
+                                     const gchar          *element_name,
+                                     const gchar         **attribute_names,
+                                     const gchar         **attribute_values,
+                                     ParseInfo            *info,
+                                     GError              **error);
+
+static void parse_menu_icon_element (GMarkupParseContext  *context,
+                                     const gchar          *element_name,
+                                     const gchar         **attribute_names,
+                                     const gchar         **attribute_values,
+                                     ParseInfo            *info,
+                                     GError              **error);
+
+static void start_element_handler (GMarkupParseContext  *context,
+                                   const gchar          *element_name,
+                                   const gchar         **attribute_names,
+                                   const gchar         **attribute_values,
+                                   gpointer              user_data,
+                                   GError              **error);
+static void end_element_handler   (GMarkupParseContext  *context,
+                                   const gchar          *element_name,
+                                   gpointer              user_data,
+                                   GError              **error);
+static void text_handler          (GMarkupParseContext  *context,
+                                   const gchar          *text,
+                                   gsize                 text_len,
+                                   gpointer              user_data,
+                                   GError              **error);
+
+/* Translators: This means that an attribute which should have been found
+ * on an XML element was not in fact found.
+ */
+#define ATTRIBUTE_NOT_FOUND _("No \"%s\" attribute on element <%s>")
+
+static GMarkupParser 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->theme)
+    meta_theme_free (info->theme);
+
+  if (info->layout)
+    meta_frame_layout_unref (info->layout);
+
+  if (info->op_list)
+    meta_draw_op_list_unref (info->op_list);
+
+  if (info->op)
+    meta_draw_op_free (info->op);
+  
+  if (info->style)
+    meta_frame_style_unref (info->style);
+
+  if (info->style_set)
+    meta_frame_style_set_unref (info->style_set);
+}
+
+static void
+push_state (ParseInfo  *info,
+            ParseState  state)
+{
+  info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state));
+}
+
+static void
+pop_state (ParseInfo *info)
+{
+  g_return_if_fail (info->states != NULL);
+  
+  info->states = g_slist_remove (info->states, info->states->data);
+}
+
+static ParseState
+peek_state (ParseInfo *info)
+{
+  g_return_val_if_fail (info->states != NULL, STATE_START);
+
+  return GPOINTER_TO_INT (info->states->data);
+}
+
+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)
+    {
+      g_return_val_if_fail (retloc != NULL, FALSE);
+
+      g_assert (n_attrs < MAX_ATTRS);
+      
+      attrs[n_attrs].name = name;
+      attrs[n_attrs].retloc = retloc;
+      attrs[n_attrs].required = attrs[n_attrs].name[0]=='!';
+      if (attrs[n_attrs].required)
+        attrs[n_attrs].name++; /* skip past it */
+
+      n_attrs += 1;
+      *retloc = NULL;      
+
+      name = va_arg (args, const char*);
+      retloc = va_arg (args, const char**);
+    }
+
+  va_end (args);
+
+  i = 0;
+  while (attribute_names[i])
+    {
+      int j;
+      gboolean found;
+
+      /* 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_lookup_int_constant (theme, str, &j))
+    {
+      /* Yes. */
+      l = j;
+    }
+  else
+    {
+      /* No. Let's try parsing it instead. */
+
+      l = strtol (str, &end, 10);
+
+      if (end == NULL || end == str)
+      {
+        set_error (error, context, G_MARKUP_ERROR,
+                   G_MARKUP_ERROR_PARSE,
+                   _("Could not parse \"%s\" as an integer"),
+                   str);
+        return FALSE;
+      }
+
+    if (*end != '\0')
+      {
+        set_error (error, context, G_MARKUP_ERROR,
+                   G_MARKUP_ERROR_PARSE,
+                   _("Did not understand trailing characters \"%s\" in string \"%s\""),
+                   end, str);
+        return FALSE;
+      }
+    }
+
+  if (l < 0)
+    {
+      set_error (error, context, G_MARKUP_ERROR,
+                 G_MARKUP_ERROR_PARSE,
+                 _("Integer %ld must be positive"), l);
+      return FALSE;
+    }
+
+  if (l > MAX_REASONABLE)
+    {
+      set_error (error, context, G_MARKUP_ERROR,
+                 G_MARKUP_ERROR_PARSE,
+                 _("Integer %ld is too large, current max is %d"),
+                 l, MAX_REASONABLE);
+      return FALSE;
+    }
+  
+  *val = (int) l;
+
+  return TRUE;
+}
+
+static gboolean
+parse_double (const char          *str,
+              double              *val,
+              GMarkupParseContext *context,
+              GError             **error)
+{
+  char *end;
+
+  *val = 0;
+  
+  end = NULL;
+  
+  *val = g_ascii_strtod (str, &end);
+
+  if (end == NULL || end == str)
+    {
+      set_error (error, context, G_MARKUP_ERROR,
+                 G_MARKUP_ERROR_PARSE,
+                 _("Could not parse \"%s\" as a floating point number"),
+                 str);
+      return FALSE;
+    }
+
+  if (*end != '\0')
+    {
+      set_error (error, context, G_MARKUP_ERROR,
+                 G_MARKUP_ERROR_PARSE,
+                 _("Did not understand trailing characters \"%s\" in string \"%s\""),
+                 end, str);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+parse_boolean (const char          *str,
+               gboolean            *val,
+               GMarkupParseContext *context,
+               GError             **error)
+{
+  if (strcmp ("true", str) == 0)
+    *val = TRUE;
+  else if (strcmp ("false", str) == 0)
+    *val = FALSE;
+  else
+    {
+      set_error (error, context, G_MARKUP_ERROR,
+                 G_MARKUP_ERROR_PARSE,
+                 _("Boolean values must be \"true\" or \"false\" not \"%s\""),
+                 str);
+      return FALSE;
+    }
+  
+  return TRUE;
+}
+
+static gboolean
+parse_rounding (const char          *str,
+                guint               *val,
+                GMarkupParseContext *context,
+                MetaTheme           *theme,
+                GError             **error)
+{
+  if (strcmp ("true", str) == 0)
+    *val = 5; /* historical "true" value */
+  else if (strcmp ("false", str) == 0)
+    *val = 0;
+  else
+    {
+      int tmp;
+      gboolean result;
+       if (!META_THEME_ALLOWS (theme, META_THEME_VARIED_ROUND_CORNERS))
+         {
+           /* Not known in this version, so bail. */
+           set_error (error, context, G_MARKUP_ERROR,
+                      G_MARKUP_ERROR_PARSE,
+                      _("Boolean values must be \"true\" or \"false\" not \"%s\""),
+                      str);
+           return FALSE;
+         }
+   
+      result = parse_positive_integer (str, &tmp, context, theme, error);
+
+      *val = tmp;
+
+      return result;    
+    }
+  
+  return TRUE;
+}
+
+static gboolean
+parse_angle (const char          *str,
+             double              *val,
+             GMarkupParseContext *context,
+             GError             **error)
+{
+  if (!parse_double (str, val, context, error))
+    return FALSE;
+
+  if (*val < (0.0 - 1e6) || *val > (360.0 + 1e6))
+    {
+      set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Angle must be between 0.0 and 360.0, was %g\n"),
+                 *val);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+parse_alpha (const char             *str,
+             MetaAlphaGradientSpec **spec_ret,
+             GMarkupParseContext    *context,
+             GError                **error)
+{
+  char **split;
+  int i;
+  int n_alphas;
+  MetaAlphaGradientSpec *spec;
+
+  *spec_ret = NULL;
+  
+  split = g_strsplit (str, ":", -1);
+
+  i = 0;
+  while (split[i])
+    ++i;
+
+  if (i == 0)
+    {
+      set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Could not parse \"%s\" as a floating point number"),
+                 str);
+
+      g_strfreev (split);
+      
+      return FALSE;
+    }
+
+  n_alphas = i;
+
+  /* FIXME allow specifying horizontal/vertical/diagonal in theme format,
+   * once we implement vertical/diagonal in gradient.c
+   */
+  spec = meta_alpha_gradient_spec_new (META_GRADIENT_HORIZONTAL,
+                                       n_alphas);
+
+  i = 0;
+  while (i < n_alphas)
+    {
+      double v;
+      
+      if (!parse_double (split[i], &v, context, error))
+        {
+          /* clear up, but don't set error: it was set by parse_double */
+          g_strfreev (split);
+          meta_alpha_gradient_spec_free (spec);
+          
+          return FALSE;
+        }
+
+      if (v < (0.0 - 1e-6) || v > (1.0 + 1e-6))
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("Alpha must be between 0.0 (invisible) and 1.0 (fully opaque), was %g\n"),
+                     v);
+
+          g_strfreev (split);
+          meta_alpha_gradient_spec_free (spec);          
+          
+          return FALSE;
+        }
+
+      spec->alphas[i] = (unsigned char) (v * 255);
+      
+      ++i;
+    }  
+
+  g_strfreev (split);
+  
+  *spec_ret = spec;
+  
+  return TRUE;
+}
+
+static MetaColorSpec*
+parse_color (MetaTheme *theme,
+             const char        *str,
+             GError           **err)
+{
+  char* referent;
+
+  if (META_THEME_ALLOWS (theme, META_THEME_COLOR_CONSTANTS) &&
+      meta_theme_lookup_color_constant (theme, str, &referent))
+    {
+      if (referent)
+        return meta_color_spec_new_from_string (referent, err);
+      
+      /* no need to free referent: it's a pointer into the actual hash table */
+    }
+  
+  return meta_color_spec_new_from_string (str, err);
+}
+
+static gboolean
+parse_title_scale (const char          *str,
+                   double              *val,
+                   GMarkupParseContext *context,
+                   GError             **error)
+{
+  double factor;
+  
+  if (strcmp (str, "xx-small") == 0)
+    factor = PANGO_SCALE_XX_SMALL;
+  else if (strcmp (str, "x-small") == 0)
+    factor = PANGO_SCALE_X_SMALL;
+  else if (strcmp (str, "small") == 0)
+    factor = PANGO_SCALE_SMALL;
+  else if (strcmp (str, "medium") == 0)
+    factor = PANGO_SCALE_MEDIUM;
+  else if (strcmp (str, "large") == 0)
+    factor = PANGO_SCALE_LARGE;
+  else if (strcmp (str, "x-large") == 0)
+    factor = PANGO_SCALE_X_LARGE;
+  else if (strcmp (str, "xx-large") == 0)
+    factor = PANGO_SCALE_XX_LARGE;
+  else
+    {
+      set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Invalid title scale \"%s\" (must be one of xx-small,x-small,small,medium,large,x-large,xx-large)\n"),
+                 str);
+      return FALSE;
+    }
+
+  *val = factor;
+  
+  return TRUE;
+}
+
+static void
+parse_toplevel_element (GMarkupParseContext  *context,
+                        const gchar          *element_name,
+                        const gchar         **attribute_names,
+                        const gchar         **attribute_values,
+                        ParseInfo            *info,
+                        GError              **error)
+{
+  g_return_if_fail (peek_state (info) == STATE_THEME);
+
+  if (ELEMENT_IS ("info"))
+    {
+      if (!check_no_attributes (context, element_name,
+                                attribute_names, attribute_values,
+                                error))
+        return;
+
+      push_state (info, STATE_INFO);
+    }
+  else if (ELEMENT_IS ("constant"))
+    {
+      const char *name;
+      const char *value;
+      int ival = 0;
+      double dval = 0.0;
+      
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!name", &name, "!value", &value,
+                              NULL))
+        return;
+
+      if (strchr (value, '.') && parse_double (value, &dval, context, error))
+        {
+          g_clear_error (error);
+
+          if (!meta_theme_define_float_constant (info->theme,
+                                                 name,
+                                                 dval,
+                                                 error))
+            {
+              add_context_to_error (error, context);
+              return;
+            }
+        }
+      else if (parse_positive_integer (value, &ival, context, info->theme, error))
+        {
+          g_clear_error (error);
+
+          if (!meta_theme_define_int_constant (info->theme,
+                                               name,
+                                               ival,
+                                               error))
+            {
+              add_context_to_error (error, context);
+              return;
+            }
+        }
+      else
+        {
+          g_clear_error (error);
+
+          if (!meta_theme_define_color_constant (info->theme,
+                                                 name,
+                                                 value,
+                                                 error))
+            {
+              add_context_to_error (error, context);
+              return;
+            }
+        }
+
+      push_state (info, STATE_CONSTANT);
+    }
+  else if (ELEMENT_IS ("frame_geometry"))
+    {
+      const char *name = NULL;
+      const char *parent = NULL;
+      const char *has_title = NULL;
+      const char *title_scale = NULL;
+      const char *rounded_top_left = NULL;
+      const char *rounded_top_right = NULL;
+      const char *rounded_bottom_left = NULL;
+      const char *rounded_bottom_right = NULL;
+      const char *hide_buttons = NULL;
+      gboolean has_title_val;
+      guint rounded_top_left_val;
+      guint rounded_top_right_val;
+      guint rounded_bottom_left_val;
+      guint rounded_bottom_right_val;
+      gboolean hide_buttons_val;
+      double title_scale_val;
+      MetaFrameLayout *parent_layout;
+
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!name", &name, "parent", &parent,
+                              "has_title", &has_title, "title_scale", &title_scale,
+                              "rounded_top_left", &rounded_top_left,
+                              "rounded_top_right", &rounded_top_right,
+                              "rounded_bottom_left", &rounded_bottom_left,
+                              "rounded_bottom_right", &rounded_bottom_right,
+                              "hide_buttons", &hide_buttons,
+                              NULL))
+        return;
+
+      has_title_val = TRUE;
+      if (has_title && !parse_boolean (has_title, &has_title_val, context, error))
+        return;
+
+      hide_buttons_val = FALSE;
+      if (hide_buttons && !parse_boolean (hide_buttons, &hide_buttons_val, context, error))
+        return;
+
+      rounded_top_left_val = 0;
+      rounded_top_right_val = 0;
+      rounded_bottom_left_val = 0;
+      rounded_bottom_right_val = 0;
+
+      if (rounded_top_left && !parse_rounding (rounded_top_left, &rounded_top_left_val, context, info->theme, error))
+        return;
+      if (rounded_top_right && !parse_rounding (rounded_top_right, &rounded_top_right_val, context, info->theme, error))
+        return;
+      if (rounded_bottom_left && !parse_rounding (rounded_bottom_left, &rounded_bottom_left_val, context, info->theme, error))
+        return;      
+      if (rounded_bottom_right && !parse_rounding (rounded_bottom_right, &rounded_bottom_right_val, context, info->theme, error))
+        return;
+      
+      title_scale_val = 1.0;
+      if (title_scale && !parse_title_scale (title_scale, &title_scale_val, context, error))
+        return;
+      
+      if (meta_theme_lookup_layout (info->theme, name))
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("<%s> name \"%s\" used a second time"),
+                     element_name, name);
+          return;
+        }
+
+      parent_layout = NULL;
+      if (parent)
+        {
+          parent_layout = meta_theme_lookup_layout (info->theme, parent);
+          if (parent_layout == NULL)
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         _("<%s> parent \"%s\" has not been defined"),
+                         element_name, parent);
+              return;
+            }
+        }
+
+      g_assert (info->layout == NULL);
+
+      if (parent_layout)
+        info->layout = meta_frame_layout_copy (parent_layout);
+      else
+        info->layout = meta_frame_layout_new ();
+
+      if (has_title) /* only if explicit, otherwise inherit */
+        info->layout->has_title = has_title_val;
+
+      if (META_THEME_ALLOWS (info->theme, META_THEME_HIDDEN_BUTTONS) && hide_buttons_val)
+          info->layout->hide_buttons = hide_buttons_val;
+
+      if (title_scale)
+	info->layout->title_scale = title_scale_val;
+
+      if (rounded_top_left)
+        info->layout->top_left_corner_rounded_radius = rounded_top_left_val;
+
+      if (rounded_top_right)
+        info->layout->top_right_corner_rounded_radius = rounded_top_right_val;
+
+      if (rounded_bottom_left)
+        info->layout->bottom_left_corner_rounded_radius = rounded_bottom_left_val;
+
+      if (rounded_bottom_right)
+        info->layout->bottom_right_corner_rounded_radius = rounded_bottom_right_val;
+      
+      meta_theme_insert_layout (info->theme, name, info->layout);
+
+      push_state (info, STATE_FRAME_GEOMETRY);
+    }
+  else if (ELEMENT_IS ("draw_ops"))
+    {
+      const char *name = NULL;
+
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!name", &name,
+                              NULL))
+        return;
+
+      if (meta_theme_lookup_draw_op_list (info->theme, name))
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("<%s> name \"%s\" used a second time"),
+                     element_name, name);
+          return;
+        }
+
+      g_assert (info->op_list == NULL);
+      info->op_list = meta_draw_op_list_new (2);
+
+      meta_theme_insert_draw_op_list (info->theme, name, info->op_list);
+
+      push_state (info, STATE_DRAW_OPS);
+    }
+  else if (ELEMENT_IS ("frame_style"))
+    {
+      const char *name = NULL;
+      const char *parent = NULL;
+      const char *geometry = NULL;
+      const char *background = NULL;
+      const char *alpha = NULL;
+      MetaFrameStyle *parent_style;
+      MetaFrameLayout *layout;
+
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!name", &name, "parent", &parent,
+                              "geometry", &geometry,
+                              "background", &background,
+                              "alpha", &alpha,
+                              NULL))
+        return;
+
+      if (meta_theme_lookup_style (info->theme, name))
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("<%s> name \"%s\" used a second time"),
+                     element_name, name);
+          return;
+        }
+
+      parent_style = NULL;
+      if (parent)
+        {
+          parent_style = meta_theme_lookup_style (info->theme, parent);
+          if (parent_style == NULL)
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         _("<%s> parent \"%s\" has not been defined"),
+                         element_name, parent);
+              return;
+            }
+        }
+
+      layout = NULL;
+      if (geometry)
+        {
+          layout = meta_theme_lookup_layout (info->theme, geometry);
+          if (layout == NULL)
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         _("<%s> geometry \"%s\" has not been defined"),
+                         element_name, geometry);
+              return;
+            }
+        }
+      else if (parent_style)
+        {
+          layout = parent_style->layout;
+        }
+
+      if (layout == NULL)
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("<%s> must specify either a geometry or a parent that has a geometry"),
+                     element_name);
+          return;
+        }
+
+      g_assert (info->style == NULL);
+
+      info->style = meta_frame_style_new (parent_style);
+      g_assert (info->style->layout == NULL);
+      meta_frame_layout_ref (layout);
+      info->style->layout = layout;
+
+      if (background != NULL && META_THEME_ALLOWS (info->theme, META_THEME_FRAME_BACKGROUNDS))
+        {
+          info->style->window_background_color = meta_color_spec_new_from_string (background, error);
+          if (!info->style->window_background_color)
+            return;
+
+          if (alpha != NULL)
+            {
+            
+               gboolean success;
+               MetaAlphaGradientSpec *alpha_vector;
+               
+               g_clear_error (error);
+               /* fortunately, we already have a routine to parse alpha values,
+                * though it produces a vector of them, which is a superset of
+                * what we want.
+                */
+               success = parse_alpha (alpha, &alpha_vector, context, error); 
+               if (!success)
+                 return;
+
+               /* alpha_vector->alphas must contain at least one element */
+               info->style->window_background_alpha = alpha_vector->alphas[0];
+
+               meta_alpha_gradient_spec_free (alpha_vector);
+            }
+        }
+      else if (alpha != NULL)
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("You must specify a background for an alpha value to be meaningful"));
+          return;
+        }
+
+      meta_theme_insert_style (info->theme, name, info->style);
+
+      push_state (info, STATE_FRAME_STYLE);
+    }
+  else if (ELEMENT_IS ("frame_style_set"))
+    {
+      const char *name = NULL;
+      const char *parent = NULL;
+      MetaFrameStyleSet *parent_set;
+
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!name", &name, "parent", &parent,
+                              NULL))
+        return;
+
+      if (meta_theme_lookup_style_set (info->theme, name))
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("<%s> name \"%s\" used a second time"),
+                     element_name, name);
+          return;
+        }
+
+      parent_set = NULL;
+      if (parent)
+        {
+          parent_set = meta_theme_lookup_style_set (info->theme, parent);
+          if (parent_set == NULL)
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         _("<%s> parent \"%s\" has not been defined"),
+                         element_name, parent);
+              return;
+            }
+        }
+
+      g_assert (info->style_set == NULL);
+
+      info->style_set = meta_frame_style_set_new (parent_set);
+
+      meta_theme_insert_style_set (info->theme, name, info->style_set);
+
+      push_state (info, STATE_FRAME_STYLE_SET);
+    }
+  else if (ELEMENT_IS ("window"))
+    {
+      const char *type_name = NULL;
+      const char *style_set_name = NULL;
+      MetaFrameStyleSet *style_set;
+      MetaFrameType type;
+
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!type", &type_name, "!style_set", &style_set_name,
+                              NULL))
+        return;
+
+      type = meta_frame_type_from_string (type_name);
+
+      if (type == META_FRAME_TYPE_LAST)
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("Unknown type \"%s\" on <%s> element"),
+                     type_name, element_name);
+          return;
+        }
+
+      style_set = meta_theme_lookup_style_set (info->theme,
+                                               style_set_name);
+
+      if (style_set == NULL)
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("Unknown style_set \"%s\" on <%s> element"),
+                     style_set_name, element_name);
+          return;
+        }
+
+      if (info->theme->style_sets_by_type[type] != NULL)
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("Window type \"%s\" has already been assigned a style set"),
+                     type_name);
+          return;
+        }
+
+      meta_frame_style_set_ref (style_set);
+      info->theme->style_sets_by_type[type] = style_set;
+
+      push_state (info, STATE_WINDOW);
+    }
+  else if (ELEMENT_IS ("menu_icon"))
+    {
+      /* Not supported any more, but we have to parse it if they include it,
+       * for backwards compatibility.
+       */
+      g_assert (info->op_list == NULL);
+      
+      push_state (info, STATE_MENU_ICON);
+    }
+  else if (ELEMENT_IS ("fallback"))
+    {
+      /* Not supported any more, but we have to parse it if they include it,
+       * for backwards compatibility.
+       */
+      push_state (info, STATE_FALLBACK);
+    }
+   else
+    {
+      set_error (error, context,
+                 G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed below <%s>"),
+                 element_name, "metacity_theme");
+    }
+}
+
+static void
+parse_info_element (GMarkupParseContext  *context,
+                    const gchar          *element_name,
+                    const gchar         **attribute_names,
+                    const gchar         **attribute_values,
+                    ParseInfo            *info,
+                    GError              **error)
+{
+  g_return_if_fail (peek_state (info) == STATE_INFO);
+
+  if (ELEMENT_IS ("name"))
+    {
+      if (!check_no_attributes (context, element_name,
+                                attribute_names, attribute_values,
+                                error))
+        return;
+
+      push_state (info, STATE_NAME);
+    }
+  else if (ELEMENT_IS ("author"))
+    {
+      if (!check_no_attributes (context, element_name,
+                                attribute_names, attribute_values,
+                                error))
+        return;
+
+      push_state (info, STATE_AUTHOR);
+    }
+  else if (ELEMENT_IS ("copyright"))
+    {
+      if (!check_no_attributes (context, element_name,
+                                attribute_names, attribute_values,
+                                error))
+        return;
+
+      push_state (info, STATE_COPYRIGHT);
+    }
+  else if (ELEMENT_IS ("description"))
+    {
+      if (!check_no_attributes (context, element_name,
+                                attribute_names, attribute_values,
+                                error))
+        return;
+
+      push_state (info, STATE_DESCRIPTION);
+    }
+  else if (ELEMENT_IS ("date"))
+    {
+      if (!check_no_attributes (context, element_name,
+                                attribute_names, attribute_values,
+                                error))
+        return;
+
+      push_state (info, STATE_DATE);
+    }
+  else
+    {
+      set_error (error, context,
+                 G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed below <%s>"),
+                 element_name, "info");
+    }
+}
+
+static void
+parse_distance (GMarkupParseContext  *context,
+                const gchar          *element_name,
+                const gchar         **attribute_names,
+                const gchar         **attribute_values,
+                ParseInfo            *info,
+                GError              **error)
+{
+  const char *name;
+  const char *value;
+  int val;
+  
+  if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                          error,
+                          "!name", &name, "!value", &value,
+                          NULL))
+    return;
+
+  val = 0;
+  if (!parse_positive_integer (value, &val, context, info->theme, error))
+    return;
+
+  g_assert (val >= 0); /* yeah, "non-negative" not "positive" get over it */
+  g_assert (info->layout);
+
+  if (strcmp (name, "left_width") == 0)
+    info->layout->left_width = val;
+  else if (strcmp (name, "right_width") == 0)
+    info->layout->right_width = val;
+  else if (strcmp (name, "bottom_height") == 0)
+    info->layout->bottom_height = val;
+  else if (strcmp (name, "title_vertical_pad") == 0)
+    info->layout->title_vertical_pad = val;
+  else if (strcmp (name, "right_titlebar_edge") == 0)
+    info->layout->right_titlebar_edge = val;
+  else if (strcmp (name, "left_titlebar_edge") == 0)
+    info->layout->left_titlebar_edge = val;
+  else if (strcmp (name, "button_width") == 0)
+    {
+      info->layout->button_width = val;
+            
+      if (!(info->layout->button_sizing == META_BUTTON_SIZING_LAST ||
+            info->layout->button_sizing == META_BUTTON_SIZING_FIXED))
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("Cannot specify both \"button_width\"/\"button_height\" and \"aspect_ratio\" for buttons"));
+          return;      
+        }
+
+      info->layout->button_sizing = META_BUTTON_SIZING_FIXED;
+    }
+  else if (strcmp (name, "button_height") == 0)
+    {
+      info->layout->button_height = val;
+      
+      if (!(info->layout->button_sizing == META_BUTTON_SIZING_LAST ||
+            info->layout->button_sizing == META_BUTTON_SIZING_FIXED))
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("Cannot specify both \"button_width\"/\"button_height\" and \"aspect_ratio\" for buttons"));
+          return;      
+        }
+
+      info->layout->button_sizing = META_BUTTON_SIZING_FIXED;
+    }
+  else
+    {
+      set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Distance \"%s\" is unknown"), name);
+      return;
+    }
+}
+
+static void
+parse_aspect_ratio (GMarkupParseContext  *context,
+                    const gchar          *element_name,
+                    const gchar         **attribute_names,
+                    const gchar         **attribute_values,
+                    ParseInfo            *info,
+                    GError              **error)
+{
+  const char *name;
+  const char *value;
+  double val;
+  
+  if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                          error,
+                          "!name", &name, "!value", &value,
+                          NULL))
+    return;
+
+  val = 0;
+  if (!parse_double (value, &val, context, error))
+    return;
+
+  g_assert (info->layout);
+  
+  if (strcmp (name, "button") == 0)
+    {
+      info->layout->button_aspect = val;
+
+      if (info->layout->button_sizing != META_BUTTON_SIZING_LAST)
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("Cannot specify both \"button_width\"/\"button_height\" and \"aspect_ratio\" for buttons"));
+          return;
+        }
+      
+      info->layout->button_sizing = META_BUTTON_SIZING_ASPECT;
+    }
+  else
+    {
+      set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Aspect ratio \"%s\" is unknown"), name);
+      return;
+    }
+}
+
+static void
+parse_border (GMarkupParseContext  *context,
+              const gchar          *element_name,
+              const gchar         **attribute_names,
+              const gchar         **attribute_values,
+              ParseInfo            *info,
+              GError              **error)
+{
+  const char *name;
+  const char *top;
+  const char *bottom;
+  const char *left;
+  const char *right;
+  int top_val;
+  int bottom_val;
+  int left_val;
+  int right_val;
+  GtkBorder *border;
+  
+  if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                          error,
+                          "!name", &name,
+                          "!top", &top,
+                          "!bottom", &bottom,
+                          "!left", &left,
+                          "!right", &right,
+                          NULL))
+    return;
+  
+  top_val = 0;
+  if (!parse_positive_integer (top, &top_val, context, info->theme, error))
+    return;
+
+  bottom_val = 0;
+  if (!parse_positive_integer (bottom, &bottom_val, context, info->theme, error))
+    return;
+
+  left_val = 0;
+  if (!parse_positive_integer (left, &left_val, context, info->theme, error))
+    return;
+
+  right_val = 0;
+  if (!parse_positive_integer (right, &right_val, context, info->theme, error))
+    return;
+  
+  g_assert (info->layout);
+
+  border = NULL;
+  
+  if (strcmp (name, "title_border") == 0)
+    border = &info->layout->title_border;
+  else if (strcmp (name, "button_border") == 0)
+    border = &info->layout->button_border;
+
+  if (border == NULL)
+    {
+      set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Border \"%s\" is unknown"), name);
+      return;
+    }
+
+  border->top = top_val;
+  border->bottom = bottom_val;
+  border->left = left_val;
+  border->right = right_val;
+}
+
+static void
+parse_geometry_element (GMarkupParseContext  *context,
+                        const gchar          *element_name,
+                        const gchar         **attribute_names,
+                        const gchar         **attribute_values,
+                        ParseInfo            *info,
+                        GError              **error)
+{
+  g_return_if_fail (peek_state (info) == STATE_FRAME_GEOMETRY);
+
+  if (ELEMENT_IS ("distance"))
+    {
+      parse_distance (context, element_name,
+                      attribute_names, attribute_values,
+                      info, error);
+      push_state (info, STATE_DISTANCE);
+    }
+  else if (ELEMENT_IS ("border"))
+    {
+      parse_border (context, element_name,
+                    attribute_names, attribute_values,
+                    info, error);
+      push_state (info, STATE_BORDER);
+    }
+  else if (ELEMENT_IS ("aspect_ratio"))
+    {
+      parse_aspect_ratio (context, element_name,
+                          attribute_names, attribute_values,
+                          info, error);
+
+      push_state (info, STATE_ASPECT_RATIO);
+    }
+  else
+    {
+      set_error (error, context,
+                 G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed below <%s>"),
+                 element_name, "frame_geometry");
+    }
+}
+
+#if 0
+static gboolean
+check_expression (PosToken            *tokens,
+                  int                  n_tokens,
+                  gboolean             has_object,
+                  MetaTheme           *theme,
+                  GMarkupParseContext *context,
+                  GError             **error)
+{
+  MetaPositionExprEnv env;
+  int x, y;
+
+  /* We set it all to 0 to try and catch divide-by-zero screwups.
+   * it's possible we should instead guarantee that widths and heights
+   * are at least 1.
+   */
+  
+  env.rect = meta_rect (0, 0, 0, 0);
+  if (has_object)
+    {
+      env.object_width = 0;
+      env.object_height = 0;
+    }
+  else
+    {
+      env.object_width = -1;
+      env.object_height = -1;
+    }
+
+  env.left_width = 0;
+  env.right_width = 0;
+  env.top_height = 0;
+  env.bottom_height = 0;
+  env.title_width = 0;
+  env.title_height = 0;
+  
+  env.icon_width = 0;
+  env.icon_height = 0;
+  env.mini_icon_width = 0;
+  env.mini_icon_height = 0;
+  env.theme = theme;
+  
+  if (!meta_parse_position_expression (tokens, n_tokens,
+                                       &env,
+                                       &x, &y,
+                                       error))
+    {
+      add_context_to_error (error, context);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+#endif
+
+static void
+parse_draw_op_element (GMarkupParseContext  *context,
+                       const gchar          *element_name,
+                       const gchar         **attribute_names,
+                       const gchar         **attribute_values,
+                       ParseInfo            *info,
+                       GError              **error)
+{  
+  g_return_if_fail (peek_state (info) == STATE_DRAW_OPS);
+
+  if (ELEMENT_IS ("line"))
+    {
+      MetaDrawOp *op;
+      const char *color;
+      const char *x1;
+      const char *y1;
+      const char *x2;
+      const char *y2;
+      const char *dash_on_length;
+      const char *dash_off_length;
+      const char *width;
+      MetaColorSpec *color_spec;
+      int dash_on_val;
+      int dash_off_val;
+      int width_val;
+      
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!color", &color,
+                              "!x1", &x1, "!y1", &y1,
+                              "!x2", &x2, "!y2", &y2,
+                              "dash_on_length", &dash_on_length,
+                              "dash_off_length", &dash_off_length,
+                              "width", &width,
+                              NULL))
+        return;
+
+#if 0
+      if (!check_expression (x1, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (y1, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (x2, FALSE, info->theme, context, error))
+        return;
+      
+      if (!check_expression (y2, FALSE, info->theme, context, error))
+        return;
+#endif
+ 
+      dash_on_val = 0;
+      if (dash_on_length &&
+          !parse_positive_integer (dash_on_length, &dash_on_val, context, info->theme, error))
+        return;
+
+      dash_off_val = 0;
+      if (dash_off_length &&
+          !parse_positive_integer (dash_off_length, &dash_off_val, context, info->theme, error))
+        return;
+
+      width_val = 0;
+      if (width &&
+          !parse_positive_integer (width, &width_val, context, info->theme, error))
+        return;
+
+      /* Check last so we don't have to free it when other
+       * stuff fails
+       */
+      color_spec = parse_color (info->theme, color, error);
+      if (color_spec == NULL)
+        {
+          add_context_to_error (error, context);
+          return;
+        }
+      
+      op = meta_draw_op_new (META_DRAW_LINE);
+
+      op->data.line.color_spec = color_spec;
+
+      op->data.line.x1 = meta_draw_spec_new (info->theme, x1, NULL);
+      op->data.line.y1 = meta_draw_spec_new (info->theme, y1, NULL);
+
+      if (strcmp(x1, x2)==0)
+        op->data.line.x2 = NULL;
+      else
+        op->data.line.x2 = meta_draw_spec_new (info->theme, x2, NULL);
+
+      if (strcmp(y1, y2)==0)
+        op->data.line.y2 = NULL;
+      else
+        op->data.line.y2 = meta_draw_spec_new (info->theme, y2, NULL);
+
+      op->data.line.width = width_val;
+      op->data.line.dash_on_length = dash_on_val;
+      op->data.line.dash_off_length = dash_off_val;
+
+      g_assert (info->op_list);
+      
+      meta_draw_op_list_append (info->op_list, op);
+
+      push_state (info, STATE_LINE);
+    }
+  else if (ELEMENT_IS ("rectangle"))
+    {
+      MetaDrawOp *op;
+      const char *color;
+      const char *x;
+      const char *y;
+      const char *width;
+      const char *height;
+      const char *filled;
+      gboolean filled_val;
+      MetaColorSpec *color_spec;
+      
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!color", &color,
+                              "!x", &x, "!y", &y,
+                              "!width", &width, "!height", &height,
+                              "filled", &filled,
+                              NULL))
+        return;
+
+#if 0
+      if (!check_expression (x, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (y, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (width, FALSE, info->theme, context, error))
+        return;
+      
+      if (!check_expression (height, FALSE, info->theme, context, error))
+        return;
+#endif
+
+      filled_val = FALSE;
+      if (filled && !parse_boolean (filled, &filled_val, context, error))
+        return;
+      
+      /* Check last so we don't have to free it when other
+       * stuff fails
+       */
+      color_spec = parse_color (info->theme, color, error);
+      if (color_spec == NULL)
+        {
+          add_context_to_error (error, context);
+          return;
+        }
+      
+      op = meta_draw_op_new (META_DRAW_RECTANGLE);
+
+      op->data.rectangle.color_spec = color_spec;
+      op->data.rectangle.x = meta_draw_spec_new (info->theme, x, NULL);
+      op->data.rectangle.y = meta_draw_spec_new (info->theme, y, NULL);
+      op->data.rectangle.width = meta_draw_spec_new (info->theme, width, NULL);
+      op->data.rectangle.height = meta_draw_spec_new (info->theme, 
+                                                      height, NULL);
+
+      op->data.rectangle.filled = filled_val;
+
+      g_assert (info->op_list);
+      
+      meta_draw_op_list_append (info->op_list, op);
+
+      push_state (info, STATE_RECTANGLE);
+    }
+  else if (ELEMENT_IS ("arc"))
+    {
+      MetaDrawOp *op;
+      const char *color;
+      const char *x;
+      const char *y;
+      const char *width;
+      const char *height;
+      const char *filled;
+      const char *start_angle;
+      const char *extent_angle;
+      const char *from;
+      const char *to;
+      gboolean filled_val;
+      double start_angle_val;
+      double extent_angle_val;
+      MetaColorSpec *color_spec;
+      
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!color", &color,
+                              "!x", &x, "!y", &y,
+                              "!width", &width, "!height", &height,
+                              "filled", &filled,
+                              "start_angle", &start_angle,
+                              "extent_angle", &extent_angle,
+                              "from", &from,
+                              "to", &to,
+                              NULL))
+        return;
+
+      if (META_THEME_ALLOWS (info->theme, META_THEME_DEGREES_IN_ARCS) )
+        {
+          if (start_angle == NULL && from == NULL)
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         _("No \"start_angle\" or \"from\" attribute on element <%s>"), element_name);
+              return;
+            }
+
+          if (extent_angle == NULL && to == NULL)
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         _("No \"extent_angle\" or \"to\" attribute on element <%s>"), element_name);
+              return;
+            }
+        }
+      else
+        {
+          if (start_angle == NULL)
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         ATTRIBUTE_NOT_FOUND, "start_angle", element_name);
+              return;
+            }
+
+          if (extent_angle == NULL)
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         ATTRIBUTE_NOT_FOUND, "extent_angle", element_name);
+              return;
+            }
+        }
+
+#if 0     
+      if (!check_expression (x, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (y, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (width, FALSE, info->theme, context, error))
+        return;
+      
+      if (!check_expression (height, FALSE, info->theme, context, error))
+        return;
+#endif
+
+      if (start_angle == NULL)
+        {
+          if (!parse_angle (from, &start_angle_val, context, error))
+            return;
+          
+          start_angle_val = (180-start_angle_val)/360.0;
+        }
+      else
+        {
+          if (!parse_angle (start_angle, &start_angle_val, context, error))
+            return;
+        }
+      
+      if (extent_angle == NULL)
+        {
+          if (!parse_angle (to, &extent_angle_val, context, error))
+            return;
+          
+          extent_angle_val = ((180-extent_angle_val)/360.0) - start_angle_val;
+        }
+      else
+        {
+           if (!parse_angle (extent_angle, &extent_angle_val, context, error))
+             return;
+        }
+     
+      filled_val = FALSE;
+      if (filled && !parse_boolean (filled, &filled_val, context, error))
+        return;
+      
+      /* Check last so we don't have to free it when other
+       * stuff fails
+       */
+      color_spec = parse_color (info->theme, color, error);
+      if (color_spec == NULL)
+        {
+          add_context_to_error (error, context);
+          return;
+        }
+      
+      op = meta_draw_op_new (META_DRAW_ARC);
+
+      op->data.arc.color_spec = color_spec;
+
+      op->data.arc.x = meta_draw_spec_new (info->theme, x, NULL);
+      op->data.arc.y = meta_draw_spec_new (info->theme, y, NULL);
+      op->data.arc.width = meta_draw_spec_new (info->theme, width, NULL);
+      op->data.arc.height = meta_draw_spec_new (info->theme, height, NULL);
+
+      op->data.arc.filled = filled_val;
+      op->data.arc.start_angle = start_angle_val;
+      op->data.arc.extent_angle = extent_angle_val;
+      
+      g_assert (info->op_list);
+      
+      meta_draw_op_list_append (info->op_list, op);
+
+      push_state (info, STATE_ARC);
+    }
+  else if (ELEMENT_IS ("clip"))
+    {
+      MetaDrawOp *op;
+      const char *x;
+      const char *y;
+      const char *width;
+      const char *height;
+      
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!x", &x, "!y", &y,
+                              "!width", &width, "!height", &height,
+                              NULL))
+        return;
+      
+#if 0
+      if (!check_expression (x, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (y, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (width, FALSE, info->theme, context, error))
+        return;
+      
+      if (!check_expression (height, FALSE, info->theme, context, error))
+        return;
+#endif 
+      op = meta_draw_op_new (META_DRAW_CLIP);
+
+      op->data.clip.x = meta_draw_spec_new (info->theme, x, NULL);
+      op->data.clip.y = meta_draw_spec_new (info->theme, y, NULL);
+      op->data.clip.width = meta_draw_spec_new (info->theme, width, NULL);
+      op->data.clip.height = meta_draw_spec_new (info->theme, height, NULL);
+
+      g_assert (info->op_list);
+      
+      meta_draw_op_list_append (info->op_list, op);
+
+      push_state (info, STATE_CLIP);
+    }
+  else if (ELEMENT_IS ("tint"))
+    {
+      MetaDrawOp *op;
+      const char *color;
+      const char *x;
+      const char *y;
+      const char *width;
+      const char *height;
+      const char *alpha;
+      MetaAlphaGradientSpec *alpha_spec;
+      MetaColorSpec *color_spec;
+      
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!color", &color,
+                              "!x", &x, "!y", &y,
+                              "!width", &width, "!height", &height,
+                              "!alpha", &alpha,
+                              NULL))
+        return;
+
+#if 0
+      if (!check_expression (x, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (y, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (width, FALSE, info->theme, context, error))
+        return;
+      
+      if (!check_expression (height, FALSE, info->theme, context, error))
+        return;
+#endif
+      alpha_spec = NULL;
+      if (!parse_alpha (alpha, &alpha_spec, context, error))
+        return;
+      
+      /* Check last so we don't have to free it when other
+       * stuff fails
+       */
+      color_spec = parse_color (info->theme, color, error);
+      if (color_spec == NULL)
+        {
+          if (alpha_spec)
+            meta_alpha_gradient_spec_free (alpha_spec);
+          
+          add_context_to_error (error, context);
+          return;
+        }
+      
+      op = meta_draw_op_new (META_DRAW_TINT);
+
+      op->data.tint.color_spec = color_spec;
+      op->data.tint.alpha_spec = alpha_spec;
+
+      op->data.tint.x = meta_draw_spec_new (info->theme, x, NULL);
+      op->data.tint.y = meta_draw_spec_new (info->theme, y, NULL);
+      op->data.tint.width = meta_draw_spec_new (info->theme, width, NULL);
+      op->data.tint.height = meta_draw_spec_new (info->theme, height, NULL);
+
+      g_assert (info->op_list);
+      
+      meta_draw_op_list_append (info->op_list, op);
+
+      push_state (info, STATE_TINT);
+    }
+  else if (ELEMENT_IS ("gradient"))
+    {
+      const char *x;
+      const char *y;
+      const char *width;
+      const char *height;
+      const char *type;
+      const char *alpha;
+      MetaAlphaGradientSpec *alpha_spec;
+      MetaGradientType type_val;
+      
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!type", &type,
+                              "!x", &x, "!y", &y,
+                              "!width", &width, "!height", &height,
+                              "alpha", &alpha,
+                              NULL))
+        return;
+
+#if 0
+      if (!check_expression (x, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (y, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (width, FALSE, info->theme, context, error))
+        return;
+      
+      if (!check_expression (height, FALSE, info->theme, context, error))
+        return;
+#endif
+  
+      type_val = meta_gradient_type_from_string (type);
+      if (type_val == META_GRADIENT_LAST)
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("Did not understand value \"%s\" for type of gradient"),
+                     type);
+          return;
+        }
+
+      alpha_spec = NULL;
+      if (alpha && !parse_alpha (alpha, &alpha_spec, context, error))
+        return;
+      
+      g_assert (info->op == NULL);
+      info->op = meta_draw_op_new (META_DRAW_GRADIENT);
+
+      info->op->data.gradient.x = meta_draw_spec_new (info->theme, x, NULL);
+      info->op->data.gradient.y = meta_draw_spec_new (info->theme, y, NULL);
+      info->op->data.gradient.width = meta_draw_spec_new (info->theme, 
+                                                        width, NULL);
+      info->op->data.gradient.height = meta_draw_spec_new (info->theme,
+                                                         height, NULL);
+
+      info->op->data.gradient.gradient_spec = meta_gradient_spec_new (type_val);
+
+      info->op->data.gradient.alpha_spec = alpha_spec;
+      
+      push_state (info, STATE_GRADIENT);
+
+      /* op gets appended on close tag */
+    }
+  else if (ELEMENT_IS ("image"))
+    {
+      MetaDrawOp *op;
+      const char *filename;
+      const char *x;
+      const char *y;
+      const char *width;
+      const char *height;
+      const char *alpha;
+      const char *colorize;
+      const char *fill_type;
+      MetaAlphaGradientSpec *alpha_spec;
+      GdkPixbuf *pixbuf;
+      MetaColorSpec *colorize_spec = NULL;
+      MetaImageFillType fill_type_val;
+      int h, w, c;
+      int pixbuf_width, pixbuf_height, pixbuf_n_channels, pixbuf_rowstride;
+      guchar *pixbuf_pixels;
+      
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!x", &x, "!y", &y,
+                              "!width", &width, "!height", &height,
+                              "alpha", &alpha, "!filename", &filename,
+                              "colorize", &colorize,
+                              "fill_type", &fill_type,
+                              NULL))
+        return;
+      
+#if 0      
+      if (!check_expression (x, TRUE, info->theme, context, error))
+        return;
+
+      if (!check_expression (y, TRUE, info->theme, context, error))
+        return;
+
+      if (!check_expression (width, TRUE, info->theme, context, error))
+        return;
+      
+      if (!check_expression (height, TRUE, info->theme, context, error))
+        return;
+#endif
+      fill_type_val = META_IMAGE_FILL_SCALE;
+      if (fill_type)
+        {
+          fill_type_val = meta_image_fill_type_from_string (fill_type);
+          
+          if (((int) fill_type_val) == -1)
+            {
+              set_error (error, context, G_MARKUP_ERROR,
+                         G_MARKUP_ERROR_PARSE,
+                         _("Did not understand fill type \"%s\" for <%s> element"),
+                         fill_type, element_name);
+            }
+        }
+      
+      /* Check last so we don't have to free it when other
+       * stuff fails.
+       *
+       * If it's a theme image, ask for it at 64px, which is
+       * the largest possible. We scale it anyway.
+       */
+      pixbuf = meta_theme_load_image (info->theme, filename, 64, error);
+
+      if (pixbuf == NULL)
+        {
+          add_context_to_error (error, context);
+          return;
+        }
+
+      if (colorize)
+        {
+          colorize_spec = parse_color (info->theme, colorize, error);
+          
+          if (colorize_spec == NULL)
+            {
+              add_context_to_error (error, context);
+              g_object_unref (G_OBJECT (pixbuf));
+              return;
+            }
+        }
+
+      alpha_spec = NULL;
+      if (alpha && !parse_alpha (alpha, &alpha_spec, context, error))
+        {
+          g_object_unref (G_OBJECT (pixbuf));
+          return;
+        }
+      
+      op = meta_draw_op_new (META_DRAW_IMAGE);
+
+      op->data.image.pixbuf = pixbuf;
+      op->data.image.colorize_spec = colorize_spec;
+
+      op->data.image.x = meta_draw_spec_new (info->theme, x, NULL);
+      op->data.image.y = meta_draw_spec_new (info->theme, y, NULL);
+      op->data.image.width = meta_draw_spec_new (info->theme, width, NULL);
+      op->data.image.height = meta_draw_spec_new (info->theme, height, NULL);
+
+      op->data.image.alpha_spec = alpha_spec;
+      op->data.image.fill_type = fill_type_val;
+      
+      /* Check for vertical & horizontal stripes */
+      pixbuf_n_channels = gdk_pixbuf_get_n_channels(pixbuf);
+      pixbuf_width = gdk_pixbuf_get_width(pixbuf);
+      pixbuf_height = gdk_pixbuf_get_height(pixbuf);
+      pixbuf_rowstride = gdk_pixbuf_get_rowstride(pixbuf);
+      pixbuf_pixels = gdk_pixbuf_get_pixels(pixbuf);
+
+      /* Check for horizontal stripes */
+      for (h = 0; h < pixbuf_height; h++)
+        {
+          for (w = 1; w < pixbuf_width; w++)
+            {
+              for (c = 0; c < pixbuf_n_channels; c++)
+                {
+                  if (pixbuf_pixels[(h * pixbuf_rowstride) + c] !=
+                      pixbuf_pixels[(h * pixbuf_rowstride) + w + c])
+                    break;
+                }
+              if (c < pixbuf_n_channels)
+                break;
+            }
+          if (w < pixbuf_width)
+            break;
+        }
+
+      if (h >= pixbuf_height)
+        {
+          op->data.image.horizontal_stripes = TRUE; 
+        }
+      else
+        {
+          op->data.image.horizontal_stripes = FALSE; 
+        }
+
+      /* Check for vertical stripes */
+      for (w = 0; w < pixbuf_width; w++)
+        {
+          for (h = 1; h < pixbuf_height; h++)
+            {
+              for (c = 0; c < pixbuf_n_channels; c++)
+                {
+                  if (pixbuf_pixels[w + c] !=
+                      pixbuf_pixels[(h * pixbuf_rowstride) + w + c])
+                    break;
+                }
+              if (c < pixbuf_n_channels)
+                break;
+            }
+          if (h < pixbuf_height)
+            break;
+        }
+
+      if (w >= pixbuf_width)
+        {
+          op->data.image.vertical_stripes = TRUE; 
+        }
+      else
+        {
+          op->data.image.vertical_stripes = FALSE; 
+        }
+      
+      g_assert (info->op_list);
+      
+      meta_draw_op_list_append (info->op_list, op);
+
+      push_state (info, STATE_IMAGE);
+    }
+  else if (ELEMENT_IS ("gtk_arrow"))
+    {
+      MetaDrawOp *op;
+      const char *state;
+      const char *shadow;
+      const char *arrow;
+      const char *x;
+      const char *y;
+      const char *width;
+      const char *height;
+      const char *filled;
+      gboolean filled_val;
+      GtkStateType state_val;
+      GtkShadowType shadow_val;
+      GtkArrowType arrow_val;
+      
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!state", &state,
+                              "!shadow", &shadow,
+                              "!arrow", &arrow,
+                              "!x", &x, "!y", &y,
+                              "!width", &width, "!height", &height,
+                              "filled", &filled,
+                              NULL))
+        return;
+
+#if 0
+      if (!check_expression (x, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (y, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (width, FALSE, info->theme, context, error))
+        return;
+      
+      if (!check_expression (height, FALSE, info->theme, context, error))
+        return;
+#endif
+      filled_val = TRUE;
+      if (filled && !parse_boolean (filled, &filled_val, context, error))
+        return;
+
+      state_val = meta_gtk_state_from_string (state);
+      if (((int) state_val) == -1)
+        {
+          set_error (error, context, G_MARKUP_ERROR,
+                     G_MARKUP_ERROR_PARSE,
+                     _("Did not understand state \"%s\" for <%s> element"),
+                     state, element_name);
+          return;
+        }
+
+      shadow_val = meta_gtk_shadow_from_string (shadow);
+      if (((int) shadow_val) == -1)
+        {
+          set_error (error, context, G_MARKUP_ERROR,
+                     G_MARKUP_ERROR_PARSE,
+                     _("Did not understand shadow \"%s\" for <%s> element"),
+                     shadow, element_name);
+          return;
+        }
+
+      arrow_val = meta_gtk_arrow_from_string (arrow);
+      if (((int) arrow_val) == -1)
+        {
+          set_error (error, context, G_MARKUP_ERROR,
+                     G_MARKUP_ERROR_PARSE,
+                     _("Did not understand arrow \"%s\" for <%s> element"),
+                     arrow, element_name);
+          return;
+        }
+      
+      op = meta_draw_op_new (META_DRAW_GTK_ARROW);
+
+      op->data.gtk_arrow.x = meta_draw_spec_new (info->theme, x, NULL);
+      op->data.gtk_arrow.y = meta_draw_spec_new (info->theme, y, NULL);
+      op->data.gtk_arrow.width = meta_draw_spec_new (info->theme, width, NULL);
+      op->data.gtk_arrow.height = meta_draw_spec_new (info->theme, 
+                                                      height, NULL);
+
+      op->data.gtk_arrow.filled = filled_val;
+      op->data.gtk_arrow.state = state_val;
+      op->data.gtk_arrow.shadow = shadow_val;
+      op->data.gtk_arrow.arrow = arrow_val;
+      
+      g_assert (info->op_list);
+      
+      meta_draw_op_list_append (info->op_list, op);
+
+      push_state (info, STATE_GTK_ARROW);
+    }
+  else if (ELEMENT_IS ("gtk_box"))
+    {
+      MetaDrawOp *op;
+      const char *state;
+      const char *shadow;
+      const char *x;
+      const char *y;
+      const char *width;
+      const char *height;
+      GtkStateType state_val;
+      GtkShadowType shadow_val;
+      
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!state", &state,
+                              "!shadow", &shadow,
+                              "!x", &x, "!y", &y,
+                              "!width", &width, "!height", &height,
+                              NULL))
+        return;
+
+#if 0
+      if (!check_expression (x, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (y, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (width, FALSE, info->theme, context, error))
+        return;
+      
+      if (!check_expression (height, FALSE, info->theme, context, error))
+        return;
+#endif
+      state_val = meta_gtk_state_from_string (state);
+      if (((int) state_val) == -1)
+        {
+          set_error (error, context, G_MARKUP_ERROR,
+                     G_MARKUP_ERROR_PARSE,
+                     _("Did not understand state \"%s\" for <%s> element"),
+                     state, element_name);
+          return;
+        }
+
+      shadow_val = meta_gtk_shadow_from_string (shadow);
+      if (((int) shadow_val) == -1)
+        {
+          set_error (error, context, G_MARKUP_ERROR,
+                     G_MARKUP_ERROR_PARSE,
+                     _("Did not understand shadow \"%s\" for <%s> element"),
+                     shadow, element_name);
+          return;
+        }
+      
+      op = meta_draw_op_new (META_DRAW_GTK_BOX);
+
+      op->data.gtk_box.x = meta_draw_spec_new (info->theme, x, NULL);
+      op->data.gtk_box.y = meta_draw_spec_new (info->theme, y, NULL);
+      op->data.gtk_box.width = meta_draw_spec_new (info->theme, width, NULL);
+      op->data.gtk_box.height = meta_draw_spec_new (info->theme, height, NULL);
+
+      op->data.gtk_box.state = state_val;
+      op->data.gtk_box.shadow = shadow_val;
+      
+      g_assert (info->op_list);
+      
+      meta_draw_op_list_append (info->op_list, op);
+
+      push_state (info, STATE_GTK_BOX);
+    }
+  else if (ELEMENT_IS ("gtk_vline"))
+    {
+      MetaDrawOp *op;
+      const char *state;
+      const char *x;
+      const char *y1;
+      const char *y2;
+      GtkStateType state_val;
+      
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!state", &state,
+                              "!x", &x, "!y1", &y1, "!y2", &y2,
+                              NULL))
+        return;
+
+#if 0
+      if (!check_expression (x, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (y1, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (y2, FALSE, info->theme, context, error))
+        return;
+#endif
+
+      state_val = meta_gtk_state_from_string (state);
+      if (((int) state_val) == -1)
+        {
+          set_error (error, context, G_MARKUP_ERROR,
+                     G_MARKUP_ERROR_PARSE,
+                     _("Did not understand state \"%s\" for <%s> element"),
+                     state, element_name);
+          return;
+        }
+      
+      op = meta_draw_op_new (META_DRAW_GTK_VLINE);
+
+      op->data.gtk_vline.x = meta_draw_spec_new (info->theme, x, NULL);
+      op->data.gtk_vline.y1 = meta_draw_spec_new (info->theme, y1, NULL);
+      op->data.gtk_vline.y2 = meta_draw_spec_new (info->theme, y2, NULL);
+
+      op->data.gtk_vline.state = state_val;
+      
+      g_assert (info->op_list);
+      
+      meta_draw_op_list_append (info->op_list, op);
+
+      push_state (info, STATE_GTK_VLINE);
+    }
+  else if (ELEMENT_IS ("icon"))
+    {
+      MetaDrawOp *op;
+      const char *x;
+      const char *y;
+      const char *width;
+      const char *height;
+      const char *alpha;
+      const char *fill_type;
+      MetaAlphaGradientSpec *alpha_spec;
+      MetaImageFillType fill_type_val;
+      
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!x", &x, "!y", &y,
+                              "!width", &width, "!height", &height,
+                              "alpha", &alpha,
+                              "fill_type", &fill_type,
+                              NULL))
+        return;
+      
+#if 0      
+      if (!check_expression (x, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (y, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (width, FALSE, info->theme, context, error))
+        return;
+      
+      if (!check_expression (height, FALSE, info->theme, context, error))
+        return;
+#endif
+      fill_type_val = META_IMAGE_FILL_SCALE;
+      if (fill_type)
+        {
+          fill_type_val = meta_image_fill_type_from_string (fill_type);
+
+          if (((int) fill_type_val) == -1)
+            {
+              set_error (error, context, G_MARKUP_ERROR,
+                         G_MARKUP_ERROR_PARSE,
+                         _("Did not understand fill type \"%s\" for <%s> element"),
+                         fill_type, element_name);
+            }
+        }
+      
+      alpha_spec = NULL;
+      if (alpha && !parse_alpha (alpha, &alpha_spec, context, error))
+        return;
+      
+      op = meta_draw_op_new (META_DRAW_ICON);
+      
+      op->data.icon.x = meta_draw_spec_new (info->theme, x, NULL);
+      op->data.icon.y = meta_draw_spec_new (info->theme, y, NULL);
+      op->data.icon.width = meta_draw_spec_new (info->theme, width, NULL);
+      op->data.icon.height = meta_draw_spec_new (info->theme, height, NULL);
+
+      op->data.icon.alpha_spec = alpha_spec;
+      op->data.icon.fill_type = fill_type_val;
+      
+      g_assert (info->op_list);
+      
+      meta_draw_op_list_append (info->op_list, op);
+
+      push_state (info, STATE_ICON);
+    }
+  else if (ELEMENT_IS ("title"))
+    {
+      MetaDrawOp *op;
+      const char *color;
+      const char *x;
+      const char *y;
+      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 0
+      if (!check_expression (x, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (y, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (ellipsize_width, FALSE, info->theme, context, error))
+        return;
+#endif
+
+      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 (info->theme, x, NULL);
+      op->data.title.y = meta_draw_spec_new (info->theme, y, NULL);
+      if (ellipsize_width)
+        op->data.title.ellipsize_width = meta_draw_spec_new (info->theme, 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
+       */
+#if 0      
+      if (x && !check_expression (x, FALSE, info->theme, context, error))
+        return;
+
+      if (y && !check_expression (y, FALSE, info->theme, context, error))
+        return;
+
+      if (width && !check_expression (width, FALSE, info->theme, context, error))
+        return;
+      
+      if (height && !check_expression (height, FALSE, info->theme, context, error))
+        return;
+#endif
+
+      op_list = meta_theme_lookup_draw_op_list (info->theme,
+                                                name);
+      if (op_list == NULL)
+        {
+          set_error (error, context, G_MARKUP_ERROR,
+                     G_MARKUP_ERROR_PARSE,
+                     _("No <draw_ops> called \"%s\" has been defined"),
+                     name);
+          return;
+        }
+
+      g_assert (info->op_list);
+      
+      if (op_list == info->op_list ||
+          meta_draw_op_list_contains (op_list, info->op_list))
+        {
+          set_error (error, context, G_MARKUP_ERROR,
+                     G_MARKUP_ERROR_PARSE,
+                     _("Including draw_ops \"%s\" here would create a circular reference"),
+                     name);
+          return;
+        }
+      
+      op = meta_draw_op_new (META_DRAW_OP_LIST);
+
+      meta_draw_op_list_ref (op_list);
+      op->data.op_list.op_list = op_list;      
+
+      op->data.op_list.x = meta_draw_spec_new (info->theme, x ? x : "0", NULL);
+      op->data.op_list.y = meta_draw_spec_new (info->theme, y ? y : "0", NULL);
+      op->data.op_list.width = meta_draw_spec_new (info->theme, 
+                                                   width ? width : "width",
+                                                   NULL);
+      op->data.op_list.height = meta_draw_spec_new (info->theme,
+                                                    height ? height : "height",
+                                                    NULL);
+
+      meta_draw_op_list_append (info->op_list, op);
+
+      push_state (info, STATE_INCLUDE);
+    }
+  else if (ELEMENT_IS ("tile"))
+    {
+      MetaDrawOp *op;
+      const char *name;
+      const char *x;
+      const char *y;
+      const char *width;
+      const char *height;
+      const char *tile_xoffset;
+      const char *tile_yoffset;
+      const char *tile_width;
+      const char *tile_height;
+      MetaDrawOpList *op_list;
+      
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "x", &x, "y", &y,
+                              "width", &width, "height", &height,
+                              "!name", &name,
+                              "tile_xoffset", &tile_xoffset,
+                              "tile_yoffset", &tile_yoffset,
+                              "!tile_width", &tile_width,
+                              "!tile_height", &tile_height,
+                              NULL))
+        return;
+
+      /* These default to 0 */
+#if 0
+      if (tile_xoffset && !check_expression (tile_xoffset, FALSE, info->theme, context, error))
+        return;
+
+      if (tile_yoffset && !check_expression (tile_yoffset, FALSE, info->theme, context, error))
+        return;
+      
+      /* x/y/width/height default to 0,0,width,height - should
+       * probably do this for all the draw ops
+       */
+      if (x && !check_expression (x, FALSE, info->theme, context, error))
+        return;
+
+      if (y && !check_expression (y, FALSE, info->theme, context, error))
+        return;
+
+      if (width && !check_expression (width, FALSE, info->theme, context, error))
+        return;
+      
+      if (height && !check_expression (height, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (tile_width, FALSE, info->theme, context, error))
+        return;
+
+      if (!check_expression (tile_height, FALSE, info->theme, context, error))
+        return;
+#endif 
+      op_list = meta_theme_lookup_draw_op_list (info->theme,
+                                                name);
+      if (op_list == NULL)
+        {
+          set_error (error, context, G_MARKUP_ERROR,
+                     G_MARKUP_ERROR_PARSE,
+                     _("No <draw_ops> called \"%s\" has been defined"),
+                     name);
+          return;
+        }
+
+      g_assert (info->op_list);
+      
+      if (op_list == info->op_list ||
+          meta_draw_op_list_contains (op_list, info->op_list))
+        {
+          set_error (error, context, G_MARKUP_ERROR,
+                     G_MARKUP_ERROR_PARSE,
+                     _("Including draw_ops \"%s\" here would create a circular reference"),
+                     name);
+          return;
+        }
+      
+      op = meta_draw_op_new (META_DRAW_TILE);
+
+      meta_draw_op_list_ref (op_list);
+
+      op->data.tile.x = meta_draw_spec_new (info->theme, x ? x : "0", NULL);
+      op->data.tile.y = meta_draw_spec_new (info->theme, y ? y : "0", NULL);
+      op->data.tile.width = meta_draw_spec_new (info->theme,
+                                                width ? width : "width",
+                                                NULL);
+      op->data.tile.height = meta_draw_spec_new (info->theme,
+                                                 height ? height : "height",
+                                                 NULL);
+      op->data.tile.tile_xoffset = meta_draw_spec_new (info->theme,
+                                                       tile_xoffset ? tile_xoffset : "0",
+                                                       NULL);
+      op->data.tile.tile_yoffset = meta_draw_spec_new (info->theme,
+                                                       tile_yoffset ? tile_yoffset : "0",
+                                                       NULL);
+      op->data.tile.tile_width = meta_draw_spec_new (info->theme, tile_width, NULL);
+      op->data.tile.tile_height = meta_draw_spec_new (info->theme, tile_height, NULL);
+
+      op->data.tile.op_list = op_list;      
+      
+      meta_draw_op_list_append (info->op_list, op);
+
+      push_state (info, STATE_TILE);
+    }
+  else
+    {
+      set_error (error, context,
+                 G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed below <%s>"),
+                 element_name, "draw_ops");
+    }
+}
+
+static void
+parse_gradient_element (GMarkupParseContext  *context,
+                        const gchar          *element_name,
+                        const gchar         **attribute_names,
+                        const gchar         **attribute_values,
+                        ParseInfo            *info,
+                        GError              **error)
+{
+  g_return_if_fail (peek_state (info) == STATE_GRADIENT);
+
+  if (ELEMENT_IS ("color"))
+    {
+      const char *value = NULL;
+      MetaColorSpec *color_spec;
+
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!value", &value,
+                              NULL))
+        return;
+
+      color_spec = parse_color (info->theme, value, error);
+      if (color_spec == NULL)
+        {
+          add_context_to_error (error, context);
+          return;
+        }
+
+      g_assert (info->op);
+      g_assert (info->op->type == META_DRAW_GRADIENT);
+      g_assert (info->op->data.gradient.gradient_spec != NULL);
+      info->op->data.gradient.gradient_spec->color_specs =
+        g_slist_append (info->op->data.gradient.gradient_spec->color_specs,
+                        color_spec);
+      
+      push_state (info, STATE_COLOR);
+    }
+  else
+    {
+      set_error (error, context,
+                 G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed below <%s>"),
+                 element_name, "gradient");
+    }
+}
+
+static void
+parse_style_element (GMarkupParseContext  *context,
+                     const gchar          *element_name,
+                     const gchar         **attribute_names,
+                     const gchar         **attribute_values,
+                     ParseInfo            *info,
+                     GError              **error)
+{
+  g_return_if_fail (peek_state (info) == STATE_FRAME_STYLE);
+
+  g_assert (info->style);
+  
+  if (ELEMENT_IS ("piece"))
+    {
+      const char *position = NULL;
+      const char *draw_ops = NULL;
+      
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!position", &position,
+                              "draw_ops", &draw_ops,
+                              NULL))
+        return;
+
+      info->piece = meta_frame_piece_from_string (position);
+      if (info->piece == META_FRAME_PIECE_LAST)
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("Unknown position \"%s\" for frame piece"),
+                     position);
+          return;
+        }
+      
+      if (info->style->pieces[info->piece] != NULL)
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("Frame style already has a piece at position %s"),
+                     position);
+          return;
+        }
+
+      g_assert (info->op_list == NULL);
+      
+      if (draw_ops)
+        {
+          MetaDrawOpList *op_list;
+
+          op_list = meta_theme_lookup_draw_op_list (info->theme,
+                                                    draw_ops);
+
+          if (op_list == NULL)
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         _("No <draw_ops> with the name \"%s\" has been defined"),
+                         draw_ops);
+              return;
+            }
+
+          meta_draw_op_list_ref (op_list);
+          info->op_list = op_list;
+        }
+      
+      push_state (info, STATE_PIECE);
+    }
+  else if (ELEMENT_IS ("button"))
+    {
+      const char *function = NULL;
+      const char *state = NULL;
+      const char *draw_ops = NULL;
+      
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!function", &function,
+                              "!state", &state,
+                              "draw_ops", &draw_ops,
+                              NULL))
+        return;
+
+      info->button_type = meta_button_type_from_string (function, info->theme);
+      if (info->button_type == META_BUTTON_TYPE_LAST)
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("Unknown function \"%s\" for button"),
+                     function);
+          return;
+        }
+
+      if (meta_theme_earliest_version_with_button (info->button_type) >
+          info->theme->format_version)
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("Button function \"%s\" does not exist in this version (%d, need %d)"),
+                     function,
+                     info->theme->format_version,
+                     meta_theme_earliest_version_with_button (info->button_type)
+                     );
+          return;
+        }
+
+      info->button_state = meta_button_state_from_string (state);
+      if (info->button_state == META_BUTTON_STATE_LAST)
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("Unknown state \"%s\" for button"),
+                     state);
+          return;
+        }
+      
+      if (info->style->buttons[info->button_type][info->button_state] != NULL)
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("Frame style already has a button for function %s state %s"),
+                     function, state);
+          return;
+        }
+
+      g_assert (info->op_list == NULL);
+      
+      if (draw_ops)
+        {
+          MetaDrawOpList *op_list;
+
+          op_list = meta_theme_lookup_draw_op_list (info->theme,
+                                                    draw_ops);
+
+          if (op_list == NULL)
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         _("No <draw_ops> with the name \"%s\" has been defined"),
+                         draw_ops);
+              return;
+            }
+
+          meta_draw_op_list_ref (op_list);
+          info->op_list = op_list;
+        }
+      
+      push_state (info, STATE_BUTTON);
+    }
+  else
+    {
+      set_error (error, context,
+                 G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed below <%s>"),
+                 element_name, "frame_style");
+    }
+}
+
+static void
+parse_style_set_element (GMarkupParseContext  *context,
+                         const gchar          *element_name,
+                         const gchar         **attribute_names,
+                         const gchar         **attribute_values,
+                         ParseInfo            *info,
+                         GError              **error)
+{
+  g_return_if_fail (peek_state (info) == STATE_FRAME_STYLE_SET);
+
+  if (ELEMENT_IS ("frame"))
+    {
+      const char *focus = NULL;
+      const char *state = NULL;
+      const char *resize = NULL;
+      const char *style = NULL;
+      MetaFrameFocus frame_focus;
+      MetaFrameState frame_state;
+      MetaFrameResize frame_resize;
+      MetaFrameStyle *frame_style;
+      
+      if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                              error,
+                              "!focus", &focus,
+                              "!state", &state,
+                              "resize", &resize,
+                              "!style", &style,
+                              NULL))
+        return;
+
+      frame_focus = meta_frame_focus_from_string (focus);
+      if (frame_focus == META_FRAME_FOCUS_LAST)
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("\"%s\" is not a valid value for focus attribute"),
+                     focus);
+          return;
+        }
+      
+      frame_state = meta_frame_state_from_string (state);
+      if (frame_state == META_FRAME_STATE_LAST)
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("\"%s\" is not a valid value for state attribute"),
+                     focus);
+          return;
+        }
+
+      frame_style = meta_theme_lookup_style (info->theme, style);
+
+      if (frame_style == NULL)
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("A style called \"%s\" has not been defined"),
+                     style);
+          return;
+        }
+
+      switch (frame_state)
+        {
+        case META_FRAME_STATE_NORMAL:
+          if (resize == NULL)
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         ATTRIBUTE_NOT_FOUND,
+                         "resize", element_name);
+              return;
+            }
+
+          
+          frame_resize = meta_frame_resize_from_string (resize);
+          if (frame_resize == META_FRAME_RESIZE_LAST)
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         _("\"%s\" is not a valid value for resize attribute"),
+                         focus);
+              return;
+            }
+          
+          break;
+
+        case META_FRAME_STATE_SHADED:
+          if (META_THEME_ALLOWS (info->theme, META_THEME_UNRESIZABLE_SHADED_STYLES))
+            {
+              if (resize == NULL)
+                /* In state="normal" we would complain here. But instead we accept
+                 * not having a resize attribute and default to resize="both", since
+                 * that most closely mimics what we did in v1, and thus people can
+                 * upgrade a theme to v2 without as much hassle.
+                 */
+                frame_resize = META_FRAME_RESIZE_BOTH;
+              else
+                {
+                  frame_resize = meta_frame_resize_from_string (resize);
+                  if (frame_resize == META_FRAME_RESIZE_LAST)
+                    {
+                      set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                                 _("\"%s\" is not a valid value for resize attribute"),
+                                 focus);
+                      return;
+                    }
+                }
+            }
+          else /* v1 theme */
+            {
+              if (resize != NULL)
+                {
+                  set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                       _("Should not have \"resize\" attribute on <%s> element for maximized/shaded states"),
+                      element_name);
+                  return;
+                }
+
+              /* resize="both" is equivalent to the old behaviour */
+              frame_resize = META_FRAME_RESIZE_BOTH;
+            }
+          break;
+          
+        default:
+          if (resize != NULL)
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         _("Should not have \"resize\" attribute on <%s> element for maximized states"),
+                         element_name);
+              return;
+            }
+
+          frame_resize = META_FRAME_RESIZE_LAST;
+        }
+      
+      switch (frame_state)
+        {
+        case META_FRAME_STATE_NORMAL:
+          if (info->style_set->normal_styles[frame_resize][frame_focus])
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         _("Style has already been specified for state %s resize %s focus %s"),
+                         state, resize, focus);
+              return;
+            }
+          meta_frame_style_ref (frame_style);
+          info->style_set->normal_styles[frame_resize][frame_focus] = frame_style;
+          break;
+        case META_FRAME_STATE_MAXIMIZED:
+          if (info->style_set->maximized_styles[frame_focus])
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         _("Style has already been specified for state %s focus %s"),
+                         state, focus);
+              return;
+            }
+          meta_frame_style_ref (frame_style);
+          info->style_set->maximized_styles[frame_focus] = frame_style;
+          break;
+        case META_FRAME_STATE_SHADED:
+          if (info->style_set->shaded_styles[frame_resize][frame_focus])
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         _("Style has already been specified for state %s resize %s focus %s"),
+                         state, resize, focus);
+              return;
+            }
+          meta_frame_style_ref (frame_style);
+          info->style_set->shaded_styles[frame_resize][frame_focus] = frame_style;
+          break;
+        case META_FRAME_STATE_MAXIMIZED_AND_SHADED:
+          if (info->style_set->maximized_and_shaded_styles[frame_focus])
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         _("Style has already been specified for state %s focus %s"),
+                         state, focus);
+              return;
+            }
+          meta_frame_style_ref (frame_style);
+          info->style_set->maximized_and_shaded_styles[frame_focus] = frame_style;
+          break;
+        case META_FRAME_STATE_LAST:
+          g_assert_not_reached ();
+          break;
+        }
+
+      push_state (info, STATE_FRAME);      
+    }
+  else
+    {
+      set_error (error, context,
+                 G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed below <%s>"),
+                 element_name, "frame_style_set");
+    }
+}
+
+static void
+parse_piece_element (GMarkupParseContext  *context,
+                     const gchar          *element_name,
+                     const gchar         **attribute_names,
+                     const gchar         **attribute_values,
+                     ParseInfo            *info,
+                     GError              **error)
+{
+  g_return_if_fail (peek_state (info) == STATE_PIECE);
+
+  if (ELEMENT_IS ("draw_ops"))
+    {
+      if (info->op_list)
+        {
+          set_error (error, context,
+                     G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("Can't have a two draw_ops for a <piece> element (theme specified a draw_ops attribute and also a <draw_ops> element, or specified two elements)"));
+          return;
+        }
+            
+      if (!check_no_attributes (context, element_name, attribute_names, attribute_values,
+                                error))
+        return;
+
+      g_assert (info->op_list == NULL);
+      info->op_list = meta_draw_op_list_new (2);
+
+      push_state (info, STATE_DRAW_OPS);
+    }
+  else
+    {
+      set_error (error, context,
+                 G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed below <%s>"),
+                 element_name, "piece");
+    }
+}
+
+static void
+parse_button_element (GMarkupParseContext  *context,
+                      const gchar          *element_name,
+                      const gchar         **attribute_names,
+                      const gchar         **attribute_values,
+                      ParseInfo            *info,
+                      GError              **error)
+{
+  g_return_if_fail (peek_state (info) == STATE_BUTTON);
+  
+  if (ELEMENT_IS ("draw_ops"))
+    {
+      if (info->op_list)
+        {
+          set_error (error, context,
+                     G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("Can't have a two draw_ops for a <button> element (theme specified a draw_ops attribute and also a <draw_ops> element, or specified two elements)"));
+          return;
+        }
+            
+      if (!check_no_attributes (context, element_name, attribute_names, attribute_values,
+                                error))
+        return;
+
+      g_assert (info->op_list == NULL);
+      info->op_list = meta_draw_op_list_new (2);
+
+      push_state (info, STATE_DRAW_OPS);
+    }
+  else
+    {
+      set_error (error, context,
+                 G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed below <%s>"),
+                 element_name, "button");
+    }
+}
+
+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, THEME_PARSE_ERROR, THEME_PARSE_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 = meta_theme_new ();
+          info->theme->name = g_strdup (info->theme_name);
+          info->theme->filename = g_strdup (info->theme_file);
+          info->theme->dirname = g_strdup (info->theme_dir);
+          info->theme->format_version = info->format_version;
+          
+          push_state (info, STATE_THEME);
+        }
+      else
+        set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                   _("Outermost element in theme must be <metacity_theme> not <%s>"),
+                   element_name);
+      break;
+
+    case STATE_THEME:
+      parse_toplevel_element (context, element_name,
+                              attribute_names, attribute_values,
+                              info, error);
+      break;
+    case STATE_INFO:
+      parse_info_element (context, element_name,
+                          attribute_names, attribute_values,
+                          info, error);
+      break;
+    case STATE_NAME:
+    case STATE_AUTHOR:
+    case STATE_COPYRIGHT:
+    case STATE_DATE:
+    case STATE_DESCRIPTION:
+      set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed inside a name/author/date/description element"),
+                 element_name);
+      break;
+    case STATE_CONSTANT:
+      set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed inside a <constant> element"),
+                 element_name);
+      break;
+    case STATE_FRAME_GEOMETRY:
+      parse_geometry_element (context, element_name,
+                              attribute_names, attribute_values,
+                              info, error);
+      break;
+    case STATE_DISTANCE:
+    case STATE_BORDER:
+    case STATE_ASPECT_RATIO:
+      set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed inside a distance/border/aspect_ratio element"),
+                 element_name);
+      break;
+    case STATE_DRAW_OPS:
+      parse_draw_op_element (context, element_name,
+                             attribute_names, attribute_values,
+                             info, error);
+      break;
+    case STATE_LINE:
+    case STATE_RECTANGLE:
+    case STATE_ARC:
+    case STATE_CLIP:
+    case STATE_TINT:
+    case STATE_IMAGE:
+    case STATE_GTK_ARROW:
+    case STATE_GTK_BOX:
+    case STATE_GTK_VLINE:
+    case STATE_ICON:
+    case STATE_TITLE:
+    case STATE_INCLUDE:
+    case STATE_TILE:
+      set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed inside a draw operation element"),
+                 element_name);
+      break;
+    case STATE_GRADIENT:
+      parse_gradient_element (context, element_name,
+                              attribute_names, attribute_values,
+                              info, error);
+      break;
+    case STATE_COLOR:
+      set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed inside a <%s> element"),
+                 element_name, "color");
+      break;
+    case STATE_FRAME_STYLE:
+      parse_style_element (context, element_name,
+                           attribute_names, attribute_values,
+                           info, error);
+      break;
+    case STATE_PIECE:
+      parse_piece_element (context, element_name,
+                           attribute_names, attribute_values,
+                           info, error);
+      break;
+    case STATE_BUTTON:
+      parse_button_element (context, element_name,
+                            attribute_names, attribute_values,
+                            info, error);
+      break;
+    case STATE_MENU_ICON:
+      parse_menu_icon_element (context, element_name,
+                               attribute_names, attribute_values,
+                               info, error);
+      break;
+    case STATE_FRAME_STYLE_SET:
+      parse_style_set_element (context, element_name,
+                               attribute_names, attribute_values,
+                               info, error);
+      break;
+    case STATE_FRAME:
+      set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed inside a <%s> element"),
+                 element_name, "frame");
+      break;
+    case STATE_WINDOW:
+      set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed inside a <%s> element"),
+                 element_name, "window");
+      break;
+    case STATE_FALLBACK:
+      set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                 _("Element <%s> is not allowed inside a <%s> element"),
+                 element_name, "fallback");
+      break;
+    }
+}
+
+static void
+end_element_handler (GMarkupParseContext *context,
+                     const gchar         *element_name,
+                     gpointer             user_data,
+                     GError             **error)
+{
+  ParseInfo *info = user_data;
+
+  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);
+          meta_theme_free (info->theme);
+          info->theme = NULL;
+        }
+      
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_START);
+      break;
+    case STATE_INFO:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_THEME);
+      break;
+    case STATE_NAME:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_INFO);
+      break;
+    case STATE_AUTHOR:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_INFO);
+      break;
+    case STATE_COPYRIGHT:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_INFO);
+      break;
+    case STATE_DATE:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_INFO);
+      break;
+    case STATE_DESCRIPTION:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_INFO);
+      break;
+    case STATE_CONSTANT:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_THEME);
+      break;
+    case STATE_FRAME_GEOMETRY:
+      g_assert (info->layout);
+
+      if (!meta_frame_layout_validate (info->layout,
+                                       error))
+        {
+          add_context_to_error (error, context);
+        }
+
+      /* layout will already be stored in the theme under
+       * its name
+       */
+      meta_frame_layout_unref (info->layout);
+      info->layout = NULL;
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_THEME);
+      break;
+    case STATE_DISTANCE:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_FRAME_GEOMETRY);
+      break;
+    case STATE_BORDER:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_FRAME_GEOMETRY);
+      break;
+    case STATE_ASPECT_RATIO:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_FRAME_GEOMETRY);
+      break;
+    case STATE_DRAW_OPS:
+      {
+        g_assert (info->op_list);
+        
+        if (!meta_draw_op_list_validate (info->op_list,
+                                         error))
+          {
+            add_context_to_error (error, context);
+            meta_draw_op_list_unref (info->op_list);
+            info->op_list = NULL;
+          }
+
+        pop_state (info);
+
+        switch (peek_state (info))
+          {
+          case STATE_BUTTON:
+          case STATE_PIECE:
+          case STATE_MENU_ICON:
+            /* Leave info->op_list to be picked up
+             * when these elements are closed
+             */
+            g_assert (info->op_list);
+            break;
+          case STATE_THEME:
+            g_assert (info->op_list);
+            meta_draw_op_list_unref (info->op_list);
+            info->op_list = NULL;
+            break;
+          default:
+            /* Op list can't occur in other contexts */
+            g_assert_not_reached ();
+            break;
+          }
+      }
+      break;
+    case STATE_LINE:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_DRAW_OPS);
+      break;
+    case STATE_RECTANGLE:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_DRAW_OPS);
+      break;
+    case STATE_ARC:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_DRAW_OPS);
+      break;
+    case STATE_CLIP:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_DRAW_OPS);
+      break;
+    case STATE_TINT:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_DRAW_OPS);
+      break;
+    case STATE_GRADIENT:
+      g_assert (info->op);
+      g_assert (info->op->type == META_DRAW_GRADIENT);
+      if (!meta_gradient_spec_validate (info->op->data.gradient.gradient_spec,
+                                        error))
+        {
+          add_context_to_error (error, context);
+          meta_draw_op_free (info->op);
+          info->op = NULL;
+        }
+      else
+        {
+          g_assert (info->op_list);
+          meta_draw_op_list_append (info->op_list, info->op);
+          info->op = NULL;
+        }
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_DRAW_OPS);
+      break;
+    case STATE_IMAGE:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_DRAW_OPS);
+      break;
+    case STATE_GTK_ARROW:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_DRAW_OPS);
+      break;
+    case STATE_GTK_BOX:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_DRAW_OPS);
+      break;
+    case STATE_GTK_VLINE:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_DRAW_OPS);
+      break;
+    case STATE_ICON:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_DRAW_OPS);
+      break;
+    case STATE_TITLE:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_DRAW_OPS);
+      break;
+    case STATE_INCLUDE:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_DRAW_OPS);
+      break;
+    case STATE_TILE:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_DRAW_OPS);
+      break;
+    case STATE_COLOR:
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_GRADIENT);
+      break;
+    case STATE_FRAME_STYLE:
+      g_assert (info->style);
+
+      if (!meta_frame_style_validate (info->style,
+                                      info->theme->format_version,
+                                      error))
+        {
+          add_context_to_error (error, context);
+        }
+
+      /* Frame style is in the theme hash table and a ref
+       * is held there
+       */
+      meta_frame_style_unref (info->style);
+      info->style = NULL;
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_THEME);
+      break;
+    case STATE_PIECE:
+      g_assert (info->style);
+      if (info->op_list == NULL)
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("No draw_ops provided for frame piece"));
+        }
+      else
+        {
+          info->style->pieces[info->piece] = info->op_list;
+          info->op_list = NULL;
+        }
+      pop_state (info);
+      g_assert (peek_state (info) == STATE_FRAME_STYLE);
+      break;
+    case STATE_BUTTON:
+      g_assert (info->style);
+      if (info->op_list == NULL)
+        {
+          set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                     _("No draw_ops provided for button"));
+        }
+      else
+        {
+          info->style->buttons[info->button_type][info->button_state] =
+            info->op_list;
+          info->op_list = NULL;
+        }
+      pop_state (info);
+      break;
+    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;
+    }
+
+  pop_required_version (info);
+}
+
+#define NO_TEXT(element_name) set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, _("No text is allowed inside element <%s>"), element_name)
+
+static gboolean
+all_whitespace (const char *text,
+                int         text_len)
+{
+  const char *p;
+  const char *end;
+  
+  p = text;
+  end = text + text_len;
+  
+  while (p != end)
+    {
+      if (!g_ascii_isspace (*p))
+        return FALSE;
+
+      p = g_utf8_next_char (p);
+    }
+
+  return TRUE;
+}
+
+static void
+text_handler (GMarkupParseContext *context,
+              const gchar         *text,
+              gsize                text_len,
+              gpointer             user_data,
+              GError             **error)
+{
+  ParseInfo *info = user_data;
+
+  if (info->skip_level > 0)
+    return;
+
+  if (all_whitespace (text, text_len))
+    return;
+  
+  /* FIXME http://bugzilla.gnome.org/show_bug.cgi?id=70448 would
+   * allow a nice cleanup here.
+   */
+
+  switch (peek_state (info))
+    {
+    case STATE_START:
+      g_assert_not_reached (); /* gmarkup shouldn't do this */
+      break;
+    case STATE_THEME:
+      NO_TEXT ("metacity_theme");
+      break;
+    case STATE_INFO:
+      NO_TEXT ("info");
+      break;
+    case STATE_NAME:
+      if (info->theme->readable_name != NULL)
+        {
+          set_error (error, context, G_MARKUP_ERROR,
+                     G_MARKUP_ERROR_PARSE,
+                     _("<%s> specified twice for this theme"),
+                     "name");
+          return;
+        }
+
+      info->theme->readable_name = g_strndup (text, text_len);
+      break;
+    case STATE_AUTHOR:
+      if (info->theme->author != NULL)
+        {
+          set_error (error, context, G_MARKUP_ERROR,
+                     G_MARKUP_ERROR_PARSE,
+                     _("<%s> specified twice for this theme"),
+                     "author");
+          return;
+        }
+
+      info->theme->author = g_strndup (text, text_len);
+      break;
+    case STATE_COPYRIGHT:
+      if (info->theme->copyright != NULL)
+        {
+          set_error (error, context, G_MARKUP_ERROR,
+                     G_MARKUP_ERROR_PARSE,
+                     _("<%s> specified twice for this theme"),
+                     "copyright");
+          return;
+        }
+
+      info->theme->copyright = g_strndup (text, text_len);
+      break;
+    case STATE_DATE:
+      if (info->theme->date != NULL)
+        {
+          set_error (error, context, G_MARKUP_ERROR,
+                     G_MARKUP_ERROR_PARSE,
+                     _("<%s> specified twice for this theme"),
+                     "date");
+          return;
+        }
+
+      info->theme->date = g_strndup (text, text_len);
+      break;
+    case STATE_DESCRIPTION:
+      if (info->theme->description != NULL)
+        {
+          set_error (error, context, G_MARKUP_ERROR,
+                     G_MARKUP_ERROR_PARSE,
+                     _("<%s> specified twice for this theme"),
+                     "description");
+          return;
+        }
+
+      info->theme->description = g_strndup (text, text_len);
+      break;
+    case STATE_CONSTANT:
+      NO_TEXT ("constant");
+      break;
+    case STATE_FRAME_GEOMETRY:
+      NO_TEXT ("frame_geometry");
+      break;
+    case STATE_DISTANCE:
+      NO_TEXT ("distance");
+      break;
+    case STATE_BORDER:
+      NO_TEXT ("border");
+      break;
+    case STATE_ASPECT_RATIO:
+      NO_TEXT ("aspect_ratio");
+      break;
+    case STATE_DRAW_OPS:
+      NO_TEXT ("draw_ops");
+      break;
+    case STATE_LINE:
+      NO_TEXT ("line");
+      break;
+    case STATE_RECTANGLE:
+      NO_TEXT ("rectangle");
+      break;
+    case STATE_ARC:
+      NO_TEXT ("arc");
+      break;
+    case STATE_CLIP:
+      NO_TEXT ("clip");
+      break;
+    case STATE_TINT:
+      NO_TEXT ("tint");
+      break;
+    case STATE_GRADIENT:
+      NO_TEXT ("gradient");
+      break;
+    case STATE_IMAGE:
+      NO_TEXT ("image");
+      break;
+    case STATE_GTK_ARROW:
+      NO_TEXT ("gtk_arrow");
+      break;
+    case STATE_GTK_BOX:
+      NO_TEXT ("gtk_box");
+      break;
+    case STATE_GTK_VLINE:
+      NO_TEXT ("gtk_vline");
+      break;
+    case STATE_ICON:
+      NO_TEXT ("icon");
+      break;
+    case STATE_TITLE:
+      NO_TEXT ("title");
+      break;
+    case STATE_INCLUDE:
+      NO_TEXT ("include");
+      break;
+    case STATE_TILE:
+      NO_TEXT ("tile");
+      break;
+    case STATE_COLOR:
+      NO_TEXT ("color");
+      break;
+    case STATE_FRAME_STYLE:
+      NO_TEXT ("frame_style");
+      break;
+    case STATE_PIECE:
+      NO_TEXT ("piece");
+      break;
+    case STATE_BUTTON:
+      NO_TEXT ("button");
+      break;
+    case STATE_MENU_ICON:
+      NO_TEXT ("menu_icon");
+      break;
+    case STATE_FRAME_STYLE_SET:
+      NO_TEXT ("frame_style_set");
+      break;
+    case STATE_FRAME:
+      NO_TEXT ("frame");
+      break;
+    case STATE_WINDOW:
+      NO_TEXT ("window");
+      break;
+    case STATE_FALLBACK:
+      NO_TEXT ("fallback");
+      break;
+    }
+}
+
+/* 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 == THEME_PARSE_ERROR &&
+           error->code == THEME_PARSE_ERROR_TOO_OLD));
+}
+
+static MetaTheme *
+load_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;
+  MetaTheme *retval;
+
+  g_return_val_if_fail (error && *error == NULL, NULL);
+
+  text = NULL;
+  retval = NULL;
+  context = NULL;
+
+  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_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 = info.theme;
+  info.theme = NULL;
+
+ 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;
+}
+
+MetaTheme*
+meta_theme_load (const char *theme_name,
+                 GError    **err)
+{
+  GError *error = NULL;
+  char *theme_dir;
+  MetaTheme *retval;
+  const gchar* const* xdg_data_dirs;
+  int major_version;
+  int i;
+
+  retval = NULL;
+
+  if (meta_is_debugging ())
+    {
+      /* Try in themes in our source tree */
+      /* We try all supported major versions from current to oldest */
+      for (major_version = THEME_MAJOR_VERSION; (major_version > 0); major_version--)
+        {
+          theme_dir = g_build_filename ("./themes", theme_name, NULL);
+          retval = load_theme (theme_dir, theme_name, major_version, &error);
+          g_free (theme_dir);
+          if (!keep_trying (&error))
+            goto out;
+        }
+    }
+  
+  /* 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 home dir, XDG_DATA_DIRS, then system dir for themes */
+
+      /* Try home dir for themes */
+      theme_dir = g_build_filename (g_get_home_dir (),
+                                    ".themes",
+                                    theme_name,
+                                    THEME_SUBDIR,
+                                    NULL);
+
+      retval = load_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_dir, theme_name, major_version, &error);
+          g_free (theme_dir);
+          if (!keep_trying (&error))
+            goto out;
+        }
+
+      /* Look for themes in MUTTER_DATADIR */
+      theme_dir = g_build_filename (MUTTER_DATADIR,
+                                    "themes",
+                                    theme_name,
+                                    THEME_SUBDIR,
+                                    NULL);
+
+      retval = load_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/gtk/theme-parser.h b/gtk/theme-parser.h
new file mode 100644
index 0000000..035d700
--- /dev/null
+++ b/gtk/theme-parser.h
@@ -0,0 +1,32 @@
+/* -*- 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "theme.h"
+
+#ifndef META_THEME_PARSER_H
+#define META_THEME_PARSER_H
+
+MetaTheme* meta_theme_load (const char *theme_name,
+                            GError    **err);
+
+#endif
diff --git a/gtk/theme.c b/gtk/theme.c
new file mode 100644
index 0000000..3e90f4c
--- /dev/null
+++ b/gtk/theme.c
@@ -0,0 +1,6694 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity Theme Rendering */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+/**
+ * \file theme.c    Making Metacity look pretty
+ *
+ * The window decorations drawn by Metacity are described by files on disk
+ * known internally as "themes" (externally as "window border themes" on
+ * http://art.gnome.org/themes/metacity/ or "Metacity themes"). This file
+ * contains most of the code necessary to support themes; it does not
+ * contain the XML parser, which is in theme-parser.c.
+ *
+ * \bug This is a big file with lots of different subsystems, which might
+ * be better split out into separate files.
+ */
+
+/**
+ * \defgroup tokenizer   The theme expression tokenizer
+ *
+ * Themes can use a simple expression language to represent the values of
+ * things. This is the tokeniser used for that language.
+ *
+ * \bug We could remove almost all this code by using GScanner instead,
+ * but we would also have to find every expression in every existing theme
+ * we could and make sure the parse trees were the same.
+ */
+
+/**
+ * \defgroup parser  The theme expression parser
+ *
+ * Themes can use a simple expression language to represent the values of
+ * things. This is the parser used for that language.
+ */
+
+#include <config.h>
+#include "theme.h"
+#include "theme-parser.h"
+#include "util.h"
+#include "gradient.h"
+#include <gtk/gtk.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "gtk-compat.h"
+
+#define GDK_COLOR_RGBA(color)                                           \
+                         ((guint32) (0xff                         |     \
+                                     (((color).red / 256) << 24)   |    \
+                                     (((color).green / 256) << 16) |    \
+                                     (((color).blue / 256) << 8)))
+
+#define GDK_COLOR_RGB(color)                                            \
+                         ((guint32) ((((color).red / 256) << 16)   |    \
+                                     (((color).green / 256) << 8)  |    \
+                                     (((color).blue / 256))))
+
+#define ALPHA_TO_UCHAR(d) ((unsigned char) ((d) * 255))
+
+#define DEBUG_FILL_STRUCT(s) memset ((s), 0xef, sizeof (*(s)))
+#define CLAMP_UCHAR(v) ((guchar) (CLAMP (((int)v), (int)0, (int)255)))
+#define INTENSITY(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11)
+
+static void gtk_style_shade		(GdkColor	 *a,
+					 GdkColor	 *b,
+					 gdouble	  k);
+static void rgb_to_hls			(gdouble	 *r,
+					 gdouble	 *g,
+					 gdouble	 *b);
+static void hls_to_rgb			(gdouble	 *h,
+					 gdouble	 *l,
+					 gdouble	 *s);
+
+/**
+ * The current theme. (Themes are singleton.)
+ */
+static MetaTheme *meta_current_theme = NULL;
+
+static GdkPixbuf *
+colorize_pixbuf (GdkPixbuf *orig,
+                 GdkColor  *new_color)
+{
+  GdkPixbuf *pixbuf;
+  double intensity;
+  int x, y;
+  const guchar *src;
+  guchar *dest;
+  int orig_rowstride;
+  int dest_rowstride;
+  int width, height;
+  gboolean has_alpha;
+  const guchar *src_pixels;
+  guchar *dest_pixels;
+  
+  pixbuf = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (orig), gdk_pixbuf_get_has_alpha (orig),
+			   gdk_pixbuf_get_bits_per_sample (orig),
+			   gdk_pixbuf_get_width (orig), gdk_pixbuf_get_height (orig));
+
+  if (pixbuf == NULL)
+    return NULL;
+  
+  orig_rowstride = gdk_pixbuf_get_rowstride (orig);
+  dest_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+  width = gdk_pixbuf_get_width (pixbuf);
+  height = gdk_pixbuf_get_height (pixbuf);
+  has_alpha = gdk_pixbuf_get_has_alpha (orig);
+  src_pixels = gdk_pixbuf_get_pixels (orig);
+  dest_pixels = gdk_pixbuf_get_pixels (pixbuf);
+  
+  for (y = 0; y < height; y++)
+    {
+      src = src_pixels + y * orig_rowstride;
+      dest = dest_pixels + y * dest_rowstride;
+
+      for (x = 0; x < width; x++)
+        {
+          double dr, dg, db;
+          
+          intensity = INTENSITY (src[0], src[1], src[2]) / 255.0;
+
+          if (intensity <= 0.5)
+            {
+              /* Go from black at intensity = 0.0 to new_color at intensity = 0.5 */
+              dr = (new_color->red * intensity * 2.0) / 65535.0;
+              dg = (new_color->green * intensity * 2.0) / 65535.0;
+              db = (new_color->blue * intensity * 2.0) / 65535.0;
+            }
+          else
+            {
+              /* Go from new_color at intensity = 0.5 to white at intensity = 1.0 */
+              dr = (new_color->red + (65535 - new_color->red) * (intensity - 0.5) * 2.0) / 65535.0;
+              dg = (new_color->green + (65535 - new_color->green) * (intensity - 0.5) * 2.0) / 65535.0;
+              db = (new_color->blue + (65535 - new_color->blue) * (intensity - 0.5) * 2.0) / 65535.0;
+            }
+          
+          dest[0] = CLAMP_UCHAR (255 * dr);
+          dest[1] = CLAMP_UCHAR (255 * dg);
+          dest[2] = CLAMP_UCHAR (255 * db);
+          
+          if (has_alpha)
+            {
+              dest[3] = src[3];
+              src += 4;
+              dest += 4;
+            }
+          else
+            {
+              src += 3;
+              dest += 3;
+            }
+        }
+    }
+
+  return pixbuf;
+}
+
+static void
+color_composite (const GdkColor *bg,
+                 const GdkColor *fg,
+                 double          alpha_d,
+                 GdkColor       *color)
+{
+  guint16 alpha;
+
+  *color = *bg;
+  alpha = alpha_d * 0xffff;
+  color->red = color->red + (((fg->red - color->red) * alpha + 0x8000) >> 16);
+  color->green = color->green + (((fg->green - color->green) * alpha + 0x8000) >> 16);
+  color->blue = color->blue + (((fg->blue - color->blue) * alpha + 0x8000) >> 16);
+}
+
+/**
+ * Sets all the fields of a border to dummy values.
+ *
+ * \param border The border whose fields should be reset.
+ */
+static void
+init_border (GtkBorder *border)
+{
+  border->top = -1;
+  border->bottom = -1;
+  border->left = -1;
+  border->right = -1;
+}
+
+/**
+ * Creates a new, empty MetaFrameLayout. The fields will be set to dummy
+ * values.
+ *
+ * \return The newly created MetaFrameLayout.
+ */
+MetaFrameLayout*
+meta_frame_layout_new  (void)
+{
+  MetaFrameLayout *layout;
+
+  layout = g_new0 (MetaFrameLayout, 1);
+
+  layout->refcount = 1;
+
+  /* Fill with -1 values to detect invalid themes */
+  layout->left_width = -1;
+  layout->right_width = -1;
+  layout->bottom_height = -1;
+
+  init_border (&layout->title_border);
+
+  layout->title_vertical_pad = -1;
+  
+  layout->right_titlebar_edge = -1;
+  layout->left_titlebar_edge = -1;
+
+  layout->button_sizing = META_BUTTON_SIZING_LAST;
+  layout->button_aspect = 1.0;
+  layout->button_width = -1;
+  layout->button_height = -1;
+
+  layout->has_title = TRUE;
+  layout->title_scale = 1.0;
+  
+  init_border (&layout->button_border);
+
+  return layout;
+}
+
+/**
+ *
+ */
+static gboolean
+validate_border (const GtkBorder *border,
+                 const char     **bad)
+{
+  *bad = NULL;
+  
+  if (border->top < 0)
+    *bad = _("top");
+  else if (border->bottom < 0)
+    *bad = _("bottom");
+  else if (border->left < 0)
+    *bad = _("left");
+  else if (border->right < 0)
+    *bad = _("right");
+
+  return *bad == NULL;
+}
+
+/**
+ * Ensures that the theme supplied a particular dimension. When a
+ * MetaFrameLayout is created, all its integer fields are set to -1
+ * by meta_frame_layout_new(). After an instance of this type
+ * should have been initialised, this function checks that
+ * a given field is not still at -1. It is never called directly, but
+ * rather via the CHECK_GEOMETRY_VALUE and CHECK_GEOMETRY_BORDER
+ * macros.
+ *
+ * \param      val    The value to check
+ * \param      name   The name to use in the error message
+ * \param[out] error  Set to an error if val was not initialised
+ */
+static gboolean
+validate_geometry_value (int         val,
+                         const char *name,
+                         GError    **error)
+{
+  if (val < 0)
+    {
+      g_set_error (error, META_THEME_ERROR,
+                   META_THEME_ERROR_FRAME_GEOMETRY,
+                   _("frame geometry does not specify \"%s\" dimension"),
+                   name);
+      return FALSE;
+    }
+  else
+    return TRUE;
+}
+
+static gboolean
+validate_geometry_border (const GtkBorder *border,
+                          const char      *name,
+                          GError         **error)
+{
+  const char *bad;
+
+  if (!validate_border (border, &bad))
+    {
+      g_set_error (error, META_THEME_ERROR,
+                   META_THEME_ERROR_FRAME_GEOMETRY,
+                   _("frame geometry does not specify dimension \"%s\" for border \"%s\""),
+                   bad, name);
+      return FALSE;
+    }
+  else
+    return TRUE;
+}
+
+gboolean
+meta_frame_layout_validate (const MetaFrameLayout *layout,
+                            GError               **error)
+{
+  g_return_val_if_fail (layout != NULL, FALSE);
+
+#define CHECK_GEOMETRY_VALUE(vname) if (!validate_geometry_value (layout->vname, #vname, error)) return FALSE
+
+#define CHECK_GEOMETRY_BORDER(bname) if (!validate_geometry_border (&layout->bname, #bname, error)) return FALSE
+
+  CHECK_GEOMETRY_VALUE (left_width);
+  CHECK_GEOMETRY_VALUE (right_width);
+  CHECK_GEOMETRY_VALUE (bottom_height);
+
+  CHECK_GEOMETRY_BORDER (title_border);
+
+  CHECK_GEOMETRY_VALUE (title_vertical_pad);
+
+  CHECK_GEOMETRY_VALUE (right_titlebar_edge);
+  CHECK_GEOMETRY_VALUE (left_titlebar_edge);
+
+  switch (layout->button_sizing)
+    {
+    case META_BUTTON_SIZING_ASPECT:
+      if (layout->button_aspect < (0.1) ||
+          layout->button_aspect > (15.0))
+        {
+          g_set_error (error, META_THEME_ERROR,
+                       META_THEME_ERROR_FRAME_GEOMETRY,
+                       _("Button aspect ratio %g is not reasonable"),
+                       layout->button_aspect);
+          return FALSE;
+        }
+      break;
+    case META_BUTTON_SIZING_FIXED:
+      CHECK_GEOMETRY_VALUE (button_width);
+      CHECK_GEOMETRY_VALUE (button_height);
+      break;
+    case META_BUTTON_SIZING_LAST:
+      g_set_error (error, META_THEME_ERROR,
+                   META_THEME_ERROR_FRAME_GEOMETRY,
+                   _("Frame geometry does not specify size of buttons"));
+      return FALSE;
+    }
+
+  CHECK_GEOMETRY_BORDER (button_border);
+
+  return TRUE;
+}
+
+MetaFrameLayout*
+meta_frame_layout_copy (const MetaFrameLayout *src)
+{
+  MetaFrameLayout *layout;
+
+  layout = g_new0 (MetaFrameLayout, 1);
+
+  *layout = *src;
+
+  layout->refcount = 1;
+
+  return layout;
+}
+
+void
+meta_frame_layout_ref (MetaFrameLayout *layout)
+{
+  g_return_if_fail (layout != NULL);
+
+  layout->refcount += 1;
+}
+
+void
+meta_frame_layout_unref (MetaFrameLayout *layout)
+{
+  g_return_if_fail (layout != NULL);
+  g_return_if_fail (layout->refcount > 0);
+
+  layout->refcount -= 1;
+
+  if (layout->refcount == 0)
+    {
+      DEBUG_FILL_STRUCT (layout);
+      g_free (layout);
+    }
+}
+
+void
+meta_frame_layout_get_borders (const MetaFrameLayout *layout,
+                               int                    text_height,
+                               MetaFrameFlags         flags,
+                               int                   *top_height,
+                               int                   *bottom_height,
+                               int                   *left_width,
+                               int                   *right_width)
+{
+  int buttons_height, title_height;
+  
+  g_return_if_fail (top_height != NULL);
+  g_return_if_fail (bottom_height != NULL);
+  g_return_if_fail (left_width != NULL);
+  g_return_if_fail (right_width != NULL);
+
+  if (!layout->has_title)
+    text_height = 0;
+  
+  buttons_height = layout->button_height +
+    layout->button_border.top + layout->button_border.bottom;
+  title_height = text_height +
+    layout->title_vertical_pad +
+    layout->title_border.top + layout->title_border.bottom;
+
+  if (top_height)
+    {
+      *top_height = MAX (buttons_height, title_height);
+    }
+
+  if (left_width)
+    *left_width = layout->left_width;
+  if (right_width)
+    *right_width = layout->right_width;
+
+  if (bottom_height)
+    {
+      if (flags & META_FRAME_SHADED)
+        *bottom_height = 0;
+      else
+        *bottom_height = layout->bottom_height;
+    }
+
+  if (flags & META_FRAME_FULLSCREEN)
+    {
+      if (top_height)
+        *top_height = 0;
+      if (bottom_height)
+        *bottom_height = 0;
+      if (left_width)
+        *left_width = 0;
+      if (right_width)
+        *right_width = 0;
+    }
+}
+
+static MetaButtonSpace*
+rect_for_function (MetaFrameGeometry *fgeom,
+                   MetaFrameFlags     flags,
+                   MetaButtonFunction function,
+                   MetaTheme         *theme)
+{
+
+  /* Firstly, check version-specific things. */
+  
+  if (META_THEME_ALLOWS(theme, META_THEME_SHADE_STICK_ABOVE_BUTTONS))
+    {
+      switch (function)
+        {
+        case META_BUTTON_FUNCTION_SHADE:
+          if ((flags & META_FRAME_ALLOWS_SHADE) && !(flags & META_FRAME_SHADED))
+            return &fgeom->shade_rect;
+          else
+            return NULL;
+        case META_BUTTON_FUNCTION_ABOVE:
+          if (!(flags & META_FRAME_ABOVE))
+            return &fgeom->above_rect;
+          else
+            return NULL;
+        case META_BUTTON_FUNCTION_STICK:
+          if (!(flags & META_FRAME_STUCK))
+            return &fgeom->stick_rect;
+          else
+            return NULL;
+        case META_BUTTON_FUNCTION_UNSHADE:
+          if ((flags & META_FRAME_ALLOWS_SHADE) && (flags & META_FRAME_SHADED))
+            return &fgeom->unshade_rect;
+          else
+            return NULL;
+        case META_BUTTON_FUNCTION_UNABOVE:
+          if (flags & META_FRAME_ABOVE)
+            return &fgeom->unabove_rect;
+          else
+            return NULL;
+        case META_BUTTON_FUNCTION_UNSTICK:
+          if (flags & META_FRAME_STUCK)
+            return &fgeom->unstick_rect;
+        default:
+          /* just go on to the next switch block */;
+        }
+    }
+
+  /* now consider the buttons which exist in all versions */
+
+  switch (function)
+    {
+    case META_BUTTON_FUNCTION_MENU:
+      if (flags & META_FRAME_ALLOWS_MENU)
+        return &fgeom->menu_rect;
+      else
+        return NULL;
+    case META_BUTTON_FUNCTION_MINIMIZE:
+      if (flags & META_FRAME_ALLOWS_MINIMIZE)
+        return &fgeom->min_rect;
+      else
+        return NULL;
+    case META_BUTTON_FUNCTION_MAXIMIZE:
+      if (flags & META_FRAME_ALLOWS_MAXIMIZE)
+        return &fgeom->max_rect;
+      else
+        return NULL;
+    case META_BUTTON_FUNCTION_CLOSE:
+      if (flags & META_FRAME_ALLOWS_DELETE)
+        return &fgeom->close_rect;
+      else
+        return NULL;
+    case META_BUTTON_FUNCTION_STICK:
+    case META_BUTTON_FUNCTION_SHADE:
+    case META_BUTTON_FUNCTION_ABOVE:
+    case META_BUTTON_FUNCTION_UNSTICK:
+    case META_BUTTON_FUNCTION_UNSHADE:
+    case META_BUTTON_FUNCTION_UNABOVE:
+      /* we are being asked for a >v1 button which hasn't been handled yet,
+       * so obviously we're not in a theme which supports that version.
+       * therefore, we don't show the button. return NULL and all will
+       * be well.
+       */
+      return NULL;
+      
+    case META_BUTTON_FUNCTION_LAST:
+      return NULL;
+    }
+
+  return NULL;
+}
+
+static gboolean
+strip_button (MetaButtonSpace *func_rects[MAX_BUTTONS_PER_CORNER],
+              GdkRectangle    *bg_rects[MAX_BUTTONS_PER_CORNER],
+              int             *n_rects,
+              MetaButtonSpace *to_strip)
+{
+  int i;
+  
+  i = 0;
+  while (i < *n_rects)
+    {
+      if (func_rects[i] == to_strip)
+        {
+          *n_rects -= 1;
+
+          /* shift the other rects back in the array */
+          while (i < *n_rects)
+            {
+              func_rects[i] = func_rects[i+1];
+              bg_rects[i] = bg_rects[i+1];
+
+              ++i;
+            }
+
+          func_rects[i] = NULL;
+          bg_rects[i] = NULL;
+          
+          return TRUE;
+        }
+
+      ++i;
+    }
+
+  return FALSE; /* did not strip anything */
+}
+
+void
+meta_frame_layout_calc_geometry (const MetaFrameLayout  *layout,
+                                 int                     text_height,
+                                 MetaFrameFlags          flags,
+                                 int                     client_width,
+                                 int                     client_height,
+                                 const MetaButtonLayout *button_layout,
+                                 MetaFrameGeometry      *fgeom,
+                                 MetaTheme              *theme)
+{
+  int i, n_left, n_right, n_left_spacers, n_right_spacers;
+  int x;
+  int button_y;
+  int title_right_edge;
+  int width, height;
+  int button_width, button_height;
+  int min_size_for_rounding;
+  
+  /* the left/right rects in order; the max # of rects
+   * is the number of button functions
+   */
+  MetaButtonSpace *left_func_rects[MAX_BUTTONS_PER_CORNER];
+  MetaButtonSpace *right_func_rects[MAX_BUTTONS_PER_CORNER];
+  GdkRectangle *left_bg_rects[MAX_BUTTONS_PER_CORNER];
+  gboolean left_buttons_has_spacer[MAX_BUTTONS_PER_CORNER];
+  GdkRectangle *right_bg_rects[MAX_BUTTONS_PER_CORNER];
+  gboolean right_buttons_has_spacer[MAX_BUTTONS_PER_CORNER];
+  
+  meta_frame_layout_get_borders (layout, text_height,
+                                 flags,
+                                 &fgeom->top_height,
+                                 &fgeom->bottom_height,
+                                 &fgeom->left_width,
+                                 &fgeom->right_width);
+
+  width = client_width + fgeom->left_width + fgeom->right_width;
+
+  height = ((flags & META_FRAME_SHADED) ? 0: client_height) +
+    fgeom->top_height + fgeom->bottom_height;
+
+  fgeom->width = width;
+  fgeom->height = height;
+
+  fgeom->top_titlebar_edge = layout->title_border.top;
+  fgeom->bottom_titlebar_edge = layout->title_border.bottom;
+  fgeom->left_titlebar_edge = layout->left_titlebar_edge;
+  fgeom->right_titlebar_edge = layout->right_titlebar_edge;
+
+  /* gcc warnings */
+  button_width = -1;
+  button_height = -1;
+  
+  switch (layout->button_sizing)
+    {
+    case META_BUTTON_SIZING_ASPECT:
+      button_height = fgeom->top_height - layout->button_border.top - layout->button_border.bottom;
+      button_width = button_height / layout->button_aspect;
+      break;
+    case META_BUTTON_SIZING_FIXED:
+      button_width = layout->button_width;
+      button_height = layout->button_height;
+      break;
+    case META_BUTTON_SIZING_LAST:
+      g_assert_not_reached ();
+      break;
+    }
+
+  /* FIXME all this code sort of pretends that duplicate buttons
+   * with the same function are allowed, but that breaks the
+   * code in frames.c, so isn't really allowed right now.
+   * Would need left_close_rect, right_close_rect, etc.
+   */
+  
+  /* Init all button rects to 0, lame hack */
+  memset (ADDRESS_OF_BUTTON_RECTS (fgeom), '\0',
+          LENGTH_OF_BUTTON_RECTS);
+  
+  n_left = 0;
+  n_right = 0;
+  n_left_spacers = 0;
+  n_right_spacers = 0;
+
+  if (!layout->hide_buttons)
+    {
+      /* Try to fill in rects */
+      for (i = 0; i < MAX_BUTTONS_PER_CORNER && button_layout->left_buttons[i] != META_BUTTON_FUNCTION_LAST; i++)
+        {
+          left_func_rects[n_left] = rect_for_function (fgeom, flags,
+                                                       button_layout->left_buttons[i],
+                                                       theme);
+          if (left_func_rects[n_left] != NULL)
+            {
+              left_buttons_has_spacer[n_left] = button_layout->left_buttons_has_spacer[i];
+              if (button_layout->left_buttons_has_spacer[i])
+                ++n_left_spacers;
+
+              ++n_left;
+            }
+        }
+      
+      for (i = 0; i < MAX_BUTTONS_PER_CORNER && button_layout->right_buttons[i] != META_BUTTON_FUNCTION_LAST; i++)
+        {
+          right_func_rects[n_right] = rect_for_function (fgeom, flags,
+                                                         button_layout->right_buttons[i],
+                                                         theme);
+          if (right_func_rects[n_right] != NULL)
+            {
+              right_buttons_has_spacer[n_right] = button_layout->right_buttons_has_spacer[i];
+              if (button_layout->right_buttons_has_spacer[i])
+                ++n_right_spacers;
+
+              ++n_right;
+            }
+        }
+    }
+
+  for (i = 0; i < MAX_BUTTONS_PER_CORNER; i++)
+    {
+      left_bg_rects[i] = NULL;
+      right_bg_rects[i] = NULL;
+    }
+
+  for (i = 0; i < n_left; i++)
+    {
+      if (i == 0) /* prefer left background if only one button */
+        left_bg_rects[i] = &fgeom->left_left_background;
+      else if (i == (n_left - 1))
+        left_bg_rects[i] = &fgeom->left_right_background;
+      else
+        left_bg_rects[i] = &fgeom->left_middle_backgrounds[i - 1];
+    }
+
+  for (i = 0; i < n_right; i++)
+    {
+      /* prefer right background if only one button */
+      if (i == (n_right - 1))
+        right_bg_rects[i] = &fgeom->right_right_background;
+      else if (i == 0)
+        right_bg_rects[i] = &fgeom->right_left_background;
+      else
+        right_bg_rects[i] = &fgeom->right_middle_backgrounds[i - 1];
+    }
+  
+  /* Be sure buttons fit */
+  while (n_left > 0 || n_right > 0)
+    {
+      int space_used_by_buttons;
+      int space_available;
+
+      space_available = fgeom->width - layout->left_titlebar_edge - layout->right_titlebar_edge;
+      
+      space_used_by_buttons = 0;
+
+      space_used_by_buttons += button_width * n_left;
+      space_used_by_buttons += (button_width * 0.75) * n_left_spacers;
+      space_used_by_buttons += layout->button_border.left * n_left;
+      space_used_by_buttons += layout->button_border.right * n_left;
+
+      space_used_by_buttons += button_width * n_right;
+      space_used_by_buttons += (button_width * 0.75) * n_right_spacers;
+      space_used_by_buttons += layout->button_border.left * n_right;
+      space_used_by_buttons += layout->button_border.right * n_right;
+
+      if (space_used_by_buttons <= space_available)
+        break; /* Everything fits, bail out */
+      
+      /* First try to remove separators */
+      if (n_left_spacers > 0)
+        {
+          left_buttons_has_spacer[--n_left_spacers] = FALSE;
+          continue;
+        }
+      else if (n_right_spacers > 0)
+        {
+          right_buttons_has_spacer[--n_right_spacers] = FALSE;
+          continue;
+        }
+
+      /* Otherwise we need to shave out a button. Shave
+       * above, stick, shade, min, max, close, then menu (menu is most useful);
+       * prefer the default button locations.
+       */
+      if (strip_button (left_func_rects, left_bg_rects,
+                        &n_left, &fgeom->above_rect))
+        continue;
+      else if (strip_button (right_func_rects, right_bg_rects,
+                             &n_right, &fgeom->above_rect))
+        continue;
+      else if (strip_button (left_func_rects, left_bg_rects,
+                        &n_left, &fgeom->stick_rect))
+        continue;
+      else if (strip_button (right_func_rects, right_bg_rects,
+                             &n_right, &fgeom->stick_rect))
+        continue;
+      else if (strip_button (left_func_rects, left_bg_rects,
+                        &n_left, &fgeom->shade_rect))
+        continue;
+      else if (strip_button (right_func_rects, right_bg_rects,
+                             &n_right, &fgeom->shade_rect))
+        continue;
+      else if (strip_button (left_func_rects, left_bg_rects,
+                        &n_left, &fgeom->min_rect))
+        continue;
+      else if (strip_button (right_func_rects, right_bg_rects,
+                             &n_right, &fgeom->min_rect))
+        continue;
+      else if (strip_button (left_func_rects, left_bg_rects,
+                             &n_left, &fgeom->max_rect))
+        continue;
+      else if (strip_button (right_func_rects, right_bg_rects,
+                             &n_right, &fgeom->max_rect))
+        continue;
+      else if (strip_button (left_func_rects, left_bg_rects,
+                             &n_left, &fgeom->close_rect))
+        continue;
+      else if (strip_button (right_func_rects, right_bg_rects,
+                             &n_right, &fgeom->close_rect))
+        continue;
+      else if (strip_button (right_func_rects, right_bg_rects,
+                             &n_right, &fgeom->menu_rect))
+        continue;
+      else if (strip_button (left_func_rects, left_bg_rects,
+                             &n_left, &fgeom->menu_rect))
+        continue;
+      else
+        {
+          meta_bug ("Could not find a button to strip. n_left = %d n_right = %d\n",
+                    n_left, n_right);
+        }
+    }
+  
+  /* center buttons vertically */
+  button_y = (fgeom->top_height -
+              (button_height + layout->button_border.top + layout->button_border.bottom)) / 2 + layout->button_border.top;
+
+  /* right edge of farthest-right button */
+  x = width - layout->right_titlebar_edge;
+  
+  i = n_right - 1;
+  while (i >= 0)
+    {
+      MetaButtonSpace *rect;
+
+      if (x < 0) /* if we go negative, leave the buttons we don't get to as 0-width */
+        break;
+      
+      rect = right_func_rects[i];
+      rect->visible.x = x - layout->button_border.right - button_width;
+      if (right_buttons_has_spacer[i])
+        rect->visible.x -= (button_width * 0.75);
+
+      rect->visible.y = button_y;
+      rect->visible.width = button_width;
+      rect->visible.height = button_height;
+
+      if (flags & META_FRAME_MAXIMIZED)
+        {
+          rect->clickable.x = rect->visible.x;
+          rect->clickable.y = 0;
+          rect->clickable.width = rect->visible.width;
+          rect->clickable.height = button_height + button_y;
+
+          if (i == n_right - 1)
+            rect->clickable.width += layout->right_titlebar_edge + layout->right_width + layout->button_border.right;
+
+        }
+      else
+        g_memmove (&(rect->clickable), &(rect->visible), sizeof(rect->clickable));
+
+      *(right_bg_rects[i]) = rect->visible;
+      
+      x = rect->visible.x - layout->button_border.left;
+      
+      --i;
+    }
+
+  /* save right edge of titlebar for later use */
+  title_right_edge = x - layout->title_border.right;
+
+  /* Now x changes to be position from the left and we go through
+   * the left-side buttons
+   */
+  x = layout->left_titlebar_edge;
+  for (i = 0; i < n_left; i++)
+    {
+      MetaButtonSpace *rect;
+
+      rect = left_func_rects[i];
+      
+      rect->visible.x = x + layout->button_border.left;
+      rect->visible.y = button_y;
+      rect->visible.width = button_width;
+      rect->visible.height = button_height;
+
+      if (flags & META_FRAME_MAXIMIZED)
+        {
+          if (i==0)
+            {
+              rect->clickable.x = 0;
+              rect->clickable.width = button_width + x;
+            }
+          else
+            {
+              rect->clickable.x = rect->visible.x;
+              rect->clickable.width = button_width;
+            }
+
+            rect->clickable.y = 0;
+            rect->clickable.height = button_height + button_y;
+          }
+        else
+          g_memmove (&(rect->clickable), &(rect->visible), sizeof(rect->clickable));
+
+
+      x = rect->visible.x + rect->visible.width + layout->button_border.right;
+      if (left_buttons_has_spacer[i])
+        x += (button_width * 0.75);
+
+      *(left_bg_rects[i]) = rect->visible;
+    }
+
+  /* We always fill as much vertical space as possible with title rect,
+   * rather than centering it like the buttons
+   */
+  fgeom->title_rect.x = x + layout->title_border.left;
+  fgeom->title_rect.y = layout->title_border.top;
+  fgeom->title_rect.width = title_right_edge - fgeom->title_rect.x;
+  fgeom->title_rect.height = fgeom->top_height - layout->title_border.top - layout->title_border.bottom;
+
+  /* Nuke title if it won't fit */
+  if (fgeom->title_rect.width < 0 ||
+      fgeom->title_rect.height < 0)
+    {
+      fgeom->title_rect.width = 0;
+      fgeom->title_rect.height = 0;
+    }
+
+  if (flags & META_FRAME_SHADED)
+    min_size_for_rounding = 0;
+  else
+    min_size_for_rounding = 5;
+  
+  fgeom->top_left_corner_rounded_radius = 0;
+  fgeom->top_right_corner_rounded_radius = 0;
+  fgeom->bottom_left_corner_rounded_radius = 0;
+  fgeom->bottom_right_corner_rounded_radius = 0;
+
+  if (fgeom->top_height + fgeom->left_width >= min_size_for_rounding)
+    fgeom->top_left_corner_rounded_radius = layout->top_left_corner_rounded_radius;
+  if (fgeom->top_height + fgeom->right_width >= min_size_for_rounding)
+    fgeom->top_right_corner_rounded_radius = layout->top_right_corner_rounded_radius;
+
+  if (fgeom->bottom_height + fgeom->left_width >= min_size_for_rounding)
+    fgeom->bottom_left_corner_rounded_radius = layout->bottom_left_corner_rounded_radius;
+  if (fgeom->bottom_height + fgeom->right_width >= min_size_for_rounding)
+    fgeom->bottom_right_corner_rounded_radius = layout->bottom_right_corner_rounded_radius;
+}
+
+MetaGradientSpec*
+meta_gradient_spec_new (MetaGradientType type)
+{
+  MetaGradientSpec *spec;
+
+  spec = g_new (MetaGradientSpec, 1);
+
+  spec->type = type;
+  spec->color_specs = NULL;
+
+  return spec;
+}
+
+static void
+free_color_spec (gpointer spec, gpointer user_data)
+{
+  meta_color_spec_free (spec);
+}
+
+void
+meta_gradient_spec_free (MetaGradientSpec *spec)
+{
+  g_return_if_fail (spec != NULL);
+
+  g_slist_foreach (spec->color_specs, free_color_spec, NULL);
+  g_slist_free (spec->color_specs);
+  
+  DEBUG_FILL_STRUCT (spec);
+  g_free (spec);
+}
+
+GdkPixbuf*
+meta_gradient_spec_render (const MetaGradientSpec *spec,
+                           GtkWidget              *widget,
+                           int                     width,
+                           int                     height)
+{
+  int n_colors;
+  GdkColor *colors;
+  GSList *tmp;
+  int i;
+  GdkPixbuf *pixbuf;
+
+  n_colors = g_slist_length (spec->color_specs);
+
+  if (n_colors == 0)
+    return NULL;
+
+  colors = g_new (GdkColor, n_colors);
+
+  i = 0;
+  tmp = spec->color_specs;
+  while (tmp != NULL)
+    {
+      meta_color_spec_render (tmp->data, widget, &colors[i]);
+
+      tmp = tmp->next;
+      ++i;
+    }
+
+  pixbuf = meta_gradient_create_multi (width, height,
+                                       colors, n_colors,
+                                       spec->type);
+
+  g_free (colors);
+
+  return pixbuf;
+}
+
+gboolean
+meta_gradient_spec_validate (MetaGradientSpec *spec,
+                             GError          **error)
+{
+  g_return_val_if_fail (spec != NULL, FALSE);
+  
+  if (g_slist_length (spec->color_specs) < 2)
+    {
+      g_set_error (error, META_THEME_ERROR,
+                   META_THEME_ERROR_FAILED,
+                   _("Gradients should have at least two colors"));
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+MetaAlphaGradientSpec*
+meta_alpha_gradient_spec_new (MetaGradientType       type,
+                              int                    n_alphas)
+{
+  MetaAlphaGradientSpec *spec;
+
+  g_return_val_if_fail (n_alphas > 0, NULL);
+  
+  spec = g_new0 (MetaAlphaGradientSpec, 1);
+
+  spec->type = type;
+  spec->alphas = g_new0 (unsigned char, n_alphas);
+  spec->n_alphas = n_alphas;
+
+  return spec;
+}
+
+void
+meta_alpha_gradient_spec_free (MetaAlphaGradientSpec *spec)
+{
+  g_return_if_fail (spec != NULL);
+
+  g_free (spec->alphas);
+  g_free (spec);
+}
+
+MetaColorSpec*
+meta_color_spec_new (MetaColorSpecType type)
+{
+  MetaColorSpec *spec;
+  MetaColorSpec dummy;
+  int size;
+
+  size = G_STRUCT_OFFSET (MetaColorSpec, data);
+
+  switch (type)
+    {
+    case META_COLOR_SPEC_BASIC:
+      size += sizeof (dummy.data.basic);
+      break;
+
+    case META_COLOR_SPEC_GTK:
+      size += sizeof (dummy.data.gtk);
+      break;
+
+    case META_COLOR_SPEC_BLEND:
+      size += sizeof (dummy.data.blend);
+      break;
+
+    case META_COLOR_SPEC_SHADE:
+      size += sizeof (dummy.data.shade);
+      break;
+    }
+
+  spec = g_malloc0 (size);
+
+  spec->type = type;
+
+  return spec;
+}
+
+void
+meta_color_spec_free (MetaColorSpec *spec)
+{
+  g_return_if_fail (spec != NULL);
+
+  switch (spec->type)
+    {
+    case META_COLOR_SPEC_BASIC:
+      DEBUG_FILL_STRUCT (&spec->data.basic);
+      break;
+
+    case META_COLOR_SPEC_GTK:
+      DEBUG_FILL_STRUCT (&spec->data.gtk);
+      break;
+
+    case META_COLOR_SPEC_BLEND:
+      if (spec->data.blend.foreground)
+        meta_color_spec_free (spec->data.blend.foreground);
+      if (spec->data.blend.background)
+        meta_color_spec_free (spec->data.blend.background);
+      DEBUG_FILL_STRUCT (&spec->data.blend);
+      break;
+
+    case META_COLOR_SPEC_SHADE:
+      if (spec->data.shade.base)
+        meta_color_spec_free (spec->data.shade.base);
+      DEBUG_FILL_STRUCT (&spec->data.shade);
+      break;
+    }
+
+  g_free (spec);
+}
+
+MetaColorSpec*
+meta_color_spec_new_from_string (const char *str,
+                                 GError    **err)
+{
+  MetaColorSpec *spec;
+
+  spec = NULL;
+  
+  if (str[0] == 'g' && str[1] == 't' && str[2] == 'k' && str[3] == ':')
+    {
+      /* GTK color */
+      const char *bracket;
+      const char *end_bracket;
+      char *tmp;
+      GtkStateType state;
+      MetaGtkColorComponent component;
+      
+      bracket = str;
+      while (*bracket && *bracket != '[')
+        ++bracket;
+
+      if (*bracket == '\0')
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("GTK color specification must have the state in brackets, e.g. gtk:fg[NORMAL] where NORMAL is the state; could not parse \"%s\""),
+                       str);
+          return NULL;
+        }
+
+      end_bracket = bracket;
+      ++end_bracket;
+      while (*end_bracket && *end_bracket != ']')
+        ++end_bracket;
+      
+      if (*end_bracket == '\0')
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("GTK color specification must have a close bracket after the state, e.g. gtk:fg[NORMAL] where NORMAL is the state; could not parse \"%s\""),
+                       str);
+          return NULL;
+        }
+
+      tmp = g_strndup (bracket + 1, end_bracket - bracket - 1);
+      state = meta_gtk_state_from_string (tmp);
+      if (((int) state) == -1)
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("Did not understand state \"%s\" in color specification"),
+                       tmp);
+          g_free (tmp);
+          return NULL;
+        }
+      g_free (tmp);
+      
+      tmp = g_strndup (str + 4, bracket - str - 4);
+      component = meta_color_component_from_string (tmp);
+      if (component == META_GTK_COLOR_LAST)
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("Did not understand color component \"%s\" in color specification"),
+                       tmp);
+          g_free (tmp);
+          return NULL;
+        }
+      g_free (tmp);
+
+      spec = meta_color_spec_new (META_COLOR_SPEC_GTK);
+      spec->data.gtk.state = state;
+      spec->data.gtk.component = component;
+      g_assert (spec->data.gtk.state < N_GTK_STATES);
+      g_assert (spec->data.gtk.component < META_GTK_COLOR_LAST);
+    }
+  else if (str[0] == 'b' && str[1] == 'l' && str[2] == 'e' && str[3] == 'n' &&
+           str[4] == 'd' && str[5] == '/')
+    {
+      /* blend */
+      char **split;
+      double alpha;
+      char *end;
+      MetaColorSpec *fg;
+      MetaColorSpec *bg;
+      
+      split = g_strsplit (str, "/", 4);
+      
+      if (split[0] == NULL || split[1] == NULL ||
+          split[2] == NULL || split[3] == NULL)
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("Blend format is \"blend/bg_color/fg_color/alpha\", \"%s\" does not fit the format"),
+                       str);
+          g_strfreev (split);
+          return NULL;
+        }
+
+      alpha = g_ascii_strtod (split[3], &end);
+      if (end == split[3])
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("Could not parse alpha value \"%s\" in blended color"),
+                       split[3]);
+          g_strfreev (split);
+          return NULL;
+        }
+
+      if (alpha < (0.0 - 1e6) || alpha > (1.0 + 1e6))
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("Alpha value \"%s\" in blended color is not between 0.0 and 1.0"),
+                       split[3]);
+          g_strfreev (split);
+          return NULL;
+        }
+      
+      fg = NULL;
+      bg = NULL;
+
+      bg = meta_color_spec_new_from_string (split[1], err);
+      if (bg == NULL)
+        {
+          g_strfreev (split);
+          return NULL;
+        }
+
+      fg = meta_color_spec_new_from_string (split[2], err);
+      if (fg == NULL)
+        {
+          meta_color_spec_free (bg);
+          g_strfreev (split);
+          return NULL;
+        }
+
+      g_strfreev (split);
+      
+      spec = meta_color_spec_new (META_COLOR_SPEC_BLEND);
+      spec->data.blend.alpha = alpha;
+      spec->data.blend.background = bg;
+      spec->data.blend.foreground = fg;
+    }
+  else if (str[0] == 's' && str[1] == 'h' && str[2] == 'a' && str[3] == 'd' &&
+           str[4] == 'e' && str[5] == '/')
+    {
+      /* shade */
+      char **split;
+      double factor;
+      char *end;
+      MetaColorSpec *base;
+      
+      split = g_strsplit (str, "/", 3);
+      
+      if (split[0] == NULL || split[1] == NULL ||
+          split[2] == NULL)
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("Shade format is \"shade/base_color/factor\", \"%s\" does not fit the format"),
+                       str);
+          g_strfreev (split);
+          return NULL;
+        }
+
+      factor = g_ascii_strtod (split[2], &end);
+      if (end == split[2])
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("Could not parse shade factor \"%s\" in shaded color"),
+                       split[2]);
+          g_strfreev (split);
+          return NULL;
+        }
+
+      if (factor < (0.0 - 1e6))
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("Shade factor \"%s\" in shaded color is negative"),
+                       split[2]);
+          g_strfreev (split);
+          return NULL;
+        }
+      
+      base = NULL;
+
+      base = meta_color_spec_new_from_string (split[1], err);
+      if (base == NULL)
+        {
+          g_strfreev (split);
+          return NULL;
+        }
+
+      g_strfreev (split);
+      
+      spec = meta_color_spec_new (META_COLOR_SPEC_SHADE);
+      spec->data.shade.factor = factor;
+      spec->data.shade.base = base;
+    }
+  else
+    {
+      spec = meta_color_spec_new (META_COLOR_SPEC_BASIC);
+      
+      if (!gdk_color_parse (str, &spec->data.basic.color))
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("Could not parse color \"%s\""),
+                       str);
+          meta_color_spec_free (spec);
+          return NULL;
+        }
+    }
+
+  g_assert (spec);
+  
+  return spec;
+}
+
+MetaColorSpec*
+meta_color_spec_new_gtk (MetaGtkColorComponent component,
+                         GtkStateType          state)
+{
+  MetaColorSpec *spec;
+
+  spec = meta_color_spec_new (META_COLOR_SPEC_GTK);
+
+  spec->data.gtk.component = component;
+  spec->data.gtk.state = state;
+
+  return spec;
+}
+
+void
+meta_color_spec_render (MetaColorSpec *spec,
+                        GtkWidget     *widget,
+                        GdkColor      *color)
+{
+  GtkStyle *style;
+
+  style = gtk_widget_get_style (widget);
+
+  g_return_if_fail (spec != NULL);
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+  g_return_if_fail (style != NULL);
+
+  switch (spec->type)
+    {
+    case META_COLOR_SPEC_BASIC:
+      *color = spec->data.basic.color;
+      break;
+
+    case META_COLOR_SPEC_GTK:
+      switch (spec->data.gtk.component)
+        {
+        case META_GTK_COLOR_BG:
+          *color = style->bg[spec->data.gtk.state];
+          break;
+        case META_GTK_COLOR_FG:
+          *color = style->fg[spec->data.gtk.state];
+          break;
+        case META_GTK_COLOR_BASE:
+          *color = style->base[spec->data.gtk.state];
+          break;
+        case META_GTK_COLOR_TEXT:
+          *color = style->text[spec->data.gtk.state];
+          break;
+        case META_GTK_COLOR_LIGHT:
+          *color = style->light[spec->data.gtk.state];
+          break;
+        case META_GTK_COLOR_DARK:
+          *color = style->dark[spec->data.gtk.state];
+          break;
+        case META_GTK_COLOR_MID:
+          *color = style->mid[spec->data.gtk.state];
+          break;
+        case META_GTK_COLOR_TEXT_AA:
+          *color = style->text_aa[spec->data.gtk.state];
+          break;
+        case META_GTK_COLOR_LAST:
+          g_assert_not_reached ();
+          break;
+        }
+      break;
+
+    case META_COLOR_SPEC_BLEND:
+      {
+        GdkColor bg, fg;
+
+        meta_color_spec_render (spec->data.blend.background, widget, &bg);
+        meta_color_spec_render (spec->data.blend.foreground, widget, &fg);
+
+        color_composite (&bg, &fg, spec->data.blend.alpha, 
+                         &spec->data.blend.color);
+
+        *color = spec->data.blend.color;
+      }
+      break;
+
+    case META_COLOR_SPEC_SHADE:
+      {
+        meta_color_spec_render (spec->data.shade.base, widget, 
+                                &spec->data.shade.color);
+            
+        gtk_style_shade (&spec->data.shade.color, 
+                         &spec->data.shade.color, spec->data.shade.factor);
+
+        *color = spec->data.shade.color;
+      }
+      break;
+    }
+}
+
+/**
+ * Represents an operation as a string.
+ *
+ * \param type  an operation, such as addition
+ * \return  a string, such as "+"
+ */
+static const char*
+op_name (PosOperatorType type)
+{
+  switch (type)
+    {
+    case POS_OP_ADD:
+      return "+";
+    case POS_OP_SUBTRACT:
+      return "-";
+    case POS_OP_MULTIPLY:
+      return "*";
+    case POS_OP_DIVIDE:
+      return "/";
+    case POS_OP_MOD:
+      return "%";
+    case POS_OP_MAX:
+      return "`max`";
+    case POS_OP_MIN:
+      return "`min`";
+    case POS_OP_NONE:
+      break;
+    }
+
+  return "<unknown>";
+}
+
+/**
+ * Parses a string and returns an operation.
+ *
+ * \param p  a pointer into a string representing an operation; part of an
+ *           expression somewhere, so not null-terminated
+ * \param len  set to the length of the string found. Set to 0 if none is.
+ * \return  the operation found. If none was, returns POS_OP_NONE.
+ */
+static PosOperatorType
+op_from_string (const char *p,
+                int        *len)
+{
+  *len = 0;
+  
+  switch (*p)
+    {
+    case '+':
+      *len = 1;
+      return POS_OP_ADD;
+    case '-':
+      *len = 1;
+      return POS_OP_SUBTRACT;
+    case '*':
+      *len = 1;
+      return POS_OP_MULTIPLY;
+    case '/':
+      *len = 1;
+      return POS_OP_DIVIDE;
+    case '%':
+      *len = 1;
+      return POS_OP_MOD;
+
+    case '`':
+      if (p[0] == '`' &&
+          p[1] == 'm' &&
+          p[2] == 'a' &&
+          p[3] == 'x' &&
+          p[4] == '`')
+        {
+          *len = 5;
+          return POS_OP_MAX;
+        }
+      else if (p[0] == '`' &&
+               p[1] == 'm' &&
+               p[2] == 'i' &&
+               p[3] == 'n' &&
+               p[4] == '`')
+        {
+          *len = 5;
+          return POS_OP_MIN;
+        }
+    }
+
+  return POS_OP_NONE;
+}
+
+/**
+ * Frees an array of tokens. All the tokens and their associated memory
+ * will be freed.
+ *
+ * \param tokens  an array of tokens to be freed
+ * \param n_tokens  how many tokens are in the array.
+ */
+static void
+free_tokens (PosToken *tokens,
+             int       n_tokens)
+{
+  int i;
+
+  /* n_tokens can be 0 since tokens may have been allocated more than
+   * it was initialized
+   */
+
+  for (i = 0; i < n_tokens; i++)
+    if (tokens[i].type == POS_TOKEN_VARIABLE)
+      g_free (tokens[i].d.v.name);
+
+  g_free (tokens);
+}
+
+/**
+ * Tokenises a number in an expression.
+ *
+ * \param p  a pointer into a string representing an operation; part of an
+ *           expression somewhere, so not null-terminated
+ * \param end_return  set to a pointer to the end of the number found; but
+ *                    not updated if no number was found at all
+ * \param next  set to either an integer or a float token
+ * \param[out] err  set to the problem if there was a problem
+ * \return TRUE if a valid number was found, FALSE otherwise (and "err" will
+ *         have been set)
+ *
+ * \bug The "while (*start)..." part: what's wrong with strchr-ish things?
+ * \bug The name is wrong: it doesn't parse anything.
+ * \ingroup tokenizer
+ */
+static gboolean
+parse_number (const char  *p,
+              const char **end_return,
+              PosToken    *next,
+              GError     **err)
+{
+  const char *start = p;
+  char *end;
+  gboolean is_float;
+  char *num_str;
+
+  while (*p && (*p == '.' || g_ascii_isdigit (*p)))
+    ++p;
+
+  if (p == start)
+    {
+      char buf[7] = { '\0' };
+      buf[g_unichar_to_utf8 (g_utf8_get_char (p), buf)] = '\0';
+      g_set_error (err, META_THEME_ERROR,
+                   META_THEME_ERROR_BAD_CHARACTER,
+                   _("Coordinate expression contains character '%s' which is not allowed"),
+                   buf);
+      return FALSE;
+    }
+
+  *end_return = p;
+
+  /* we need this to exclude floats like "1e6" */
+  num_str = g_strndup (start, p - start);
+  start = num_str;
+  is_float = FALSE;
+  while (*start)
+    {
+      if (*start == '.')
+        is_float = TRUE;
+      ++start;
+    }
+
+  if (is_float)
+    {
+      next->type = POS_TOKEN_DOUBLE;
+      next->d.d.val = g_ascii_strtod (num_str, &end);
+
+      if (end == num_str)
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("Coordinate expression contains floating point number '%s' which could not be parsed"),
+                       num_str);
+          g_free (num_str);
+          return FALSE;
+        }
+    }
+  else
+    {
+      next->type = POS_TOKEN_INT;
+      next->d.i.val = strtol (num_str, &end, 10);
+      if (end == num_str)
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("Coordinate expression contains integer '%s' which could not be parsed"),
+                       num_str);
+          g_free (num_str);
+          return FALSE;
+        }
+    }
+
+  g_free (num_str);
+
+  g_assert (next->type == POS_TOKEN_INT || next->type == POS_TOKEN_DOUBLE);
+
+  return TRUE;
+}
+
+/**
+ * Whether a variable can validly appear as part of the name of a variable.
+ */
+#define IS_VARIABLE_CHAR(c) (g_ascii_isalpha ((c)) || (c) == '_')
+
+#if 0
+static void
+debug_print_tokens (PosToken *tokens,
+                    int       n_tokens)
+{
+  int i;
+  
+  for (i = 0; i < n_tokens; i++)
+    {
+      PosToken *t = &tokens[i];
+
+      g_print (" ");
+
+      switch (t->type)
+        {
+        case POS_TOKEN_INT:
+          g_print ("\"%d\"", t->d.i.val);
+          break;
+        case POS_TOKEN_DOUBLE:
+          g_print ("\"%g\"", t->d.d.val);
+          break;
+        case POS_TOKEN_OPEN_PAREN:
+          g_print ("\"(\"");
+          break;
+        case POS_TOKEN_CLOSE_PAREN:
+          g_print ("\")\"");
+          break;
+        case POS_TOKEN_VARIABLE:
+          g_print ("\"%s\"", t->d.v.name);
+          break;
+        case POS_TOKEN_OPERATOR:
+          g_print ("\"%s\"", op_name (t->d.o.op));
+          break;
+        }
+    }
+
+  g_print ("\n");
+}
+#endif
+
+/**
+ * Tokenises an expression.
+ *
+ * \param      expr        The expression
+ * \param[out] tokens_p    The resulting tokens
+ * \param[out] n_tokens_p  The number of resulting tokens
+ * \param[out] err  set to the problem if there was a problem
+ *
+ * \return  True if the expression was successfully tokenised; false otherwise.
+ *
+ * \ingroup tokenizer
+ */
+static gboolean
+pos_tokenize (const char  *expr,
+              PosToken   **tokens_p,
+              int         *n_tokens_p,
+              GError     **err)
+{
+  PosToken *tokens;
+  int n_tokens;
+  int allocated;
+  const char *p;
+  
+  *tokens_p = NULL;
+  *n_tokens_p = 0;
+
+  allocated = 3;
+  n_tokens = 0;
+  tokens = g_new (PosToken, allocated);
+
+  p = expr;
+  while (*p)
+    {
+      PosToken *next;
+      int len;
+      
+      if (n_tokens == allocated)
+        {
+          allocated *= 2;
+          tokens = g_renew (PosToken, tokens, allocated);
+        }
+
+      next = &tokens[n_tokens];
+
+      switch (*p)
+        {
+        case '*':
+        case '/':
+        case '+':
+        case '-': /* negative numbers aren't allowed so this is easy */
+        case '%':
+        case '`':
+          next->type = POS_TOKEN_OPERATOR;
+          next->d.o.op = op_from_string (p, &len);
+          if (next->d.o.op != POS_OP_NONE)
+            {
+              ++n_tokens;
+              p = p + (len - 1); /* -1 since we ++p later */
+            }
+          else
+            {
+              g_set_error (err, META_THEME_ERROR,
+                           META_THEME_ERROR_FAILED,
+                           _("Coordinate expression contained unknown operator at the start of this text: \"%s\""),
+                           p);
+              
+              goto error;
+            }
+          break;
+
+        case '(':
+          next->type = POS_TOKEN_OPEN_PAREN;
+          ++n_tokens;
+          break;
+
+        case ')':
+          next->type = POS_TOKEN_CLOSE_PAREN;
+          ++n_tokens;
+          break;
+
+        case ' ':
+        case '\t':
+        case '\n':		
+          break;
+
+        default:
+          if (IS_VARIABLE_CHAR (*p))
+            {
+              /* Assume variable */
+              const char *start = p;
+              while (*p && IS_VARIABLE_CHAR (*p))
+                ++p;
+              g_assert (p != start);
+              next->type = POS_TOKEN_VARIABLE;
+              next->d.v.name = g_strndup (start, p - start);
+              ++n_tokens;
+              --p; /* since we ++p again at the end of while loop */
+            }
+          else
+            {
+              /* Assume number */
+              const char *end;
+
+              if (!parse_number (p, &end, next, err))
+                goto error;
+
+              ++n_tokens;
+              p = end - 1; /* -1 since we ++p again at the end of while loop */
+            }
+
+          break;
+        }
+
+      ++p;
+    }
+
+  if (n_tokens == 0)
+    {
+      g_set_error (err, META_THEME_ERROR,
+                   META_THEME_ERROR_FAILED,
+                   _("Coordinate expression was empty or not understood"));
+
+      goto error;
+    }
+
+  *tokens_p = tokens;
+  *n_tokens_p = n_tokens;
+
+  return TRUE;
+
+ error:
+  g_assert (err == NULL || *err != NULL);
+
+  free_tokens (tokens, n_tokens);
+  return FALSE;
+}
+
+/**
+ * The type of a PosExpr: either integer, double, or an operation.
+ * \ingroup parser
+ */
+typedef enum
+{
+  POS_EXPR_INT,
+  POS_EXPR_DOUBLE,
+  POS_EXPR_OPERATOR
+} PosExprType;
+
+/**
+ * Type and value of an expression in a parsed sequence. We don't
+ * keep expressions in a tree; if this is of type POS_EXPR_OPERATOR,
+ * the arguments of the operator will be in the array positions
+ * immediately preceding and following this operator; they cannot
+ * themselves be operators.
+ *
+ * \bug operator is char; it should really be of PosOperatorType.
+ * \ingroup parser
+ */
+typedef struct
+{
+  PosExprType type;
+  union
+  {
+    double double_val;
+    int int_val;
+    char operator;
+  } d;
+} PosExpr;
+
+#if 0
+static void
+debug_print_exprs (PosExpr *exprs,
+                   int      n_exprs)
+{
+  int i;
+
+  for (i = 0; i < n_exprs; i++)
+    {
+      switch (exprs[i].type)
+        {
+        case POS_EXPR_INT:
+          g_print (" %d", exprs[i].d.int_val);
+          break;
+        case POS_EXPR_DOUBLE:
+          g_print (" %g", exprs[i].d.double_val);
+          break;
+        case POS_EXPR_OPERATOR:
+          g_print (" %s", op_name (exprs[i].d.operator));
+          break;
+        }
+    }
+  g_print ("\n");
+}
+#endif
+
+static gboolean
+do_operation (PosExpr *a,
+              PosExpr *b,
+              PosOperatorType op,
+              GError **err)
+{
+  /* Promote types to double if required */
+  if (a->type == POS_EXPR_DOUBLE ||
+      b->type == POS_EXPR_DOUBLE)
+    {
+      if (a->type != POS_EXPR_DOUBLE)
+        {
+          a->type = POS_EXPR_DOUBLE;
+          a->d.double_val = a->d.int_val;
+        }
+      if (b->type != POS_EXPR_DOUBLE)
+        {
+          b->type = POS_EXPR_DOUBLE;
+          b->d.double_val = b->d.int_val;
+        }
+    }
+
+  g_assert (a->type == b->type);
+
+  if (a->type == POS_EXPR_INT)
+    {
+      switch (op)
+        {
+        case POS_OP_MULTIPLY:
+          a->d.int_val = a->d.int_val * b->d.int_val;
+          break;
+        case POS_OP_DIVIDE:
+          if (b->d.int_val == 0)
+            {
+              g_set_error (err, META_THEME_ERROR,
+                           META_THEME_ERROR_DIVIDE_BY_ZERO,
+                           _("Coordinate expression results in division by zero"));
+              return FALSE;
+            }
+          a->d.int_val = a->d.int_val / b->d.int_val;
+          break;
+        case POS_OP_MOD:
+          if (b->d.int_val == 0)
+            {
+              g_set_error (err, META_THEME_ERROR,
+                           META_THEME_ERROR_DIVIDE_BY_ZERO,
+                           _("Coordinate expression results in division by zero"));
+              return FALSE;
+            }
+          a->d.int_val = a->d.int_val % b->d.int_val;
+          break;
+        case POS_OP_ADD:
+          a->d.int_val = a->d.int_val + b->d.int_val;
+          break;
+        case POS_OP_SUBTRACT:
+          a->d.int_val = a->d.int_val - b->d.int_val;
+          break;
+        case POS_OP_MAX:
+          a->d.int_val = MAX (a->d.int_val, b->d.int_val);
+          break;
+        case POS_OP_MIN:
+          a->d.int_val = MIN (a->d.int_val, b->d.int_val);
+          break;
+        case POS_OP_NONE:
+          g_assert_not_reached ();
+          break;
+        }
+    }
+  else if (a->type == POS_EXPR_DOUBLE)
+    {
+      switch (op)
+        {
+        case POS_OP_MULTIPLY:
+          a->d.double_val = a->d.double_val * b->d.double_val;
+          break;
+        case POS_OP_DIVIDE:
+          if (b->d.double_val == 0.0)
+            {
+              g_set_error (err, META_THEME_ERROR,
+                           META_THEME_ERROR_DIVIDE_BY_ZERO,
+                           _("Coordinate expression results in division by zero"));
+              return FALSE;
+            }
+          a->d.double_val = a->d.double_val / b->d.double_val;
+          break;
+        case POS_OP_MOD:
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_MOD_ON_FLOAT,
+                       _("Coordinate expression tries to use mod operator on a floating-point number"));
+          return FALSE;
+        case POS_OP_ADD:
+          a->d.double_val = a->d.double_val + b->d.double_val;
+          break;
+        case POS_OP_SUBTRACT:
+          a->d.double_val = a->d.double_val - b->d.double_val;
+          break;
+        case POS_OP_MAX:
+          a->d.double_val = MAX (a->d.double_val, b->d.double_val);
+          break;
+        case POS_OP_MIN:
+          a->d.double_val = MIN (a->d.double_val, b->d.double_val);
+          break;
+        case POS_OP_NONE:
+          g_assert_not_reached ();
+          break;
+        }
+    }
+  else
+    g_assert_not_reached ();
+
+  return TRUE;
+}
+
+static gboolean
+do_operations (PosExpr *exprs,
+               int     *n_exprs,
+               int      precedence,
+               GError **err)
+{
+  int i;
+
+#if 0
+  g_print ("Doing prec %d ops on %d exprs\n", precedence, *n_exprs);
+  debug_print_exprs (exprs, *n_exprs);
+#endif
+
+  i = 1;
+  while (i < *n_exprs)
+    {
+      gboolean compress;
+
+      /* exprs[i-1] first operand
+       * exprs[i]   operator
+       * exprs[i+1] second operand
+       *
+       * we replace first operand with result of mul/div/mod,
+       * or skip over operator and second operand if we have
+       * an add/subtract
+       */
+
+      if (exprs[i-1].type == POS_EXPR_OPERATOR)
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("Coordinate expression has an operator \"%s\" where an operand was expected"),
+                       op_name (exprs[i-1].d.operator));
+          return FALSE;
+        }
+
+      if (exprs[i].type != POS_EXPR_OPERATOR)
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("Coordinate expression had an operand where an operator was expected"));
+          return FALSE;
+        }
+
+      if (i == (*n_exprs - 1))
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("Coordinate expression ended with an operator instead of an operand"));
+          return FALSE;
+        }
+
+      g_assert ((i+1) < *n_exprs);
+
+      if (exprs[i+1].type == POS_EXPR_OPERATOR)
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("Coordinate expression has operator \"%c\" following operator \"%c\" with no operand in between"),
+                       exprs[i+1].d.operator,
+                       exprs[i].d.operator);
+          return FALSE;
+        }
+
+      compress = FALSE;
+
+      switch (precedence)
+        {
+        case 2:
+          switch (exprs[i].d.operator)
+            {
+            case POS_OP_DIVIDE:
+            case POS_OP_MOD:
+            case POS_OP_MULTIPLY:
+              compress = TRUE;
+              if (!do_operation (&exprs[i-1], &exprs[i+1],
+                                 exprs[i].d.operator,
+                                 err))
+                return FALSE;
+              break;
+            }
+          break;
+        case 1:
+          switch (exprs[i].d.operator)
+            {
+            case POS_OP_ADD:
+            case POS_OP_SUBTRACT:
+              compress = TRUE;
+              if (!do_operation (&exprs[i-1], &exprs[i+1],
+                                 exprs[i].d.operator,
+                                 err))
+                return FALSE;
+              break;
+            }
+          break;
+          /* I have no rationale at all for making these low-precedence */
+        case 0:
+          switch (exprs[i].d.operator)
+            {
+            case POS_OP_MAX:
+            case POS_OP_MIN:
+              compress = TRUE;
+              if (!do_operation (&exprs[i-1], &exprs[i+1],
+                                 exprs[i].d.operator,
+                                 err))
+                return FALSE;
+              break;
+            }
+          break;
+        }
+
+      if (compress)
+        {
+          /* exprs[i-1] first operand (now result)
+           * exprs[i]   operator
+           * exprs[i+1] second operand
+           * exprs[i+2] new operator
+           *
+           * we move new operator just after first operand
+           */
+          if ((i+2) < *n_exprs)
+            {
+              g_memmove (&exprs[i], &exprs[i+2],
+                         sizeof (PosExpr) * (*n_exprs - i - 2));
+            }
+
+          *n_exprs -= 2;
+        }
+      else
+        {
+          /* Skip operator and next operand */
+          i += 2;
+        }
+    }
+
+  return TRUE;
+}
+
+/**
+ * There is a predefined set of variables which can appear in an expression.
+ * Here we take a token representing a variable, and return the current value
+ * of that variable in a particular environment.
+ * (The value is always an integer.)
+ *
+ * There are supposedly some circumstances in which this function can be
+ * called from outside Metacity, in which case env->theme will be NULL, and
+ * therefore we can't use it to find out quark values, so we do the comparison
+ * using strcmp, which is slower.
+ *
+ * \param t  The token representing a variable
+ * \param[out] result  The value of that variable; not set if the token did
+ *                     not represent a known variable
+ * \param env  The environment within which t should be evaluated
+ * \param[out] err  set to the problem if there was a problem
+ *
+ * \return true if we found the variable asked for, false if we didn't
+ *
+ * \bug shouldn't t be const?
+ * \bug we should perhaps consider some sort of lookup arrangement into an
+ *      array; also, the duplication of code is unlovely; perhaps using glib
+ *      string hashes instead of quarks would fix both problems?
+ * \ingroup parser
+ */
+static gboolean
+pos_eval_get_variable (PosToken                  *t,
+                       int                       *result,
+                       const MetaPositionExprEnv *env,
+                       GError                   **err)
+{
+  if (env->theme)
+    {
+      if (t->d.v.name_quark == env->theme->quark_width)
+        *result = env->rect.width;
+      else if (t->d.v.name_quark == env->theme->quark_height)
+        *result = env->rect.height;
+      else if (env->object_width >= 0 &&
+               t->d.v.name_quark == env->theme->quark_object_width)
+        *result = env->object_width;
+      else if (env->object_height >= 0 &&
+               t->d.v.name_quark == env->theme->quark_object_height)
+        *result = env->object_height;
+      else if (t->d.v.name_quark == env->theme->quark_left_width)
+        *result = env->left_width;
+      else if (t->d.v.name_quark == env->theme->quark_right_width)
+        *result = env->right_width;
+      else if (t->d.v.name_quark == env->theme->quark_top_height)
+        *result = env->top_height;
+      else if (t->d.v.name_quark == env->theme->quark_bottom_height)
+        *result = env->bottom_height;
+      else if (t->d.v.name_quark == env->theme->quark_mini_icon_width)
+        *result = env->mini_icon_width;
+      else if (t->d.v.name_quark == env->theme->quark_mini_icon_height)
+        *result = env->mini_icon_height;
+      else if (t->d.v.name_quark == env->theme->quark_icon_width)
+        *result = env->icon_width;
+      else if (t->d.v.name_quark == env->theme->quark_icon_height)
+        *result = env->icon_height;
+      else if (t->d.v.name_quark == env->theme->quark_title_width)
+        *result = env->title_width;
+      else if (t->d.v.name_quark == env->theme->quark_title_height)
+        *result = env->title_height;
+      else if (t->d.v.name_quark == env->theme->quark_frame_x_center)
+        *result = env->frame_x_center;
+      else if (t->d.v.name_quark == env->theme->quark_frame_y_center)
+        *result = env->frame_y_center;
+      else
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_UNKNOWN_VARIABLE,
+                       _("Coordinate expression had unknown variable or constant \"%s\""),
+                       t->d.v.name);
+          return FALSE;
+        }
+    }
+  else 
+    {
+      if (strcmp (t->d.v.name, "width") == 0)
+        *result = env->rect.width;
+      else if (strcmp (t->d.v.name, "height") == 0)
+        *result = env->rect.height;
+      else if (env->object_width >= 0 &&
+               strcmp (t->d.v.name, "object_width") == 0)
+        *result = env->object_width;
+      else if (env->object_height >= 0 &&
+               strcmp (t->d.v.name, "object_height") == 0)
+        *result = env->object_height;
+      else if (strcmp (t->d.v.name, "left_width") == 0)
+        *result = env->left_width;
+      else if (strcmp (t->d.v.name, "right_width") == 0)
+        *result = env->right_width;
+      else if (strcmp (t->d.v.name, "top_height") == 0)
+        *result = env->top_height;
+      else if (strcmp (t->d.v.name, "bottom_height") == 0)
+        *result = env->bottom_height;
+      else if (strcmp (t->d.v.name, "mini_icon_width") == 0)
+        *result = env->mini_icon_width;
+      else if (strcmp (t->d.v.name, "mini_icon_height") == 0)
+        *result = env->mini_icon_height;
+      else if (strcmp (t->d.v.name, "icon_width") == 0)
+        *result = env->icon_width;
+      else if (strcmp (t->d.v.name, "icon_height") == 0)
+        *result = env->icon_height;
+      else if (strcmp (t->d.v.name, "title_width") == 0)
+        *result = env->title_width;
+      else if (strcmp (t->d.v.name, "title_height") == 0)
+        *result = env->title_height;
+      else if (strcmp (t->d.v.name, "frame_x_center") == 0)
+        *result = env->frame_x_center;
+      else if (strcmp (t->d.v.name, "frame_y_center") == 0)
+        *result = env->frame_y_center;
+      else
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_UNKNOWN_VARIABLE,
+                       _("Coordinate expression had unknown variable or constant \"%s\""),
+                       t->d.v.name);
+          return FALSE;
+        }
+    }
+
+  return TRUE;
+}
+
+/**
+ * Evaluates a sequence of tokens within a particular environment context,
+ * and returns the current value. May recur if parantheses are found.
+ *
+ * \param tokens  A list of tokens to evaluate.
+ * \param n_tokens  How many tokens are in the list.
+ * \param env  The environment context in which to evaluate the expression.
+ * \param[out] result  The current value of the expression
+ * 
+ * \bug Yes, we really do reparse the expression every time it's evaluated.
+ *      We should keep the parse tree around all the time and just
+ *      run the new values through it.
+ * \ingroup parser
+ */
+static gboolean
+pos_eval_helper (PosToken                   *tokens,
+                 int                         n_tokens,
+                 const MetaPositionExprEnv  *env,
+                 PosExpr                    *result,
+                 GError                    **err)
+{
+  /* Lazy-ass hardcoded limit on number of terms in expression */
+#define MAX_EXPRS 32
+  int paren_level;
+  int first_paren;
+  int i;
+  PosExpr exprs[MAX_EXPRS];
+  int n_exprs;
+  int precedence;
+  
+  /* Our first goal is to get a list of PosExpr, essentially
+   * substituting variables and handling parentheses.
+   */
+
+  first_paren = 0;
+  paren_level = 0;
+  n_exprs = 0;
+  for (i = 0; i < n_tokens; i++)
+    {
+      PosToken *t = &tokens[i];
+
+      if (n_exprs >= MAX_EXPRS)
+        {
+          g_set_error (err, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("Coordinate expression parser overflowed its buffer."));
+          return FALSE;
+        }
+
+      if (paren_level == 0)
+        {
+          switch (t->type)
+            {
+            case POS_TOKEN_INT:
+              exprs[n_exprs].type = POS_EXPR_INT;
+              exprs[n_exprs].d.int_val = t->d.i.val;
+              ++n_exprs;
+              break;
+
+            case POS_TOKEN_DOUBLE:
+              exprs[n_exprs].type = POS_EXPR_DOUBLE;
+              exprs[n_exprs].d.double_val = t->d.d.val;
+              ++n_exprs;
+              break;
+
+            case POS_TOKEN_OPEN_PAREN:
+              ++paren_level;
+              if (paren_level == 1)
+                first_paren = i;
+              break;
+
+            case POS_TOKEN_CLOSE_PAREN:
+              g_set_error (err, META_THEME_ERROR,
+                           META_THEME_ERROR_BAD_PARENS,
+                           _("Coordinate expression had a close parenthesis with no open parenthesis"));
+              return FALSE;
+
+            case POS_TOKEN_VARIABLE:
+              exprs[n_exprs].type = POS_EXPR_INT;
+
+              /* FIXME we should just dump all this crap
+               * in a hash, maybe keep width/height out
+               * for optimization purposes
+               */
+              if (!pos_eval_get_variable (t, &exprs[n_exprs].d.int_val, env, err))
+                return FALSE;
+                  
+              ++n_exprs;
+              break;
+
+            case POS_TOKEN_OPERATOR:
+              exprs[n_exprs].type = POS_EXPR_OPERATOR;
+              exprs[n_exprs].d.operator = t->d.o.op;
+              ++n_exprs;
+              break;
+            }
+        }
+      else
+        {
+          g_assert (paren_level > 0);
+
+          switch (t->type)
+            {
+            case POS_TOKEN_INT:
+            case POS_TOKEN_DOUBLE:
+            case POS_TOKEN_VARIABLE:
+            case POS_TOKEN_OPERATOR:
+              break;
+
+            case POS_TOKEN_OPEN_PAREN:
+              ++paren_level;
+              break;
+
+            case POS_TOKEN_CLOSE_PAREN:
+              if (paren_level == 1)
+                {
+                  /* We closed a toplevel paren group, so recurse */
+                  if (!pos_eval_helper (&tokens[first_paren+1],
+                                        i - first_paren - 1,
+                                        env,
+                                        &exprs[n_exprs],
+                                        err))
+                    return FALSE;
+
+                  ++n_exprs;
+                }
+
+              --paren_level;
+              break;
+
+            }
+        }
+    }
+
+  if (paren_level > 0)
+    {
+      g_set_error (err, META_THEME_ERROR,
+                   META_THEME_ERROR_BAD_PARENS,
+                   _("Coordinate expression had an open parenthesis with no close parenthesis"));
+      return FALSE;
+    }
+
+  /* Now we have no parens and no vars; so we just do all the multiplies
+   * and divides, then all the add and subtract.
+   */
+  if (n_exprs == 0)
+    {
+      g_set_error (err, META_THEME_ERROR,
+                   META_THEME_ERROR_FAILED,
+                   _("Coordinate expression doesn't seem to have any operators or operands"));
+      return FALSE;
+    }
+
+  /* precedence 1 ops */
+  precedence = 2;
+  while (precedence >= 0)
+    {
+      if (!do_operations (exprs, &n_exprs, precedence, err))
+        return FALSE;
+      --precedence;
+    }
+
+  g_assert (n_exprs == 1);
+
+  *result = *exprs;
+
+  return TRUE;
+}
+
+/*
+ *   expr = int | double | expr * expr | expr / expr |
+ *          expr + expr | expr - expr | (expr)
+ *
+ *   so very not worth fooling with bison, yet so very painful by hand.
+ */
+/**
+ * Evaluates an expression.
+ *
+ * \param spec  The expression to evaluate.
+ * \param env   The environment context to evaluate the expression in.
+ * \param[out] val_p  The integer value of the expression; if the expression
+ *                    is of type float, this will be rounded. If we return
+ *                    FALSE because the expression is invalid, this will be
+ *                    zero.
+ * \param[out] err    The error, if anything went wrong.
+ *
+ * \return  True if we evaluated the expression successfully; false otherwise.
+ *
+ * \bug Shouldn't spec be const?
+ * \ingroup parser
+ */
+static gboolean
+pos_eval (MetaDrawSpec              *spec,
+          const MetaPositionExprEnv *env,
+          int                       *val_p,
+          GError                   **err)
+{
+  PosExpr expr;
+
+  *val_p = 0;
+
+  if (pos_eval_helper (spec->tokens, spec->n_tokens, env, &expr, err))
+    {
+      switch (expr.type)
+        {
+        case POS_EXPR_INT:
+          *val_p = expr.d.int_val;
+          break;
+        case POS_EXPR_DOUBLE:
+          *val_p = expr.d.double_val;
+          break;
+        case POS_EXPR_OPERATOR:
+          g_assert_not_reached ();
+          break;
+        }
+      return TRUE;
+    }
+  else
+    {
+      return FALSE;
+    }
+}
+
+/* We always return both X and Y, but only one will be meaningful in
+ * most contexts.
+ */
+
+gboolean
+meta_parse_position_expression (MetaDrawSpec              *spec,
+                                const MetaPositionExprEnv *env,
+                                int                       *x_return,
+                                int                       *y_return,
+                                GError                   **err)
+{
+  /* All positions are in a coordinate system with x, y at the origin.
+   * The expression can have -, +, *, / as operators, floating point
+   * or integer constants, and the variables "width" and "height" and
+   * optionally "object_width" and object_height". Negative numbers
+   * aren't allowed.
+   */
+  int val;
+
+  if (spec->constant)
+    val = spec->value;
+  else
+    {
+      if (pos_eval (spec, env, &spec->value, err) == FALSE)
+        {
+          g_assert (err == NULL || *err != NULL);
+          return FALSE;
+        }
+
+      val = spec->value;
+    }
+
+  if (x_return)
+    *x_return = env->rect.x + val;
+  if (y_return)
+    *y_return = env->rect.y + val;
+
+  return TRUE;
+}
+
+
+gboolean
+meta_parse_size_expression (MetaDrawSpec              *spec,
+                            const MetaPositionExprEnv *env,
+                            int                       *val_return,
+                            GError                   **err)
+{
+  int val;
+
+  if (spec->constant)
+    val = spec->value;
+  else 
+    {
+      if (pos_eval (spec, env, &spec->value, err) == FALSE)
+        {
+          g_assert (err == NULL || *err != NULL);
+          return FALSE;
+        }
+
+      val = spec->value;
+    }
+
+  if (val_return)
+    *val_return = MAX (val, 1); /* require that sizes be at least 1x1 */
+
+  return TRUE;
+}
+
+/* To do this we tokenize, replace variable tokens
+ * that are constants, then reassemble. The purpose
+ * here is to optimize expressions so we don't do hash
+ * lookups to eval them. Obviously it's a tradeoff that
+ * slows down theme load times.
+ */
+gboolean
+meta_theme_replace_constants (MetaTheme   *theme,
+                              PosToken    *tokens,
+                              int          n_tokens,
+                              GError     **err)
+{
+  int i;
+  double dval;
+  int ival;
+  gboolean is_constant = TRUE;
+  
+  /* Loop through tokenized string looking for variables to replace */
+  for (i = 0; i < n_tokens; i++)
+    {
+      PosToken *t = &tokens[i];      
+
+      if (t->type == POS_TOKEN_VARIABLE)
+        {
+          if (meta_theme_lookup_int_constant (theme, t->d.v.name, &ival))
+            {
+              t->type = POS_TOKEN_INT;
+              t->d.i.val = ival;
+            }
+          else if (meta_theme_lookup_float_constant (theme, t->d.v.name, &dval))
+            {
+              t->type = POS_TOKEN_DOUBLE;
+              t->d.d.val = dval;
+            }
+          else 
+            {
+              /* If we've found a variable that cannot be replaced then the
+                 expression is not a constant expression and we want to 
+                 replace it with a GQuark */
+
+              t->d.v.name_quark = g_quark_from_string (t->d.v.name);
+              is_constant = FALSE;
+            }
+        }
+    }  
+
+  return is_constant;
+}
+
+static int
+parse_x_position_unchecked (MetaDrawSpec              *spec,
+                            const MetaPositionExprEnv *env)
+{
+  int retval;
+  GError *error;
+
+  retval = 0;
+  error = NULL;
+  if (!meta_parse_position_expression (spec, env, &retval, NULL, &error))
+    {
+      meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
+                    error->message);
+      
+      g_error_free (error);
+    }
+  
+  return retval;
+}
+
+static int
+parse_y_position_unchecked (MetaDrawSpec              *spec,
+                            const MetaPositionExprEnv *env)
+{
+  int retval;
+  GError *error;
+
+  retval = 0;
+  error = NULL;
+  if (!meta_parse_position_expression (spec, env, NULL, &retval, &error))
+    {
+      meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
+                    error->message);
+
+      g_error_free (error);
+    }
+
+  return retval;
+}
+
+static int
+parse_size_unchecked (MetaDrawSpec        *spec,
+                      MetaPositionExprEnv *env)
+{
+  int retval;
+  GError *error;
+
+  retval = 0;
+  error = NULL;
+  if (!meta_parse_size_expression (spec, env, &retval, &error))
+    {
+      meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
+                    error->message);
+
+      g_error_free (error);
+    }
+
+  return retval;
+}
+
+void
+meta_draw_spec_free (MetaDrawSpec *spec)
+{
+  if (!spec) return;
+  free_tokens (spec->tokens, spec->n_tokens);
+  g_slice_free (MetaDrawSpec, spec);
+}
+
+MetaDrawSpec *
+meta_draw_spec_new (MetaTheme  *theme,
+                    const char *expr,
+                    GError    **error)
+{
+  MetaDrawSpec *spec;
+
+  spec = g_slice_new0 (MetaDrawSpec);
+
+  pos_tokenize (expr, &spec->tokens, &spec->n_tokens, NULL);
+  
+  spec->constant = meta_theme_replace_constants (theme, spec->tokens, 
+                                                 spec->n_tokens, NULL);
+  if (spec->constant) 
+    {
+      gboolean result;
+
+      result = pos_eval (spec, NULL, &spec->value, error);
+      if (result == FALSE)
+        {
+          meta_draw_spec_free (spec);
+          return NULL;
+        }
+    }
+    
+  return spec;
+}
+
+MetaDrawOp*
+meta_draw_op_new (MetaDrawType type)
+{
+  MetaDrawOp *op;
+  MetaDrawOp dummy;
+  int size;
+
+  size = G_STRUCT_OFFSET (MetaDrawOp, data);
+
+  switch (type)
+    {
+    case META_DRAW_LINE:
+      size += sizeof (dummy.data.line);
+      break;
+
+    case META_DRAW_RECTANGLE:
+      size += sizeof (dummy.data.rectangle);
+      break;
+
+    case META_DRAW_ARC:
+      size += sizeof (dummy.data.arc);
+      break;
+
+    case META_DRAW_CLIP:
+      size += sizeof (dummy.data.clip);
+      break;
+      
+    case META_DRAW_TINT:
+      size += sizeof (dummy.data.tint);
+      break;
+
+    case META_DRAW_GRADIENT:
+      size += sizeof (dummy.data.gradient);
+      break;
+
+    case META_DRAW_IMAGE:
+      size += sizeof (dummy.data.image);
+      break;
+
+    case META_DRAW_GTK_ARROW:
+      size += sizeof (dummy.data.gtk_arrow);
+      break;
+
+    case META_DRAW_GTK_BOX:
+      size += sizeof (dummy.data.gtk_box);
+      break;
+
+    case META_DRAW_GTK_VLINE:
+      size += sizeof (dummy.data.gtk_vline);
+      break;
+
+    case META_DRAW_ICON:
+      size += sizeof (dummy.data.icon);
+      break;
+
+    case META_DRAW_TITLE:
+      size += sizeof (dummy.data.title);
+      break;
+    case META_DRAW_OP_LIST:
+      size += sizeof (dummy.data.op_list);
+      break;
+    case META_DRAW_TILE:
+      size += sizeof (dummy.data.tile);
+      break;
+    }
+
+  op = g_malloc0 (size);
+
+  op->type = type;
+
+  return op;
+}
+
+void
+meta_draw_op_free (MetaDrawOp *op)
+{
+  g_return_if_fail (op != NULL);
+
+  switch (op->type)
+    {
+    case META_DRAW_LINE:
+      if (op->data.line.color_spec)
+        meta_color_spec_free (op->data.line.color_spec);
+
+      meta_draw_spec_free (op->data.line.x1);
+      meta_draw_spec_free (op->data.line.y1);
+      meta_draw_spec_free (op->data.line.x2);
+      meta_draw_spec_free (op->data.line.y2);
+      break;
+
+    case META_DRAW_RECTANGLE:
+      if (op->data.rectangle.color_spec)
+        g_free (op->data.rectangle.color_spec);
+
+      meta_draw_spec_free (op->data.rectangle.x);
+      meta_draw_spec_free (op->data.rectangle.y);
+      meta_draw_spec_free (op->data.rectangle.width);
+      meta_draw_spec_free (op->data.rectangle.height);
+      break;
+
+    case META_DRAW_ARC:
+      if (op->data.arc.color_spec)
+        g_free (op->data.arc.color_spec);
+
+      meta_draw_spec_free (op->data.arc.x);
+      meta_draw_spec_free (op->data.arc.y);
+      meta_draw_spec_free (op->data.arc.width);
+      meta_draw_spec_free (op->data.arc.height);
+      break;
+
+    case META_DRAW_CLIP:
+      meta_draw_spec_free (op->data.clip.x);
+      meta_draw_spec_free (op->data.clip.y);
+      meta_draw_spec_free (op->data.clip.width);
+      meta_draw_spec_free (op->data.clip.height);
+      break;
+      
+    case META_DRAW_TINT:
+      if (op->data.tint.color_spec)
+        meta_color_spec_free (op->data.tint.color_spec);
+
+      if (op->data.tint.alpha_spec)
+        meta_alpha_gradient_spec_free (op->data.tint.alpha_spec);
+
+      meta_draw_spec_free (op->data.tint.x);
+      meta_draw_spec_free (op->data.tint.y);
+      meta_draw_spec_free (op->data.tint.width);
+      meta_draw_spec_free (op->data.tint.height);
+      break;
+
+    case META_DRAW_GRADIENT:
+      if (op->data.gradient.gradient_spec)
+        meta_gradient_spec_free (op->data.gradient.gradient_spec);
+
+      if (op->data.gradient.alpha_spec)
+        meta_alpha_gradient_spec_free (op->data.gradient.alpha_spec);
+
+      meta_draw_spec_free (op->data.gradient.x);
+      meta_draw_spec_free (op->data.gradient.y);
+      meta_draw_spec_free (op->data.gradient.width);
+      meta_draw_spec_free (op->data.gradient.height);
+      break;
+
+    case META_DRAW_IMAGE:
+      if (op->data.image.alpha_spec)
+        meta_alpha_gradient_spec_free (op->data.image.alpha_spec);
+
+      if (op->data.image.pixbuf)
+        g_object_unref (G_OBJECT (op->data.image.pixbuf));
+
+      if (op->data.image.colorize_spec)
+	meta_color_spec_free (op->data.image.colorize_spec);
+
+      if (op->data.image.colorize_cache_pixbuf)
+        g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf));
+
+      meta_draw_spec_free (op->data.image.x);
+      meta_draw_spec_free (op->data.image.y);
+      meta_draw_spec_free (op->data.image.width);
+      meta_draw_spec_free (op->data.image.height);
+      break;
+
+    case META_DRAW_GTK_ARROW:
+      meta_draw_spec_free (op->data.gtk_arrow.x);
+      meta_draw_spec_free (op->data.gtk_arrow.y);
+      meta_draw_spec_free (op->data.gtk_arrow.width);
+      meta_draw_spec_free (op->data.gtk_arrow.height);
+      break;
+
+    case META_DRAW_GTK_BOX:
+      meta_draw_spec_free (op->data.gtk_box.x);
+      meta_draw_spec_free (op->data.gtk_box.y);
+      meta_draw_spec_free (op->data.gtk_box.width);
+      meta_draw_spec_free (op->data.gtk_box.height);
+      break;
+
+    case META_DRAW_GTK_VLINE:
+      meta_draw_spec_free (op->data.gtk_vline.x);
+      meta_draw_spec_free (op->data.gtk_vline.y1);
+      meta_draw_spec_free (op->data.gtk_vline.y2);
+      break;
+
+    case META_DRAW_ICON:
+      if (op->data.icon.alpha_spec)
+        meta_alpha_gradient_spec_free (op->data.icon.alpha_spec);
+
+      meta_draw_spec_free (op->data.icon.x);
+      meta_draw_spec_free (op->data.icon.y);
+      meta_draw_spec_free (op->data.icon.width);
+      meta_draw_spec_free (op->data.icon.height);
+      break;
+
+    case META_DRAW_TITLE:
+      if (op->data.title.color_spec)
+        meta_color_spec_free (op->data.title.color_spec);
+
+      meta_draw_spec_free (op->data.title.x);
+      meta_draw_spec_free (op->data.title.y);
+      if (op->data.title.ellipsize_width)
+        meta_draw_spec_free (op->data.title.ellipsize_width);
+      break;
+
+    case META_DRAW_OP_LIST:
+      if (op->data.op_list.op_list)
+        meta_draw_op_list_unref (op->data.op_list.op_list);
+
+      meta_draw_spec_free (op->data.op_list.x);
+      meta_draw_spec_free (op->data.op_list.y);
+      meta_draw_spec_free (op->data.op_list.width);
+      meta_draw_spec_free (op->data.op_list.height);
+      break;
+
+    case META_DRAW_TILE:
+      if (op->data.tile.op_list)
+        meta_draw_op_list_unref (op->data.tile.op_list);
+
+      meta_draw_spec_free (op->data.tile.x);
+      meta_draw_spec_free (op->data.tile.y);
+      meta_draw_spec_free (op->data.tile.width);
+      meta_draw_spec_free (op->data.tile.height);
+      meta_draw_spec_free (op->data.tile.tile_xoffset);
+      meta_draw_spec_free (op->data.tile.tile_yoffset);
+      meta_draw_spec_free (op->data.tile.tile_width);
+      meta_draw_spec_free (op->data.tile.tile_height);
+      break;
+    }
+
+  g_free (op);
+}
+
+static GdkGC*
+get_gc_for_primitive (GtkWidget          *widget,
+                      GdkDrawable        *drawable,
+                      MetaColorSpec      *color_spec,
+                      const GdkRectangle *clip,
+                      int                 line_width)
+{
+  GdkGC *gc;
+  GdkGCValues values;
+  GdkColor color;
+
+  meta_color_spec_render (color_spec, widget, &color);
+
+  values.foreground = color;
+
+  gdk_rgb_find_color (gdk_drawable_get_colormap (drawable),
+                      &values.foreground);
+
+  values.line_width = line_width;
+
+  gc = gdk_gc_new_with_values (drawable, &values,
+                               GDK_GC_FOREGROUND | GDK_GC_LINE_WIDTH);
+
+  if (clip)
+    gdk_gc_set_clip_rectangle (gc,
+                               (GdkRectangle*) clip); /* const cast */
+
+  return gc;
+}
+
+static GdkPixbuf*
+apply_alpha (GdkPixbuf             *pixbuf,
+             MetaAlphaGradientSpec *spec,
+             gboolean               force_copy)
+{
+  GdkPixbuf *new_pixbuf;
+  gboolean needs_alpha;
+  
+  g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
+  
+  needs_alpha = spec && (spec->n_alphas > 1 ||
+                         spec->alphas[0] != 0xff);
+
+  if (!needs_alpha)
+    return pixbuf;
+  
+  if (!gdk_pixbuf_get_has_alpha (pixbuf))
+    {
+      new_pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);
+      g_object_unref (G_OBJECT (pixbuf));
+      pixbuf = new_pixbuf;
+    }
+  else if (force_copy)
+    {
+      new_pixbuf = gdk_pixbuf_copy (pixbuf);
+      g_object_unref (G_OBJECT (pixbuf));
+      pixbuf = new_pixbuf;
+    }
+  
+  g_assert (gdk_pixbuf_get_has_alpha (pixbuf));
+
+  meta_gradient_add_alpha (pixbuf, spec->alphas, spec->n_alphas, spec->type);
+  
+  return pixbuf;
+}
+
+static void
+render_pixbuf (GdkDrawable        *drawable,
+               const GdkRectangle *clip,
+               GdkPixbuf          *pixbuf,
+               int                 x,
+               int                 y)
+{
+  /* grumble, render_to_drawable_alpha does not accept a clip
+   * mask, so we have to go through some BS
+   */
+  /* FIXME once GTK 1.3.13 has been out a while we can use
+   * render_to_drawable() which now does alpha with clip.
+   *
+   * Though the gdk_rectangle_intersect() check may be a useful
+   * optimization anyway.
+   */
+  GdkRectangle pixbuf_rect;
+  GdkRectangle draw_rect;
+
+  pixbuf_rect.x = x;
+  pixbuf_rect.y = y;
+  pixbuf_rect.width = gdk_pixbuf_get_width (pixbuf);
+  pixbuf_rect.height = gdk_pixbuf_get_height (pixbuf);
+
+  if (clip)
+    {
+      if (!gdk_rectangle_intersect ((GdkRectangle*)clip,
+                                    &pixbuf_rect, &draw_rect))
+        return;
+    }
+  else
+    {
+      draw_rect = pixbuf_rect;
+    }
+
+  gdk_draw_pixbuf (drawable,
+                   NULL,
+                   pixbuf,
+                   draw_rect.x - pixbuf_rect.x,
+                   draw_rect.y - pixbuf_rect.y,
+                   draw_rect.x, draw_rect.y,
+                   draw_rect.width,
+                   draw_rect.height,
+                   GDK_RGB_DITHER_NORMAL,
+                   draw_rect.x - pixbuf_rect.x,
+                   draw_rect.y - pixbuf_rect.y);
+}
+
+static GdkPixbuf*
+pixbuf_tile (GdkPixbuf *tile,
+             int        width,
+             int        height)
+{
+  GdkPixbuf *pixbuf;
+  int tile_width;
+  int tile_height;
+  int i, j;
+  
+  tile_width = gdk_pixbuf_get_width (tile);
+  tile_height = gdk_pixbuf_get_height (tile);
+  
+  pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+                           gdk_pixbuf_get_has_alpha (tile),
+                           8, width, height);
+
+  i = 0;
+  while (i < width)
+    {
+      j = 0;
+      while (j < height)
+        {
+          int w, h;
+
+          w = MIN (tile_width, width - i);
+          h = MIN (tile_height, height - j);
+          
+          gdk_pixbuf_copy_area (tile,
+                                0, 0,
+                                w, h,
+                                pixbuf,
+                                i, j);
+
+          j += tile_height;
+        }
+      
+      i += tile_width;
+    }
+  
+  return pixbuf;
+}
+
+static GdkPixbuf *
+replicate_rows (GdkPixbuf  *src,
+                int         src_x,
+                int         src_y,
+                int         width,
+                int         height)
+{
+  unsigned int n_channels = gdk_pixbuf_get_n_channels (src);
+  unsigned int src_rowstride = gdk_pixbuf_get_rowstride (src);
+  unsigned char *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x
+                           * n_channels);
+  unsigned char *dest_pixels;
+  GdkPixbuf *result;
+  unsigned int dest_rowstride;
+  int i;
+
+  result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
+                           width, height);
+  dest_rowstride = gdk_pixbuf_get_rowstride (result);
+  dest_pixels = gdk_pixbuf_get_pixels (result);
+  
+  for (i = 0; i < height; i++)
+    memcpy (dest_pixels + dest_rowstride * i, pixels, n_channels * width);
+
+  return result;
+}
+
+static GdkPixbuf *
+replicate_cols (GdkPixbuf  *src,
+                int         src_x,
+                int         src_y,
+                int         width,
+                int         height)
+{
+  unsigned int n_channels = gdk_pixbuf_get_n_channels (src);
+  unsigned int src_rowstride = gdk_pixbuf_get_rowstride (src);
+  unsigned char *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x
+                           * n_channels);
+  unsigned char *dest_pixels;
+  GdkPixbuf *result;
+  unsigned int dest_rowstride;
+  int i, j;
+
+  result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
+                           width, height);
+  dest_rowstride = gdk_pixbuf_get_rowstride (result);
+  dest_pixels = gdk_pixbuf_get_pixels (result);
+
+  for (i = 0; i < height; i++)
+    {
+      unsigned char *p = dest_pixels + dest_rowstride * i;
+      unsigned char *q = pixels + src_rowstride * i;
+
+      unsigned char r = *(q++);
+      unsigned char g = *(q++);
+      unsigned char b = *(q++);
+      
+      if (n_channels == 4)
+        {
+          unsigned char a;
+          
+          a = *(q++);
+          
+          for (j = 0; j < width; j++)
+            {
+              *(p++) = r;
+              *(p++) = g;
+              *(p++) = b;                    
+              *(p++) = a;
+            }
+        }
+      else
+        {
+          for (j = 0; j < width; j++)
+            {
+              *(p++) = r;
+              *(p++) = g;
+              *(p++) = b;
+            }
+        }
+    }
+
+  return result;
+}
+
+static GdkPixbuf*
+scale_and_alpha_pixbuf (GdkPixbuf             *src,
+                        MetaAlphaGradientSpec *alpha_spec,
+                        MetaImageFillType      fill_type,
+                        int                    width,
+                        int                    height,
+                        gboolean               vertical_stripes,
+                        gboolean               horizontal_stripes)
+{
+  GdkPixbuf *pixbuf;
+  GdkPixbuf *temp_pixbuf;
+
+  pixbuf = NULL;
+
+  pixbuf = src;
+
+  if (gdk_pixbuf_get_width (pixbuf) == width &&
+      gdk_pixbuf_get_height (pixbuf) == height)
+    {
+      g_object_ref (G_OBJECT (pixbuf));
+    }
+  else
+    {
+      if (fill_type == META_IMAGE_FILL_TILE)
+        {
+          pixbuf = pixbuf_tile (pixbuf, width, height);
+        }
+      else
+        {
+    	  int src_h, src_w, dest_h, dest_w;
+          src_h = gdk_pixbuf_get_height (src);
+          src_w = gdk_pixbuf_get_width (src);
+
+          /* prefer to replicate_cols if possible, as that
+           * is faster (no memory reads)
+           */
+          if (horizontal_stripes)
+            {
+              dest_w = gdk_pixbuf_get_width (src);
+              dest_h = height;
+            }
+          else if (vertical_stripes)
+            {
+              dest_w = width;
+              dest_h = gdk_pixbuf_get_height (src);
+            }
+
+          else
+            {
+              dest_w = width;
+              dest_h = height;
+            }
+
+          if (dest_w == src_w && dest_h == src_h)
+            {
+              temp_pixbuf = src;
+              g_object_ref (G_OBJECT (temp_pixbuf));
+            }
+          else
+            {
+              temp_pixbuf = gdk_pixbuf_scale_simple (src,
+                                                     dest_w, dest_h,
+                                                     GDK_INTERP_BILINEAR);
+            }
+
+          /* prefer to replicate_cols if possible, as that
+           * is faster (no memory reads)
+           */
+          if (horizontal_stripes)
+            {
+              pixbuf = replicate_cols (temp_pixbuf, 0, 0, width, height);
+              g_object_unref (G_OBJECT (temp_pixbuf));
+            }
+          else if (vertical_stripes)
+            {
+              pixbuf = replicate_rows (temp_pixbuf, 0, 0, width, height);
+              g_object_unref (G_OBJECT (temp_pixbuf));
+            }
+          else 
+            {
+              pixbuf = temp_pixbuf;
+            }
+        }
+    }
+
+  if (pixbuf)
+    pixbuf = apply_alpha (pixbuf, alpha_spec, pixbuf == src);
+  
+  return pixbuf;
+}
+
+static GdkPixbuf*
+draw_op_as_pixbuf (const MetaDrawOp    *op,
+                   GtkWidget           *widget,
+                   const MetaDrawInfo  *info,
+                   int                  width,
+                   int                  height)
+{
+  /* Try to get the op as a pixbuf, assuming w/h in the op
+   * matches the width/height passed in. return NULL
+   * if the op can't be converted to an equivalent pixbuf.
+   */
+  GdkPixbuf *pixbuf;
+
+  pixbuf = NULL;
+
+  switch (op->type)
+    {
+    case META_DRAW_LINE:
+      break;
+
+    case META_DRAW_RECTANGLE:
+      if (op->data.rectangle.filled)
+        {
+          GdkColor color;
+
+          meta_color_spec_render (op->data.rectangle.color_spec,
+                                  widget,
+                                  &color);
+
+          pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+                                   FALSE,
+                                   8, width, height);
+
+          gdk_pixbuf_fill (pixbuf, GDK_COLOR_RGBA (color));
+        }
+      break;
+
+    case META_DRAW_ARC:
+      break;
+
+    case META_DRAW_CLIP:
+      break;
+      
+    case META_DRAW_TINT:
+      {
+        GdkColor color;
+        guint32 rgba;
+        gboolean has_alpha;
+
+        meta_color_spec_render (op->data.rectangle.color_spec,
+                                widget,
+                                &color);
+
+        has_alpha =
+          op->data.tint.alpha_spec &&
+          (op->data.tint.alpha_spec->n_alphas > 1 ||
+           op->data.tint.alpha_spec->alphas[0] != 0xff);
+        
+        pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+                                 has_alpha,
+                                 8, width, height);
+
+        if (!has_alpha)
+          {
+            rgba = GDK_COLOR_RGBA (color);
+            
+            gdk_pixbuf_fill (pixbuf, rgba);
+          }
+        else if (op->data.tint.alpha_spec->n_alphas == 1)
+          {
+            rgba = GDK_COLOR_RGBA (color);
+            rgba &= ~0xff;
+            rgba |= op->data.tint.alpha_spec->alphas[0];
+            
+            gdk_pixbuf_fill (pixbuf, rgba);
+          }
+        else
+          {
+            rgba = GDK_COLOR_RGBA (color);
+            
+            gdk_pixbuf_fill (pixbuf, rgba);
+
+            meta_gradient_add_alpha (pixbuf,
+                                     op->data.tint.alpha_spec->alphas,
+                                     op->data.tint.alpha_spec->n_alphas,
+                                     op->data.tint.alpha_spec->type);
+          }
+      }
+      break;
+
+    case META_DRAW_GRADIENT:
+      {
+        pixbuf = meta_gradient_spec_render (op->data.gradient.gradient_spec,
+                                            widget, width, height);
+
+        pixbuf = apply_alpha (pixbuf,
+                              op->data.gradient.alpha_spec,
+                              FALSE);
+      }
+      break;
+
+      
+    case META_DRAW_IMAGE:
+      {
+	if (op->data.image.colorize_spec)
+	  {
+	    GdkColor color;
+
+            meta_color_spec_render (op->data.image.colorize_spec,
+                                    widget, &color);
+            
+            if (op->data.image.colorize_cache_pixbuf == NULL ||
+                op->data.image.colorize_cache_pixel != GDK_COLOR_RGB (color))
+              {
+                if (op->data.image.colorize_cache_pixbuf)
+                  g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf));
+                
+                /* const cast here */
+                ((MetaDrawOp*)op)->data.image.colorize_cache_pixbuf =
+                  colorize_pixbuf (op->data.image.pixbuf,
+                                   &color);
+                ((MetaDrawOp*)op)->data.image.colorize_cache_pixel =
+                  GDK_COLOR_RGB (color);
+              }
+            
+            if (op->data.image.colorize_cache_pixbuf)
+              {
+                pixbuf = scale_and_alpha_pixbuf (op->data.image.colorize_cache_pixbuf,
+                                                 op->data.image.alpha_spec,
+                                                 op->data.image.fill_type,
+                                                 width, height,
+                                                 op->data.image.vertical_stripes,
+                                                 op->data.image.horizontal_stripes);
+              }
+	  }
+	else
+	  {
+	    pixbuf = scale_and_alpha_pixbuf (op->data.image.pixbuf,
+                                             op->data.image.alpha_spec,
+                                             op->data.image.fill_type,
+                                             width, height,
+                                             op->data.image.vertical_stripes,
+                                             op->data.image.horizontal_stripes);
+	  }
+        break;
+      }
+      
+    case META_DRAW_GTK_ARROW:
+    case META_DRAW_GTK_BOX:
+    case META_DRAW_GTK_VLINE:
+      break;
+
+    case META_DRAW_ICON:
+      if (info->mini_icon &&
+          width <= gdk_pixbuf_get_width (info->mini_icon) &&
+          height <= gdk_pixbuf_get_height (info->mini_icon))
+        pixbuf = scale_and_alpha_pixbuf (info->mini_icon,
+                                         op->data.icon.alpha_spec,
+                                         op->data.icon.fill_type,
+                                         width, height,
+                                         FALSE, FALSE);
+      else if (info->icon)
+        pixbuf = scale_and_alpha_pixbuf (info->icon,
+                                         op->data.icon.alpha_spec,
+                                         op->data.icon.fill_type,
+                                         width, height,
+                                         FALSE, FALSE);
+      break;
+
+    case META_DRAW_TITLE:
+      break;
+
+    case META_DRAW_OP_LIST:
+      break;
+
+    case META_DRAW_TILE:
+      break;
+    }
+
+  return pixbuf;
+}
+
+static void
+fill_env (MetaPositionExprEnv *env,
+          const MetaDrawInfo  *info,
+          MetaRectangle        logical_region)
+{
+  /* FIXME this stuff could be raised into draw_op_list_draw() probably
+   */
+  env->rect = logical_region;
+  env->object_width = -1;
+  env->object_height = -1;
+  if (info->fgeom)
+    {
+      env->left_width = info->fgeom->left_width;
+      env->right_width = info->fgeom->right_width;
+      env->top_height = info->fgeom->top_height;
+      env->bottom_height = info->fgeom->bottom_height;
+      env->frame_x_center = info->fgeom->width / 2 - logical_region.x;
+      env->frame_y_center = info->fgeom->height / 2 - logical_region.y;
+    }
+  else
+    {
+      env->left_width = 0;
+      env->right_width = 0;
+      env->top_height = 0;
+      env->bottom_height = 0;
+      env->frame_x_center = 0;
+      env->frame_y_center = 0;
+    }
+  
+  env->mini_icon_width = info->mini_icon ? gdk_pixbuf_get_width (info->mini_icon) : 0;
+  env->mini_icon_height = info->mini_icon ? gdk_pixbuf_get_height (info->mini_icon) : 0;
+  env->icon_width = info->icon ? gdk_pixbuf_get_width (info->icon) : 0;
+  env->icon_height = info->icon ? gdk_pixbuf_get_height (info->icon) : 0;
+
+  env->title_width = info->title_layout_width;
+  env->title_height = info->title_layout_height;
+  env->theme = meta_current_theme;
+}
+
+static void
+meta_draw_op_draw_with_env (const MetaDrawOp    *op,
+                            GtkStyle            *style_gtk,
+                            GtkWidget           *widget,
+                            GdkDrawable         *drawable,
+                            const GdkRectangle  *clip,
+                            const MetaDrawInfo  *info,
+                            MetaRectangle        rect,
+                            MetaPositionExprEnv *env)
+{
+  GdkGC *gc;
+  
+  switch (op->type)
+    {
+    case META_DRAW_LINE:
+      {
+        int x1, x2, y1, y2;
+
+        gc = get_gc_for_primitive (widget, drawable,
+                                   op->data.line.color_spec,
+                                   clip,
+                                   op->data.line.width);
+
+        if (op->data.line.dash_on_length > 0 &&
+            op->data.line.dash_off_length > 0)
+          {
+            gint8 dash_list[2];
+            dash_list[0] = op->data.line.dash_on_length;
+            dash_list[1] = op->data.line.dash_off_length;
+            gdk_gc_set_dashes (gc, 0, dash_list, 2);
+          }
+
+        x1 = parse_x_position_unchecked (op->data.line.x1, env);
+        y1 = parse_y_position_unchecked (op->data.line.y1, env); 
+
+        if (!op->data.line.x2 &&
+            !op->data.line.y2 &&
+            op->data.line.width==0)
+          gdk_draw_point (drawable, gc, x1, y1);
+        else
+          {
+            if (op->data.line.x2)
+              x2 = parse_x_position_unchecked (op->data.line.x2, env);
+            else
+              x2 = x1;
+
+            if (op->data.line.y2)
+              y2 = parse_y_position_unchecked (op->data.line.y2, env);
+            else
+              y2 = y1;
+
+            gdk_draw_line (drawable, gc, x1, y1, x2, y2);
+          }
+
+        g_object_unref (G_OBJECT (gc));
+      }
+      break;
+
+    case META_DRAW_RECTANGLE:
+      {
+        int rx, ry, rwidth, rheight;
+
+        gc = get_gc_for_primitive (widget, drawable,
+                                   op->data.rectangle.color_spec,
+                                   clip, 0);
+
+        rx = parse_x_position_unchecked (op->data.rectangle.x, env);
+        ry = parse_y_position_unchecked (op->data.rectangle.y, env);
+        rwidth = parse_size_unchecked (op->data.rectangle.width, env);
+        rheight = parse_size_unchecked (op->data.rectangle.height, env);
+
+        gdk_draw_rectangle (drawable, gc,
+                            op->data.rectangle.filled,
+                            rx, ry, rwidth, rheight);
+
+        g_object_unref (G_OBJECT (gc));
+      }
+      break;
+
+    case META_DRAW_ARC:
+      {
+        int rx, ry, rwidth, rheight;
+
+        gc = get_gc_for_primitive (widget, drawable,
+                                   op->data.arc.color_spec,
+                                   clip, 0);
+
+        rx = parse_x_position_unchecked (op->data.arc.x, env);
+        ry = parse_y_position_unchecked (op->data.arc.y, env);
+        rwidth = parse_size_unchecked (op->data.arc.width, env);
+        rheight = parse_size_unchecked (op->data.arc.height, env);
+
+        gdk_draw_arc (drawable,
+                      gc,
+                      op->data.arc.filled,
+                      rx, ry, rwidth, rheight,
+                      op->data.arc.start_angle * (360.0 * 64.0) -
+                      (90.0 * 64.0), /* start at 12 instead of 3 oclock */
+                      op->data.arc.extent_angle * (360.0 * 64.0));
+
+        g_object_unref (G_OBJECT (gc));
+      }
+      break;
+
+    case META_DRAW_CLIP:
+      break;
+      
+    case META_DRAW_TINT:
+      {
+        int rx, ry, rwidth, rheight;
+        gboolean needs_alpha;
+        
+        needs_alpha = op->data.tint.alpha_spec &&
+          (op->data.tint.alpha_spec->n_alphas > 1 ||
+           op->data.tint.alpha_spec->alphas[0] != 0xff);
+        
+        rx = parse_x_position_unchecked (op->data.tint.x, env);
+        ry = parse_y_position_unchecked (op->data.tint.y, env);
+        rwidth = parse_size_unchecked (op->data.tint.width, env);
+        rheight = parse_size_unchecked (op->data.tint.height, env);
+
+        if (!needs_alpha)
+          {
+            gc = get_gc_for_primitive (widget, drawable,
+                                       op->data.tint.color_spec,
+                                       clip, 0);
+
+            gdk_draw_rectangle (drawable, gc,
+                                TRUE,
+                                rx, ry, rwidth, rheight);
+
+            g_object_unref (G_OBJECT (gc));
+          }
+        else
+          {
+            GdkPixbuf *pixbuf;
+
+            pixbuf = draw_op_as_pixbuf (op, widget, info,
+                                        rwidth, rheight);
+
+            if (pixbuf)
+              {
+                render_pixbuf (drawable, clip, pixbuf, rx, ry);
+
+                g_object_unref (G_OBJECT (pixbuf));
+              }
+          }
+      }
+      break;
+
+    case META_DRAW_GRADIENT:
+      {
+        int rx, ry, rwidth, rheight;
+        GdkPixbuf *pixbuf;
+
+        rx = parse_x_position_unchecked (op->data.gradient.x, env);
+        ry = parse_y_position_unchecked (op->data.gradient.y, env);
+        rwidth = parse_size_unchecked (op->data.gradient.width, env);
+        rheight = parse_size_unchecked (op->data.gradient.height, env);
+
+        pixbuf = draw_op_as_pixbuf (op, widget, info,
+                                    rwidth, rheight);
+
+        if (pixbuf)
+          {
+            render_pixbuf (drawable, clip, pixbuf, rx, ry);
+
+            g_object_unref (G_OBJECT (pixbuf));
+          }
+      }
+      break;
+
+    case META_DRAW_IMAGE:
+      {
+        int rx, ry, rwidth, rheight;
+        GdkPixbuf *pixbuf;
+
+        if (op->data.image.pixbuf)
+          {
+            env->object_width = gdk_pixbuf_get_width (op->data.image.pixbuf);
+            env->object_height = gdk_pixbuf_get_height (op->data.image.pixbuf);
+          }
+
+        rwidth = parse_size_unchecked (op->data.image.width, env);
+        rheight = parse_size_unchecked (op->data.image.height, env);
+        
+        pixbuf = draw_op_as_pixbuf (op, widget, info,
+                                    rwidth, rheight);
+
+        if (pixbuf)
+          {
+            rx = parse_x_position_unchecked (op->data.image.x, env);
+            ry = parse_y_position_unchecked (op->data.image.y, env);
+
+            render_pixbuf (drawable, clip, pixbuf, rx, ry);
+
+            g_object_unref (G_OBJECT (pixbuf));
+          }
+      }
+      break;
+
+    case META_DRAW_GTK_ARROW:
+      {
+        int rx, ry, rwidth, rheight;
+
+        rx = parse_x_position_unchecked (op->data.gtk_arrow.x, env);
+        ry = parse_y_position_unchecked (op->data.gtk_arrow.y, env);
+        rwidth = parse_size_unchecked (op->data.gtk_arrow.width, env);
+        rheight = parse_size_unchecked (op->data.gtk_arrow.height, env);
+
+        gtk_paint_arrow (style_gtk,
+                         drawable,
+                         op->data.gtk_arrow.state,
+                         op->data.gtk_arrow.shadow,
+                         (GdkRectangle*) clip,
+                         widget,
+                         "metacity",
+                         op->data.gtk_arrow.arrow,
+                         op->data.gtk_arrow.filled,
+                         rx, ry, rwidth, rheight);
+      }
+      break;
+
+    case META_DRAW_GTK_BOX:
+      {
+        int rx, ry, rwidth, rheight;
+
+        rx = parse_x_position_unchecked (op->data.gtk_box.x, env);
+        ry = parse_y_position_unchecked (op->data.gtk_box.y, env);
+        rwidth = parse_size_unchecked (op->data.gtk_box.width, env);
+        rheight = parse_size_unchecked (op->data.gtk_box.height, env);
+
+        gtk_paint_box (style_gtk,
+                       drawable,
+                       op->data.gtk_box.state,
+                       op->data.gtk_box.shadow,
+                       (GdkRectangle*) clip,
+                       widget,
+                       "metacity",
+                       rx, ry, rwidth, rheight);
+      }
+      break;
+
+    case META_DRAW_GTK_VLINE:
+      {
+        int rx, ry1, ry2;
+
+        rx = parse_x_position_unchecked (op->data.gtk_vline.x, env);
+        ry1 = parse_y_position_unchecked (op->data.gtk_vline.y1, env);
+        ry2 = parse_y_position_unchecked (op->data.gtk_vline.y2, env);
+        
+        gtk_paint_vline (style_gtk,
+                         drawable,
+                         op->data.gtk_vline.state,
+                         (GdkRectangle*) clip,
+                         widget,
+                         "metacity",
+                         ry1, ry2, rx);
+      }
+      break;
+
+    case META_DRAW_ICON:
+      {
+        int rx, ry, rwidth, rheight;
+        GdkPixbuf *pixbuf;
+
+        rwidth = parse_size_unchecked (op->data.icon.width, env);
+        rheight = parse_size_unchecked (op->data.icon.height, env);
+        
+        pixbuf = draw_op_as_pixbuf (op, widget, info,
+                                    rwidth, rheight);
+
+        if (pixbuf)
+          {
+            rx = parse_x_position_unchecked (op->data.icon.x, env);
+            ry = parse_y_position_unchecked (op->data.icon.y, env);
+
+            render_pixbuf (drawable, clip, pixbuf, rx, ry);
+
+            g_object_unref (G_OBJECT (pixbuf));
+          }
+      }
+      break;
+
+    case META_DRAW_TITLE:
+      if (info->title_layout)
+        {
+          int rx, ry;
+          PangoRectangle ink_rect, logical_rect;
+
+          gc = get_gc_for_primitive (widget, drawable,
+                                     op->data.title.color_spec,
+                                     clip, 0);
+
+          rx = parse_x_position_unchecked (op->data.title.x, env);
+          ry = parse_y_position_unchecked (op->data.title.y, env);
+
+          if (op->data.title.ellipsize_width)
+            {
+              int ellipsize_width;
+              int right_bearing;
+
+              ellipsize_width = parse_x_position_unchecked (op->data.title.ellipsize_width, env);
+              /* HACK: parse_x_position_unchecked adds in env->rect.x, subtract out again */
+              ellipsize_width -= env->rect.x;
+
+              pango_layout_set_width (info->title_layout, -1);
+              pango_layout_get_pixel_extents (info->title_layout,
+                                              &ink_rect, &logical_rect);
+
+              /* Pango's idea of ellipsization is with respect to the logical rect.
+               * correct for this, by reducing the ellipsization width by the overflow
+               * of the un-ellipsized text on the right... it's always the visual
+               * right we want regardless of bidi, since since the X we pass in to
+               * gdk_draw_layout() is always the left edge of the line.
+               */
+              right_bearing = (ink_rect.x + ink_rect.width) - (logical_rect.x + logical_rect.width);
+              right_bearing = MAX (right_bearing, 0);
+
+              ellipsize_width -= right_bearing;
+              ellipsize_width = MAX (ellipsize_width, 0);
+
+              /* Only ellipsizing when necessary is a performance optimization -
+               * pango_layout_set_width() will force a relayout if it isn't the
+               * same as the current width of -1.
+               */
+              if (ellipsize_width < logical_rect.width)
+                pango_layout_set_width (info->title_layout, PANGO_SCALE * ellipsize_width);
+            }
+
+          gdk_draw_layout (drawable, gc,
+                           rx, ry,
+                           info->title_layout);
+
+          /* Remove any ellipsization we might have set; will short-circuit
+           * if the width is already -1 */
+          pango_layout_set_width (info->title_layout, -1);
+
+          g_object_unref (G_OBJECT (gc));
+        }
+      break;
+
+    case META_DRAW_OP_LIST:
+      {
+        MetaRectangle d_rect;
+
+        d_rect.x = parse_x_position_unchecked (op->data.op_list.x, env);
+        d_rect.y = parse_y_position_unchecked (op->data.op_list.y, env);
+        d_rect.width = parse_size_unchecked (op->data.op_list.width, env);
+        d_rect.height = parse_size_unchecked (op->data.op_list.height, env);
+
+        meta_draw_op_list_draw_with_style (op->data.op_list.op_list,
+                                           style_gtk, widget, drawable, clip, info,
+                                d_rect);
+      }
+      break;
+
+    case META_DRAW_TILE:
+      {
+        int rx, ry, rwidth, rheight;
+        int tile_xoffset, tile_yoffset; 
+        GdkRectangle new_clip;
+        MetaRectangle tile;
+        
+        rx = parse_x_position_unchecked (op->data.tile.x, env);
+        ry = parse_y_position_unchecked (op->data.tile.y, env);
+        rwidth = parse_size_unchecked (op->data.tile.width, env);
+        rheight = parse_size_unchecked (op->data.tile.height, env);
+
+        new_clip.x = rx;
+        new_clip.y = ry;
+        new_clip.width = rwidth;
+        new_clip.height = rheight;
+
+        if (clip == NULL || gdk_rectangle_intersect ((GdkRectangle*)clip, &new_clip,
+                                                     &new_clip))
+          {
+            tile_xoffset = parse_x_position_unchecked (op->data.tile.tile_xoffset, env);
+            tile_yoffset = parse_y_position_unchecked (op->data.tile.tile_yoffset, env);
+            /* tile offset should not include x/y */
+            tile_xoffset -= rect.x;
+            tile_yoffset -= rect.y;
+            
+            tile.width = parse_size_unchecked (op->data.tile.tile_width, env);
+            tile.height = parse_size_unchecked (op->data.tile.tile_height, env);
+
+            tile.x = rx - tile_xoffset;
+        
+            while (tile.x < (rx + rwidth))
+              {
+                tile.y = ry - tile_yoffset;
+                while (tile.y < (ry + rheight))
+                  {
+                    meta_draw_op_list_draw_with_style (op->data.tile.op_list,
+                                                       style_gtk, widget, drawable, &new_clip, info,
+                                            tile);
+
+                    tile.y += tile.height;
+                  }
+
+                tile.x += tile.width;
+              }
+          }
+      }
+      break;
+    }
+}
+
+void
+meta_draw_op_draw_with_style (const MetaDrawOp    *op,
+                              GtkStyle            *style_gtk,
+                   GtkWidget           *widget,
+                   GdkDrawable         *drawable,
+                   const GdkRectangle  *clip,
+                   const MetaDrawInfo  *info,
+                   MetaRectangle        logical_region)
+{
+  MetaPositionExprEnv env;
+
+  g_return_if_fail (style_gtk->colormap == gdk_drawable_get_colormap (drawable));
+
+  fill_env (&env, info, logical_region);
+
+  meta_draw_op_draw_with_env (op, style_gtk, widget, drawable, clip,
+                              info, logical_region,
+                              &env);
+
+}
+
+void
+meta_draw_op_draw (const MetaDrawOp    *op,
+                   GtkWidget           *widget,
+                   GdkDrawable         *drawable,
+                   const GdkRectangle  *clip,
+                   const MetaDrawInfo  *info,
+                   MetaRectangle        logical_region)
+{
+  meta_draw_op_draw_with_style (op, gtk_widget_get_style (widget), widget,
+                                drawable, clip, info, logical_region);
+}
+
+MetaDrawOpList*
+meta_draw_op_list_new (int n_preallocs)
+{
+  MetaDrawOpList *op_list;
+
+  g_return_val_if_fail (n_preallocs >= 0, NULL);
+
+  op_list = g_new (MetaDrawOpList, 1);
+
+  op_list->refcount = 1;
+  op_list->n_allocated = n_preallocs;
+  op_list->ops = g_new (MetaDrawOp*, op_list->n_allocated);
+  op_list->n_ops = 0;
+
+  return op_list;
+}
+
+void
+meta_draw_op_list_ref (MetaDrawOpList *op_list)
+{
+  g_return_if_fail (op_list != NULL);
+
+  op_list->refcount += 1;
+}
+
+void
+meta_draw_op_list_unref (MetaDrawOpList *op_list)
+{
+  g_return_if_fail (op_list != NULL);
+  g_return_if_fail (op_list->refcount > 0);
+
+  op_list->refcount -= 1;
+
+  if (op_list->refcount == 0)
+    {
+      int i;
+
+      for (i = 0; i < op_list->n_ops; i++)
+        meta_draw_op_free (op_list->ops[i]);
+
+      g_free (op_list->ops);
+
+      DEBUG_FILL_STRUCT (op_list);
+      g_free (op_list);
+    }
+}
+
+void
+meta_draw_op_list_draw_with_style  (const MetaDrawOpList *op_list,
+                                    GtkStyle             *style_gtk,
+                         GtkWidget            *widget,
+                         GdkDrawable          *drawable,
+                         const GdkRectangle   *clip,
+                         const MetaDrawInfo   *info,
+                         MetaRectangle         rect)
+{
+  int i;
+  GdkRectangle active_clip;
+  GdkRectangle orig_clip;
+  MetaPositionExprEnv env;
+
+  g_return_if_fail (style_gtk->colormap == gdk_drawable_get_colormap (drawable));
+
+  if (op_list->n_ops == 0)
+    return;
+  
+  fill_env (&env, info, rect);
+  
+  /* FIXME this can be optimized, potentially a lot, by
+   * compressing multiple ops when possible. For example,
+   * anything convertible to a pixbuf can be composited
+   * client-side, and putting a color tint over a pixbuf
+   * can be done without creating the solid-color pixbuf.
+   *
+   * To implement this my plan is to have the idea of a
+   * compiled draw op (with the string expressions already
+   * evaluated), we make an array of those, and then fold
+   * adjacent items when possible.
+   */
+  if (clip)
+    {
+      orig_clip = *clip;
+    }
+  else
+    {
+      orig_clip.x = rect.x;
+      orig_clip.y = rect.y;
+      orig_clip.width = rect.width;
+      orig_clip.height = rect.height;
+    }
+
+  active_clip = orig_clip;
+
+  for (i = 0; i < op_list->n_ops; i++)
+    {
+      MetaDrawOp *op = op_list->ops[i];
+      
+      if (op->type == META_DRAW_CLIP)
+        {
+          active_clip.x = parse_x_position_unchecked (op->data.clip.x, &env);
+          active_clip.y = parse_y_position_unchecked (op->data.clip.y, &env);
+          active_clip.width = parse_size_unchecked (op->data.clip.width, &env);
+          active_clip.height = parse_size_unchecked (op->data.clip.height, &env);
+          
+          gdk_rectangle_intersect (&orig_clip, &active_clip, &active_clip);
+        }
+      else if (active_clip.width > 0 &&
+               active_clip.height > 0)
+        {
+          meta_draw_op_draw_with_env (op,
+                                      style_gtk, widget, drawable, &active_clip, info,
+                                      rect,
+                                      &env);
+        }
+    }
+}
+
+void
+meta_draw_op_list_draw  (const MetaDrawOpList *op_list,
+                         GtkWidget            *widget,
+                         GdkDrawable          *drawable,
+                         const GdkRectangle   *clip,
+                         const MetaDrawInfo   *info,
+                         MetaRectangle         rect)
+
+{
+  meta_draw_op_list_draw_with_style (op_list, gtk_widget_get_style (widget), widget,
+                                     drawable, clip, info, rect);
+}
+
+void
+meta_draw_op_list_append (MetaDrawOpList       *op_list,
+                          MetaDrawOp           *op)
+{
+  if (op_list->n_ops == op_list->n_allocated)
+    {
+      op_list->n_allocated *= 2;
+      op_list->ops = g_renew (MetaDrawOp*, op_list->ops, op_list->n_allocated);
+    }
+
+  op_list->ops[op_list->n_ops] = op;
+  op_list->n_ops += 1;
+}
+
+gboolean
+meta_draw_op_list_validate (MetaDrawOpList    *op_list,
+                            GError           **error)
+{
+  g_return_val_if_fail (op_list != NULL, FALSE);
+
+  /* empty lists are OK, nothing else to check really */
+
+  return TRUE;
+}
+
+/* This is not done in validate, since we wouldn't know the name
+ * of the list to report the error. It might be nice to
+ * store names inside the list sometime.
+ */
+gboolean
+meta_draw_op_list_contains (MetaDrawOpList    *op_list,
+                            MetaDrawOpList    *child)
+{
+  int i;
+
+  /* mmm, huge tree recursion */
+
+  for (i = 0; i < op_list->n_ops; i++)
+    {
+      if (op_list->ops[i]->type == META_DRAW_OP_LIST)
+        {
+          if (op_list->ops[i]->data.op_list.op_list == child)
+            return TRUE;
+          
+          if (meta_draw_op_list_contains (op_list->ops[i]->data.op_list.op_list,
+                                          child))
+            return TRUE;
+        }
+      else if (op_list->ops[i]->type == META_DRAW_TILE)
+        {
+          if (op_list->ops[i]->data.tile.op_list == child)
+            return TRUE;
+          
+          if (meta_draw_op_list_contains (op_list->ops[i]->data.tile.op_list,
+                                          child))
+            return TRUE;
+        }
+    }
+
+  return FALSE;
+}
+
+/**
+ * Constructor for a MetaFrameStyle.
+ *
+ * \param parent  The parent style. Data not filled in here will be
+ *                looked for in the parent style, and in its parent
+ *                style, and so on.
+ *
+ * \return The newly-constructed style.
+ */
+MetaFrameStyle*
+meta_frame_style_new (MetaFrameStyle *parent)
+{
+  MetaFrameStyle *style;
+
+  style = g_new0 (MetaFrameStyle, 1);
+
+  style->refcount = 1;
+
+  /* Default alpha is fully opaque */
+  style->window_background_alpha = 255;
+
+  style->parent = parent;
+  if (parent)
+    meta_frame_style_ref (parent);
+
+  return style;
+}
+
+/**
+ * Increases the reference count of a frame style.
+ * If the style is NULL, this is a no-op.
+ *
+ * \param style  The style.
+ */
+void
+meta_frame_style_ref (MetaFrameStyle *style)
+{
+  g_return_if_fail (style != NULL);
+
+  style->refcount += 1;
+}
+
+static void
+free_button_ops (MetaDrawOpList *op_lists[META_BUTTON_TYPE_LAST][META_BUTTON_STATE_LAST])
+{
+  int i, j;
+
+  for (i = 0; i < META_BUTTON_TYPE_LAST; i++)
+    for (j = 0; j < META_BUTTON_STATE_LAST; j++)
+      if (op_lists[i][j])
+        meta_draw_op_list_unref (op_lists[i][j]);
+}
+
+void
+meta_frame_style_unref (MetaFrameStyle *style)
+{
+  g_return_if_fail (style != NULL);
+  g_return_if_fail (style->refcount > 0);
+
+  style->refcount -= 1;
+
+  if (style->refcount == 0)
+    {
+      int i;
+
+      free_button_ops (style->buttons);
+
+      for (i = 0; i < META_FRAME_PIECE_LAST; i++)
+        if (style->pieces[i])
+          meta_draw_op_list_unref (style->pieces[i]);
+
+      if (style->layout)
+        meta_frame_layout_unref (style->layout);
+
+      if (style->window_background_color)
+        meta_color_spec_free (style->window_background_color);
+
+      /* we hold a reference to any parent style */
+      if (style->parent)
+        meta_frame_style_unref (style->parent);
+
+      DEBUG_FILL_STRUCT (style);
+      g_free (style);
+    }
+}
+
+static MetaDrawOpList*
+get_button (MetaFrameStyle *style,
+            MetaButtonType  type,
+            MetaButtonState state)
+{
+  MetaDrawOpList *op_list;
+  MetaFrameStyle *parent;
+  
+  parent = style;
+  op_list = NULL;
+  while (parent && op_list == NULL)
+    {
+      op_list = parent->buttons[type][state];
+      parent = parent->parent;
+    }
+
+  /* We fall back to middle button backgrounds if we don't
+   * have the ones on the sides
+   */
+
+  if (op_list == NULL &&
+      (type == META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND ||
+       type == META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND))
+    return get_button (style, META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND,
+                       state);
+
+  if (op_list == NULL &&
+      (type == META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND ||
+       type == META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND))
+    return get_button (style, META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND,
+                       state);
+  
+  /* We fall back to normal if no prelight */
+  if (op_list == NULL &&
+      state == META_BUTTON_STATE_PRELIGHT)
+    return get_button (style, type, META_BUTTON_STATE_NORMAL);
+
+  return op_list;
+}
+
+gboolean
+meta_frame_style_validate (MetaFrameStyle    *style,
+                           guint              current_theme_version,
+                           GError           **error)
+{
+  int i, j;
+  
+  g_return_val_if_fail (style != NULL, FALSE);
+  g_return_val_if_fail (style->layout != NULL, FALSE);
+
+  for (i = 0; i < META_BUTTON_TYPE_LAST; i++)
+    {
+      /* for now the "positional" buttons are optional */
+      if (i >= META_BUTTON_TYPE_CLOSE)
+        {
+          for (j = 0; j < META_BUTTON_STATE_LAST; j++)
+            {
+              if (get_button (style, i, j) == NULL &&
+                  meta_theme_earliest_version_with_button (i) <= current_theme_version
+                  )
+                {
+                  g_set_error (error, META_THEME_ERROR,
+                               META_THEME_ERROR_FAILED,
+                               _("<button function=\"%s\" state=\"%s\" draw_ops=\"whatever\"/> must be specified for this frame style"),
+                               meta_button_type_to_string (i),
+                               meta_button_state_to_string (j));
+                  return FALSE;
+                }
+            }
+        }
+    }
+  
+  return TRUE;
+}
+
+static void
+button_rect (MetaButtonType           type,
+             const MetaFrameGeometry *fgeom,
+             int                      middle_background_offset,
+             GdkRectangle            *rect)
+{
+  switch (type)
+    {
+    case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND:
+      *rect = fgeom->left_left_background;
+      break;
+
+    case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND:
+      *rect = fgeom->left_middle_backgrounds[middle_background_offset];
+      break;
+      
+    case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND:
+      *rect = fgeom->left_right_background;
+      break;
+      
+    case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND:
+      *rect = fgeom->right_left_background;
+      break;
+      
+    case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND:
+      *rect = fgeom->right_middle_backgrounds[middle_background_offset];
+      break;
+      
+    case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND:
+      *rect = fgeom->right_right_background;
+      break;
+      
+    case META_BUTTON_TYPE_CLOSE:
+      *rect = fgeom->close_rect.visible;
+      break;
+
+    case META_BUTTON_TYPE_SHADE:
+      *rect = fgeom->shade_rect.visible;
+      break;
+
+    case META_BUTTON_TYPE_UNSHADE:
+      *rect = fgeom->unshade_rect.visible;
+      break;
+
+    case META_BUTTON_TYPE_ABOVE:
+      *rect = fgeom->above_rect.visible;
+      break;
+
+    case META_BUTTON_TYPE_UNABOVE:
+      *rect = fgeom->unabove_rect.visible;
+      break;
+
+    case META_BUTTON_TYPE_STICK:
+      *rect = fgeom->stick_rect.visible;
+      break;
+
+    case META_BUTTON_TYPE_UNSTICK:
+      *rect = fgeom->unstick_rect.visible;
+      break;
+
+    case META_BUTTON_TYPE_MAXIMIZE:
+      *rect = fgeom->max_rect.visible;
+      break;
+
+    case META_BUTTON_TYPE_MINIMIZE:
+      *rect = fgeom->min_rect.visible;
+      break;
+
+    case META_BUTTON_TYPE_MENU:
+      *rect = fgeom->menu_rect.visible;
+      break;
+      
+    case META_BUTTON_TYPE_LAST:
+      g_assert_not_reached ();
+      break;
+    }
+}
+
+void
+meta_frame_style_draw_with_style (MetaFrameStyle          *style,
+                                  GtkStyle                *style_gtk,
+                       GtkWidget               *widget,
+                       GdkDrawable             *drawable,
+                       int                      x_offset,
+                       int                      y_offset,
+                       const GdkRectangle      *clip,
+                       const MetaFrameGeometry *fgeom,
+                       int                      client_width,
+                       int                      client_height,
+                       PangoLayout             *title_layout,
+                       int                      text_height,
+                       MetaButtonState          button_states[META_BUTTON_TYPE_LAST],
+                       GdkPixbuf               *mini_icon,
+                       GdkPixbuf               *icon)
+{
+  int i, j;
+  GdkRectangle titlebar_rect;
+  GdkRectangle left_titlebar_edge;
+  GdkRectangle right_titlebar_edge;
+  GdkRectangle bottom_titlebar_edge;
+  GdkRectangle top_titlebar_edge;
+  GdkRectangle left_edge, right_edge, bottom_edge;
+  PangoRectangle logical_rect;
+  MetaDrawInfo draw_info;
+  
+  g_return_if_fail (style_gtk->colormap == gdk_drawable_get_colormap (drawable));
+
+  titlebar_rect.x = 0;
+  titlebar_rect.y = 0;
+  titlebar_rect.width = fgeom->width;
+  titlebar_rect.height = fgeom->top_height;
+
+  left_titlebar_edge.x = titlebar_rect.x;
+  left_titlebar_edge.y = titlebar_rect.y + fgeom->top_titlebar_edge;
+  left_titlebar_edge.width = fgeom->left_titlebar_edge;
+  left_titlebar_edge.height = titlebar_rect.height - fgeom->top_titlebar_edge - fgeom->bottom_titlebar_edge;
+
+  right_titlebar_edge.y = left_titlebar_edge.y;
+  right_titlebar_edge.height = left_titlebar_edge.height;
+  right_titlebar_edge.width = fgeom->right_titlebar_edge;
+  right_titlebar_edge.x = titlebar_rect.x + titlebar_rect.width - right_titlebar_edge.width;
+
+  top_titlebar_edge.x = titlebar_rect.x;
+  top_titlebar_edge.y = titlebar_rect.y;
+  top_titlebar_edge.width = titlebar_rect.width;
+  top_titlebar_edge.height = fgeom->top_titlebar_edge;
+
+  bottom_titlebar_edge.x = titlebar_rect.x;
+  bottom_titlebar_edge.width = titlebar_rect.width;
+  bottom_titlebar_edge.height = fgeom->bottom_titlebar_edge;
+  bottom_titlebar_edge.y = titlebar_rect.y + titlebar_rect.height - bottom_titlebar_edge.height;
+
+  left_edge.x = 0;
+  left_edge.y = fgeom->top_height;
+  left_edge.width = fgeom->left_width;
+  left_edge.height = fgeom->height - fgeom->top_height - fgeom->bottom_height;
+
+  right_edge.x = fgeom->width - fgeom->right_width;
+  right_edge.y = fgeom->top_height;
+  right_edge.width = fgeom->right_width;
+  right_edge.height = fgeom->height - fgeom->top_height - fgeom->bottom_height;
+
+  bottom_edge.x = 0;
+  bottom_edge.y = fgeom->height - fgeom->bottom_height;
+  bottom_edge.width = fgeom->width;
+  bottom_edge.height = fgeom->bottom_height;
+
+  if (title_layout)
+    pango_layout_get_pixel_extents (title_layout,
+                                    NULL, &logical_rect);
+
+  draw_info.mini_icon = mini_icon;
+  draw_info.icon = icon;
+  draw_info.title_layout = title_layout;
+  draw_info.title_layout_width = title_layout ? logical_rect.width : 0;
+  draw_info.title_layout_height = title_layout ? logical_rect.height : 0;
+  draw_info.fgeom = fgeom;
+  
+  /* The enum is in the order the pieces should be rendered. */
+  i = 0;
+  while (i < META_FRAME_PIECE_LAST)
+    {
+      GdkRectangle rect;
+      GdkRectangle combined_clip;
+      
+      switch ((MetaFramePiece) i)
+        {
+        case META_FRAME_PIECE_ENTIRE_BACKGROUND:
+          rect.x = 0;
+          rect.y = 0;
+          rect.width = fgeom->width;
+          rect.height = fgeom->height;
+          break;
+
+        case META_FRAME_PIECE_TITLEBAR:
+          rect = titlebar_rect;
+          break;
+
+        case META_FRAME_PIECE_LEFT_TITLEBAR_EDGE:
+          rect = left_titlebar_edge;
+          break;
+
+        case META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE:
+          rect = right_titlebar_edge;
+          break;
+
+        case META_FRAME_PIECE_TOP_TITLEBAR_EDGE:
+          rect = top_titlebar_edge;
+          break;
+
+        case META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE:
+          rect = bottom_titlebar_edge;
+          break;
+
+        case META_FRAME_PIECE_TITLEBAR_MIDDLE:
+          rect.x = left_titlebar_edge.x + left_titlebar_edge.width;
+          rect.y = top_titlebar_edge.y + top_titlebar_edge.height;
+          rect.width = titlebar_rect.width - left_titlebar_edge.width -
+            right_titlebar_edge.width;
+          rect.height = titlebar_rect.height - top_titlebar_edge.height - bottom_titlebar_edge.height;
+          break;
+
+        case META_FRAME_PIECE_TITLE:
+          rect = fgeom->title_rect;
+          break;
+
+        case META_FRAME_PIECE_LEFT_EDGE:
+          rect = left_edge;
+          break;
+
+        case META_FRAME_PIECE_RIGHT_EDGE:
+          rect = right_edge;
+          break;
+
+        case META_FRAME_PIECE_BOTTOM_EDGE:
+          rect = bottom_edge;
+          break;
+
+        case META_FRAME_PIECE_OVERLAY:
+          rect.x = 0;
+          rect.y = 0;
+          rect.width = fgeom->width;
+          rect.height = fgeom->height;
+          break;
+
+        case META_FRAME_PIECE_LAST:
+          g_assert_not_reached ();
+          break;
+        }
+
+      rect.x += x_offset;
+      rect.y += y_offset;
+
+      if (clip == NULL)
+        combined_clip = rect;
+      else
+        gdk_rectangle_intersect ((GdkRectangle*) clip, /* const cast */
+                                 &rect,
+                                 &combined_clip);
+
+      if (combined_clip.width > 0 && combined_clip.height > 0)
+        {
+          MetaDrawOpList *op_list;
+          MetaFrameStyle *parent;
+
+          parent = style;
+          op_list = NULL;
+          while (parent && op_list == NULL)
+            {
+              op_list = parent->pieces[i];
+              parent = parent->parent;
+            }
+
+          if (op_list)
+            {
+              MetaRectangle m_rect;
+              m_rect = meta_rect (rect.x, rect.y, rect.width, rect.height);
+              meta_draw_op_list_draw_with_style (op_list,
+                                                 style_gtk,
+                                      widget,
+                                      drawable,
+                                      &combined_clip,
+                                      &draw_info,
+                                      m_rect);
+            }
+        }
+
+
+      /* Draw buttons just before overlay */
+      if ((i + 1) == META_FRAME_PIECE_OVERLAY)
+        {
+          int middle_bg_offset;
+
+          middle_bg_offset = 0;
+          j = 0;
+          while (j < META_BUTTON_TYPE_LAST)
+            {
+              button_rect (j, fgeom, middle_bg_offset, &rect);
+              
+              rect.x += x_offset;
+              rect.y += y_offset;
+              
+              if (clip == NULL)
+                combined_clip = rect;
+              else
+                gdk_rectangle_intersect ((GdkRectangle*) clip, /* const cast */
+                                         &rect,
+                                         &combined_clip);
+              
+              if (combined_clip.width > 0 && combined_clip.height > 0)
+                {
+                  MetaDrawOpList *op_list;
+                  
+                  op_list = get_button (style, j, button_states[j]);
+                  
+                  if (op_list)
+                    {
+                      MetaRectangle m_rect;
+                      m_rect = meta_rect (rect.x, rect.y,
+                                          rect.width, rect.height);
+                      meta_draw_op_list_draw_with_style (op_list,
+                                                         style_gtk,
+                                              widget,
+                                              drawable,
+                                              &combined_clip,
+                                              &draw_info,
+                                              m_rect);
+                    }
+                }
+
+              /* MIDDLE_BACKGROUND type may get drawn more than once */
+              if ((j == META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND ||
+                   j == META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND) &&
+                  middle_bg_offset < MAX_MIDDLE_BACKGROUNDS)
+                {
+                  ++middle_bg_offset;
+                }
+              else
+                {
+                  middle_bg_offset = 0;
+                  ++j;
+                }
+            }
+        }
+      
+      ++i;
+    }
+}
+
+void
+meta_frame_style_draw (MetaFrameStyle          *style,
+                       GtkWidget               *widget,
+                       GdkDrawable             *drawable,
+                       int                      x_offset,
+                       int                      y_offset,
+                       const GdkRectangle      *clip,
+                       const MetaFrameGeometry *fgeom,
+                       int                      client_width,
+                       int                      client_height,
+                       PangoLayout             *title_layout,
+                       int                      text_height,
+                       MetaButtonState          button_states[META_BUTTON_TYPE_LAST],
+                       GdkPixbuf               *mini_icon,
+                       GdkPixbuf               *icon)
+{
+  meta_frame_style_draw_with_style (style, gtk_widget_get_style (widget), widget,
+                                    drawable, x_offset, y_offset,
+                                    clip, fgeom, client_width, client_height,
+                                    title_layout, text_height,
+                                    button_states, mini_icon, icon);
+}
+
+MetaFrameStyleSet*
+meta_frame_style_set_new (MetaFrameStyleSet *parent)
+{
+  MetaFrameStyleSet *style_set;
+
+  style_set = g_new0 (MetaFrameStyleSet, 1);
+
+  style_set->parent = parent;
+  if (parent)
+    meta_frame_style_set_ref (parent);
+
+  style_set->refcount = 1;
+  
+  return style_set;
+}
+
+static void
+free_focus_styles (MetaFrameStyle *focus_styles[META_FRAME_FOCUS_LAST])
+{
+  int i;
+
+  for (i = 0; i < META_FRAME_FOCUS_LAST; i++)
+    if (focus_styles[i])
+      meta_frame_style_unref (focus_styles[i]);
+}
+
+void
+meta_frame_style_set_ref (MetaFrameStyleSet *style_set)
+{
+  g_return_if_fail (style_set != NULL);
+
+  style_set->refcount += 1;
+}
+
+void
+meta_frame_style_set_unref (MetaFrameStyleSet *style_set)
+{
+  g_return_if_fail (style_set != NULL);
+  g_return_if_fail (style_set->refcount > 0);
+
+  style_set->refcount -= 1;
+
+  if (style_set->refcount == 0)
+    {
+      int i;
+
+      for (i = 0; i < META_FRAME_RESIZE_LAST; i++)
+        {
+          free_focus_styles (style_set->normal_styles[i]);
+          free_focus_styles (style_set->shaded_styles[i]);
+        }
+
+      free_focus_styles (style_set->maximized_styles);
+      free_focus_styles (style_set->maximized_and_shaded_styles);
+
+      if (style_set->parent)
+        meta_frame_style_set_unref (style_set->parent);
+
+      DEBUG_FILL_STRUCT (style_set);
+      g_free (style_set);
+    }
+}
+
+
+static MetaFrameStyle*
+get_style (MetaFrameStyleSet *style_set,
+           MetaFrameState     state,
+           MetaFrameResize    resize,
+           MetaFrameFocus     focus)
+{
+  MetaFrameStyle *style;  
+  
+  style = NULL;
+
+  switch (state)
+    {
+    case META_FRAME_STATE_NORMAL:
+    case META_FRAME_STATE_SHADED:
+      {
+        if (state == META_FRAME_STATE_SHADED)
+          style = style_set->shaded_styles[resize][focus];
+        else
+          style = style_set->normal_styles[resize][focus];
+
+        /* Try parent if we failed here */
+        if (style == NULL && style_set->parent)
+          style = get_style (style_set->parent, state, resize, focus);
+      
+        /* Allow people to omit the vert/horz/none resize modes */
+        if (style == NULL &&
+            resize != META_FRAME_RESIZE_BOTH)
+          style = get_style (style_set, state, META_FRAME_RESIZE_BOTH, focus);
+      }
+      break;
+    default:
+      {
+        MetaFrameStyle **styles;
+
+        styles = NULL;
+      
+        switch (state)
+          {
+          case META_FRAME_STATE_MAXIMIZED:
+            styles = style_set->maximized_styles;
+            break;
+          case META_FRAME_STATE_MAXIMIZED_AND_SHADED:
+            styles = style_set->maximized_and_shaded_styles;
+            break;
+          case META_FRAME_STATE_NORMAL:
+          case META_FRAME_STATE_SHADED:
+          case META_FRAME_STATE_LAST:
+            g_assert_not_reached ();
+            break;
+          }
+
+        style = styles[focus];
+
+        /* Try parent if we failed here */
+        if (style == NULL && style_set->parent)
+          style = get_style (style_set->parent, state, resize, focus);      
+      }
+    }
+
+  return style;
+}
+
+static gboolean
+check_state  (MetaFrameStyleSet *style_set,
+              MetaFrameState     state,
+              GError           **error)
+{
+  int i;
+
+  for (i = 0; i < META_FRAME_FOCUS_LAST; i++)
+    {
+      if (get_style (style_set, state,
+                     META_FRAME_RESIZE_NONE, i) == NULL)
+        {
+          /* Translators: This error occurs when a <frame> tag is missing
+           * in theme XML.  The "<frame ...>" is intended as a noun phrase,
+           * and the "missing" qualifies it.  You should translate "whatever".
+           */
+          g_set_error (error, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("Missing <frame state=\"%s\" resize=\"%s\" focus=\"%s\" style=\"whatever\"/>"),
+                       meta_frame_state_to_string (state),
+                       meta_frame_resize_to_string (META_FRAME_RESIZE_NONE),
+                       meta_frame_focus_to_string (i));
+          return FALSE;
+        }
+    }
+
+  return TRUE;
+}
+
+gboolean
+meta_frame_style_set_validate  (MetaFrameStyleSet *style_set,
+                                GError           **error)
+{
+  int i, j;
+  
+  g_return_val_if_fail (style_set != NULL, FALSE);
+
+  for (i = 0; i < META_FRAME_RESIZE_LAST; i++)
+    for (j = 0; j < META_FRAME_FOCUS_LAST; j++)
+      if (get_style (style_set, META_FRAME_STATE_NORMAL, i, j) == NULL)
+        {
+          g_set_error (error, META_THEME_ERROR,
+                       META_THEME_ERROR_FAILED,
+                       _("Missing <frame state=\"%s\" resize=\"%s\" focus=\"%s\" style=\"whatever\"/>"),
+                       meta_frame_state_to_string (META_FRAME_STATE_NORMAL),
+                       meta_frame_resize_to_string (i),
+                       meta_frame_focus_to_string (j));
+          return FALSE;
+        }
+
+  if (!check_state (style_set, META_FRAME_STATE_SHADED, error))
+    return FALSE;
+  
+  if (!check_state (style_set, META_FRAME_STATE_MAXIMIZED, error))
+    return FALSE;
+
+  if (!check_state (style_set, META_FRAME_STATE_MAXIMIZED_AND_SHADED, error))
+    return FALSE;
+  
+  return TRUE;
+}
+
+MetaTheme*
+meta_theme_get_current (void)
+{
+  return meta_current_theme;
+}
+
+void
+meta_theme_set_current (const char *name,
+                        gboolean    force_reload)
+{
+  MetaTheme *new_theme;
+  GError *err;
+
+  meta_topic (META_DEBUG_THEMES, "Setting current theme to \"%s\"\n", name);
+  
+  if (!force_reload &&
+      meta_current_theme &&
+      strcmp (name, meta_current_theme->name) == 0)
+    return;
+  
+  err = NULL;
+  new_theme = meta_theme_load (name, &err);
+
+  if (new_theme == NULL)
+    {
+      meta_warning (_("Failed to load theme \"%s\": %s\n"),
+                    name, err->message);
+      g_error_free (err);
+    }
+  else
+    {
+      if (meta_current_theme)
+        meta_theme_free (meta_current_theme);
+
+      meta_current_theme = new_theme;
+
+      meta_topic (META_DEBUG_THEMES, "New theme is \"%s\"\n", meta_current_theme->name);
+    }
+}
+
+MetaTheme*
+meta_theme_new (void)
+{
+  MetaTheme *theme;
+
+  theme = g_new0 (MetaTheme, 1);
+
+  theme->images_by_filename =
+    g_hash_table_new_full (g_str_hash,
+                           g_str_equal,
+                           g_free,
+                           (GDestroyNotify) g_object_unref);
+  
+  theme->layouts_by_name =
+    g_hash_table_new_full (g_str_hash,
+                           g_str_equal,
+                           g_free,
+                           (GDestroyNotify) meta_frame_layout_unref);
+
+  theme->draw_op_lists_by_name =
+    g_hash_table_new_full (g_str_hash,
+                           g_str_equal,
+                           g_free,
+                           (GDestroyNotify) meta_draw_op_list_unref);
+
+  theme->styles_by_name =
+    g_hash_table_new_full (g_str_hash,
+                           g_str_equal,
+                           g_free,
+                           (GDestroyNotify) meta_frame_style_unref);
+
+  theme->style_sets_by_name =
+    g_hash_table_new_full (g_str_hash,
+                           g_str_equal,
+                           g_free,
+                           (GDestroyNotify) meta_frame_style_set_unref);
+  
+  /* Create our variable quarks so we can look up variables without
+     having to strcmp for the names */
+  theme->quark_width = g_quark_from_static_string ("width");
+  theme->quark_height = g_quark_from_static_string ("height");
+  theme->quark_object_width = g_quark_from_static_string ("object_width");
+  theme->quark_object_height = g_quark_from_static_string ("object_height");
+  theme->quark_left_width = g_quark_from_static_string ("left_width");
+  theme->quark_right_width = g_quark_from_static_string ("right_width");
+  theme->quark_top_height = g_quark_from_static_string ("top_height");
+  theme->quark_bottom_height = g_quark_from_static_string ("bottom_height");
+  theme->quark_mini_icon_width = g_quark_from_static_string ("mini_icon_width");
+  theme->quark_mini_icon_height = g_quark_from_static_string ("mini_icon_height");
+  theme->quark_icon_width = g_quark_from_static_string ("icon_width");
+  theme->quark_icon_height = g_quark_from_static_string ("icon_height");
+  theme->quark_title_width = g_quark_from_static_string ("title_width");
+  theme->quark_title_height = g_quark_from_static_string ("title_height");
+  theme->quark_frame_x_center = g_quark_from_static_string ("frame_x_center");
+  theme->quark_frame_y_center = g_quark_from_static_string ("frame_y_center");
+  return theme;
+}
+
+
+void
+meta_theme_free (MetaTheme *theme)
+{
+  int i;
+
+  g_return_if_fail (theme != NULL);
+
+  g_free (theme->name);
+  g_free (theme->dirname);
+  g_free (theme->filename);
+  g_free (theme->readable_name);
+  g_free (theme->date);
+  g_free (theme->description);
+  g_free (theme->author);
+  g_free (theme->copyright);
+
+  /* be more careful when destroying the theme hash tables,
+     since they are only constructed as needed, and may be NULL. */
+  if (theme->integer_constants)
+    g_hash_table_destroy (theme->integer_constants);
+  if (theme->images_by_filename)
+    g_hash_table_destroy (theme->images_by_filename);
+  if (theme->layouts_by_name)
+    g_hash_table_destroy (theme->layouts_by_name);
+  if (theme->draw_op_lists_by_name)  
+    g_hash_table_destroy (theme->draw_op_lists_by_name);
+  if (theme->styles_by_name)  
+    g_hash_table_destroy (theme->styles_by_name);
+  if (theme->style_sets_by_name)  
+    g_hash_table_destroy (theme->style_sets_by_name);
+
+  for (i = 0; i < META_FRAME_TYPE_LAST; i++)
+    if (theme->style_sets_by_type[i])
+      meta_frame_style_set_unref (theme->style_sets_by_type[i]);
+
+  DEBUG_FILL_STRUCT (theme);
+  g_free (theme);
+}
+
+gboolean
+meta_theme_validate (MetaTheme *theme,
+                     GError   **error)
+{
+  int i;
+  
+  g_return_val_if_fail (theme != NULL, FALSE);
+
+  /* FIXME what else should be checked? */
+
+  g_assert (theme->name);
+  
+  if (theme->readable_name == NULL)
+    {
+      /* Translators: This error means that a necessary XML tag (whose name
+       * is given in angle brackets) was not found in a given theme (whose
+       * name is given second, in quotation marks).
+       */
+      g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+                   _("No <%s> set for theme \"%s\""), "name", theme->name);
+      return FALSE;
+    }
+
+  if (theme->author == NULL)
+    {
+      g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+                   _("No <%s> set for theme \"%s\""), "author", theme->name);
+      return FALSE;
+    }
+
+  if (theme->date == NULL)
+    {
+      g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+                   _("No <%s> set for theme \"%s\""), "date", theme->name);
+      return FALSE;
+    }
+
+  if (theme->description == NULL)
+    {
+      g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+                   _("No <%s> set for theme \"%s\""), "description", theme->name);
+      return FALSE;
+    }
+
+  if (theme->copyright == NULL)
+    {
+      g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+                   _("No <%s> set for theme \"%s\""), "copyright", theme->name);
+      return FALSE;
+    }
+
+  for (i = 0; i < (int)META_FRAME_TYPE_LAST; i++)
+    if (theme->style_sets_by_type[i] == NULL)
+      {
+        g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+                     _("No frame style set for window type \"%s\" in theme \"%s\", add a <window type=\"%s\" style_set=\"whatever\"/> element"),
+                     meta_frame_type_to_string (i),
+                     theme->name,
+                     meta_frame_type_to_string (i));
+        
+        return FALSE;          
+      }
+
+  return TRUE;
+}
+
+GdkPixbuf*
+meta_theme_load_image (MetaTheme  *theme,
+                       const char *filename,
+                       guint size_of_theme_icons,
+                       GError    **error)
+{
+  GdkPixbuf *pixbuf;
+
+  pixbuf = g_hash_table_lookup (theme->images_by_filename,
+                                filename);
+
+  if (pixbuf == NULL)
+    {
+       
+      if (g_str_has_prefix (filename, "theme:") &&
+          META_THEME_ALLOWS (theme, META_THEME_IMAGES_FROM_ICON_THEMES))
+        {
+          pixbuf = gtk_icon_theme_load_icon (
+              gtk_icon_theme_get_default (),
+              filename+6,
+              size_of_theme_icons,
+              0,
+              error);
+          if (pixbuf == NULL) return NULL;
+         }
+      else
+        {
+          char *full_path;
+          full_path = g_build_filename (theme->dirname, filename, NULL);
+      
+          pixbuf = gdk_pixbuf_new_from_file (full_path, error);
+          if (pixbuf == NULL)
+            {
+              g_free (full_path);
+              return NULL;
+            }
+      
+          g_free (full_path);
+        }      
+      g_hash_table_replace (theme->images_by_filename,
+                            g_strdup (filename),
+                            pixbuf);
+    }
+
+  g_assert (pixbuf);
+  
+  g_object_ref (G_OBJECT (pixbuf));
+
+  return pixbuf;
+}
+
+static MetaFrameStyle*
+theme_get_style (MetaTheme     *theme,
+                 MetaFrameType  type,
+                 MetaFrameFlags flags)
+{
+  MetaFrameState state;
+  MetaFrameResize resize;
+  MetaFrameFocus focus;
+  MetaFrameStyle *style;
+  MetaFrameStyleSet *style_set;
+
+  style_set = theme->style_sets_by_type[type];
+
+  /* Right now the parser forces a style set for all types,
+   * but this fallback code is here in case I take that out.
+   */
+  if (style_set == NULL)
+    style_set = theme->style_sets_by_type[META_FRAME_TYPE_NORMAL];
+  if (style_set == NULL)
+    return NULL;
+  
+  switch (flags & (META_FRAME_MAXIMIZED | META_FRAME_SHADED))
+    {
+    case 0:
+      state = META_FRAME_STATE_NORMAL;
+      break;
+    case META_FRAME_MAXIMIZED:
+      state = META_FRAME_STATE_MAXIMIZED;
+      break;
+    case META_FRAME_SHADED:
+      state = META_FRAME_STATE_SHADED;
+      break;
+    case (META_FRAME_MAXIMIZED | META_FRAME_SHADED):
+      state = META_FRAME_STATE_MAXIMIZED_AND_SHADED;
+      break;
+    default:
+      g_assert_not_reached ();
+      state = META_FRAME_STATE_LAST; /* compiler */
+      break;
+    }
+
+  switch (flags & (META_FRAME_ALLOWS_VERTICAL_RESIZE | META_FRAME_ALLOWS_HORIZONTAL_RESIZE))
+    {
+    case 0:
+      resize = META_FRAME_RESIZE_NONE;
+      break;
+    case META_FRAME_ALLOWS_VERTICAL_RESIZE:
+      resize = META_FRAME_RESIZE_VERTICAL;
+      break;
+    case META_FRAME_ALLOWS_HORIZONTAL_RESIZE:
+      resize = META_FRAME_RESIZE_HORIZONTAL;
+      break;
+    case (META_FRAME_ALLOWS_VERTICAL_RESIZE | META_FRAME_ALLOWS_HORIZONTAL_RESIZE):
+      resize = META_FRAME_RESIZE_BOTH;
+      break;
+    default:
+      g_assert_not_reached ();
+      resize = META_FRAME_RESIZE_LAST; /* compiler */
+      break;
+    }
+  
+  /* re invert the styles used for focus/unfocussed while flashing a frame */
+  if (((flags & META_FRAME_HAS_FOCUS) && !(flags & META_FRAME_IS_FLASHING))
+      || (!(flags & META_FRAME_HAS_FOCUS) && (flags & META_FRAME_IS_FLASHING)))
+    focus = META_FRAME_FOCUS_YES;
+  else
+    focus = META_FRAME_FOCUS_NO;
+
+  style = get_style (style_set, state, resize, focus);
+
+  return style;
+}
+
+MetaFrameStyle*
+meta_theme_get_frame_style (MetaTheme     *theme,
+                            MetaFrameType  type,
+                            MetaFrameFlags flags)
+{
+  MetaFrameStyle *style;
+
+  g_return_val_if_fail (type < META_FRAME_TYPE_LAST, NULL);
+  
+  style = theme_get_style (theme, type, flags);
+
+  return style;
+}
+
+double
+meta_theme_get_title_scale (MetaTheme     *theme,
+                            MetaFrameType  type,
+                            MetaFrameFlags flags)
+{
+  MetaFrameStyle *style;
+
+  g_return_val_if_fail (type < META_FRAME_TYPE_LAST, 1.0);
+  
+  style = theme_get_style (theme, type, flags);
+  
+  /* Parser is not supposed to allow this currently */
+  if (style == NULL)
+    return 1.0;
+
+  return style->layout->title_scale;
+}
+
+void
+meta_theme_draw_frame_with_style (MetaTheme              *theme,
+                                  GtkStyle               *style_gtk,
+                       GtkWidget              *widget,
+                       GdkDrawable            *drawable,
+                       const GdkRectangle     *clip,
+                       int                     x_offset,
+                       int                     y_offset,
+                       MetaFrameType           type,
+                       MetaFrameFlags          flags,
+                       int                     client_width,
+                       int                     client_height,
+                       PangoLayout            *title_layout,
+                       int                     text_height,
+                       const MetaButtonLayout *button_layout,
+                       MetaButtonState         button_states[META_BUTTON_TYPE_LAST],
+                       GdkPixbuf              *mini_icon,
+                       GdkPixbuf              *icon)
+{
+  MetaFrameGeometry fgeom;
+  MetaFrameStyle *style;
+
+  g_return_if_fail (type < META_FRAME_TYPE_LAST);
+  
+  style = theme_get_style (theme, type, flags);
+  
+  /* Parser is not supposed to allow this currently */
+  if (style == NULL)
+    return;
+  
+  meta_frame_layout_calc_geometry (style->layout,
+                                   text_height,
+                                   flags,
+                                   client_width, client_height,
+                                   button_layout,
+                                   &fgeom,
+                                   theme);  
+
+  meta_frame_style_draw_with_style (style,
+                                    style_gtk,
+                                    widget,
+                                    drawable,
+                                    x_offset, y_offset,
+                                    clip,
+                                    &fgeom,
+                                    client_width, client_height,
+                                    title_layout,
+                                    text_height,
+                                    button_states,
+                                    mini_icon, icon);
+}
+
+void
+meta_theme_draw_frame (MetaTheme              *theme,
+                       GtkWidget              *widget,
+                       GdkDrawable            *drawable,
+                       const GdkRectangle     *clip,
+                       int                     x_offset,
+                       int                     y_offset,
+                       MetaFrameType           type,
+                       MetaFrameFlags          flags,
+                       int                     client_width,
+                       int                     client_height,
+                       PangoLayout            *title_layout,
+                       int                     text_height,
+                       const MetaButtonLayout *button_layout,
+                       MetaButtonState         button_states[META_BUTTON_TYPE_LAST],
+                       GdkPixbuf              *mini_icon,
+                       GdkPixbuf              *icon)
+{
+  meta_theme_draw_frame_with_style (theme, gtk_widget_get_style (widget), widget,
+                                    drawable, clip, x_offset, y_offset, type,flags,
+                                    client_width, client_height,
+                                    title_layout, text_height,
+                                    button_layout, button_states,
+                                    mini_icon, icon);
+}
+
+void
+meta_theme_draw_frame_by_name (MetaTheme              *theme,
+                               GtkWidget              *widget,
+                               GdkDrawable            *drawable,
+                               const GdkRectangle     *clip,
+                               int                     x_offset,
+                               int                     y_offset,
+                               const gchar             *style_name,
+                               MetaFrameFlags          flags,
+                               int                     client_width,
+                               int                     client_height,
+                               PangoLayout            *title_layout,
+                               int                     text_height,
+                               const MetaButtonLayout *button_layout,
+                               MetaButtonState         button_states[META_BUTTON_TYPE_LAST],
+                               GdkPixbuf              *mini_icon,
+                               GdkPixbuf              *icon)
+{
+  MetaFrameGeometry fgeom;
+  MetaFrameStyle *style;
+
+  style = meta_theme_lookup_style (theme, style_name);
+  
+  /* Parser is not supposed to allow this currently */
+  if (style == NULL)
+    return;
+  
+  meta_frame_layout_calc_geometry (style->layout,
+                                   text_height,
+                                   flags,
+                                   client_width, client_height,
+                                   button_layout,
+                                   &fgeom,
+                                   theme);  
+
+  meta_frame_style_draw (style,
+                         widget,
+                         drawable,
+                         x_offset, y_offset,
+                         clip,
+                         &fgeom,
+                         client_width, client_height,
+                         title_layout,
+                         text_height,
+                         button_states,
+                         mini_icon, icon);
+}
+
+void
+meta_theme_get_frame_borders (MetaTheme      *theme,
+                              MetaFrameType   type,
+                              int             text_height,
+                              MetaFrameFlags  flags,
+                              int            *top_height,
+                              int            *bottom_height,
+                              int            *left_width,
+                              int            *right_width)
+{
+  MetaFrameStyle *style;
+
+  g_return_if_fail (type < META_FRAME_TYPE_LAST);
+  
+  if (top_height)
+    *top_height = 0;
+  if (bottom_height)
+    *bottom_height = 0;
+  if (left_width)
+    *left_width = 0;
+  if (right_width)
+    *right_width = 0;
+  
+  style = theme_get_style (theme, type, flags);
+  
+  /* Parser is not supposed to allow this currently */
+  if (style == NULL)
+    return;
+
+  meta_frame_layout_get_borders (style->layout,
+                                 text_height,
+                                 flags,
+                                 top_height, bottom_height,
+                                 left_width, right_width);
+}
+
+void
+meta_theme_calc_geometry (MetaTheme              *theme,
+                          MetaFrameType           type,
+                          int                     text_height,
+                          MetaFrameFlags          flags,
+                          int                     client_width,
+                          int                     client_height,
+                          const MetaButtonLayout *button_layout,
+                          MetaFrameGeometry      *fgeom)
+{
+  MetaFrameStyle *style;
+
+  g_return_if_fail (type < META_FRAME_TYPE_LAST);
+  
+  style = theme_get_style (theme, type, flags);
+  
+  /* Parser is not supposed to allow this currently */
+  if (style == NULL)
+    return;
+
+  meta_frame_layout_calc_geometry (style->layout,
+                                   text_height,
+                                   flags,
+                                   client_width, client_height,
+                                   button_layout,
+                                   fgeom,
+                                   theme);
+}
+
+MetaFrameLayout*
+meta_theme_lookup_layout (MetaTheme         *theme,
+                          const char        *name)
+{
+  return g_hash_table_lookup (theme->layouts_by_name, name);
+}
+
+void
+meta_theme_insert_layout (MetaTheme         *theme,
+                          const char        *name,
+                          MetaFrameLayout   *layout)
+{
+  meta_frame_layout_ref (layout);
+  g_hash_table_replace (theme->layouts_by_name, g_strdup (name), layout);
+}
+
+MetaDrawOpList*
+meta_theme_lookup_draw_op_list (MetaTheme         *theme,
+                                const char        *name)
+{
+  return g_hash_table_lookup (theme->draw_op_lists_by_name, name);
+}
+
+void
+meta_theme_insert_draw_op_list (MetaTheme         *theme,
+                                const char        *name,
+                                MetaDrawOpList    *op_list)
+{
+  meta_draw_op_list_ref (op_list);
+  g_hash_table_replace (theme->draw_op_lists_by_name, g_strdup (name), op_list);
+}
+
+MetaFrameStyle*
+meta_theme_lookup_style (MetaTheme         *theme,
+                         const char        *name)
+{
+  return g_hash_table_lookup (theme->styles_by_name, name);
+}
+
+void
+meta_theme_insert_style (MetaTheme         *theme,
+                         const char        *name,
+                         MetaFrameStyle    *style)
+{
+  meta_frame_style_ref (style);
+  g_hash_table_replace (theme->styles_by_name, g_strdup (name), style);
+}
+
+MetaFrameStyleSet*
+meta_theme_lookup_style_set (MetaTheme         *theme,
+                             const char        *name)
+{
+  return g_hash_table_lookup (theme->style_sets_by_name, name);
+}
+
+void
+meta_theme_insert_style_set    (MetaTheme         *theme,
+                                const char        *name,
+                                MetaFrameStyleSet *style_set)
+{
+  meta_frame_style_set_ref (style_set);
+  g_hash_table_replace (theme->style_sets_by_name, g_strdup (name), style_set);
+}
+
+static gboolean
+first_uppercase (const char *str)
+{  
+  return g_ascii_isupper (*str);
+}
+
+gboolean
+meta_theme_define_int_constant (MetaTheme   *theme,
+                                const char  *name,
+                                int          value,
+                                GError     **error)
+{
+  if (theme->integer_constants == NULL)
+    theme->integer_constants = g_hash_table_new_full (g_str_hash,
+                                                      g_str_equal,
+                                                      g_free,
+                                                      NULL);
+
+  if (!first_uppercase (name))
+    {
+      g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+                   _("User-defined constants must begin with a capital letter; \"%s\" does not"),
+                   name);
+      return FALSE;
+    }
+  
+  if (g_hash_table_lookup_extended (theme->integer_constants, name, NULL, NULL))
+    {
+      g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+                   _("Constant \"%s\" has already been defined"),
+                   name);
+      
+      return FALSE;
+    }
+
+  g_hash_table_insert (theme->integer_constants,
+                       g_strdup (name),
+                       GINT_TO_POINTER (value));
+
+  return TRUE;
+}
+
+gboolean
+meta_theme_lookup_int_constant (MetaTheme   *theme,
+                                const char  *name,
+                                int         *value)
+{
+  gpointer old_value;
+
+  *value = 0;
+  
+  if (theme->integer_constants == NULL)
+    return FALSE;
+  
+  if (g_hash_table_lookup_extended (theme->integer_constants,
+                                    name, NULL, &old_value))
+    {
+      *value = GPOINTER_TO_INT (old_value);
+      return TRUE;
+    }
+  else
+    {
+      return FALSE;
+    }
+}
+
+gboolean
+meta_theme_define_float_constant (MetaTheme   *theme,
+                                  const char  *name,
+                                  double       value,
+                                  GError     **error)
+{
+  double *d;
+  
+  if (theme->float_constants == NULL)
+    theme->float_constants = g_hash_table_new_full (g_str_hash,
+                                                    g_str_equal,
+                                                    g_free,
+                                                    g_free);
+
+  if (!first_uppercase (name))
+    {
+      g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+                   _("User-defined constants must begin with a capital letter; \"%s\" does not"),
+                   name);
+      return FALSE;
+    }
+  
+  if (g_hash_table_lookup_extended (theme->float_constants, name, NULL, NULL))
+    {
+      g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+                   _("Constant \"%s\" has already been defined"),
+                   name);
+      
+      return FALSE;
+    }
+
+  d = g_new (double, 1);
+  *d = value;
+  
+  g_hash_table_insert (theme->float_constants,
+                       g_strdup (name), d);
+
+  return TRUE;
+}
+
+gboolean
+meta_theme_lookup_float_constant (MetaTheme   *theme,
+                                  const char  *name,
+                                  double      *value)
+{
+  double *d;
+
+  *value = 0.0;
+  
+  if (theme->float_constants == NULL)
+    return FALSE;
+
+  d = g_hash_table_lookup (theme->float_constants, name);
+
+  if (d)
+    {
+      *value = *d;
+      return TRUE;
+    }
+  else
+    {
+      return FALSE;
+    }
+}
+
+gboolean
+meta_theme_define_color_constant (MetaTheme   *theme,
+                                  const char  *name,
+                                  const char  *value,
+                                  GError     **error)
+{
+  if (theme->color_constants == NULL)
+    theme->color_constants = g_hash_table_new_full (g_str_hash,
+                                                    g_str_equal,
+                                                    g_free,
+                                                    NULL);
+
+  if (!first_uppercase (name))
+    {
+      g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+                   _("User-defined constants must begin with a capital letter; \"%s\" does not"),
+                   name);
+      return FALSE;
+    }
+  
+  if (g_hash_table_lookup_extended (theme->color_constants, name, NULL, NULL))
+    {
+      g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+                   _("Constant \"%s\" has already been defined"),
+                   name);
+      
+      return FALSE;
+    }
+
+  g_hash_table_insert (theme->color_constants,
+                       g_strdup (name),
+                       g_strdup (value));
+
+  return TRUE;
+}
+
+/**
+ * Looks up a colour constant.
+ *
+ * \param theme  the theme containing the constant
+ * \param name  the name of the constant
+ * \param value  [out] the string representation of the colour, or NULL if it
+ *               doesn't exist
+ * \return  TRUE if it exists, FALSE otherwise
+ */
+gboolean
+meta_theme_lookup_color_constant (MetaTheme   *theme,
+                                  const char  *name,
+                                  char       **value)
+{
+  char *result;
+
+  *value = NULL;
+  
+  if (theme->color_constants == NULL)
+    return FALSE;
+
+  result = g_hash_table_lookup (theme->color_constants, name);
+
+  if (result)
+    {
+      *value = result;
+      return TRUE;
+    }
+  else
+    {
+      return FALSE;
+    }
+}
+
+
+PangoFontDescription*
+meta_gtk_widget_get_font_desc (GtkWidget *widget,
+                               double     scale,
+			       const PangoFontDescription *override)
+{
+  PangoFontDescription *font_desc;
+  
+  g_return_val_if_fail (gtk_widget_get_realized (widget), NULL);
+
+  font_desc = pango_font_description_copy (gtk_widget_get_style (widget)->font_desc);
+
+  if (override)
+    pango_font_description_merge (font_desc, override, TRUE);
+
+  pango_font_description_set_size (font_desc,
+                                   MAX (pango_font_description_get_size (font_desc) * scale, 1));
+
+  return font_desc;
+}
+
+/**
+ * Returns the height of the letters in a particular font.
+ *
+ * \param font_desc  the font
+ * \param context  the context of the font
+ * \return  the height of the letters
+ */
+int
+meta_pango_font_desc_get_text_height (const PangoFontDescription *font_desc,
+                                      PangoContext         *context)
+{
+  PangoFontMetrics *metrics;
+  PangoLanguage *lang;
+  int retval;
+
+  lang = pango_context_get_language (context);
+  metrics = pango_context_get_metrics (context, font_desc, lang);
+
+  retval = PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) + 
+                         pango_font_metrics_get_descent (metrics));
+  
+  pango_font_metrics_unref (metrics);
+  
+  return retval;
+}
+
+MetaGtkColorComponent
+meta_color_component_from_string (const char *str)
+{
+  if (strcmp ("fg", str) == 0)
+    return META_GTK_COLOR_FG;
+  else if (strcmp ("bg", str) == 0)
+    return META_GTK_COLOR_BG;
+  else if (strcmp ("light", str) == 0)
+    return META_GTK_COLOR_LIGHT;
+  else if (strcmp ("dark", str) == 0)
+    return META_GTK_COLOR_DARK;
+  else if (strcmp ("mid", str) == 0)
+    return META_GTK_COLOR_MID;
+  else if (strcmp ("text", str) == 0)
+    return META_GTK_COLOR_TEXT;
+  else if (strcmp ("base", str) == 0)
+    return META_GTK_COLOR_BASE;
+  else if (strcmp ("text_aa", str) == 0)
+    return META_GTK_COLOR_TEXT_AA;
+  else
+    return META_GTK_COLOR_LAST;
+}
+
+const char*
+meta_color_component_to_string (MetaGtkColorComponent component)
+{
+  switch (component)
+    {
+    case META_GTK_COLOR_FG:
+      return "fg";
+    case META_GTK_COLOR_BG:
+      return "bg";
+    case META_GTK_COLOR_LIGHT:
+      return "light";
+    case META_GTK_COLOR_DARK:
+      return "dark";
+    case META_GTK_COLOR_MID:
+      return "mid";
+    case META_GTK_COLOR_TEXT:
+      return "text";
+    case META_GTK_COLOR_BASE:
+      return "base";
+    case META_GTK_COLOR_TEXT_AA:
+      return "text_aa";
+    case META_GTK_COLOR_LAST:
+      break;
+    }
+
+  return "<unknown>";
+}
+
+MetaButtonState
+meta_button_state_from_string (const char *str)
+{
+  if (strcmp ("normal", str) == 0)
+    return META_BUTTON_STATE_NORMAL;
+  else if (strcmp ("pressed", str) == 0)
+    return META_BUTTON_STATE_PRESSED;
+  else if (strcmp ("prelight", str) == 0)
+    return META_BUTTON_STATE_PRELIGHT;
+  else
+    return META_BUTTON_STATE_LAST;
+}
+
+const char*
+meta_button_state_to_string (MetaButtonState state)
+{
+  switch (state)
+    {
+    case META_BUTTON_STATE_NORMAL:
+      return "normal";
+    case META_BUTTON_STATE_PRESSED:
+      return "pressed";
+    case META_BUTTON_STATE_PRELIGHT:
+      return "prelight";
+    case META_BUTTON_STATE_LAST:
+      break;
+    }
+
+  return "<unknown>";
+}
+
+MetaButtonType
+meta_button_type_from_string (const char *str, MetaTheme *theme)
+{
+  if (META_THEME_ALLOWS(theme, META_THEME_SHADE_STICK_ABOVE_BUTTONS))
+    {
+      if (strcmp ("shade", str) == 0)
+        return META_BUTTON_TYPE_SHADE;
+      else if (strcmp ("above", str) == 0)
+        return META_BUTTON_TYPE_ABOVE;
+      else if (strcmp ("stick", str) == 0)
+        return META_BUTTON_TYPE_STICK;
+      else if (strcmp ("unshade", str) == 0)
+        return META_BUTTON_TYPE_UNSHADE;
+      else if (strcmp ("unabove", str) == 0)
+        return META_BUTTON_TYPE_UNABOVE;
+      else if (strcmp ("unstick", str) == 0)
+        return META_BUTTON_TYPE_UNSTICK;
+     }
+
+  if (strcmp ("close", str) == 0)
+    return META_BUTTON_TYPE_CLOSE;
+  else if (strcmp ("maximize", str) == 0)
+    return META_BUTTON_TYPE_MAXIMIZE;
+  else if (strcmp ("minimize", str) == 0)
+    return META_BUTTON_TYPE_MINIMIZE;
+  else if (strcmp ("menu", str) == 0)
+    return META_BUTTON_TYPE_MENU;
+  else if (strcmp ("left_left_background", str) == 0)
+    return META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND;
+  else if (strcmp ("left_middle_background", str) == 0)
+    return META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND;
+  else if (strcmp ("left_right_background", str) == 0)
+    return META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND;
+  else if (strcmp ("right_left_background", str) == 0)
+    return META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND;
+  else if (strcmp ("right_middle_background", str) == 0)
+    return META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND;
+  else if (strcmp ("right_right_background", str) == 0)
+    return META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND;
+  else
+    return META_BUTTON_TYPE_LAST;
+}
+
+const char*
+meta_button_type_to_string (MetaButtonType type)
+{
+  switch (type)
+    {
+    case META_BUTTON_TYPE_CLOSE:
+      return "close";
+    case META_BUTTON_TYPE_MAXIMIZE:
+      return "maximize";
+    case META_BUTTON_TYPE_MINIMIZE:
+      return "minimize";
+    case META_BUTTON_TYPE_SHADE:
+     return "shade";
+    case META_BUTTON_TYPE_ABOVE:
+      return "above";
+    case META_BUTTON_TYPE_STICK:
+      return "stick";
+    case META_BUTTON_TYPE_UNSHADE:
+      return "unshade";
+    case META_BUTTON_TYPE_UNABOVE:
+      return "unabove";
+    case META_BUTTON_TYPE_UNSTICK:
+      return "unstick";
+     case META_BUTTON_TYPE_MENU:
+      return "menu";
+    case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND:
+      return "left_left_background";
+    case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND:
+      return "left_middle_background";
+    case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND:
+      return "left_right_background";
+    case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND:
+      return "right_left_background";
+    case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND:
+      return "right_middle_background";
+    case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND:
+      return "right_right_background";      
+    case META_BUTTON_TYPE_LAST:
+      break;
+    }
+
+  return "<unknown>";
+}
+
+MetaFramePiece
+meta_frame_piece_from_string (const char *str)
+{
+  if (strcmp ("entire_background", str) == 0)
+    return META_FRAME_PIECE_ENTIRE_BACKGROUND;
+  else if (strcmp ("titlebar", str) == 0)
+    return META_FRAME_PIECE_TITLEBAR;
+  else if (strcmp ("titlebar_middle", str) == 0)
+    return META_FRAME_PIECE_TITLEBAR_MIDDLE;
+  else if (strcmp ("left_titlebar_edge", str) == 0)
+    return META_FRAME_PIECE_LEFT_TITLEBAR_EDGE;
+  else if (strcmp ("right_titlebar_edge", str) == 0)
+    return META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE;
+  else if (strcmp ("top_titlebar_edge", str) == 0)
+    return META_FRAME_PIECE_TOP_TITLEBAR_EDGE;
+  else if (strcmp ("bottom_titlebar_edge", str) == 0)
+    return META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE;
+  else if (strcmp ("title", str) == 0)
+    return META_FRAME_PIECE_TITLE;
+  else if (strcmp ("left_edge", str) == 0)
+    return META_FRAME_PIECE_LEFT_EDGE;
+  else if (strcmp ("right_edge", str) == 0)
+    return META_FRAME_PIECE_RIGHT_EDGE;
+  else if (strcmp ("bottom_edge", str) == 0)
+    return META_FRAME_PIECE_BOTTOM_EDGE;
+  else if (strcmp ("overlay", str) == 0)
+    return META_FRAME_PIECE_OVERLAY;
+  else
+    return META_FRAME_PIECE_LAST;
+}
+
+const char*
+meta_frame_piece_to_string (MetaFramePiece piece)
+{
+  switch (piece)
+    {
+    case META_FRAME_PIECE_ENTIRE_BACKGROUND:
+      return "entire_background";
+    case META_FRAME_PIECE_TITLEBAR:
+      return "titlebar";
+    case META_FRAME_PIECE_TITLEBAR_MIDDLE:
+      return "titlebar_middle";
+    case META_FRAME_PIECE_LEFT_TITLEBAR_EDGE:
+      return "left_titlebar_edge";
+    case META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE:
+      return "right_titlebar_edge";
+    case META_FRAME_PIECE_TOP_TITLEBAR_EDGE:
+      return "top_titlebar_edge";
+    case META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE:
+      return "bottom_titlebar_edge";
+    case META_FRAME_PIECE_TITLE:
+      return "title";
+    case META_FRAME_PIECE_LEFT_EDGE:
+      return "left_edge";
+    case META_FRAME_PIECE_RIGHT_EDGE:
+      return "right_edge";
+    case META_FRAME_PIECE_BOTTOM_EDGE:
+      return "bottom_edge";
+    case META_FRAME_PIECE_OVERLAY:
+      return "overlay";
+    case META_FRAME_PIECE_LAST:
+      break;
+    }
+
+  return "<unknown>";
+}
+
+MetaFrameState
+meta_frame_state_from_string (const char *str)
+{
+  if (strcmp ("normal", str) == 0)
+    return META_FRAME_STATE_NORMAL;
+  else if (strcmp ("maximized", str) == 0)
+    return META_FRAME_STATE_MAXIMIZED;
+  else if (strcmp ("shaded", str) == 0)
+    return META_FRAME_STATE_SHADED;
+  else if (strcmp ("maximized_and_shaded", str) == 0)
+    return META_FRAME_STATE_MAXIMIZED_AND_SHADED;
+  else
+    return META_FRAME_STATE_LAST;
+}
+
+const char*
+meta_frame_state_to_string (MetaFrameState state)
+{
+  switch (state)
+    {
+    case META_FRAME_STATE_NORMAL:
+      return "normal";
+    case META_FRAME_STATE_MAXIMIZED:
+      return "maximized";
+    case META_FRAME_STATE_SHADED:
+      return "shaded";
+    case META_FRAME_STATE_MAXIMIZED_AND_SHADED:
+      return "maximized_and_shaded";
+    case META_FRAME_STATE_LAST:
+      break;
+    }
+
+  return "<unknown>";
+}
+
+MetaFrameResize
+meta_frame_resize_from_string (const char *str)
+{
+  if (strcmp ("none", str) == 0)
+    return META_FRAME_RESIZE_NONE;
+  else if (strcmp ("vertical", str) == 0)
+    return META_FRAME_RESIZE_VERTICAL;
+  else if (strcmp ("horizontal", str) == 0)
+    return META_FRAME_RESIZE_HORIZONTAL;
+  else if (strcmp ("both", str) == 0)
+    return META_FRAME_RESIZE_BOTH;
+  else
+    return META_FRAME_RESIZE_LAST;
+}
+
+const char*
+meta_frame_resize_to_string (MetaFrameResize resize)
+{
+  switch (resize)
+    {
+    case META_FRAME_RESIZE_NONE:
+      return "none";
+    case META_FRAME_RESIZE_VERTICAL:
+      return "vertical";
+    case META_FRAME_RESIZE_HORIZONTAL:
+      return "horizontal";
+    case META_FRAME_RESIZE_BOTH:
+      return "both";
+    case META_FRAME_RESIZE_LAST:
+      break;
+    }
+
+  return "<unknown>";
+}
+
+MetaFrameFocus
+meta_frame_focus_from_string (const char *str)
+{
+  if (strcmp ("no", str) == 0)
+    return META_FRAME_FOCUS_NO;
+  else if (strcmp ("yes", str) == 0)
+    return META_FRAME_FOCUS_YES;
+  else
+    return META_FRAME_FOCUS_LAST;
+}
+
+const char*
+meta_frame_focus_to_string (MetaFrameFocus focus)
+{
+  switch (focus)
+    {
+    case META_FRAME_FOCUS_NO:
+      return "no";
+    case META_FRAME_FOCUS_YES:
+      return "yes";
+    case META_FRAME_FOCUS_LAST:
+      break;
+    }
+
+  return "<unknown>";
+}
+
+MetaFrameType
+meta_frame_type_from_string (const char *str)
+{
+  if (strcmp ("normal", str) == 0)
+    return META_FRAME_TYPE_NORMAL;
+  else if (strcmp ("dialog", str) == 0)
+    return META_FRAME_TYPE_DIALOG;
+  else if (strcmp ("modal_dialog", str) == 0)
+    return META_FRAME_TYPE_MODAL_DIALOG;
+  else if (strcmp ("utility", str) == 0)
+    return META_FRAME_TYPE_UTILITY;
+  else if (strcmp ("menu", str) == 0)
+    return META_FRAME_TYPE_MENU;
+  else if (strcmp ("border", str) == 0)
+    return META_FRAME_TYPE_BORDER;
+#if 0
+  else if (strcmp ("toolbar", str) == 0)
+    return META_FRAME_TYPE_TOOLBAR;
+#endif
+  else
+    return META_FRAME_TYPE_LAST;
+}
+
+const char*
+meta_frame_type_to_string (MetaFrameType type)
+{
+  switch (type)
+    {
+    case META_FRAME_TYPE_NORMAL:
+      return "normal";
+    case META_FRAME_TYPE_DIALOG:
+      return "dialog";
+    case META_FRAME_TYPE_MODAL_DIALOG:
+      return "modal_dialog";
+    case META_FRAME_TYPE_UTILITY:
+      return "utility";
+    case META_FRAME_TYPE_MENU:
+      return "menu";
+    case META_FRAME_TYPE_BORDER:
+      return "border";
+#if 0
+    case META_FRAME_TYPE_TOOLBAR:
+      return "toolbar";
+#endif
+    case  META_FRAME_TYPE_LAST:
+      break;
+    }
+
+  return "<unknown>";
+}
+
+MetaGradientType
+meta_gradient_type_from_string (const char *str)
+{
+  if (strcmp ("vertical", str) == 0)
+    return META_GRADIENT_VERTICAL;
+  else if (strcmp ("horizontal", str) == 0)
+    return META_GRADIENT_HORIZONTAL;
+  else if (strcmp ("diagonal", str) == 0)
+    return META_GRADIENT_DIAGONAL;
+  else
+    return META_GRADIENT_LAST;
+}
+
+const char*
+meta_gradient_type_to_string (MetaGradientType type)
+{
+  switch (type)
+    {
+    case META_GRADIENT_VERTICAL:
+      return "vertical";
+    case META_GRADIENT_HORIZONTAL:
+      return "horizontal";
+    case META_GRADIENT_DIAGONAL:
+      return "diagonal";
+    case META_GRADIENT_LAST:
+      break;
+    }
+
+  return "<unknown>";
+}
+
+GtkStateType
+meta_gtk_state_from_string (const char *str)
+{
+  if (strcmp ("normal", str) == 0 || strcmp ("NORMAL", str) == 0)
+    return GTK_STATE_NORMAL;
+  else if (strcmp ("prelight", str) == 0 || strcmp ("PRELIGHT", str) == 0)
+    return GTK_STATE_PRELIGHT;
+  else if (strcmp ("active", str) == 0 || strcmp ("ACTIVE", str) == 0)
+    return GTK_STATE_ACTIVE;
+  else if (strcmp ("selected", str) == 0 || strcmp ("SELECTED", str) == 0)
+    return GTK_STATE_SELECTED;
+  else if (strcmp ("insensitive", str) == 0 || strcmp ("INSENSITIVE", str) == 0)
+    return GTK_STATE_INSENSITIVE;
+  else
+    return -1; /* hack */
+}
+
+const char*
+meta_gtk_state_to_string (GtkStateType state)
+{
+  switch (state)
+    {
+    case GTK_STATE_NORMAL:
+      return "NORMAL";
+    case GTK_STATE_PRELIGHT:
+      return "PRELIGHT";
+    case GTK_STATE_ACTIVE:
+      return "ACTIVE";
+    case GTK_STATE_SELECTED:
+      return "SELECTED";
+    case GTK_STATE_INSENSITIVE:
+      return "INSENSITIVE";
+    }
+
+  return "<unknown>";
+}
+
+GtkShadowType
+meta_gtk_shadow_from_string (const char *str)
+{
+  if (strcmp ("none", str) == 0)
+    return GTK_SHADOW_NONE;
+  else if (strcmp ("in", str) == 0)
+    return GTK_SHADOW_IN;
+  else if (strcmp ("out", str) == 0)
+    return GTK_SHADOW_OUT;
+  else if (strcmp ("etched_in", str) == 0)
+    return GTK_SHADOW_ETCHED_IN;
+  else if (strcmp ("etched_out", str) == 0)
+    return GTK_SHADOW_ETCHED_OUT;
+  else
+    return -1;
+}
+
+const char*
+meta_gtk_shadow_to_string (GtkShadowType shadow)
+{
+  switch (shadow)
+    {
+    case GTK_SHADOW_NONE:
+      return "none";
+    case GTK_SHADOW_IN:
+      return "in";
+    case GTK_SHADOW_OUT:
+      return "out";
+    case GTK_SHADOW_ETCHED_IN:
+      return "etched_in";
+    case GTK_SHADOW_ETCHED_OUT:
+      return "etched_out";
+    }
+
+  return "<unknown>";
+}
+
+GtkArrowType
+meta_gtk_arrow_from_string (const char *str)
+{
+  if (strcmp ("up", str) == 0)
+    return GTK_ARROW_UP;
+  else if (strcmp ("down", str) == 0)
+    return GTK_ARROW_DOWN;
+  else if (strcmp ("left", str) == 0)
+    return GTK_ARROW_LEFT;
+  else if (strcmp ("right", str) == 0)
+    return GTK_ARROW_RIGHT;
+  else if (strcmp ("none", str) == 0)
+    return GTK_ARROW_NONE;
+  else
+    return -1;
+}
+
+const char*
+meta_gtk_arrow_to_string (GtkArrowType arrow)
+{
+  switch (arrow)
+    {
+    case GTK_ARROW_UP:
+      return "up";
+    case GTK_ARROW_DOWN:
+      return "down";
+    case GTK_ARROW_LEFT:
+      return "left";
+    case GTK_ARROW_RIGHT:
+      return "right";
+    case GTK_ARROW_NONE:
+      return "none";
+    }
+
+  return "<unknown>";
+}
+
+/**
+ * Returns a fill_type from a string.  The inverse of
+ * meta_image_fill_type_to_string().
+ *
+ * \param str  a string representing a fill_type
+ * \result  the fill_type, or -1 if it represents no fill_type.
+ */
+MetaImageFillType
+meta_image_fill_type_from_string (const char *str)
+{
+  if (strcmp ("tile", str) == 0)
+    return META_IMAGE_FILL_TILE;
+  else if (strcmp ("scale", str) == 0)
+    return META_IMAGE_FILL_SCALE;
+  else
+    return -1;
+}
+
+/**
+ * Returns a string representation of a fill_type.  The inverse of
+ * meta_image_fill_type_from_string().
+ *
+ * \param fill_type  the fill type
+ * \result  a string representing that type
+ */
+const char*
+meta_image_fill_type_to_string (MetaImageFillType fill_type)
+{
+  switch (fill_type)
+    {
+    case META_IMAGE_FILL_TILE:
+      return "tile";
+    case META_IMAGE_FILL_SCALE:
+      return "scale";
+    }
+  
+  return "<unknown>";
+}
+
+/**
+ * Takes a colour "a", scales the lightness and saturation by a certain amount,
+ * and sets "b" to the resulting colour.
+ * gtkstyle.c cut-and-pastage.
+ *
+ * \param a  the starting colour
+ * \param b  [out] the resulting colour
+ * \param k  amount to scale lightness and saturation by
+ */ 
+static void
+gtk_style_shade (GdkColor *a,
+                 GdkColor *b,
+                 gdouble   k)
+{
+  gdouble red;
+  gdouble green;
+  gdouble blue;
+  
+  red = (gdouble) a->red / 65535.0;
+  green = (gdouble) a->green / 65535.0;
+  blue = (gdouble) a->blue / 65535.0;
+  
+  rgb_to_hls (&red, &green, &blue);
+  
+  green *= k;
+  if (green > 1.0)
+    green = 1.0;
+  else if (green < 0.0)
+    green = 0.0;
+  
+  blue *= k;
+  if (blue > 1.0)
+    blue = 1.0;
+  else if (blue < 0.0)
+    blue = 0.0;
+  
+  hls_to_rgb (&red, &green, &blue);
+  
+  b->red = red * 65535.0;
+  b->green = green * 65535.0;
+  b->blue = blue * 65535.0;
+}
+
+/**
+ * Converts a red/green/blue triplet to a hue/lightness/saturation triplet.
+ *
+ * \param r  on input, red; on output, hue
+ * \param g  on input, green; on output, lightness
+ * \param b  on input, blue; on output, saturation
+ */
+static void
+rgb_to_hls (gdouble *r,
+            gdouble *g,
+            gdouble *b)
+{
+  gdouble min;
+  gdouble max;
+  gdouble red;
+  gdouble green;
+  gdouble blue;
+  gdouble h, l, s;
+  gdouble delta;
+  
+  red = *r;
+  green = *g;
+  blue = *b;
+  
+  if (red > green)
+    {
+      if (red > blue)
+        max = red;
+      else
+        max = blue;
+      
+      if (green < blue)
+        min = green;
+      else
+        min = blue;
+    }
+  else
+    {
+      if (green > blue)
+        max = green;
+      else
+        max = blue;
+      
+      if (red < blue)
+        min = red;
+      else
+        min = blue;
+    }
+  
+  l = (max + min) / 2;
+  s = 0;
+  h = 0;
+  
+  if (max != min)
+    {
+      if (l <= 0.5)
+        s = (max - min) / (max + min);
+      else
+        s = (max - min) / (2 - max - min);
+      
+      delta = max -min;
+      if (red == max)
+        h = (green - blue) / delta;
+      else if (green == max)
+        h = 2 + (blue - red) / delta;
+      else if (blue == max)
+        h = 4 + (red - green) / delta;
+      
+      h *= 60;
+      if (h < 0.0)
+        h += 360;
+    }
+  
+  *r = h;
+  *g = l;
+  *b = s;
+}
+
+/**
+ * Converts a hue/lightness/saturation triplet to a red/green/blue triplet.
+ *
+ * \param h  on input, hue; on output, red
+ * \param l  on input, lightness; on output, green
+ * \param s  on input, saturation; on output, blue
+ */
+static void
+hls_to_rgb (gdouble *h,
+            gdouble *l,
+            gdouble *s)
+{
+  gdouble hue;
+  gdouble lightness;
+  gdouble saturation;
+  gdouble m1, m2;
+  gdouble r, g, b;
+  
+  lightness = *l;
+  saturation = *s;
+  
+  if (lightness <= 0.5)
+    m2 = lightness * (1 + saturation);
+  else
+    m2 = lightness + saturation - lightness * saturation;
+  m1 = 2 * lightness - m2;
+  
+  if (saturation == 0)
+    {
+      *h = lightness;
+      *l = lightness;
+      *s = lightness;
+    }
+  else
+    {
+      hue = *h + 120;
+      while (hue > 360)
+        hue -= 360;
+      while (hue < 0)
+        hue += 360;
+      
+      if (hue < 60)
+        r = m1 + (m2 - m1) * hue / 60;
+      else if (hue < 180)
+        r = m2;
+      else if (hue < 240)
+        r = m1 + (m2 - m1) * (240 - hue) / 60;
+      else
+        r = m1;
+      
+      hue = *h;
+      while (hue > 360)
+        hue -= 360;
+      while (hue < 0)
+        hue += 360;
+      
+      if (hue < 60)
+        g = m1 + (m2 - m1) * hue / 60;
+      else if (hue < 180)
+        g = m2;
+      else if (hue < 240)
+        g = m1 + (m2 - m1) * (240 - hue) / 60;
+      else
+        g = m1;
+      
+      hue = *h - 120;
+      while (hue > 360)
+        hue -= 360;
+      while (hue < 0)
+        hue += 360;
+      
+      if (hue < 60)
+        b = m1 + (m2 - m1) * hue / 60;
+      else if (hue < 180)
+        b = m2;
+      else if (hue < 240)
+        b = m1 + (m2 - m1) * (240 - hue) / 60;
+      else
+        b = m1;
+      
+      *h = r;
+      *l = g;
+      *s = b;
+    }
+}
+
+#if 0
+/* These are some functions I'm saving to use in optimizing
+ * MetaDrawOpList, namely to pre-composite pixbufs on client side
+ * prior to rendering to the server
+ */
+static void
+draw_bg_solid_composite (const MetaTextureSpec *bg,
+                         const MetaTextureSpec *fg,
+                         double                 alpha,
+                         GtkWidget             *widget,
+                         GdkDrawable           *drawable,
+                         const GdkRectangle    *clip,
+                         MetaTextureDrawMode    mode,
+                         double                 xalign,
+                         double                 yalign,
+                         int                    x,
+                         int                    y,
+                         int                    width,
+                         int                    height)
+{
+  GdkColor bg_color;
+
+  g_assert (bg->type == META_TEXTURE_SOLID);
+  g_assert (fg->type != META_TEXTURE_COMPOSITE);
+  g_assert (fg->type != META_TEXTURE_SHAPE_LIST);
+
+  meta_color_spec_render (bg->data.solid.color_spec,
+                          widget,
+                          &bg_color);
+
+  switch (fg->type)
+    {
+    case META_TEXTURE_SOLID:
+      {
+        GdkColor fg_color;
+
+        meta_color_spec_render (fg->data.solid.color_spec,
+                                widget,
+                                &fg_color);
+
+        color_composite (&bg_color, &fg_color,
+                         alpha, &fg_color);
+
+        draw_color_rectangle (widget, drawable, &fg_color, clip,
+                              x, y, width, height);
+      }
+      break;
+
+    case META_TEXTURE_GRADIENT:
+      /* FIXME I think we could just composite all the colors in
+       * the gradient prior to generating the gradient?
+       */
+      /* FALL THRU */
+    case META_TEXTURE_IMAGE:
+      {
+        GdkPixbuf *pixbuf;
+        GdkPixbuf *composited;
+
+        pixbuf = meta_texture_spec_render (fg, widget, mode, 255,
+                                           width, height);
+
+        if (pixbuf == NULL)
+          return;
+
+        composited = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+                                     gdk_pixbuf_get_has_alpha (pixbuf), 8,
+                                     gdk_pixbuf_get_width (pixbuf),
+                                     gdk_pixbuf_get_height (pixbuf));
+
+        if (composited == NULL)
+          {
+            g_object_unref (G_OBJECT (pixbuf));
+            return;
+          }
+
+        gdk_pixbuf_composite_color (pixbuf,
+                                    composited,
+                                    0, 0,
+                                    gdk_pixbuf_get_width (pixbuf),
+                                    gdk_pixbuf_get_height (pixbuf),
+                                    0.0, 0.0, /* offsets */
+                                    1.0, 1.0, /* scale */
+                                    GDK_INTERP_BILINEAR,
+                                    255 * alpha,
+                                    0, 0,     /* check offsets */
+                                    0,        /* check size */
+                                    GDK_COLOR_RGB (bg_color),
+                                    GDK_COLOR_RGB (bg_color));
+
+        /* Need to draw background since pixbuf is not
+         * necessarily covering the whole thing
+         */
+        draw_color_rectangle (widget, drawable, &bg_color, clip,
+                              x, y, width, height);
+
+        render_pixbuf_aligned (drawable, clip, composited,
+                               xalign, yalign,
+                               x, y, width, height);
+
+        g_object_unref (G_OBJECT (pixbuf));
+        g_object_unref (G_OBJECT (composited));
+      }
+      break;
+
+    case META_TEXTURE_BLANK:
+    case META_TEXTURE_COMPOSITE:
+    case META_TEXTURE_SHAPE_LIST:
+      g_assert_not_reached ();
+      break;
+    }
+}
+
+static void
+draw_bg_gradient_composite (const MetaTextureSpec *bg,
+                            const MetaTextureSpec *fg,
+                            double                 alpha,
+                            GtkWidget             *widget,
+                            GdkDrawable           *drawable,
+                            const GdkRectangle    *clip,
+                            MetaTextureDrawMode    mode,
+                            double                 xalign,
+                            double                 yalign,
+                            int                    x,
+                            int                    y,
+                            int                    width,
+                            int                    height)
+{
+  g_assert (bg->type == META_TEXTURE_GRADIENT);
+  g_assert (fg->type != META_TEXTURE_COMPOSITE);
+  g_assert (fg->type != META_TEXTURE_SHAPE_LIST);
+
+  switch (fg->type)
+    {
+    case META_TEXTURE_SOLID:
+    case META_TEXTURE_GRADIENT:
+    case META_TEXTURE_IMAGE:
+      {
+        GdkPixbuf *bg_pixbuf;
+        GdkPixbuf *fg_pixbuf;
+        GdkPixbuf *composited;
+        int fg_width, fg_height;
+
+        bg_pixbuf = meta_texture_spec_render (bg, widget, mode, 255,
+                                              width, height);
+
+        if (bg_pixbuf == NULL)
+          return;
+
+        fg_pixbuf = meta_texture_spec_render (fg, widget, mode, 255,
+                                              width, height);
+
+        if (fg_pixbuf == NULL)
+          {
+            g_object_unref (G_OBJECT (bg_pixbuf));
+            return;
+          }
+
+        /* gradients always fill the entire target area */
+        g_assert (gdk_pixbuf_get_width (bg_pixbuf) == width);
+        g_assert (gdk_pixbuf_get_height (bg_pixbuf) == height);
+
+        composited = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+                                     gdk_pixbuf_get_has_alpha (bg_pixbuf), 8,
+                                     gdk_pixbuf_get_width (bg_pixbuf),
+                                     gdk_pixbuf_get_height (bg_pixbuf));
+
+        if (composited == NULL)
+          {
+            g_object_unref (G_OBJECT (bg_pixbuf));
+            g_object_unref (G_OBJECT (fg_pixbuf));
+            return;
+          }
+
+        fg_width = gdk_pixbuf_get_width (fg_pixbuf);
+        fg_height = gdk_pixbuf_get_height (fg_pixbuf);
+
+        /* If we wanted to be all cool we could deal with the
+         * offsets and try to composite only in the clip rectangle,
+         * but I just don't care enough to figure it out.
+         */
+
+        gdk_pixbuf_composite (fg_pixbuf,
+                              composited,
+                              x + (width - fg_width) * xalign,
+                              y + (height - fg_height) * yalign,
+                              gdk_pixbuf_get_width (fg_pixbuf),
+                              gdk_pixbuf_get_height (fg_pixbuf),
+                              0.0, 0.0, /* offsets */
+                              1.0, 1.0, /* scale */
+                              GDK_INTERP_BILINEAR,
+                              255 * alpha);
+
+        render_pixbuf (drawable, clip, composited, x, y);
+
+        g_object_unref (G_OBJECT (bg_pixbuf));
+        g_object_unref (G_OBJECT (fg_pixbuf));
+        g_object_unref (G_OBJECT (composited));
+      }
+      break;
+
+    case META_TEXTURE_BLANK:
+    case META_TEXTURE_SHAPE_LIST:
+    case META_TEXTURE_COMPOSITE:
+      g_assert_not_reached ();
+      break;
+    }
+}
+#endif
+
+/**
+ * Returns the earliest version of the theme format which required support
+ * for a particular button.  (For example, "shade" first appeared in v2, and
+ * "close" in v1.)
+ *
+ * \param type  the button type
+ * \return  the number of the theme format
+ */
+guint
+meta_theme_earliest_version_with_button (MetaButtonType type)
+{
+  switch (type)
+    {
+    case META_BUTTON_TYPE_CLOSE:
+    case META_BUTTON_TYPE_MAXIMIZE:
+    case META_BUTTON_TYPE_MINIMIZE:
+    case META_BUTTON_TYPE_MENU:
+    case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND:
+    case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND:
+    case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND:
+    case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND:
+    case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND:
+    case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND:
+      return 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;
+
+    default:
+      meta_warning("Unknown button %d\n", type);
+      return 1000;
+    }
+}
diff --git a/gtk/theme.h b/gtk/theme.h
new file mode 100644
index 0000000..e9369b1
--- /dev/null
+++ b/gtk/theme.h
@@ -0,0 +1,1195 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity Theme Rendering */
+
+/* 
+ * Copyright (C) 2001 Havoc Pennington
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_THEME_H
+#define META_THEME_H
+
+#include "boxes.h"
+#include "gradient.h"
+#include "common.h"
+#include <gtk/gtk.h>
+
+typedef struct _MetaFrameStyle MetaFrameStyle;
+typedef struct _MetaFrameStyleSet MetaFrameStyleSet;
+typedef struct _MetaDrawOp MetaDrawOp;
+typedef struct _MetaDrawOpList MetaDrawOpList;
+typedef struct _MetaGradientSpec MetaGradientSpec;
+typedef struct _MetaAlphaGradientSpec MetaAlphaGradientSpec; 
+typedef struct _MetaColorSpec MetaColorSpec;
+typedef struct _MetaFrameLayout MetaFrameLayout;
+typedef struct _MetaButtonSpace MetaButtonSpace;
+typedef struct _MetaFrameGeometry MetaFrameGeometry;
+typedef struct _MetaTheme MetaTheme;
+typedef struct _MetaPositionExprEnv MetaPositionExprEnv;
+typedef struct _MetaDrawInfo MetaDrawInfo;
+
+#define META_THEME_ERROR (g_quark_from_static_string ("meta-theme-error"))
+
+typedef enum
+{
+  META_THEME_ERROR_FRAME_GEOMETRY,
+  META_THEME_ERROR_BAD_CHARACTER,
+  META_THEME_ERROR_BAD_PARENS,
+  META_THEME_ERROR_UNKNOWN_VARIABLE,
+  META_THEME_ERROR_DIVIDE_BY_ZERO,
+  META_THEME_ERROR_MOD_ON_FLOAT,
+  META_THEME_ERROR_FAILED
+} MetaThemeError;
+
+/**
+ * Whether a button's size is calculated from the area around it (aspect
+ * sizing) or is given as a fixed height and width in pixels (fixed sizing).
+ *
+ * \bug This could be done away with; see the comment at the top of
+ * MetaFrameLayout.
+ */
+typedef enum
+{
+  META_BUTTON_SIZING_ASPECT,
+  META_BUTTON_SIZING_FIXED,
+  META_BUTTON_SIZING_LAST
+} MetaButtonSizing;
+
+/**
+ * Various parameters used to calculate the geometry of a frame.
+ * They are used inside a MetaFrameStyle.
+ * This corresponds closely to the <frame_geometry> tag in a theme file.
+ *
+ * \bug button_sizing isn't really necessary, because we could easily say
+ * that if button_aspect is zero, the height and width are fixed values.
+ * This would also mean that MetaButtonSizing didn't need to exist, and
+ * save code.
+ **/
+struct _MetaFrameLayout
+{
+  /** Reference count. */
+  int refcount;
+  
+  /** Size of left side */
+  int left_width;
+  /** Size of right side */
+  int right_width;
+  /** Size of bottom side */
+  int bottom_height;
+  
+  /** Border of blue title region
+   * \bug (blue?!)
+   **/
+  GtkBorder title_border;
+
+  /** Extra height for inside of title region, above the font height */
+  int title_vertical_pad;
+  
+  /** Right indent of buttons from edges of frame */
+  int right_titlebar_edge;
+  /** Left indent of buttons from edges of frame */
+  int left_titlebar_edge;
+  
+  /**
+   * Sizing rule of buttons, either META_BUTTON_SIZING_ASPECT
+   * (in which case button_aspect will be honoured, and
+   * button_width and button_height set from it), or
+   * META_BUTTON_SIZING_FIXED (in which case we read the width
+   * and height directly).
+   */
+  MetaButtonSizing button_sizing;
+
+  /**
+   * Ratio of height/width. Honoured only if
+   * button_sizing==META_BUTTON_SIZING_ASPECT.
+   * Otherwise we figure out the height from the button_border.
+   */
+  double button_aspect;
+  
+  /** Width of a button; set even when we are using aspect sizing */
+  int button_width;
+
+  /** Height of a button; set even when we are using aspect sizing */
+  int button_height;
+
+  /** Space around buttons */
+  GtkBorder button_border;
+
+  /** scale factor for title text */
+  double title_scale;
+  
+  /** Whether title text will be displayed */
+  guint has_title : 1;
+
+  /** Whether we should hide the buttons */
+  guint hide_buttons : 1;
+
+  /** Radius of the top left-hand corner; 0 if not rounded */
+  guint top_left_corner_rounded_radius;
+  /** Radius of the top right-hand corner; 0 if not rounded */
+  guint top_right_corner_rounded_radius;
+  /** Radius of the bottom left-hand corner; 0 if not rounded */
+  guint bottom_left_corner_rounded_radius;
+  /** Radius of the bottom right-hand corner; 0 if not rounded */
+  guint bottom_right_corner_rounded_radius;
+};
+
+/**
+ * The computed size of a button (really just a way of tying its
+ * visible and clickable areas together).
+ * The reason for two different rectangles here is Fitts' law & maximized
+ * windows; see bug #97703 for more details.
+ */
+struct _MetaButtonSpace
+{
+  /** The screen area where the button's image is drawn */
+  GdkRectangle visible;
+  /** The screen area where the button can be activated by clicking */
+  GdkRectangle clickable;
+};
+
+/**
+ * Calculated actual geometry of the frame
+ */
+struct _MetaFrameGeometry
+{
+  int left_width;
+  int right_width;
+  int top_height;
+  int bottom_height;
+
+  int width;
+  int height;  
+
+  GdkRectangle title_rect;
+
+  int left_titlebar_edge;
+  int right_titlebar_edge;
+  int top_titlebar_edge;
+  int bottom_titlebar_edge;
+
+  /* used for a memset hack */
+#define ADDRESS_OF_BUTTON_RECTS(fgeom) (((char*)(fgeom)) + G_STRUCT_OFFSET (MetaFrameGeometry, close_rect))
+#define LENGTH_OF_BUTTON_RECTS (G_STRUCT_OFFSET (MetaFrameGeometry, right_right_background) + sizeof (GdkRectangle) - G_STRUCT_OFFSET (MetaFrameGeometry, close_rect))
+  
+  /* The button rects (if changed adjust memset hack) */
+  MetaButtonSpace close_rect;
+  MetaButtonSpace max_rect;
+  MetaButtonSpace min_rect;
+  MetaButtonSpace menu_rect;
+  MetaButtonSpace shade_rect;
+  MetaButtonSpace above_rect;
+  MetaButtonSpace stick_rect;
+  MetaButtonSpace unshade_rect;
+  MetaButtonSpace unabove_rect;
+  MetaButtonSpace unstick_rect;
+
+#define MAX_MIDDLE_BACKGROUNDS (MAX_BUTTONS_PER_CORNER - 2)
+  GdkRectangle left_left_background;
+  GdkRectangle left_middle_backgrounds[MAX_MIDDLE_BACKGROUNDS];
+  GdkRectangle left_right_background;
+  GdkRectangle right_left_background;
+  GdkRectangle right_middle_backgrounds[MAX_MIDDLE_BACKGROUNDS];
+  GdkRectangle right_right_background;
+  /* End of button rects (if changed adjust memset hack) */
+  
+  /* Round corners */
+  guint top_left_corner_rounded_radius;
+  guint top_right_corner_rounded_radius;
+  guint bottom_left_corner_rounded_radius;
+  guint bottom_right_corner_rounded_radius;
+};
+
+typedef enum
+{
+  META_IMAGE_FILL_SCALE, /* default, needs to be all-bits-zero for g_new0 */
+  META_IMAGE_FILL_TILE
+} MetaImageFillType;
+
+typedef enum
+{
+  META_COLOR_SPEC_BASIC,
+  META_COLOR_SPEC_GTK,
+  META_COLOR_SPEC_BLEND,
+  META_COLOR_SPEC_SHADE
+} MetaColorSpecType;
+
+typedef enum
+{
+  META_GTK_COLOR_FG,
+  META_GTK_COLOR_BG,
+  META_GTK_COLOR_LIGHT,
+  META_GTK_COLOR_DARK,
+  META_GTK_COLOR_MID,
+  META_GTK_COLOR_TEXT,
+  META_GTK_COLOR_BASE,
+  META_GTK_COLOR_TEXT_AA,
+  META_GTK_COLOR_LAST
+} MetaGtkColorComponent;
+
+struct _MetaColorSpec
+{
+  MetaColorSpecType type;
+  union
+  {
+    struct {
+      GdkColor color;
+    } basic;
+    struct {
+      MetaGtkColorComponent component;
+      GtkStateType state;
+    } gtk;
+    struct {
+      MetaColorSpec *foreground;
+      MetaColorSpec *background;
+      double alpha;
+
+      GdkColor color;
+    } blend;
+    struct {
+      MetaColorSpec *base;
+      double factor;
+
+      GdkColor color;
+    } shade;
+  } data;
+};
+
+struct _MetaGradientSpec
+{
+  MetaGradientType type;
+  GSList *color_specs;
+};
+
+struct _MetaAlphaGradientSpec
+{
+  MetaGradientType type;
+  unsigned char *alphas;
+  int n_alphas;
+};
+
+struct _MetaDrawInfo
+{
+  GdkPixbuf   *mini_icon;
+  GdkPixbuf   *icon;
+  PangoLayout *title_layout;
+  int title_layout_width;
+  int title_layout_height;
+  const MetaFrameGeometry *fgeom;
+};
+
+/**
+ * A drawing operation in our simple vector drawing language.
+ */
+typedef enum
+{
+  /** Basic drawing-- line */
+  META_DRAW_LINE,
+  /** Basic drawing-- rectangle */
+  META_DRAW_RECTANGLE,
+  /** Basic drawing-- arc */
+  META_DRAW_ARC,
+
+  /** Clip to a rectangle */
+  META_DRAW_CLIP,
+  
+  /* Texture thingies */
+
+  /** Just a filled rectangle with alpha */
+  META_DRAW_TINT,
+  META_DRAW_GRADIENT,
+  META_DRAW_IMAGE,
+  
+  /** GTK theme engine stuff */
+  META_DRAW_GTK_ARROW,
+  META_DRAW_GTK_BOX,
+  META_DRAW_GTK_VLINE,
+
+  /** App's window icon */
+  META_DRAW_ICON,
+  /** App's window title */
+  META_DRAW_TITLE,
+  /** a draw op list */
+  META_DRAW_OP_LIST,
+  /** tiled draw op list */
+  META_DRAW_TILE
+} MetaDrawType;
+
+typedef enum
+{
+  POS_TOKEN_INT,
+  POS_TOKEN_DOUBLE,
+  POS_TOKEN_OPERATOR,
+  POS_TOKEN_VARIABLE,
+  POS_TOKEN_OPEN_PAREN,
+  POS_TOKEN_CLOSE_PAREN
+} PosTokenType;
+
+typedef enum
+{
+  POS_OP_NONE,
+  POS_OP_ADD,
+  POS_OP_SUBTRACT,
+  POS_OP_MULTIPLY,
+  POS_OP_DIVIDE,
+  POS_OP_MOD,
+  POS_OP_MAX,
+  POS_OP_MIN
+} PosOperatorType;
+
+/**
+ * A token, as output by the tokeniser.
+ *
+ * \ingroup tokenizer
+ */
+typedef struct
+{
+  PosTokenType type;
+
+  union
+  {
+    struct {
+      int val;
+    } i;
+
+    struct {
+      double val;
+    } d;
+
+    struct {
+      PosOperatorType op;
+    } o;
+
+    struct {
+      char *name;
+      GQuark name_quark;
+    } v;
+
+  } d;
+} PosToken;
+
+/**
+ * A computed expression in our simple vector drawing language.
+ * While it appears to take the form of a tree, this is actually
+ * merely a list; concerns such as precedence of operators are
+ * currently recomputed on every recalculation.
+ *
+ * Created by meta_draw_spec_new(), destroyed by meta_draw_spec_free().
+ * pos_eval() fills this with ...FIXME. Are tokens a tree or a list?
+ * \ingroup parser
+ */
+typedef struct _MetaDrawSpec
+{
+  /**
+   * If this spec is constant, this is the value of the constant;
+   * otherwise it is zero.
+   */
+  int value;
+  
+  /** A list of tokens in the expression. */
+  PosToken *tokens;
+
+  /** How many tokens are in the tokens list. */
+  int n_tokens;
+
+  /** Does the expression contain any variables? */
+  gboolean constant : 1;
+} MetaDrawSpec;
+
+/**
+ * A single drawing operation in our simple vector drawing language.
+ */
+struct _MetaDrawOp
+{
+  MetaDrawType type;
+
+  /* Positions are strings because they can be expressions */
+  union
+  {
+    struct {
+      MetaColorSpec *color_spec;
+      int dash_on_length;
+      int dash_off_length;
+      int width;
+      MetaDrawSpec *x1;
+      MetaDrawSpec *y1;
+      MetaDrawSpec *x2;
+      MetaDrawSpec *y2;
+    } line;
+
+    struct {
+      MetaColorSpec *color_spec;
+      gboolean filled;
+      MetaDrawSpec *x;
+      MetaDrawSpec *y;
+      MetaDrawSpec *width;
+      MetaDrawSpec *height;
+    } rectangle;
+
+    struct {
+      MetaColorSpec *color_spec;
+      gboolean filled;
+      MetaDrawSpec *x;
+      MetaDrawSpec *y;
+      MetaDrawSpec *width;
+      MetaDrawSpec *height;
+      double start_angle;
+      double extent_angle;
+    } arc;
+
+    struct {
+      MetaDrawSpec *x;
+      MetaDrawSpec *y;
+      MetaDrawSpec *width;
+      MetaDrawSpec *height;
+    } clip;
+    
+    struct {
+      MetaColorSpec *color_spec;
+      MetaAlphaGradientSpec *alpha_spec;
+      MetaDrawSpec *x;
+      MetaDrawSpec *y;
+      MetaDrawSpec *width;
+      MetaDrawSpec *height;
+    } tint;
+
+    struct {
+      MetaGradientSpec *gradient_spec;
+      MetaAlphaGradientSpec *alpha_spec;
+      MetaDrawSpec *x;
+      MetaDrawSpec *y;
+      MetaDrawSpec *width;
+      MetaDrawSpec *height;
+    } gradient;
+
+    struct {
+      MetaColorSpec *colorize_spec;
+      MetaAlphaGradientSpec *alpha_spec;
+      GdkPixbuf *pixbuf;
+      MetaDrawSpec *x;
+      MetaDrawSpec *y;
+      MetaDrawSpec *width;
+      MetaDrawSpec *height;
+
+      guint32 colorize_cache_pixel;
+      GdkPixbuf *colorize_cache_pixbuf;
+      MetaImageFillType fill_type;
+      unsigned int vertical_stripes : 1;
+      unsigned int horizontal_stripes : 1;
+    } image;
+    
+    struct {
+      GtkStateType state;
+      GtkShadowType shadow;
+      GtkArrowType arrow;
+      gboolean filled;
+
+      MetaDrawSpec *x;
+      MetaDrawSpec *y;
+      MetaDrawSpec *width;
+      MetaDrawSpec *height;
+    } gtk_arrow;
+
+    struct {
+      GtkStateType state;
+      GtkShadowType shadow;
+      MetaDrawSpec *x;
+      MetaDrawSpec *y;
+      MetaDrawSpec *width;
+      MetaDrawSpec *height;
+    } gtk_box;
+
+    struct {
+      GtkStateType state;
+      MetaDrawSpec *x;
+      MetaDrawSpec *y1;
+      MetaDrawSpec *y2;  
+    } gtk_vline;
+
+    struct {
+      MetaAlphaGradientSpec *alpha_spec;
+      MetaDrawSpec *x;
+      MetaDrawSpec *y;
+      MetaDrawSpec *width;
+      MetaDrawSpec *height;
+      MetaImageFillType fill_type;
+    } icon;
+
+    struct {
+      MetaColorSpec *color_spec;
+      MetaDrawSpec *x;
+      MetaDrawSpec *y;
+      MetaDrawSpec *ellipsize_width;
+    } title;
+
+    struct {
+      MetaDrawOpList *op_list;
+      MetaDrawSpec *x;
+      MetaDrawSpec *y;
+      MetaDrawSpec *width;
+      MetaDrawSpec *height;
+    } op_list;
+
+    struct {
+      MetaDrawOpList *op_list;
+      MetaDrawSpec *x;
+      MetaDrawSpec *y;
+      MetaDrawSpec *width;
+      MetaDrawSpec *height;
+      MetaDrawSpec *tile_xoffset;
+      MetaDrawSpec *tile_yoffset;
+      MetaDrawSpec *tile_width;
+      MetaDrawSpec *tile_height;
+    } tile;
+    
+  } data;
+};
+
+/**
+ * A list of MetaDrawOp objects. Maintains a reference count.
+ * Grows as necessary and allows the allocation of unused spaces
+ * to keep reallocations to a minimum.
+ *
+ * \bug Do we really win anything from not using the equivalent
+ * GLib structures?
+ */
+struct _MetaDrawOpList
+{
+  int refcount;
+  MetaDrawOp **ops;
+  int n_ops;
+  int n_allocated;
+};
+
+typedef enum
+{
+  META_BUTTON_STATE_NORMAL,
+  META_BUTTON_STATE_PRESSED,
+  META_BUTTON_STATE_PRELIGHT,
+  META_BUTTON_STATE_LAST
+} MetaButtonState;
+
+typedef enum
+{
+  /* Ordered so that background is drawn first */
+  META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND,
+  META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND,
+  META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND,
+  META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND,
+  META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND,
+  META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND,
+  META_BUTTON_TYPE_CLOSE,
+  META_BUTTON_TYPE_MAXIMIZE,
+  META_BUTTON_TYPE_MINIMIZE,
+  META_BUTTON_TYPE_MENU,
+  META_BUTTON_TYPE_SHADE,
+  META_BUTTON_TYPE_ABOVE,
+  META_BUTTON_TYPE_STICK,
+  META_BUTTON_TYPE_UNSHADE,
+  META_BUTTON_TYPE_UNABOVE,
+  META_BUTTON_TYPE_UNSTICK,
+  META_BUTTON_TYPE_LAST
+} MetaButtonType;
+
+typedef enum
+{
+  META_MENU_ICON_TYPE_CLOSE,
+  META_MENU_ICON_TYPE_MAXIMIZE,
+  META_MENU_ICON_TYPE_UNMAXIMIZE,
+  META_MENU_ICON_TYPE_MINIMIZE,
+  META_MENU_ICON_TYPE_LAST
+} MetaMenuIconType;
+
+typedef enum
+{
+  /* Listed in the order in which the textures are drawn.
+   * (though this only matters for overlaps of course.)
+   * Buttons are drawn after the frame textures.
+   *
+   * On the corners, horizontal pieces are arbitrarily given the
+   * corner area:
+   *
+   *   =====                 |====
+   *   |                     |
+   *   |       rather than   |
+   *
+   */
+  
+  /* entire frame */
+  META_FRAME_PIECE_ENTIRE_BACKGROUND,
+  /* entire titlebar background */
+  META_FRAME_PIECE_TITLEBAR,
+  /* portion of the titlebar background inside the titlebar
+   * background edges
+   */
+  META_FRAME_PIECE_TITLEBAR_MIDDLE,
+  /* left end of titlebar */
+  META_FRAME_PIECE_LEFT_TITLEBAR_EDGE,
+  /* right end of titlebar */
+  META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE,
+  /* top edge of titlebar */
+  META_FRAME_PIECE_TOP_TITLEBAR_EDGE,
+  /* bottom edge of titlebar */
+  META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE,
+  /* render over title background (text area) */
+  META_FRAME_PIECE_TITLE,
+  /* left edge of the frame */
+  META_FRAME_PIECE_LEFT_EDGE,
+  /* right edge of the frame */
+  META_FRAME_PIECE_RIGHT_EDGE,
+  /* bottom edge of the frame */
+  META_FRAME_PIECE_BOTTOM_EDGE,
+  /* place over entire frame, after drawing everything else */
+  META_FRAME_PIECE_OVERLAY,
+  /* Used to get size of the enum */
+  META_FRAME_PIECE_LAST
+} MetaFramePiece;
+
+#define N_GTK_STATES 5
+
+/**
+ * How to draw a frame in a particular state (say, a focussed, non-maximised,
+ * resizable frame). This corresponds closely to the <frame_style> tag
+ * in a theme file.
+ */
+struct _MetaFrameStyle
+{
+  /** Reference count. */
+  int refcount;
+  /**
+   * Parent style.
+   * Settings which are unspecified here will be taken from there.
+   */
+  MetaFrameStyle *parent;
+  /** Operations for drawing each kind of button in each state. */
+  MetaDrawOpList *buttons[META_BUTTON_TYPE_LAST][META_BUTTON_STATE_LAST];
+  /** Operations for drawing each piece of the frame. */
+  MetaDrawOpList *pieces[META_FRAME_PIECE_LAST];
+  /**
+   * Details such as the height and width of each edge, the corner rounding,
+   * and the aspect ratio of the buttons.
+   */
+  MetaFrameLayout *layout;
+  /**
+   * Background colour of the window. Only present in theme formats
+   * 2 and above. Can be NULL to use the standard GTK theme engine.
+   */
+  MetaColorSpec *window_background_color;
+  /**
+   * Transparency of the window background. 0=transparent; 255=opaque.
+   */
+  guint8 window_background_alpha;
+};
+
+/* Kinds of frame...
+ * 
+ *  normal ->   noresize / vert only / horz only / both
+ *              focused / unfocused
+ *  max    ->   focused / unfocused
+ *  shaded ->   focused / unfocused
+ *  max/shaded -> focused / unfocused
+ *
+ *  so 4 states with 8 sub-states in one, 2 sub-states in the other 3,
+ *  meaning 14 total
+ *
+ * 14 window states times 7 or 8 window types. Except some
+ * window types never get a frame so that narrows it down a bit.
+ * 
+ */
+typedef enum
+{
+  META_FRAME_STATE_NORMAL,
+  META_FRAME_STATE_MAXIMIZED,
+  META_FRAME_STATE_SHADED,
+  META_FRAME_STATE_MAXIMIZED_AND_SHADED,
+  META_FRAME_STATE_LAST
+} MetaFrameState;
+
+typedef enum
+{
+  META_FRAME_RESIZE_NONE,
+  META_FRAME_RESIZE_VERTICAL,
+  META_FRAME_RESIZE_HORIZONTAL,
+  META_FRAME_RESIZE_BOTH,
+  META_FRAME_RESIZE_LAST
+} MetaFrameResize;
+
+typedef enum
+{
+  META_FRAME_FOCUS_NO,
+  META_FRAME_FOCUS_YES,
+  META_FRAME_FOCUS_LAST
+} MetaFrameFocus;
+
+/**
+ * How to draw frames at different times: when it's maximised or not, shaded
+ * or not, when it's focussed or not, and (for non-maximised windows), when
+ * it can be horizontally or vertically resized, both, or neither.
+ * Not all window types actually get a frame.
+ *
+ * A theme contains one of these objects for each type of window (each
+ * MetaFrameType), that is, normal, dialogue (modal and non-modal), etc.
+ *
+ * This corresponds closely to the <frame_style_set> tag in a theme file.
+ */
+struct _MetaFrameStyleSet
+{
+  int refcount;
+  MetaFrameStyleSet *parent;
+  MetaFrameStyle *normal_styles[META_FRAME_RESIZE_LAST][META_FRAME_FOCUS_LAST];
+  MetaFrameStyle *maximized_styles[META_FRAME_FOCUS_LAST];
+  MetaFrameStyle *shaded_styles[META_FRAME_RESIZE_LAST][META_FRAME_FOCUS_LAST];
+  MetaFrameStyle *maximized_and_shaded_styles[META_FRAME_FOCUS_LAST];
+};
+
+/**
+ * A theme. This is a singleton class which groups all settings from a theme
+ * on disk together.
+ *
+ * \bug It is rather useless to keep the metadata fields in core, I think.
+ */
+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;
+
+  /** Symbol table of integer constants. */
+  GHashTable *integer_constants;
+  /** Symbol table of float constants. */
+  GHashTable *float_constants;
+  /**
+   * Symbol table of colour constants (hex triples, and triples
+   * plus alpha).
+   * */
+  GHashTable *color_constants;
+  GHashTable *images_by_filename;
+  GHashTable *layouts_by_name;
+  GHashTable *draw_op_lists_by_name;
+  GHashTable *styles_by_name;
+  GHashTable *style_sets_by_name;
+  MetaFrameStyleSet *style_sets_by_type[META_FRAME_TYPE_LAST];
+
+  GQuark quark_width;
+  GQuark quark_height;
+  GQuark quark_object_width;
+  GQuark quark_object_height;
+  GQuark quark_left_width;
+  GQuark quark_right_width;
+  GQuark quark_top_height;
+  GQuark quark_bottom_height;
+  GQuark quark_mini_icon_width;
+  GQuark quark_mini_icon_height;
+  GQuark quark_icon_width;
+  GQuark quark_icon_height;
+  GQuark quark_title_width;
+  GQuark quark_title_height;
+  GQuark quark_frame_x_center;
+  GQuark quark_frame_y_center;
+};
+
+struct _MetaPositionExprEnv
+{
+  MetaRectangle rect;
+  /* size of an object being drawn, if it has a natural size */
+  int object_width;
+  int object_height;
+  /* global object sizes, always available */
+  int left_width;
+  int right_width;
+  int top_height;
+  int bottom_height;
+  int title_width;
+  int title_height;
+  int frame_x_center;
+  int frame_y_center;
+  int mini_icon_width;
+  int mini_icon_height;
+  int icon_width;
+  int icon_height;
+  /* Theme so we can look up constants */
+  MetaTheme *theme;
+};
+
+MetaFrameLayout* meta_frame_layout_new           (void);
+MetaFrameLayout* meta_frame_layout_copy          (const MetaFrameLayout *src);
+void             meta_frame_layout_ref           (MetaFrameLayout       *layout);
+void             meta_frame_layout_unref         (MetaFrameLayout       *layout);
+void             meta_frame_layout_get_borders   (const MetaFrameLayout *layout,
+                                                  int                    text_height,
+                                                  MetaFrameFlags         flags,
+                                                  int                   *top_height,
+                                                  int                   *bottom_height,
+                                                  int                   *left_width,
+                                                  int                   *right_width);
+void             meta_frame_layout_calc_geometry (const MetaFrameLayout  *layout,
+                                                  int                     text_height,
+                                                  MetaFrameFlags          flags,
+                                                  int                     client_width,
+                                                  int                     client_height,
+                                                  const MetaButtonLayout *button_layout,
+                                                  MetaFrameGeometry      *fgeom,
+                                                  MetaTheme              *theme);
+
+gboolean         meta_frame_layout_validate      (const MetaFrameLayout *layout,
+                                                  GError               **error);
+
+gboolean meta_parse_position_expression (MetaDrawSpec               *spec,
+                                         const MetaPositionExprEnv  *env,
+                                         int                        *x_return,
+                                         int                        *y_return,
+                                         GError                    **err);
+gboolean meta_parse_size_expression     (MetaDrawSpec               *spec,
+                                         const MetaPositionExprEnv  *env,
+                                         int                        *val_return,
+                                         GError                    **err);
+
+MetaDrawSpec* meta_draw_spec_new (MetaTheme  *theme,
+                                  const char *expr,
+                                  GError    **error);
+void          meta_draw_spec_free (MetaDrawSpec *spec);
+
+MetaColorSpec* meta_color_spec_new             (MetaColorSpecType  type);
+MetaColorSpec* meta_color_spec_new_from_string (const char        *str,
+                                                GError           **err);
+MetaColorSpec* meta_color_spec_new_gtk         (MetaGtkColorComponent component,
+                                                GtkStateType          state);
+void           meta_color_spec_free            (MetaColorSpec     *spec);
+void           meta_color_spec_render          (MetaColorSpec     *spec,
+                                                GtkWidget         *widget,
+                                                GdkColor          *color);
+
+
+MetaDrawOp*    meta_draw_op_new  (MetaDrawType        type);
+void           meta_draw_op_free (MetaDrawOp          *op);
+void           meta_draw_op_draw (const MetaDrawOp    *op,
+                                  GtkWidget           *widget,
+                                  GdkDrawable         *drawable,
+                                  const GdkRectangle  *clip,
+                                  const MetaDrawInfo  *info,
+                                  /* logical region being drawn */
+                                  MetaRectangle        logical_region);
+
+void           meta_draw_op_draw_with_style (const MetaDrawOp    *op,
+                                             GtkStyle            *style_gtk,
+                                             GtkWidget           *widget,
+                                             GdkDrawable         *drawable,
+                                             const GdkRectangle  *clip,
+                                             const MetaDrawInfo  *info,
+                                             /* logical region being drawn */
+                                             MetaRectangle        logical_region);
+
+MetaDrawOpList* meta_draw_op_list_new   (int                   n_preallocs);
+void            meta_draw_op_list_ref   (MetaDrawOpList       *op_list);
+void            meta_draw_op_list_unref (MetaDrawOpList       *op_list);
+void            meta_draw_op_list_draw  (const MetaDrawOpList *op_list,
+                                         GtkWidget            *widget,
+                                         GdkDrawable          *drawable,
+                                         const GdkRectangle   *clip,
+                                         const MetaDrawInfo   *info,
+                                         MetaRectangle         rect);
+void            meta_draw_op_list_draw_with_style  (const MetaDrawOpList *op_list,
+                                                    GtkStyle             *style_gtk,
+                                                    GtkWidget            *widget,
+                                                    GdkDrawable          *drawable,
+                                                    const GdkRectangle   *clip,
+                                                    const MetaDrawInfo   *info,
+                                                    MetaRectangle         rect);
+void           meta_draw_op_list_append (MetaDrawOpList       *op_list,
+                                         MetaDrawOp           *op);
+gboolean       meta_draw_op_list_validate (MetaDrawOpList    *op_list,
+                                           GError           **error);
+gboolean       meta_draw_op_list_contains (MetaDrawOpList    *op_list,
+                                           MetaDrawOpList    *child);
+
+MetaGradientSpec* meta_gradient_spec_new    (MetaGradientType        type);
+void              meta_gradient_spec_free   (MetaGradientSpec       *desc);
+GdkPixbuf*        meta_gradient_spec_render (const MetaGradientSpec *desc,
+                                             GtkWidget              *widget,
+                                             int                     width,
+                                             int                     height);
+gboolean          meta_gradient_spec_validate (MetaGradientSpec     *spec,
+                                               GError              **error);
+
+MetaAlphaGradientSpec* meta_alpha_gradient_spec_new  (MetaGradientType       type,
+                                                      int                    n_alphas);
+void                   meta_alpha_gradient_spec_free (MetaAlphaGradientSpec *spec);
+
+
+MetaFrameStyle* meta_frame_style_new   (MetaFrameStyle *parent);
+void            meta_frame_style_ref   (MetaFrameStyle *style);
+void            meta_frame_style_unref (MetaFrameStyle *style);
+
+void meta_frame_style_draw (MetaFrameStyle          *style,
+                            GtkWidget               *widget,
+                            GdkDrawable             *drawable,
+                            int                      x_offset,
+                            int                      y_offset,
+                            const GdkRectangle      *clip,
+                            const MetaFrameGeometry *fgeom,
+                            int                      client_width,
+                            int                      client_height,
+                            PangoLayout             *title_layout,
+                            int                      text_height,
+                            MetaButtonState          button_states[META_BUTTON_TYPE_LAST],
+                            GdkPixbuf               *mini_icon,
+                            GdkPixbuf               *icon);
+
+
+void meta_frame_style_draw_with_style (MetaFrameStyle          *style,
+                                       GtkStyle                *style_gtk,
+                                       GtkWidget               *widget,
+                                       GdkDrawable             *drawable,
+                                       int                      x_offset,
+                                       int                      y_offset,
+                                       const GdkRectangle      *clip,
+                                       const MetaFrameGeometry *fgeom,
+                                       int                      client_width,
+                                       int                      client_height,
+                                       PangoLayout             *title_layout,
+                                       int                      text_height,
+                                       MetaButtonState          button_states[META_BUTTON_TYPE_LAST],
+                                       GdkPixbuf               *mini_icon,
+                                       GdkPixbuf               *icon);
+
+
+gboolean       meta_frame_style_validate (MetaFrameStyle    *style,
+                                          guint              current_theme_version,
+                                          GError           **error);
+
+MetaFrameStyleSet* meta_frame_style_set_new   (MetaFrameStyleSet *parent);
+void               meta_frame_style_set_ref   (MetaFrameStyleSet *style_set);
+void               meta_frame_style_set_unref (MetaFrameStyleSet *style_set);
+
+gboolean       meta_frame_style_set_validate  (MetaFrameStyleSet *style_set,
+                                               GError           **error);
+
+MetaTheme* meta_theme_get_current (void);
+void       meta_theme_set_current (const char *name,
+                                   gboolean    force_reload);
+
+MetaTheme* meta_theme_new      (void);
+void       meta_theme_free     (MetaTheme *theme);
+gboolean   meta_theme_validate (MetaTheme *theme,
+                                GError   **error);
+GdkPixbuf* meta_theme_load_image (MetaTheme  *theme,
+                                  const char *filename,
+                                  guint       size_of_theme_icons,
+                                  GError    **error);
+
+MetaFrameStyle* meta_theme_get_frame_style (MetaTheme     *theme,
+                                            MetaFrameType  type,
+                                            MetaFrameFlags flags);
+
+double meta_theme_get_title_scale (MetaTheme     *theme,
+                                   MetaFrameType  type,
+                                   MetaFrameFlags flags);
+
+void meta_theme_draw_frame (MetaTheme              *theme,
+                            GtkWidget              *widget,
+                            GdkDrawable            *drawable,
+                            const GdkRectangle     *clip,
+                            int                     x_offset,
+                            int                     y_offset,
+                            MetaFrameType           type,
+                            MetaFrameFlags          flags,
+                            int                     client_width,
+                            int                     client_height,
+                            PangoLayout            *title_layout,
+                            int                     text_height,
+                            const MetaButtonLayout *button_layout,
+                            MetaButtonState         button_states[META_BUTTON_TYPE_LAST],
+                            GdkPixbuf              *mini_icon,
+                            GdkPixbuf              *icon);
+
+void meta_theme_draw_frame_by_name (MetaTheme              *theme,
+                                    GtkWidget              *widget,
+                                    GdkDrawable            *drawable,
+                                    const GdkRectangle     *clip,
+                                    int                     x_offset,
+                                    int                     y_offset,
+                                    const gchar             *style_name,
+                                    MetaFrameFlags          flags,
+                                    int                     client_width,
+                                    int                     client_height,
+                                    PangoLayout            *title_layout,
+                                    int                     text_height,
+                                    const MetaButtonLayout *button_layout,
+                                    MetaButtonState         button_states[META_BUTTON_TYPE_LAST],
+                                    GdkPixbuf              *mini_icon,
+                                    GdkPixbuf              *icon);
+
+void meta_theme_draw_frame_with_style (MetaTheme              *theme,
+                                       GtkStyle               *style_gtk,
+                                       GtkWidget              *widget,
+                                       GdkDrawable            *drawable,
+                                       const GdkRectangle     *clip,
+                                       int                     x_offset,
+                                       int                     y_offset,
+                                       MetaFrameType           type,
+                                       MetaFrameFlags          flags,
+                                       int                     client_width,
+                                       int                     client_height,
+                                       PangoLayout            *title_layout,
+                                       int                     text_height,
+                                       const MetaButtonLayout *button_layout,
+                                       MetaButtonState         button_states[META_BUTTON_TYPE_LAST],
+                                       GdkPixbuf              *mini_icon,
+                                       GdkPixbuf              *icon);
+
+void meta_theme_get_frame_borders (MetaTheme         *theme,
+                                   MetaFrameType      type,
+                                   int                text_height,
+                                   MetaFrameFlags     flags,
+                                   int               *top_height,
+                                   int               *bottom_height,
+                                   int               *left_width,
+                                   int               *right_width);
+void meta_theme_calc_geometry (MetaTheme              *theme,
+                               MetaFrameType           type,
+                               int                     text_height,
+                               MetaFrameFlags          flags,
+                               int                     client_width,
+                               int                     client_height,
+                               const MetaButtonLayout *button_layout,
+                               MetaFrameGeometry      *fgeom);
+                                   
+MetaFrameLayout*   meta_theme_lookup_layout       (MetaTheme         *theme,
+                                                   const char        *name);
+void               meta_theme_insert_layout       (MetaTheme         *theme,
+                                                   const char        *name,
+                                                   MetaFrameLayout   *layout);
+MetaDrawOpList*    meta_theme_lookup_draw_op_list (MetaTheme         *theme,
+                                                   const char        *name);
+void               meta_theme_insert_draw_op_list (MetaTheme         *theme,
+                                                   const char        *name,
+                                                   MetaDrawOpList    *op_list);
+MetaFrameStyle*    meta_theme_lookup_style        (MetaTheme         *theme,
+                                                   const char        *name);
+void               meta_theme_insert_style        (MetaTheme         *theme,
+                                                   const char        *name,
+                                                   MetaFrameStyle    *style);
+MetaFrameStyleSet* meta_theme_lookup_style_set    (MetaTheme         *theme,
+                                                   const char        *name);
+void               meta_theme_insert_style_set    (MetaTheme         *theme,
+                                                   const char        *name,
+                                                   MetaFrameStyleSet *style_set);
+gboolean meta_theme_define_int_constant   (MetaTheme   *theme,
+                                           const char  *name,
+                                           int          value,
+                                           GError     **error);
+gboolean meta_theme_lookup_int_constant   (MetaTheme   *theme,
+                                           const char  *name,
+                                           int         *value);
+gboolean meta_theme_define_float_constant (MetaTheme   *theme,
+                                           const char  *name,
+                                           double       value,
+                                           GError     **error);
+gboolean meta_theme_lookup_float_constant (MetaTheme   *theme,
+                                           const char  *name,
+                                           double      *value);
+
+gboolean meta_theme_define_color_constant (MetaTheme   *theme,
+                                           const char  *name,
+                                           const char  *value,
+                                           GError     **error);
+gboolean meta_theme_lookup_color_constant (MetaTheme   *theme,
+                                           const char  *name,
+                                           char       **value);
+
+gboolean     meta_theme_replace_constants     (MetaTheme    *theme,
+                                               PosToken     *tokens,
+                                               int           n_tokens,
+                                               GError      **err);
+
+/* random stuff */
+
+PangoFontDescription* meta_gtk_widget_get_font_desc        (GtkWidget            *widget,
+                                                            double                scale,
+							    const PangoFontDescription *override);
+int                   meta_pango_font_desc_get_text_height (const PangoFontDescription *font_desc,
+                                                            PangoContext         *context);
+
+
+/* Enum converters */
+MetaGtkColorComponent meta_color_component_from_string (const char            *str);
+const char*           meta_color_component_to_string   (MetaGtkColorComponent  component);
+MetaButtonState       meta_button_state_from_string    (const char            *str);
+const char*           meta_button_state_to_string      (MetaButtonState        state);
+MetaButtonType        meta_button_type_from_string     (const char            *str,
+                                                        MetaTheme             *theme);
+const char*           meta_button_type_to_string       (MetaButtonType         type);
+MetaFramePiece        meta_frame_piece_from_string     (const char            *str);
+const char*           meta_frame_piece_to_string       (MetaFramePiece         piece);
+MetaFrameState        meta_frame_state_from_string     (const char            *str);
+const char*           meta_frame_state_to_string       (MetaFrameState         state);
+MetaFrameResize       meta_frame_resize_from_string    (const char            *str);
+const char*           meta_frame_resize_to_string      (MetaFrameResize        resize);
+MetaFrameFocus        meta_frame_focus_from_string     (const char            *str);
+const char*           meta_frame_focus_to_string       (MetaFrameFocus         focus);
+MetaFrameType         meta_frame_type_from_string      (const char            *str);
+const char*           meta_frame_type_to_string        (MetaFrameType          type);
+MetaGradientType      meta_gradient_type_from_string   (const char            *str);
+const char*           meta_gradient_type_to_string     (MetaGradientType       type);
+GtkStateType          meta_gtk_state_from_string       (const char            *str);
+const char*           meta_gtk_state_to_string         (GtkStateType           state);
+GtkShadowType         meta_gtk_shadow_from_string      (const char            *str);
+const char*           meta_gtk_shadow_to_string        (GtkShadowType          shadow);
+GtkArrowType          meta_gtk_arrow_from_string       (const char            *str);
+const char*           meta_gtk_arrow_to_string         (GtkArrowType           arrow);
+MetaImageFillType     meta_image_fill_type_from_string (const char            *str);
+const char*           meta_image_fill_type_to_string   (MetaImageFillType      fill_type);
+
+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
+
+#endif



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]