diff options
author | Alberts Muktupāvels <alberts.muktupavels@gmail.com> | 2016-01-29 01:08:44 +0200 |
---|---|---|
committer | Alberts Muktupāvels <alberts.muktupavels@gmail.com> | 2016-01-29 01:08:44 +0200 |
commit | de5f3fbd10d3e8b44a0be5723272c0d35bc7ff8b (patch) | |
tree | 2980305d3e1e6629db90014b7fa9a40497312ed4 | |
parent | 624308207a1692a5e13d17c58e2554891bb1b5a9 (diff) | |
download | metacity-de5f3fbd10d3e8b44a0be5723272c0d35bc7ff8b.tar.gz |
libmetacity: add meta-draw-spec.[c/h]
-rw-r--r-- | libmetacity/Makefile.am | 3 | ||||
-rw-r--r-- | libmetacity/meta-draw-spec.c | 1272 | ||||
-rw-r--r-- | libmetacity/meta-draw-spec.h | 68 | ||||
-rw-r--r-- | po/POTFILES.in | 1 | ||||
-rw-r--r-- | src/ui/theme-parser.c | 120 | ||||
-rw-r--r-- | src/ui/theme-private.h | 110 | ||||
-rw-r--r-- | src/ui/theme.c | 1297 |
7 files changed, 1466 insertions, 1405 deletions
diff --git a/libmetacity/Makefile.am b/libmetacity/Makefile.am index 5c2a89b4..54dde93a 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 00000000..f9b1a089 --- /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 00000000..5f7dfddc --- /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 e56b9051..020b48d5 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 faab4ddd..48cea2ee 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 c9cdebaf..ebfc6e8a 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 a0de20a5..10598cfa 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); |