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"
24 #include "gtkcssprovider.h"
25 #include "gtkstylecontextprivate.h"
27 typedef struct _GtkCssSelectorClass GtkCssSelectorClass;
29 struct _GtkCssSelectorClass {
32 void (* print) (const GtkCssSelector *selector,
34 gboolean (* match) (const GtkCssSelector *selector,
35 const GtkCssMatcher *matcher);
36 GtkCssChange (* get_change) (const GtkCssSelector *selector);
38 guint increase_id_specificity :1;
39 guint increase_class_specificity :1;
40 guint increase_element_specificity :1;
43 struct _GtkCssSelector
45 const GtkCssSelectorClass *class; /* type of check this selector does */
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,
53 const GtkCssMatcher *matcher)
58 return selector->class->match (selector, matcher);
62 gtk_css_selector_get_change (const GtkCssSelector *selector)
67 return selector->class->get_change (selector);
70 static const GtkCssSelector *
71 gtk_css_selector_previous (const GtkCssSelector *selector)
73 selector = selector + 1;
75 return selector->class ? selector : NULL;
81 gtk_css_selector_descendant_print (const GtkCssSelector *selector,
84 g_string_append_c (string, ' ');
88 gtk_css_selector_descendant_match (const GtkCssSelector *selector,
89 const GtkCssMatcher *matcher)
91 GtkCssMatcher ancestor;
93 while (_gtk_css_matcher_get_parent (&ancestor, matcher))
97 if (gtk_css_selector_match (gtk_css_selector_previous (selector), matcher))
105 gtk_css_selector_descendant_get_change (const GtkCssSelector *selector)
107 return _gtk_css_change_for_child (gtk_css_selector_get_change (gtk_css_selector_previous (selector)));
110 static const GtkCssSelectorClass GTK_CSS_SELECTOR_DESCENDANT = {
112 gtk_css_selector_descendant_print,
113 gtk_css_selector_descendant_match,
114 gtk_css_selector_descendant_get_change,
121 gtk_css_selector_child_print (const GtkCssSelector *selector,
124 g_string_append (string, " > ");
128 gtk_css_selector_child_match (const GtkCssSelector *selector,
129 const GtkCssMatcher *matcher)
131 GtkCssMatcher parent;
133 if (!_gtk_css_matcher_get_parent (&parent, matcher))
136 return gtk_css_selector_match (gtk_css_selector_previous (selector), &parent);
140 gtk_css_selector_child_get_change (const GtkCssSelector *selector)
142 return _gtk_css_change_for_child (gtk_css_selector_get_change (gtk_css_selector_previous (selector)));
145 static const GtkCssSelectorClass GTK_CSS_SELECTOR_CHILD = {
147 gtk_css_selector_child_print,
148 gtk_css_selector_child_match,
149 gtk_css_selector_child_get_change,
156 gtk_css_selector_sibling_print (const GtkCssSelector *selector,
159 g_string_append (string, " ~ ");
163 gtk_css_selector_sibling_match (const GtkCssSelector *selector,
164 const GtkCssMatcher *matcher)
166 GtkCssMatcher previous;
168 while (_gtk_css_matcher_get_previous (&previous, matcher))
172 if (gtk_css_selector_match (gtk_css_selector_previous (selector), matcher))
180 gtk_css_selector_sibling_get_change (const GtkCssSelector *selector)
182 return _gtk_css_change_for_sibling (gtk_css_selector_get_change (gtk_css_selector_previous (selector)));
185 static const GtkCssSelectorClass GTK_CSS_SELECTOR_SIBLING = {
187 gtk_css_selector_sibling_print,
188 gtk_css_selector_sibling_match,
189 gtk_css_selector_sibling_get_change,
196 gtk_css_selector_adjacent_print (const GtkCssSelector *selector,
199 g_string_append (string, " + ");
203 gtk_css_selector_adjacent_match (const GtkCssSelector *selector,
204 const GtkCssMatcher *matcher)
206 GtkCssMatcher previous;
208 if (!_gtk_css_matcher_get_previous (&previous, matcher))
211 return gtk_css_selector_match (gtk_css_selector_previous (selector), &previous);
215 gtk_css_selector_adjacent_get_change (const GtkCssSelector *selector)
217 return _gtk_css_change_for_sibling (gtk_css_selector_get_change (gtk_css_selector_previous (selector)));
220 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ADJACENT = {
222 gtk_css_selector_adjacent_print,
223 gtk_css_selector_adjacent_match,
224 gtk_css_selector_adjacent_get_change,
231 gtk_css_selector_any_print (const GtkCssSelector *selector,
234 g_string_append_c (string, '*');
238 gtk_css_selector_any_match (const GtkCssSelector *selector,
239 const GtkCssMatcher *matcher)
241 const GtkCssSelector *previous = gtk_css_selector_previous (selector);
244 previous->class == >K_CSS_SELECTOR_DESCENDANT &&
245 _gtk_css_matcher_has_regions (matcher))
247 if (gtk_css_selector_match (gtk_css_selector_previous (previous), matcher))
251 return gtk_css_selector_match (previous, matcher);
255 gtk_css_selector_any_get_change (const GtkCssSelector *selector)
257 return gtk_css_selector_get_change (gtk_css_selector_previous (selector));
260 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ANY = {
262 gtk_css_selector_any_print,
263 gtk_css_selector_any_match,
264 gtk_css_selector_any_get_change,
271 gtk_css_selector_name_print (const GtkCssSelector *selector,
274 g_string_append (string, selector->data);
278 gtk_css_selector_name_match (const GtkCssSelector *selector,
279 const GtkCssMatcher *matcher)
281 if (!_gtk_css_matcher_has_name (matcher, selector->data))
284 return gtk_css_selector_match (gtk_css_selector_previous (selector), matcher);
288 gtk_css_selector_name_get_change (const GtkCssSelector *selector)
290 return gtk_css_selector_get_change (gtk_css_selector_previous (selector)) | GTK_CSS_CHANGE_NAME;
293 static const GtkCssSelectorClass GTK_CSS_SELECTOR_NAME = {
295 gtk_css_selector_name_print,
296 gtk_css_selector_name_match,
297 gtk_css_selector_name_get_change,
304 gtk_css_selector_region_print (const GtkCssSelector *selector,
307 g_string_append (string, selector->data);
311 gtk_css_selector_region_match (const GtkCssSelector *selector,
312 const GtkCssMatcher *matcher)
314 const GtkCssSelector *previous;
316 if (!_gtk_css_matcher_has_region (matcher, selector->data, 0))
319 previous = gtk_css_selector_previous (selector);
320 if (previous && previous->class == >K_CSS_SELECTOR_DESCENDANT &&
321 gtk_css_selector_match (gtk_css_selector_previous (previous), matcher))
324 return gtk_css_selector_match (previous, matcher);
328 gtk_css_selector_region_get_change (const GtkCssSelector *selector)
332 change = gtk_css_selector_get_change (gtk_css_selector_previous (selector));
333 change |= GTK_CSS_CHANGE_REGION;
334 change |= _gtk_css_change_for_child (change);
339 static const GtkCssSelectorClass GTK_CSS_SELECTOR_REGION = {
341 gtk_css_selector_region_print,
342 gtk_css_selector_region_match,
343 gtk_css_selector_region_get_change,
350 gtk_css_selector_class_print (const GtkCssSelector *selector,
353 g_string_append_c (string, '.');
354 g_string_append (string, selector->data);
358 gtk_css_selector_class_match (const GtkCssSelector *selector,
359 const GtkCssMatcher *matcher)
361 if (!_gtk_css_matcher_has_class (matcher, selector->data))
364 return gtk_css_selector_match (gtk_css_selector_previous (selector), matcher);
368 gtk_css_selector_class_get_change (const GtkCssSelector *selector)
370 return gtk_css_selector_get_change (gtk_css_selector_previous (selector)) | GTK_CSS_CHANGE_CLASS;
373 static const GtkCssSelectorClass GTK_CSS_SELECTOR_CLASS = {
375 gtk_css_selector_class_print,
376 gtk_css_selector_class_match,
377 gtk_css_selector_class_get_change,
384 gtk_css_selector_id_print (const GtkCssSelector *selector,
387 g_string_append_c (string, '#');
388 g_string_append (string, selector->data);
392 gtk_css_selector_id_match (const GtkCssSelector *selector,
393 const GtkCssMatcher *matcher)
395 if (!_gtk_css_matcher_has_id (matcher, selector->data))
398 return gtk_css_selector_match (gtk_css_selector_previous (selector), matcher);
402 gtk_css_selector_id_get_change (const GtkCssSelector *selector)
404 return gtk_css_selector_get_change (gtk_css_selector_previous (selector)) | GTK_CSS_CHANGE_ID;
407 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ID = {
409 gtk_css_selector_id_print,
410 gtk_css_selector_id_match,
411 gtk_css_selector_id_get_change,
415 /* PSEUDOCLASS FOR STATE */
418 gtk_css_selector_pseudoclass_state_print (const GtkCssSelector *selector,
421 static const char * state_names[] = {
432 state = GPOINTER_TO_UINT (selector->data);
433 g_string_append_c (string, ':');
435 for (i = 0; i < G_N_ELEMENTS (state_names); i++)
437 if (state == (1 << i))
439 g_string_append (string, state_names[i]);
444 g_assert_not_reached ();
448 gtk_css_selector_pseudoclass_state_match (const GtkCssSelector *selector,
449 const GtkCssMatcher *matcher)
451 GtkStateFlags state = GPOINTER_TO_UINT (selector->data);
453 if ((_gtk_css_matcher_get_state (matcher) & state) != state)
456 return gtk_css_selector_match (gtk_css_selector_previous (selector), matcher);
460 gtk_css_selector_pseudoclass_state_get_change (const GtkCssSelector *selector)
462 return gtk_css_selector_get_change (gtk_css_selector_previous (selector)) | GTK_CSS_CHANGE_STATE;
465 static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_STATE = {
467 gtk_css_selector_pseudoclass_state_print,
468 gtk_css_selector_pseudoclass_state_match,
469 gtk_css_selector_pseudoclass_state_get_change,
473 /* PSEUDOCLASS FOR POSITION */
476 gtk_css_selector_pseudoclass_position_print (const GtkCssSelector *selector,
479 static const char * flag_names[] = {
489 state = GPOINTER_TO_UINT (selector->data);
490 g_string_append_c (string, ':');
492 for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
494 if (state == (1 << i))
496 g_string_append (string, flag_names[i]);
501 g_assert_not_reached ();
505 gtk_css_selector_pseudoclass_position_match_for_region (const GtkCssSelector *selector,
506 const GtkCssMatcher *matcher)
508 GtkRegionFlags selector_flags;
509 const GtkCssSelector *previous;
511 selector_flags = GPOINTER_TO_UINT (selector->data);
512 selector = gtk_css_selector_previous (selector);
514 if (!_gtk_css_matcher_has_region (matcher, selector->data, selector_flags))
517 previous = gtk_css_selector_previous (selector);
518 if (previous && previous->class == >K_CSS_SELECTOR_DESCENDANT &&
519 gtk_css_selector_match (gtk_css_selector_previous (previous), matcher))
522 return gtk_css_selector_match (previous, matcher);
526 gtk_css_selector_pseudoclass_position_match (const GtkCssSelector *selector,
527 const GtkCssMatcher *matcher)
529 GtkRegionFlags region;
530 guint sibling, n_siblings;
531 const GtkCssSelector *previous;
533 previous = gtk_css_selector_previous (selector);
534 if (previous && previous->class == >K_CSS_SELECTOR_REGION)
535 return gtk_css_selector_pseudoclass_position_match_for_region (selector, matcher);
537 n_siblings = _gtk_css_matcher_get_n_siblings (matcher);
540 sibling = _gtk_css_matcher_get_sibling_index (matcher);
542 region = GPOINTER_TO_UINT (selector->data);
546 case GTK_REGION_EVEN:
554 case GTK_REGION_FIRST:
558 case GTK_REGION_LAST:
559 if (sibling + 1 != n_siblings)
562 case GTK_REGION_ONLY:
566 case GTK_REGION_SORTED:
569 g_assert_not_reached ();
573 return gtk_css_selector_match (previous, matcher);
577 gtk_css_selector_pseudoclass_position_get_change (const GtkCssSelector *selector)
579 return gtk_css_selector_get_change (gtk_css_selector_previous (selector)) | GTK_CSS_CHANGE_POSITION;
582 static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION = {
583 "pseudoclass-position",
584 gtk_css_selector_pseudoclass_position_print,
585 gtk_css_selector_pseudoclass_position_match,
586 gtk_css_selector_pseudoclass_position_get_change,
593 gtk_css_selector_size (const GtkCssSelector *selector)
599 selector = gtk_css_selector_previous (selector);
606 static GtkCssSelector *
607 gtk_css_selector_new (const GtkCssSelectorClass *class,
608 GtkCssSelector *selector,
613 size = gtk_css_selector_size (selector);
614 selector = g_realloc (selector, sizeof (GtkCssSelector) * (size + 1) + sizeof (gpointer));
616 selector[1].class = NULL;
618 memmove (selector + 1, selector, sizeof (GtkCssSelector) * size + sizeof (gpointer));
620 selector->class = class;
621 selector->data = data;
626 static GtkCssSelector *
627 parse_selector_class (GtkCssParser *parser, GtkCssSelector *selector)
631 name = _gtk_css_parser_try_name (parser, FALSE);
635 _gtk_css_parser_error (parser, "Expected a valid name for class");
637 _gtk_css_selector_free (selector);
641 selector = gtk_css_selector_new (>K_CSS_SELECTOR_CLASS,
643 g_intern_string (name));
650 static GtkCssSelector *
651 parse_selector_id (GtkCssParser *parser, GtkCssSelector *selector)
655 name = _gtk_css_parser_try_name (parser, FALSE);
659 _gtk_css_parser_error (parser, "Expected a valid name for id");
661 _gtk_css_selector_free (selector);
665 selector = gtk_css_selector_new (>K_CSS_SELECTOR_ID,
667 g_intern_string (name));
674 static GtkCssSelector *
675 parse_selector_pseudo_class (GtkCssParser *parser,
676 GtkCssSelector *selector)
680 GtkRegionFlags region_flag;
681 GtkStateFlags state_flag;
682 } pseudo_classes[] = {
683 { "first-child", GTK_REGION_FIRST, 0 },
684 { "last-child", GTK_REGION_LAST, 0 },
685 { "only-child", GTK_REGION_ONLY, 0 },
686 { "sorted", GTK_REGION_SORTED, 0 },
687 { "active", 0, GTK_STATE_FLAG_ACTIVE },
688 { "prelight", 0, GTK_STATE_FLAG_PRELIGHT },
689 { "hover", 0, GTK_STATE_FLAG_PRELIGHT },
690 { "selected", 0, GTK_STATE_FLAG_SELECTED },
691 { "insensitive", 0, GTK_STATE_FLAG_INSENSITIVE },
692 { "inconsistent", 0, GTK_STATE_FLAG_INCONSISTENT },
693 { "focused", 0, GTK_STATE_FLAG_FOCUSED },
694 { "focus", 0, GTK_STATE_FLAG_FOCUSED },
695 { "backdrop", 0, GTK_STATE_FLAG_BACKDROP },
697 }, nth_child_classes[] = {
698 { "first", GTK_REGION_FIRST, 0 },
699 { "last", GTK_REGION_LAST, 0 },
700 { "even", GTK_REGION_EVEN, 0 },
701 { "odd", GTK_REGION_ODD, 0 },
708 name = _gtk_css_parser_try_ident (parser, FALSE);
711 _gtk_css_parser_error (parser, "Missing name of pseudo-class");
713 _gtk_css_selector_free (selector);
717 if (_gtk_css_parser_try (parser, "(", TRUE))
719 char *function = name;
721 name = _gtk_css_parser_try_ident (parser, TRUE);
722 if (!_gtk_css_parser_try (parser, ")", FALSE))
724 _gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class");
726 _gtk_css_selector_free (selector);
730 if (g_ascii_strcasecmp (function, "nth-child") != 0)
732 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
733 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
734 "Unknown pseudo-class '%s(%s)'", function, name ? name : "");
735 _gtk_css_parser_take_error (parser, error);
739 _gtk_css_selector_free (selector);
747 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
748 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
749 "Unknown pseudo-class 'nth-child(%s)'", name);
750 _gtk_css_parser_take_error (parser, error);
752 _gtk_css_selector_free (selector);
756 classes = nth_child_classes;
759 classes = pseudo_classes;
761 for (i = 0; classes[i].name != NULL; i++)
763 if (g_ascii_strcasecmp (name, classes[i].name) == 0)
767 if (classes[i].region_flag)
768 selector = gtk_css_selector_new (>K_CSS_SELECTOR_PSEUDOCLASS_POSITION,
770 GUINT_TO_POINTER (classes[i].region_flag));
772 selector = gtk_css_selector_new (>K_CSS_SELECTOR_PSEUDOCLASS_STATE,
774 GUINT_TO_POINTER (classes[i].state_flag));
780 if (classes == nth_child_classes)
781 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
782 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
783 "Unknown pseudo-class 'nth-child(%s)'", name);
785 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
786 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
787 "Unknown pseudo-class '%s'", name);
789 _gtk_css_parser_take_error (parser, error);
792 _gtk_css_selector_free (selector);
797 static GtkCssSelector *
798 try_parse_name (GtkCssParser *parser,
799 GtkCssSelector *selector)
803 name = _gtk_css_parser_try_ident (parser, FALSE);
806 selector = gtk_css_selector_new (_gtk_style_context_check_region_name (name)
807 ? >K_CSS_SELECTOR_REGION
808 : >K_CSS_SELECTOR_NAME,
810 g_intern_string (name));
813 else if (_gtk_css_parser_try (parser, "*", FALSE))
814 selector = gtk_css_selector_new (>K_CSS_SELECTOR_ANY, selector, NULL);
819 static GtkCssSelector *
820 parse_simple_selector (GtkCssParser *parser,
821 GtkCssSelector *selector)
823 guint size = gtk_css_selector_size (selector);
825 selector = try_parse_name (parser, selector);
828 if (_gtk_css_parser_try (parser, "#", FALSE))
829 selector = parse_selector_id (parser, selector);
830 else if (_gtk_css_parser_try (parser, ".", FALSE))
831 selector = parse_selector_class (parser, selector);
832 else if (_gtk_css_parser_try (parser, ":", FALSE))
833 selector = parse_selector_pseudo_class (parser, selector);
834 else if (gtk_css_selector_size (selector) == size)
836 _gtk_css_parser_error (parser, "Expected a valid selector");
838 _gtk_css_selector_free (selector);
844 while (selector && !_gtk_css_parser_is_eof (parser));
846 _gtk_css_parser_skip_whitespace (parser);
852 _gtk_css_selector_parse (GtkCssParser *parser)
854 GtkCssSelector *selector = NULL;
856 while ((selector = parse_simple_selector (parser, selector)) &&
857 !_gtk_css_parser_is_eof (parser) &&
858 !_gtk_css_parser_begins_with (parser, ',') &&
859 !_gtk_css_parser_begins_with (parser, '{'))
861 if (_gtk_css_parser_try (parser, "+", TRUE))
862 selector = gtk_css_selector_new (>K_CSS_SELECTOR_ADJACENT, selector, NULL);
863 else if (_gtk_css_parser_try (parser, "~", TRUE))
864 selector = gtk_css_selector_new (>K_CSS_SELECTOR_SIBLING, selector, NULL);
865 else if (_gtk_css_parser_try (parser, ">", TRUE))
866 selector = gtk_css_selector_new (>K_CSS_SELECTOR_CHILD, selector, NULL);
868 selector = gtk_css_selector_new (>K_CSS_SELECTOR_DESCENDANT, selector, NULL);
875 _gtk_css_selector_free (GtkCssSelector *selector)
877 g_return_if_fail (selector != NULL);
883 _gtk_css_selector_print (const GtkCssSelector *selector,
886 const GtkCssSelector *previous;
888 g_return_if_fail (selector != NULL);
890 previous = gtk_css_selector_previous (selector);
892 _gtk_css_selector_print (previous, str);
894 selector->class->print (selector, str);
898 _gtk_css_selector_to_string (const GtkCssSelector *selector)
902 g_return_val_if_fail (selector != NULL, NULL);
904 string = g_string_new (NULL);
906 _gtk_css_selector_print (selector, string);
908 return g_string_free (string, FALSE);
912 _gtk_css_selector_get_change (const GtkCssSelector *selector)
914 g_return_val_if_fail (selector != NULL, 0);
916 return gtk_css_selector_get_change (selector);
920 * _gtk_css_selector_matches:
921 * @selector: the selector
922 * @path: the path to check
923 * @state: The state to match
925 * Checks if the @selector matches the given @path. If @length is
926 * smaller than the number of elements in @path, it is assumed that
927 * only the first @length element of @path are valid and the rest
928 * does not exist. This is useful for doing parent matches for the
931 * Returns: %TRUE if the selector matches @path
934 _gtk_css_selector_matches (const GtkCssSelector *selector,
935 const GtkCssMatcher *matcher)
938 g_return_val_if_fail (selector != NULL, FALSE);
939 g_return_val_if_fail (matcher != NULL, FALSE);
941 return gtk_css_selector_match (selector, matcher);
944 /* Computes specificity according to CSS 2.1.
945 * The arguments must be initialized to 0 */
947 _gtk_css_selector_get_specificity (const GtkCssSelector *selector,
952 for (; selector; selector = gtk_css_selector_previous (selector))
954 const GtkCssSelectorClass *klass = selector->class;
956 if (klass->increase_id_specificity)
958 if (klass->increase_class_specificity)
960 if (klass->increase_element_specificity)
966 _gtk_css_selector_compare (const GtkCssSelector *a,
967 const GtkCssSelector *b)
969 guint a_ids = 0, a_classes = 0, a_elements = 0;
970 guint b_ids = 0, b_classes = 0, b_elements = 0;
973 _gtk_css_selector_get_specificity (a, &a_ids, &a_classes, &a_elements);
974 _gtk_css_selector_get_specificity (b, &b_ids, &b_classes, &b_elements);
976 compare = a_ids - b_ids;
980 compare = a_classes - b_classes;
984 return a_elements - b_elements;
988 _gtk_css_selector_get_state_flags (const GtkCssSelector *selector)
990 GtkStateFlags state = 0;
992 g_return_val_if_fail (selector != NULL, 0);
994 for (; selector && selector->class == >K_CSS_SELECTOR_NAME; selector = gtk_css_selector_previous (selector))
995 state |= GPOINTER_TO_UINT (selector->data);