[gtk+] css: Rewrite selectors



commit fc88b0f47c7dc198cc6674cb8819f5b43e45ef3c
Author: Benjamin Otte <otte redhat com>
Date:   Sat May 14 13:27:31 2011 +0200

    css: Rewrite selectors
    
    Selectors now go into their own C file. The new selectors are modeled a
    lot closer to the CSS spec. In particular the specificity computation
    matches CSS 2.1 exactly.
    
    For details about the why, see also:
    http://mail.gnome.org/archives/gtk-devel-list/2011-May/msg00061.html
    https://bugzilla.gnome.org/show_bug.cgi?id=649798

 gtk/Makefile.am             |    2 +
 gtk/gtkcssprovider.c        |  701 +++++++++----------------------------------
 gtk/gtkcssselector.c        |  440 +++++++++++++++++++++++++++
 gtk/gtkcssselectorprivate.h |   57 ++++
 4 files changed, 641 insertions(+), 559 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index f675830..4111938 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -388,6 +388,7 @@ gtk_private_h_sources =		\
 	gtkcellareaboxcontextprivate.h	\
 	gtkcssparserprivate.h	\
 	gtkcssproviderprivate.h	\
+	gtkcssselectorprivate.h	\
 	gtkcssstringfuncsprivate.h	\
 	gtkcustompaperunixdialog.h \
 	gtkdndcursors.h		\
@@ -515,6 +516,7 @@ gtk_base_c_sources = 		\
 	gtkcontainer.c		\
 	gtkcssparser.c		\
 	gtkcssprovider.c	\
+	gtkcssselector.c	\
 	gtkcssstringfuncs.c	\
 	gtkdialog.c		\
 	gtkdrawingarea.c	\
diff --git a/gtk/gtkcssprovider.c b/gtk/gtkcssprovider.c
index 234bcc4..191502a 100644
--- a/gtk/gtkcssprovider.c
+++ b/gtk/gtkcssprovider.c
@@ -28,6 +28,7 @@
 #include "gtkcssproviderprivate.h"
 
 #include "gtkcssparserprivate.h"
+#include "gtkcssselectorprivate.h"
 #include "gtkcssstringfuncsprivate.h"
 #include "gtksymboliccolor.h"
 #include "gtkstyleprovider.h"
@@ -733,57 +734,14 @@
  * </refsect2>
  */
 
-typedef struct SelectorElement SelectorElement;
-typedef struct SelectorPath SelectorPath;
 typedef struct SelectorStyleInfo SelectorStyleInfo;
 typedef struct _GtkCssScanner GtkCssScanner;
-typedef enum SelectorElementType SelectorElementType;
-typedef enum CombinatorType CombinatorType;
 typedef enum ParserScope ParserScope;
 typedef enum ParserSymbol ParserSymbol;
 
-enum SelectorElementType {
-  SELECTOR_TYPE_NAME,
-  SELECTOR_NAME,
-  SELECTOR_GTYPE,
-  SELECTOR_REGION,
-  SELECTOR_CLASS,
-  SELECTOR_GLOB
-};
-
-enum CombinatorType {
-  COMBINATOR_DESCENDANT, /* No direct relation needed */
-  COMBINATOR_CHILD       /* Direct child */
-};
-
-struct SelectorElement
-{
-  SelectorElementType elem_type;
-  CombinatorType combinator;
-
-  union
-  {
-    GQuark name;
-    GType type;
-
-    struct
-    {
-      GQuark name;
-      GtkRegionFlags flags;
-    } region;
-  };
-};
-
-struct SelectorPath
-{
-  GSList *elements;
-  GtkStateFlags state;
-  guint ref_count;
-};
-
 struct SelectorStyleInfo
 {
-  SelectorPath *path;
+  GtkCssSelector *selector;
   GHashTable *style;
 };
 
@@ -949,145 +907,13 @@ gtk_css_provider_take_error_full (GtkCssProvider *provider,
   g_error_free (error);
 }
 
-static SelectorPath *
-selector_path_new (void)
-{
-  SelectorPath *path;
-
-  path = g_slice_new0 (SelectorPath);
-  path->ref_count = 1;
-
-  return path;
-}
-
-static SelectorPath *
-selector_path_ref (SelectorPath *path)
-{
-  path->ref_count++;
-  return path;
-}
-
-static void
-selector_path_unref (SelectorPath *path)
-{
-  path->ref_count--;
-
-  if (path->ref_count > 0)
-    return;
-
-  while (path->elements)
-    {
-      g_slice_free (SelectorElement, path->elements->data);
-      path->elements = g_slist_delete_link (path->elements, path->elements);
-    }
-
-  g_slice_free (SelectorPath, path);
-}
-
-static void
-selector_path_prepend_type (SelectorPath *path,
-                            const gchar  *type_name)
-{
-  SelectorElement *elem;
-  GType type;
-
-  elem = g_slice_new (SelectorElement);
-  elem->combinator = COMBINATOR_DESCENDANT;
-  type = g_type_from_name (type_name);
-
-  if (type == G_TYPE_INVALID)
-    {
-      elem->elem_type = SELECTOR_TYPE_NAME;
-      elem->name = g_quark_from_string (type_name);
-    }
-  else
-    {
-      elem->elem_type = SELECTOR_GTYPE;
-      elem->type = type;
-    }
-
-  path->elements = g_slist_prepend (path->elements, elem);
-}
-
-static void
-selector_path_prepend_glob (SelectorPath *path)
-{
-  SelectorElement *elem;
-
-  elem = g_slice_new (SelectorElement);
-  elem->elem_type = SELECTOR_GLOB;
-  elem->combinator = COMBINATOR_DESCENDANT;
-
-  path->elements = g_slist_prepend (path->elements, elem);
-}
-
-static void
-selector_path_prepend_region (SelectorPath   *path,
-                              const gchar    *name,
-                              GtkRegionFlags  flags)
-{
-  SelectorElement *elem;
-
-  elem = g_slice_new (SelectorElement);
-  elem->combinator = COMBINATOR_DESCENDANT;
-  elem->elem_type = SELECTOR_REGION;
-
-  elem->region.name = g_quark_from_string (name);
-  elem->region.flags = flags;
-
-  path->elements = g_slist_prepend (path->elements, elem);
-}
-
-static void
-selector_path_prepend_name (SelectorPath *path,
-                            const gchar  *name)
-{
-  SelectorElement *elem;
-
-  elem = g_slice_new (SelectorElement);
-  elem->combinator = COMBINATOR_DESCENDANT;
-  elem->elem_type = SELECTOR_NAME;
-
-  elem->name = g_quark_from_string (name);
-
-  path->elements = g_slist_prepend (path->elements, elem);
-}
-
-static void
-selector_path_prepend_class (SelectorPath *path,
-                             const gchar  *name)
-{
-  SelectorElement *elem;
-
-  elem = g_slice_new (SelectorElement);
-  elem->combinator = COMBINATOR_DESCENDANT;
-  elem->elem_type = SELECTOR_CLASS;
-
-  elem->name = g_quark_from_string (name);
-
-  path->elements = g_slist_prepend (path->elements, elem);
-}
-
-static void
-selector_path_prepend_combinator (SelectorPath   *path,
-                                  CombinatorType  combinator)
-{
-  SelectorElement *elem;
-
-  g_assert (path->elements != NULL);
-
-  /* It is actually stored in the last element */
-  elem = path->elements->data;
-  elem->combinator = combinator;
-}
-
 static SelectorStyleInfo *
-selector_style_info_new (SelectorPath *path)
+selector_style_info_new (GtkCssSelector *selector)
 {
   SelectorStyleInfo *info;
 
   info = g_slice_new0 (SelectorStyleInfo);
-  info->path = selector_path_ref (path);
+  info->selector = selector;
 
   return info;
 }
@@ -1098,8 +924,8 @@ selector_style_info_free (SelectorStyleInfo *info)
   if (info->style)
     g_hash_table_unref (info->style);
 
-  if (info->path)
-    selector_path_unref (info->path);
+  if (info->selector)
+    _gtk_css_selector_free (info->selector);
 
   g_slice_free (SelectorStyleInfo, info);
 }
@@ -1132,8 +958,7 @@ gtk_css_scanner_reset (GtkCssScanner *scanner)
   g_slist_free (scanner->state);
   scanner->state = NULL;
 
-  g_slist_foreach (scanner->cur_selectors, (GFunc) selector_path_unref, NULL);
-  g_slist_free (scanner->cur_selectors);
+  g_slist_free_full (scanner->cur_selectors, (GDestroyNotify) _gtk_css_selector_free);
   scanner->cur_selectors = NULL;
 
   if (scanner->cur_properties)
@@ -1252,196 +1077,10 @@ gtk_css_provider_init (GtkCssProvider *css_provider)
                                                  (GDestroyNotify) gtk_symbolic_color_unref);
 }
 
-typedef struct ComparePathData ComparePathData;
-
-struct ComparePathData
-{
-  guint64 score;
-  SelectorPath *path;
-  GSList *iter;
-};
-
-static gboolean
-compare_selector_element (GtkWidgetPath   *path,
-                          guint            index,
-                          SelectorElement *elem,
-                          guint8          *score)
-{
-  *score = 0;
-
-  if (elem->elem_type == SELECTOR_TYPE_NAME)
-    {
-      const gchar *type_name;
-      GType resolved_type;
-
-      /* Resolve the type name */
-      type_name = g_quark_to_string (elem->name);
-      resolved_type = g_type_from_name (type_name);
-
-      if (resolved_type == G_TYPE_INVALID)
-        {
-          /* Type couldn't be resolved, so the selector
-           * clearly doesn't affect the given widget path
-           */
-          return FALSE;
-        }
-
-      elem->elem_type = SELECTOR_GTYPE;
-      elem->type = resolved_type;
-    }
-
-  if (elem->elem_type == SELECTOR_GTYPE)
-    {
-      GType type;
-
-      type = gtk_widget_path_iter_get_object_type (path, index);
-
-      if (!g_type_is_a (type, elem->type))
-        return FALSE;
-
-      if (type == elem->type)
-        *score |= 0xF;
-      else
-        {
-          guint diff = g_type_depth (type) - g_type_depth (elem->type);
-
-          if (G_UNLIKELY (diff > 0xE))
-            {
-              g_warning ("Hierarchy is higher than expected.");
-              diff = 0xE;
-            }
-          
-          *score = 0XF - diff;
-        }
-
-      return TRUE;
-    }
-  else if (elem->elem_type == SELECTOR_REGION)
-    {
-      GtkRegionFlags flags;
-
-      if (!gtk_widget_path_iter_has_qregion (path, index,
-                                             elem->region.name,
-                                             &flags))
-        return FALSE;
-
-      if (elem->region.flags != 0 &&
-          (flags & elem->region.flags) == 0)
-        return FALSE;
-
-      *score = 0xF;
-      return TRUE;
-    }
-  else if (elem->elem_type == SELECTOR_GLOB)
-    {
-      /* Treat as lowest matching type */
-      *score = 1;
-      return TRUE;
-    }
-  else if (elem->elem_type == SELECTOR_NAME)
-    {
-      if (!gtk_widget_path_iter_has_qname (path, index, elem->name))
-        return FALSE;
-
-      *score = 0xF;
-      return TRUE;
-    }
-  else if (elem->elem_type == SELECTOR_CLASS)
-    {
-      if (!gtk_widget_path_iter_has_qclass (path, index, elem->name))
-        return FALSE;
-
-      *score = 0xF;
-      return TRUE;
-    }
-
-  return FALSE;
-}
-
-static guint64
-compare_selector (GtkWidgetPath *path,
-                  SelectorPath  *selector)
-{
-  GSList *elements = selector->elements;
-  gboolean match = TRUE, first = TRUE, first_match = FALSE;
-  guint64 score = 0;
-  gint i;
-
-  i = gtk_widget_path_length (path) - 1;
-
-  while (elements && match && i >= 0)
-    {
-      SelectorElement *elem;
-      guint8 elem_score;
-
-      elem = elements->data;
-
-      match = compare_selector_element (path, i, elem, &elem_score);
-
-      if (match && first)
-        first_match = TRUE;
-
-      /* Only move on to the next index if there is no match
-       * with the current element (whether to continue or not
-       * handled right after in the combinator check), or a
-       * GType or glob has just been matched.
-       *
-       * Region and widget names do not trigger this because
-       * the next element in the selector path could also be
-       * related to the same index.
-       */
-      if (!match ||
-          (elem->elem_type == SELECTOR_GTYPE ||
-           elem->elem_type == SELECTOR_GLOB))
-        i--;
-
-      if (!match &&
-          elem->elem_type != SELECTOR_TYPE_NAME &&
-          elem->combinator == COMBINATOR_DESCENDANT)
-        {
-          /* With descendant combinators there may
-           * be intermediate chidren in the hierarchy
-           */
-          match = TRUE;
-        }
-      else if (match)
-        elements = elements->next;
-
-      if (match)
-        {
-          /* Only 4 bits are actually used */
-          score <<= 4;
-          score |= elem_score;
-        }
-
-      first = FALSE;
-    }
-
-  /* If there are pending selector
-   * elements to compare, it's not
-   * a match.
-   */
-  if (elements)
-    match = FALSE;
-
-  if (!match)
-    score = 0;
-  else if (first_match)
-    {
-      /* Assign more weight to these selectors
-       * that matched right from the first element.
-       */
-      score <<= 4;
-    }
-
-  return score;
-}
-
 typedef struct StylePriorityInfo StylePriorityInfo;
 
 struct StylePriorityInfo
 {
-  guint64 score;
   GHashTable *style;
   GtkStateFlags state;
 };
@@ -1452,7 +1091,7 @@ css_provider_get_selectors (GtkCssProvider *css_provider,
 {
   GtkCssProviderPrivate *priv;
   GArray *priority_info;
-  guint i, j;
+  guint i;
 
   priv = css_provider->priv;
   priority_info = g_array_new (FALSE, FALSE, sizeof (StylePriorityInfo));
@@ -1461,35 +1100,16 @@ css_provider_get_selectors (GtkCssProvider *css_provider,
     {
       SelectorStyleInfo *info;
       StylePriorityInfo new;
-      gboolean added = FALSE;
-      guint64 score;
 
       info = g_ptr_array_index (priv->selectors_info, i);
-      score = compare_selector (path, info->path);
-
-      if (score <= 0)
-        continue;
-
-      new.score = score;
-      new.style = info->style;
-      new.state = info->path->state;
 
-      for (j = 0; j < priority_info->len; j++)
+      if (_gtk_css_selector_matches (info->selector, path))
         {
-          StylePriorityInfo *cur;
+          new.style = info->style;
+          new.state = _gtk_css_selector_get_state_flags (info->selector);
 
-          cur = &g_array_index (priority_info, StylePriorityInfo, j);
-
-          if (cur->score > new.score)
-            {
-              g_array_insert_val (priority_info, j, new);
-              added = TRUE;
-              break;
-            }
+          g_array_append_val (priority_info, new);
         }
-
-      if (!added)
-        g_array_append_val (priority_info, new);
     }
 
   return priority_info;
@@ -1728,18 +1348,24 @@ css_provider_commit (GtkCssProvider *css_provider,
   priv = css_provider->priv;
 
   if (g_hash_table_size (properties) == 0)
-    return;
+    {
+      g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free);
+      g_hash_table_unref (properties);
+      return;
+    }
 
   for (l = selectors; l; l = l->next)
     {
-      SelectorPath *path = l->data;
+      GtkCssSelector *selector = l->data;
       SelectorStyleInfo *info;
 
-      info = selector_style_info_new (path);
+      info = selector_style_info_new (selector);
       selector_style_info_set_style (info, properties);
 
       g_ptr_array_add (priv->selectors_info, info);
     }
+
+  g_hash_table_unref (properties);
 }
 
 static void
@@ -1990,7 +1616,8 @@ parse_at_keyword (GtkCssScanner *scanner)
 }
 
 static gboolean
-parse_selector_pseudo_class (GtkCssScanner *scanner, SelectorPath *path)
+parse_selector_pseudo_class (GtkCssScanner *scanner,
+                             GtkStateFlags *flags_to_modify)
 {
   struct {
     const char *name;
@@ -2011,7 +1638,15 @@ parse_selector_pseudo_class (GtkCssScanner *scanner, SelectorPath *path)
     {
       if (_gtk_css_parser_try (scanner->parser, classes[i].name, FALSE))
         {
-          path->state |= classes[i].flag;
+          if (*flags_to_modify & classes[i].flag)
+            {
+              gtk_css_provider_error (scanner->provider,
+                                      scanner,
+                                      GTK_CSS_PROVIDER_ERROR,
+                                      GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                      "Duplicate pseudo-class %s in selector", classes[i].name);
+            }
+          *flags_to_modify |= classes[i].flag;
           return TRUE;
         }
     }
@@ -2025,9 +1660,12 @@ parse_selector_pseudo_class (GtkCssScanner *scanner, SelectorPath *path)
 }
 
 static gboolean
-parse_selector_class (GtkCssScanner *scanner, SelectorPath *path)
+parse_selector_class (GtkCssScanner *scanner, GArray *classes)
 {
-  char *name = _gtk_css_parser_try_name (scanner->parser, FALSE);
+  GQuark qname;
+  char *name;
+    
+  name = _gtk_css_parser_try_name (scanner->parser, FALSE);
 
   if (name == NULL)
     {
@@ -2035,19 +1673,23 @@ parse_selector_class (GtkCssScanner *scanner, SelectorPath *path)
                                       scanner,
                                       GTK_CSS_PROVIDER_ERROR,
                                       GTK_CSS_PROVIDER_ERROR_SYNTAX,
-                                      "Expected a valid name");
+                                      "Expected a valid name for class");
       return FALSE;
     }
 
-  selector_path_prepend_combinator (path, COMBINATOR_CHILD);
-  selector_path_prepend_class (path, name);
+  qname = g_quark_from_string (name);
+  g_array_append_val (classes, qname);
+  g_free (name);
   return TRUE;
 }
 
 static gboolean
-parse_selector_name (GtkCssScanner *scanner, SelectorPath *path)
+parse_selector_name (GtkCssScanner *scanner, GArray *names)
 {
-  char *name = _gtk_css_parser_try_name (scanner->parser, FALSE);
+  GQuark qname;
+  char *name;
+    
+  name = _gtk_css_parser_try_name (scanner->parser, FALSE);
 
   if (name == NULL)
     {
@@ -2055,19 +1697,20 @@ parse_selector_name (GtkCssScanner *scanner, SelectorPath *path)
                                       scanner,
                                       GTK_CSS_PROVIDER_ERROR,
                                       GTK_CSS_PROVIDER_ERROR_SYNTAX,
-                                      "Expected a valid name");
+                                      "Expected a valid name for id");
       return FALSE;
     }
 
-  selector_path_prepend_combinator (path, COMBINATOR_CHILD);
-  selector_path_prepend_name (path, name);
+  qname = g_quark_from_string (name);
+  g_array_append_val (names, qname);
+  g_free (name);
   return TRUE;
 }
 
 static gboolean
 parse_selector_pseudo_class_for_region (GtkCssScanner  *scanner,
-                                        SelectorPath   *path,
-                                        GtkRegionFlags *flags_to_modify)
+                                        GtkRegionFlags *flags_to_modify,
+                                        GtkStateFlags  *state_to_modify)
 {
   struct {
     const char *name;
@@ -2094,7 +1737,7 @@ parse_selector_pseudo_class_for_region (GtkCssScanner  *scanner,
     }
 
   if (!_gtk_css_parser_try (scanner->parser, "nth-child(", TRUE))
-    return parse_selector_pseudo_class (scanner, path);
+    return parse_selector_pseudo_class (scanner, state_to_modify);
 
   for (i = 0; i < G_N_ELEMENTS (nth_child); i++)
     {
@@ -2129,60 +1772,54 @@ parse_selector_pseudo_class_for_region (GtkCssScanner  *scanner,
 }
 
 static gboolean
-parse_simple_selector (GtkCssScanner *scanner, SelectorPath *path)
+parse_simple_selector (GtkCssScanner *scanner,
+                       char **name,
+                       GArray *ids,
+                       GArray *classes,
+                       GtkRegionFlags *pseudo_classes,
+                       GtkStateFlags *state)
 {
-  char *name;
   gboolean parsed_something;
   
-  name = _gtk_css_parser_try_ident (scanner->parser, FALSE);
-  if (name)
+  *name = _gtk_css_parser_try_ident (scanner->parser, FALSE);
+  if (*name)
     {
-      if (_gtk_style_context_check_region_name (name))
+      if (_gtk_style_context_check_region_name (*name))
         {
-          GtkRegionFlags flags;
-          
-          flags = 0;
-
           while (_gtk_css_parser_try (scanner->parser, ":", FALSE))
             {
-              if (!parse_selector_pseudo_class_for_region (scanner, path, &flags))
+              if (!parse_selector_pseudo_class_for_region (scanner, pseudo_classes, state))
                 {
                   g_free (name);
                   return FALSE;
                 }
             }
 
-          selector_path_prepend_region (path, name, flags);
-          g_free (name);
           _gtk_css_parser_skip_whitespace (scanner->parser);
           return TRUE;
         }
-      else
-        {
-          selector_path_prepend_type (path, name);
-          parsed_something = TRUE;
-        }
+      
+      parsed_something = TRUE;
     }
   else
     {
       parsed_something = _gtk_css_parser_try (scanner->parser, "*", FALSE);
-      selector_path_prepend_glob (path);
     }
 
   do {
       if (_gtk_css_parser_try (scanner->parser, "#", FALSE))
         {
-          if (!parse_selector_name (scanner, path))
+          if (!parse_selector_name (scanner, ids))
             return FALSE;
         }
       else if (_gtk_css_parser_try (scanner->parser, ".", FALSE))
         {
-          if (!parse_selector_class (scanner, path))
+          if (!parse_selector_class (scanner, classes))
             return FALSE;
         }
       else if (_gtk_css_parser_try (scanner->parser, ":", FALSE))
         {
-          if (!parse_selector_pseudo_class (scanner, path))
+          if (!parse_selector_pseudo_class (scanner, state))
             return FALSE;
         }
       else if (!parsed_something)
@@ -2205,29 +1842,48 @@ parse_simple_selector (GtkCssScanner *scanner, SelectorPath *path)
   return TRUE;
 }
 
-static SelectorPath *
+static GtkCssSelector *
 parse_selector (GtkCssScanner *scanner)
 {
-  SelectorPath *path;
-
-  path = selector_path_new ();
+  GtkCssSelector *selector = NULL;
 
   do {
-      if (!parse_simple_selector (scanner, path))
+      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)
         {
-          selector_path_unref (path);
+          if (_gtk_css_parser_try (scanner->parser, ">", TRUE))
+            combine = GTK_CSS_COMBINE_CHILD;
+        }
+
+      if (!parse_simple_selector (scanner, &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;
         }
 
-      if (_gtk_css_parser_try (scanner->parser, ">", TRUE))
-        selector_path_prepend_combinator (path, COMBINATOR_CHILD);
+      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 (path->state == 0 &&
-         !_gtk_css_parser_is_eof (scanner->parser) &&
+  while (!_gtk_css_parser_is_eof (scanner->parser) &&
          !_gtk_css_parser_begins_with (scanner->parser, ',') &&
          !_gtk_css_parser_begins_with (scanner->parser, '{'));
 
-  return path;
+  return selector;
 }
 
 static GSList *
@@ -2236,16 +1892,16 @@ parse_selector_list (GtkCssScanner *scanner)
   GSList *selectors = NULL;
 
   do {
-      SelectorPath *path = parse_selector (scanner);
+      GtkCssSelector *select = parse_selector (scanner);
 
-      if (path == NULL)
+      if (select == NULL)
         {
-          g_slist_free_full (selectors, (GDestroyNotify) selector_path_unref);
+          g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free);
           _gtk_css_parser_resync (scanner->parser, FALSE, 0);
           return NULL;
         }
 
-      selectors = g_slist_prepend (selectors, path);
+      selectors = g_slist_prepend (selectors, select);
     }
   while (_gtk_css_parser_try (scanner->parser, ",", TRUE));
 
@@ -2412,7 +2068,7 @@ parse_ruleset (GtkCssScanner *scanner)
                                       GTK_CSS_PROVIDER_ERROR_SYNTAX,
                                       "expected '{' after selectors");
       _gtk_css_parser_resync (scanner->parser, FALSE, 0);
-      g_slist_free_full (selectors, (GDestroyNotify) selector_path_unref);
+      g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free);
       return;
     }
 
@@ -2428,7 +2084,7 @@ parse_ruleset (GtkCssScanner *scanner)
       if (!_gtk_css_parser_is_eof (scanner->parser))
         {
           _gtk_css_parser_resync (scanner->parser, FALSE, 0);
-          g_slist_free_full (selectors, (GDestroyNotify) selector_path_unref);
+          g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free);
           if (properties)
             g_hash_table_unref (properties);
           return;
@@ -2436,11 +2092,9 @@ parse_ruleset (GtkCssScanner *scanner)
     }
 
   if (properties)
-    {
-      css_provider_commit (scanner->provider, selectors, properties);
-      g_hash_table_unref (properties);
-    }
-  g_slist_free_full (selectors, (GDestroyNotify) selector_path_unref);
+    css_provider_commit (scanner->provider, selectors, properties);
+  else
+    g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free);
 }
 
 static void
@@ -2453,7 +2107,7 @@ parse_statement (GtkCssScanner *scanner)
 }
 
 static void
-parse_stylesheet (GtkCssScanner   *scanner)
+parse_stylesheet (GtkCssScanner *scanner)
 {
   _gtk_css_parser_skip_whitespace (scanner->parser);
 
@@ -2467,6 +2121,36 @@ parse_stylesheet (GtkCssScanner   *scanner)
     }
 }
 
+static int
+gtk_css_provider_compare_rule (gconstpointer a_,
+                               gconstpointer b_)
+{
+  const SelectorStyleInfo *a = *(const SelectorStyleInfo **) a_;
+  const SelectorStyleInfo *b = *(const SelectorStyleInfo **) b_;
+  int compare;
+
+  compare = _gtk_css_selector_compare (a->selector, b->selector);
+  if (compare != 0)
+    return compare;
+
+  /* compare pointers in array to ensure a stable sort */
+  if (a_ < b_)
+    return -1;
+
+  if (a_ > b_)
+    return 1;
+
+  return 0;
+}
+
+static void
+gtk_css_provider_postprocess (GtkCssProvider *css_provider)
+{
+  GtkCssProviderPrivate *priv = css_provider->priv;
+
+  g_ptr_array_sort (priv->selectors_info, gtk_css_provider_compare_rule);
+}
+
 static gboolean
 gtk_css_provider_load_internal (GtkCssProvider *css_provider,
                                 GtkCssScanner  *parent,
@@ -2528,6 +2212,9 @@ gtk_css_provider_load_internal (GtkCssProvider *css_provider,
       parse_stylesheet (scanner);
 
       gtk_css_scanner_destroy (scanner);
+
+      if (parent == NULL)
+        gtk_css_provider_postprocess (css_provider);
     }
 
   if (error)
@@ -3133,117 +2820,13 @@ gtk_css_provider_get_named (const gchar *name,
 }
 
 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);
+  _gtk_css_selector_print (info->selector, str);
 
   g_string_append (str, " {\n");
 
diff --git a/gtk/gtkcssselector.c b/gtk/gtkcssselector.c
new file mode 100644
index 0000000..799f3a0
--- /dev/null
+++ b/gtk/gtkcssselector.c
@@ -0,0 +1,440 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2011 Benjamin Otte <otte gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "gtkcssselectorprivate.h"
+
+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) */
+};
+
+GtkCssSelector *
+_gtk_css_selector_new (GtkCssSelector         *previous,
+                       GtkCssCombinator        combine,
+                       const char *            name,
+                       GQuark *                ids,
+                       GQuark *                classes,
+                       GtkRegionFlags          pseudo_classes,
+                       GtkStateFlags           state)
+{
+  GtkCssSelector *selector;
+
+  selector = g_slice_new0 (GtkCssSelector);
+  selector->previous = previous;
+  selector->combine = combine;
+  selector->name = name ? g_quark_to_string (g_quark_from_string (name)) : NULL;
+  selector->type = G_TYPE_INVALID;
+  selector->ids = ids;
+  selector->classes = classes;
+  selector->pseudo_classes = pseudo_classes;
+  selector->state = state;
+
+  return selector;
+}
+
+void
+_gtk_css_selector_free (GtkCssSelector *selector)
+{
+  g_return_if_fail (selector != NULL);
+
+  if (selector->previous)
+    _gtk_css_selector_free (selector->previous);
+
+  g_free (selector->ids);
+  g_free (selector->classes);
+
+  g_slice_free (GtkCssSelector, selector);
+}
+
+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));
+        }
+    }
+
+  if (selector->pseudo_classes)
+    {
+      static const 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 (selector->pseudo_classes & (1 << i))
+            {
+              g_string_append_c (str, ':');
+              g_string_append (str, flag_names[i]);
+            }
+        }
+    }
+
+  if (selector->state)
+    {
+      static const char * state_names[] = {
+        "active",
+        "hover",
+        "selected",
+        "insensitive",
+        "inconsistent",
+        "focus"
+      };
+      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]);
+            }
+        }
+    }
+}
+
+char *
+_gtk_css_selector_to_string (const GtkCssSelector *selector)
+{
+  GString *string;
+
+  g_return_val_if_fail (selector != NULL, NULL);
+
+  string = g_string_new (NULL);
+
+  _gtk_css_selector_print (selector, string);
+
+  return g_string_free (string, FALSE);
+}
+
+static gboolean
+gtk_css_selector_matches_type (const GtkCssSelector      *selector,
+                               /* const */ GtkWidgetPath *path,
+                               guint                      id)
+{
+  if (selector->name == NULL)
+    return TRUE;
+
+  if (selector->pseudo_classes)
+    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;
+}
+
+gboolean
+_gtk_css_selector_matches (const GtkCssSelector      *selector,
+                           /* const */ GtkWidgetPath *path)
+{
+  GSList *list;
+  guint length;
+  gboolean match;
+
+  g_return_val_if_fail (selector != NULL, FALSE);
+  g_return_val_if_fail (path != NULL, 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;
+}
+
+/* Computes specificity according to CSS 2.1.
+ * The arguments must be initialized to 0 */
+static void
+_gtk_css_selector_get_specificity (const GtkCssSelector *selector,
+                                   guint                *ids,
+                                   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)++;
+}
+
+int
+_gtk_css_selector_compare (const GtkCssSelector *a,
+                           const GtkCssSelector *b)
+{
+  guint a_ids = 0, a_classes = 0, a_elements = 0;
+  guint b_ids = 0, b_classes = 0, b_elements = 0;
+  int compare;
+
+  _gtk_css_selector_get_specificity (a, &a_ids, &a_classes, &a_elements);
+  _gtk_css_selector_get_specificity (b, &b_ids, &b_classes, &b_elements);
+
+  compare = a_ids - b_ids;
+  if (compare)
+    return compare;
+
+  compare = a_classes - b_classes;
+  if (compare)
+    return compare;
+
+  return a_elements - b_elements;
+}
+
+GtkStateFlags
+_gtk_css_selector_get_state_flags (GtkCssSelector *selector)
+{
+  g_return_val_if_fail (selector != NULL, 0);
+
+  return selector->state;
+}
+
diff --git a/gtk/gtkcssselectorprivate.h b/gtk/gtkcssselectorprivate.h
new file mode 100644
index 0000000..70c5e60
--- /dev/null
+++ b/gtk/gtkcssselectorprivate.h
@@ -0,0 +1,57 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2011 Benjamin Otte <otte gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GTK_CSS_SELECTOR_PRIVATE_H__
+#define __GTK_CSS_SELECTOR_PRIVATE_H__
+
+#include <gtk/gtkenums.h>
+#include <gtk/gtkwidgetpath.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+  GTK_CSS_COMBINE_DESCANDANT,
+  GTK_CSS_COMBINE_CHILD
+} GtkCssCombinator;
+
+typedef struct _GtkCssSelector GtkCssSelector;
+
+GtkCssSelector *  _gtk_css_selector_new             (GtkCssSelector         *previous,
+                                                     GtkCssCombinator        combine,
+                                                     const char *            name,
+                                                     GQuark *                ids,
+                                                     GQuark *                classes,
+                                                     GtkRegionFlags          pseudo_classes,
+                                                     GtkStateFlags           state);
+void              _gtk_css_selector_free            (GtkCssSelector         *selector);
+
+char *            _gtk_css_selector_to_string       (const GtkCssSelector   *selector);
+void              _gtk_css_selector_print           (const GtkCssSelector   *selector,
+                                                     GString                *str);
+
+GtkStateFlags     _gtk_css_selector_get_state_flags (GtkCssSelector         *selector);
+
+gboolean          _gtk_css_selector_matches         (const GtkCssSelector   *selector,
+                                                     /* const */ GtkWidgetPath *path);
+int               _gtk_css_selector_compare         (const GtkCssSelector   *a,
+                                                     const GtkCssSelector   *b);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_SELECTOR_PRIVATE_H__ */



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