[metacity] libmetacity: add meta-draw-spec.[c/h]



commit de5f3fbd10d3e8b44a0be5723272c0d35bc7ff8b
Author: Alberts Muktupāvels <alberts muktupavels gmail com>
Date:   Fri Jan 29 01:08:44 2016 +0200

    libmetacity: add meta-draw-spec.[c/h]

 libmetacity/Makefile.am      |    3 +
 libmetacity/meta-draw-spec.c | 1272 +++++++++++++++++++++++++++++++++++++++++
 libmetacity/meta-draw-spec.h |   68 +++
 po/POTFILES.in               |    1 +
 src/ui/theme-parser.c        |  120 ++--
 src/ui/theme-private.h       |  110 +----
 src/ui/theme.c               | 1297 ++----------------------------------------
 7 files changed, 1466 insertions(+), 1405 deletions(-)
---
diff --git a/libmetacity/Makefile.am b/libmetacity/Makefile.am
index 5c2a89b..54dde93 100644
--- a/libmetacity/Makefile.am
+++ b/libmetacity/Makefile.am
@@ -12,6 +12,8 @@ libmetacity_la_SOURCES = \
        meta-color-private.h \
        meta-color-spec.c \
        meta-color-spec.h \
+       meta-draw-spec.c \
+       meta-draw-spec.h \
        meta-frame-borders.c \
        meta-frame-borders.h \
        meta-frame-flags.h \
@@ -61,6 +63,7 @@ libmetacity_include_HEADERS = \
        meta-button-layout.h \
        meta-color.h \
        meta-color-spec.h \
+       meta-draw-spec.h \
        meta-frame-borders.h \
        meta-frame-flags.h \
        meta-frame-type.h \
diff --git a/libmetacity/meta-draw-spec.c b/libmetacity/meta-draw-spec.c
new file mode 100644
index 0000000..f9b1a08
--- /dev/null
+++ b/libmetacity/meta-draw-spec.c
@@ -0,0 +1,1272 @@
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n-lib.h>
+#include <stdlib.h>
+
+#include "meta-draw-spec.h"
+#include "meta-theme.h"
+
+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
+ */
+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;
+};
+
+/**
+ * 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;
+
+/**
+ * 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;
+}
+
+/**
+ * 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 (strncmp (p, "`max`", 5) == 0)
+        {
+          *len = 5;
+          return POS_OP_MAX;
+        }
+      else if (strncmp (p, "`min`", 5) == 0)
+        {
+          *len = 5;
+          return POS_OP_MIN;
+        }
+
+    default:
+      break;
+    }
+
+  return POS_OP_NONE;
+}
+
+/**
+ * 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 (g_ascii_isalpha (*p) || *p == '_')
+            {
+              /* Assume variable */
+              const char *start = p;
+              while (*p && (g_ascii_isalpha (*p) || *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;
+}
+
+/* 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.
+ */
+static gboolean
+replace_constants (MetaThemeMetacity  *metacity,
+                   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_metacity_lookup_int (metacity, t->d.v.name, &ival))
+            {
+              g_free (t->d.v.name);
+              t->type = POS_TOKEN_INT;
+              t->d.i.val = ival;
+            }
+          else if (meta_theme_metacity_lookup_float (metacity, t->d.v.name, &dval))
+            {
+              g_free (t->d.v.name);
+              t->type = POS_TOKEN_DOUBLE;
+              t->d.d.val = dval;
+            }
+          else
+            {
+              /* If we've found a variable that cannot be replaced then the
+                 expression is not a constant expression and we want to
+                 replace it with a GQuark */
+
+              t->d.v.name_quark = g_quark_from_string (t->d.v.name);
+              is_constant = FALSE;
+            }
+        }
+    }
+
+  return is_constant;
+}
+
+/**
+ * pos_eval_get_variable:
+ * @token: The token representing a variable
+ * @result: (out): The value of that variable; not set if the token did not
+ *     represent a known variable
+ * @env: The environment within which @token should be evaluated
+ * @err: (out): Set to the problem if there was a problem
+ *
+ * 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.)
+ *
+ * Returns: %TRUE if we found the variable asked for, %FALSE if we didn't
+ */
+static gboolean
+pos_eval_get_variable (const PosToken             *token,
+                       int                        *result,
+                       const MetaPositionExprEnv  *env,
+                       GError                    **err)
+{
+  GQuark quark;
+
+  quark = token->d.v.name_quark;
+
+  if (quark == g_quark_from_static_string ("width"))
+    {
+      *result = env->rect.width;
+    }
+  else if (quark == g_quark_from_static_string ("height"))
+    {
+      *result = env->rect.height;
+    }
+  else if (env->object_width >= 0 &&
+           quark == g_quark_from_static_string ("object_width"))
+    {
+      *result = env->object_width;
+    }
+  else if (env->object_height >= 0 &&
+           quark == g_quark_from_static_string ("object_height"))
+    {
+      *result = env->object_height;
+    }
+  else if (quark == g_quark_from_static_string ("left_width"))
+    {
+      *result = env->left_width;
+    }
+  else if (quark == g_quark_from_static_string ("right_width"))
+    {
+      *result = env->right_width;
+    }
+  else if (quark == g_quark_from_static_string ("top_height"))
+    {
+      *result = env->top_height;
+    }
+  else if (quark == g_quark_from_static_string ("bottom_height"))
+    {
+      *result = env->bottom_height;
+    }
+  else if (quark == g_quark_from_static_string ("mini_icon_width"))
+    {
+      *result = env->mini_icon_width;
+    }
+  else if (quark == g_quark_from_static_string ("mini_icon_height"))
+    {
+      *result = env->mini_icon_height;
+    }
+  else if (quark == g_quark_from_static_string ("icon_width"))
+    {
+      *result = env->icon_width;
+    }
+  else if (quark == g_quark_from_static_string ("icon_height"))
+    {
+      *result = env->icon_height;
+    }
+  else if (quark == g_quark_from_static_string ("title_width"))
+    {
+      *result = env->title_width;
+    }
+  else if (quark == g_quark_from_static_string ("title_height"))
+    {
+      *result = env->title_height;
+    }
+  else if (quark == g_quark_from_static_string ("frame_x_center"))
+    {
+      *result = env->frame_x_center;
+    }
+  else if (quark == g_quark_from_static_string ("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'"),
+                   token->d.v.name);
+
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+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:
+        default:
+          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:
+        default:
+          g_assert_not_reached ();
+          break;
+        }
+    }
+  else
+    g_assert_not_reached ();
+
+  return TRUE;
+}
+
+/**
+ * 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;
+    default:
+      break;
+    }
+
+  return "<unknown>";
+}
+
+static gboolean
+do_operations (PosExpr *exprs,
+               int     *n_exprs,
+               int      precedence,
+               GError **err)
+{
+  int i;
+
+  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;
+            default:
+              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;
+            default:
+              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;
+            default:
+              break;
+            }
+          break;
+        default:
+          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;
+}
+
+/**
+ * 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;
+
+            default:
+              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;
+
+            default:
+              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:
+        default:
+          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.
+ */
+static gboolean
+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;
+}
+
+static gboolean
+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;
+}
+
+MetaDrawSpec *
+meta_draw_spec_new (MetaThemeMetacity  *metacity,
+                    const gchar        *expr,
+                    GError            **error)
+{
+  MetaDrawSpec *spec;
+
+  spec = g_slice_new0 (MetaDrawSpec);
+
+  pos_tokenize (expr, &spec->tokens, &spec->n_tokens, NULL);
+
+  spec->constant = replace_constants (metacity, 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;
+}
+
+void
+meta_draw_spec_free (MetaDrawSpec *spec)
+{
+  if (!spec)
+    return;
+
+  free_tokens (spec->tokens, spec->n_tokens);
+  g_slice_free (MetaDrawSpec, spec);
+}
+
+gint
+meta_draw_spec_parse_x_position (MetaDrawSpec              *spec,
+                                 const MetaPositionExprEnv *env)
+{
+  int retval;
+  GError *error;
+
+  retval = 0;
+  error = NULL;
+  if (!parse_position_expression (spec, env, &retval, NULL, &error))
+    {
+      g_warning (_("Theme contained an expression that resulted in an error: %s"),
+                 error->message);
+
+      g_error_free (error);
+    }
+
+  return retval;
+}
+
+gint
+meta_draw_spec_parse_y_position (MetaDrawSpec              *spec,
+                                 const MetaPositionExprEnv *env)
+{
+  int retval;
+  GError *error;
+
+  retval = 0;
+  error = NULL;
+  if (!parse_position_expression (spec, env, NULL, &retval, &error))
+    {
+      g_warning (_("Theme contained an expression that resulted in an error: %s"),
+                 error->message);
+
+      g_error_free (error);
+    }
+
+  return retval;
+}
+
+gint
+meta_draw_spec_parse_size (MetaDrawSpec              *spec,
+                           const MetaPositionExprEnv *env)
+{
+  int retval;
+  GError *error;
+
+  retval = 0;
+  error = NULL;
+  if (!parse_size_expression (spec, env, &retval, &error))
+    {
+      g_warning (_("Theme contained an expression that resulted in an error: %s"),
+                 error->message);
+
+      g_error_free (error);
+    }
+
+  return retval;
+}
diff --git a/libmetacity/meta-draw-spec.h b/libmetacity/meta-draw-spec.h
new file mode 100644
index 0000000..5f7dfdd
--- /dev/null
+++ b/libmetacity/meta-draw-spec.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef META_DRAW_SPEC_H
+#define META_DRAW_SPEC_H
+
+#include <gdk/gdk.h>
+#include <libmetacity/meta-theme-metacity.h>
+
+G_BEGIN_DECLS
+
+typedef struct _MetaDrawSpec MetaDrawSpec;
+typedef struct _MetaPositionExprEnv MetaPositionExprEnv;
+
+struct _MetaPositionExprEnv
+{
+  GdkRectangle 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;
+};
+
+MetaDrawSpec *meta_draw_spec_new              (MetaThemeMetacity          *metacity,
+                                               const char                 *expr,
+                                               GError                    **error);
+
+void          meta_draw_spec_free             (MetaDrawSpec               *spec);
+
+gint          meta_draw_spec_parse_x_position (MetaDrawSpec               *spec,
+                                               const MetaPositionExprEnv  *env);
+
+gint          meta_draw_spec_parse_y_position (MetaDrawSpec               *spec,
+                                               const MetaPositionExprEnv  *env);
+
+gint          meta_draw_spec_parse_size       (MetaDrawSpec               *spec,
+                                               const MetaPositionExprEnv  *env);
+
+G_END_DECLS
+
+#endif
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e56b905..020b48d 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,6 +1,7 @@
 # List of source files containing translatable strings.
 # Please keep this file sorted alphabetically.
 libmetacity/meta-color-spec.c
+libmetacity/meta-draw-spec.c
 libmetacity/meta-gradient-spec.c
 libmetacity/meta-theme-metacity.c
 src/50-metacity-navigation.xml.in
diff --git a/src/ui/theme-parser.c b/src/ui/theme-parser.c
index faab4dd..48cea2e 100644
--- a/src/ui/theme-parser.c
+++ b/src/ui/theme-parser.c
@@ -1713,8 +1713,12 @@ parse_draw_op_element (GMarkupParseContext  *context,
                        ParseInfo            *info,
                        GError              **error)
 {
+  MetaThemeMetacity *metacity;
+
   g_return_if_fail (peek_state (info) == STATE_DRAW_OPS);
 
+  metacity = META_THEME_METACITY (info->theme->impl);
+
   if (ELEMENT_IS ("line"))
     {
       MetaDrawOp *op;
@@ -1771,18 +1775,18 @@ parse_draw_op_element (GMarkupParseContext  *context,
 
       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);
+      op->data.line.x1 = meta_draw_spec_new (metacity, x1, NULL);
+      op->data.line.y1 = meta_draw_spec_new (metacity, y1, NULL);
 
       if (strcmp(x1, x2)==0)
         op->data.line.x2 = NULL;
       else
-        op->data.line.x2 = meta_draw_spec_new (info->theme, x2, NULL);
+        op->data.line.x2 = meta_draw_spec_new (metacity, x2, NULL);
 
       if (strcmp(y1, y2)==0)
         op->data.line.y2 = NULL;
       else
-        op->data.line.y2 = meta_draw_spec_new (info->theme, y2, NULL);
+        op->data.line.y2 = meta_draw_spec_new (metacity, y2, NULL);
 
       op->data.line.width = width_val;
       op->data.line.dash_on_length = dash_on_val;
@@ -1832,10 +1836,10 @@ parse_draw_op_element (GMarkupParseContext  *context,
       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,
+      op->data.rectangle.x = meta_draw_spec_new (metacity, x, NULL);
+      op->data.rectangle.y = meta_draw_spec_new (metacity, y, NULL);
+      op->data.rectangle.width = meta_draw_spec_new (metacity, width, NULL);
+      op->data.rectangle.height = meta_draw_spec_new (metacity,
                                                       height, NULL);
 
       op->data.rectangle.filled = filled_val;
@@ -1954,10 +1958,10 @@ parse_draw_op_element (GMarkupParseContext  *context,
 
       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.x = meta_draw_spec_new (metacity, x, NULL);
+      op->data.arc.y = meta_draw_spec_new (metacity, y, NULL);
+      op->data.arc.width = meta_draw_spec_new (metacity, width, NULL);
+      op->data.arc.height = meta_draw_spec_new (metacity, height, NULL);
 
       op->data.arc.filled = filled_val;
       op->data.arc.start_angle = start_angle_val;
@@ -1986,10 +1990,10 @@ parse_draw_op_element (GMarkupParseContext  *context,
 
       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);
+      op->data.clip.x = meta_draw_spec_new (metacity, x, NULL);
+      op->data.clip.y = meta_draw_spec_new (metacity, y, NULL);
+      op->data.clip.width = meta_draw_spec_new (metacity, width, NULL);
+      op->data.clip.height = meta_draw_spec_new (metacity, height, NULL);
 
       g_assert (info->op_list);
 
@@ -2040,10 +2044,10 @@ parse_draw_op_element (GMarkupParseContext  *context,
       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);
+      op->data.tint.x = meta_draw_spec_new (metacity, x, NULL);
+      op->data.tint.y = meta_draw_spec_new (metacity, y, NULL);
+      op->data.tint.width = meta_draw_spec_new (metacity, width, NULL);
+      op->data.tint.height = meta_draw_spec_new (metacity, height, NULL);
 
       g_assert (info->op_list);
 
@@ -2087,11 +2091,11 @@ parse_draw_op_element (GMarkupParseContext  *context,
       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,
+      info->op->data.gradient.x = meta_draw_spec_new (metacity, x, NULL);
+      info->op->data.gradient.y = meta_draw_spec_new (metacity, y, NULL);
+      info->op->data.gradient.width = meta_draw_spec_new (metacity,
                                                         width, NULL);
-      info->op->data.gradient.height = meta_draw_spec_new (info->theme,
+      info->op->data.gradient.height = meta_draw_spec_new (metacity,
                                                          height, NULL);
 
       info->op->data.gradient.gradient_spec = meta_gradient_spec_new (type_val);
@@ -2183,10 +2187,10 @@ parse_draw_op_element (GMarkupParseContext  *context,
       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.x = meta_draw_spec_new (metacity, x, NULL);
+      op->data.image.y = meta_draw_spec_new (metacity, y, NULL);
+      op->data.image.width = meta_draw_spec_new (metacity, width, NULL);
+      op->data.image.height = meta_draw_spec_new (metacity, height, NULL);
 
       op->data.image.alpha_spec = alpha_spec;
       op->data.image.fill_type = fill_type_val;
@@ -2320,10 +2324,10 @@ parse_draw_op_element (GMarkupParseContext  *context,
 
       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,
+      op->data.gtk_arrow.x = meta_draw_spec_new (metacity, x, NULL);
+      op->data.gtk_arrow.y = meta_draw_spec_new (metacity, y, NULL);
+      op->data.gtk_arrow.width = meta_draw_spec_new (metacity, width, NULL);
+      op->data.gtk_arrow.height = meta_draw_spec_new (metacity,
                                                       height, NULL);
 
       op->data.gtk_arrow.filled = filled_val;
@@ -2379,10 +2383,10 @@ parse_draw_op_element (GMarkupParseContext  *context,
 
       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.x = meta_draw_spec_new (metacity, x, NULL);
+      op->data.gtk_box.y = meta_draw_spec_new (metacity, y, NULL);
+      op->data.gtk_box.width = meta_draw_spec_new (metacity, width, NULL);
+      op->data.gtk_box.height = meta_draw_spec_new (metacity, height, NULL);
 
       op->data.gtk_box.state = state_val;
       op->data.gtk_box.shadow = shadow_val;
@@ -2420,9 +2424,9 @@ parse_draw_op_element (GMarkupParseContext  *context,
 
       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.x = meta_draw_spec_new (metacity, x, NULL);
+      op->data.gtk_vline.y1 = meta_draw_spec_new (metacity, y1, NULL);
+      op->data.gtk_vline.y2 = meta_draw_spec_new (metacity, y2, NULL);
 
       op->data.gtk_vline.state = state_val;
 
@@ -2473,10 +2477,10 @@ parse_draw_op_element (GMarkupParseContext  *context,
 
       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.x = meta_draw_spec_new (metacity, x, NULL);
+      op->data.icon.y = meta_draw_spec_new (metacity, y, NULL);
+      op->data.icon.width = meta_draw_spec_new (metacity, width, NULL);
+      op->data.icon.height = meta_draw_spec_new (metacity, height, NULL);
 
       op->data.icon.alpha_spec = alpha_spec;
       op->data.icon.fill_type = fill_type_val;
@@ -2525,10 +2529,10 @@ parse_draw_op_element (GMarkupParseContext  *context,
 
       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);
+      op->data.title.x = meta_draw_spec_new (metacity, x, NULL);
+      op->data.title.y = meta_draw_spec_new (metacity, y, NULL);
       if (ellipsize_width)
-        op->data.title.ellipsize_width = meta_draw_spec_new (info->theme, ellipsize_width, NULL);
+        op->data.title.ellipsize_width = meta_draw_spec_new (metacity, ellipsize_width, NULL);
 
       g_assert (info->op_list);
 
@@ -2585,12 +2589,12 @@ parse_draw_op_element (GMarkupParseContext  *context,
       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,
+      op->data.op_list.x = meta_draw_spec_new (metacity, x ? x : "0", NULL);
+      op->data.op_list.y = meta_draw_spec_new (metacity, y ? y : "0", NULL);
+      op->data.op_list.width = meta_draw_spec_new (metacity,
                                                    width ? width : "width",
                                                    NULL);
-      op->data.op_list.height = meta_draw_spec_new (info->theme,
+      op->data.op_list.height = meta_draw_spec_new (metacity,
                                                     height ? height : "height",
                                                     NULL);
 
@@ -2652,22 +2656,22 @@ parse_draw_op_element (GMarkupParseContext  *context,
 
       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,
+      op->data.tile.x = meta_draw_spec_new (metacity, x ? x : "0", NULL);
+      op->data.tile.y = meta_draw_spec_new (metacity, y ? y : "0", NULL);
+      op->data.tile.width = meta_draw_spec_new (metacity,
                                                 width ? width : "width",
                                                 NULL);
-      op->data.tile.height = meta_draw_spec_new (info->theme,
+      op->data.tile.height = meta_draw_spec_new (metacity,
                                                  height ? height : "height",
                                                  NULL);
-      op->data.tile.tile_xoffset = meta_draw_spec_new (info->theme,
+      op->data.tile.tile_xoffset = meta_draw_spec_new (metacity,
                                                        tile_xoffset ? tile_xoffset : "0",
                                                        NULL);
-      op->data.tile.tile_yoffset = meta_draw_spec_new (info->theme,
+      op->data.tile.tile_yoffset = meta_draw_spec_new (metacity,
                                                        tile_yoffset ? tile_yoffset : "0",
                                                        NULL);
-      op->data.tile.tile_width = meta_draw_spec_new (info->theme, tile_width, NULL);
-      op->data.tile.tile_height = meta_draw_spec_new (info->theme, tile_height, NULL);
+      op->data.tile.tile_width = meta_draw_spec_new (metacity, tile_width, NULL);
+      op->data.tile.tile_height = meta_draw_spec_new (metacity, tile_height, NULL);
 
       op->data.tile.op_list = op_list;
 
diff --git a/src/ui/theme-private.h b/src/ui/theme-private.h
index c9cdeba..ebfc6e8 100644
--- a/src/ui/theme-private.h
+++ b/src/ui/theme-private.h
@@ -19,6 +19,7 @@
 #define META_THEME_PRIVATE_H
 
 #include <libmetacity/meta-color-spec.h>
+#include <libmetacity/meta-draw-spec.h>
 #include <libmetacity/meta-gradient-spec.h>
 #include <libmetacity/meta-theme-impl.h>
 
@@ -29,11 +30,9 @@ G_BEGIN_DECLS
 typedef struct _MetaDrawInfo MetaDrawInfo;
 typedef struct _MetaDrawOp MetaDrawOp;
 typedef struct _MetaDrawOpList MetaDrawOpList;
-typedef struct _MetaDrawSpec MetaDrawSpec;
 typedef struct _MetaFrameLayout MetaFrameLayout;
 typedef struct _MetaFrameStyle MetaFrameStyle;
 typedef struct _MetaFrameStyleSet MetaFrameStyleSet;
-typedef struct _MetaPositionExprEnv MetaPositionExprEnv;
 
 /**
  * A drawing operation in our simple vector drawing language.
@@ -74,59 +73,6 @@ typedef enum
 
 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;
-
-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.
@@ -334,55 +280,6 @@ struct _MetaDrawInfo
   const MetaFrameGeometry *fgeom;
 };
 
-struct _MetaPositionExprEnv
-{
-  GdkRectangle 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;
-};
-
-/**
- * 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
- */
-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;
-};
-
 /**
  * A single drawing operation in our simple vector drawing language.
  */
@@ -665,11 +562,6 @@ void                   meta_frame_layout_unref                 (MetaFrameLayout
 gboolean               meta_frame_layout_validate              (const MetaFrameLayout       *layout,
                                                                 GError                     **error);
 
-MetaDrawSpec          *meta_draw_spec_new                      (MetaTheme                   *theme,
-                                                                const char                  *expr,
-                                                                GError                     **error);
-void                   meta_draw_spec_free                     (MetaDrawSpec                *spec);
-
 MetaDrawOp            *meta_draw_op_new                        (MetaDrawType                 type);
 void                   meta_draw_op_free                       (MetaDrawOp                  *op);
 
diff --git a/src/ui/theme.c b/src/ui/theme.c
index a0de20a..10598cf 100644
--- a/src/ui/theme.c
+++ b/src/ui/theme.c
@@ -1159,1185 +1159,6 @@ meta_frame_layout_calc_geometry (MetaFrameLayout        *layout,
     fgeom->bottom_right_corner_rounded_radius = layout->bottom_right_corner_rounded_radius;
 }
 
-/**
- * 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;
-    default:
-      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 (strncmp (p, "`max`", 5) == 0)
-        {
-          *len = 5;
-          return POS_OP_MAX;
-        }
-      else if (strncmp (p, "`min`", 5) == 0)
-        {
-          *len = 5;
-          return POS_OP_MIN;
-        }
-
-    default:
-      break;
-    }
-
-  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) == '_')
-
-/**
- * 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;
-
-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:
-        default:
-          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:
-        default:
-          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;
-
-  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;
-            default:
-              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;
-            default:
-              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;
-            default:
-              break;
-            }
-          break;
-        default:
-          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;
-}
-
-/**
- * pos_eval_get_variable:
- * @token: The token representing a variable
- * @result: (out): The value of that variable; not set if the token did not
- *     represent a known variable
- * @env: The environment within which @token should be evaluated
- * @err: (out): Set to the problem if there was a problem
- *
- * 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.)
- *
- * Returns: %TRUE if we found the variable asked for, %FALSE if we didn't
- */
-static gboolean
-pos_eval_get_variable (const PosToken             *token,
-                       int                        *result,
-                       const MetaPositionExprEnv  *env,
-                       GError                    **err)
-{
-  GQuark quark;
-
-  quark = token->d.v.name_quark;
-
-  if (quark == g_quark_from_static_string ("width"))
-    {
-      *result = env->rect.width;
-    }
-  else if (quark == g_quark_from_static_string ("height"))
-    {
-      *result = env->rect.height;
-    }
-  else if (env->object_width >= 0 &&
-           quark == g_quark_from_static_string ("object_width"))
-    {
-      *result = env->object_width;
-    }
-  else if (env->object_height >= 0 &&
-           quark == g_quark_from_static_string ("object_height"))
-    {
-      *result = env->object_height;
-    }
-  else if (quark == g_quark_from_static_string ("left_width"))
-    {
-      *result = env->left_width;
-    }
-  else if (quark == g_quark_from_static_string ("right_width"))
-    {
-      *result = env->right_width;
-    }
-  else if (quark == g_quark_from_static_string ("top_height"))
-    {
-      *result = env->top_height;
-    }
-  else if (quark == g_quark_from_static_string ("bottom_height"))
-    {
-      *result = env->bottom_height;
-    }
-  else if (quark == g_quark_from_static_string ("mini_icon_width"))
-    {
-      *result = env->mini_icon_width;
-    }
-  else if (quark == g_quark_from_static_string ("mini_icon_height"))
-    {
-      *result = env->mini_icon_height;
-    }
-  else if (quark == g_quark_from_static_string ("icon_width"))
-    {
-      *result = env->icon_width;
-    }
-  else if (quark == g_quark_from_static_string ("icon_height"))
-    {
-      *result = env->icon_height;
-    }
-  else if (quark == g_quark_from_static_string ("title_width"))
-    {
-      *result = env->title_width;
-    }
-  else if (quark == g_quark_from_static_string ("title_height"))
-    {
-      *result = env->title_height;
-    }
-  else if (quark == g_quark_from_static_string ("frame_x_center"))
-    {
-      *result = env->frame_x_center;
-    }
-  else if (quark == g_quark_from_static_string ("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'"),
-                   token->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;
-
-            default:
-              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;
-
-            default:
-              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:
-        default:
-          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.
- */
-
-static 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;
-}
-
-
-static 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.
- */
-static 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_metacity_lookup_int (META_THEME_METACITY (theme->impl),
-                                              t->d.v.name, &ival))
-            {
-              g_free (t->d.v.name);
-              t->type = POS_TOKEN_INT;
-              t->d.i.val = ival;
-            }
-          else if (meta_theme_metacity_lookup_float (META_THEME_METACITY (theme->impl),
-                                                     t->d.v.name, &dval))
-            {
-              g_free (t->d.v.name);
-              t->type = POS_TOKEN_DOUBLE;
-              t->d.d.val = dval;
-            }
-          else
-            {
-              /* If we've found a variable that cannot be replaced then the
-                 expression is not a constant expression and we want to
-                 replace it with a GQuark */
-
-              t->d.v.name_quark = g_quark_from_string (t->d.v.name);
-              is_constant = FALSE;
-            }
-        }
-    }
-
-  return is_constant;
-}
-
-static int
-parse_x_position_unchecked (MetaDrawSpec              *spec,
-                            const MetaPositionExprEnv *env)
-{
-  int retval;
-  GError *error;
-
-  retval = 0;
-  error = NULL;
-  if (!meta_parse_position_expression (spec, env, &retval, NULL, &error))
-    {
-      meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
-                    error->message);
-
-      g_error_free (error);
-    }
-
-  return retval;
-}
-
-static int
-parse_y_position_unchecked (MetaDrawSpec              *spec,
-                            const MetaPositionExprEnv *env)
-{
-  int retval;
-  GError *error;
-
-  retval = 0;
-  error = NULL;
-  if (!meta_parse_position_expression (spec, env, NULL, &retval, &error))
-    {
-      meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
-                    error->message);
-
-      g_error_free (error);
-    }
-
-  return retval;
-}
-
-static int
-parse_size_unchecked (MetaDrawSpec        *spec,
-                      MetaPositionExprEnv *env)
-{
-  int retval;
-  GError *error;
-
-  retval = 0;
-  error = NULL;
-  if (!meta_parse_size_expression (spec, env, &retval, &error))
-    {
-      meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
-                    error->message);
-
-      g_error_free (error);
-    }
-
-  return retval;
-}
-
-void
-meta_draw_spec_free (MetaDrawSpec *spec)
-{
-  if (!spec) return;
-  free_tokens (spec->tokens, spec->n_tokens);
-  g_slice_free (MetaDrawSpec, spec);
-}
-
-MetaDrawSpec *
-meta_draw_spec_new (MetaTheme  *theme,
-                    const char *expr,
-                    GError    **error)
-{
-  MetaDrawSpec *spec;
-
-  spec = g_slice_new0 (MetaDrawSpec);
-
-  pos_tokenize (expr, &spec->tokens, &spec->n_tokens, NULL);
-
-  spec->constant = meta_theme_replace_constants (theme, spec->tokens,
-                                                 spec->n_tokens, NULL);
-  if (spec->constant)
-    {
-      gboolean result;
-
-      result = pos_eval (spec, NULL, &spec->value, error);
-      if (result == FALSE)
-        {
-          meta_draw_spec_free (spec);
-          return NULL;
-        }
-    }
-
-  return spec;
-}
-
 MetaDrawOp*
 meta_draw_op_new (MetaDrawType type)
 {
@@ -3025,8 +1846,8 @@ meta_draw_op_draw_with_env (const MetaDrawOp    *op,
             cairo_set_dash (cr, dash_list, 2, 0);
           }
 
-        x1 = parse_x_position_unchecked (op->data.line.x1, env);
-        y1 = parse_y_position_unchecked (op->data.line.y1, env);
+        x1 = meta_draw_spec_parse_x_position (op->data.line.x1, env);
+        y1 = meta_draw_spec_parse_y_position (op->data.line.y1, env);
 
         if (!op->data.line.x2 &&
             !op->data.line.y2 &&
@@ -3038,12 +1859,12 @@ meta_draw_op_draw_with_env (const MetaDrawOp    *op,
         else
           {
             if (op->data.line.x2)
-              x2 = parse_x_position_unchecked (op->data.line.x2, env);
+              x2 = meta_draw_spec_parse_x_position (op->data.line.x2, env);
             else
               x2 = x1;
 
             if (op->data.line.y2)
-              y2 = parse_y_position_unchecked (op->data.line.y2, env);
+              y2 = meta_draw_spec_parse_y_position (op->data.line.y2, env);
             else
               y2 = y1;
 
@@ -3088,10 +1909,10 @@ meta_draw_op_draw_with_env (const MetaDrawOp    *op,
         meta_color_spec_render (op->data.rectangle.color_spec, style_gtk, &color);
         gdk_cairo_set_source_rgba (cr, &color);
 
-        rx = parse_x_position_unchecked (op->data.rectangle.x, env);
-        ry = parse_y_position_unchecked (op->data.rectangle.y, env);
-        rwidth = parse_size_unchecked (op->data.rectangle.width, env);
-        rheight = parse_size_unchecked (op->data.rectangle.height, env);
+        rx = meta_draw_spec_parse_x_position (op->data.rectangle.x, env);
+        ry = meta_draw_spec_parse_y_position (op->data.rectangle.y, env);
+        rwidth = meta_draw_spec_parse_size (op->data.rectangle.width, env);
+        rheight = meta_draw_spec_parse_size (op->data.rectangle.height, env);
 
         /* Filled and stroked rectangles are the other cases
          * we pixel-align to X rasterization
@@ -3118,10 +1939,10 @@ meta_draw_op_draw_with_env (const MetaDrawOp    *op,
         meta_color_spec_render (op->data.arc.color_spec, style_gtk, &color);
         gdk_cairo_set_source_rgba (cr, &color);
 
-        rx = parse_x_position_unchecked (op->data.arc.x, env);
-        ry = parse_y_position_unchecked (op->data.arc.y, env);
-        rwidth = parse_size_unchecked (op->data.arc.width, env);
-        rheight = parse_size_unchecked (op->data.arc.height, env);
+        rx = meta_draw_spec_parse_x_position (op->data.arc.x, env);
+        ry = meta_draw_spec_parse_y_position (op->data.arc.y, env);
+        rwidth = meta_draw_spec_parse_size (op->data.arc.width, env);
+        rheight = meta_draw_spec_parse_size (op->data.arc.height, env);
 
         start_angle = op->data.arc.start_angle * (M_PI / 180.)
                       - (.5 * M_PI); /* start at 12 instead of 3 oclock */
@@ -3161,10 +1982,10 @@ meta_draw_op_draw_with_env (const MetaDrawOp    *op,
 
         needs_alpha = meta_alpha_gradient_spec_needs_alpha (op->data.tint.alpha_spec);
 
-        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);
+        rx = meta_draw_spec_parse_x_position (op->data.tint.x, env);
+        ry = meta_draw_spec_parse_y_position (op->data.tint.y, env);
+        rwidth = meta_draw_spec_parse_size (op->data.tint.width, env);
+        rheight = meta_draw_spec_parse_size (op->data.tint.height, env);
 
         if (!needs_alpha)
           {
@@ -3197,10 +2018,10 @@ meta_draw_op_draw_with_env (const MetaDrawOp    *op,
         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);
+        rx = meta_draw_spec_parse_x_position (op->data.gradient.x, env);
+        ry = meta_draw_spec_parse_y_position (op->data.gradient.y, env);
+        rwidth = meta_draw_spec_parse_size (op->data.gradient.width, env);
+        rheight = meta_draw_spec_parse_size (op->data.gradient.height, env);
 
         pixbuf = draw_op_as_pixbuf (op, style_gtk, info,
                                     rwidth, rheight);
@@ -3226,16 +2047,16 @@ meta_draw_op_draw_with_env (const MetaDrawOp    *op,
             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);
+        rwidth = meta_draw_spec_parse_size (op->data.image.width, env);
+        rheight = meta_draw_spec_parse_size (op->data.image.height, env);
 
         pixbuf = draw_op_as_pixbuf (op, style_gtk, 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);
+            rx = meta_draw_spec_parse_x_position (op->data.image.x, env);
+            ry = meta_draw_spec_parse_y_position (op->data.image.y, env);
 
             gdk_cairo_set_source_pixbuf (cr, pixbuf, rx, ry);
             cairo_paint (cr);
@@ -3250,10 +2071,10 @@ meta_draw_op_draw_with_env (const MetaDrawOp    *op,
         int rx, ry, rwidth, rheight;
         double angle = 0, size;
 
-        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);
+        rx = meta_draw_spec_parse_x_position (op->data.gtk_arrow.x, env);
+        ry = meta_draw_spec_parse_y_position (op->data.gtk_arrow.y, env);
+        rwidth = meta_draw_spec_parse_size (op->data.gtk_arrow.width, env);
+        rheight = meta_draw_spec_parse_size (op->data.gtk_arrow.height, env);
 
         size = MAX(rwidth, rheight);
 
@@ -3286,10 +2107,10 @@ meta_draw_op_draw_with_env (const MetaDrawOp    *op,
       {
         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);
+        rx = meta_draw_spec_parse_x_position (op->data.gtk_box.x, env);
+        ry = meta_draw_spec_parse_y_position (op->data.gtk_box.y, env);
+        rwidth = meta_draw_spec_parse_size (op->data.gtk_box.width, env);
+        rheight = meta_draw_spec_parse_size (op->data.gtk_box.height, env);
 
         gtk_style_context_set_state (style_gtk, op->data.gtk_box.state);
         gtk_render_background (style_gtk, cr, rx, ry, rwidth, rheight);
@@ -3301,9 +2122,9 @@ meta_draw_op_draw_with_env (const MetaDrawOp    *op,
       {
         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);
+        rx = meta_draw_spec_parse_x_position (op->data.gtk_vline.x, env);
+        ry1 = meta_draw_spec_parse_y_position (op->data.gtk_vline.y1, env);
+        ry2 = meta_draw_spec_parse_y_position (op->data.gtk_vline.y2, env);
 
         gtk_style_context_set_state (style_gtk, op->data.gtk_vline.state);
         gtk_render_line (style_gtk, cr, rx, ry1, rx, ry2);
@@ -3315,16 +2136,16 @@ meta_draw_op_draw_with_env (const MetaDrawOp    *op,
         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);
+        rwidth = meta_draw_spec_parse_size (op->data.icon.width, env);
+        rheight = meta_draw_spec_parse_size (op->data.icon.height, env);
 
         pixbuf = draw_op_as_pixbuf (op, style_gtk, 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);
+            rx = meta_draw_spec_parse_x_position (op->data.icon.x, env);
+            ry = meta_draw_spec_parse_y_position (op->data.icon.y, env);
 
             gdk_cairo_set_source_pixbuf (cr, pixbuf, rx, ry);
             cairo_paint (cr);
@@ -3343,16 +2164,16 @@ meta_draw_op_draw_with_env (const MetaDrawOp    *op,
           meta_color_spec_render (op->data.title.color_spec, style_gtk, &color);
           gdk_cairo_set_source_rgba (cr, &color);
 
-          rx = parse_x_position_unchecked (op->data.title.x, env);
-          ry = parse_y_position_unchecked (op->data.title.y, env);
+          rx = meta_draw_spec_parse_x_position (op->data.title.x, env);
+          ry = meta_draw_spec_parse_y_position (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 = meta_draw_spec_parse_x_position (op->data.title.ellipsize_width, env);
+              /* HACK: meta_draw_spec_parse_x_position adds in env->rect.x, subtract out again */
               ellipsize_width -= env->rect.x;
 
               pango_layout_set_width (info->title_layout, -1);
@@ -3418,10 +2239,10 @@ meta_draw_op_draw_with_env (const MetaDrawOp    *op,
       {
         GdkRectangle 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);
+        d_rect.x = meta_draw_spec_parse_x_position (op->data.op_list.x, env);
+        d_rect.y = meta_draw_spec_parse_y_position (op->data.op_list.y, env);
+        d_rect.width = meta_draw_spec_parse_size (op->data.op_list.width, env);
+        d_rect.height = meta_draw_spec_parse_size (op->data.op_list.height, env);
 
         meta_draw_op_list_draw_with_style (op->data.op_list.op_list,
                                            style_gtk, cr, info,
@@ -3435,24 +2256,24 @@ meta_draw_op_draw_with_env (const MetaDrawOp    *op,
         int tile_xoffset, tile_yoffset;
         GdkRectangle 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);
+        rx = meta_draw_spec_parse_x_position (op->data.tile.x, env);
+        ry = meta_draw_spec_parse_y_position (op->data.tile.y, env);
+        rwidth = meta_draw_spec_parse_size (op->data.tile.width, env);
+        rheight = meta_draw_spec_parse_size (op->data.tile.height, env);
 
         cairo_save (cr);
 
         cairo_rectangle (cr, rx, ry, rwidth, rheight);
         cairo_clip (cr);
 
-        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_xoffset = meta_draw_spec_parse_x_position (op->data.tile.tile_xoffset, env);
+        tile_yoffset = meta_draw_spec_parse_y_position (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.width = meta_draw_spec_parse_size (op->data.tile.tile_width, env);
+        tile.height = meta_draw_spec_parse_size (op->data.tile.tile_height, env);
 
         tile.x = rx - tile_xoffset;
 
@@ -3562,10 +2383,10 @@ meta_draw_op_list_draw_with_style  (const MetaDrawOpList *op_list,
           cairo_restore (cr);
 
           cairo_rectangle (cr,
-                           parse_x_position_unchecked (op->data.clip.x, &env),
-                           parse_y_position_unchecked (op->data.clip.y, &env),
-                           parse_size_unchecked (op->data.clip.width, &env),
-                           parse_size_unchecked (op->data.clip.height, &env));
+                           meta_draw_spec_parse_x_position (op->data.clip.x, &env),
+                           meta_draw_spec_parse_y_position (op->data.clip.y, &env),
+                           meta_draw_spec_parse_size (op->data.clip.width, &env),
+                           meta_draw_spec_parse_size (op->data.clip.height, &env));
           cairo_clip (cr);
 
           cairo_save (cr);


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