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