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);
66 gtk_css_selector_any_print (const GtkCssSelector *selector,
69 g_string_append_c (string, '*');
73 gtk_css_selector_any_match (const GtkCssSelector *selector,
75 const GtkWidgetPath *path,
78 return gtk_css_selector_match (selector->previous, state, path, id);
81 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ANY = {
83 gtk_css_selector_any_print,
84 gtk_css_selector_any_match,
91 gtk_css_selector_descendant_print (const GtkCssSelector *selector,
94 g_string_append_c (string, ' ');
98 gtk_css_selector_descendant_match (const GtkCssSelector *selector,
100 const GtkWidgetPath *path,
105 if (gtk_css_selector_match (selector->previous, 0, path, id))
112 static const GtkCssSelectorClass GTK_CSS_SELECTOR_DESCENDANT = {
114 gtk_css_selector_descendant_print,
115 gtk_css_selector_descendant_match,
122 gtk_css_selector_child_print (const GtkCssSelector *selector,
125 g_string_append (string, " > ");
129 gtk_css_selector_child_match (const GtkCssSelector *selector,
131 const GtkWidgetPath *path,
137 return gtk_css_selector_match (selector->previous, 0, path, id - 1);
140 static const GtkCssSelectorClass GTK_CSS_SELECTOR_CHILD = {
142 gtk_css_selector_child_print,
143 gtk_css_selector_child_match,
150 gtk_css_selector_name_print (const GtkCssSelector *selector,
153 g_string_append (string, selector->data);
157 gtk_css_selector_name_match (const GtkCssSelector *selector,
159 const GtkWidgetPath *path,
162 GType type = g_type_from_name (selector->data);
164 if (!g_type_is_a (gtk_widget_path_iter_get_object_type (path, id), type))
167 return gtk_css_selector_match (selector->previous, state, path, id);
170 static const GtkCssSelectorClass GTK_CSS_SELECTOR_NAME = {
172 gtk_css_selector_name_print,
173 gtk_css_selector_name_match,
180 gtk_css_selector_region_print (const GtkCssSelector *selector,
183 g_string_append (string, selector->data);
187 gtk_css_selector_region_match (const GtkCssSelector *selector,
189 const GtkWidgetPath *path,
192 if (!gtk_widget_path_iter_has_region (path, id, selector->data, NULL))
195 if (selector->previous &&
196 selector->previous->class == >K_CSS_SELECTOR_DESCENDANT &&
197 gtk_css_selector_match (selector->previous->previous, state, path, id))
200 return gtk_css_selector_match (selector->previous, state, path, id);
203 static const GtkCssSelectorClass GTK_CSS_SELECTOR_REGION = {
205 gtk_css_selector_region_print,
206 gtk_css_selector_region_match,
213 gtk_css_selector_class_print (const GtkCssSelector *selector,
216 g_string_append_c (string, '.');
217 g_string_append (string, selector->data);
221 gtk_css_selector_class_match (const GtkCssSelector *selector,
223 const GtkWidgetPath *path,
226 if (!gtk_widget_path_iter_has_class (path, id, selector->data))
229 return gtk_css_selector_match (selector->previous, state, path, id);
232 static const GtkCssSelectorClass GTK_CSS_SELECTOR_CLASS = {
234 gtk_css_selector_class_print,
235 gtk_css_selector_class_match,
242 gtk_css_selector_id_print (const GtkCssSelector *selector,
245 g_string_append_c (string, '#');
246 g_string_append (string, selector->data);
250 gtk_css_selector_id_match (const GtkCssSelector *selector,
252 const GtkWidgetPath *path,
255 if (!gtk_widget_path_iter_has_name (path, id, selector->data))
258 return gtk_css_selector_match (selector->previous, state, path, id);
261 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ID = {
263 gtk_css_selector_id_print,
264 gtk_css_selector_id_match,
268 /* PSEUDOCLASS FOR STATE */
271 gtk_css_selector_pseudoclass_state_print (const GtkCssSelector *selector,
274 static const char * state_names[] = {
285 state = GPOINTER_TO_UINT (selector->data);
286 g_string_append_c (string, ':');
288 for (i = 0; i < G_N_ELEMENTS (state_names); i++)
290 if (state == (1 << i))
292 g_string_append (string, state_names[i]);
297 g_assert_not_reached ();
301 gtk_css_selector_pseudoclass_state_match (const GtkCssSelector *selector,
303 const GtkWidgetPath *path,
306 if (!(GPOINTER_TO_UINT (selector->data) & state))
309 return gtk_css_selector_match (selector->previous, state, path, id);
312 static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_STATE = {
314 gtk_css_selector_pseudoclass_state_print,
315 gtk_css_selector_pseudoclass_state_match,
319 /* PSEUDOCLASS FOR REGION */
322 gtk_css_selector_pseudoclass_region_print (const GtkCssSelector *selector,
325 static const char * flag_names[] = {
335 state = GPOINTER_TO_UINT (selector->data);
336 g_string_append_c (string, ':');
338 for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
340 if (state == (1 << i))
342 g_string_append (string, flag_names[i]);
347 g_assert_not_reached ();
351 gtk_css_selector_pseudoclass_region_match_for_region (const GtkCssSelector *selector,
353 const GtkWidgetPath *path,
356 GtkRegionFlags selector_flags, path_flags;
358 selector_flags = GPOINTER_TO_UINT (selector->data);
359 selector = selector->previous;
361 if (!gtk_widget_path_iter_has_region (path, id, selector->data, &path_flags))
364 if ((selector_flags & path_flags) != selector_flags)
367 if (selector->previous &&
368 selector->previous->class == >K_CSS_SELECTOR_DESCENDANT &&
369 gtk_css_selector_match (selector->previous->previous, state, path, id))
372 return gtk_css_selector_match (selector->previous, state, path, id);
376 gtk_css_selector_pseudoclass_region_match (const GtkCssSelector *selector,
378 const GtkWidgetPath *path,
381 GtkRegionFlags region;
382 const GtkWidgetPath *siblings;
383 guint sibling_id, n_siblings;
385 if (selector->previous &&
386 selector->previous->class == >K_CSS_SELECTOR_REGION)
387 return gtk_css_selector_pseudoclass_region_match_for_region (selector, state, path, id);
389 siblings = gtk_widget_path_iter_get_siblings (path, id);
390 if (siblings == NULL)
393 region = GPOINTER_TO_UINT (selector->data);
394 sibling_id = gtk_widget_path_iter_get_sibling_index (path, id);
395 n_siblings = gtk_widget_path_length (siblings);
399 case GTK_REGION_EVEN:
400 if (!(sibling_id % 2))
407 case GTK_REGION_FIRST:
411 case GTK_REGION_LAST:
412 if (sibling_id + 1 != n_siblings)
415 case GTK_REGION_ONLY:
419 case GTK_REGION_SORTED:
422 g_assert_not_reached ();
426 return gtk_css_selector_match (selector->previous, state, path, id);
429 static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_REGION = {
430 "pseudoclass-region",
431 gtk_css_selector_pseudoclass_region_print,
432 gtk_css_selector_pseudoclass_region_match,
438 static GtkCssSelector *
439 gtk_css_selector_new (const GtkCssSelectorClass *class,
440 GtkCssSelector *previous,
443 GtkCssSelector *selector;
445 selector = g_slice_new0 (GtkCssSelector);
446 selector->class = class;
447 selector->previous = previous;
448 selector->data = data;
453 static GtkCssSelector *
454 parse_selector_class (GtkCssParser *parser, GtkCssSelector *selector)
458 name = _gtk_css_parser_try_name (parser, FALSE);
462 _gtk_css_parser_error (parser, "Expected a valid name for class");
464 _gtk_css_selector_free (selector);
468 selector = gtk_css_selector_new (>K_CSS_SELECTOR_CLASS,
470 g_intern_string (name));
477 static GtkCssSelector *
478 parse_selector_id (GtkCssParser *parser, GtkCssSelector *selector)
482 name = _gtk_css_parser_try_name (parser, FALSE);
486 _gtk_css_parser_error (parser, "Expected a valid name for id");
488 _gtk_css_selector_free (selector);
492 selector = gtk_css_selector_new (>K_CSS_SELECTOR_ID,
494 g_intern_string (name));
501 static GtkCssSelector *
502 parse_selector_pseudo_class (GtkCssParser *parser,
503 GtkCssSelector *selector)
507 GtkRegionFlags region_flag;
508 GtkStateFlags state_flag;
509 } pseudo_classes[] = {
510 { "first-child", GTK_REGION_FIRST, 0 },
511 { "last-child", GTK_REGION_LAST, 0 },
512 { "only-child", GTK_REGION_ONLY, 0 },
513 { "sorted", GTK_REGION_SORTED, 0 },
514 { "active", 0, GTK_STATE_FLAG_ACTIVE },
515 { "prelight", 0, GTK_STATE_FLAG_PRELIGHT },
516 { "hover", 0, GTK_STATE_FLAG_PRELIGHT },
517 { "selected", 0, GTK_STATE_FLAG_SELECTED },
518 { "insensitive", 0, GTK_STATE_FLAG_INSENSITIVE },
519 { "inconsistent", 0, GTK_STATE_FLAG_INCONSISTENT },
520 { "focused", 0, GTK_STATE_FLAG_FOCUSED },
521 { "focus", 0, GTK_STATE_FLAG_FOCUSED },
522 { "backdrop", 0, GTK_STATE_FLAG_BACKDROP },
524 }, nth_child_classes[] = {
525 { "first", GTK_REGION_FIRST, 0 },
526 { "last", GTK_REGION_LAST, 0 },
527 { "even", GTK_REGION_EVEN, 0 },
528 { "odd", GTK_REGION_ODD, 0 },
535 name = _gtk_css_parser_try_ident (parser, FALSE);
538 _gtk_css_parser_error (parser, "Missing name of pseudo-class");
540 _gtk_css_selector_free (selector);
544 if (_gtk_css_parser_try (parser, "(", TRUE))
546 char *function = name;
548 name = _gtk_css_parser_try_ident (parser, TRUE);
549 if (!_gtk_css_parser_try (parser, ")", FALSE))
551 _gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class");
553 _gtk_css_selector_free (selector);
557 if (g_ascii_strcasecmp (function, "nth-child") != 0)
559 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
560 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
561 "Unknown pseudo-class '%s(%s)'", function, name ? name : "");
562 _gtk_css_parser_take_error (parser, error);
566 _gtk_css_selector_free (selector);
574 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
575 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
576 "Unknown pseudo-class 'nth-child(%s)'", name);
577 _gtk_css_parser_take_error (parser, error);
579 _gtk_css_selector_free (selector);
583 classes = nth_child_classes;
586 classes = pseudo_classes;
588 for (i = 0; classes[i].name != NULL; i++)
590 if (g_ascii_strcasecmp (name, classes[i].name) == 0)
594 if (classes[i].region_flag)
595 selector = gtk_css_selector_new (>K_CSS_SELECTOR_PSEUDOCLASS_REGION,
597 GUINT_TO_POINTER (classes[i].region_flag));
599 selector = gtk_css_selector_new (>K_CSS_SELECTOR_PSEUDOCLASS_STATE,
601 GUINT_TO_POINTER (classes[i].state_flag));
607 if (classes == nth_child_classes)
608 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
609 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
610 "Unknown pseudo-class 'nth-child(%s)'", name);
612 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
613 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
614 "Unknown pseudo-class '%s'", name);
616 _gtk_css_parser_take_error (parser, error);
619 _gtk_css_selector_free (selector);
624 static GtkCssSelector *
625 try_parse_name (GtkCssParser *parser,
626 GtkCssSelector *selector)
630 name = _gtk_css_parser_try_ident (parser, FALSE);
633 selector = gtk_css_selector_new (_gtk_style_context_check_region_name (name)
634 ? >K_CSS_SELECTOR_REGION
635 : >K_CSS_SELECTOR_NAME,
637 g_intern_string (name));
640 else if (_gtk_css_parser_try (parser, "*", FALSE))
641 selector = gtk_css_selector_new (>K_CSS_SELECTOR_ANY, selector, NULL);
646 static GtkCssSelector *
647 parse_simple_selector (GtkCssParser *parser,
648 GtkCssSelector *previous)
650 GtkCssSelector *selector = previous;
652 selector = try_parse_name (parser, selector);
655 if (_gtk_css_parser_try (parser, "#", FALSE))
656 selector = parse_selector_id (parser, selector);
657 else if (_gtk_css_parser_try (parser, ".", FALSE))
658 selector = parse_selector_class (parser, selector);
659 else if (_gtk_css_parser_try (parser, ":", FALSE))
660 selector = parse_selector_pseudo_class (parser, selector);
661 else if (selector == previous)
663 _gtk_css_parser_error (parser, "Expected a valid selector");
665 _gtk_css_selector_free (selector);
671 while (selector && !_gtk_css_parser_is_eof (parser));
673 _gtk_css_parser_skip_whitespace (parser);
679 _gtk_css_selector_parse (GtkCssParser *parser)
681 GtkCssSelector *selector = NULL;
683 while ((selector = parse_simple_selector (parser, selector)) &&
684 !_gtk_css_parser_is_eof (parser) &&
685 !_gtk_css_parser_begins_with (parser, ',') &&
686 !_gtk_css_parser_begins_with (parser, '{'))
688 if (_gtk_css_parser_try (parser, ">", TRUE))
689 selector = gtk_css_selector_new (>K_CSS_SELECTOR_CHILD, selector, NULL);
691 selector = gtk_css_selector_new (>K_CSS_SELECTOR_DESCENDANT, selector, NULL);
698 _gtk_css_selector_free (GtkCssSelector *selector)
700 g_return_if_fail (selector != NULL);
702 if (selector->previous)
703 _gtk_css_selector_free (selector->previous);
705 g_slice_free (GtkCssSelector, selector);
709 _gtk_css_selector_print (const GtkCssSelector *selector,
712 g_return_if_fail (selector != NULL);
714 if (selector->previous)
715 _gtk_css_selector_print (selector->previous, str);
717 selector->class->print (selector, str);
721 _gtk_css_selector_to_string (const GtkCssSelector *selector)
725 g_return_val_if_fail (selector != NULL, NULL);
727 string = g_string_new (NULL);
729 _gtk_css_selector_print (selector, string);
731 return g_string_free (string, FALSE);
735 * _gtk_css_selector_matches:
736 * @selector: the selector
737 * @path: the path to check
738 * @state: The state to match
740 * Checks if the @selector matches the given @path. If @length is
741 * smaller than the number of elements in @path, it is assumed that
742 * only the first @length element of @path are valid and the rest
743 * does not exist. This is useful for doing parent matches for the
746 * Returns: %TRUE if the selector matches @path
749 _gtk_css_selector_matches (const GtkCssSelector *selector,
750 const GtkWidgetPath *path,
755 g_return_val_if_fail (selector != NULL, FALSE);
756 g_return_val_if_fail (path != NULL, FALSE);
758 length = gtk_widget_path_length (path);
762 return gtk_css_selector_match (selector,
768 /* Computes specificity according to CSS 2.1.
769 * The arguments must be initialized to 0 */
771 _gtk_css_selector_get_specificity (const GtkCssSelector *selector,
776 for (; selector; selector = selector->previous)
778 const GtkCssSelectorClass *klass = selector->class;
780 if (klass->increase_id_specificity)
782 if (klass->increase_class_specificity)
784 if (klass->increase_element_specificity)
790 _gtk_css_selector_compare (const GtkCssSelector *a,
791 const GtkCssSelector *b)
793 guint a_ids = 0, a_classes = 0, a_elements = 0;
794 guint b_ids = 0, b_classes = 0, b_elements = 0;
797 _gtk_css_selector_get_specificity (a, &a_ids, &a_classes, &a_elements);
798 _gtk_css_selector_get_specificity (b, &b_ids, &b_classes, &b_elements);
800 compare = a_ids - b_ids;
804 compare = a_classes - b_classes;
808 return a_elements - b_elements;
812 _gtk_css_selector_get_state_flags (GtkCssSelector *selector)
814 GtkStateFlags state = 0;
816 g_return_val_if_fail (selector != NULL, 0);
818 for (; selector && selector->class == >K_CSS_SELECTOR_NAME; selector = selector->previous)
819 state |= GPOINTER_TO_UINT (selector->data);