mirror of
https://github.com/brl/mutter.git
synced 2024-11-28 02:50:41 -05:00
54b2fab849
Add an additional color type to pick up colors defined with @define-color in the GTK+ theme's CSS: gtk:custom(name,fallback) (where "name" refers to the name defined in GTK+'s CSS, and fallback refers to an alternative color spec which is used when the color referenced by "name" is not found) The main intent of the change is to allow designers to improve Adwaita's dark theme variant without having to compromise on colors which work in the light variant as well. https://bugzilla.gnome.org/show_bug.cgi?id=648709
4358 lines
134 KiB
C
4358 lines
134 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
|
|
/* Metacity theme parsing */
|
|
|
|
/*
|
|
* Copyright (C) 2001 Havoc Pennington
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
|
* 02111-1307, USA.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include "theme-private.h"
|
|
#include <meta/util.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
/* We were intending to put the version number
|
|
* in the subdirectory name, but we ended up
|
|
* using the filename instead. The "-1" survives
|
|
* as a fossil.
|
|
*/
|
|
#define THEME_SUBDIR "metacity-1"
|
|
|
|
/* Highest version of the theme format to
|
|
* look out for.
|
|
*/
|
|
#define THEME_MAJOR_VERSION 3
|
|
#define THEME_MINOR_VERSION 4
|
|
#define THEME_VERSION (1000 * THEME_MAJOR_VERSION + THEME_MINOR_VERSION)
|
|
|
|
#define METACITY_THEME_FILENAME_FORMAT "metacity-theme-%d.xml"
|
|
|
|
typedef enum
|
|
{
|
|
STATE_START,
|
|
STATE_THEME,
|
|
/* info section */
|
|
STATE_INFO,
|
|
STATE_NAME,
|
|
STATE_AUTHOR,
|
|
STATE_COPYRIGHT,
|
|
STATE_DATE,
|
|
STATE_DESCRIPTION,
|
|
/* constants */
|
|
STATE_CONSTANT,
|
|
/* geometry */
|
|
STATE_FRAME_GEOMETRY,
|
|
STATE_DISTANCE,
|
|
STATE_BORDER,
|
|
STATE_ASPECT_RATIO,
|
|
/* draw ops */
|
|
STATE_DRAW_OPS,
|
|
STATE_LINE,
|
|
STATE_RECTANGLE,
|
|
STATE_ARC,
|
|
STATE_CLIP,
|
|
STATE_TINT,
|
|
STATE_GRADIENT,
|
|
STATE_IMAGE,
|
|
STATE_GTK_ARROW,
|
|
STATE_GTK_BOX,
|
|
STATE_GTK_VLINE,
|
|
STATE_ICON,
|
|
STATE_TITLE,
|
|
STATE_INCLUDE, /* include another draw op list */
|
|
STATE_TILE, /* tile another draw op list */
|
|
/* sub-parts of gradient */
|
|
STATE_COLOR,
|
|
/* frame style */
|
|
STATE_FRAME_STYLE,
|
|
STATE_PIECE,
|
|
STATE_BUTTON,
|
|
/* style set */
|
|
STATE_FRAME_STYLE_SET,
|
|
STATE_FRAME,
|
|
/* assigning style sets to windows */
|
|
STATE_WINDOW,
|
|
/* things we don't use any more but we can still parse: */
|
|
STATE_MENU_ICON,
|
|
STATE_FALLBACK
|
|
} ParseState;
|
|
|
|
typedef struct
|
|
{
|
|
/* This two lists contain stacks of state and required version
|
|
* (cast to pointers.) There is one list item for each currently
|
|
* open element. */
|
|
GSList *states;
|
|
GSList *required_versions;
|
|
|
|
const char *theme_name; /* name of theme (directory it's in) */
|
|
const char *theme_file; /* theme filename */
|
|
const char *theme_dir; /* dir the theme is inside */
|
|
MetaTheme *theme; /* theme being parsed */
|
|
guint format_version; /* version of format of theme file */
|
|
char *name; /* name of named thing being parsed */
|
|
MetaFrameLayout *layout; /* layout being parsed if any */
|
|
MetaDrawOpList *op_list; /* op list being parsed if any */
|
|
MetaDrawOp *op; /* op being parsed if any */
|
|
MetaFrameStyle *style; /* frame style being parsed if any */
|
|
MetaFrameStyleSet *style_set; /* frame style set being parsed if any */
|
|
MetaFramePiece piece; /* position of piece being parsed */
|
|
MetaButtonType button_type; /* type of button/menuitem being parsed */
|
|
MetaButtonState button_state; /* state of button being parsed */
|
|
int skip_level; /* depth of elements that we're ignoring */
|
|
} ParseInfo;
|
|
|
|
typedef enum {
|
|
THEME_PARSE_ERROR_TOO_OLD,
|
|
THEME_PARSE_ERROR_TOO_FAILED
|
|
} ThemeParseError;
|
|
|
|
static GQuark
|
|
theme_parse_error_quark (void)
|
|
{
|
|
return g_quark_from_static_string ("theme-parse-error-quark");
|
|
}
|
|
|
|
#define THEME_PARSE_ERROR (theme_parse_error_quark ())
|
|
|
|
static void set_error (GError **err,
|
|
GMarkupParseContext *context,
|
|
int error_domain,
|
|
int error_code,
|
|
const char *format,
|
|
...) G_GNUC_PRINTF (5, 6);
|
|
|
|
static void add_context_to_error (GError **err,
|
|
GMarkupParseContext *context);
|
|
|
|
static void parse_info_init (ParseInfo *info);
|
|
static void parse_info_free (ParseInfo *info);
|
|
|
|
static void push_state (ParseInfo *info,
|
|
ParseState state);
|
|
static void pop_state (ParseInfo *info);
|
|
static ParseState peek_state (ParseInfo *info);
|
|
|
|
|
|
static void parse_toplevel_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error);
|
|
static void parse_info_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error);
|
|
static void parse_geometry_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error);
|
|
static void parse_draw_op_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error);
|
|
static void parse_gradient_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error);
|
|
static void parse_style_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error);
|
|
static void parse_style_set_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error);
|
|
|
|
static void parse_piece_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error);
|
|
|
|
static void parse_button_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error);
|
|
|
|
static void parse_menu_icon_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error);
|
|
|
|
static void start_element_handler (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
gpointer user_data,
|
|
GError **error);
|
|
static void end_element_handler (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
gpointer user_data,
|
|
GError **error);
|
|
static void text_handler (GMarkupParseContext *context,
|
|
const gchar *text,
|
|
gsize text_len,
|
|
gpointer user_data,
|
|
GError **error);
|
|
|
|
/* Translators: This means that an attribute which should have been found
|
|
* on an XML element was not in fact found.
|
|
*/
|
|
#define ATTRIBUTE_NOT_FOUND _("No \"%s\" attribute on element <%s>")
|
|
|
|
static GMarkupParser metacity_theme_parser = {
|
|
start_element_handler,
|
|
end_element_handler,
|
|
text_handler,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
static void
|
|
set_error (GError **err,
|
|
GMarkupParseContext *context,
|
|
int error_domain,
|
|
int error_code,
|
|
const char *format,
|
|
...)
|
|
{
|
|
int line, ch;
|
|
va_list args;
|
|
char *str;
|
|
|
|
g_markup_parse_context_get_position (context, &line, &ch);
|
|
|
|
va_start (args, format);
|
|
str = g_strdup_vprintf (format, args);
|
|
va_end (args);
|
|
|
|
g_set_error (err, error_domain, error_code,
|
|
_("Line %d character %d: %s"),
|
|
line, ch, str);
|
|
|
|
g_free (str);
|
|
}
|
|
|
|
static void
|
|
add_context_to_error (GError **err,
|
|
GMarkupParseContext *context)
|
|
{
|
|
int line, ch;
|
|
char *str;
|
|
|
|
if (err == NULL || *err == NULL)
|
|
return;
|
|
|
|
g_markup_parse_context_get_position (context, &line, &ch);
|
|
|
|
str = g_strdup_printf (_("Line %d character %d: %s"),
|
|
line, ch, (*err)->message);
|
|
g_free ((*err)->message);
|
|
(*err)->message = str;
|
|
}
|
|
|
|
static void
|
|
parse_info_init (ParseInfo *info)
|
|
{
|
|
info->theme_file = NULL;
|
|
info->states = g_slist_prepend (NULL, GINT_TO_POINTER (STATE_START));
|
|
info->required_versions = NULL;
|
|
info->theme = NULL;
|
|
info->name = NULL;
|
|
info->layout = NULL;
|
|
info->op_list = NULL;
|
|
info->op = NULL;
|
|
info->style = NULL;
|
|
info->style_set = NULL;
|
|
info->piece = META_FRAME_PIECE_LAST;
|
|
info->button_type = META_BUTTON_TYPE_LAST;
|
|
info->button_state = META_BUTTON_STATE_LAST;
|
|
info->skip_level = 0;
|
|
}
|
|
|
|
static void
|
|
parse_info_free (ParseInfo *info)
|
|
{
|
|
g_slist_free (info->states);
|
|
g_slist_free (info->required_versions);
|
|
|
|
if (info->theme)
|
|
meta_theme_free (info->theme);
|
|
|
|
if (info->layout)
|
|
meta_frame_layout_unref (info->layout);
|
|
|
|
if (info->op_list)
|
|
meta_draw_op_list_unref (info->op_list);
|
|
|
|
if (info->op)
|
|
meta_draw_op_free (info->op);
|
|
|
|
if (info->style)
|
|
meta_frame_style_unref (info->style);
|
|
|
|
if (info->style_set)
|
|
meta_frame_style_set_unref (info->style_set);
|
|
}
|
|
|
|
static void
|
|
push_state (ParseInfo *info,
|
|
ParseState state)
|
|
{
|
|
info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state));
|
|
}
|
|
|
|
static void
|
|
pop_state (ParseInfo *info)
|
|
{
|
|
g_return_if_fail (info->states != NULL);
|
|
|
|
info->states = g_slist_remove (info->states, info->states->data);
|
|
}
|
|
|
|
static ParseState
|
|
peek_state (ParseInfo *info)
|
|
{
|
|
g_return_val_if_fail (info->states != NULL, STATE_START);
|
|
|
|
return GPOINTER_TO_INT (info->states->data);
|
|
}
|
|
|
|
static void
|
|
push_required_version (ParseInfo *info,
|
|
int version)
|
|
{
|
|
info->required_versions = g_slist_prepend (info->required_versions,
|
|
GINT_TO_POINTER (version));
|
|
}
|
|
|
|
static void
|
|
pop_required_version (ParseInfo *info)
|
|
{
|
|
g_return_if_fail (info->required_versions != NULL);
|
|
|
|
info->required_versions = g_slist_delete_link (info->required_versions, info->required_versions);
|
|
}
|
|
|
|
static int
|
|
peek_required_version (ParseInfo *info)
|
|
{
|
|
if (info->required_versions)
|
|
return GPOINTER_TO_INT (info->required_versions->data);
|
|
else
|
|
return info->format_version;
|
|
}
|
|
|
|
#define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0)
|
|
|
|
typedef struct
|
|
{
|
|
const char *name;
|
|
const char **retloc;
|
|
gboolean required;
|
|
} LocateAttr;
|
|
|
|
/* Attribute names can have a leading '!' to indicate that they are
|
|
* required.
|
|
*/
|
|
static gboolean
|
|
locate_attributes (GMarkupParseContext *context,
|
|
const char *element_name,
|
|
const char **attribute_names,
|
|
const char **attribute_values,
|
|
GError **error,
|
|
const char *first_attribute_name,
|
|
const char **first_attribute_retloc,
|
|
...)
|
|
{
|
|
va_list args;
|
|
const char *name;
|
|
const char **retloc;
|
|
int n_attrs;
|
|
#define MAX_ATTRS 24
|
|
LocateAttr attrs[MAX_ATTRS];
|
|
gboolean retval;
|
|
int i;
|
|
|
|
g_return_val_if_fail (first_attribute_name != NULL, FALSE);
|
|
g_return_val_if_fail (first_attribute_retloc != NULL, FALSE);
|
|
|
|
retval = TRUE;
|
|
|
|
/* FIXME: duplicated code; refactor loop */
|
|
n_attrs = 1;
|
|
attrs[0].name = first_attribute_name;
|
|
attrs[0].retloc = first_attribute_retloc;
|
|
attrs[0].required = attrs[0].name[0]=='!';
|
|
if (attrs[0].required)
|
|
attrs[0].name++; /* skip past it */
|
|
*first_attribute_retloc = NULL;
|
|
|
|
va_start (args, first_attribute_retloc);
|
|
|
|
name = va_arg (args, const char*);
|
|
retloc = va_arg (args, const char**);
|
|
|
|
while (name != NULL)
|
|
{
|
|
g_return_val_if_fail (retloc != NULL, FALSE);
|
|
|
|
g_assert (n_attrs < MAX_ATTRS);
|
|
|
|
attrs[n_attrs].name = name;
|
|
attrs[n_attrs].retloc = retloc;
|
|
attrs[n_attrs].required = attrs[n_attrs].name[0]=='!';
|
|
if (attrs[n_attrs].required)
|
|
attrs[n_attrs].name++; /* skip past it */
|
|
|
|
n_attrs += 1;
|
|
*retloc = NULL;
|
|
|
|
name = va_arg (args, const char*);
|
|
retloc = va_arg (args, const char**);
|
|
}
|
|
|
|
va_end (args);
|
|
|
|
i = 0;
|
|
while (attribute_names[i])
|
|
{
|
|
int j;
|
|
gboolean found;
|
|
|
|
/* Can be present anywhere */
|
|
if (strcmp (attribute_names[i], "version") == 0)
|
|
{
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
found = FALSE;
|
|
j = 0;
|
|
while (j < n_attrs)
|
|
{
|
|
if (strcmp (attrs[j].name, attribute_names[i]) == 0)
|
|
{
|
|
retloc = attrs[j].retloc;
|
|
|
|
if (*retloc != NULL)
|
|
{
|
|
|
|
set_error (error, context,
|
|
G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Attribute \"%s\" repeated twice on the same <%s> element"),
|
|
attrs[j].name, element_name);
|
|
retval = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
*retloc = attribute_values[i];
|
|
found = TRUE;
|
|
}
|
|
|
|
++j;
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
j = 0;
|
|
while (j < n_attrs)
|
|
{
|
|
g_warning ("It could have been %s.\n", attrs[j++].name);
|
|
}
|
|
|
|
set_error (error, context,
|
|
G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Attribute \"%s\" is invalid on <%s> element in this context"),
|
|
attribute_names[i], element_name);
|
|
retval = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
++i;
|
|
}
|
|
|
|
/* Did we catch them all? */
|
|
i = 0;
|
|
while (i < n_attrs)
|
|
{
|
|
if (attrs[i].required && *(attrs[i].retloc)==NULL)
|
|
{
|
|
set_error (error, context,
|
|
G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
ATTRIBUTE_NOT_FOUND,
|
|
attrs[i].name, element_name);
|
|
retval = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
++i;
|
|
}
|
|
|
|
out:
|
|
return retval;
|
|
}
|
|
|
|
static gboolean
|
|
check_no_attributes (GMarkupParseContext *context,
|
|
const char *element_name,
|
|
const char **attribute_names,
|
|
const char **attribute_values,
|
|
GError **error)
|
|
{
|
|
int i = 0;
|
|
|
|
/* Can be present anywhere */
|
|
if (attribute_names[0] && strcmp (attribute_names[i], "version") == 0)
|
|
i++;
|
|
|
|
if (attribute_names[i] != NULL)
|
|
{
|
|
set_error (error, context,
|
|
G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Attribute \"%s\" is invalid on <%s> element in this context"),
|
|
attribute_names[0], element_name);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#define MAX_REASONABLE 4096
|
|
static gboolean
|
|
parse_positive_integer (const char *str,
|
|
int *val,
|
|
GMarkupParseContext *context,
|
|
MetaTheme *theme,
|
|
GError **error)
|
|
{
|
|
char *end;
|
|
long l;
|
|
int j;
|
|
|
|
*val = 0;
|
|
|
|
end = NULL;
|
|
|
|
/* Is str a constant? */
|
|
|
|
if (META_THEME_ALLOWS (theme, META_THEME_UBIQUITOUS_CONSTANTS) &&
|
|
meta_theme_lookup_int_constant (theme, str, &j))
|
|
{
|
|
/* Yes. */
|
|
l = j;
|
|
}
|
|
else
|
|
{
|
|
/* No. Let's try parsing it instead. */
|
|
|
|
l = strtol (str, &end, 10);
|
|
|
|
if (end == NULL || end == str)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Could not parse \"%s\" as an integer"),
|
|
str);
|
|
return FALSE;
|
|
}
|
|
|
|
if (*end != '\0')
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Did not understand trailing characters \"%s\" in string \"%s\""),
|
|
end, str);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (l < 0)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Integer %ld must be positive"), l);
|
|
return FALSE;
|
|
}
|
|
|
|
if (l > MAX_REASONABLE)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Integer %ld is too large, current max is %d"),
|
|
l, MAX_REASONABLE);
|
|
return FALSE;
|
|
}
|
|
|
|
*val = (int) l;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_double (const char *str,
|
|
double *val,
|
|
GMarkupParseContext *context,
|
|
GError **error)
|
|
{
|
|
char *end;
|
|
|
|
*val = 0;
|
|
|
|
end = NULL;
|
|
|
|
*val = g_ascii_strtod (str, &end);
|
|
|
|
if (end == NULL || end == str)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Could not parse \"%s\" as a floating point number"),
|
|
str);
|
|
return FALSE;
|
|
}
|
|
|
|
if (*end != '\0')
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Did not understand trailing characters \"%s\" in string \"%s\""),
|
|
end, str);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_boolean (const char *str,
|
|
gboolean *val,
|
|
GMarkupParseContext *context,
|
|
GError **error)
|
|
{
|
|
if (strcmp ("true", str) == 0)
|
|
*val = TRUE;
|
|
else if (strcmp ("false", str) == 0)
|
|
*val = FALSE;
|
|
else
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Boolean values must be \"true\" or \"false\" not \"%s\""),
|
|
str);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_rounding (const char *str,
|
|
guint *val,
|
|
GMarkupParseContext *context,
|
|
MetaTheme *theme,
|
|
GError **error)
|
|
{
|
|
if (strcmp ("true", str) == 0)
|
|
*val = 5; /* historical "true" value */
|
|
else if (strcmp ("false", str) == 0)
|
|
*val = 0;
|
|
else
|
|
{
|
|
int tmp;
|
|
gboolean result;
|
|
if (!META_THEME_ALLOWS (theme, META_THEME_VARIED_ROUND_CORNERS))
|
|
{
|
|
/* Not known in this version, so bail. */
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Boolean values must be \"true\" or \"false\" not \"%s\""),
|
|
str);
|
|
return FALSE;
|
|
}
|
|
|
|
result = parse_positive_integer (str, &tmp, context, theme, error);
|
|
|
|
*val = tmp;
|
|
|
|
return result;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_angle (const char *str,
|
|
double *val,
|
|
GMarkupParseContext *context,
|
|
GError **error)
|
|
{
|
|
if (!parse_double (str, val, context, error))
|
|
return FALSE;
|
|
|
|
if (*val < (0.0 - 1e6) || *val > (360.0 + 1e6))
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Angle must be between 0.0 and 360.0, was %g\n"),
|
|
*val);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_alpha (const char *str,
|
|
MetaAlphaGradientSpec **spec_ret,
|
|
GMarkupParseContext *context,
|
|
GError **error)
|
|
{
|
|
char **split;
|
|
int i;
|
|
int n_alphas;
|
|
MetaAlphaGradientSpec *spec;
|
|
|
|
*spec_ret = NULL;
|
|
|
|
split = g_strsplit (str, ":", -1);
|
|
|
|
i = 0;
|
|
while (split[i])
|
|
++i;
|
|
|
|
if (i == 0)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Could not parse \"%s\" as a floating point number"),
|
|
str);
|
|
|
|
g_strfreev (split);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
n_alphas = i;
|
|
|
|
/* FIXME allow specifying horizontal/vertical/diagonal in theme format,
|
|
* once we implement vertical/diagonal in gradient.c
|
|
*/
|
|
spec = meta_alpha_gradient_spec_new (META_GRADIENT_HORIZONTAL,
|
|
n_alphas);
|
|
|
|
i = 0;
|
|
while (i < n_alphas)
|
|
{
|
|
double v;
|
|
|
|
if (!parse_double (split[i], &v, context, error))
|
|
{
|
|
/* clear up, but don't set error: it was set by parse_double */
|
|
g_strfreev (split);
|
|
meta_alpha_gradient_spec_free (spec);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (v < (0.0 - 1e-6) || v > (1.0 + 1e-6))
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Alpha must be between 0.0 (invisible) and 1.0 (fully opaque), was %g\n"),
|
|
v);
|
|
|
|
g_strfreev (split);
|
|
meta_alpha_gradient_spec_free (spec);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
spec->alphas[i] = (unsigned char) (v * 255);
|
|
|
|
++i;
|
|
}
|
|
|
|
g_strfreev (split);
|
|
|
|
*spec_ret = spec;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static MetaColorSpec*
|
|
parse_color (MetaTheme *theme,
|
|
const char *str,
|
|
GError **err)
|
|
{
|
|
char* referent;
|
|
|
|
if (META_THEME_ALLOWS (theme, META_THEME_COLOR_CONSTANTS) &&
|
|
meta_theme_lookup_color_constant (theme, str, &referent))
|
|
{
|
|
if (referent)
|
|
return meta_color_spec_new_from_string (referent, err);
|
|
|
|
/* no need to free referent: it's a pointer into the actual hash table */
|
|
}
|
|
|
|
return meta_color_spec_new_from_string (str, err);
|
|
}
|
|
|
|
static gboolean
|
|
parse_title_scale (const char *str,
|
|
double *val,
|
|
GMarkupParseContext *context,
|
|
GError **error)
|
|
{
|
|
double factor;
|
|
|
|
if (strcmp (str, "xx-small") == 0)
|
|
factor = PANGO_SCALE_XX_SMALL;
|
|
else if (strcmp (str, "x-small") == 0)
|
|
factor = PANGO_SCALE_X_SMALL;
|
|
else if (strcmp (str, "small") == 0)
|
|
factor = PANGO_SCALE_SMALL;
|
|
else if (strcmp (str, "medium") == 0)
|
|
factor = PANGO_SCALE_MEDIUM;
|
|
else if (strcmp (str, "large") == 0)
|
|
factor = PANGO_SCALE_LARGE;
|
|
else if (strcmp (str, "x-large") == 0)
|
|
factor = PANGO_SCALE_X_LARGE;
|
|
else if (strcmp (str, "xx-large") == 0)
|
|
factor = PANGO_SCALE_XX_LARGE;
|
|
else
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Invalid title scale \"%s\" (must be one of xx-small,x-small,small,medium,large,x-large,xx-large)\n"),
|
|
str);
|
|
return FALSE;
|
|
}
|
|
|
|
*val = factor;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
parse_toplevel_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error)
|
|
{
|
|
g_return_if_fail (peek_state (info) == STATE_THEME);
|
|
|
|
if (ELEMENT_IS ("info"))
|
|
{
|
|
if (!check_no_attributes (context, element_name,
|
|
attribute_names, attribute_values,
|
|
error))
|
|
return;
|
|
|
|
push_state (info, STATE_INFO);
|
|
}
|
|
else if (ELEMENT_IS ("constant"))
|
|
{
|
|
const char *name;
|
|
const char *value;
|
|
int ival = 0;
|
|
double dval = 0.0;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!name", &name, "!value", &value,
|
|
NULL))
|
|
return;
|
|
|
|
/* 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_define_float_constant (info->theme,
|
|
name,
|
|
dval,
|
|
error))
|
|
{
|
|
add_context_to_error (error, context);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!parse_positive_integer (value, &ival, context, info->theme, error))
|
|
return;
|
|
|
|
if (!meta_theme_define_int_constant (info->theme,
|
|
name,
|
|
ival,
|
|
error))
|
|
{
|
|
add_context_to_error (error, context);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!meta_theme_define_color_constant (info->theme,
|
|
name,
|
|
value,
|
|
error))
|
|
{
|
|
add_context_to_error (error, context);
|
|
return;
|
|
}
|
|
}
|
|
|
|
push_state (info, STATE_CONSTANT);
|
|
}
|
|
else if (ELEMENT_IS ("frame_geometry"))
|
|
{
|
|
const char *name = NULL;
|
|
const char *parent = NULL;
|
|
const char *has_title = NULL;
|
|
const char *title_scale = NULL;
|
|
const char *rounded_top_left = NULL;
|
|
const char *rounded_top_right = NULL;
|
|
const char *rounded_bottom_left = NULL;
|
|
const char *rounded_bottom_right = NULL;
|
|
const char *hide_buttons = NULL;
|
|
gboolean has_title_val;
|
|
guint rounded_top_left_val;
|
|
guint rounded_top_right_val;
|
|
guint rounded_bottom_left_val;
|
|
guint rounded_bottom_right_val;
|
|
gboolean hide_buttons_val;
|
|
double title_scale_val;
|
|
MetaFrameLayout *parent_layout;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!name", &name, "parent", &parent,
|
|
"has_title", &has_title, "title_scale", &title_scale,
|
|
"rounded_top_left", &rounded_top_left,
|
|
"rounded_top_right", &rounded_top_right,
|
|
"rounded_bottom_left", &rounded_bottom_left,
|
|
"rounded_bottom_right", &rounded_bottom_right,
|
|
"hide_buttons", &hide_buttons,
|
|
NULL))
|
|
return;
|
|
|
|
has_title_val = TRUE;
|
|
if (has_title && !parse_boolean (has_title, &has_title_val, context, error))
|
|
return;
|
|
|
|
hide_buttons_val = FALSE;
|
|
if (hide_buttons && !parse_boolean (hide_buttons, &hide_buttons_val, context, error))
|
|
return;
|
|
|
|
rounded_top_left_val = 0;
|
|
rounded_top_right_val = 0;
|
|
rounded_bottom_left_val = 0;
|
|
rounded_bottom_right_val = 0;
|
|
|
|
if (rounded_top_left && !parse_rounding (rounded_top_left, &rounded_top_left_val, context, info->theme, error))
|
|
return;
|
|
if (rounded_top_right && !parse_rounding (rounded_top_right, &rounded_top_right_val, context, info->theme, error))
|
|
return;
|
|
if (rounded_bottom_left && !parse_rounding (rounded_bottom_left, &rounded_bottom_left_val, context, info->theme, error))
|
|
return;
|
|
if (rounded_bottom_right && !parse_rounding (rounded_bottom_right, &rounded_bottom_right_val, context, info->theme, error))
|
|
return;
|
|
|
|
title_scale_val = 1.0;
|
|
if (title_scale && !parse_title_scale (title_scale, &title_scale_val, context, error))
|
|
return;
|
|
|
|
if (meta_theme_lookup_layout (info->theme, name))
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("<%s> name \"%s\" used a second time"),
|
|
element_name, name);
|
|
return;
|
|
}
|
|
|
|
parent_layout = NULL;
|
|
if (parent)
|
|
{
|
|
parent_layout = meta_theme_lookup_layout (info->theme, parent);
|
|
if (parent_layout == NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("<%s> parent \"%s\" has not been defined"),
|
|
element_name, parent);
|
|
return;
|
|
}
|
|
}
|
|
|
|
g_assert (info->layout == NULL);
|
|
|
|
if (parent_layout)
|
|
info->layout = meta_frame_layout_copy (parent_layout);
|
|
else
|
|
info->layout = meta_frame_layout_new ();
|
|
|
|
if (has_title) /* only if explicit, otherwise inherit */
|
|
info->layout->has_title = has_title_val;
|
|
|
|
if (META_THEME_ALLOWS (info->theme, META_THEME_HIDDEN_BUTTONS) && hide_buttons_val)
|
|
info->layout->hide_buttons = hide_buttons_val;
|
|
|
|
if (title_scale)
|
|
info->layout->title_scale = title_scale_val;
|
|
|
|
if (rounded_top_left)
|
|
info->layout->top_left_corner_rounded_radius = rounded_top_left_val;
|
|
|
|
if (rounded_top_right)
|
|
info->layout->top_right_corner_rounded_radius = rounded_top_right_val;
|
|
|
|
if (rounded_bottom_left)
|
|
info->layout->bottom_left_corner_rounded_radius = rounded_bottom_left_val;
|
|
|
|
if (rounded_bottom_right)
|
|
info->layout->bottom_right_corner_rounded_radius = rounded_bottom_right_val;
|
|
|
|
meta_theme_insert_layout (info->theme, name, info->layout);
|
|
|
|
push_state (info, STATE_FRAME_GEOMETRY);
|
|
}
|
|
else if (ELEMENT_IS ("draw_ops"))
|
|
{
|
|
const char *name = NULL;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!name", &name,
|
|
NULL))
|
|
return;
|
|
|
|
if (meta_theme_lookup_draw_op_list (info->theme, name))
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("<%s> name \"%s\" used a second time"),
|
|
element_name, name);
|
|
return;
|
|
}
|
|
|
|
g_assert (info->op_list == NULL);
|
|
info->op_list = meta_draw_op_list_new (2);
|
|
|
|
meta_theme_insert_draw_op_list (info->theme, name, info->op_list);
|
|
|
|
push_state (info, STATE_DRAW_OPS);
|
|
}
|
|
else if (ELEMENT_IS ("frame_style"))
|
|
{
|
|
const char *name = NULL;
|
|
const char *parent = NULL;
|
|
const char *geometry = NULL;
|
|
const char *background = NULL;
|
|
const char *alpha = NULL;
|
|
MetaFrameStyle *parent_style;
|
|
MetaFrameLayout *layout;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!name", &name, "parent", &parent,
|
|
"geometry", &geometry,
|
|
"background", &background,
|
|
"alpha", &alpha,
|
|
NULL))
|
|
return;
|
|
|
|
if (meta_theme_lookup_style (info->theme, name))
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("<%s> name \"%s\" used a second time"),
|
|
element_name, name);
|
|
return;
|
|
}
|
|
|
|
parent_style = NULL;
|
|
if (parent)
|
|
{
|
|
parent_style = meta_theme_lookup_style (info->theme, parent);
|
|
if (parent_style == NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("<%s> parent \"%s\" has not been defined"),
|
|
element_name, parent);
|
|
return;
|
|
}
|
|
}
|
|
|
|
layout = NULL;
|
|
if (geometry)
|
|
{
|
|
layout = meta_theme_lookup_layout (info->theme, geometry);
|
|
if (layout == NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("<%s> geometry \"%s\" has not been defined"),
|
|
element_name, geometry);
|
|
return;
|
|
}
|
|
}
|
|
else if (parent_style)
|
|
{
|
|
layout = parent_style->layout;
|
|
}
|
|
|
|
if (layout == NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("<%s> must specify either a geometry or a parent that has a geometry"),
|
|
element_name);
|
|
return;
|
|
}
|
|
|
|
g_assert (info->style == NULL);
|
|
|
|
info->style = meta_frame_style_new (parent_style);
|
|
g_assert (info->style->layout == NULL);
|
|
meta_frame_layout_ref (layout);
|
|
info->style->layout = layout;
|
|
|
|
if (background != NULL && META_THEME_ALLOWS (info->theme, META_THEME_FRAME_BACKGROUNDS))
|
|
{
|
|
info->style->window_background_color = meta_color_spec_new_from_string (background, error);
|
|
if (!info->style->window_background_color)
|
|
return;
|
|
|
|
if (alpha != NULL)
|
|
{
|
|
|
|
gboolean success;
|
|
MetaAlphaGradientSpec *alpha_vector;
|
|
|
|
g_clear_error (error);
|
|
/* fortunately, we already have a routine to parse alpha values,
|
|
* though it produces a vector of them, which is a superset of
|
|
* what we want.
|
|
*/
|
|
success = parse_alpha (alpha, &alpha_vector, context, error);
|
|
if (!success)
|
|
return;
|
|
|
|
/* alpha_vector->alphas must contain at least one element */
|
|
info->style->window_background_alpha = alpha_vector->alphas[0];
|
|
|
|
meta_alpha_gradient_spec_free (alpha_vector);
|
|
}
|
|
}
|
|
else if (alpha != NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("You must specify a background for an alpha value to be meaningful"));
|
|
return;
|
|
}
|
|
|
|
meta_theme_insert_style (info->theme, name, info->style);
|
|
|
|
push_state (info, STATE_FRAME_STYLE);
|
|
}
|
|
else if (ELEMENT_IS ("frame_style_set"))
|
|
{
|
|
const char *name = NULL;
|
|
const char *parent = NULL;
|
|
MetaFrameStyleSet *parent_set;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!name", &name, "parent", &parent,
|
|
NULL))
|
|
return;
|
|
|
|
if (meta_theme_lookup_style_set (info->theme, name))
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("<%s> name \"%s\" used a second time"),
|
|
element_name, name);
|
|
return;
|
|
}
|
|
|
|
parent_set = NULL;
|
|
if (parent)
|
|
{
|
|
parent_set = meta_theme_lookup_style_set (info->theme, parent);
|
|
if (parent_set == NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("<%s> parent \"%s\" has not been defined"),
|
|
element_name, parent);
|
|
return;
|
|
}
|
|
}
|
|
|
|
g_assert (info->style_set == NULL);
|
|
|
|
info->style_set = meta_frame_style_set_new (parent_set);
|
|
|
|
meta_theme_insert_style_set (info->theme, name, info->style_set);
|
|
|
|
push_state (info, STATE_FRAME_STYLE_SET);
|
|
}
|
|
else if (ELEMENT_IS ("window"))
|
|
{
|
|
const char *type_name = NULL;
|
|
const char *style_set_name = NULL;
|
|
MetaFrameStyleSet *style_set;
|
|
MetaFrameType type;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!type", &type_name, "!style_set", &style_set_name,
|
|
NULL))
|
|
return;
|
|
|
|
type = meta_frame_type_from_string (type_name);
|
|
|
|
if (type == META_FRAME_TYPE_LAST ||
|
|
(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_lookup_style_set (info->theme,
|
|
style_set_name);
|
|
|
|
if (style_set == NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Unknown style_set \"%s\" on <%s> element"),
|
|
style_set_name, element_name);
|
|
return;
|
|
}
|
|
|
|
if (info->theme->style_sets_by_type[type] != NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Window type \"%s\" has already been assigned a style set"),
|
|
type_name);
|
|
return;
|
|
}
|
|
|
|
meta_frame_style_set_ref (style_set);
|
|
info->theme->style_sets_by_type[type] = style_set;
|
|
|
|
push_state (info, STATE_WINDOW);
|
|
}
|
|
else if (ELEMENT_IS ("menu_icon"))
|
|
{
|
|
/* Not supported any more, but we have to parse it if they include it,
|
|
* for backwards compatibility.
|
|
*/
|
|
g_assert (info->op_list == NULL);
|
|
|
|
push_state (info, STATE_MENU_ICON);
|
|
}
|
|
else if (ELEMENT_IS ("fallback"))
|
|
{
|
|
/* Not supported any more, but we have to parse it if they include it,
|
|
* for backwards compatibility.
|
|
*/
|
|
push_state (info, STATE_FALLBACK);
|
|
}
|
|
else
|
|
{
|
|
set_error (error, context,
|
|
G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Element <%s> is not allowed below <%s>"),
|
|
element_name, "metacity_theme");
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_info_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error)
|
|
{
|
|
g_return_if_fail (peek_state (info) == STATE_INFO);
|
|
|
|
if (ELEMENT_IS ("name"))
|
|
{
|
|
if (!check_no_attributes (context, element_name,
|
|
attribute_names, attribute_values,
|
|
error))
|
|
return;
|
|
|
|
push_state (info, STATE_NAME);
|
|
}
|
|
else if (ELEMENT_IS ("author"))
|
|
{
|
|
if (!check_no_attributes (context, element_name,
|
|
attribute_names, attribute_values,
|
|
error))
|
|
return;
|
|
|
|
push_state (info, STATE_AUTHOR);
|
|
}
|
|
else if (ELEMENT_IS ("copyright"))
|
|
{
|
|
if (!check_no_attributes (context, element_name,
|
|
attribute_names, attribute_values,
|
|
error))
|
|
return;
|
|
|
|
push_state (info, STATE_COPYRIGHT);
|
|
}
|
|
else if (ELEMENT_IS ("description"))
|
|
{
|
|
if (!check_no_attributes (context, element_name,
|
|
attribute_names, attribute_values,
|
|
error))
|
|
return;
|
|
|
|
push_state (info, STATE_DESCRIPTION);
|
|
}
|
|
else if (ELEMENT_IS ("date"))
|
|
{
|
|
if (!check_no_attributes (context, element_name,
|
|
attribute_names, attribute_values,
|
|
error))
|
|
return;
|
|
|
|
push_state (info, STATE_DATE);
|
|
}
|
|
else
|
|
{
|
|
set_error (error, context,
|
|
G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Element <%s> is not allowed below <%s>"),
|
|
element_name, "info");
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_distance (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error)
|
|
{
|
|
const char *name;
|
|
const char *value;
|
|
int val;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!name", &name, "!value", &value,
|
|
NULL))
|
|
return;
|
|
|
|
val = 0;
|
|
if (!parse_positive_integer (value, &val, context, info->theme, error))
|
|
return;
|
|
|
|
g_assert (val >= 0); /* yeah, "non-negative" not "positive" get over it */
|
|
g_assert (info->layout);
|
|
|
|
if (strcmp (name, "left_width") == 0)
|
|
info->layout->left_width = val;
|
|
else if (strcmp (name, "right_width") == 0)
|
|
info->layout->right_width = val;
|
|
else if (strcmp (name, "bottom_height") == 0)
|
|
info->layout->bottom_height = val;
|
|
else if (strcmp (name, "title_vertical_pad") == 0)
|
|
info->layout->title_vertical_pad = val;
|
|
else if (strcmp (name, "right_titlebar_edge") == 0)
|
|
info->layout->right_titlebar_edge = val;
|
|
else if (strcmp (name, "left_titlebar_edge") == 0)
|
|
info->layout->left_titlebar_edge = val;
|
|
else if (strcmp (name, "button_width") == 0)
|
|
{
|
|
info->layout->button_width = val;
|
|
|
|
if (!(info->layout->button_sizing == META_BUTTON_SIZING_LAST ||
|
|
info->layout->button_sizing == META_BUTTON_SIZING_FIXED))
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Cannot specify both \"button_width\"/\"button_height\" and \"aspect_ratio\" for buttons"));
|
|
return;
|
|
}
|
|
|
|
info->layout->button_sizing = META_BUTTON_SIZING_FIXED;
|
|
}
|
|
else if (strcmp (name, "button_height") == 0)
|
|
{
|
|
info->layout->button_height = val;
|
|
|
|
if (!(info->layout->button_sizing == META_BUTTON_SIZING_LAST ||
|
|
info->layout->button_sizing == META_BUTTON_SIZING_FIXED))
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Cannot specify both \"button_width\"/\"button_height\" and \"aspect_ratio\" for buttons"));
|
|
return;
|
|
}
|
|
|
|
info->layout->button_sizing = META_BUTTON_SIZING_FIXED;
|
|
}
|
|
else
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Distance \"%s\" is unknown"), name);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_aspect_ratio (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error)
|
|
{
|
|
const char *name;
|
|
const char *value;
|
|
double val;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!name", &name, "!value", &value,
|
|
NULL))
|
|
return;
|
|
|
|
val = 0;
|
|
if (!parse_double (value, &val, context, error))
|
|
return;
|
|
|
|
g_assert (info->layout);
|
|
|
|
if (strcmp (name, "button") == 0)
|
|
{
|
|
info->layout->button_aspect = val;
|
|
|
|
if (info->layout->button_sizing != META_BUTTON_SIZING_LAST)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Cannot specify both \"button_width\"/\"button_height\" and \"aspect_ratio\" for buttons"));
|
|
return;
|
|
}
|
|
|
|
info->layout->button_sizing = META_BUTTON_SIZING_ASPECT;
|
|
}
|
|
else
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Aspect ratio \"%s\" is unknown"), name);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_border (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error)
|
|
{
|
|
const char *name;
|
|
const char *top;
|
|
const char *bottom;
|
|
const char *left;
|
|
const char *right;
|
|
int top_val;
|
|
int bottom_val;
|
|
int left_val;
|
|
int right_val;
|
|
GtkBorder *border;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!name", &name,
|
|
"!top", &top,
|
|
"!bottom", &bottom,
|
|
"!left", &left,
|
|
"!right", &right,
|
|
NULL))
|
|
return;
|
|
|
|
top_val = 0;
|
|
if (!parse_positive_integer (top, &top_val, context, info->theme, error))
|
|
return;
|
|
|
|
bottom_val = 0;
|
|
if (!parse_positive_integer (bottom, &bottom_val, context, info->theme, error))
|
|
return;
|
|
|
|
left_val = 0;
|
|
if (!parse_positive_integer (left, &left_val, context, info->theme, error))
|
|
return;
|
|
|
|
right_val = 0;
|
|
if (!parse_positive_integer (right, &right_val, context, info->theme, error))
|
|
return;
|
|
|
|
g_assert (info->layout);
|
|
|
|
border = NULL;
|
|
|
|
if (strcmp (name, "title_border") == 0)
|
|
border = &info->layout->title_border;
|
|
else if (strcmp (name, "button_border") == 0)
|
|
border = &info->layout->button_border;
|
|
|
|
if (border == NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Border \"%s\" is unknown"), name);
|
|
return;
|
|
}
|
|
|
|
border->top = top_val;
|
|
border->bottom = bottom_val;
|
|
border->left = left_val;
|
|
border->right = right_val;
|
|
}
|
|
|
|
static void
|
|
parse_geometry_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error)
|
|
{
|
|
g_return_if_fail (peek_state (info) == STATE_FRAME_GEOMETRY);
|
|
|
|
if (ELEMENT_IS ("distance"))
|
|
{
|
|
parse_distance (context, element_name,
|
|
attribute_names, attribute_values,
|
|
info, error);
|
|
push_state (info, STATE_DISTANCE);
|
|
}
|
|
else if (ELEMENT_IS ("border"))
|
|
{
|
|
parse_border (context, element_name,
|
|
attribute_names, attribute_values,
|
|
info, error);
|
|
push_state (info, STATE_BORDER);
|
|
}
|
|
else if (ELEMENT_IS ("aspect_ratio"))
|
|
{
|
|
parse_aspect_ratio (context, element_name,
|
|
attribute_names, attribute_values,
|
|
info, error);
|
|
|
|
push_state (info, STATE_ASPECT_RATIO);
|
|
}
|
|
else
|
|
{
|
|
set_error (error, context,
|
|
G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Element <%s> is not allowed below <%s>"),
|
|
element_name, "frame_geometry");
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
static gboolean
|
|
check_expression (PosToken *tokens,
|
|
int n_tokens,
|
|
gboolean has_object,
|
|
MetaTheme *theme,
|
|
GMarkupParseContext *context,
|
|
GError **error)
|
|
{
|
|
MetaPositionExprEnv env;
|
|
int x, y;
|
|
|
|
/* We set it all to 0 to try and catch divide-by-zero screwups.
|
|
* it's possible we should instead guarantee that widths and heights
|
|
* are at least 1.
|
|
*/
|
|
|
|
env.rect = meta_rect (0, 0, 0, 0);
|
|
if (has_object)
|
|
{
|
|
env.object_width = 0;
|
|
env.object_height = 0;
|
|
}
|
|
else
|
|
{
|
|
env.object_width = -1;
|
|
env.object_height = -1;
|
|
}
|
|
|
|
env.left_width = 0;
|
|
env.right_width = 0;
|
|
env.top_height = 0;
|
|
env.bottom_height = 0;
|
|
env.title_width = 0;
|
|
env.title_height = 0;
|
|
|
|
env.icon_width = 0;
|
|
env.icon_height = 0;
|
|
env.mini_icon_width = 0;
|
|
env.mini_icon_height = 0;
|
|
env.theme = theme;
|
|
|
|
if (!meta_parse_position_expression (tokens, n_tokens,
|
|
&env,
|
|
&x, &y,
|
|
error))
|
|
{
|
|
add_context_to_error (error, context);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
parse_draw_op_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error)
|
|
{
|
|
g_return_if_fail (peek_state (info) == STATE_DRAW_OPS);
|
|
|
|
if (ELEMENT_IS ("line"))
|
|
{
|
|
MetaDrawOp *op;
|
|
const char *color;
|
|
const char *x1;
|
|
const char *y1;
|
|
const char *x2;
|
|
const char *y2;
|
|
const char *dash_on_length;
|
|
const char *dash_off_length;
|
|
const char *width;
|
|
MetaColorSpec *color_spec;
|
|
int dash_on_val;
|
|
int dash_off_val;
|
|
int width_val;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!color", &color,
|
|
"!x1", &x1, "!y1", &y1,
|
|
"!x2", &x2, "!y2", &y2,
|
|
"dash_on_length", &dash_on_length,
|
|
"dash_off_length", &dash_off_length,
|
|
"width", &width,
|
|
NULL))
|
|
return;
|
|
|
|
#if 0
|
|
if (!check_expression (x1, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (y1, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (x2, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (y2, FALSE, info->theme, context, error))
|
|
return;
|
|
#endif
|
|
|
|
dash_on_val = 0;
|
|
if (dash_on_length &&
|
|
!parse_positive_integer (dash_on_length, &dash_on_val, context, info->theme, error))
|
|
return;
|
|
|
|
dash_off_val = 0;
|
|
if (dash_off_length &&
|
|
!parse_positive_integer (dash_off_length, &dash_off_val, context, info->theme, error))
|
|
return;
|
|
|
|
width_val = 0;
|
|
if (width &&
|
|
!parse_positive_integer (width, &width_val, context, info->theme, error))
|
|
return;
|
|
|
|
/* Check last so we don't have to free it when other
|
|
* stuff fails
|
|
*/
|
|
color_spec = parse_color (info->theme, color, error);
|
|
if (color_spec == NULL)
|
|
{
|
|
add_context_to_error (error, context);
|
|
return;
|
|
}
|
|
|
|
op = meta_draw_op_new (META_DRAW_LINE);
|
|
|
|
op->data.line.color_spec = color_spec;
|
|
|
|
op->data.line.x1 = meta_draw_spec_new (info->theme, x1, NULL);
|
|
op->data.line.y1 = meta_draw_spec_new (info->theme, y1, NULL);
|
|
|
|
if (strcmp(x1, x2)==0)
|
|
op->data.line.x2 = NULL;
|
|
else
|
|
op->data.line.x2 = meta_draw_spec_new (info->theme, x2, NULL);
|
|
|
|
if (strcmp(y1, y2)==0)
|
|
op->data.line.y2 = NULL;
|
|
else
|
|
op->data.line.y2 = meta_draw_spec_new (info->theme, y2, NULL);
|
|
|
|
op->data.line.width = width_val;
|
|
op->data.line.dash_on_length = dash_on_val;
|
|
op->data.line.dash_off_length = dash_off_val;
|
|
|
|
g_assert (info->op_list);
|
|
|
|
meta_draw_op_list_append (info->op_list, op);
|
|
|
|
push_state (info, STATE_LINE);
|
|
}
|
|
else if (ELEMENT_IS ("rectangle"))
|
|
{
|
|
MetaDrawOp *op;
|
|
const char *color;
|
|
const char *x;
|
|
const char *y;
|
|
const char *width;
|
|
const char *height;
|
|
const char *filled;
|
|
gboolean filled_val;
|
|
MetaColorSpec *color_spec;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!color", &color,
|
|
"!x", &x, "!y", &y,
|
|
"!width", &width, "!height", &height,
|
|
"filled", &filled,
|
|
NULL))
|
|
return;
|
|
|
|
#if 0
|
|
if (!check_expression (x, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (y, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (width, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (height, FALSE, info->theme, context, error))
|
|
return;
|
|
#endif
|
|
|
|
filled_val = FALSE;
|
|
if (filled && !parse_boolean (filled, &filled_val, context, error))
|
|
return;
|
|
|
|
/* Check last so we don't have to free it when other
|
|
* stuff fails
|
|
*/
|
|
color_spec = parse_color (info->theme, color, error);
|
|
if (color_spec == NULL)
|
|
{
|
|
add_context_to_error (error, context);
|
|
return;
|
|
}
|
|
|
|
op = meta_draw_op_new (META_DRAW_RECTANGLE);
|
|
|
|
op->data.rectangle.color_spec = color_spec;
|
|
op->data.rectangle.x = meta_draw_spec_new (info->theme, x, NULL);
|
|
op->data.rectangle.y = meta_draw_spec_new (info->theme, y, NULL);
|
|
op->data.rectangle.width = meta_draw_spec_new (info->theme, width, NULL);
|
|
op->data.rectangle.height = meta_draw_spec_new (info->theme,
|
|
height, NULL);
|
|
|
|
op->data.rectangle.filled = filled_val;
|
|
|
|
g_assert (info->op_list);
|
|
|
|
meta_draw_op_list_append (info->op_list, op);
|
|
|
|
push_state (info, STATE_RECTANGLE);
|
|
}
|
|
else if (ELEMENT_IS ("arc"))
|
|
{
|
|
MetaDrawOp *op;
|
|
const char *color;
|
|
const char *x;
|
|
const char *y;
|
|
const char *width;
|
|
const char *height;
|
|
const char *filled;
|
|
const char *start_angle;
|
|
const char *extent_angle;
|
|
const char *from;
|
|
const char *to;
|
|
gboolean filled_val;
|
|
double start_angle_val;
|
|
double extent_angle_val;
|
|
MetaColorSpec *color_spec;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!color", &color,
|
|
"!x", &x, "!y", &y,
|
|
"!width", &width, "!height", &height,
|
|
"filled", &filled,
|
|
"start_angle", &start_angle,
|
|
"extent_angle", &extent_angle,
|
|
"from", &from,
|
|
"to", &to,
|
|
NULL))
|
|
return;
|
|
|
|
if (META_THEME_ALLOWS (info->theme, META_THEME_DEGREES_IN_ARCS) )
|
|
{
|
|
if (start_angle == NULL && from == NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("No \"start_angle\" or \"from\" attribute on element <%s>"), element_name);
|
|
return;
|
|
}
|
|
|
|
if (extent_angle == NULL && to == NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("No \"extent_angle\" or \"to\" attribute on element <%s>"), element_name);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (start_angle == NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
ATTRIBUTE_NOT_FOUND, "start_angle", element_name);
|
|
return;
|
|
}
|
|
|
|
if (extent_angle == NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
ATTRIBUTE_NOT_FOUND, "extent_angle", element_name);
|
|
return;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
if (!check_expression (x, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (y, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (width, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (height, FALSE, info->theme, context, error))
|
|
return;
|
|
#endif
|
|
|
|
if (start_angle == NULL)
|
|
{
|
|
if (!parse_angle (from, &start_angle_val, context, error))
|
|
return;
|
|
|
|
start_angle_val = (180-start_angle_val)/360.0;
|
|
}
|
|
else
|
|
{
|
|
if (!parse_angle (start_angle, &start_angle_val, context, error))
|
|
return;
|
|
}
|
|
|
|
if (extent_angle == NULL)
|
|
{
|
|
if (!parse_angle (to, &extent_angle_val, context, error))
|
|
return;
|
|
|
|
extent_angle_val = ((180-extent_angle_val)/360.0) - start_angle_val;
|
|
}
|
|
else
|
|
{
|
|
if (!parse_angle (extent_angle, &extent_angle_val, context, error))
|
|
return;
|
|
}
|
|
|
|
filled_val = FALSE;
|
|
if (filled && !parse_boolean (filled, &filled_val, context, error))
|
|
return;
|
|
|
|
/* Check last so we don't have to free it when other
|
|
* stuff fails
|
|
*/
|
|
color_spec = parse_color (info->theme, color, error);
|
|
if (color_spec == NULL)
|
|
{
|
|
add_context_to_error (error, context);
|
|
return;
|
|
}
|
|
|
|
op = meta_draw_op_new (META_DRAW_ARC);
|
|
|
|
op->data.arc.color_spec = color_spec;
|
|
|
|
op->data.arc.x = meta_draw_spec_new (info->theme, x, NULL);
|
|
op->data.arc.y = meta_draw_spec_new (info->theme, y, NULL);
|
|
op->data.arc.width = meta_draw_spec_new (info->theme, width, NULL);
|
|
op->data.arc.height = meta_draw_spec_new (info->theme, height, NULL);
|
|
|
|
op->data.arc.filled = filled_val;
|
|
op->data.arc.start_angle = start_angle_val;
|
|
op->data.arc.extent_angle = extent_angle_val;
|
|
|
|
g_assert (info->op_list);
|
|
|
|
meta_draw_op_list_append (info->op_list, op);
|
|
|
|
push_state (info, STATE_ARC);
|
|
}
|
|
else if (ELEMENT_IS ("clip"))
|
|
{
|
|
MetaDrawOp *op;
|
|
const char *x;
|
|
const char *y;
|
|
const char *width;
|
|
const char *height;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!x", &x, "!y", &y,
|
|
"!width", &width, "!height", &height,
|
|
NULL))
|
|
return;
|
|
|
|
#if 0
|
|
if (!check_expression (x, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (y, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (width, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (height, FALSE, info->theme, context, error))
|
|
return;
|
|
#endif
|
|
op = meta_draw_op_new (META_DRAW_CLIP);
|
|
|
|
op->data.clip.x = meta_draw_spec_new (info->theme, x, NULL);
|
|
op->data.clip.y = meta_draw_spec_new (info->theme, y, NULL);
|
|
op->data.clip.width = meta_draw_spec_new (info->theme, width, NULL);
|
|
op->data.clip.height = meta_draw_spec_new (info->theme, height, NULL);
|
|
|
|
g_assert (info->op_list);
|
|
|
|
meta_draw_op_list_append (info->op_list, op);
|
|
|
|
push_state (info, STATE_CLIP);
|
|
}
|
|
else if (ELEMENT_IS ("tint"))
|
|
{
|
|
MetaDrawOp *op;
|
|
const char *color;
|
|
const char *x;
|
|
const char *y;
|
|
const char *width;
|
|
const char *height;
|
|
const char *alpha;
|
|
MetaAlphaGradientSpec *alpha_spec;
|
|
MetaColorSpec *color_spec;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!color", &color,
|
|
"!x", &x, "!y", &y,
|
|
"!width", &width, "!height", &height,
|
|
"!alpha", &alpha,
|
|
NULL))
|
|
return;
|
|
|
|
#if 0
|
|
if (!check_expression (x, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (y, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (width, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (height, FALSE, info->theme, context, error))
|
|
return;
|
|
#endif
|
|
alpha_spec = NULL;
|
|
if (!parse_alpha (alpha, &alpha_spec, context, error))
|
|
return;
|
|
|
|
/* Check last so we don't have to free it when other
|
|
* stuff fails
|
|
*/
|
|
color_spec = parse_color (info->theme, color, error);
|
|
if (color_spec == NULL)
|
|
{
|
|
if (alpha_spec)
|
|
meta_alpha_gradient_spec_free (alpha_spec);
|
|
|
|
add_context_to_error (error, context);
|
|
return;
|
|
}
|
|
|
|
op = meta_draw_op_new (META_DRAW_TINT);
|
|
|
|
op->data.tint.color_spec = color_spec;
|
|
op->data.tint.alpha_spec = alpha_spec;
|
|
|
|
op->data.tint.x = meta_draw_spec_new (info->theme, x, NULL);
|
|
op->data.tint.y = meta_draw_spec_new (info->theme, y, NULL);
|
|
op->data.tint.width = meta_draw_spec_new (info->theme, width, NULL);
|
|
op->data.tint.height = meta_draw_spec_new (info->theme, height, NULL);
|
|
|
|
g_assert (info->op_list);
|
|
|
|
meta_draw_op_list_append (info->op_list, op);
|
|
|
|
push_state (info, STATE_TINT);
|
|
}
|
|
else if (ELEMENT_IS ("gradient"))
|
|
{
|
|
const char *x;
|
|
const char *y;
|
|
const char *width;
|
|
const char *height;
|
|
const char *type;
|
|
const char *alpha;
|
|
MetaAlphaGradientSpec *alpha_spec;
|
|
MetaGradientType type_val;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!type", &type,
|
|
"!x", &x, "!y", &y,
|
|
"!width", &width, "!height", &height,
|
|
"alpha", &alpha,
|
|
NULL))
|
|
return;
|
|
|
|
#if 0
|
|
if (!check_expression (x, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (y, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (width, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (height, FALSE, info->theme, context, error))
|
|
return;
|
|
#endif
|
|
|
|
type_val = meta_gradient_type_from_string (type);
|
|
if (type_val == META_GRADIENT_LAST)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Did not understand value \"%s\" for type of gradient"),
|
|
type);
|
|
return;
|
|
}
|
|
|
|
alpha_spec = NULL;
|
|
if (alpha && !parse_alpha (alpha, &alpha_spec, context, error))
|
|
return;
|
|
|
|
g_assert (info->op == NULL);
|
|
info->op = meta_draw_op_new (META_DRAW_GRADIENT);
|
|
|
|
info->op->data.gradient.x = meta_draw_spec_new (info->theme, x, NULL);
|
|
info->op->data.gradient.y = meta_draw_spec_new (info->theme, y, NULL);
|
|
info->op->data.gradient.width = meta_draw_spec_new (info->theme,
|
|
width, NULL);
|
|
info->op->data.gradient.height = meta_draw_spec_new (info->theme,
|
|
height, NULL);
|
|
|
|
info->op->data.gradient.gradient_spec = meta_gradient_spec_new (type_val);
|
|
|
|
info->op->data.gradient.alpha_spec = alpha_spec;
|
|
|
|
push_state (info, STATE_GRADIENT);
|
|
|
|
/* op gets appended on close tag */
|
|
}
|
|
else if (ELEMENT_IS ("image"))
|
|
{
|
|
MetaDrawOp *op;
|
|
const char *filename;
|
|
const char *x;
|
|
const char *y;
|
|
const char *width;
|
|
const char *height;
|
|
const char *alpha;
|
|
const char *colorize;
|
|
const char *fill_type;
|
|
MetaAlphaGradientSpec *alpha_spec;
|
|
GdkPixbuf *pixbuf;
|
|
MetaColorSpec *colorize_spec = NULL;
|
|
MetaImageFillType fill_type_val;
|
|
int h, w, c;
|
|
int pixbuf_width, pixbuf_height, pixbuf_n_channels, pixbuf_rowstride;
|
|
guchar *pixbuf_pixels;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!x", &x, "!y", &y,
|
|
"!width", &width, "!height", &height,
|
|
"alpha", &alpha, "!filename", &filename,
|
|
"colorize", &colorize,
|
|
"fill_type", &fill_type,
|
|
NULL))
|
|
return;
|
|
|
|
#if 0
|
|
if (!check_expression (x, TRUE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (y, TRUE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (width, TRUE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (height, TRUE, info->theme, context, error))
|
|
return;
|
|
#endif
|
|
fill_type_val = META_IMAGE_FILL_SCALE;
|
|
if (fill_type)
|
|
{
|
|
fill_type_val = meta_image_fill_type_from_string (fill_type);
|
|
|
|
if (((int) fill_type_val) == -1)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Did not understand fill type \"%s\" for <%s> element"),
|
|
fill_type, element_name);
|
|
}
|
|
}
|
|
|
|
/* Check last so we don't have to free it when other
|
|
* stuff fails.
|
|
*
|
|
* If it's a theme image, ask for it at 64px, which is
|
|
* the largest possible. We scale it anyway.
|
|
*/
|
|
pixbuf = meta_theme_load_image (info->theme, filename, 64, error);
|
|
|
|
if (pixbuf == NULL)
|
|
{
|
|
add_context_to_error (error, context);
|
|
return;
|
|
}
|
|
|
|
if (colorize)
|
|
{
|
|
colorize_spec = parse_color (info->theme, colorize, error);
|
|
|
|
if (colorize_spec == NULL)
|
|
{
|
|
add_context_to_error (error, context);
|
|
g_object_unref (G_OBJECT (pixbuf));
|
|
return;
|
|
}
|
|
}
|
|
|
|
alpha_spec = NULL;
|
|
if (alpha && !parse_alpha (alpha, &alpha_spec, context, error))
|
|
{
|
|
g_object_unref (G_OBJECT (pixbuf));
|
|
return;
|
|
}
|
|
|
|
op = meta_draw_op_new (META_DRAW_IMAGE);
|
|
|
|
op->data.image.pixbuf = pixbuf;
|
|
op->data.image.colorize_spec = colorize_spec;
|
|
|
|
op->data.image.x = meta_draw_spec_new (info->theme, x, NULL);
|
|
op->data.image.y = meta_draw_spec_new (info->theme, y, NULL);
|
|
op->data.image.width = meta_draw_spec_new (info->theme, width, NULL);
|
|
op->data.image.height = meta_draw_spec_new (info->theme, height, NULL);
|
|
|
|
op->data.image.alpha_spec = alpha_spec;
|
|
op->data.image.fill_type = fill_type_val;
|
|
|
|
/* Check for vertical & horizontal stripes */
|
|
pixbuf_n_channels = gdk_pixbuf_get_n_channels(pixbuf);
|
|
pixbuf_width = gdk_pixbuf_get_width(pixbuf);
|
|
pixbuf_height = gdk_pixbuf_get_height(pixbuf);
|
|
pixbuf_rowstride = gdk_pixbuf_get_rowstride(pixbuf);
|
|
pixbuf_pixels = gdk_pixbuf_get_pixels(pixbuf);
|
|
|
|
/* Check for horizontal stripes */
|
|
for (h = 0; h < pixbuf_height; h++)
|
|
{
|
|
for (w = 1; w < pixbuf_width; w++)
|
|
{
|
|
for (c = 0; c < pixbuf_n_channels; c++)
|
|
{
|
|
if (pixbuf_pixels[(h * pixbuf_rowstride) + c] !=
|
|
pixbuf_pixels[(h * pixbuf_rowstride) + w + c])
|
|
break;
|
|
}
|
|
if (c < pixbuf_n_channels)
|
|
break;
|
|
}
|
|
if (w < pixbuf_width)
|
|
break;
|
|
}
|
|
|
|
if (h >= pixbuf_height)
|
|
{
|
|
op->data.image.horizontal_stripes = TRUE;
|
|
}
|
|
else
|
|
{
|
|
op->data.image.horizontal_stripes = FALSE;
|
|
}
|
|
|
|
/* Check for vertical stripes */
|
|
for (w = 0; w < pixbuf_width; w++)
|
|
{
|
|
for (h = 1; h < pixbuf_height; h++)
|
|
{
|
|
for (c = 0; c < pixbuf_n_channels; c++)
|
|
{
|
|
if (pixbuf_pixels[w + c] !=
|
|
pixbuf_pixels[(h * pixbuf_rowstride) + w + c])
|
|
break;
|
|
}
|
|
if (c < pixbuf_n_channels)
|
|
break;
|
|
}
|
|
if (h < pixbuf_height)
|
|
break;
|
|
}
|
|
|
|
if (w >= pixbuf_width)
|
|
{
|
|
op->data.image.vertical_stripes = TRUE;
|
|
}
|
|
else
|
|
{
|
|
op->data.image.vertical_stripes = FALSE;
|
|
}
|
|
|
|
g_assert (info->op_list);
|
|
|
|
meta_draw_op_list_append (info->op_list, op);
|
|
|
|
push_state (info, STATE_IMAGE);
|
|
}
|
|
else if (ELEMENT_IS ("gtk_arrow"))
|
|
{
|
|
MetaDrawOp *op;
|
|
const char *state;
|
|
const char *shadow;
|
|
const char *arrow;
|
|
const char *x;
|
|
const char *y;
|
|
const char *width;
|
|
const char *height;
|
|
const char *filled;
|
|
gboolean filled_val;
|
|
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;
|
|
|
|
#if 0
|
|
if (!check_expression (x, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (y, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (width, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (height, FALSE, info->theme, context, error))
|
|
return;
|
|
#endif
|
|
filled_val = TRUE;
|
|
if (filled && !parse_boolean (filled, &filled_val, context, error))
|
|
return;
|
|
|
|
state_val = meta_gtk_state_from_string (state);
|
|
if (((int) state_val) == -1)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Did not understand state \"%s\" for <%s> element"),
|
|
state, element_name);
|
|
return;
|
|
}
|
|
|
|
shadow_val = meta_gtk_shadow_from_string (shadow);
|
|
if (((int) shadow_val) == -1)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Did not understand shadow \"%s\" for <%s> element"),
|
|
shadow, element_name);
|
|
return;
|
|
}
|
|
|
|
arrow_val = meta_gtk_arrow_from_string (arrow);
|
|
if (((int) arrow_val) == -1)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Did not understand arrow \"%s\" for <%s> element"),
|
|
arrow, element_name);
|
|
return;
|
|
}
|
|
|
|
op = meta_draw_op_new (META_DRAW_GTK_ARROW);
|
|
|
|
op->data.gtk_arrow.x = meta_draw_spec_new (info->theme, x, NULL);
|
|
op->data.gtk_arrow.y = meta_draw_spec_new (info->theme, y, NULL);
|
|
op->data.gtk_arrow.width = meta_draw_spec_new (info->theme, width, NULL);
|
|
op->data.gtk_arrow.height = meta_draw_spec_new (info->theme,
|
|
height, NULL);
|
|
|
|
op->data.gtk_arrow.filled = filled_val;
|
|
op->data.gtk_arrow.state = state_val;
|
|
op->data.gtk_arrow.shadow = shadow_val;
|
|
op->data.gtk_arrow.arrow = arrow_val;
|
|
|
|
g_assert (info->op_list);
|
|
|
|
meta_draw_op_list_append (info->op_list, op);
|
|
|
|
push_state (info, STATE_GTK_ARROW);
|
|
}
|
|
else if (ELEMENT_IS ("gtk_box"))
|
|
{
|
|
MetaDrawOp *op;
|
|
const char *state;
|
|
const char *shadow;
|
|
const char *x;
|
|
const char *y;
|
|
const char *width;
|
|
const char *height;
|
|
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 0
|
|
if (!check_expression (x, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (y, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (width, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (height, FALSE, info->theme, context, error))
|
|
return;
|
|
#endif
|
|
state_val = meta_gtk_state_from_string (state);
|
|
if (((int) state_val) == -1)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Did not understand state \"%s\" for <%s> element"),
|
|
state, element_name);
|
|
return;
|
|
}
|
|
|
|
shadow_val = meta_gtk_shadow_from_string (shadow);
|
|
if (((int) shadow_val) == -1)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Did not understand shadow \"%s\" for <%s> element"),
|
|
shadow, element_name);
|
|
return;
|
|
}
|
|
|
|
op = meta_draw_op_new (META_DRAW_GTK_BOX);
|
|
|
|
op->data.gtk_box.x = meta_draw_spec_new (info->theme, x, NULL);
|
|
op->data.gtk_box.y = meta_draw_spec_new (info->theme, y, NULL);
|
|
op->data.gtk_box.width = meta_draw_spec_new (info->theme, width, NULL);
|
|
op->data.gtk_box.height = meta_draw_spec_new (info->theme, height, NULL);
|
|
|
|
op->data.gtk_box.state = state_val;
|
|
op->data.gtk_box.shadow = shadow_val;
|
|
|
|
g_assert (info->op_list);
|
|
|
|
meta_draw_op_list_append (info->op_list, op);
|
|
|
|
push_state (info, STATE_GTK_BOX);
|
|
}
|
|
else if (ELEMENT_IS ("gtk_vline"))
|
|
{
|
|
MetaDrawOp *op;
|
|
const char *state;
|
|
const char *x;
|
|
const char *y1;
|
|
const char *y2;
|
|
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 0
|
|
if (!check_expression (x, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (y1, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (y2, FALSE, info->theme, context, error))
|
|
return;
|
|
#endif
|
|
|
|
state_val = meta_gtk_state_from_string (state);
|
|
if (((int) state_val) == -1)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Did not understand state \"%s\" for <%s> element"),
|
|
state, element_name);
|
|
return;
|
|
}
|
|
|
|
op = meta_draw_op_new (META_DRAW_GTK_VLINE);
|
|
|
|
op->data.gtk_vline.x = meta_draw_spec_new (info->theme, x, NULL);
|
|
op->data.gtk_vline.y1 = meta_draw_spec_new (info->theme, y1, NULL);
|
|
op->data.gtk_vline.y2 = meta_draw_spec_new (info->theme, y2, NULL);
|
|
|
|
op->data.gtk_vline.state = state_val;
|
|
|
|
g_assert (info->op_list);
|
|
|
|
meta_draw_op_list_append (info->op_list, op);
|
|
|
|
push_state (info, STATE_GTK_VLINE);
|
|
}
|
|
else if (ELEMENT_IS ("icon"))
|
|
{
|
|
MetaDrawOp *op;
|
|
const char *x;
|
|
const char *y;
|
|
const char *width;
|
|
const char *height;
|
|
const char *alpha;
|
|
const char *fill_type;
|
|
MetaAlphaGradientSpec *alpha_spec;
|
|
MetaImageFillType fill_type_val;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!x", &x, "!y", &y,
|
|
"!width", &width, "!height", &height,
|
|
"alpha", &alpha,
|
|
"fill_type", &fill_type,
|
|
NULL))
|
|
return;
|
|
|
|
#if 0
|
|
if (!check_expression (x, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (y, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (width, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (height, FALSE, info->theme, context, error))
|
|
return;
|
|
#endif
|
|
fill_type_val = META_IMAGE_FILL_SCALE;
|
|
if (fill_type)
|
|
{
|
|
fill_type_val = meta_image_fill_type_from_string (fill_type);
|
|
|
|
if (((int) fill_type_val) == -1)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Did not understand fill type \"%s\" for <%s> element"),
|
|
fill_type, element_name);
|
|
}
|
|
}
|
|
|
|
alpha_spec = NULL;
|
|
if (alpha && !parse_alpha (alpha, &alpha_spec, context, error))
|
|
return;
|
|
|
|
op = meta_draw_op_new (META_DRAW_ICON);
|
|
|
|
op->data.icon.x = meta_draw_spec_new (info->theme, x, NULL);
|
|
op->data.icon.y = meta_draw_spec_new (info->theme, y, NULL);
|
|
op->data.icon.width = meta_draw_spec_new (info->theme, width, NULL);
|
|
op->data.icon.height = meta_draw_spec_new (info->theme, height, NULL);
|
|
|
|
op->data.icon.alpha_spec = alpha_spec;
|
|
op->data.icon.fill_type = fill_type_val;
|
|
|
|
g_assert (info->op_list);
|
|
|
|
meta_draw_op_list_append (info->op_list, op);
|
|
|
|
push_state (info, STATE_ICON);
|
|
}
|
|
else if (ELEMENT_IS ("title"))
|
|
{
|
|
MetaDrawOp *op;
|
|
const char *color;
|
|
const char *x;
|
|
const char *y;
|
|
const char *ellipsize_width;
|
|
MetaColorSpec *color_spec;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!color", &color,
|
|
"!x", &x, "!y", &y,
|
|
"ellipsize_width", &ellipsize_width,
|
|
NULL))
|
|
return;
|
|
|
|
#if 0
|
|
if (!check_expression (x, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (y, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (ellipsize_width, FALSE, info->theme, context, error))
|
|
return;
|
|
#endif
|
|
|
|
if (ellipsize_width && peek_required_version (info) < 3001)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
ATTRIBUTE_NOT_FOUND, "ellipsize_width", element_name);
|
|
return;
|
|
}
|
|
|
|
/* Check last so we don't have to free it when other
|
|
* stuff fails
|
|
*/
|
|
color_spec = parse_color (info->theme, color, error);
|
|
if (color_spec == NULL)
|
|
{
|
|
add_context_to_error (error, context);
|
|
return;
|
|
}
|
|
|
|
op = meta_draw_op_new (META_DRAW_TITLE);
|
|
|
|
op->data.title.color_spec = color_spec;
|
|
|
|
op->data.title.x = meta_draw_spec_new (info->theme, x, NULL);
|
|
op->data.title.y = meta_draw_spec_new (info->theme, y, NULL);
|
|
if (ellipsize_width)
|
|
op->data.title.ellipsize_width = meta_draw_spec_new (info->theme, ellipsize_width, NULL);
|
|
|
|
g_assert (info->op_list);
|
|
|
|
meta_draw_op_list_append (info->op_list, op);
|
|
|
|
push_state (info, STATE_TITLE);
|
|
}
|
|
else if (ELEMENT_IS ("include"))
|
|
{
|
|
MetaDrawOp *op;
|
|
const char *name;
|
|
const char *x;
|
|
const char *y;
|
|
const char *width;
|
|
const char *height;
|
|
MetaDrawOpList *op_list;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"x", &x, "y", &y,
|
|
"width", &width, "height", &height,
|
|
"!name", &name,
|
|
NULL))
|
|
return;
|
|
|
|
/* x/y/width/height default to 0,0,width,height - should
|
|
* probably do this for all the draw ops
|
|
*/
|
|
#if 0
|
|
if (x && !check_expression (x, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (y && !check_expression (y, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (width && !check_expression (width, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (height && !check_expression (height, FALSE, info->theme, context, error))
|
|
return;
|
|
#endif
|
|
|
|
op_list = meta_theme_lookup_draw_op_list (info->theme,
|
|
name);
|
|
if (op_list == NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("No <draw_ops> called \"%s\" has been defined"),
|
|
name);
|
|
return;
|
|
}
|
|
|
|
g_assert (info->op_list);
|
|
|
|
if (op_list == info->op_list ||
|
|
meta_draw_op_list_contains (op_list, info->op_list))
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Including draw_ops \"%s\" here would create a circular reference"),
|
|
name);
|
|
return;
|
|
}
|
|
|
|
op = meta_draw_op_new (META_DRAW_OP_LIST);
|
|
|
|
meta_draw_op_list_ref (op_list);
|
|
op->data.op_list.op_list = op_list;
|
|
|
|
op->data.op_list.x = meta_draw_spec_new (info->theme, x ? x : "0", NULL);
|
|
op->data.op_list.y = meta_draw_spec_new (info->theme, y ? y : "0", NULL);
|
|
op->data.op_list.width = meta_draw_spec_new (info->theme,
|
|
width ? width : "width",
|
|
NULL);
|
|
op->data.op_list.height = meta_draw_spec_new (info->theme,
|
|
height ? height : "height",
|
|
NULL);
|
|
|
|
meta_draw_op_list_append (info->op_list, op);
|
|
|
|
push_state (info, STATE_INCLUDE);
|
|
}
|
|
else if (ELEMENT_IS ("tile"))
|
|
{
|
|
MetaDrawOp *op;
|
|
const char *name;
|
|
const char *x;
|
|
const char *y;
|
|
const char *width;
|
|
const char *height;
|
|
const char *tile_xoffset;
|
|
const char *tile_yoffset;
|
|
const char *tile_width;
|
|
const char *tile_height;
|
|
MetaDrawOpList *op_list;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"x", &x, "y", &y,
|
|
"width", &width, "height", &height,
|
|
"!name", &name,
|
|
"tile_xoffset", &tile_xoffset,
|
|
"tile_yoffset", &tile_yoffset,
|
|
"!tile_width", &tile_width,
|
|
"!tile_height", &tile_height,
|
|
NULL))
|
|
return;
|
|
|
|
/* These default to 0 */
|
|
#if 0
|
|
if (tile_xoffset && !check_expression (tile_xoffset, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (tile_yoffset && !check_expression (tile_yoffset, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
/* x/y/width/height default to 0,0,width,height - should
|
|
* probably do this for all the draw ops
|
|
*/
|
|
if (x && !check_expression (x, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (y && !check_expression (y, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (width && !check_expression (width, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (height && !check_expression (height, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (tile_width, FALSE, info->theme, context, error))
|
|
return;
|
|
|
|
if (!check_expression (tile_height, FALSE, info->theme, context, error))
|
|
return;
|
|
#endif
|
|
op_list = meta_theme_lookup_draw_op_list (info->theme,
|
|
name);
|
|
if (op_list == NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("No <draw_ops> called \"%s\" has been defined"),
|
|
name);
|
|
return;
|
|
}
|
|
|
|
g_assert (info->op_list);
|
|
|
|
if (op_list == info->op_list ||
|
|
meta_draw_op_list_contains (op_list, info->op_list))
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("Including draw_ops \"%s\" here would create a circular reference"),
|
|
name);
|
|
return;
|
|
}
|
|
|
|
op = meta_draw_op_new (META_DRAW_TILE);
|
|
|
|
meta_draw_op_list_ref (op_list);
|
|
|
|
op->data.tile.x = meta_draw_spec_new (info->theme, x ? x : "0", NULL);
|
|
op->data.tile.y = meta_draw_spec_new (info->theme, y ? y : "0", NULL);
|
|
op->data.tile.width = meta_draw_spec_new (info->theme,
|
|
width ? width : "width",
|
|
NULL);
|
|
op->data.tile.height = meta_draw_spec_new (info->theme,
|
|
height ? height : "height",
|
|
NULL);
|
|
op->data.tile.tile_xoffset = meta_draw_spec_new (info->theme,
|
|
tile_xoffset ? tile_xoffset : "0",
|
|
NULL);
|
|
op->data.tile.tile_yoffset = meta_draw_spec_new (info->theme,
|
|
tile_yoffset ? tile_yoffset : "0",
|
|
NULL);
|
|
op->data.tile.tile_width = meta_draw_spec_new (info->theme, tile_width, NULL);
|
|
op->data.tile.tile_height = meta_draw_spec_new (info->theme, tile_height, NULL);
|
|
|
|
op->data.tile.op_list = op_list;
|
|
|
|
meta_draw_op_list_append (info->op_list, op);
|
|
|
|
push_state (info, STATE_TILE);
|
|
}
|
|
else
|
|
{
|
|
set_error (error, context,
|
|
G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Element <%s> is not allowed below <%s>"),
|
|
element_name, "draw_ops");
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_gradient_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error)
|
|
{
|
|
g_return_if_fail (peek_state (info) == STATE_GRADIENT);
|
|
|
|
if (ELEMENT_IS ("color"))
|
|
{
|
|
const char *value = NULL;
|
|
MetaColorSpec *color_spec;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!value", &value,
|
|
NULL))
|
|
return;
|
|
|
|
color_spec = parse_color (info->theme, value, error);
|
|
if (color_spec == NULL)
|
|
{
|
|
add_context_to_error (error, context);
|
|
return;
|
|
}
|
|
|
|
g_assert (info->op);
|
|
g_assert (info->op->type == META_DRAW_GRADIENT);
|
|
g_assert (info->op->data.gradient.gradient_spec != NULL);
|
|
info->op->data.gradient.gradient_spec->color_specs =
|
|
g_slist_append (info->op->data.gradient.gradient_spec->color_specs,
|
|
color_spec);
|
|
|
|
push_state (info, STATE_COLOR);
|
|
}
|
|
else
|
|
{
|
|
set_error (error, context,
|
|
G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Element <%s> is not allowed below <%s>"),
|
|
element_name, "gradient");
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_style_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error)
|
|
{
|
|
g_return_if_fail (peek_state (info) == STATE_FRAME_STYLE);
|
|
|
|
g_assert (info->style);
|
|
|
|
if (ELEMENT_IS ("piece"))
|
|
{
|
|
const char *position = NULL;
|
|
const char *draw_ops = NULL;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!position", &position,
|
|
"draw_ops", &draw_ops,
|
|
NULL))
|
|
return;
|
|
|
|
info->piece = meta_frame_piece_from_string (position);
|
|
if (info->piece == META_FRAME_PIECE_LAST)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Unknown position \"%s\" for frame piece"),
|
|
position);
|
|
return;
|
|
}
|
|
|
|
if (info->style->pieces[info->piece] != NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Frame style already has a piece at position %s"),
|
|
position);
|
|
return;
|
|
}
|
|
|
|
g_assert (info->op_list == NULL);
|
|
|
|
if (draw_ops)
|
|
{
|
|
MetaDrawOpList *op_list;
|
|
|
|
op_list = meta_theme_lookup_draw_op_list (info->theme,
|
|
draw_ops);
|
|
|
|
if (op_list == NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("No <draw_ops> with the name \"%s\" has been defined"),
|
|
draw_ops);
|
|
return;
|
|
}
|
|
|
|
meta_draw_op_list_ref (op_list);
|
|
info->op_list = op_list;
|
|
}
|
|
|
|
push_state (info, STATE_PIECE);
|
|
}
|
|
else if (ELEMENT_IS ("button"))
|
|
{
|
|
const char *function = NULL;
|
|
const char *state = NULL;
|
|
const char *draw_ops = NULL;
|
|
gint required_version;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!function", &function,
|
|
"!state", &state,
|
|
"draw_ops", &draw_ops,
|
|
NULL))
|
|
return;
|
|
|
|
info->button_type = meta_button_type_from_string (function, info->theme);
|
|
if (info->button_type == META_BUTTON_TYPE_LAST)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Unknown function \"%s\" for button"),
|
|
function);
|
|
return;
|
|
}
|
|
|
|
required_version = peek_required_version (info);
|
|
if (meta_theme_earliest_version_with_button (info->button_type) >
|
|
(guint)required_version)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Button function \"%s\" does not exist in this version (%d, need %d)"),
|
|
function,
|
|
required_version,
|
|
meta_theme_earliest_version_with_button (info->button_type)
|
|
);
|
|
return;
|
|
}
|
|
|
|
info->button_state = meta_button_state_from_string (state);
|
|
if (info->button_state == META_BUTTON_STATE_LAST)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Unknown state \"%s\" for button"),
|
|
state);
|
|
return;
|
|
}
|
|
|
|
if (info->style->buttons[info->button_type][info->button_state] != NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Frame style already has a button for function %s state %s"),
|
|
function, state);
|
|
return;
|
|
}
|
|
|
|
g_assert (info->op_list == NULL);
|
|
|
|
if (draw_ops)
|
|
{
|
|
MetaDrawOpList *op_list;
|
|
|
|
op_list = meta_theme_lookup_draw_op_list (info->theme,
|
|
draw_ops);
|
|
|
|
if (op_list == NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("No <draw_ops> with the name \"%s\" has been defined"),
|
|
draw_ops);
|
|
return;
|
|
}
|
|
|
|
meta_draw_op_list_ref (op_list);
|
|
info->op_list = op_list;
|
|
}
|
|
|
|
push_state (info, STATE_BUTTON);
|
|
}
|
|
else
|
|
{
|
|
set_error (error, context,
|
|
G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Element <%s> is not allowed below <%s>"),
|
|
element_name, "frame_style");
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_style_set_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error)
|
|
{
|
|
g_return_if_fail (peek_state (info) == STATE_FRAME_STYLE_SET);
|
|
|
|
if (ELEMENT_IS ("frame"))
|
|
{
|
|
const char *focus = NULL;
|
|
const char *state = NULL;
|
|
const char *resize = NULL;
|
|
const char *style = NULL;
|
|
MetaFrameFocus frame_focus;
|
|
MetaFrameState frame_state;
|
|
MetaFrameResize frame_resize;
|
|
MetaFrameStyle *frame_style;
|
|
|
|
if (!locate_attributes (context, element_name, attribute_names, attribute_values,
|
|
error,
|
|
"!focus", &focus,
|
|
"!state", &state,
|
|
"resize", &resize,
|
|
"!style", &style,
|
|
NULL))
|
|
return;
|
|
|
|
frame_focus = meta_frame_focus_from_string (focus);
|
|
if (frame_focus == META_FRAME_FOCUS_LAST)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("\"%s\" is not a valid value for focus attribute"),
|
|
focus);
|
|
return;
|
|
}
|
|
|
|
frame_state = meta_frame_state_from_string (state);
|
|
if (frame_state == META_FRAME_STATE_LAST)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("\"%s\" is not a valid value for state attribute"),
|
|
focus);
|
|
return;
|
|
}
|
|
|
|
frame_style = meta_theme_lookup_style (info->theme, style);
|
|
|
|
if (frame_style == NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("A style called \"%s\" has not been defined"),
|
|
style);
|
|
return;
|
|
}
|
|
|
|
switch (frame_state)
|
|
{
|
|
case META_FRAME_STATE_NORMAL:
|
|
if (resize == NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
ATTRIBUTE_NOT_FOUND,
|
|
"resize", element_name);
|
|
return;
|
|
}
|
|
|
|
|
|
frame_resize = meta_frame_resize_from_string (resize);
|
|
if (frame_resize == META_FRAME_RESIZE_LAST)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("\"%s\" is not a valid value for resize attribute"),
|
|
focus);
|
|
return;
|
|
}
|
|
|
|
break;
|
|
|
|
case META_FRAME_STATE_SHADED:
|
|
if (META_THEME_ALLOWS (info->theme, META_THEME_UNRESIZABLE_SHADED_STYLES))
|
|
{
|
|
if (resize == NULL)
|
|
/* In state="normal" we would complain here. But instead we accept
|
|
* not having a resize attribute and default to resize="both", since
|
|
* that most closely mimics what we did in v1, and thus people can
|
|
* upgrade a theme to v2 without as much hassle.
|
|
*/
|
|
frame_resize = META_FRAME_RESIZE_BOTH;
|
|
else
|
|
{
|
|
frame_resize = meta_frame_resize_from_string (resize);
|
|
if (frame_resize == META_FRAME_RESIZE_LAST)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("\"%s\" is not a valid value for resize attribute"),
|
|
focus);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else /* v1 theme */
|
|
{
|
|
if (resize != NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Should not have \"resize\" attribute on <%s> element for maximized/shaded states"),
|
|
element_name);
|
|
return;
|
|
}
|
|
|
|
/* resize="both" is equivalent to the old behaviour */
|
|
frame_resize = META_FRAME_RESIZE_BOTH;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (resize != NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Should not have \"resize\" attribute on <%s> element for maximized states"),
|
|
element_name);
|
|
return;
|
|
}
|
|
|
|
frame_resize = META_FRAME_RESIZE_LAST;
|
|
}
|
|
|
|
switch (frame_state)
|
|
{
|
|
case META_FRAME_STATE_NORMAL:
|
|
if (info->style_set->normal_styles[frame_resize][frame_focus])
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Style has already been specified for state %s resize %s focus %s"),
|
|
state, resize, focus);
|
|
return;
|
|
}
|
|
meta_frame_style_ref (frame_style);
|
|
info->style_set->normal_styles[frame_resize][frame_focus] = frame_style;
|
|
break;
|
|
case META_FRAME_STATE_MAXIMIZED:
|
|
if (info->style_set->maximized_styles[frame_focus])
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Style has already been specified for state %s focus %s"),
|
|
state, focus);
|
|
return;
|
|
}
|
|
meta_frame_style_ref (frame_style);
|
|
info->style_set->maximized_styles[frame_focus] = frame_style;
|
|
break;
|
|
case META_FRAME_STATE_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;
|
|
meta_frame_style_ref (frame_style);
|
|
info->style_set->tiled_right_styles[frame_focus] = frame_style;
|
|
break;
|
|
case META_FRAME_STATE_SHADED:
|
|
if (info->style_set->shaded_styles[frame_resize][frame_focus])
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Style has already been specified for state %s resize %s focus %s"),
|
|
state, resize, focus);
|
|
return;
|
|
}
|
|
meta_frame_style_ref (frame_style);
|
|
info->style_set->shaded_styles[frame_resize][frame_focus] = frame_style;
|
|
break;
|
|
case META_FRAME_STATE_MAXIMIZED_AND_SHADED:
|
|
if (info->style_set->maximized_and_shaded_styles[frame_focus])
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Style has already been specified for state %s focus %s"),
|
|
state, focus);
|
|
return;
|
|
}
|
|
meta_frame_style_ref (frame_style);
|
|
info->style_set->maximized_and_shaded_styles[frame_focus] = frame_style;
|
|
break;
|
|
case META_FRAME_STATE_TILED_LEFT_AND_SHADED:
|
|
if (info->style_set->tiled_left_and_shaded_styles[frame_focus])
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Style has already been specified for state %s focus %s"),
|
|
state, focus);
|
|
return;
|
|
}
|
|
meta_frame_style_ref (frame_style);
|
|
info->style_set->tiled_left_and_shaded_styles[frame_focus] = frame_style;
|
|
break;
|
|
case META_FRAME_STATE_TILED_RIGHT_AND_SHADED:
|
|
if (info->style_set->tiled_right_and_shaded_styles[frame_focus])
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Style has already been specified for state %s focus %s"),
|
|
state, focus);
|
|
return;
|
|
}
|
|
meta_frame_style_ref (frame_style);
|
|
info->style_set->tiled_right_and_shaded_styles[frame_focus] = frame_style;
|
|
break;
|
|
case META_FRAME_STATE_LAST:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
push_state (info, STATE_FRAME);
|
|
}
|
|
else
|
|
{
|
|
set_error (error, context,
|
|
G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Element <%s> is not allowed below <%s>"),
|
|
element_name, "frame_style_set");
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_piece_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error)
|
|
{
|
|
g_return_if_fail (peek_state (info) == STATE_PIECE);
|
|
|
|
if (ELEMENT_IS ("draw_ops"))
|
|
{
|
|
if (info->op_list)
|
|
{
|
|
set_error (error, context,
|
|
G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Can't have a two draw_ops for a <piece> element (theme specified a draw_ops attribute and also a <draw_ops> element, or specified two elements)"));
|
|
return;
|
|
}
|
|
|
|
if (!check_no_attributes (context, element_name, attribute_names, attribute_values,
|
|
error))
|
|
return;
|
|
|
|
g_assert (info->op_list == NULL);
|
|
info->op_list = meta_draw_op_list_new (2);
|
|
|
|
push_state (info, STATE_DRAW_OPS);
|
|
}
|
|
else
|
|
{
|
|
set_error (error, context,
|
|
G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Element <%s> is not allowed below <%s>"),
|
|
element_name, "piece");
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_button_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error)
|
|
{
|
|
g_return_if_fail (peek_state (info) == STATE_BUTTON);
|
|
|
|
if (ELEMENT_IS ("draw_ops"))
|
|
{
|
|
if (info->op_list)
|
|
{
|
|
set_error (error, context,
|
|
G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Can't have a two draw_ops for a <button> element (theme specified a draw_ops attribute and also a <draw_ops> element, or specified two elements)"));
|
|
return;
|
|
}
|
|
|
|
if (!check_no_attributes (context, element_name, attribute_names, attribute_values,
|
|
error))
|
|
return;
|
|
|
|
g_assert (info->op_list == NULL);
|
|
info->op_list = meta_draw_op_list_new (2);
|
|
|
|
push_state (info, STATE_DRAW_OPS);
|
|
}
|
|
else
|
|
{
|
|
set_error (error, context,
|
|
G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Element <%s> is not allowed below <%s>"),
|
|
element_name, "button");
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_menu_icon_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParseInfo *info,
|
|
GError **error)
|
|
{
|
|
g_return_if_fail (peek_state (info) == STATE_MENU_ICON);
|
|
|
|
if (ELEMENT_IS ("draw_ops"))
|
|
{
|
|
if (info->op_list)
|
|
{
|
|
set_error (error, context,
|
|
G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Can't have a two draw_ops for a <menu_icon> element (theme specified a draw_ops attribute and also a <draw_ops> element, or specified two elements)"));
|
|
return;
|
|
}
|
|
|
|
if (!check_no_attributes (context, element_name, attribute_names, attribute_values,
|
|
error))
|
|
return;
|
|
|
|
g_assert (info->op_list == NULL);
|
|
info->op_list = meta_draw_op_list_new (2);
|
|
|
|
push_state (info, STATE_DRAW_OPS);
|
|
}
|
|
else
|
|
{
|
|
set_error (error, context,
|
|
G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Element <%s> is not allowed below <%s>"),
|
|
element_name, "menu_icon");
|
|
}
|
|
}
|
|
|
|
static const char *
|
|
find_version (const char **attribute_names,
|
|
const char **attribute_values)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; attribute_names[i]; i++)
|
|
{
|
|
if (strcmp (attribute_names[i], "version") == 0)
|
|
return attribute_values[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Returns whether the version element was successfully parsed.
|
|
* If successfully parsed, then two additional items are returned:
|
|
*
|
|
* satisfied: whether this version of Mutter meets the version check
|
|
* minimum_required: minimum version of theme format required by version check
|
|
*/
|
|
static gboolean
|
|
check_version (GMarkupParseContext *context,
|
|
const char *version_str,
|
|
gboolean *satisfied,
|
|
guint *minimum_required,
|
|
GError **error)
|
|
{
|
|
static GRegex *version_regex;
|
|
GMatchInfo *info;
|
|
char *comparison_str, *major_str, *minor_str;
|
|
guint version;
|
|
|
|
*minimum_required = 0;
|
|
|
|
if (!version_regex)
|
|
version_regex = g_regex_new ("^\\s*([<>]=?)\\s*(\\d+)(\\.\\d+)?\\s*$", 0, 0, NULL);
|
|
|
|
if (!g_regex_match (version_regex, version_str, 0, &info))
|
|
{
|
|
g_match_info_free (info);
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Bad version specification '%s'"), version_str);
|
|
return FALSE;
|
|
}
|
|
|
|
comparison_str = g_match_info_fetch (info, 1);
|
|
major_str = g_match_info_fetch (info, 2);
|
|
minor_str = g_match_info_fetch (info, 3);
|
|
|
|
version = 1000 * atoi (major_str);
|
|
/* might get NULL, see: https://bugzilla.gnome.org/review?bug=588217 */
|
|
if (minor_str && minor_str[0])
|
|
version += atoi (minor_str + 1);
|
|
|
|
if (comparison_str[0] == '<')
|
|
{
|
|
if (comparison_str[1] == '=')
|
|
*satisfied = THEME_VERSION <= version;
|
|
else
|
|
{
|
|
*satisfied = THEME_VERSION < version;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (comparison_str[1] == '=')
|
|
{
|
|
*satisfied = THEME_VERSION >= version;
|
|
*minimum_required = version;
|
|
}
|
|
else
|
|
{
|
|
*satisfied = THEME_VERSION > version;
|
|
*minimum_required = version + 1;
|
|
}
|
|
}
|
|
|
|
g_free (comparison_str);
|
|
g_free (major_str);
|
|
g_free (minor_str);
|
|
g_match_info_free (info);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
start_element_handler (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
gpointer user_data,
|
|
GError **error)
|
|
{
|
|
ParseInfo *info = user_data;
|
|
const char *version;
|
|
guint required_version = 0;
|
|
|
|
if (info->skip_level > 0)
|
|
{
|
|
info->skip_level++;
|
|
return;
|
|
}
|
|
|
|
required_version = peek_required_version (info);
|
|
|
|
version = find_version (attribute_names, attribute_values);
|
|
if (version != NULL)
|
|
{
|
|
gboolean satisfied;
|
|
guint element_required;
|
|
|
|
if (required_version < 3000)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("\"version\" attribute cannot be used in metacity-theme-1.xml or metacity-theme-2.xml"));
|
|
return;
|
|
}
|
|
|
|
if (!check_version (context, version, &satisfied, &element_required, error))
|
|
return;
|
|
|
|
/* Two different ways of handling an unsatisfied version check:
|
|
* for the toplevel element of a file, we throw an error back so
|
|
* that the controlling code can go ahead and look for an
|
|
* alternate metacity-theme-1.xml or metacity-theme-2.xml; for
|
|
* other elements we just silently skip the element and children.
|
|
*/
|
|
if (peek_state (info) == STATE_START)
|
|
{
|
|
if (satisfied)
|
|
{
|
|
if (element_required > info->format_version)
|
|
info->format_version = element_required;
|
|
}
|
|
else
|
|
{
|
|
set_error (error, context, THEME_PARSE_ERROR, THEME_PARSE_ERROR_TOO_OLD,
|
|
_("Theme requires version %s but latest supported theme version is %d.%d"),
|
|
version, THEME_VERSION, THEME_MINOR_VERSION);
|
|
return;
|
|
}
|
|
}
|
|
else if (!satisfied)
|
|
{
|
|
info->skip_level = 1;
|
|
return;
|
|
}
|
|
|
|
if (element_required > required_version)
|
|
required_version = element_required;
|
|
}
|
|
|
|
push_required_version (info, required_version);
|
|
|
|
switch (peek_state (info))
|
|
{
|
|
case STATE_START:
|
|
if (strcmp (element_name, "metacity_theme") == 0)
|
|
{
|
|
info->theme = meta_theme_new ();
|
|
info->theme->name = g_strdup (info->theme_name);
|
|
info->theme->filename = g_strdup (info->theme_file);
|
|
info->theme->dirname = g_strdup (info->theme_dir);
|
|
info->theme->format_version = info->format_version;
|
|
|
|
push_state (info, STATE_THEME);
|
|
}
|
|
else
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Outermost element in theme must be <metacity_theme> not <%s>"),
|
|
element_name);
|
|
break;
|
|
|
|
case STATE_THEME:
|
|
parse_toplevel_element (context, element_name,
|
|
attribute_names, attribute_values,
|
|
info, error);
|
|
break;
|
|
case STATE_INFO:
|
|
parse_info_element (context, element_name,
|
|
attribute_names, attribute_values,
|
|
info, error);
|
|
break;
|
|
case STATE_NAME:
|
|
case STATE_AUTHOR:
|
|
case STATE_COPYRIGHT:
|
|
case STATE_DATE:
|
|
case STATE_DESCRIPTION:
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Element <%s> is not allowed inside a name/author/date/description element"),
|
|
element_name);
|
|
break;
|
|
case STATE_CONSTANT:
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Element <%s> is not allowed inside a <constant> element"),
|
|
element_name);
|
|
break;
|
|
case STATE_FRAME_GEOMETRY:
|
|
parse_geometry_element (context, element_name,
|
|
attribute_names, attribute_values,
|
|
info, error);
|
|
break;
|
|
case STATE_DISTANCE:
|
|
case STATE_BORDER:
|
|
case STATE_ASPECT_RATIO:
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Element <%s> is not allowed inside a distance/border/aspect_ratio element"),
|
|
element_name);
|
|
break;
|
|
case STATE_DRAW_OPS:
|
|
parse_draw_op_element (context, element_name,
|
|
attribute_names, attribute_values,
|
|
info, error);
|
|
break;
|
|
case STATE_LINE:
|
|
case STATE_RECTANGLE:
|
|
case STATE_ARC:
|
|
case STATE_CLIP:
|
|
case STATE_TINT:
|
|
case STATE_IMAGE:
|
|
case STATE_GTK_ARROW:
|
|
case STATE_GTK_BOX:
|
|
case STATE_GTK_VLINE:
|
|
case STATE_ICON:
|
|
case STATE_TITLE:
|
|
case STATE_INCLUDE:
|
|
case STATE_TILE:
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Element <%s> is not allowed inside a draw operation element"),
|
|
element_name);
|
|
break;
|
|
case STATE_GRADIENT:
|
|
parse_gradient_element (context, element_name,
|
|
attribute_names, attribute_values,
|
|
info, error);
|
|
break;
|
|
case STATE_COLOR:
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Element <%s> is not allowed inside a <%s> element"),
|
|
element_name, "color");
|
|
break;
|
|
case STATE_FRAME_STYLE:
|
|
parse_style_element (context, element_name,
|
|
attribute_names, attribute_values,
|
|
info, error);
|
|
break;
|
|
case STATE_PIECE:
|
|
parse_piece_element (context, element_name,
|
|
attribute_names, attribute_values,
|
|
info, error);
|
|
break;
|
|
case STATE_BUTTON:
|
|
parse_button_element (context, element_name,
|
|
attribute_names, attribute_values,
|
|
info, error);
|
|
break;
|
|
case STATE_MENU_ICON:
|
|
parse_menu_icon_element (context, element_name,
|
|
attribute_names, attribute_values,
|
|
info, error);
|
|
break;
|
|
case STATE_FRAME_STYLE_SET:
|
|
parse_style_set_element (context, element_name,
|
|
attribute_names, attribute_values,
|
|
info, error);
|
|
break;
|
|
case STATE_FRAME:
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Element <%s> is not allowed inside a <%s> element"),
|
|
element_name, "frame");
|
|
break;
|
|
case STATE_WINDOW:
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Element <%s> is not allowed inside a <%s> element"),
|
|
element_name, "window");
|
|
break;
|
|
case STATE_FALLBACK:
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("Element <%s> is not allowed inside a <%s> element"),
|
|
element_name, "fallback");
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
end_element_handler (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
gpointer user_data,
|
|
GError **error)
|
|
{
|
|
ParseInfo *info = user_data;
|
|
|
|
if (info->skip_level > 0)
|
|
{
|
|
info->skip_level--;
|
|
return;
|
|
}
|
|
|
|
switch (peek_state (info))
|
|
{
|
|
case STATE_START:
|
|
break;
|
|
case STATE_THEME:
|
|
g_assert (info->theme);
|
|
|
|
if (!meta_theme_validate (info->theme, error))
|
|
{
|
|
add_context_to_error (error, context);
|
|
meta_theme_free (info->theme);
|
|
info->theme = NULL;
|
|
}
|
|
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_START);
|
|
break;
|
|
case STATE_INFO:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_THEME);
|
|
break;
|
|
case STATE_NAME:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_INFO);
|
|
break;
|
|
case STATE_AUTHOR:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_INFO);
|
|
break;
|
|
case STATE_COPYRIGHT:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_INFO);
|
|
break;
|
|
case STATE_DATE:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_INFO);
|
|
break;
|
|
case STATE_DESCRIPTION:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_INFO);
|
|
break;
|
|
case STATE_CONSTANT:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_THEME);
|
|
break;
|
|
case STATE_FRAME_GEOMETRY:
|
|
g_assert (info->layout);
|
|
|
|
if (!meta_frame_layout_validate (info->layout,
|
|
error))
|
|
{
|
|
add_context_to_error (error, context);
|
|
}
|
|
|
|
/* layout will already be stored in the theme under
|
|
* its name
|
|
*/
|
|
meta_frame_layout_unref (info->layout);
|
|
info->layout = NULL;
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_THEME);
|
|
break;
|
|
case STATE_DISTANCE:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_FRAME_GEOMETRY);
|
|
break;
|
|
case STATE_BORDER:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_FRAME_GEOMETRY);
|
|
break;
|
|
case STATE_ASPECT_RATIO:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_FRAME_GEOMETRY);
|
|
break;
|
|
case STATE_DRAW_OPS:
|
|
{
|
|
g_assert (info->op_list);
|
|
|
|
if (!meta_draw_op_list_validate (info->op_list,
|
|
error))
|
|
{
|
|
add_context_to_error (error, context);
|
|
meta_draw_op_list_unref (info->op_list);
|
|
info->op_list = NULL;
|
|
}
|
|
|
|
pop_state (info);
|
|
|
|
switch (peek_state (info))
|
|
{
|
|
case STATE_BUTTON:
|
|
case STATE_PIECE:
|
|
case STATE_MENU_ICON:
|
|
/* Leave info->op_list to be picked up
|
|
* when these elements are closed
|
|
*/
|
|
g_assert (info->op_list);
|
|
break;
|
|
case STATE_THEME:
|
|
g_assert (info->op_list);
|
|
meta_draw_op_list_unref (info->op_list);
|
|
info->op_list = NULL;
|
|
break;
|
|
default:
|
|
/* Op list can't occur in other contexts */
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case STATE_LINE:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_DRAW_OPS);
|
|
break;
|
|
case STATE_RECTANGLE:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_DRAW_OPS);
|
|
break;
|
|
case STATE_ARC:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_DRAW_OPS);
|
|
break;
|
|
case STATE_CLIP:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_DRAW_OPS);
|
|
break;
|
|
case STATE_TINT:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_DRAW_OPS);
|
|
break;
|
|
case STATE_GRADIENT:
|
|
g_assert (info->op);
|
|
g_assert (info->op->type == META_DRAW_GRADIENT);
|
|
if (!meta_gradient_spec_validate (info->op->data.gradient.gradient_spec,
|
|
error))
|
|
{
|
|
add_context_to_error (error, context);
|
|
meta_draw_op_free (info->op);
|
|
info->op = NULL;
|
|
}
|
|
else
|
|
{
|
|
g_assert (info->op_list);
|
|
meta_draw_op_list_append (info->op_list, info->op);
|
|
info->op = NULL;
|
|
}
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_DRAW_OPS);
|
|
break;
|
|
case STATE_IMAGE:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_DRAW_OPS);
|
|
break;
|
|
case STATE_GTK_ARROW:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_DRAW_OPS);
|
|
break;
|
|
case STATE_GTK_BOX:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_DRAW_OPS);
|
|
break;
|
|
case STATE_GTK_VLINE:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_DRAW_OPS);
|
|
break;
|
|
case STATE_ICON:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_DRAW_OPS);
|
|
break;
|
|
case STATE_TITLE:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_DRAW_OPS);
|
|
break;
|
|
case STATE_INCLUDE:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_DRAW_OPS);
|
|
break;
|
|
case STATE_TILE:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_DRAW_OPS);
|
|
break;
|
|
case STATE_COLOR:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_GRADIENT);
|
|
break;
|
|
case STATE_FRAME_STYLE:
|
|
g_assert (info->style);
|
|
|
|
if (!meta_frame_style_validate (info->style,
|
|
peek_required_version (info),
|
|
error))
|
|
{
|
|
add_context_to_error (error, context);
|
|
}
|
|
|
|
/* Frame style is in the theme hash table and a ref
|
|
* is held there
|
|
*/
|
|
meta_frame_style_unref (info->style);
|
|
info->style = NULL;
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_THEME);
|
|
break;
|
|
case STATE_PIECE:
|
|
g_assert (info->style);
|
|
if (info->op_list == NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("No draw_ops provided for frame piece"));
|
|
}
|
|
else
|
|
{
|
|
info->style->pieces[info->piece] = info->op_list;
|
|
info->op_list = NULL;
|
|
}
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_FRAME_STYLE);
|
|
break;
|
|
case STATE_BUTTON:
|
|
g_assert (info->style);
|
|
if (info->op_list == NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
|
|
_("No draw_ops provided for button"));
|
|
}
|
|
else
|
|
{
|
|
info->style->buttons[info->button_type][info->button_state] =
|
|
info->op_list;
|
|
info->op_list = NULL;
|
|
}
|
|
pop_state (info);
|
|
break;
|
|
case STATE_MENU_ICON:
|
|
g_assert (info->theme);
|
|
if (info->op_list != NULL)
|
|
{
|
|
meta_draw_op_list_unref (info->op_list);
|
|
info->op_list = NULL;
|
|
}
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_THEME);
|
|
break;
|
|
case STATE_FRAME_STYLE_SET:
|
|
g_assert (info->style_set);
|
|
|
|
if (!meta_frame_style_set_validate (info->style_set,
|
|
error))
|
|
{
|
|
add_context_to_error (error, context);
|
|
}
|
|
|
|
/* Style set is in the theme hash table and a reference
|
|
* is held there.
|
|
*/
|
|
meta_frame_style_set_unref (info->style_set);
|
|
info->style_set = NULL;
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_THEME);
|
|
break;
|
|
case STATE_FRAME:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_FRAME_STYLE_SET);
|
|
break;
|
|
case STATE_WINDOW:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_THEME);
|
|
break;
|
|
case STATE_FALLBACK:
|
|
pop_state (info);
|
|
g_assert (peek_state (info) == STATE_THEME);
|
|
break;
|
|
}
|
|
|
|
pop_required_version (info);
|
|
}
|
|
|
|
#define NO_TEXT(element_name) set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, _("No text is allowed inside element <%s>"), element_name)
|
|
|
|
static gboolean
|
|
all_whitespace (const char *text,
|
|
int text_len)
|
|
{
|
|
const char *p;
|
|
const char *end;
|
|
|
|
p = text;
|
|
end = text + text_len;
|
|
|
|
while (p != end)
|
|
{
|
|
if (!g_ascii_isspace (*p))
|
|
return FALSE;
|
|
|
|
p = g_utf8_next_char (p);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
text_handler (GMarkupParseContext *context,
|
|
const gchar *text,
|
|
gsize text_len,
|
|
gpointer user_data,
|
|
GError **error)
|
|
{
|
|
ParseInfo *info = user_data;
|
|
|
|
if (info->skip_level > 0)
|
|
return;
|
|
|
|
if (all_whitespace (text, text_len))
|
|
return;
|
|
|
|
/* FIXME http://bugzilla.gnome.org/show_bug.cgi?id=70448 would
|
|
* allow a nice cleanup here.
|
|
*/
|
|
|
|
switch (peek_state (info))
|
|
{
|
|
case STATE_START:
|
|
g_assert_not_reached (); /* gmarkup shouldn't do this */
|
|
break;
|
|
case STATE_THEME:
|
|
NO_TEXT ("metacity_theme");
|
|
break;
|
|
case STATE_INFO:
|
|
NO_TEXT ("info");
|
|
break;
|
|
case STATE_NAME:
|
|
if (info->theme->readable_name != NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("<%s> specified twice for this theme"),
|
|
"name");
|
|
return;
|
|
}
|
|
|
|
info->theme->readable_name = g_strndup (text, text_len);
|
|
break;
|
|
case STATE_AUTHOR:
|
|
if (info->theme->author != NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("<%s> specified twice for this theme"),
|
|
"author");
|
|
return;
|
|
}
|
|
|
|
info->theme->author = g_strndup (text, text_len);
|
|
break;
|
|
case STATE_COPYRIGHT:
|
|
if (info->theme->copyright != NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("<%s> specified twice for this theme"),
|
|
"copyright");
|
|
return;
|
|
}
|
|
|
|
info->theme->copyright = g_strndup (text, text_len);
|
|
break;
|
|
case STATE_DATE:
|
|
if (info->theme->date != NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("<%s> specified twice for this theme"),
|
|
"date");
|
|
return;
|
|
}
|
|
|
|
info->theme->date = g_strndup (text, text_len);
|
|
break;
|
|
case STATE_DESCRIPTION:
|
|
if (info->theme->description != NULL)
|
|
{
|
|
set_error (error, context, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_PARSE,
|
|
_("<%s> specified twice for this theme"),
|
|
"description");
|
|
return;
|
|
}
|
|
|
|
info->theme->description = g_strndup (text, text_len);
|
|
break;
|
|
case STATE_CONSTANT:
|
|
NO_TEXT ("constant");
|
|
break;
|
|
case STATE_FRAME_GEOMETRY:
|
|
NO_TEXT ("frame_geometry");
|
|
break;
|
|
case STATE_DISTANCE:
|
|
NO_TEXT ("distance");
|
|
break;
|
|
case STATE_BORDER:
|
|
NO_TEXT ("border");
|
|
break;
|
|
case STATE_ASPECT_RATIO:
|
|
NO_TEXT ("aspect_ratio");
|
|
break;
|
|
case STATE_DRAW_OPS:
|
|
NO_TEXT ("draw_ops");
|
|
break;
|
|
case STATE_LINE:
|
|
NO_TEXT ("line");
|
|
break;
|
|
case STATE_RECTANGLE:
|
|
NO_TEXT ("rectangle");
|
|
break;
|
|
case STATE_ARC:
|
|
NO_TEXT ("arc");
|
|
break;
|
|
case STATE_CLIP:
|
|
NO_TEXT ("clip");
|
|
break;
|
|
case STATE_TINT:
|
|
NO_TEXT ("tint");
|
|
break;
|
|
case STATE_GRADIENT:
|
|
NO_TEXT ("gradient");
|
|
break;
|
|
case STATE_IMAGE:
|
|
NO_TEXT ("image");
|
|
break;
|
|
case STATE_GTK_ARROW:
|
|
NO_TEXT ("gtk_arrow");
|
|
break;
|
|
case STATE_GTK_BOX:
|
|
NO_TEXT ("gtk_box");
|
|
break;
|
|
case STATE_GTK_VLINE:
|
|
NO_TEXT ("gtk_vline");
|
|
break;
|
|
case STATE_ICON:
|
|
NO_TEXT ("icon");
|
|
break;
|
|
case STATE_TITLE:
|
|
NO_TEXT ("title");
|
|
break;
|
|
case STATE_INCLUDE:
|
|
NO_TEXT ("include");
|
|
break;
|
|
case STATE_TILE:
|
|
NO_TEXT ("tile");
|
|
break;
|
|
case STATE_COLOR:
|
|
NO_TEXT ("color");
|
|
break;
|
|
case STATE_FRAME_STYLE:
|
|
NO_TEXT ("frame_style");
|
|
break;
|
|
case STATE_PIECE:
|
|
NO_TEXT ("piece");
|
|
break;
|
|
case STATE_BUTTON:
|
|
NO_TEXT ("button");
|
|
break;
|
|
case STATE_MENU_ICON:
|
|
NO_TEXT ("menu_icon");
|
|
break;
|
|
case STATE_FRAME_STYLE_SET:
|
|
NO_TEXT ("frame_style_set");
|
|
break;
|
|
case STATE_FRAME:
|
|
NO_TEXT ("frame");
|
|
break;
|
|
case STATE_WINDOW:
|
|
NO_TEXT ("window");
|
|
break;
|
|
case STATE_FALLBACK:
|
|
NO_TEXT ("fallback");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If the theme is not-corrupt, keep looking for alternate versions
|
|
* in other locations we might be compatible with
|
|
*/
|
|
static gboolean
|
|
theme_error_is_fatal (GError *error)
|
|
{
|
|
return !(error->domain == G_FILE_ERROR ||
|
|
(error->domain == THEME_PARSE_ERROR &&
|
|
error->code == THEME_PARSE_ERROR_TOO_OLD));
|
|
}
|
|
|
|
static MetaTheme *
|
|
load_theme (const char *theme_dir,
|
|
const char *theme_name,
|
|
guint major_version,
|
|
GError **error)
|
|
{
|
|
GMarkupParseContext *context;
|
|
ParseInfo info;
|
|
char *text;
|
|
gsize length;
|
|
char *theme_filename;
|
|
char *theme_file;
|
|
MetaTheme *retval;
|
|
|
|
g_return_val_if_fail (error && *error == NULL, NULL);
|
|
|
|
text = NULL;
|
|
retval = NULL;
|
|
context = NULL;
|
|
|
|
theme_filename = g_strdup_printf (METACITY_THEME_FILENAME_FORMAT, major_version);
|
|
theme_file = g_build_filename (theme_dir, theme_filename, NULL);
|
|
|
|
if (!g_file_get_contents (theme_file,
|
|
&text,
|
|
&length,
|
|
error))
|
|
goto out;
|
|
|
|
meta_topic (META_DEBUG_THEMES, "Parsing theme file %s\n", theme_file);
|
|
|
|
parse_info_init (&info);
|
|
|
|
info.theme_name = theme_name;
|
|
info.theme_file = theme_file;
|
|
info.theme_dir = theme_dir;
|
|
|
|
info.format_version = 1000 * major_version;
|
|
|
|
context = g_markup_parse_context_new (&metacity_theme_parser,
|
|
0, &info, NULL);
|
|
|
|
if (!g_markup_parse_context_parse (context,
|
|
text,
|
|
length,
|
|
error))
|
|
goto out;
|
|
|
|
if (!g_markup_parse_context_end_parse (context, error))
|
|
goto out;
|
|
|
|
retval = info.theme;
|
|
info.theme = NULL;
|
|
|
|
out:
|
|
if (*error && !theme_error_is_fatal (*error))
|
|
{
|
|
meta_topic (META_DEBUG_THEMES, "Failed to read theme from file %s: %s\n",
|
|
theme_file, (*error)->message);
|
|
}
|
|
|
|
g_free (theme_filename);
|
|
g_free (theme_file);
|
|
g_free (text);
|
|
|
|
if (context)
|
|
{
|
|
g_markup_parse_context_free (context);
|
|
parse_info_free (&info);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static gboolean
|
|
keep_trying (GError **error)
|
|
{
|
|
if (*error && !theme_error_is_fatal (*error))
|
|
{
|
|
g_clear_error (error);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* meta_theme_load: (skip)
|
|
*
|
|
*/
|
|
MetaTheme*
|
|
meta_theme_load (const char *theme_name,
|
|
GError **err)
|
|
{
|
|
GError *error = NULL;
|
|
char *theme_dir;
|
|
MetaTheme *retval;
|
|
const gchar* const* xdg_data_dirs;
|
|
int major_version;
|
|
int i;
|
|
|
|
retval = NULL;
|
|
|
|
if (meta_is_debugging ())
|
|
{
|
|
/* Try in themes in our source tree */
|
|
/* We try all supported major versions from current to oldest */
|
|
for (major_version = THEME_MAJOR_VERSION; (major_version > 0); major_version--)
|
|
{
|
|
theme_dir = g_build_filename ("./themes", theme_name, NULL);
|
|
retval = load_theme (theme_dir, theme_name, major_version, &error);
|
|
g_free (theme_dir);
|
|
if (!keep_trying (&error))
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* We try all supported major versions from current to oldest */
|
|
for (major_version = THEME_MAJOR_VERSION; (major_version > 0); major_version--)
|
|
{
|
|
/* We try first in home dir, XDG_DATA_DIRS, then system dir for themes */
|
|
|
|
/* Try home dir for themes */
|
|
theme_dir = g_build_filename (g_get_home_dir (),
|
|
".themes",
|
|
theme_name,
|
|
THEME_SUBDIR,
|
|
NULL);
|
|
|
|
retval = load_theme (theme_dir, theme_name, major_version, &error);
|
|
g_free (theme_dir);
|
|
if (!keep_trying (&error))
|
|
goto out;
|
|
|
|
/* Try each XDG_DATA_DIRS for theme */
|
|
xdg_data_dirs = g_get_system_data_dirs();
|
|
for(i = 0; xdg_data_dirs[i] != NULL; i++)
|
|
{
|
|
theme_dir = g_build_filename (xdg_data_dirs[i],
|
|
"themes",
|
|
theme_name,
|
|
THEME_SUBDIR,
|
|
NULL);
|
|
|
|
retval = load_theme (theme_dir, theme_name, major_version, &error);
|
|
g_free (theme_dir);
|
|
if (!keep_trying (&error))
|
|
goto out;
|
|
}
|
|
|
|
/* Look for themes in MUTTER_DATADIR */
|
|
theme_dir = g_build_filename (MUTTER_DATADIR,
|
|
"themes",
|
|
theme_name,
|
|
THEME_SUBDIR,
|
|
NULL);
|
|
|
|
retval = load_theme (theme_dir, theme_name, major_version, &error);
|
|
g_free (theme_dir);
|
|
if (!keep_trying (&error))
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
if (!error && !retval)
|
|
g_set_error (&error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
|
|
_("Failed to find a valid file for theme %s\n"),
|
|
theme_name);
|
|
|
|
if (error)
|
|
{
|
|
g_propagate_error (err, error);
|
|
}
|
|
|
|
return retval;
|
|
}
|