]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssstylefuncs.c
7b53bbb73698012891caee6f345cd4fe0c3b9c23
[~andy/gtk] / gtk / gtkcssstylefuncs.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2010 Carlos Garnacho <carlosg@gnome.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include "config.h"
21
22 #include "gtkcssstylefuncsprivate.h"
23
24 #include <errno.h>
25 #include <math.h>
26 #include <stdlib.h>
27 #include <string.h>
28
29 #include <gdk-pixbuf/gdk-pixbuf.h>
30 #include <cairo-gobject.h>
31
32 #include "gtkanimationdescription.h"
33 #include "gtkcssimagegradientprivate.h"
34 #include "gtkcssprovider.h"
35 #include "gtkcsstypesprivate.h"
36 #include "gtkgradient.h"
37 #include "gtkprivatetypebuiltins.h"
38 #include "gtkshadowprivate.h"
39 #include "gtkstylecontextprivate.h"
40 #include "gtkthemingengine.h"
41 #include "gtktypebuiltins.h"
42 #include "gtkwin32themeprivate.h"
43
44 /* this is in case round() is not provided by the compiler, 
45  * such as in the case of C89 compilers, like MSVC
46  */
47 #include "fallback-c89.c"
48
49 static GHashTable *parse_funcs = NULL;
50 static GHashTable *print_funcs = NULL;
51 static GHashTable *compute_funcs = NULL;
52
53 typedef gboolean         (* GtkStyleParseFunc)             (GtkCssParser           *parser,
54                                                             GFile                  *base,
55                                                             GValue                 *value);
56 typedef void             (* GtkStylePrintFunc)             (const GValue           *value,
57                                                             GString                *string);
58 typedef void             (* GtkStylePrintFunc)             (const GValue           *value,
59                                                             GString                *string);
60 typedef void             (* GtkStyleComputeFunc)           (GValue                 *computed,
61                                                             GtkStyleContext        *context,
62                                                             const GValue           *specified);
63
64 static void
65 register_conversion_function (GType               type,
66                               GtkStyleParseFunc   parse,
67                               GtkStylePrintFunc   print,
68                               GtkStyleComputeFunc compute)
69 {
70   if (parse)
71     g_hash_table_insert (parse_funcs, GSIZE_TO_POINTER (type), parse);
72   if (print)
73     g_hash_table_insert (print_funcs, GSIZE_TO_POINTER (type), print);
74   if (compute)
75     g_hash_table_insert (compute_funcs, GSIZE_TO_POINTER (type), compute);
76 }
77
78 static void
79 string_append_double (GString *string,
80                       double   d)
81 {
82   char buf[G_ASCII_DTOSTR_BUF_SIZE];
83
84   g_ascii_dtostr (buf, sizeof (buf), d);
85   g_string_append (string, buf);
86 }
87
88 static void
89 string_append_string (GString    *str,
90                       const char *string)
91 {
92   gsize len;
93
94   g_string_append_c (str, '"');
95
96   do {
97     len = strcspn (string, "\"\n\r\f");
98     g_string_append (str, string);
99     string += len;
100     switch (*string)
101       {
102       case '\0':
103         break;
104       case '\n':
105         g_string_append (str, "\\A ");
106         break;
107       case '\r':
108         g_string_append (str, "\\D ");
109         break;
110       case '\f':
111         g_string_append (str, "\\C ");
112         break;
113       case '\"':
114         g_string_append (str, "\\\"");
115         break;
116       default:
117         g_assert_not_reached ();
118         break;
119       }
120   } while (*string);
121
122   g_string_append_c (str, '"');
123 }
124
125 /*** IMPLEMENTATIONS ***/
126
127 static gboolean 
128 enum_parse (GtkCssParser *parser,
129             GType         type,
130             int          *res)
131 {
132   char *str;
133
134   if (_gtk_css_parser_try_enum (parser, type, res))
135     return TRUE;
136
137   str = _gtk_css_parser_try_ident (parser, TRUE);
138   if (str == NULL)
139     {
140       _gtk_css_parser_error (parser, "Expected an identifier");
141       return FALSE;
142     }
143
144   _gtk_css_parser_error (parser,
145                          "Unknown value '%s' for enum type '%s'",
146                          str, g_type_name (type));
147   g_free (str);
148
149   return FALSE;
150 }
151
152 static void
153 enum_print (int         value,
154             GType       type,
155             GString    *string)
156 {
157   GEnumClass *enum_class;
158   GEnumValue *enum_value;
159
160   enum_class = g_type_class_ref (type);
161   enum_value = g_enum_get_value (enum_class, value);
162
163   g_string_append (string, enum_value->value_nick);
164
165   g_type_class_unref (enum_class);
166 }
167
168 static gboolean
169 rgba_value_parse (GtkCssParser *parser,
170                   GFile        *base,
171                   GValue       *value)
172 {
173   GtkSymbolicColor *symbolic;
174   GdkRGBA rgba;
175
176   if (_gtk_css_parser_try (parser, "currentcolor", TRUE))
177     {
178       g_value_unset (value);
179       g_value_init (value, GTK_TYPE_CSS_SPECIAL_VALUE);
180       g_value_set_enum (value, GTK_CSS_CURRENT_COLOR);
181       return TRUE;
182     }
183
184   symbolic = _gtk_css_parser_read_symbolic_color (parser);
185   if (symbolic == NULL)
186     return FALSE;
187
188   if (gtk_symbolic_color_resolve (symbolic, NULL, &rgba))
189     {
190       g_value_set_boxed (value, &rgba);
191       gtk_symbolic_color_unref (symbolic);
192     }
193   else
194     {
195       g_value_unset (value);
196       g_value_init (value, GTK_TYPE_SYMBOLIC_COLOR);
197       g_value_take_boxed (value, symbolic);
198     }
199
200   return TRUE;
201 }
202
203 static void
204 rgba_value_print (const GValue *value,
205                   GString      *string)
206 {
207   const GdkRGBA *rgba = g_value_get_boxed (value);
208
209   if (rgba == NULL)
210     g_string_append (string, "none");
211   else
212     {
213       char *s = gdk_rgba_to_string (rgba);
214       g_string_append (string, s);
215       g_free (s);
216     }
217 }
218
219 static void
220 rgba_value_compute (GValue          *computed,
221                     GtkStyleContext *context,
222                     const GValue    *specified)
223 {
224   GdkRGBA rgba, white = { 1, 1, 1, 1 };
225
226   if (G_VALUE_HOLDS (specified, GTK_TYPE_CSS_SPECIAL_VALUE))
227     {
228       g_assert (g_value_get_enum (specified) == GTK_CSS_CURRENT_COLOR);
229       g_value_copy (_gtk_style_context_peek_property (context, "color"), computed);
230     }
231   else if (G_VALUE_HOLDS (specified, GTK_TYPE_SYMBOLIC_COLOR))
232     {
233       if (_gtk_style_context_resolve_color (context,
234                                             g_value_get_boxed (specified),
235                                             &rgba))
236         g_value_set_boxed (computed, &rgba);
237       else
238         g_value_set_boxed (computed, &white);
239     }
240   else
241     g_value_copy (specified, computed);
242 }
243
244 static gboolean 
245 color_value_parse (GtkCssParser *parser,
246                    GFile        *base,
247                    GValue       *value)
248 {
249   GtkSymbolicColor *symbolic;
250   GdkRGBA rgba;
251
252   symbolic = _gtk_css_parser_read_symbolic_color (parser);
253   if (symbolic == NULL)
254     return FALSE;
255
256   if (gtk_symbolic_color_resolve (symbolic, NULL, &rgba))
257     {
258       GdkColor color;
259
260       color.red = rgba.red * 65535. + 0.5;
261       color.green = rgba.green * 65535. + 0.5;
262       color.blue = rgba.blue * 65535. + 0.5;
263
264       g_value_set_boxed (value, &color);
265     }
266   else
267     {
268       g_value_unset (value);
269       g_value_init (value, GTK_TYPE_SYMBOLIC_COLOR);
270       g_value_take_boxed (value, symbolic);
271     }
272
273   return TRUE;
274 }
275
276 static void
277 color_value_print (const GValue *value,
278                    GString      *string)
279 {
280   const GdkColor *color = g_value_get_boxed (value);
281
282   if (color == NULL)
283     g_string_append (string, "none");
284   else
285     {
286       char *s = gdk_color_to_string (color);
287       g_string_append (string, s);
288       g_free (s);
289     }
290 }
291
292 static void
293 color_value_compute (GValue          *computed,
294                      GtkStyleContext *context,
295                      const GValue    *specified)
296 {
297   GdkRGBA rgba;
298   GdkColor color = { 0, 65535, 65535, 65535 };
299
300   if (G_VALUE_HOLDS (specified, GTK_TYPE_SYMBOLIC_COLOR))
301     {
302       if (_gtk_style_context_resolve_color (context,
303                                             g_value_get_boxed (specified),
304                                             &rgba))
305         {
306           color.red = rgba.red * 65535. + 0.5;
307           color.green = rgba.green * 65535. + 0.5;
308           color.blue = rgba.blue * 65535. + 0.5;
309         }
310       
311       g_value_set_boxed (computed, &color);
312     }
313   else
314     g_value_copy (specified, computed);
315 }
316
317 static gboolean
318 symbolic_color_value_parse (GtkCssParser *parser,
319                             GFile        *base,
320                             GValue       *value)
321 {
322   GtkSymbolicColor *symbolic;
323
324   symbolic = _gtk_css_parser_read_symbolic_color (parser);
325   if (symbolic == NULL)
326     return FALSE;
327
328   g_value_take_boxed (value, symbolic);
329   return TRUE;
330 }
331
332 static void
333 symbolic_color_value_print (const GValue *value,
334                             GString      *string)
335 {
336   GtkSymbolicColor *symbolic = g_value_get_boxed (value);
337
338   if (symbolic == NULL)
339     g_string_append (string, "none");
340   else
341     {
342       char *s = gtk_symbolic_color_to_string (symbolic);
343       g_string_append (string, s);
344       g_free (s);
345     }
346 }
347
348 static gboolean 
349 font_description_value_parse (GtkCssParser *parser,
350                               GFile        *base,
351                               GValue       *value)
352 {
353   PangoFontDescription *font_desc;
354   guint mask;
355   char *str;
356
357   str = _gtk_css_parser_read_value (parser);
358   if (str == NULL)
359     return FALSE;
360
361   font_desc = pango_font_description_from_string (str);
362   mask = pango_font_description_get_set_fields (font_desc);
363   /* These values are not really correct,
364    * but the fields must be set, so we set them to something */
365   if ((mask & PANGO_FONT_MASK_FAMILY) == 0)
366     pango_font_description_set_family_static (font_desc, "Sans");
367   if ((mask & PANGO_FONT_MASK_SIZE) == 0)
368     pango_font_description_set_size (font_desc, 10 * PANGO_SCALE);
369   g_free (str);
370   g_value_take_boxed (value, font_desc);
371   return TRUE;
372 }
373
374 static void
375 font_description_value_print (const GValue *value,
376                               GString      *string)
377 {
378   const PangoFontDescription *desc = g_value_get_boxed (value);
379
380   if (desc == NULL)
381     g_string_append (string, "none");
382   else
383     {
384       char *s = pango_font_description_to_string (desc);
385       g_string_append (string, s);
386       g_free (s);
387     }
388 }
389
390 static gboolean 
391 boolean_value_parse (GtkCssParser *parser,
392                      GFile        *base,
393                      GValue       *value)
394 {
395   if (_gtk_css_parser_try (parser, "true", TRUE) ||
396       _gtk_css_parser_try (parser, "1", TRUE))
397     {
398       g_value_set_boolean (value, TRUE);
399       return TRUE;
400     }
401   else if (_gtk_css_parser_try (parser, "false", TRUE) ||
402            _gtk_css_parser_try (parser, "0", TRUE))
403     {
404       g_value_set_boolean (value, FALSE);
405       return TRUE;
406     }
407   else
408     {
409       _gtk_css_parser_error (parser, "Expected a boolean value");
410       return FALSE;
411     }
412 }
413
414 static void
415 boolean_value_print (const GValue *value,
416                      GString      *string)
417 {
418   if (g_value_get_boolean (value))
419     g_string_append (string, "true");
420   else
421     g_string_append (string, "false");
422 }
423
424 static gboolean 
425 int_value_parse (GtkCssParser *parser,
426                  GFile        *base,
427                  GValue       *value)
428 {
429   gint i;
430
431   if (_gtk_css_parser_begins_with (parser, '-'))
432     {
433       int res = _gtk_win32_theme_int_parse (parser, base, &i);
434       if (res >= 0)
435         {
436           g_value_set_int (value, i);
437           return res > 0;
438         }
439       /* < 0 => continue */
440     }
441
442   if (!_gtk_css_parser_try_int (parser, &i))
443     {
444       _gtk_css_parser_error (parser, "Expected a valid integer value");
445       return FALSE;
446     }
447
448   g_value_set_int (value, i);
449   return TRUE;
450 }
451
452 static void
453 int_value_print (const GValue *value,
454                  GString      *string)
455 {
456   g_string_append_printf (string, "%d", g_value_get_int (value));
457 }
458
459 static gboolean 
460 uint_value_parse (GtkCssParser *parser,
461                   GFile        *base,
462                   GValue       *value)
463 {
464   guint u;
465
466   if (!_gtk_css_parser_try_uint (parser, &u))
467     {
468       _gtk_css_parser_error (parser, "Expected a valid unsigned value");
469       return FALSE;
470     }
471
472   g_value_set_uint (value, u);
473   return TRUE;
474 }
475
476 static void
477 uint_value_print (const GValue *value,
478                   GString      *string)
479 {
480   g_string_append_printf (string, "%u", g_value_get_uint (value));
481 }
482
483 static gboolean 
484 double_value_parse (GtkCssParser *parser,
485                     GFile        *base,
486                     GValue       *value)
487 {
488   gdouble d;
489
490   if (!_gtk_css_parser_try_double (parser, &d))
491     {
492       _gtk_css_parser_error (parser, "Expected a number");
493       return FALSE;
494     }
495
496   g_value_set_double (value, d);
497   return TRUE;
498 }
499
500 static void
501 double_value_print (const GValue *value,
502                     GString      *string)
503 {
504   string_append_double (string, g_value_get_double (value));
505 }
506
507 static gboolean 
508 float_value_parse (GtkCssParser *parser,
509                    GFile        *base,
510                    GValue       *value)
511 {
512   gdouble d;
513
514   if (!_gtk_css_parser_try_double (parser, &d))
515     {
516       _gtk_css_parser_error (parser, "Expected a number");
517       return FALSE;
518     }
519
520   g_value_set_float (value, d);
521   return TRUE;
522 }
523
524 static void
525 float_value_print (const GValue *value,
526                    GString      *string)
527 {
528   string_append_double (string, g_value_get_float (value));
529 }
530
531 static gboolean 
532 string_value_parse (GtkCssParser *parser,
533                     GFile        *base,
534                     GValue       *value)
535 {
536   char *str = _gtk_css_parser_read_string (parser);
537
538   if (str == NULL)
539     return FALSE;
540
541   g_value_take_string (value, str);
542   return TRUE;
543 }
544
545 static void
546 string_value_print (const GValue *value,
547                     GString      *str)
548 {
549   string_append_string (str, g_value_get_string (value));
550 }
551
552 static gboolean 
553 theming_engine_value_parse (GtkCssParser *parser,
554                             GFile        *base,
555                             GValue       *value)
556 {
557   GtkThemingEngine *engine;
558   char *str;
559
560   if (_gtk_css_parser_try (parser, "none", TRUE))
561     {
562       g_value_set_object (value, gtk_theming_engine_load (NULL));
563       return TRUE;
564     }
565
566   str = _gtk_css_parser_try_ident (parser, TRUE);
567   if (str == NULL)
568     {
569       _gtk_css_parser_error (parser, "Expected a valid theme name");
570       return FALSE;
571     }
572
573   engine = gtk_theming_engine_load (str);
574
575   if (engine == NULL)
576     {
577       _gtk_css_parser_error (parser, "Themeing engine '%s' not found", str);
578       g_free (str);
579       return FALSE;
580     }
581
582   g_value_set_object (value, engine);
583   g_free (str);
584   return TRUE;
585 }
586
587 static void
588 theming_engine_value_print (const GValue *value,
589                             GString      *string)
590 {
591   GtkThemingEngine *engine;
592   char *name;
593
594   engine = g_value_get_object (value);
595   if (engine == NULL)
596     g_string_append (string, "none");
597   else
598     {
599       /* XXX: gtk_theming_engine_get_name()? */
600       g_object_get (engine, "name", &name, NULL);
601       g_string_append (string, name ? name : "none");
602       g_free (name);
603     }
604 }
605
606 static gboolean 
607 animation_description_value_parse (GtkCssParser *parser,
608                                    GFile        *base,
609                                    GValue       *value)
610 {
611   GtkAnimationDescription *desc;
612   char *str;
613
614   str = _gtk_css_parser_read_value (parser);
615   if (str == NULL)
616     return FALSE;
617
618   desc = _gtk_animation_description_from_string (str);
619   g_free (str);
620
621   if (desc == NULL)
622     {
623       _gtk_css_parser_error (parser, "Invalid animation description");
624       return FALSE;
625     }
626   
627   g_value_take_boxed (value, desc);
628   return TRUE;
629 }
630
631 static void
632 animation_description_value_print (const GValue *value,
633                                    GString      *string)
634 {
635   GtkAnimationDescription *desc = g_value_get_boxed (value);
636
637   if (desc == NULL)
638     g_string_append (string, "none");
639   else
640     _gtk_animation_description_print (desc, string);
641 }
642
643 static gboolean 
644 border_value_parse (GtkCssParser *parser,
645                     GFile        *base,
646                     GValue       *value)
647 {
648   GtkBorder border = { 0, };
649   guint i, numbers[4];
650
651   for (i = 0; i < G_N_ELEMENTS (numbers); i++)
652     {
653       if (_gtk_css_parser_begins_with (parser, '-'))
654         {
655           /* These are strictly speaking signed, but we want to be able to use them
656              for unsigned types too, as the actual ranges of values make this safe */
657           int res = _gtk_win32_theme_int_parse (parser, base, (int *)&numbers[i]);
658
659           if (res == 0) /* Parse error, report */
660             return FALSE;
661
662           if (res < 0) /* Nothing known to expand */
663             break;
664         }
665       else
666         {
667           if (!_gtk_css_parser_try_uint (parser, &numbers[i]))
668             break;
669
670           /* XXX: shouldn't allow spaces here? */
671           _gtk_css_parser_try (parser, "px", TRUE);
672         }
673     }
674
675   if (i == 0)
676     {
677       _gtk_css_parser_error (parser, "Expected valid border");
678       return FALSE;
679     }
680
681   border.top = numbers[0];
682   if (i > 1)
683     border.right = numbers[1];
684   else
685     border.right = border.top;
686   if (i > 2)
687     border.bottom = numbers[2];
688   else
689     border.bottom = border.top;
690   if (i > 3)
691     border.left = numbers[3];
692   else
693     border.left = border.right;
694
695   g_value_set_boxed (value, &border);
696   return TRUE;
697 }
698
699 static void
700 border_value_print (const GValue *value, GString *string)
701 {
702   const GtkBorder *border = g_value_get_boxed (value);
703
704   if (border == NULL)
705     g_string_append (string, "none");
706   else if (border->left != border->right)
707     g_string_append_printf (string, "%d %d %d %d", border->top, border->right, border->bottom, border->left);
708   else if (border->top != border->bottom)
709     g_string_append_printf (string, "%d %d %d", border->top, border->right, border->bottom);
710   else if (border->top != border->left)
711     g_string_append_printf (string, "%d %d", border->top, border->right);
712   else
713     g_string_append_printf (string, "%d", border->top);
714 }
715
716 static gboolean 
717 gradient_value_parse (GtkCssParser *parser,
718                       GFile        *base,
719                       GValue       *value)
720 {
721   GtkGradient *gradient;
722
723   gradient = _gtk_gradient_parse (parser);
724   if (gradient == NULL)
725     return FALSE;
726
727   g_value_take_boxed (value, gradient);
728   return TRUE;
729 }
730
731 static void
732 gradient_value_print (const GValue *value,
733                       GString      *string)
734 {
735   GtkGradient *gradient = g_value_get_boxed (value);
736
737   if (gradient == NULL)
738     g_string_append (string, "none");
739   else
740     {
741       char *s = gtk_gradient_to_string (gradient);
742       g_string_append (string, s);
743       g_free (s);
744     }
745 }
746
747 static GFile *
748 gtk_css_parse_url (GtkCssParser *parser,
749                    GFile        *base)
750 {
751   gchar *path;
752   GFile *file;
753
754   if (_gtk_css_parser_try (parser, "url", FALSE))
755     {
756       if (!_gtk_css_parser_try (parser, "(", TRUE))
757         {
758           _gtk_css_parser_skip_whitespace (parser);
759           if (_gtk_css_parser_try (parser, "(", TRUE))
760             {
761               GError *error;
762               
763               error = g_error_new_literal (GTK_CSS_PROVIDER_ERROR,
764                                            GTK_CSS_PROVIDER_ERROR_DEPRECATED,
765                                            "Whitespace between 'url' and '(' is deprecated");
766                              
767               _gtk_css_parser_take_error (parser, error);
768             }
769           else
770             {
771               _gtk_css_parser_error (parser, "Expected '(' after 'url'");
772               return NULL;
773             }
774         }
775
776       path = _gtk_css_parser_read_string (parser);
777       if (path == NULL)
778         return NULL;
779
780       if (!_gtk_css_parser_try (parser, ")", TRUE))
781         {
782           _gtk_css_parser_error (parser, "No closing ')' found for 'url'");
783           g_free (path);
784           return NULL;
785         }
786     }
787   else
788     {
789       path = _gtk_css_parser_try_name (parser, TRUE);
790       if (path == NULL)
791         {
792           _gtk_css_parser_error (parser, "Not a valid url");
793           return NULL;
794         }
795     }
796
797   file = g_file_resolve_relative_path (base, path);
798   g_free (path);
799
800   return file;
801 }
802
803 static gboolean 
804 pattern_value_parse (GtkCssParser *parser,
805                      GFile        *base,
806                      GValue       *value)
807 {
808   if (_gtk_css_parser_try (parser, "none", TRUE))
809     {
810       /* nothing to do here */
811     }
812   else if (_gtk_css_parser_begins_with (parser, '-'))
813     {
814       g_value_unset (value);
815       g_value_init (value, GTK_TYPE_GRADIENT);
816       return gradient_value_parse (parser, base, value);
817     }
818   else
819     {
820       GError *error = NULL;
821       gchar *path;
822       GdkPixbuf *pixbuf;
823       GFile *file;
824       cairo_surface_t *surface;
825       cairo_pattern_t *pattern;
826       cairo_t *cr;
827       cairo_matrix_t matrix;
828
829       file = gtk_css_parse_url (parser, base);
830       if (file == NULL)
831         return FALSE;
832
833       path = g_file_get_path (file);
834       g_object_unref (file);
835
836       pixbuf = gdk_pixbuf_new_from_file (path, &error);
837       g_free (path);
838       if (pixbuf == NULL)
839         {
840           _gtk_css_parser_take_error (parser, error);
841           return FALSE;
842         }
843
844       surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
845                                             gdk_pixbuf_get_width (pixbuf),
846                                             gdk_pixbuf_get_height (pixbuf));
847       cr = cairo_create (surface);
848       gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
849       cairo_paint (cr);
850       pattern = cairo_pattern_create_for_surface (surface);
851
852       cairo_matrix_init_scale (&matrix,
853                                gdk_pixbuf_get_width (pixbuf),
854                                gdk_pixbuf_get_height (pixbuf));
855       cairo_pattern_set_matrix (pattern, &matrix);
856
857       cairo_surface_destroy (surface);
858       cairo_destroy (cr);
859       g_object_unref (pixbuf);
860
861       g_value_take_boxed (value, pattern);
862     }
863   
864   return TRUE;
865 }
866
867 static cairo_status_t
868 surface_write (void                *closure,
869                const unsigned char *data,
870                unsigned int         length)
871 {
872   g_byte_array_append (closure, data, length);
873
874   return CAIRO_STATUS_SUCCESS;
875 }
876
877 static void
878 surface_print (cairo_surface_t *surface,
879                GString *        string)
880 {
881 #if CAIRO_HAS_PNG_FUNCTIONS
882   GByteArray *array;
883   char *base64;
884   
885   array = g_byte_array_new ();
886   cairo_surface_write_to_png_stream (surface, surface_write, array);
887   base64 = g_base64_encode (array->data, array->len);
888   g_byte_array_free (array, TRUE);
889
890   g_string_append (string, "url(\"data:image/png;base64,");
891   g_string_append (string, base64);
892   g_string_append (string, "\")");
893
894   g_free (base64);
895 #else
896   g_string_append (string, "none /* you need cairo png functions enabled to make this work */");
897 #endif
898 }
899
900 static void
901 pattern_value_print (const GValue *value,
902                      GString      *string)
903 {
904   cairo_pattern_t *pattern;
905   cairo_surface_t *surface;
906
907   pattern = g_value_get_boxed (value);
908
909   if (pattern == NULL)
910     {
911       g_string_append (string, "none");
912       return;
913     }
914
915   switch (cairo_pattern_get_type (pattern))
916     {
917     case CAIRO_PATTERN_TYPE_SURFACE:
918       if (cairo_pattern_get_surface (pattern, &surface) != CAIRO_STATUS_SUCCESS)
919         {
920           g_assert_not_reached ();
921         }
922       surface_print (surface, string);
923       break;
924     case CAIRO_PATTERN_TYPE_SOLID:
925     case CAIRO_PATTERN_TYPE_LINEAR:
926     case CAIRO_PATTERN_TYPE_RADIAL:
927     default:
928       g_assert_not_reached ();
929       break;
930     }
931 }
932
933 static void
934 pattern_value_compute (GValue          *computed,
935                        GtkStyleContext *context,
936                        const GValue    *specified)
937 {
938   if (G_VALUE_HOLDS (specified, GTK_TYPE_GRADIENT))
939     {
940       cairo_pattern_t *gradient;
941       
942       gradient = gtk_gradient_resolve_for_context (g_value_get_boxed (specified), context);
943
944       g_value_take_boxed (computed, gradient);
945     }
946   else
947     g_value_copy (specified, computed);
948 }
949
950 static gboolean
951 shadow_value_parse (GtkCssParser *parser,
952                     GFile *base,
953                     GValue *value)
954 {
955   gboolean have_inset, have_color, have_lengths;
956   gdouble hoffset, voffset, blur, spread;
957   GtkSymbolicColor *color;
958   GtkShadow *shadow;
959   guint i;
960
961   shadow = _gtk_shadow_new ();
962
963   if (_gtk_css_parser_try (parser, "none", TRUE))
964     return TRUE;
965
966   do
967     {
968       have_inset = have_lengths = have_color = FALSE;
969
970       for (i = 0; i < 3; i++)
971         {
972           if (!have_inset && 
973               _gtk_css_parser_try (parser, "inset", TRUE))
974             {
975               have_inset = TRUE;
976               continue;
977             }
978             
979           if (!have_lengths &&
980               _gtk_css_parser_try_double (parser, &hoffset))
981             {
982               have_lengths = TRUE;
983
984               if (!_gtk_css_parser_try_double (parser, &voffset))
985                 {
986                   _gtk_css_parser_error (parser, "Horizontal and vertical offsets are required");
987                   _gtk_shadow_unref (shadow);
988                   return FALSE;
989                 }
990
991               if (!_gtk_css_parser_try_double (parser, &blur))
992                 blur = 0;
993
994               if (!_gtk_css_parser_try_double (parser, &spread))
995                 spread = 0;
996
997               continue;
998             }
999
1000           if (!have_color)
1001             {
1002               have_color = TRUE;
1003
1004               /* XXX: the color is optional and UA-defined if it's missing,
1005                * but it doesn't really make sense for us...
1006                */
1007               color = _gtk_css_parser_read_symbolic_color (parser);
1008
1009               if (color == NULL)
1010                 {
1011                   _gtk_shadow_unref (shadow);
1012                   return FALSE;
1013                 }
1014             }
1015         }
1016
1017       if (!have_color || !have_lengths)
1018         {
1019           _gtk_css_parser_error (parser, "Must specify at least color and offsets");
1020           _gtk_shadow_unref (shadow);
1021           return FALSE;
1022         }
1023
1024       _gtk_shadow_append (shadow,
1025                           hoffset, voffset,
1026                           blur, spread,
1027                           have_inset, color);
1028
1029       gtk_symbolic_color_unref (color);
1030
1031     }
1032   while (_gtk_css_parser_try (parser, ",", TRUE));
1033
1034   g_value_take_boxed (value, shadow);
1035   return TRUE;
1036 }
1037
1038 static void
1039 shadow_value_print (const GValue *value,
1040                     GString      *string)
1041 {
1042   GtkShadow *shadow;
1043
1044   shadow = g_value_get_boxed (value);
1045
1046   if (shadow == NULL)
1047     g_string_append (string, "none");
1048   else
1049     _gtk_shadow_print (shadow, string);
1050 }
1051
1052 static void
1053 shadow_value_compute (GValue          *computed,
1054                       GtkStyleContext *context,
1055                       const GValue    *specified)
1056 {
1057   GtkShadow *shadow;
1058
1059   shadow = g_value_get_boxed (specified);
1060   if (shadow)
1061     shadow = _gtk_shadow_resolve (shadow, context);
1062
1063   g_value_take_boxed (computed, shadow);
1064 }
1065
1066 static gboolean
1067 background_repeat_value_parse (GtkCssParser *parser,
1068                                GFile *file,
1069                                GValue *value)
1070 {
1071   GtkCssBackgroundRepeat repeat;
1072   int style;
1073
1074   if (!enum_parse (parser, GTK_TYPE_CSS_BACKGROUND_REPEAT_STYLE, &style))
1075     return FALSE;
1076
1077   repeat.repeat = style;
1078
1079   g_value_set_boxed (value, &repeat);
1080
1081   return TRUE;
1082 }
1083
1084 static void
1085 background_repeat_value_print (const GValue *value,
1086                                GString      *string)
1087 {
1088   GtkCssBackgroundRepeat *repeat;
1089
1090   repeat = g_value_get_boxed (value);
1091
1092   enum_print (repeat->repeat, GTK_TYPE_CSS_BACKGROUND_REPEAT_STYLE, string);
1093 }
1094
1095 static gboolean
1096 border_image_repeat_value_parse (GtkCssParser *parser,
1097                                  GFile *file,
1098                                  GValue *value)
1099 {
1100   GtkCssBorderImageRepeat image_repeat;
1101   GtkCssBorderRepeatStyle styles[2];
1102   gint i, v;
1103
1104   for (i = 0; i < 2; i++)
1105     {
1106       if (_gtk_css_parser_try_enum (parser, GTK_TYPE_CSS_BORDER_REPEAT_STYLE, &v))
1107         styles[i] = v;
1108       else if (i == 0)
1109         {
1110           styles[1] = styles[0] = GTK_CSS_REPEAT_STYLE_STRETCH;
1111           break;
1112         }
1113       else
1114         styles[i] = styles[0];
1115     }
1116
1117   image_repeat.hrepeat = styles[0];
1118   image_repeat.vrepeat = styles[1];
1119
1120   g_value_set_boxed (value, &image_repeat);
1121
1122   return TRUE;
1123 }
1124
1125 static void
1126 border_image_repeat_value_print (const GValue *value,
1127                                  GString      *string)
1128 {
1129   GtkCssBorderImageRepeat *image_repeat;
1130
1131   image_repeat = g_value_get_boxed (value);
1132
1133   enum_print (image_repeat->hrepeat, GTK_TYPE_CSS_BORDER_REPEAT_STYLE, string);
1134   if (image_repeat->hrepeat != image_repeat->vrepeat)
1135     {
1136       g_string_append (string, " ");
1137       enum_print (image_repeat->vrepeat, GTK_TYPE_CSS_BORDER_REPEAT_STYLE, string);
1138     }
1139 }
1140
1141 static gboolean 
1142 enum_value_parse (GtkCssParser *parser,
1143                   GFile        *base,
1144                   GValue       *value)
1145 {
1146   int v;
1147
1148   if (enum_parse (parser, G_VALUE_TYPE (value), &v))
1149     {
1150       g_value_set_enum (value, v);
1151       return TRUE;
1152     }
1153
1154   return FALSE;
1155 }
1156
1157 static void
1158 enum_value_print (const GValue *value,
1159                   GString      *string)
1160 {
1161   enum_print (g_value_get_enum (value), G_VALUE_TYPE (value), string);
1162 }
1163
1164 static gboolean 
1165 flags_value_parse (GtkCssParser *parser,
1166                    GFile        *base,
1167                    GValue       *value)
1168 {
1169   GFlagsClass *flags_class;
1170   GFlagsValue *flag_value;
1171   guint flags = 0;
1172   char *str;
1173
1174   flags_class = g_type_class_ref (G_VALUE_TYPE (value));
1175
1176   do {
1177     str = _gtk_css_parser_try_ident (parser, TRUE);
1178     if (str == NULL)
1179       {
1180         _gtk_css_parser_error (parser, "Expected an identifier");
1181         g_type_class_unref (flags_class);
1182         return FALSE;
1183       }
1184
1185       flag_value = g_flags_get_value_by_nick (flags_class, str);
1186       if (!flag_value)
1187         {
1188           _gtk_css_parser_error (parser,
1189                                  "Unknown flag value '%s' for type '%s'",
1190                                  str, g_type_name (G_VALUE_TYPE (value)));
1191           /* XXX Do we want to return FALSE here? We can get
1192            * forward-compatibility for new values this way
1193            */
1194           g_free (str);
1195           g_type_class_unref (flags_class);
1196           return FALSE;
1197         }
1198
1199       g_free (str);
1200     }
1201   while (_gtk_css_parser_try (parser, ",", FALSE));
1202
1203   g_type_class_unref (flags_class);
1204
1205   g_value_set_enum (value, flags);
1206
1207   return TRUE;
1208 }
1209
1210 static void
1211 flags_value_print (const GValue *value,
1212                    GString      *string)
1213 {
1214   GFlagsClass *flags_class;
1215   guint i, flags;
1216
1217   flags_class = g_type_class_ref (G_VALUE_TYPE (value));
1218   flags = g_value_get_flags (value);
1219
1220   for (i = 0; i < flags_class->n_values; i++)
1221     {
1222       GFlagsValue *flags_value = &flags_class->values[i];
1223
1224       if (flags & flags_value->value)
1225         {
1226           if (string->len != 0)
1227             g_string_append (string, ", ");
1228
1229           g_string_append (string, flags_value->value_nick);
1230         }
1231     }
1232
1233   g_type_class_unref (flags_class);
1234 }
1235
1236 /*** API ***/
1237
1238 static void
1239 gtk_css_style_funcs_init (void)
1240 {
1241   if (G_LIKELY (parse_funcs != NULL))
1242     return;
1243
1244   parse_funcs = g_hash_table_new (NULL, NULL);
1245   print_funcs = g_hash_table_new (NULL, NULL);
1246   compute_funcs = g_hash_table_new (NULL, NULL);
1247
1248   register_conversion_function (GDK_TYPE_RGBA,
1249                                 rgba_value_parse,
1250                                 rgba_value_print,
1251                                 rgba_value_compute);
1252   register_conversion_function (GDK_TYPE_COLOR,
1253                                 color_value_parse,
1254                                 color_value_print,
1255                                 color_value_compute);
1256   register_conversion_function (GTK_TYPE_SYMBOLIC_COLOR,
1257                                 symbolic_color_value_parse,
1258                                 symbolic_color_value_print,
1259                                 NULL);
1260   register_conversion_function (PANGO_TYPE_FONT_DESCRIPTION,
1261                                 font_description_value_parse,
1262                                 font_description_value_print,
1263                                 NULL);
1264   register_conversion_function (G_TYPE_BOOLEAN,
1265                                 boolean_value_parse,
1266                                 boolean_value_print,
1267                                 NULL);
1268   register_conversion_function (G_TYPE_INT,
1269                                 int_value_parse,
1270                                 int_value_print,
1271                                 NULL);
1272   register_conversion_function (G_TYPE_UINT,
1273                                 uint_value_parse,
1274                                 uint_value_print,
1275                                 NULL);
1276   register_conversion_function (G_TYPE_DOUBLE,
1277                                 double_value_parse,
1278                                 double_value_print,
1279                                 NULL);
1280   register_conversion_function (G_TYPE_FLOAT,
1281                                 float_value_parse,
1282                                 float_value_print,
1283                                 NULL);
1284   register_conversion_function (G_TYPE_STRING,
1285                                 string_value_parse,
1286                                 string_value_print,
1287                                 NULL);
1288   register_conversion_function (GTK_TYPE_THEMING_ENGINE,
1289                                 theming_engine_value_parse,
1290                                 theming_engine_value_print,
1291                                 NULL);
1292   register_conversion_function (GTK_TYPE_ANIMATION_DESCRIPTION,
1293                                 animation_description_value_parse,
1294                                 animation_description_value_print,
1295                                 NULL);
1296   register_conversion_function (GTK_TYPE_BORDER,
1297                                 border_value_parse,
1298                                 border_value_print,
1299                                 NULL);
1300   register_conversion_function (GTK_TYPE_GRADIENT,
1301                                 gradient_value_parse,
1302                                 gradient_value_print,
1303                                 NULL);
1304   register_conversion_function (CAIRO_GOBJECT_TYPE_PATTERN,
1305                                 pattern_value_parse,
1306                                 pattern_value_print,
1307                                 pattern_value_compute);
1308   register_conversion_function (GTK_TYPE_CSS_BORDER_IMAGE_REPEAT,
1309                                 border_image_repeat_value_parse,
1310                                 border_image_repeat_value_print,
1311                                 NULL);
1312   register_conversion_function (GTK_TYPE_SHADOW,
1313                                 shadow_value_parse,
1314                                 shadow_value_print,
1315                                 shadow_value_compute);
1316   register_conversion_function (G_TYPE_ENUM,
1317                                 enum_value_parse,
1318                                 enum_value_print,
1319                                 NULL);
1320   register_conversion_function (G_TYPE_FLAGS,
1321                                 flags_value_parse,
1322                                 flags_value_print,
1323                                 NULL);
1324   register_conversion_function (GTK_TYPE_CSS_BACKGROUND_REPEAT,
1325                                 background_repeat_value_parse,
1326                                 background_repeat_value_print,
1327                                 NULL);
1328 }
1329
1330 /**
1331  * _gtk_css_style_parse_value:
1332  * @value: the value to parse into. Must be a valid initialized #GValue
1333  * @parser: the parser to parse from
1334  * @base: the base URL for @parser
1335  *
1336  * This is the generic parsing function used for CSS values. If the
1337  * function fails to parse a value, it will emit an error on @parser,
1338  * return %FALSE and not touch @value.
1339  *
1340  * Returns: %TRUE if parsing succeeded.
1341  **/
1342 gboolean
1343 _gtk_css_style_parse_value (GValue       *value,
1344                             GtkCssParser *parser,
1345                             GFile        *base)
1346 {
1347   GtkStyleParseFunc func;
1348
1349   g_return_val_if_fail (value != NULL, FALSE);
1350   g_return_val_if_fail (parser != NULL, FALSE);
1351
1352   gtk_css_style_funcs_init ();
1353
1354   func = g_hash_table_lookup (parse_funcs,
1355                               GSIZE_TO_POINTER (G_VALUE_TYPE (value)));
1356   if (func == NULL)
1357     func = g_hash_table_lookup (parse_funcs,
1358                                 GSIZE_TO_POINTER (g_type_fundamental (G_VALUE_TYPE (value))));
1359
1360   if (func == NULL)
1361     {
1362       _gtk_css_parser_error (parser,
1363                              "Cannot convert to type '%s'",
1364                              g_type_name (G_VALUE_TYPE (value)));
1365       return FALSE;
1366     }
1367
1368   return (*func) (parser, base, value);
1369 }
1370
1371 /**
1372  * _gtk_css_style_print_value:
1373  * @value: an initialized GValue returned from _gtk_css_style_parse()
1374  * @string: the string to print into
1375  *
1376  * Prints @value into @string as a CSS value. If @value is not a
1377  * valid value, a random string will be printed instead.
1378  **/
1379 void
1380 _gtk_css_style_print_value (const GValue *value,
1381                             GString      *string)
1382 {
1383   GtkStylePrintFunc func;
1384
1385   gtk_css_style_funcs_init ();
1386
1387   func = g_hash_table_lookup (print_funcs,
1388                               GSIZE_TO_POINTER (G_VALUE_TYPE (value)));
1389   if (func == NULL)
1390     func = g_hash_table_lookup (print_funcs,
1391                                 GSIZE_TO_POINTER (g_type_fundamental (G_VALUE_TYPE (value))));
1392
1393   if (func == NULL)
1394     {
1395       char *s = g_strdup_value_contents (value);
1396       g_string_append (string, s);
1397       g_free (s);
1398       return;
1399     }
1400   
1401   func (value, string);
1402 }
1403
1404 /**
1405  * _gtk_css_style_compute_value:
1406  * @computed: (out): a value to be filled with the result
1407  * @context: the context to use for computing the value
1408  * @specified: the value to use for the computation
1409  *
1410  * Converts the @specified value into the @computed value using the
1411  * information in @context. The values must have matching types, ie
1412  * @specified must be a result of a call to
1413  * _gtk_css_style_parse_value() with the same type as @computed.
1414  **/
1415 void
1416 _gtk_css_style_compute_value (GValue          *computed,
1417                               GtkStyleContext *context,
1418                               const GValue    *specified)
1419 {
1420   GtkStyleComputeFunc func;
1421
1422   g_return_if_fail (G_IS_VALUE (computed));
1423   g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
1424   g_return_if_fail (G_IS_VALUE (specified));
1425
1426   gtk_css_style_funcs_init ();
1427
1428   func = g_hash_table_lookup (compute_funcs,
1429                               GSIZE_TO_POINTER (G_VALUE_TYPE (computed)));
1430   if (func == NULL)
1431     func = g_hash_table_lookup (compute_funcs,
1432                                 GSIZE_TO_POINTER (g_type_fundamental (G_VALUE_TYPE (computed))));
1433
1434   if (func)
1435     func (computed, context, specified);
1436   else
1437     g_value_copy (specified, computed);
1438 }
1439