]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssselector.c
colorchooser: Fix default palette orientation
[~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 <string.h>
23
24 #include "gtkcssprovider.h"
25 #include "gtkstylecontextprivate.h"
26
27 typedef struct _GtkCssSelectorClass GtkCssSelectorClass;
28
29 struct _GtkCssSelectorClass {
30   const char        *name;
31
32   void              (* print)   (const GtkCssSelector       *selector,
33                                  GString                    *string);
34   gboolean          (* match)   (const GtkCssSelector       *selector,
35                                  GtkStateFlags               state,
36                                  const GtkWidgetPath        *path,
37                                  guint                       id,
38                                  guint                       sibling);
39
40   guint         increase_id_specificity :1;
41   guint         increase_class_specificity :1;
42   guint         increase_element_specificity :1;
43 };
44
45 struct _GtkCssSelector
46 {
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 */
51 };
52
53 static gboolean
54 gtk_css_selector_match (const GtkCssSelector *selector,
55                         GtkStateFlags         state,
56                         const GtkWidgetPath  *path,
57                         guint                 id,
58                         guint                 sibling)
59 {
60   if (selector == NULL)
61     return TRUE;
62
63   return selector->class->match (selector, state, path, id, sibling);
64 }
65
66 static const GtkCssSelector *
67 gtk_css_selector_previous (const GtkCssSelector *selector)
68 {
69   selector = selector + 1;
70
71   return selector->class ? selector : NULL;
72 }
73
74 /* DESCENDANT */
75
76 static void
77 gtk_css_selector_descendant_print (const GtkCssSelector *selector,
78                                    GString              *string)
79 {
80   g_string_append_c (string, ' ');
81 }
82
83 static gboolean
84 gtk_css_selector_descendant_match (const GtkCssSelector *selector,
85                                    GtkStateFlags         state,
86                                    const GtkWidgetPath  *path,
87                                    guint                 id,
88                                    guint                 sibling)
89 {
90   while (id-- > 0)
91     {
92       if (gtk_css_selector_match (gtk_css_selector_previous (selector),
93                                   0,
94                                   path,
95                                   id,
96                                   gtk_widget_path_iter_get_sibling_index (path, id)))
97         return TRUE;
98     }
99
100   return FALSE;
101 }
102
103 static const GtkCssSelectorClass GTK_CSS_SELECTOR_DESCENDANT = {
104   "descendant",
105   gtk_css_selector_descendant_print,
106   gtk_css_selector_descendant_match,
107   FALSE, FALSE, FALSE
108 };
109
110 /* CHILD */
111
112 static void
113 gtk_css_selector_child_print (const GtkCssSelector *selector,
114                               GString              *string)
115 {
116   g_string_append (string, " > ");
117 }
118
119 static gboolean
120 gtk_css_selector_child_match (const GtkCssSelector *selector,
121                               GtkStateFlags         state,
122                               const GtkWidgetPath  *path,
123                               guint                 id,
124                               guint                 sibling)
125 {
126   if (id == 0)
127     return FALSE;
128
129   return gtk_css_selector_match (gtk_css_selector_previous (selector),
130                                  0,
131                                  path,
132                                  id - 1,
133                                  gtk_widget_path_iter_get_sibling_index (path, id - 1));
134 }
135
136 static const GtkCssSelectorClass GTK_CSS_SELECTOR_CHILD = {
137   "child",
138   gtk_css_selector_child_print,
139   gtk_css_selector_child_match,
140   FALSE, FALSE, FALSE
141 };
142
143 /* SIBLING */
144
145 static void
146 gtk_css_selector_sibling_print (const GtkCssSelector *selector,
147                                 GString              *string)
148 {
149   g_string_append (string, " ~ ");
150 }
151
152 static gboolean
153 gtk_css_selector_sibling_match (const GtkCssSelector *selector,
154                                 GtkStateFlags         state,
155                                 const GtkWidgetPath  *path,
156                                 guint                 id,
157                                 guint                 sibling)
158 {
159   while (sibling-- > 0)
160     {
161       if (gtk_css_selector_match (gtk_css_selector_previous (selector),
162                                   0,
163                                   path,
164                                   id,
165                                   sibling))
166         return TRUE;
167     }
168
169   return FALSE;
170 }
171
172 static const GtkCssSelectorClass GTK_CSS_SELECTOR_SIBLING = {
173   "sibling",
174   gtk_css_selector_sibling_print,
175   gtk_css_selector_sibling_match,
176   FALSE, FALSE, FALSE
177 };
178
179 /* ADJACENT */
180
181 static void
182 gtk_css_selector_adjacent_print (const GtkCssSelector *selector,
183                                  GString              *string)
184 {
185   g_string_append (string, " + ");
186 }
187
188 static gboolean
189 gtk_css_selector_adjacent_match (const GtkCssSelector *selector,
190                                  GtkStateFlags         state,
191                                  const GtkWidgetPath  *path,
192                                  guint                 id,
193                                  guint                 sibling)
194 {
195   if (sibling == 0)
196     return FALSE;
197
198   return gtk_css_selector_match (gtk_css_selector_previous (selector),
199                                  0,
200                                  path,
201                                  id,
202                                  sibling - 1);
203 }
204
205 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ADJACENT = {
206   "adjacent",
207   gtk_css_selector_adjacent_print,
208   gtk_css_selector_adjacent_match,
209   FALSE, FALSE, FALSE
210 };
211
212 /* ANY */
213
214 static void
215 gtk_css_selector_any_print (const GtkCssSelector *selector,
216                             GString              *string)
217 {
218   g_string_append_c (string, '*');
219 }
220
221 static gboolean
222 gtk_css_selector_any_match (const GtkCssSelector *selector,
223                             GtkStateFlags         state,
224                             const GtkWidgetPath  *path,
225                             guint                 id,
226                             guint                 sibling)
227 {
228   const GtkCssSelector *previous = gtk_css_selector_previous (selector);
229   GSList *regions;
230   
231   if (previous &&
232       previous->class == &GTK_CSS_SELECTOR_DESCENDANT &&
233       (regions = gtk_widget_path_iter_list_regions (path, id)) != NULL)
234     {
235       g_slist_free (regions);
236       if (gtk_css_selector_match (gtk_css_selector_previous (previous), state, path, id, sibling))
237         return TRUE;
238     }
239   
240   return gtk_css_selector_match (previous, state, path, id, sibling);
241 }
242
243 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ANY = {
244   "any",
245   gtk_css_selector_any_print,
246   gtk_css_selector_any_match,
247   FALSE, FALSE, FALSE
248 };
249
250 /* NAME */
251
252 static void
253 gtk_css_selector_name_print (const GtkCssSelector *selector,
254                              GString              *string)
255 {
256   g_string_append (string, selector->data);
257 }
258
259 static gboolean
260 gtk_css_selector_name_match (const GtkCssSelector *selector,
261                              GtkStateFlags         state,
262                              const GtkWidgetPath  *path,
263                              guint                 id,
264                              guint                 sibling)
265 {
266   GType type = g_type_from_name (selector->data);
267
268   if (!g_type_is_a (gtk_widget_path_iter_get_object_type (path, id), type))
269     return FALSE;
270
271   return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id, sibling);
272 }
273
274 static const GtkCssSelectorClass GTK_CSS_SELECTOR_NAME = {
275   "name",
276   gtk_css_selector_name_print,
277   gtk_css_selector_name_match,
278   FALSE, FALSE, TRUE
279 };
280
281 /* REGION */
282
283 static void
284 gtk_css_selector_region_print (const GtkCssSelector *selector,
285                                GString              *string)
286 {
287   g_string_append (string, selector->data);
288 }
289
290 static gboolean
291 gtk_css_selector_region_match (const GtkCssSelector *selector,
292                                GtkStateFlags         state,
293                                const GtkWidgetPath  *path,
294                                guint                 id,
295                                guint                 sibling)
296 {
297   const GtkCssSelector *previous;
298
299   if (!gtk_widget_path_iter_has_region (path, id, selector->data, NULL))
300     return FALSE;
301
302   previous = gtk_css_selector_previous (selector);
303   if (previous && previous->class == &GTK_CSS_SELECTOR_DESCENDANT &&
304       gtk_css_selector_match (gtk_css_selector_previous (previous), state, path, id, sibling))
305     return TRUE;
306
307   return gtk_css_selector_match (previous, state, path, id, sibling);
308 }
309
310 static const GtkCssSelectorClass GTK_CSS_SELECTOR_REGION = {
311   "region",
312   gtk_css_selector_region_print,
313   gtk_css_selector_region_match,
314   FALSE, FALSE, TRUE
315 };
316
317 /* CLASS */
318
319 static void
320 gtk_css_selector_class_print (const GtkCssSelector *selector,
321                               GString              *string)
322 {
323   g_string_append_c (string, '.');
324   g_string_append (string, selector->data);
325 }
326
327 static gboolean
328 gtk_css_selector_class_match (const GtkCssSelector *selector,
329                               GtkStateFlags         state,
330                               const GtkWidgetPath  *path,
331                               guint                 id,
332                               guint                 sibling)
333 {
334   if (!gtk_widget_path_iter_has_class (path, id, selector->data))
335     return FALSE;
336
337   return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id, sibling);
338 }
339
340 static const GtkCssSelectorClass GTK_CSS_SELECTOR_CLASS = {
341   "class",
342   gtk_css_selector_class_print,
343   gtk_css_selector_class_match,
344   FALSE, TRUE, FALSE
345 };
346
347 /* ID */
348
349 static void
350 gtk_css_selector_id_print (const GtkCssSelector *selector,
351                            GString              *string)
352 {
353   g_string_append_c (string, '#');
354   g_string_append (string, selector->data);
355 }
356
357 static gboolean
358 gtk_css_selector_id_match (const GtkCssSelector *selector,
359                            GtkStateFlags         state,
360                            const GtkWidgetPath  *path,
361                            guint                 id,
362                            guint                 sibling)
363 {
364   if (!gtk_widget_path_iter_has_name (path, id, selector->data))
365     return FALSE;
366
367   return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id, sibling);
368 }
369
370 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ID = {
371   "id",
372   gtk_css_selector_id_print,
373   gtk_css_selector_id_match,
374   TRUE, FALSE, FALSE
375 };
376
377 /* PSEUDOCLASS FOR STATE */
378
379 static void
380 gtk_css_selector_pseudoclass_state_print (const GtkCssSelector *selector,
381                                           GString              *string)
382 {
383   static const char * state_names[] = {
384     "active",
385     "hover",
386     "selected",
387     "insensitive",
388     "inconsistent",
389     "focus",
390     "backdrop"
391   };
392   guint i, state;
393
394   state = GPOINTER_TO_UINT (selector->data);
395   g_string_append_c (string, ':');
396
397   for (i = 0; i < G_N_ELEMENTS (state_names); i++)
398     {
399       if (state == (1 << i))
400         {
401           g_string_append (string, state_names[i]);
402           return;
403         }
404     }
405
406   g_assert_not_reached ();
407 }
408
409 static gboolean
410 gtk_css_selector_pseudoclass_state_match (const GtkCssSelector *selector,
411                                           GtkStateFlags         state,
412                                           const GtkWidgetPath  *path,
413                                           guint                 id,
414                                           guint                 sibling)
415 {
416   if (!(GPOINTER_TO_UINT (selector->data) & state))
417     return FALSE;
418
419   return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id, sibling);
420 }
421
422 static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_STATE = {
423   "pseudoclass-state",
424   gtk_css_selector_pseudoclass_state_print,
425   gtk_css_selector_pseudoclass_state_match,
426   FALSE, TRUE, FALSE
427 };
428
429 /* PSEUDOCLASS FOR REGION */
430
431 static void
432 gtk_css_selector_pseudoclass_region_print (const GtkCssSelector *selector,
433                                            GString              *string)
434 {
435   static const char * flag_names[] = {
436     "nth-child(even)",
437     "nth-child(odd)",
438     "first-child",
439     "last-child",
440     "only-child",
441     "sorted"
442   };
443   guint i, state;
444
445   state = GPOINTER_TO_UINT (selector->data);
446   g_string_append_c (string, ':');
447
448   for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
449     {
450       if (state == (1 << i))
451         {
452           g_string_append (string, flag_names[i]);
453           return;
454         }
455     }
456
457   g_assert_not_reached ();
458 }
459
460 static gboolean
461 gtk_css_selector_pseudoclass_region_match_for_region (const GtkCssSelector *selector,
462                                                       GtkStateFlags         state,
463                                                       const GtkWidgetPath  *path,
464                                                       guint                 id,
465                                                       guint                 sibling)
466 {
467   GtkRegionFlags selector_flags, path_flags;
468   const GtkCssSelector *previous;
469   
470   selector_flags = GPOINTER_TO_UINT (selector->data);
471   selector = gtk_css_selector_previous (selector);
472
473   if (!gtk_widget_path_iter_has_region (path, id, selector->data, &path_flags))
474     return FALSE;
475
476   if ((selector_flags & path_flags) != selector_flags)
477     return FALSE;
478
479   previous = gtk_css_selector_previous (selector);
480   if (previous && previous->class == &GTK_CSS_SELECTOR_DESCENDANT &&
481       gtk_css_selector_match (gtk_css_selector_previous (previous), state, path, id, sibling))
482     return TRUE;
483
484   return gtk_css_selector_match (previous, state, path, id, sibling);
485 }
486
487 static gboolean
488 gtk_css_selector_pseudoclass_region_match (const GtkCssSelector *selector,
489                                            GtkStateFlags         state,
490                                            const GtkWidgetPath  *path,
491                                            guint                 id,
492                                            guint                 sibling)
493 {
494   GtkRegionFlags region;
495   const GtkWidgetPath *siblings;
496   guint n_siblings;
497   const GtkCssSelector *previous;
498
499   previous = gtk_css_selector_previous (selector);
500   if (previous && previous->class == &GTK_CSS_SELECTOR_REGION)
501     return gtk_css_selector_pseudoclass_region_match_for_region (selector, state, path, id, sibling);
502
503   siblings = gtk_widget_path_iter_get_siblings (path, id);
504   if (siblings == NULL)
505     return 0;
506
507   region = GPOINTER_TO_UINT (selector->data);
508   n_siblings = gtk_widget_path_length (siblings);
509
510   switch (region)
511     {
512     case GTK_REGION_EVEN:
513       if (!(sibling % 2))
514         return FALSE;
515       break;
516     case GTK_REGION_ODD:
517       if (sibling % 2)
518         return FALSE;
519       break;
520     case GTK_REGION_FIRST:
521       if (sibling != 0)
522         return FALSE;
523       break;
524     case GTK_REGION_LAST:
525       if (sibling + 1 != n_siblings)
526         return FALSE;
527       break;
528     case GTK_REGION_ONLY:
529       if (n_siblings != 1)
530         return FALSE;
531       break;
532     case GTK_REGION_SORTED:
533       return FALSE;
534     default:
535       g_assert_not_reached ();
536       return FALSE;
537     }
538
539   return gtk_css_selector_match (previous, state, path, id, sibling);
540 }
541
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,
546   FALSE, TRUE, FALSE
547 };
548
549 /* API */
550
551 static guint
552 gtk_css_selector_size (const GtkCssSelector *selector)
553 {
554   guint size = 0;
555
556   while (selector)
557     {
558       selector = gtk_css_selector_previous (selector);
559       size++;
560     }
561
562   return size;
563 }
564
565 static GtkCssSelector *
566 gtk_css_selector_new (const GtkCssSelectorClass *class,
567                       GtkCssSelector            *selector,
568                       gconstpointer              data)
569 {
570   guint size;
571
572   size = gtk_css_selector_size (selector);
573   selector = g_realloc (selector, sizeof (GtkCssSelector) * (size + 1) + sizeof (gpointer));
574   if (size == 0)
575     selector[1].class = NULL;
576   else
577     memmove (selector + 1, selector, sizeof (GtkCssSelector) * size + sizeof (gpointer));
578
579   selector->class = class;
580   selector->data = data;
581
582   return selector;
583 }
584
585 static GtkCssSelector *
586 parse_selector_class (GtkCssParser *parser, GtkCssSelector *selector)
587 {
588   char *name;
589     
590   name = _gtk_css_parser_try_name (parser, FALSE);
591
592   if (name == NULL)
593     {
594       _gtk_css_parser_error (parser, "Expected a valid name for class");
595       if (selector)
596         _gtk_css_selector_free (selector);
597       return NULL;
598     }
599
600   selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_CLASS,
601                                    selector,
602                                    g_intern_string (name));
603
604   g_free (name);
605
606   return selector;
607 }
608
609 static GtkCssSelector *
610 parse_selector_id (GtkCssParser *parser, GtkCssSelector *selector)
611 {
612   char *name;
613     
614   name = _gtk_css_parser_try_name (parser, FALSE);
615
616   if (name == NULL)
617     {
618       _gtk_css_parser_error (parser, "Expected a valid name for id");
619       if (selector)
620         _gtk_css_selector_free (selector);
621       return NULL;
622     }
623
624   selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ID,
625                                    selector,
626                                    g_intern_string (name));
627
628   g_free (name);
629
630   return selector;
631 }
632
633 static GtkCssSelector *
634 parse_selector_pseudo_class (GtkCssParser   *parser,
635                              GtkCssSelector *selector)
636 {
637   struct {
638     const char *name;
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 },
655     { NULL, }
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 },
661     { NULL, }
662   }, *classes;
663   guint i;
664   char *name;
665   GError *error;
666
667   name = _gtk_css_parser_try_ident (parser, FALSE);
668   if (name == NULL)
669     {
670       _gtk_css_parser_error (parser, "Missing name of pseudo-class");
671       if (selector)
672         _gtk_css_selector_free (selector);
673       return NULL;
674     }
675
676   if (_gtk_css_parser_try (parser, "(", TRUE))
677     {
678       char *function = name;
679
680       name = _gtk_css_parser_try_ident (parser, TRUE);
681       if (!_gtk_css_parser_try (parser, ")", FALSE))
682         {
683           _gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class");
684           if (selector)
685             _gtk_css_selector_free (selector);
686           return NULL;
687         }
688
689       if (g_ascii_strcasecmp (function, "nth-child") != 0)
690         {
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);
695           g_free (function);
696           g_free (name);
697           if (selector)
698             _gtk_css_selector_free (selector);
699           return NULL;
700         }
701       
702       g_free (function);
703     
704       if (name == NULL)
705         {
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);
710           if (selector)
711             _gtk_css_selector_free (selector);
712           return NULL;
713         }
714
715       classes = nth_child_classes;
716     }
717   else
718     classes = pseudo_classes;
719
720   for (i = 0; classes[i].name != NULL; i++)
721     {
722       if (g_ascii_strcasecmp (name, classes[i].name) == 0)
723         {
724           g_free (name);
725
726           if (classes[i].region_flag)
727             selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_PSEUDOCLASS_REGION,
728                                              selector,
729                                              GUINT_TO_POINTER (classes[i].region_flag));
730           else
731             selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_PSEUDOCLASS_STATE,
732                                              selector,
733                                              GUINT_TO_POINTER (classes[i].state_flag));
734
735           return selector;
736         }
737     }
738
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);
743   else
744     error = g_error_new (GTK_CSS_PROVIDER_ERROR,
745                          GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
746                          "Unknown pseudo-class '%s'", name);
747
748   _gtk_css_parser_take_error (parser, error);
749   g_free (name);
750   if (selector)
751     _gtk_css_selector_free (selector);
752
753   return NULL;
754 }
755
756 static GtkCssSelector *
757 try_parse_name (GtkCssParser   *parser,
758                 GtkCssSelector *selector)
759 {
760   char *name;
761
762   name = _gtk_css_parser_try_ident (parser, FALSE);
763   if (name)
764     {
765       selector = gtk_css_selector_new (_gtk_style_context_check_region_name (name)
766                                        ? &GTK_CSS_SELECTOR_REGION
767                                        : &GTK_CSS_SELECTOR_NAME,
768                                        selector,
769                                        g_intern_string (name));
770       g_free (name);
771     }
772   else if (_gtk_css_parser_try (parser, "*", FALSE))
773     selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ANY, selector, NULL);
774   
775   return selector;
776 }
777
778 static GtkCssSelector *
779 parse_simple_selector (GtkCssParser   *parser,
780                        GtkCssSelector *selector)
781 {
782   guint size = gtk_css_selector_size (selector);
783   
784   selector = try_parse_name (parser, selector);
785
786   do {
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)
794         {
795           _gtk_css_parser_error (parser, "Expected a valid selector");
796           if (selector)
797             _gtk_css_selector_free (selector);
798           return NULL;
799         }
800       else
801         break;
802     }
803   while (selector && !_gtk_css_parser_is_eof (parser));
804
805   _gtk_css_parser_skip_whitespace (parser);
806
807   return selector;
808 }
809
810 GtkCssSelector *
811 _gtk_css_selector_parse (GtkCssParser *parser)
812 {
813   GtkCssSelector *selector = NULL;
814
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, '{'))
819     {
820       if (_gtk_css_parser_try (parser, "+", TRUE))
821         selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ADJACENT, selector, NULL);
822       else if (_gtk_css_parser_try (parser, "~", TRUE))
823         selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_SIBLING, selector, NULL);
824       else if (_gtk_css_parser_try (parser, ">", TRUE))
825         selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_CHILD, selector, NULL);
826       else
827         selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_DESCENDANT, selector, NULL);
828     }
829
830   return selector;
831 }
832
833 void
834 _gtk_css_selector_free (GtkCssSelector *selector)
835 {
836   g_return_if_fail (selector != NULL);
837
838   g_free (selector);
839 }
840
841 void
842 _gtk_css_selector_print (const GtkCssSelector *selector,
843                          GString *             str)
844 {
845   const GtkCssSelector *previous;
846
847   g_return_if_fail (selector != NULL);
848
849   previous = gtk_css_selector_previous (selector);
850   if (previous)
851     _gtk_css_selector_print (previous, str);
852
853   selector->class->print (selector, str);
854 }
855
856 char *
857 _gtk_css_selector_to_string (const GtkCssSelector *selector)
858 {
859   GString *string;
860
861   g_return_val_if_fail (selector != NULL, NULL);
862
863   string = g_string_new (NULL);
864
865   _gtk_css_selector_print (selector, string);
866
867   return g_string_free (string, FALSE);
868 }
869
870 /**
871  * _gtk_css_selector_matches:
872  * @selector: the selector
873  * @path: the path to check
874  * @state: The state to match
875  *
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
880  * 'inherit' keyword.
881  *
882  * Returns: %TRUE if the selector matches @path
883  **/
884 gboolean
885 _gtk_css_selector_matches (const GtkCssSelector      *selector,
886                            const GtkWidgetPath       *path,
887                            GtkStateFlags              state)
888 {
889   guint length;
890
891   g_return_val_if_fail (selector != NULL, FALSE);
892   g_return_val_if_fail (path != NULL, FALSE);
893
894   length = gtk_widget_path_length (path);
895   if (length == 0)
896     return FALSE;
897
898   return gtk_css_selector_match (selector,
899                                  state,
900                                  path,
901                                  length - 1,
902                                  gtk_widget_path_iter_get_sibling_index (path, length - 1));
903 }
904
905 /* Computes specificity according to CSS 2.1.
906  * The arguments must be initialized to 0 */
907 static void
908 _gtk_css_selector_get_specificity (const GtkCssSelector *selector,
909                                    guint                *ids,
910                                    guint                *classes,
911                                    guint                *elements)
912 {
913   for (; selector; selector = gtk_css_selector_previous (selector))
914     {
915       const GtkCssSelectorClass *klass = selector->class;
916
917       if (klass->increase_id_specificity)
918         (*ids)++;
919       if (klass->increase_class_specificity)
920         (*classes)++;
921       if (klass->increase_element_specificity)
922         (*elements)++;
923     }
924 }
925
926 int
927 _gtk_css_selector_compare (const GtkCssSelector *a,
928                            const GtkCssSelector *b)
929 {
930   guint a_ids = 0, a_classes = 0, a_elements = 0;
931   guint b_ids = 0, b_classes = 0, b_elements = 0;
932   int compare;
933
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);
936
937   compare = a_ids - b_ids;
938   if (compare)
939     return compare;
940
941   compare = a_classes - b_classes;
942   if (compare)
943     return compare;
944
945   return a_elements - b_elements;
946 }
947
948 GtkStateFlags
949 _gtk_css_selector_get_state_flags (const GtkCssSelector *selector)
950 {
951   GtkStateFlags state = 0;
952
953   g_return_val_if_fail (selector != NULL, 0);
954
955   for (; selector && selector->class == &GTK_CSS_SELECTOR_NAME; selector = gtk_css_selector_previous (selector))
956     state |= GPOINTER_TO_UINT (selector->data);
957
958   return state;
959 }
960