]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssshorthandpropertyimpl.c
shorthand: Move border radius parsing to new parsing code
[~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 parse_border (GtkCssShorthandProperty *shorthand,
41               GValue                  *values,
42               GtkCssParser            *parser,
43               GFile                   *base)
44 {
45   GValue temp = G_VALUE_INIT;
46   GtkBorder *border;
47
48   g_value_init (&temp, GTK_TYPE_BORDER);
49   if (!_gtk_css_style_parse_value (&temp, parser, base))
50     {
51       g_value_unset (&temp);
52       return FALSE;
53     }
54
55   border = g_value_get_boxed (&temp);
56
57   g_value_init (&values[0], G_TYPE_INT);
58   g_value_init (&values[1], G_TYPE_INT);
59   g_value_init (&values[2], G_TYPE_INT);
60   g_value_init (&values[3], G_TYPE_INT);
61   g_value_set_int (&values[0], border->top);
62   g_value_set_int (&values[1], border->right);
63   g_value_set_int (&values[2], border->bottom);
64   g_value_set_int (&values[3], border->left);
65
66   g_value_unset (&temp);
67
68   return TRUE;
69 }
70                     
71 static gboolean 
72 parse_border_radius (GtkCssShorthandProperty *shorthand,
73                      GValue                  *values,
74                      GtkCssParser            *parser,
75                      GFile                   *base)
76 {
77   GtkCssBorderCornerRadius borders[4];
78   guint i;
79
80   for (i = 0; i < G_N_ELEMENTS (borders); i++)
81     {
82       if (!_gtk_css_parser_try_double (parser, &borders[i].horizontal))
83         break;
84       if (borders[i].horizontal < 0)
85         {
86           _gtk_css_parser_error (parser, "Border radius values cannot be negative");
87           return FALSE;
88         }
89     }
90
91   if (i == 0)
92     {
93       _gtk_css_parser_error (parser, "Expected a number");
94       return FALSE;
95     }
96
97   /* The magic (i - 1) >> 1 below makes it take the correct value
98    * according to spec. Feel free to check the 4 cases */
99   for (; i < G_N_ELEMENTS (borders); i++)
100     borders[i].horizontal = borders[(i - 1) >> 1].horizontal;
101
102   if (_gtk_css_parser_try (parser, "/", TRUE))
103     {
104       for (i = 0; i < G_N_ELEMENTS (borders); i++)
105         {
106           if (!_gtk_css_parser_try_double (parser, &borders[i].vertical))
107             break;
108           if (borders[i].vertical < 0)
109             {
110               _gtk_css_parser_error (parser, "Border radius values cannot be negative");
111               return FALSE;
112             }
113         }
114
115       if (i == 0)
116         {
117           _gtk_css_parser_error (parser, "Expected a number");
118           return FALSE;
119         }
120
121       for (; i < G_N_ELEMENTS (borders); i++)
122         borders[i].vertical = borders[(i - 1) >> 1].vertical;
123
124     }
125   else
126     {
127       for (i = 0; i < G_N_ELEMENTS (borders); i++)
128         borders[i].vertical = borders[i].horizontal;
129     }
130
131   for (i = 0; i < G_N_ELEMENTS (borders); i++)
132     {
133       g_value_init (&values[i], GTK_TYPE_CSS_BORDER_CORNER_RADIUS);
134       g_value_set_boxed (&values[i], &borders[i]);
135     }
136
137   return TRUE;
138 }
139
140 /*** OLD PARSING ***/
141
142 static gboolean
143 border_image_value_parse (GtkCssParser *parser,
144                           GFile *base,
145                           GValue *value)
146 {
147   GValue temp = G_VALUE_INIT;
148   cairo_pattern_t *pattern = NULL;
149   gconstpointer *boxed = NULL;
150   GType boxed_type;
151   GtkBorder slice, *width = NULL, *parsed_slice;
152   GtkCssBorderImageRepeat repeat, *parsed_repeat;
153   gboolean retval = FALSE;
154   GtkBorderImage *image = NULL;
155
156   if (_gtk_css_parser_try (parser, "none", TRUE))
157     return TRUE;
158
159   g_value_init (&temp, CAIRO_GOBJECT_TYPE_PATTERN);
160
161   if (!_gtk_css_style_parse_value (&temp, parser, base))
162     return FALSE;
163
164   boxed_type = G_VALUE_TYPE (&temp);
165   if (boxed_type != CAIRO_GOBJECT_TYPE_PATTERN)
166     boxed = g_value_dup_boxed (&temp);
167   else
168     pattern = g_value_dup_boxed (&temp);
169
170   g_value_unset (&temp);
171   g_value_init (&temp, GTK_TYPE_BORDER);
172
173   if (!_gtk_css_style_parse_value (&temp, parser, base))
174     goto out;
175
176   parsed_slice = g_value_get_boxed (&temp);
177   slice = *parsed_slice;
178
179   if (_gtk_css_parser_try (parser, "/", TRUE))
180     {
181       g_value_unset (&temp);
182       g_value_init (&temp, GTK_TYPE_BORDER);
183
184       if (!_gtk_css_style_parse_value (&temp, parser, base))
185         goto out;
186
187       width = g_value_dup_boxed (&temp);
188     }
189
190   g_value_unset (&temp);
191   g_value_init (&temp, GTK_TYPE_CSS_BORDER_IMAGE_REPEAT);
192
193   if (!_gtk_css_style_parse_value (&temp, parser, base))
194     goto out;
195
196   parsed_repeat = g_value_get_boxed (&temp);
197   repeat = *parsed_repeat;
198
199   g_value_unset (&temp);
200
201   if (boxed != NULL)
202     image = _gtk_border_image_new_for_boxed (boxed_type, boxed, &slice, width, &repeat);
203   else if (pattern != NULL)
204     image = _gtk_border_image_new (pattern, &slice, width, &repeat);
205
206   if (image != NULL)
207     {
208       retval = TRUE;
209       g_value_take_boxed (value, image);
210     }
211
212  out:
213   if (pattern != NULL)
214     cairo_pattern_destroy (pattern);
215
216   if (boxed != NULL)
217     g_boxed_free (boxed_type, boxed);
218
219   if (width != NULL)
220     gtk_border_free (width);
221
222   return retval;
223 }
224
225 static gboolean 
226 border_color_shorthand_value_parse (GtkCssParser *parser,
227                                     GFile        *base,
228                                     GValue       *value)
229 {
230   GtkSymbolicColor *symbolic;
231   GPtrArray *array;
232
233   array = g_ptr_array_new_with_free_func ((GDestroyNotify) gtk_symbolic_color_unref);
234
235   do
236     {
237       if (_gtk_css_parser_try (parser, "transparent", TRUE))
238         {
239           GdkRGBA transparent = { 0, 0, 0, 0 };
240           
241           symbolic = gtk_symbolic_color_new_literal (&transparent);
242         }
243       else
244         {
245           symbolic = _gtk_css_parser_read_symbolic_color (parser);
246       
247           if (symbolic == NULL)
248             return FALSE;
249         }
250       
251       g_ptr_array_add (array, symbolic);
252     }
253   while (array->len < 4 && 
254          !_gtk_css_parser_is_eof (parser) &&
255          !_gtk_css_parser_begins_with (parser, ';') &&
256          !_gtk_css_parser_begins_with (parser, '}'));
257
258   switch (array->len)
259     {
260       default:
261         g_assert_not_reached ();
262         break;
263       case 1:
264         g_ptr_array_add (array, gtk_symbolic_color_ref (g_ptr_array_index (array, 0)));
265         /* fall through */
266       case 2:
267         g_ptr_array_add (array, gtk_symbolic_color_ref (g_ptr_array_index (array, 0)));
268         /* fall through */
269       case 3:
270         g_ptr_array_add (array, gtk_symbolic_color_ref (g_ptr_array_index (array, 1)));
271         /* fall through */
272       case 4:
273         break;
274     }
275
276   g_value_unset (value);
277   g_value_init (value, G_TYPE_PTR_ARRAY);
278   g_value_take_boxed (value, array);
279
280   return TRUE;
281 }
282
283 /*** PACKING ***/
284
285 static GParameter *
286 unpack_border (const GValue *value,
287                guint        *n_params,
288                const char   *top,
289                const char   *left,
290                const char   *bottom,
291                const char   *right)
292 {
293   GParameter *parameter = g_new0 (GParameter, 4);
294   GtkBorder *border = g_value_get_boxed (value);
295
296   parameter[0].name = top;
297   g_value_init (&parameter[0].value, G_TYPE_INT);
298   g_value_set_int (&parameter[0].value, border->top);
299   parameter[1].name = left;
300   g_value_init (&parameter[1].value, G_TYPE_INT);
301   g_value_set_int (&parameter[1].value, border->left);
302   parameter[2].name = bottom;
303   g_value_init (&parameter[2].value, G_TYPE_INT);
304   g_value_set_int (&parameter[2].value, border->bottom);
305   parameter[3].name = right;
306   g_value_init (&parameter[3].value, G_TYPE_INT);
307   g_value_set_int (&parameter[3].value, border->right);
308
309   *n_params = 4;
310   return parameter;
311 }
312
313 static void
314 pack_border (GValue             *value,
315              GtkStyleProperties *props,
316              GtkStateFlags       state,
317              const char         *top,
318              const char         *left,
319              const char         *bottom,
320              const char         *right)
321 {
322   GtkBorder border;
323   int t, l, b, r;
324
325   gtk_style_properties_get (props,
326                             state,
327                             top, &t,
328                             left, &l,
329                             bottom, &b,
330                             right, &r,
331                             NULL);
332
333   border.top = t;
334   border.left = l;
335   border.bottom = b;
336   border.right = r;
337
338   g_value_set_boxed (value, &border);
339 }
340
341 static GParameter *
342 unpack_border_width (const GValue *value,
343                      guint        *n_params)
344 {
345   return unpack_border (value, n_params,
346                         "border-top-width", "border-left-width",
347                         "border-bottom-width", "border-right-width");
348 }
349
350 static void
351 pack_border_width (GValue             *value,
352                    GtkStyleProperties *props,
353                    GtkStateFlags       state,
354                    GtkStylePropertyContext *context)
355 {
356   pack_border (value, props, state,
357                "border-top-width", "border-left-width",
358                "border-bottom-width", "border-right-width");
359 }
360
361 static GParameter *
362 unpack_padding (const GValue *value,
363                 guint        *n_params)
364 {
365   return unpack_border (value, n_params,
366                         "padding-top", "padding-left",
367                         "padding-bottom", "padding-right");
368 }
369
370 static void
371 pack_padding (GValue             *value,
372               GtkStyleProperties *props,
373               GtkStateFlags       state,
374               GtkStylePropertyContext *context)
375 {
376   pack_border (value, props, state,
377                "padding-top", "padding-left",
378                "padding-bottom", "padding-right");
379 }
380
381 static GParameter *
382 unpack_margin (const GValue *value,
383                guint        *n_params)
384 {
385   return unpack_border (value, n_params,
386                         "margin-top", "margin-left",
387                         "margin-bottom", "margin-right");
388 }
389
390 static void
391 pack_margin (GValue             *value,
392              GtkStyleProperties *props,
393              GtkStateFlags       state,
394              GtkStylePropertyContext *context)
395 {
396   pack_border (value, props, state,
397                "margin-top", "margin-left",
398                "margin-bottom", "margin-right");
399 }
400
401 static GParameter *
402 unpack_border_radius (const GValue *value,
403                       guint        *n_params)
404 {
405   GParameter *parameter = g_new0 (GParameter, 4);
406   GtkCssBorderCornerRadius border;
407   
408   border.horizontal = border.vertical = g_value_get_int (value);
409
410   parameter[0].name = "border-top-left-radius";
411   g_value_init (&parameter[0].value, GTK_TYPE_CSS_BORDER_CORNER_RADIUS);
412   g_value_set_boxed (&parameter[0].value, &border);
413   parameter[1].name = "border-top-right-radius";
414   g_value_init (&parameter[1].value, GTK_TYPE_CSS_BORDER_CORNER_RADIUS);
415   g_value_set_boxed (&parameter[1].value, &border);
416   parameter[2].name = "border-bottom-right-radius";
417   g_value_init (&parameter[2].value, GTK_TYPE_CSS_BORDER_CORNER_RADIUS);
418   g_value_set_boxed (&parameter[2].value, &border);
419   parameter[3].name = "border-bottom-left-radius";
420   g_value_init (&parameter[3].value, GTK_TYPE_CSS_BORDER_CORNER_RADIUS);
421   g_value_set_boxed (&parameter[3].value, &border);
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   /* The order is important here, be careful when changing it */
652   const char *font_subproperties[] = { "font-family", "font-style", "font-variant", "font-weight", "font-size", NULL };
653   const char *margin_subproperties[] = { "margin-top", "margin-right", "margin-bottom", "margin-left", NULL };
654   const char *padding_subproperties[] = { "padding-top", "padding-right", "padding-bottom", "padding-left", NULL };
655   const char *border_width_subproperties[] = { "border-top-width", "border-right-width", "border-bottom-width", "border-left-width", NULL };
656   const char *border_radius_subproperties[] = { "border-top-left-radius", "border-top-right-radius",
657                                                 "border-bottom-right-radius", "border-bottom-left-radius", NULL };
658   const char *border_color_subproperties[] = { "border-top-color", "border-right-color", "border-bottom-color", "border-left-color", NULL };
659   const char *border_image_subproperties[] = { "border-image-source", "border-image-slice", "border-image-width", "border-image-repeat", NULL };
660
661   _gtk_css_shorthand_property_register   ("font",
662                                           PANGO_TYPE_FONT_DESCRIPTION,
663                                           font_subproperties,
664                                           NULL,
665                                           unpack_font_description,
666                                           pack_font_description,
667                                           NULL);
668   _gtk_css_shorthand_property_register   ("margin",
669                                           GTK_TYPE_BORDER,
670                                           margin_subproperties,
671                                           parse_border,
672                                           unpack_margin,
673                                           pack_margin,
674                                           NULL);
675   _gtk_css_shorthand_property_register   ("padding",
676                                           GTK_TYPE_BORDER,
677                                           padding_subproperties,
678                                           parse_border,
679                                           unpack_padding,
680                                           pack_padding,
681                                           NULL);
682   _gtk_css_shorthand_property_register   ("border-width",
683                                           GTK_TYPE_BORDER,
684                                           border_width_subproperties,
685                                           parse_border,
686                                           unpack_border_width,
687                                           pack_border_width,
688                                           NULL);
689   _gtk_css_shorthand_property_register   ("border-radius",
690                                           G_TYPE_INT,
691                                           border_radius_subproperties,
692                                           parse_border_radius,
693                                           unpack_border_radius,
694                                           pack_border_radius,
695                                           NULL);
696   _gtk_css_shorthand_property_register   ("border-color",
697                                           GDK_TYPE_RGBA,
698                                           border_color_subproperties,
699                                           NULL,
700                                           unpack_border_color,
701                                           pack_border_color,
702                                           border_color_shorthand_value_parse);
703   _gtk_css_shorthand_property_register   ("border-image",
704                                           GTK_TYPE_BORDER_IMAGE,
705                                           border_image_subproperties,
706                                           NULL,
707                                           _gtk_border_image_unpack,
708                                           _gtk_border_image_pack,
709                                           border_image_value_parse);
710 }