]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssshorthandpropertyimpl.c
d303643018da8de153e8517af566d1611f9ca832
[~andy/gtk] / gtk / gtkcssshorthandpropertyimpl.c
1 /*
2  * Copyright © 2011 Red Hat Inc.
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.1 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 Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  *
18  * Authors: Benjamin Otte <otte@gnome.org>
19  */
20
21 #include "config.h"
22
23 #include "gtkcssshorthandpropertyprivate.h"
24
25 #include <cairo-gobject.h>
26 #include <math.h>
27
28 #include "gtkborderimageprivate.h"
29 #include "gtkcssstylefuncsprivate.h"
30 #include "gtkcsstypesprivate.h"
31
32 /* this is in case round() is not provided by the compiler, 
33  * such as in the case of C89 compilers, like MSVC
34  */
35 #include "fallback-c89.c"
36
37 /*** PARSING ***/
38
39 static gboolean
40 border_image_value_parse (GtkCssParser *parser,
41                           GFile *base,
42                           GValue *value)
43 {
44   GValue temp = G_VALUE_INIT;
45   cairo_pattern_t *pattern = NULL;
46   gconstpointer *boxed = NULL;
47   GType boxed_type;
48   GtkBorder slice, *width = NULL, *parsed_slice;
49   GtkCssBorderImageRepeat repeat, *parsed_repeat;
50   gboolean retval = FALSE;
51   GtkBorderImage *image = NULL;
52
53   if (_gtk_css_parser_try (parser, "none", TRUE))
54     return TRUE;
55
56   g_value_init (&temp, CAIRO_GOBJECT_TYPE_PATTERN);
57
58   if (!_gtk_css_style_parse_value (&temp, parser, base))
59     return FALSE;
60
61   boxed_type = G_VALUE_TYPE (&temp);
62   if (boxed_type != CAIRO_GOBJECT_TYPE_PATTERN)
63     boxed = g_value_dup_boxed (&temp);
64   else
65     pattern = g_value_dup_boxed (&temp);
66
67   g_value_unset (&temp);
68   g_value_init (&temp, GTK_TYPE_BORDER);
69
70   if (!_gtk_css_style_parse_value (&temp, parser, base))
71     goto out;
72
73   parsed_slice = g_value_get_boxed (&temp);
74   slice = *parsed_slice;
75
76   if (_gtk_css_parser_try (parser, "/", TRUE))
77     {
78       g_value_unset (&temp);
79       g_value_init (&temp, GTK_TYPE_BORDER);
80
81       if (!_gtk_css_style_parse_value (&temp, parser, base))
82         goto out;
83
84       width = g_value_dup_boxed (&temp);
85     }
86
87   g_value_unset (&temp);
88   g_value_init (&temp, GTK_TYPE_CSS_BORDER_IMAGE_REPEAT);
89
90   if (!_gtk_css_style_parse_value (&temp, parser, base))
91     goto out;
92
93   parsed_repeat = g_value_get_boxed (&temp);
94   repeat = *parsed_repeat;
95
96   g_value_unset (&temp);
97
98   if (boxed != NULL)
99     image = _gtk_border_image_new_for_boxed (boxed_type, boxed, &slice, width, &repeat);
100   else if (pattern != NULL)
101     image = _gtk_border_image_new (pattern, &slice, width, &repeat);
102
103   if (image != NULL)
104     {
105       retval = TRUE;
106       g_value_take_boxed (value, image);
107     }
108
109  out:
110   if (pattern != NULL)
111     cairo_pattern_destroy (pattern);
112
113   if (boxed != NULL)
114     g_boxed_free (boxed_type, boxed);
115
116   if (width != NULL)
117     gtk_border_free (width);
118
119   return retval;
120 }
121
122 static gboolean 
123 border_radius_value_parse (GtkCssParser *parser,
124                            GFile        *base,
125                            GValue       *value)
126 {
127   GtkCssBorderRadius border;
128
129   if (!_gtk_css_parser_try_double (parser, &border.top_left.horizontal))
130     {
131       _gtk_css_parser_error (parser, "Expected a number");
132       return FALSE;
133     }
134   else if (border.top_left.horizontal < 0)
135     goto negative;
136
137   if (_gtk_css_parser_try_double (parser, &border.top_right.horizontal))
138     {
139       if (border.top_right.horizontal < 0)
140         goto negative;
141       if (_gtk_css_parser_try_double (parser, &border.bottom_right.horizontal))
142         {
143           if (border.bottom_right.horizontal < 0)
144             goto negative;
145           if (!_gtk_css_parser_try_double (parser, &border.bottom_left.horizontal))
146             border.bottom_left.horizontal = border.top_right.horizontal;
147           else if (border.bottom_left.horizontal < 0)
148             goto negative;
149         }
150       else
151         {
152           border.bottom_right.horizontal = border.top_left.horizontal;
153           border.bottom_left.horizontal = border.top_right.horizontal;
154         }
155     }
156   else
157     {
158       border.top_right.horizontal = border.top_left.horizontal;
159       border.bottom_right.horizontal = border.top_left.horizontal;
160       border.bottom_left.horizontal = border.top_left.horizontal;
161     }
162
163   if (_gtk_css_parser_try (parser, "/", TRUE))
164     {
165       if (!_gtk_css_parser_try_double (parser, &border.top_left.vertical))
166         {
167           _gtk_css_parser_error (parser, "Expected a number");
168           return FALSE;
169         }
170       else if (border.top_left.vertical < 0)
171         goto negative;
172
173       if (_gtk_css_parser_try_double (parser, &border.top_right.vertical))
174         {
175           if (border.top_right.vertical < 0)
176             goto negative;
177           if (_gtk_css_parser_try_double (parser, &border.bottom_right.vertical))
178             {
179               if (border.bottom_right.vertical < 0)
180                 goto negative;
181               if (!_gtk_css_parser_try_double (parser, &border.bottom_left.vertical))
182                 border.bottom_left.vertical = border.top_right.vertical;
183               else if (border.bottom_left.vertical < 0)
184                 goto negative;
185             }
186           else
187             {
188               border.bottom_right.vertical = border.top_left.vertical;
189               border.bottom_left.vertical = border.top_right.vertical;
190             }
191         }
192       else
193         {
194           border.top_right.vertical = border.top_left.vertical;
195           border.bottom_right.vertical = border.top_left.vertical;
196           border.bottom_left.vertical = border.top_left.vertical;
197         }
198     }
199   else
200     {
201       border.top_left.vertical = border.top_left.horizontal;
202       border.top_right.vertical = border.top_right.horizontal;
203       border.bottom_right.vertical = border.bottom_right.horizontal;
204       border.bottom_left.vertical = border.bottom_left.horizontal;
205     }
206
207   /* border-radius is an int property for backwards-compat reasons */
208   g_value_unset (value);
209   g_value_init (value, GTK_TYPE_CSS_BORDER_RADIUS);
210   g_value_set_boxed (value, &border);
211
212   return TRUE;
213
214 negative:
215   _gtk_css_parser_error (parser, "Border radius values cannot be negative");
216   return FALSE;
217 }
218
219 static gboolean 
220 border_color_shorthand_value_parse (GtkCssParser *parser,
221                                     GFile        *base,
222                                     GValue       *value)
223 {
224   GtkSymbolicColor *symbolic;
225   GPtrArray *array;
226
227   array = g_ptr_array_new_with_free_func ((GDestroyNotify) gtk_symbolic_color_unref);
228
229   do
230     {
231       if (_gtk_css_parser_try (parser, "transparent", TRUE))
232         {
233           GdkRGBA transparent = { 0, 0, 0, 0 };
234           
235           symbolic = gtk_symbolic_color_new_literal (&transparent);
236         }
237       else
238         {
239           symbolic = _gtk_css_parser_read_symbolic_color (parser);
240       
241           if (symbolic == NULL)
242             return FALSE;
243         }
244       
245       g_ptr_array_add (array, symbolic);
246     }
247   while (array->len < 4 && 
248          !_gtk_css_parser_is_eof (parser) &&
249          !_gtk_css_parser_begins_with (parser, ';') &&
250          !_gtk_css_parser_begins_with (parser, '}'));
251
252   switch (array->len)
253     {
254       default:
255         g_assert_not_reached ();
256         break;
257       case 1:
258         g_ptr_array_add (array, gtk_symbolic_color_ref (g_ptr_array_index (array, 0)));
259         /* fall through */
260       case 2:
261         g_ptr_array_add (array, gtk_symbolic_color_ref (g_ptr_array_index (array, 0)));
262         /* fall through */
263       case 3:
264         g_ptr_array_add (array, gtk_symbolic_color_ref (g_ptr_array_index (array, 1)));
265         /* fall through */
266       case 4:
267         break;
268     }
269
270   g_value_unset (value);
271   g_value_init (value, G_TYPE_PTR_ARRAY);
272   g_value_take_boxed (value, array);
273
274   return TRUE;
275 }
276
277 /*** PACKING ***/
278
279 static GParameter *
280 unpack_border (const GValue *value,
281                guint        *n_params,
282                const char   *top,
283                const char   *left,
284                const char   *bottom,
285                const char   *right)
286 {
287   GParameter *parameter = g_new0 (GParameter, 4);
288   GtkBorder *border = g_value_get_boxed (value);
289
290   parameter[0].name = top;
291   g_value_init (&parameter[0].value, G_TYPE_INT);
292   g_value_set_int (&parameter[0].value, border->top);
293   parameter[1].name = left;
294   g_value_init (&parameter[1].value, G_TYPE_INT);
295   g_value_set_int (&parameter[1].value, border->left);
296   parameter[2].name = bottom;
297   g_value_init (&parameter[2].value, G_TYPE_INT);
298   g_value_set_int (&parameter[2].value, border->bottom);
299   parameter[3].name = right;
300   g_value_init (&parameter[3].value, G_TYPE_INT);
301   g_value_set_int (&parameter[3].value, border->right);
302
303   *n_params = 4;
304   return parameter;
305 }
306
307 static void
308 pack_border (GValue             *value,
309              GtkStyleProperties *props,
310              GtkStateFlags       state,
311              const char         *top,
312              const char         *left,
313              const char         *bottom,
314              const char         *right)
315 {
316   GtkBorder border;
317   int t, l, b, r;
318
319   gtk_style_properties_get (props,
320                             state,
321                             top, &t,
322                             left, &l,
323                             bottom, &b,
324                             right, &r,
325                             NULL);
326
327   border.top = t;
328   border.left = l;
329   border.bottom = b;
330   border.right = r;
331
332   g_value_set_boxed (value, &border);
333 }
334
335 static GParameter *
336 unpack_border_width (const GValue *value,
337                      guint        *n_params)
338 {
339   return unpack_border (value, n_params,
340                         "border-top-width", "border-left-width",
341                         "border-bottom-width", "border-right-width");
342 }
343
344 static void
345 pack_border_width (GValue             *value,
346                    GtkStyleProperties *props,
347                    GtkStateFlags       state,
348                    GtkStylePropertyContext *context)
349 {
350   pack_border (value, props, state,
351                "border-top-width", "border-left-width",
352                "border-bottom-width", "border-right-width");
353 }
354
355 static GParameter *
356 unpack_padding (const GValue *value,
357                 guint        *n_params)
358 {
359   return unpack_border (value, n_params,
360                         "padding-top", "padding-left",
361                         "padding-bottom", "padding-right");
362 }
363
364 static void
365 pack_padding (GValue             *value,
366               GtkStyleProperties *props,
367               GtkStateFlags       state,
368               GtkStylePropertyContext *context)
369 {
370   pack_border (value, props, state,
371                "padding-top", "padding-left",
372                "padding-bottom", "padding-right");
373 }
374
375 static GParameter *
376 unpack_margin (const GValue *value,
377                guint        *n_params)
378 {
379   return unpack_border (value, n_params,
380                         "margin-top", "margin-left",
381                         "margin-bottom", "margin-right");
382 }
383
384 static void
385 pack_margin (GValue             *value,
386              GtkStyleProperties *props,
387              GtkStateFlags       state,
388              GtkStylePropertyContext *context)
389 {
390   pack_border (value, props, state,
391                "margin-top", "margin-left",
392                "margin-bottom", "margin-right");
393 }
394
395 static GParameter *
396 unpack_border_radius (const GValue *value,
397                       guint        *n_params)
398 {
399   GParameter *parameter = g_new0 (GParameter, 4);
400   GtkCssBorderRadius *border;
401   
402   if (G_VALUE_HOLDS_BOXED (value))
403     border = g_value_get_boxed (value);
404   else
405     border = NULL;
406
407   parameter[0].name = "border-top-left-radius";
408   g_value_init (&parameter[0].value, GTK_TYPE_CSS_BORDER_CORNER_RADIUS);
409   parameter[1].name = "border-top-right-radius";
410   g_value_init (&parameter[1].value, GTK_TYPE_CSS_BORDER_CORNER_RADIUS);
411   parameter[2].name = "border-bottom-right-radius";
412   g_value_init (&parameter[2].value, GTK_TYPE_CSS_BORDER_CORNER_RADIUS);
413   parameter[3].name = "border-bottom-left-radius";
414   g_value_init (&parameter[3].value, GTK_TYPE_CSS_BORDER_CORNER_RADIUS);
415   if (border)
416     {
417       g_value_set_boxed (&parameter[0].value, &border->top_left);
418       g_value_set_boxed (&parameter[1].value, &border->top_right);
419       g_value_set_boxed (&parameter[2].value, &border->bottom_right);
420       g_value_set_boxed (&parameter[3].value, &border->bottom_left);
421     }
422
423   *n_params = 4;
424   return parameter;
425 }
426
427 static void
428 pack_border_radius (GValue             *value,
429                     GtkStyleProperties *props,
430                     GtkStateFlags       state,
431                     GtkStylePropertyContext *context)
432 {
433   GtkCssBorderCornerRadius *top_left;
434
435   /* NB: We are an int property, so we have to resolve to an int here.
436    * So we just resolve to an int. We pick one and stick to it.
437    * Lesson learned: Don't query border-radius shorthand, query the 
438    * real properties instead. */
439   gtk_style_properties_get (props,
440                             state,
441                             "border-top-left-radius", &top_left,
442                             NULL);
443
444   if (top_left)
445     g_value_set_int (value, top_left->horizontal);
446
447   g_free (top_left);
448 }
449
450 static GParameter *
451 unpack_font_description (const GValue *value,
452                          guint        *n_params)
453 {
454   GParameter *parameter = g_new0 (GParameter, 5);
455   PangoFontDescription *description;
456   PangoFontMask mask;
457   guint n;
458   
459   /* For backwards compat, we only unpack values that are indeed set.
460    * For strict CSS conformance we need to unpack all of them.
461    * Note that we do set all of them in the parse function, so it
462    * will not have effects when parsing CSS files. It will though
463    * for custom style providers.
464    */
465
466   description = g_value_get_boxed (value);
467   n = 0;
468
469   if (description)
470     mask = pango_font_description_get_set_fields (description);
471   else
472     mask = 0;
473
474   if (mask & PANGO_FONT_MASK_FAMILY)
475     {
476       GPtrArray *strv = g_ptr_array_new ();
477
478       g_ptr_array_add (strv, g_strdup (pango_font_description_get_family (description)));
479       g_ptr_array_add (strv, NULL);
480       parameter[n].name = "font-family";
481       g_value_init (&parameter[n].value, G_TYPE_STRV);
482       g_value_take_boxed (&parameter[n].value,
483                           g_ptr_array_free (strv, FALSE));
484       n++;
485     }
486
487   if (mask & PANGO_FONT_MASK_STYLE)
488     {
489       parameter[n].name = "font-style";
490       g_value_init (&parameter[n].value, PANGO_TYPE_STYLE);
491       g_value_set_enum (&parameter[n].value,
492                         pango_font_description_get_style (description));
493       n++;
494     }
495
496   if (mask & PANGO_FONT_MASK_VARIANT)
497     {
498       parameter[n].name = "font-variant";
499       g_value_init (&parameter[n].value, PANGO_TYPE_VARIANT);
500       g_value_set_enum (&parameter[n].value,
501                         pango_font_description_get_variant (description));
502       n++;
503     }
504
505   if (mask & PANGO_FONT_MASK_WEIGHT)
506     {
507       parameter[n].name = "font-weight";
508       g_value_init (&parameter[n].value, PANGO_TYPE_WEIGHT);
509       g_value_set_enum (&parameter[n].value,
510                         pango_font_description_get_weight (description));
511       n++;
512     }
513
514   if (mask & PANGO_FONT_MASK_SIZE)
515     {
516       parameter[n].name = "font-size";
517       g_value_init (&parameter[n].value, G_TYPE_DOUBLE);
518       g_value_set_double (&parameter[n].value,
519                           (double) pango_font_description_get_size (description) / PANGO_SCALE);
520       n++;
521     }
522
523   *n_params = n;
524
525   return parameter;
526 }
527
528 static void
529 pack_font_description (GValue             *value,
530                        GtkStyleProperties *props,
531                        GtkStateFlags       state,
532                        GtkStylePropertyContext *context)
533 {
534   PangoFontDescription *description;
535   char **families;
536   PangoStyle style;
537   PangoVariant variant;
538   PangoWeight weight;
539   double size;
540
541   gtk_style_properties_get (props,
542                             state,
543                             "font-family", &families,
544                             "font-style", &style,
545                             "font-variant", &variant,
546                             "font-weight", &weight,
547                             "font-size", &size,
548                             NULL);
549
550   description = pango_font_description_new ();
551   /* xxx: Can we set all the families here somehow? */
552   if (families)
553     pango_font_description_set_family (description, families[0]);
554   pango_font_description_set_size (description, round (size * PANGO_SCALE));
555   pango_font_description_set_style (description, style);
556   pango_font_description_set_variant (description, variant);
557   pango_font_description_set_weight (description, weight);
558
559   g_strfreev (families);
560
561   g_value_take_boxed (value, description);
562 }
563
564 static GParameter *
565 unpack_border_color (const GValue *value,
566                      guint        *n_params)
567 {
568   GParameter *parameter = g_new0 (GParameter, 4);
569   GType type;
570   
571   type = G_VALUE_TYPE (value);
572   if (type == G_TYPE_PTR_ARRAY)
573     type = GTK_TYPE_SYMBOLIC_COLOR;
574
575   parameter[0].name = "border-top-color";
576   g_value_init (&parameter[0].value, type);
577   parameter[1].name = "border-right-color";
578   g_value_init (&parameter[1].value, type);
579   parameter[2].name = "border-bottom-color";
580   g_value_init (&parameter[2].value, type);
581   parameter[3].name = "border-left-color";
582   g_value_init (&parameter[3].value, type);
583
584   if (G_VALUE_TYPE (value) == G_TYPE_PTR_ARRAY)
585     {
586       GPtrArray *array = g_value_get_boxed (value);
587       guint i;
588
589       for (i = 0; i < 4; i++)
590         g_value_set_boxed (&parameter[i].value, g_ptr_array_index (array, i));
591     }
592   else
593     {
594       /* can be RGBA or symbolic color */
595       gpointer p = g_value_get_boxed (value);
596
597       g_value_set_boxed (&parameter[0].value, p);
598       g_value_set_boxed (&parameter[1].value, p);
599       g_value_set_boxed (&parameter[2].value, p);
600       g_value_set_boxed (&parameter[3].value, p);
601     }
602
603   *n_params = 4;
604   return parameter;
605 }
606
607 static void
608 pack_border_color (GValue             *value,
609                    GtkStyleProperties *props,
610                    GtkStateFlags       state,
611                    GtkStylePropertyContext *context)
612 {
613   /* NB: We are a color property, so we have to resolve to a color here.
614    * So we just resolve to a color. We pick one and stick to it.
615    * Lesson learned: Don't query border-color shorthand, query the 
616    * real properties instead. */
617   g_value_unset (value);
618   gtk_style_properties_get_property (props, "border-top-color", state, value);
619 }
620
621 static void
622 _gtk_css_shorthand_property_register (const char                        *name,
623                                       GType                              value_type,
624                                       const char                       **subproperties,
625                                       GtkCssShorthandPropertyParseFunc   parse_func,
626                                       GtkStyleUnpackFunc                 unpack_func,
627                                       GtkStylePackFunc                   pack_func,
628                                       GtkStyleParseFunc                  old_parse_func)
629 {
630   GtkStyleProperty *node;
631
632   g_return_if_fail (pack_func != NULL);
633   g_return_if_fail (unpack_func != NULL);
634
635   node = g_object_new (GTK_TYPE_CSS_SHORTHAND_PROPERTY,
636                        "name", name,
637                        "value-type", value_type,
638                        "subproperties", subproperties,
639                        NULL);
640
641   if (parse_func)
642     GTK_CSS_SHORTHAND_PROPERTY (node)->parse = parse_func;
643   node->pack_func = pack_func;
644   node->unpack_func = unpack_func;
645   node->parse_func = old_parse_func;
646 }
647
648 void
649 _gtk_css_shorthand_property_init_properties (void)
650 {
651   const char *font_subproperties[] = { "font-family", "font-style", "font-variant", "font-weight", "font-size", NULL };
652   const char *margin_subproperties[] = { "margin-top", "margin-right", "margin-bottom", "margin-left", NULL };
653   const char *padding_subproperties[] = { "padding-top", "padding-right", "padding-bottom", "padding-left", NULL };
654   const char *border_width_subproperties[] = { "border-top-width", "border-right-width", "border-bottom-width", "border-left-width", NULL };
655   const char *border_radius_subproperties[] = { "border-top-left-radius", "border-top-right-radius",
656                                                 "border-bottom-right-radius", "border-bottom-left-radius", NULL };
657   const char *border_color_subproperties[] = { "border-top-color", "border-right-color", "border-bottom-color", "border-left-color", NULL };
658   const char *border_image_subproperties[] = { "border-image-source", "border-image-slice", "border-image-width", "border-image-repeat", NULL };
659
660   _gtk_css_shorthand_property_register   ("font",
661                                           PANGO_TYPE_FONT_DESCRIPTION,
662                                           font_subproperties,
663                                           NULL,
664                                           unpack_font_description,
665                                           pack_font_description,
666                                           NULL);
667   _gtk_css_shorthand_property_register   ("margin",
668                                           GTK_TYPE_BORDER,
669                                           margin_subproperties,
670                                           NULL,
671                                           unpack_margin,
672                                           pack_margin,
673                                           NULL);
674   _gtk_css_shorthand_property_register   ("padding",
675                                           GTK_TYPE_BORDER,
676                                           padding_subproperties,
677                                           NULL,
678                                           unpack_padding,
679                                           pack_padding,
680                                           NULL);
681   _gtk_css_shorthand_property_register   ("border-width",
682                                           GTK_TYPE_BORDER,
683                                           border_width_subproperties,
684                                           NULL,
685                                           unpack_border_width,
686                                           pack_border_width,
687                                           NULL);
688   _gtk_css_shorthand_property_register   ("border-radius",
689                                           G_TYPE_INT,
690                                           border_radius_subproperties,
691                                           NULL,
692                                           unpack_border_radius,
693                                           pack_border_radius,
694                                           border_radius_value_parse);
695   _gtk_css_shorthand_property_register   ("border-color",
696                                           GDK_TYPE_RGBA,
697                                           border_color_subproperties,
698                                           NULL,
699                                           unpack_border_color,
700                                           pack_border_color,
701                                           border_color_shorthand_value_parse);
702   _gtk_css_shorthand_property_register   ("border-image",
703                                           GTK_TYPE_BORDER_IMAGE,
704                                           border_image_subproperties,
705                                           NULL,
706                                           _gtk_border_image_unpack,
707                                           _gtk_border_image_pack,
708                                           border_image_value_parse);
709 }