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