]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssprovider.c
b48b0b6e29fcf33251780973b783ca3b3557e3e4
[~andy/gtk] / gtk / gtkcssprovider.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2010 Carlos Garnacho <carlosg@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, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include "config.h"
21
22 #include <string.h>
23 #include <stdlib.h>
24 #include <gtk/gtk.h>
25 #include <gtkstyleprovider.h>
26
27 #include "gtkcssprovider.h"
28
29 #include "gtkalias.h"
30
31 typedef struct GtkCssProviderPrivate GtkCssProviderPrivate;
32 typedef struct SelectorElement SelectorElement;
33 typedef struct SelectorPath SelectorPath;
34 typedef struct SelectorStyleInfo SelectorStyleInfo;
35 typedef enum SelectorElementType SelectorElementType;
36 typedef enum CombinatorType CombinatorType;
37 typedef enum ParserScope ParserScope;
38 typedef enum ParserSymbol ParserSymbol;
39
40 enum SelectorElementType {
41   SELECTOR_TYPE_NAME,
42   SELECTOR_NAME,
43   SELECTOR_GTYPE,
44   SELECTOR_REGION,
45   SELECTOR_GLOB
46 };
47
48 enum CombinatorType {
49   COMBINATOR_DESCENDANT, /* No direct relation needed */
50   COMBINATOR_CHILD       /* Direct child */
51 };
52
53 struct SelectorElement
54 {
55   SelectorElementType elem_type;
56   CombinatorType combinator;
57
58   union
59   {
60     GQuark name;
61     GType type;
62
63     struct
64     {
65       GQuark name;
66       GtkChildClassFlags flags;
67     } region;
68   };
69 };
70
71 struct SelectorPath
72 {
73   GSList *elements;
74   GtkStateType state;
75   guint ref_count;
76 };
77
78 struct SelectorStyleInfo
79 {
80   SelectorPath *path;
81   GHashTable *style;
82 };
83
84 struct GtkCssProviderPrivate
85 {
86   GScanner *scanner;
87   gchar *filename;
88
89   GHashTable *symbolic_colors;
90
91   GPtrArray *selectors_info;
92
93   /* Current parser state */
94   GSList *state;
95   GSList *cur_selectors;
96   GHashTable *cur_properties;
97 };
98
99 enum ParserScope {
100   SCOPE_SELECTOR,
101   SCOPE_PSEUDO_CLASS,
102   SCOPE_NTH_CHILD,
103   SCOPE_DECLARATION,
104   SCOPE_VALUE
105 };
106
107 /* Extend GtkStateType, since these
108  * values are also used as symbols
109  */
110 enum ParserSymbol {
111   /* Scope: pseudo-class */
112   SYMBOL_NTH_CHILD = GTK_STATE_LAST,
113   SYMBOL_FIRST_CHILD,
114   SYMBOL_LAST_CHILD,
115
116   /* Scope: nth-child */
117   SYMBOL_NTH_CHILD_EVEN,
118   SYMBOL_NTH_CHILD_ODD,
119   SYMBOL_NTH_CHILD_FIRST,
120   SYMBOL_NTH_CHILD_LAST
121 };
122
123 #define GTK_CSS_PROVIDER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTK_TYPE_CSS_PROVIDER, GtkCssProviderPrivate))
124
125 static void gtk_css_provider_finalize (GObject *object);
126 static void gtk_css_style_provider_iface_init (GtkStyleProviderIface *iface);
127
128 static void css_provider_apply_scope (GtkCssProvider *css_provider,
129                                       ParserScope     scope);
130 static gboolean css_provider_parse_value (const gchar *value_str,
131                                           GValue      *value);
132
133 G_DEFINE_TYPE_EXTENDED (GtkCssProvider, gtk_css_provider, G_TYPE_OBJECT, 0,
134                         G_IMPLEMENT_INTERFACE (GTK_TYPE_STYLE_PROVIDER,
135                                                gtk_css_style_provider_iface_init));
136
137 static void
138 gtk_css_provider_class_init (GtkCssProviderClass *klass)
139 {
140   GObjectClass *object_class = G_OBJECT_CLASS (klass);
141
142   object_class->finalize = gtk_css_provider_finalize;
143
144   g_type_class_add_private (object_class, sizeof (GtkCssProviderPrivate));
145 }
146
147 static SelectorPath *
148 selector_path_new (void)
149 {
150   SelectorPath *path;
151
152   path = g_slice_new0 (SelectorPath);
153   path->state = GTK_STATE_NORMAL;
154   path->ref_count = 1;
155
156   return path;
157 }
158
159 static SelectorPath *
160 selector_path_ref (SelectorPath *path)
161 {
162   path->ref_count++;
163   return path;
164 }
165
166 static void
167 selector_path_unref (SelectorPath *path)
168 {
169   path->ref_count--;
170
171   if (path->ref_count > 0)
172     return;
173
174   while (path->elements)
175     {
176       g_slice_free (SelectorElement, path->elements->data);
177       path->elements = g_slist_delete_link (path->elements, path->elements);
178     }
179
180   g_slice_free (SelectorPath, path);
181 }
182
183 static void
184 selector_path_prepend_type (SelectorPath *path,
185                             const gchar  *type_name)
186 {
187   SelectorElement *elem;
188   GType type;
189
190   elem = g_slice_new (SelectorElement);
191   elem->combinator = COMBINATOR_DESCENDANT;
192   type = g_type_from_name (type_name);
193
194   if (type == G_TYPE_INVALID)
195     {
196       elem->elem_type = SELECTOR_TYPE_NAME;
197       elem->name = g_quark_from_string (type_name);
198     }
199   else
200     {
201       elem->elem_type = SELECTOR_GTYPE;
202       elem->type = type;
203     }
204
205   path->elements = g_slist_prepend (path->elements, elem);
206 }
207
208 static void
209 selector_path_prepend_glob (SelectorPath *path)
210 {
211   SelectorElement *elem;
212
213   elem = g_slice_new (SelectorElement);
214   elem->elem_type = SELECTOR_GLOB;
215   elem->combinator = COMBINATOR_DESCENDANT;
216
217   path->elements = g_slist_prepend (path->elements, elem);
218 }
219
220 static void
221 selector_path_prepend_region (SelectorPath       *path,
222                               const gchar        *name,
223                               GtkChildClassFlags  flags)
224 {
225   SelectorElement *elem;
226
227   elem = g_slice_new (SelectorElement);
228   elem->combinator = COMBINATOR_DESCENDANT;
229   elem->elem_type = SELECTOR_REGION;
230
231   elem->region.name = g_quark_from_string (name);
232   elem->region.flags = flags;
233
234   path->elements = g_slist_prepend (path->elements, elem);
235 }
236
237 static void
238 selector_path_prepend_combinator (SelectorPath   *path,
239                                   CombinatorType  combinator)
240 {
241   SelectorElement *elem;
242
243   g_assert (path->elements != NULL);
244
245   /* It is actually stored in the last element */
246   elem = path->elements->data;
247   elem->combinator = combinator;
248 }
249
250 static gint
251 selector_path_depth (SelectorPath *path)
252 {
253   return g_slist_length (path->elements);
254 }
255
256 static SelectorStyleInfo *
257 selector_style_info_new (SelectorPath *path)
258 {
259   SelectorStyleInfo *info;
260
261   info = g_slice_new0 (SelectorStyleInfo);
262   info->path = selector_path_ref (path);
263
264   return info;
265 }
266
267 static void
268 selector_style_info_free (SelectorStyleInfo *info)
269 {
270   if (info->style)
271     g_hash_table_unref (info->style);
272
273   if (info->path)
274     selector_path_unref (info->path);
275 }
276
277 static void
278 selector_style_info_set_style (SelectorStyleInfo *info,
279                                GHashTable        *style)
280 {
281   if (info->style)
282     g_hash_table_unref (info->style);
283
284   if (style)
285     info->style = g_hash_table_ref (style);
286   else
287     info->style = NULL;
288 }
289
290 static void
291 gtk_css_provider_init (GtkCssProvider *css_provider)
292 {
293   GtkCssProviderPrivate *priv;
294   GScanner *scanner;
295
296   priv = GTK_CSS_PROVIDER_GET_PRIVATE (css_provider);
297   priv->selectors_info = g_ptr_array_new_with_free_func ((GDestroyNotify) selector_style_info_free);
298
299   scanner = g_scanner_new (NULL);
300
301   g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "active", GUINT_TO_POINTER (GTK_STATE_ACTIVE));
302   g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "prelight", GUINT_TO_POINTER (GTK_STATE_PRELIGHT));
303   g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "hover", GUINT_TO_POINTER (GTK_STATE_PRELIGHT));
304   g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "selected", GUINT_TO_POINTER (GTK_STATE_SELECTED));
305   g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "insensitive", GUINT_TO_POINTER (GTK_STATE_INSENSITIVE));
306
307   g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "nth-child", GUINT_TO_POINTER (SYMBOL_NTH_CHILD));
308   g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "first-child", GUINT_TO_POINTER (SYMBOL_FIRST_CHILD));
309   g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "last-child", GUINT_TO_POINTER (SYMBOL_LAST_CHILD));
310
311   g_scanner_scope_add_symbol (scanner, SCOPE_NTH_CHILD, "even", GUINT_TO_POINTER (SYMBOL_NTH_CHILD_EVEN));
312   g_scanner_scope_add_symbol (scanner, SCOPE_NTH_CHILD, "odd", GUINT_TO_POINTER (SYMBOL_NTH_CHILD_ODD));
313   g_scanner_scope_add_symbol (scanner, SCOPE_NTH_CHILD, "first", GUINT_TO_POINTER (SYMBOL_NTH_CHILD_FIRST));
314   g_scanner_scope_add_symbol (scanner, SCOPE_NTH_CHILD, "last", GUINT_TO_POINTER (SYMBOL_NTH_CHILD_LAST));
315
316   priv->scanner = scanner;
317   css_provider_apply_scope (css_provider, SCOPE_SELECTOR);
318
319   priv->symbolic_colors = g_hash_table_new_full (g_str_hash, g_str_equal,
320                                                  (GDestroyNotify) g_free,
321                                                  (GDestroyNotify) gtk_symbolic_color_unref);
322 }
323
324 typedef struct ComparePathData ComparePathData;
325
326 struct ComparePathData
327 {
328   guint64 score;
329   SelectorPath *path;
330   GSList *iter;
331 };
332
333 static gboolean
334 compare_selector_element (GtkWidgetPath   *path,
335                           guint            index,
336                           SelectorElement *elem,
337                           guint8          *score)
338 {
339   *score = 0;
340
341   if (elem->elem_type == SELECTOR_TYPE_NAME)
342     {
343       const gchar *type_name;
344       GType resolved_type;
345
346       /* Resolve the type name */
347       type_name = g_quark_to_string (elem->name);
348       resolved_type = g_type_from_name (type_name);
349
350       if (resolved_type == G_TYPE_INVALID)
351         {
352           /* Type couldn't be resolved, so the selector
353            * clearly doesn't affect the given widget path
354            */
355           return FALSE;
356         }
357
358       elem->elem_type = SELECTOR_GTYPE;
359       elem->type = resolved_type;
360     }
361
362   if (elem->elem_type == SELECTOR_GTYPE)
363     {
364       GType type;
365
366       type = gtk_widget_path_get_element_type (path, index);
367
368       if (!g_type_is_a (type, elem->type))
369         return FALSE;
370
371       if (type == elem->type)
372         *score |= 0xF;
373       else
374         {
375           GType parent = type;
376
377           *score = 0xE;
378
379           while ((parent = g_type_parent (parent)) != G_TYPE_INVALID)
380             {
381               if (parent == elem->type)
382                 break;
383
384               *score -= 1;
385
386               if (*score == 1)
387                 {
388                   g_warning ("Hierarchy is higher than expected.");
389                   break;
390                 }
391             }
392         }
393
394       return TRUE;
395     }
396   else if (elem->elem_type == SELECTOR_REGION)
397     {
398       const gchar *region_name;
399       GtkChildClassFlags flags;
400
401       /* FIXME: Need GQuark API here */
402       region_name = g_quark_to_string (elem->region.name);
403
404       if (!gtk_widget_path_iter_has_region (path, index, region_name, &flags))
405         return FALSE;
406
407       if (elem->region.flags != 0 &&
408           (flags & elem->region.flags) == 0)
409         return FALSE;
410
411       *score = 0xF;
412       return TRUE;
413     }
414   else if (elem->elem_type == SELECTOR_GLOB)
415     {
416       /* Treat as lowest matching type */
417       *score = 1;
418       return TRUE;
419     }
420
421   return FALSE;
422 }
423
424 static guint64
425 compare_selector (GtkWidgetPath *path,
426                   SelectorPath  *selector)
427 {
428   GSList *elements = selector->elements;
429   gboolean match = TRUE;
430   guint64 score = 0;
431   guint i = 0;
432
433   while (elements && match &&
434          i < gtk_widget_path_length (path))
435     {
436       SelectorElement *elem;
437       guint8 elem_score;
438
439       elem = elements->data;
440
441       match = compare_selector_element (path, i, elem, &elem_score);
442       i++;
443
444       if (!match && elem->combinator == COMBINATOR_DESCENDANT)
445         {
446           /* With descendant combinators there may
447            * be intermediate chidren in the hierarchy
448            */
449           match = TRUE;
450         }
451       else
452         elements = elements->next;
453
454       if (match)
455         {
456           /* Only 4 bits are actually used */
457           score <<= 4;
458           score |= elem_score;
459         }
460     }
461
462   /* If there are pending selector
463    * elements to compare, it's not
464    * a match.
465    */
466   if (elements)
467     match = FALSE;
468
469   if (!match)
470     score = 0;
471
472   return score;
473 }
474
475 typedef struct StylePriorityInfo StylePriorityInfo;
476
477 struct StylePriorityInfo
478 {
479   guint64 score;
480   GHashTable *style;
481   GtkStateType state;
482 };
483
484 static GArray *
485 css_provider_get_selectors (GtkCssProvider *css_provider,
486                             GtkWidgetPath  *path)
487 {
488   GtkCssProviderPrivate *priv;
489   GArray *priority_info;
490   guint i, j;
491
492   priv = GTK_CSS_PROVIDER_GET_PRIVATE (css_provider);
493   priority_info = g_array_new (FALSE, FALSE, sizeof (StylePriorityInfo));
494
495   for (i = 0; i < priv->selectors_info->len; i++)
496     {
497       SelectorStyleInfo *info;
498       StylePriorityInfo new;
499       gboolean added = FALSE;
500       guint64 score;
501
502       info = g_ptr_array_index (priv->selectors_info, i);
503       score = compare_selector (path, info->path);
504
505       if (score <= 0)
506         continue;
507
508       new.score = score;
509       new.style = info->style;
510       new.state = info->path->state;
511
512       for (j = 0; j < priority_info->len; j++)
513         {
514           StylePriorityInfo *cur;
515
516           cur = &g_array_index (priority_info, StylePriorityInfo, j);
517
518           if (cur->score > new.score)
519             {
520               g_array_insert_val (priority_info, j, new);
521               added = TRUE;
522               break;
523             }
524         }
525
526       if (!added)
527         g_array_append_val (priority_info, new);
528     }
529
530   return priority_info;
531 }
532
533 static void
534 css_provider_dump_symbolic_colors (GtkCssProvider *css_provider,
535                                    GtkStyleSet    *set)
536 {
537   GtkCssProviderPrivate *priv;
538   GHashTableIter iter;
539   gpointer key, value;
540
541   priv = GTK_CSS_PROVIDER_GET_PRIVATE (css_provider);
542   g_hash_table_iter_init (&iter, priv->symbolic_colors);
543
544   while (g_hash_table_iter_next (&iter, &key, &value))
545     {
546       const gchar *name;
547       GtkSymbolicColor *color;
548
549       name = key;
550       color = value;
551
552       gtk_style_set_map_color (set, name, color);
553     }
554 }
555
556 static GtkStyleSet *
557 gtk_css_provider_get_style (GtkStyleProvider *provider,
558                             GtkWidgetPath    *path)
559 {
560   GtkCssProviderPrivate *priv;
561   GtkStyleSet *set;
562   GArray *priority_info;
563   guint i;
564
565   priv = GTK_CSS_PROVIDER_GET_PRIVATE (provider);
566   set = gtk_style_set_new ();
567
568   css_provider_dump_symbolic_colors ((GtkCssProvider *) provider, set);
569   priority_info = css_provider_get_selectors (GTK_CSS_PROVIDER (provider), path);
570
571   for (i = 0; i < priority_info->len; i++)
572     {
573       StylePriorityInfo *info;
574       GHashTableIter iter;
575       gpointer key, value;
576
577       info = &g_array_index (priority_info, StylePriorityInfo, i);
578       g_hash_table_iter_init (&iter, info->style);
579
580       while (g_hash_table_iter_next (&iter, &key, &value))
581         {
582           gchar *prop = key;
583
584           if (prop[0] == '-')
585             continue;
586
587           if (info->state == GTK_STATE_NORMAL)
588             gtk_style_set_set_default (set, key, value);
589           else
590             gtk_style_set_set_property (set, key, info->state, value);
591         }
592     }
593
594   g_array_free (priority_info, TRUE);
595
596   return set;
597 }
598
599 static gboolean
600 gtk_css_provider_get_style_property (GtkStyleProvider *provider,
601                                      GtkWidgetPath    *path,
602                                      const gchar      *property_name,
603                                      GValue           *value)
604 {
605   GArray *priority_info;
606   gboolean found = FALSE;
607   gchar *prop_name;
608   GType path_type;
609   gint i;
610
611   path_type = gtk_widget_path_get_widget_type (path);
612
613   prop_name = g_strdup_printf ("-%s-%s",
614                                g_type_name (path_type),
615                                property_name);
616
617   priority_info = css_provider_get_selectors (GTK_CSS_PROVIDER (provider), path);
618
619   for (i = priority_info->len - 1; i >= 0; i--)
620     {
621       StylePriorityInfo *info;
622       GValue *val;
623
624       info = &g_array_index (priority_info, StylePriorityInfo, i);
625       val = g_hash_table_lookup (info->style, prop_name);
626
627       if (val)
628         {
629           const gchar *val_str;
630
631           val_str = g_value_get_string (val);
632           found = TRUE;
633
634           css_provider_parse_value (val_str, value);
635           break;
636         }
637     }
638
639   g_array_free (priority_info, TRUE);
640   g_free (prop_name);
641
642   return found;
643 }
644
645 static void
646 gtk_css_style_provider_iface_init (GtkStyleProviderIface *iface)
647 {
648   iface->get_style = gtk_css_provider_get_style;
649   iface->get_style_property = gtk_css_provider_get_style_property;
650 }
651
652 static void
653 gtk_css_provider_finalize (GObject *object)
654 {
655   GtkCssProviderPrivate *priv;
656
657   priv = GTK_CSS_PROVIDER_GET_PRIVATE (object);
658
659   g_scanner_destroy (priv->scanner);
660   g_free (priv->filename);
661
662   g_ptr_array_free (priv->selectors_info, TRUE);
663
664   g_slist_foreach (priv->cur_selectors, (GFunc) selector_path_unref, NULL);
665   g_slist_free (priv->cur_selectors);
666
667   g_hash_table_unref (priv->cur_properties);
668   g_hash_table_destroy (priv->symbolic_colors);
669
670   G_OBJECT_CLASS (gtk_css_provider_parent_class)->finalize (object);
671 }
672
673 GtkCssProvider *
674 gtk_css_provider_new (void)
675 {
676   return g_object_new (GTK_TYPE_CSS_PROVIDER, NULL);
677 }
678
679 static void
680 property_value_free (GValue *value)
681 {
682   if (G_IS_VALUE (value))
683     g_value_unset (value);
684
685   g_slice_free (GValue, value);
686 }
687
688 static void
689 css_provider_apply_scope (GtkCssProvider *css_provider,
690                           ParserScope     scope)
691 {
692   GtkCssProviderPrivate *priv;
693
694   priv = GTK_CSS_PROVIDER_GET_PRIVATE (css_provider);
695
696   g_scanner_set_scope (priv->scanner, scope);
697
698   if (scope == SCOPE_VALUE)
699     {
700       priv->scanner->config->cset_identifier_first = G_CSET_a_2_z "@#-_0123456789" G_CSET_A_2_Z;
701       priv->scanner->config->cset_identifier_nth = G_CSET_a_2_z "@#-_ 0123456789(),." G_CSET_A_2_Z;
702       priv->scanner->config->scan_identifier_1char = TRUE;
703     }
704   else if (scope == SCOPE_SELECTOR)
705     {
706       priv->scanner->config->cset_identifier_first = G_CSET_a_2_z G_CSET_A_2_Z "*@";
707       priv->scanner->config->cset_identifier_nth = G_CSET_a_2_z "-" G_CSET_A_2_Z;
708       priv->scanner->config->scan_identifier_1char = TRUE;
709     }
710   else if (scope == SCOPE_PSEUDO_CLASS ||
711            scope == SCOPE_NTH_CHILD ||
712            scope == SCOPE_DECLARATION)
713     {
714       priv->scanner->config->cset_identifier_first = G_CSET_a_2_z "-" G_CSET_A_2_Z;
715       priv->scanner->config->cset_identifier_nth = G_CSET_a_2_z "-" G_CSET_A_2_Z;
716       priv->scanner->config->scan_identifier_1char = FALSE;
717     }
718   else
719     g_assert_not_reached ();
720
721   priv->scanner->config->scan_float = FALSE;
722 }
723
724 static void
725 css_provider_push_scope (GtkCssProvider *css_provider,
726                          ParserScope     scope)
727 {
728   GtkCssProviderPrivate *priv;
729
730   priv = GTK_CSS_PROVIDER_GET_PRIVATE (css_provider);
731   priv->state = g_slist_prepend (priv->state, GUINT_TO_POINTER (scope));
732
733   css_provider_apply_scope (css_provider, scope);
734 }
735
736 static ParserScope
737 css_provider_pop_scope (GtkCssProvider *css_provider)
738 {
739   GtkCssProviderPrivate *priv;
740   ParserScope scope = SCOPE_SELECTOR;
741
742   priv = GTK_CSS_PROVIDER_GET_PRIVATE (css_provider);
743
744   if (!priv->state)
745     {
746       g_warning ("Push/pop calls to parser scope aren't paired");
747       css_provider_apply_scope (css_provider, SCOPE_SELECTOR);
748       return SCOPE_SELECTOR;
749     }
750
751   priv->state = g_slist_delete_link (priv->state, priv->state);
752
753   /* Fetch new scope */
754   if (priv->state)
755     scope = GPOINTER_TO_INT (priv->state->data);
756
757   css_provider_apply_scope (css_provider, scope);
758
759   return scope;
760 }
761
762 static void
763 css_provider_reset_parser (GtkCssProvider *css_provider)
764 {
765   GtkCssProviderPrivate *priv;
766
767   priv = GTK_CSS_PROVIDER_GET_PRIVATE (css_provider);
768
769   g_slist_free (priv->state);
770   priv->state = NULL;
771
772   css_provider_apply_scope (css_provider, SCOPE_SELECTOR);
773
774   g_slist_foreach (priv->cur_selectors, (GFunc) selector_path_unref, NULL);
775   g_slist_free (priv->cur_selectors);
776   priv->cur_selectors = NULL;
777
778   if (priv->cur_properties)
779     g_hash_table_unref (priv->cur_properties);
780
781   priv->cur_properties = g_hash_table_new_full (g_str_hash,
782                                                 g_str_equal,
783                                                 (GDestroyNotify) g_free,
784                                                 (GDestroyNotify) property_value_free);
785 }
786
787 static void
788 css_provider_commit (GtkCssProvider *css_provider)
789 {
790   GtkCssProviderPrivate *priv;
791   GSList *l;
792
793   priv = GTK_CSS_PROVIDER_GET_PRIVATE (css_provider);
794   l = priv->cur_selectors;
795
796   while (l)
797     {
798       SelectorPath *path = l->data;
799       SelectorStyleInfo *info;
800
801       info = selector_style_info_new (path);
802       selector_style_info_set_style (info, priv->cur_properties);
803
804       g_ptr_array_add (priv->selectors_info, info);
805       l = l->next;
806     }
807 }
808
809 static GTokenType
810 parse_pseudo_class (GtkCssProvider     *css_provider,
811                     GScanner           *scanner,
812                     SelectorPath       *selector,
813                     GtkChildClassFlags *flags)
814 {
815   ParserSymbol symbol;
816
817   css_provider_push_scope (css_provider, SCOPE_PSEUDO_CLASS);
818   g_scanner_get_next_token (scanner);
819
820   if (scanner->token != G_TOKEN_SYMBOL)
821     return G_TOKEN_SYMBOL;
822
823   symbol = GPOINTER_TO_INT (scanner->value.v_symbol);
824
825   if (symbol == SYMBOL_NTH_CHILD)
826     {
827       g_scanner_get_next_token (scanner);
828
829       if (scanner->token != G_TOKEN_LEFT_PAREN)
830         return G_TOKEN_LEFT_PAREN;
831
832       css_provider_push_scope (css_provider, SCOPE_NTH_CHILD);
833       g_scanner_get_next_token (scanner);
834
835       if (scanner->token != G_TOKEN_SYMBOL)
836         return G_TOKEN_SYMBOL;
837
838       symbol = GPOINTER_TO_INT (scanner->value.v_symbol);
839
840       switch (symbol)
841         {
842         case SYMBOL_NTH_CHILD_EVEN:
843           *flags = GTK_CHILD_CLASS_EVEN;
844           break;
845         case SYMBOL_NTH_CHILD_ODD:
846           *flags = GTK_CHILD_CLASS_ODD;
847           break;
848         case SYMBOL_NTH_CHILD_FIRST:
849           *flags = GTK_CHILD_CLASS_FIRST;
850           break;
851         case SYMBOL_NTH_CHILD_LAST:
852           *flags = GTK_CHILD_CLASS_LAST;
853           break;
854         default:
855           break;
856         }
857
858       g_scanner_get_next_token (scanner);
859
860       if (scanner->token != G_TOKEN_RIGHT_PAREN)
861         return G_TOKEN_RIGHT_PAREN;
862
863       css_provider_pop_scope (css_provider);
864     }
865   else if (symbol == SYMBOL_FIRST_CHILD)
866     *flags = GTK_CHILD_CLASS_FIRST;
867   else if (symbol == SYMBOL_LAST_CHILD)
868     *flags = GTK_CHILD_CLASS_LAST;
869   else
870     {
871       GtkStateType state;
872
873       state = GPOINTER_TO_INT (scanner->value.v_symbol);
874       selector->state = state;
875     }
876
877   css_provider_pop_scope (css_provider);
878   return G_TOKEN_NONE;
879 }
880
881 static GTokenType
882 parse_selector (GtkCssProvider  *css_provider,
883                 GScanner        *scanner,
884                 SelectorPath   **selector_out)
885 {
886   SelectorPath *path;
887
888   path = selector_path_new ();
889   *selector_out = path;
890
891   if (scanner->token != ':' &&
892       scanner->token != G_TOKEN_IDENTIFIER)
893     return G_TOKEN_IDENTIFIER;
894
895   while (scanner->token == G_TOKEN_IDENTIFIER)
896     {
897       if (g_ascii_isupper (scanner->value.v_identifier[0]))
898         selector_path_prepend_type (path, scanner->value.v_identifier);
899       else if (g_ascii_islower (scanner->value.v_identifier[0]))
900         {
901           GtkChildClassFlags flags = 0;
902           gchar *region_name;
903
904           region_name = g_strdup (scanner->value.v_identifier);
905
906           /* Parse nth-child type pseudo-class, and
907            * possibly a state pseudo-class after it.
908            */
909           while (path->state == GTK_STATE_NORMAL &&
910                  g_scanner_peek_next_token (scanner) == ':')
911             {
912               GTokenType token;
913
914               g_scanner_get_next_token (scanner);
915
916               if ((token = parse_pseudo_class (css_provider, scanner, path, &flags)) != G_TOKEN_NONE)
917                 return token;
918             }
919
920           selector_path_prepend_region (path, region_name, flags);
921           g_free (region_name);
922         }
923       else if (scanner->value.v_identifier[0] == '*')
924         selector_path_prepend_glob (path);
925       else
926         return G_TOKEN_IDENTIFIER;
927
928       g_scanner_get_next_token (scanner);
929
930       /* State is the last element in the selector */
931       if (path->state != GTK_STATE_NORMAL)
932         break;
933
934       if (scanner->token == '>')
935         {
936           selector_path_prepend_combinator (path, COMBINATOR_CHILD);
937           g_scanner_get_next_token (scanner);
938         }
939     }
940
941   if (scanner->token == ':' &&
942       path->state == GTK_STATE_NORMAL)
943     {
944       GtkChildClassFlags flags = 0;
945       GTokenType token;
946
947       /* Add glob selector if path is empty */
948       if (selector_path_depth (path) == 0)
949         selector_path_prepend_glob (path);
950
951       if ((token = parse_pseudo_class (css_provider, scanner, path, &flags)) != G_TOKEN_NONE)
952         return token;
953
954       if (flags != 0)
955         {
956           /* This means either a standalone :nth-child
957            * selector, or on a invalid element type.
958            */
959           return G_TOKEN_SYMBOL;
960         }
961
962       g_scanner_get_next_token (scanner);
963     }
964   else if (scanner->token == G_TOKEN_SYMBOL)
965     {
966       /* A new pseudo-class was starting, but the state was already
967        * parsed, so nothing is supposed to go after that.
968        */
969       return G_TOKEN_LEFT_CURLY;
970     }
971
972   return G_TOKEN_NONE;
973 }
974
975 #define SKIP_SPACES(s) while (s[0] == ' ') s++;
976
977 static GtkSymbolicColor *
978 symbolic_color_parse_str (const gchar  *string,
979                           gchar       **end_ptr)
980 {
981   GtkSymbolicColor *symbolic_color = NULL;
982   gchar *str;
983
984   str = (gchar *) string;
985
986   if (str[0] == '#')
987     {
988       GdkColor color;
989       gchar *color_str;
990       const gchar *end;
991
992       end = str + 1;
993
994       while (g_ascii_isxdigit (*end))
995         end++;
996
997       color_str = g_strndup (str, end - str);
998       *end_ptr = (gchar *) end;
999
1000       if (!gdk_color_parse (color_str, &color))
1001         {
1002           g_free (color_str);
1003           return NULL;
1004         }
1005
1006       symbolic_color = gtk_symbolic_color_new_literal (&color);
1007       g_free (color_str);
1008     }
1009   else if (str[0] == '@')
1010     {
1011       const gchar *end;
1012       gchar *name;
1013
1014       str++;
1015       end = str;
1016
1017       while (*end == '-' || *end == '_' ||
1018              g_ascii_isalpha (*end))
1019         end++;
1020
1021       name = g_strndup (str, end - str);
1022       symbolic_color = gtk_symbolic_color_new_name (name);
1023       g_free (name);
1024
1025       *end_ptr = (gchar *) end;
1026     }
1027   else if (g_str_has_prefix (str, "shade"))
1028     {
1029       GtkSymbolicColor *param_color;
1030       gdouble factor;
1031
1032       str += strlen ("shade");
1033
1034       SKIP_SPACES (str);
1035
1036       if (*str != '(')
1037         {
1038           *end_ptr = (gchar *) str;
1039           return NULL;
1040         }
1041
1042       str++;
1043       SKIP_SPACES (str);
1044       param_color = symbolic_color_parse_str (str, end_ptr);
1045
1046       if (!param_color)
1047         return NULL;
1048
1049       str = *end_ptr;
1050       SKIP_SPACES (str);
1051
1052       if (str[0] != ',')
1053         {
1054           gtk_symbolic_color_unref (param_color);
1055           *end_ptr = (gchar *) str;
1056           return NULL;
1057         }
1058
1059       str++;
1060       SKIP_SPACES (str);
1061       factor = g_ascii_strtod (str, end_ptr);
1062
1063       str = *end_ptr;
1064       SKIP_SPACES (str);
1065       *end_ptr = (gchar *) str;
1066
1067       if (str[0] != ')')
1068         {
1069           gtk_symbolic_color_unref (param_color);
1070           return NULL;
1071         }
1072
1073       symbolic_color = gtk_symbolic_color_new_shade (param_color, factor);
1074       (*end_ptr)++;
1075     }
1076   else if (g_str_has_prefix (str, "mix"))
1077     {
1078       GtkSymbolicColor *color1, *color2;
1079       gdouble factor;
1080
1081       str += strlen ("mix");
1082       SKIP_SPACES (str);
1083
1084       if (*str != '(')
1085         {
1086           *end_ptr = (gchar *) str;
1087           return NULL;
1088         }
1089
1090       str++;
1091       SKIP_SPACES (str);
1092       color1 = symbolic_color_parse_str (str, end_ptr);
1093
1094       if (!color1)
1095         return NULL;
1096
1097       str = *end_ptr;
1098       SKIP_SPACES (str);
1099
1100       if (str[0] != ',')
1101         {
1102           gtk_symbolic_color_unref (color1);
1103           *end_ptr = (gchar *) str;
1104           return NULL;
1105         }
1106
1107       str++;
1108       SKIP_SPACES (str);
1109       color2 = symbolic_color_parse_str (str, end_ptr);
1110
1111       if (!color2 || *end_ptr[0] != ',')
1112         {
1113           gtk_symbolic_color_unref (color1);
1114           return NULL;
1115         }
1116
1117       str = *end_ptr;
1118       SKIP_SPACES (str);
1119
1120       if (str[0] != ',')
1121         {
1122           gtk_symbolic_color_unref (color1);
1123           gtk_symbolic_color_unref (color2);
1124           *end_ptr = (gchar *) str;
1125           return NULL;
1126         }
1127
1128       str++;
1129       SKIP_SPACES (str);
1130       factor = g_ascii_strtod (str, end_ptr);
1131
1132       str = *end_ptr;
1133       SKIP_SPACES (str);
1134       *end_ptr = (gchar *) str;
1135
1136       if (str[0] != ')')
1137         {
1138           gtk_symbolic_color_unref (color1);
1139           gtk_symbolic_color_unref (color2);
1140           return NULL;
1141         }
1142
1143       symbolic_color = gtk_symbolic_color_new_mix (color1, color2, factor);
1144       (*end_ptr)++;
1145     }
1146
1147   return symbolic_color;
1148 }
1149
1150 #undef SKIP_SPACES
1151
1152 static GtkSymbolicColor *
1153 symbolic_color_parse (const gchar *str)
1154 {
1155   GtkSymbolicColor *color;
1156   gchar *end;
1157
1158   color = symbolic_color_parse_str (str, &end);
1159
1160   if (*end != '\0')
1161     {
1162       g_warning ("Error parsing symbolic color \"%s\", stopped at char %ld : '%c'",
1163                  str, end - str, *end);
1164
1165       if (color)
1166         {
1167           gtk_symbolic_color_unref (color);
1168           color = NULL;
1169         }
1170     }
1171
1172   return color;
1173 }
1174
1175 static gboolean
1176 css_provider_parse_value (const gchar *value_str,
1177                           GValue      *value)
1178 {
1179   GType type;
1180   gboolean parsed = TRUE;
1181
1182   type = G_VALUE_TYPE (value);
1183
1184   if (type == GDK_TYPE_COLOR)
1185     {
1186       GdkColor color;
1187
1188       if (gdk_color_parse (value_str, &color) == TRUE)
1189         g_value_set_boxed (value, &color);
1190       else
1191         {
1192           GtkSymbolicColor *symbolic_color;
1193
1194           symbolic_color = symbolic_color_parse (value_str);
1195
1196           if (!symbolic_color)
1197             return FALSE;
1198
1199           g_value_unset (value);
1200           g_value_init (value, GTK_TYPE_SYMBOLIC_COLOR);
1201           g_value_take_boxed (value, symbolic_color);
1202         }
1203     }
1204   else if (type == PANGO_TYPE_FONT_DESCRIPTION)
1205     {
1206       PangoFontDescription *font_desc;
1207
1208       font_desc = pango_font_description_from_string (value_str);
1209       g_value_take_boxed (value, font_desc);
1210     }
1211   else if (type == G_TYPE_BOOLEAN)
1212     {
1213       if (value_str[0] == '1' ||
1214           g_ascii_strcasecmp (value_str, "true") == 0)
1215         g_value_set_boolean (value, TRUE);
1216       else
1217         g_value_set_boolean (value, FALSE);
1218     }
1219   else if (type == G_TYPE_INT)
1220     g_value_set_int (value, atoi (value_str));
1221   else if (type == G_TYPE_DOUBLE)
1222     g_value_set_double (value, g_ascii_strtod (value_str, NULL));
1223   else if (type == GTK_TYPE_THEMING_ENGINE)
1224     {
1225       GtkThemingEngine *engine;
1226
1227       engine = gtk_theming_engine_load (value_str);
1228       g_value_set_object (value, engine);
1229     }
1230   else
1231     {
1232       g_warning ("Cannot parse string '%s' for type %s", value_str, g_type_name (type));
1233       parsed = FALSE;
1234     }
1235
1236   return parsed;
1237 }
1238
1239 static GTokenType
1240 parse_rule (GtkCssProvider *css_provider,
1241             GScanner       *scanner)
1242 {
1243   GtkCssProviderPrivate *priv;
1244   GTokenType expected_token;
1245   SelectorPath *selector;
1246
1247   priv = GTK_CSS_PROVIDER_GET_PRIVATE (css_provider);
1248
1249   css_provider_push_scope (css_provider, SCOPE_SELECTOR);
1250
1251   if (scanner->token == G_TOKEN_IDENTIFIER &&
1252       scanner->value.v_identifier[0] == '@')
1253     {
1254       GtkSymbolicColor *color;
1255       gchar *color_name;
1256
1257       /* Rule is a color mapping */
1258       color_name = g_strdup (&scanner->value.v_identifier[1]);
1259       g_scanner_get_next_token (scanner);
1260
1261       if (scanner->token != ':')
1262         return ':';
1263
1264       css_provider_push_scope (css_provider, SCOPE_VALUE);
1265       g_scanner_get_next_token (scanner);
1266
1267       if (scanner->token != G_TOKEN_IDENTIFIER)
1268         return G_TOKEN_IDENTIFIER;
1269
1270       color = symbolic_color_parse (scanner->value.v_identifier);
1271
1272       if (!color)
1273         return G_TOKEN_IDENTIFIER;
1274
1275       g_hash_table_insert (priv->symbolic_colors, color_name, color);
1276
1277       css_provider_pop_scope (css_provider);
1278       g_scanner_get_next_token (scanner);
1279
1280       if (scanner->token != ';')
1281         return ';';
1282
1283       return G_TOKEN_NONE;
1284     }
1285
1286   expected_token = parse_selector (css_provider, scanner, &selector);
1287
1288   if (expected_token != G_TOKEN_NONE)
1289     {
1290       selector_path_unref (selector);
1291       return expected_token;
1292     }
1293
1294   priv->cur_selectors = g_slist_prepend (priv->cur_selectors, selector);
1295
1296   while (scanner->token == ',')
1297     {
1298       g_scanner_get_next_token (scanner);
1299
1300       expected_token = parse_selector (css_provider, scanner, &selector);
1301
1302       if (expected_token != G_TOKEN_NONE)
1303         {
1304           selector_path_unref (selector);
1305           return expected_token;
1306         }
1307
1308       priv->cur_selectors = g_slist_prepend (priv->cur_selectors, selector);
1309     }
1310
1311   css_provider_pop_scope (css_provider);
1312
1313   if (scanner->token != G_TOKEN_LEFT_CURLY)
1314     return G_TOKEN_LEFT_CURLY;
1315
1316   /* Declarations parsing */
1317   css_provider_push_scope (css_provider, SCOPE_DECLARATION);
1318   g_scanner_get_next_token (scanner);
1319
1320   while (scanner->token == G_TOKEN_IDENTIFIER)
1321     {
1322       const gchar *value_str = NULL;
1323       GType prop_type;
1324       gchar *prop;
1325
1326       prop = g_strdup (scanner->value.v_identifier);
1327       g_scanner_get_next_token (scanner);
1328
1329       if (scanner->token != ':')
1330         {
1331           g_free (prop);
1332           return ':';
1333         }
1334
1335       css_provider_push_scope (css_provider, SCOPE_VALUE);
1336       g_scanner_get_next_token (scanner);
1337
1338       if (scanner->token != G_TOKEN_IDENTIFIER)
1339         {
1340           g_free (prop);
1341           return G_TOKEN_IDENTIFIER;
1342         }
1343
1344       value_str = g_strstrip (scanner->value.v_identifier);
1345
1346       if (prop[0] == '-' &&
1347           g_ascii_isupper (prop[1]))
1348         {
1349           GValue *val;
1350
1351           val = g_slice_new0 (GValue);
1352           g_value_init (val, G_TYPE_STRING);
1353           g_value_set_string (val, value_str);
1354
1355           g_hash_table_insert (priv->cur_properties, prop, val);
1356         }
1357       else if (gtk_style_set_lookup_property (prop, &prop_type))
1358         {
1359           GValue *val;
1360
1361           val = g_slice_new0 (GValue);
1362           g_value_init (val, prop_type);
1363
1364           if (css_provider_parse_value (value_str, val))
1365             g_hash_table_insert (priv->cur_properties, prop, val);
1366           else
1367             {
1368               g_value_unset (val);
1369               g_slice_free (GValue, val);
1370               g_free (prop);
1371
1372               return G_TOKEN_IDENTIFIER;
1373             }
1374         }
1375       else
1376         g_free (prop);
1377
1378       css_provider_pop_scope (css_provider);
1379       g_scanner_get_next_token (scanner);
1380
1381       if (scanner->token != ';')
1382         return ';';
1383
1384       g_scanner_get_next_token (scanner);
1385     }
1386
1387   if (scanner->token != G_TOKEN_RIGHT_CURLY)
1388     return G_TOKEN_RIGHT_CURLY;
1389
1390   css_provider_pop_scope (css_provider);
1391
1392   return G_TOKEN_NONE;
1393 }
1394
1395 static gboolean
1396 parse_stylesheet (GtkCssProvider *css_provider)
1397 {
1398   GtkCssProviderPrivate *priv;
1399
1400   priv = GTK_CSS_PROVIDER_GET_PRIVATE (css_provider);
1401   g_scanner_get_next_token (priv->scanner);
1402
1403   while (!g_scanner_eof (priv->scanner))
1404     {
1405       GTokenType expected_token;
1406
1407       css_provider_reset_parser (css_provider);
1408       expected_token = parse_rule (css_provider, priv->scanner);
1409
1410       if (expected_token != G_TOKEN_NONE)
1411         {
1412           g_scanner_unexp_token (priv->scanner, expected_token,
1413                                  NULL, NULL, NULL,
1414                                  "Error parsing style resource", FALSE);
1415
1416           while (!g_scanner_eof (priv->scanner) &&
1417                  priv->scanner->token != G_TOKEN_RIGHT_CURLY)
1418             g_scanner_get_next_token (priv->scanner);
1419         }
1420       else
1421         css_provider_commit (css_provider);
1422
1423       g_scanner_get_next_token (priv->scanner);
1424     }
1425
1426   return TRUE;
1427 }
1428
1429 gboolean
1430 gtk_css_provider_load_from_data (GtkCssProvider *css_provider,
1431                                  const gchar    *data,
1432                                  gssize          length,
1433                                  GError         *error)
1434 {
1435   GtkCssProviderPrivate *priv;
1436
1437   g_return_val_if_fail (GTK_IS_CSS_PROVIDER (css_provider), FALSE);
1438   g_return_val_if_fail (data != NULL, FALSE);
1439
1440   priv = GTK_CSS_PROVIDER_GET_PRIVATE (css_provider);
1441
1442   if (length < 0)
1443     length = strlen (data);
1444
1445   if (priv->selectors_info->len > 0)
1446     g_ptr_array_remove_range (priv->selectors_info, 0, priv->selectors_info->len);
1447
1448   css_provider_reset_parser (css_provider);
1449   priv->scanner->input_name = "-";
1450   g_scanner_input_text (priv->scanner, data, (guint) length);
1451
1452   parse_stylesheet (css_provider);
1453
1454   return TRUE;
1455 }
1456
1457 gboolean
1458 gtk_css_provider_load_from_file (GtkCssProvider  *css_provider,
1459                                  GFile           *file,
1460                                  GError         **error)
1461 {
1462   GtkCssProviderPrivate *priv;
1463   GError *internal_error = NULL;
1464   gchar *data;
1465   gsize length;
1466
1467   g_return_val_if_fail (GTK_IS_CSS_PROVIDER (css_provider), FALSE);
1468   g_return_val_if_fail (G_IS_FILE (file), FALSE);
1469
1470   priv = GTK_CSS_PROVIDER_GET_PRIVATE (css_provider);
1471
1472   if (!g_file_load_contents (file, NULL,
1473                              &data, &length,
1474                              NULL, &internal_error))
1475     {
1476       g_propagate_error (error, internal_error);
1477       return FALSE;
1478     }
1479
1480   if (priv->selectors_info->len > 0)
1481     g_ptr_array_remove_range (priv->selectors_info, 0, priv->selectors_info->len);
1482
1483   g_free (priv->filename);
1484   priv->filename = g_file_get_path (file);
1485
1486   css_provider_reset_parser (css_provider);
1487   priv->scanner->input_name = priv->filename;
1488   g_scanner_input_text (priv->scanner, data, (guint) length);
1489
1490   parse_stylesheet (css_provider);
1491
1492   g_free (data);
1493
1494   return TRUE;
1495 }
1496
1497 #define __GTK_CSS_PROVIDER_C__
1498 #include "gtkaliasdef.c"