]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssselector.c
selector: Introduce gtk_css_selector_previous()
[~andy/gtk] / gtk / gtkcssselector.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2011 Benjamin Otte <otte@gnome.org>
3  *
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.
8  *
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.
13  *
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/>.
16  */
17
18 #include "config.h"
19
20 #include "gtkcssselectorprivate.h"
21
22 #include "gtkcssprovider.h"
23 #include "gtkstylecontextprivate.h"
24
25 typedef struct _GtkCssSelectorClass GtkCssSelectorClass;
26
27 struct _GtkCssSelectorClass {
28   const char        *name;
29
30   void              (* print)   (const GtkCssSelector       *selector,
31                                  GString                    *string);
32   gboolean          (* match)   (const GtkCssSelector       *selector,
33                                  GtkStateFlags               state,
34                                  const GtkWidgetPath        *path,
35                                  guint                       id);
36
37   guint         increase_id_specificity :1;
38   guint         increase_class_specificity :1;
39   guint         increase_element_specificity :1;
40 };
41
42 struct _GtkCssSelector
43 {
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 */
49 };
50
51 static gboolean
52 gtk_css_selector_match (const GtkCssSelector *selector,
53                         GtkStateFlags         state,
54                         const GtkWidgetPath  *path,
55                         guint                 id)
56 {
57   if (selector == NULL)
58     return TRUE;
59
60   return selector->class->match (selector, state, path, id);
61 }
62
63 static const GtkCssSelector *
64 gtk_css_selector_previous (const GtkCssSelector *selector)
65 {
66   return selector->previous;
67 }
68
69 /* ANY */
70
71 static void
72 gtk_css_selector_any_print (const GtkCssSelector *selector,
73                             GString              *string)
74 {
75   g_string_append_c (string, '*');
76 }
77
78 static gboolean
79 gtk_css_selector_any_match (const GtkCssSelector *selector,
80                             GtkStateFlags         state,
81                             const GtkWidgetPath  *path,
82                             guint                 id)
83 {
84   return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id);
85 }
86
87 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ANY = {
88   "any",
89   gtk_css_selector_any_print,
90   gtk_css_selector_any_match,
91   FALSE, FALSE, FALSE
92 };
93
94 /* DESCENDANT */
95
96 static void
97 gtk_css_selector_descendant_print (const GtkCssSelector *selector,
98                                    GString              *string)
99 {
100   g_string_append_c (string, ' ');
101 }
102
103 static gboolean
104 gtk_css_selector_descendant_match (const GtkCssSelector *selector,
105                                    GtkStateFlags         state,
106                                    const GtkWidgetPath  *path,
107                                    guint                 id)
108 {
109   while (id-- > 0)
110     {
111       if (gtk_css_selector_match (gtk_css_selector_previous (selector), 0, path, id))
112         return TRUE;
113     }
114
115   return FALSE;
116 }
117
118 static const GtkCssSelectorClass GTK_CSS_SELECTOR_DESCENDANT = {
119   "descendant",
120   gtk_css_selector_descendant_print,
121   gtk_css_selector_descendant_match,
122   FALSE, FALSE, FALSE
123 };
124
125 /* CHILD */
126
127 static void
128 gtk_css_selector_child_print (const GtkCssSelector *selector,
129                               GString              *string)
130 {
131   g_string_append (string, " > ");
132 }
133
134 static gboolean
135 gtk_css_selector_child_match (const GtkCssSelector *selector,
136                               GtkStateFlags         state,
137                               const GtkWidgetPath  *path,
138                               guint                 id)
139 {
140   if (id == 0)
141     return FALSE;
142
143   return gtk_css_selector_match (gtk_css_selector_previous (selector), 0, path, id - 1);
144 }
145
146 static const GtkCssSelectorClass GTK_CSS_SELECTOR_CHILD = {
147   "child",
148   gtk_css_selector_child_print,
149   gtk_css_selector_child_match,
150   FALSE, FALSE, FALSE
151 };
152
153 /* NAME */
154
155 static void
156 gtk_css_selector_name_print (const GtkCssSelector *selector,
157                              GString              *string)
158 {
159   g_string_append (string, selector->data);
160 }
161
162 static gboolean
163 gtk_css_selector_name_match (const GtkCssSelector *selector,
164                              GtkStateFlags         state,
165                              const GtkWidgetPath  *path,
166                              guint                 id)
167 {
168   GType type = g_type_from_name (selector->data);
169
170   if (!g_type_is_a (gtk_widget_path_iter_get_object_type (path, id), type))
171     return FALSE;
172
173   return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id);
174 }
175
176 static const GtkCssSelectorClass GTK_CSS_SELECTOR_NAME = {
177   "name",
178   gtk_css_selector_name_print,
179   gtk_css_selector_name_match,
180   FALSE, FALSE, TRUE
181 };
182
183 /* REGION */
184
185 static void
186 gtk_css_selector_region_print (const GtkCssSelector *selector,
187                                GString              *string)
188 {
189   g_string_append (string, selector->data);
190 }
191
192 static gboolean
193 gtk_css_selector_region_match (const GtkCssSelector *selector,
194                                GtkStateFlags         state,
195                                const GtkWidgetPath  *path,
196                                guint                 id)
197 {
198   const GtkCssSelector *previous;
199
200   if (!gtk_widget_path_iter_has_region (path, id, selector->data, NULL))
201     return FALSE;
202
203   previous = gtk_css_selector_previous (selector);
204   if (previous && previous->class == &GTK_CSS_SELECTOR_DESCENDANT &&
205       gtk_css_selector_match (gtk_css_selector_previous (previous), state, path, id))
206     return TRUE;
207
208   return gtk_css_selector_match (previous, state, path, id);
209 }
210
211 static const GtkCssSelectorClass GTK_CSS_SELECTOR_REGION = {
212   "region",
213   gtk_css_selector_region_print,
214   gtk_css_selector_region_match,
215   FALSE, FALSE, TRUE
216 };
217
218 /* CLASS */
219
220 static void
221 gtk_css_selector_class_print (const GtkCssSelector *selector,
222                               GString              *string)
223 {
224   g_string_append_c (string, '.');
225   g_string_append (string, selector->data);
226 }
227
228 static gboolean
229 gtk_css_selector_class_match (const GtkCssSelector *selector,
230                               GtkStateFlags         state,
231                               const GtkWidgetPath  *path,
232                               guint                 id)
233 {
234   if (!gtk_widget_path_iter_has_class (path, id, selector->data))
235     return FALSE;
236
237   return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id);
238 }
239
240 static const GtkCssSelectorClass GTK_CSS_SELECTOR_CLASS = {
241   "class",
242   gtk_css_selector_class_print,
243   gtk_css_selector_class_match,
244   FALSE, TRUE, FALSE
245 };
246
247 /* ID */
248
249 static void
250 gtk_css_selector_id_print (const GtkCssSelector *selector,
251                            GString              *string)
252 {
253   g_string_append_c (string, '#');
254   g_string_append (string, selector->data);
255 }
256
257 static gboolean
258 gtk_css_selector_id_match (const GtkCssSelector *selector,
259                            GtkStateFlags         state,
260                            const GtkWidgetPath  *path,
261                            guint                 id)
262 {
263   if (!gtk_widget_path_iter_has_name (path, id, selector->data))
264     return FALSE;
265
266   return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id);
267 }
268
269 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ID = {
270   "id",
271   gtk_css_selector_id_print,
272   gtk_css_selector_id_match,
273   TRUE, FALSE, FALSE
274 };
275
276 /* PSEUDOCLASS FOR STATE */
277
278 static void
279 gtk_css_selector_pseudoclass_state_print (const GtkCssSelector *selector,
280                                           GString              *string)
281 {
282   static const char * state_names[] = {
283     "active",
284     "hover",
285     "selected",
286     "insensitive",
287     "inconsistent",
288     "focus",
289     "backdrop"
290   };
291   guint i, state;
292
293   state = GPOINTER_TO_UINT (selector->data);
294   g_string_append_c (string, ':');
295
296   for (i = 0; i < G_N_ELEMENTS (state_names); i++)
297     {
298       if (state == (1 << i))
299         {
300           g_string_append (string, state_names[i]);
301           return;
302         }
303     }
304
305   g_assert_not_reached ();
306 }
307
308 static gboolean
309 gtk_css_selector_pseudoclass_state_match (const GtkCssSelector *selector,
310                                           GtkStateFlags         state,
311                                           const GtkWidgetPath  *path,
312                                           guint                 id)
313 {
314   if (!(GPOINTER_TO_UINT (selector->data) & state))
315     return FALSE;
316
317   return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id);
318 }
319
320 static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_STATE = {
321   "pseudoclass-state",
322   gtk_css_selector_pseudoclass_state_print,
323   gtk_css_selector_pseudoclass_state_match,
324   FALSE, TRUE, FALSE
325 };
326
327 /* PSEUDOCLASS FOR REGION */
328
329 static void
330 gtk_css_selector_pseudoclass_region_print (const GtkCssSelector *selector,
331                                            GString              *string)
332 {
333   static const char * flag_names[] = {
334     "nth-child(even)",
335     "nth-child(odd)",
336     "first-child",
337     "last-child",
338     "only-child",
339     "sorted"
340   };
341   guint i, state;
342
343   state = GPOINTER_TO_UINT (selector->data);
344   g_string_append_c (string, ':');
345
346   for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
347     {
348       if (state == (1 << i))
349         {
350           g_string_append (string, flag_names[i]);
351           return;
352         }
353     }
354
355   g_assert_not_reached ();
356 }
357
358 static gboolean
359 gtk_css_selector_pseudoclass_region_match_for_region (const GtkCssSelector *selector,
360                                                       GtkStateFlags         state,
361                                                       const GtkWidgetPath  *path,
362                                                       guint                 id)
363 {
364   GtkRegionFlags selector_flags, path_flags;
365   const GtkCssSelector *previous;
366   
367   selector_flags = GPOINTER_TO_UINT (selector->data);
368   selector = gtk_css_selector_previous (selector);
369
370   if (!gtk_widget_path_iter_has_region (path, id, selector->data, &path_flags))
371     return FALSE;
372
373   if ((selector_flags & path_flags) != selector_flags)
374     return FALSE;
375
376   previous = gtk_css_selector_previous (selector);
377   if (previous && previous->class == &GTK_CSS_SELECTOR_DESCENDANT &&
378       gtk_css_selector_match (gtk_css_selector_previous (previous), state, path, id))
379     return TRUE;
380
381   return gtk_css_selector_match (previous, state, path, id);
382 }
383
384 static gboolean
385 gtk_css_selector_pseudoclass_region_match (const GtkCssSelector *selector,
386                                            GtkStateFlags         state,
387                                            const GtkWidgetPath  *path,
388                                            guint                 id)
389 {
390   GtkRegionFlags region;
391   const GtkWidgetPath *siblings;
392   guint sibling_id, n_siblings;
393   const GtkCssSelector *previous;
394
395   previous = gtk_css_selector_previous (selector);
396   if (previous && previous->class == &GTK_CSS_SELECTOR_REGION)
397     return gtk_css_selector_pseudoclass_region_match_for_region (selector, state, path, id);
398
399   siblings = gtk_widget_path_iter_get_siblings (path, id);
400   if (siblings == NULL)
401     return 0;
402
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);
406
407   switch (region)
408     {
409     case GTK_REGION_EVEN:
410       if (!(sibling_id % 2))
411         return FALSE;
412       break;
413     case GTK_REGION_ODD:
414       if (sibling_id % 2)
415         return FALSE;
416       break;
417     case GTK_REGION_FIRST:
418       if (sibling_id != 0)
419         return FALSE;
420       break;
421     case GTK_REGION_LAST:
422       if (sibling_id + 1 != n_siblings)
423         return FALSE;
424       break;
425     case GTK_REGION_ONLY:
426       if (n_siblings != 1)
427         return FALSE;
428       break;
429     case GTK_REGION_SORTED:
430       return FALSE;
431     default:
432       g_assert_not_reached ();
433       return FALSE;
434     }
435
436   return gtk_css_selector_match (previous, state, path, id);
437 }
438
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,
443   FALSE, TRUE, FALSE
444 };
445
446 /* API */
447
448 static GtkCssSelector *
449 gtk_css_selector_new (const GtkCssSelectorClass *class,
450                       GtkCssSelector            *previous,
451                       gconstpointer              data)
452 {
453   GtkCssSelector *selector;
454
455   selector = g_slice_new0 (GtkCssSelector);
456   selector->class = class;
457   selector->previous = previous;
458   selector->data = data;
459
460   return selector;
461 }
462
463 static GtkCssSelector *
464 parse_selector_class (GtkCssParser *parser, GtkCssSelector *selector)
465 {
466   char *name;
467     
468   name = _gtk_css_parser_try_name (parser, FALSE);
469
470   if (name == NULL)
471     {
472       _gtk_css_parser_error (parser, "Expected a valid name for class");
473       if (selector)
474         _gtk_css_selector_free (selector);
475       return NULL;
476     }
477
478   selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_CLASS,
479                                    selector,
480                                    g_intern_string (name));
481
482   g_free (name);
483
484   return selector;
485 }
486
487 static GtkCssSelector *
488 parse_selector_id (GtkCssParser *parser, GtkCssSelector *selector)
489 {
490   char *name;
491     
492   name = _gtk_css_parser_try_name (parser, FALSE);
493
494   if (name == NULL)
495     {
496       _gtk_css_parser_error (parser, "Expected a valid name for id");
497       if (selector)
498         _gtk_css_selector_free (selector);
499       return NULL;
500     }
501
502   selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ID,
503                                    selector,
504                                    g_intern_string (name));
505
506   g_free (name);
507
508   return selector;
509 }
510
511 static GtkCssSelector *
512 parse_selector_pseudo_class (GtkCssParser   *parser,
513                              GtkCssSelector *selector)
514 {
515   struct {
516     const char *name;
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 },
533     { NULL, }
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 },
539     { NULL, }
540   }, *classes;
541   guint i;
542   char *name;
543   GError *error;
544
545   name = _gtk_css_parser_try_ident (parser, FALSE);
546   if (name == NULL)
547     {
548       _gtk_css_parser_error (parser, "Missing name of pseudo-class");
549       if (selector)
550         _gtk_css_selector_free (selector);
551       return NULL;
552     }
553
554   if (_gtk_css_parser_try (parser, "(", TRUE))
555     {
556       char *function = name;
557
558       name = _gtk_css_parser_try_ident (parser, TRUE);
559       if (!_gtk_css_parser_try (parser, ")", FALSE))
560         {
561           _gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class");
562           if (selector)
563             _gtk_css_selector_free (selector);
564           return NULL;
565         }
566
567       if (g_ascii_strcasecmp (function, "nth-child") != 0)
568         {
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);
573           g_free (function);
574           g_free (name);
575           if (selector)
576             _gtk_css_selector_free (selector);
577           return NULL;
578         }
579       
580       g_free (function);
581     
582       if (name == NULL)
583         {
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);
588           if (selector)
589             _gtk_css_selector_free (selector);
590           return NULL;
591         }
592
593       classes = nth_child_classes;
594     }
595   else
596     classes = pseudo_classes;
597
598   for (i = 0; classes[i].name != NULL; i++)
599     {
600       if (g_ascii_strcasecmp (name, classes[i].name) == 0)
601         {
602           g_free (name);
603
604           if (classes[i].region_flag)
605             selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_PSEUDOCLASS_REGION,
606                                              selector,
607                                              GUINT_TO_POINTER (classes[i].region_flag));
608           else
609             selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_PSEUDOCLASS_STATE,
610                                              selector,
611                                              GUINT_TO_POINTER (classes[i].state_flag));
612
613           return selector;
614         }
615     }
616
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);
621   else
622     error = g_error_new (GTK_CSS_PROVIDER_ERROR,
623                          GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
624                          "Unknown pseudo-class '%s'", name);
625
626   _gtk_css_parser_take_error (parser, error);
627   g_free (name);
628   if (selector)
629     _gtk_css_selector_free (selector);
630
631   return NULL;
632 }
633
634 static GtkCssSelector *
635 try_parse_name (GtkCssParser   *parser,
636                 GtkCssSelector *selector)
637 {
638   char *name;
639
640   name = _gtk_css_parser_try_ident (parser, FALSE);
641   if (name)
642     {
643       selector = gtk_css_selector_new (_gtk_style_context_check_region_name (name)
644                                        ? &GTK_CSS_SELECTOR_REGION
645                                        : &GTK_CSS_SELECTOR_NAME,
646                                        selector,
647                                        g_intern_string (name));
648       g_free (name);
649     }
650   else if (_gtk_css_parser_try (parser, "*", FALSE))
651     selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ANY, selector, NULL);
652   
653   return selector;
654 }
655
656 static GtkCssSelector *
657 parse_simple_selector (GtkCssParser   *parser,
658                        GtkCssSelector *previous)
659 {
660   GtkCssSelector *selector = previous;
661   
662   selector = try_parse_name (parser, selector);
663
664   do {
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)
672         {
673           _gtk_css_parser_error (parser, "Expected a valid selector");
674           if (selector)
675             _gtk_css_selector_free (selector);
676           selector = NULL;
677         }
678       else
679         break;
680     }
681   while (selector && !_gtk_css_parser_is_eof (parser));
682
683   _gtk_css_parser_skip_whitespace (parser);
684
685   return selector;
686 }
687
688 GtkCssSelector *
689 _gtk_css_selector_parse (GtkCssParser *parser)
690 {
691   GtkCssSelector *selector = NULL;
692
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, '{'))
697     {
698       if (_gtk_css_parser_try (parser, ">", TRUE))
699         selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_CHILD, selector, NULL);
700       else
701         selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_DESCENDANT, selector, NULL);
702     }
703
704   return selector;
705 }
706
707 void
708 _gtk_css_selector_free (GtkCssSelector *selector)
709 {
710   g_return_if_fail (selector != NULL);
711
712   if (selector->previous)
713     _gtk_css_selector_free (selector->previous);
714
715   g_slice_free (GtkCssSelector, selector);
716 }
717
718 void
719 _gtk_css_selector_print (const GtkCssSelector *selector,
720                          GString *             str)
721 {
722   const GtkCssSelector *previous;
723
724   g_return_if_fail (selector != NULL);
725
726   previous = gtk_css_selector_previous (selector);
727   if (previous)
728     _gtk_css_selector_print (previous, str);
729
730   selector->class->print (selector, str);
731 }
732
733 char *
734 _gtk_css_selector_to_string (const GtkCssSelector *selector)
735 {
736   GString *string;
737
738   g_return_val_if_fail (selector != NULL, NULL);
739
740   string = g_string_new (NULL);
741
742   _gtk_css_selector_print (selector, string);
743
744   return g_string_free (string, FALSE);
745 }
746
747 /**
748  * _gtk_css_selector_matches:
749  * @selector: the selector
750  * @path: the path to check
751  * @state: The state to match
752  *
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
757  * 'inherit' keyword.
758  *
759  * Returns: %TRUE if the selector matches @path
760  **/
761 gboolean
762 _gtk_css_selector_matches (const GtkCssSelector      *selector,
763                            const GtkWidgetPath       *path,
764                            GtkStateFlags              state)
765 {
766   guint length;
767
768   g_return_val_if_fail (selector != NULL, FALSE);
769   g_return_val_if_fail (path != NULL, FALSE);
770
771   length = gtk_widget_path_length (path);
772   if (length == 0)
773     return FALSE;
774
775   return gtk_css_selector_match (selector,
776                                  state,
777                                  path,
778                                  length - 1);
779 }
780
781 /* Computes specificity according to CSS 2.1.
782  * The arguments must be initialized to 0 */
783 static void
784 _gtk_css_selector_get_specificity (const GtkCssSelector *selector,
785                                    guint                *ids,
786                                    guint                *classes,
787                                    guint                *elements)
788 {
789   for (; selector; selector = gtk_css_selector_previous (selector))
790     {
791       const GtkCssSelectorClass *klass = selector->class;
792
793       if (klass->increase_id_specificity)
794         (*ids)++;
795       if (klass->increase_class_specificity)
796         (*classes)++;
797       if (klass->increase_element_specificity)
798         (*elements)++;
799     }
800 }
801
802 int
803 _gtk_css_selector_compare (const GtkCssSelector *a,
804                            const GtkCssSelector *b)
805 {
806   guint a_ids = 0, a_classes = 0, a_elements = 0;
807   guint b_ids = 0, b_classes = 0, b_elements = 0;
808   int compare;
809
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);
812
813   compare = a_ids - b_ids;
814   if (compare)
815     return compare;
816
817   compare = a_classes - b_classes;
818   if (compare)
819     return compare;
820
821   return a_elements - b_elements;
822 }
823
824 GtkStateFlags
825 _gtk_css_selector_get_state_flags (const GtkCssSelector *selector)
826 {
827   GtkStateFlags state = 0;
828
829   g_return_val_if_fail (selector != NULL, 0);
830
831   for (; selector && selector->class == &GTK_CSS_SELECTOR_NAME; selector = gtk_css_selector_previous (selector))
832     state |= GPOINTER_TO_UINT (selector->data);
833
834   return state;
835 }
836