[gtk+] css: Rewrite selectors



commit 35a0fb09acb1cbf1a1e99f33d73597c7d46173ea
Author: Benjamin Otte <otte redhat com>
Date:   Thu Feb 16 15:16:18 2012 +0100

    css: Rewrite selectors
    
    Previously we kept a Selector object for every "simple selector" (term
    from CSS spec). Now we keep one for every match operation. So given the
    selector
      ".a b:focus"
    we will have 4 elements:
      - pseudoclass ":focus"
      - element "b"
      - match any desendant (the space)
      - class ".a"
    Each of those is represented by a "selector class" which is basically
    the collection of vfuncs for this selector.

 gtk/gtkcssselector.c              |  988 ++++++++++++++++++++-----------------
 tests/css/parser/selector.ref.css |   12 +-
 2 files changed, 542 insertions(+), 458 deletions(-)
---
diff --git a/gtk/gtkcssselector.c b/gtk/gtkcssselector.c
index 96f010a..20aec45 100644
--- a/gtk/gtkcssselector.c
+++ b/gtk/gtkcssselector.c
@@ -22,51 +22,437 @@
 #include "gtkcssprovider.h"
 #include "gtkstylecontextprivate.h"
 
-typedef enum {
-  GTK_CSS_COMBINE_DESCANDANT,
-  GTK_CSS_COMBINE_CHILD
-} GtkCssCombinator;
+typedef struct _GtkCssSelectorClass GtkCssSelectorClass;
+
+struct _GtkCssSelectorClass {
+  const char        *name;
+
+  void              (* print)   (const GtkCssSelector       *selector,
+                                 GString                    *string);
+  gboolean          (* match)   (const GtkCssSelector       *selector,
+                                 GtkStateFlags               state,
+                                 const GtkWidgetPath        *path,
+                                 guint                       id);
+
+  guint         increase_id_specificity :1;
+  guint         increase_class_specificity :1;
+  guint         increase_element_specificity :1;
+};
 
 struct _GtkCssSelector
 {
-  GtkCssSelector *  previous;        /* link to next element in selector or NULL if last */
-  GtkCssCombinator  combine;         /* how to combine with the previous element */
-  const char *      name;            /* quarked name of element we match or NULL if any */
-  GType             type;            /* cache for type belonging to name - G_TYPE_INVALID if uncached */
-  GQuark *          ids;             /* 0-terminated list of required ids or NULL if none */
-  GQuark *          classes;         /* 0-terminated list of required classes or NULL if none */
-  GtkRegionFlags    pseudo_classes;  /* required pseudo classes */
-  GtkStateFlags     state;           /* required state flags (currently not checked when matching) */
+  const GtkCssSelectorClass *class;       /* type of check this selector does */
+  GtkCssSelector            *previous;    /* link to next element in selector or NULL if last */
+  gconstpointer              data;        /* data for matching:
+                                             - interned string for CLASS, NAME and ID
+                                             - GUINT_TO_POINTER() for PSEUDOCLASS_REGION/STATE */
+};
+
+static gboolean
+gtk_css_selector_match (const GtkCssSelector *selector,
+                        GtkStateFlags         state,
+                        const GtkWidgetPath  *path,
+                        guint                 id)
+{
+  if (selector == NULL)
+    return TRUE;
+
+  return selector->class->match (selector, state, path, id);
+}
+
+/* ANY */
+
+static void
+gtk_css_selector_any_print (const GtkCssSelector *selector,
+                            GString              *string)
+{
+  g_string_append_c (string, '*');
+}
+
+static gboolean
+gtk_css_selector_any_match (const GtkCssSelector *selector,
+                            GtkStateFlags         state,
+                            const GtkWidgetPath  *path,
+                            guint                 id)
+{
+  return gtk_css_selector_match (selector->previous, state, path, id);
+}
+
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_ANY = {
+  "any",
+  gtk_css_selector_any_print,
+  gtk_css_selector_any_match,
+  FALSE, FALSE, FALSE
+};
+
+/* DESCENDANT */
+
+static void
+gtk_css_selector_descendant_print (const GtkCssSelector *selector,
+                                   GString              *string)
+{
+  g_string_append_c (string, ' ');
+}
+
+static gboolean
+gtk_css_selector_descendant_match (const GtkCssSelector *selector,
+                                   GtkStateFlags         state,
+                                   const GtkWidgetPath  *path,
+                                   guint                 id)
+{
+  while (id-- > 0)
+    {
+      if (gtk_css_selector_match (selector->previous, 0, path, id))
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_DESCENDANT = {
+  "descendant",
+  gtk_css_selector_descendant_print,
+  gtk_css_selector_descendant_match,
+  FALSE, FALSE, FALSE
+};
+
+/* CHILD */
+
+static void
+gtk_css_selector_child_print (const GtkCssSelector *selector,
+                              GString              *string)
+{
+  g_string_append (string, " > ");
+}
+
+static gboolean
+gtk_css_selector_child_match (const GtkCssSelector *selector,
+                              GtkStateFlags         state,
+                              const GtkWidgetPath  *path,
+                              guint                 id)
+{
+  if (id == 0)
+    return FALSE;
+
+  return gtk_css_selector_match (selector->previous, 0, path, id - 1);
+}
+
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_CHILD = {
+  "child",
+  gtk_css_selector_child_print,
+  gtk_css_selector_child_match,
+  FALSE, FALSE, FALSE
+};
+
+/* NAME */
+
+static void
+gtk_css_selector_name_print (const GtkCssSelector *selector,
+                             GString              *string)
+{
+  g_string_append (string, selector->data);
+}
+
+static gboolean
+gtk_css_selector_name_match (const GtkCssSelector *selector,
+                             GtkStateFlags         state,
+                             const GtkWidgetPath  *path,
+                             guint                 id)
+{
+  GType type = g_type_from_name (selector->data);
+
+  if (!g_type_is_a (gtk_widget_path_iter_get_object_type (path, id), type))
+    return FALSE;
+
+  return gtk_css_selector_match (selector->previous, state, path, id);
+}
+
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_NAME = {
+  "name",
+  gtk_css_selector_name_print,
+  gtk_css_selector_name_match,
+  FALSE, FALSE, TRUE
+};
+
+/* REGION */
+
+static void
+gtk_css_selector_region_print (const GtkCssSelector *selector,
+                               GString              *string)
+{
+  g_string_append (string, selector->data);
+}
+
+static gboolean
+gtk_css_selector_region_match (const GtkCssSelector *selector,
+                               GtkStateFlags         state,
+                               const GtkWidgetPath  *path,
+                               guint                 id)
+{
+  if (!gtk_widget_path_iter_has_region (path, id, selector->data, NULL))
+    return FALSE;
+
+  if (selector->previous &&
+      selector->previous->class == &GTK_CSS_SELECTOR_DESCENDANT &&
+      gtk_css_selector_match (selector->previous->previous, state, path, id))
+    return TRUE;
+
+  return gtk_css_selector_match (selector->previous, state, path, id);
+}
+
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_REGION = {
+  "region",
+  gtk_css_selector_region_print,
+  gtk_css_selector_region_match,
+  FALSE, FALSE, TRUE
+};
+
+/* CLASS */
+
+static void
+gtk_css_selector_class_print (const GtkCssSelector *selector,
+                              GString              *string)
+{
+  g_string_append_c (string, '.');
+  g_string_append (string, selector->data);
+}
+
+static gboolean
+gtk_css_selector_class_match (const GtkCssSelector *selector,
+                              GtkStateFlags         state,
+                              const GtkWidgetPath  *path,
+                              guint                 id)
+{
+  if (!gtk_widget_path_iter_has_class (path, id, selector->data))
+    return FALSE;
+
+  return gtk_css_selector_match (selector->previous, state, path, id);
+}
+
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_CLASS = {
+  "class",
+  gtk_css_selector_class_print,
+  gtk_css_selector_class_match,
+  FALSE, TRUE, FALSE
+};
+
+/* ID */
+
+static void
+gtk_css_selector_id_print (const GtkCssSelector *selector,
+                           GString              *string)
+{
+  g_string_append_c (string, '#');
+  g_string_append (string, selector->data);
+}
+
+static gboolean
+gtk_css_selector_id_match (const GtkCssSelector *selector,
+                           GtkStateFlags         state,
+                           const GtkWidgetPath  *path,
+                           guint                 id)
+{
+  if (!gtk_widget_path_iter_has_name (path, id, selector->data))
+    return FALSE;
+
+  return gtk_css_selector_match (selector->previous, state, path, id);
+}
+
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_ID = {
+  "id",
+  gtk_css_selector_id_print,
+  gtk_css_selector_id_match,
+  TRUE, FALSE, FALSE
+};
+
+/* PSEUDOCLASS FOR STATE */
+
+static void
+gtk_css_selector_pseudoclass_state_print (const GtkCssSelector *selector,
+                                          GString              *string)
+{
+  static const char * state_names[] = {
+    "active",
+    "hover",
+    "selected",
+    "insensitive",
+    "inconsistent",
+    "focus",
+    "backdrop"
+  };
+  guint i, state;
+
+  state = GPOINTER_TO_UINT (selector->data);
+  g_string_append_c (string, ':');
+
+  for (i = 0; i < G_N_ELEMENTS (state_names); i++)
+    {
+      if (state == (1 << i))
+        {
+          g_string_append (string, state_names[i]);
+          return;
+        }
+    }
+
+  g_assert_not_reached ();
+}
+
+static gboolean
+gtk_css_selector_pseudoclass_state_match (const GtkCssSelector *selector,
+                                          GtkStateFlags         state,
+                                          const GtkWidgetPath  *path,
+                                          guint                 id)
+{
+  if (!(GPOINTER_TO_UINT (selector->data) & state))
+    return FALSE;
+
+  return gtk_css_selector_match (selector->previous, state, path, id);
+}
+
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_STATE = {
+  "pseudoclass-state",
+  gtk_css_selector_pseudoclass_state_print,
+  gtk_css_selector_pseudoclass_state_match,
+  FALSE, TRUE, FALSE
 };
 
+/* PSEUDOCLASS FOR REGION */
+
+static void
+gtk_css_selector_pseudoclass_region_print (const GtkCssSelector *selector,
+                                           GString              *string)
+{
+  static const char * flag_names[] = {
+    "nth-child(even)",
+    "nth-child(odd)",
+    "first-child",
+    "last-child",
+    "only-child",
+    "sorted"
+  };
+  guint i, state;
+
+  state = GPOINTER_TO_UINT (selector->data);
+  g_string_append_c (string, ':');
+
+  for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
+    {
+      if (state == (1 << i))
+        {
+          g_string_append (string, flag_names[i]);
+          return;
+        }
+    }
+
+  g_assert_not_reached ();
+}
+
+static gboolean
+gtk_css_selector_pseudoclass_region_match_for_region (const GtkCssSelector *selector,
+                                                      GtkStateFlags         state,
+                                                      const GtkWidgetPath  *path,
+                                                      guint                 id)
+{
+  GtkRegionFlags selector_flags, path_flags;
+  
+  selector_flags = GPOINTER_TO_UINT (selector->data);
+  selector = selector->previous;
+
+  if (!gtk_widget_path_iter_has_region (path, id, selector->data, &path_flags))
+    return FALSE;
+
+  if ((selector_flags & path_flags) != selector_flags)
+    return FALSE;
+
+  if (selector->previous &&
+      selector->previous->class == &GTK_CSS_SELECTOR_DESCENDANT &&
+      gtk_css_selector_match (selector->previous->previous, state, path, id))
+    return TRUE;
+
+  return gtk_css_selector_match (selector->previous, state, path, id);
+}
+
+static gboolean
+gtk_css_selector_pseudoclass_region_match (const GtkCssSelector *selector,
+                                           GtkStateFlags         state,
+                                           const GtkWidgetPath  *path,
+                                           guint                 id)
+{
+  GtkRegionFlags region;
+  const GtkWidgetPath *siblings;
+  guint sibling_id, n_siblings;
+
+  if (selector->previous &&
+      selector->previous->class == &GTK_CSS_SELECTOR_REGION)
+    return gtk_css_selector_pseudoclass_region_match_for_region (selector, state, path, id);
+
+  siblings = gtk_widget_path_iter_get_siblings (path, id);
+  if (siblings == NULL)
+    return 0;
+
+  region = GPOINTER_TO_UINT (selector->data);
+  sibling_id = gtk_widget_path_iter_get_sibling_index (path, id);
+  n_siblings = gtk_widget_path_length (siblings);
+
+  switch (region)
+    {
+    case GTK_REGION_EVEN:
+      if (!(sibling_id % 2))
+        return FALSE;
+      break;
+    case GTK_REGION_ODD:
+      if (sibling_id % 2)
+        return FALSE;
+      break;
+    case GTK_REGION_FIRST:
+      if (sibling_id != 0)
+        return FALSE;
+      break;
+    case GTK_REGION_LAST:
+      if (sibling_id + 1 != n_siblings)
+        return FALSE;
+      break;
+    case GTK_REGION_ONLY:
+      if (n_siblings != 1)
+        return FALSE;
+      break;
+    case GTK_REGION_SORTED:
+      return FALSE;
+    default:
+      g_assert_not_reached ();
+      return FALSE;
+    }
+
+  return gtk_css_selector_match (selector->previous, state, path, id);
+}
+
+static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_REGION = {
+  "pseudoclass-region",
+  gtk_css_selector_pseudoclass_region_print,
+  gtk_css_selector_pseudoclass_region_match,
+  FALSE, TRUE, FALSE
+};
+
+/* API */
+
 static GtkCssSelector *
-gtk_css_selector_new (GtkCssSelector         *previous,
-                      GtkCssCombinator        combine,
-                      const char *            name,
-                      GQuark *                ids,
-                      GQuark *                classes,
-                      GtkRegionFlags          pseudo_classes,
-                      GtkStateFlags           state)
+gtk_css_selector_new (const GtkCssSelectorClass *class,
+                      GtkCssSelector            *previous,
+                      gconstpointer              data)
 {
   GtkCssSelector *selector;
 
   selector = g_slice_new0 (GtkCssSelector);
+  selector->class = class;
   selector->previous = previous;
-  selector->combine = combine;
-  selector->name = name ? g_quark_to_string (g_quark_from_string (name)) : NULL;
-  selector->type = !name || _gtk_style_context_check_region_name (name) ? G_TYPE_NONE : G_TYPE_INVALID;
-  selector->ids = ids;
-  selector->classes = classes;
-  selector->pseudo_classes = pseudo_classes;
-  selector->state = state;
+  selector->data = data;
 
   return selector;
 }
 
-static gboolean
-parse_selector_class (GtkCssParser *parser, GArray *classes)
+static GtkCssSelector *
+parse_selector_class (GtkCssParser *parser, GtkCssSelector *selector)
 {
-  GQuark qname;
   char *name;
     
   name = _gtk_css_parser_try_name (parser, FALSE);
@@ -74,19 +460,23 @@ parse_selector_class (GtkCssParser *parser, GArray *classes)
   if (name == NULL)
     {
       _gtk_css_parser_error (parser, "Expected a valid name for class");
-      return FALSE;
+      if (selector)
+        _gtk_css_selector_free (selector);
+      return NULL;
     }
 
-  qname = g_quark_from_string (name);
-  g_array_append_val (classes, qname);
+  selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_CLASS,
+                                   selector,
+                                   g_intern_string (name));
+
   g_free (name);
-  return TRUE;
+
+  return selector;
 }
 
-static gboolean
-parse_selector_name (GtkCssParser *parser, GArray *names)
+static GtkCssSelector *
+parse_selector_id (GtkCssParser *parser, GtkCssSelector *selector)
 {
-  GQuark qname;
   char *name;
     
   name = _gtk_css_parser_try_name (parser, FALSE);
@@ -94,19 +484,23 @@ parse_selector_name (GtkCssParser *parser, GArray *names)
   if (name == NULL)
     {
       _gtk_css_parser_error (parser, "Expected a valid name for id");
-      return FALSE;
+      if (selector)
+        _gtk_css_selector_free (selector);
+      return NULL;
     }
 
-  qname = g_quark_from_string (name);
-  g_array_append_val (names, qname);
+  selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ID,
+                                   selector,
+                                   g_intern_string (name));
+
   g_free (name);
-  return TRUE;
+
+  return selector;
 }
 
-static gboolean
+static GtkCssSelector *
 parse_selector_pseudo_class (GtkCssParser   *parser,
-                             GtkRegionFlags *region_to_modify,
-                             GtkStateFlags  *state_to_modify)
+                             GtkCssSelector *selector)
 {
   struct {
     const char *name;
@@ -142,7 +536,9 @@ parse_selector_pseudo_class (GtkCssParser   *parser,
   if (name == NULL)
     {
       _gtk_css_parser_error (parser, "Missing name of pseudo-class");
-      return FALSE;
+      if (selector)
+        _gtk_css_selector_free (selector);
+      return NULL;
     }
 
   if (_gtk_css_parser_try (parser, "(", TRUE))
@@ -153,7 +549,9 @@ parse_selector_pseudo_class (GtkCssParser   *parser,
       if (!_gtk_css_parser_try (parser, ")", FALSE))
         {
           _gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class");
-          return FALSE;
+          if (selector)
+            _gtk_css_selector_free (selector);
+          return NULL;
         }
 
       if (g_ascii_strcasecmp (function, "nth-child") != 0)
@@ -164,7 +562,9 @@ parse_selector_pseudo_class (GtkCssParser   *parser,
           _gtk_css_parser_take_error (parser, error);
           g_free (function);
           g_free (name);
-          return FALSE;
+          if (selector)
+            _gtk_css_selector_free (selector);
+          return NULL;
         }
       
       g_free (function);
@@ -175,7 +575,9 @@ parse_selector_pseudo_class (GtkCssParser   *parser,
                                GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
                                "Unknown pseudo-class 'nth-child(%s)'", name);
           _gtk_css_parser_take_error (parser, error);
-          return FALSE;
+          if (selector)
+            _gtk_css_selector_free (selector);
+          return NULL;
         }
 
       classes = nth_child_classes;
@@ -187,11 +589,18 @@ parse_selector_pseudo_class (GtkCssParser   *parser,
     {
       if (g_ascii_strcasecmp (name, classes[i].name) == 0)
         {
-          *region_to_modify |= classes[i].region_flag;
-          *state_to_modify |= classes[i].state_flag;
-
           g_free (name);
-          return TRUE;
+
+          if (classes[i].region_flag)
+            selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_PSEUDOCLASS_REGION,
+                                             selector,
+                                             GUINT_TO_POINTER (classes[i].region_flag));
+          else
+            selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_PSEUDOCLASS_STATE,
+                                             selector,
+                                             GUINT_TO_POINTER (classes[i].state_flag));
+
+          return selector;
         }
     }
 
@@ -204,58 +613,66 @@ parse_selector_pseudo_class (GtkCssParser   *parser,
                          GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
                          "Unknown pseudo-class '%s'", name);
 
-  g_free (name);
   _gtk_css_parser_take_error (parser, error);
+  g_free (name);
+  if (selector)
+    _gtk_css_selector_free (selector);
 
-  return FALSE;
+  return NULL;
 }
 
-static gboolean
-parse_simple_selector (GtkCssParser *parser,
-                       char **name,
-                       GArray *ids,
-                       GArray *classes,
-                       GtkRegionFlags *pseudo_classes,
-                       GtkStateFlags *state)
-{
-  gboolean parsed_something;
+static GtkCssSelector *
+try_parse_name (GtkCssParser   *parser,
+                GtkCssSelector *selector)
+{
+  char *name;
+
+  name = _gtk_css_parser_try_ident (parser, FALSE);
+  if (name)
+    {
+      selector = gtk_css_selector_new (_gtk_style_context_check_region_name (name)
+                                       ? &GTK_CSS_SELECTOR_REGION
+                                       : &GTK_CSS_SELECTOR_NAME,
+                                       selector,
+                                       g_intern_string (name));
+      g_free (name);
+    }
+  else if (_gtk_css_parser_try (parser, "*", FALSE))
+    selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ANY, selector, NULL);
   
-  *name = _gtk_css_parser_try_ident (parser, FALSE);
-  if (*name)
-    parsed_something = TRUE;
-  else
-    parsed_something = _gtk_css_parser_try (parser, "*", FALSE);
+  return selector;
+}
+
+static GtkCssSelector *
+parse_simple_selector (GtkCssParser   *parser,
+                       GtkCssSelector *previous)
+{
+  GtkCssSelector *selector = previous;
+  
+  selector = try_parse_name (parser, selector);
 
   do {
       if (_gtk_css_parser_try (parser, "#", FALSE))
-        {
-          if (!parse_selector_name (parser, ids))
-            return FALSE;
-        }
+        selector = parse_selector_id (parser, selector);
       else if (_gtk_css_parser_try (parser, ".", FALSE))
-        {
-          if (!parse_selector_class (parser, classes))
-            return FALSE;
-        }
+        selector = parse_selector_class (parser, selector);
       else if (_gtk_css_parser_try (parser, ":", FALSE))
-        {
-          if (!parse_selector_pseudo_class (parser, pseudo_classes, state))
-            return FALSE;
-        }
-      else if (!parsed_something)
+        selector = parse_selector_pseudo_class (parser, selector);
+      else if (selector == previous)
         {
           _gtk_css_parser_error (parser, "Expected a valid selector");
-          return FALSE;
+          if (selector)
+            _gtk_css_selector_free (selector);
+          selector = NULL;
         }
       else
         break;
-
-      parsed_something = TRUE;
     }
-  while (!_gtk_css_parser_is_eof (parser));
+  while (selector && !_gtk_css_parser_is_eof (parser));
 
   _gtk_css_parser_skip_whitespace (parser);
-  return TRUE;
+
+  return selector;
 }
 
 GtkCssSelector *
@@ -263,41 +680,16 @@ _gtk_css_selector_parse (GtkCssParser *parser)
 {
   GtkCssSelector *selector = NULL;
 
-  do {
-      char *name = NULL;
-      GArray *ids = g_array_new (TRUE, FALSE, sizeof (GQuark));
-      GArray *classes = g_array_new (TRUE, FALSE, sizeof (GQuark));
-      GtkRegionFlags pseudo_classes = 0;
-      GtkStateFlags state = 0;
-      GtkCssCombinator combine = GTK_CSS_COMBINE_DESCANDANT;
-
-      if (selector)
-        {
-          if (_gtk_css_parser_try (parser, ">", TRUE))
-            combine = GTK_CSS_COMBINE_CHILD;
-        }
-
-      if (!parse_simple_selector (parser, &name, ids, classes, &pseudo_classes, &state))
-        {
-          g_array_free (ids, TRUE);
-          g_array_free (classes, TRUE);
-          if (selector)
-            _gtk_css_selector_free (selector);
-          return NULL;
-        }
-
-      selector = gtk_css_selector_new (selector,
-                                       combine,
-                                       name,
-                                       (GQuark *) g_array_free (ids, ids->len == 0),
-                                       (GQuark *) g_array_free (classes, classes->len == 0),
-                                       pseudo_classes,
-                                       state);
-      g_free (name);
-    }
-  while (!_gtk_css_parser_is_eof (parser) &&
+  while ((selector = parse_simple_selector (parser, selector)) &&
+         !_gtk_css_parser_is_eof (parser) &&
          !_gtk_css_parser_begins_with (parser, ',') &&
-         !_gtk_css_parser_begins_with (parser, '{'));
+         !_gtk_css_parser_begins_with (parser, '{'))
+    {
+      if (_gtk_css_parser_try (parser, ">", TRUE))
+        selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_CHILD, selector, NULL);
+      else
+        selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_DESCENDANT, selector, NULL);
+    }
 
   return selector;
 }
@@ -310,9 +702,6 @@ _gtk_css_selector_free (GtkCssSelector *selector)
   if (selector->previous)
     _gtk_css_selector_free (selector->previous);
 
-  g_free (selector->ids);
-  g_free (selector->classes);
-
   g_slice_free (GtkCssSelector, selector);
 }
 
@@ -320,96 +709,12 @@ void
 _gtk_css_selector_print (const GtkCssSelector *selector,
                          GString *             str)
 {
-  if (selector->previous)
-    {
-      _gtk_css_selector_print (selector->previous, str);
-      switch (selector->combine)
-        {
-          case GTK_CSS_COMBINE_DESCANDANT:
-            g_string_append (str, " ");
-            break;
-          case GTK_CSS_COMBINE_CHILD:
-            g_string_append (str, " > ");
-            break;
-          default:
-            g_assert_not_reached ();
-        }
-    }
-
-  if (selector->name)
-    g_string_append (str, selector->name);
-  else if (selector->ids == NULL && 
-           selector->classes == NULL && 
-           selector->pseudo_classes == 0 &&
-           selector->state == 0)
-    g_string_append (str, "*");
-
-  if (selector->ids)
-    {
-      GQuark *id;
-
-      for (id = selector->ids; *id != 0; id++)
-        {
-          g_string_append_c (str, '#');
-          g_string_append (str, g_quark_to_string (*id));
-        }
-    }
-
-  if (selector->classes)
-    {
-      GQuark *class;
-
-      for (class = selector->classes; *class != 0; class++)
-        {
-          g_string_append_c (str, '.');
-          g_string_append (str, g_quark_to_string (*class));
-        }
-    }
+  g_return_if_fail (selector != NULL);
 
-  if (selector->pseudo_classes)
-    {
-      static const char * flag_names[] = {
-        "nth-child(even)",
-        "nth-child(odd)",
-        "first-child",
-        "last-child",
-        "only-child",
-        "sorted"
-      };
-      guint i;
-
-      for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
-        {
-          if (selector->pseudo_classes & (1 << i))
-            {
-              g_string_append_c (str, ':');
-              g_string_append (str, flag_names[i]);
-            }
-        }
-    }
+  if (selector->previous)
+    _gtk_css_selector_print (selector->previous, str);
 
-  if (selector->state)
-    {
-      static const char * state_names[] = {
-        "active",
-        "hover",
-        "selected",
-        "insensitive",
-        "inconsistent",
-        "focus",
-        "backdrop"
-      };
-      guint i;
-
-      for (i = 0; i < G_N_ELEMENTS (state_names); i++)
-        {
-          if (selector->state & (1 << i))
-            {
-              g_string_append_c (str, ':');
-              g_string_append (str, state_names[i]);
-            }
-        }
-    }
+  selector->class->print (selector, str);
 }
 
 char *
@@ -426,209 +731,6 @@ _gtk_css_selector_to_string (const GtkCssSelector *selector)
   return g_string_free (string, FALSE);
 }
 
-static GtkRegionFlags
-compute_region_flags_for_index (const GtkWidgetPath *path,
-                                guint                id)
-{
-  const GtkWidgetPath *siblings;
-  guint sibling_id, n_siblings;
-  GtkRegionFlags flags;
-  
-  siblings = gtk_widget_path_iter_get_siblings (path, id);
-  if (siblings == NULL)
-    return 0;
-
-  sibling_id = gtk_widget_path_iter_get_sibling_index (path, id);
-  n_siblings = gtk_widget_path_length (siblings);
-
-  flags = (sibling_id % 2) ? GTK_REGION_EVEN : GTK_REGION_ODD;
-  if (sibling_id == 0)
-    flags |= GTK_REGION_FIRST;
-  if (sibling_id + 1 == n_siblings)
-    flags |= GTK_REGION_LAST;
-  if (n_siblings == 1)
-    flags |= GTK_REGION_ONLY;
-
-  return flags;
-}
-
-static gboolean
-gtk_css_selector_matches_type (const GtkCssSelector      *selector,
-                               const GtkWidgetPath       *path,
-                               guint                      id)
-{
-  if (selector->pseudo_classes)
-    {
-      GtkRegionFlags flags = compute_region_flags_for_index (path, id);
-
-      if ((selector->pseudo_classes & flags) != selector->pseudo_classes)
-        return FALSE;
-    }
-
-  if (selector->name == NULL)
-    return TRUE;
-
-  if (selector->type == G_TYPE_NONE)
-    return FALSE;
-
-  /* ugh, assigning to a const variable */
-  if (selector->type == G_TYPE_INVALID)
-    ((GtkCssSelector *) selector)->type = g_type_from_name (selector->name);
-
-  if (selector->type == G_TYPE_INVALID)
-    return FALSE;
-
-  return g_type_is_a (gtk_widget_path_iter_get_object_type (path, id), selector->type);
-}
-
-static gboolean
-gtk_css_selector_matches_region (const GtkCssSelector      *selector,
-                                 const GtkWidgetPath       *path,
-                                 guint                      id,
-                                 const char *               region)
-{
-  GtkRegionFlags flags;
-
-  if (selector->name == NULL)
-    return TRUE;
-  
-  if (selector->name != region)
-    return FALSE;
-
-  if (!gtk_widget_path_iter_has_region (path, id, region, &flags))
-    {
-      /* This function must be called with existing regions */
-      g_assert_not_reached ();
-    }
-
-  return (selector->pseudo_classes & flags) == selector->pseudo_classes;
-}
-
-static gboolean
-gtk_css_selector_matches_rest (const GtkCssSelector      *selector,
-                               const GtkWidgetPath       *path,
-                               guint                      id)
-{
-  if (selector->ids)
-    {
-      GQuark *name;
-      
-      for (name = selector->ids; *name; name++)
-        {
-          if (!gtk_widget_path_iter_has_qname (path, id, *name))
-            return FALSE;
-        }
-    }
-
-  if (selector->classes)
-    {
-      GQuark *class;
-      
-      for (class = selector->classes; *class; class++)
-        {
-          if (!gtk_widget_path_iter_has_qclass (path, id, *class))
-            return FALSE;
-        }
-    }
-
-  return TRUE;
-}
-
-static gboolean
-gtk_css_selector_matches_previous (const GtkCssSelector      *selector,
-                                   const GtkWidgetPath       *path,
-                                   guint                      id,
-                                   GSList                    *regions);
-
-static gboolean
-gtk_css_selector_matches_from (const GtkCssSelector      *selector,
-                               const GtkWidgetPath       *path,
-                               guint                      id,
-                               GSList                    *regions)
-{
-  GSList *l;
-
-  if (!gtk_css_selector_matches_rest (selector, path, id))
-    return FALSE;
-
-  for (l = regions; l; l = l->next)
-    {
-      const char *region = l->data;
-
-      if (gtk_css_selector_matches_region (selector, path, id, region))
-        {
-          GSList *remaining;
-          gboolean match;
-
-          remaining = g_slist_copy (regions);
-          remaining = g_slist_remove (remaining, region);
-          match = gtk_css_selector_matches_previous (selector,
-                                                     path,
-                                                     id,
-                                                     remaining);
-          g_slist_free (remaining);
-          if (match)
-            return TRUE;
-        }
-    }
-
-  if (gtk_css_selector_matches_type (selector, path, id))
-    {
-      GSList *regions;
-      gboolean match;
-      
-      if (id <= 0)
-        return selector->previous == NULL;
-
-      regions = gtk_widget_path_iter_list_regions (path, id - 1);
-      match = gtk_css_selector_matches_previous (selector,
-                                                 path,
-                                                 id - 1,
-                                                 regions);
-      g_slist_free (regions);
-      return match;
-    }
-
-  return FALSE;
-}
-
-static gboolean
-gtk_css_selector_matches_previous (const GtkCssSelector      *selector,
-                                   const GtkWidgetPath       *path,
-                                   guint                      id,
-                                   GSList                    *regions)
-{
-  if (!selector->previous)
-    return TRUE;
-
-  if (gtk_css_selector_matches_from (selector->previous,
-                                     path,
-                                     id,
-                                     regions))
-    return TRUE;
-
-  if (selector->combine == GTK_CSS_COMBINE_DESCANDANT)
-    {
-      /* with this magic we run the loop while id >= 0 */
-      while (id-- != 0)
-        {
-          GSList *list;
-          gboolean match;
-
-          list = gtk_widget_path_iter_list_regions (path, id);
-          match = gtk_css_selector_matches_from (selector->previous,
-                                                 path,
-                                                 id,
-                                                 list);
-          g_slist_free (list);
-          if (match)
-            return TRUE;
-        }
-    }
-
-  return FALSE;
-}
-
 /**
  * _gtk_css_selector_matches:
  * @selector: the selector
@@ -648,36 +750,19 @@ _gtk_css_selector_matches (const GtkCssSelector      *selector,
                            const GtkWidgetPath       *path,
                            GtkStateFlags              state)
 {
-  GSList *list;
-  gboolean match;
   guint length;
 
   g_return_val_if_fail (selector != NULL, FALSE);
   g_return_val_if_fail (path != NULL, FALSE);
 
-  if ((selector->state & state) != selector->state)
-    return FALSE;
-
   length = gtk_widget_path_length (path);
   if (length == 0)
     return FALSE;
 
-  list = gtk_widget_path_iter_list_regions (path, length - 1);
-  match = gtk_css_selector_matches_from (selector,
-                                         path,
-                                         length - 1,
-                                         list);
-  g_slist_free (list);
-  return match;
-}
-
-static guint
-count_bits (guint v)
-{
-  /* http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel */
-  v = v - ((v >> 1) & 0x55555555);
-  v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
-  return (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
+  return gtk_css_selector_match (selector,
+                                 state,
+                                 path,
+                                 length - 1);
 }
 
 /* Computes specificity according to CSS 2.1.
@@ -688,23 +773,17 @@ _gtk_css_selector_get_specificity (const GtkCssSelector *selector,
                                    guint                *classes,
                                    guint                *elements)
 {
-  GQuark *count;
-
-  if (selector->previous)
-    _gtk_css_selector_get_specificity (selector->previous, ids, classes, elements);
-
-  if (selector->ids)
-    for (count = selector->ids; *count; count++)
-      (*ids)++;
-
-  if (selector->classes)
-    for (count = selector->classes; *count; count++)
-      (*classes)++;
-  
-  *classes += count_bits (selector->state) + count_bits (selector->pseudo_classes);
-
-  if (selector->name)
-    (*elements)++;
+  for (; selector; selector = selector->previous)
+    {
+      const GtkCssSelectorClass *klass = selector->class;
+
+      if (klass->increase_id_specificity)
+        (*ids)++;
+      if (klass->increase_class_specificity)
+        (*classes)++;
+      if (klass->increase_element_specificity)
+        (*elements)++;
+    }
 }
 
 int
@@ -732,8 +811,13 @@ _gtk_css_selector_compare (const GtkCssSelector *a,
 GtkStateFlags
 _gtk_css_selector_get_state_flags (GtkCssSelector *selector)
 {
+  GtkStateFlags state = 0;
+
   g_return_val_if_fail (selector != NULL, 0);
 
-  return selector->state;
+  for (; selector && selector->class == &GTK_CSS_SELECTOR_NAME; selector = selector->previous)
+    state |= GPOINTER_TO_UINT (selector->data);
+
+  return state;
 }
 
diff --git a/tests/css/parser/selector.ref.css b/tests/css/parser/selector.ref.css
index e982145..5be0d41 100644
--- a/tests/css/parser/selector.ref.css
+++ b/tests/css/parser/selector.ref.css
@@ -38,7 +38,7 @@ a > b {
   int-property: 42;
 }
 
-.b {
+*.b {
   int-property: 42;
 }
 
@@ -50,7 +50,7 @@ a > b {
   int-property: 42;
 }
 
-:hover {
+*:hover {
   int-property: 42;
 }
 
@@ -150,7 +150,7 @@ a > :hover {
   int-property: 42;
 }
 
-.b:hover {
+:hover.b {
   int-property: 42;
 }
 
@@ -174,7 +174,7 @@ a > :hover {
   int-property: 42;
 }
 
-#b {
+*#b {
   int-property: 42;
 }
 
@@ -218,7 +218,7 @@ a > #b {
   int-property: 42;
 }
 
-#b.a {
+.a#b {
   int-property: 42;
 }
 
@@ -230,7 +230,7 @@ a > #b {
   int-property: 42;
 }
 
-#b:hover {
+:hover#b {
   int-property: 42;
 }
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]