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,
36 const GtkWidgetPath *path,
40 guint increase_id_specificity :1;
41 guint increase_class_specificity :1;
42 guint increase_element_specificity :1;
45 struct _GtkCssSelector
47 const GtkCssSelectorClass *class; /* type of check this selector does */
48 gconstpointer data; /* data for matching:
49 - interned string for CLASS, NAME and ID
50 - GUINT_TO_POINTER() for PSEUDOCLASS_REGION/STATE */
54 gtk_css_selector_match (const GtkCssSelector *selector,
56 const GtkWidgetPath *path,
63 return selector->class->match (selector, state, path, id, sibling);
66 static const GtkCssSelector *
67 gtk_css_selector_previous (const GtkCssSelector *selector)
69 selector = selector + 1;
71 return selector->class ? selector : NULL;
77 gtk_css_selector_descendant_print (const GtkCssSelector *selector,
80 g_string_append_c (string, ' ');
84 gtk_css_selector_descendant_match (const GtkCssSelector *selector,
86 const GtkWidgetPath *path,
92 if (gtk_css_selector_match (gtk_css_selector_previous (selector),
96 gtk_widget_path_iter_get_sibling_index (path, id)))
103 static const GtkCssSelectorClass GTK_CSS_SELECTOR_DESCENDANT = {
105 gtk_css_selector_descendant_print,
106 gtk_css_selector_descendant_match,
113 gtk_css_selector_child_print (const GtkCssSelector *selector,
116 g_string_append (string, " > ");
120 gtk_css_selector_child_match (const GtkCssSelector *selector,
122 const GtkWidgetPath *path,
129 return gtk_css_selector_match (gtk_css_selector_previous (selector),
133 gtk_widget_path_iter_get_sibling_index (path, id - 1));
136 static const GtkCssSelectorClass GTK_CSS_SELECTOR_CHILD = {
138 gtk_css_selector_child_print,
139 gtk_css_selector_child_match,
146 gtk_css_selector_sibling_print (const GtkCssSelector *selector,
149 g_string_append (string, " ~ ");
153 gtk_css_selector_sibling_match (const GtkCssSelector *selector,
155 const GtkWidgetPath *path,
159 while (sibling-- > 0)
161 if (gtk_css_selector_match (gtk_css_selector_previous (selector),
172 static const GtkCssSelectorClass GTK_CSS_SELECTOR_SIBLING = {
174 gtk_css_selector_sibling_print,
175 gtk_css_selector_sibling_match,
182 gtk_css_selector_adjacent_print (const GtkCssSelector *selector,
185 g_string_append (string, " + ");
189 gtk_css_selector_adjacent_match (const GtkCssSelector *selector,
191 const GtkWidgetPath *path,
198 return gtk_css_selector_match (gtk_css_selector_previous (selector),
205 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ADJACENT = {
207 gtk_css_selector_adjacent_print,
208 gtk_css_selector_adjacent_match,
215 gtk_css_selector_any_print (const GtkCssSelector *selector,
218 g_string_append_c (string, '*');
222 gtk_css_selector_any_match (const GtkCssSelector *selector,
224 const GtkWidgetPath *path,
228 const GtkCssSelector *previous = gtk_css_selector_previous (selector);
232 previous->class == >K_CSS_SELECTOR_DESCENDANT &&
233 (regions = gtk_widget_path_iter_list_regions (path, id)) != NULL)
235 g_slist_free (regions);
236 if (gtk_css_selector_match (gtk_css_selector_previous (previous), state, path, id, sibling))
240 return gtk_css_selector_match (previous, state, path, id, sibling);
243 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ANY = {
245 gtk_css_selector_any_print,
246 gtk_css_selector_any_match,
253 gtk_css_selector_name_print (const GtkCssSelector *selector,
256 g_string_append (string, selector->data);
260 gtk_css_selector_name_match (const GtkCssSelector *selector,
262 const GtkWidgetPath *path,
266 GType type = g_type_from_name (selector->data);
268 if (!g_type_is_a (gtk_widget_path_iter_get_object_type (path, id), type))
271 return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id, sibling);
274 static const GtkCssSelectorClass GTK_CSS_SELECTOR_NAME = {
276 gtk_css_selector_name_print,
277 gtk_css_selector_name_match,
284 gtk_css_selector_region_print (const GtkCssSelector *selector,
287 g_string_append (string, selector->data);
291 gtk_css_selector_region_match (const GtkCssSelector *selector,
293 const GtkWidgetPath *path,
297 const GtkCssSelector *previous;
299 if (!gtk_widget_path_iter_has_region (path, id, selector->data, NULL))
302 previous = gtk_css_selector_previous (selector);
303 if (previous && previous->class == >K_CSS_SELECTOR_DESCENDANT &&
304 gtk_css_selector_match (gtk_css_selector_previous (previous), state, path, id, sibling))
307 return gtk_css_selector_match (previous, state, path, id, sibling);
310 static const GtkCssSelectorClass GTK_CSS_SELECTOR_REGION = {
312 gtk_css_selector_region_print,
313 gtk_css_selector_region_match,
320 gtk_css_selector_class_print (const GtkCssSelector *selector,
323 g_string_append_c (string, '.');
324 g_string_append (string, selector->data);
328 gtk_css_selector_class_match (const GtkCssSelector *selector,
330 const GtkWidgetPath *path,
334 if (!gtk_widget_path_iter_has_class (path, id, selector->data))
337 return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id, sibling);
340 static const GtkCssSelectorClass GTK_CSS_SELECTOR_CLASS = {
342 gtk_css_selector_class_print,
343 gtk_css_selector_class_match,
350 gtk_css_selector_id_print (const GtkCssSelector *selector,
353 g_string_append_c (string, '#');
354 g_string_append (string, selector->data);
358 gtk_css_selector_id_match (const GtkCssSelector *selector,
360 const GtkWidgetPath *path,
364 if (!gtk_widget_path_iter_has_name (path, id, selector->data))
367 return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id, sibling);
370 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ID = {
372 gtk_css_selector_id_print,
373 gtk_css_selector_id_match,
377 /* PSEUDOCLASS FOR STATE */
380 gtk_css_selector_pseudoclass_state_print (const GtkCssSelector *selector,
383 static const char * state_names[] = {
394 state = GPOINTER_TO_UINT (selector->data);
395 g_string_append_c (string, ':');
397 for (i = 0; i < G_N_ELEMENTS (state_names); i++)
399 if (state == (1 << i))
401 g_string_append (string, state_names[i]);
406 g_assert_not_reached ();
410 gtk_css_selector_pseudoclass_state_match (const GtkCssSelector *selector,
412 const GtkWidgetPath *path,
416 if (!(GPOINTER_TO_UINT (selector->data) & state))
419 return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id, sibling);
422 static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_STATE = {
424 gtk_css_selector_pseudoclass_state_print,
425 gtk_css_selector_pseudoclass_state_match,
429 /* PSEUDOCLASS FOR REGION */
432 gtk_css_selector_pseudoclass_region_print (const GtkCssSelector *selector,
435 static const char * flag_names[] = {
445 state = GPOINTER_TO_UINT (selector->data);
446 g_string_append_c (string, ':');
448 for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
450 if (state == (1 << i))
452 g_string_append (string, flag_names[i]);
457 g_assert_not_reached ();
461 gtk_css_selector_pseudoclass_region_match_for_region (const GtkCssSelector *selector,
463 const GtkWidgetPath *path,
467 GtkRegionFlags selector_flags, path_flags;
468 const GtkCssSelector *previous;
470 selector_flags = GPOINTER_TO_UINT (selector->data);
471 selector = gtk_css_selector_previous (selector);
473 if (!gtk_widget_path_iter_has_region (path, id, selector->data, &path_flags))
476 if ((selector_flags & path_flags) != selector_flags)
479 previous = gtk_css_selector_previous (selector);
480 if (previous && previous->class == >K_CSS_SELECTOR_DESCENDANT &&
481 gtk_css_selector_match (gtk_css_selector_previous (previous), state, path, id, sibling))
484 return gtk_css_selector_match (previous, state, path, id, sibling);
488 gtk_css_selector_pseudoclass_region_match (const GtkCssSelector *selector,
490 const GtkWidgetPath *path,
494 GtkRegionFlags region;
495 const GtkWidgetPath *siblings;
497 const GtkCssSelector *previous;
499 previous = gtk_css_selector_previous (selector);
500 if (previous && previous->class == >K_CSS_SELECTOR_REGION)
501 return gtk_css_selector_pseudoclass_region_match_for_region (selector, state, path, id, sibling);
503 siblings = gtk_widget_path_iter_get_siblings (path, id);
504 if (siblings == NULL)
507 region = GPOINTER_TO_UINT (selector->data);
508 n_siblings = gtk_widget_path_length (siblings);
512 case GTK_REGION_EVEN:
520 case GTK_REGION_FIRST:
524 case GTK_REGION_LAST:
525 if (sibling + 1 != n_siblings)
528 case GTK_REGION_ONLY:
532 case GTK_REGION_SORTED:
535 g_assert_not_reached ();
539 return gtk_css_selector_match (previous, state, path, id, sibling);
542 static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_REGION = {
543 "pseudoclass-region",
544 gtk_css_selector_pseudoclass_region_print,
545 gtk_css_selector_pseudoclass_region_match,
552 gtk_css_selector_size (const GtkCssSelector *selector)
558 selector = gtk_css_selector_previous (selector);
565 static GtkCssSelector *
566 gtk_css_selector_new (const GtkCssSelectorClass *class,
567 GtkCssSelector *selector,
572 size = gtk_css_selector_size (selector);
573 selector = g_realloc (selector, sizeof (GtkCssSelector) * (size + 1) + sizeof (gpointer));
575 selector[1].class = NULL;
577 memmove (selector + 1, selector, sizeof (GtkCssSelector) * size + sizeof (gpointer));
579 selector->class = class;
580 selector->data = data;
585 static GtkCssSelector *
586 parse_selector_class (GtkCssParser *parser, GtkCssSelector *selector)
590 name = _gtk_css_parser_try_name (parser, FALSE);
594 _gtk_css_parser_error (parser, "Expected a valid name for class");
596 _gtk_css_selector_free (selector);
600 selector = gtk_css_selector_new (>K_CSS_SELECTOR_CLASS,
602 g_intern_string (name));
609 static GtkCssSelector *
610 parse_selector_id (GtkCssParser *parser, GtkCssSelector *selector)
614 name = _gtk_css_parser_try_name (parser, FALSE);
618 _gtk_css_parser_error (parser, "Expected a valid name for id");
620 _gtk_css_selector_free (selector);
624 selector = gtk_css_selector_new (>K_CSS_SELECTOR_ID,
626 g_intern_string (name));
633 static GtkCssSelector *
634 parse_selector_pseudo_class (GtkCssParser *parser,
635 GtkCssSelector *selector)
639 GtkRegionFlags region_flag;
640 GtkStateFlags state_flag;
641 } pseudo_classes[] = {
642 { "first-child", GTK_REGION_FIRST, 0 },
643 { "last-child", GTK_REGION_LAST, 0 },
644 { "only-child", GTK_REGION_ONLY, 0 },
645 { "sorted", GTK_REGION_SORTED, 0 },
646 { "active", 0, GTK_STATE_FLAG_ACTIVE },
647 { "prelight", 0, GTK_STATE_FLAG_PRELIGHT },
648 { "hover", 0, GTK_STATE_FLAG_PRELIGHT },
649 { "selected", 0, GTK_STATE_FLAG_SELECTED },
650 { "insensitive", 0, GTK_STATE_FLAG_INSENSITIVE },
651 { "inconsistent", 0, GTK_STATE_FLAG_INCONSISTENT },
652 { "focused", 0, GTK_STATE_FLAG_FOCUSED },
653 { "focus", 0, GTK_STATE_FLAG_FOCUSED },
654 { "backdrop", 0, GTK_STATE_FLAG_BACKDROP },
656 }, nth_child_classes[] = {
657 { "first", GTK_REGION_FIRST, 0 },
658 { "last", GTK_REGION_LAST, 0 },
659 { "even", GTK_REGION_EVEN, 0 },
660 { "odd", GTK_REGION_ODD, 0 },
667 name = _gtk_css_parser_try_ident (parser, FALSE);
670 _gtk_css_parser_error (parser, "Missing name of pseudo-class");
672 _gtk_css_selector_free (selector);
676 if (_gtk_css_parser_try (parser, "(", TRUE))
678 char *function = name;
680 name = _gtk_css_parser_try_ident (parser, TRUE);
681 if (!_gtk_css_parser_try (parser, ")", FALSE))
683 _gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class");
685 _gtk_css_selector_free (selector);
689 if (g_ascii_strcasecmp (function, "nth-child") != 0)
691 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
692 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
693 "Unknown pseudo-class '%s(%s)'", function, name ? name : "");
694 _gtk_css_parser_take_error (parser, error);
698 _gtk_css_selector_free (selector);
706 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
707 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
708 "Unknown pseudo-class 'nth-child(%s)'", name);
709 _gtk_css_parser_take_error (parser, error);
711 _gtk_css_selector_free (selector);
715 classes = nth_child_classes;
718 classes = pseudo_classes;
720 for (i = 0; classes[i].name != NULL; i++)
722 if (g_ascii_strcasecmp (name, classes[i].name) == 0)
726 if (classes[i].region_flag)
727 selector = gtk_css_selector_new (>K_CSS_SELECTOR_PSEUDOCLASS_REGION,
729 GUINT_TO_POINTER (classes[i].region_flag));
731 selector = gtk_css_selector_new (>K_CSS_SELECTOR_PSEUDOCLASS_STATE,
733 GUINT_TO_POINTER (classes[i].state_flag));
739 if (classes == nth_child_classes)
740 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
741 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
742 "Unknown pseudo-class 'nth-child(%s)'", name);
744 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
745 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
746 "Unknown pseudo-class '%s'", name);
748 _gtk_css_parser_take_error (parser, error);
751 _gtk_css_selector_free (selector);
756 static GtkCssSelector *
757 try_parse_name (GtkCssParser *parser,
758 GtkCssSelector *selector)
762 name = _gtk_css_parser_try_ident (parser, FALSE);
765 selector = gtk_css_selector_new (_gtk_style_context_check_region_name (name)
766 ? >K_CSS_SELECTOR_REGION
767 : >K_CSS_SELECTOR_NAME,
769 g_intern_string (name));
772 else if (_gtk_css_parser_try (parser, "*", FALSE))
773 selector = gtk_css_selector_new (>K_CSS_SELECTOR_ANY, selector, NULL);
778 static GtkCssSelector *
779 parse_simple_selector (GtkCssParser *parser,
780 GtkCssSelector *selector)
782 guint size = gtk_css_selector_size (selector);
784 selector = try_parse_name (parser, selector);
787 if (_gtk_css_parser_try (parser, "#", FALSE))
788 selector = parse_selector_id (parser, selector);
789 else if (_gtk_css_parser_try (parser, ".", FALSE))
790 selector = parse_selector_class (parser, selector);
791 else if (_gtk_css_parser_try (parser, ":", FALSE))
792 selector = parse_selector_pseudo_class (parser, selector);
793 else if (gtk_css_selector_size (selector) == size)
795 _gtk_css_parser_error (parser, "Expected a valid selector");
797 _gtk_css_selector_free (selector);
803 while (selector && !_gtk_css_parser_is_eof (parser));
805 _gtk_css_parser_skip_whitespace (parser);
811 _gtk_css_selector_parse (GtkCssParser *parser)
813 GtkCssSelector *selector = NULL;
815 while ((selector = parse_simple_selector (parser, selector)) &&
816 !_gtk_css_parser_is_eof (parser) &&
817 !_gtk_css_parser_begins_with (parser, ',') &&
818 !_gtk_css_parser_begins_with (parser, '{'))
820 if (_gtk_css_parser_try (parser, "+", TRUE))
821 selector = gtk_css_selector_new (>K_CSS_SELECTOR_ADJACENT, selector, NULL);
822 else if (_gtk_css_parser_try (parser, "~", TRUE))
823 selector = gtk_css_selector_new (>K_CSS_SELECTOR_SIBLING, selector, NULL);
824 else if (_gtk_css_parser_try (parser, ">", TRUE))
825 selector = gtk_css_selector_new (>K_CSS_SELECTOR_CHILD, selector, NULL);
827 selector = gtk_css_selector_new (>K_CSS_SELECTOR_DESCENDANT, selector, NULL);
834 _gtk_css_selector_free (GtkCssSelector *selector)
836 g_return_if_fail (selector != NULL);
842 _gtk_css_selector_print (const GtkCssSelector *selector,
845 const GtkCssSelector *previous;
847 g_return_if_fail (selector != NULL);
849 previous = gtk_css_selector_previous (selector);
851 _gtk_css_selector_print (previous, str);
853 selector->class->print (selector, str);
857 _gtk_css_selector_to_string (const GtkCssSelector *selector)
861 g_return_val_if_fail (selector != NULL, NULL);
863 string = g_string_new (NULL);
865 _gtk_css_selector_print (selector, string);
867 return g_string_free (string, FALSE);
871 * _gtk_css_selector_matches:
872 * @selector: the selector
873 * @path: the path to check
874 * @state: The state to match
876 * Checks if the @selector matches the given @path. If @length is
877 * smaller than the number of elements in @path, it is assumed that
878 * only the first @length element of @path are valid and the rest
879 * does not exist. This is useful for doing parent matches for the
882 * Returns: %TRUE if the selector matches @path
885 _gtk_css_selector_matches (const GtkCssSelector *selector,
886 const GtkWidgetPath *path,
891 g_return_val_if_fail (selector != NULL, FALSE);
892 g_return_val_if_fail (path != NULL, FALSE);
894 length = gtk_widget_path_length (path);
898 return gtk_css_selector_match (selector,
902 gtk_widget_path_iter_get_sibling_index (path, length - 1));
905 /* Computes specificity according to CSS 2.1.
906 * The arguments must be initialized to 0 */
908 _gtk_css_selector_get_specificity (const GtkCssSelector *selector,
913 for (; selector; selector = gtk_css_selector_previous (selector))
915 const GtkCssSelectorClass *klass = selector->class;
917 if (klass->increase_id_specificity)
919 if (klass->increase_class_specificity)
921 if (klass->increase_element_specificity)
927 _gtk_css_selector_compare (const GtkCssSelector *a,
928 const GtkCssSelector *b)
930 guint a_ids = 0, a_classes = 0, a_elements = 0;
931 guint b_ids = 0, b_classes = 0, b_elements = 0;
934 _gtk_css_selector_get_specificity (a, &a_ids, &a_classes, &a_elements);
935 _gtk_css_selector_get_specificity (b, &b_ids, &b_classes, &b_elements);
937 compare = a_ids - b_ids;
941 compare = a_classes - b_classes;
945 return a_elements - b_elements;
949 _gtk_css_selector_get_state_flags (const GtkCssSelector *selector)
951 GtkStateFlags state = 0;
953 g_return_val_if_fail (selector != NULL, 0);
955 for (; selector && selector->class == >K_CSS_SELECTOR_NAME; selector = gtk_css_selector_previous (selector))
956 state |= GPOINTER_TO_UINT (selector->data);