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_any_print (const GtkCssSelector *selector,
80 g_string_append_c (string, '*');
84 gtk_css_selector_any_match (const GtkCssSelector *selector,
86 const GtkWidgetPath *path,
90 return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id, sibling);
93 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ANY = {
95 gtk_css_selector_any_print,
96 gtk_css_selector_any_match,
103 gtk_css_selector_descendant_print (const GtkCssSelector *selector,
106 g_string_append_c (string, ' ');
110 gtk_css_selector_descendant_match (const GtkCssSelector *selector,
112 const GtkWidgetPath *path,
118 if (gtk_css_selector_match (gtk_css_selector_previous (selector),
122 gtk_widget_path_iter_get_sibling_index (path, id)))
129 static const GtkCssSelectorClass GTK_CSS_SELECTOR_DESCENDANT = {
131 gtk_css_selector_descendant_print,
132 gtk_css_selector_descendant_match,
139 gtk_css_selector_child_print (const GtkCssSelector *selector,
142 g_string_append (string, " > ");
146 gtk_css_selector_child_match (const GtkCssSelector *selector,
148 const GtkWidgetPath *path,
155 return gtk_css_selector_match (gtk_css_selector_previous (selector),
159 gtk_widget_path_iter_get_sibling_index (path, id - 1));
162 static const GtkCssSelectorClass GTK_CSS_SELECTOR_CHILD = {
164 gtk_css_selector_child_print,
165 gtk_css_selector_child_match,
172 gtk_css_selector_name_print (const GtkCssSelector *selector,
175 g_string_append (string, selector->data);
179 gtk_css_selector_name_match (const GtkCssSelector *selector,
181 const GtkWidgetPath *path,
185 GType type = g_type_from_name (selector->data);
187 if (!g_type_is_a (gtk_widget_path_iter_get_object_type (path, id), type))
190 return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id, sibling);
193 static const GtkCssSelectorClass GTK_CSS_SELECTOR_NAME = {
195 gtk_css_selector_name_print,
196 gtk_css_selector_name_match,
203 gtk_css_selector_region_print (const GtkCssSelector *selector,
206 g_string_append (string, selector->data);
210 gtk_css_selector_region_match (const GtkCssSelector *selector,
212 const GtkWidgetPath *path,
216 const GtkCssSelector *previous;
218 if (!gtk_widget_path_iter_has_region (path, id, selector->data, NULL))
221 previous = gtk_css_selector_previous (selector);
222 if (previous && previous->class == >K_CSS_SELECTOR_DESCENDANT &&
223 gtk_css_selector_match (gtk_css_selector_previous (previous), state, path, id, sibling))
226 return gtk_css_selector_match (previous, state, path, id, sibling);
229 static const GtkCssSelectorClass GTK_CSS_SELECTOR_REGION = {
231 gtk_css_selector_region_print,
232 gtk_css_selector_region_match,
239 gtk_css_selector_class_print (const GtkCssSelector *selector,
242 g_string_append_c (string, '.');
243 g_string_append (string, selector->data);
247 gtk_css_selector_class_match (const GtkCssSelector *selector,
249 const GtkWidgetPath *path,
253 if (!gtk_widget_path_iter_has_class (path, id, selector->data))
256 return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id, sibling);
259 static const GtkCssSelectorClass GTK_CSS_SELECTOR_CLASS = {
261 gtk_css_selector_class_print,
262 gtk_css_selector_class_match,
269 gtk_css_selector_id_print (const GtkCssSelector *selector,
272 g_string_append_c (string, '#');
273 g_string_append (string, selector->data);
277 gtk_css_selector_id_match (const GtkCssSelector *selector,
279 const GtkWidgetPath *path,
283 if (!gtk_widget_path_iter_has_name (path, id, selector->data))
286 return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id, sibling);
289 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ID = {
291 gtk_css_selector_id_print,
292 gtk_css_selector_id_match,
296 /* PSEUDOCLASS FOR STATE */
299 gtk_css_selector_pseudoclass_state_print (const GtkCssSelector *selector,
302 static const char * state_names[] = {
313 state = GPOINTER_TO_UINT (selector->data);
314 g_string_append_c (string, ':');
316 for (i = 0; i < G_N_ELEMENTS (state_names); i++)
318 if (state == (1 << i))
320 g_string_append (string, state_names[i]);
325 g_assert_not_reached ();
329 gtk_css_selector_pseudoclass_state_match (const GtkCssSelector *selector,
331 const GtkWidgetPath *path,
335 if (!(GPOINTER_TO_UINT (selector->data) & state))
338 return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id, sibling);
341 static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_STATE = {
343 gtk_css_selector_pseudoclass_state_print,
344 gtk_css_selector_pseudoclass_state_match,
348 /* PSEUDOCLASS FOR REGION */
351 gtk_css_selector_pseudoclass_region_print (const GtkCssSelector *selector,
354 static const char * flag_names[] = {
364 state = GPOINTER_TO_UINT (selector->data);
365 g_string_append_c (string, ':');
367 for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
369 if (state == (1 << i))
371 g_string_append (string, flag_names[i]);
376 g_assert_not_reached ();
380 gtk_css_selector_pseudoclass_region_match_for_region (const GtkCssSelector *selector,
382 const GtkWidgetPath *path,
386 GtkRegionFlags selector_flags, path_flags;
387 const GtkCssSelector *previous;
389 selector_flags = GPOINTER_TO_UINT (selector->data);
390 selector = gtk_css_selector_previous (selector);
392 if (!gtk_widget_path_iter_has_region (path, id, selector->data, &path_flags))
395 if ((selector_flags & path_flags) != selector_flags)
398 previous = gtk_css_selector_previous (selector);
399 if (previous && previous->class == >K_CSS_SELECTOR_DESCENDANT &&
400 gtk_css_selector_match (gtk_css_selector_previous (previous), state, path, id, sibling))
403 return gtk_css_selector_match (previous, state, path, id, sibling);
407 gtk_css_selector_pseudoclass_region_match (const GtkCssSelector *selector,
409 const GtkWidgetPath *path,
413 GtkRegionFlags region;
414 const GtkWidgetPath *siblings;
416 const GtkCssSelector *previous;
418 previous = gtk_css_selector_previous (selector);
419 if (previous && previous->class == >K_CSS_SELECTOR_REGION)
420 return gtk_css_selector_pseudoclass_region_match_for_region (selector, state, path, id, sibling);
422 siblings = gtk_widget_path_iter_get_siblings (path, id);
423 if (siblings == NULL)
426 region = GPOINTER_TO_UINT (selector->data);
427 n_siblings = gtk_widget_path_length (siblings);
431 case GTK_REGION_EVEN:
439 case GTK_REGION_FIRST:
443 case GTK_REGION_LAST:
444 if (sibling + 1 != n_siblings)
447 case GTK_REGION_ONLY:
451 case GTK_REGION_SORTED:
454 g_assert_not_reached ();
458 return gtk_css_selector_match (previous, state, path, id, sibling);
461 static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_REGION = {
462 "pseudoclass-region",
463 gtk_css_selector_pseudoclass_region_print,
464 gtk_css_selector_pseudoclass_region_match,
471 gtk_css_selector_size (const GtkCssSelector *selector)
477 selector = gtk_css_selector_previous (selector);
484 static GtkCssSelector *
485 gtk_css_selector_new (const GtkCssSelectorClass *class,
486 GtkCssSelector *selector,
491 size = gtk_css_selector_size (selector);
492 selector = g_realloc (selector, sizeof (GtkCssSelector) * (size + 1) + sizeof (gpointer));
494 selector[1].class = NULL;
496 memmove (selector + 1, selector, sizeof (GtkCssSelector) * size + sizeof (gpointer));
498 selector->class = class;
499 selector->data = data;
504 static GtkCssSelector *
505 parse_selector_class (GtkCssParser *parser, GtkCssSelector *selector)
509 name = _gtk_css_parser_try_name (parser, FALSE);
513 _gtk_css_parser_error (parser, "Expected a valid name for class");
515 _gtk_css_selector_free (selector);
519 selector = gtk_css_selector_new (>K_CSS_SELECTOR_CLASS,
521 g_intern_string (name));
528 static GtkCssSelector *
529 parse_selector_id (GtkCssParser *parser, GtkCssSelector *selector)
533 name = _gtk_css_parser_try_name (parser, FALSE);
537 _gtk_css_parser_error (parser, "Expected a valid name for id");
539 _gtk_css_selector_free (selector);
543 selector = gtk_css_selector_new (>K_CSS_SELECTOR_ID,
545 g_intern_string (name));
552 static GtkCssSelector *
553 parse_selector_pseudo_class (GtkCssParser *parser,
554 GtkCssSelector *selector)
558 GtkRegionFlags region_flag;
559 GtkStateFlags state_flag;
560 } pseudo_classes[] = {
561 { "first-child", GTK_REGION_FIRST, 0 },
562 { "last-child", GTK_REGION_LAST, 0 },
563 { "only-child", GTK_REGION_ONLY, 0 },
564 { "sorted", GTK_REGION_SORTED, 0 },
565 { "active", 0, GTK_STATE_FLAG_ACTIVE },
566 { "prelight", 0, GTK_STATE_FLAG_PRELIGHT },
567 { "hover", 0, GTK_STATE_FLAG_PRELIGHT },
568 { "selected", 0, GTK_STATE_FLAG_SELECTED },
569 { "insensitive", 0, GTK_STATE_FLAG_INSENSITIVE },
570 { "inconsistent", 0, GTK_STATE_FLAG_INCONSISTENT },
571 { "focused", 0, GTK_STATE_FLAG_FOCUSED },
572 { "focus", 0, GTK_STATE_FLAG_FOCUSED },
573 { "backdrop", 0, GTK_STATE_FLAG_BACKDROP },
575 }, nth_child_classes[] = {
576 { "first", GTK_REGION_FIRST, 0 },
577 { "last", GTK_REGION_LAST, 0 },
578 { "even", GTK_REGION_EVEN, 0 },
579 { "odd", GTK_REGION_ODD, 0 },
586 name = _gtk_css_parser_try_ident (parser, FALSE);
589 _gtk_css_parser_error (parser, "Missing name of pseudo-class");
591 _gtk_css_selector_free (selector);
595 if (_gtk_css_parser_try (parser, "(", TRUE))
597 char *function = name;
599 name = _gtk_css_parser_try_ident (parser, TRUE);
600 if (!_gtk_css_parser_try (parser, ")", FALSE))
602 _gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class");
604 _gtk_css_selector_free (selector);
608 if (g_ascii_strcasecmp (function, "nth-child") != 0)
610 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
611 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
612 "Unknown pseudo-class '%s(%s)'", function, name ? name : "");
613 _gtk_css_parser_take_error (parser, error);
617 _gtk_css_selector_free (selector);
625 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
626 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
627 "Unknown pseudo-class 'nth-child(%s)'", name);
628 _gtk_css_parser_take_error (parser, error);
630 _gtk_css_selector_free (selector);
634 classes = nth_child_classes;
637 classes = pseudo_classes;
639 for (i = 0; classes[i].name != NULL; i++)
641 if (g_ascii_strcasecmp (name, classes[i].name) == 0)
645 if (classes[i].region_flag)
646 selector = gtk_css_selector_new (>K_CSS_SELECTOR_PSEUDOCLASS_REGION,
648 GUINT_TO_POINTER (classes[i].region_flag));
650 selector = gtk_css_selector_new (>K_CSS_SELECTOR_PSEUDOCLASS_STATE,
652 GUINT_TO_POINTER (classes[i].state_flag));
658 if (classes == nth_child_classes)
659 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
660 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
661 "Unknown pseudo-class 'nth-child(%s)'", name);
663 error = g_error_new (GTK_CSS_PROVIDER_ERROR,
664 GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
665 "Unknown pseudo-class '%s'", name);
667 _gtk_css_parser_take_error (parser, error);
670 _gtk_css_selector_free (selector);
675 static GtkCssSelector *
676 try_parse_name (GtkCssParser *parser,
677 GtkCssSelector *selector)
681 name = _gtk_css_parser_try_ident (parser, FALSE);
684 selector = gtk_css_selector_new (_gtk_style_context_check_region_name (name)
685 ? >K_CSS_SELECTOR_REGION
686 : >K_CSS_SELECTOR_NAME,
688 g_intern_string (name));
691 else if (_gtk_css_parser_try (parser, "*", FALSE))
692 selector = gtk_css_selector_new (>K_CSS_SELECTOR_ANY, selector, NULL);
697 static GtkCssSelector *
698 parse_simple_selector (GtkCssParser *parser,
699 GtkCssSelector *selector)
701 guint size = gtk_css_selector_size (selector);
703 selector = try_parse_name (parser, selector);
706 if (_gtk_css_parser_try (parser, "#", FALSE))
707 selector = parse_selector_id (parser, selector);
708 else if (_gtk_css_parser_try (parser, ".", FALSE))
709 selector = parse_selector_class (parser, selector);
710 else if (_gtk_css_parser_try (parser, ":", FALSE))
711 selector = parse_selector_pseudo_class (parser, selector);
712 else if (gtk_css_selector_size (selector) == size)
714 _gtk_css_parser_error (parser, "Expected a valid selector");
716 _gtk_css_selector_free (selector);
722 while (selector && !_gtk_css_parser_is_eof (parser));
724 _gtk_css_parser_skip_whitespace (parser);
730 _gtk_css_selector_parse (GtkCssParser *parser)
732 GtkCssSelector *selector = NULL;
734 while ((selector = parse_simple_selector (parser, selector)) &&
735 !_gtk_css_parser_is_eof (parser) &&
736 !_gtk_css_parser_begins_with (parser, ',') &&
737 !_gtk_css_parser_begins_with (parser, '{'))
739 if (_gtk_css_parser_try (parser, ">", TRUE))
740 selector = gtk_css_selector_new (>K_CSS_SELECTOR_CHILD, selector, NULL);
742 selector = gtk_css_selector_new (>K_CSS_SELECTOR_DESCENDANT, selector, NULL);
749 _gtk_css_selector_free (GtkCssSelector *selector)
751 g_return_if_fail (selector != NULL);
757 _gtk_css_selector_print (const GtkCssSelector *selector,
760 const GtkCssSelector *previous;
762 g_return_if_fail (selector != NULL);
764 previous = gtk_css_selector_previous (selector);
766 _gtk_css_selector_print (previous, str);
768 selector->class->print (selector, str);
772 _gtk_css_selector_to_string (const GtkCssSelector *selector)
776 g_return_val_if_fail (selector != NULL, NULL);
778 string = g_string_new (NULL);
780 _gtk_css_selector_print (selector, string);
782 return g_string_free (string, FALSE);
786 * _gtk_css_selector_matches:
787 * @selector: the selector
788 * @path: the path to check
789 * @state: The state to match
791 * Checks if the @selector matches the given @path. If @length is
792 * smaller than the number of elements in @path, it is assumed that
793 * only the first @length element of @path are valid and the rest
794 * does not exist. This is useful for doing parent matches for the
797 * Returns: %TRUE if the selector matches @path
800 _gtk_css_selector_matches (const GtkCssSelector *selector,
801 const GtkWidgetPath *path,
806 g_return_val_if_fail (selector != NULL, FALSE);
807 g_return_val_if_fail (path != NULL, FALSE);
809 length = gtk_widget_path_length (path);
813 return gtk_css_selector_match (selector,
817 gtk_widget_path_iter_get_sibling_index (path, length - 1));
820 /* Computes specificity according to CSS 2.1.
821 * The arguments must be initialized to 0 */
823 _gtk_css_selector_get_specificity (const GtkCssSelector *selector,
828 for (; selector; selector = gtk_css_selector_previous (selector))
830 const GtkCssSelectorClass *klass = selector->class;
832 if (klass->increase_id_specificity)
834 if (klass->increase_class_specificity)
836 if (klass->increase_element_specificity)
842 _gtk_css_selector_compare (const GtkCssSelector *a,
843 const GtkCssSelector *b)
845 guint a_ids = 0, a_classes = 0, a_elements = 0;
846 guint b_ids = 0, b_classes = 0, b_elements = 0;
849 _gtk_css_selector_get_specificity (a, &a_ids, &a_classes, &a_elements);
850 _gtk_css_selector_get_specificity (b, &b_ids, &b_classes, &b_elements);
852 compare = a_ids - b_ids;
856 compare = a_classes - b_classes;
860 return a_elements - b_elements;
864 _gtk_css_selector_get_state_flags (const GtkCssSelector *selector)
866 GtkStateFlags state = 0;
868 g_return_val_if_fail (selector != NULL, 0);
870 for (; selector && selector->class == >K_CSS_SELECTOR_NAME; selector = gtk_css_selector_previous (selector))
871 state |= GPOINTER_TO_UINT (selector->data);