/* GTK - The GIMP Toolkit * Copyright (C) 2010 Carlos Garnacho * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "config.h" #include "gtkcssstylefuncsprivate.h" #include #include #include #include #include #include #include "gtkcssimagegradientprivate.h" #include "gtkcssprovider.h" #include "gtkcsstypedvalueprivate.h" #include "gtkcsstypesprivate.h" #include "gtkgradient.h" #include "gtkprivatetypebuiltins.h" #include "gtkstylecontextprivate.h" #include "gtksymboliccolorprivate.h" #include "gtkthemingengine.h" #include "gtktypebuiltins.h" #include "gtkwin32themeprivate.h" /* this is in case round() is not provided by the compiler, * such as in the case of C89 compilers, like MSVC */ #include "fallback-c89.c" static GHashTable *parse_funcs = NULL; static GHashTable *print_funcs = NULL; static GHashTable *compute_funcs = NULL; typedef gboolean (* GtkStyleParseFunc) (GtkCssParser *parser, GValue *value); typedef void (* GtkStylePrintFunc) (const GValue *value, GString *string); typedef GtkCssValue * (* GtkStyleComputeFunc) (GtkStyleContext *context, GtkCssValue *specified); static void register_conversion_function (GType type, GtkStyleParseFunc parse, GtkStylePrintFunc print, GtkStyleComputeFunc compute) { if (parse) g_hash_table_insert (parse_funcs, GSIZE_TO_POINTER (type), parse); if (print) g_hash_table_insert (print_funcs, GSIZE_TO_POINTER (type), print); if (compute) g_hash_table_insert (compute_funcs, GSIZE_TO_POINTER (type), compute); } static void string_append_double (GString *string, double d) { char buf[G_ASCII_DTOSTR_BUF_SIZE]; g_ascii_dtostr (buf, sizeof (buf), d); g_string_append (string, buf); } static void string_append_string (GString *str, const char *string) { gsize len; g_string_append_c (str, '"'); do { len = strcspn (string, "\"\n\r\f"); g_string_append (str, string); string += len; switch (*string) { case '\0': break; case '\n': g_string_append (str, "\\A "); break; case '\r': g_string_append (str, "\\D "); break; case '\f': g_string_append (str, "\\C "); break; case '\"': g_string_append (str, "\\\""); break; default: g_assert_not_reached (); break; } } while (*string); g_string_append_c (str, '"'); } /*** IMPLEMENTATIONS ***/ static gboolean enum_parse (GtkCssParser *parser, GType type, int *res) { char *str; if (_gtk_css_parser_try_enum (parser, type, res)) return TRUE; str = _gtk_css_parser_try_ident (parser, TRUE); if (str == NULL) { _gtk_css_parser_error (parser, "Expected an identifier"); return FALSE; } _gtk_css_parser_error (parser, "Unknown value '%s' for enum type '%s'", str, g_type_name (type)); g_free (str); return FALSE; } static void enum_print (int value, GType type, GString *string) { GEnumClass *enum_class; GEnumValue *enum_value; enum_class = g_type_class_ref (type); enum_value = g_enum_get_value (enum_class, value); g_string_append (string, enum_value->value_nick); g_type_class_unref (enum_class); } static gboolean rgba_value_parse (GtkCssParser *parser, GValue *value) { GtkSymbolicColor *symbolic; GdkRGBA rgba; symbolic = _gtk_symbolic_color_new_take_value (_gtk_css_symbolic_value_new (parser)); if (symbolic == NULL) return FALSE; if (gtk_symbolic_color_resolve (symbolic, NULL, &rgba)) { g_value_set_boxed (value, &rgba); gtk_symbolic_color_unref (symbolic); } else { g_value_unset (value); g_value_init (value, GTK_TYPE_SYMBOLIC_COLOR); g_value_take_boxed (value, symbolic); } return TRUE; } static void rgba_value_print (const GValue *value, GString *string) { const GdkRGBA *rgba = g_value_get_boxed (value); if (rgba == NULL) g_string_append (string, "none"); else { char *s = gdk_rgba_to_string (rgba); g_string_append (string, s); g_free (s); } } static GtkCssValue * rgba_value_compute (GtkStyleContext *context, GtkCssValue *specified) { GdkRGBA white = { 1, 1, 1, 1 }; const GValue *value; value = _gtk_css_typed_value_get (specified); if (G_VALUE_HOLDS (value, GTK_TYPE_SYMBOLIC_COLOR)) { GtkSymbolicColor *symbolic = g_value_get_boxed (value); GValue new_value = G_VALUE_INIT; GdkRGBA rgba; if (!_gtk_style_context_resolve_color (context, symbolic, &rgba)) rgba = white; g_value_init (&new_value, GDK_TYPE_RGBA); g_value_set_boxed (&new_value, &rgba); return _gtk_css_typed_value_new_take (&new_value); } else return _gtk_css_value_ref (specified); } static gboolean color_value_parse (GtkCssParser *parser, GValue *value) { GtkSymbolicColor *symbolic; GdkRGBA rgba; symbolic = _gtk_symbolic_color_new_take_value (_gtk_css_symbolic_value_new (parser)); if (symbolic == NULL) return FALSE; if (gtk_symbolic_color_resolve (symbolic, NULL, &rgba)) { GdkColor color; color.red = rgba.red * 65535. + 0.5; color.green = rgba.green * 65535. + 0.5; color.blue = rgba.blue * 65535. + 0.5; g_value_set_boxed (value, &color); } else { g_value_unset (value); g_value_init (value, GTK_TYPE_SYMBOLIC_COLOR); g_value_take_boxed (value, symbolic); } return TRUE; } static void color_value_print (const GValue *value, GString *string) { const GdkColor *color = g_value_get_boxed (value); if (color == NULL) g_string_append (string, "none"); else { char *s = gdk_color_to_string (color); g_string_append (string, s); g_free (s); } } static GtkCssValue * color_value_compute (GtkStyleContext *context, GtkCssValue *specified) { GdkRGBA rgba; GdkColor color = { 0, 65535, 65535, 65535 }; const GValue *value; value = _gtk_css_typed_value_get (specified); if (G_VALUE_HOLDS (value, GTK_TYPE_SYMBOLIC_COLOR)) { GValue new_value = G_VALUE_INIT; if (_gtk_style_context_resolve_color (context, g_value_get_boxed (value), &rgba)) { color.red = rgba.red * 65535. + 0.5; color.green = rgba.green * 65535. + 0.5; color.blue = rgba.blue * 65535. + 0.5; } g_value_init (&new_value, GDK_TYPE_COLOR); g_value_set_boxed (&new_value, &color); return _gtk_css_typed_value_new_take (&new_value); } else return _gtk_css_value_ref (specified); } static gboolean symbolic_color_value_parse (GtkCssParser *parser, GValue *value) { GtkSymbolicColor *symbolic; symbolic = _gtk_symbolic_color_new_take_value (_gtk_css_symbolic_value_new (parser)); if (symbolic == NULL) return FALSE; g_value_take_boxed (value, symbolic); return TRUE; } static void symbolic_color_value_print (const GValue *value, GString *string) { GtkSymbolicColor *symbolic = g_value_get_boxed (value); if (symbolic == NULL) g_string_append (string, "none"); else { char *s = gtk_symbolic_color_to_string (symbolic); g_string_append (string, s); g_free (s); } } static gboolean font_description_value_parse (GtkCssParser *parser, GValue *value) { PangoFontDescription *font_desc; guint mask; char *str; str = _gtk_css_parser_read_value (parser); if (str == NULL) return FALSE; font_desc = pango_font_description_from_string (str); mask = pango_font_description_get_set_fields (font_desc); /* These values are not really correct, * but the fields must be set, so we set them to something */ if ((mask & PANGO_FONT_MASK_FAMILY) == 0) pango_font_description_set_family_static (font_desc, "Sans"); if ((mask & PANGO_FONT_MASK_SIZE) == 0) pango_font_description_set_size (font_desc, 10 * PANGO_SCALE); g_free (str); g_value_take_boxed (value, font_desc); return TRUE; } static void font_description_value_print (const GValue *value, GString *string) { const PangoFontDescription *desc = g_value_get_boxed (value); if (desc == NULL) g_string_append (string, "none"); else { char *s = pango_font_description_to_string (desc); g_string_append (string, s); g_free (s); } } static gboolean boolean_value_parse (GtkCssParser *parser, GValue *value) { if (_gtk_css_parser_try (parser, "true", TRUE) || _gtk_css_parser_try (parser, "1", TRUE)) { g_value_set_boolean (value, TRUE); return TRUE; } else if (_gtk_css_parser_try (parser, "false", TRUE) || _gtk_css_parser_try (parser, "0", TRUE)) { g_value_set_boolean (value, FALSE); return TRUE; } else { _gtk_css_parser_error (parser, "Expected a boolean value"); return FALSE; } } static void boolean_value_print (const GValue *value, GString *string) { if (g_value_get_boolean (value)) g_string_append (string, "true"); else g_string_append (string, "false"); } static gboolean int_value_parse (GtkCssParser *parser, GValue *value) { gint i; if (_gtk_css_parser_begins_with (parser, '-')) { int res = _gtk_win32_theme_int_parse (parser, &i); if (res >= 0) { g_value_set_int (value, i); return res > 0; } /* < 0 => continue */ } if (!_gtk_css_parser_try_int (parser, &i)) { _gtk_css_parser_error (parser, "Expected a valid integer value"); return FALSE; } g_value_set_int (value, i); return TRUE; } static void int_value_print (const GValue *value, GString *string) { g_string_append_printf (string, "%d", g_value_get_int (value)); } static gboolean uint_value_parse (GtkCssParser *parser, GValue *value) { guint u; if (!_gtk_css_parser_try_uint (parser, &u)) { _gtk_css_parser_error (parser, "Expected a valid unsigned value"); return FALSE; } g_value_set_uint (value, u); return TRUE; } static void uint_value_print (const GValue *value, GString *string) { g_string_append_printf (string, "%u", g_value_get_uint (value)); } static gboolean double_value_parse (GtkCssParser *parser, GValue *value) { gdouble d; if (!_gtk_css_parser_try_double (parser, &d)) { _gtk_css_parser_error (parser, "Expected a number"); return FALSE; } g_value_set_double (value, d); return TRUE; } static void double_value_print (const GValue *value, GString *string) { string_append_double (string, g_value_get_double (value)); } static gboolean float_value_parse (GtkCssParser *parser, GValue *value) { gdouble d; if (!_gtk_css_parser_try_double (parser, &d)) { _gtk_css_parser_error (parser, "Expected a number"); return FALSE; } g_value_set_float (value, d); return TRUE; } static void float_value_print (const GValue *value, GString *string) { string_append_double (string, g_value_get_float (value)); } static gboolean string_value_parse (GtkCssParser *parser, GValue *value) { char *str = _gtk_css_parser_read_string (parser); if (str == NULL) return FALSE; g_value_take_string (value, str); return TRUE; } static void string_value_print (const GValue *value, GString *str) { string_append_string (str, g_value_get_string (value)); } static gboolean theming_engine_value_parse (GtkCssParser *parser, GValue *value) { GtkThemingEngine *engine; char *str; if (_gtk_css_parser_try (parser, "none", TRUE)) { g_value_set_object (value, gtk_theming_engine_load (NULL)); return TRUE; } str = _gtk_css_parser_try_ident (parser, TRUE); if (str == NULL) { _gtk_css_parser_error (parser, "Expected a valid theme name"); return FALSE; } engine = gtk_theming_engine_load (str); if (engine == NULL) { _gtk_css_parser_error (parser, "Theming engine '%s' not found", str); g_free (str); return FALSE; } g_value_set_object (value, engine); g_free (str); return TRUE; } static void theming_engine_value_print (const GValue *value, GString *string) { GtkThemingEngine *engine; char *name; engine = g_value_get_object (value); if (engine == NULL) g_string_append (string, "none"); else { /* XXX: gtk_theming_engine_get_name()? */ g_object_get (engine, "name", &name, NULL); g_string_append (string, name ? name : "none"); g_free (name); } } static gboolean border_value_parse (GtkCssParser *parser, GValue *value) { GtkBorder border = { 0, }; guint i; int numbers[4]; for (i = 0; i < G_N_ELEMENTS (numbers); i++) { if (_gtk_css_parser_begins_with (parser, '-')) { /* These are strictly speaking signed, but we want to be able to use them for unsigned types too, as the actual ranges of values make this safe */ int res = _gtk_win32_theme_int_parse (parser, &numbers[i]); if (res == 0) /* Parse error, report */ return FALSE; if (res < 0) /* Nothing known to expand */ break; } else { if (!_gtk_css_parser_try_length (parser, &numbers[i])) break; } } if (i == 0) { _gtk_css_parser_error (parser, "Expected valid border"); return FALSE; } border.top = numbers[0]; if (i > 1) border.right = numbers[1]; else border.right = border.top; if (i > 2) border.bottom = numbers[2]; else border.bottom = border.top; if (i > 3) border.left = numbers[3]; else border.left = border.right; g_value_set_boxed (value, &border); return TRUE; } static void border_value_print (const GValue *value, GString *string) { const GtkBorder *border = g_value_get_boxed (value); if (border == NULL) g_string_append (string, "none"); else if (border->left != border->right) g_string_append_printf (string, "%d %d %d %d", border->top, border->right, border->bottom, border->left); else if (border->top != border->bottom) g_string_append_printf (string, "%d %d %d", border->top, border->right, border->bottom); else if (border->top != border->left) g_string_append_printf (string, "%d %d", border->top, border->right); else g_string_append_printf (string, "%d", border->top); } static gboolean gradient_value_parse (GtkCssParser *parser, GValue *value) { GtkGradient *gradient; gradient = _gtk_gradient_parse (parser); if (gradient == NULL) return FALSE; g_value_take_boxed (value, gradient); return TRUE; } static void gradient_value_print (const GValue *value, GString *string) { GtkGradient *gradient = g_value_get_boxed (value); if (gradient == NULL) g_string_append (string, "none"); else { char *s = gtk_gradient_to_string (gradient); g_string_append (string, s); g_free (s); } } static gboolean pattern_value_parse (GtkCssParser *parser, GValue *value) { if (_gtk_css_parser_try (parser, "none", TRUE)) { /* nothing to do here */ } else if (_gtk_css_parser_begins_with (parser, '-')) { g_value_unset (value); g_value_init (value, GTK_TYPE_GRADIENT); return gradient_value_parse (parser, value); } else { GError *error = NULL; gchar *path; GdkPixbuf *pixbuf; GFile *file; cairo_surface_t *surface; cairo_pattern_t *pattern; cairo_t *cr; cairo_matrix_t matrix; file = _gtk_css_parser_read_url (parser); if (file == NULL) return FALSE; path = g_file_get_path (file); g_object_unref (file); pixbuf = gdk_pixbuf_new_from_file (path, &error); g_free (path); if (pixbuf == NULL) { _gtk_css_parser_take_error (parser, error); return FALSE; } surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf)); cr = cairo_create (surface); gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); cairo_paint (cr); pattern = cairo_pattern_create_for_surface (surface); cairo_matrix_init_scale (&matrix, gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf)); cairo_pattern_set_matrix (pattern, &matrix); cairo_surface_destroy (surface); cairo_destroy (cr); g_object_unref (pixbuf); g_value_take_boxed (value, pattern); } return TRUE; } static cairo_status_t surface_write (void *closure, const unsigned char *data, unsigned int length) { g_byte_array_append (closure, data, length); return CAIRO_STATUS_SUCCESS; } static void surface_print (cairo_surface_t *surface, GString * string) { #if CAIRO_HAS_PNG_FUNCTIONS GByteArray *array; char *base64; array = g_byte_array_new (); cairo_surface_write_to_png_stream (surface, surface_write, array); base64 = g_base64_encode (array->data, array->len); g_byte_array_free (array, TRUE); g_string_append (string, "url(\"data:image/png;base64,"); g_string_append (string, base64); g_string_append (string, "\")"); g_free (base64); #else g_string_append (string, "none /* you need cairo png functions enabled to make this work */"); #endif } static void pattern_value_print (const GValue *value, GString *string) { cairo_pattern_t *pattern; cairo_surface_t *surface; pattern = g_value_get_boxed (value); if (pattern == NULL) { g_string_append (string, "none"); return; } switch (cairo_pattern_get_type (pattern)) { case CAIRO_PATTERN_TYPE_SURFACE: if (cairo_pattern_get_surface (pattern, &surface) != CAIRO_STATUS_SUCCESS) { g_assert_not_reached (); } surface_print (surface, string); break; case CAIRO_PATTERN_TYPE_SOLID: case CAIRO_PATTERN_TYPE_LINEAR: case CAIRO_PATTERN_TYPE_RADIAL: default: g_assert_not_reached (); break; } } static GtkCssValue * pattern_value_compute (GtkStyleContext *context, GtkCssValue *specified) { const GValue *value = _gtk_css_typed_value_get (specified); if (G_VALUE_HOLDS (value, GTK_TYPE_GRADIENT)) { GValue new_value = G_VALUE_INIT; cairo_pattern_t *gradient; gradient = gtk_gradient_resolve_for_context (g_value_get_boxed (value), context); g_value_init (&new_value, CAIRO_GOBJECT_TYPE_PATTERN); g_value_take_boxed (&new_value, gradient); return _gtk_css_typed_value_new_take (&new_value); } else return _gtk_css_value_ref (specified); } static gboolean enum_value_parse (GtkCssParser *parser, GValue *value) { int v; if (enum_parse (parser, G_VALUE_TYPE (value), &v)) { g_value_set_enum (value, v); return TRUE; } return FALSE; } static void enum_value_print (const GValue *value, GString *string) { enum_print (g_value_get_enum (value), G_VALUE_TYPE (value), string); } static gboolean flags_value_parse (GtkCssParser *parser, GValue *value) { GFlagsClass *flags_class; GFlagsValue *flag_value; guint flags = 0; char *str; flags_class = g_type_class_ref (G_VALUE_TYPE (value)); do { str = _gtk_css_parser_try_ident (parser, TRUE); if (str == NULL) { _gtk_css_parser_error (parser, "Expected an identifier"); g_type_class_unref (flags_class); return FALSE; } flag_value = g_flags_get_value_by_nick (flags_class, str); if (!flag_value) { _gtk_css_parser_error (parser, "Unknown flag value '%s' for type '%s'", str, g_type_name (G_VALUE_TYPE (value))); /* XXX Do we want to return FALSE here? We can get * forward-compatibility for new values this way */ g_free (str); g_type_class_unref (flags_class); return FALSE; } g_free (str); } while (_gtk_css_parser_try (parser, ",", FALSE)); g_type_class_unref (flags_class); g_value_set_enum (value, flags); return TRUE; } static void flags_value_print (const GValue *value, GString *string) { GFlagsClass *flags_class; guint i, flags; flags_class = g_type_class_ref (G_VALUE_TYPE (value)); flags = g_value_get_flags (value); for (i = 0; i < flags_class->n_values; i++) { GFlagsValue *flags_value = &flags_class->values[i]; if (flags & flags_value->value) { if (string->len != 0) g_string_append (string, ", "); g_string_append (string, flags_value->value_nick); } } g_type_class_unref (flags_class); } /*** API ***/ static void gtk_css_style_funcs_init (void) { if (G_LIKELY (parse_funcs != NULL)) return; parse_funcs = g_hash_table_new (NULL, NULL); print_funcs = g_hash_table_new (NULL, NULL); compute_funcs = g_hash_table_new (NULL, NULL); register_conversion_function (GDK_TYPE_RGBA, rgba_value_parse, rgba_value_print, rgba_value_compute); register_conversion_function (GDK_TYPE_COLOR, color_value_parse, color_value_print, color_value_compute); register_conversion_function (GTK_TYPE_SYMBOLIC_COLOR, symbolic_color_value_parse, symbolic_color_value_print, NULL); register_conversion_function (PANGO_TYPE_FONT_DESCRIPTION, font_description_value_parse, font_description_value_print, NULL); register_conversion_function (G_TYPE_BOOLEAN, boolean_value_parse, boolean_value_print, NULL); register_conversion_function (G_TYPE_INT, int_value_parse, int_value_print, NULL); register_conversion_function (G_TYPE_UINT, uint_value_parse, uint_value_print, NULL); register_conversion_function (G_TYPE_DOUBLE, double_value_parse, double_value_print, NULL); register_conversion_function (G_TYPE_FLOAT, float_value_parse, float_value_print, NULL); register_conversion_function (G_TYPE_STRING, string_value_parse, string_value_print, NULL); register_conversion_function (GTK_TYPE_THEMING_ENGINE, theming_engine_value_parse, theming_engine_value_print, NULL); register_conversion_function (GTK_TYPE_BORDER, border_value_parse, border_value_print, NULL); register_conversion_function (GTK_TYPE_GRADIENT, gradient_value_parse, gradient_value_print, NULL); register_conversion_function (CAIRO_GOBJECT_TYPE_PATTERN, pattern_value_parse, pattern_value_print, pattern_value_compute); register_conversion_function (G_TYPE_ENUM, enum_value_parse, enum_value_print, NULL); register_conversion_function (G_TYPE_FLAGS, flags_value_parse, flags_value_print, NULL); } /** * _gtk_css_style_parse_value: * @value: the value to parse into. Must be a valid initialized #GValue * @parser: the parser to parse from * * This is the generic parsing function used for CSS values. If the * function fails to parse a value, it will emit an error on @parser, * return %FALSE and not touch @value. * * Returns: %TRUE if parsing succeeded. **/ gboolean _gtk_css_style_parse_value (GValue *value, GtkCssParser *parser) { GtkStyleParseFunc func; g_return_val_if_fail (value != NULL, FALSE); g_return_val_if_fail (parser != NULL, FALSE); gtk_css_style_funcs_init (); func = g_hash_table_lookup (parse_funcs, GSIZE_TO_POINTER (G_VALUE_TYPE (value))); if (func == NULL) func = g_hash_table_lookup (parse_funcs, GSIZE_TO_POINTER (g_type_fundamental (G_VALUE_TYPE (value)))); if (func == NULL) { _gtk_css_parser_error (parser, "Cannot convert to type '%s'", g_type_name (G_VALUE_TYPE (value))); return FALSE; } return (*func) (parser, value); } /** * _gtk_css_style_print_value: * @value: an initialized GValue returned from _gtk_css_style_parse() * @string: the string to print into * * Prints @value into @string as a CSS value. If @value is not a * valid value, a random string will be printed instead. **/ void _gtk_css_style_print_value (const GValue *value, GString *string) { GtkStylePrintFunc func; gtk_css_style_funcs_init (); func = g_hash_table_lookup (print_funcs, GSIZE_TO_POINTER (G_VALUE_TYPE (value))); if (func == NULL) func = g_hash_table_lookup (print_funcs, GSIZE_TO_POINTER (g_type_fundamental (G_VALUE_TYPE (value)))); if (func == NULL) { char *s = g_strdup_value_contents (value); g_string_append (string, s); g_free (s); return; } func (value, string); } /** * _gtk_css_style_compute_value: * @computed: (out): a value to be filled with the result * @context: the context to use for computing the value * @specified: the value to use for the computation * * Converts the @specified value into the @computed value using the * information in @context. The values must have matching types, ie * @specified must be a result of a call to * _gtk_css_style_parse_value() with the same type as @computed. **/ GtkCssValue * _gtk_css_style_compute_value (GtkStyleContext *context, GType target_type, GtkCssValue *specified) { GtkStyleComputeFunc func; g_return_val_if_fail (GTK_IS_STYLE_CONTEXT (context), NULL); gtk_css_style_funcs_init (); func = g_hash_table_lookup (compute_funcs, GSIZE_TO_POINTER (target_type)); if (func == NULL) func = g_hash_table_lookup (compute_funcs, GSIZE_TO_POINTER (g_type_fundamental (target_type))); if (func) return func (context, specified); else return _gtk_css_value_ref (specified); }