]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssshorthandpropertyimpl.c
borderimage: Convert to using GtkCssImage
[~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   GtkCssImage *image;
186   
187   if (_gtk_css_parser_try (parser, "none", TRUE))
188     image = NULL;
189   else
190     {
191       image = _gtk_css_image_new_parse (parser, base);
192       if (!image)
193         return FALSE;
194     }
195   g_value_init (&values[0], GTK_TYPE_CSS_IMAGE);
196   g_value_set_object (&values[0], image);
197
198   if (value_is_done_parsing (parser))
199     return TRUE;
200
201   g_value_init (&values[1], GTK_TYPE_BORDER);
202   if (!_gtk_css_style_parse_value (&values[1], parser, base))
203     return FALSE;
204
205   if (_gtk_css_parser_try (parser, "/", TRUE))
206     {
207       g_value_init (&values[2], GTK_TYPE_BORDER);
208       if (!_gtk_css_style_parse_value (&values[2], parser, base))
209         return FALSE;
210     }
211
212   if (value_is_done_parsing (parser))
213     return TRUE;
214
215   g_value_init (&values[3], GTK_TYPE_CSS_BORDER_IMAGE_REPEAT);
216   if (!_gtk_css_style_parse_value (&values[3], parser, base))
217     return FALSE;
218
219   return TRUE;
220 }
221
222 static gboolean
223 parse_font (GtkCssShorthandProperty *shorthand,
224             GValue                  *values,
225             GtkCssParser            *parser,
226             GFile                   *base)
227 {
228   PangoFontDescription *desc;
229   guint mask;
230   char *str;
231
232   str = _gtk_css_parser_read_value (parser);
233   if (str == NULL)
234     return FALSE;
235
236   desc = pango_font_description_from_string (str);
237   g_free (str);
238
239   mask = pango_font_description_get_set_fields (desc);
240
241   if (mask & PANGO_FONT_MASK_FAMILY)
242     {
243       GPtrArray *strv = g_ptr_array_new ();
244
245       g_ptr_array_add (strv, g_strdup (pango_font_description_get_family (desc)));
246       g_ptr_array_add (strv, NULL);
247       g_value_init (&values[0], G_TYPE_STRV);
248       g_value_take_boxed (&values[0], g_ptr_array_free (strv, FALSE));
249     }
250   if (mask & PANGO_FONT_MASK_STYLE)
251     {
252       g_value_init (&values[1], PANGO_TYPE_STYLE);
253       g_value_set_enum (&values[1], pango_font_description_get_style (desc));
254     }
255   if (mask & PANGO_FONT_MASK_VARIANT)
256     {
257       g_value_init (&values[2], PANGO_TYPE_VARIANT);
258       g_value_set_enum (&values[2], pango_font_description_get_variant (desc));
259     }
260   if (mask & PANGO_FONT_MASK_WEIGHT)
261     {
262       g_value_init (&values[3], PANGO_TYPE_WEIGHT);
263       g_value_set_enum (&values[3], pango_font_description_get_weight (desc));
264     }
265   if (mask & PANGO_FONT_MASK_SIZE)
266     {
267       g_value_init (&values[4], G_TYPE_DOUBLE);
268       g_value_set_double (&values[4],
269                           (double) pango_font_description_get_size (desc) / PANGO_SCALE);
270     }
271
272   pango_font_description_free (desc);
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 {
349   pack_border (value, props, state,
350                "border-top-width", "border-left-width",
351                "border-bottom-width", "border-right-width");
352 }
353
354 static GParameter *
355 unpack_padding (const GValue *value,
356                 guint        *n_params)
357 {
358   return unpack_border (value, n_params,
359                         "padding-top", "padding-left",
360                         "padding-bottom", "padding-right");
361 }
362
363 static void
364 pack_padding (GValue             *value,
365               GtkStyleProperties *props,
366               GtkStateFlags       state)
367 {
368   pack_border (value, props, state,
369                "padding-top", "padding-left",
370                "padding-bottom", "padding-right");
371 }
372
373 static GParameter *
374 unpack_margin (const GValue *value,
375                guint        *n_params)
376 {
377   return unpack_border (value, n_params,
378                         "margin-top", "margin-left",
379                         "margin-bottom", "margin-right");
380 }
381
382 static void
383 pack_margin (GValue             *value,
384              GtkStyleProperties *props,
385              GtkStateFlags       state)
386 {
387   pack_border (value, props, state,
388                "margin-top", "margin-left",
389                "margin-bottom", "margin-right");
390 }
391
392 static GParameter *
393 unpack_border_radius (const GValue *value,
394                       guint        *n_params)
395 {
396   GParameter *parameter = g_new0 (GParameter, 4);
397   GtkCssBorderCornerRadius border;
398   
399   border.horizontal = border.vertical = g_value_get_int (value);
400
401   parameter[0].name = "border-top-left-radius";
402   g_value_init (&parameter[0].value, GTK_TYPE_CSS_BORDER_CORNER_RADIUS);
403   g_value_set_boxed (&parameter[0].value, &border);
404   parameter[1].name = "border-top-right-radius";
405   g_value_init (&parameter[1].value, GTK_TYPE_CSS_BORDER_CORNER_RADIUS);
406   g_value_set_boxed (&parameter[1].value, &border);
407   parameter[2].name = "border-bottom-right-radius";
408   g_value_init (&parameter[2].value, GTK_TYPE_CSS_BORDER_CORNER_RADIUS);
409   g_value_set_boxed (&parameter[2].value, &border);
410   parameter[3].name = "border-bottom-left-radius";
411   g_value_init (&parameter[3].value, GTK_TYPE_CSS_BORDER_CORNER_RADIUS);
412   g_value_set_boxed (&parameter[3].value, &border);
413
414   *n_params = 4;
415   return parameter;
416 }
417
418 static void
419 pack_border_radius (GValue             *value,
420                     GtkStyleProperties *props,
421                     GtkStateFlags       state)
422 {
423   GtkCssBorderCornerRadius *top_left;
424
425   /* NB: We are an int property, so we have to resolve to an int here.
426    * So we just resolve to an int. We pick one and stick to it.
427    * Lesson learned: Don't query border-radius shorthand, query the 
428    * real properties instead. */
429   gtk_style_properties_get (props,
430                             state,
431                             "border-top-left-radius", &top_left,
432                             NULL);
433
434   if (top_left)
435     g_value_set_int (value, top_left->horizontal);
436
437   g_free (top_left);
438 }
439
440 static GParameter *
441 unpack_font_description (const GValue *value,
442                          guint        *n_params)
443 {
444   GParameter *parameter = g_new0 (GParameter, 5);
445   PangoFontDescription *description;
446   PangoFontMask mask;
447   guint n;
448   
449   /* For backwards compat, we only unpack values that are indeed set.
450    * For strict CSS conformance we need to unpack all of them.
451    * Note that we do set all of them in the parse function, so it
452    * will not have effects when parsing CSS files. It will though
453    * for custom style providers.
454    */
455
456   description = g_value_get_boxed (value);
457   n = 0;
458
459   if (description)
460     mask = pango_font_description_get_set_fields (description);
461   else
462     mask = 0;
463
464   if (mask & PANGO_FONT_MASK_FAMILY)
465     {
466       GPtrArray *strv = g_ptr_array_new ();
467
468       g_ptr_array_add (strv, g_strdup (pango_font_description_get_family (description)));
469       g_ptr_array_add (strv, NULL);
470       parameter[n].name = "font-family";
471       g_value_init (&parameter[n].value, G_TYPE_STRV);
472       g_value_take_boxed (&parameter[n].value,
473                           g_ptr_array_free (strv, FALSE));
474       n++;
475     }
476
477   if (mask & PANGO_FONT_MASK_STYLE)
478     {
479       parameter[n].name = "font-style";
480       g_value_init (&parameter[n].value, PANGO_TYPE_STYLE);
481       g_value_set_enum (&parameter[n].value,
482                         pango_font_description_get_style (description));
483       n++;
484     }
485
486   if (mask & PANGO_FONT_MASK_VARIANT)
487     {
488       parameter[n].name = "font-variant";
489       g_value_init (&parameter[n].value, PANGO_TYPE_VARIANT);
490       g_value_set_enum (&parameter[n].value,
491                         pango_font_description_get_variant (description));
492       n++;
493     }
494
495   if (mask & PANGO_FONT_MASK_WEIGHT)
496     {
497       parameter[n].name = "font-weight";
498       g_value_init (&parameter[n].value, PANGO_TYPE_WEIGHT);
499       g_value_set_enum (&parameter[n].value,
500                         pango_font_description_get_weight (description));
501       n++;
502     }
503
504   if (mask & PANGO_FONT_MASK_SIZE)
505     {
506       parameter[n].name = "font-size";
507       g_value_init (&parameter[n].value, G_TYPE_DOUBLE);
508       g_value_set_double (&parameter[n].value,
509                           (double) pango_font_description_get_size (description) / PANGO_SCALE);
510       n++;
511     }
512
513   *n_params = n;
514
515   return parameter;
516 }
517
518 static void
519 pack_font_description (GValue             *value,
520                        GtkStyleProperties *props,
521                        GtkStateFlags       state)
522 {
523   PangoFontDescription *description;
524   char **families;
525   PangoStyle style;
526   PangoVariant variant;
527   PangoWeight weight;
528   double size;
529
530   gtk_style_properties_get (props,
531                             state,
532                             "font-family", &families,
533                             "font-style", &style,
534                             "font-variant", &variant,
535                             "font-weight", &weight,
536                             "font-size", &size,
537                             NULL);
538
539   description = pango_font_description_new ();
540   /* xxx: Can we set all the families here somehow? */
541   if (families)
542     pango_font_description_set_family (description, families[0]);
543   pango_font_description_set_size (description, round (size * PANGO_SCALE));
544   pango_font_description_set_style (description, style);
545   pango_font_description_set_variant (description, variant);
546   pango_font_description_set_weight (description, weight);
547
548   g_strfreev (families);
549
550   g_value_take_boxed (value, description);
551 }
552
553 static GParameter *
554 unpack_border_color (const GValue *value,
555                      guint        *n_params)
556 {
557   GParameter *parameter = g_new0 (GParameter, 4);
558   GType type;
559   
560   type = G_VALUE_TYPE (value);
561   if (type == G_TYPE_PTR_ARRAY)
562     type = GTK_TYPE_SYMBOLIC_COLOR;
563
564   parameter[0].name = "border-top-color";
565   g_value_init (&parameter[0].value, type);
566   parameter[1].name = "border-right-color";
567   g_value_init (&parameter[1].value, type);
568   parameter[2].name = "border-bottom-color";
569   g_value_init (&parameter[2].value, type);
570   parameter[3].name = "border-left-color";
571   g_value_init (&parameter[3].value, type);
572
573   if (G_VALUE_TYPE (value) == G_TYPE_PTR_ARRAY)
574     {
575       GPtrArray *array = g_value_get_boxed (value);
576       guint i;
577
578       for (i = 0; i < 4; i++)
579         g_value_set_boxed (&parameter[i].value, g_ptr_array_index (array, i));
580     }
581   else
582     {
583       /* can be RGBA or symbolic color */
584       gpointer p = g_value_get_boxed (value);
585
586       g_value_set_boxed (&parameter[0].value, p);
587       g_value_set_boxed (&parameter[1].value, p);
588       g_value_set_boxed (&parameter[2].value, p);
589       g_value_set_boxed (&parameter[3].value, p);
590     }
591
592   *n_params = 4;
593   return parameter;
594 }
595
596 static void
597 pack_border_color (GValue             *value,
598                    GtkStyleProperties *props,
599                    GtkStateFlags       state)
600 {
601   /* NB: We are a color property, so we have to resolve to a color here.
602    * So we just resolve to a color. We pick one and stick to it.
603    * Lesson learned: Don't query border-color shorthand, query the 
604    * real properties instead. */
605   g_value_unset (value);
606   gtk_style_properties_get_property (props, "border-top-color", state, value);
607 }
608
609 static void
610 _gtk_css_shorthand_property_register (const char                        *name,
611                                       GType                              value_type,
612                                       const char                       **subproperties,
613                                       GtkCssShorthandPropertyParseFunc   parse_func,
614                                       GtkStyleUnpackFunc                 unpack_func,
615                                       GtkStylePackFunc                   pack_func)
616 {
617   GtkStyleProperty *node;
618
619   g_return_if_fail (pack_func != NULL);
620   g_return_if_fail (unpack_func != NULL);
621
622   node = g_object_new (GTK_TYPE_CSS_SHORTHAND_PROPERTY,
623                        "name", name,
624                        "value-type", value_type,
625                        "subproperties", subproperties,
626                        NULL);
627
628   GTK_CSS_SHORTHAND_PROPERTY (node)->parse = parse_func;
629   node->pack_func = pack_func;
630   node->unpack_func = unpack_func;
631 }
632
633 void
634 _gtk_css_shorthand_property_init_properties (void)
635 {
636   /* The order is important here, be careful when changing it */
637   const char *font_subproperties[] = { "font-family", "font-style", "font-variant", "font-weight", "font-size", NULL };
638   const char *margin_subproperties[] = { "margin-top", "margin-right", "margin-bottom", "margin-left", NULL };
639   const char *padding_subproperties[] = { "padding-top", "padding-right", "padding-bottom", "padding-left", NULL };
640   const char *border_width_subproperties[] = { "border-top-width", "border-right-width", "border-bottom-width", "border-left-width", NULL };
641   const char *border_radius_subproperties[] = { "border-top-left-radius", "border-top-right-radius",
642                                                 "border-bottom-right-radius", "border-bottom-left-radius", NULL };
643   const char *border_color_subproperties[] = { "border-top-color", "border-right-color", "border-bottom-color", "border-left-color", NULL };
644   const char *border_image_subproperties[] = { "border-image-source", "border-image-slice", "border-image-width", "border-image-repeat", NULL };
645
646   _gtk_css_shorthand_property_register   ("font",
647                                           PANGO_TYPE_FONT_DESCRIPTION,
648                                           font_subproperties,
649                                           parse_font,
650                                           unpack_font_description,
651                                           pack_font_description);
652   _gtk_css_shorthand_property_register   ("margin",
653                                           GTK_TYPE_BORDER,
654                                           margin_subproperties,
655                                           parse_border,
656                                           unpack_margin,
657                                           pack_margin);
658   _gtk_css_shorthand_property_register   ("padding",
659                                           GTK_TYPE_BORDER,
660                                           padding_subproperties,
661                                           parse_border,
662                                           unpack_padding,
663                                           pack_padding);
664   _gtk_css_shorthand_property_register   ("border-width",
665                                           GTK_TYPE_BORDER,
666                                           border_width_subproperties,
667                                           parse_border,
668                                           unpack_border_width,
669                                           pack_border_width);
670   _gtk_css_shorthand_property_register   ("border-radius",
671                                           G_TYPE_INT,
672                                           border_radius_subproperties,
673                                           parse_border_radius,
674                                           unpack_border_radius,
675                                           pack_border_radius);
676   _gtk_css_shorthand_property_register   ("border-color",
677                                           GDK_TYPE_RGBA,
678                                           border_color_subproperties,
679                                           parse_border_color,
680                                           unpack_border_color,
681                                           pack_border_color);
682   _gtk_css_shorthand_property_register   ("border-image",
683                                           GTK_TYPE_BORDER_IMAGE,
684                                           border_image_subproperties,
685                                           parse_border_image,
686                                           _gtk_border_image_unpack,
687                                           _gtk_border_image_pack);
688 }