1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 2011 Benjamin Otte <otte@gnome.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20 #include "gtkcssselectorprivate.h"
22 #include "gtkcssprovider.h"
23 #include "gtkstylecontextprivate.h"
26 GTK_CSS_COMBINE_DESCANDANT,
30 struct _GtkCssSelector
32 GtkCssSelector * previous; /* link to next element in selector or NULL if last */
33 GtkCssCombinator combine; /* how to combine with the previous element */
34 const char * name; /* quarked name of element we match or NULL if any */
35 GType type; /* cache for type belonging to name - G_TYPE_INVALID if uncached */
36 GQuark * ids; /* 0-terminated list of required ids or NULL if none */
37 GQuark * classes; /* 0-terminated list of required classes or NULL if none */
38 GtkRegionFlags pseudo_classes; /* required pseudo classes */
39 GtkStateFlags state; /* required state flags (currently not checked when matching) */
42 static GtkCssSelector *
43 gtk_css_selector_new (GtkCssSelector *previous,
44 GtkCssCombinator combine,
48 GtkRegionFlags pseudo_classes,
51 GtkCssSelector *selector;
53 selector = g_slice_new0 (GtkCssSelector);
54 selector->previous = previous;
55 selector->combine = combine;
56 selector->name = name ? g_quark_to_string (g_quark_from_string (name)) : NULL;
57 selector->type = !name || _gtk_style_context_check_region_name (name) ? G_TYPE_NONE : G_TYPE_INVALID;
59 selector->classes = classes;
60 selector->pseudo_classes = pseudo_classes;
61 selector->state = state;
67 parse_selector_class (GtkCssParser *parser, GArray *classes)
72 name = _gtk_css_parser_try_name (parser, FALSE);
76 _gtk_css_parser_error (parser, "Expected a valid name for class");
80 qname = g_quark_from_string (name);
81 g_array_append_val (classes, qname);
87 parse_selector_name (GtkCssParser *parser, GArray *names)
92 name = _gtk_css_parser_try_name (parser, FALSE);
96 _gtk_css_parser_error (parser, "Expected a valid name for id");
100 qname = g_quark_from_string (name);
101 g_array_append_val (names, qname);
107 parse_selector_pseudo_class (GtkCssParser *parser,
108 GtkRegionFlags *region_to_modify,
109 GtkStateFlags *state_to_modify)
113 GtkRegionFlags region_flag;
114 GtkStateFlags state_flag;
115 } pseudo_classes[] = {
116 { "first-child", GTK_REGION_FIRST, 0 },
117 { "last-child", GTK_REGION_LAST, 0 },
118 { "only-child", GTK_REGION_ONLY, 0 },
119 { "sorted", GTK_REGION_SORTED, 0 },
120 { "active", 0, GTK_STATE_FLAG_ACTIVE },
121 { "prelight", 0, GTK_STATE_FLAG_PRELIGHT },
122 { "hover", 0, GTK_STATE_FLAG_PRELIGHT },
123 { "selected", 0, GTK_STATE_FLAG_SELECTED },
124 { "insensitive", 0, GTK_STATE_FLAG_INSENSITIVE },
125 { "inconsistent", 0, GTK_STATE_FLAG_INCONSISTENT },
126 { "focused", 0, GTK_STATE_FLAG_FOCUSED },
127 { "focus", 0, GTK_STATE_FLAG_FOCUSED },
128 { "backdrop", 0, GTK_STATE_FLAG_BACKDROP },
130 }, nth_child_classes[] = {
131 { "first", GTK_REGION_FIRST, 0 },
132 { "last", GTK_REGION_LAST, 0 },
133 { "even", GTK_REGION_EVEN, 0 },
134 { "odd", GTK_REGION_ODD, 0 },
141 name = _gtk_css_parser_try_ident (parser, FALSE);
144 _gtk_css_parser_error (parser, "Missing name of pseudo-class");
148 if (_gtk_css_parser_try (parser, "(", TRUE))
150 char *function = name;
152 name = _gtk_css_parser_try_ident (parser, TRUE);
153 if (!_gtk_css_parser_try (parser, ")", FALSE))
155 _gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class");
159 if (g_ascii_strcasecmp (function, "nth-child") != 0)
161 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
162 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
163 "Unknown pseudo-class '%s(%s)'", function, name ? name : "");
164 _gtk_css_parser_take_error (parser, error);
174 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
175 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
176 "Unknown pseudo-class 'nth-child(%s)'", name);
177 _gtk_css_parser_take_error (parser, error);
181 classes = nth_child_classes;
184 classes = pseudo_classes;
186 for (i = 0; classes[i].name != NULL; i++)
188 if (g_ascii_strcasecmp (name, classes[i].name) == 0)
190 if ((*region_to_modify & classes[i].region_flag) ||
191 (*state_to_modify & classes[i].state_flag))
193 if (classes == nth_child_classes)
194 _gtk_css_parser_error (parser, "Duplicate pseudo-class 'nth-child(%s)'", name);
196 _gtk_css_parser_error (parser, "Duplicate pseudo-class '%s'", name);
198 *region_to_modify |= classes[i].region_flag;
199 *state_to_modify |= classes[i].state_flag;
206 if (classes == nth_child_classes)
207 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
208 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
209 "Unknown pseudo-class 'nth-child(%s)'", name);
211 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
212 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
213 "Unknown pseudo-class '%s'", name);
216 _gtk_css_parser_take_error (parser, error);
222 parse_simple_selector (GtkCssParser *parser,
226 GtkRegionFlags *pseudo_classes,
227 GtkStateFlags *state)
229 gboolean parsed_something;
231 *name = _gtk_css_parser_try_ident (parser, FALSE);
233 parsed_something = TRUE;
235 parsed_something = _gtk_css_parser_try (parser, "*", FALSE);
238 if (_gtk_css_parser_try (parser, "#", FALSE))
240 if (!parse_selector_name (parser, ids))
243 else if (_gtk_css_parser_try (parser, ".", FALSE))
245 if (!parse_selector_class (parser, classes))
248 else if (_gtk_css_parser_try (parser, ":", FALSE))
250 if (!parse_selector_pseudo_class (parser, pseudo_classes, state))
253 else if (!parsed_something)
255 _gtk_css_parser_error (parser, "Expected a valid selector");
261 parsed_something = TRUE;
263 while (!_gtk_css_parser_is_eof (parser));
265 _gtk_css_parser_skip_whitespace (parser);
270 _gtk_css_selector_parse (GtkCssParser *parser)
272 GtkCssSelector *selector = NULL;
276 GArray *ids = g_array_new (TRUE, FALSE, sizeof (GQuark));
277 GArray *classes = g_array_new (TRUE, FALSE, sizeof (GQuark));
278 GtkRegionFlags pseudo_classes = 0;
279 GtkStateFlags state = 0;
280 GtkCssCombinator combine = GTK_CSS_COMBINE_DESCANDANT;
284 if (_gtk_css_parser_try (parser, ">", TRUE))
285 combine = GTK_CSS_COMBINE_CHILD;
288 if (!parse_simple_selector (parser, &name, ids, classes, &pseudo_classes, &state))
290 g_array_free (ids, TRUE);
291 g_array_free (classes, TRUE);
293 _gtk_css_selector_free (selector);
297 selector = gtk_css_selector_new (selector,
300 (GQuark *) g_array_free (ids, ids->len == 0),
301 (GQuark *) g_array_free (classes, classes->len == 0),
306 while (!_gtk_css_parser_is_eof (parser) &&
307 !_gtk_css_parser_begins_with (parser, ',') &&
308 !_gtk_css_parser_begins_with (parser, '{'));
314 _gtk_css_selector_free (GtkCssSelector *selector)
316 g_return_if_fail (selector != NULL);
318 if (selector->previous)
319 _gtk_css_selector_free (selector->previous);
321 g_free (selector->ids);
322 g_free (selector->classes);
324 g_slice_free (GtkCssSelector, selector);
328 _gtk_css_selector_print (const GtkCssSelector *selector,
331 if (selector->previous)
333 _gtk_css_selector_print (selector->previous, str);
334 switch (selector->combine)
336 case GTK_CSS_COMBINE_DESCANDANT:
337 g_string_append (str, " ");
339 case GTK_CSS_COMBINE_CHILD:
340 g_string_append (str, " > ");
343 g_assert_not_reached ();
348 g_string_append (str, selector->name);
349 else if (selector->ids == NULL &&
350 selector->classes == NULL &&
351 selector->pseudo_classes == 0 &&
352 selector->state == 0)
353 g_string_append (str, "*");
359 for (id = selector->ids; *id != 0; id++)
361 g_string_append_c (str, '#');
362 g_string_append (str, g_quark_to_string (*id));
366 if (selector->classes)
370 for (class = selector->classes; *class != 0; class++)
372 g_string_append_c (str, '.');
373 g_string_append (str, g_quark_to_string (*class));
377 if (selector->pseudo_classes)
379 static const char * flag_names[] = {
389 for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
391 if (selector->pseudo_classes & (1 << i))
393 g_string_append_c (str, ':');
394 g_string_append (str, flag_names[i]);
401 static const char * state_names[] = {
412 for (i = 0; i < G_N_ELEMENTS (state_names); i++)
414 if (selector->state & (1 << i))
416 g_string_append_c (str, ':');
417 g_string_append (str, state_names[i]);
424 _gtk_css_selector_to_string (const GtkCssSelector *selector)
428 g_return_val_if_fail (selector != NULL, NULL);
430 string = g_string_new (NULL);
432 _gtk_css_selector_print (selector, string);
434 return g_string_free (string, FALSE);
437 static GtkRegionFlags
438 compute_region_flags_for_index (const GtkWidgetPath *path,
441 const GtkWidgetPath *siblings;
442 guint sibling_id, n_siblings;
443 GtkRegionFlags flags;
445 siblings = gtk_widget_path_iter_get_siblings (path, id);
446 if (siblings == NULL)
449 sibling_id = gtk_widget_path_iter_get_sibling_index (path, id);
450 n_siblings = gtk_widget_path_length (siblings);
452 flags = (sibling_id % 2) ? GTK_REGION_EVEN : GTK_REGION_ODD;
454 flags |= GTK_REGION_FIRST;
455 if (sibling_id + 1 == n_siblings)
456 flags |= GTK_REGION_LAST;
458 flags |= GTK_REGION_ONLY;
464 gtk_css_selector_matches_type (const GtkCssSelector *selector,
465 const GtkWidgetPath *path,
468 if (selector->pseudo_classes)
470 GtkRegionFlags flags = compute_region_flags_for_index (path, id);
472 if ((selector->pseudo_classes & flags) != selector->pseudo_classes)
476 if (selector->name == NULL)
479 if (selector->type == G_TYPE_NONE)
482 /* ugh, assigning to a const variable */
483 if (selector->type == G_TYPE_INVALID)
484 ((GtkCssSelector *) selector)->type = g_type_from_name (selector->name);
486 if (selector->type == G_TYPE_INVALID)
489 return g_type_is_a (gtk_widget_path_iter_get_object_type (path, id), selector->type);
493 gtk_css_selector_matches_region (const GtkCssSelector *selector,
494 const GtkWidgetPath *path,
498 GtkRegionFlags flags;
500 if (selector->name == NULL)
503 if (selector->name != region)
506 if (!gtk_widget_path_iter_has_region (path, id, region, &flags))
508 /* This function must be called with existing regions */
509 g_assert_not_reached ();
512 return (selector->pseudo_classes & flags) == selector->pseudo_classes;
516 gtk_css_selector_matches_rest (const GtkCssSelector *selector,
517 const GtkWidgetPath *path,
524 for (name = selector->ids; *name; name++)
526 if (!gtk_widget_path_iter_has_qname (path, id, *name))
531 if (selector->classes)
535 for (class = selector->classes; *class; class++)
537 if (!gtk_widget_path_iter_has_qclass (path, id, *class))
546 gtk_css_selector_matches_previous (const GtkCssSelector *selector,
547 const GtkWidgetPath *path,
552 gtk_css_selector_matches_from (const GtkCssSelector *selector,
553 const GtkWidgetPath *path,
559 if (!gtk_css_selector_matches_rest (selector, path, id))
562 for (l = regions; l; l = l->next)
564 const char *region = l->data;
566 if (gtk_css_selector_matches_region (selector, path, id, region))
571 remaining = g_slist_copy (regions);
572 remaining = g_slist_remove (remaining, region);
573 match = gtk_css_selector_matches_previous (selector,
577 g_slist_free (remaining);
583 if (gtk_css_selector_matches_type (selector, path, id))
589 return selector->previous == NULL;
591 regions = gtk_widget_path_iter_list_regions (path, id - 1);
592 match = gtk_css_selector_matches_previous (selector,
596 g_slist_free (regions);
604 gtk_css_selector_matches_previous (const GtkCssSelector *selector,
605 const GtkWidgetPath *path,
609 if (!selector->previous)
612 if (gtk_css_selector_matches_from (selector->previous,
618 if (selector->combine == GTK_CSS_COMBINE_DESCANDANT)
620 /* with this magic we run the loop while id >= 0 */
626 list = gtk_widget_path_iter_list_regions (path, id);
627 match = gtk_css_selector_matches_from (selector->previous,
641 * _gtk_css_selector_matches:
642 * @selector: the selector
643 * @path: the path to check
644 * @state: The state to match
646 * Checks if the @selector matches the given @path. If @length is
647 * smaller than the number of elements in @path, it is assumed that
648 * only the first @length element of @path are valid and the rest
649 * does not exist. This is useful for doing parent matches for the
652 * Returns: %TRUE if the selector matches @path
655 _gtk_css_selector_matches (const GtkCssSelector *selector,
656 const GtkWidgetPath *path,
663 g_return_val_if_fail (selector != NULL, FALSE);
664 g_return_val_if_fail (path != NULL, FALSE);
666 if ((selector->state & state) != selector->state)
669 length = gtk_widget_path_length (path);
673 list = gtk_widget_path_iter_list_regions (path, length - 1);
674 match = gtk_css_selector_matches_from (selector,
685 /* http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel */
686 v = v - ((v >> 1) & 0x55555555);
687 v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
688 return (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
691 /* Computes specificity according to CSS 2.1.
692 * The arguments must be initialized to 0 */
694 _gtk_css_selector_get_specificity (const GtkCssSelector *selector,
701 if (selector->previous)
702 _gtk_css_selector_get_specificity (selector->previous, ids, classes, elements);
705 for (count = selector->ids; *count; count++)
708 if (selector->classes)
709 for (count = selector->classes; *count; count++)
712 *classes += count_bits (selector->state) + count_bits (selector->pseudo_classes);
719 _gtk_css_selector_compare (const GtkCssSelector *a,
720 const GtkCssSelector *b)
722 guint a_ids = 0, a_classes = 0, a_elements = 0;
723 guint b_ids = 0, b_classes = 0, b_elements = 0;
726 _gtk_css_selector_get_specificity (a, &a_ids, &a_classes, &a_elements);
727 _gtk_css_selector_get_specificity (b, &b_ids, &b_classes, &b_elements);
729 compare = a_ids - b_ids;
733 compare = a_classes - b_classes;
737 return a_elements - b_elements;
741 _gtk_css_selector_get_state_flags (GtkCssSelector *selector)
743 g_return_val_if_fail (selector != NULL, 0);
745 return selector->state;