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