]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtkcssprovider.c
cssprovider: Move properties to scanner constructor
[~andy/gtk] / gtk / gtkcssprovider.c
index 48349b4b88617d5be5a977b94230824e34d22b74..d89b504b19b196a21c2d70180a002a4f4cf7eff6 100644 (file)
 
 #include <string.h>
 #include <stdlib.h>
-#include <gtk/gtk.h>
-#include <gtkstyleprovider.h>
+
 #include <gdk-pixbuf/gdk-pixbuf.h>
 #include <cairo-gobject.h>
 
-#include "gtkanimationdescription.h"
-#include "gtk9slice.h"
-#include "gtkcssprovider.h"
+#include "gtkcssproviderprivate.h"
+
+#include "gtkcssstringfuncsprivate.h"
+#include "gtksymboliccolor.h"
+#include "gtkstyleprovider.h"
+#include "gtkstylecontextprivate.h"
+#include "gtkbindings.h"
+#include "gtkmarshalers.h"
 #include "gtkprivate.h"
+#include "gtkintl.h"
 
 /**
  * SECTION:gtkcssprovider
  * &commat;import url ("path/to/common.css");
  * </programlisting>
  * </example>
+ * <para id="css-binding-set">
+ * In order to extend key bindings affecting different widgets, GTK+
+ * supports the &commat;binding-set rule to parse a set of bind/unbind
+ * directives, see #GtkBindingSet for the supported syntax. Note that
+ * the binding sets defined in this way must be associated with rule sets
+ * by setting the gtk-key-bindings style property.
+ * </para>
+ * <para>
+ * Customized key bindings are typically defined in a separate
+ * <filename>gtk-keys.css</filename> CSS file and GTK+ loads this file
+ * according to the current key theme, which is defined by the
+ * #GtkSettings:gtk-key-theme-name setting.
+ * </para>
+ * <example>
+ * <title>Using the &commat;binding rule</title>
+ * <programlisting language="text">
+ * &commat;binding-set binding-set1 {
+ *   bind "&lt;alt&gt;Left" { "move-cursor" (visual-positions, -3, 0) };
+ *   unbind "End";
+ * };
+ *
+ * &commat;binding-set binding-set2 {
+ *   bind "&lt;alt&gt;Right" { "move-cursor" (visual-positions, 3, 0) };
+ *   bind "&lt;alt&gt;KP_space" { "delete-from-cursor" (whitespace, 1)
+ *                          "insert-at-cursor" (" ") };
+ * };
+ *
+ * GtkEntry {
+ *   gtk-key-bindings: binding-set1, binding-set2;
+ * }
+ * </programlisting>
+ * </example>
  * <para>
  * GTK+ also supports an additional &commat;define-color rule, in order
  * to define a color name which may be used instead of color numeric
  * transition: 1s linear loop;</literallayout>
  *         </entry>
  *       </row>
+ *       <row>
+ *         <entry>gtk-key-bindings</entry>
+ *         <entry>binding set name list</entry>
+ *         <entry>internal use only</entry>
+ *         <entry><literallayout>gtk-bindings: binding1, binding2, ...;</literallayout>
+ *         </entry>
+ *       </row>
  *     </tbody>
  *   </tgroup>
  * </informaltable>
  * </refsect2>
  */
 
-typedef struct GtkCssProviderPrivate GtkCssProviderPrivate;
 typedef struct SelectorElement SelectorElement;
 typedef struct SelectorPath SelectorPath;
 typedef struct SelectorStyleInfo SelectorStyleInfo;
+typedef struct _GtkCssScannerPrivate GtkCssScannerPrivate;
 typedef enum SelectorElementType SelectorElementType;
 typedef enum CombinatorType CombinatorType;
 typedef enum ParserScope ParserScope;
@@ -742,22 +786,21 @@ struct SelectorStyleInfo
   GHashTable *style;
 };
 
-struct GtkCssProviderPrivate
+struct _GtkCssScannerPrivate
 {
-  GScanner *scanner;
-  gchar *filename;
+  GFile *file;
+  GSList *state;
+  GSList *cur_selectors;
+  GHashTable *cur_properties;
+};
 
-  const gchar *buffer;
-  const gchar *value_pos;
+struct _GtkCssProviderPrivate
+{
+  GScanner *scanner;
 
   GHashTable *symbolic_colors;
 
   GPtrArray *selectors_info;
-
-  /* Current parser state */
-  GSList *state;
-  GSList *cur_selectors;
-  GHashTable *cur_properties;
 };
 
 enum ParserScope {
@@ -765,7 +808,8 @@ enum ParserScope {
   SCOPE_PSEUDO_CLASS,
   SCOPE_NTH_CHILD,
   SCOPE_DECLARATION,
-  SCOPE_VALUE
+  SCOPE_VALUE,
+  SCOPE_BINDING_SET
 };
 
 /* Extend GtkStateType, since these
@@ -785,19 +829,25 @@ enum ParserSymbol {
   SYMBOL_NTH_CHILD_LAST
 };
 
+enum {
+  PARSING_ERROR,
+  LAST_SIGNAL
+};
+
+static guint css_provider_signals[LAST_SIGNAL] = { 0 };
+
 static void gtk_css_provider_finalize (GObject *object);
 static void gtk_css_style_provider_iface_init (GtkStyleProviderIface *iface);
 
 static void scanner_apply_scope (GScanner    *scanner,
                                  ParserScope  scope);
-static gboolean css_provider_parse_value (GtkCssProvider  *css_provider,
-                                          const gchar     *value_str,
-                                          GValue          *value,
-                                          GError         **error);
 static gboolean gtk_css_provider_load_from_path_internal (GtkCssProvider  *css_provider,
                                                           const gchar     *path,
                                                           gboolean         reset,
                                                           GError         **error);
+static void     gtk_css_provider_take_error (GtkCssProvider *provider,
+                                             GScanner       *scanner,
+                                             GError         *error);
 
 GQuark
 gtk_css_provider_error_quark (void)
@@ -809,13 +859,69 @@ G_DEFINE_TYPE_EXTENDED (GtkCssProvider, gtk_css_provider, G_TYPE_OBJECT, 0,
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_STYLE_PROVIDER,
                                                gtk_css_style_provider_iface_init));
 
+static void
+gtk_css_provider_parsing_error (GtkCssProvider  *provider,
+                                const gchar     *path,
+                                guint            line,
+                                guint            position,
+                                const GError *   error)
+{
+  /* Only emit a warning when we have no error handlers. This is our
+   * default handlers. And in this case erroneous CSS files are a bug
+   * and should be fixed.
+   * Note that these warnings can also be triggered by a broken theme
+   * that people installed from some weird location on the internets.
+   */
+  if (!g_signal_has_handler_pending (provider,
+                                     css_provider_signals[PARSING_ERROR],
+                                     0,
+                                     TRUE))
+    {
+      g_warning ("Theme parsing error: %s:%u:%u: %s", path ? path : "<unknown>", line, position, error->message);
+    }
+}
+
 static void
 gtk_css_provider_class_init (GtkCssProviderClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
+  /**
+   * GtkCssProvider::parsing-error:
+   * @provider: the provider that had a parsing error
+   * @path: path to the parsed file or %NULL if the file cannot be
+   *   identified or the data was not loaded from a file
+   * @line: line in the file or data or 0 if unknown
+   * @position: offset into the current line or 0 if unknown or the
+   *   whole line is affected
+   * @error: The parsing error
+   *
+   * Signals that a parsing error occured. the @path, @line and @position
+   * describe the actual location of the error as accurately as possible.
+   *
+   * Parsing errors are never fatal, so the parsing will resume after
+   * the error. Errors may however cause parts of the given
+   * data or even all of it to not be parsed at all. So it is a useful idea
+   * to check that the parsing succeeds by connecting to this signal.
+   *
+   * Note that this signal may be emitted at any time as the css provider
+   * may opt to defer parsing parts or all of the input to a later time
+   * than when a loading function was called.
+   */
+  css_provider_signals[PARSING_ERROR] =
+    g_signal_new (I_("parsing-error"),
+                  G_TYPE_FROM_CLASS (object_class),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (GtkCssProviderClass, parsing_error),
+                  NULL, NULL,
+                  _gtk_marshal_VOID__STRING_UINT_UINT_BOXED,
+                  G_TYPE_NONE, 4,
+                  G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_ERROR);
+
   object_class->finalize = gtk_css_provider_finalize;
 
+  klass->parsing_error = gtk_css_provider_parsing_error;
+
   g_type_class_add_private (object_class, sizeof (GtkCssProviderPrivate));
 }
 
@@ -993,13 +1099,73 @@ selector_style_info_set_style (SelectorStyleInfo *info,
     info->style = NULL;
 }
 
+static void
+property_value_free (GValue *value)
+{
+  if (G_IS_VALUE (value))
+    g_value_unset (value);
+
+  g_slice_free (GValue, value);
+}
+
+static void
+gtk_css_scanner_reset (GScanner *scanner)
+{
+  GtkCssScannerPrivate *priv = scanner->user_data;
+
+  g_slist_free (priv->state);
+  priv->state = NULL;
+
+  g_slist_foreach (priv->cur_selectors, (GFunc) selector_path_unref, NULL);
+  g_slist_free (priv->cur_selectors);
+  priv->cur_selectors = NULL;
+
+  if (priv->cur_properties)
+    g_hash_table_unref (priv->cur_properties);
+
+  priv->cur_properties = g_hash_table_new_full (g_str_hash,
+                                                g_str_equal,
+                                                (GDestroyNotify) g_free,
+                                                (GDestroyNotify) property_value_free);
+
+  scanner_apply_scope (scanner, SCOPE_SELECTOR);
+}
+
+static void
+gtk_css_scanner_destroy (GScanner *scanner)
+{
+  GtkCssScannerPrivate *priv = scanner->user_data;
+
+  gtk_css_scanner_reset (scanner);
+
+  if (priv->file)
+    g_object_unref (priv->file);
+  g_hash_table_destroy (priv->cur_properties);
+  g_slice_free (GtkCssScannerPrivate, priv);
+  
+  g_scanner_destroy (scanner);
+}
+
 static GScanner *
-create_scanner (void)
+gtk_css_scanner_new (GFile       *file,
+                     const gchar *data,
+                     gsize        length)
 {
+  GtkCssScannerPrivate *priv;
   GScanner *scanner;
 
   scanner = g_scanner_new (NULL);
 
+  priv = scanner->user_data = g_slice_new0 (GtkCssScannerPrivate);
+
+  if (file)
+    priv->file = g_object_ref (file);
+
+  priv->cur_properties = g_hash_table_new_full (g_str_hash,
+                                                g_str_equal,
+                                                (GDestroyNotify) g_free,
+                                                (GDestroyNotify) property_value_free);
+
   g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "active", GUINT_TO_POINTER (GTK_STATE_ACTIVE));
   g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "prelight", GUINT_TO_POINTER (GTK_STATE_PRELIGHT));
   g_scanner_scope_add_symbol (scanner, SCOPE_PSEUDO_CLASS, "hover", GUINT_TO_POINTER (GTK_STATE_PRELIGHT));
@@ -1021,6 +1187,11 @@ create_scanner (void)
 
   scanner_apply_scope (scanner, SCOPE_SELECTOR);
 
+  if (length > G_MAXUINT32)
+    g_warning ("CSS file too large, truncating");
+
+  g_scanner_input_text (scanner, data, length);
+
   return scanner;
 }
 
@@ -1034,7 +1205,6 @@ gtk_css_provider_init (GtkCssProvider *css_provider)
                                                            GtkCssProviderPrivate);
 
   priv->selectors_info = g_ptr_array_new_with_free_func ((GDestroyNotify) selector_style_info_free);
-  priv->scanner = create_scanner ();
 
   priv->symbolic_colors = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                  (GDestroyNotify) g_free,
@@ -1092,23 +1262,15 @@ compare_selector_element (GtkWidgetPath   *path,
         *score |= 0xF;
       else
         {
-          GType parent = type;
+          guint diff = g_type_depth (type) - g_type_depth (elem->type);
 
-          *score = 0xE;
-
-          while ((parent = g_type_parent (parent)) != G_TYPE_INVALID)
+          if (G_UNLIKELY (diff > 0xE))
             {
-              if (parent == elem->type)
-                break;
-
-              *score -= 1;
-
-              if (*score == 1)
-                {
-                  g_warning ("Hierarchy is higher than expected.");
-                  break;
-                }
+              g_warning ("Hierarchy is higher than expected.");
+              diff = 0xE;
             }
+          
+          *score = 0XF - diff;
         }
 
       return TRUE;
@@ -1320,14 +1482,12 @@ gtk_css_provider_get_style (GtkStyleProvider *provider,
                             GtkWidgetPath    *path)
 {
   GtkCssProvider *css_provider;
-  GtkCssProviderPrivate *priv;
   GtkStyleProperties *props;
   GArray *priority_info;
   guint i;
 
   css_provider = GTK_CSS_PROVIDER (provider);
   props = gtk_style_properties_new ();
-  priv = css_provider->priv;
 
   css_provider_dump_symbolic_colors (css_provider, props);
   priority_info = css_provider_get_selectors (css_provider, path);
@@ -1394,13 +1554,17 @@ gtk_css_provider_get_style_property (GtkStyleProvider *provider,
            ((info->state & state) != 0 &&
             (info->state & ~(state)) == 0)))
         {
-          const gchar *val_str;
+          GError *error = NULL;
 
-          val_str = g_value_get_string (val);
-          found = TRUE;
+          found = _gtk_css_value_from_string (value,
+                                              NULL,
+                                              g_value_get_string (val),
+                                              &error);
 
-          css_provider_parse_value (GTK_CSS_PROVIDER (provider), val_str, value, NULL);
-          break;
+          if (found)
+            break;
+          
+          gtk_css_provider_take_error (GTK_CSS_PROVIDER (provider), NULL, error);
         }
     }
 
@@ -1426,16 +1590,8 @@ gtk_css_provider_finalize (GObject *object)
   css_provider = GTK_CSS_PROVIDER (object);
   priv = css_provider->priv;
 
-  g_scanner_destroy (priv->scanner);
-  g_free (priv->filename);
-
   g_ptr_array_free (priv->selectors_info, TRUE);
 
-  g_slist_foreach (priv->cur_selectors, (GFunc) selector_path_unref, NULL);
-  g_slist_free (priv->cur_selectors);
-
-  if (priv->cur_properties)
-    g_hash_table_unref (priv->cur_properties);
   if (priv->symbolic_colors)
     g_hash_table_destroy (priv->symbolic_colors);
 
@@ -1456,12 +1612,84 @@ gtk_css_provider_new (void)
 }
 
 static void
-property_value_free (GValue *value)
+gtk_css_provider_take_error (GtkCssProvider *provider,
+                             GScanner       *scanner,
+                             GError         *error)
 {
-  if (G_IS_VALUE (value))
-    g_value_unset (value);
+  char *filename;
+  guint line, position;
 
-  g_slice_free (GValue, value);
+  if (scanner)
+    {
+      GtkCssScannerPrivate *priv = scanner->user_data;
+      if (priv->file)
+        filename = g_file_get_path (priv->file);
+      else
+        filename = NULL;
+      line = scanner->line;
+      position = scanner->position;
+    }
+  else
+    {
+      filename = NULL;
+      line = 0;
+      position = 0;
+    }
+
+  g_signal_emit (provider, css_provider_signals[PARSING_ERROR], 0,
+                 filename, line, position, error);
+
+  g_free (filename);
+  g_error_free (error);
+}
+
+static void
+gtk_css_provider_error_literal (GtkCssProvider *provider,
+                                GScanner       *scanner,
+                                GQuark          domain,
+                                gint            code,
+                                const char     *message)
+{
+  gtk_css_provider_take_error (provider,
+                               scanner,
+                               g_error_new_literal (domain, code, message));
+}
+
+static void
+gtk_css_provider_error (GtkCssProvider *provider,
+                        GScanner       *scanner,
+                        GQuark          domain,
+                        gint            code,
+                        const char     *format,
+                        ...)  G_GNUC_PRINTF (5, 6);
+static void
+gtk_css_provider_error (GtkCssProvider *provider,
+                        GScanner       *scanner,
+                        GQuark          domain,
+                        gint            code,
+                        const char     *format,
+                        ...)
+{
+  GError *error;
+  va_list args;
+
+  va_start (args, format);
+  error = g_error_new_valist (domain, code, format, args);
+  va_end (args);
+
+  gtk_css_provider_take_error (provider, scanner, error);
+}
+
+static void
+gtk_css_provider_invalid_token (GtkCssProvider *provider,
+                                GScanner       *scanner,
+                                const char     *expected)
+{
+  gtk_css_provider_error (provider,
+                          scanner,
+                          GTK_CSS_PROVIDER_ERROR,
+                          GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                          "expected a valid %s", expected);
 }
 
 static void
@@ -1476,6 +1704,12 @@ scanner_apply_scope (GScanner    *scanner,
       scanner->config->cset_identifier_nth = G_CSET_a_2_z G_CSET_A_2_Z G_CSET_DIGITS "@#-_ +(),.%\t\n'/\"";
       scanner->config->scan_identifier_1char = TRUE;
     }
+  else if (scope == SCOPE_BINDING_SET)
+    {
+      scanner->config->cset_identifier_first = G_CSET_a_2_z G_CSET_A_2_Z G_CSET_DIGITS "@#-_";
+      scanner->config->cset_identifier_nth = G_CSET_a_2_z G_CSET_A_2_Z G_CSET_DIGITS "@#-_ +(){}<>,.%\t\n'/\"";
+      scanner->config->scan_identifier_1char = TRUE;
+    }
   else if (scope == SCOPE_SELECTOR)
     {
       scanner->config->cset_identifier_first = G_CSET_a_2_z G_CSET_A_2_Z "*@";
@@ -1498,30 +1732,30 @@ scanner_apply_scope (GScanner    *scanner,
 }
 
 static void
-css_provider_push_scope (GtkCssProvider *css_provider,
-                         ParserScope     scope)
+gtk_css_scanner_push_scope (GScanner    *scanner,
+                            ParserScope  scope)
 {
-  GtkCssProviderPrivate *priv;
+  GtkCssScannerPrivate *priv;
 
-  priv = css_provider->priv;
+  priv = scanner->user_data;
   priv->state = g_slist_prepend (priv->state, GUINT_TO_POINTER (scope));
 
-  scanner_apply_scope (priv->scanner, scope);
+  scanner_apply_scope (scanner, scope);
 }
 
-static ParserScope
-css_provider_pop_scope (GtkCssProvider *css_provider)
+static void
+gtk_css_scanner_pop_scope (GScanner *scanner)
 {
-  GtkCssProviderPrivate *priv;
+  GtkCssScannerPrivate *priv;
   ParserScope scope = SCOPE_SELECTOR;
 
-  priv = css_provider->priv;
+  priv = scanner->user_data;
 
   if (!priv->state)
     {
       g_warning ("Push/pop calls to parser scope aren't paired");
-      scanner_apply_scope (priv->scanner, SCOPE_SELECTOR);
-      return SCOPE_SELECTOR;
+      scanner_apply_scope (scanner, SCOPE_SELECTOR);
+      return;
     }
 
   priv->state = g_slist_delete_link (priv->state, priv->state);
@@ -1530,46 +1764,24 @@ css_provider_pop_scope (GtkCssProvider *css_provider)
   if (priv->state)
     scope = GPOINTER_TO_INT (priv->state->data);
 
-  scanner_apply_scope (priv->scanner, scope);
-
-  return scope;
+  scanner_apply_scope (scanner, scope);
 }
 
 static void
-css_provider_reset_parser (GtkCssProvider *css_provider)
+css_provider_commit (GtkCssProvider *css_provider,
+                     GScanner       *scanner)
 {
+  GtkCssScannerPrivate *scanner_priv;
   GtkCssProviderPrivate *priv;
+  GSList *l;
 
   priv = css_provider->priv;
+  scanner_priv = scanner->user_data;
 
-  g_slist_free (priv->state);
-  priv->state = NULL;
-
-  scanner_apply_scope (priv->scanner, SCOPE_SELECTOR);
-  priv->scanner->user_data = NULL;
-  priv->value_pos = NULL;
-
-  g_slist_foreach (priv->cur_selectors, (GFunc) selector_path_unref, NULL);
-  g_slist_free (priv->cur_selectors);
-  priv->cur_selectors = NULL;
-
-  if (priv->cur_properties)
-    g_hash_table_unref (priv->cur_properties);
-
-  priv->cur_properties = g_hash_table_new_full (g_str_hash,
-                                                g_str_equal,
-                                                (GDestroyNotify) g_free,
-                                                (GDestroyNotify) property_value_free);
-}
-
-static void
-css_provider_commit (GtkCssProvider *css_provider)
-{
-  GtkCssProviderPrivate *priv;
-  GSList *l;
+  l = scanner_priv->cur_selectors;
 
-  priv = css_provider->priv;
-  l = priv->cur_selectors;
+  if (g_hash_table_size (scanner_priv->cur_properties) == 0)
+    return;
 
   while (l)
     {
@@ -1577,7 +1789,7 @@ css_provider_commit (GtkCssProvider *css_provider)
       SelectorStyleInfo *info;
 
       info = selector_style_info_new (path);
-      selector_style_info_set_style (info, priv->cur_properties);
+      selector_style_info_set_style (info, scanner_priv->cur_properties);
 
       g_ptr_array_add (priv->selectors_info, info);
       l = l->next;
@@ -1605,7 +1817,7 @@ parse_nth_child (GtkCssProvider *css_provider,
       if (scanner->token != G_TOKEN_LEFT_PAREN)
         return G_TOKEN_LEFT_PAREN;
 
-      css_provider_push_scope (css_provider, SCOPE_NTH_CHILD);
+      gtk_css_scanner_push_scope (scanner, SCOPE_NTH_CHILD);
       g_scanner_get_next_token (scanner);
 
       if (scanner->token != G_TOKEN_SYMBOL)
@@ -1636,7 +1848,7 @@ parse_nth_child (GtkCssProvider *css_provider,
       if (scanner->token != G_TOKEN_RIGHT_PAREN)
         return G_TOKEN_RIGHT_PAREN;
 
-      css_provider_pop_scope (css_provider);
+      gtk_css_scanner_pop_scope (scanner);
     }
   else if (symbol == SYMBOL_FIRST_CHILD)
     *flags = GTK_REGION_FIRST;
@@ -1840,7 +2052,7 @@ parse_selector (GtkCssProvider  *css_provider,
               ParserSymbol symbol;
 
               g_scanner_get_next_token (scanner);
-              css_provider_push_scope (css_provider, SCOPE_PSEUDO_CLASS);
+              gtk_css_scanner_push_scope (scanner, SCOPE_PSEUDO_CLASS);
 
               /* Check for the next token being nth-child, parse in that
                * case, and fallback into common state parsing if not.
@@ -1860,11 +2072,11 @@ parse_selector (GtkCssProvider  *css_provider,
                   if ((token = parse_nth_child (css_provider, scanner, &flags)) != G_TOKEN_NONE)
                     return token;
 
-                  css_provider_pop_scope (css_provider);
+                  gtk_css_scanner_pop_scope (scanner);
                 }
               else
                 {
-                  css_provider_pop_scope (css_provider);
+                  gtk_css_scanner_pop_scope (scanner);
                   selector_path_prepend_region (path, region_name, 0);
                   g_free (region_name);
                   break;
@@ -1894,7 +2106,7 @@ parse_selector (GtkCssProvider  *css_provider,
       if (selector_path_depth (path) == 0)
         selector_path_prepend_glob (path);
 
-      css_provider_push_scope (css_provider, SCOPE_PSEUDO_CLASS);
+      gtk_css_scanner_push_scope (scanner, SCOPE_PSEUDO_CLASS);
 
       while (scanner->token == ':')
         {
@@ -1906,1334 +2118,229 @@ parse_selector (GtkCssProvider  *css_provider,
           g_scanner_get_next_token (scanner);
         }
 
-      css_provider_pop_scope (css_provider);
+      gtk_css_scanner_pop_scope (scanner);
     }
 
   return G_TOKEN_NONE;
 }
 
-#define SKIP_SPACES(s) while (s[0] == ' ' || s[0] == '\t' || s[0] == '\n') s++;
-#define SKIP_SPACES_BACK(s) while (s[0] == ' ' || s[0] == '\t' || s[0] == '\n') s--;
-
-static GtkSymbolicColor *
-symbolic_color_parse_str (const gchar  *string,
-                          gchar       **end_ptr)
+static void
+resolve_binding_sets (const gchar *value_str,
+                      GValue      *value)
 {
-  GtkSymbolicColor *symbolic_color = NULL;
-  gchar *str;
+  GPtrArray *array;
+  gchar **bindings, **str;
 
-  str = (gchar *) string;
-  *end_ptr = str;
+  bindings = g_strsplit (value_str, ",", -1);
+  array = g_ptr_array_new ();
 
-  if (str[0] == '@')
+  for (str = bindings; *str; str++)
     {
-      const gchar *end;
-      gchar *name;
-
-      str++;
-      end = str;
+      GtkBindingSet *binding_set;
 
-      while (*end == '-' || *end == '_' || g_ascii_isalpha (*end))
-        end++;
+      binding_set = gtk_binding_set_find (g_strstrip (*str));
 
-      name = g_strndup (str, end - str);
-      symbolic_color = gtk_symbolic_color_new_name (name);
-      g_free (name);
+      if (!binding_set)
+        continue;
 
-      *end_ptr = (gchar *) end;
+      g_ptr_array_add (array, binding_set);
     }
-  else if (g_str_has_prefix (str, "lighter") ||
-           g_str_has_prefix (str, "darker"))
-    {
-      GtkSymbolicColor *param_color;
-      gboolean is_lighter = FALSE;
-
-      is_lighter = g_str_has_prefix (str, "lighter");
 
-      if (is_lighter)
-        str += strlen ("lighter");
-      else
-        str += strlen ("darker");
+  g_value_take_boxed (value, array);
+  g_strfreev (bindings);
+}
 
-      SKIP_SPACES (str);
+static GTokenType
+parse_rule (GtkCssProvider  *css_provider,
+            GScanner        *scanner)
+{
+  GtkCssScannerPrivate *priv;
+  GTokenType expected_token;
+  SelectorPath *selector;
 
-      if (*str != '(')
-        {
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
+  priv = scanner->user_data;
 
-      str++;
-      SKIP_SPACES (str);
-      param_color = symbolic_color_parse_str (str, end_ptr);
+  gtk_css_scanner_push_scope (scanner, SCOPE_SELECTOR);
 
-      if (!param_color)
-        return NULL;
+  /* Handle directives */
+  if (scanner->token == G_TOKEN_IDENTIFIER &&
+      scanner->value.v_identifier[0] == '@')
+    {
+      gchar *directive;
 
-      str = *end_ptr;
-      SKIP_SPACES (str);
-      *end_ptr = (gchar *) str;
+      directive = &scanner->value.v_identifier[1];
 
-      if (*str != ')')
+      if (strcmp (directive, "define-color") == 0)
         {
-          gtk_symbolic_color_unref (param_color);
-          return NULL;
-        }
-
-      if (is_lighter)
-        symbolic_color = gtk_symbolic_color_new_shade (param_color, 1.3);
-      else
-        symbolic_color = gtk_symbolic_color_new_shade (param_color, 0.7);
+          GtkSymbolicColor *color;
+          gchar *color_name, *color_str;
+          GError *error = NULL;
 
-      gtk_symbolic_color_unref (param_color);
-      (*end_ptr)++;
-    }
-  else if (g_str_has_prefix (str, "shade") ||
-           g_str_has_prefix (str, "alpha"))
-    {
-      GtkSymbolicColor *param_color;
-      gboolean is_shade = FALSE;
-      gdouble factor;
+          /* Directive is a color mapping */
+          g_scanner_get_next_token (scanner);
 
-      is_shade = g_str_has_prefix (str, "shade");
+          if (scanner->token != G_TOKEN_IDENTIFIER)
+            {
+              gtk_css_provider_invalid_token (css_provider, scanner, "Color name");
+              return G_TOKEN_IDENTIFIER;
+            }
 
-      if (is_shade)
-        str += strlen ("shade");
-      else
-        str += strlen ("alpha");
+          color_name = g_strdup (scanner->value.v_identifier);
+          gtk_css_scanner_push_scope (scanner, SCOPE_VALUE);
+          g_scanner_get_next_token (scanner);
 
-      SKIP_SPACES (str);
+          if (scanner->token != G_TOKEN_IDENTIFIER)
+            {
+              gtk_css_provider_invalid_token (css_provider, scanner, "Color definition");
+              return G_TOKEN_IDENTIFIER;
+            }
 
-      if (*str != '(')
-        {
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
+          color_str = g_strstrip (scanner->value.v_identifier);
+          color = _gtk_css_parse_symbolic_color (color_str, &error);
+          if (!color)
+            {
+              gtk_css_provider_take_error (css_provider, scanner, error);
+              return G_TOKEN_IDENTIFIER;
+            }
 
-      str++;
-      SKIP_SPACES (str);
-      param_color = symbolic_color_parse_str (str, end_ptr);
+          g_hash_table_insert (css_provider->priv->symbolic_colors, color_name, color);
 
-      if (!param_color)
-        return NULL;
+          gtk_css_scanner_pop_scope (scanner);
+          g_scanner_get_next_token (scanner);
 
-      str = *end_ptr;
-      SKIP_SPACES (str);
+          if (scanner->token != ';')
+            return ';';
 
-      if (str[0] != ',')
-        {
-          gtk_symbolic_color_unref (param_color);
-          *end_ptr = (gchar *) str;
-          return NULL;
+          return G_TOKEN_NONE;
         }
-
-      str++;
-      SKIP_SPACES (str);
-      factor = g_ascii_strtod (str, end_ptr);
-
-      str = *end_ptr;
-      SKIP_SPACES (str);
-      *end_ptr = (gchar *) str;
-
-      if (str[0] != ')')
+      else if (strcmp (directive, "import") == 0)
         {
-          gtk_symbolic_color_unref (param_color);
-          return NULL;
-        }
+          gboolean loaded;
+          gchar *path = NULL;
+          GFile *base, *actual;
+          char *dirname;
+          GError *error = NULL;
 
-      if (is_shade)
-        symbolic_color = gtk_symbolic_color_new_shade (param_color, factor);
-      else
-        symbolic_color = gtk_symbolic_color_new_alpha (param_color, factor);
+          gtk_css_scanner_push_scope (scanner, SCOPE_VALUE);
+          g_scanner_get_next_token (scanner);
 
-      gtk_symbolic_color_unref (param_color);
-      (*end_ptr)++;
-    }
-  else if (g_str_has_prefix (str, "mix"))
-    {
-      GtkSymbolicColor *color1, *color2;
-      gdouble factor;
+          if (scanner->token == G_TOKEN_IDENTIFIER &&
+              g_str_has_prefix (scanner->value.v_identifier, "url"))
+            path = g_strstrip (scanner->value.v_identifier);
+          else if (scanner->token == G_TOKEN_STRING)
+            path = g_strstrip (scanner->value.v_string);
+          else
+            {
+              gtk_css_provider_invalid_token (css_provider, scanner, "File URL");
+              return G_TOKEN_IDENTIFIER;
+            }
 
-      str += strlen ("mix");
-      SKIP_SPACES (str);
+          if (scanner->input_name)
+            dirname = g_path_get_dirname (scanner->input_name);
+          else
+            dirname = g_get_current_dir ();
 
-      if (*str != '(')
-        {
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
+          base = g_file_new_for_path (dirname);
+          g_free (dirname);
 
-      str++;
-      SKIP_SPACES (str);
-      color1 = symbolic_color_parse_str (str, end_ptr);
+          actual = _gtk_css_parse_url (base, path, NULL, &error);
+          g_object_unref (base);
 
-      if (!color1)
-        return NULL;
+          if (actual == NULL)
+            {
+              gtk_css_provider_take_error (css_provider, scanner, error);
+              return G_TOKEN_IDENTIFIER;
+            }
 
-      str = *end_ptr;
-      SKIP_SPACES (str);
+          gtk_css_scanner_pop_scope (scanner);
+          g_scanner_get_next_token (scanner);
 
-      if (str[0] != ',')
-        {
-          gtk_symbolic_color_unref (color1);
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
+          if (scanner->token != ';')
+            {
+              g_object_unref (actual);
+              return ';';
+            }
 
-      str++;
-      SKIP_SPACES (str);
-      color2 = symbolic_color_parse_str (str, end_ptr);
+          path = g_file_get_path (actual);
+          g_object_unref (actual);
 
-      if (!color2 || *end_ptr[0] != ',')
-        {
-          gtk_symbolic_color_unref (color1);
-          return NULL;
-        }
-
-      str = *end_ptr;
-      SKIP_SPACES (str);
-
-      if (str[0] != ',')
-        {
-          gtk_symbolic_color_unref (color1);
-          gtk_symbolic_color_unref (color2);
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      str++;
-      SKIP_SPACES (str);
-      factor = g_ascii_strtod (str, end_ptr);
-
-      str = *end_ptr;
-      SKIP_SPACES (str);
-      *end_ptr = (gchar *) str;
-
-      if (str[0] != ')')
-        {
-          gtk_symbolic_color_unref (color1);
-          gtk_symbolic_color_unref (color2);
-          return NULL;
-        }
-
-      symbolic_color = gtk_symbolic_color_new_mix (color1, color2, factor);
-      gtk_symbolic_color_unref (color1);
-      gtk_symbolic_color_unref (color2);
-      (*end_ptr)++;
-    }
-  else
-    {
-      GdkRGBA color;
-      gchar *color_str;
-      const gchar *end;
-
-      end = str + 1;
-
-      if (str[0] == '#')
-        {
-          /* Color in hex format */
-          while (g_ascii_isxdigit (*end))
-            end++;
-        }
-      else if (g_str_has_prefix (str, "rgb"))
-        {
-          /* color in rgb/rgba format */
-          while (*end != ')' && *end != '\0')
-            end++;
-
-          if (*end == ')')
-            end++;
-        }
-      else
-        {
-          /* Color name */
-          while (*end != '\0' &&
-                 (g_ascii_isalnum (*end) || *end == ' '))
-            end++;
-        }
-
-      color_str = g_strndup (str, end - str);
-      *end_ptr = (gchar *) end;
-
-      if (!gdk_rgba_parse (&color, color_str))
-        {
-          g_free (color_str);
-          return NULL;
-        }
-
-      symbolic_color = gtk_symbolic_color_new_literal (&color);
-      g_free (color_str);
-    }
-
-  return symbolic_color;
-}
-
-static GtkSymbolicColor *
-symbolic_color_parse (const gchar  *str,
-                      GError      **error)
-{
-  GtkSymbolicColor *color;
-  gchar *end;
-
-  color = symbolic_color_parse_str (str, &end);
-
-  if (*end != '\0')
-    {
-      g_set_error_literal (error,
-                           GTK_CSS_PROVIDER_ERROR,
-                           GTK_CSS_PROVIDER_ERROR_FAILED,
-                           "Could not parse symbolic color");
-
-      if (color)
-        {
-          gtk_symbolic_color_unref (color);
-          color = NULL;
-        }
-    }
-
-  return color;
-}
-
-static GtkGradient *
-gradient_parse_str (const gchar  *str,
-                    gchar       **end_ptr)
-{
-  GtkGradient *gradient = NULL;
-  gdouble coords[6];
-  gchar *end;
-  guint i;
-
-  if (g_str_has_prefix (str, "-gtk-gradient"))
-    {
-      cairo_pattern_type_t type;
-
-      str += strlen ("-gtk-gradient");
-      SKIP_SPACES (str);
-
-      if (*str != '(')
-        {
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      str++;
-      SKIP_SPACES (str);
-
-      /* Parse gradient type */
-      if (g_str_has_prefix (str, "linear"))
-        {
-          type = CAIRO_PATTERN_TYPE_LINEAR;
-          str += strlen ("linear");
-        }
-      else if (g_str_has_prefix (str, "radial"))
-        {
-          type = CAIRO_PATTERN_TYPE_RADIAL;
-          str += strlen ("radial");
-        }
-      else
-        {
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      SKIP_SPACES (str);
-
-      /* Parse start/stop position parameters */
-      for (i = 0; i < 2; i++)
-        {
-          if (*str != ',')
-            {
-              *end_ptr = (gchar *) str;
-              return NULL;
-            }
-
-          str++;
-          SKIP_SPACES (str);
-
-          if (strncmp (str, "left", 4) == 0)
-            {
-              coords[i * 3] = 0;
-              str += strlen ("left");
-            }
-          else if (strncmp (str, "right", 5) == 0)
-            {
-              coords[i * 3] = 1;
-              str += strlen ("right");
-            }
-          else if (strncmp (str, "center", 6) == 0)
-            {
-              coords[i * 3] = 0.5;
-              str += strlen ("center");
-            }
-          else
-            {
-              coords[i * 3] = g_ascii_strtod (str, &end);
-
-              if (str == end)
-                {
-                  *end_ptr = (gchar *) str;
-                  return NULL;
-                }
-
-              str = end;
-            }
-
-          SKIP_SPACES (str);
-
-          if (strncmp (str, "top", 3) == 0)
-            {
-              coords[(i * 3) + 1] = 0;
-              str += strlen ("top");
-            }
-          else if (strncmp (str, "bottom", 6) == 0)
-            {
-              coords[(i * 3) + 1] = 1;
-              str += strlen ("bottom");
-            }
-          else if (strncmp (str, "center", 6) == 0)
-            {
-              coords[(i * 3) + 1] = 0.5;
-              str += strlen ("center");
-            }
-          else
-            {
-              coords[(i * 3) + 1] = g_ascii_strtod (str, &end);
-
-              if (str == end)
-                {
-                  *end_ptr = (gchar *) str;
-                  return NULL;
-                }
-
-              str = end;
-            }
-
-          SKIP_SPACES (str);
-
-          if (type == CAIRO_PATTERN_TYPE_RADIAL)
-            {
-              /* Parse radius */
-              if (*str != ',')
-                {
-                  *end_ptr = (gchar *) str;
-                  return NULL;
-                }
-
-              str++;
-              SKIP_SPACES (str);
-
-              coords[(i * 3) + 2] = g_ascii_strtod (str, &end);
-              str = end;
-
-              SKIP_SPACES (str);
-            }
-        }
-
-      if (type == CAIRO_PATTERN_TYPE_LINEAR)
-        gradient = gtk_gradient_new_linear (coords[0], coords[1], coords[3], coords[4]);
-      else
-        gradient = gtk_gradient_new_radial (coords[0], coords[1], coords[2],
-                                            coords[3], coords[4], coords[5]);
-
-      while (*str == ',')
-        {
-          GtkSymbolicColor *color;
-          gdouble position;
-
-          if (*str != ',')
-            {
-              *end_ptr = (gchar *) str;
-              return gradient;
-            }
-
-          str++;
-          SKIP_SPACES (str);
-
-          if (g_str_has_prefix (str, "from"))
-            {
-              position = 0;
-              str += strlen ("from");
-              SKIP_SPACES (str);
-
-              if (*str != '(')
-                {
-                  *end_ptr = (gchar *) str;
-                  return gradient;
-                }
-            }
-          else if (g_str_has_prefix (str, "to"))
-            {
-              position = 1;
-              str += strlen ("to");
-              SKIP_SPACES (str);
-
-              if (*str != '(')
-                {
-                  *end_ptr = (gchar *) str;
-                  return gradient;
-                }
-            }
-          else if (g_str_has_prefix (str, "color-stop"))
-            {
-              str += strlen ("color-stop");
-              SKIP_SPACES (str);
-
-              if (*str != '(')
-                {
-                  *end_ptr = (gchar *) str;
-                  return gradient;
-                }
-
-              str++;
-              SKIP_SPACES (str);
-
-              position = g_ascii_strtod (str, &end);
-
-              str = end;
-              SKIP_SPACES (str);
-
-              if (*str != ',')
-                {
-                  *end_ptr = (gchar *) str;
-                  return gradient;
-                }
-            }
-          else
-            {
-              *end_ptr = (gchar *) str;
-              return gradient;
-            }
-
-          str++;
-          SKIP_SPACES (str);
-
-          color = symbolic_color_parse_str (str, &end);
-
-          str = end;
-          SKIP_SPACES (str);
-
-          if (*str != ')')
-            {
-              *end_ptr = (gchar *) str;
-              return gradient;
-            }
-
-          str++;
-          SKIP_SPACES (str);
-
-          if (color)
-            {
-              gtk_gradient_add_color_stop (gradient, position, color);
-              gtk_symbolic_color_unref (color);
-            }
-        }
-
-      if (*str != ')')
-        {
-          *end_ptr = (gchar *) str;
-          return gradient;
-        }
-
-      str++;
-    }
-
-  *end_ptr = (gchar *) str;
-
-  return gradient;
-}
-
-static gchar *
-path_parse_str (GtkCssProvider  *css_provider,
-                const gchar     *str,
-                gchar          **end_ptr,
-                GError         **error)
-{
-  gchar *path, *chr;
-  const gchar *start, *end;
-  start = str;
-
-  if (g_str_has_prefix (str, "url"))
-    {
-      str += strlen ("url");
-      SKIP_SPACES (str);
-
-      if (*str != '(')
-        {
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      chr = strchr (str, ')');
-      if (!chr)
-        {
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      end = chr + 1;
-
-      str++;
-      SKIP_SPACES (str);
-
-      if (*str == '"' || *str == '\'')
-        {
-          const gchar *p;
-          p = str;
-          str++;
-
-          chr--;
-          SKIP_SPACES_BACK (chr);
-
-          if (*chr != *p || chr == p)
-            {
-              *end_ptr = (gchar *)str;
-              return NULL;
-            }
-        }
-      else
-        {
-          *end_ptr = (gchar *)str;
-          return NULL;
-        }
-
-      path = g_strndup (str, chr - str);
-      g_strstrip (path);
-
-      *end_ptr = (gchar *)end;
-    }
-  else
-    {
-      path = g_strdup (str);
-      *end_ptr = (gchar *)str + strlen (str);
-    }
-
-  /* Always return an absolute path */
-  if (!g_path_is_absolute (path))
-    {
-      GtkCssProviderPrivate *priv;
-      gchar *dirname, *full_path;
-
-      priv = css_provider->priv;
-
-      /* Use relative path to the current CSS file path, if any */
-      if (priv->filename)
-        dirname = g_path_get_dirname (priv->filename);
-      else
-        dirname = g_get_current_dir ();
-
-      full_path = g_build_filename (dirname, path, NULL);
-      g_free (path);
-      g_free (dirname);
-
-      path = full_path;
-    }
-
-  if (!g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))
-    {
-      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_EXIST,
-                   "File doesn't exist: %s", path);
-      g_free (path);
-      path = NULL;
-      *end_ptr = (gchar *)start;
-    }
-
-  return path;
-}
-
-static gchar *
-path_parse (GtkCssProvider  *css_provider,
-            const gchar     *str,
-            GError         **error)
-{
-  gchar *path;
-  gchar *end;
-
-  path = path_parse_str (css_provider, str, &end, error);
-
-  if (!path)
-    return NULL;
-
-  if (*end != '\0')
-    {
-      g_set_error_literal (error,
-                           GTK_CSS_PROVIDER_ERROR,
-                           GTK_CSS_PROVIDER_ERROR_FAILED,
-                           "Error parsing path");
-      g_free (path);
-      path = NULL;
-    }
-
-  return path;
-}
-
-static Gtk9Slice *
-slice_parse_str (GtkCssProvider  *css_provider,
-                 const gchar     *str,
-                 gchar          **end_ptr,
-                 GError         **error)
-{
-  gdouble distance_top, distance_bottom;
-  gdouble distance_left, distance_right;
-  GtkSliceSideModifier mods[2];
-  GdkPixbuf *pixbuf;
-  Gtk9Slice *slice;
-  gchar *path;
-  gint i = 0;
-
-  SKIP_SPACES (str);
-
-  /* Parse image url */
-  path = path_parse_str (css_provider, str, end_ptr, error);
-
-  if (!path)
-      return NULL;
-
-  str = *end_ptr;
-  SKIP_SPACES (str);
-
-  /* Parse top/left/bottom/right distances */
-  distance_top = g_ascii_strtod (str, end_ptr);
-
-  str = *end_ptr;
-  SKIP_SPACES (str);
-
-  distance_right = g_ascii_strtod (str, end_ptr);
-
-  str = *end_ptr;
-  SKIP_SPACES (str);
-
-  distance_bottom = g_ascii_strtod (str, end_ptr);
-
-  str = *end_ptr;
-  SKIP_SPACES (str);
-
-  distance_left = g_ascii_strtod (str, end_ptr);
-
-  str = *end_ptr;
-  SKIP_SPACES (str);
+          /* FIXME: Avoid recursive importing */
+          loaded = gtk_css_provider_load_from_path_internal (css_provider, path,
+                                                             FALSE, NULL);
 
-  while (*str && i < 2)
-    {
-      if (g_str_has_prefix (str, "stretch"))
-        {
-          str += strlen ("stretch");
-          mods[i] = GTK_SLICE_STRETCH;
-        }
-      else if (g_str_has_prefix (str, "repeat"))
-        {
-          str += strlen ("repeat");
-          mods[i] = GTK_SLICE_REPEAT;
-        }
-      else
-        {
+          /* Restore previous state */
           g_free (path);
-          *end_ptr = (gchar *) str;
-          return NULL;
-        }
-
-      SKIP_SPACES (str);
-      i++;
-    }
-
-  *end_ptr = (gchar *) str;
-
-  if (*str != '\0')
-    {
-      g_free (path);
-      return NULL;
-    }
-
-  if (i != 2)
-    {
-      /* Fill in second modifier, same as the first */
-      mods[1] = mods[0];
-    }
-
-  pixbuf = gdk_pixbuf_new_from_file (path, error);
-  g_free (path);
-
-  if (!pixbuf)
-    {
-      *end_ptr = (gchar *) str;
-      return NULL;
-    }
-
-  slice = _gtk_9slice_new (pixbuf,
-                           distance_top, distance_bottom,
-                           distance_left, distance_right,
-                           mods[0], mods[1]);
-  g_object_unref (pixbuf);
-
-  return slice;
-}
-
-static gdouble
-unit_parse_str (const gchar     *str,
-                gchar          **end_str)
-{
-  gdouble unit;
-
-  SKIP_SPACES (str);
-  unit = g_ascii_strtod (str, end_str);
-  str = *end_str;
-
-  /* Now parse the unit type, if any. We
-   * don't admit spaces between these.
-   */
-  if (*str != ' ' && *str != '\0')
-    {
-      while (**end_str != ' ' && **end_str != '\0')
-        (*end_str)++;
-
-      /* Only handle pixels at the moment */
-      if (strncmp (str, "px", 2) != 0)
-        {
-          gchar *type;
-
-          type = g_strndup (str, *end_str - str);
-          g_warning ("Unknown unit '%s', only pixel units are "
-                     "currently supported in CSS style", type);
-          g_free (type);
-        }
-    }
-
-  return unit;
-}
-
-static GtkBorder *
-border_parse_str (const gchar  *str,
-                  gchar       **end_str)
-{
-  gdouble first, second, third, fourth;
-  GtkBorder *border;
-
-  border = gtk_border_new ();
-
-  SKIP_SPACES (str);
-  if (!g_ascii_isdigit (*str) && *str != '-')
-    return border;
-
-  first = unit_parse_str (str, end_str);
-  str = *end_str;
-  SKIP_SPACES (str);
-
-  if (!g_ascii_isdigit (*str) && *str != '-')
-    {
-      border->left = border->right = border->top = border->bottom = (gint) first;
-      *end_str = (gchar *) str;
-      return border;
-    }
-
-  second = unit_parse_str (str, end_str);
-  str = *end_str;
-  SKIP_SPACES (str);
-
-  if (!g_ascii_isdigit (*str) && *str != '-')
-    {
-      border->top = border->bottom = (gint) first;
-      border->left = border->right = (gint) second;
-      *end_str = (gchar *) str;
-      return border;
-    }
-
-  third = unit_parse_str (str, end_str);
-  str = *end_str;
-  SKIP_SPACES (str);
-
-  if (!g_ascii_isdigit (*str) && *str != '-')
-    {
-      border->top = (gint) first;
-      border->left = border->right = (gint) second;
-      border->bottom = (gint) third;
-      *end_str = (gchar *) str;
-      return border;
-    }
-
-  fourth = unit_parse_str (str, end_str);
-
-  border->top = (gint) first;
-  border->right = (gint) second;
-  border->bottom = (gint) third;
-  border->left = (gint) fourth;
-
-  return border;
-}
-
-static gboolean
-css_provider_parse_value (GtkCssProvider  *css_provider,
-                          const gchar     *value_str,
-                          GValue          *value,
-                          GError         **error)
-{
-  GtkCssProviderPrivate *priv;
-  GType type;
-  gboolean parsed = TRUE;
-  gchar *end = NULL;
-
-  priv = css_provider->priv;
-  type = G_VALUE_TYPE (value);
-
-  if (type == GDK_TYPE_RGBA ||
-      type == GDK_TYPE_COLOR)
-    {
-      GdkRGBA rgba;
-      GdkColor color;
-
-      if (type == GDK_TYPE_RGBA &&
-          gdk_rgba_parse (&rgba, value_str))
-        g_value_set_boxed (value, &rgba);
-      else if (type == GDK_TYPE_COLOR &&
-               gdk_color_parse (value_str, &color))
-        g_value_set_boxed (value, &color);
-      else
-        {
-          GtkSymbolicColor *symbolic_color;
-
-          symbolic_color = symbolic_color_parse_str (value_str, &end);
-
-          if (symbolic_color)
-            {
-              g_value_unset (value);
-              g_value_init (value, GTK_TYPE_SYMBOLIC_COLOR);
-              g_value_take_boxed (value, symbolic_color);
-            }
-          else
-            parsed = FALSE;
-        }
-    }
-  else if (type == PANGO_TYPE_FONT_DESCRIPTION)
-    {
-      PangoFontDescription *font_desc;
-
-      font_desc = pango_font_description_from_string (value_str);
-      g_value_take_boxed (value, font_desc);
-    }
-  else if (type == G_TYPE_BOOLEAN)
-    {
-      if (value_str[0] == '1' ||
-          g_ascii_strcasecmp (value_str, "true") == 0)
-        g_value_set_boolean (value, TRUE);
-      else
-        g_value_set_boolean (value, FALSE);
-    }
-  else if (type == G_TYPE_INT)
-    g_value_set_int (value, atoi (value_str));
-  else if (type == G_TYPE_UINT)
-    g_value_set_uint (value, (guint) atoi (value_str));
-  else if (type == G_TYPE_DOUBLE)
-    g_value_set_double (value, g_ascii_strtod (value_str, NULL));
-  else if (type == G_TYPE_FLOAT)
-    g_value_set_float (value, (gfloat) g_ascii_strtod (value_str, NULL));
-  else if (type == GTK_TYPE_THEMING_ENGINE)
-    {
-      GtkThemingEngine *engine;
-
-      engine = gtk_theming_engine_load (value_str);
-      if (engine)
-        g_value_set_object (value, engine);
-      else
-        parsed = FALSE;
-    }
-  else if (type == GTK_TYPE_ANIMATION_DESCRIPTION)
-    {
-      GtkAnimationDescription *desc;
-
-      desc = _gtk_animation_description_from_string (value_str);
-
-      if (desc)
-        g_value_take_boxed (value, desc);
-      else
-        parsed = FALSE;
-    }
-  else if (type == GTK_TYPE_BORDER)
-    {
-      GtkBorder *border;
-
-      border = border_parse_str (value_str, &end);
-      g_value_take_boxed (value, border);
-    }
-  else if (type == CAIRO_GOBJECT_TYPE_PATTERN)
-    {
-      GtkGradient *gradient;
-
-      gradient = gradient_parse_str (value_str, &end);
-
-      if (gradient)
-        {
-          g_value_unset (value);
-          g_value_init (value, GTK_TYPE_GRADIENT);
-          g_value_take_boxed (value, gradient);
-        }
-      else
-        {
-          gchar *path;
-          GdkPixbuf *pixbuf;
-
-          g_clear_error (error);
-          path = path_parse_str (css_provider, value_str, &end, error);
-
-          if (path)
-            {
-              pixbuf = gdk_pixbuf_new_from_file (path, NULL);
-              g_free (path);
-
-              if (pixbuf)
-                {
-                  cairo_surface_t *surface;
-                  cairo_pattern_t *pattern;
-                  cairo_t *cr;
-                  cairo_matrix_t matrix;
-
-                  surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
-                                                        gdk_pixbuf_get_width (pixbuf),
-                                                        gdk_pixbuf_get_height (pixbuf));
-                  cr = cairo_create (surface);
-                  gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
-                  cairo_paint (cr);
-                  pattern = cairo_pattern_create_for_surface (surface);
-
-                  cairo_matrix_init_scale (&matrix,
-                                           gdk_pixbuf_get_width (pixbuf),
-                                           gdk_pixbuf_get_height (pixbuf));
-                  cairo_pattern_set_matrix (pattern, &matrix);
-
-                  cairo_surface_destroy (surface);
-                  cairo_destroy (cr);
-                  g_object_unref (pixbuf);
-
-                  g_value_take_boxed (value, pattern);
-                }
-              else
-                parsed = FALSE;
-            }
-          else
-            parsed = FALSE;
-        }
-    }
-  else if (G_TYPE_IS_ENUM (type))
-    {
-      GEnumClass *enum_class;
-      GEnumValue *enum_value;
-
-      enum_class = g_type_class_ref (type);
-      enum_value = g_enum_get_value_by_nick (enum_class, value_str);
-
-      if (!enum_value)
-        {
-          g_set_error (error,
-                       GTK_CSS_PROVIDER_ERROR,
-                       GTK_CSS_PROVIDER_ERROR_FAILED,
-                       "Unknown value '%s' for enum type '%s'",
-                       value_str, g_type_name (type));
-          parsed = FALSE;
-        }
-      else
-        g_value_set_enum (value, enum_value->value);
-
-      g_type_class_unref (enum_class);
-    }
-  else if (G_TYPE_IS_FLAGS (type))
-    {
-      GFlagsClass *flags_class;
-      GFlagsValue *flag_value;
-      guint flags = 0;
-      gchar *ptr;
-
-      flags_class = g_type_class_ref (type);
-
-      /* Parse comma separated values */
-      ptr = strchr (value_str, ',');
-
-      while (ptr && parsed)
-        {
-          gchar *flag_str;
-
-          *ptr = '\0';
-          ptr++;
 
-          flag_str = (gchar *) value_str;
-          flag_value = g_flags_get_value_by_nick (flags_class,
-                                                  g_strstrip (flag_str));
-
-          if (!flag_value)
-            {
-              g_set_error (error,
-                           GTK_CSS_PROVIDER_ERROR,
-                           GTK_CSS_PROVIDER_ERROR_FAILED,
-                           "Unknown flag '%s' for type '%s'",
-                           value_str, g_type_name (type));
-              parsed = FALSE;
-            }
+          if (!loaded)
+            return G_TOKEN_IDENTIFIER;
           else
-            flags |= flag_value->value;
-
-          value_str = ptr;
-          ptr = strchr (value_str, ',');
-        }
-
-      /* Store last/only value */
-      flag_value = g_flags_get_value_by_nick (flags_class, value_str);
-
-      if (!flag_value)
-        {
-          g_set_error (error,
-                       GTK_CSS_PROVIDER_ERROR,
-                       GTK_CSS_PROVIDER_ERROR_FAILED,
-                       "Unknown flag '%s' for type '%s'",
-                       value_str, g_type_name (type));
-          parsed = FALSE;
+            return G_TOKEN_NONE;
         }
-      else
-        flags |= flag_value->value;
-
-      if (parsed)
-        g_value_set_enum (value, flags);
-
-      g_type_class_unref (flags_class);
-    }
-  else if (type == GTK_TYPE_9SLICE)
-    {
-      Gtk9Slice *slice;
-
-      slice = slice_parse_str (css_provider, value_str, &end, error);
-
-      if (slice)
-        g_value_take_boxed (value, slice);
-      else
-        parsed = FALSE;
-    }
-  else
-    {
-      g_set_error (error,
-                   GTK_CSS_PROVIDER_ERROR,
-                   GTK_CSS_PROVIDER_ERROR_FAILED,
-                   "Cannot parse string '%s' for type %s",
-                   value_str, g_type_name (type));
-      parsed = FALSE;
-    }
-
-  if (end && *end)
-    {
-      /* Set error position in the scanner
-       * according to what we've parsed so far
-       */
-      priv->value_pos += (end - value_str);
-
-      if (error && !*error)
-        g_set_error_literal (error,
-                             GTK_CSS_PROVIDER_ERROR,
-                             GTK_CSS_PROVIDER_ERROR_FAILED,
-                             "Failed to parse value");
-    }
-
-  return parsed;
-}
-
-static void
-scanner_report_warning (GtkCssProvider *css_provider,
-                        GTokenType      expected_token,
-                        GError         *error)
-{
-  GtkCssProviderPrivate *priv;
-  const gchar *line_end, *line_start;
-  const gchar *expected_str;
-  gchar buf[2], *line, *str;
-  guint pos;
-
-  priv = css_provider->priv;
-
-  if (error)
-    str = g_strdup (error->message);
-  else
-    {
-      if (priv->scanner->user_data)
-        expected_str = priv->scanner->user_data;
-      else
+      else if (strcmp (directive, "binding-set") == 0)
         {
-          switch (expected_token)
-            {
-            case G_TOKEN_SYMBOL:
-              expected_str = "Symbol";
-            case G_TOKEN_IDENTIFIER:
-              expected_str = "Identifier";
-            default:
-              buf[0] = expected_token;
-              buf[1] = '\0';
-              expected_str = buf;
-            }
-        }
-
-      str = g_strdup_printf ("Parse error, expecting a %s '%s'",
-                             (expected_str != buf) ? "valid" : "",
-                             expected_str);
-    }
-
-  if (priv->value_pos)
-    line_start = priv->value_pos - 1;
-  else
-    line_start = priv->scanner->text - 1;
-
-  while (*line_start != '\n' &&
-         line_start != priv->buffer)
-    line_start--;
-
-  if (*line_start == '\n')
-    line_start++;
+          GtkBindingSet *binding_set;
+          gchar *binding_set_name;
 
-  if (priv->value_pos)
-    pos = priv->value_pos - line_start + 1;
-  else
-    pos = priv->scanner->text - line_start - 1;
-
-  line_end = strchr (line_start, '\n');
-
-  if (line_end)
-    line = g_strndup (line_start, (line_end - line_start));
-  else
-    line = g_strdup (line_start);
-
-  g_message ("CSS: %s\n"
-             "%s, line %d, char %d:\n"
-             "%*c %s\n"
-             "%*c ^",
-             str, priv->scanner->input_name,
-             priv->scanner->line, priv->scanner->position,
-             3, ' ', line,
-             3 + pos, ' ');
-
-  g_free (line);
-  g_free (str);
-}
-
-static GTokenType
-parse_rule (GtkCssProvider  *css_provider,
-            GScanner        *scanner,
-            GError         **error)
-{
-  GtkCssProviderPrivate *priv;
-  GTokenType expected_token;
-  SelectorPath *selector;
-
-  priv = css_provider->priv;
-
-  css_provider_push_scope (css_provider, SCOPE_SELECTOR);
-
-  /* Handle directives */
-  if (scanner->token == G_TOKEN_IDENTIFIER &&
-      scanner->value.v_identifier[0] == '@')
-    {
-      gchar *directive;
-
-      directive = &scanner->value.v_identifier[1];
-
-      if (strcmp (directive, "define-color") == 0)
-        {
-          GtkSymbolicColor *color;
-          gchar *color_name, *color_str;
-
-          /* Directive is a color mapping */
           g_scanner_get_next_token (scanner);
 
           if (scanner->token != G_TOKEN_IDENTIFIER)
             {
-              scanner->user_data = "Color name";
+              gtk_css_provider_invalid_token (css_provider, scanner, "Binding name");
               return G_TOKEN_IDENTIFIER;
             }
 
-          color_name = g_strdup (scanner->value.v_identifier);
-          css_provider_push_scope (css_provider, SCOPE_VALUE);
-          g_scanner_get_next_token (scanner);
+          binding_set_name = scanner->value.v_identifier;
+          binding_set = gtk_binding_set_find (binding_set_name);
 
-          if (scanner->token != G_TOKEN_IDENTIFIER)
+          if (!binding_set)
             {
-              scanner->user_data = "Color definition";
-              return G_TOKEN_IDENTIFIER;
+              binding_set = gtk_binding_set_new (binding_set_name);
+              binding_set->parsed = TRUE;
             }
 
-          color_str = g_strstrip (scanner->value.v_identifier);
-          color = symbolic_color_parse (color_str, error);
-
-          if (!color)
-            {
-              scanner->user_data = "Color definition";
-              return G_TOKEN_IDENTIFIER;
-            }
-
-          g_hash_table_insert (priv->symbolic_colors, color_name, color);
-
-          css_provider_pop_scope (css_provider);
           g_scanner_get_next_token (scanner);
 
-          if (scanner->token != ';')
-            return ';';
+          if (scanner->token != G_TOKEN_LEFT_CURLY)
+            return G_TOKEN_LEFT_CURLY;
 
-          return G_TOKEN_NONE;
-        }
-      else if (strcmp (directive, "import") == 0)
-        {
-          GScanner *scanner_backup;
-          GSList *state_backup;
-          gboolean loaded;
-          gchar *path = NULL;
-
-          css_provider_push_scope (css_provider, SCOPE_VALUE);
+          gtk_css_scanner_push_scope (scanner, SCOPE_BINDING_SET);
           g_scanner_get_next_token (scanner);
 
-          if (scanner->token == G_TOKEN_IDENTIFIER &&
-              g_str_has_prefix (scanner->value.v_identifier, "url"))
-            path = path_parse (css_provider,
-                               g_strstrip (scanner->value.v_identifier),
-                               error);
-          else if (scanner->token == G_TOKEN_STRING)
-            path = path_parse (css_provider,
-                               g_strstrip (scanner->value.v_string),
-                               error);
-
-          if (path == NULL)
-            {
-              scanner->user_data = "File URL";
-              return G_TOKEN_IDENTIFIER;
-            }
-
-          css_provider_pop_scope (css_provider);
-          g_scanner_get_next_token (scanner);
-
-          if (scanner->token != ';')
+          do
             {
-              g_free (path);
-              return ';';
-            }
-
-          /* Snapshot current parser state and scanner in order to restore after importing */
-          state_backup = priv->state;
-          scanner_backup = priv->scanner;
+              GTokenType ret;
 
-          priv->state = NULL;
-          priv->scanner = create_scanner ();
+              if (scanner->token != G_TOKEN_IDENTIFIER)
+                {
+                  gtk_css_provider_invalid_token (css_provider, scanner, "Binding definition");
+                  return G_TOKEN_IDENTIFIER;
+                }
 
-          /* FIXME: Avoid recursive importing */
-          loaded = gtk_css_provider_load_from_path_internal (css_provider, path,
-                                                             FALSE, error);
+              ret = gtk_binding_entry_add_signal_from_string (binding_set,
+                                                              scanner->value.v_identifier);
+              if (ret != G_TOKEN_NONE)
+                {
+                  gtk_css_provider_invalid_token (css_provider, scanner, "Binding definition");
+                  return ret;
+                }
 
-          /* Restore previous state */
-          css_provider_reset_parser (css_provider);
-          priv->state = state_backup;
-          g_scanner_destroy (priv->scanner);
-          priv->scanner = scanner_backup;
+              g_scanner_get_next_token (scanner);
 
-          g_free (path);
+              if (scanner->token != ';')
+                return ';';
 
-          if (!loaded)
-            {
-              scanner->user_data = "File URL";
-              return G_TOKEN_IDENTIFIER;
+              g_scanner_get_next_token (scanner);
             }
-          else
-            return G_TOKEN_NONE;
+          while (scanner->token != G_TOKEN_RIGHT_CURLY);
+
+          gtk_css_scanner_pop_scope (scanner);
+          g_scanner_get_next_token (scanner);
+
+          return G_TOKEN_NONE;
         }
       else
         {
-          scanner->user_data = "Directive";
+          gtk_css_provider_invalid_token (css_provider, scanner, "Directive");
           return G_TOKEN_IDENTIFIER;
         }
     }
@@ -3243,7 +2350,7 @@ parse_rule (GtkCssProvider  *css_provider,
   if (expected_token != G_TOKEN_NONE)
     {
       selector_path_unref (selector);
-      scanner->user_data = "Selector";
+      gtk_css_provider_invalid_token (css_provider, scanner, "Selector");
       return expected_token;
     }
 
@@ -3258,55 +2365,106 @@ parse_rule (GtkCssProvider  *css_provider,
       if (expected_token != G_TOKEN_NONE)
         {
           selector_path_unref (selector);
-          scanner->user_data = "Selector";
+          gtk_css_provider_invalid_token (css_provider, scanner, "Selector");
           return expected_token;
         }
 
       priv->cur_selectors = g_slist_prepend (priv->cur_selectors, selector);
     }
 
-  css_provider_pop_scope (css_provider);
+  gtk_css_scanner_pop_scope (scanner);
 
   if (scanner->token != G_TOKEN_LEFT_CURLY)
     return G_TOKEN_LEFT_CURLY;
 
   /* Declarations parsing */
-  css_provider_push_scope (css_provider, SCOPE_DECLARATION);
-  g_scanner_get_next_token (scanner);
+  gtk_css_scanner_push_scope (scanner, SCOPE_DECLARATION);
 
-  while (scanner->token == G_TOKEN_IDENTIFIER)
+  g_scanner_get_next_token (scanner);
+    
+  while (scanner->token != G_TOKEN_RIGHT_CURLY &&
+         !g_scanner_eof (scanner))
     {
       gchar *value_str = NULL;
       GtkStylePropertyParser parse_func = NULL;
-      GParamSpec *pspec;
+      GParamSpec *pspec = NULL;;
       gchar *prop;
 
+      if (scanner->token == ';')
+        {
+          g_scanner_get_next_token (scanner);
+          continue;
+        }
+
+      if (scanner->token != G_TOKEN_IDENTIFIER)
+        {
+          gtk_css_provider_error_literal (css_provider,
+                                          scanner,
+                                          GTK_CSS_PROVIDER_ERROR,
+                                          GTK_CSS_PROVIDER_ERROR_PROPERTY_NAME,
+                                          "Expected a valid property name");
+          goto find_end_of_declaration;
+        }
+
       prop = g_strdup (scanner->value.v_identifier);
+
+      if (!gtk_style_properties_lookup_property (prop, &parse_func, &pspec) &&
+          prop[0] != '-')
+        {
+          gtk_css_provider_error (css_provider,
+                                  scanner,
+                                  GTK_CSS_PROVIDER_ERROR,
+                                  GTK_CSS_PROVIDER_ERROR_PROPERTY_NAME,
+                                  "'%s' is not a valid property name",
+                                  prop);
+          g_free (prop);
+          goto find_end_of_declaration;
+        }
+
       g_scanner_get_next_token (scanner);
 
       if (scanner->token != ':')
         {
           g_free (prop);
-          return ':';
+          gtk_css_provider_invalid_token (css_provider, scanner, "':'");
+          goto find_end_of_declaration;
         }
 
-      priv->value_pos = priv->scanner->text;
-
-      css_provider_push_scope (css_provider, SCOPE_VALUE);
+      gtk_css_scanner_push_scope (scanner, SCOPE_VALUE);
       g_scanner_get_next_token (scanner);
 
       if (scanner->token != G_TOKEN_IDENTIFIER)
         {
           g_free (prop);
-          scanner->user_data = "Property value";
-          return G_TOKEN_IDENTIFIER;
+          /* the error value here is hacky. But strings should be used for
+           * strings really, so a string is not a syntax error but a broken
+           * value for everything that we support. */
+          gtk_css_provider_error (css_provider,
+                                  scanner,
+                                  GTK_CSS_PROVIDER_ERROR,
+                                  scanner->token == G_TOKEN_STRING ? GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE
+                                                                   : GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                  "Not a property value");
+          gtk_css_scanner_pop_scope (scanner);
+          goto find_end_of_declaration;
         }
 
       value_str = scanner->value.v_identifier;
-      SKIP_SPACES (value_str);
       g_strchomp (value_str);
 
-      if (gtk_style_properties_lookup_property (prop, &parse_func, &pspec))
+      gtk_css_scanner_pop_scope (scanner);
+      g_scanner_peek_next_token (scanner);
+
+      if (scanner->next_token != ';' &&
+          scanner->next_token != G_TOKEN_RIGHT_CURLY &&
+          scanner->next_token != G_TOKEN_EOF)
+        {
+          gtk_css_provider_invalid_token (css_provider, scanner, "';'");
+          g_free (prop);
+          goto find_end_of_declaration;
+        }
+
+      if (pspec)
         {
           GValue *val;
 
@@ -3321,22 +2479,60 @@ parse_rule (GtkCssProvider  *css_provider,
               g_param_value_set_default (pspec, val);
               g_hash_table_insert (priv->cur_properties, prop, val);
             }
+          else if (strcmp (prop, "gtk-key-bindings") == 0)
+            {
+              /* Private property holding the binding sets */
+              resolve_binding_sets (value_str, val);
+              g_hash_table_insert (priv->cur_properties, prop, val);
+            }
           else if (pspec->value_type == G_TYPE_STRING)
             {
               g_value_set_string (val, value_str);
               g_hash_table_insert (priv->cur_properties, prop, val);
             }
-          else if ((parse_func && (parse_func) (value_str, val, error)) ||
-                   (!parse_func && css_provider_parse_value (css_provider, value_str, val, error)))
-            g_hash_table_insert (priv->cur_properties, prop, val);
+          else if (parse_func)
+            {
+              GError *error = NULL;
+              
+              if ((*parse_func) (value_str, val, &error))
+                g_hash_table_insert (priv->cur_properties, prop, val);
+              else
+                gtk_css_provider_take_error (css_provider, scanner, error);
+            }
           else
             {
-              g_value_unset (val);
-              g_slice_free (GValue, val);
-              g_free (prop);
+              GError *error = NULL;
+              GFile *base;
+              char *dirname;
+              gboolean success;
 
-              scanner->user_data = "Property value";
-              return G_TOKEN_IDENTIFIER;
+              if (scanner->input_name)
+                dirname = g_path_get_dirname (scanner->input_name);
+              else
+                dirname = g_get_current_dir ();
+
+              base = g_file_new_for_path (dirname);
+              g_free (dirname);
+
+              success = _gtk_css_value_from_string (val,
+                                                    base,
+                                                    value_str,
+                                                    &error);
+
+              g_object_unref (base);
+
+              if (success)
+                {
+                  g_hash_table_insert (priv->cur_properties, prop, val);
+                }
+              else
+                {
+                  g_value_unset (val);
+                  g_slice_free (GValue, val);
+                  g_free (prop);
+
+                  gtk_css_provider_take_error (css_provider, scanner, error);
+                }
             }
         }
       else if (prop[0] == '-')
@@ -3352,77 +2548,103 @@ parse_rule (GtkCssProvider  *css_provider,
       else
         g_free (prop);
 
-      css_provider_pop_scope (css_provider);
       g_scanner_get_next_token (scanner);
 
-      if (scanner->token != ';')
-        break;
+      if (g_scanner_eof (scanner))
+        {
+          gtk_css_provider_invalid_token (css_provider, scanner, "}");
+          break;
+        }
 
-      g_scanner_get_next_token (scanner);
+find_end_of_declaration:
+      while (scanner->token != ';' &&
+             scanner->token != G_TOKEN_RIGHT_CURLY &&
+             !g_scanner_eof (scanner))
+        g_scanner_get_next_token (scanner);
     }
 
-  if (scanner->token != G_TOKEN_RIGHT_CURLY)
-    return G_TOKEN_RIGHT_CURLY;
-
-  css_provider_pop_scope (css_provider);
+  gtk_css_scanner_pop_scope (scanner);
 
   return G_TOKEN_NONE;
 }
 
+static void
+gtk_css_provider_reset (GtkCssProvider *css_provider)
+{
+  GtkCssProviderPrivate *priv;
+
+  priv = css_provider->priv;
+
+  if (priv->selectors_info->len > 0)
+    g_ptr_array_remove_range (priv->selectors_info, 0, priv->selectors_info->len);
+}
+
+static void
+gtk_css_provider_propagate_error (GtkCssProvider  *provider,
+                                  const gchar     *path,
+                                  guint            line,
+                                  guint            position,
+                                  const GError    *error,
+                                  GError         **propagate_to)
+{
+  /* we already set an error. And we'd like to keep the first one */
+  if (*propagate_to)
+    return;
+
+  *propagate_to = g_error_copy (error);
+  g_prefix_error (propagate_to, "%s:%u:%u: ", path ? path : "<unknown>", line, position);
+}
+
 static gboolean
 parse_stylesheet (GtkCssProvider  *css_provider,
+                  GScanner        *scanner,
                   GError         **error)
 {
-  GtkCssProviderPrivate *priv;
-  gboolean result;
+  gulong error_handler;
 
-  result = TRUE;
+  if (error)
+    error_handler = g_signal_connect (css_provider,
+                                      "parsing-error",
+                                      G_CALLBACK (gtk_css_provider_propagate_error),
+                                      error);
+  else
+    error_handler = 0; /* silence gcc */
 
-  priv = css_provider->priv;
-  g_scanner_get_next_token (priv->scanner);
+  g_scanner_get_next_token (scanner);
 
-  while (!g_scanner_eof (priv->scanner))
+  while (!g_scanner_eof (scanner))
     {
       GTokenType expected_token;
-      GError *err = NULL;
 
-      css_provider_reset_parser (css_provider);
-      expected_token = parse_rule (css_provider, priv->scanner, &err);
+      expected_token = parse_rule (css_provider, scanner);
 
       if (expected_token != G_TOKEN_NONE)
         {
-          /* If a GError was passed in, propagate the error and bail out,
-           * else report a warning and keep going
-           */
-          if (error != NULL)
-            {
-              result = FALSE;
-              if (err)
-                g_propagate_error (error, err);
-              else
-                g_set_error_literal (error,
-                                     GTK_CSS_PROVIDER_ERROR,
-                                     GTK_CSS_PROVIDER_ERROR_FAILED,
-                                     "Error parsing stylesheet");
-              break;
-            }
-          else
-            {
-              scanner_report_warning (css_provider, expected_token, err);
-              g_clear_error (&err);
-            }
-
-          while (!g_scanner_eof (priv->scanner) &&
-                 priv->scanner->token != G_TOKEN_RIGHT_CURLY)
-            g_scanner_get_next_token (priv->scanner);
+          while (!g_scanner_eof (scanner) &&
+                 scanner->token != G_TOKEN_RIGHT_CURLY)
+            g_scanner_get_next_token (scanner);
         }
       else
-        css_provider_commit (css_provider);
+        css_provider_commit (css_provider, scanner);
+
+      g_scanner_get_next_token (scanner);
+      
+      gtk_css_scanner_reset (scanner);
+    }
 
-      g_scanner_get_next_token (priv->scanner);
+  if (error)
+    {
+      g_signal_handler_disconnect (css_provider, error_handler);
+      
+      if (*error)
+        {
+          /* We clear all contents from the provider for backwards compat reasons */
+          gtk_css_provider_reset (css_provider);
+          return FALSE;
+        }
     }
 
-  return result;
+  return TRUE;
 }
 
 /**
@@ -3443,28 +2665,24 @@ gtk_css_provider_load_from_data (GtkCssProvider  *css_provider,
                                  gssize           length,
                                  GError         **error)
 {
-  GtkCssProviderPrivate *priv;
+  GScanner *scanner;
+  gboolean result;
 
   g_return_val_if_fail (GTK_IS_CSS_PROVIDER (css_provider), FALSE);
   g_return_val_if_fail (data != NULL, FALSE);
 
-  priv = css_provider->priv;
-
   if (length < 0)
     length = strlen (data);
 
-  if (priv->selectors_info->len > 0)
-    g_ptr_array_remove_range (priv->selectors_info, 0, priv->selectors_info->len);
+  gtk_css_provider_reset (css_provider);
+
+  scanner = gtk_css_scanner_new (NULL, data, length);
 
-  priv->scanner->input_name = "-";
-  priv->buffer = data;
-  g_scanner_input_text (priv->scanner, data, (guint) length);
+  result = parse_stylesheet (css_provider, scanner, error);
 
-  g_free (priv->filename);
-  priv->filename = NULL;
-  priv->buffer = NULL;
+  gtk_css_scanner_destroy (scanner);
 
-  return parse_stylesheet (css_provider, error);
+  return result;
 }
 
 /**
@@ -3483,8 +2701,8 @@ gtk_css_provider_load_from_file (GtkCssProvider  *css_provider,
                                  GFile           *file,
                                  GError         **error)
 {
-  GtkCssProviderPrivate *priv;
   GError *internal_error = NULL;
+  GScanner *scanner;
   gchar *data;
   gsize length;
   gboolean ret;
@@ -3492,8 +2710,6 @@ gtk_css_provider_load_from_file (GtkCssProvider  *css_provider,
   g_return_val_if_fail (GTK_IS_CSS_PROVIDER (css_provider), FALSE);
   g_return_val_if_fail (G_IS_FILE (file), FALSE);
 
-  priv = css_provider->priv;
-
   if (!g_file_load_contents (file, NULL,
                              &data, &length,
                              NULL, &internal_error))
@@ -3502,20 +2718,14 @@ gtk_css_provider_load_from_file (GtkCssProvider  *css_provider,
       return FALSE;
     }
 
-  if (priv->selectors_info->len > 0)
-    g_ptr_array_remove_range (priv->selectors_info, 0, priv->selectors_info->len);
-
-  g_free (priv->filename);
-  priv->filename = g_file_get_path (file);
+  gtk_css_provider_reset (css_provider);
 
-  priv->scanner->input_name = priv->filename;
-  priv->buffer = data;
-  g_scanner_input_text (priv->scanner, data, (guint) length);
+  scanner = gtk_css_scanner_new (file, data, length);
 
-  ret = parse_stylesheet (css_provider, error);
+  ret = parse_stylesheet (css_provider, scanner, error);
 
-  priv->buffer = NULL;
   g_free (data);
+  gtk_css_scanner_destroy (scanner);
 
   return ret;
 }
@@ -3529,8 +2739,10 @@ gtk_css_provider_load_from_path_internal (GtkCssProvider  *css_provider,
   GtkCssProviderPrivate *priv;
   GError *internal_error = NULL;
   GMappedFile *mapped_file;
+  GScanner *scanner;
   const gchar *data;
   gsize length;
+  GFile *file;
   gboolean ret;
 
   priv = css_provider->priv;
@@ -3550,21 +2762,16 @@ gtk_css_provider_load_from_path_internal (GtkCssProvider  *css_provider,
     data = "";
 
   if (reset)
-    {
-      if (priv->selectors_info->len > 0)
-        g_ptr_array_remove_range (priv->selectors_info, 0, priv->selectors_info->len);
+    gtk_css_provider_reset (css_provider);
 
-      g_free (priv->filename);
-      priv->filename = g_strdup (path);
-    }
+  file = g_file_new_for_path (path);
+  scanner = gtk_css_scanner_new (file, data, length);
+  g_object_unref (file);
 
-  priv->scanner->input_name = priv->filename;
-  priv->buffer = data;
-  g_scanner_input_text (priv->scanner, data, (guint) length);
+  ret = parse_stylesheet (css_provider, scanner, error);
 
-  ret = parse_stylesheet (css_provider, error);
+  gtk_css_scanner_destroy (scanner);
 
-  priv->buffer = NULL;
   g_mapped_file_unref (mapped_file);
 
   return ret;
@@ -3618,6 +2825,7 @@ gtk_css_provider_get_default (void)
         "@define-color selected_fg_color #fff; \n"
         "@define-color tooltip_bg_color #eee1b3; \n"
         "@define-color tooltip_fg_color #000; \n"
+        "@define-color placeholder_text_color #808080; \n"
         "\n"
         "@define-color info_fg_color rgb (181, 171, 156);\n"
         "@define-color info_bg_color rgb (252, 252, 189);\n"
@@ -3628,8 +2836,7 @@ gtk_css_provider_get_default (void)
         "@define-color error_fg_color rgb (166, 38, 38);\n"
         "@define-color error_bg_color rgb (237, 54, 54);\n"
         "\n"
-        "*,\n"
-        "GtkTreeView > GtkButton {\n"
+        "* {\n"
         "  background-color: @bg_color;\n"
         "  color: @fg_color;\n"
         "  border-color: shade (@bg_color, 0.6);\n"
@@ -3647,16 +2854,17 @@ gtk_css_provider_get_default (void)
         "  color: @selected_fg_color;\n"
         "}\n"
         "\n"
-        ".expander, .view.expander {\n"
+        ".expander, GtkTreeView.view.expander {\n"
         "  color: #fff;\n"
         "}\n"
         "\n"
-        ".expander:prelight {\n"
+        ".expander:prelight,\n"
+        "GtkTreeView.view.expander:selected:prelight {\n"
         "  color: @text_color;\n"
         "}\n"
         "\n"
         ".expander:active {\n"
-        "  transition: 300ms linear;\n"
+        "  transition: 200ms linear;\n"
         "}\n"
         "\n"
         "*:insensitive {\n"
@@ -3665,12 +2873,9 @@ gtk_css_provider_get_default (void)
         "  color: shade (@bg_color, 0.7);\n"
         "}\n"
         "\n"
-        "GtkTreeView, GtkIconView {\n"
-        "  background-color: @base_color;\n"
-        "  color: @text_color;\n"
-        "}\n"
-        "\n"
         ".view {\n"
+        "  border-width: 0;\n"
+        "  border-radius: 0;\n"
         "  background-color: @base_color;\n"
         "  color: @text_color;\n"
         "}\n"
@@ -3684,15 +2889,59 @@ gtk_css_provider_get_default (void)
         "  color: @selected_fg_color;\n"
         "}\n"
         "\n"
-        "GtkTreeView > row {\n"
+        ".view column:sorted row,\n"
+        ".view column:sorted row:prelight {\n"
+        "  background-color: shade (@bg_color, 0.85);\n"
+        "}\n"
+        "\n"
+        ".view column:sorted row:nth-child(odd),\n"
+        ".view column:sorted row:nth-child(odd):prelight {\n"
+        "  background-color: shade (@bg_color, 0.8);\n"
+        "}\n"
+        "\n"
+        ".view row,\n"
+        ".view row:prelight {\n"
         "  background-color: @base_color;\n"
         "  color: @text_color;\n"
         "}\n"
         "\n"
-        "GtkTreeView > row:nth-child(odd) { \n"
+        ".view row:nth-child(odd),\n"
+        ".view row:nth-child(odd):prelight {\n"
         "  background-color: shade (@base_color, 0.93); \n"
         "}\n"
         "\n"
+        ".view row:selected:focused {\n"
+        "  background-color: @selected_bg_color;\n"
+        "}\n"
+        "\n"
+        ".view row:selected {\n"
+        "  background-color: darker (@bg_color);\n"
+        "  color: @selected_fg_color;\n"
+        "}\n"
+        "\n"
+        ".view.cell.trough,\n"
+        ".view.cell.trough:hover,\n"
+        ".view.cell.trough:selected,\n"
+        ".view.cell.trough:selected:focused {\n"
+        "  background-color: @bg_color;\n"
+        "  color: @fg_color;\n"
+        "}\n"
+        "\n"
+        ".view.cell.progressbar,\n"
+        ".view.cell.progressbar:hover,\n"
+        ".view.cell.progressbar:selected,\n"
+        ".view.cell.progressbar:selected:focused {\n"
+        "  background-color: @selected_bg_color;\n"
+        "  color: @selected_fg_color;\n"
+        "}\n"
+        "\n"
+        ".rubberband {\n"
+        "  background-color: alpha (@fg_color, 0.25);\n"
+        "  border-color: @fg_color;\n"
+        "  border-style: solid;\n"
+        "  border-width: 1;\n"
+        "}\n"
+        "\n"
         ".tooltip {\n"
         "  background-color: @tooltip_bg_color; \n"
         "  color: @tooltip_fg_color; \n"
@@ -3720,6 +2969,7 @@ gtk_css_provider_get_default (void)
         "}\n"
         "\n"
         ".trough {\n"
+        "  background-color: darker (@bg_color);\n"
         "  border-style: inset;\n"
         "  border-width: 1;\n"
         "  padding: 0;\n"
@@ -3742,7 +2992,8 @@ gtk_css_provider_get_default (void)
         "}\n"
         "\n"
         ".progressbar,\n"
-        ".entry.progressbar {\n"
+        ".entry.progressbar, \n"
+        ".cell.progressbar {\n"
         "  background-color: @selected_bg_color;\n"
         "  border-color: shade (@selected_bg_color, 0.7);\n"
         "  color: @selected_fg_color;\n"
@@ -3757,7 +3008,9 @@ gtk_css_provider_get_default (void)
         "  background-color: shade (@bg_color, 1.05);\n"
         "}\n"
         "\n"
-        ".check, .radio {\n"
+        ".check, .radio,"
+        ".cell.check, .cell.radio,\n"
+        ".cell.check:hover, .cell.radio:hover {\n"
         "  border-style: solid;\n"
         "  border-width: 1;\n"
         "  background-color: @base_color;\n"
@@ -3772,8 +3025,13 @@ gtk_css_provider_get_default (void)
         "}\n"
         "\n"
         ".check:selected, .radio:selected {\n"
-        "  background-color: @selected_bg_color;\n"
+        "  background-color: darker (@bg_color);\n"
         "  color: @selected_fg_color;\n"
+        "  border-color: @selected_fg_color;\n"
+        "}\n"
+        "\n"
+        ".check:selected:focused, .radio:selected:focused {\n"
+        "  background-color: @selected_bg_color;\n"
         "}\n"
         "\n"
         ".menu.check, .menu.radio {\n"
@@ -3831,6 +3089,11 @@ gtk_css_provider_get_default (void)
         "  border-color: shade (@bg_color, 0.8);\n"
         "}\n"
         "\n"
+        "GtkSwitch.trough:active {\n"
+        "  background-color: @selected_bg_color;\n"
+        "  color: @selected_fg_color;\n"
+        "}\n"
+        "\n"
         "GtkToggleButton.button:inconsistent {\n"
         "  border-style: outset;\n"
         "  border-width: 1px;\n"
@@ -3840,12 +3103,10 @@ gtk_css_provider_get_default (void)
         "\n"
         "GtkLabel:selected {\n"
         "  background-color: shade (@bg_color, 0.9);\n"
-        "  color: @fg_color;\n"
         "}\n"
         "\n"
         "GtkLabel:selected:focused {\n"
         "  background-color: @selected_bg_color;\n"
-        "  color: @selected_fg_color;\n"
         "}\n"
         "\n"
         ".spinner:active {\n"
@@ -3913,11 +3174,6 @@ gtk_css_provider_get_default (void)
         "  color: @fg_color;\n"
         "}\n"
         "\n"
-        ".menu {\n"
-        "  border-width: 1;\n"
-        "  padding: 0;\n"
-        "}\n"
-        "\n"
         ".menu * {\n"
         "  border-width: 0;\n"
         "  padding: 2;\n"
@@ -3934,8 +3190,8 @@ gtk_css_provider_get_default (void)
   return provider;
 }
 
-static gchar *
-css_provider_get_theme_dir (void)
+gchar *
+_gtk_css_provider_get_theme_dir (void)
 {
   const gchar *var;
   gchar *path;
@@ -3953,24 +3209,31 @@ css_provider_get_theme_dir (void)
 /**
  * gtk_css_provider_get_named:
  * @name: A theme name
- * @variant: variant to load, for example, "dark", or %NULL for the default
+ * @variant: (allow-none): variant to load, for example, "dark", or
+ *     %NULL for the default
  *
  * Loads a theme from the usual theme paths
  *
  * Returns: (transfer none): a #GtkCssProvider with the theme loaded.
- *          This memory is owned by GTK+, and you must not free it.
- **/
+ *     This memory is owned by GTK+, and you must not free it.
+ */
 GtkCssProvider *
 gtk_css_provider_get_named (const gchar *name,
                             const gchar *variant)
 {
   static GHashTable *themes = NULL;
   GtkCssProvider *provider;
+  gchar *key;
 
   if (G_UNLIKELY (!themes))
     themes = g_hash_table_new (g_str_hash, g_str_equal);
 
-  provider = g_hash_table_lookup (themes, name);
+  if (variant == NULL)
+    key = (gchar *)name;
+  else
+    key = g_strconcat (name, "-", variant, NULL);
+
+  provider = g_hash_table_lookup (themes, key);
 
   if (!provider)
     {
@@ -3998,7 +3261,9 @@ gtk_css_provider_get_named (const gchar *name,
 
       if (!path)
         {
-          gchar *theme_dir = css_provider_get_theme_dir ();
+          gchar *theme_dir;
+
+          theme_dir = _gtk_css_provider_get_theme_dir ();
           path = g_build_filename (theme_dir, name, subpath, NULL);
           g_free (theme_dir);
 
@@ -4013,12 +3278,11 @@ gtk_css_provider_get_named (const gchar *name,
 
       if (path)
         {
-          GError *error = NULL;
+          GError *error;
 
           provider = gtk_css_provider_new ();
-          gtk_css_provider_load_from_path (provider, path, &error);
-
-          if (error)
+          error = NULL;
+          if (!gtk_css_provider_load_from_path (provider, path, &error))
             {
               g_warning ("Could not load named theme \"%s\": %s", name, error->message);
               g_error_free (error);
@@ -4027,9 +3291,221 @@ gtk_css_provider_get_named (const gchar *name,
               provider = NULL;
             }
           else
-            g_hash_table_insert (themes, g_strdup (name), provider);
+            g_hash_table_insert (themes, g_strdup (key), provider);
+
+          g_free (path);
         }
     }
 
+  if (key != name)
+    g_free (key);
+
   return provider;
 }
+
+static void
+selector_path_print (const SelectorPath *path,
+                     GString *           str)
+{
+  GSList *walk, *reverse;
+
+  reverse = g_slist_copy (path->elements);
+  reverse = g_slist_reverse (reverse);
+
+  for (walk = reverse; walk; walk = walk->next)
+    {
+      SelectorElement *elem = walk->data;
+
+      switch (elem->elem_type)
+        {
+        case SELECTOR_TYPE_NAME:
+        case SELECTOR_NAME:
+          g_string_append (str, g_quark_to_string (elem->name));
+          break;
+        case SELECTOR_GTYPE:
+          g_string_append (str, g_type_name (elem->type));
+          break;
+        case SELECTOR_REGION:
+          g_string_append (str, g_quark_to_string (elem->region.name));
+          if (elem->region.flags)
+            {
+              char * flag_names[] = {
+                "nth-child(even)",
+                "nth-child(odd)",
+                "first-child",
+                "last-child",
+                "sorted"
+              };
+              guint i;
+
+              for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
+                {
+                  if (elem->region.flags & (1 << i))
+                    {
+                      g_string_append_c (str, ':');
+                      g_string_append (str, flag_names[i]);
+                    }
+                }
+            }
+          break;
+        case SELECTOR_CLASS:
+          g_string_append_c (str, '.');
+          g_string_append (str, g_quark_to_string (elem->name));
+          break;
+        case SELECTOR_GLOB:
+          if (walk->next == NULL ||
+              elem->combinator != COMBINATOR_CHILD ||
+              ((SelectorElement *) walk->next->data)->elem_type != SELECTOR_CLASS)
+            g_string_append (str, "*");
+          break;
+        default:
+          g_assert_not_reached ();
+        }
+
+      if (walk->next)
+        {
+          switch (elem->combinator)
+            {
+            case COMBINATOR_DESCENDANT:
+              if (elem->elem_type != SELECTOR_CLASS ||
+                  ((SelectorElement *) walk->next->data)->elem_type != SELECTOR_CLASS)
+                g_string_append_c (str, ' ');
+              break;
+            case COMBINATOR_CHILD:
+              if (((SelectorElement *) walk->next->data)->elem_type != SELECTOR_CLASS)
+                g_string_append (str, " > ");
+              break;
+            default:
+              g_assert_not_reached ();
+            }
+        }
+        
+    }
+
+  if (path->state)
+    {
+      char * state_names[] = {
+        "active",
+        "hover",
+        "selected",
+        "insensitive",
+        "inconsistent",
+        "focus"
+      };
+      guint i;
+
+      for (i = 0; i < G_N_ELEMENTS (state_names); i++)
+        {
+          if (path->state & (1 << i))
+            {
+              g_string_append_c (str, ':');
+              g_string_append (str, state_names[i]);
+            }
+        }
+    }
+
+  g_slist_free (reverse);
+}
+
+static void
+selector_style_info_print (const SelectorStyleInfo *info,
+                           GString                 *str)
+{
+  GList *keys, *walk;
+  char *s;
+
+  selector_path_print (info->path, str);
+
+  g_string_append (str, " {\n");
+
+  keys = g_hash_table_get_keys (info->style);
+  /* so the output is identical for identical selector styles */
+  keys = g_list_sort (keys, (GCompareFunc) strcmp);
+
+  for (walk = keys; walk; walk = walk->next)
+    {
+      const char *name = walk->data;
+      const GValue *value = g_hash_table_lookup (info->style, (gpointer) name);
+
+      g_string_append (str, "  ");
+      g_string_append (str, name);
+      g_string_append (str, ": ");
+      s = _gtk_css_value_to_string (value);
+      g_string_append (str, s);
+      g_free (s);
+      g_string_append (str, ";\n");
+    }
+
+  g_list_free (keys);
+
+  g_string_append (str, "}\n");
+}
+
+static void
+gtk_css_provider_print_colors (GHashTable *colors,
+                               GString    *str)
+{
+  GList *keys, *walk;
+  char *s;
+
+  keys = g_hash_table_get_keys (colors);
+  /* so the output is identical for identical styles */
+  keys = g_list_sort (keys, (GCompareFunc) strcmp);
+
+  for (walk = keys; walk; walk = walk->next)
+    {
+      const char *name = walk->data;
+      GtkSymbolicColor *symbolic = g_hash_table_lookup (colors, (gpointer) name);
+
+      g_string_append (str, "@define-color ");
+      g_string_append (str, name);
+      g_string_append (str, " ");
+      s = gtk_symbolic_color_to_string (symbolic);
+      g_string_append (str, s);
+      g_free (s);
+      g_string_append (str, ";\n");
+    }
+
+  g_list_free (keys);
+}
+
+/**
+ * gtk_css_provider_to_string:
+ * @provider: the provider to write to a string
+ *
+ * Convertes the @provider into a string representation in CSS
+ * format.
+ * 
+ * Using gtk_css_provider_load_from_data() with the return value
+ * from this function on a new provider created with
+ * gtk_css_provider_new() will basicallu create a duplicate of
+ * this @provider.
+ *
+ * Returns: a new string representing the @provider.
+ **/
+char *
+gtk_css_provider_to_string (GtkCssProvider *provider)
+{
+  GtkCssProviderPrivate *priv;
+  GString *str;
+  guint i;
+
+  g_return_val_if_fail (GTK_IS_CSS_PROVIDER (provider), NULL);
+
+  priv = provider->priv;
+
+  str = g_string_new ("");
+
+  gtk_css_provider_print_colors (priv->symbolic_colors, str);
+
+  for (i = 0; i < priv->selectors_info->len; i++)
+    {
+      if (i > 0)
+        g_string_append (str, "\n");
+      selector_style_info_print (g_ptr_array_index (priv->selectors_info, i),
+                                 str);
+    }
+
+  return g_string_free (str, FALSE);
+}
+