]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssshorthandpropertyimpl.c
shorthand: Move border-color 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 static gboolean 
141 parse_border_color (GtkCssShorthandProperty *shorthand,
142                     GValue                  *values,
143                     GtkCssParser            *parser,
144                     GFile                   *base)
145 {
146   GtkSymbolicColor *symbolic;
147   guint i;
148
149   for (i = 0; i < 4; i++)
150     {
151       symbolic = _gtk_css_parser_read_symbolic_color (parser);
152       if (symbolic == NULL)
153         return FALSE;
154
155       g_value_init (&values[i], GTK_TYPE_SYMBOLIC_COLOR);
156       g_value_set_boxed (&values[i], symbolic);
157
158       if (_gtk_css_parser_is_eof (parser) ||
159           _gtk_css_parser_begins_with (parser, ';') ||
160           _gtk_css_parser_begins_with (parser, '}'))
161         break;
162     }
163
164   for (i++; i < 4; i++)
165     {
166       g_value_init (&values[i], GTK_TYPE_SYMBOLIC_COLOR);
167       g_value_copy (&values[(i - 1) >> 1], &values[i]);
168     }
169
170   return TRUE;
171 }
172
173 /*** OLD PARSING ***/
174
175 static gboolean
176 border_image_value_parse (GtkCssParser *parser,
177                           GFile *base,
178                           GValue *value)
179 {
180   GValue temp = G_VALUE_INIT;
181   cairo_pattern_t *pattern = NULL;
182   gconstpointer *boxed = NULL;
183   GType boxed_type;
184   GtkBorder slice, *width = NULL, *parsed_slice;
185   GtkCssBorderImageRepeat repeat, *parsed_repeat;
186   gboolean retval = FALSE;
187   GtkBorderImage *image = NULL;
188
189   if (_gtk_css_parser_try (parser, "none", TRUE))
190     return TRUE;
191
192   g_value_init (&temp, CAIRO_GOBJECT_TYPE_PATTERN);
193
194   if (!_gtk_css_style_parse_value (&temp, parser, base))
195     return FALSE;
196
197   boxed_type = G_VALUE_TYPE (&temp);
198   if (boxed_type != CAIRO_GOBJECT_TYPE_PATTERN)
199     boxed = g_value_dup_boxed (&temp);
200   else
201     pattern = g_value_dup_boxed (&temp);
202
203   g_value_unset (&temp);
204   g_value_init (&temp, GTK_TYPE_BORDER);
205
206   if (!_gtk_css_style_parse_value (&temp, parser, base))
207     goto out;
208
209   parsed_slice = g_value_get_boxed (&temp);
210   slice = *parsed_slice;
211
212   if (_gtk_css_parser_try (parser, "/", TRUE))
213     {
214       g_value_unset (&temp);
215       g_value_init (&temp, GTK_TYPE_BORDER);
216
217       if (!_gtk_css_style_parse_value (&temp, parser, base))
218         goto out;
219
220       width = g_value_dup_boxed (&temp);
221     }
222
223   g_value_unset (&temp);
224   g_value_init (&temp, GTK_TYPE_CSS_BORDER_IMAGE_REPEAT);
225
226   if (!_gtk_css_style_parse_value (&temp, parser, base))
227     goto out;
228
229   parsed_repeat = g_value_get_boxed (&temp);
230   repeat = *parsed_repeat;
231
232   g_value_unset (&temp);
233
234   if (boxed != NULL)
235     image = _gtk_border_image_new_for_boxed (boxed_type, boxed, &slice, width, &repeat);
236   else if (pattern != NULL)
237     image = _gtk_border_image_new (pattern, &slice, width, &repeat);
238
239   if (image != NULL)
240     {
241       retval = TRUE;
242       g_value_take_boxed (value, image);
243     }
244
245  out:
246   if (pattern != NULL)
247     cairo_pattern_destroy (pattern);
248
249   if (boxed != NULL)
250     g_boxed_free (boxed_type, boxed);
251
252   if (width != NULL)
253     gtk_border_free (width);
254
255   return retval;
256 }
257
258 /*** PACKING ***/
259
260 static GParameter *
261 unpack_border (const GValue *value,
262                guint        *n_params,
263                const char   *top,
264                const char   *left,
265                const char   *bottom,
266                const char   *right)
267 {
268   GParameter *parameter = g_new0 (GParameter, 4);
269   GtkBorder *border = g_value_get_boxed (value);
270
271   parameter[0].name = top;
272   g_value_init (&parameter[0].value, G_TYPE_INT);
273   g_value_set_int (&parameter[0].value, border->top);
274   parameter[1].name = left;
275   g_value_init (&parameter[1].value, G_TYPE_INT);
276   g_value_set_int (&parameter[1].value, border->left);
277   parameter[2].name = bottom;
278   g_value_init (&parameter[2].value, G_TYPE_INT);
279   g_value_set_int (&parameter[2].value, border->bottom);
280   parameter[3].name = right;
281   g_value_init (&parameter[3].value, G_TYPE_INT);
282   g_value_set_int (&parameter[3].value, border->right);
283
284   *n_params = 4;
285   return parameter;
286 }
287
288 static void
289 pack_border (GValue             *value,
290              GtkStyleProperties *props,
291              GtkStateFlags       state,
292              const char         *top,
293              const char         *left,
294              const char         *bottom,
295              const char         *right)
296 {
297   GtkBorder border;
298   int t, l, b, r;
299
300   gtk_style_properties_get (props,
301                             state,
302                             top, &t,
303                             left, &l,
304                             bottom, &b,
305                             right, &r,
306                             NULL);
307
308   border.top = t;
309   border.left = l;
310   border.bottom = b;
311   border.right = r;
312
313   g_value_set_boxed (value, &border);
314 }
315
316 static GParameter *
317 unpack_border_width (const GValue *value,
318                      guint        *n_params)
319 {
320   return unpack_border (value, n_params,
321                         "border-top-width", "border-left-width",
322                         "border-bottom-width", "border-right-width");
323 }
324
325 static void
326 pack_border_width (GValue             *value,
327                    GtkStyleProperties *props,
328                    GtkStateFlags       state,
329                    GtkStylePropertyContext *context)
330 {
331   pack_border (value, props, state,
332                "border-top-width", "border-left-width",
333                "border-bottom-width", "border-right-width");
334 }
335
336 static GParameter *
337 unpack_padding (const GValue *value,
338                 guint        *n_params)
339 {
340   return unpack_border (value, n_params,
341                         "padding-top", "padding-left",
342                         "padding-bottom", "padding-right");
343 }
344
345 static void
346 pack_padding (GValue             *value,
347               GtkStyleProperties *props,
348               GtkStateFlags       state,
349               GtkStylePropertyContext *context)
350 {
351   pack_border (value, props, state,
352                "padding-top", "padding-left",
353                "padding-bottom", "padding-right");
354 }
355
356 static GParameter *
357 unpack_margin (const GValue *value,
358                guint        *n_params)
359 {
360   return unpack_border (value, n_params,
361                         "margin-top", "margin-left",
362                         "margin-bottom", "margin-right");
363 }
364
365 static void
366 pack_margin (GValue             *value,
367              GtkStyleProperties *props,
368              GtkStateFlags       state,
369              GtkStylePropertyContext *context)
370 {
371   pack_border (value, props, state,
372                "margin-top", "margin-left",
373                "margin-bottom", "margin-right");
374 }
375
376 static GParameter *
377 unpack_border_radius (const GValue *value,
378                       guint        *n_params)
379 {
380   GParameter *parameter = g_new0 (GParameter, 4);
381   GtkCssBorderCornerRadius border;
382   
383   border.horizontal = border.vertical = g_value_get_int (value);
384
385   parameter[0].name = "border-top-left-radius";
386   g_value_init (&parameter[0].value, GTK_TYPE_CSS_BORDER_CORNER_RADIUS);
387   g_value_set_boxed (&parameter[0].value, &border);
388   parameter[1].name = "border-top-right-radius";
389   g_value_init (&parameter[1].value, GTK_TYPE_CSS_BORDER_CORNER_RADIUS);
390   g_value_set_boxed (&parameter[1].value, &border);
391   parameter[2].name = "border-bottom-right-radius";
392   g_value_init (&parameter[2].value, GTK_TYPE_CSS_BORDER_CORNER_RADIUS);
393   g_value_set_boxed (&parameter[2].value, &border);
394   parameter[3].name = "border-bottom-left-radius";
395   g_value_init (&parameter[3].value, GTK_TYPE_CSS_BORDER_CORNER_RADIUS);
396   g_value_set_boxed (&parameter[3].value, &border);
397
398   *n_params = 4;
399   return parameter;
400 }
401
402 static void
403 pack_border_radius (GValue             *value,
404                     GtkStyleProperties *props,
405                     GtkStateFlags       state,
406                     GtkStylePropertyContext *context)
407 {
408   GtkCssBorderCornerRadius *top_left;
409
410   /* NB: We are an int property, so we have to resolve to an int here.
411    * So we just resolve to an int. We pick one and stick to it.
412    * Lesson learned: Don't query border-radius shorthand, query the 
413    * real properties instead. */
414   gtk_style_properties_get (props,
415                             state,
416                             "border-top-left-radius", &top_left,
417                             NULL);
418
419   if (top_left)
420     g_value_set_int (value, top_left->horizontal);
421
422   g_free (top_left);
423 }
424
425 static GParameter *
426 unpack_font_description (const GValue *value,
427                          guint        *n_params)
428 {
429   GParameter *parameter = g_new0 (GParameter, 5);
430   PangoFontDescription *description;
431   PangoFontMask mask;
432   guint n;
433   
434   /* For backwards compat, we only unpack values that are indeed set.
435    * For strict CSS conformance we need to unpack all of them.
436    * Note that we do set all of them in the parse function, so it
437    * will not have effects when parsing CSS files. It will though
438    * for custom style providers.
439    */
440
441   description = g_value_get_boxed (value);
442   n = 0;
443
444   if (description)
445     mask = pango_font_description_get_set_fields (description);
446   else
447     mask = 0;
448
449   if (mask & PANGO_FONT_MASK_FAMILY)
450     {
451       GPtrArray *strv = g_ptr_array_new ();
452
453       g_ptr_array_add (strv, g_strdup (pango_font_description_get_family (description)));
454       g_ptr_array_add (strv, NULL);
455       parameter[n].name = "font-family";
456       g_value_init (&parameter[n].value, G_TYPE_STRV);
457       g_value_take_boxed (&parameter[n].value,
458                           g_ptr_array_free (strv, FALSE));
459       n++;
460     }
461
462   if (mask & PANGO_FONT_MASK_STYLE)
463     {
464       parameter[n].name = "font-style";
465       g_value_init (&parameter[n].value, PANGO_TYPE_STYLE);
466       g_value_set_enum (&parameter[n].value,
467                         pango_font_description_get_style (description));
468       n++;
469     }
470
471   if (mask & PANGO_FONT_MASK_VARIANT)
472     {
473       parameter[n].name = "font-variant";
474       g_value_init (&parameter[n].value, PANGO_TYPE_VARIANT);
475       g_value_set_enum (&parameter[n].value,
476                         pango_font_description_get_variant (description));
477       n++;
478     }
479
480   if (mask & PANGO_FONT_MASK_WEIGHT)
481     {
482       parameter[n].name = "font-weight";
483       g_value_init (&parameter[n].value, PANGO_TYPE_WEIGHT);
484       g_value_set_enum (&parameter[n].value,
485                         pango_font_description_get_weight (description));
486       n++;
487     }
488
489   if (mask & PANGO_FONT_MASK_SIZE)
490     {
491       parameter[n].name = "font-size";
492       g_value_init (&parameter[n].value, G_TYPE_DOUBLE);
493       g_value_set_double (&parameter[n].value,
494                           (double) pango_font_description_get_size (description) / PANGO_SCALE);
495       n++;
496     }
497
498   *n_params = n;
499
500   return parameter;
501 }
502
503 static void
504 pack_font_description (GValue             *value,
505                        GtkStyleProperties *props,
506                        GtkStateFlags       state,
507                        GtkStylePropertyContext *context)
508 {
509   PangoFontDescription *description;
510   char **families;
511   PangoStyle style;
512   PangoVariant variant;
513   PangoWeight weight;
514   double size;
515
516   gtk_style_properties_get (props,
517                             state,
518                             "font-family", &families,
519                             "font-style", &style,
520                             "font-variant", &variant,
521                             "font-weight", &weight,
522                             "font-size", &size,
523                             NULL);
524
525   description = pango_font_description_new ();
526   /* xxx: Can we set all the families here somehow? */
527   if (families)
528     pango_font_description_set_family (description, families[0]);
529   pango_font_description_set_size (description, round (size * PANGO_SCALE));
530   pango_font_description_set_style (description, style);
531   pango_font_description_set_variant (description, variant);
532   pango_font_description_set_weight (description, weight);
533
534   g_strfreev (families);
535
536   g_value_take_boxed (value, description);
537 }
538
539 static GParameter *
540 unpack_border_color (const GValue *value,
541                      guint        *n_params)
542 {
543   GParameter *parameter = g_new0 (GParameter, 4);
544   GType type;
545   
546   type = G_VALUE_TYPE (value);
547   if (type == G_TYPE_PTR_ARRAY)
548     type = GTK_TYPE_SYMBOLIC_COLOR;
549
550   parameter[0].name = "border-top-color";
551   g_value_init (&parameter[0].value, type);
552   parameter[1].name = "border-right-color";
553   g_value_init (&parameter[1].value, type);
554   parameter[2].name = "border-bottom-color";
555   g_value_init (&parameter[2].value, type);
556   parameter[3].name = "border-left-color";
557   g_value_init (&parameter[3].value, type);
558
559   if (G_VALUE_TYPE (value) == G_TYPE_PTR_ARRAY)
560     {
561       GPtrArray *array = g_value_get_boxed (value);
562       guint i;
563
564       for (i = 0; i < 4; i++)
565         g_value_set_boxed (&parameter[i].value, g_ptr_array_index (array, i));
566     }
567   else
568     {
569       /* can be RGBA or symbolic color */
570       gpointer p = g_value_get_boxed (value);
571
572       g_value_set_boxed (&parameter[0].value, p);
573       g_value_set_boxed (&parameter[1].value, p);
574       g_value_set_boxed (&parameter[2].value, p);
575       g_value_set_boxed (&parameter[3].value, p);
576     }
577
578   *n_params = 4;
579   return parameter;
580 }
581
582 static void
583 pack_border_color (GValue             *value,
584                    GtkStyleProperties *props,
585                    GtkStateFlags       state,
586                    GtkStylePropertyContext *context)
587 {
588   /* NB: We are a color property, so we have to resolve to a color here.
589    * So we just resolve to a color. We pick one and stick to it.
590    * Lesson learned: Don't query border-color shorthand, query the 
591    * real properties instead. */
592   g_value_unset (value);
593   gtk_style_properties_get_property (props, "border-top-color", state, value);
594 }
595
596 static void
597 _gtk_css_shorthand_property_register (const char                        *name,
598                                       GType                              value_type,
599                                       const char                       **subproperties,
600                                       GtkCssShorthandPropertyParseFunc   parse_func,
601                                       GtkStyleUnpackFunc                 unpack_func,
602                                       GtkStylePackFunc                   pack_func,
603                                       GtkStyleParseFunc                  old_parse_func)
604 {
605   GtkStyleProperty *node;
606
607   g_return_if_fail (pack_func != NULL);
608   g_return_if_fail (unpack_func != NULL);
609
610   node = g_object_new (GTK_TYPE_CSS_SHORTHAND_PROPERTY,
611                        "name", name,
612                        "value-type", value_type,
613                        "subproperties", subproperties,
614                        NULL);
615
616   if (parse_func)
617     GTK_CSS_SHORTHAND_PROPERTY (node)->parse = parse_func;
618   node->pack_func = pack_func;
619   node->unpack_func = unpack_func;
620   node->parse_func = old_parse_func;
621 }
622
623 void
624 _gtk_css_shorthand_property_init_properties (void)
625 {
626   /* The order is important here, be careful when changing it */
627   const char *font_subproperties[] = { "font-family", "font-style", "font-variant", "font-weight", "font-size", NULL };
628   const char *margin_subproperties[] = { "margin-top", "margin-right", "margin-bottom", "margin-left", NULL };
629   const char *padding_subproperties[] = { "padding-top", "padding-right", "padding-bottom", "padding-left", NULL };
630   const char *border_width_subproperties[] = { "border-top-width", "border-right-width", "border-bottom-width", "border-left-width", NULL };
631   const char *border_radius_subproperties[] = { "border-top-left-radius", "border-top-right-radius",
632                                                 "border-bottom-right-radius", "border-bottom-left-radius", NULL };
633   const char *border_color_subproperties[] = { "border-top-color", "border-right-color", "border-bottom-color", "border-left-color", NULL };
634   const char *border_image_subproperties[] = { "border-image-source", "border-image-slice", "border-image-width", "border-image-repeat", NULL };
635
636   _gtk_css_shorthand_property_register   ("font",
637                                           PANGO_TYPE_FONT_DESCRIPTION,
638                                           font_subproperties,
639                                           NULL,
640                                           unpack_font_description,
641                                           pack_font_description,
642                                           NULL);
643   _gtk_css_shorthand_property_register   ("margin",
644                                           GTK_TYPE_BORDER,
645                                           margin_subproperties,
646                                           parse_border,
647                                           unpack_margin,
648                                           pack_margin,
649                                           NULL);
650   _gtk_css_shorthand_property_register   ("padding",
651                                           GTK_TYPE_BORDER,
652                                           padding_subproperties,
653                                           parse_border,
654                                           unpack_padding,
655                                           pack_padding,
656                                           NULL);
657   _gtk_css_shorthand_property_register   ("border-width",
658                                           GTK_TYPE_BORDER,
659                                           border_width_subproperties,
660                                           parse_border,
661                                           unpack_border_width,
662                                           pack_border_width,
663                                           NULL);
664   _gtk_css_shorthand_property_register   ("border-radius",
665                                           G_TYPE_INT,
666                                           border_radius_subproperties,
667                                           parse_border_radius,
668                                           unpack_border_radius,
669                                           pack_border_radius,
670                                           NULL);
671   _gtk_css_shorthand_property_register   ("border-color",
672                                           GDK_TYPE_RGBA,
673                                           border_color_subproperties,
674                                           parse_border_color,
675                                           unpack_border_color,
676                                           pack_border_color,
677                                           NULL);
678   _gtk_css_shorthand_property_register   ("border-image",
679                                           GTK_TYPE_BORDER_IMAGE,
680                                           border_image_subproperties,
681                                           NULL,
682                                           _gtk_border_image_unpack,
683                                           _gtk_border_image_pack,
684                                           border_image_value_parse);
685 }