/*
* 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 <math.h>
#include <stdlib.h>
#include <string.h>
#include "meta-draw-op-private.h"
#include "meta-frame-layout-private.h"
#include "meta-frame-style-private.h"
#include "meta-theme.h"
#include "meta-theme-metacity-private.h"
/* We were intending to put the version number
* in the subdirectory name, but we ended up
* using the filename instead. The "-1" survives
* as a fossil.
*/
#define THEME_SUBDIR "metacity-1"
#define METACITY_THEME_FILENAME_FORMAT "metacity-theme-%d.xml"
/* Highest version of the theme format to
* look out for.
*/
#define THEME_MAJOR_VERSION 3
#define THEME_MINOR_VERSION 5
#define THEME_VERSION (1000 * THEME_MAJOR_VERSION + THEME_MINOR_VERSION)
/* Translators: This means that an attribute which should have been found
* on an XML element was not in fact found.
*/
#define ATTRIBUTE_NOT_FOUND _("No '%s' attribute on element <%s>")
/* What version of the theme file format were feature introduced in? */
#define META_THEME_COLOR_CONSTANTS 2
#define META_THEME_DEGREES_IN_ARCS 2
#define META_THEME_FRAME_BACKGROUNDS 2
#define META_THEME_HIDDEN_BUTTONS 2
#define META_THEME_IMAGES_FROM_ICON_THEMES 2
#define META_THEME_SHADE_STICK_ABOVE_BUTTONS 2
#define META_THEME_UBIQUITOUS_CONSTANTS 2
#define META_THEME_UNRESIZABLE_SHADED_STYLES 2
#define META_THEME_VARIED_ROUND_CORNERS 2
struct _MetaThemeMetacity
{
MetaThemeImpl parent;
MetaFrameStyleSet *style_sets_by_type[META_FRAME_TYPE_LAST];
gchar *name;
gchar *dirname;
guint format_version;
gchar *readable_name;
gchar *author;
gchar *copyright;
gchar *date;
gchar *description;
GHashTable *integers;
GHashTable *floats;
GHashTable *colors;
GHashTable *draw_op_lists;
GHashTable *frame_layouts;
GHashTable *styles;
GHashTable *style_sets;
GHashTable *images;
};
typedef enum
{
STATE_START,
STATE_THEME,
/* info section */
STATE_INFO,
STATE_NAME,
STATE_AUTHOR,
STATE_COPYRIGHT,
STATE_DATE,
STATE_DESCRIPTION,
/* constants */
STATE_CONSTANT,
/* geometry */
STATE_FRAME_GEOMETRY,
STATE_DISTANCE,
STATE_BORDER,
STATE_ASPECT_RATIO,
/* draw ops */
STATE_DRAW_OPS,
STATE_LINE,
STATE_RECTANGLE,
STATE_ARC,
STATE_CLIP,
STATE_TINT,
STATE_GRADIENT,
STATE_IMAGE,
STATE_GTK_ARROW,
STATE_GTK_BOX,
STATE_GTK_VLINE,
STATE_ICON,
STATE_TITLE,
STATE_INCLUDE, /* include another draw op list */
STATE_TILE, /* tile another draw op list */
/* sub-parts of gradient */
STATE_COLOR,
/* frame style */
STATE_FRAME_STYLE,
STATE_PIECE,
STATE_BUTTON,
/* style set */
STATE_FRAME_STYLE_SET,
STATE_FRAME,
/* assigning style sets to windows */
STATE_WINDOW,
/* things we don't use any more but we can still parse: */
STATE_MENU_ICON,
STATE_FALLBACK
} ParseState;
typedef struct
{
/* This two lists contain stacks of state and required version
* (cast to pointers.) There is one list item for each currently
* open element. */
GSList *states;
GSList *required_versions;
MetaThemeMetacity *metacity; /* theme being parsed */
gchar *name; /* name of named thing being parsed */
MetaFrameLayout *layout; /* layout being parsed if any */
MetaDrawOpList *op_list; /* op list being parsed if any */
MetaDrawOp *op; /* op being parsed if any */
MetaFrameStyle *style; /* frame style being parsed if any */
MetaFrameStyleSet *style_set; /* frame style set being parsed if any */
MetaFramePiece piece; /* position of piece being parsed */
MetaButtonFunction button_function; /* type of button/menuitem being parsed */
MetaButtonState button_state; /* state of button being parsed */
gint skip_level; /* depth of elements that we're ignoring */
} ParseInfo;
typedef struct
{
const gchar *name;
const gchar **retloc;
gboolean required;
} LocateAttr;
G_DEFINE_TYPE (MetaThemeMetacity, meta_theme_metacity, META_TYPE_THEME_IMPL)
static gboolean
theme_allows (MetaThemeMetacity *metacity,
guint feature)
{
if (metacity->format_version >= feature)
return TRUE;
return FALSE;
}
static ParseInfo *
parse_info_new (MetaThemeMetacity *metacity)
{
ParseInfo *info;
info = g_new0 (ParseInfo, 1);
info->states = g_slist_prepend (NULL, GINT_TO_POINTER (STATE_START));
info->required_versions = NULL;
info->metacity = metacity;
info->name = NULL;
info->layout = NULL;
info->op_list = NULL;
info->op = NULL;
info->style = NULL;
info->style_set = NULL;
info->piece = META_FRAME_PIECE_LAST;
info->button_function = META_BUTTON_FUNCTION_LAST;
info->button_state = META_BUTTON_STATE_LAST;
info->skip_level = 0;
return info;
}
static void
parse_info_free (ParseInfo *info)
{
g_slist_free (info->states);
g_slist_free (info->required_versions);
if (info->layout)
meta_frame_layout_unref (info->layout);
if (info->op_list)
meta_draw_op_list_unref (info->op_list);
if (info->op)
meta_draw_op_free (info->op);
if (info->style)
meta_frame_style_unref (info->style);
if (info->style_set)
meta_frame_style_set_unref (info->style_set);
g_free (info);
}
static void
push_state (ParseInfo *info,
ParseState state)
{
info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state));
}
static void
pop_state (ParseInfo *info)
{
g_return_if_fail (info->states != NULL);
info->states = g_slist_remove (info->states, info->states->data);
}
static void
push_required_version (ParseInfo *info,
int version)
{
info->required_versions = g_slist_prepend (info->required_versions,
GINT_TO_POINTER (version));
}
static void
pop_required_version (ParseInfo *info)
{
g_return_if_fail (info->required_versions != NULL);
info->required_versions = g_slist_delete_link (info->required_versions,
info->required_versions);
}
static ParseState
peek_state (ParseInfo *info)
{
g_return_val_if_fail (info->states != NULL, STATE_START);
return GPOINTER_TO_INT (info->states->data);
}
static int
peek_required_version (ParseInfo *info)
{
if (info->required_versions)
return GPOINTER_TO_INT (info->required_versions->data);
else
return info->metacity->format_version;
}
G_GNUC_PRINTF(5, 6)
static void
set_error (GError **err,
GMarkupParseContext *context,
gint error_domain,
gint error_code,
const gchar *format,
...)
{
gint line;
gint ch;
va_list args;
gchar *str;
g_markup_parse_context_get_position (context, &line, &ch);
va_start (args, format);
str = g_strdup_vprintf (format, args);
va_end (args);
g_set_error (err, error_domain, error_code,
_("Line %d character %d: %s"),
line, ch, str);
g_free (str);
}
static void
add_context_to_error (GError **err,
GMarkupParseContext *context)
{
gint line;
gint ch;
gchar *str;
if (err == NULL || *err == NULL)
return;
g_markup_parse_context_get_position (context, &line, &ch);
str = g_strdup_printf (_("Line %d character %d: %s"),
line, ch, (*err)->message);
g_free ((*err)->message);
(*err)->message = str;
}
#define MAX_REASONABLE 4096
static gboolean
parse_positive_integer (const char *str,
int *val,
GMarkupParseContext *context,
MetaThemeMetacity *metacity,
GError **error)
{
char *end;
long l;
int j;
*val = 0;
end = NULL;
/* Is str a constant? */
if (theme_allows (metacity, META_THEME_UBIQUITOUS_CONSTANTS) &&
meta_theme_metacity_lookup_int (metacity, str, &j))
{
/* Yes. */
l = j;
}
else
{
/* No. Let's try parsing it instead. */
l = strtol (str, &end, 10);
if (end == NULL || end == str)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Could not parse '%s' as an integer"),
str);
return FALSE;
}
if (*end != '\0')
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Did not understand trailing characters '%s' in string '%s'"),
end, str);
return FALSE;
}
}
if (l < 0)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Integer %ld must be positive"), l);
return FALSE;
}
if (l > MAX_REASONABLE)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Integer %ld is too large, current max is %d"),
l, MAX_REASONABLE);
return FALSE;
}
*val = (int) l;
return TRUE;
}
/* Attribute names can have a leading '!' to indicate that they are
* required.
*/
static gboolean
locate_attributes (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
GError **error,
const gchar *first_attribute_name,
const gchar **first_attribute_retloc,
...)
{
va_list args;
const char *name;
const char **retloc;
int n_attrs;
#define MAX_ATTRS 24
LocateAttr attrs[MAX_ATTRS];
gboolean retval;
int i;
g_return_val_if_fail (first_attribute_name != NULL, FALSE);
g_return_val_if_fail (first_attribute_retloc != NULL, FALSE);
retval = TRUE;
/* FIXME: duplicated code; refactor loop */
n_attrs = 1;
attrs[0].name = first_attribute_name;
attrs[0].retloc = first_attribute_retloc;
attrs[0].required = attrs[0].name[0]=='!';
if (attrs[0].required)
attrs[0].name++; /* skip past it */
*first_attribute_retloc = NULL;
va_start (args, first_attribute_retloc);
name = va_arg (args, const gchar *);
retloc = va_arg (args, const gchar **);
while (name != NULL)
{
if (retloc == NULL)
{
retval = FALSE;
goto out;
}
g_assert (n_attrs < MAX_ATTRS);
attrs[n_attrs].name = name;
attrs[n_attrs].retloc = retloc;
attrs[n_attrs].required = attrs[n_attrs].name[0]=='!';
if (attrs[n_attrs].required)
attrs[n_attrs].name++; /* skip past it */
n_attrs += 1;
*retloc = NULL;
name = va_arg (args, const char*);
retloc = va_arg (args, const char**);
}
va_end (args);
i = 0;
while (attribute_names[i])
{
int j;
gboolean found;
/* Can be present anywhere */
if (strcmp (attribute_names[i], "version") == 0)
{
++i;
continue;
}
found = FALSE;
j = 0;
while (j < n_attrs)
{
if (strcmp (attrs[j].name, attribute_names[i]) == 0)
{
retloc = attrs[j].retloc;
if (*retloc != NULL)
{
set_error (error, context,
G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Attribute '%s' repeated twice on the same <%s> element"),
attrs[j].name, element_name);
retval = FALSE;
goto out;
}
*retloc = attribute_values[i];
found = TRUE;
}
++j;
}
if (!found)
{
j = 0;
while (j < n_attrs)
{
g_warning ("It could have been %s.\n", attrs[j++].name);
}
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Attribute '%s' is invalid on <%s> element in this context"),
attribute_names[i], element_name);
retval = FALSE;
goto out;
}
++i;
}
/* Did we catch them all? */
i = 0;
while (i < n_attrs)
{
if (attrs[i].required && *(attrs[i].retloc)==NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
ATTRIBUTE_NOT_FOUND, attrs[i].name, element_name);
retval = FALSE;
goto out;
}
++i;
}
out:
return retval;
}
static gboolean
check_no_attributes (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
GError **error)
{
int i = 0;
/* Can be present anywhere */
if (attribute_names[0] && strcmp (attribute_names[i], "version") == 0)
i++;
if (attribute_names[i] != NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Attribute '%s' is invalid on <%s> element in this context"),
attribute_names[0], element_name);
return FALSE;
}
return TRUE;
}
static gboolean
parse_double (const char *str,
double *val,
GMarkupParseContext *context,
GError **error)
{
char *end;
*val = 0;
end = NULL;
*val = g_ascii_strtod (str, &end);
if (end == NULL || end == str)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Could not parse '%s' as a floating point number"),
str);
return FALSE;
}
if (*end != '\0')
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Did not understand trailing characters '%s' in string '%s'"),
end, str);
return FALSE;
}
return TRUE;
}
static gboolean
parse_alpha (const char *str,
MetaAlphaGradientSpec **spec_ret,
GMarkupParseContext *context,
GError **error)
{
char **split;
int i;
int n_alphas;
MetaAlphaGradientSpec *spec;
*spec_ret = NULL;
split = g_strsplit (str, ":", -1);
i = 0;
while (split[i])
++i;
if (i == 0)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Could not parse '%s' as a floating point number"),
str);
g_strfreev (split);
return FALSE;
}
n_alphas = i;
/* FIXME allow specifying horizontal/vertical/diagonal in theme format,
* once we implement vertical/diagonal in gradient.c
*/
spec = meta_alpha_gradient_spec_new (META_GRADIENT_HORIZONTAL, n_alphas);
i = 0;
while (i < n_alphas)
{
double v;
if (!parse_double (split[i], &v, context, error))
{
/* clear up, but don't set error: it was set by parse_double */
g_strfreev (split);
meta_alpha_gradient_spec_free (spec);
return FALSE;
}
if (v < (0.0 - 1e-6) || v > (1.0 + 1e-6))
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Alpha must be between 0.0 (invisible) and 1.0 (fully opaque), was %g"),
v);
g_strfreev (split);
meta_alpha_gradient_spec_free (spec);
return FALSE;
}
meta_alpha_gradient_spec_add_alpha (spec, i, v);
++i;
}
g_strfreev (split);
*spec_ret = spec;
return TRUE;
}
static gboolean
parse_title_scale (const char *str,
double *val,
GMarkupParseContext *context,
GError **error)
{
double factor;
if (strcmp (str, "xx-small") == 0)
factor = PANGO_SCALE_XX_SMALL;
else if (strcmp (str, "x-small") == 0)
factor = PANGO_SCALE_X_SMALL;
else if (strcmp (str, "small") == 0)
factor = PANGO_SCALE_SMALL;
else if (strcmp (str, "medium") == 0)
factor = PANGO_SCALE_MEDIUM;
else if (strcmp (str, "large") == 0)
factor = PANGO_SCALE_LARGE;
else if (strcmp (str, "x-large") == 0)
factor = PANGO_SCALE_X_LARGE;
else if (strcmp (str, "xx-large") == 0)
factor = PANGO_SCALE_XX_LARGE;
else
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Invalid title scale '%s' (must be one of xx-small,x-small,small,medium,large,x-large,xx-large)\n"),
str);
return FALSE;
}
*val = factor;
return TRUE;
}
static gboolean
parse_rounding (const char *str,
guint *val,
GMarkupParseContext *context,
MetaThemeMetacity *metacity,
GError **error)
{
if (strcmp ("true", str) == 0)
*val = 5; /* historical "true" value */
else if (strcmp ("false", str) == 0)
*val = 0;
else
{
int tmp;
gboolean result;
if (!theme_allows (metacity, META_THEME_VARIED_ROUND_CORNERS))
{
/* Not known in this version, so bail. */
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Boolean values must be 'true' or 'false' not '%s'"),
str);
return FALSE;
}
result = parse_positive_integer (str, &tmp, context, metacity, error);
*val = tmp;
return result;
}
return TRUE;
}
static gboolean
parse_boolean (const char *str,
gboolean *val,
GMarkupParseContext *context,
GError **error)
{
if (strcmp ("true", str) == 0)
*val = TRUE;
else if (strcmp ("false", str) == 0)
*val = FALSE;
else
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Boolean values must be 'true' or 'false' not '%s'"),
str);
return FALSE;
}
return TRUE;
}
static gboolean
first_uppercase (const gchar *str)
{
return g_ascii_isupper (*str);
}
static gboolean
meta_theme_metacity_define_int (MetaThemeMetacity *metacity,
const gchar *name,
gint value,
GError **error)
{
if (metacity->integers == NULL)
{
metacity->integers = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, NULL);
}
if (!first_uppercase (name))
{
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
_("User-defined constants must begin with a capital letter; '%s' does not"),
name);
return FALSE;
}
if (g_hash_table_lookup_extended (metacity->integers, name, NULL, NULL))
{
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
_("Constant '%s' has already been defined"), name);
return FALSE;
}
g_hash_table_insert (metacity->integers, g_strdup (name),
GINT_TO_POINTER (value));
return TRUE;
}
static gboolean
meta_theme_metacity_define_float (MetaThemeMetacity *metacity,
const gchar *name,
gdouble value,
GError **error)
{
gdouble *d;
if (metacity->floats == NULL)
{
metacity->floats = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_free);
}
if (!first_uppercase (name))
{
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
_("User-defined constants must begin with a capital letter; '%s' does not"),
name);
return FALSE;
}
if (g_hash_table_lookup_extended (metacity->floats, name, NULL, NULL))
{
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
_("Constant '%s' has already been defined"), name);
return FALSE;
}
d = g_new (gdouble, 1);
*d = value;
g_hash_table_insert (metacity->floats, g_strdup (name), d);
return TRUE;
}
static gboolean
meta_theme_metacity_define_color (MetaThemeMetacity *metacity,
const gchar *name,
const gchar *value,
GError **error)
{
if (metacity->colors == NULL)
{
metacity->colors = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_free);
}
if (!first_uppercase (name))
{
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
_("User-defined constants must begin with a capital letter; '%s' does not"),
name);
return FALSE;
}
if (g_hash_table_lookup_extended (metacity->colors, name, NULL, NULL))
{
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
_("Constant '%s' has already been defined"), name);
return FALSE;
}
g_hash_table_insert (metacity->colors, g_strdup (name), g_strdup (value));
return TRUE;
}
static void
meta_theme_metacity_insert_draw_op_list (MetaThemeMetacity *metacity,
const gchar *name,
MetaDrawOpList *op_list)
{
meta_draw_op_list_ref (op_list);
g_hash_table_replace (metacity->draw_op_lists, g_strdup (name), op_list);
}
static void
meta_theme_metacity_insert_layout (MetaThemeMetacity *metacity,
const gchar *name,
MetaFrameLayout *layout)
{
meta_frame_layout_ref (layout);
g_hash_table_replace (metacity->frame_layouts, g_strdup (name), layout);
}
static void
meta_theme_metacity_insert_style (MetaThemeMetacity *metacity,
const gchar *name,
MetaFrameStyle *style)
{
meta_frame_style_ref (style);
g_hash_table_replace (metacity->styles, g_strdup (name), style);
}
static void
meta_theme_metacity_insert_style_set (MetaThemeMetacity *metacity,
const gchar *name,
MetaFrameStyleSet *style_set)
{
meta_frame_style_set_ref (style_set);
g_hash_table_replace (metacity->style_sets, g_strdup (name), style_set);
}
static MetaFrameType
meta_frame_type_from_string (const gchar *str)
{
if (strcmp ("normal", str) == 0)
return META_FRAME_TYPE_NORMAL;
else if (strcmp ("dialog", str) == 0)
return META_FRAME_TYPE_DIALOG;
else if (strcmp ("modal_dialog", str) == 0)
return META_FRAME_TYPE_MODAL_DIALOG;
else if (strcmp ("utility", str) == 0)
return META_FRAME_TYPE_UTILITY;
else if (strcmp ("menu", str) == 0)
return META_FRAME_TYPE_MENU;
else if (strcmp ("border", str) == 0)
return META_FRAME_TYPE_BORDER;
else if (strcmp ("attached", str) == 0)
return META_FRAME_TYPE_ATTACHED;
else
return META_FRAME_TYPE_LAST;
}
static void
parse_toplevel_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
ParseInfo *info,
GError **error)
{
g_return_if_fail (peek_state (info) == STATE_THEME);
if (g_strcmp0 (element_name, "info") == 0)
{
if (!check_no_attributes (context, element_name,
attribute_names, attribute_values,
error))
return;
push_state (info, STATE_INFO);
}
else if (g_strcmp0 (element_name, "constant") == 0)
{
const char *name;
const char *value;
int ival = 0;
double dval = 0.0;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"!name", &name, "!value", &value,
NULL))
return;
/* We don't know how a a constant is going to be used, so we have guess its
* type from its contents:
*
* - Starts like a number and contains a '.': float constant
* - Starts like a number and doesn't contain a '.': int constant
* - Starts with anything else: a color constant.
* (colors always start with # or a letter)
*/
if (value[0] == '.' || value[0] == '+' || value[0] == '-' || (value[0] >= '0' && value[0] <= '9'))
{
if (strchr (value, '.'))
{
if (!parse_double (value, &dval, context, error))
return;
if (!meta_theme_metacity_define_float (info->metacity,
name, dval, error))
{
add_context_to_error (error, context);
return;
}
}
else
{
if (!parse_positive_integer (value, &ival, context, info->metacity, error))
return;
if (!meta_theme_metacity_define_int (info->metacity,
name, ival, error))
{
add_context_to_error (error, context);
return;
}
}
}
else
{
if (!meta_theme_metacity_define_color (info->metacity,
name, value, error))
{
add_context_to_error (error, context);
return;
}
}
push_state (info, STATE_CONSTANT);
}
else if (g_strcmp0 (element_name, "frame_geometry") == 0)
{
const char *name = NULL;
const char *parent = NULL;
const char *has_title = NULL;
const char *title_scale = NULL;
const char *rounded_top_left = NULL;
const char *rounded_top_right = NULL;
const char *rounded_bottom_left = NULL;
const char *rounded_bottom_right = NULL;
const char *hide_buttons = NULL;
gboolean has_title_val;
guint rounded_top_left_val;
guint rounded_top_right_val;
guint rounded_bottom_left_val;
guint rounded_bottom_right_val;
gboolean hide_buttons_val;
double title_scale_val;
MetaFrameLayout *parent_layout;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"!name", &name, "parent", &parent,
"has_title", &has_title, "title_scale", &title_scale,
"rounded_top_left", &rounded_top_left,
"rounded_top_right", &rounded_top_right,
"rounded_bottom_left", &rounded_bottom_left,
"rounded_bottom_right", &rounded_bottom_right,
"hide_buttons", &hide_buttons,
NULL))
return;
has_title_val = TRUE;
if (has_title && !parse_boolean (has_title, &has_title_val, context, error))
return;
hide_buttons_val = FALSE;
if (hide_buttons && !parse_boolean (hide_buttons, &hide_buttons_val, context, error))
return;
rounded_top_left_val = 0;
rounded_top_right_val = 0;
rounded_bottom_left_val = 0;
rounded_bottom_right_val = 0;
if (rounded_top_left && !parse_rounding (rounded_top_left, &rounded_top_left_val, context, info->metacity, error))
return;
if (rounded_top_right && !parse_rounding (rounded_top_right, &rounded_top_right_val, context, info->metacity, error))
return;
if (rounded_bottom_left && !parse_rounding (rounded_bottom_left, &rounded_bottom_left_val, context, info->metacity, error))
return;
if (rounded_bottom_right && !parse_rounding (rounded_bottom_right, &rounded_bottom_right_val, context, info->metacity, error))
return;
title_scale_val = 1.0;
if (title_scale && !parse_title_scale (title_scale, &title_scale_val, context, error))
return;
if (meta_theme_metacity_lookup_layout (info->metacity, name))
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("<%s> name \"%s\" used a second time"),
element_name, name);
return;
}
parent_layout = NULL;
if (parent)
{
parent_layout = meta_theme_metacity_lookup_layout (info->metacity, parent);
if (parent_layout == NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("<%s> parent \"%s\" has not been defined"),
element_name, parent);
return;
}
}
g_assert (info->layout == NULL);
if (parent_layout)
info->layout = meta_frame_layout_copy (parent_layout);
else
info->layout = meta_frame_layout_new ();
if (has_title) /* only if explicit, otherwise inherit */
info->layout->has_title = has_title_val;
if (theme_allows (info->metacity, META_THEME_HIDDEN_BUTTONS) && hide_buttons_val)
info->layout->hide_buttons = hide_buttons_val;
if (title_scale)
info->layout->title_scale = title_scale_val;
if (rounded_top_left)
info->layout->top_left_corner_rounded_radius = rounded_top_left_val;
if (rounded_top_right)
info->layout->top_right_corner_rounded_radius = rounded_top_right_val;
if (rounded_bottom_left)
info->layout->bottom_left_corner_rounded_radius = rounded_bottom_left_val;
if (rounded_bottom_right)
info->layout->bottom_right_corner_rounded_radius = rounded_bottom_right_val;
meta_theme_metacity_insert_layout (info->metacity, name, info->layout);
push_state (info, STATE_FRAME_GEOMETRY);
}
else if (g_strcmp0 (element_name, "draw_ops") == 0)
{
const char *name = NULL;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"!name", &name,
NULL))
return;
if (meta_theme_metacity_lookup_draw_op_list (info->metacity, name))
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("<%s> name '%s' used a second time"),
element_name, name);
return;
}
g_assert (info->op_list == NULL);
info->op_list = meta_draw_op_list_new (2);
meta_theme_metacity_insert_draw_op_list (info->metacity, name, info->op_list);
push_state (info, STATE_DRAW_OPS);
}
else if (g_strcmp0 (element_name, "frame_style") == 0)
{
const char *name = NULL;
const char *parent = NULL;
const char *geometry = NULL;
const char *background = NULL;
const char *alpha = NULL;
MetaFrameStyle *parent_style;
MetaFrameLayout *layout;
if (!locate_attributes (context, element_name, attribute_names,
attribute_values, error,
"!name", &name, "parent", &parent,
"geometry", &geometry,
"background", &background,
"alpha", &alpha,
NULL))
return;
if (meta_theme_metacity_lookup_style (info->metacity, name))
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("<%s> name \"%s\" used a second time"),
element_name, name);
return;
}
parent_style = NULL;
if (parent)
{
parent_style = meta_theme_metacity_lookup_style (info->metacity, parent);
if (parent_style == NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("<%s> parent '%s' has not been defined"),
element_name, parent);
return;
}
}
layout = NULL;
if (geometry)
{
layout = meta_theme_metacity_lookup_layout (info->metacity, geometry);
if (layout == NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("<%s> geometry '%s' has not been defined"),
element_name, geometry);
return;
}
}
else if (parent_style)
{
layout = parent_style->layout;
}
if (layout == NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("<%s> must specify either a geometry or a parent that has a geometry"),
element_name);
return;
}
g_assert (info->style == NULL);
info->style = meta_frame_style_new (parent_style);
g_assert (info->style->layout == NULL);
meta_frame_layout_ref (layout);
info->style->layout = layout;
if (background != NULL && theme_allows (info->metacity, META_THEME_FRAME_BACKGROUNDS))
{
info->style->window_background_color = meta_color_spec_new_from_string (background, error);
if (!info->style->window_background_color)
return;
if (alpha != NULL)
{
gboolean success;
MetaAlphaGradientSpec *alpha_vector;
g_clear_error (error);
/* fortunately, we already have a routine to parse alpha values,
* though it produces a vector of them, which is a superset of
* what we want.
*/
success = parse_alpha (alpha, &alpha_vector, context, error);
if (!success)
return;
/* alpha_vector->alphas must contain at least one element */
info->style->window_background_alpha = meta_alpha_gradient_spec_get_alpha (alpha_vector, 0);
meta_alpha_gradient_spec_free (alpha_vector);
}
}
else if (alpha != NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("You must specify a background for an alpha value to be meaningful"));
return;
}
meta_theme_metacity_insert_style (info->metacity, name, info->style);
push_state (info, STATE_FRAME_STYLE);
}
else if (g_strcmp0 (element_name, "frame_style_set") == 0)
{
const char *name = NULL;
const char *parent = NULL;
MetaFrameStyleSet *parent_set;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"!name", &name, "parent", &parent,
NULL))
return;
if (meta_theme_metacity_lookup_style_set (info->metacity, name))
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("<%s> name \"%s\" used a second time"),
element_name, name);
return;
}
parent_set = NULL;
if (parent)
{
parent_set = meta_theme_metacity_lookup_style_set (info->metacity, parent);
if (parent_set == NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("<%s> parent '%s' has not been defined"),
element_name, parent);
return;
}
}
g_assert (info->style_set == NULL);
info->style_set = meta_frame_style_set_new (parent_set);
meta_theme_metacity_insert_style_set (info->metacity, name, info->style_set);
push_state (info, STATE_FRAME_STYLE_SET);
}
else if (g_strcmp0 (element_name, "window") == 0)
{
const char *type_name = NULL;
const char *style_set_name = NULL;
MetaFrameStyleSet *style_set;
MetaFrameType type;
if (!locate_attributes (context, element_name, attribute_names,
attribute_values, error,
"!type", &type_name, "!style_set", &style_set_name,
NULL))
return;
type = meta_frame_type_from_string (type_name);
if (type == META_FRAME_TYPE_LAST ||
(type == META_FRAME_TYPE_ATTACHED && peek_required_version (info) < 3002))
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Unknown type '%s' on <%s> element"),
type_name, element_name);
return;
}
style_set = meta_theme_metacity_lookup_style_set (info->metacity, style_set_name);
if (style_set == NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Unknown style_set '%s' on <%s> element"),
style_set_name, element_name);
return;
}
if (info->metacity->style_sets_by_type[type] != NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Window type '%s' has already been assigned a style set"),
type_name);
return;
}
meta_frame_style_set_ref (style_set);
info->metacity->style_sets_by_type[type] = style_set;
push_state (info, STATE_WINDOW);
}
else if (g_strcmp0 (element_name, "menu_icon") == 0)
{
/* Not supported any more, but we have to parse it if they include it,
* for backwards compatibility.
*/
g_assert (info->op_list == NULL);
push_state (info, STATE_MENU_ICON);
}
else if (g_strcmp0 (element_name, "fallback") == 0)
{
/* Not supported any more, but we have to parse it if they include it,
* for backwards compatibility.
*/
push_state (info, STATE_FALLBACK);
}
else
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Element <%s> is not allowed below <%s>"),
element_name, "metacity_theme");
}
}
static void
parse_info_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
ParseInfo *info,
GError **error)
{
g_return_if_fail (peek_state (info) == STATE_INFO);
if (g_strcmp0 (element_name, "name") == 0)
{
if (!check_no_attributes (context, element_name,
attribute_names, attribute_values,
error))
return;
push_state (info, STATE_NAME);
}
else if (g_strcmp0 (element_name, "author") == 0)
{
if (!check_no_attributes (context, element_name,
attribute_names, attribute_values,
error))
return;
push_state (info, STATE_AUTHOR);
}
else if (g_strcmp0 (element_name, "copyright") == 0)
{
if (!check_no_attributes (context, element_name,
attribute_names, attribute_values,
error))
return;
push_state (info, STATE_COPYRIGHT);
}
else if (g_strcmp0 (element_name, "description") == 0)
{
if (!check_no_attributes (context, element_name,
attribute_names, attribute_values,
error))
return;
push_state (info, STATE_DESCRIPTION);
}
else if (g_strcmp0 (element_name, "date") == 0)
{
if (!check_no_attributes (context, element_name,
attribute_names, attribute_values,
error))
return;
push_state (info, STATE_DATE);
}
else
{
set_error (error, context,
G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Element <%s> is not allowed below <%s>"),
element_name, "info");
}
}
static void
parse_aspect_ratio (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
ParseInfo *info,
GError **error)
{
const char *name;
const char *value;
double val;
if (!locate_attributes (context, element_name, attribute_names,
attribute_values, error,
"!name", &name, "!value", &value,
NULL))
return;
val = 0;
if (!parse_double (value, &val, context, error))
return;
g_assert (info->layout);
if (strcmp (name, "button") == 0)
{
info->layout->metacity.button_aspect = val;
if (info->layout->metacity.button_sizing != META_BUTTON_SIZING_LAST)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Cannot specify both 'button_width'/'button_height' and 'aspect_ratio' for buttons"));
return;
}
info->layout->metacity.button_sizing = META_BUTTON_SIZING_ASPECT;
}
else
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Aspect ratio '%s' is unknown"), name);
return;
}
}
static void
parse_border (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
ParseInfo *info,
GError **error)
{
const char *name;
const char *top;
const char *bottom;
const char *left;
const char *right;
int top_val;
int bottom_val;
int left_val;
int right_val;
GtkBorder *border;
if (!locate_attributes (context, element_name, attribute_names,
attribute_values, error,
"!name", &name,
"!top", &top,
"!bottom", &bottom,
"!left", &left,
"!right", &right,
NULL))
return;
top_val = 0;
if (!parse_positive_integer (top, &top_val, context, info->metacity, error))
return;
bottom_val = 0;
if (!parse_positive_integer (bottom, &bottom_val, context, info->metacity, error))
return;
left_val = 0;
if (!parse_positive_integer (left, &left_val, context, info->metacity, error))
return;
right_val = 0;
if (!parse_positive_integer (right, &right_val, context, info->metacity, error))
return;
g_assert (info->layout);
border = NULL;
if (strcmp (name, "title_border") == 0)
border = &info->layout->metacity.title_border;
else if (strcmp (name, "button_border") == 0)
border = &info->layout->button_border;
if (border == NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Border '%s' is unknown"), name);
return;
}
border->top = top_val;
border->bottom = bottom_val;
border->left = left_val;
border->right = right_val;
}
static void
parse_distance (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
ParseInfo *info,
GError **error)
{
const char *name;
const char *value;
int val;
if (!locate_attributes (context, element_name, attribute_names,
attribute_values, error,
"!name", &name, "!value", &value,
NULL))
return;
val = 0;
if (!parse_positive_integer (value, &val, context, info->metacity, error))
return;
g_assert (val >= 0); /* yeah, "non-negative" not "positive" get over it */
g_assert (info->layout);
if (strcmp (name, "left_width") == 0)
info->layout->metacity.left_width = val;
else if (strcmp (name, "right_width") == 0)
info->layout->metacity.right_width = val;
else if (strcmp (name, "bottom_height") == 0)
info->layout->metacity.bottom_height = val;
else if (strcmp (name, "title_vertical_pad") == 0)
info->layout->metacity.title_vertical_pad = val;
else if (strcmp (name, "right_titlebar_edge") == 0)
info->layout->metacity.right_titlebar_edge = val;
else if (strcmp (name, "left_titlebar_edge") == 0)
info->layout->metacity.left_titlebar_edge = val;
else if (strcmp (name, "button_width") == 0)
{
info->layout->metacity.button_width = val;
if (!(info->layout->metacity.button_sizing == META_BUTTON_SIZING_LAST ||
info->layout->metacity.button_sizing == META_BUTTON_SIZING_FIXED))
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Cannot specify both 'button_width'/'button_height' and 'aspect_ratio' for buttons"));
return;
}
info->layout->metacity.button_sizing = META_BUTTON_SIZING_FIXED;
}
else if (strcmp (name, "button_height") == 0)
{
info->layout->metacity.button_height = val;
if (!(info->layout->metacity.button_sizing == META_BUTTON_SIZING_LAST ||
info->layout->metacity.button_sizing == META_BUTTON_SIZING_FIXED))
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Cannot specify both 'button_width'/'button_height' and 'aspect_ratio' for buttons"));
return;
}
info->layout->metacity.button_sizing = META_BUTTON_SIZING_FIXED;
}
else
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Distance '%s' is unknown"), name);
return;
}
}
static void
parse_geometry_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
ParseInfo *info,
GError **error)
{
g_return_if_fail (peek_state (info) == STATE_FRAME_GEOMETRY);
if (g_strcmp0 (element_name, "distance") == 0)
{
parse_distance (context, element_name,
attribute_names, attribute_values,
info, error);
push_state (info, STATE_DISTANCE);
}
else if (g_strcmp0 (element_name, "border") == 0)
{
parse_border (context, element_name,
attribute_names, attribute_values,
info, error);
push_state (info, STATE_BORDER);
}
else if (g_strcmp0 (element_name, "aspect_ratio") == 0)
{
parse_aspect_ratio (context, element_name,
attribute_names, attribute_values,
info, error);
push_state (info, STATE_ASPECT_RATIO);
}
else
{
set_error (error, context,
G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Element <%s> is not allowed below <%s>"),
element_name, "frame_geometry");
}
}
static GtkArrowType
meta_gtk_arrow_from_string (const char *str)
{
if (strcmp ("up", str) == 0)
return GTK_ARROW_UP;
else if (strcmp ("down", str) == 0)
return GTK_ARROW_DOWN;
else if (strcmp ("left", str) == 0)
return GTK_ARROW_LEFT;
else if (strcmp ("right", str) == 0)
return GTK_ARROW_RIGHT;
else if (strcmp ("none", str) == 0)
return GTK_ARROW_NONE;
else
return -1;
}
static GtkShadowType
meta_gtk_shadow_from_string (const char *str)
{
if (strcmp ("none", str) == 0)
return GTK_SHADOW_NONE;
else if (strcmp ("in", str) == 0)
return GTK_SHADOW_IN;
else if (strcmp ("out", str) == 0)
return GTK_SHADOW_OUT;
else if (strcmp ("etched_in", str) == 0)
return GTK_SHADOW_ETCHED_IN;
else if (strcmp ("etched_out", str) == 0)
return GTK_SHADOW_ETCHED_OUT;
else
return -1;
}
static MetaGradientType
meta_gradient_type_from_string (const char *str)
{
if (strcmp ("vertical", str) == 0)
return META_GRADIENT_VERTICAL;
else if (strcmp ("horizontal", str) == 0)
return META_GRADIENT_HORIZONTAL;
else if (strcmp ("diagonal", str) == 0)
return META_GRADIENT_DIAGONAL;
else
return META_GRADIENT_LAST;
}
/**
* Returns a fill_type from a string. The inverse of
* meta_image_fill_type_to_string().
*
* \param str a string representing a fill_type
* \result the fill_type, or -1 if it represents no fill_type.
*/
static MetaImageFillType
meta_image_fill_type_from_string (const char *str)
{
if (strcmp ("tile", str) == 0)
return META_IMAGE_FILL_TILE;
else if (strcmp ("scale", str) == 0)
return META_IMAGE_FILL_SCALE;
else
return -1;
}
static gboolean
meta_theme_metacity_lookup_color (MetaThemeMetacity *metacity,
const gchar *name,
gchar **value)
{
gchar *result;
*value = NULL;
if (metacity->colors == NULL)
return FALSE;
result = g_hash_table_lookup (metacity->colors, name);
if (!result)
return FALSE;
*value = result;
return TRUE;
}
static MetaColorSpec*
parse_color (MetaThemeMetacity *metacity,
const gchar *str,
GError **err)
{
gchar* referent;
if (theme_allows (metacity, META_THEME_COLOR_CONSTANTS) &&
meta_theme_metacity_lookup_color (metacity, str, &referent))
{
if (referent)
return meta_color_spec_new_from_string (referent, err);
/* no need to free referent: it's a pointer into the actual hash table */
}
return meta_color_spec_new_from_string (str, err);
}
static GdkPixbuf *
meta_theme_load_image (MetaThemeMetacity *metacity,
const gchar *filename,
guint size_of_theme_icons,
GError **error)
{
GdkPixbuf *pixbuf;
pixbuf = g_hash_table_lookup (metacity->images, filename);
if (pixbuf == NULL)
{
if (g_str_has_prefix (filename, "theme:") &&
theme_allows (metacity, META_THEME_IMAGES_FROM_ICON_THEMES))
{
pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
filename + 6, size_of_theme_icons,
0, error);
if (pixbuf == NULL)
return NULL;
}
else
{
char *full_path;
full_path = g_build_filename (metacity->dirname, filename, NULL);
pixbuf = gdk_pixbuf_new_from_file (full_path, error);
if (pixbuf == NULL)
{
g_free (full_path);
return NULL;
}
g_free (full_path);
}
g_hash_table_replace (metacity->images, g_strdup (filename), pixbuf);
}
g_assert (pixbuf);
g_object_ref (G_OBJECT (pixbuf));
return pixbuf;
}
static gboolean
parse_angle (const char *str,
double *val,
GMarkupParseContext *context,
GError **error)
{
if (!parse_double (str, val, context, error))
return FALSE;
if (*val < (0.0 - 1e6) || *val > (360.0 + 1e6))
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Angle must be between 0.0 and 360.0, was %g\n"),
*val);
return FALSE;
}
return TRUE;
}
static void
parse_draw_op_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
ParseInfo *info,
GError **error)
{
MetaThemeMetacity *metacity;
g_return_if_fail (peek_state (info) == STATE_DRAW_OPS);
metacity = info->metacity;
if (g_strcmp0 (element_name, "line") == 0)
{
MetaDrawOp *op;
const char *color;
const char *x1;
const char *y1;
const char *x2;
const char *y2;
const char *dash_on_length;
const char *dash_off_length;
const char *width;
MetaColorSpec *color_spec;
int dash_on_val;
int dash_off_val;
int width_val;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"!color", &color,
"!x1", &x1, "!y1", &y1,
"!x2", &x2, "!y2", &y2,
"dash_on_length", &dash_on_length,
"dash_off_length", &dash_off_length,
"width", &width,
NULL))
return;
dash_on_val = 0;
if (dash_on_length &&
!parse_positive_integer (dash_on_length, &dash_on_val, context, info->metacity, error))
return;
dash_off_val = 0;
if (dash_off_length &&
!parse_positive_integer (dash_off_length, &dash_off_val, context, info->metacity, error))
return;
width_val = 0;
if (width &&
!parse_positive_integer (width, &width_val, context, info->metacity, error))
return;
/* Check last so we don't have to free it when other
* stuff fails
*/
color_spec = parse_color (info->metacity, color, error);
if (color_spec == NULL)
{
add_context_to_error (error, context);
return;
}
op = meta_draw_op_new (META_DRAW_LINE);
op->data.line.color_spec = color_spec;
op->data.line.x1 = meta_draw_spec_new (metacity, x1, NULL);
op->data.line.y1 = meta_draw_spec_new (metacity, y1, NULL);
if (strcmp(x1, x2)==0)
op->data.line.x2 = NULL;
else
op->data.line.x2 = meta_draw_spec_new (metacity, x2, NULL);
if (strcmp(y1, y2)==0)
op->data.line.y2 = NULL;
else
op->data.line.y2 = meta_draw_spec_new (metacity, y2, NULL);
op->data.line.width = width_val;
op->data.line.dash_on_length = dash_on_val;
op->data.line.dash_off_length = dash_off_val;
g_assert (info->op_list);
meta_draw_op_list_append (info->op_list, op);
push_state (info, STATE_LINE);
}
else if (g_strcmp0 (element_name, "rectangle") == 0)
{
MetaDrawOp *op;
const char *color;
const char *x;
const char *y;
const char *width;
const char *height;
const char *filled;
gboolean filled_val;
MetaColorSpec *color_spec;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"!color", &color,
"!x", &x, "!y", &y,
"!width", &width, "!height", &height,
"filled", &filled,
NULL))
return;
filled_val = FALSE;
if (filled && !parse_boolean (filled, &filled_val, context, error))
return;
/* Check last so we don't have to free it when other
* stuff fails
*/
color_spec = parse_color (info->metacity, color, error);
if (color_spec == NULL)
{
add_context_to_error (error, context);
return;
}
op = meta_draw_op_new (META_DRAW_RECTANGLE);
op->data.rectangle.color_spec = color_spec;
op->data.rectangle.x = meta_draw_spec_new (metacity, x, NULL);
op->data.rectangle.y = meta_draw_spec_new (metacity, y, NULL);
op->data.rectangle.width = meta_draw_spec_new (metacity, width, NULL);
op->data.rectangle.height = meta_draw_spec_new (metacity,
height, NULL);
op->data.rectangle.filled = filled_val;
g_assert (info->op_list);
meta_draw_op_list_append (info->op_list, op);
push_state (info, STATE_RECTANGLE);
}
else if (g_strcmp0 (element_name, "arc") == 0)
{
MetaDrawOp *op;
const char *color;
const char *x;
const char *y;
const char *width;
const char *height;
const char *filled;
const char *start_angle;
const char *extent_angle;
const char *from;
const char *to;
gboolean filled_val;
double start_angle_val;
double extent_angle_val;
MetaColorSpec *color_spec;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"!color", &color,
"!x", &x, "!y", &y,
"!width", &width, "!height", &height,
"filled", &filled,
"start_angle", &start_angle,
"extent_angle", &extent_angle,
"from", &from,
"to", &to,
NULL))
return;
if (theme_allows (info->metacity, META_THEME_DEGREES_IN_ARCS))
{
if (start_angle == NULL && from == NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("No \"start_angle\" or \"from\" attribute on element <%s>"), element_name);
return;
}
if (extent_angle == NULL && to == NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("No \"extent_angle\" or \"to\" attribute on element <%s>"), element_name);
return;
}
}
else
{
if (start_angle == NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
ATTRIBUTE_NOT_FOUND, "start_angle", element_name);
return;
}
if (extent_angle == NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
ATTRIBUTE_NOT_FOUND, "extent_angle", element_name);
return;
}
}
if (start_angle == NULL)
{
if (!parse_angle (from, &start_angle_val, context, error))
return;
start_angle_val = (180-start_angle_val)/360.0;
}
else
{
if (!parse_angle (start_angle, &start_angle_val, context, error))
return;
}
if (extent_angle == NULL)
{
if (!parse_angle (to, &extent_angle_val, context, error))
return;
extent_angle_val = ((180-extent_angle_val)/360.0) - start_angle_val;
}
else
{
if (!parse_angle (extent_angle, &extent_angle_val, context, error))
return;
}
filled_val = FALSE;
if (filled && !parse_boolean (filled, &filled_val, context, error))
return;
/* Check last so we don't have to free it when other
* stuff fails
*/
color_spec = parse_color (info->metacity, color, error);
if (color_spec == NULL)
{
add_context_to_error (error, context);
return;
}
op = meta_draw_op_new (META_DRAW_ARC);
op->data.arc.color_spec = color_spec;
op->data.arc.x = meta_draw_spec_new (metacity, x, NULL);
op->data.arc.y = meta_draw_spec_new (metacity, y, NULL);
op->data.arc.width = meta_draw_spec_new (metacity, width, NULL);
op->data.arc.height = meta_draw_spec_new (metacity, height, NULL);
op->data.arc.filled = filled_val;
op->data.arc.start_angle = start_angle_val;
op->data.arc.extent_angle = extent_angle_val;
g_assert (info->op_list);
meta_draw_op_list_append (info->op_list, op);
push_state (info, STATE_ARC);
}
else if (g_strcmp0 (element_name, "clip") == 0)
{
MetaDrawOp *op;
const char *x;
const char *y;
const char *width;
const char *height;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"!x", &x, "!y", &y,
"!width", &width, "!height", &height,
NULL))
return;
op = meta_draw_op_new (META_DRAW_CLIP);
op->data.clip.x = meta_draw_spec_new (metacity, x, NULL);
op->data.clip.y = meta_draw_spec_new (metacity, y, NULL);
op->data.clip.width = meta_draw_spec_new (metacity, width, NULL);
op->data.clip.height = meta_draw_spec_new (metacity, height, NULL);
g_assert (info->op_list);
meta_draw_op_list_append (info->op_list, op);
push_state (info, STATE_CLIP);
}
else if (g_strcmp0 (element_name, "tint") == 0)
{
MetaDrawOp *op;
const char *color;
const char *x;
const char *y;
const char *width;
const char *height;
const char *alpha;
MetaAlphaGradientSpec *alpha_spec;
MetaColorSpec *color_spec;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"!color", &color,
"!x", &x, "!y", &y,
"!width", &width, "!height", &height,
"!alpha", &alpha,
NULL))
return;
alpha_spec = NULL;
if (!parse_alpha (alpha, &alpha_spec, context, error))
return;
/* Check last so we don't have to free it when other
* stuff fails
*/
color_spec = parse_color (info->metacity, color, error);
if (color_spec == NULL)
{
if (alpha_spec)
meta_alpha_gradient_spec_free (alpha_spec);
add_context_to_error (error, context);
return;
}
op = meta_draw_op_new (META_DRAW_TINT);
op->data.tint.color_spec = color_spec;
op->data.tint.alpha_spec = alpha_spec;
op->data.tint.x = meta_draw_spec_new (metacity, x, NULL);
op->data.tint.y = meta_draw_spec_new (metacity, y, NULL);
op->data.tint.width = meta_draw_spec_new (metacity, width, NULL);
op->data.tint.height = meta_draw_spec_new (metacity, height, NULL);
g_assert (info->op_list);
meta_draw_op_list_append (info->op_list, op);
push_state (info, STATE_TINT);
}
else if (g_strcmp0 (element_name, "gradient") == 0)
{
const char *x;
const char *y;
const char *width;
const char *height;
const char *type;
const char *alpha;
MetaAlphaGradientSpec *alpha_spec;
MetaGradientType type_val;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"!type", &type,
"!x", &x, "!y", &y,
"!width", &width, "!height", &height,
"alpha", &alpha,
NULL))
return;
type_val = meta_gradient_type_from_string (type);
if (type_val == META_GRADIENT_LAST)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Did not understand value \"%s\" for type of gradient"),
type);
return;
}
alpha_spec = NULL;
if (alpha && !parse_alpha (alpha, &alpha_spec, context, error))
return;
g_assert (info->op == NULL);
info->op = meta_draw_op_new (META_DRAW_GRADIENT);
info->op->data.gradient.x = meta_draw_spec_new (metacity, x, NULL);
info->op->data.gradient.y = meta_draw_spec_new (metacity, y, NULL);
info->op->data.gradient.width = meta_draw_spec_new (metacity,
width, NULL);
info->op->data.gradient.height = meta_draw_spec_new (metacity,
height, NULL);
info->op->data.gradient.gradient_spec = meta_gradient_spec_new (type_val);
info->op->data.gradient.alpha_spec = alpha_spec;
push_state (info, STATE_GRADIENT);
/* op gets appended on close tag */
}
else if (g_strcmp0 (element_name, "image") == 0)
{
MetaDrawOp *op;
const char *filename;
const char *x;
const char *y;
const char *width;
const char *height;
const char *alpha;
const char *colorize;
const char *fill_type;
MetaAlphaGradientSpec *alpha_spec;
GdkPixbuf *pixbuf;
MetaColorSpec *colorize_spec = NULL;
MetaImageFillType fill_type_val;
int h, w, c;
int pixbuf_width, pixbuf_height, pixbuf_n_channels, pixbuf_rowstride;
guchar *pixbuf_pixels;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"!x", &x, "!y", &y,
"!width", &width, "!height", &height,
"alpha", &alpha, "!filename", &filename,
"colorize", &colorize,
"fill_type", &fill_type,
NULL))
return;
fill_type_val = META_IMAGE_FILL_SCALE;
if (fill_type)
{
fill_type_val = meta_image_fill_type_from_string (fill_type);
if (((int) fill_type_val) == -1)
{
set_error (error, context, G_MARKUP_ERROR,
G_MARKUP_ERROR_PARSE,
_("Did not understand fill type \"%s\" for <%s> element"),
fill_type, element_name);
}
}
/* Check last so we don't have to free it when other
* stuff fails.
*
* If it's a theme image, ask for it at 64px, which is
* the largest possible. We scale it anyway.
*/
pixbuf = meta_theme_load_image (info->metacity, filename, 64, error);
if (pixbuf == NULL)
{
add_context_to_error (error, context);
return;
}
if (colorize)
{
colorize_spec = parse_color (info->metacity, colorize, error);
if (colorize_spec == NULL)
{
add_context_to_error (error, context);
g_object_unref (G_OBJECT (pixbuf));
return;
}
}
alpha_spec = NULL;
if (alpha && !parse_alpha (alpha, &alpha_spec, context, error))
{
g_object_unref (G_OBJECT (pixbuf));
return;
}
op = meta_draw_op_new (META_DRAW_IMAGE);
op->data.image.pixbuf = pixbuf;
op->data.image.colorize_spec = colorize_spec;
op->data.image.x = meta_draw_spec_new (metacity, x, NULL);
op->data.image.y = meta_draw_spec_new (metacity, y, NULL);
op->data.image.width = meta_draw_spec_new (metacity, width, NULL);
op->data.image.height = meta_draw_spec_new (metacity, height, NULL);
op->data.image.alpha_spec = alpha_spec;
op->data.image.fill_type = fill_type_val;
/* Check for vertical & horizontal stripes */
pixbuf_n_channels = gdk_pixbuf_get_n_channels(pixbuf);
pixbuf_width = gdk_pixbuf_get_width(pixbuf);
pixbuf_height = gdk_pixbuf_get_height(pixbuf);
pixbuf_rowstride = gdk_pixbuf_get_rowstride(pixbuf);
pixbuf_pixels = gdk_pixbuf_get_pixels(pixbuf);
/* Check for horizontal stripes */
for (h = 0; h < pixbuf_height; h++)
{
for (w = 1; w < pixbuf_width; w++)
{
for (c = 0; c < pixbuf_n_channels; c++)
{
if (pixbuf_pixels[(h * pixbuf_rowstride) + c] !=
pixbuf_pixels[(h * pixbuf_rowstride) + w + c])
break;
}
if (c < pixbuf_n_channels)
break;
}
if (w < pixbuf_width)
break;
}
if (h >= pixbuf_height)
{
op->data.image.horizontal_stripes = TRUE;
}
else
{
op->data.image.horizontal_stripes = FALSE;
}
/* Check for vertical stripes */
for (w = 0; w < pixbuf_width; w++)
{
for (h = 1; h < pixbuf_height; h++)
{
for (c = 0; c < pixbuf_n_channels; c++)
{
if (pixbuf_pixels[w + c] !=
pixbuf_pixels[(h * pixbuf_rowstride) + w + c])
break;
}
if (c < pixbuf_n_channels)
break;
}
if (h < pixbuf_height)
break;
}
if (w >= pixbuf_width)
{
op->data.image.vertical_stripes = TRUE;
}
else
{
op->data.image.vertical_stripes = FALSE;
}
g_assert (info->op_list);
meta_draw_op_list_append (info->op_list, op);
push_state (info, STATE_IMAGE);
}
else if (g_strcmp0 (element_name, "gtk_arrow") == 0)
{
MetaDrawOp *op;
const char *state;
const char *shadow;
const char *arrow;
const char *x;
const char *y;
const char *width;
const char *height;
const char *filled;
gboolean filled_val;
GtkStateFlags state_val;
GtkShadowType shadow_val;
GtkArrowType arrow_val;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"!state", &state,
"!shadow", &shadow,
"!arrow", &arrow,
"!x", &x, "!y", &y,
"!width", &width, "!height", &height,
"filled", &filled,
NULL))
return;
filled_val = TRUE;
if (filled && !parse_boolean (filled, &filled_val, context, error))
return;
if (!meta_gtk_state_from_string (state, &state_val))
{
set_error (error, context, G_MARKUP_ERROR,
G_MARKUP_ERROR_PARSE,
_("Did not understand state \"%s\" for <%s> element"),
state, element_name);
return;
}
shadow_val = meta_gtk_shadow_from_string (shadow);
if (((int) shadow_val) == -1)
{
set_error (error, context, G_MARKUP_ERROR,
G_MARKUP_ERROR_PARSE,
_("Did not understand shadow \"%s\" for <%s> element"),
shadow, element_name);
return;
}
arrow_val = meta_gtk_arrow_from_string (arrow);
if (((int) arrow_val) == -1)
{
set_error (error, context, G_MARKUP_ERROR,
G_MARKUP_ERROR_PARSE,
_("Did not understand arrow \"%s\" for <%s> element"),
arrow, element_name);
return;
}
op = meta_draw_op_new (META_DRAW_GTK_ARROW);
op->data.gtk_arrow.x = meta_draw_spec_new (metacity, x, NULL);
op->data.gtk_arrow.y = meta_draw_spec_new (metacity, y, NULL);
op->data.gtk_arrow.width = meta_draw_spec_new (metacity, width, NULL);
op->data.gtk_arrow.height = meta_draw_spec_new (metacity,
height, NULL);
op->data.gtk_arrow.filled = filled_val;
op->data.gtk_arrow.state = state_val;
op->data.gtk_arrow.shadow = shadow_val;
op->data.gtk_arrow.arrow = arrow_val;
g_assert (info->op_list);
meta_draw_op_list_append (info->op_list, op);
push_state (info, STATE_GTK_ARROW);
}
else if (g_strcmp0 (element_name, "gtk_box") == 0)
{
MetaDrawOp *op;
const char *state;
const char *shadow;
const char *x;
const char *y;
const char *width;
const char *height;
GtkStateFlags state_val;
GtkShadowType shadow_val;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"!state", &state,
"!shadow", &shadow,
"!x", &x, "!y", &y,
"!width", &width, "!height", &height,
NULL))
return;
if (!meta_gtk_state_from_string (state, &state_val))
{
set_error (error, context, G_MARKUP_ERROR,
G_MARKUP_ERROR_PARSE,
_("Did not understand state \"%s\" for <%s> element"),
state, element_name);
return;
}
shadow_val = meta_gtk_shadow_from_string (shadow);
if (((int) shadow_val) == -1)
{
set_error (error, context, G_MARKUP_ERROR,
G_MARKUP_ERROR_PARSE,
_("Did not understand shadow \"%s\" for <%s> element"),
shadow, element_name);
return;
}
op = meta_draw_op_new (META_DRAW_GTK_BOX);
op->data.gtk_box.x = meta_draw_spec_new (metacity, x, NULL);
op->data.gtk_box.y = meta_draw_spec_new (metacity, y, NULL);
op->data.gtk_box.width = meta_draw_spec_new (metacity, width, NULL);
op->data.gtk_box.height = meta_draw_spec_new (metacity, height, NULL);
op->data.gtk_box.state = state_val;
op->data.gtk_box.shadow = shadow_val;
g_assert (info->op_list);
meta_draw_op_list_append (info->op_list, op);
push_state (info, STATE_GTK_BOX);
}
else if (g_strcmp0 (element_name, "gtk_vline") == 0)
{
MetaDrawOp *op;
const char *state;
const char *x;
const char *y1;
const char *y2;
GtkStateFlags state_val;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"!state", &state,
"!x", &x, "!y1", &y1, "!y2", &y2,
NULL))
return;
if (!meta_gtk_state_from_string (state, &state_val))
{
set_error (error, context, G_MARKUP_ERROR,
G_MARKUP_ERROR_PARSE,
_("Did not understand state \"%s\" for <%s> element"),
state, element_name);
return;
}
op = meta_draw_op_new (META_DRAW_GTK_VLINE);
op->data.gtk_vline.x = meta_draw_spec_new (metacity, x, NULL);
op->data.gtk_vline.y1 = meta_draw_spec_new (metacity, y1, NULL);
op->data.gtk_vline.y2 = meta_draw_spec_new (metacity, y2, NULL);
op->data.gtk_vline.state = state_val;
g_assert (info->op_list);
meta_draw_op_list_append (info->op_list, op);
push_state (info, STATE_GTK_VLINE);
}
else if (g_strcmp0 (element_name, "icon") == 0)
{
MetaDrawOp *op;
const char *x;
const char *y;
const char *width;
const char *height;
const char *alpha;
const char *fill_type;
MetaAlphaGradientSpec *alpha_spec;
MetaImageFillType fill_type_val;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"!x", &x, "!y", &y,
"!width", &width, "!height", &height,
"alpha", &alpha,
"fill_type", &fill_type,
NULL))
return;
fill_type_val = META_IMAGE_FILL_SCALE;
if (fill_type)
{
fill_type_val = meta_image_fill_type_from_string (fill_type);
if (((int) fill_type_val) == -1)
{
set_error (error, context, G_MARKUP_ERROR,
G_MARKUP_ERROR_PARSE,
_("Did not understand fill type \"%s\" for <%s> element"),
fill_type, element_name);
}
}
alpha_spec = NULL;
if (alpha && !parse_alpha (alpha, &alpha_spec, context, error))
return;
op = meta_draw_op_new (META_DRAW_ICON);
op->data.icon.x = meta_draw_spec_new (metacity, x, NULL);
op->data.icon.y = meta_draw_spec_new (metacity, y, NULL);
op->data.icon.width = meta_draw_spec_new (metacity, width, NULL);
op->data.icon.height = meta_draw_spec_new (metacity, height, NULL);
op->data.icon.alpha_spec = alpha_spec;
op->data.icon.fill_type = fill_type_val;
g_assert (info->op_list);
meta_draw_op_list_append (info->op_list, op);
push_state (info, STATE_ICON);
}
else if (g_strcmp0 (element_name, "title") == 0)
{
MetaDrawOp *op;
const char *color;
const char *x;
const char *y;
const char *ellipsize_width;
MetaColorSpec *color_spec;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"!color", &color,
"!x", &x, "!y", &y,
"ellipsize_width", &ellipsize_width,
NULL))
return;
if (ellipsize_width && peek_required_version (info) < 3001)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
ATTRIBUTE_NOT_FOUND, "ellipsize_width", element_name);
return;
}
/* Check last so we don't have to free it when other
* stuff fails
*/
color_spec = parse_color (info->metacity, color, error);
if (color_spec == NULL)
{
add_context_to_error (error, context);
return;
}
op = meta_draw_op_new (META_DRAW_TITLE);
op->data.title.color_spec = color_spec;
op->data.title.x = meta_draw_spec_new (metacity, x, NULL);
op->data.title.y = meta_draw_spec_new (metacity, y, NULL);
if (ellipsize_width)
op->data.title.ellipsize_width = meta_draw_spec_new (metacity, ellipsize_width, NULL);
g_assert (info->op_list);
meta_draw_op_list_append (info->op_list, op);
push_state (info, STATE_TITLE);
}
else if (g_strcmp0 (element_name, "include") == 0)
{
MetaDrawOp *op;
const char *name;
const char *x;
const char *y;
const char *width;
const char *height;
MetaDrawOpList *op_list;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"x", &x, "y", &y,
"width", &width, "height", &height,
"!name", &name,
NULL))
return;
/* x/y/width/height default to 0,0,width,height - should
* probably do this for all the draw ops
*/
op_list = meta_theme_metacity_lookup_draw_op_list (info->metacity, name);
if (op_list == NULL)
{
set_error (error, context, G_MARKUP_ERROR,
G_MARKUP_ERROR_PARSE,
_("No <draw_ops> called \"%s\" has been defined"),
name);
return;
}
g_assert (info->op_list);
if (op_list == info->op_list ||
meta_draw_op_list_contains (op_list, info->op_list))
{
set_error (error, context, G_MARKUP_ERROR,
G_MARKUP_ERROR_PARSE,
_("Including draw_ops \"%s\" here would create a circular reference"),
name);
return;
}
op = meta_draw_op_new (META_DRAW_OP_LIST);
meta_draw_op_list_ref (op_list);
op->data.op_list.op_list = op_list;
op->data.op_list.x = meta_draw_spec_new (metacity, x ? x : "0", NULL);
op->data.op_list.y = meta_draw_spec_new (metacity, y ? y : "0", NULL);
op->data.op_list.width = meta_draw_spec_new (metacity,
width ? width : "width",
NULL);
op->data.op_list.height = meta_draw_spec_new (metacity,
height ? height : "height",
NULL);
meta_draw_op_list_append (info->op_list, op);
push_state (info, STATE_INCLUDE);
}
else if (g_strcmp0 (element_name, "tile") == 0)
{
MetaDrawOp *op;
const char *name;
const char *x;
const char *y;
const char *width;
const char *height;
const char *tile_xoffset;
const char *tile_yoffset;
const char *tile_width;
const char *tile_height;
MetaDrawOpList *op_list;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"x", &x, "y", &y,
"width", &width, "height", &height,
"!name", &name,
"tile_xoffset", &tile_xoffset,
"tile_yoffset", &tile_yoffset,
"!tile_width", &tile_width,
"!tile_height", &tile_height,
NULL))
return;
/* These default to 0 */
op_list = meta_theme_metacity_lookup_draw_op_list (info->metacity, name);
if (op_list == NULL)
{
set_error (error, context, G_MARKUP_ERROR,
G_MARKUP_ERROR_PARSE,
_("No <draw_ops> called \"%s\" has been defined"),
name);
return;
}
g_assert (info->op_list);
if (op_list == info->op_list ||
meta_draw_op_list_contains (op_list, info->op_list))
{
set_error (error, context, G_MARKUP_ERROR,
G_MARKUP_ERROR_PARSE,
_("Including draw_ops \"%s\" here would create a circular reference"),
name);
return;
}
op = meta_draw_op_new (META_DRAW_TILE);
meta_draw_op_list_ref (op_list);
op->data.tile.x = meta_draw_spec_new (metacity, x ? x : "0", NULL);
op->data.tile.y = meta_draw_spec_new (metacity, y ? y : "0", NULL);
op->data.tile.width = meta_draw_spec_new (metacity,
width ? width : "width",
NULL);
op->data.tile.height = meta_draw_spec_new (metacity,
height ? height : "height",
NULL);
op->data.tile.tile_xoffset = meta_draw_spec_new (metacity,
tile_xoffset ? tile_xoffset : "0",
NULL);
op->data.tile.tile_yoffset = meta_draw_spec_new (metacity,
tile_yoffset ? tile_yoffset : "0",
NULL);
op->data.tile.tile_width = meta_draw_spec_new (metacity, tile_width, NULL);
op->data.tile.tile_height = meta_draw_spec_new (metacity, tile_height, NULL);
op->data.tile.op_list = op_list;
meta_draw_op_list_append (info->op_list, op);
push_state (info, STATE_TILE);
}
else
{
set_error (error, context,
G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Element <%s> is not allowed below <%s>"),
element_name, "draw_ops");
}
}
static void
parse_gradient_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
ParseInfo *info,
GError **error)
{
g_return_if_fail (peek_state (info) == STATE_GRADIENT);
if (g_strcmp0 (element_name, "color") == 0)
{
const char *value = NULL;
MetaColorSpec *color_spec;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"!value", &value,
NULL))
return;
color_spec = parse_color (info->metacity, value, error);
if (color_spec == NULL)
{
add_context_to_error (error, context);
return;
}
g_assert (info->op);
g_assert (info->op->type == META_DRAW_GRADIENT);
g_assert (info->op->data.gradient.gradient_spec != NULL);
meta_gradient_spec_add_color_spec (info->op->data.gradient.gradient_spec,
color_spec);
push_state (info, STATE_COLOR);
}
else
{
set_error (error, context,
G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Element <%s> is not allowed below <%s>"),
element_name, "gradient");
}
}
static MetaButtonState
meta_button_state_from_string (const char *str)
{
if (strcmp ("normal", str) == 0)
return META_BUTTON_STATE_NORMAL;
else if (strcmp ("pressed", str) == 0)
return META_BUTTON_STATE_PRESSED;
else if (strcmp ("prelight", str) == 0)
return META_BUTTON_STATE_PRELIGHT;
else
return META_BUTTON_STATE_LAST;
}
static MetaButtonFunction
meta_button_function_from_string (MetaThemeMetacity *metacity,
const gchar *str)
{
if (theme_allows (metacity, META_THEME_SHADE_STICK_ABOVE_BUTTONS))
{
if (strcmp ("shade", str) == 0)
return META_BUTTON_FUNCTION_SHADE;
else if (strcmp ("above", str) == 0)
return META_BUTTON_FUNCTION_ABOVE;
else if (strcmp ("stick", str) == 0)
return META_BUTTON_FUNCTION_STICK;
else if (strcmp ("unshade", str) == 0)
return META_BUTTON_FUNCTION_UNSHADE;
else if (strcmp ("unabove", str) == 0)
return META_BUTTON_FUNCTION_UNABOVE;
else if (strcmp ("unstick", str) == 0)
return META_BUTTON_FUNCTION_UNSTICK;
}
if (strcmp ("close", str) == 0)
return META_BUTTON_FUNCTION_CLOSE;
else if (strcmp ("maximize", str) == 0)
return META_BUTTON_FUNCTION_MAXIMIZE;
else if (strcmp ("minimize", str) == 0)
return META_BUTTON_FUNCTION_MINIMIZE;
else if (strcmp ("menu", str) == 0)
return META_BUTTON_FUNCTION_MENU;
else if (strcmp ("appmenu", str) == 0)
return META_BUTTON_FUNCTION_APPMENU;
else if (strcmp ("left_left_background", str) == 0)
return META_BUTTON_FUNCTION_LEFT_LEFT_BACKGROUND;
else if (strcmp ("left_middle_background", str) == 0)
return META_BUTTON_FUNCTION_LEFT_MIDDLE_BACKGROUND;
else if (strcmp ("left_right_background", str) == 0)
return META_BUTTON_FUNCTION_LEFT_RIGHT_BACKGROUND;
else if (strcmp ("left_single_background", str) == 0)
return META_BUTTON_FUNCTION_LEFT_SINGLE_BACKGROUND;
else if (strcmp ("right_left_background", str) == 0)
return META_BUTTON_FUNCTION_RIGHT_LEFT_BACKGROUND;
else if (strcmp ("right_middle_background", str) == 0)
return META_BUTTON_FUNCTION_RIGHT_MIDDLE_BACKGROUND;
else if (strcmp ("right_right_background", str) == 0)
return META_BUTTON_FUNCTION_RIGHT_RIGHT_BACKGROUND;
else if (strcmp ("right_single_background", str) == 0)
return META_BUTTON_FUNCTION_RIGHT_SINGLE_BACKGROUND;
else
return META_BUTTON_FUNCTION_LAST;
}
static MetaFramePiece
meta_frame_piece_from_string (const char *str)
{
if (strcmp ("entire_background", str) == 0)
return META_FRAME_PIECE_ENTIRE_BACKGROUND;
else if (strcmp ("titlebar", str) == 0)
return META_FRAME_PIECE_TITLEBAR;
else if (strcmp ("titlebar_middle", str) == 0)
return META_FRAME_PIECE_TITLEBAR_MIDDLE;
else if (strcmp ("left_titlebar_edge", str) == 0)
return META_FRAME_PIECE_LEFT_TITLEBAR_EDGE;
else if (strcmp ("right_titlebar_edge", str) == 0)
return META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE;
else if (strcmp ("top_titlebar_edge", str) == 0)
return META_FRAME_PIECE_TOP_TITLEBAR_EDGE;
else if (strcmp ("bottom_titlebar_edge", str) == 0)
return META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE;
else if (strcmp ("title", str) == 0)
return META_FRAME_PIECE_TITLE;
else if (strcmp ("left_edge", str) == 0)
return META_FRAME_PIECE_LEFT_EDGE;
else if (strcmp ("right_edge", str) == 0)
return META_FRAME_PIECE_RIGHT_EDGE;
else if (strcmp ("bottom_edge", str) == 0)
return META_FRAME_PIECE_BOTTOM_EDGE;
else if (strcmp ("overlay", str) == 0)
return META_FRAME_PIECE_OVERLAY;
else
return META_FRAME_PIECE_LAST;
}
static void
parse_style_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
ParseInfo *info,
GError **error)
{
g_return_if_fail (peek_state (info) == STATE_FRAME_STYLE);
g_assert (info->style);
if (g_strcmp0 (element_name, "piece") == 0)
{
const char *position = NULL;
const char *draw_ops = NULL;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"!position", &position,
"draw_ops", &draw_ops,
NULL))
return;
info->piece = meta_frame_piece_from_string (position);
if (info->piece == META_FRAME_PIECE_LAST)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Unknown position \"%s\" for frame piece"),
position);
return;
}
if (info->style->pieces[info->piece] != NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Frame style already has a piece at position %s"),
position);
return;
}
g_assert (info->op_list == NULL);
if (draw_ops)
{
MetaDrawOpList *op_list;
op_list = meta_theme_metacity_lookup_draw_op_list (info->metacity, draw_ops);
if (op_list == NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("No <draw_ops> with the name \"%s\" has been defined"),
draw_ops);
return;
}
meta_draw_op_list_ref (op_list);
info->op_list = op_list;
}
push_state (info, STATE_PIECE);
}
else if (g_strcmp0 (element_name, "button") == 0)
{
const char *function = NULL;
const char *state = NULL;
const char *draw_ops = NULL;
guint earliest_version;
gint required_version;
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
error,
"!function", &function,
"!state", &state,
"draw_ops", &draw_ops,
NULL))
return;
info->button_function = meta_button_function_from_string (info->metacity, function);
if (info->button_function == META_BUTTON_FUNCTION_LAST)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Unknown function \"%s\" for button"),
function);
return;
}
earliest_version = meta_theme_metacity_earliest_version_with_button (info->button_function);
required_version = peek_required_version (info);
if (earliest_version > (guint) required_version)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Button function '%s' does not exist in this version (%d, need %d)"),
function, required_version, earliest_version);
return;
}
info->button_state = meta_button_state_from_string (state);
if (info->button_state == META_BUTTON_STATE_LAST)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Unknown state '%s' for button"), state);
return;
}
if (info->style->buttons[info->button_function][info->button_state] != NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Frame style already has a button for function %s state %s"),
function, state);
return;
}
g_assert (info->op_list == NULL);
if (draw_ops)
{
MetaDrawOpList *op_list;
op_list = meta_theme_metacity_lookup_draw_op_list (info->metacity,
draw_ops);
if (op_list == NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("No <draw_ops> with the name '%s' has been defined"),
draw_ops);
return;
}
meta_draw_op_list_ref (op_list);
info->op_list = op_list;
}
push_state (info, STATE_BUTTON);
}
else
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Element <%s> is not allowed below <%s>"),
element_name, "frame_style");
}
}
static void
parse_piece_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
ParseInfo *info,
GError **error)
{
g_return_if_fail (peek_state (info) == STATE_PIECE);
if (g_strcmp0 (element_name, "draw_ops") == 0)
{
if (info->op_list)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Can't have a two draw_ops for a <piece> element (theme specified a draw_ops attribute and also a <draw_ops> element, or specified two elements)"));
return;
}
if (!check_no_attributes (context, element_name, attribute_names,
attribute_values, error))
return;
g_assert (info->op_list == NULL);
info->op_list = meta_draw_op_list_new (2);
push_state (info, STATE_DRAW_OPS);
}
else
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Element <%s> is not allowed below <%s>"),
element_name, "piece");
}
}
static void
parse_button_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
ParseInfo *info,
GError **error)
{
g_return_if_fail (peek_state (info) == STATE_BUTTON);
if (g_strcmp0 (element_name, "draw_ops") == 0)
{
if (info->op_list)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Can't have a two draw_ops for a <button> element (theme specified a draw_ops attribute and also a <draw_ops> element, or specified two elements)"));
return;
}
if (!check_no_attributes (context, element_name, attribute_names,
attribute_values, error))
return;
g_assert (info->op_list == NULL);
info->op_list = meta_draw_op_list_new (2);
push_state (info, STATE_DRAW_OPS);
}
else
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Element <%s> is not allowed below <%s>"),
element_name, "button");
}
}
static void
parse_menu_icon_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
ParseInfo *info,
GError **error)
{
g_return_if_fail (peek_state (info) == STATE_MENU_ICON);
if (g_strcmp0 (element_name, "draw_ops") == 0)
{
if (info->op_list)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Can't have a two draw_ops for a <menu_icon> element (theme specified a draw_ops attribute and also a <draw_ops> element, or specified two elements)"));
return;
}
if (!check_no_attributes (context, element_name, attribute_names,
attribute_values, error))
return;
g_assert (info->op_list == NULL);
info->op_list = meta_draw_op_list_new (2);
push_state (info, STATE_DRAW_OPS);
}
else
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Element <%s> is not allowed below <%s>"),
element_name, "menu_icon");
}
}
static MetaFrameFocus
meta_frame_focus_from_string (const char *str)
{
if (strcmp ("no", str) == 0)
return META_FRAME_FOCUS_NO;
else if (strcmp ("yes", str) == 0)
return META_FRAME_FOCUS_YES;
else
return META_FRAME_FOCUS_LAST;
}
static MetaFrameResize
meta_frame_resize_from_string (const char *str)
{
if (strcmp ("none", str) == 0)
return META_FRAME_RESIZE_NONE;
else if (strcmp ("vertical", str) == 0)
return META_FRAME_RESIZE_VERTICAL;
else if (strcmp ("horizontal", str) == 0)
return META_FRAME_RESIZE_HORIZONTAL;
else if (strcmp ("both", str) == 0)
return META_FRAME_RESIZE_BOTH;
else
return META_FRAME_RESIZE_LAST;
}
static MetaFrameState
meta_frame_state_from_string (const char *str)
{
if (strcmp ("normal", str) == 0)
return META_FRAME_STATE_NORMAL;
else if (strcmp ("maximized", str) == 0)
return META_FRAME_STATE_MAXIMIZED;
else if (strcmp ("tiled_left", str) == 0)
return META_FRAME_STATE_TILED_LEFT;
else if (strcmp ("tiled_right", str) == 0)
return META_FRAME_STATE_TILED_RIGHT;
else if (strcmp ("shaded", str) == 0)
return META_FRAME_STATE_SHADED;
else if (strcmp ("maximized_and_shaded", str) == 0)
return META_FRAME_STATE_MAXIMIZED_AND_SHADED;
else if (strcmp ("tiled_left_and_shaded", str) == 0)
return META_FRAME_STATE_TILED_LEFT_AND_SHADED;
else if (strcmp ("tiled_right_and_shaded", str) == 0)
return META_FRAME_STATE_TILED_RIGHT_AND_SHADED;
else
return META_FRAME_STATE_LAST;
}
static void
parse_style_set_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
ParseInfo *info,
GError **error)
{
g_return_if_fail (peek_state (info) == STATE_FRAME_STYLE_SET);
if (g_strcmp0 (element_name, "frame") == 0)
{
const char *focus = NULL;
const char *state = NULL;
const char *resize = NULL;
const char *style = NULL;
MetaFrameFocus frame_focus;
MetaFrameState frame_state;
MetaFrameResize frame_resize;
MetaFrameStyle *frame_style;
if (!locate_attributes (context, element_name, attribute_names,
attribute_values, error,
"!focus", &focus,
"!state", &state,
"resize", &resize,
"!style", &style,
NULL))
return;
frame_focus = meta_frame_focus_from_string (focus);
if (frame_focus == META_FRAME_FOCUS_LAST)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("'%s' is not a valid value for focus attribute"),
focus);
return;
}
frame_state = meta_frame_state_from_string (state);
if (frame_state == META_FRAME_STATE_LAST)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("'%s' is not a valid value for state attribute"),
focus);
return;
}
frame_style = meta_theme_metacity_lookup_style (info->metacity, style);
if (frame_style == NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("A style called '%s' has not been defined"), style);
return;
}
switch (frame_state)
{
case META_FRAME_STATE_NORMAL:
if (resize == NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
ATTRIBUTE_NOT_FOUND, "resize", element_name);
return;
}
frame_resize = meta_frame_resize_from_string (resize);
if (frame_resize == META_FRAME_RESIZE_LAST)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("'%s' is not a valid value for resize attribute"),
focus);
return;
}
break;
case META_FRAME_STATE_SHADED:
if (theme_allows (info->metacity, META_THEME_UNRESIZABLE_SHADED_STYLES))
{
if (resize == NULL)
/* In state="normal" we would complain here. But instead we accept
* not having a resize attribute and default to resize="both", since
* that most closely mimics what we did in v1, and thus people can
* upgrade a theme to v2 without as much hassle.
*/
frame_resize = META_FRAME_RESIZE_BOTH;
else
{
frame_resize = meta_frame_resize_from_string (resize);
if (frame_resize == META_FRAME_RESIZE_LAST)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("'%s' is not a valid value for resize attribute"),
focus);
return;
}
}
}
else /* v1 theme */
{
if (resize != NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Should not have 'resize' attribute on <%s> element for maximized/shaded states"),
element_name);
return;
}
/* resize="both" is equivalent to the old behaviour */
frame_resize = META_FRAME_RESIZE_BOTH;
}
break;
case META_FRAME_STATE_MAXIMIZED:
case META_FRAME_STATE_TILED_LEFT:
case META_FRAME_STATE_TILED_RIGHT:
case META_FRAME_STATE_MAXIMIZED_AND_SHADED:
case META_FRAME_STATE_TILED_LEFT_AND_SHADED:
case META_FRAME_STATE_TILED_RIGHT_AND_SHADED:
case META_FRAME_STATE_LAST:
default:
if (resize != NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Should not have 'resize' attribute on <%s> element for maximized states"),
element_name);
return;
}
frame_resize = META_FRAME_RESIZE_LAST;
break;
}
switch (frame_state)
{
case META_FRAME_STATE_NORMAL:
if (info->style_set->normal_styles[frame_resize][frame_focus])
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Style has already been specified for state %s resize %s focus %s"),
state, resize, focus);
return;
}
meta_frame_style_ref (frame_style);
info->style_set->normal_styles[frame_resize][frame_focus] = frame_style;
break;
case META_FRAME_STATE_MAXIMIZED:
if (info->style_set->maximized_styles[frame_focus])
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Style has already been specified for state %s focus %s"),
state, focus);
return;
}
meta_frame_style_ref (frame_style);
info->style_set->maximized_styles[frame_focus] = frame_style;
break;
case META_FRAME_STATE_TILED_LEFT:
if (info->style_set->tiled_left_styles[frame_focus])
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Style has already been specified for state %s focus %s"),
state, focus);
return;
}
meta_frame_style_ref (frame_style);
info->style_set->tiled_left_styles[frame_focus] = frame_style;
break;
case META_FRAME_STATE_TILED_RIGHT:
if (info->style_set->tiled_right_styles[frame_focus])
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Style has already been specified for state %s focus %s"),
state, focus);
return;
}
meta_frame_style_ref (frame_style);
info->style_set->tiled_right_styles[frame_focus] = frame_style;
break;
case META_FRAME_STATE_SHADED:
if (info->style_set->shaded_styles[frame_resize][frame_focus])
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Style has already been specified for state %s resize %s focus %s"),
state, resize, focus);
return;
}
meta_frame_style_ref (frame_style);
info->style_set->shaded_styles[frame_resize][frame_focus] = frame_style;
break;
case META_FRAME_STATE_MAXIMIZED_AND_SHADED:
if (info->style_set->maximized_and_shaded_styles[frame_focus])
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Style has already been specified for state %s focus %s"),
state, focus);
return;
}
meta_frame_style_ref (frame_style);
info->style_set->maximized_and_shaded_styles[frame_focus] = frame_style;
break;
case META_FRAME_STATE_TILED_LEFT_AND_SHADED:
if (info->style_set->tiled_left_and_shaded_styles[frame_focus])
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Style has already been specified for state %s focus %s"),
state, focus);
return;
}
meta_frame_style_ref (frame_style);
info->style_set->tiled_left_and_shaded_styles[frame_focus] = frame_style;
break;
case META_FRAME_STATE_TILED_RIGHT_AND_SHADED:
if (info->style_set->tiled_right_and_shaded_styles[frame_focus])
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Style has already been specified for state %s focus %s"),
state, focus);
return;
}
meta_frame_style_ref (frame_style);
info->style_set->tiled_right_and_shaded_styles[frame_focus] = frame_style;
break;
case META_FRAME_STATE_LAST:
default:
g_assert_not_reached ();
break;
}
push_state (info, STATE_FRAME);
}
else
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Element <%s> is not allowed below <%s>"),
element_name, "frame_style_set");
}
}
static const gchar *
find_version (const gchar **attribute_names,
const gchar **attribute_values)
{
int i;
for (i = 0; attribute_names[i]; i++)
{
if (strcmp (attribute_names[i], "version") == 0)
return attribute_values[i];
}
return NULL;
}
/* Returns whether the version element was successfully parsed.
* If successfully parsed, then two additional items are returned:
*
* satisfied: whether this version of Mutter meets the version check
* minimum_required: minimum version of theme format required by version check
*/
static gboolean
check_version (GMarkupParseContext *context,
const char *version_str,
gboolean *satisfied,
guint *minimum_required,
GError **error)
{
static GRegex *version_regex;
GMatchInfo *info;
char *comparison_str, *major_str, *minor_str;
guint version;
*minimum_required = 0;
if (!version_regex)
version_regex = g_regex_new ("^\\s*([<>]=?)\\s*(\\d+)(\\.\\d+)?\\s*$", 0, 0, NULL);
if (!g_regex_match (version_regex, version_str, 0, &info))
{
g_match_info_free (info);
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Bad version specification '%s'"), version_str);
return FALSE;
}
comparison_str = g_match_info_fetch (info, 1);
major_str = g_match_info_fetch (info, 2);
minor_str = g_match_info_fetch (info, 3);
version = 1000 * atoi (major_str);
/* might get NULL, see: https://bugzilla.gnome.org/review?bug=588217 */
if (minor_str && minor_str[0])
version += atoi (minor_str + 1);
if (comparison_str[0] == '<')
{
if (comparison_str[1] == '=')
*satisfied = THEME_VERSION <= version;
else
*satisfied = THEME_VERSION < version;
}
else
{
if (comparison_str[1] == '=')
{
*satisfied = THEME_VERSION >= version;
*minimum_required = version;
}
else
{
*satisfied = THEME_VERSION > version;
*minimum_required = version + 1;
}
}
g_free (comparison_str);
g_free (major_str);
g_free (minor_str);
g_match_info_free (info);
return TRUE;
}
static void
start_element_handler (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
gpointer user_data,
GError **error)
{
ParseInfo *info = user_data;
const char *version;
guint required_version = 0;
if (info->skip_level > 0)
{
info->skip_level++;
return;
}
required_version = peek_required_version (info);
version = find_version (attribute_names, attribute_values);
if (version != NULL)
{
gboolean satisfied;
guint element_required;
if (required_version < 3000)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("'version' attribute cannot be used in metacity-theme-1.xml or metacity-theme-2.xml"));
return;
}
if (!check_version (context, version, &satisfied, &element_required, error))
return;
/* Two different ways of handling an unsatisfied version check:
* for the toplevel element of a file, we throw an error back so
* that the controlling code can go ahead and look for an
* alternate metacity-theme-1.xml or metacity-theme-2.xml; for
* other elements we just silently skip the element and children.
*/
if (peek_state (info) == STATE_START)
{
if (satisfied)
{
if (element_required > info->metacity->format_version)
info->metacity->format_version = element_required;
}
else
{
set_error (error, context, META_THEME_ERROR, META_THEME_ERROR_TOO_OLD,
_("Theme requires version %s but latest supported theme version is %d.%d"),
version, THEME_VERSION, THEME_MINOR_VERSION);
return;
}
}
else if (!satisfied)
{
info->skip_level = 1;
return;
}
if (element_required > required_version)
required_version = element_required;
}
push_required_version (info, required_version);
switch (peek_state (info))
{
case STATE_START:
if (strcmp (element_name, "metacity_theme") == 0)
push_state (info, STATE_THEME);
else
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Outermost element in theme must be <metacity_theme> not <%s>"),
element_name);
break;
case STATE_THEME:
parse_toplevel_element (context, element_name, attribute_names,
attribute_values, info, error);
break;
case STATE_INFO:
parse_info_element (context, element_name, attribute_names,
attribute_values, info, error);
break;
case STATE_NAME:
case STATE_AUTHOR:
case STATE_COPYRIGHT:
case STATE_DATE:
case STATE_DESCRIPTION:
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Element <%s> is not allowed inside a name/author/date/description element"),
element_name);
break;
case STATE_CONSTANT:
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Element <%s> is not allowed inside a <constant> element"),
element_name);
break;
case STATE_FRAME_GEOMETRY:
parse_geometry_element (context, element_name, attribute_names,
attribute_values, info, error);
break;
case STATE_DISTANCE:
case STATE_BORDER:
case STATE_ASPECT_RATIO:
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Element <%s> is not allowed inside a distance/border/aspect_ratio element"),
element_name);
break;
case STATE_DRAW_OPS:
parse_draw_op_element (context, element_name, attribute_names,
attribute_values, info, error);
break;
case STATE_LINE:
case STATE_RECTANGLE:
case STATE_ARC:
case STATE_CLIP:
case STATE_TINT:
case STATE_IMAGE:
case STATE_GTK_ARROW:
case STATE_GTK_BOX:
case STATE_GTK_VLINE:
case STATE_ICON:
case STATE_TITLE:
case STATE_INCLUDE:
case STATE_TILE:
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Element <%s> is not allowed inside a draw operation element"),
element_name);
break;
case STATE_GRADIENT:
parse_gradient_element (context, element_name, attribute_names,
attribute_values, info, error);
break;
case STATE_COLOR:
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Element <%s> is not allowed inside a <%s> element"),
element_name, "color");
break;
case STATE_FRAME_STYLE:
parse_style_element (context, element_name, attribute_names,
attribute_values, info, error);
break;
case STATE_PIECE:
parse_piece_element (context, element_name, attribute_names,
attribute_values, info, error);
break;
case STATE_BUTTON:
parse_button_element (context, element_name, attribute_names,
attribute_values, info, error);
break;
case STATE_MENU_ICON:
parse_menu_icon_element (context, element_name, attribute_names,
attribute_values, info, error);
break;
case STATE_FRAME_STYLE_SET:
parse_style_set_element (context, element_name, attribute_names,
attribute_values, info, error);
break;
case STATE_FRAME:
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Element <%s> is not allowed inside a <%s> element"),
element_name, "frame");
break;
case STATE_WINDOW:
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Element <%s> is not allowed inside a <%s> element"),
element_name, "window");
break;
case STATE_FALLBACK:
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("Element <%s> is not allowed inside a <%s> element"),
element_name, "fallback");
break;
default:
break;
}
}
static const char*
meta_frame_type_to_string (MetaFrameType type)
{
switch (type)
{
case META_FRAME_TYPE_NORMAL:
return "normal";
case META_FRAME_TYPE_DIALOG:
return "dialog";
case META_FRAME_TYPE_MODAL_DIALOG:
return "modal_dialog";
case META_FRAME_TYPE_UTILITY:
return "utility";
case META_FRAME_TYPE_MENU:
return "menu";
case META_FRAME_TYPE_BORDER:
return "border";
case META_FRAME_TYPE_ATTACHED:
return "attached";
case META_FRAME_TYPE_LAST:
break;
default:
break;
}
return "<unknown>";
}
static gboolean
theme_validate (MetaThemeMetacity *metacity,
GError **error)
{
guint i;
g_return_val_if_fail (metacity != NULL, FALSE);
g_assert (metacity->name);
if (metacity->readable_name == NULL)
{
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
/* Translators: This error means that a necessary XML tag (whose name
* is given in angle brackets) was not found in a given theme (whose
* name is given second, in quotation marks).
*/
_("No <%s> set for theme '%s'"), "name", metacity->name);
return FALSE;
}
if (metacity->author == NULL)
{
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
_("No <%s> set for theme '%s'"), "author", metacity->name);
return FALSE;
}
if (metacity->date == NULL)
{
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
_("No <%s> set for theme '%s'"), "date", metacity->name);
return FALSE;
}
if (metacity->description == NULL)
{
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
_("No <%s> set for theme '%s'"), "description",
metacity->name);
return FALSE;
}
if (metacity->copyright == NULL)
{
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
_("No <%s> set for theme '%s'"), "copyright",
metacity->name);
return FALSE;
}
for (i = 0; i < META_FRAME_TYPE_LAST; i++)
{
if (i != META_FRAME_TYPE_ATTACHED && metacity->style_sets_by_type[i] == NULL)
{
g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
_("No frame style set for window type '%s' in theme '%s', add a <window type='%s' style_set='whatever' /> element"),
meta_frame_type_to_string (i), metacity->name,
meta_frame_type_to_string (i));
return FALSE;
}
}
return TRUE;
}
static void
end_element_handler (GMarkupParseContext *context,
const gchar *element_name,
gpointer user_data,
GError **error)
{
ParseInfo *info;
info = (ParseInfo *) user_data;
if (info->skip_level > 0)
{
info->skip_level--;
return;
}
switch (peek_state (info))
{
case STATE_START:
break;
case STATE_THEME:
g_assert (info->metacity);
if (!theme_validate (info->metacity, error))
add_context_to_error (error, context);
pop_state (info);
g_assert (peek_state (info) == STATE_START);
break;
case STATE_INFO:
pop_state (info);
g_assert (peek_state (info) == STATE_THEME);
break;
case STATE_NAME:
pop_state (info);
g_assert (peek_state (info) == STATE_INFO);
break;
case STATE_AUTHOR:
pop_state (info);
g_assert (peek_state (info) == STATE_INFO);
break;
case STATE_COPYRIGHT:
pop_state (info);
g_assert (peek_state (info) == STATE_INFO);
break;
case STATE_DATE:
pop_state (info);
g_assert (peek_state (info) == STATE_INFO);
break;
case STATE_DESCRIPTION:
pop_state (info);
g_assert (peek_state (info) == STATE_INFO);
break;
case STATE_CONSTANT:
pop_state (info);
g_assert (peek_state (info) == STATE_THEME);
break;
case STATE_FRAME_GEOMETRY:
g_assert (info->layout);
if (!meta_frame_layout_validate (info->layout, error))
add_context_to_error (error, context);
/* layout will already be stored in the theme under
* its name
*/
meta_frame_layout_unref (info->layout);
info->layout = NULL;
pop_state (info);
g_assert (peek_state (info) == STATE_THEME);
break;
case STATE_DISTANCE:
pop_state (info);
g_assert (peek_state (info) == STATE_FRAME_GEOMETRY);
break;
case STATE_BORDER:
pop_state (info);
g_assert (peek_state (info) == STATE_FRAME_GEOMETRY);
break;
case STATE_ASPECT_RATIO:
pop_state (info);
g_assert (peek_state (info) == STATE_FRAME_GEOMETRY);
break;
case STATE_DRAW_OPS:
{
ParseState parse_state;
g_assert (info->op_list);
if (!meta_draw_op_list_validate (info->op_list, error))
{
add_context_to_error (error, context);
meta_draw_op_list_unref (info->op_list);
info->op_list = NULL;
}
pop_state (info);
parse_state = peek_state (info);
if (parse_state == STATE_BUTTON || parse_state == STATE_PIECE ||
parse_state == STATE_MENU_ICON)
{
/* Leave info->op_list to be picked up
* when these elements are closed
*/
g_assert (info->op_list);
}
else if (parse_state == STATE_THEME)
{
g_assert (info->op_list);
meta_draw_op_list_unref (info->op_list);
info->op_list = NULL;
}
else
{
/* Op list can't occur in other contexts */
g_assert_not_reached ();
}
}
break;
case STATE_LINE:
pop_state (info);
g_assert (peek_state (info) == STATE_DRAW_OPS);
break;
case STATE_RECTANGLE:
pop_state (info);
g_assert (peek_state (info) == STATE_DRAW_OPS);
break;
case STATE_ARC:
pop_state (info);
g_assert (peek_state (info) == STATE_DRAW_OPS);
break;
case STATE_CLIP:
pop_state (info);
g_assert (peek_state (info) == STATE_DRAW_OPS);
break;
case STATE_TINT:
pop_state (info);
g_assert (peek_state (info) == STATE_DRAW_OPS);
break;
case STATE_GRADIENT:
g_assert (info->op);
g_assert (info->op->type == META_DRAW_GRADIENT);
if (!meta_gradient_spec_validate (info->op->data.gradient.gradient_spec,
error))
{
add_context_to_error (error, context);
meta_draw_op_free (info->op);
info->op = NULL;
}
else
{
g_assert (info->op_list);
meta_draw_op_list_append (info->op_list, info->op);
info->op = NULL;
}
pop_state (info);
g_assert (peek_state (info) == STATE_DRAW_OPS);
break;
case STATE_IMAGE:
pop_state (info);
g_assert (peek_state (info) == STATE_DRAW_OPS);
break;
case STATE_GTK_ARROW:
pop_state (info);
g_assert (peek_state (info) == STATE_DRAW_OPS);
break;
case STATE_GTK_BOX:
pop_state (info);
g_assert (peek_state (info) == STATE_DRAW_OPS);
break;
case STATE_GTK_VLINE:
pop_state (info);
g_assert (peek_state (info) == STATE_DRAW_OPS);
break;
case STATE_ICON:
pop_state (info);
g_assert (peek_state (info) == STATE_DRAW_OPS);
break;
case STATE_TITLE:
pop_state (info);
g_assert (peek_state (info) == STATE_DRAW_OPS);
break;
case STATE_INCLUDE:
pop_state (info);
g_assert (peek_state (info) == STATE_DRAW_OPS);
break;
case STATE_TILE:
pop_state (info);
g_assert (peek_state (info) == STATE_DRAW_OPS);
break;
case STATE_COLOR:
pop_state (info);
g_assert (peek_state (info) == STATE_GRADIENT);
break;
case STATE_FRAME_STYLE:
g_assert (info->style);
if (!meta_frame_style_validate (info->style,
peek_required_version (info),
error))
{
add_context_to_error (error, context);
}
/* Frame style is in the theme hash table and a ref
* is held there
*/
meta_frame_style_unref (info->style);
info->style = NULL;
pop_state (info);
g_assert (peek_state (info) == STATE_THEME);
break;
case STATE_PIECE:
g_assert (info->style);
if (info->op_list == NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("No draw_ops provided for frame piece"));
}
else
{
info->style->pieces[info->piece] = info->op_list;
info->op_list = NULL;
}
pop_state (info);
g_assert (peek_state (info) == STATE_FRAME_STYLE);
break;
case STATE_BUTTON:
g_assert (info->style);
if (info->op_list == NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("No draw_ops provided for button"));
}
else
{
MetaButtonFunction function;
MetaButtonState state;
function = info->button_function;
state = info->button_state;
info->style->buttons[function][state] = info->op_list;
info->op_list = NULL;
}
pop_state (info);
break;
case STATE_MENU_ICON:
g_assert (info->metacity);
if (info->op_list != NULL)
{
meta_draw_op_list_unref (info->op_list);
info->op_list = NULL;
}
pop_state (info);
g_assert (peek_state (info) == STATE_THEME);
break;
case STATE_FRAME_STYLE_SET:
g_assert (info->style_set);
if (!meta_frame_style_set_validate (info->style_set, error))
add_context_to_error (error, context);
/* Style set is in the theme hash table and a reference
* is held there.
*/
meta_frame_style_set_unref (info->style_set);
info->style_set = NULL;
pop_state (info);
g_assert (peek_state (info) == STATE_THEME);
break;
case STATE_FRAME:
pop_state (info);
g_assert (peek_state (info) == STATE_FRAME_STYLE_SET);
break;
case STATE_WINDOW:
pop_state (info);
g_assert (peek_state (info) == STATE_THEME);
break;
case STATE_FALLBACK:
pop_state (info);
g_assert (peek_state (info) == STATE_THEME);
break;
default:
break;
}
pop_required_version (info);
}
static gboolean
all_whitespace (const gchar *text,
gint text_len)
{
const gchar *p;
const gchar *end;
p = text;
end = text + text_len;
while (p != end)
{
if (!g_ascii_isspace (*p))
return FALSE;
p = g_utf8_next_char (p);
}
return TRUE;
}
static void
text_handler (GMarkupParseContext *context,
const gchar *text,
gsize text_len,
gpointer user_data,
GError **error)
{
ParseInfo *info;
info = (ParseInfo *) user_data;
if (info->skip_level > 0)
return;
if (all_whitespace (text, text_len))
return;
switch (peek_state (info))
{
case STATE_START:
g_assert_not_reached (); /* gmarkup shouldn't do this */
break;
case STATE_NAME:
if (info->metacity->readable_name != NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("<%s> specified twice for this theme"), "name");
return;
}
info->metacity->readable_name = g_strndup (text, text_len);
break;
case STATE_AUTHOR:
if (info->metacity->author != NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("<%s> specified twice for this theme"), "author");
return;
}
info->metacity->author = g_strndup (text, text_len);
break;
case STATE_COPYRIGHT:
if (info->metacity->copyright != NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("<%s> specified twice for this theme"), "copyright");
return;
}
info->metacity->copyright = g_strndup (text, text_len);
break;
case STATE_DATE:
if (info->metacity->date != NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("<%s> specified twice for this theme"), "date");
return;
}
info->metacity->date = g_strndup (text, text_len);
break;
case STATE_DESCRIPTION:
if (info->metacity->description != NULL)
{
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("<%s> specified twice for this theme"), "description");
return;
}
info->metacity->description = g_strndup (text, text_len);
break;
case STATE_THEME:
case STATE_INFO:
case STATE_CONSTANT:
case STATE_FRAME_GEOMETRY:
case STATE_DISTANCE:
case STATE_BORDER:
case STATE_ASPECT_RATIO:
case STATE_DRAW_OPS:
case STATE_LINE:
case STATE_RECTANGLE:
case STATE_ARC:
case STATE_CLIP:
case STATE_TINT:
case STATE_GRADIENT:
case STATE_IMAGE:
case STATE_GTK_ARROW:
case STATE_GTK_BOX:
case STATE_GTK_VLINE:
case STATE_ICON:
case STATE_TITLE:
case STATE_INCLUDE:
case STATE_TILE:
case STATE_COLOR:
case STATE_FRAME_STYLE:
case STATE_PIECE:
case STATE_BUTTON:
case STATE_MENU_ICON:
case STATE_FRAME_STYLE_SET:
case STATE_FRAME:
case STATE_WINDOW:
case STATE_FALLBACK:
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
_("No text is allowed inside element <%s>"),
g_markup_parse_context_get_element (context));
break;
default:
break;
}
}
/* If the theme is not-corrupt, keep looking for alternate versions
* in other locations we might be compatible with
*/
static gboolean
theme_error_is_fatal (GError *error)
{
return !(error->domain == G_FILE_ERROR ||
(error->domain == META_THEME_ERROR &&
error->code == META_THEME_ERROR_TOO_OLD));
}
static gboolean
keep_trying (GError **error)
{
if (*error && !theme_error_is_fatal (*error))
{
g_clear_error (error);
return TRUE;
}
return FALSE;
}
static void
clear_theme (MetaThemeMetacity *metacity)
{
MetaFrameType type;
g_free (metacity->name);
metacity->name = NULL;
g_free (metacity->dirname);
metacity->dirname = NULL;
g_free (metacity->readable_name);
metacity->readable_name = NULL;
g_free (metacity->date);
metacity->date = NULL;
g_free (metacity->description);
metacity->description = NULL;
g_free (metacity->author);
metacity->author = NULL;
g_free (metacity->copyright);
metacity->copyright = NULL;
g_clear_pointer (&metacity->integers, g_hash_table_destroy);
g_clear_pointer (&metacity->floats, g_hash_table_destroy);
g_clear_pointer (&metacity->colors, g_hash_table_destroy);
g_hash_table_remove_all (metacity->draw_op_lists);
g_hash_table_remove_all (metacity->frame_layouts);
g_hash_table_remove_all (metacity->styles);
g_hash_table_remove_all (metacity->style_sets);
g_hash_table_remove_all (metacity->images);
for (type = 0; type < META_FRAME_TYPE_LAST; type++)
{
if (metacity->style_sets_by_type[type] != NULL)
{
meta_frame_style_set_unref (metacity->style_sets_by_type[type]);
metacity->style_sets_by_type[type] = NULL;
}
}
}
static GMarkupParser metacity_theme_parser =
{
start_element_handler,
end_element_handler,
text_handler,
NULL,
NULL
};
static gboolean
load_theme (MetaThemeMetacity *metacity,
const gchar *theme_dir,
const gchar *theme_name,
guint major_version,
GError **error)
{
gchar *filename;
gchar *file;
gboolean retval;
gchar *text;
gsize length;
ParseInfo *info;
GMarkupParseContext *context;
g_return_val_if_fail (error && *error == NULL, FALSE);
clear_theme (metacity);
metacity->name = g_strdup (theme_name);
metacity->dirname = g_strdup (theme_dir);
metacity->format_version = 1000 * major_version;
filename = g_strdup_printf (METACITY_THEME_FILENAME_FORMAT, major_version);
file = g_build_filename (theme_dir, filename, NULL);
retval = FALSE;
text = NULL;
info = NULL;
context = NULL;
if (!g_file_get_contents (file, &text, &length, error))
goto out;
g_debug ("Parsing theme file %s", file);
info = parse_info_new (metacity);
context = g_markup_parse_context_new (&metacity_theme_parser, 0, info, NULL);
if (!g_markup_parse_context_parse (context, text, length, error))
goto out;
if (!g_markup_parse_context_end_parse (context, error))
goto out;
retval = TRUE;
out:
if (*error && !theme_error_is_fatal (*error))
g_debug ("Failed to read theme from file %s: %s", file, (*error)->message);
if (context)
g_markup_parse_context_free (context);
if (info)
parse_info_free (info);
g_free (filename);
g_free (file);
g_free (text);
return retval;
}
static gchar *
get_theme_dir (const gchar *dir,
const gchar *theme_name)
{
return g_build_filename (dir, "themes", theme_name, THEME_SUBDIR, NULL);
}
static void
meta_theme_metacity_dispose (GObject *object)
{
MetaThemeMetacity *metacity;
gint i;
metacity = META_THEME_METACITY (object);
for (i = 0; i < META_FRAME_TYPE_LAST; i++)
{
if (metacity->style_sets_by_type[i] != NULL)
{
meta_frame_style_set_unref (metacity->style_sets_by_type[i]);
metacity->style_sets_by_type[i] = NULL;
}
}
g_clear_pointer (&metacity->integers, g_hash_table_destroy);
g_clear_pointer (&metacity->floats, g_hash_table_destroy);
g_clear_pointer (&metacity->colors, g_hash_table_destroy);
g_clear_pointer (&metacity->draw_op_lists, g_hash_table_destroy);
g_clear_pointer (&metacity->frame_layouts, g_hash_table_destroy);
g_clear_pointer (&metacity->styles, g_hash_table_destroy);
g_clear_pointer (&metacity->style_sets, g_hash_table_destroy);
G_OBJECT_CLASS (meta_theme_metacity_parent_class)->dispose (object);
}
static void
meta_theme_metacity_finalize (GObject *object)
{
MetaThemeMetacity *metacity;
metacity = META_THEME_METACITY (object);
g_free (metacity->name);
g_free (metacity->dirname);
g_free (metacity->readable_name);
g_free (metacity->author);
g_free (metacity->copyright);
g_free (metacity->date);
g_free (metacity->description);
G_OBJECT_CLASS (meta_theme_metacity_parent_class)->finalize (object);
}
static gboolean
meta_theme_metacity_load (MetaThemeImpl *impl,
const gchar *name,
GError **err)
{
MetaThemeMetacity *metacity;
gboolean retval;
GError *error;
gint version;
g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
metacity = META_THEME_METACITY (impl);
retval = FALSE;
error = NULL;
/* We try all supported major versions from current to oldest */
for (version = THEME_MAJOR_VERSION; version > 0; version--)
{
gchar *dir;
const gchar *const *xdg_data_dirs;
gint i;
/* Try XDG_USER_DATA_DIR first */
dir = get_theme_dir (g_get_user_data_dir(), name);
retval = load_theme (metacity, dir, name, version, &error);
g_free (dir);
if (!keep_trying (&error))
goto out;
/* Try each XDG_DATA_DIRS for theme */
xdg_data_dirs = g_get_system_data_dirs();
for (i = 0; xdg_data_dirs[i] != NULL; i++)
{
dir = get_theme_dir (xdg_data_dirs[i], name);
retval = load_theme (metacity, dir, name, version, &error);
g_free (dir);
if (!keep_trying (&error))
goto out;
}
/* Look for themes in DATADIR */
dir = get_theme_dir (DATADIR, name);
retval = load_theme (metacity, dir, name, version, &error);
g_free (dir);
if (!keep_trying (&error))
goto out;
}
out:
if (!error && !retval)
{
g_set_error (&error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
_("Failed to find a valid file for theme '%s'"), name);
}
if (error)
g_propagate_error (err, error);
return retval;
}
static MetaFrameStyle *
meta_theme_metacity_get_frame_style (MetaThemeImpl *impl,
MetaFrameType type,
MetaFrameFlags flags)
{
MetaThemeMetacity *metacity;
MetaFrameState state;
MetaFrameResize resize;
MetaFrameFocus focus;
MetaFrameStyle *style;
MetaFrameStyleSet *style_set;
g_return_val_if_fail (type < META_FRAME_TYPE_LAST, NULL);
metacity = META_THEME_METACITY (impl);
style_set = metacity->style_sets_by_type[type];
if (style_set == NULL && type == META_FRAME_TYPE_ATTACHED)
style_set = metacity->style_sets_by_type[META_FRAME_TYPE_BORDER];
/* Right now the parser forces a style set for all other types,
* but this fallback code is here in case I take that out.
*/
if (style_set == NULL)
style_set = metacity->style_sets_by_type[META_FRAME_TYPE_NORMAL];
if (style_set == NULL)
return NULL;
switch (flags & (META_FRAME_MAXIMIZED | META_FRAME_SHADED | META_FRAME_TILED_LEFT | META_FRAME_TILED_RIGHT))
{
case 0:
state = META_FRAME_STATE_NORMAL;
break;
case META_FRAME_MAXIMIZED:
state = META_FRAME_STATE_MAXIMIZED;
break;
case META_FRAME_TILED_LEFT:
state = META_FRAME_STATE_TILED_LEFT;
break;
case META_FRAME_TILED_RIGHT:
state = META_FRAME_STATE_TILED_RIGHT;
break;
case META_FRAME_SHADED:
state = META_FRAME_STATE_SHADED;
break;
case (META_FRAME_MAXIMIZED | META_FRAME_SHADED):
state = META_FRAME_STATE_MAXIMIZED_AND_SHADED;
break;
case (META_FRAME_TILED_LEFT | META_FRAME_SHADED):
state = META_FRAME_STATE_TILED_LEFT_AND_SHADED;
break;
case (META_FRAME_TILED_RIGHT | META_FRAME_SHADED):
state = META_FRAME_STATE_TILED_RIGHT_AND_SHADED;
break;
default:
g_assert_not_reached ();
state = META_FRAME_STATE_LAST; /* compiler */
break;
}
switch (flags & (META_FRAME_ALLOWS_VERTICAL_RESIZE | META_FRAME_ALLOWS_HORIZONTAL_RESIZE))
{
case 0:
resize = META_FRAME_RESIZE_NONE;
break;
case META_FRAME_ALLOWS_VERTICAL_RESIZE:
resize = META_FRAME_RESIZE_VERTICAL;
break;
case META_FRAME_ALLOWS_HORIZONTAL_RESIZE:
resize = META_FRAME_RESIZE_HORIZONTAL;
break;
case (META_FRAME_ALLOWS_VERTICAL_RESIZE | META_FRAME_ALLOWS_HORIZONTAL_RESIZE):
resize = META_FRAME_RESIZE_BOTH;
break;
default:
g_assert_not_reached ();
resize = META_FRAME_RESIZE_LAST; /* compiler */
break;
}
/* re invert the styles used for focus/unfocussed while flashing a frame */
if (((flags & META_FRAME_HAS_FOCUS) && !(flags & META_FRAME_IS_FLASHING))
|| (!(flags & META_FRAME_HAS_FOCUS) && (flags & META_FRAME_IS_FLASHING)))
focus = META_FRAME_FOCUS_YES;
else
focus = META_FRAME_FOCUS_NO;
style = meta_frame_style_set_get_style (style_set, state, resize, focus);
return style;
}
static void
meta_theme_metacity_get_frame_borders (MetaThemeImpl *impl,
MetaFrameLayout *layout,
MetaStyleInfo *style_info,
gint text_height,
MetaFrameFlags flags,
MetaFrameType type,
MetaFrameBorders *borders)
{
gint scale;
gint buttons_height;
gint title_height;
meta_frame_borders_clear (borders);
/* For a full-screen window, we don't have any borders, visible or not. */
if (flags & META_FRAME_FULLSCREEN)
return;
g_return_if_fail (layout != NULL);
if (!layout->has_title)
text_height = 0;
/* Scale geometry for HiDPI, see comment in meta_theme_metacity_draw_frame () */
scale = meta_theme_impl_get_scale (impl);
buttons_height = layout->metacity.button_height +
layout->button_border.top +
layout->button_border.bottom;
title_height = text_height / scale +
layout->metacity.title_vertical_pad +
layout->metacity.title_border.top +
layout->metacity.title_border.bottom;
borders->visible.top = MAX (buttons_height, title_height);
borders->visible.left = layout->metacity.left_width;
borders->visible.right = layout->metacity.right_width;
borders->visible.bottom = layout->metacity.bottom_height;
borders->shadow.top = 0;
borders->shadow.left = 0;
borders->shadow.right = 0;
borders->shadow.bottom = 0;
if (flags & META_FRAME_ALLOWS_HORIZONTAL_RESIZE)
{
borders->resize.left = layout->invisible_resize_border.left;
borders->resize.right = layout->invisible_resize_border.right;
}
if (flags & META_FRAME_ALLOWS_VERTICAL_RESIZE)
{
borders->resize.bottom = layout->invisible_resize_border.bottom;
if (type != META_FRAME_TYPE_ATTACHED)
borders->resize.top = layout->invisible_resize_border.top;
}
borders->invisible.left = MAX (borders->shadow.left, borders->resize.left);
borders->invisible.right = MAX (borders->shadow.right, borders->resize.right);
borders->invisible.bottom = MAX (borders->shadow.bottom, borders->resize.bottom);
borders->invisible.top = MAX (borders->shadow.top, borders->resize.top);
borders->total.left = borders->invisible.left + borders->visible.left;
borders->total.right = borders->invisible.right + borders->visible.right;
borders->total.bottom = borders->invisible.bottom + borders->visible.bottom;
borders->total.top = borders->invisible.top + borders->visible.top;
scale_border (&borders->visible, scale);
scale_border (&borders->shadow, scale);
scale_border (&borders->resize, scale);
scale_border (&borders->invisible, scale);
scale_border (&borders->total, scale);
}
static gboolean
is_button_allowed (MetaThemeMetacity *metacity,
MetaButtonType type)
{
if (theme_allows (metacity, META_THEME_SHADE_STICK_ABOVE_BUTTONS))
{
switch (type)
{
case META_BUTTON_TYPE_SHADE:
case META_BUTTON_TYPE_ABOVE:
case META_BUTTON_TYPE_STICK:
case META_BUTTON_TYPE_UNSHADE:
case META_BUTTON_TYPE_UNABOVE:
case META_BUTTON_TYPE_UNSTICK:
return TRUE;
case META_BUTTON_TYPE_MENU:
case META_BUTTON_TYPE_APPMENU:
case META_BUTTON_TYPE_MINIMIZE:
case META_BUTTON_TYPE_MAXIMIZE:
case META_BUTTON_TYPE_CLOSE:
case META_BUTTON_TYPE_SPACER:
case META_BUTTON_TYPE_LAST:
default:
break;
}
}
/* now consider the buttons which exist in all versions */
switch (type)
{
case META_BUTTON_TYPE_MENU:
case META_BUTTON_TYPE_APPMENU:
case META_BUTTON_TYPE_MINIMIZE:
case META_BUTTON_TYPE_MAXIMIZE:
case META_BUTTON_TYPE_CLOSE:
case META_BUTTON_TYPE_SPACER:
return TRUE;
case META_BUTTON_TYPE_STICK:
case META_BUTTON_TYPE_SHADE:
case META_BUTTON_TYPE_ABOVE:
case META_BUTTON_TYPE_UNSTICK:
case META_BUTTON_TYPE_UNSHADE:
case META_BUTTON_TYPE_UNABOVE:
/* we are being asked for a >v1 button which hasn't been handled yet,
* so obviously we're not in a theme which supports that version.
* therefore, we don't show the button. return NULL and all will
* be well.
*/
break;
case META_BUTTON_TYPE_LAST:
default:
break;
}
return FALSE;
}
static void
meta_theme_metacity_calc_geometry (MetaThemeImpl *impl,
MetaFrameLayout *layout,
MetaStyleInfo *style_info,
gint text_height,
MetaFrameFlags flags,
gint client_width,
gint client_height,
MetaButtonLayout *button_layout,
MetaFrameType type,
MetaFrameGeometry *fgeom)
{
MetaFrameBorders borders;
int i, n_left, n_right, n_left_spacers, n_right_spacers;
MetaThemeMetacity *metacity;
int x;
int button_y;
int title_right_edge;
int width, height;
int button_width, button_height;
int min_size_for_rounding;
int scale;
META_THEME_IMPL_GET_CLASS (impl)->get_frame_borders (impl, layout,
style_info, text_height,
flags, type, &borders);
fgeom->borders = borders;
width = client_width + borders.total.left + borders.total.right;
height = ((flags & META_FRAME_SHADED) ? 0: client_height) +
borders.total.top + borders.total.bottom;
fgeom->width = width;
fgeom->height = height;
/* gcc warnings */
button_width = -1;
button_height = -1;
/* Scale geometry for HiDPI, see comment in meta_theme_metacity_draw_frame () */
scale = meta_theme_impl_get_scale (impl);
switch (layout->metacity.button_sizing)
{
case META_BUTTON_SIZING_ASPECT:
button_height = borders.visible.top - layout->button_border.top * scale - layout->button_border.bottom * scale;
button_width = button_height / layout->metacity.button_aspect;
break;
case META_BUTTON_SIZING_FIXED:
button_width = layout->metacity.button_width * scale;
button_height = layout->metacity.button_height * scale;
break;
case META_BUTTON_SIZING_LAST:
default:
g_assert_not_reached ();
break;
}
n_left = 0;
n_right = 0;
n_left_spacers = 0;
n_right_spacers = 0;
metacity = META_THEME_METACITY (impl);
if (!layout->hide_buttons)
{
MetaButton *button;
for (i = 0; i < button_layout->n_left_buttons; i++)
{
button = &button_layout->left_buttons[i];
button->visible = is_button_visible (button, flags) &&
is_button_allowed (metacity, button->type);
if (button->visible)
{
if (button->type != META_BUTTON_TYPE_SPACER)
n_left++;
else
n_left_spacers++;
}
}
for (i = 0; i < button_layout->n_right_buttons; i++)
{
button = &button_layout->right_buttons[i];
button->visible = is_button_visible (button, flags) &&
is_button_allowed (metacity, button->type);
if (button->visible)
{
if (button->type != META_BUTTON_TYPE_SPACER)
n_right++;
else
n_right_spacers++;
}
}
}
else
{
for (i = 0; i < button_layout->n_left_buttons; i++)
button_layout->left_buttons[i].visible = FALSE;
for (i = 0; i < button_layout->n_right_buttons; i++)
button_layout->right_buttons[i].visible = FALSE;
}
/* Be sure buttons fit */
while (n_left > 0 || n_right > 0)
{
int space_used_by_buttons;
int space_available;
space_available = fgeom->width -
borders.invisible.left -
layout->metacity.left_titlebar_edge * scale -
borders.invisible.right -
layout->metacity.right_titlebar_edge * scale;
space_used_by_buttons = 0;
space_used_by_buttons += button_width * n_left;
space_used_by_buttons += (button_width * 0.75) * n_left_spacers;
space_used_by_buttons += layout->button_border.left * scale * n_left;
space_used_by_buttons += layout->button_border.right * scale * n_left;
space_used_by_buttons += button_width * n_right;
space_used_by_buttons += (button_width * 0.75) * n_right_spacers;
space_used_by_buttons += layout->button_border.left * scale * n_right;
space_used_by_buttons += layout->button_border.right * scale * n_right;
if (space_used_by_buttons <= space_available)
break; /* Everything fits, bail out */
/* First try to remove separators */
if (n_left_spacers > 0)
{
if (strip_button (button_layout->left_buttons,
button_layout->n_left_buttons,
META_BUTTON_TYPE_SPACER))
{
n_left_spacers--;
continue;
}
else
{
g_assert_not_reached ();
}
}
else if (n_right_spacers > 0)
{
if (strip_button (button_layout->right_buttons,
button_layout->n_right_buttons,
META_BUTTON_TYPE_SPACER))
{
n_right_spacers--;
continue;
}
else
{
g_assert_not_reached ();
}
}
/* Otherwise we need to shave out a button. Shave
* above, stick, shade, min, max, close, then menu (menu is most useful);
* prefer the default button locations.
*/
if (strip_buttons (button_layout, &n_left, &n_right))
{
continue;
}
else
{
g_error ("Could not find a button to strip. n_left = %d n_right = %d",
n_left, n_right);
}
}
/* center buttons vertically */
button_y = (borders.visible.top -
(button_height + layout->button_border.top * scale + layout->button_border.bottom * scale)) / 2 + layout->button_border.top * scale + borders.invisible.top;
/* right edge of farthest-right button */
x = width - layout->metacity.right_titlebar_edge * scale - borders.invisible.right;
for (i = button_layout->n_right_buttons - 1; i >= 0; i--)
{
MetaButton *button;
GdkRectangle rect;
button = &button_layout->right_buttons[i];
if (button->visible == FALSE)
continue;
/* if we go negative, leave the buttons we don't get to as 0 - width */
if (x < 0)
break;
rect.y = button_y;
rect.width = button_width;
rect.height = button_height;
if (button->type == META_BUTTON_TYPE_SPACER)
{
rect.x = x - button_width * 0.75;
rect.width *= 0.75;
}
else
{
rect.x = x - layout->button_border.right * scale - button_width;
}
button->rect.visible = rect;
button->rect.clickable = rect;
if ((flags & META_FRAME_MAXIMIZED || flags & META_FRAME_TILED_RIGHT) &&
i == button_layout->n_right_buttons - 1)
{
gint extra_width;
gint extra_height;
extra_width = layout->metacity.right_titlebar_edge * scale +
layout->metacity.right_width * scale +
layout->button_border.right * scale;
/* FIXME: */
extra_height = 0;
button->rect.clickable.y -= extra_height;
button->rect.clickable.width += extra_width;
button->rect.clickable.height += extra_height;
}
x = rect.x - layout->button_border.left * scale;
}
/* save right edge of titlebar for later use */
title_right_edge = x - layout->metacity.title_border.right * scale;
/* Now x changes to be position from the left and we go through
* the left-side buttons
*/
x = layout->metacity.left_titlebar_edge * scale + borders.invisible.left;
for (i = 0; i < button_layout->n_left_buttons; i++)
{
MetaButton *button;
GdkRectangle rect;
button = &button_layout->left_buttons[i];
if (button->visible == FALSE)
continue;
rect.x = x + layout->button_border.left * scale;;
rect.y = button_y;
rect.width = button_width;
rect.height = button_height;
if (button->type == META_BUTTON_TYPE_SPACER)
rect.width *= 0.75;
button->rect.visible = rect;
button->rect.clickable = rect;
if ((flags & META_FRAME_MAXIMIZED || flags & META_FRAME_TILED_LEFT) &&
i == 0)
{
gint extra_width;
gint extra_height;
extra_width = layout->metacity.left_titlebar_edge * scale +
layout->metacity.left_width * scale +
layout->button_border.left * scale;
/* FIXME: */
extra_height = 0;
button->rect.clickable.x -= extra_width;
button->rect.clickable.y -= extra_height;
button->rect.clickable.width += extra_width;
button->rect.clickable.height += extra_height;
}
x = rect.x + rect.width + layout->button_border.right * scale;
}
/* We always fill as much vertical space as possible with title rect,
* rather than centering it like the buttons
*/
fgeom->title_rect.x = x + layout->metacity.title_border.left * scale;
fgeom->title_rect.y = layout->metacity.title_border.top * scale + borders.invisible.top;
fgeom->title_rect.width = title_right_edge - fgeom->title_rect.x;
fgeom->title_rect.height = borders.visible.top -
layout->metacity.title_border.top * scale -
layout->metacity.title_border.bottom * scale;
/* Nuke title if it won't fit */
if (fgeom->title_rect.width < 0 ||
fgeom->title_rect.height < 0)
{
fgeom->title_rect.width = 0;
fgeom->title_rect.height = 0;
}
if (flags & META_FRAME_SHADED)
min_size_for_rounding = 0;
else
min_size_for_rounding = 5 * scale;
fgeom->top_left_corner_rounded_radius = 0;
fgeom->top_right_corner_rounded_radius = 0;
fgeom->bottom_left_corner_rounded_radius = 0;
fgeom->bottom_right_corner_rounded_radius = 0;
if (borders.visible.top + borders.visible.left >= min_size_for_rounding)
fgeom->top_left_corner_rounded_radius = layout->top_left_corner_rounded_radius * scale;
if (borders.visible.top + borders.visible.right >= min_size_for_rounding)
fgeom->top_right_corner_rounded_radius = layout->top_right_corner_rounded_radius * scale;
if (borders.visible.bottom + borders.visible.left >= min_size_for_rounding)
fgeom->bottom_left_corner_rounded_radius = layout->bottom_left_corner_rounded_radius * scale;
if (borders.visible.bottom + borders.visible.right >= min_size_for_rounding)
fgeom->bottom_right_corner_rounded_radius = layout->bottom_right_corner_rounded_radius * scale;
}
static void
clip_to_rounded_corners (cairo_t *cr,
MetaRectangleDouble rect,
const MetaFrameGeometry *fgeom,
gint scale)
{
gdouble x;
gdouble y;
gdouble width;
gdouble height;
gint radius;
x = rect.x;
y = rect.y;
width = rect.width;
height = rect.height;
cairo_new_path (cr);
if (fgeom->top_left_corner_rounded_radius != 0)
{
radius = fgeom->top_left_corner_rounded_radius / scale;
radius += sqrt(fgeom->top_left_corner_rounded_radius / scale);
cairo_line_to (cr, x, y + radius);
cairo_arc (cr, x + radius, y + radius, radius,
180.0 * G_PI / 180.0, 270.0 * G_PI / 180.0);
}
else
cairo_line_to (cr, x, y);
if (fgeom->top_right_corner_rounded_radius != 0)
{
radius = fgeom->top_right_corner_rounded_radius / scale;
radius += sqrt(fgeom->top_right_corner_rounded_radius / scale);
cairo_line_to (cr, x + width - radius, y);
cairo_arc (cr, x + width - radius, y + radius, radius,
-90.0 * G_PI / 180.0, 0.0 * G_PI / 180.0);
}
else
cairo_line_to (cr, x + width, y);
if (fgeom->bottom_right_corner_rounded_radius != 0)
{
radius = fgeom->bottom_right_corner_rounded_radius / scale;
radius += sqrt(fgeom->bottom_right_corner_rounded_radius / scale);
cairo_line_to (cr, x + width, y + height - radius);
cairo_arc (cr, x + width - radius, y + height - radius, radius,
0.0 * G_PI / 180.0, 90.0 * G_PI / 180.0);
}
else
cairo_line_to (cr, x + width, y + height);
if (fgeom->bottom_left_corner_rounded_radius != 0)
{
radius = fgeom->bottom_left_corner_rounded_radius / scale;
radius += sqrt(fgeom->bottom_left_corner_rounded_radius / scale);
cairo_line_to (cr, x + radius, y + height);
cairo_arc (cr, x + radius, y + height - radius, radius,
90.0 * G_PI / 180.0, 180.0 * G_PI / 180.0);
}
else
cairo_line_to (cr, x, y + height);
cairo_close_path (cr);
cairo_clip (cr);
}
static MetaButtonFunction
get_button_function (MetaButtonType type,
gboolean background,
gint button,
gint n_buttons,
gint side)
{
if (background)
{
if (side == 0) /* left */
{
if (n_buttons == 1)
{
return META_BUTTON_FUNCTION_LEFT_SINGLE_BACKGROUND;
}
else if (n_buttons == 2)
{
if (button == 0)
return META_BUTTON_FUNCTION_LEFT_LEFT_BACKGROUND;
else
return META_BUTTON_FUNCTION_LEFT_RIGHT_BACKGROUND;
}
else if (n_buttons > 2)
{
if (button == 0)
return META_BUTTON_FUNCTION_LEFT_LEFT_BACKGROUND;
else if (button == n_buttons - 1)
return META_BUTTON_FUNCTION_LEFT_RIGHT_BACKGROUND;
else
return META_BUTTON_FUNCTION_LEFT_MIDDLE_BACKGROUND;
}
}
else if (side == 1) /* right */
{
if (n_buttons == 1)
{
return META_BUTTON_FUNCTION_RIGHT_SINGLE_BACKGROUND;
}
else if (n_buttons == 2)
{
if (button == 0)
return META_BUTTON_FUNCTION_RIGHT_LEFT_BACKGROUND;
else
return META_BUTTON_FUNCTION_RIGHT_RIGHT_BACKGROUND;
}
else if (n_buttons > 2)
{
if (button == 0)
return META_BUTTON_FUNCTION_RIGHT_LEFT_BACKGROUND;
else if (button == n_buttons - 1)
return META_BUTTON_FUNCTION_RIGHT_RIGHT_BACKGROUND;
else
return META_BUTTON_FUNCTION_RIGHT_MIDDLE_BACKGROUND;
}
}
else
{
g_assert_not_reached ();
}
}
else
{
switch (type)
{
case META_BUTTON_TYPE_SHADE:
return META_BUTTON_FUNCTION_SHADE;
case META_BUTTON_TYPE_UNSHADE:
return META_BUTTON_FUNCTION_UNSHADE;
case META_BUTTON_TYPE_ABOVE:
return META_BUTTON_FUNCTION_ABOVE;
case META_BUTTON_TYPE_UNABOVE:
return META_BUTTON_FUNCTION_UNABOVE;
case META_BUTTON_TYPE_STICK:
return META_BUTTON_FUNCTION_STICK;
case META_BUTTON_TYPE_UNSTICK:
return META_BUTTON_FUNCTION_UNSTICK;
case META_BUTTON_TYPE_MENU:
return META_BUTTON_FUNCTION_MENU;
case META_BUTTON_TYPE_APPMENU:
return META_BUTTON_FUNCTION_APPMENU;
case META_BUTTON_TYPE_MINIMIZE:
return META_BUTTON_FUNCTION_MINIMIZE;
case META_BUTTON_TYPE_MAXIMIZE:
return META_BUTTON_FUNCTION_MAXIMIZE;
case META_BUTTON_TYPE_CLOSE:
return META_BUTTON_FUNCTION_CLOSE;
case META_BUTTON_TYPE_SPACER:
case META_BUTTON_TYPE_LAST:
default:
break;
}
}
return META_BUTTON_FUNCTION_LAST;
}
static void
meta_theme_metacity_draw_frame (MetaThemeImpl *impl,
MetaFrameStyle *style,
MetaStyleInfo *style_info,
cairo_t *cr,
const MetaFrameGeometry *fgeom,
PangoLayout *title_layout,
MetaFrameFlags flags,
const MetaButtonLayout *button_layout,
GdkPixbuf *mini_icon,
GdkPixbuf *icon)
{
gdouble scale;
gint i;
MetaRectangleDouble visible_rect;
MetaRectangleDouble titlebar_rect;
MetaRectangleDouble left_titlebar_edge;
MetaRectangleDouble right_titlebar_edge;
MetaRectangleDouble bottom_titlebar_edge;
MetaRectangleDouble top_titlebar_edge;
MetaRectangleDouble left_edge;
MetaRectangleDouble right_edge;
MetaRectangleDouble bottom_edge;
PangoRectangle extents;
MetaDrawInfo draw_info;
const MetaFrameBorders *borders;
GtkStyleContext *context;
/* We opt out of GTK+ HiDPI handling, so we have to do the scaling
* ourselves; the nitty-gritty is a bit confusing, so here is an overview:
* - the values in MetaFrameLayout are always as they appear in the theme,
* i.e. unscaled
* - calculated values (borders, MetaFrameGeometry) include the scale - as
* the geometry is comprised of scaled decorations and the client size
* which we must not scale, we don't have another option
* - for drawing, we scale the canvas to have GTK+ render elements (borders,
* radii, ...) at the correct scale - as a result, we have to "unscale"
* the geometry again to not apply the scaling twice
*/
scale = meta_theme_impl_get_scale (impl);
cairo_scale (cr, scale, scale);
borders = &fgeom->borders;
visible_rect.x = borders->invisible.left / scale;
visible_rect.y = borders->invisible.top / scale;
visible_rect.width = (fgeom->width - borders->invisible.left - borders->invisible.right) / scale;
visible_rect.height = (fgeom->height - borders->invisible.top - borders->invisible.bottom) / scale;
titlebar_rect.x = visible_rect.x;
titlebar_rect.y = visible_rect.y;
titlebar_rect.width = visible_rect.width;
titlebar_rect.height = borders->visible.top / scale;
left_titlebar_edge.x = titlebar_rect.x;
left_titlebar_edge.y = titlebar_rect.y + style->layout->metacity.title_border.top;
left_titlebar_edge.width = style->layout->metacity.title_border.left;
left_titlebar_edge.height = titlebar_rect.height - style->layout->metacity.title_border.top -
style->layout->metacity.title_border.bottom;
right_titlebar_edge.y = left_titlebar_edge.y;
right_titlebar_edge.height = left_titlebar_edge.height;
right_titlebar_edge.width = style->layout->metacity.title_border.right;
right_titlebar_edge.x = titlebar_rect.x + titlebar_rect.width - right_titlebar_edge.width;
top_titlebar_edge.x = titlebar_rect.x;
top_titlebar_edge.y = titlebar_rect.y;
top_titlebar_edge.width = titlebar_rect.width;
top_titlebar_edge.height = style->layout->metacity.title_border.top;
bottom_titlebar_edge.x = titlebar_rect.x;
bottom_titlebar_edge.width = titlebar_rect.width;
bottom_titlebar_edge.height = style->layout->metacity.title_border.bottom;
bottom_titlebar_edge.y = titlebar_rect.y + titlebar_rect.height - bottom_titlebar_edge.height;
left_edge.x = visible_rect.x;
left_edge.y = visible_rect.y + borders->visible.top / scale;
left_edge.width = borders->visible.left / scale;
left_edge.height = visible_rect.height - borders->visible.top / scale - borders->visible.bottom / scale;
right_edge.x = visible_rect.x + visible_rect.width - borders->visible.right / scale;
right_edge.y = visible_rect.y + borders->visible.top / scale;
right_edge.width = borders->visible.right / scale;
right_edge.height = visible_rect.height - borders->visible.top / scale - borders->visible.bottom / scale;
bottom_edge.x = visible_rect.x;
bottom_edge.y = visible_rect.y + visible_rect.height - borders->visible.bottom / scale;
bottom_edge.width = visible_rect.width;
bottom_edge.height = borders->visible.bottom / scale;
if (title_layout)
pango_layout_get_pixel_extents (title_layout,
NULL, &extents);
draw_info.scale = scale;
draw_info.mini_icon = mini_icon;
draw_info.icon = icon;
draw_info.title_layout = title_layout;
draw_info.title_layout_width = title_layout ? extents.width : 0;
draw_info.title_layout_height = title_layout ? extents.height : 0;
draw_info.left_width = borders->visible.left / scale;
draw_info.right_width = borders->visible.right / scale;
draw_info.top_height = borders->visible.top / scale;
draw_info.bottom_height = borders->visible.bottom / scale;
draw_info.width = fgeom->width / scale;
draw_info.height = fgeom->height / scale;
cairo_save (cr);
clip_to_rounded_corners (cr, visible_rect, fgeom, scale);
context = meta_style_info_get_style (style_info, META_STYLE_ELEMENT_WINDOW);
if (style->window_background_color != NULL)
{
GdkRGBA color;
meta_color_spec_render (style->window_background_color, context, &color);
if (meta_theme_impl_get_composited (impl))
color.alpha = style->window_background_alpha / 255.0;
gdk_cairo_set_source_rgba (cr, &color);
cairo_paint (cr);
}
/* The enum is in the order the pieces should be rendered. */
i = 0;
while (i < META_FRAME_PIECE_LAST)
{
MetaRectangleDouble rect;
switch ((MetaFramePiece) i)
{
case META_FRAME_PIECE_ENTIRE_BACKGROUND:
rect = visible_rect;
break;
case META_FRAME_PIECE_TITLEBAR:
rect = titlebar_rect;
break;
case META_FRAME_PIECE_LEFT_TITLEBAR_EDGE:
rect = left_titlebar_edge;
break;
case META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE:
rect = right_titlebar_edge;
break;
case META_FRAME_PIECE_TOP_TITLEBAR_EDGE:
rect = top_titlebar_edge;
break;
case META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE:
rect = bottom_titlebar_edge;
break;
case META_FRAME_PIECE_TITLEBAR_MIDDLE:
rect.x = left_titlebar_edge.x + left_titlebar_edge.width;
rect.y = top_titlebar_edge.y + top_titlebar_edge.height;
rect.width = titlebar_rect.width - left_titlebar_edge.width - right_titlebar_edge.width;
rect.height = titlebar_rect.height - top_titlebar_edge.height - bottom_titlebar_edge.height;
break;
case META_FRAME_PIECE_TITLE:
rect.x = fgeom->title_rect.x / scale;
rect.y = fgeom->title_rect.y / scale;
rect.width = fgeom->title_rect.width / scale;
rect.height = fgeom->title_rect.height / scale;
break;
case META_FRAME_PIECE_LEFT_EDGE:
rect = left_edge;
break;
case META_FRAME_PIECE_RIGHT_EDGE:
rect = right_edge;
break;
case META_FRAME_PIECE_BOTTOM_EDGE:
rect = bottom_edge;
break;
case META_FRAME_PIECE_OVERLAY:
rect = visible_rect;
break;
case META_FRAME_PIECE_LAST:
default:
g_assert_not_reached ();
break;
}
cairo_save (cr);
cairo_rectangle (cr, rect.x, rect.y, rect.width, rect.height);
cairo_clip (cr);
if (gdk_cairo_get_clip_rectangle (cr, NULL))
{
MetaDrawOpList *op_list;
MetaFrameStyle *parent;
parent = style;
op_list = NULL;
while (parent && op_list == NULL)
{
op_list = parent->pieces[i];
parent = parent->parent;
}
if (op_list)
{
meta_draw_op_list_draw_with_style (op_list, context, cr,
&draw_info, rect);
}
}
cairo_restore (cr);
/* Draw buttons just before overlay */
if ((i + 1) == META_FRAME_PIECE_OVERLAY)
{
gint side;
for (side = 0; side < 2; side++)
{
MetaButton *buttons;
gint n_buttons;
gint j;
if (side == 0)
{
buttons = button_layout->left_buttons;
n_buttons = button_layout->n_left_buttons;
}
else if (side == 1)
{
buttons = button_layout->right_buttons;
n_buttons = button_layout->n_right_buttons;
}
else
{
g_assert_not_reached ();
}
for (j = 0; j < n_buttons; j++)
{
MetaButton *button;
gint op;
button = &buttons[j];
rect.x = button->rect.visible.x / scale;
rect.y = button->rect.visible.y / scale;
rect.width = button->rect.visible.width / scale;
rect.height = button->rect.visible.height / scale;
if (!button->visible ||
button->type == META_BUTTON_TYPE_SPACER ||
rect.width <= 0 || rect.height <= 0)
{
continue;
}
for (op = 0; op < 2; op++)
{
MetaButtonFunction function;
MetaDrawOpList *op_list;
function = get_button_function (button->type, op == 0,
j, n_buttons, op);
op_list = meta_frame_style_get_button (style, function,
button->state);
if (op_list)
{
cairo_save (cr);
cairo_rectangle (cr, rect.x, rect.y,
rect.width, rect.height);
cairo_clip (cr);
if (gdk_cairo_get_clip_rectangle (cr, NULL))
{
meta_draw_op_list_draw_with_style (op_list,
context, cr,
&draw_info,
rect);
}
cairo_restore (cr);
}
}
}
}
}
++i;
}
cairo_restore (cr);
}
static void
meta_theme_metacity_class_init (MetaThemeMetacityClass *metacity_class)
{
GObjectClass *object_class;
MetaThemeImplClass *impl_class;
object_class = G_OBJECT_CLASS (metacity_class);
impl_class = META_THEME_IMPL_CLASS (metacity_class);
object_class->dispose = meta_theme_metacity_dispose;
object_class->finalize = meta_theme_metacity_finalize;
impl_class->load = meta_theme_metacity_load;
impl_class->get_frame_style = meta_theme_metacity_get_frame_style;
impl_class->get_frame_borders = meta_theme_metacity_get_frame_borders;
impl_class->calc_geometry = meta_theme_metacity_calc_geometry;
impl_class->draw_frame = meta_theme_metacity_draw_frame;
}
static void
meta_theme_metacity_init (MetaThemeMetacity *metacity)
{
metacity->draw_op_lists = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
(GDestroyNotify) meta_draw_op_list_unref);
metacity->frame_layouts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
(GDestroyNotify) meta_frame_layout_unref);
metacity->styles = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
(GDestroyNotify) meta_frame_style_unref);
metacity->style_sets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
(GDestroyNotify) meta_frame_style_set_unref);
metacity->images = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
(GDestroyNotify) g_object_unref);
}
gboolean
meta_theme_metacity_lookup_int (MetaThemeMetacity *metacity,
const gchar *name,
gint *value)
{
gpointer tmp;
*value = 0;
if (metacity->integers == NULL)
return FALSE;
if (!g_hash_table_lookup_extended (metacity->integers, name, NULL, &tmp))
return FALSE;
*value = GPOINTER_TO_INT (tmp);
return TRUE;
}
gboolean
meta_theme_metacity_lookup_float (MetaThemeMetacity *metacity,
const gchar *name,
gdouble *value)
{
gdouble *d;
*value = 0.0;
if (metacity->floats == NULL)
return FALSE;
d = g_hash_table_lookup (metacity->floats, name);
if (!d)
return FALSE;
*value = *d;
return TRUE;
}
MetaDrawOpList *
meta_theme_metacity_lookup_draw_op_list (MetaThemeMetacity *metacity,
const gchar *name)
{
return g_hash_table_lookup (metacity->draw_op_lists, name);
}
MetaFrameLayout *
meta_theme_metacity_lookup_layout (MetaThemeMetacity *metacity,
const gchar *name)
{
return g_hash_table_lookup (metacity->frame_layouts, name);
}
MetaFrameStyle *
meta_theme_metacity_lookup_style (MetaThemeMetacity *metacity,
const gchar *name)
{
return g_hash_table_lookup (metacity->styles, name);
}
MetaFrameStyleSet *
meta_theme_metacity_lookup_style_set (MetaThemeMetacity *metacity,
const gchar *name)
{
return g_hash_table_lookup (metacity->style_sets, name);
}
/**
* Returns the earliest version of the theme format which required support
* for a particular button. (For example, "shade" first appeared in v2, and
* "close" in v1.)
*
* \param type the button type
* \return the number of the theme format
*/
guint
meta_theme_metacity_earliest_version_with_button (MetaButtonFunction function)
{
switch (function)
{
case META_BUTTON_FUNCTION_CLOSE:
case META_BUTTON_FUNCTION_MAXIMIZE:
case META_BUTTON_FUNCTION_MINIMIZE:
case META_BUTTON_FUNCTION_MENU:
case META_BUTTON_FUNCTION_LEFT_LEFT_BACKGROUND:
case META_BUTTON_FUNCTION_LEFT_MIDDLE_BACKGROUND:
case META_BUTTON_FUNCTION_LEFT_RIGHT_BACKGROUND:
case META_BUTTON_FUNCTION_RIGHT_LEFT_BACKGROUND:
case META_BUTTON_FUNCTION_RIGHT_MIDDLE_BACKGROUND:
case META_BUTTON_FUNCTION_RIGHT_RIGHT_BACKGROUND:
return 1000;
case META_BUTTON_FUNCTION_SHADE:
case META_BUTTON_FUNCTION_ABOVE:
case META_BUTTON_FUNCTION_STICK:
case META_BUTTON_FUNCTION_UNSHADE:
case META_BUTTON_FUNCTION_UNABOVE:
case META_BUTTON_FUNCTION_UNSTICK:
return 2000;
case META_BUTTON_FUNCTION_LEFT_SINGLE_BACKGROUND:
case META_BUTTON_FUNCTION_RIGHT_SINGLE_BACKGROUND:
return 3003;
case META_BUTTON_FUNCTION_APPMENU:
return 3005;
case META_BUTTON_FUNCTION_LAST:
default:
g_warning ("Unknown button %d", (gint) function);
break;
}
return 1000;
}