]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssselector.c
71247c0cc368cbb586519bfef4c61285f63a2699
[~andy/gtk] / gtk / gtkcssselector.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2011 Benjamin Otte <otte@gnome.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 #include "config.h"
19
20 #include "gtkcssselectorprivate.h"
21
22 #include "gtkcssprovider.h"
23 #include "gtkstylecontextprivate.h"
24
25 typedef enum {
26   GTK_CSS_COMBINE_DESCANDANT,
27   GTK_CSS_COMBINE_CHILD
28 } GtkCssCombinator;
29
30 struct _GtkCssSelector
31 {
32   GtkCssSelector *  previous;        /* link to next element in selector or NULL if last */
33   GtkCssCombinator  combine;         /* how to combine with the previous element */
34   const char *      name;            /* quarked name of element we match or NULL if any */
35   GType             type;            /* cache for type belonging to name - G_TYPE_INVALID if uncached */
36   GQuark *          ids;             /* 0-terminated list of required ids or NULL if none */
37   GQuark *          classes;         /* 0-terminated list of required classes or NULL if none */
38   GtkRegionFlags    pseudo_classes;  /* required pseudo classes */
39   GtkStateFlags     state;           /* required state flags (currently not checked when matching) */
40 };
41
42 static GtkCssSelector *
43 gtk_css_selector_new (GtkCssSelector         *previous,
44                       GtkCssCombinator        combine,
45                       const char *            name,
46                       GQuark *                ids,
47                       GQuark *                classes,
48                       GtkRegionFlags          pseudo_classes,
49                       GtkStateFlags           state)
50 {
51   GtkCssSelector *selector;
52
53   selector = g_slice_new0 (GtkCssSelector);
54   selector->previous = previous;
55   selector->combine = combine;
56   selector->name = name ? g_quark_to_string (g_quark_from_string (name)) : NULL;
57   selector->type = !name || _gtk_style_context_check_region_name (name) ? G_TYPE_NONE : G_TYPE_INVALID;
58   selector->ids = ids;
59   selector->classes = classes;
60   selector->pseudo_classes = pseudo_classes;
61   selector->state = state;
62
63   return selector;
64 }
65
66 static gboolean
67 parse_selector_class (GtkCssParser *parser, GArray *classes)
68 {
69   GQuark qname;
70   char *name;
71     
72   name = _gtk_css_parser_try_name (parser, FALSE);
73
74   if (name == NULL)
75     {
76       _gtk_css_parser_error (parser, "Expected a valid name for class");
77       return FALSE;
78     }
79
80   qname = g_quark_from_string (name);
81   g_array_append_val (classes, qname);
82   g_free (name);
83   return TRUE;
84 }
85
86 static gboolean
87 parse_selector_name (GtkCssParser *parser, GArray *names)
88 {
89   GQuark qname;
90   char *name;
91     
92   name = _gtk_css_parser_try_name (parser, FALSE);
93
94   if (name == NULL)
95     {
96       _gtk_css_parser_error (parser, "Expected a valid name for id");
97       return FALSE;
98     }
99
100   qname = g_quark_from_string (name);
101   g_array_append_val (names, qname);
102   g_free (name);
103   return TRUE;
104 }
105
106 static gboolean
107 parse_selector_pseudo_class (GtkCssParser   *parser,
108                              GtkRegionFlags *region_to_modify,
109                              GtkStateFlags  *state_to_modify)
110 {
111   struct {
112     const char *name;
113     GtkRegionFlags region_flag;
114     GtkStateFlags state_flag;
115   } pseudo_classes[] = {
116     { "first-child",  GTK_REGION_FIRST, 0 },
117     { "last-child",   GTK_REGION_LAST, 0 },
118     { "only-child",   GTK_REGION_ONLY, 0 },
119     { "sorted",       GTK_REGION_SORTED, 0 },
120     { "active",       0, GTK_STATE_FLAG_ACTIVE },
121     { "prelight",     0, GTK_STATE_FLAG_PRELIGHT },
122     { "hover",        0, GTK_STATE_FLAG_PRELIGHT },
123     { "selected",     0, GTK_STATE_FLAG_SELECTED },
124     { "insensitive",  0, GTK_STATE_FLAG_INSENSITIVE },
125     { "inconsistent", 0, GTK_STATE_FLAG_INCONSISTENT },
126     { "focused",      0, GTK_STATE_FLAG_FOCUSED },
127     { "focus",        0, GTK_STATE_FLAG_FOCUSED },
128     { "backdrop",     0, GTK_STATE_FLAG_BACKDROP },
129     { NULL, }
130   }, nth_child_classes[] = {
131     { "first",        GTK_REGION_FIRST, 0 },
132     { "last",         GTK_REGION_LAST, 0 },
133     { "even",         GTK_REGION_EVEN, 0 },
134     { "odd",          GTK_REGION_ODD, 0 },
135     { NULL, }
136   }, *classes;
137   guint i;
138   char *name;
139   GError *error;
140
141   name = _gtk_css_parser_try_ident (parser, FALSE);
142   if (name == NULL)
143     {
144       _gtk_css_parser_error (parser, "Missing name of pseudo-class");
145       return FALSE;
146     }
147
148   if (_gtk_css_parser_try (parser, "(", TRUE))
149     {
150       char *function = name;
151
152       name = _gtk_css_parser_try_ident (parser, TRUE);
153       if (!_gtk_css_parser_try (parser, ")", FALSE))
154         {
155           _gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class");
156           return FALSE;
157         }
158
159       if (g_ascii_strcasecmp (function, "nth-child") != 0)
160         {
161           error = g_error_new (GTK_CSS_PROVIDER_ERROR,
162                                GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
163                                "Unknown pseudo-class '%s(%s)'", function, name ? name : "");
164           _gtk_css_parser_take_error (parser, error);
165           g_free (function);
166           g_free (name);
167           return FALSE;
168         }
169       
170       g_free (function);
171     
172       if (name == NULL)
173         {
174           error = g_error_new (GTK_CSS_PROVIDER_ERROR,
175                                GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
176                                "Unknown pseudo-class 'nth-child(%s)'", name);
177           _gtk_css_parser_take_error (parser, error);
178           return FALSE;
179         }
180
181       classes = nth_child_classes;
182     }
183   else
184     classes = pseudo_classes;
185
186   for (i = 0; classes[i].name != NULL; i++)
187     {
188       if (g_ascii_strcasecmp (name, classes[i].name) == 0)
189         {
190           if ((*region_to_modify & classes[i].region_flag) ||
191               (*state_to_modify & classes[i].state_flag))
192             {
193               if (classes == nth_child_classes)
194                 _gtk_css_parser_error (parser, "Duplicate pseudo-class 'nth-child(%s)'", name);
195               else
196                 _gtk_css_parser_error (parser, "Duplicate pseudo-class '%s'", name);
197             }
198           *region_to_modify |= classes[i].region_flag;
199           *state_to_modify |= classes[i].state_flag;
200
201           g_free (name);
202           return TRUE;
203         }
204     }
205
206   if (classes == nth_child_classes)
207     error = g_error_new (GTK_CSS_PROVIDER_ERROR,
208                          GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
209                          "Unknown pseudo-class 'nth-child(%s)'", name);
210   else
211     error = g_error_new (GTK_CSS_PROVIDER_ERROR,
212                          GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
213                          "Unknown pseudo-class '%s'", name);
214
215   g_free (name);
216   _gtk_css_parser_take_error (parser, error);
217
218   return FALSE;
219 }
220
221 static gboolean
222 parse_simple_selector (GtkCssParser *parser,
223                        char **name,
224                        GArray *ids,
225                        GArray *classes,
226                        GtkRegionFlags *pseudo_classes,
227                        GtkStateFlags *state)
228 {
229   gboolean parsed_something;
230   
231   *name = _gtk_css_parser_try_ident (parser, FALSE);
232   if (*name)
233     parsed_something = TRUE;
234   else
235     parsed_something = _gtk_css_parser_try (parser, "*", FALSE);
236
237   do {
238       if (_gtk_css_parser_try (parser, "#", FALSE))
239         {
240           if (!parse_selector_name (parser, ids))
241             return FALSE;
242         }
243       else if (_gtk_css_parser_try (parser, ".", FALSE))
244         {
245           if (!parse_selector_class (parser, classes))
246             return FALSE;
247         }
248       else if (_gtk_css_parser_try (parser, ":", FALSE))
249         {
250           if (!parse_selector_pseudo_class (parser, pseudo_classes, state))
251             return FALSE;
252         }
253       else if (!parsed_something)
254         {
255           _gtk_css_parser_error (parser, "Expected a valid selector");
256           return FALSE;
257         }
258       else
259         break;
260
261       parsed_something = TRUE;
262     }
263   while (!_gtk_css_parser_is_eof (parser));
264
265   _gtk_css_parser_skip_whitespace (parser);
266   return TRUE;
267 }
268
269 GtkCssSelector *
270 _gtk_css_selector_parse (GtkCssParser *parser)
271 {
272   GtkCssSelector *selector = NULL;
273
274   do {
275       char *name = NULL;
276       GArray *ids = g_array_new (TRUE, FALSE, sizeof (GQuark));
277       GArray *classes = g_array_new (TRUE, FALSE, sizeof (GQuark));
278       GtkRegionFlags pseudo_classes = 0;
279       GtkStateFlags state = 0;
280       GtkCssCombinator combine = GTK_CSS_COMBINE_DESCANDANT;
281
282       if (selector)
283         {
284           if (_gtk_css_parser_try (parser, ">", TRUE))
285             combine = GTK_CSS_COMBINE_CHILD;
286         }
287
288       if (!parse_simple_selector (parser, &name, ids, classes, &pseudo_classes, &state))
289         {
290           g_array_free (ids, TRUE);
291           g_array_free (classes, TRUE);
292           if (selector)
293             _gtk_css_selector_free (selector);
294           return NULL;
295         }
296
297       selector = gtk_css_selector_new (selector,
298                                        combine,
299                                        name,
300                                        (GQuark *) g_array_free (ids, ids->len == 0),
301                                        (GQuark *) g_array_free (classes, classes->len == 0),
302                                        pseudo_classes,
303                                        state);
304       g_free (name);
305     }
306   while (!_gtk_css_parser_is_eof (parser) &&
307          !_gtk_css_parser_begins_with (parser, ',') &&
308          !_gtk_css_parser_begins_with (parser, '{'));
309
310   return selector;
311 }
312
313 void
314 _gtk_css_selector_free (GtkCssSelector *selector)
315 {
316   g_return_if_fail (selector != NULL);
317
318   if (selector->previous)
319     _gtk_css_selector_free (selector->previous);
320
321   g_free (selector->ids);
322   g_free (selector->classes);
323
324   g_slice_free (GtkCssSelector, selector);
325 }
326
327 void
328 _gtk_css_selector_print (const GtkCssSelector *selector,
329                          GString *             str)
330 {
331   if (selector->previous)
332     {
333       _gtk_css_selector_print (selector->previous, str);
334       switch (selector->combine)
335         {
336           case GTK_CSS_COMBINE_DESCANDANT:
337             g_string_append (str, " ");
338             break;
339           case GTK_CSS_COMBINE_CHILD:
340             g_string_append (str, " > ");
341             break;
342           default:
343             g_assert_not_reached ();
344         }
345     }
346
347   if (selector->name)
348     g_string_append (str, selector->name);
349   else if (selector->ids == NULL && 
350            selector->classes == NULL && 
351            selector->pseudo_classes == 0 &&
352            selector->state == 0)
353     g_string_append (str, "*");
354
355   if (selector->ids)
356     {
357       GQuark *id;
358
359       for (id = selector->ids; *id != 0; id++)
360         {
361           g_string_append_c (str, '#');
362           g_string_append (str, g_quark_to_string (*id));
363         }
364     }
365
366   if (selector->classes)
367     {
368       GQuark *class;
369
370       for (class = selector->classes; *class != 0; class++)
371         {
372           g_string_append_c (str, '.');
373           g_string_append (str, g_quark_to_string (*class));
374         }
375     }
376
377   if (selector->pseudo_classes)
378     {
379       static const char * flag_names[] = {
380         "nth-child(even)",
381         "nth-child(odd)",
382         "first-child",
383         "last-child",
384         "only-child",
385         "sorted"
386       };
387       guint i;
388
389       for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
390         {
391           if (selector->pseudo_classes & (1 << i))
392             {
393               g_string_append_c (str, ':');
394               g_string_append (str, flag_names[i]);
395             }
396         }
397     }
398
399   if (selector->state)
400     {
401       static const char * state_names[] = {
402         "active",
403         "hover",
404         "selected",
405         "insensitive",
406         "inconsistent",
407         "focus",
408         "backdrop"
409       };
410       guint i;
411
412       for (i = 0; i < G_N_ELEMENTS (state_names); i++)
413         {
414           if (selector->state & (1 << i))
415             {
416               g_string_append_c (str, ':');
417               g_string_append (str, state_names[i]);
418             }
419         }
420     }
421 }
422
423 char *
424 _gtk_css_selector_to_string (const GtkCssSelector *selector)
425 {
426   GString *string;
427
428   g_return_val_if_fail (selector != NULL, NULL);
429
430   string = g_string_new (NULL);
431
432   _gtk_css_selector_print (selector, string);
433
434   return g_string_free (string, FALSE);
435 }
436
437 static GtkRegionFlags
438 compute_region_flags_for_index (const GtkWidgetPath *path,
439                                 guint                id)
440 {
441   const GtkWidgetPath *siblings;
442   guint sibling_id, n_siblings;
443   GtkRegionFlags flags;
444   
445   siblings = gtk_widget_path_iter_get_siblings (path, id);
446   if (siblings == NULL)
447     return 0;
448
449   sibling_id = gtk_widget_path_iter_get_sibling_index (path, id);
450   n_siblings = gtk_widget_path_length (siblings);
451
452   flags = (sibling_id % 2) ? GTK_REGION_EVEN : GTK_REGION_ODD;
453   if (sibling_id == 0)
454     flags |= GTK_REGION_FIRST;
455   if (sibling_id + 1 == n_siblings)
456     flags |= GTK_REGION_LAST;
457   if (n_siblings == 1)
458     flags |= GTK_REGION_ONLY;
459
460   return flags;
461 }
462
463 static gboolean
464 gtk_css_selector_matches_type (const GtkCssSelector      *selector,
465                                const GtkWidgetPath       *path,
466                                guint                      id)
467 {
468   if (selector->pseudo_classes)
469     {
470       GtkRegionFlags flags = compute_region_flags_for_index (path, id);
471
472       if ((selector->pseudo_classes & flags) != selector->pseudo_classes)
473         return FALSE;
474     }
475
476   if (selector->name == NULL)
477     return TRUE;
478
479   if (selector->type == G_TYPE_NONE)
480     return FALSE;
481
482   /* ugh, assigning to a const variable */
483   if (selector->type == G_TYPE_INVALID)
484     ((GtkCssSelector *) selector)->type = g_type_from_name (selector->name);
485
486   if (selector->type == G_TYPE_INVALID)
487     return FALSE;
488
489   return g_type_is_a (gtk_widget_path_iter_get_object_type (path, id), selector->type);
490 }
491
492 static gboolean
493 gtk_css_selector_matches_region (const GtkCssSelector      *selector,
494                                  const GtkWidgetPath       *path,
495                                  guint                      id,
496                                  const char *               region)
497 {
498   GtkRegionFlags flags;
499
500   if (selector->name == NULL)
501     return TRUE;
502   
503   if (selector->name != region)
504     return FALSE;
505
506   if (!gtk_widget_path_iter_has_region (path, id, region, &flags))
507     {
508       /* This function must be called with existing regions */
509       g_assert_not_reached ();
510     }
511
512   return (selector->pseudo_classes & flags) == selector->pseudo_classes;
513 }
514
515 static gboolean
516 gtk_css_selector_matches_rest (const GtkCssSelector      *selector,
517                                const GtkWidgetPath       *path,
518                                guint                      id)
519 {
520   if (selector->ids)
521     {
522       GQuark *name;
523       
524       for (name = selector->ids; *name; name++)
525         {
526           if (!gtk_widget_path_iter_has_qname (path, id, *name))
527             return FALSE;
528         }
529     }
530
531   if (selector->classes)
532     {
533       GQuark *class;
534       
535       for (class = selector->classes; *class; class++)
536         {
537           if (!gtk_widget_path_iter_has_qclass (path, id, *class))
538             return FALSE;
539         }
540     }
541
542   return TRUE;
543 }
544
545 static gboolean
546 gtk_css_selector_matches_previous (const GtkCssSelector      *selector,
547                                    const GtkWidgetPath       *path,
548                                    guint                      id,
549                                    GSList                    *regions);
550
551 static gboolean
552 gtk_css_selector_matches_from (const GtkCssSelector      *selector,
553                                const GtkWidgetPath       *path,
554                                guint                      id,
555                                GSList                    *regions)
556 {
557   GSList *l;
558
559   if (!gtk_css_selector_matches_rest (selector, path, id))
560     return FALSE;
561
562   for (l = regions; l; l = l->next)
563     {
564       const char *region = l->data;
565
566       if (gtk_css_selector_matches_region (selector, path, id, region))
567         {
568           GSList *remaining;
569           gboolean match;
570
571           remaining = g_slist_copy (regions);
572           remaining = g_slist_remove (remaining, region);
573           match = gtk_css_selector_matches_previous (selector,
574                                                      path,
575                                                      id,
576                                                      remaining);
577           g_slist_free (remaining);
578           if (match)
579             return TRUE;
580         }
581     }
582
583   if (gtk_css_selector_matches_type (selector, path, id))
584     {
585       GSList *regions;
586       gboolean match;
587       
588       if (id <= 0)
589         return selector->previous == NULL;
590
591       regions = gtk_widget_path_iter_list_regions (path, id - 1);
592       match = gtk_css_selector_matches_previous (selector,
593                                                  path,
594                                                  id - 1,
595                                                  regions);
596       g_slist_free (regions);
597       return match;
598     }
599
600   return FALSE;
601 }
602
603 static gboolean
604 gtk_css_selector_matches_previous (const GtkCssSelector      *selector,
605                                    const GtkWidgetPath       *path,
606                                    guint                      id,
607                                    GSList                    *regions)
608 {
609   if (!selector->previous)
610     return TRUE;
611
612   if (gtk_css_selector_matches_from (selector->previous,
613                                      path,
614                                      id,
615                                      regions))
616     return TRUE;
617
618   if (selector->combine == GTK_CSS_COMBINE_DESCANDANT)
619     {
620       /* with this magic we run the loop while id >= 0 */
621       while (id-- != 0)
622         {
623           GSList *list;
624           gboolean match;
625
626           list = gtk_widget_path_iter_list_regions (path, id);
627           match = gtk_css_selector_matches_from (selector->previous,
628                                                  path,
629                                                  id,
630                                                  list);
631           g_slist_free (list);
632           if (match)
633             return TRUE;
634         }
635     }
636
637   return FALSE;
638 }
639
640 /**
641  * _gtk_css_selector_matches:
642  * @selector: the selector
643  * @path: the path to check
644  * @state: The state to match
645  *
646  * Checks if the @selector matches the given @path. If @length is
647  * smaller than the number of elements in @path, it is assumed that
648  * only the first @length element of @path are valid and the rest
649  * does not exist. This is useful for doing parent matches for the
650  * 'inherit' keyword.
651  *
652  * Returns: %TRUE if the selector matches @path
653  **/
654 gboolean
655 _gtk_css_selector_matches (const GtkCssSelector      *selector,
656                            const GtkWidgetPath       *path,
657                            GtkStateFlags              state)
658 {
659   GSList *list;
660   gboolean match;
661   guint length;
662
663   g_return_val_if_fail (selector != NULL, FALSE);
664   g_return_val_if_fail (path != NULL, FALSE);
665
666   if ((selector->state & state) != selector->state)
667     return FALSE;
668
669   length = gtk_widget_path_length (path);
670   if (length == 0)
671     return FALSE;
672
673   list = gtk_widget_path_iter_list_regions (path, length - 1);
674   match = gtk_css_selector_matches_from (selector,
675                                          path,
676                                          length - 1,
677                                          list);
678   g_slist_free (list);
679   return match;
680 }
681
682 static guint
683 count_bits (guint v)
684 {
685   /* http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel */
686   v = v - ((v >> 1) & 0x55555555);
687   v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
688   return (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
689 }
690
691 /* Computes specificity according to CSS 2.1.
692  * The arguments must be initialized to 0 */
693 static void
694 _gtk_css_selector_get_specificity (const GtkCssSelector *selector,
695                                    guint                *ids,
696                                    guint                *classes,
697                                    guint                *elements)
698 {
699   GQuark *count;
700
701   if (selector->previous)
702     _gtk_css_selector_get_specificity (selector->previous, ids, classes, elements);
703
704   if (selector->ids)
705     for (count = selector->ids; *count; count++)
706       (*ids)++;
707
708   if (selector->classes)
709     for (count = selector->classes; *count; count++)
710       (*classes)++;
711   
712   *classes += count_bits (selector->state) + count_bits (selector->pseudo_classes);
713
714   if (selector->name)
715     (*elements)++;
716 }
717
718 int
719 _gtk_css_selector_compare (const GtkCssSelector *a,
720                            const GtkCssSelector *b)
721 {
722   guint a_ids = 0, a_classes = 0, a_elements = 0;
723   guint b_ids = 0, b_classes = 0, b_elements = 0;
724   int compare;
725
726   _gtk_css_selector_get_specificity (a, &a_ids, &a_classes, &a_elements);
727   _gtk_css_selector_get_specificity (b, &b_ids, &b_classes, &b_elements);
728
729   compare = a_ids - b_ids;
730   if (compare)
731     return compare;
732
733   compare = a_classes - b_classes;
734   if (compare)
735     return compare;
736
737   return a_elements - b_elements;
738 }
739
740 GtkStateFlags
741 _gtk_css_selector_get_state_flags (GtkCssSelector *selector)
742 {
743   g_return_val_if_fail (selector != NULL, 0);
744
745   return selector->state;
746 }
747