]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssselector.c
selector: Rewrite position tracking
[~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                                      const GtkCssMatcher        *matcher);
36   GtkCssChange      (* get_change)  (const GtkCssSelector       *selector);
37
38   guint         increase_id_specificity :1;
39   guint         increase_class_specificity :1;
40   guint         increase_element_specificity :1;
41 };
42
43 struct _GtkCssSelector
44 {
45   const GtkCssSelectorClass *class;       /* type of check this selector does */
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                         const GtkCssMatcher  *matcher)
54 {
55   if (selector == NULL)
56     return TRUE;
57
58   return selector->class->match (selector, matcher);
59 }
60
61 static GtkCssChange
62 gtk_css_selector_get_change (const GtkCssSelector *selector)
63 {
64   if (selector == NULL)
65     return 0;
66
67   return selector->class->get_change (selector);
68 }
69
70 static const GtkCssSelector *
71 gtk_css_selector_previous (const GtkCssSelector *selector)
72 {
73   selector = selector + 1;
74
75   return selector->class ? selector : NULL;
76 }
77
78 /* DESCENDANT */
79
80 static void
81 gtk_css_selector_descendant_print (const GtkCssSelector *selector,
82                                    GString              *string)
83 {
84   g_string_append_c (string, ' ');
85 }
86
87 static gboolean
88 gtk_css_selector_descendant_match (const GtkCssSelector *selector,
89                                    const GtkCssMatcher  *matcher)
90 {
91   GtkCssMatcher ancestor;
92
93   while (_gtk_css_matcher_get_parent (&ancestor, matcher))
94     {
95       matcher = &ancestor;
96
97       if (gtk_css_selector_match (gtk_css_selector_previous (selector), matcher))
98         return TRUE;
99     }
100
101   return FALSE;
102 }
103
104 static GtkCssChange
105 gtk_css_selector_descendant_get_change (const GtkCssSelector *selector)
106 {
107   return _gtk_css_change_for_child (gtk_css_selector_get_change (gtk_css_selector_previous (selector)));
108 }
109
110 static const GtkCssSelectorClass GTK_CSS_SELECTOR_DESCENDANT = {
111   "descendant",
112   gtk_css_selector_descendant_print,
113   gtk_css_selector_descendant_match,
114   gtk_css_selector_descendant_get_change,
115   FALSE, FALSE, FALSE
116 };
117
118 /* CHILD */
119
120 static void
121 gtk_css_selector_child_print (const GtkCssSelector *selector,
122                               GString              *string)
123 {
124   g_string_append (string, " > ");
125 }
126
127 static gboolean
128 gtk_css_selector_child_match (const GtkCssSelector *selector,
129                               const GtkCssMatcher  *matcher)
130 {
131   GtkCssMatcher parent;
132
133   if (!_gtk_css_matcher_get_parent (&parent, matcher))
134     return FALSE;
135
136   return gtk_css_selector_match (gtk_css_selector_previous (selector), &parent);
137 }
138
139 static GtkCssChange
140 gtk_css_selector_child_get_change (const GtkCssSelector *selector)
141 {
142   return _gtk_css_change_for_child (gtk_css_selector_get_change (gtk_css_selector_previous (selector)));
143 }
144
145 static const GtkCssSelectorClass GTK_CSS_SELECTOR_CHILD = {
146   "child",
147   gtk_css_selector_child_print,
148   gtk_css_selector_child_match,
149   gtk_css_selector_child_get_change,
150   FALSE, FALSE, FALSE
151 };
152
153 /* SIBLING */
154
155 static void
156 gtk_css_selector_sibling_print (const GtkCssSelector *selector,
157                                 GString              *string)
158 {
159   g_string_append (string, " ~ ");
160 }
161
162 static gboolean
163 gtk_css_selector_sibling_match (const GtkCssSelector *selector,
164                                 const GtkCssMatcher  *matcher)
165 {
166   GtkCssMatcher previous;
167
168   while (_gtk_css_matcher_get_previous (&previous, matcher))
169     {
170       matcher = &previous;
171
172       if (gtk_css_selector_match (gtk_css_selector_previous (selector), matcher))
173         return TRUE;
174     }
175
176   return FALSE;
177 }
178
179 static GtkCssChange
180 gtk_css_selector_sibling_get_change (const GtkCssSelector *selector)
181 {
182   return _gtk_css_change_for_sibling (gtk_css_selector_get_change (gtk_css_selector_previous (selector)));
183 }
184
185 static const GtkCssSelectorClass GTK_CSS_SELECTOR_SIBLING = {
186   "sibling",
187   gtk_css_selector_sibling_print,
188   gtk_css_selector_sibling_match,
189   gtk_css_selector_sibling_get_change,
190   FALSE, FALSE, FALSE
191 };
192
193 /* ADJACENT */
194
195 static void
196 gtk_css_selector_adjacent_print (const GtkCssSelector *selector,
197                                  GString              *string)
198 {
199   g_string_append (string, " + ");
200 }
201
202 static gboolean
203 gtk_css_selector_adjacent_match (const GtkCssSelector *selector,
204                                  const GtkCssMatcher  *matcher)
205 {
206   GtkCssMatcher previous;
207
208   if (!_gtk_css_matcher_get_previous (&previous, matcher))
209     return FALSE;
210
211   return gtk_css_selector_match (gtk_css_selector_previous (selector), &previous);
212 }
213
214 static GtkCssChange
215 gtk_css_selector_adjacent_get_change (const GtkCssSelector *selector)
216 {
217   return _gtk_css_change_for_sibling (gtk_css_selector_get_change (gtk_css_selector_previous (selector)));
218 }
219
220 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ADJACENT = {
221   "adjacent",
222   gtk_css_selector_adjacent_print,
223   gtk_css_selector_adjacent_match,
224   gtk_css_selector_adjacent_get_change,
225   FALSE, FALSE, FALSE
226 };
227
228 /* ANY */
229
230 static void
231 gtk_css_selector_any_print (const GtkCssSelector *selector,
232                             GString              *string)
233 {
234   g_string_append_c (string, '*');
235 }
236
237 static gboolean
238 gtk_css_selector_any_match (const GtkCssSelector *selector,
239                             const GtkCssMatcher  *matcher)
240 {
241   const GtkCssSelector *previous = gtk_css_selector_previous (selector);
242   
243   if (previous &&
244       previous->class == &GTK_CSS_SELECTOR_DESCENDANT &&
245       _gtk_css_matcher_has_regions (matcher))
246     {
247       if (gtk_css_selector_match (gtk_css_selector_previous (previous), matcher))
248         return TRUE;
249     }
250   
251   return gtk_css_selector_match (previous, matcher);
252 }
253
254 static GtkCssChange
255 gtk_css_selector_any_get_change (const GtkCssSelector *selector)
256 {
257   return gtk_css_selector_get_change (gtk_css_selector_previous (selector));
258 }
259
260 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ANY = {
261   "any",
262   gtk_css_selector_any_print,
263   gtk_css_selector_any_match,
264   gtk_css_selector_any_get_change,
265   FALSE, FALSE, FALSE
266 };
267
268 /* NAME */
269
270 static void
271 gtk_css_selector_name_print (const GtkCssSelector *selector,
272                              GString              *string)
273 {
274   g_string_append (string, selector->data);
275 }
276
277 static gboolean
278 gtk_css_selector_name_match (const GtkCssSelector *selector,
279                              const GtkCssMatcher  *matcher)
280 {
281   if (!_gtk_css_matcher_has_name (matcher, selector->data))
282     return FALSE;
283
284   return gtk_css_selector_match (gtk_css_selector_previous (selector), matcher);
285 }
286
287 static GtkCssChange
288 gtk_css_selector_name_get_change (const GtkCssSelector *selector)
289 {
290   return gtk_css_selector_get_change (gtk_css_selector_previous (selector)) | GTK_CSS_CHANGE_NAME;
291 }
292
293 static const GtkCssSelectorClass GTK_CSS_SELECTOR_NAME = {
294   "name",
295   gtk_css_selector_name_print,
296   gtk_css_selector_name_match,
297   gtk_css_selector_name_get_change,
298   FALSE, FALSE, TRUE
299 };
300
301 /* REGION */
302
303 static void
304 gtk_css_selector_region_print (const GtkCssSelector *selector,
305                                GString              *string)
306 {
307   g_string_append (string, selector->data);
308 }
309
310 static gboolean
311 gtk_css_selector_region_match (const GtkCssSelector *selector,
312                                const GtkCssMatcher  *matcher)
313 {
314   const GtkCssSelector *previous;
315
316   if (!_gtk_css_matcher_has_region (matcher, selector->data, 0))
317     return FALSE;
318
319   previous = gtk_css_selector_previous (selector);
320   if (previous && previous->class == &GTK_CSS_SELECTOR_DESCENDANT &&
321       gtk_css_selector_match (gtk_css_selector_previous (previous), matcher))
322     return TRUE;
323
324   return gtk_css_selector_match (previous, matcher);
325 }
326
327 static GtkCssChange
328 gtk_css_selector_region_get_change (const GtkCssSelector *selector)
329 {
330   GtkCssChange change;
331
332   change = gtk_css_selector_get_change (gtk_css_selector_previous (selector));
333   change |= GTK_CSS_CHANGE_REGION;
334   change |= _gtk_css_change_for_child (change);
335
336   return change;
337 }
338
339 static const GtkCssSelectorClass GTK_CSS_SELECTOR_REGION = {
340   "region",
341   gtk_css_selector_region_print,
342   gtk_css_selector_region_match,
343   gtk_css_selector_region_get_change,
344   FALSE, FALSE, TRUE
345 };
346
347 /* CLASS */
348
349 static void
350 gtk_css_selector_class_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_class_match (const GtkCssSelector *selector,
359                               const GtkCssMatcher  *matcher)
360 {
361   if (!_gtk_css_matcher_has_class (matcher, selector->data))
362     return FALSE;
363
364   return gtk_css_selector_match (gtk_css_selector_previous (selector), matcher);
365 }
366
367 static GtkCssChange
368 gtk_css_selector_class_get_change (const GtkCssSelector *selector)
369 {
370   return gtk_css_selector_get_change (gtk_css_selector_previous (selector)) | GTK_CSS_CHANGE_CLASS;
371 }
372
373 static const GtkCssSelectorClass GTK_CSS_SELECTOR_CLASS = {
374   "class",
375   gtk_css_selector_class_print,
376   gtk_css_selector_class_match,
377   gtk_css_selector_class_get_change,
378   FALSE, TRUE, FALSE
379 };
380
381 /* ID */
382
383 static void
384 gtk_css_selector_id_print (const GtkCssSelector *selector,
385                            GString              *string)
386 {
387   g_string_append_c (string, '#');
388   g_string_append (string, selector->data);
389 }
390
391 static gboolean
392 gtk_css_selector_id_match (const GtkCssSelector *selector,
393                            const GtkCssMatcher  *matcher)
394 {
395   if (!_gtk_css_matcher_has_id (matcher, selector->data))
396     return FALSE;
397
398   return gtk_css_selector_match (gtk_css_selector_previous (selector), matcher);
399 }
400
401 static GtkCssChange
402 gtk_css_selector_id_get_change (const GtkCssSelector *selector)
403 {
404   return gtk_css_selector_get_change (gtk_css_selector_previous (selector)) | GTK_CSS_CHANGE_ID;
405 }
406
407 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ID = {
408   "id",
409   gtk_css_selector_id_print,
410   gtk_css_selector_id_match,
411   gtk_css_selector_id_get_change,
412   TRUE, FALSE, FALSE
413 };
414
415 /* PSEUDOCLASS FOR STATE */
416
417 static void
418 gtk_css_selector_pseudoclass_state_print (const GtkCssSelector *selector,
419                                           GString              *string)
420 {
421   static const char * state_names[] = {
422     "active",
423     "hover",
424     "selected",
425     "insensitive",
426     "inconsistent",
427     "focus",
428     "backdrop"
429   };
430   guint i, state;
431
432   state = GPOINTER_TO_UINT (selector->data);
433   g_string_append_c (string, ':');
434
435   for (i = 0; i < G_N_ELEMENTS (state_names); i++)
436     {
437       if (state == (1 << i))
438         {
439           g_string_append (string, state_names[i]);
440           return;
441         }
442     }
443
444   g_assert_not_reached ();
445 }
446
447 static gboolean
448 gtk_css_selector_pseudoclass_state_match (const GtkCssSelector *selector,
449                                           const GtkCssMatcher  *matcher)
450 {
451   GtkStateFlags state = GPOINTER_TO_UINT (selector->data);
452
453   if ((_gtk_css_matcher_get_state (matcher) & state) != state)
454     return FALSE;
455
456   return gtk_css_selector_match (gtk_css_selector_previous (selector), matcher);
457 }
458
459 static GtkCssChange
460 gtk_css_selector_pseudoclass_state_get_change (const GtkCssSelector *selector)
461 {
462   return gtk_css_selector_get_change (gtk_css_selector_previous (selector)) | GTK_CSS_CHANGE_STATE;
463 }
464
465 static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_STATE = {
466   "pseudoclass-state",
467   gtk_css_selector_pseudoclass_state_print,
468   gtk_css_selector_pseudoclass_state_match,
469   gtk_css_selector_pseudoclass_state_get_change,
470   FALSE, TRUE, FALSE
471 };
472
473 /* PSEUDOCLASS FOR POSITION */
474
475 typedef enum {
476   POSITION_FORWARD,
477   POSITION_BACKWARD,
478   POSITION_ONLY,
479   POSITION_SORTED
480 } PositionType;
481 #define POSITION_TYPE_BITS 2
482 #define POSITION_NUMBER_BITS ((sizeof (gpointer) * 8 - POSITION_TYPE_BITS) / 2)
483
484 static gconstpointer
485 encode_position (PositionType type,
486                  int          a,
487                  int          b)
488 {
489   union {
490     gconstpointer p;
491     struct {
492       gssize type :POSITION_TYPE_BITS;
493       gssize a :POSITION_NUMBER_BITS;
494       gssize b :POSITION_NUMBER_BITS;
495     } data;
496   } result;
497   G_STATIC_ASSERT (sizeof (gconstpointer) == sizeof (result));
498
499   g_assert (type < (1 << POSITION_TYPE_BITS));
500
501   result.data.type = type;
502   result.data.a = a;
503   result.data.b = b;
504
505   return result.p;
506 }
507
508 static void
509 decode_position (const GtkCssSelector *selector,
510                  PositionType         *type,
511                  int                  *a,
512                  int                  *b)
513 {
514   union {
515     gconstpointer p;
516     struct {
517       gssize type :POSITION_TYPE_BITS;
518       gssize a :POSITION_NUMBER_BITS;
519       gssize b :POSITION_NUMBER_BITS;
520     } data;
521   } result;
522   G_STATIC_ASSERT (sizeof (gconstpointer) == sizeof (result));
523
524   result.p = selector->data;
525
526   *type = result.data.type & ((1 << POSITION_TYPE_BITS) - 1);
527   *a = result.data.a;
528   *b = result.data.b;
529 }
530
531 static void
532 gtk_css_selector_pseudoclass_position_print (const GtkCssSelector *selector,
533                                              GString              *string)
534 {
535   PositionType type;
536   int a, b;
537
538   decode_position (selector, &type, &a, &b);
539   switch (type)
540     {
541     case POSITION_FORWARD:
542       if (a == 0)
543         {
544           if (b == 1)
545             g_string_append (string, ":first-child");
546           else
547             g_string_append_printf (string, ":nth-child(%d)", b);
548         }
549       else if (a == 2 && b == 0)
550         g_string_append (string, ":nth-child(even)");
551       else if (a == 2 && b == 1)
552         g_string_append (string, ":nth-child(odd)");
553       else
554         {
555           g_string_append (string, ":nth-child(");
556           if (a == 1)
557             g_string_append (string, "n");
558           else if (a == -1)
559             g_string_append (string, "-n");
560           else
561             g_string_append_printf (string, "%dn", a);
562           if (b > 0)
563             g_string_append_printf (string, "+%d)", b);
564           else if (b < 0)
565             g_string_append_printf (string, "%d)", b);
566           else
567             g_string_append (string, ")");
568         }
569       break;
570     case POSITION_BACKWARD:
571       if (a == 0)
572         {
573           if (b == 1)
574             g_string_append (string, ":last-child");
575           else
576             g_string_append_printf (string, ":nth-last-child(%d)", b);
577         }
578       else if (a == 2 && b == 0)
579         g_string_append (string, ":nth-last-child(even)");
580       else if (a == 2 && b == 1)
581         g_string_append (string, ":nth-last-child(odd)");
582       else
583         {
584           g_string_append (string, ":nth-last-child(");
585           if (a == 1)
586             g_string_append (string, "n");
587           else if (a == -1)
588             g_string_append (string, "-n");
589           else
590             g_string_append_printf (string, "%dn", a);
591           if (b > 0)
592             g_string_append_printf (string, "+%d)", b);
593           else if (b < 0)
594             g_string_append_printf (string, "%d)", b);
595           else
596             g_string_append (string, ")");
597         }
598       break;
599     case POSITION_ONLY:
600       g_string_append (string, ":only-child");
601       break;
602     case POSITION_SORTED:
603       g_string_append (string, ":sorted");
604       break;
605     default:
606       g_assert_not_reached ();
607       break;
608     }
609 }
610
611 static gboolean
612 gtk_css_selector_pseudoclass_position_match_for_region (const GtkCssSelector *selector,
613                                                         const GtkCssMatcher  *matcher)
614 {
615   GtkRegionFlags selector_flags;
616   const GtkCssSelector *previous;
617   PositionType type;
618   int a, b;
619
620   decode_position (selector, &type, &a, &b);
621   switch (type)
622     {
623     case POSITION_FORWARD:
624       if (a == 0 && b == 1)
625         selector_flags = GTK_REGION_FIRST;
626       else if (a == 2 && b == 0)
627         selector_flags = GTK_REGION_EVEN;
628       else if (a == 2 && b == 1)
629         selector_flags = GTK_REGION_ODD;
630       else
631         return FALSE;
632       break;
633     case POSITION_BACKWARD:
634       if (a == 0 && b == 1)
635         selector_flags = GTK_REGION_LAST;
636       else
637         return FALSE;
638       break;
639     case POSITION_ONLY:
640       selector_flags = GTK_REGION_ONLY;
641       break;
642     case POSITION_SORTED:
643       selector_flags = GTK_REGION_SORTED;
644       break;
645     default:
646       g_assert_not_reached ();
647       break;
648     }
649   selector = gtk_css_selector_previous (selector);
650
651   if (!_gtk_css_matcher_has_region (matcher, selector->data, selector_flags))
652     return FALSE;
653
654   previous = gtk_css_selector_previous (selector);
655   if (previous && previous->class == &GTK_CSS_SELECTOR_DESCENDANT &&
656       gtk_css_selector_match (gtk_css_selector_previous (previous), matcher))
657     return TRUE;
658
659   return gtk_css_selector_match (previous, matcher);
660 }
661
662 static gboolean
663 gtk_css_selector_pseudoclass_position_match (const GtkCssSelector *selector,
664                                              const GtkCssMatcher  *matcher)
665 {
666   const GtkCssSelector *previous;
667   PositionType type;
668   int a, b;
669
670   previous = gtk_css_selector_previous (selector);
671   if (previous && previous->class == &GTK_CSS_SELECTOR_REGION)
672     return gtk_css_selector_pseudoclass_position_match_for_region (selector, matcher);
673
674   decode_position (selector, &type, &a, &b);
675   switch (type)
676     {
677     case POSITION_FORWARD:
678       if (!_gtk_css_matcher_has_position (matcher, TRUE, a, b))
679         return FALSE;
680       break;
681     case POSITION_BACKWARD:
682       if (!_gtk_css_matcher_has_position (matcher, FALSE, a, b))
683         return FALSE;
684       break;
685     case POSITION_ONLY:
686       if (!_gtk_css_matcher_has_position (matcher, TRUE, 0, 1) ||
687           !_gtk_css_matcher_has_position (matcher, FALSE, 0, 1))
688         return FALSE;
689       break;
690     case POSITION_SORTED:
691       return FALSE;
692     default:
693       g_assert_not_reached ();
694       return FALSE;
695     }
696
697   return gtk_css_selector_match (previous, matcher);
698 }
699
700 static GtkCssChange
701 gtk_css_selector_pseudoclass_position_get_change (const GtkCssSelector *selector)
702 {
703   return gtk_css_selector_get_change (gtk_css_selector_previous (selector)) | GTK_CSS_CHANGE_POSITION;
704 }
705
706 static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION = {
707   "pseudoclass-position",
708   gtk_css_selector_pseudoclass_position_print,
709   gtk_css_selector_pseudoclass_position_match,
710   gtk_css_selector_pseudoclass_position_get_change,
711   FALSE, TRUE, FALSE
712 };
713
714 /* API */
715
716 static guint
717 gtk_css_selector_size (const GtkCssSelector *selector)
718 {
719   guint size = 0;
720
721   while (selector)
722     {
723       selector = gtk_css_selector_previous (selector);
724       size++;
725     }
726
727   return size;
728 }
729
730 static GtkCssSelector *
731 gtk_css_selector_new (const GtkCssSelectorClass *class,
732                       GtkCssSelector            *selector,
733                       gconstpointer              data)
734 {
735   guint size;
736
737   size = gtk_css_selector_size (selector);
738   selector = g_realloc (selector, sizeof (GtkCssSelector) * (size + 1) + sizeof (gpointer));
739   if (size == 0)
740     selector[1].class = NULL;
741   else
742     memmove (selector + 1, selector, sizeof (GtkCssSelector) * size + sizeof (gpointer));
743
744   selector->class = class;
745   selector->data = data;
746
747   return selector;
748 }
749
750 static GtkCssSelector *
751 parse_selector_class (GtkCssParser *parser, GtkCssSelector *selector)
752 {
753   char *name;
754     
755   name = _gtk_css_parser_try_name (parser, FALSE);
756
757   if (name == NULL)
758     {
759       _gtk_css_parser_error (parser, "Expected a valid name for class");
760       if (selector)
761         _gtk_css_selector_free (selector);
762       return NULL;
763     }
764
765   selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_CLASS,
766                                    selector,
767                                    g_intern_string (name));
768
769   g_free (name);
770
771   return selector;
772 }
773
774 static GtkCssSelector *
775 parse_selector_id (GtkCssParser *parser, GtkCssSelector *selector)
776 {
777   char *name;
778     
779   name = _gtk_css_parser_try_name (parser, FALSE);
780
781   if (name == NULL)
782     {
783       _gtk_css_parser_error (parser, "Expected a valid name for id");
784       if (selector)
785         _gtk_css_selector_free (selector);
786       return NULL;
787     }
788
789   selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ID,
790                                    selector,
791                                    g_intern_string (name));
792
793   g_free (name);
794
795   return selector;
796 }
797
798 static GtkCssSelector *
799 parse_selector_pseudo_class_nth_child (GtkCssParser   *parser,
800                                        GtkCssSelector *selector,
801                                        PositionType    type)
802 {
803   int a, b;
804
805   if (!_gtk_css_parser_try (parser, "(", TRUE))
806     {
807       _gtk_css_parser_error (parser, "Missing opening bracket for pseudo-class");
808       if (selector)
809         _gtk_css_selector_free (selector);
810       return NULL;
811     }
812
813   if (_gtk_css_parser_try (parser, "even", TRUE))
814     {
815       a = 2;
816       b = 0;
817     }
818   else if (_gtk_css_parser_try (parser, "odd", TRUE))
819     {
820       a = 2;
821       b = 1;
822     }
823   else if (type == POSITION_FORWARD &&
824            _gtk_css_parser_try (parser, "first", TRUE))
825     {
826       a = 0;
827       b = 1;
828     }
829   else if (type == POSITION_FORWARD &&
830            _gtk_css_parser_try (parser, "last", TRUE))
831     {
832       a = 0;
833       b = 1;
834       type = POSITION_BACKWARD;
835     }
836   else
837     {
838       int multiplier;
839
840       if (_gtk_css_parser_try (parser, "+", TRUE))
841         multiplier = 1;
842       else if (_gtk_css_parser_try (parser, "-", TRUE))
843         multiplier = -1;
844       else
845         multiplier = 1;
846
847       if (_gtk_css_parser_try_int (parser, &a))
848         {
849           if (a < 0)
850             {
851               _gtk_css_parser_error (parser, "Expected an integer");
852               if (selector)
853                 _gtk_css_selector_free (selector);
854               return NULL;
855             }
856           a *= multiplier;
857         }
858       else if (_gtk_css_parser_has_prefix (parser, "n"))
859         {
860           a = multiplier;
861         }
862       else
863         {
864           _gtk_css_parser_error (parser, "Expected an integer");
865           if (selector)
866             _gtk_css_selector_free (selector);
867           return NULL;
868         }
869
870       if (_gtk_css_parser_try (parser, "n", TRUE))
871         {
872           if (_gtk_css_parser_try (parser, "+", TRUE))
873             multiplier = 1;
874           else if (_gtk_css_parser_try (parser, "-", TRUE))
875             multiplier = -1;
876           else
877             multiplier = 1;
878
879           if (_gtk_css_parser_try_int (parser, &b))
880             {
881               if (b < 0)
882                 {
883                   _gtk_css_parser_error (parser, "Expected an integer");
884                   if (selector)
885                     _gtk_css_selector_free (selector);
886                   return NULL;
887                 }
888             }
889           else
890             b = 0;
891
892           b *= multiplier;
893         }
894       else
895         {
896           b = a;
897           a = 0;
898         }
899     }
900
901   if (!_gtk_css_parser_try (parser, ")", FALSE))
902     {
903       _gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class");
904       if (selector)
905         _gtk_css_selector_free (selector);
906       return NULL;
907     }
908
909   selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION,
910                                    selector,
911                                    encode_position (type, a, b));
912
913   return selector;
914 }
915
916 static GtkCssSelector *
917 parse_selector_pseudo_class (GtkCssParser   *parser,
918                              GtkCssSelector *selector)
919 {
920   static const struct {
921     const char    *name;
922     GtkStateFlags  state_flag;
923     PositionType   position_type;
924     int            position_a;
925     int            position_b;
926   } pseudo_classes[] = {
927     { "first-child",  0,                           POSITION_FORWARD,  0, 1 },
928     { "last-child",   0,                           POSITION_BACKWARD, 0, 1 },
929     { "only-child",   0,                           POSITION_ONLY,     0, 0 },
930     { "sorted",       0,                           POSITION_SORTED,   0, 0 },
931     { "active",       GTK_STATE_FLAG_ACTIVE, },
932     { "prelight",     GTK_STATE_FLAG_PRELIGHT, },
933     { "hover",        GTK_STATE_FLAG_PRELIGHT, },
934     { "selected",     GTK_STATE_FLAG_SELECTED, },
935     { "insensitive",  GTK_STATE_FLAG_INSENSITIVE, },
936     { "inconsistent", GTK_STATE_FLAG_INCONSISTENT, },
937     { "focused",      GTK_STATE_FLAG_FOCUSED, },
938     { "focus",        GTK_STATE_FLAG_FOCUSED, },
939     { "backdrop",     GTK_STATE_FLAG_BACKDROP, }
940   };
941   guint i;
942
943   if (_gtk_css_parser_try (parser, "nth-child", FALSE))
944     return parse_selector_pseudo_class_nth_child (parser, selector, POSITION_FORWARD);
945   else if (_gtk_css_parser_try (parser, "nth-last-child", FALSE))
946     return parse_selector_pseudo_class_nth_child (parser, selector, POSITION_BACKWARD);
947
948   for (i = 0; i < G_N_ELEMENTS (pseudo_classes); i++)
949     {
950       if (_gtk_css_parser_try (parser, pseudo_classes[i].name, FALSE))
951         {
952           if (pseudo_classes[i].state_flag)
953             selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_PSEUDOCLASS_STATE,
954                                              selector,
955                                              GUINT_TO_POINTER (pseudo_classes[i].state_flag));
956           else
957             selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION,
958                                              selector,
959                                              encode_position (pseudo_classes[i].position_type,
960                                                               pseudo_classes[i].position_a,
961                                                               pseudo_classes[i].position_b));
962           return selector;
963         }
964     }
965       
966   _gtk_css_parser_error (parser, "Missing name of pseudo-class");
967   if (selector)
968     _gtk_css_selector_free (selector);
969   return NULL;
970 }
971
972 static GtkCssSelector *
973 try_parse_name (GtkCssParser   *parser,
974                 GtkCssSelector *selector)
975 {
976   char *name;
977
978   name = _gtk_css_parser_try_ident (parser, FALSE);
979   if (name)
980     {
981       selector = gtk_css_selector_new (_gtk_style_context_check_region_name (name)
982                                        ? &GTK_CSS_SELECTOR_REGION
983                                        : &GTK_CSS_SELECTOR_NAME,
984                                        selector,
985                                        g_intern_string (name));
986       g_free (name);
987     }
988   else if (_gtk_css_parser_try (parser, "*", FALSE))
989     selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ANY, selector, NULL);
990   
991   return selector;
992 }
993
994 static GtkCssSelector *
995 parse_simple_selector (GtkCssParser   *parser,
996                        GtkCssSelector *selector)
997 {
998   guint size = gtk_css_selector_size (selector);
999   
1000   selector = try_parse_name (parser, selector);
1001
1002   do {
1003       if (_gtk_css_parser_try (parser, "#", FALSE))
1004         selector = parse_selector_id (parser, selector);
1005       else if (_gtk_css_parser_try (parser, ".", FALSE))
1006         selector = parse_selector_class (parser, selector);
1007       else if (_gtk_css_parser_try (parser, ":", FALSE))
1008         selector = parse_selector_pseudo_class (parser, selector);
1009       else if (gtk_css_selector_size (selector) == size)
1010         {
1011           _gtk_css_parser_error (parser, "Expected a valid selector");
1012           if (selector)
1013             _gtk_css_selector_free (selector);
1014           return NULL;
1015         }
1016       else
1017         break;
1018     }
1019   while (selector && !_gtk_css_parser_is_eof (parser));
1020
1021   _gtk_css_parser_skip_whitespace (parser);
1022
1023   return selector;
1024 }
1025
1026 GtkCssSelector *
1027 _gtk_css_selector_parse (GtkCssParser *parser)
1028 {
1029   GtkCssSelector *selector = NULL;
1030
1031   while ((selector = parse_simple_selector (parser, selector)) &&
1032          !_gtk_css_parser_is_eof (parser) &&
1033          !_gtk_css_parser_begins_with (parser, ',') &&
1034          !_gtk_css_parser_begins_with (parser, '{'))
1035     {
1036       if (_gtk_css_parser_try (parser, "+", TRUE))
1037         selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ADJACENT, selector, NULL);
1038       else if (_gtk_css_parser_try (parser, "~", TRUE))
1039         selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_SIBLING, selector, NULL);
1040       else if (_gtk_css_parser_try (parser, ">", TRUE))
1041         selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_CHILD, selector, NULL);
1042       else
1043         selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_DESCENDANT, selector, NULL);
1044     }
1045
1046   return selector;
1047 }
1048
1049 void
1050 _gtk_css_selector_free (GtkCssSelector *selector)
1051 {
1052   g_return_if_fail (selector != NULL);
1053
1054   g_free (selector);
1055 }
1056
1057 void
1058 _gtk_css_selector_print (const GtkCssSelector *selector,
1059                          GString *             str)
1060 {
1061   const GtkCssSelector *previous;
1062
1063   g_return_if_fail (selector != NULL);
1064
1065   previous = gtk_css_selector_previous (selector);
1066   if (previous)
1067     _gtk_css_selector_print (previous, str);
1068
1069   selector->class->print (selector, str);
1070 }
1071
1072 char *
1073 _gtk_css_selector_to_string (const GtkCssSelector *selector)
1074 {
1075   GString *string;
1076
1077   g_return_val_if_fail (selector != NULL, NULL);
1078
1079   string = g_string_new (NULL);
1080
1081   _gtk_css_selector_print (selector, string);
1082
1083   return g_string_free (string, FALSE);
1084 }
1085
1086 GtkCssChange
1087 _gtk_css_selector_get_change (const GtkCssSelector *selector)
1088 {
1089   g_return_val_if_fail (selector != NULL, 0);
1090
1091   return gtk_css_selector_get_change (selector);
1092 }
1093
1094 /**
1095  * _gtk_css_selector_matches:
1096  * @selector: the selector
1097  * @path: the path to check
1098  * @state: The state to match
1099  *
1100  * Checks if the @selector matches the given @path. If @length is
1101  * smaller than the number of elements in @path, it is assumed that
1102  * only the first @length element of @path are valid and the rest
1103  * does not exist. This is useful for doing parent matches for the
1104  * 'inherit' keyword.
1105  *
1106  * Returns: %TRUE if the selector matches @path
1107  **/
1108 gboolean
1109 _gtk_css_selector_matches (const GtkCssSelector *selector,
1110                            const GtkCssMatcher  *matcher)
1111 {
1112
1113   g_return_val_if_fail (selector != NULL, FALSE);
1114   g_return_val_if_fail (matcher != NULL, FALSE);
1115
1116   return gtk_css_selector_match (selector, matcher);
1117 }
1118
1119 /* Computes specificity according to CSS 2.1.
1120  * The arguments must be initialized to 0 */
1121 static void
1122 _gtk_css_selector_get_specificity (const GtkCssSelector *selector,
1123                                    guint                *ids,
1124                                    guint                *classes,
1125                                    guint                *elements)
1126 {
1127   for (; selector; selector = gtk_css_selector_previous (selector))
1128     {
1129       const GtkCssSelectorClass *klass = selector->class;
1130
1131       if (klass->increase_id_specificity)
1132         (*ids)++;
1133       if (klass->increase_class_specificity)
1134         (*classes)++;
1135       if (klass->increase_element_specificity)
1136         (*elements)++;
1137     }
1138 }
1139
1140 int
1141 _gtk_css_selector_compare (const GtkCssSelector *a,
1142                            const GtkCssSelector *b)
1143 {
1144   guint a_ids = 0, a_classes = 0, a_elements = 0;
1145   guint b_ids = 0, b_classes = 0, b_elements = 0;
1146   int compare;
1147
1148   _gtk_css_selector_get_specificity (a, &a_ids, &a_classes, &a_elements);
1149   _gtk_css_selector_get_specificity (b, &b_ids, &b_classes, &b_elements);
1150
1151   compare = a_ids - b_ids;
1152   if (compare)
1153     return compare;
1154
1155   compare = a_classes - b_classes;
1156   if (compare)
1157     return compare;
1158
1159   return a_elements - b_elements;
1160 }
1161
1162 GtkStateFlags
1163 _gtk_css_selector_get_state_flags (const GtkCssSelector *selector)
1164 {
1165   GtkStateFlags state = 0;
1166
1167   g_return_val_if_fail (selector != NULL, 0);
1168
1169   for (; selector && selector->class == &GTK_CSS_SELECTOR_NAME; selector = gtk_css_selector_previous (selector))
1170     state |= GPOINTER_TO_UINT (selector->data);
1171
1172   return state;
1173 }
1174