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