No issues found
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /*
3 * st-theme-node.c: style information for one node in a tree of themed objects
4 *
5 * Copyright 2008-2010 Red Hat, Inc.
6 * Copyright 2009 Steve FrĂŠcinaux
7 * Copyright 2009, 2010 Florian MĂźllner
8 * Copyright 2010 Adel Gadllah
9 * Copyright 2010 Giovanni Campagna
10 * Copyright 2011 Quentin "Sardem FF7" Glidic
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU Lesser General Public License as
14 * published by the Free Software Foundation, either version 2.1 of
15 * the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope it will be useful, but WITHOUT ANY
18 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
20 * more details.
21 *
22 * You should have received a copy of the GNU Lesser General Public License
23 * along with this program. If not, see <http://www.gnu.org/licenses/>.
24 */
25
26 #include <stdlib.h>
27 #include <string.h>
28
29 #include "st-theme-private.h"
30 #include "st-theme-context.h"
31 #include "st-theme-node-private.h"
32
33 static void st_theme_node_init (StThemeNode *node);
34 static void st_theme_node_class_init (StThemeNodeClass *klass);
35 static void st_theme_node_dispose (GObject *object);
36 static void st_theme_node_finalize (GObject *object);
37
38 static const ClutterColor BLACK_COLOR = { 0, 0, 0, 0xff };
39 static const ClutterColor TRANSPARENT_COLOR = { 0, 0, 0, 0 };
40 static const ClutterColor DEFAULT_SUCCESS_COLOR = { 0x4e, 0x9a, 0x06, 0xff };
41 static const ClutterColor DEFAULT_WARNING_COLOR = { 0xf5, 0x79, 0x3e, 0xff };
42 static const ClutterColor DEFAULT_ERROR_COLOR = { 0xcc, 0x00, 0x00, 0xff };
43
44 extern gfloat st_slow_down_factor;
45
46 G_DEFINE_TYPE (StThemeNode, st_theme_node, G_TYPE_OBJECT)
47
48 static void
49 st_theme_node_init (StThemeNode *node)
50 {
51 node->transition_duration = -1;
52 _st_theme_node_init_drawing_state (node);
53 }
54
55 static void
56 st_theme_node_class_init (StThemeNodeClass *klass)
57 {
58 GObjectClass *object_class = G_OBJECT_CLASS (klass);
59
60 object_class->dispose = st_theme_node_dispose;
61 object_class->finalize = st_theme_node_finalize;
62 }
63
64
65 static void
66 st_theme_node_dispose (GObject *gobject)
67 {
68 StThemeNode *node = ST_THEME_NODE (gobject);
69
70 if (node->parent_node)
71 {
72 g_object_unref (node->parent_node);
73 node->parent_node = NULL;
74 }
75
76 if (node->border_image)
77 {
78 g_object_unref (node->border_image);
79 node->border_image = NULL;
80 }
81
82 if (node->icon_colors)
83 {
84 st_icon_colors_unref (node->icon_colors);
85 node->icon_colors = NULL;
86 }
87
88 g_clear_object (&node->theme);
89
90 G_OBJECT_CLASS (st_theme_node_parent_class)->dispose (gobject);
91 }
92
93 static void
94 st_theme_node_finalize (GObject *object)
95 {
96 StThemeNode *node = ST_THEME_NODE (object);
97
98 g_free (node->element_id);
99 g_free (node->element_class);
100 g_free (node->pseudo_class);
101 g_free (node->inline_style);
102
103 if (node->properties)
104 {
105 g_free (node->properties);
106 node->properties = NULL;
107 node->n_properties = 0;
108 }
109
110 if (node->inline_properties)
111 {
112 /* This destroys the list, not just the head of the list */
113 cr_declaration_destroy (node->inline_properties);
114 }
115
116 if (node->font_desc)
117 {
118 pango_font_description_free (node->font_desc);
119 node->font_desc = NULL;
120 }
121
122 if (node->box_shadow)
123 {
124 st_shadow_unref (node->box_shadow);
125 node->box_shadow = NULL;
126 }
127
128 if (node->background_image_shadow)
129 {
130 st_shadow_unref (node->background_image_shadow);
131 node->background_image_shadow = NULL;
132 }
133
134 if (node->text_shadow)
135 {
136 st_shadow_unref (node->text_shadow);
137 node->text_shadow = NULL;
138 }
139
140 if (node->background_image)
141 g_free (node->background_image);
142
143 _st_theme_node_free_drawing_state (node);
144
145 G_OBJECT_CLASS (st_theme_node_parent_class)->finalize (object);
146 }
147
148 /**
149 * st_theme_node_new:
150 * @context: the context representing global state for this themed tree
151 * @parent_node: (allow-none): the parent node of this node
152 * @theme: (allow-none): a theme (stylesheet set) that overrides the
153 * theme inherited from the parent node
154 * @element_type: the type of the GObject represented by this node
155 * in the tree (corresponding to an element if we were theming an XML
156 * document. %G_TYPE_NONE means this style was created for the stage
157 * actor and matches a selector element name of 'stage'.
158 * @element_id: (allow-none): the ID to match CSS rules against
159 * @element_class: (allow-none): a whitespace-separated list of classes
160 * to match CSS rules against
161 * @pseudo_class: (allow-none): a whitespace-separated list of pseudo-classes
162 * (like 'hover' or 'visited') to match CSS rules against
163 *
164 * Creates a new #StThemeNode. Once created, a node is immutable. Of any
165 * of the attributes of the node (like the @element_class) change the node
166 * and its child nodes must be destroyed and recreated.
167 *
168 * Return value: (transfer full): the theme node
169 */
170 StThemeNode *
171 st_theme_node_new (StThemeContext *context,
172 StThemeNode *parent_node,
173 StTheme *theme,
174 GType element_type,
175 const char *element_id,
176 const char *element_class,
177 const char *pseudo_class,
178 const char *inline_style)
179 {
180 StThemeNode *node;
181
182 g_return_val_if_fail (ST_IS_THEME_CONTEXT (context), NULL);
183 g_return_val_if_fail (parent_node == NULL || ST_IS_THEME_NODE (parent_node), NULL);
184
185 node = g_object_new (ST_TYPE_THEME_NODE, NULL);
186
187 node->context = context;
188 if (parent_node != NULL)
189 node->parent_node = g_object_ref (parent_node);
190 else
191 node->parent_node = NULL;
192
193 if (theme == NULL && parent_node != NULL)
194 theme = parent_node->theme;
195
196 if (theme != NULL)
197 node->theme = theme;
198
199 if (node->theme != NULL)
200 g_object_ref (node->theme);
201
202 node->element_type = element_type;
203 node->element_id = g_strdup (element_id);
204 node->element_class = g_strdup (element_class);
205 node->pseudo_class = g_strdup (pseudo_class);
206 node->inline_style = g_strdup (inline_style);
207
208 return node;
209 }
210
211 /**
212 * st_theme_node_get_parent:
213 * @node: a #StThemeNode
214 *
215 * Gets the parent themed element node.
216 *
217 * Return value: (transfer none): the parent #StThemeNode, or %NULL if this
218 * is the root node of the tree of theme elements.
219 */
220 StThemeNode *
221 st_theme_node_get_parent (StThemeNode *node)
222 {
223 g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
224
225 return node->parent_node;
226 }
227
228 /**
229 * st_theme_node_get_theme:
230 * @node: a #StThemeNode
231 *
232 * Gets the theme stylesheet set that styles this node
233 *
234 * Return value: (transfer none): the theme stylesheet set
235 */
236 StTheme *
237 st_theme_node_get_theme (StThemeNode *node)
238 {
239 g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
240
241 return node->theme;
242 }
243
244 GType
245 st_theme_node_get_element_type (StThemeNode *node)
246 {
247 g_return_val_if_fail (ST_IS_THEME_NODE (node), G_TYPE_NONE);
248
249 return node->element_type;
250 }
251
252 const char *
253 st_theme_node_get_element_id (StThemeNode *node)
254 {
255 g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
256
257 return node->element_id;
258 }
259
260 const char *
261 st_theme_node_get_element_class (StThemeNode *node)
262 {
263 g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
264
265 return node->element_class;
266 }
267
268 const char *
269 st_theme_node_get_pseudo_class (StThemeNode *node)
270 {
271 g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
272
273 return node->pseudo_class;
274 }
275
276 /**
277 * st_theme_node_equal:
278 * @node_a: first #StThemeNode
279 * @node_b: second #StThemeNode
280 *
281 * Compare two #StThemeNodes. Two nodes which compare equal will match
282 * the same CSS rules and have the same style properties. However, two
283 * nodes that have ended up with identical style properties do not
284 * necessarily compare equal.
285 * In detail, @node_a and @node_b are considered equal iff
286 * <itemizedlist>
287 * <listitem>
288 * <para>they share the same #StTheme and #StThemeContext</para>
289 * </listitem>
290 * <listitem>
291 * <para>they have the same parent</para>
292 * </listitem>
293 * <listitem>
294 * <para>they have the same element type</para>
295 * </listitem>
296 * <listitem>
297 * <para>their id, class, pseudo-class and inline-style match</para>
298 * </listitem>
299 * </itemizedlist>
300 *
301 * Returns: %TRUE if @node_a equals @node_b
302 */
303 gboolean
304 st_theme_node_equal (StThemeNode *node_a, StThemeNode *node_b)
305 {
306 g_return_val_if_fail (ST_IS_THEME_NODE (node_a), FALSE);
307 g_return_val_if_fail (ST_IS_THEME_NODE (node_b), FALSE);
308
309 return node_a->parent_node == node_b->parent_node &&
310 node_a->context == node_b->context &&
311 node_a->theme == node_b->theme &&
312 node_a->element_type == node_b->element_type &&
313 !g_strcmp0 (node_a->element_id, node_b->element_id) &&
314 !g_strcmp0 (node_a->element_class, node_b->element_class) &&
315 !g_strcmp0 (node_a->pseudo_class, node_b->pseudo_class) &&
316 !g_strcmp0 (node_a->inline_style, node_b->inline_style);
317 }
318
319 static void
320 ensure_properties (StThemeNode *node)
321 {
322 if (!node->properties_computed)
323 {
324 GPtrArray *properties = NULL;
325
326 node->properties_computed = TRUE;
327
328 if (node->theme)
329 properties = _st_theme_get_matched_properties (node->theme, node);
330
331 if (node->inline_style)
332 {
333 CRDeclaration *cur_decl;
334
335 if (!properties)
336 properties = g_ptr_array_new ();
337
338 node->inline_properties = _st_theme_parse_declaration_list (node->inline_style);
339 for (cur_decl = node->inline_properties; cur_decl; cur_decl = cur_decl->next)
340 g_ptr_array_add (properties, cur_decl);
341 }
342
343 if (properties)
344 {
345 node->n_properties = properties->len;
346 node->properties = (CRDeclaration **)g_ptr_array_free (properties, FALSE);
347 }
348 }
349 }
350
351 typedef enum {
352 VALUE_FOUND,
353 VALUE_NOT_FOUND,
354 VALUE_INHERIT
355 } GetFromTermResult;
356
357 static gboolean
358 term_is_inherit (CRTerm *term)
359 {
360 return (term->type == TERM_IDENT &&
361 strcmp (term->content.str->stryng->str, "inherit") == 0);
362 }
363
364 static gboolean
365 term_is_none (CRTerm *term)
366 {
367 return (term->type == TERM_IDENT &&
368 strcmp (term->content.str->stryng->str, "none") == 0);
369 }
370
371 static gboolean
372 term_is_transparent (CRTerm *term)
373 {
374 return (term->type == TERM_IDENT &&
375 strcmp (term->content.str->stryng->str, "transparent") == 0);
376 }
377
378 static int
379 color_component_from_double (double component)
380 {
381 /* We want to spread the range 0-1 equally over 0..255, but
382 * 1.0 should map to 255 not 256, so we need to special-case it.
383 * See http://people.redhat.com/otaylor/pixel-converting.html
384 * for (very) detailed discussion of related issues. */
385 if (component >= 1.0)
386 return 255;
387 else
388 return (int)(component * 256);
389 }
390
391 static GetFromTermResult
392 get_color_from_rgba_term (CRTerm *term,
393 ClutterColor *color)
394 {
395 CRTerm *arg = term->ext_content.func_param;
396 CRNum *num;
397 double r = 0, g = 0, b = 0, a = 0;
398 int i;
399
400 for (i = 0; i < 4; i++)
401 {
402 double value;
403
404 if (arg == NULL)
405 return VALUE_NOT_FOUND;
406
407 if ((i == 0 && arg->the_operator != NO_OP) ||
408 (i > 0 && arg->the_operator != COMMA))
409 return VALUE_NOT_FOUND;
410
411 if (arg->type != TERM_NUMBER)
412 return VALUE_NOT_FOUND;
413
414 num = arg->content.num;
415
416 /* For simplicity, we convert a,r,g,b to [0,1.0] floats and then
417 * convert them back below. Then when we set them on a cairo content
418 * we convert them back to floats, and then cairo converts them
419 * back to integers to pass them to X, and so forth...
420 */
421 if (i < 3)
422 {
423 if (num->type == NUM_PERCENTAGE)
424 value = num->val / 100;
425 else if (num->type == NUM_GENERIC)
426 value = num->val / 255;
427 else
428 return VALUE_NOT_FOUND;
429 }
430 else
431 {
432 if (num->type != NUM_GENERIC)
433 return VALUE_NOT_FOUND;
434
435 value = num->val;
436 }
437
438 value = CLAMP (value, 0, 1);
439
440 switch (i)
441 {
442 case 0:
443 r = value;
444 break;
445 case 1:
446 g = value;
447 break;
448 case 2:
449 b = value;
450 break;
451 case 3:
452 a = value;
453 break;
454 }
455
456 arg = arg->next;
457 }
458
459 color->red = color_component_from_double (r);
460 color->green = color_component_from_double (g);
461 color->blue = color_component_from_double (b);
462 color->alpha = color_component_from_double (a);
463
464 return VALUE_FOUND;
465 }
466
467 static GetFromTermResult
468 get_color_from_term (StThemeNode *node,
469 CRTerm *term,
470 ClutterColor *color)
471 {
472 CRRgb rgb;
473 enum CRStatus status;
474
475 /* Since libcroco doesn't know about rgba colors, it can't handle
476 * the transparent keyword
477 */
478 if (term_is_transparent (term))
479 {
480 *color = TRANSPARENT_COLOR;
481 return VALUE_FOUND;
482 }
483 /* rgba () colors - a CSS3 addition, are not supported by libcroco,
484 * but they are parsed as a "function", so we can emulate the
485 * functionality.
486 */
487 else if (term->type == TERM_FUNCTION &&
488 term->content.str &&
489 term->content.str->stryng &&
490 term->content.str->stryng->str &&
491 strcmp (term->content.str->stryng->str, "rgba") == 0)
492 {
493 return get_color_from_rgba_term (term, color);
494 }
495
496 status = cr_rgb_set_from_term (&rgb, term);
497 if (status != CR_OK)
498 return VALUE_NOT_FOUND;
499
500 if (rgb.inherit)
501 return VALUE_INHERIT;
502
503 if (rgb.is_percentage)
504 cr_rgb_compute_from_percentage (&rgb);
505
506 color->red = rgb.red;
507 color->green = rgb.green;
508 color->blue = rgb.blue;
509 color->alpha = 0xff;
510
511 return VALUE_FOUND;
512 }
513
514 /**
515 * st_theme_node_lookup_color:
516 * @node: a #StThemeNode
517 * @property_name: The name of the color property
518 * @inherit: if %TRUE, if a value is not found for the property on the
519 * node, then it will be looked up on the parent node, and then on the
520 * parent's parent, and so forth. Note that if the property has a
521 * value of 'inherit' it will be inherited even if %FALSE is passed
522 * in for @inherit; this only affects the default behavior for inheritance.
523 * @color: (out caller-allocates): location to store the color that was
524 * determined. If the property is not found, the value in this location
525 * will not be changed.
526 *
527 * Generically looks up a property containing a single color value. When
528 * specific getters (like st_theme_node_get_background_color()) exist, they
529 * should be used instead. They are cached, so more efficient, and have
530 * handling for shortcut properties and other details of CSS.
531 *
532 * See also st_theme_node_get_color(), which provides a simpler API.
533 *
534 * Return value: %TRUE if the property was found in the properties for this
535 * theme node (or in the properties of parent nodes when inheriting.)
536 */
537 gboolean
538 st_theme_node_lookup_color (StThemeNode *node,
539 const char *property_name,
540 gboolean inherit,
541 ClutterColor *color)
542 {
543
544 int i;
545
546 ensure_properties (node);
547
548 for (i = node->n_properties - 1; i >= 0; i--)
549 {
550 CRDeclaration *decl = node->properties[i];
551
552 if (strcmp (decl->property->stryng->str, property_name) == 0)
553 {
554 GetFromTermResult result = get_color_from_term (node, decl->value, color);
555 if (result == VALUE_FOUND)
556 {
557 return TRUE;
558 }
559 else if (result == VALUE_INHERIT)
560 {
561 if (node->parent_node)
562 return st_theme_node_lookup_color (node->parent_node, property_name, inherit, color);
563 else
564 break;
565 }
566 }
567 }
568
569 if (inherit && node->parent_node)
570 return st_theme_node_lookup_color (node->parent_node, property_name, inherit, color);
571
572 return FALSE;
573 }
574
575 /**
576 * st_theme_node_get_color:
577 * @node: a #StThemeNode
578 * @property_name: The name of the color property
579 * @color: (out caller-allocates): location to store the color that
580 * was determined.
581 *
582 * Generically looks up a property containing a single color value. When
583 * specific getters (like st_theme_node_get_background_color()) exist, they
584 * should be used instead. They are cached, so more efficient, and have
585 * handling for shortcut properties and other details of CSS.
586 *
587 * If @property_name is not found, a warning will be logged and a
588 * default color returned.
589 *
590 * See also st_theme_node_lookup_color(), which provides more options,
591 * and lets you handle the case where the theme does not specify the
592 * indicated color.
593 */
594 void
595 st_theme_node_get_color (StThemeNode *node,
596 const char *property_name,
597 ClutterColor *color)
598 {
599 if (!st_theme_node_lookup_color (node, property_name, FALSE, color))
600 {
601 g_warning ("Did not find color property '%s'", property_name);
602 memset (color, 0, sizeof (ClutterColor));
603 }
604 }
605
606 /**
607 * st_theme_node_lookup_double:
608 * @node: a #StThemeNode
609 * @property_name: The name of the numeric property
610 * @inherit: if %TRUE, if a value is not found for the property on the
611 * node, then it will be looked up on the parent node, and then on the
612 * parent's parent, and so forth. Note that if the property has a
613 * value of 'inherit' it will be inherited even if %FALSE is passed
614 * in for @inherit; this only affects the default behavior for inheritance.
615 * @value: (out): location to store the value that was determined.
616 * If the property is not found, the value in this location
617 * will not be changed.
618 *
619 * Generically looks up a property containing a single numeric value
620 * without units.
621 *
622 * See also st_theme_node_get_double(), which provides a simpler API.
623 *
624 * Return value: %TRUE if the property was found in the properties for this
625 * theme node (or in the properties of parent nodes when inheriting.)
626 */
627 gboolean
628 st_theme_node_lookup_double (StThemeNode *node,
629 const char *property_name,
630 gboolean inherit,
631 double *value)
632 {
633 gboolean result = FALSE;
634 int i;
635
636 ensure_properties (node);
637
638 for (i = node->n_properties - 1; i >= 0; i--)
639 {
640 CRDeclaration *decl = node->properties[i];
641
642 if (strcmp (decl->property->stryng->str, property_name) == 0)
643 {
644 CRTerm *term = decl->value;
645
646 if (term->type != TERM_NUMBER || term->content.num->type != NUM_GENERIC)
647 continue;
648
649 *value = term->content.num->val;
650 result = TRUE;
651 break;
652 }
653 }
654
655 if (!result && inherit && node->parent_node)
656 result = st_theme_node_lookup_double (node->parent_node, property_name, inherit, value);
657
658 return result;
659 }
660
661 /**
662 * st_theme_node_get_double:
663 * @node: a #StThemeNode
664 * @property_name: The name of the numeric property
665 *
666 * Generically looks up a property containing a single numeric value
667 * without units.
668 *
669 * See also st_theme_node_lookup_double(), which provides more options,
670 * and lets you handle the case where the theme does not specify the
671 * indicated value.
672 *
673 * Return value: the value found. If @property_name is not
674 * found, a warning will be logged and 0 will be returned.
675 */
676 gdouble
677 st_theme_node_get_double (StThemeNode *node,
678 const char *property_name)
679 {
680 gdouble value;
681
682 if (st_theme_node_lookup_double (node, property_name, FALSE, &value))
683 return value;
684 else
685 {
686 g_warning ("Did not find double property '%s'", property_name);
687 return 0.0;
688 }
689 }
690
691 static const PangoFontDescription *
692 get_parent_font (StThemeNode *node)
693 {
694 if (node->parent_node)
695 return st_theme_node_get_font (node->parent_node);
696 else
697 return st_theme_context_get_font (node->context);
698 }
699
700 static GetFromTermResult
701 get_length_from_term (StThemeNode *node,
702 CRTerm *term,
703 gboolean use_parent_font,
704 gdouble *length)
705 {
706 CRNum *num;
707
708 enum {
709 ABSOLUTE,
710 POINTS,
711 FONT_RELATIVE,
712 } type = ABSOLUTE;
713
714 double multiplier = 1.0;
715
716 if (term->type != TERM_NUMBER)
717 {
718 g_warning ("Ignoring length property that isn't a number");
719 return VALUE_NOT_FOUND;
720 }
721
722 num = term->content.num;
723
724 switch (num->type)
725 {
726 case NUM_LENGTH_PX:
727 type = ABSOLUTE;
728 multiplier = 1;
729 break;
730 case NUM_LENGTH_PT:
731 type = POINTS;
732 multiplier = 1;
733 break;
734 case NUM_LENGTH_IN:
735 type = POINTS;
736 multiplier = 72;
737 break;
738 case NUM_LENGTH_CM:
739 type = POINTS;
740 multiplier = 72. / 2.54;
741 break;
742 case NUM_LENGTH_MM:
743 type = POINTS;
744 multiplier = 72. / 25.4;
745 break;
746 case NUM_LENGTH_PC:
747 type = POINTS;
748 multiplier = 12. / 25.4;
749 break;
750 case NUM_LENGTH_EM:
751 {
752 type = FONT_RELATIVE;
753 multiplier = 1;
754 break;
755 }
756 case NUM_LENGTH_EX:
757 {
758 /* Doing better would require actually resolving the font description
759 * to a specific font, and Pango doesn't have an ex metric anyways,
760 * so we'd have to try and synthesize it by complicated means.
761 *
762 * The 0.5em is the CSS spec suggested thing to use when nothing
763 * better is available.
764 */
765 type = FONT_RELATIVE;
766 multiplier = 0.5;
767 break;
768 }
769
770 case NUM_INHERIT:
771 return VALUE_INHERIT;
772
773 case NUM_AUTO:
774 g_warning ("'auto' not supported for lengths");
775 return VALUE_NOT_FOUND;
776
777 case NUM_GENERIC:
778 {
779 if (num->val != 0)
780 {
781 g_warning ("length values must specify a unit");
782 return VALUE_NOT_FOUND;
783 }
784 else
785 {
786 type = ABSOLUTE;
787 multiplier = 0;
788 }
789 break;
790 }
791
792 case NUM_PERCENTAGE:
793 g_warning ("percentage lengths not currently supported");
794 return VALUE_NOT_FOUND;
795
796 case NUM_ANGLE_DEG:
797 case NUM_ANGLE_RAD:
798 case NUM_ANGLE_GRAD:
799 case NUM_TIME_MS:
800 case NUM_TIME_S:
801 case NUM_FREQ_HZ:
802 case NUM_FREQ_KHZ:
803 case NUM_UNKNOWN_TYPE:
804 case NB_NUM_TYPE:
805 g_warning ("Ignoring invalid type of number of length property");
806 return VALUE_NOT_FOUND;
807 }
808
809 switch (type)
810 {
811 case ABSOLUTE:
812 *length = num->val * multiplier;
813 break;
814 case POINTS:
815 {
816 double resolution = clutter_backend_get_resolution (clutter_get_default_backend ());
817 *length = num->val * multiplier * (resolution / 72.);
818 }
819 break;
820 case FONT_RELATIVE:
821 {
822 const PangoFontDescription *desc;
823 double font_size;
824
825 if (use_parent_font)
826 desc = get_parent_font (node);
827 else
828 desc = st_theme_node_get_font (node);
829
830 font_size = (double)pango_font_description_get_size (desc) / PANGO_SCALE;
831
832 if (pango_font_description_get_size_is_absolute (desc))
833 {
834 *length = num->val * multiplier * font_size;
835 }
836 else
837 {
838 double resolution = clutter_backend_get_resolution (clutter_get_default_backend ());
839 *length = num->val * multiplier * (resolution / 72.) * font_size;
840 }
841 }
842 break;
843 default:
844 g_assert_not_reached ();
845 }
846
847 return VALUE_FOUND;
848 }
849
850 static GetFromTermResult
851 get_length_from_term_int (StThemeNode *node,
852 CRTerm *term,
853 gboolean use_parent_font,
854 gint *length)
855 {
856 double value;
857 GetFromTermResult result;
858
859 result = get_length_from_term (node, term, use_parent_font, &value);
860 if (result == VALUE_FOUND)
861 *length = (int) (0.5 + value);
862 return result;
863 }
864
865 static GetFromTermResult
866 get_length_internal (StThemeNode *node,
867 const char *property_name,
868 const char *suffixed,
869 gdouble *length)
870 {
871 int i;
872
873 ensure_properties (node);
874
875 for (i = node->n_properties - 1; i >= 0; i--)
876 {
877 CRDeclaration *decl = node->properties[i];
878
879 if (strcmp (decl->property->stryng->str, property_name) == 0 ||
880 (suffixed != NULL && strcmp (decl->property->stryng->str, suffixed) == 0))
881 {
882 GetFromTermResult result = get_length_from_term (node, decl->value, FALSE, length);
883 if (result != VALUE_NOT_FOUND)
884 return result;
885 }
886 }
887
888 return VALUE_NOT_FOUND;
889 }
890
891 /**
892 * st_theme_node_lookup_length:
893 * @node: a #StThemeNode
894 * @property_name: The name of the length property
895 * @inherit: if %TRUE, if a value is not found for the property on the
896 * node, then it will be looked up on the parent node, and then on the
897 * parent's parent, and so forth. Note that if the property has a
898 * value of 'inherit' it will be inherited even if %FALSE is passed
899 * in for @inherit; this only affects the default behavior for inheritance.
900 * @length: (out): location to store the length that was determined.
901 * If the property is not found, the value in this location
902 * will not be changed. The returned length is resolved
903 * to pixels.
904 *
905 * Generically looks up a property containing a single length value. When
906 * specific getters (like st_theme_node_get_border_width()) exist, they
907 * should be used instead. They are cached, so more efficient, and have
908 * handling for shortcut properties and other details of CSS.
909 *
910 * See also st_theme_node_get_length(), which provides a simpler API.
911 *
912 * Return value: %TRUE if the property was found in the properties for this
913 * theme node (or in the properties of parent nodes when inheriting.)
914 */
915 gboolean
916 st_theme_node_lookup_length (StThemeNode *node,
917 const char *property_name,
918 gboolean inherit,
919 gdouble *length)
920 {
921 GetFromTermResult result = get_length_internal (node, property_name, NULL, length);
922 if (result == VALUE_FOUND)
923 return TRUE;
924 else if (result == VALUE_INHERIT)
925 inherit = TRUE;
926
927 if (inherit && node->parent_node &&
928 st_theme_node_lookup_length (node->parent_node, property_name, inherit, length))
929 return TRUE;
930 else
931 return FALSE;
932 }
933
934 /**
935 * st_theme_node_get_length:
936 * @node: a #StThemeNode
937 * @property_name: The name of the length property
938 *
939 * Generically looks up a property containing a single length value. When
940 * specific getters (like st_theme_node_get_border_width()) exist, they
941 * should be used instead. They are cached, so more efficient, and have
942 * handling for shortcut properties and other details of CSS.
943 *
944 * Unlike st_theme_node_get_color() and st_theme_node_get_double(),
945 * this does not print a warning if the property is not found; it just
946 * returns 0.
947 *
948 * See also st_theme_node_lookup_length(), which provides more options.
949 *
950 * Return value: the length, in pixels, or 0 if the property was not found.
951 */
952 gdouble
953 st_theme_node_get_length (StThemeNode *node,
954 const char *property_name)
955 {
956 gdouble length;
957
958 if (st_theme_node_lookup_length (node, property_name, FALSE, &length))
959 return length;
960 else
961 return 0.0;
962 }
963
964 static void
965 do_border_radius_term (StThemeNode *node,
966 CRTerm *term,
967 gboolean topleft,
968 gboolean topright,
969 gboolean bottomright,
970 gboolean bottomleft)
971 {
972 int value;
973
974 if (get_length_from_term_int (node, term, FALSE, &value) != VALUE_FOUND)
975 return;
976
977 if (topleft)
978 node->border_radius[ST_CORNER_TOPLEFT] = value;
979 if (topright)
980 node->border_radius[ST_CORNER_TOPRIGHT] = value;
981 if (bottomright)
982 node->border_radius[ST_CORNER_BOTTOMRIGHT] = value;
983 if (bottomleft)
984 node->border_radius[ST_CORNER_BOTTOMLEFT] = value;
985 }
986
987 static void
988 do_border_radius (StThemeNode *node,
989 CRDeclaration *decl)
990 {
991 const char *property_name = decl->property->stryng->str + 13; /* Skip 'border-radius' */
992
993 if (strcmp (property_name, "") == 0)
994 {
995 /* Slight deviation ... if we don't understand some of the terms and understand others,
996 * then we set the ones we understand and ignore the others instead of ignoring the
997 * whole thing
998 */
999 if (decl->value == NULL) /* 0 values */
1000 return;
1001 else if (decl->value->next == NULL) /* 1 value */
1002 {
1003 do_border_radius_term (node, decl->value, TRUE, TRUE, TRUE, TRUE); /* all corners */
1004 return;
1005 }
1006 else if (decl->value->next->next == NULL) /* 2 values */
1007 {
1008 do_border_radius_term (node, decl->value, TRUE, FALSE, TRUE, FALSE); /* topleft/bottomright */
1009 do_border_radius_term (node, decl->value->next, FALSE, TRUE, FALSE, TRUE); /* topright/bottomleft */
1010 }
1011 else if (decl->value->next->next->next == NULL) /* 3 values */
1012 {
1013 do_border_radius_term (node, decl->value, TRUE, FALSE, FALSE, FALSE); /* topleft */
1014 do_border_radius_term (node, decl->value->next, FALSE, TRUE, FALSE, TRUE); /* topright/bottomleft */
1015 do_border_radius_term (node, decl->value->next->next, FALSE, FALSE, TRUE, FALSE); /* bottomright */
1016 }
1017 else if (decl->value->next->next->next->next == NULL) /* 4 values */
1018 {
1019 do_border_radius_term (node, decl->value, TRUE, FALSE, FALSE, FALSE); /* topleft */
1020 do_border_radius_term (node, decl->value->next, FALSE, TRUE, FALSE, FALSE); /* topright */
1021 do_border_radius_term (node, decl->value->next->next, FALSE, FALSE, TRUE, FALSE); /* bottomright */
1022 do_border_radius_term (node, decl->value->next->next->next, FALSE, FALSE, FALSE, TRUE); /* bottomleft */
1023 }
1024 else
1025 {
1026 g_warning ("Too many values for border-radius property");
1027 return;
1028 }
1029 }
1030 else
1031 {
1032 if (decl->value == NULL || decl->value->next != NULL)
1033 return;
1034
1035 if (strcmp (property_name, "-topleft") == 0)
1036 do_border_radius_term (node, decl->value, TRUE, FALSE, FALSE, FALSE);
1037 else if (strcmp (property_name, "-topright") == 0)
1038 do_border_radius_term (node, decl->value, FALSE, TRUE, FALSE, FALSE);
1039 else if (strcmp (property_name, "-bottomright") == 0)
1040 do_border_radius_term (node, decl->value, FALSE, FALSE, TRUE, FALSE);
1041 else if (strcmp (property_name, "-bottomleft") == 0)
1042 do_border_radius_term (node, decl->value, FALSE, FALSE, FALSE, TRUE);
1043 }
1044 }
1045
1046 static void
1047 do_border_property (StThemeNode *node,
1048 CRDeclaration *decl)
1049 {
1050 const char *property_name = decl->property->stryng->str + 6; /* Skip 'border' */
1051 StSide side = (StSide)-1;
1052 ClutterColor color;
1053 gboolean color_set = FALSE;
1054 int width = 0; /* suppress warning */
1055 gboolean width_set = FALSE;
1056 int j;
1057
1058 if (g_str_has_prefix (property_name, "-radius"))
1059 {
1060 do_border_radius (node, decl);
1061 return;
1062 }
1063
1064 if (g_str_has_prefix (property_name, "-left"))
1065 {
1066 side = ST_SIDE_LEFT;
1067 property_name += 5;
1068 }
1069 else if (g_str_has_prefix (property_name, "-right"))
1070 {
1071 side = ST_SIDE_RIGHT;
1072 property_name += 6;
1073 }
1074 else if (g_str_has_prefix (property_name, "-top"))
1075 {
1076 side = ST_SIDE_TOP;
1077 property_name += 4;
1078 }
1079 else if (g_str_has_prefix (property_name, "-bottom"))
1080 {
1081 side = ST_SIDE_BOTTOM;
1082 property_name += 7;
1083 }
1084
1085 if (strcmp (property_name, "") == 0)
1086 {
1087 /* Set value for width/color/style in any order */
1088 CRTerm *term;
1089
1090 for (term = decl->value; term; term = term->next)
1091 {
1092 GetFromTermResult result;
1093
1094 if (term->type == TERM_IDENT)
1095 {
1096 const char *ident = term->content.str->stryng->str;
1097 if (strcmp (ident, "none") == 0 || strcmp (ident, "hidden") == 0)
1098 {
1099 width = 0;
1100 width_set = TRUE;
1101 continue;
1102 }
1103 else if (strcmp (ident, "solid") == 0)
1104 {
1105 /* The only thing we support */
1106 continue;
1107 }
1108 else if (strcmp (ident, "dotted") == 0 ||
1109 strcmp (ident, "dashed") == 0 ||
1110 strcmp (ident, "double") == 0 ||
1111 strcmp (ident, "groove") == 0 ||
1112 strcmp (ident, "ridge") == 0 ||
1113 strcmp (ident, "inset") == 0 ||
1114 strcmp (ident, "outset") == 0)
1115 {
1116 /* Treat the same as solid */
1117 continue;
1118 }
1119
1120 /* Presumably a color, fall through */
1121 }
1122
1123 if (term->type == TERM_NUMBER)
1124 {
1125 result = get_length_from_term_int (node, term, FALSE, &width);
1126 if (result != VALUE_NOT_FOUND)
1127 {
1128 width_set = result == VALUE_FOUND;
1129 continue;
1130 }
1131 }
1132
1133 result = get_color_from_term (node, term, &color);
1134 if (result != VALUE_NOT_FOUND)
1135 {
1136 color_set = result == VALUE_FOUND;
1137 continue;
1138 }
1139 }
1140
1141 }
1142 else if (strcmp (property_name, "-color") == 0)
1143 {
1144 if (decl->value == NULL || decl->value->next != NULL)
1145 return;
1146
1147 if (get_color_from_term (node, decl->value, &color) == VALUE_FOUND)
1148 /* Ignore inherit */
1149 color_set = TRUE;
1150 }
1151 else if (strcmp (property_name, "-width") == 0)
1152 {
1153 if (decl->value == NULL || decl->value->next != NULL)
1154 return;
1155
1156 if (get_length_from_term_int (node, decl->value, FALSE, &width) == VALUE_FOUND)
1157 /* Ignore inherit */
1158 width_set = TRUE;
1159 }
1160
1161 if (side == (StSide)-1)
1162 {
1163 for (j = 0; j < 4; j++)
1164 {
1165 if (color_set)
1166 node->border_color[j] = color;
1167 if (width_set)
1168 node->border_width[j] = width;
1169 }
1170 }
1171 else
1172 {
1173 if (color_set)
1174 node->border_color[side] = color;
1175 if (width_set)
1176 node->border_width[side] = width;
1177 }
1178 }
1179
1180 static void
1181 do_outline_property (StThemeNode *node,
1182 CRDeclaration *decl)
1183 {
1184 const char *property_name = decl->property->stryng->str + 7; /* Skip 'outline' */
1185 ClutterColor color;
1186 gboolean color_set = FALSE;
1187 int width = 0; /* suppress warning */
1188 gboolean width_set = FALSE;
1189
1190 if (strcmp (property_name, "") == 0)
1191 {
1192 /* Set value for width/color/style in any order */
1193 CRTerm *term;
1194
1195 for (term = decl->value; term; term = term->next)
1196 {
1197 GetFromTermResult result;
1198
1199 if (term->type == TERM_IDENT)
1200 {
1201 const char *ident = term->content.str->stryng->str;
1202 if (strcmp (ident, "none") == 0 || strcmp (ident, "hidden") == 0)
1203 {
1204 width = 0;
1205 width_set = TRUE;
1206 continue;
1207 }
1208 else if (strcmp (ident, "solid") == 0)
1209 {
1210 /* The only thing we support */
1211 continue;
1212 }
1213 else if (strcmp (ident, "dotted") == 0 ||
1214 strcmp (ident, "dashed") == 0 ||
1215 strcmp (ident, "double") == 0 ||
1216 strcmp (ident, "groove") == 0 ||
1217 strcmp (ident, "ridge") == 0 ||
1218 strcmp (ident, "inset") == 0 ||
1219 strcmp (ident, "outset") == 0)
1220 {
1221 /* Treat the same as solid */
1222 continue;
1223 }
1224
1225 /* Presumably a color, fall through */
1226 }
1227
1228 if (term->type == TERM_NUMBER)
1229 {
1230 result = get_length_from_term_int (node, term, FALSE, &width);
1231 if (result != VALUE_NOT_FOUND)
1232 {
1233 width_set = result == VALUE_FOUND;
1234 continue;
1235 }
1236 }
1237
1238 result = get_color_from_term (node, term, &color);
1239 if (result != VALUE_NOT_FOUND)
1240 {
1241 color_set = result == VALUE_FOUND;
1242 continue;
1243 }
1244 }
1245
1246 }
1247 else if (strcmp (property_name, "-color") == 0)
1248 {
1249 if (decl->value == NULL || decl->value->next != NULL)
1250 return;
1251
1252 if (get_color_from_term (node, decl->value, &color) == VALUE_FOUND)
1253 /* Ignore inherit */
1254 color_set = TRUE;
1255 }
1256 else if (strcmp (property_name, "-width") == 0)
1257 {
1258 if (decl->value == NULL || decl->value->next != NULL)
1259 return;
1260
1261 if (get_length_from_term_int (node, decl->value, FALSE, &width) == VALUE_FOUND)
1262 /* Ignore inherit */
1263 width_set = TRUE;
1264 }
1265
1266 if (color_set)
1267 node->outline_color = color;
1268 if (width_set)
1269 node->outline_width = width;
1270 }
1271
1272 static void
1273 do_padding_property_term (StThemeNode *node,
1274 CRTerm *term,
1275 gboolean left,
1276 gboolean right,
1277 gboolean top,
1278 gboolean bottom)
1279 {
1280 int value;
1281
1282 if (get_length_from_term_int (node, term, FALSE, &value) != VALUE_FOUND)
1283 return;
1284
1285 if (left)
1286 node->padding[ST_SIDE_LEFT] = value;
1287 if (right)
1288 node->padding[ST_SIDE_RIGHT] = value;
1289 if (top)
1290 node->padding[ST_SIDE_TOP] = value;
1291 if (bottom)
1292 node->padding[ST_SIDE_BOTTOM] = value;
1293 }
1294
1295 static void
1296 do_padding_property (StThemeNode *node,
1297 CRDeclaration *decl)
1298 {
1299 const char *property_name = decl->property->stryng->str + 7; /* Skip 'padding' */
1300
1301 if (strcmp (property_name, "") == 0)
1302 {
1303 /* Slight deviation ... if we don't understand some of the terms and understand others,
1304 * then we set the ones we understand and ignore the others instead of ignoring the
1305 * whole thing
1306 */
1307 if (decl->value == NULL) /* 0 values */
1308 return;
1309 else if (decl->value->next == NULL) /* 1 value */
1310 {
1311 do_padding_property_term (node, decl->value, TRUE, TRUE, TRUE, TRUE); /* left/right/top/bottom */
1312 return;
1313 }
1314 else if (decl->value->next->next == NULL) /* 2 values */
1315 {
1316 do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE, TRUE); /* top/bottom */
1317 do_padding_property_term (node, decl->value->next, TRUE, TRUE, FALSE, FALSE); /* left/right */
1318 }
1319 else if (decl->value->next->next->next == NULL) /* 3 values */
1320 {
1321 do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE); /* top */
1322 do_padding_property_term (node, decl->value->next, TRUE, TRUE, FALSE, FALSE); /* left/right */
1323 do_padding_property_term (node, decl->value->next->next, FALSE, FALSE, FALSE, TRUE); /* bottom */
1324 }
1325 else if (decl->value->next->next->next->next == NULL) /* 4 values */
1326 {
1327 do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE); /* top */
1328 do_padding_property_term (node, decl->value->next, FALSE, TRUE, FALSE, FALSE); /* right */
1329 do_padding_property_term (node, decl->value->next->next, FALSE, FALSE, FALSE, TRUE); /* bottom */
1330 do_padding_property_term (node, decl->value->next->next->next, TRUE, FALSE, FALSE, FALSE); /* left */
1331 }
1332 else
1333 {
1334 g_warning ("Too many values for padding property");
1335 return;
1336 }
1337 }
1338 else
1339 {
1340 if (decl->value == NULL || decl->value->next != NULL)
1341 return;
1342
1343 if (strcmp (property_name, "-left") == 0)
1344 do_padding_property_term (node, decl->value, TRUE, FALSE, FALSE, FALSE);
1345 else if (strcmp (property_name, "-right") == 0)
1346 do_padding_property_term (node, decl->value, FALSE, TRUE, FALSE, FALSE);
1347 else if (strcmp (property_name, "-top") == 0)
1348 do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE);
1349 else if (strcmp (property_name, "-bottom") == 0)
1350 do_padding_property_term (node, decl->value, FALSE, FALSE, FALSE, TRUE);
1351 }
1352 }
1353
1354 static void
1355 do_size_property (StThemeNode *node,
1356 CRDeclaration *decl,
1357 int *node_value)
1358 {
1359 get_length_from_term_int (node, decl->value, FALSE, node_value);
1360 }
1361
1362 void
1363 _st_theme_node_ensure_geometry (StThemeNode *node)
1364 {
1365 int i, j;
1366
1367 if (node->geometry_computed)
1368 return;
1369
1370 node->geometry_computed = TRUE;
1371
1372 ensure_properties (node);
1373
1374 for (j = 0; j < 4; j++)
1375 {
1376 node->border_width[j] = 0;
1377 node->border_color[j] = TRANSPARENT_COLOR;
1378 }
1379
1380 node->outline_width = 0;
1381 node->outline_color = TRANSPARENT_COLOR;
1382
1383 node->width = -1;
1384 node->height = -1;
1385 node->min_width = -1;
1386 node->min_height = -1;
1387 node->max_width = -1;
1388 node->max_height = -1;
1389
1390 for (i = 0; i < node->n_properties; i++)
1391 {
1392 CRDeclaration *decl = node->properties[i];
1393 const char *property_name = decl->property->stryng->str;
1394
1395 if (g_str_has_prefix (property_name, "border"))
1396 do_border_property (node, decl);
1397 else if (g_str_has_prefix (property_name, "outline"))
1398 do_outline_property (node, decl);
1399 else if (g_str_has_prefix (property_name, "padding"))
1400 do_padding_property (node, decl);
1401 else if (strcmp (property_name, "width") == 0)
1402 do_size_property (node, decl, &node->width);
1403 else if (strcmp (property_name, "height") == 0)
1404 do_size_property (node, decl, &node->height);
1405 else if (strcmp (property_name, "min-width") == 0)
1406 do_size_property (node, decl, &node->min_width);
1407 else if (strcmp (property_name, "min-height") == 0)
1408 do_size_property (node, decl, &node->min_height);
1409 else if (strcmp (property_name, "max-width") == 0)
1410 do_size_property (node, decl, &node->max_width);
1411 else if (strcmp (property_name, "max-height") == 0)
1412 do_size_property (node, decl, &node->max_height);
1413 }
1414
1415 if (node->width != -1)
1416 {
1417 if (node->min_width == -1)
1418 node->min_width = node->width;
1419 else if (node->width < node->min_width)
1420 node->width = node->min_width;
1421 if (node->max_width == -1)
1422 node->max_width = node->width;
1423 else if (node->width > node->max_width)
1424 node->width = node->max_width;
1425 }
1426
1427 if (node->height != -1)
1428 {
1429 if (node->min_height == -1)
1430 node->min_height = node->height;
1431 else if (node->height < node->min_height)
1432 node->height = node->min_height;
1433 if (node->max_height == -1)
1434 node->max_height = node->height;
1435 else if (node->height > node->max_height)
1436 node->height = node->max_height;
1437 }
1438 }
1439
1440 int
1441 st_theme_node_get_border_width (StThemeNode *node,
1442 StSide side)
1443 {
1444 g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
1445 g_return_val_if_fail (side >= ST_SIDE_TOP && side <= ST_SIDE_LEFT, 0.);
1446
1447 _st_theme_node_ensure_geometry (node);
1448
1449 return node->border_width[side];
1450 }
1451
1452 int
1453 st_theme_node_get_border_radius (StThemeNode *node,
1454 StCorner corner)
1455 {
1456 g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
1457 g_return_val_if_fail (corner >= ST_CORNER_TOPLEFT && corner <= ST_CORNER_BOTTOMLEFT, 0.);
1458
1459 _st_theme_node_ensure_geometry (node);
1460
1461 return node->border_radius[corner];
1462 }
1463
1464 int
1465 st_theme_node_get_outline_width (StThemeNode *node)
1466 {
1467 g_return_val_if_fail (ST_IS_THEME_NODE (node), 0);
1468
1469 _st_theme_node_ensure_geometry (node);
1470
1471 return node->outline_width;
1472 }
1473
1474 /**
1475 * st_theme_node_get_outline_color:
1476 * @node: a #StThemeNode
1477 * @color: (out caller-allocates): location to store the color
1478 *
1479 * Gets the color of @node's outline.
1480 */
1481 void
1482 st_theme_node_get_outline_color (StThemeNode *node,
1483 ClutterColor *color)
1484 {
1485 g_return_if_fail (ST_IS_THEME_NODE (node));
1486
1487 _st_theme_node_ensure_geometry (node);
1488
1489 *color = node->outline_color;
1490 }
1491
1492 int
1493 st_theme_node_get_width (StThemeNode *node)
1494 {
1495 g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
1496
1497 _st_theme_node_ensure_geometry (node);
1498 return node->width;
1499 }
1500
1501 int
1502 st_theme_node_get_height (StThemeNode *node)
1503 {
1504 g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
1505
1506 _st_theme_node_ensure_geometry (node);
1507 return node->height;
1508 }
1509
1510 int
1511 st_theme_node_get_min_width (StThemeNode *node)
1512 {
1513 g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
1514
1515 _st_theme_node_ensure_geometry (node);
1516 return node->min_width;
1517 }
1518
1519 int
1520 st_theme_node_get_min_height (StThemeNode *node)
1521 {
1522 g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
1523
1524 _st_theme_node_ensure_geometry (node);
1525 return node->min_height;
1526 }
1527
1528 int
1529 st_theme_node_get_max_width (StThemeNode *node)
1530 {
1531 g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
1532
1533 _st_theme_node_ensure_geometry (node);
1534 return node->max_width;
1535 }
1536
1537 int
1538 st_theme_node_get_max_height (StThemeNode *node)
1539 {
1540 g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
1541
1542 _st_theme_node_ensure_geometry (node);
1543 return node->max_height;
1544 }
1545
1546 static GetFromTermResult
1547 get_background_color_from_term (StThemeNode *node,
1548 CRTerm *term,
1549 ClutterColor *color)
1550 {
1551 GetFromTermResult result = get_color_from_term (node, term, color);
1552 if (result == VALUE_NOT_FOUND)
1553 {
1554 if (term_is_transparent (term))
1555 {
1556 *color = TRANSPARENT_COLOR;
1557 return VALUE_FOUND;
1558 }
1559 }
1560
1561 return result;
1562 }
1563
1564 void
1565 _st_theme_node_ensure_background (StThemeNode *node)
1566 {
1567 int i;
1568
1569 if (node->background_computed)
1570 return;
1571
1572 node->background_repeat = FALSE;
1573 node->background_computed = TRUE;
1574 node->background_color = TRANSPARENT_COLOR;
1575 node->background_gradient_type = ST_GRADIENT_NONE;
1576 node->background_position_set = FALSE;
1577 node->background_size = ST_BACKGROUND_SIZE_AUTO;
1578
1579 ensure_properties (node);
1580
1581 for (i = 0; i < node->n_properties; i++)
1582 {
1583 CRDeclaration *decl = node->properties[i];
1584 const char *property_name = decl->property->stryng->str;
1585
1586 if (g_str_has_prefix (property_name, "background"))
1587 property_name += 10;
1588 else
1589 continue;
1590
1591 if (strcmp (property_name, "") == 0)
1592 {
1593 /* We're very liberal here ... if we recognize any term in the expression we take it, and
1594 * we ignore the rest. The actual specification is:
1595 *
1596 * background: [<'background-color'> || <'background-image'> || <'background-repeat'> || <'background-attachment'> || <'background-position'>] | inherit
1597 */
1598
1599 CRTerm *term;
1600 /* background: property sets all terms to specified or default values */
1601 node->background_color = TRANSPARENT_COLOR;
1602 g_free (node->background_image);
1603 node->background_image = NULL;
1604 node->background_position_set = FALSE;
1605 node->background_size = ST_BACKGROUND_SIZE_AUTO;
1606
1607 for (term = decl->value; term; term = term->next)
1608 {
1609 GetFromTermResult result = get_background_color_from_term (node, term, &node->background_color);
1610 if (result == VALUE_FOUND)
1611 {
1612 /* color stored in node->background_color */
1613 }
1614 else if (result == VALUE_INHERIT)
1615 {
1616 if (node->parent_node)
1617 {
1618 st_theme_node_get_background_color (node->parent_node, &node->background_color);
1619 node->background_image = g_strdup (st_theme_node_get_background_image (node->parent_node));
1620 }
1621 }
1622 else if (term_is_none (term))
1623 {
1624 /* leave node->background_color as transparent */
1625 }
1626 else if (term->type == TERM_URI)
1627 {
1628 CRStyleSheet *base_stylesheet;
1629
1630 if (decl->parent_statement != NULL)
1631 base_stylesheet = decl->parent_statement->parent_sheet;
1632 else
1633 base_stylesheet = NULL;
1634
1635 node->background_image = _st_theme_resolve_url (node->theme,
1636 base_stylesheet,
1637 term->content.str->stryng->str);
1638 }
1639 }
1640 }
1641 else if (strcmp (property_name, "-position") == 0)
1642 {
1643 GetFromTermResult result = get_length_from_term_int (node, decl->value, FALSE, &node->background_position_x);
1644 if (result == VALUE_NOT_FOUND)
1645 {
1646 node->background_position_set = FALSE;
1647 continue;
1648 }
1649 else
1650 node->background_position_set = TRUE;
1651
1652 result = get_length_from_term_int (node, decl->value->next, FALSE, &node->background_position_y);
1653
1654 if (result == VALUE_NOT_FOUND)
1655 {
1656 node->background_position_set = FALSE;
1657 continue;
1658 }
1659 else
1660 node->background_position_set = TRUE;
1661 }
1662 else if (strcmp (property_name, "-repeat") == 0)
1663 {
1664 if (decl->value->type == TERM_IDENT)
1665 {
1666 if (strcmp (decl->value->content.str->stryng->str, "repeat") == 0)
1667 node->background_repeat = TRUE;
1668 }
1669 }
1670 else if (strcmp (property_name, "-size") == 0)
1671 {
1672 if (decl->value->type == TERM_IDENT)
1673 {
1674 if (strcmp (decl->value->content.str->stryng->str, "contain") == 0)
1675 node->background_size = ST_BACKGROUND_SIZE_CONTAIN;
1676 else if (strcmp (decl->value->content.str->stryng->str, "cover") == 0)
1677 node->background_size = ST_BACKGROUND_SIZE_COVER;
1678 else if ((strcmp (decl->value->content.str->stryng->str, "auto") == 0) && (decl->value->next) && (decl->value->next->type == TERM_NUMBER))
1679 {
1680 GetFromTermResult result = get_length_from_term_int (node, decl->value->next, FALSE, &node->background_size_h);
1681
1682 node->background_size_w = -1;
1683 node->background_size = (result == VALUE_FOUND) ? ST_BACKGROUND_SIZE_FIXED : ST_BACKGROUND_SIZE_AUTO;
1684 }
1685 else
1686 node->background_size = ST_BACKGROUND_SIZE_AUTO;
1687 }
1688 else if (decl->value->type == TERM_NUMBER)
1689 {
1690 GetFromTermResult result = get_length_from_term_int (node, decl->value, FALSE, &node->background_size_w);
1691 if (result == VALUE_NOT_FOUND)
1692 continue;
1693
1694 node->background_size = ST_BACKGROUND_SIZE_FIXED;
1695
1696 if ((decl->value->next) && (decl->value->next->type == TERM_NUMBER))
1697 {
1698 result = get_length_from_term_int (node, decl->value->next, FALSE, &node->background_size_h);
1699
1700 if (result == VALUE_FOUND)
1701 continue;
1702 }
1703 node->background_size_h = -1;
1704 }
1705 else
1706 node->background_size = ST_BACKGROUND_SIZE_AUTO;
1707 }
1708 else if (strcmp (property_name, "-color") == 0)
1709 {
1710 GetFromTermResult result;
1711
1712 if (decl->value == NULL || decl->value->next != NULL)
1713 continue;
1714
1715 result = get_background_color_from_term (node, decl->value, &node->background_color);
1716 if (result == VALUE_FOUND)
1717 {
1718 /* color stored in node->background_color */
1719 }
1720 else if (result == VALUE_INHERIT)
1721 {
1722 if (node->parent_node)
1723 st_theme_node_get_background_color (node->parent_node, &node->background_color);
1724 }
1725 }
1726 else if (strcmp (property_name, "-image") == 0)
1727 {
1728 if (decl->value == NULL || decl->value->next != NULL)
1729 continue;
1730
1731 if (decl->value->type == TERM_URI)
1732 {
1733 CRStyleSheet *base_stylesheet;
1734
1735 if (decl->parent_statement != NULL)
1736 base_stylesheet = decl->parent_statement->parent_sheet;
1737 else
1738 base_stylesheet = NULL;
1739
1740 g_free (node->background_image);
1741 node->background_image = _st_theme_resolve_url (node->theme,
1742 base_stylesheet,
1743 decl->value->content.str->stryng->str);
1744 }
1745 else if (term_is_inherit (decl->value))
1746 {
1747 g_free (node->background_image);
1748 node->background_image = g_strdup (st_theme_node_get_background_image (node->parent_node));
1749 }
1750 else if (term_is_none (decl->value))
1751 {
1752 g_free (node->background_image);
1753 node->background_image = NULL;
1754 }
1755 }
1756 else if (strcmp (property_name, "-gradient-direction") == 0)
1757 {
1758 CRTerm *term = decl->value;
1759 if (strcmp (term->content.str->stryng->str, "vertical") == 0)
1760 {
1761 node->background_gradient_type = ST_GRADIENT_VERTICAL;
1762 }
1763 else if (strcmp (term->content.str->stryng->str, "horizontal") == 0)
1764 {
1765 node->background_gradient_type = ST_GRADIENT_HORIZONTAL;
1766 }
1767 else if (strcmp (term->content.str->stryng->str, "radial") == 0)
1768 {
1769 node->background_gradient_type = ST_GRADIENT_RADIAL;
1770 }
1771 else if (strcmp (term->content.str->stryng->str, "none") == 0)
1772 {
1773 node->background_gradient_type = ST_GRADIENT_NONE;
1774 }
1775 else
1776 {
1777 g_warning ("Unrecognized background-gradient-direction \"%s\"",
1778 term->content.str->stryng->str);
1779 }
1780 }
1781 else if (strcmp (property_name, "-gradient-start") == 0)
1782 {
1783 get_color_from_term (node, decl->value, &node->background_color);
1784 }
1785 else if (strcmp (property_name, "-gradient-end") == 0)
1786 {
1787 get_color_from_term (node, decl->value, &node->background_gradient_end);
1788 }
1789 }
1790 }
1791
1792 /**
1793 * st_theme_node_get_background_color:
1794 * @node: a #StThemeNode
1795 * @color: (out caller-allocates): location to store the color
1796 *
1797 * Gets @node's background color.
1798 */
1799 void
1800 st_theme_node_get_background_color (StThemeNode *node,
1801 ClutterColor *color)
1802 {
1803 g_return_if_fail (ST_IS_THEME_NODE (node));
1804
1805 _st_theme_node_ensure_background (node);
1806
1807 *color = node->background_color;
1808 }
1809
1810 const char *
1811 st_theme_node_get_background_image (StThemeNode *node)
1812 {
1813 g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
1814
1815 _st_theme_node_ensure_background (node);
1816
1817 return node->background_image;
1818 }
1819
1820 /**
1821 * st_theme_node_get_foreground_color:
1822 * @node: a #StThemeNode
1823 * @color: (out caller-allocates): location to store the color
1824 *
1825 * Gets @node's foreground color.
1826 */
1827 void
1828 st_theme_node_get_foreground_color (StThemeNode *node,
1829 ClutterColor *color)
1830 {
1831 g_return_if_fail (ST_IS_THEME_NODE (node));
1832
1833 if (!node->foreground_computed)
1834 {
1835 int i;
1836
1837 node->foreground_computed = TRUE;
1838
1839 ensure_properties (node);
1840
1841 for (i = node->n_properties - 1; i >= 0; i--)
1842 {
1843 CRDeclaration *decl = node->properties[i];
1844
1845 if (strcmp (decl->property->stryng->str, "color") == 0)
1846 {
1847 GetFromTermResult result = get_color_from_term (node, decl->value, &node->foreground_color);
1848 if (result == VALUE_FOUND)
1849 goto out;
1850 else if (result == VALUE_INHERIT)
1851 break;
1852 }
1853 }
1854
1855 if (node->parent_node)
1856 st_theme_node_get_foreground_color (node->parent_node, &node->foreground_color);
1857 else
1858 node->foreground_color = BLACK_COLOR; /* default to black */
1859 }
1860
1861 out:
1862 *color = node->foreground_color;
1863 }
1864
1865
1866 /**
1867 * st_theme_node_get_background_gradient:
1868 * @node: A #StThemeNode
1869 * @type: (out): Type of gradient
1870 * @start: (out caller-allocates): Color at start of gradient
1871 * @end: (out caller-allocates): Color at end of gradient
1872 *
1873 * The @start and @end arguments will only be set if @type is not #ST_GRADIENT_NONE.
1874 */
1875 void
1876 st_theme_node_get_background_gradient (StThemeNode *node,
1877 StGradientType *type,
1878 ClutterColor *start,
1879 ClutterColor *end)
1880 {
1881 g_return_if_fail (ST_IS_THEME_NODE (node));
1882
1883 _st_theme_node_ensure_background (node);
1884
1885 *type = node->background_gradient_type;
1886 if (*type != ST_GRADIENT_NONE)
1887 {
1888 *start = node->background_color;
1889 *end = node->background_gradient_end;
1890 }
1891 }
1892
1893 /**
1894 * st_theme_node_get_border_color:
1895 * @node: a #StThemeNode
1896 * @side: a #StSide
1897 * @color: (out caller-allocates): location to store the color
1898 *
1899 * Gets the color of @node's border on @side
1900 */
1901 void
1902 st_theme_node_get_border_color (StThemeNode *node,
1903 StSide side,
1904 ClutterColor *color)
1905 {
1906 g_return_if_fail (ST_IS_THEME_NODE (node));
1907 g_return_if_fail (side >= ST_SIDE_TOP && side <= ST_SIDE_LEFT);
1908
1909 _st_theme_node_ensure_geometry (node);
1910
1911 *color = node->border_color[side];
1912 }
1913
1914 double
1915 st_theme_node_get_padding (StThemeNode *node,
1916 StSide side)
1917 {
1918 g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
1919 g_return_val_if_fail (side >= ST_SIDE_TOP && side <= ST_SIDE_LEFT, 0.);
1920
1921 _st_theme_node_ensure_geometry (node);
1922
1923 return node->padding[side];
1924 }
1925
1926 /**
1927 * st_theme_node_get_transition_duration:
1928 * @node: an #StThemeNode
1929 *
1930 * Get the value of the transition-duration property, which
1931 * specifies the transition time between the previous #StThemeNode
1932 * and @node.
1933 *
1934 * Returns: the node's transition duration in milliseconds
1935 */
1936 int
1937 st_theme_node_get_transition_duration (StThemeNode *node)
1938 {
1939 gdouble value = 0.0;
1940
1941 g_return_val_if_fail (ST_IS_THEME_NODE (node), 0);
1942
1943 if (node->transition_duration > -1)
1944 return st_slow_down_factor * node->transition_duration;
1945
1946 st_theme_node_lookup_double (node, "transition-duration", FALSE, &value);
1947
1948 node->transition_duration = (int)value;
1949
1950 return st_slow_down_factor * node->transition_duration;
1951 }
1952
1953 StTextDecoration
1954 st_theme_node_get_text_decoration (StThemeNode *node)
1955 {
1956 int i;
1957
1958 ensure_properties (node);
1959
1960 for (i = node->n_properties - 1; i >= 0; i--)
1961 {
1962 CRDeclaration *decl = node->properties[i];
1963
1964 if (strcmp (decl->property->stryng->str, "text-decoration") == 0)
1965 {
1966 CRTerm *term = decl->value;
1967 StTextDecoration decoration = 0;
1968
1969 /* Specification is none | [ underline || overline || line-through || blink ] | inherit
1970 *
1971 * We're a bit more liberal, and for example treat 'underline none' as the same as
1972 * none.
1973 */
1974 for (; term; term = term->next)
1975 {
1976 if (term->type != TERM_IDENT)
1977 goto next_decl;
1978
1979 if (strcmp (term->content.str->stryng->str, "none") == 0)
1980 {
1981 return 0;
1982 }
1983 else if (strcmp (term->content.str->stryng->str, "inherit") == 0)
1984 {
1985 if (node->parent_node)
1986 return st_theme_node_get_text_decoration (node->parent_node);
1987 }
1988 else if (strcmp (term->content.str->stryng->str, "underline") == 0)
1989 {
1990 decoration |= ST_TEXT_DECORATION_UNDERLINE;
1991 }
1992 else if (strcmp (term->content.str->stryng->str, "overline") == 0)
1993 {
1994 decoration |= ST_TEXT_DECORATION_OVERLINE;
1995 }
1996 else if (strcmp (term->content.str->stryng->str, "line-through") == 0)
1997 {
1998 decoration |= ST_TEXT_DECORATION_LINE_THROUGH;
1999 }
2000 else if (strcmp (term->content.str->stryng->str, "blink") == 0)
2001 {
2002 decoration |= ST_TEXT_DECORATION_BLINK;
2003 }
2004 else
2005 {
2006 goto next_decl;
2007 }
2008 }
2009
2010 return decoration;
2011 }
2012
2013 next_decl:
2014 ;
2015 }
2016
2017 return 0;
2018 }
2019
2020 StTextAlign
2021 st_theme_node_get_text_align(StThemeNode *node)
2022 {
2023 int i;
2024
2025 ensure_properties(node);
2026
2027 for (i = node->n_properties - 1; i >= 0; i--)
2028 {
2029 CRDeclaration *decl = node->properties[i];
2030
2031 if (strcmp(decl->property->stryng->str, "text-align") == 0)
2032 {
2033 CRTerm *term = decl->value;
2034
2035 if (term->type != TERM_IDENT || term->next)
2036 continue;
2037
2038 if (strcmp(term->content.str->stryng->str, "inherit") == 0)
2039 {
2040 if (node->parent_node)
2041 return st_theme_node_get_text_align(node->parent_node);
2042 return ST_TEXT_ALIGN_LEFT;
2043 }
2044 else if (strcmp(term->content.str->stryng->str, "left") == 0)
2045 {
2046 return ST_TEXT_ALIGN_LEFT;
2047 }
2048 else if (strcmp(term->content.str->stryng->str, "right") == 0)
2049 {
2050 return ST_TEXT_ALIGN_RIGHT;
2051 }
2052 else if (strcmp(term->content.str->stryng->str, "center") == 0)
2053 {
2054 return ST_TEXT_ALIGN_CENTER;
2055 }
2056 else if (strcmp(term->content.str->stryng->str, "justify") == 0)
2057 {
2058 return ST_TEXT_ALIGN_JUSTIFY;
2059 }
2060 }
2061 }
2062 if(node->parent_node)
2063 return st_theme_node_get_text_align(node->parent_node);
2064 return ST_TEXT_ALIGN_LEFT;
2065 }
2066
2067 static gboolean
2068 font_family_from_terms (CRTerm *term,
2069 char **family)
2070 {
2071 GString *family_string;
2072 gboolean result = FALSE;
2073 gboolean last_was_quoted = FALSE;
2074
2075 if (!term)
2076 return FALSE;
2077
2078 family_string = g_string_new (NULL);
2079
2080 while (term)
2081 {
2082 if (term->type != TERM_STRING && term->type != TERM_IDENT)
2083 {
2084 goto out;
2085 }
2086
2087 if (family_string->len > 0)
2088 {
2089 if (term->the_operator != COMMA && term->the_operator != NO_OP)
2090 goto out;
2091 /* Can concatenate two bare words, but not two quoted strings */
2092 if ((term->the_operator == NO_OP && last_was_quoted) || term->type == TERM_STRING)
2093 goto out;
2094
2095 if (term->the_operator == NO_OP)
2096 g_string_append (family_string, " ");
2097 else
2098 g_string_append (family_string, ", ");
2099 }
2100 else
2101 {
2102 if (term->the_operator != NO_OP)
2103 goto out;
2104 }
2105
2106 g_string_append (family_string, term->content.str->stryng->str);
2107
2108 term = term->next;
2109 }
2110
2111 result = TRUE;
2112
2113 out:
2114 if (result)
2115 {
2116 *family = g_string_free (family_string, FALSE);
2117 return TRUE;
2118 }
2119 else
2120 {
2121 *family = g_string_free (family_string, TRUE);
2122 return FALSE;
2123 }
2124 }
2125
2126 /* In points */
2127 static int font_sizes[] = {
2128 6 * 1024, /* xx-small */
2129 8 * 1024, /* x-small */
2130 10 * 1024, /* small */
2131 12 * 1024, /* medium */
2132 16 * 1024, /* large */
2133 20 * 1024, /* x-large */
2134 24 * 1024, /* xx-large */
2135 };
2136
2137 static gboolean
2138 font_size_from_term (StThemeNode *node,
2139 CRTerm *term,
2140 double *size)
2141 {
2142 if (term->type == TERM_IDENT)
2143 {
2144 double resolution = clutter_backend_get_resolution (clutter_get_default_backend ());
2145 /* We work in integers to avoid double comparisons when converting back
2146 * from a size in pixels to a logical size.
2147 */
2148 int size_points = (int)(0.5 + *size * (72. / resolution));
2149
2150 if (strcmp (term->content.str->stryng->str, "xx-small") == 0)
2151 size_points = font_sizes[0];
2152 else if (strcmp (term->content.str->stryng->str, "x-small") == 0)
2153 size_points = font_sizes[1];
2154 else if (strcmp (term->content.str->stryng->str, "small") == 0)
2155 size_points = font_sizes[2];
2156 else if (strcmp (term->content.str->stryng->str, "medium") == 0)
2157 size_points = font_sizes[3];
2158 else if (strcmp (term->content.str->stryng->str, "large") == 0)
2159 size_points = font_sizes[4];
2160 else if (strcmp (term->content.str->stryng->str, "x-large") == 0)
2161 size_points = font_sizes[5];
2162 else if (strcmp (term->content.str->stryng->str, "xx-large") == 0)
2163 size_points = font_sizes[6];
2164 else if (strcmp (term->content.str->stryng->str, "smaller") == 0)
2165 {
2166 /* Find the standard size equal to or smaller than the current size */
2167 int i = 0;
2168
2169 while (i <= 6 && font_sizes[i] < size_points)
2170 i++;
2171
2172 if (i > 6)
2173 {
2174 /* original size greater than any standard size */
2175 size_points = (int)(0.5 + size_points / 1.2);
2176 }
2177 else
2178 {
2179 /* Go one smaller than that, if possible */
2180 if (i > 0)
2181 i--;
2182
2183 size_points = font_sizes[i];
2184 }
2185 }
2186 else if (strcmp (term->content.str->stryng->str, "larger") == 0)
2187 {
2188 /* Find the standard size equal to or larger than the current size */
2189 int i = 6;
2190
2191 while (i >= 0 && font_sizes[i] > size_points)
2192 i--;
2193
2194 if (i < 0) /* original size smaller than any standard size */
2195 i = 0;
2196
2197 /* Go one larger than that, if possible */
2198 if (i < 6)
2199 i++;
2200
2201 size_points = font_sizes[i];
2202 }
2203 else
2204 {
2205 return FALSE;
2206 }
2207
2208 *size = size_points * (resolution / 72.);
2209 return TRUE;
2210
2211 }
2212 else if (term->type == TERM_NUMBER && term->content.num->type == NUM_PERCENTAGE)
2213 {
2214 *size *= term->content.num->val / 100.;
2215 return TRUE;
2216 }
2217 else if (get_length_from_term (node, term, TRUE, size) == VALUE_FOUND)
2218 {
2219 /* Convert from pixels to Pango units */
2220 *size *= 1024;
2221 return TRUE;
2222 }
2223
2224 return FALSE;
2225 }
2226
2227 static gboolean
2228 font_weight_from_term (CRTerm *term,
2229 PangoWeight *weight,
2230 gboolean *weight_absolute)
2231 {
2232 if (term->type == TERM_NUMBER)
2233 {
2234 int weight_int;
2235
2236 /* The spec only allows numeric weights from 100-900, though Pango
2237 * will handle any number. We just let anything through.
2238 */
2239 if (term->content.num->type != NUM_GENERIC)
2240 return FALSE;
2241
2242 weight_int = (int)(0.5 + term->content.num->val);
2243
2244 *weight = weight_int;
2245 *weight_absolute = TRUE;
2246
2247 }
2248 else if (term->type == TERM_IDENT)
2249 {
2250 /* FIXME: handle INHERIT */
2251
2252 if (strcmp (term->content.str->stryng->str, "bold") == 0)
2253 {
2254 *weight = PANGO_WEIGHT_BOLD;
2255 *weight_absolute = TRUE;
2256 }
2257 else if (strcmp (term->content.str->stryng->str, "normal") == 0)
2258 {
2259 *weight = PANGO_WEIGHT_NORMAL;
2260 *weight_absolute = TRUE;
2261 }
2262 else if (strcmp (term->content.str->stryng->str, "bolder") == 0)
2263 {
2264 *weight = PANGO_WEIGHT_BOLD;
2265 *weight_absolute = FALSE;
2266 }
2267 else if (strcmp (term->content.str->stryng->str, "lighter") == 0)
2268 {
2269 *weight = PANGO_WEIGHT_LIGHT;
2270 *weight_absolute = FALSE;
2271 }
2272 else
2273 {
2274 return FALSE;
2275 }
2276
2277 }
2278 else
2279 {
2280 return FALSE;
2281 }
2282
2283 return TRUE;
2284 }
2285
2286 static gboolean
2287 font_style_from_term (CRTerm *term,
2288 PangoStyle *style)
2289 {
2290 if (term->type != TERM_IDENT)
2291 return FALSE;
2292
2293 /* FIXME: handle INHERIT */
2294
2295 if (strcmp (term->content.str->stryng->str, "normal") == 0)
2296 *style = PANGO_STYLE_NORMAL;
2297 else if (strcmp (term->content.str->stryng->str, "oblique") == 0)
2298 *style = PANGO_STYLE_OBLIQUE;
2299 else if (strcmp (term->content.str->stryng->str, "italic") == 0)
2300 *style = PANGO_STYLE_ITALIC;
2301 else
2302 return FALSE;
2303
2304 return TRUE;
2305 }
2306
2307 static gboolean
2308 font_variant_from_term (CRTerm *term,
2309 PangoVariant *variant)
2310 {
2311 if (term->type != TERM_IDENT)
2312 return FALSE;
2313
2314 /* FIXME: handle INHERIT */
2315
2316 if (strcmp (term->content.str->stryng->str, "normal") == 0)
2317 *variant = PANGO_VARIANT_NORMAL;
2318 else if (strcmp (term->content.str->stryng->str, "small-caps") == 0)
2319 *variant = PANGO_VARIANT_SMALL_CAPS;
2320 else
2321 return FALSE;
2322
2323 return TRUE;
2324 }
2325
2326 const PangoFontDescription *
2327 st_theme_node_get_font (StThemeNode *node)
2328 {
2329 /* Initialized despite _set flags to suppress compiler warnings */
2330 PangoStyle font_style = PANGO_STYLE_NORMAL;
2331 gboolean font_style_set = FALSE;
2332 PangoVariant variant = PANGO_VARIANT_NORMAL;
2333 gboolean variant_set = FALSE;
2334 PangoWeight weight = PANGO_WEIGHT_NORMAL;
2335 gboolean weight_absolute = TRUE;
2336 gboolean weight_set = FALSE;
2337 double size = 0.;
2338 gboolean size_set = FALSE;
2339
2340 char *family = NULL;
2341 double parent_size;
2342 int i;
2343
2344 if (node->font_desc)
2345 return node->font_desc;
2346
2347 node->font_desc = pango_font_description_copy (get_parent_font (node));
2348 parent_size = pango_font_description_get_size (node->font_desc);
2349 if (!pango_font_description_get_size_is_absolute (node->font_desc))
2350 {
2351 double resolution = clutter_backend_get_resolution (clutter_get_default_backend ());
2352 parent_size *= (resolution / 72.);
2353 }
2354
2355 ensure_properties (node);
2356
2357 for (i = 0; i < node->n_properties; i++)
2358 {
2359 CRDeclaration *decl = node->properties[i];
2360
2361 if (strcmp (decl->property->stryng->str, "font") == 0)
2362 {
2363 PangoStyle tmp_style = PANGO_STYLE_NORMAL;
2364 PangoVariant tmp_variant = PANGO_VARIANT_NORMAL;
2365 PangoWeight tmp_weight = PANGO_WEIGHT_NORMAL;
2366 gboolean tmp_weight_absolute = TRUE;
2367 double tmp_size;
2368 CRTerm *term = decl->value;
2369
2370 /* A font specification starts with node/variant/weight
2371 * in any order. Each is allowed to be specified only once,
2372 * but we don't enforce that.
2373 */
2374 for (; term; term = term->next)
2375 {
2376 if (font_style_from_term (term, &tmp_style))
2377 continue;
2378 if (font_variant_from_term (term, &tmp_variant))
2379 continue;
2380 if (font_weight_from_term (term, &tmp_weight, &tmp_weight_absolute))
2381 continue;
2382
2383 break;
2384 }
2385
2386 /* The size is mandatory */
2387
2388 if (term == NULL || term->type != TERM_NUMBER)
2389 {
2390 g_warning ("Size missing from font property");
2391 continue;
2392 }
2393
2394 tmp_size = parent_size;
2395 if (!font_size_from_term (node, term, &tmp_size))
2396 {
2397 g_warning ("Couldn't parse size in font property");
2398 continue;
2399 }
2400
2401 term = term->next;
2402
2403 if (term != NULL && term->type && TERM_NUMBER && term->the_operator == DIVIDE)
2404 {
2405 /* Ignore line-height specification */
2406 term = term->next;
2407 }
2408
2409 /* the font family is mandatory - it is a comma-separated list of
2410 * names.
2411 */
2412 if (!font_family_from_terms (term, &family))
2413 {
2414 g_warning ("Couldn't parse family in font property");
2415 continue;
2416 }
2417
2418 font_style = tmp_style;
2419 font_style_set = TRUE;
2420 weight = tmp_weight;
2421 weight_absolute = tmp_weight_absolute;
2422 weight_set = TRUE;
2423 variant = tmp_variant;
2424 variant_set = TRUE;
2425
2426 size = tmp_size;
2427 size_set = TRUE;
2428
2429 }
2430 else if (strcmp (decl->property->stryng->str, "font-family") == 0)
2431 {
2432 if (!font_family_from_terms (decl->value, &family))
2433 {
2434 g_warning ("Couldn't parse family in font property");
2435 continue;
2436 }
2437 }
2438 else if (strcmp (decl->property->stryng->str, "font-weight") == 0)
2439 {
2440 if (decl->value == NULL || decl->value->next != NULL)
2441 continue;
2442
2443 if (font_weight_from_term (decl->value, &weight, &weight_absolute))
2444 weight_set = TRUE;
2445 }
2446 else if (strcmp (decl->property->stryng->str, "font-style") == 0)
2447 {
2448 if (decl->value == NULL || decl->value->next != NULL)
2449 continue;
2450
2451 if (font_style_from_term (decl->value, &font_style))
2452 font_style_set = TRUE;
2453 }
2454 else if (strcmp (decl->property->stryng->str, "font-variant") == 0)
2455 {
2456 if (decl->value == NULL || decl->value->next != NULL)
2457 continue;
2458
2459 if (font_variant_from_term (decl->value, &variant))
2460 variant_set = TRUE;
2461 }
2462 else if (strcmp (decl->property->stryng->str, "font-size") == 0)
2463 {
2464 gdouble tmp_size;
2465 if (decl->value == NULL || decl->value->next != NULL)
2466 continue;
2467
2468 tmp_size = parent_size;
2469 if (font_size_from_term (node, decl->value, &tmp_size))
2470 {
2471 size = tmp_size;
2472 size_set = TRUE;
2473 }
2474 }
2475 }
2476
2477 if (family)
2478 {
2479 pango_font_description_set_family (node->font_desc, family);
2480 g_free (family);
2481 }
2482
2483 if (size_set)
2484 pango_font_description_set_absolute_size (node->font_desc, size);
2485
2486 if (weight_set)
2487 {
2488 if (!weight_absolute)
2489 {
2490 /* bolder/lighter are supposed to switch between available styles, but with
2491 * font substitution, that gets to be a pretty fuzzy concept. So we use
2492 * a fixed step of 200. (The spec says 100, but that might not take us from
2493 * normal to bold.
2494 */
2495
2496 PangoWeight old_weight = pango_font_description_get_weight (node->font_desc);
2497 if (weight == PANGO_WEIGHT_BOLD)
2498 weight = old_weight + 200;
2499 else
2500 weight = old_weight - 200;
2501
2502 if (weight < 100)
2503 weight = 100;
2504 if (weight > 900)
2505 weight = 900;
2506 }
2507
2508 pango_font_description_set_weight (node->font_desc, weight);
2509 }
2510
2511 if (font_style_set)
2512 pango_font_description_set_style (node->font_desc, font_style);
2513 if (variant_set)
2514 pango_font_description_set_variant (node->font_desc, variant);
2515
2516 return node->font_desc;
2517 }
2518
2519 /**
2520 * st_theme_node_get_border_image:
2521 * @node: a #StThemeNode
2522 *
2523 * Gets the value for the border-image style property
2524 *
2525 * Return value: (transfer none): the border image, or %NULL
2526 * if there is no border image.
2527 */
2528 StBorderImage *
2529 st_theme_node_get_border_image (StThemeNode *node)
2530 {
2531 int i;
2532
2533 if (node->border_image_computed)
2534 return node->border_image;
2535
2536 node->border_image = NULL;
2537 node->border_image_computed = TRUE;
2538
2539 ensure_properties (node);
2540
2541 for (i = node->n_properties - 1; i >= 0; i--)
2542 {
2543 CRDeclaration *decl = node->properties[i];
2544
2545 if (strcmp (decl->property->stryng->str, "border-image") == 0)
2546 {
2547 CRTerm *term = decl->value;
2548 CRStyleSheet *base_stylesheet;
2549 int borders[4];
2550 int n_borders = 0;
2551 int i;
2552
2553 const char *url;
2554 int border_top;
2555 int border_right;
2556 int border_bottom;
2557 int border_left;
2558
2559 char *filename;
2560
2561 /* Support border-image: none; to suppress a previously specified border image */
2562 if (term_is_none (term))
2563 {
2564 if (term->next == NULL)
2565 return NULL;
2566 else
2567 goto next_property;
2568 }
2569
2570 /* First term must be the URL to the image */
2571 if (term->type != TERM_URI)
2572 goto next_property;
2573
2574 url = term->content.str->stryng->str;
2575
2576 term = term->next;
2577
2578 /* Followed by 0 to 4 numbers or percentages. *Not lengths*. The interpretation
2579 * of a number is supposed to be pixels if the image is pixel based, otherwise CSS pixels.
2580 */
2581 for (i = 0; i < 4; i++)
2582 {
2583 if (term == NULL)
2584 break;
2585
2586 if (term->type != TERM_NUMBER)
2587 goto next_property;
2588
2589 if (term->content.num->type == NUM_GENERIC)
2590 {
2591 borders[n_borders] = (int)(0.5 + term->content.num->val);
2592 n_borders++;
2593 }
2594 else if (term->content.num->type == NUM_PERCENTAGE)
2595 {
2596 /* This would be easiest to support if we moved image handling into StBorderImage */
2597 g_warning ("Percentages not supported for border-image");
2598 goto next_property;
2599 }
2600 else
2601 goto next_property;
2602
2603 term = term->next;
2604 }
2605
2606 switch (n_borders)
2607 {
2608 case 0:
2609 border_top = border_right = border_bottom = border_left = 0;
2610 break;
2611 case 1:
2612 border_top = border_right = border_bottom = border_left = borders[0];
2613 break;
2614 case 2:
2615 border_top = border_bottom = borders[0];
2616 border_left = border_right = borders[1];
2617 break;
2618 case 3:
2619 border_top = borders[0];
2620 border_left = border_right = borders[1];
2621 border_bottom = borders[2];
2622 break;
2623 case 4:
2624 default:
2625 border_top = borders[0];
2626 border_right = borders[1];
2627 border_bottom = borders[2];
2628 border_left = borders[3];
2629 break;
2630 }
2631
2632 if (decl->parent_statement != NULL)
2633 base_stylesheet = decl->parent_statement->parent_sheet;
2634 else
2635 base_stylesheet = NULL;
2636
2637 filename = _st_theme_resolve_url (node->theme, base_stylesheet, url);
2638 if (filename == NULL)
2639 goto next_property;
2640
2641 node->border_image = st_border_image_new (filename,
2642 border_top, border_right, border_bottom, border_left);
2643
2644 g_free (filename);
2645
2646 return node->border_image;
2647 }
2648
2649 next_property:
2650 ;
2651 }
2652
2653 return NULL;
2654 }
2655
2656 /**
2657 * st_theme_node_get_horizontal_padding:
2658 * @node: a #StThemeNode
2659 *
2660 * Gets the total horizonal padding (left + right padding)
2661 *
2662 * Return value: the total horizonal padding
2663 * in pixels
2664 */
2665 double
2666 st_theme_node_get_horizontal_padding (StThemeNode *node)
2667 {
2668 double padding = 0.0;
2669 padding += st_theme_node_get_padding (node, ST_SIDE_LEFT);
2670 padding += st_theme_node_get_padding (node, ST_SIDE_RIGHT);
2671
2672 return padding;
2673 }
2674
2675 /**
2676 * st_theme_node_get_vertical_padding:
2677 * @node: a #StThemeNode
2678 *
2679 * Gets the total vertical padding (top + bottom padding)
2680 *
2681 * Return value: the total vertical padding
2682 * in pixels
2683 */
2684 double
2685 st_theme_node_get_vertical_padding (StThemeNode *node)
2686 {
2687 double padding = 0.0;
2688 padding += st_theme_node_get_padding (node, ST_SIDE_TOP);
2689 padding += st_theme_node_get_padding (node, ST_SIDE_BOTTOM);
2690
2691 return padding;
2692 }
2693
2694 static GetFromTermResult
2695 parse_shadow_property (StThemeNode *node,
2696 CRDeclaration *decl,
2697 ClutterColor *color,
2698 gdouble *xoffset,
2699 gdouble *yoffset,
2700 gdouble *blur,
2701 gdouble *spread,
2702 gboolean *inset)
2703 {
2704 GetFromTermResult result;
2705 CRTerm *term;
2706 int n_offsets = 0;
2707
2708 /* default values */
2709 color->red = 0x0; color->green = 0x0; color->blue = 0x0; color->alpha = 0xff;
2710 *xoffset = 0.;
2711 *yoffset = 0.;
2712 *blur = 0.;
2713 *spread = 0.;
2714 *inset = FALSE;
2715
2716 /* The CSS3 draft of the box-shadow property[0] is a lot stricter
2717 * regarding the order of terms:
2718 * If the 'inset' keyword is specified, it has to be first or last,
2719 * and the color may not be mixed with the lengths; while we parse
2720 * length values in the correct order, we allow for arbitrary
2721 * placement of the color and 'inset' keyword.
2722 *
2723 * [0] http://www.w3.org/TR/css3-background/#box-shadow
2724 */
2725 for (term = decl->value; term; term = term->next)
2726 {
2727 if (term->type == TERM_NUMBER)
2728 {
2729 gdouble value;
2730 gdouble multiplier;
2731
2732 multiplier = (term->unary_op == MINUS_UOP) ? -1. : 1.;
2733 result = get_length_from_term (node, term, FALSE, &value);
2734
2735 if (result == VALUE_INHERIT)
2736 {
2737 /* we only allow inherit on the line by itself */
2738 if (n_offsets > 0)
2739 return VALUE_NOT_FOUND;
2740 else
2741 return VALUE_INHERIT;
2742 }
2743 else if (result == VALUE_FOUND)
2744 {
2745 switch (n_offsets++)
2746 {
2747 case 0:
2748 *xoffset = multiplier * value;
2749 break;
2750 case 1:
2751 *yoffset = multiplier * value;
2752 break;
2753 case 2:
2754 if (multiplier < 0)
2755 g_warning ("Negative blur values are "
2756 "not allowed");
2757 *blur = value;
2758 break;
2759 case 3:
2760 if (multiplier < 0)
2761 g_warning ("Negative spread values are "
2762 "not allowed");
2763 *spread = value;
2764 break;
2765 }
2766 continue;
2767 }
2768 }
2769 else if (term->type == TERM_IDENT &&
2770 strcmp (term->content.str->stryng->str, "inset") == 0)
2771 {
2772 *inset = TRUE;
2773 continue;
2774 }
2775
2776 result = get_color_from_term (node, term, color);
2777
2778 if (result == VALUE_INHERIT)
2779 {
2780 if (n_offsets > 0)
2781 return VALUE_NOT_FOUND;
2782 else
2783 return VALUE_INHERIT;
2784 }
2785 else if (result == VALUE_FOUND)
2786 {
2787 continue;
2788 }
2789 }
2790
2791 /* The only required terms are the x and y offsets
2792 */
2793 if (n_offsets >= 2)
2794 return VALUE_FOUND;
2795 else
2796 return VALUE_NOT_FOUND;
2797 }
2798
2799 /**
2800 * st_theme_node_lookup_shadow:
2801 * @node: a #StThemeNode
2802 * @property_name: The name of the shadow property
2803 * @inherit: if %TRUE, if a value is not found for the property on the
2804 * node, then it will be looked up on the parent node, and then on the
2805 * parent's parent, and so forth. Note that if the property has a
2806 * value of 'inherit' it will be inherited even if %FALSE is passed
2807 * in for @inherit; this only affects the default behavior for inheritance.
2808 * @shadow: (out): location to store the shadow
2809 *
2810 * If the property is not found, the value in the shadow variable will not
2811 * be changed.
2812 *
2813 * Generically looks up a property containing a set of shadow values. When
2814 * specific getters (like st_theme_node_get_box_shadow ()) exist, they
2815 * should be used instead. They are cached, so more efficient, and have
2816 * handling for shortcut properties and other details of CSS.
2817 *
2818 * See also st_theme_node_get_shadow(), which provides a simpler API.
2819 *
2820 * Return value: %TRUE if the property was found in the properties for this
2821 * theme node (or in the properties of parent nodes when inheriting.)
2822 */
2823 gboolean
2824 st_theme_node_lookup_shadow (StThemeNode *node,
2825 const char *property_name,
2826 gboolean inherit,
2827 StShadow **shadow)
2828 {
2829 ClutterColor color = { 0., };
2830 gdouble xoffset = 0.;
2831 gdouble yoffset = 0.;
2832 gdouble blur = 0.;
2833 gdouble spread = 0.;
2834 gboolean inset = FALSE;
2835
2836 int i;
2837
2838 ensure_properties (node);
2839
2840 for (i = node->n_properties - 1; i >= 0; i--)
2841 {
2842 CRDeclaration *decl = node->properties[i];
2843
2844 if (strcmp (decl->property->stryng->str, property_name) == 0)
2845 {
2846 GetFromTermResult result = parse_shadow_property (node,
2847 decl,
2848 &color,
2849 &xoffset,
2850 &yoffset,
2851 &blur,
2852 &spread,
2853 &inset);
2854 if (result == VALUE_FOUND)
2855 {
2856 *shadow = st_shadow_new (&color,
2857 xoffset, yoffset,
2858 blur, spread,
2859 inset);
2860 return TRUE;
2861 }
2862 else if (result == VALUE_INHERIT)
2863 {
2864 if (node->parent_node)
2865 return st_theme_node_lookup_shadow (node->parent_node,
2866 property_name,
2867 inherit,
2868 shadow);
2869 else
2870 break;
2871 }
2872 }
2873 }
2874
2875 if (inherit && node->parent_node)
2876 return st_theme_node_lookup_shadow (node->parent_node,
2877 property_name,
2878 inherit,
2879 shadow);
2880
2881 return FALSE;
2882 }
2883
2884 /**
2885 * st_theme_node_get_shadow:
2886 * @node: a #StThemeNode
2887 * @property_name: The name of the shadow property
2888 *
2889 * Generically looks up a property containing a set of shadow values. When
2890 * specific getters (like st_theme_node_get_box_shadow()) exist, they
2891 * should be used instead. They are cached, so more efficient, and have
2892 * handling for shortcut properties and other details of CSS.
2893 *
2894 * Like st_theme_get_length(), this does not print a warning if the property is
2895 * not found; it just returns %NULL
2896 *
2897 * See also st_theme_node_lookup_shadow (), which provides more options.
2898 *
2899 * Return value: (transfer full): the shadow, or %NULL if the property was not found.
2900 */
2901 StShadow *
2902 st_theme_node_get_shadow (StThemeNode *node,
2903 const char *property_name)
2904 {
2905 StShadow *shadow;
2906
2907 if (st_theme_node_lookup_shadow (node, property_name, FALSE, &shadow))
2908 return shadow;
2909 else
2910 return NULL;
2911 }
2912
2913 /**
2914 * st_theme_node_get_box_shadow:
2915 * @node: a #StThemeNode
2916 *
2917 * Gets the value for the box-shadow style property
2918 *
2919 * Return value: (transfer none): the node's shadow, or %NULL
2920 * if node has no shadow
2921 */
2922 StShadow *
2923 st_theme_node_get_box_shadow (StThemeNode *node)
2924 {
2925 StShadow *shadow;
2926
2927 if (node->box_shadow_computed)
2928 return node->box_shadow;
2929
2930 node->box_shadow = NULL;
2931 node->box_shadow_computed = TRUE;
2932
2933 if (st_theme_node_lookup_shadow (node,
2934 "box-shadow",
2935 FALSE,
2936 &shadow))
2937 {
2938 node->box_shadow = shadow;
2939
2940 return node->box_shadow;
2941 }
2942
2943 return NULL;
2944 }
2945
2946 /**
2947 * st_theme_node_get_background_image_shadow:
2948 * @node: a #StThemeNode
2949 *
2950 * Gets the value for the -st-background-image-shadow style property
2951 *
2952 * Return value: (transfer none): the node's background image shadow, or %NULL
2953 * if node has no such shadow
2954 */
2955 StShadow *
2956 st_theme_node_get_background_image_shadow (StThemeNode *node)
2957 {
2958 StShadow *shadow;
2959
2960 if (node->background_image_shadow_computed)
2961 return node->background_image_shadow;
2962
2963 node->background_image_shadow = NULL;
2964 node->background_image_shadow_computed = TRUE;
2965
2966 if (st_theme_node_lookup_shadow (node,
2967 "-st-background-image-shadow",
2968 FALSE,
2969 &shadow))
2970 {
2971 if (shadow->inset)
2972 {
2973 g_warning ("The -st-background-image-shadow property does not "
2974 "support inset shadows");
2975 st_shadow_unref (shadow);
2976 shadow = NULL;
2977 }
2978
2979 node->background_image_shadow = shadow;
2980
2981 return node->background_image_shadow;
2982 }
2983
2984 return NULL;
2985 }
2986
2987 /**
2988 * st_theme_node_get_text_shadow:
2989 * @node: a #StThemeNode
2990 *
2991 * Gets the value for the text-shadow style property
2992 *
2993 * Return value: (transfer none): the node's text-shadow, or %NULL
2994 * if node has no text-shadow
2995 */
2996 StShadow *
2997 st_theme_node_get_text_shadow (StThemeNode *node)
2998 {
2999 StShadow *result = NULL;
3000
3001 if (node->text_shadow_computed)
3002 return node->text_shadow;
3003
3004 ensure_properties (node);
3005
3006 if (!st_theme_node_lookup_shadow (node,
3007 "text-shadow",
3008 FALSE,
3009 &result))
3010 {
3011 if (node->parent_node)
3012 {
3013 result = st_theme_node_get_text_shadow (node->parent_node);
3014 if (result)
3015 st_shadow_ref (result);
3016 }
3017 }
3018
3019 if (result && result->inset)
3020 {
3021 g_warning ("The text-shadow property does not support inset shadows");
3022 st_shadow_unref (result);
3023 result = NULL;
3024 }
3025
3026 node->text_shadow = result;
3027 node->text_shadow_computed = TRUE;
3028
3029 return result;
3030 }
3031
3032 /**
3033 * st_theme_node_get_icon_colors:
3034 * @node: a #StThemeNode
3035 *
3036 * Gets the colors that should be used for colorizing symbolic icons according
3037 * the style of this node.
3038 *
3039 * Return value: (transfer none): the icon colors to use for this theme node
3040 */
3041 StIconColors *
3042 st_theme_node_get_icon_colors (StThemeNode *node)
3043 {
3044 /* Foreground here will always be the same as st_theme_node_get_foreground_color(),
3045 * but there's a loss of symmetry and little efficiency win if we try to exploit
3046 * that. */
3047
3048 enum {
3049 FOREGROUND = 1 << 0,
3050 WARNING = 1 << 1,
3051 ERROR = 1 << 2,
3052 SUCCESS = 1 << 3
3053 };
3054
3055 gboolean shared_with_parent;
3056 int i;
3057 ClutterColor color = { 0, };
3058
3059 guint still_need = FOREGROUND | WARNING | ERROR | SUCCESS;
3060
3061 g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
3062
3063 if (node->icon_colors)
3064 return node->icon_colors;
3065
3066 if (node->parent_node)
3067 {
3068 node->icon_colors = st_theme_node_get_icon_colors (node->parent_node);
3069 shared_with_parent = TRUE;
3070 }
3071 else
3072 {
3073 node->icon_colors = st_icon_colors_new ();
3074 node->icon_colors->foreground = BLACK_COLOR;
3075 node->icon_colors->warning = DEFAULT_WARNING_COLOR;
3076 node->icon_colors->error = DEFAULT_ERROR_COLOR;
3077 node->icon_colors->success = DEFAULT_SUCCESS_COLOR;
3078 shared_with_parent = FALSE;
3079 }
3080
3081 ensure_properties (node);
3082
3083 for (i = node->n_properties - 1; i >= 0 && still_need != 0; i--)
3084 {
3085 CRDeclaration *decl = node->properties[i];
3086 GetFromTermResult result = VALUE_NOT_FOUND;
3087 guint found = 0;
3088
3089 if ((still_need & FOREGROUND) != 0 &&
3090 strcmp (decl->property->stryng->str, "color") == 0)
3091 {
3092 found = FOREGROUND;
3093 result = get_color_from_term (node, decl->value, &color);
3094 }
3095 else if ((still_need & WARNING) != 0 &&
3096 strcmp (decl->property->stryng->str, "warning-color") == 0)
3097 {
3098 found = WARNING;
3099 result = get_color_from_term (node, decl->value, &color);
3100 }
3101 else if ((still_need & ERROR) != 0 &&
3102 strcmp (decl->property->stryng->str, "error-color") == 0)
3103 {
3104 found = ERROR;
3105 result = get_color_from_term (node, decl->value, &color);
3106 }
3107 else if ((still_need & SUCCESS) != 0 &&
3108 strcmp (decl->property->stryng->str, "success-color") == 0)
3109 {
3110 found = SUCCESS;
3111 result = get_color_from_term (node, decl->value, &color);
3112 }
3113
3114 if (result == VALUE_INHERIT)
3115 {
3116 still_need &= ~found;
3117 }
3118 else if (result == VALUE_FOUND)
3119 {
3120 still_need &= ~found;
3121 if (shared_with_parent)
3122 {
3123 node->icon_colors = st_icon_colors_copy (node->icon_colors);
3124 shared_with_parent = FALSE;
3125 }
3126
3127 switch (found)
3128 {
3129 case FOREGROUND:
3130 node->icon_colors->foreground = color;
3131 break;
3132 case WARNING:
3133 node->icon_colors->warning = color;
3134 break;
3135 case ERROR:
3136 node->icon_colors->error = color;
3137 break;
3138 case SUCCESS:
3139 node->icon_colors->success = color;
3140 break;
3141 }
3142 }
3143 }
3144
3145 if (shared_with_parent)
3146 st_icon_colors_ref (node->icon_colors);
3147
3148 return node->icon_colors;
3149 }
3150
3151 static float
3152 get_width_inc (StThemeNode *node)
3153 {
3154 return ((int)(0.5 + node->border_width[ST_SIDE_LEFT]) + node->padding[ST_SIDE_LEFT] +
3155 (int)(0.5 + node->border_width[ST_SIDE_RIGHT]) + node->padding[ST_SIDE_RIGHT]);
3156 }
3157
3158 static float
3159 get_height_inc (StThemeNode *node)
3160 {
3161 return ((int)(0.5 + node->border_width[ST_SIDE_TOP]) + node->padding[ST_SIDE_TOP] +
3162 (int)(0.5 + node->border_width[ST_SIDE_BOTTOM]) + node->padding[ST_SIDE_BOTTOM]);
3163 }
3164
3165 /**
3166 * st_theme_node_adjust_for_height:
3167 * @node: a #StThemeNode
3168 * @for_height: (inout): the "for height" to adjust
3169 *
3170 * Adjusts a "for height" passed to clutter_actor_get_preferred_width() to
3171 * account for borders and padding. This is a convenience function meant
3172 * to be called from a get_preferred_width() method of a #ClutterActor
3173 * subclass. The value after adjustment is the height available for the actor's
3174 * content.
3175 */
3176 void
3177 st_theme_node_adjust_for_height (StThemeNode *node,
3178 float *for_height)
3179 {
3180 g_return_if_fail (ST_IS_THEME_NODE (node));
3181 g_return_if_fail (for_height != NULL);
3182
3183 if (*for_height >= 0)
3184 {
3185 float height_inc = get_height_inc (node);
3186 *for_height = MAX (0, *for_height - height_inc);
3187 }
3188 }
3189
3190 /**
3191 * st_theme_node_adjust_preferred_width:
3192 * @node: a #StThemeNode
3193 * @min_width_p: (inout) (allow-none): the minimum width to adjust
3194 * @natural_width_p: (inout): the natural width to adjust
3195 *
3196 * Adjusts the minimum and natural width computed for an actor by
3197 * adding on the necessary space for borders and padding and taking
3198 * into account any minimum or maximum width. This is a convenience
3199 * function meant to be called from the get_preferred_width() method
3200 * of a #ClutterActor subclass
3201 */
3202 void
3203 st_theme_node_adjust_preferred_width (StThemeNode *node,
3204 float *min_width_p,
3205 float *natural_width_p)
3206 {
3207 float width_inc;
3208
3209 g_return_if_fail (ST_IS_THEME_NODE (node));
3210
3211 _st_theme_node_ensure_geometry (node);
3212
3213 width_inc = get_width_inc (node);
3214
3215 if (min_width_p)
3216 {
3217 if (node->min_width != -1)
3218 *min_width_p = node->min_width;
3219 *min_width_p += width_inc;
3220 }
3221
3222 if (natural_width_p)
3223 {
3224 if (node->width != -1)
3225 *natural_width_p = node->width;
3226 if (node->max_width != -1)
3227 *natural_width_p = MIN (*natural_width_p, node->max_width);
3228 *natural_width_p += width_inc;
3229 }
3230 }
3231
3232 /**
3233 * st_theme_node_adjust_for_width:
3234 * @node: a #StThemeNode
3235 * @for_width: (inout): the "for width" to adjust
3236 *
3237 * Adjusts a "for width" passed to clutter_actor_get_preferred_height() to
3238 * account for borders and padding. This is a convenience function meant
3239 * to be called from a get_preferred_height() method of a #ClutterActor
3240 * subclass. The value after adjustment is the width available for the actor's
3241 * content.
3242 */
3243 void
3244 st_theme_node_adjust_for_width (StThemeNode *node,
3245 float *for_width)
3246 {
3247 g_return_if_fail (ST_IS_THEME_NODE (node));
3248 g_return_if_fail (for_width != NULL);
3249
3250 if (*for_width >= 0)
3251 {
3252 float width_inc = get_width_inc (node);
3253 *for_width = MAX (0, *for_width - width_inc);
3254 }
3255 }
3256
3257 /**
3258 * st_theme_node_adjust_preferred_height:
3259 * @node: a #StThemeNode
3260 * @min_height_p: (inout) (allow-none): the minimum height to adjust
3261 * @natural_height_p: (inout): the natural height to adjust
3262 *
3263 * Adjusts the minimum and natural height computed for an actor by
3264 * adding on the necessary space for borders and padding and taking
3265 * into account any minimum or maximum height. This is a convenience
3266 * function meant to be called from the get_preferred_height() method
3267 * of a #ClutterActor subclass
3268 */
3269 void
3270 st_theme_node_adjust_preferred_height (StThemeNode *node,
3271 float *min_height_p,
3272 float *natural_height_p)
3273 {
3274 float height_inc;
3275
3276 g_return_if_fail (ST_IS_THEME_NODE (node));
3277
3278 _st_theme_node_ensure_geometry (node);
3279
3280 height_inc = get_height_inc (node);
3281
3282 if (min_height_p)
3283 {
3284 if (node->min_height != -1)
3285 *min_height_p = node->min_height;
3286 *min_height_p += height_inc;
3287 }
3288 if (natural_height_p)
3289 {
3290 if (node->height != -1)
3291 *natural_height_p = node->height;
3292 if (node->max_height != -1)
3293 *natural_height_p = MIN (*natural_height_p, node->max_height);
3294 *natural_height_p += height_inc;
3295 }
3296 }
3297
3298 /**
3299 * st_theme_node_get_content_box:
3300 * @node: a #StThemeNode
3301 * @allocation: the box allocated to a #ClutterAlctor
3302 * @content_box: (out caller-allocates): computed box occupied by the actor's content
3303 *
3304 * Gets the box within an actor's allocation that contents the content
3305 * of an actor (excluding borders and padding). This is a convenience function
3306 * meant to be used from the allocate() or paint() methods of a #ClutterActor
3307 * subclass.
3308 */
3309 void
3310 st_theme_node_get_content_box (StThemeNode *node,
3311 const ClutterActorBox *allocation,
3312 ClutterActorBox *content_box)
3313 {
3314 double noncontent_left, noncontent_top, noncontent_right, noncontent_bottom;
3315 double avail_width, avail_height, content_width, content_height;
3316
3317 g_return_if_fail (ST_IS_THEME_NODE (node));
3318
3319 _st_theme_node_ensure_geometry (node);
3320
3321 avail_width = allocation->x2 - allocation->x1;
3322 avail_height = allocation->y2 - allocation->y1;
3323
3324 noncontent_left = node->border_width[ST_SIDE_LEFT] + node->padding[ST_SIDE_LEFT];
3325 noncontent_top = node->border_width[ST_SIDE_TOP] + node->padding[ST_SIDE_TOP];
3326 noncontent_right = node->border_width[ST_SIDE_RIGHT] + node->padding[ST_SIDE_RIGHT];
3327 noncontent_bottom = node->border_width[ST_SIDE_BOTTOM] + node->padding[ST_SIDE_BOTTOM];
3328
3329 content_box->x1 = (int)(0.5 + noncontent_left);
3330 content_box->y1 = (int)(0.5 + noncontent_top);
3331
3332 content_width = avail_width - noncontent_left - noncontent_right;
3333 if (content_width < 0)
3334 content_width = 0;
3335 content_height = avail_height - noncontent_top - noncontent_bottom;
3336 if (content_height < 0)
3337 content_height = 0;
3338
3339 content_box->x2 = (int)(0.5 + content_box->x1 + content_width);
3340 content_box->y2 = (int)(0.5 + content_box->y1 + content_height);
3341 }
3342
3343 /**
3344 * st_theme_node_get_background_paint_box:
3345 * @node: a #StThemeNode
3346 * @allocation: the box allocated to a #ClutterActor
3347 * @paint_box: (out caller-allocates): computed box occupied when painting the actor's background
3348 *
3349 * Gets the box used to paint the actor's background, including the area
3350 * occupied by properties which paint outside the actor's assigned allocation.
3351 */
3352 void
3353 st_theme_node_get_background_paint_box (StThemeNode *node,
3354 const ClutterActorBox *actor_box,
3355 ClutterActorBox *paint_box)
3356 {
3357 StShadow *background_image_shadow;
3358 ClutterActorBox shadow_box;
3359
3360 g_return_if_fail (ST_IS_THEME_NODE (node));
3361 g_return_if_fail (actor_box != NULL);
3362 g_return_if_fail (paint_box != NULL);
3363
3364 background_image_shadow = st_theme_node_get_background_image_shadow (node);
3365
3366 *paint_box = *actor_box;
3367
3368 if (!background_image_shadow)
3369 return;
3370
3371 st_shadow_get_box (background_image_shadow, actor_box, &shadow_box);
3372
3373 paint_box->x1 = MIN (paint_box->x1, shadow_box.x1);
3374 paint_box->x2 = MAX (paint_box->x2, shadow_box.x2);
3375 paint_box->y1 = MIN (paint_box->y1, shadow_box.y1);
3376 paint_box->y2 = MAX (paint_box->y2, shadow_box.y2);
3377 }
3378
3379 /**
3380 * st_theme_node_get_paint_box:
3381 * @node: a #StThemeNode
3382 * @allocation: the box allocated to a #ClutterActor
3383 * @paint_box: (out caller-allocates): computed box occupied when painting the actor
3384 *
3385 * Gets the box used to paint the actor, including the area occupied
3386 * by properties which paint outside the actor's assigned allocation.
3387 * When painting @node to an offscreen buffer, this function can be
3388 * used to determine the necessary size of the buffer.
3389 */
3390 void
3391 st_theme_node_get_paint_box (StThemeNode *node,
3392 const ClutterActorBox *actor_box,
3393 ClutterActorBox *paint_box)
3394 {
3395 StShadow *box_shadow;
3396 ClutterActorBox shadow_box;
3397 int outline_width;
3398
3399 g_return_if_fail (ST_IS_THEME_NODE (node));
3400 g_return_if_fail (actor_box != NULL);
3401 g_return_if_fail (paint_box != NULL);
3402
3403 box_shadow = st_theme_node_get_box_shadow (node);
3404 outline_width = st_theme_node_get_outline_width (node);
3405
3406 st_theme_node_get_background_paint_box (node, actor_box, paint_box);
3407
3408 if (!box_shadow && !outline_width)
3409 return;
3410
3411 paint_box->x1 -= outline_width;
3412 paint_box->x2 += outline_width;
3413 paint_box->y1 -= outline_width;
3414 paint_box->y2 += outline_width;
3415
3416 if (box_shadow)
3417 {
3418 st_shadow_get_box (box_shadow, actor_box, &shadow_box);
3419
3420 paint_box->x1 = MIN (paint_box->x1, shadow_box.x1);
3421 paint_box->x2 = MAX (paint_box->x2, shadow_box.x2);
3422 paint_box->y1 = MIN (paint_box->y1, shadow_box.y1);
3423 paint_box->y2 = MAX (paint_box->y2, shadow_box.y2);
3424 }
3425 }
3426
3427 /**
3428 * st_theme_node_geometry_equal:
3429 * @node: a #StThemeNode
3430 * @other: a different #StThemeNode
3431 *
3432 * Tests if two theme nodes have the same borders and padding; this can be
3433 * used to optimize having to relayout when the style applied to a Clutter
3434 * actor changes colors without changing the geometry.
3435 */
3436 gboolean
3437 st_theme_node_geometry_equal (StThemeNode *node,
3438 StThemeNode *other)
3439 {
3440 StSide side;
3441
3442 g_return_val_if_fail (ST_IS_THEME_NODE (node), FALSE);
3443 g_return_val_if_fail (ST_IS_THEME_NODE (other), FALSE);
3444
3445 _st_theme_node_ensure_geometry (node);
3446 _st_theme_node_ensure_geometry (other);
3447
3448 for (side = ST_SIDE_TOP; side <= ST_SIDE_LEFT; side++)
3449 {
3450 if (node->border_width[side] != other->border_width[side])
3451 return FALSE;
3452 if (node->padding[side] != other->padding[side])
3453 return FALSE;
3454 }
3455
3456 if (node->width != other->width || node->height != other->height)
3457 return FALSE;
3458 if (node->min_width != other->min_width || node->min_height != other->min_height)
3459 return FALSE;
3460 if (node->max_width != other->max_width || node->max_height != other->max_height)
3461 return FALSE;
3462
3463 return TRUE;
3464 }
3465
3466 /**
3467 * st_theme_node_paint_equal:
3468 * @node: a #StThemeNode
3469 * @other: a different #StThemeNode
3470 *
3471 * Check if st_theme_node_paint() will paint identically for @node as it does
3472 * for @other. Note that in some cases this function may return %TRUE even
3473 * if there is no visible difference in the painting.
3474 *
3475 * Return value: %TRUE if the two theme nodes paint identically. %FALSE if the
3476 * two nodes potentially paint differently.
3477 */
3478 gboolean
3479 st_theme_node_paint_equal (StThemeNode *node,
3480 StThemeNode *other)
3481 {
3482 StBorderImage *border_image, *other_border_image;
3483 StShadow *shadow, *other_shadow;
3484 int i;
3485
3486 g_return_val_if_fail (ST_IS_THEME_NODE (node), FALSE);
3487 g_return_val_if_fail (ST_IS_THEME_NODE (other), FALSE);
3488
3489 _st_theme_node_ensure_background (node);
3490 _st_theme_node_ensure_background (other);
3491
3492 if (!clutter_color_equal (&node->background_color, &other->background_color))
3493 return FALSE;
3494
3495 if (node->background_gradient_type != other->background_gradient_type)
3496 return FALSE;
3497
3498 if (node->background_gradient_type != ST_GRADIENT_NONE &&
3499 !clutter_color_equal (&node->background_gradient_end, &other->background_gradient_end))
3500 return FALSE;
3501
3502 if (g_strcmp0 (node->background_image, other->background_image) != 0)
3503 return FALSE;
3504
3505 _st_theme_node_ensure_geometry (node);
3506 _st_theme_node_ensure_geometry (other);
3507
3508 for (i = 0; i < 4; i++)
3509 {
3510 if (node->border_width[i] != other->border_width[i])
3511 return FALSE;
3512
3513 if (node->border_width[i] > 0 &&
3514 !clutter_color_equal (&node->border_color[i], &other->border_color[i]))
3515 return FALSE;
3516
3517 if (node->border_radius[i] != other->border_radius[i])
3518 return FALSE;
3519 }
3520
3521 if (node->outline_width != other->outline_width)
3522 return FALSE;
3523
3524 if (node->outline_width > 0 &&
3525 !clutter_color_equal (&node->outline_color, &other->outline_color))
3526 return FALSE;
3527
3528 border_image = st_theme_node_get_border_image (node);
3529 other_border_image = st_theme_node_get_border_image (other);
3530
3531 if ((border_image == NULL) != (other_border_image == NULL))
3532 return FALSE;
3533
3534 if (border_image != NULL && !st_border_image_equal (border_image, other_border_image))
3535 return FALSE;
3536
3537 shadow = st_theme_node_get_box_shadow (node);
3538 other_shadow = st_theme_node_get_box_shadow (other);
3539
3540 if ((shadow == NULL) != (other_shadow == NULL))
3541 return FALSE;
3542
3543 if (shadow != NULL && !st_shadow_equal (shadow, other_shadow))
3544 return FALSE;
3545
3546 shadow = st_theme_node_get_background_image_shadow (node);
3547 other_shadow = st_theme_node_get_background_image_shadow (other);
3548
3549 if ((shadow == NULL) != (other_shadow == NULL))
3550 return FALSE;
3551
3552 if (shadow != NULL && !st_shadow_equal (shadow, other_shadow))
3553 return FALSE;
3554
3555 return TRUE;
3556 }