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"
25 typedef struct _GtkCssSelectorClass GtkCssSelectorClass;
27 struct _GtkCssSelectorClass {
30 void (* print) (const GtkCssSelector *selector,
32 gboolean (* match) (const GtkCssSelector *selector,
34 const GtkWidgetPath *path,
37 guint increase_id_specificity :1;
38 guint increase_class_specificity :1;
39 guint increase_element_specificity :1;
42 struct _GtkCssSelector
44 const GtkCssSelectorClass *class; /* type of check this selector does */
45 GtkCssSelector *previous; /* link to next element in selector or NULL if last */
46 gconstpointer data; /* data for matching:
47 - interned string for CLASS, NAME and ID
48 - GUINT_TO_POINTER() for PSEUDOCLASS_REGION/STATE */
52 gtk_css_selector_match (const GtkCssSelector *selector,
54 const GtkWidgetPath *path,
60 return selector->class->match (selector, state, path, id);
63 static const GtkCssSelector *
64 gtk_css_selector_previous (const GtkCssSelector *selector)
66 return selector->previous;
72 gtk_css_selector_any_print (const GtkCssSelector *selector,
75 g_string_append_c (string, '*');
79 gtk_css_selector_any_match (const GtkCssSelector *selector,
81 const GtkWidgetPath *path,
84 return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id);
87 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ANY = {
89 gtk_css_selector_any_print,
90 gtk_css_selector_any_match,
97 gtk_css_selector_descendant_print (const GtkCssSelector *selector,
100 g_string_append_c (string, ' ');
104 gtk_css_selector_descendant_match (const GtkCssSelector *selector,
106 const GtkWidgetPath *path,
111 if (gtk_css_selector_match (gtk_css_selector_previous (selector), 0, path, id))
118 static const GtkCssSelectorClass GTK_CSS_SELECTOR_DESCENDANT = {
120 gtk_css_selector_descendant_print,
121 gtk_css_selector_descendant_match,
128 gtk_css_selector_child_print (const GtkCssSelector *selector,
131 g_string_append (string, " > ");
135 gtk_css_selector_child_match (const GtkCssSelector *selector,
137 const GtkWidgetPath *path,
143 return gtk_css_selector_match (gtk_css_selector_previous (selector), 0, path, id - 1);
146 static const GtkCssSelectorClass GTK_CSS_SELECTOR_CHILD = {
148 gtk_css_selector_child_print,
149 gtk_css_selector_child_match,
156 gtk_css_selector_name_print (const GtkCssSelector *selector,
159 g_string_append (string, selector->data);
163 gtk_css_selector_name_match (const GtkCssSelector *selector,
165 const GtkWidgetPath *path,
168 GType type = g_type_from_name (selector->data);
170 if (!g_type_is_a (gtk_widget_path_iter_get_object_type (path, id), type))
173 return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id);
176 static const GtkCssSelectorClass GTK_CSS_SELECTOR_NAME = {
178 gtk_css_selector_name_print,
179 gtk_css_selector_name_match,
186 gtk_css_selector_region_print (const GtkCssSelector *selector,
189 g_string_append (string, selector->data);
193 gtk_css_selector_region_match (const GtkCssSelector *selector,
195 const GtkWidgetPath *path,
198 const GtkCssSelector *previous;
200 if (!gtk_widget_path_iter_has_region (path, id, selector->data, NULL))
203 previous = gtk_css_selector_previous (selector);
204 if (previous && previous->class == >K_CSS_SELECTOR_DESCENDANT &&
205 gtk_css_selector_match (gtk_css_selector_previous (previous), state, path, id))
208 return gtk_css_selector_match (previous, state, path, id);
211 static const GtkCssSelectorClass GTK_CSS_SELECTOR_REGION = {
213 gtk_css_selector_region_print,
214 gtk_css_selector_region_match,
221 gtk_css_selector_class_print (const GtkCssSelector *selector,
224 g_string_append_c (string, '.');
225 g_string_append (string, selector->data);
229 gtk_css_selector_class_match (const GtkCssSelector *selector,
231 const GtkWidgetPath *path,
234 if (!gtk_widget_path_iter_has_class (path, id, selector->data))
237 return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id);
240 static const GtkCssSelectorClass GTK_CSS_SELECTOR_CLASS = {
242 gtk_css_selector_class_print,
243 gtk_css_selector_class_match,
250 gtk_css_selector_id_print (const GtkCssSelector *selector,
253 g_string_append_c (string, '#');
254 g_string_append (string, selector->data);
258 gtk_css_selector_id_match (const GtkCssSelector *selector,
260 const GtkWidgetPath *path,
263 if (!gtk_widget_path_iter_has_name (path, id, selector->data))
266 return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id);
269 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ID = {
271 gtk_css_selector_id_print,
272 gtk_css_selector_id_match,
276 /* PSEUDOCLASS FOR STATE */
279 gtk_css_selector_pseudoclass_state_print (const GtkCssSelector *selector,
282 static const char * state_names[] = {
293 state = GPOINTER_TO_UINT (selector->data);
294 g_string_append_c (string, ':');
296 for (i = 0; i < G_N_ELEMENTS (state_names); i++)
298 if (state == (1 << i))
300 g_string_append (string, state_names[i]);
305 g_assert_not_reached ();
309 gtk_css_selector_pseudoclass_state_match (const GtkCssSelector *selector,
311 const GtkWidgetPath *path,
314 if (!(GPOINTER_TO_UINT (selector->data) & state))
317 return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id);
320 static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_STATE = {
322 gtk_css_selector_pseudoclass_state_print,
323 gtk_css_selector_pseudoclass_state_match,
327 /* PSEUDOCLASS FOR REGION */
330 gtk_css_selector_pseudoclass_region_print (const GtkCssSelector *selector,
333 static const char * flag_names[] = {
343 state = GPOINTER_TO_UINT (selector->data);
344 g_string_append_c (string, ':');
346 for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
348 if (state == (1 << i))
350 g_string_append (string, flag_names[i]);
355 g_assert_not_reached ();
359 gtk_css_selector_pseudoclass_region_match_for_region (const GtkCssSelector *selector,
361 const GtkWidgetPath *path,
364 GtkRegionFlags selector_flags, path_flags;
365 const GtkCssSelector *previous;
367 selector_flags = GPOINTER_TO_UINT (selector->data);
368 selector = gtk_css_selector_previous (selector);
370 if (!gtk_widget_path_iter_has_region (path, id, selector->data, &path_flags))
373 if ((selector_flags & path_flags) != selector_flags)
376 previous = gtk_css_selector_previous (selector);
377 if (previous && previous->class == >K_CSS_SELECTOR_DESCENDANT &&
378 gtk_css_selector_match (gtk_css_selector_previous (previous), state, path, id))
381 return gtk_css_selector_match (previous, state, path, id);
385 gtk_css_selector_pseudoclass_region_match (const GtkCssSelector *selector,
387 const GtkWidgetPath *path,
390 GtkRegionFlags region;
391 const GtkWidgetPath *siblings;
392 guint sibling_id, n_siblings;
393 const GtkCssSelector *previous;
395 previous = gtk_css_selector_previous (selector);
396 if (previous && previous->class == >K_CSS_SELECTOR_REGION)
397 return gtk_css_selector_pseudoclass_region_match_for_region (selector, state, path, id);
399 siblings = gtk_widget_path_iter_get_siblings (path, id);
400 if (siblings == NULL)
403 region = GPOINTER_TO_UINT (selector->data);
404 sibling_id = gtk_widget_path_iter_get_sibling_index (path, id);
405 n_siblings = gtk_widget_path_length (siblings);
409 case GTK_REGION_EVEN:
410 if (!(sibling_id % 2))
417 case GTK_REGION_FIRST:
421 case GTK_REGION_LAST:
422 if (sibling_id + 1 != n_siblings)
425 case GTK_REGION_ONLY:
429 case GTK_REGION_SORTED:
432 g_assert_not_reached ();
436 return gtk_css_selector_match (previous, state, path, id);
439 static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_REGION = {
440 "pseudoclass-region",
441 gtk_css_selector_pseudoclass_region_print,
442 gtk_css_selector_pseudoclass_region_match,
448 static GtkCssSelector *
449 gtk_css_selector_new (const GtkCssSelectorClass *class,
450 GtkCssSelector *previous,
453 GtkCssSelector *selector;
455 selector = g_slice_new0 (GtkCssSelector);
456 selector->class = class;
457 selector->previous = previous;
458 selector->data = data;
463 static GtkCssSelector *
464 parse_selector_class (GtkCssParser *parser, GtkCssSelector *selector)
468 name = _gtk_css_parser_try_name (parser, FALSE);
472 _gtk_css_parser_error (parser, "Expected a valid name for class");
474 _gtk_css_selector_free (selector);
478 selector = gtk_css_selector_new (>K_CSS_SELECTOR_CLASS,
480 g_intern_string (name));
487 static GtkCssSelector *
488 parse_selector_id (GtkCssParser *parser, GtkCssSelector *selector)
492 name = _gtk_css_parser_try_name (parser, FALSE);
496 _gtk_css_parser_error (parser, "Expected a valid name for id");
498 _gtk_css_selector_free (selector);
502 selector = gtk_css_selector_new (>K_CSS_SELECTOR_ID,
504 g_intern_string (name));
511 static GtkCssSelector *
512 parse_selector_pseudo_class (GtkCssParser *parser,
513 GtkCssSelector *selector)
517 GtkRegionFlags region_flag;
518 GtkStateFlags state_flag;
519 } pseudo_classes[] = {
520 { "first-child", GTK_REGION_FIRST, 0 },
521 { "last-child", GTK_REGION_LAST, 0 },
522 { "only-child", GTK_REGION_ONLY, 0 },
523 { "sorted", GTK_REGION_SORTED, 0 },
524 { "active", 0, GTK_STATE_FLAG_ACTIVE },
525 { "prelight", 0, GTK_STATE_FLAG_PRELIGHT },
526 { "hover", 0, GTK_STATE_FLAG_PRELIGHT },
527 { "selected", 0, GTK_STATE_FLAG_SELECTED },
528 { "insensitive", 0, GTK_STATE_FLAG_INSENSITIVE },
529 { "inconsistent", 0, GTK_STATE_FLAG_INCONSISTENT },
530 { "focused", 0, GTK_STATE_FLAG_FOCUSED },
531 { "focus", 0, GTK_STATE_FLAG_FOCUSED },
532 { "backdrop", 0, GTK_STATE_FLAG_BACKDROP },
534 }, nth_child_classes[] = {
535 { "first", GTK_REGION_FIRST, 0 },
536 { "last", GTK_REGION_LAST, 0 },
537 { "even", GTK_REGION_EVEN, 0 },
538 { "odd", GTK_REGION_ODD, 0 },
545 name = _gtk_css_parser_try_ident (parser, FALSE);
548 _gtk_css_parser_error (parser, "Missing name of pseudo-class");
550 _gtk_css_selector_free (selector);
554 if (_gtk_css_parser_try (parser, "(", TRUE))
556 char *function = name;
558 name = _gtk_css_parser_try_ident (parser, TRUE);
559 if (!_gtk_css_parser_try (parser, ")", FALSE))
561 _gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class");
563 _gtk_css_selector_free (selector);
567 if (g_ascii_strcasecmp (function, "nth-child") != 0)
569 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
570 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
571 "Unknown pseudo-class '%s(%s)'", function, name ? name : "");
572 _gtk_css_parser_take_error (parser, error);
576 _gtk_css_selector_free (selector);
584 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
585 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
586 "Unknown pseudo-class 'nth-child(%s)'", name);
587 _gtk_css_parser_take_error (parser, error);
589 _gtk_css_selector_free (selector);
593 classes = nth_child_classes;
596 classes = pseudo_classes;
598 for (i = 0; classes[i].name != NULL; i++)
600 if (g_ascii_strcasecmp (name, classes[i].name) == 0)
604 if (classes[i].region_flag)
605 selector = gtk_css_selector_new (>K_CSS_SELECTOR_PSEUDOCLASS_REGION,
607 GUINT_TO_POINTER (classes[i].region_flag));
609 selector = gtk_css_selector_new (>K_CSS_SELECTOR_PSEUDOCLASS_STATE,
611 GUINT_TO_POINTER (classes[i].state_flag));
617 if (classes == nth_child_classes)
618 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
619 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
620 "Unknown pseudo-class 'nth-child(%s)'", name);
622 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
623 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
624 "Unknown pseudo-class '%s'", name);
626 _gtk_css_parser_take_error (parser, error);
629 _gtk_css_selector_free (selector);
634 static GtkCssSelector *
635 try_parse_name (GtkCssParser *parser,
636 GtkCssSelector *selector)
640 name = _gtk_css_parser_try_ident (parser, FALSE);
643 selector = gtk_css_selector_new (_gtk_style_context_check_region_name (name)
644 ? >K_CSS_SELECTOR_REGION
645 : >K_CSS_SELECTOR_NAME,
647 g_intern_string (name));
650 else if (_gtk_css_parser_try (parser, "*", FALSE))
651 selector = gtk_css_selector_new (>K_CSS_SELECTOR_ANY, selector, NULL);
656 static GtkCssSelector *
657 parse_simple_selector (GtkCssParser *parser,
658 GtkCssSelector *previous)
660 GtkCssSelector *selector = previous;
662 selector = try_parse_name (parser, selector);
665 if (_gtk_css_parser_try (parser, "#", FALSE))
666 selector = parse_selector_id (parser, selector);
667 else if (_gtk_css_parser_try (parser, ".", FALSE))
668 selector = parse_selector_class (parser, selector);
669 else if (_gtk_css_parser_try (parser, ":", FALSE))
670 selector = parse_selector_pseudo_class (parser, selector);
671 else if (selector == previous)
673 _gtk_css_parser_error (parser, "Expected a valid selector");
675 _gtk_css_selector_free (selector);
681 while (selector && !_gtk_css_parser_is_eof (parser));
683 _gtk_css_parser_skip_whitespace (parser);
689 _gtk_css_selector_parse (GtkCssParser *parser)
691 GtkCssSelector *selector = NULL;
693 while ((selector = parse_simple_selector (parser, selector)) &&
694 !_gtk_css_parser_is_eof (parser) &&
695 !_gtk_css_parser_begins_with (parser, ',') &&
696 !_gtk_css_parser_begins_with (parser, '{'))
698 if (_gtk_css_parser_try (parser, ">", TRUE))
699 selector = gtk_css_selector_new (>K_CSS_SELECTOR_CHILD, selector, NULL);
701 selector = gtk_css_selector_new (>K_CSS_SELECTOR_DESCENDANT, selector, NULL);
708 _gtk_css_selector_free (GtkCssSelector *selector)
710 g_return_if_fail (selector != NULL);
712 if (selector->previous)
713 _gtk_css_selector_free (selector->previous);
715 g_slice_free (GtkCssSelector, selector);
719 _gtk_css_selector_print (const GtkCssSelector *selector,
722 const GtkCssSelector *previous;
724 g_return_if_fail (selector != NULL);
726 previous = gtk_css_selector_previous (selector);
728 _gtk_css_selector_print (previous, str);
730 selector->class->print (selector, str);
734 _gtk_css_selector_to_string (const GtkCssSelector *selector)
738 g_return_val_if_fail (selector != NULL, NULL);
740 string = g_string_new (NULL);
742 _gtk_css_selector_print (selector, string);
744 return g_string_free (string, FALSE);
748 * _gtk_css_selector_matches:
749 * @selector: the selector
750 * @path: the path to check
751 * @state: The state to match
753 * Checks if the @selector matches the given @path. If @length is
754 * smaller than the number of elements in @path, it is assumed that
755 * only the first @length element of @path are valid and the rest
756 * does not exist. This is useful for doing parent matches for the
759 * Returns: %TRUE if the selector matches @path
762 _gtk_css_selector_matches (const GtkCssSelector *selector,
763 const GtkWidgetPath *path,
768 g_return_val_if_fail (selector != NULL, FALSE);
769 g_return_val_if_fail (path != NULL, FALSE);
771 length = gtk_widget_path_length (path);
775 return gtk_css_selector_match (selector,
781 /* Computes specificity according to CSS 2.1.
782 * The arguments must be initialized to 0 */
784 _gtk_css_selector_get_specificity (const GtkCssSelector *selector,
789 for (; selector; selector = gtk_css_selector_previous (selector))
791 const GtkCssSelectorClass *klass = selector->class;
793 if (klass->increase_id_specificity)
795 if (klass->increase_class_specificity)
797 if (klass->increase_element_specificity)
803 _gtk_css_selector_compare (const GtkCssSelector *a,
804 const GtkCssSelector *b)
806 guint a_ids = 0, a_classes = 0, a_elements = 0;
807 guint b_ids = 0, b_classes = 0, b_elements = 0;
810 _gtk_css_selector_get_specificity (a, &a_ids, &a_classes, &a_elements);
811 _gtk_css_selector_get_specificity (b, &b_ids, &b_classes, &b_elements);
813 compare = a_ids - b_ids;
817 compare = a_classes - b_classes;
821 return a_elements - b_elements;
825 _gtk_css_selector_get_state_flags (const GtkCssSelector *selector)
827 GtkStateFlags state = 0;
829 g_return_val_if_fail (selector != NULL, 0);
831 for (; selector && selector->class == >K_CSS_SELECTOR_NAME; selector = gtk_css_selector_previous (selector))
832 state |= GPOINTER_TO_UINT (selector->data);