1115 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1115 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* GTK - The GIMP Toolkit
 | |
|  * Copyright (C) 2010 Carlos Garnacho <carlosg@gnome.org>
 | |
|  *
 | |
|  * 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 <http://www.gnu.org/licenses/>.
 | |
|  */
 | |
| 
 | |
| #include "config.h"
 | |
| 
 | |
| #include "gtkcssstylefuncsprivate.h"
 | |
| 
 | |
| #include <errno.h>
 | |
| #include <math.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| 
 | |
| #include <gdk-pixbuf/gdk-pixbuf.h>
 | |
| #include <cairo-gobject.h>
 | |
| 
 | |
| #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);
 | |
| }
 | |
| 
 | 
