No issues found
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /*
3 * st-theme.c: A set of CSS stylesheets used for rule matching
4 *
5 * Copyright 2003-2004 Dodji Seketeli
6 * Copyright 2008, 2009 Red Hat, Inc.
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms and conditions of the GNU Lesser General Public License,
10 * version 2.1, as published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope it will be useful, but WITHOUT ANY
13 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
15 * more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 * This file started as a cut-and-paste of cr-sel-eng.c from libcroco.
21 *
22 * In moving it to hippo-canvas:
23 * - Reformatted and otherwise edited to match our coding style
24 * - Switched from handling xmlNode to handling HippoStyle
25 * - Simplified by removing things that we don't need or that don't
26 * make sense in our context.
27 * - The code to get a list of matching properties works quite differently;
28 * we order things in priority order, but we don't actually try to
29 * coalesce properties with the same name.
30 *
31 * In moving it to GNOME Shell:
32 * - Renamed again to StTheme
33 * - Reformatted to match the gnome-shell coding style
34 * - Removed notion of "theme engine" from hippo-canvas
35 * - pseudo-class matching changed from link enum to strings
36 * - Some code simplification
37 */
38
39
40 #include <stdlib.h>
41 #include <string.h>
42
43 #include <gio/gio.h>
44
45 #include "st-theme-node.h"
46 #include "st-theme-private.h"
47
48 static GObject *st_theme_constructor (GType type,
49 guint n_construct_properties,
50 GObjectConstructParam *construct_properties);
51
52 static void st_theme_finalize (GObject *object);
53 static void st_theme_set_property (GObject *object,
54 guint prop_id,
55 const GValue *value,
56 GParamSpec *pspec);
57 static void st_theme_get_property (GObject *object,
58 guint prop_id,
59 GValue *value,
60 GParamSpec *pspec);
61
62 struct _StTheme
63 {
64 GObject parent;
65
66 char *application_stylesheet;
67 char *default_stylesheet;
68 char *theme_stylesheet;
69 GSList *custom_stylesheets;
70
71 GHashTable *stylesheets_by_filename;
72 GHashTable *filenames_by_stylesheet;
73
74 CRCascade *cascade;
75 };
76
77 struct _StThemeClass
78 {
79 GObjectClass parent_class;
80 };
81
82 enum
83 {
84 PROP_0,
85 PROP_APPLICATION_STYLESHEET,
86 PROP_THEME_STYLESHEET,
87 PROP_DEFAULT_STYLESHEET
88 };
89
90 G_DEFINE_TYPE (StTheme, st_theme, G_TYPE_OBJECT)
91
92 /* Quick strcmp. Test only for == 0 or != 0, not < 0 or > 0. */
93 #define strqcmp(str,lit,lit_len) \
94 (strlen (str) != (lit_len) || memcmp (str, lit, lit_len))
95
96 static void
97 st_theme_init (StTheme *theme)
98 {
99 theme->stylesheets_by_filename = g_hash_table_new_full (g_str_hash, g_str_equal,
100 (GDestroyNotify)g_free, (GDestroyNotify)cr_stylesheet_unref);
101 theme->filenames_by_stylesheet = g_hash_table_new (g_direct_hash, g_direct_equal);
102 }
103
104 static void
105 st_theme_class_init (StThemeClass *klass)
106 {
107 GObjectClass *object_class = G_OBJECT_CLASS (klass);
108
109 object_class->constructor = st_theme_constructor;
110 object_class->finalize = st_theme_finalize;
111 object_class->set_property = st_theme_set_property;
112 object_class->get_property = st_theme_get_property;
113
114 /**
115 * StTheme:application-stylesheet:
116 *
117 * The highest priority stylesheet, representing application-specific
118 * styling; this is associated with the CSS "author" stylesheet.
119 */
120 g_object_class_install_property (object_class,
121 PROP_APPLICATION_STYLESHEET,
122 g_param_spec_string ("application-stylesheet",
123 "Application Stylesheet",
124 "Stylesheet with application-specific styling",
125 NULL,
126 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
127
128 /**
129 * StTheme:theme-stylesheet:
130 *
131 * The second priority stylesheet, representing theme-specific styling;
132 * this is associated with the CSS "user" stylesheet.
133 */
134 g_object_class_install_property (object_class,
135 PROP_THEME_STYLESHEET,
136 g_param_spec_string ("theme-stylesheet",
137 "Theme Stylesheet",
138 "Stylesheet with theme-specific styling",
139 NULL,
140 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
141
142 /**
143 * StTheme:default-stylesheet:
144 *
145 * The lowest priority stylesheet, representing global default
146 * styling; this is associated with the CSS "user agent" stylesheet.
147 */
148 g_object_class_install_property (object_class,
149 PROP_DEFAULT_STYLESHEET,
150 g_param_spec_string ("default-stylesheet",
151 "Default Stylesheet",
152 "Stylesheet with global default styling",
153 NULL,
154 G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
155
156 }
157
158 static CRStyleSheet *
159 parse_stylesheet (const char *filename,
160 GError **error)
161 {
162 enum CRStatus status;
163 CRStyleSheet *stylesheet;
164
165 if (filename == NULL)
166 return NULL;
167
168 status = cr_om_parser_simply_parse_file ((const guchar *) filename,
169 CR_UTF_8,
170 &stylesheet);
171
172 if (status != CR_OK)
173 {
174 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
175 "Error parsing stylesheet '%s'; errcode:%d", filename, status);
176 return NULL;
177 }
178
179 /* Extension stylesheet */
180 stylesheet->app_data = GUINT_TO_POINTER (FALSE);
181
182 return stylesheet;
183 }
184
185 CRDeclaration *
186 _st_theme_parse_declaration_list (const char *str)
187 {
188 return cr_declaration_parse_list_from_buf ((const guchar *)str,
189 CR_UTF_8);
190 }
191
192 /* Just g_warning for now until we have something nicer to do */
193 static CRStyleSheet *
194 parse_stylesheet_nofail (const char *filename)
195 {
196 GError *error = NULL;
197 CRStyleSheet *result;
198
199 result = parse_stylesheet (filename, &error);
200 if (error)
201 {
202 g_warning ("%s", error->message);
203 g_clear_error (&error);
204 }
205 return result;
206 }
207
208 static void
209 insert_stylesheet (StTheme *theme,
210 const char *filename,
211 CRStyleSheet *stylesheet)
212 {
213 char *filename_copy;
214
215 if (stylesheet == NULL)
216 return;
217
218 filename_copy = g_strdup(filename);
219 cr_stylesheet_ref (stylesheet);
220
221 g_hash_table_insert (theme->stylesheets_by_filename, filename_copy, stylesheet);
222 g_hash_table_insert (theme->filenames_by_stylesheet, stylesheet, filename_copy);
223 }
224
225 gboolean
226 st_theme_load_stylesheet (StTheme *theme,
227 const char *path,
228 GError **error)
229 {
230 CRStyleSheet *stylesheet;
231
232 stylesheet = parse_stylesheet (path, error);
233 if (!stylesheet)
234 return FALSE;
235
236 stylesheet->app_data = GUINT_TO_POINTER (TRUE);
237
238 insert_stylesheet (theme, path, stylesheet);
239 cr_stylesheet_ref (stylesheet);
240 theme->custom_stylesheets = g_slist_prepend (theme->custom_stylesheets, stylesheet);
241
242 return TRUE;
243 }
244
245 void
246 st_theme_unload_stylesheet (StTheme *theme,
247 const char *path)
248 {
249 CRStyleSheet *stylesheet;
250
251 stylesheet = g_hash_table_lookup (theme->stylesheets_by_filename, path);
252 if (!stylesheet)
253 return;
254
255 if (!g_slist_find (theme->custom_stylesheets, stylesheet))
256 return;
257
258 theme->custom_stylesheets = g_slist_remove (theme->custom_stylesheets, stylesheet);
259 g_hash_table_remove (theme->stylesheets_by_filename, path);
260 g_hash_table_remove (theme->filenames_by_stylesheet, stylesheet);
261 cr_stylesheet_unref (stylesheet);
262 }
263
264 /**
265 * st_theme_get_custom_stylesheets:
266 * @theme: an #StTheme
267 *
268 * Returns: (transfer full) (element-type utf8): the list of stylesheet filenames
269 * that were loaded with st_theme_load_stylesheet()
270 */
271 GSList*
272 st_theme_get_custom_stylesheets (StTheme *theme)
273 {
274 GSList *result = NULL;
275 GSList *iter;
276
277 for (iter = theme->custom_stylesheets; iter; iter = iter->next)
278 {
279 CRStyleSheet *stylesheet = iter->data;
280 gchar *filename = g_hash_table_lookup (theme->filenames_by_stylesheet, stylesheet);
281
282 result = g_slist_prepend (result, g_strdup (filename));
283 }
284
285 return result;
286 }
287
288 static GObject *
289 st_theme_constructor (GType type,
290 guint n_construct_properties,
291 GObjectConstructParam *construct_properties)
292 {
293 GObject *object;
294 StTheme *theme;
295 CRStyleSheet *application_stylesheet;
296 CRStyleSheet *theme_stylesheet;
297 CRStyleSheet *default_stylesheet;
298
299 object = (*G_OBJECT_CLASS (st_theme_parent_class)->constructor) (type,
300 n_construct_properties,
301 construct_properties);
302 theme = ST_THEME (object);
303
304 application_stylesheet = parse_stylesheet_nofail (theme->application_stylesheet);
305 theme_stylesheet = parse_stylesheet_nofail (theme->theme_stylesheet);
306 default_stylesheet = parse_stylesheet_nofail (theme->default_stylesheet);
307
308 theme->cascade = cr_cascade_new (application_stylesheet,
309 theme_stylesheet,
310 default_stylesheet);
311
312 if (theme->cascade == NULL)
313 g_error ("Out of memory when creating cascade object");
314
315 insert_stylesheet (theme, theme->application_stylesheet, application_stylesheet);
316 insert_stylesheet (theme, theme->theme_stylesheet, theme_stylesheet);
317 insert_stylesheet (theme, theme->default_stylesheet, default_stylesheet);
318
319 return object;
320 }
321
322 static void
323 st_theme_finalize (GObject * object)
324 {
325 StTheme *theme = ST_THEME (object);
326
327 g_slist_foreach (theme->custom_stylesheets, (GFunc) cr_stylesheet_unref, NULL);
328 g_slist_free (theme->custom_stylesheets);
329 theme->custom_stylesheets = NULL;
330
331 g_hash_table_destroy (theme->stylesheets_by_filename);
332 g_hash_table_destroy (theme->filenames_by_stylesheet);
333
334 g_free (theme->application_stylesheet);
335 g_free (theme->theme_stylesheet);
336 g_free (theme->default_stylesheet);
337
338 if (theme->cascade)
339 {
340 cr_cascade_unref (theme->cascade);
341 theme->cascade = NULL;
342 }
343
344 G_OBJECT_CLASS (st_theme_parent_class)->finalize (object);
345 }
346
347 static void
348 st_theme_set_property (GObject *object,
349 guint prop_id,
350 const GValue *value,
351 GParamSpec *pspec)
352 {
353 StTheme *theme = ST_THEME (object);
354
355 switch (prop_id)
356 {
357 case PROP_APPLICATION_STYLESHEET:
358 {
359 const char *path = g_value_get_string (value);
360
361 if (path != theme->application_stylesheet)
362 {
363 g_free (theme->application_stylesheet);
364 theme->application_stylesheet = g_strdup (path);
365 }
366
367 break;
368 }
369 case PROP_THEME_STYLESHEET:
370 {
371 const char *path = g_value_get_string (value);
372
373 if (path != theme->theme_stylesheet)
374 {
375 g_free (theme->theme_stylesheet);
376 theme->theme_stylesheet = g_strdup (path);
377 }
378
379 break;
380 }
381 case PROP_DEFAULT_STYLESHEET:
382 {
383 const char *path = g_value_get_string (value);
384
385 if (path != theme->default_stylesheet)
386 {
387 g_free (theme->default_stylesheet);
388 theme->default_stylesheet = g_strdup (path);
389 }
390
391 break;
392 }
393 default:
394 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
395 break;
396 }
397 }
398
399 static void
400 st_theme_get_property (GObject *object,
401 guint prop_id,
402 GValue *value,
403 GParamSpec *pspec)
404 {
405 StTheme *theme = ST_THEME (object);
406
407 switch (prop_id)
408 {
409 case PROP_APPLICATION_STYLESHEET:
410 g_value_set_string (value, theme->application_stylesheet);
411 break;
412 case PROP_THEME_STYLESHEET:
413 g_value_set_string (value, theme->theme_stylesheet);
414 break;
415 case PROP_DEFAULT_STYLESHEET:
416 g_value_set_string (value, theme->default_stylesheet);
417 break;
418 default:
419 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
420 break;
421 }
422 }
423
424 /**
425 * st_theme_new:
426 * @application_stylesheet: The highest priority stylesheet, representing application-specific
427 * styling; this is associated with the CSS "author" stylesheet, may be %NULL
428 * @theme_stylesheet: The second priority stylesheet, representing theme-specific styling ;
429 * this is associated with the CSS "user" stylesheet, may be %NULL
430 * @default_stylesheet: The lowest priority stylesheet, representing global default styling;
431 * this is associated with the CSS "user agent" stylesheet, may be %NULL
432 *
433 * Return value: the newly created theme object
434 **/
435 StTheme *
436 st_theme_new (const char *application_stylesheet,
437 const char *theme_stylesheet,
438 const char *default_stylesheet)
439 {
440 StTheme *theme = g_object_new (ST_TYPE_THEME,
441 "application-stylesheet", application_stylesheet,
442 "theme-stylesheet", theme_stylesheet,
443 "default-stylesheet", default_stylesheet,
444 NULL);
445
446 return theme;
447 }
448
449 static gboolean
450 string_in_list (GString *stryng,
451 const char *list)
452 {
453 const char *cur;
454
455 for (cur = list; *cur;)
456 {
457 while (*cur && cr_utils_is_white_space (*cur))
458 cur++;
459
460 if (strncmp (cur, stryng->str, stryng->len) == 0)
461 {
462 cur += stryng->len;
463 if ((!*cur) || cr_utils_is_white_space (*cur))
464 return TRUE;
465 }
466
467 /* skip to next whitespace character */
468 while (*cur && !cr_utils_is_white_space (*cur))
469 cur++;
470 }
471
472 return FALSE;
473 }
474
475 static gboolean
476 pseudo_class_add_sel_matches_style (StTheme *a_this,
477 CRAdditionalSel *a_add_sel,
478 StThemeNode *a_node)
479 {
480 const char *node_pseudo_class;
481
482 g_return_val_if_fail (a_this
483 && a_add_sel
484 && a_add_sel->content.pseudo
485 && a_add_sel->content.pseudo->name
486 && a_add_sel->content.pseudo->name->stryng
487 && a_add_sel->content.pseudo->name->stryng->str
488 && a_node, FALSE);
489
490 node_pseudo_class = st_theme_node_get_pseudo_class (a_node);
491
492 if (node_pseudo_class == NULL)
493 return FALSE;
494
495 return string_in_list (a_add_sel->content.pseudo->name->stryng, node_pseudo_class);
496 }
497
498 /**
499 * class_add_sel_matches_style:
500 * @a_add_sel: The class additional selector to consider.
501 * @a_node: The style node to consider.
502 *
503 * Returns: %TRUE if the class additional selector matches
504 * the style node given in argument, %FALSE otherwise.
505 */
506 static gboolean
507 class_add_sel_matches_style (CRAdditionalSel *a_add_sel,
508 StThemeNode *a_node)
509 {
510 const char *element_class;
511
512 g_return_val_if_fail (a_add_sel
513 && a_add_sel->type == CLASS_ADD_SELECTOR
514 && a_add_sel->content.class_name
515 && a_add_sel->content.class_name->stryng
516 && a_add_sel->content.class_name->stryng->str
517 && a_node, FALSE);
518
519 element_class = st_theme_node_get_element_class (a_node);
520 if (element_class == NULL)
521 return FALSE;
522
523 return string_in_list (a_add_sel->content.class_name->stryng, element_class);
524 }
525
526 /*
527 *@return TRUE if the additional attribute selector matches
528 *the current style node given in argument, FALSE otherwise.
529 *@param a_add_sel the additional attribute selector to consider.
530 *@param a_node the style node to consider.
531 */
532 static gboolean
533 id_add_sel_matches_style (CRAdditionalSel *a_add_sel,
534 StThemeNode *a_node)
535 {
536 gboolean result = FALSE;
537 const char *id;
538
539 g_return_val_if_fail (a_add_sel
540 && a_add_sel->type == ID_ADD_SELECTOR
541 && a_add_sel->content.id_name
542 && a_add_sel->content.id_name->stryng
543 && a_add_sel->content.id_name->stryng->str
544 && a_node, FALSE);
545 g_return_val_if_fail (a_add_sel
546 && a_add_sel->type == ID_ADD_SELECTOR
547 && a_node, FALSE);
548
549 id = st_theme_node_get_element_id (a_node);
550
551 if (id != NULL)
552 {
553 if (!strqcmp (id, a_add_sel->content.id_name->stryng->str,
554 a_add_sel->content.id_name->stryng->len))
555 {
556 result = TRUE;
557 }
558 }
559
560 return result;
561 }
562
563 /**
564 *additional_selector_matches_style:
565 *Evaluates if a given additional selector matches an style node.
566 *@param a_add_sel the additional selector to consider.
567 *@param a_node the style node to consider.
568 *@return TRUE is a_add_sel matches a_node, FALSE otherwise.
569 */
570 static gboolean
571 additional_selector_matches_style (StTheme *a_this,
572 CRAdditionalSel *a_add_sel,
573 StThemeNode *a_node)
574 {
575 CRAdditionalSel *cur_add_sel = NULL;
576
577 g_return_val_if_fail (a_add_sel, FALSE);
578
579 for (cur_add_sel = a_add_sel; cur_add_sel; cur_add_sel = cur_add_sel->next)
580 {
581 switch (cur_add_sel->type)
582 {
583 case NO_ADD_SELECTOR:
584 return FALSE;
585 case CLASS_ADD_SELECTOR:
586 if (!class_add_sel_matches_style (cur_add_sel, a_node))
587 return FALSE;
588 break;
589 case ID_ADD_SELECTOR:
590 if (!id_add_sel_matches_style (cur_add_sel, a_node))
591 return FALSE;
592 break;
593 case ATTRIBUTE_ADD_SELECTOR:
594 g_warning ("Attribute selectors not supported");
595 return FALSE;
596 case PSEUDO_CLASS_ADD_SELECTOR:
597 if (!pseudo_class_add_sel_matches_style (a_this, cur_add_sel, a_node))
598 return FALSE;
599 break;
600 }
601 }
602
603 return TRUE;
604 }
605
606 static gboolean
607 element_name_matches_type (const char *element_name,
608 GType element_type)
609 {
610 if (element_type == G_TYPE_NONE)
611 {
612 return strcmp (element_name, "stage") == 0;
613 }
614 else
615 {
616 GType match_type = g_type_from_name (element_name);
617 if (match_type == G_TYPE_INVALID)
618 return FALSE;
619
620 return g_type_is_a (element_type, match_type);
621 }
622 }
623
624 /*
625 *Evaluate a selector (a simple selectors list) and says
626 *if it matches the style node given in parameter.
627 *The algorithm used here is the following:
628 *Walk the combinator separated list of simple selectors backward, starting
629 *from the end of the list. For each simple selector, looks if
630 *if matches the current style.
631 *
632 *@param a_this the selection engine.
633 *@param a_sel the simple selection list.
634 *@param a_node the style node.
635 *@param a_result out parameter. Set to true if the
636 *selector matches the style node, FALSE otherwise.
637 *@param a_recurse if set to TRUE, the function will walk to
638 *the next simple selector (after the evaluation of the current one)
639 *and recursively evaluate it. Must be usually set to TRUE unless you
640 *know what you are doing.
641 */
642 static enum CRStatus
643 sel_matches_style_real (StTheme *a_this,
644 CRSimpleSel *a_sel,
645 StThemeNode *a_node,
646 gboolean *a_result,
647 gboolean a_eval_sel_list_from_end,
648 gboolean a_recurse)
649 {
650 CRSimpleSel *cur_sel = NULL;
651 StThemeNode *cur_node = NULL;
652 GType cur_type;
653
654 *a_result = FALSE;
655
656 if (a_eval_sel_list_from_end)
657 {
658 /*go and get the last simple selector of the list */
659 for (cur_sel = a_sel; cur_sel && cur_sel->next; cur_sel = cur_sel->next)
660 ;
661 }
662 else
663 {
664 cur_sel = a_sel;
665 }
666
667 cur_node = a_node;
668 cur_type = st_theme_node_get_element_type (cur_node);
669
670 while (cur_sel)
671 {
672 if (((cur_sel->type_mask & TYPE_SELECTOR)
673 && (cur_sel->name
674 && cur_sel->name->stryng
675 && cur_sel->name->stryng->str)
676 &&
677 (element_name_matches_type (cur_sel->name->stryng->str, cur_type)))
678 || (cur_sel->type_mask & UNIVERSAL_SELECTOR))
679 {
680 /*
681 *this simple selector
682 *matches the current style node
683 *Let's see if the preceding
684 *simple selectors also match
685 *their style node counterpart.
686 */
687 if (cur_sel->add_sel)
688 {
689 if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node))
690 goto walk_a_step_in_expr;
691 else
692 goto done;
693 }
694 else
695 goto walk_a_step_in_expr;
696 }
697 if (!(cur_sel->type_mask & TYPE_SELECTOR)
698 && !(cur_sel->type_mask & UNIVERSAL_SELECTOR))
699 {
700 if (!cur_sel->add_sel)
701 goto done;
702 if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node))
703 goto walk_a_step_in_expr;
704 else
705 goto done;
706 }
707 else
708 {
709 goto done;
710 }
711
712 walk_a_step_in_expr:
713 if (a_recurse == FALSE)
714 {
715 *a_result = TRUE;
716 goto done;
717 }
718
719 /*
720 *here, depending on the combinator of cur_sel
721 *choose the axis of the element tree traversal
722 *and walk one step in the element tree.
723 */
724 if (!cur_sel->prev)
725 break;
726
727 switch (cur_sel->combinator)
728 {
729 case NO_COMBINATOR:
730 break;
731
732 case COMB_WS: /*descendant selector */
733 {
734 StThemeNode *n = NULL;
735
736 /*
737 *walk the element tree upward looking for a parent
738 *style that matches the preceding selector.
739 */
740 for (n = st_theme_node_get_parent (a_node); n; n = st_theme_node_get_parent (n))
741 {
742 enum CRStatus status;
743 gboolean matches = FALSE;
744
745 status = sel_matches_style_real (a_this, cur_sel->prev, n, &matches, FALSE, TRUE);
746
747 if (status != CR_OK)
748 goto done;
749
750 if (matches)
751 {
752 cur_node = n;
753 cur_type = st_theme_node_get_element_type (cur_node);
754 break;
755 }
756 }
757
758 if (!n)
759 {
760 /*
761 *didn't find any ancestor that matches
762 *the previous simple selector.
763 */
764 goto done;
765 }
766 /*
767 *in this case, the preceding simple sel
768 *will have been interpreted twice, which
769 *is a cpu and mem waste ... I need to find
770 *another way to do this. Anyway, this is
771 *my first attempt to write this function and
772 *I am a bit clueless.
773 */
774 break;
775 }
776
777 case COMB_PLUS:
778 g_warning ("+ combinators are not supported");
779 goto done;
780
781 case COMB_GT:
782 cur_node = st_theme_node_get_parent (cur_node);
783 if (!cur_node)
784 goto done;
785 cur_type = st_theme_node_get_element_type (cur_node);
786 break;
787
788 default:
789 goto done;
790 }
791
792 cur_sel = cur_sel->prev;
793 }
794
795 /*
796 *if we reached this point, it means the selector matches
797 *the style node.
798 */
799 *a_result = TRUE;
800
801 done:
802 return CR_OK;
803 }
804
805 static void
806 add_matched_properties (StTheme *a_this,
807 CRStyleSheet *a_nodesheet,
808 StThemeNode *a_node,
809 GPtrArray *props)
810 {
811 CRStatement *cur_stmt = NULL;
812 CRSelector *sel_list = NULL;
813 CRSelector *cur_sel = NULL;
814 gboolean matches = FALSE;
815 enum CRStatus status = CR_OK;
816
817 /*
818 *walk through the list of statements and,
819 *get the selectors list inside the statements that
820 *contain some, and try to match our style node in these
821 *selectors lists.
822 */
823 for (cur_stmt = a_nodesheet->statements; cur_stmt; cur_stmt = cur_stmt->next)
824 {
825 /*
826 *initialyze the selector list in which we will
827 *really perform the search.
828 */
829 sel_list = NULL;
830
831 /*
832 *get the the damn selector list in
833 *which we have to look
834 */
835 switch (cur_stmt->type)
836 {
837 case RULESET_STMT:
838 if (cur_stmt->kind.ruleset && cur_stmt->kind.ruleset->sel_list)
839 {
840 sel_list = cur_stmt->kind.ruleset->sel_list;
841 }
842 break;
843
844 case AT_MEDIA_RULE_STMT:
845 if (cur_stmt->kind.media_rule
846 && cur_stmt->kind.media_rule->rulesets
847 && cur_stmt->kind.media_rule->rulesets->kind.ruleset
848 && cur_stmt->kind.media_rule->rulesets->kind.ruleset->sel_list)
849 {
850 sel_list = cur_stmt->kind.media_rule->rulesets->kind.ruleset->sel_list;
851 }
852 break;
853
854 case AT_IMPORT_RULE_STMT:
855 {
856 CRAtImportRule *import_rule = cur_stmt->kind.import_rule;
857
858 if (import_rule->sheet == NULL)
859 {
860 char *filename = NULL;
861
862 if (import_rule->url->stryng && import_rule->url->stryng->str)
863 filename = _st_theme_resolve_url (a_this,
864 a_nodesheet,
865 import_rule->url->stryng->str);
866
867 if (filename)
868 import_rule->sheet = parse_stylesheet (filename, NULL);
869
870 if (import_rule->sheet)
871 {
872 insert_stylesheet (a_this, filename, import_rule->sheet);
873 /* refcount of stylesheets starts off at zero, so we don't need to unref! */
874 }
875 else
876 {
877 /* Set a marker to avoid repeatedly trying to parse a non-existent or
878 * broken stylesheet
879 */
880 import_rule->sheet = (CRStyleSheet *) - 1;
881 }
882
883 if (filename)
884 g_free (filename);
885 }
886
887 if (import_rule->sheet != (CRStyleSheet *) - 1)
888 {
889 add_matched_properties (a_this, import_rule->sheet,
890 a_node, props);
891 }
892 }
893 break;
894 default:
895 break;
896 }
897
898 if (!sel_list)
899 continue;
900
901 /*
902 *now, we have a comma separated selector list to look in.
903 *let's walk it and try to match the style node
904 *on each item of the list.
905 */
906 for (cur_sel = sel_list; cur_sel; cur_sel = cur_sel->next)
907 {
908 if (!cur_sel->simple_sel)
909 continue;
910
911 status = sel_matches_style_real (a_this, cur_sel->simple_sel, a_node, &matches, TRUE, TRUE);
912
913 if (status == CR_OK && matches)
914 {
915 CRDeclaration *cur_decl = NULL;
916
917 /* In order to sort the matching properties, we need to compute the
918 * specificity of the selector that actually matched this
919 * element. In a non-thread-safe fashion, we store it in the
920 * ruleset. (Fixing this would mean cut-and-pasting
921 * cr_simple_sel_compute_specificity(), and have no need for
922 * thread-safety anyways.)
923 *
924 * Once we've sorted the properties, the specificity no longer
925 * matters and it can be safely overriden.
926 */
927 cr_simple_sel_compute_specificity (cur_sel->simple_sel);
928
929 cur_stmt->specificity = cur_sel->simple_sel->specificity;
930
931 for (cur_decl = cur_stmt->kind.ruleset->decl_list; cur_decl; cur_decl = cur_decl->next)
932 g_ptr_array_add (props, cur_decl);
933 }
934 }
935 }
936 }
937
938 #define ORIGIN_OFFSET_IMPORTANT (NB_ORIGINS)
939 #define ORIGIN_OFFSET_EXTENSION (NB_ORIGINS * 2)
940
941 static inline int
942 get_origin (const CRDeclaration * decl)
943 {
944 enum CRStyleOrigin origin = decl->parent_statement->parent_sheet->origin;
945 gboolean is_extension_sheet = GPOINTER_TO_UINT (decl->parent_statement->parent_sheet->app_data);
946
947 if (decl->important)
948 origin += ORIGIN_OFFSET_IMPORTANT;
949
950 if (is_extension_sheet)
951 origin += ORIGIN_OFFSET_EXTENSION;
952
953 return origin;
954 }
955
956 /* Order of comparison is so that higher priority statements compare after
957 * lower priority statements */
958 static int
959 compare_declarations (gconstpointer a,
960 gconstpointer b)
961 {
962 /* g_ptr_array_sort() is broooken */
963 CRDeclaration *decl_a = *(CRDeclaration **) a;
964 CRDeclaration *decl_b = *(CRDeclaration **) b;
965
966 int origin_a = get_origin (decl_a);
967 int origin_b = get_origin (decl_b);
968
969 if (origin_a != origin_b)
970 return origin_a - origin_b;
971
972 if (decl_a->parent_statement->specificity != decl_b->parent_statement->specificity)
973 return decl_a->parent_statement->specificity - decl_b->parent_statement->specificity;
974
975 return 0;
976 }
977
978 GPtrArray *
979 _st_theme_get_matched_properties (StTheme *theme,
980 StThemeNode *node)
981 {
982 enum CRStyleOrigin origin = 0;
983 CRStyleSheet *sheet = NULL;
984 GPtrArray *props = g_ptr_array_new ();
985 GSList *iter;
986
987 g_return_val_if_fail (ST_IS_THEME (theme), NULL);
988 g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
989
990 for (origin = ORIGIN_UA; origin < NB_ORIGINS; origin++)
991 {
992 sheet = cr_cascade_get_sheet (theme->cascade, origin);
993 if (!sheet)
994 continue;
995
996 add_matched_properties (theme, sheet, node, props);
997 }
998
999 for (iter = theme->custom_stylesheets; iter; iter = iter->next)
1000 add_matched_properties (theme, iter->data, node, props);
1001
1002 /* We count on a stable sort here so that later declarations come
1003 * after earlier declarations */
1004 g_ptr_array_sort (props, compare_declarations);
1005
1006 return props;
1007 }
1008
1009 /* Resolve an url from an url() reference in a stylesheet into an absolute
1010 * local filename, if possible. The resolution here is distinctly lame and
1011 * will fail on many examples.
1012 */
1013 char *
1014 _st_theme_resolve_url (StTheme *theme,
1015 CRStyleSheet *base_stylesheet,
1016 const char *url)
1017 {
1018 const char *base_filename = NULL;
1019 char *dirname;
1020 char *filename;
1021
1022 /* Handle absolute file:/ URLs */
1023 if (g_str_has_prefix (url, "file:") ||
1024 g_str_has_prefix (url, "File:") ||
1025 g_str_has_prefix (url, "FILE:"))
1026 {
1027 GError *error = NULL;
1028 char *filename;
1029
1030 filename = g_filename_from_uri (url, NULL, &error);
1031 if (filename == NULL)
1032 {
1033 g_warning ("%s", error->message);
1034 g_error_free (error);
1035 }
1036
1037 return NULL;
1038 }
1039
1040 /* Guard against http:/ URLs */
1041
1042 if (g_str_has_prefix (url, "http:") ||
1043 g_str_has_prefix (url, "Http:") ||
1044 g_str_has_prefix (url, "HTTP:"))
1045 {
1046 g_warning ("Http URL '%s' in theme stylesheet is not supported", url);
1047 return NULL;
1048 }
1049
1050 /* Assume anything else is a relative URL, and "resolve" it
1051 */
1052 if (url[0] == '/')
1053 return g_strdup (url);
1054
1055 base_filename = g_hash_table_lookup (theme->filenames_by_stylesheet, base_stylesheet);
1056
1057 if (base_filename == NULL)
1058 {
1059 g_warning ("Can't get base to resolve url '%s'", url);
1060 return NULL;
1061 }
1062
1063 dirname = g_path_get_dirname (base_filename);
1064 filename = g_build_filename (dirname, url, NULL);
1065 g_free (dirname);
1066
1067 return filename;
1068 }