1975 lines
		
	
	
		
			55 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1975 lines
		
	
	
		
			55 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* 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, see <http://www.gnu.org/licenses/>.
 | |
|  */
 | |
| 
 | |
| #include "config.h"
 | |
| 
 | |
| #include "gtkcssselectorprivate.h"
 | |
| 
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| 
 | |
| #include "gtkcssprovider.h"
 | |
| #include "gtkstylecontextprivate.h"
 | |
| 
 | |
| #if defined(_MSC_VER) && _MSC_VER >= 1500
 | |
| # include <intrin.h>
 | |
| #endif
 | |
| 
 | |
| typedef struct _GtkCssSelectorClass GtkCssSelectorClass;
 | |
| typedef gboolean (* GtkCssSelectorForeachFunc) (const GtkCssSelector *selector,
 | |
|                                                 const GtkCssMatcher  *matcher,
 | |
|                                                 gpointer              data);
 | |
| 
 | |
| struct _GtkCssSelectorClass {
 | |
|   const char        *name;
 | |
| 
 | |
|   void              (* print)       (const GtkCssSelector       *selector,
 | |
|                                      GString                    *string);
 | |
|   /* NULL or an iterator that calls func with each submatcher of @matcher.
 | |
|    * Potentially no submatcher exists.
 | |
|    * If any @invocation of @func returns %TRUE, the function will immediately
 | |
|    * return %TRUE itself. If @func never returns %TRUE (or isn't called at all),
 | |
|    * %FALSE will be returned.
 | |
|    */
 | |
|   gboolean          (* foreach_matcher)  (const GtkCssSelector       *selector,
 | |
|                                           const GtkCssMatcher        *matcher,
 | |
|                                           GtkCssSelectorForeachFunc   func,
 | |
|                                           gpointer                    data);
 | |
|   gboolean          (* match_one)   (const GtkCssSelector       *selector,
 | |
|                                      const GtkCssMatcher        *matcher);
 | |
|   GtkCssChange      (* get_change)  (const GtkCssSelector       *selector,
 | |
| 				     GtkCssChange                previous_change);
 | |
|   void              (* add_specificity)  (const GtkCssSelector  *selector,
 | |
|                                           guint                 *ids,
 | |
|                                           guint                 *classes,
 | |
|                                           guint                 *elements);
 | |
|   guint             (* hash_one)    (const GtkCssSelector       *selector);
 | |
|   int               (* compare_one) (const GtkCssSelector       *a,
 | |
| 				     const GtkCssSelector       *b);
 | |
| 
 | |
|   guint         is_simple :1;
 | |
| };
 | |
| 
 | |
| typedef struct {
 | |
|   GType type;
 | |
|   const char *name;
 | |
| } TypeReference;
 | |
| 
 | |
| typedef enum {
 | |
|   POSITION_FORWARD,
 | |
|   POSITION_BACKWARD,
 | |
|   POSITION_ONLY,
 | |
|   POSITION_SORTED
 | |
| } PositionType;
 | |
| #define POSITION_TYPE_BITS 4
 | |
| #define POSITION_NUMBER_BITS ((sizeof (gpointer) * 8 - POSITION_TYPE_BITS) / 2)
 | |
| 
 | |
| union _GtkCssSelector
 | |
| {
 | |
|   const GtkCssSelectorClass     *class;         /* type of check this selector does */
 | |
|   struct {
 | |
|     const GtkCssSelectorClass   *class;
 | |
|     const char                  *name;          /* interned */
 | |
|   }                              id;
 | |
|   struct {
 | |
|     const GtkCssSelectorClass   *class;
 | |
|     const char                  *name;          /* interned */
 | |
|     GtkRegionFlags               flags;
 | |
|   }                              region;
 | |
|   struct {
 | |
|     const GtkCssSelectorClass   *class;
 | |
|     GQuark                       style_class;
 | |
|   }                              style_class;
 | |
|   struct {
 | |
|     const GtkCssSelectorClass   *class;
 | |
|     const TypeReference         *reference;
 | |
|   }                              name;
 | |
|   struct {
 | |
|     const GtkCssSelectorClass   *class;
 | |
|     GtkStateFlags                state;
 | |
|   }                              state;
 | |
|   struct {
 | |
|     const GtkCssSelectorClass   *class;
 | |
|     PositionType                 type :POSITION_TYPE_BITS;
 | |
|     gssize                       a :POSITION_NUMBER_BITS;
 | |
|     gssize                       b :POSITION_NUMBER_BITS;
 | |
|   }                              position;
 | |
| };
 | |
| 
 | |
| #define GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET G_MAXINT32
 | |
| struct _GtkCssSelectorTree
 | |
| {
 | |
|   GtkCssSelector selector;
 | |
|   gint32 parent_offset;
 | |
|   gint32 previous_offset;
 | |
|   gint32 sibling_offset;
 | |
|   gint32 matches_offset; /* pointers that we return as matches if selector matches */
 | |
| };
 | |
| 
 | |
| static gboolean
 | |
| gtk_css_selector_equal (const GtkCssSelector *a,
 | |
| 			const GtkCssSelector *b)
 | |
| {
 | |
|   return
 | |
|     a->class == b->class &&
 | |
|     a->class->compare_one (a, b) == 0;
 | |
| }
 | |
| 
 | |
| static guint
 | |
| gtk_css_selector_hash_one (const GtkCssSelector *selector)
 | |
| {
 | |
|   return GPOINTER_TO_UINT (selector->class) ^ selector->class->hash_one (selector);
 | |
| }
 | |
| 
 | |
| static gpointer *
 | |
| gtk_css_selector_tree_get_matches (const GtkCssSelectorTree *tree)
 | |
| {
 | |
|   if (tree->matches_offset == GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
 | |
|     return NULL;
 | |
| 
 | |
|   return (gpointer *) ((guint8 *)tree + tree->matches_offset);
 | |
| }
 | |
| 
 | |
| static void
 | |
| g_ptr_array_insert_sorted (GPtrArray *array,
 | |
|                            gpointer   data)
 | |
| {
 | |
|   gint i;
 | |
| 
 | |
|   for (i = 0; i < array->len; i++)
 | |
|     {
 | |
|       if (data == array->pdata[i])
 | |
|         return;
 | |
| 
 | |
|       if (data < array->pdata[i])
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|   g_ptr_array_insert (array, i, data);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_css_selector_tree_found_match (const GtkCssSelectorTree  *tree,
 | |
| 				   GPtrArray                **array)
 | |
| {
 | |
|   int i;
 | |
|   gpointer *matches;
 | |
| 
 | |
|   matches = gtk_css_selector_tree_get_matches (tree);
 | |
|   if (matches)
 | |
|     {
 | |
|       if (!*array)
 | |
|         *array = g_ptr_array_sized_new (16);
 | |
| 
 | |
|       for (i = 0; matches[i] != NULL; i++)
 | |
|         g_ptr_array_insert_sorted (*array, matches[i]);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gtk_css_selector_match (const GtkCssSelector *selector,
 | |
|                         const GtkCssMatcher  *matcher)
 | |
| {
 | |
|   return selector->class->match_one (selector, matcher);
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gtk_css_selector_foreach (const GtkCssSelector      *selector,
 | |
|                           const GtkCssMatcher       *matcher,
 | |
|                           GtkCssSelectorForeachFunc  func,
 | |
|                           gpointer                   data)
 | |
| {
 | |
|   return selector->class->foreach_matcher (selector, matcher, func, data);
 | |
| }
 | |
| 
 | |
| static int
 | |
| gtk_css_selector_compare_one (const GtkCssSelector *a, const GtkCssSelector *b)
 | |
| {
 | |
|   if (a->class != b->class)
 | |
|     return strcmp (a->class->name, b->class->name);
 | |
|   else
 | |
|     return a->class->compare_one (a, b);
 | |
| }
 | |
|   
 | |
| static const GtkCssSelector *
 | |
| gtk_css_selector_previous (const GtkCssSelector *selector)
 | |
| {
 | |
|   selector = selector + 1;
 | |
| 
 | |
|   return selector->class ? selector : NULL;
 | |
| }
 | |
| 
 | |
| static const GtkCssSelectorTree *
 | |
| gtk_css_selector_tree_at_offset (const GtkCssSelectorTree *tree,
 | |
| 				 gint32 offset)
 | |
| {
 | |
|   if (offset == GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
 | |
|     return NULL;
 | |
| 
 | |
|   return (GtkCssSelectorTree *) ((guint8 *)tree + offset);
 | |
| }
 | |
| 
 | |
| static const GtkCssSelectorTree *
 | |
| gtk_css_selector_tree_get_parent (const GtkCssSelectorTree *tree)
 | |
| {
 | |
|   return gtk_css_selector_tree_at_offset (tree, tree->parent_offset);
 | |
| }
 | |
| 
 | |
| static const GtkCssSelectorTree *
 | |
| gtk_css_selector_tree_get_previous (const GtkCssSelectorTree *tree)
 | |
| {
 | |
|   return gtk_css_selector_tree_at_offset (tree, tree->previous_offset);
 | |
| }
 | |
| 
 | |
| static const GtkCssSelectorTree *
 | |
| gtk_css_selector_tree_get_sibling (const GtkCssSelectorTree *tree)
 | |
| {
 | |
|   return gtk_css_selector_tree_at_offset (tree, tree->sibling_offset);
 | |
| }
 | |
| 
 | |
| /* DEFAULTS */
 | |
| 
 | |
| static void
 | |
| gtk_css_selector_default_add_specificity (const GtkCssSelector *selector,
 | |
|                                           guint                *ids,
 | |
|                                           guint                *classes,
 | |
|                                           guint                *elements)
 | |
| {
 | |
|   /* no specificity changes */
 | |
| }
 | |
|  
 | |
| static gboolean
 | |
| gtk_css_selector_default_foreach_matcher (const GtkCssSelector       *selector,
 | |
|                                           const GtkCssMatcher        *matcher,
 | |
|                                           GtkCssSelectorForeachFunc   func,
 | |
|                                           gpointer                    data)
 | |
| {
 | |
|   return func (selector, matcher, data);
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gtk_css_selector_default_match_one (const GtkCssSelector *selector,
 | |
|                                     const GtkCssMatcher  *matcher)
 | |
| {
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static guint
 | |
| gtk_css_selector_default_hash_one (const GtkCssSelector *selector)
 | |
| {
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static int
 | |
| gtk_css_selector_default_compare_one (const GtkCssSelector *a,
 | |
|                                       const GtkCssSelector *b)
 | |
| {
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /* DESCENDANT */
 | |
| 
 | |
| static void
 | |
| gtk_css_selector_descendant_print (const GtkCssSelector *selector,
 | |
|                                    GString              *string)
 | |
| {
 | |
|   g_string_append_c (string, ' ');
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gtk_css_selector_descendant_foreach_matcher (const GtkCssSelector      *selector,
 | |
|                                              const GtkCssMatcher       *matcher,
 | |
|                                              GtkCssSelectorForeachFunc  func,
 | |
|                                              gpointer                   data)
 | |
| {
 | |
|   GtkCssMatcher ancestor;
 | |
| 
 | |
|   while (_gtk_css_matcher_get_parent (&ancestor, matcher))
 | |
|     {
 | |
|       matcher = &ancestor;
 | |
| 
 | |
|       if (func (selector, &ancestor, data))
 | |
|         return TRUE;
 | |
| 
 | |
|       /* any matchers are dangerous here, as we may loop forever, but
 | |
| 	 we can terminate now as all possible matches have already been added */
 | |
|       if (_gtk_css_matcher_matches_any (matcher))
 | |
| 	break;
 | |
|     }
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| static GtkCssChange
 | |
| gtk_css_selector_descendant_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
 | |
| {
 | |
|   return _gtk_css_change_for_child (previous_change);
 | |
| }
 | |
| 
 | |
| static const GtkCssSelectorClass GTK_CSS_SELECTOR_DESCENDANT = {
 | |
|   "descendant",
 | |
|   gtk_css_selector_descendant_print,
 | |
|   gtk_css_selector_descendant_foreach_matcher,
 | |
|   gtk_css_selector_default_match_one,
 | |
|   gtk_css_selector_descendant_get_change,
 | |
|   gtk_css_selector_default_add_specificity,
 | |
|   gtk_css_selector_default_hash_one,
 | |
|   gtk_css_selector_default_compare_one,
 | |
|   FALSE
 | |
| };
 | |
| 
 | |
| /* CHILD */
 | |
| 
 | |
| static void
 | |
| gtk_css_selector_child_print (const GtkCssSelector *selector,
 | |
|                               GString              *string)
 | |
| {
 | |
|   g_string_append (string, " > ");
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gtk_css_selector_child_foreach_matcher (const GtkCssSelector      *selector,
 | |
|                                         const GtkCssMatcher       *matcher,
 | |
|                                         GtkCssSelectorForeachFunc  func,
 | |
|                                         gpointer                   data)
 | |
| {
 | |
|   GtkCssMatcher parent;
 | |
| 
 | |
|   if (!_gtk_css_matcher_get_parent (&parent, matcher))
 | |
|     return FALSE;
 | |
| 
 | |
|   return func (selector, &parent, data);
 | |
| }
 | |
| 
 | |
| static GtkCssChange
 | |
| gtk_css_selector_child_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
 | |
| {
 | |
|   return _gtk_css_change_for_child (previous_change);
 | |
| }
 | |
| 
 | |
| static const GtkCssSelectorClass GTK_CSS_SELECTOR_CHILD = {
 | |
|   "child",
 | |
|   gtk_css_selector_child_print,
 | |
|   gtk_css_selector_child_foreach_matcher,
 | |
|   gtk_css_selector_default_match_one,
 | |
|   gtk_css_selector_child_get_change,
 | |
|   gtk_css_selector_default_add_specificity,
 | |
|   gtk_css_selector_default_hash_one,
 | |
|   gtk_css_selector_default_compare_one,
 | |
|   FALSE
 | |
| };
 | |
| 
 | |
| /* SIBLING */
 | |
| 
 | |
| static void
 | |
| gtk_css_selector_sibling_print (const GtkCssSelector *selector,
 | |
|                                 GString              *string)
 | |
| {
 | |
|   g_string_append (string, " ~ ");
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gtk_css_selector_sibling_foreach_matcher (const GtkCssSelector      *selector,
 | |
|                                           const GtkCssMatcher       *matcher,
 | |
|                                           GtkCssSelectorForeachFunc  func,
 | |
|                                           gpointer                   data)
 | |
| {
 | |
|   GtkCssMatcher previous;
 | |
| 
 | |
|   while (_gtk_css_matcher_get_previous (&previous, matcher))
 | |
|     {
 | |
|       matcher = &previous;
 | |
| 
 | |
|       if (func (selector, matcher, data))
 | |
|         return TRUE;
 | |
| 
 | |
|       /* any matchers are dangerous here, as we may loop forever, but
 | |
| 	 we can terminate now as all possible matches have already been added */
 | |
|       if (_gtk_css_matcher_matches_any (matcher))
 | |
| 	break;
 | |
|     }
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| static GtkCssChange
 | |
| gtk_css_selector_sibling_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
 | |
| {
 | |
|   return _gtk_css_change_for_sibling (previous_change);
 | |
| }
 | |
| 
 | |
| static const GtkCssSelectorClass GTK_CSS_SELECTOR_SIBLING = {
 | |
|   "sibling",
 | |
|   gtk_css_selector_sibling_print,
 | |
|   gtk_css_selector_sibling_foreach_matcher,
 | |
|   gtk_css_selector_default_match_one,
 | |
|   gtk_css_selector_sibling_get_change,
 | |
|   gtk_css_selector_default_add_specificity,
 | |
|   gtk_css_selector_default_hash_one,
 | |
|   gtk_css_selector_default_compare_one,
 | |
|   FALSE
 | |
| };
 | |
| 
 | |
| /* ADJACENT */
 | |
| 
 | |
| static void
 | |
| gtk_css_selector_adjacent_print (const GtkCssSelector *selector,
 | |
|                                  GString              *string)
 | |
| {
 | |
|   g_string_append (string, " + ");
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gtk_css_selector_adjacent_foreach_matcher (const GtkCssSelector      *selector,
 | |
|                                            const GtkCssMatcher       *matcher,
 | |
|                                            GtkCssSelectorForeachFunc  func,
 | |
|                                            gpointer                   data)
 | |
| {
 | |
|   GtkCssMatcher previous;
 | |
| 
 | |
|   if (!_gtk_css_matcher_get_previous (&previous, matcher))
 | |
|     return FALSE;
 | |
|   
 | |
|   return func (selector, &previous, data);
 | |
| }
 | |
| 
 | |
| static GtkCssChange
 | |
| gtk_css_selector_adjacent_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
 | |
| {
 | |
|   return _gtk_css_change_for_sibling (previous_change);
 | |
| }
 | |
| 
 | |
| static const GtkCssSelectorClass GTK_CSS_SELECTOR_ADJACENT = {
 | |
|   "adjacent",
 | |
|   gtk_css_selector_adjacent_print,
 | |
|   gtk_css_selector_adjacent_foreach_matcher,
 | |
|   gtk_css_selector_default_match_one,
 | |
|   gtk_css_selector_adjacent_get_change,
 | |
|   gtk_css_selector_default_add_specificity,
 | |
|   gtk_css_selector_default_hash_one,
 | |
|   gtk_css_selector_default_compare_one,
 | |
|   FALSE
 | |
| };
 | |
| 
 | |
| /* SIMPLE SELECTOR DEFINE */
 | |
| 
 | |
| #define DEFINE_SIMPLE_SELECTOR(n, \
 | |
|                                c, \
 | |
|                                print_func, \
 | |
|                                match_func, \
 | |
|                                hash_func, \
 | |
|                                comp_func, \
 | |
|                                increase_id_specificity, \
 | |
|                                increase_class_specificity, \
 | |
|                                increase_element_specificity) \
 | |
| static void \
 | |
| gtk_css_selector_ ## n ## _print (const GtkCssSelector *selector, \
 | |
|                                     GString              *string) \
 | |
| { \
 | |
|   print_func (selector, string); \
 | |
| } \
 | |
| \
 | |
| static void \
 | |
| gtk_css_selector_not_ ## n ## _print (const GtkCssSelector *selector, \
 | |
|                                       GString              *string) \
 | |
| { \
 | |
|   g_string_append (string, ":not("); \
 | |
|   print_func (selector, string); \
 | |
|   g_string_append (string, ")"); \
 | |
| } \
 | |
| \
 | |
| static gboolean \
 | |
| gtk_css_selector_not_ ## n ## _match_one (const GtkCssSelector *selector, \
 | |
|                                           const GtkCssMatcher  *matcher) \
 | |
| { \
 | |
|   return !match_func (selector, matcher); \
 | |
| } \
 | |
| \
 | |
| static GtkCssChange \
 | |
| gtk_css_selector_ ## n ## _get_change (const GtkCssSelector *selector, GtkCssChange previous_change) \
 | |
| { \
 | |
|   return previous_change | GTK_CSS_CHANGE_ ## c; \
 | |
| } \
 | |
| \
 | |
| static void \
 | |
| gtk_css_selector_ ## n ## _add_specificity (const GtkCssSelector *selector, \
 | |
|                                             guint                *ids, \
 | |
|                                             guint                *classes, \
 | |
|                                             guint                *elements) \
 | |
| { \
 | |
|   if (increase_id_specificity) \
 | |
|     (*ids)++; \
 | |
|   if (increase_class_specificity) \
 | |
|     (*classes)++; \
 | |
|   if (increase_element_specificity) \
 | |
|     (*elements)++; \
 | |
| } \
 | |
| \
 | |
| static const GtkCssSelectorClass GTK_CSS_SELECTOR_ ## c = { \
 | |
|   G_STRINGIFY(n), \
 | |
|   gtk_css_selector_ ## n ## _print, \
 | |
|   gtk_css_selector_default_foreach_matcher, \
 | |
|   match_func, \
 | |
|   gtk_css_selector_ ## n ## _get_change, \
 | |
|   gtk_css_selector_ ## n ## _add_specificity, \
 | |
|   hash_func, \
 | |
|   comp_func, \
 | |
|   TRUE \
 | |
| };\
 | |
| \
 | |
| static const GtkCssSelectorClass GTK_CSS_SELECTOR_NOT_ ## c = { \
 | |
|   "not_" G_STRINGIFY(n), \
 | |
|   gtk_css_selector_not_ ## n ## _print, \
 | |
|   gtk_css_selector_default_foreach_matcher, \
 | |
|   gtk_css_selector_not_ ## n ## _match_one, \
 | |
|   gtk_css_selector_ ## n ## _get_change, \
 | |
|   gtk_css_selector_ ## n ## _add_specificity, \
 | |
|   hash_func, \
 | |
|   comp_func, \
 | |
|   TRUE \
 | |
| };
 | |
| 
 | |
| /* ANY */
 | |
| 
 | |
| static void
 | |
| print_any (const GtkCssSelector *selector,
 | |
|            GString              *string)
 | |
| {
 | |
|   g_string_append_c (string, '*');
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| match_any (const GtkCssSelector *selector,
 | |
|            const GtkCssMatcher  *matcher)
 | |
| {
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| #undef GTK_CSS_CHANGE_ANY
 | |
| #define GTK_CSS_CHANGE_ANY 0
 | |
| DEFINE_SIMPLE_SELECTOR(any, ANY, print_any, match_any, 
 | |
|                        gtk_css_selector_default_hash_one, gtk_css_selector_default_compare_one,
 | |
|                        FALSE, FALSE, FALSE)
 | |
| #undef GTK_CSS_CHANGE_ANY
 | |
| 
 | |
| /* NAME */
 | |
| 
 | |
| static GHashTable *type_refs_ht = NULL;
 | |
| static guint type_refs_last_serial = 0;
 | |
| 
 | |
| static TypeReference *
 | |
| get_type_reference (const char *name)
 | |
| {
 | |
|   TypeReference *ref;
 | |
| 
 | |
| 
 | |
|   if (type_refs_ht == NULL)
 | |
|     type_refs_ht = g_hash_table_new (g_str_hash, g_str_equal);
 | |
| 
 | |
|   ref = g_hash_table_lookup (type_refs_ht, name);
 | |
| 
 | |
|   if (ref != NULL)
 | |
|     return ref;
 | |
| 
 | |
|   ref = g_slice_new (TypeReference);
 | |
|   ref->name = g_intern_string (name);
 | |
|   ref->type = g_type_from_name (ref->name);
 | |
| 
 | |
|   g_hash_table_insert (type_refs_ht,
 | |
| 		       (gpointer)ref->name, ref);
 | |
| 
 | |
|   return ref;
 | |
| }
 | |
| 
 | |
| static void
 | |
| update_type_references (void)
 | |
| {
 | |
|   GHashTableIter iter;
 | |
|   guint serial;
 | |
|   gpointer value;
 | |
| 
 | |
|   serial = g_type_get_type_registration_serial ();
 | |
| 
 | |
|   if (serial == type_refs_last_serial)
 | |
|     return;
 | |
| 
 | |
|   type_refs_last_serial = serial;
 | |
| 
 | |
|   if (type_refs_ht == NULL)
 | |
|     return;
 | |
| 
 | |
|   g_hash_table_iter_init (&iter, type_refs_ht);
 | |
|   while (g_hash_table_iter_next (&iter,
 | |
| 				 NULL, &value))
 | |
|     {
 | |
|       TypeReference *ref = value;
 | |
|       if (ref->type == G_TYPE_INVALID)
 | |
| 	ref->type = g_type_from_name (ref->name);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| print_name (const GtkCssSelector *selector,
 | |
|             GString              *string)
 | |
| {
 | |
|   g_string_append (string, selector->name.reference->name);
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| match_name (const GtkCssSelector *selector,
 | |
|             const GtkCssMatcher  *matcher)
 | |
| {
 | |
|   return _gtk_css_matcher_has_name (matcher, selector->name.reference->name, selector->name.reference->type);
 | |
| }
 | |
| 
 | |
| static guint
 | |
| hash_name (const GtkCssSelector *a)
 | |
| {
 | |
|   return g_str_hash (a->name.reference->name);
 | |
| }
 | |
| 
 | |
| static int
 | |
| comp_name (const GtkCssSelector *a,
 | |
|            const GtkCssSelector *b)
 | |
| {
 | |
|   return strcmp (a->name.reference->name,
 | |
| 		 b->name.reference->name);
 | |
| }
 | |
| 
 | |
| DEFINE_SIMPLE_SELECTOR(name, NAME, print_name, match_name, hash_name, comp_name, FALSE, FALSE, TRUE)
 | |
| 
 | |
| /* CLASS */
 | |
| 
 | |
| static void
 | |
| print_class (const GtkCssSelector *selector,
 | |
|              GString              *string)
 | |
| {
 | |
|   g_string_append_c (string, '.');
 | |
|   g_string_append (string, g_quark_to_string (selector->style_class.style_class));
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| match_class (const GtkCssSelector *selector,
 | |
|              const GtkCssMatcher  *matcher)
 | |
| {
 | |
|   return _gtk_css_matcher_has_class (matcher, selector->style_class.style_class);
 | |
| }
 | |
| 
 | |
| static guint
 | |
| hash_class (const GtkCssSelector *a)
 | |
| {
 | |
|   return a->style_class.style_class;
 | |
| }
 | |
| 
 | |
| static int
 | |
| comp_class (const GtkCssSelector *a,
 | |
|             const GtkCssSelector *b)
 | |
| {
 | |
|   if (a->style_class.style_class < b->style_class.style_class)
 | |
|     return -1;
 | |
|   if (a->style_class.style_class > b->style_class.style_class)
 | |
|     return 1;
 | |
|   else
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| DEFINE_SIMPLE_SELECTOR(class, CLASS, print_class, match_class, hash_class, comp_class, FALSE, TRUE, FALSE)
 | |
| 
 | |
| /* ID */
 | |
| 
 | |
| static void
 | |
| print_id (const GtkCssSelector *selector,
 | |
|           GString              *string)
 | |
| {
 | |
|   g_string_append_c (string, '#');
 | |
|   g_string_append (string, selector->id.name);
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| match_id (const GtkCssSelector *selector,
 | |
|           const GtkCssMatcher  *matcher)
 | |
| {
 | |
|   return _gtk_css_matcher_has_id (matcher, selector->id.name);
 | |
| }
 | |
| 
 | |
| static guint
 | |
| hash_id (const GtkCssSelector *a)
 | |
| {
 | |
|   return GPOINTER_TO_UINT (a->id.name);
 | |
| }
 | |
| 
 | |
| static int
 | |
| comp_id (const GtkCssSelector *a,
 | |
| 	 const GtkCssSelector *b)
 | |
| {
 | |
|   if (a->id.name < b->id.name)
 | |
|     return -1;
 | |
|   else if (a->id.name > b->id.name)
 | |
|     return 1;
 | |
|   else
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| DEFINE_SIMPLE_SELECTOR(id, ID, print_id, match_id, hash_id, comp_id, TRUE, FALSE, FALSE)
 | |
| 
 | |
| /* PSEUDOCLASS FOR STATE */
 | |
| 
 | |
| static void
 | |
| print_pseudoclass_state (const GtkCssSelector *selector,
 | |
|                          GString              *string)
 | |
| {
 | |
|   static const char * state_names[] = {
 | |
|     "active",
 | |
|     "hover",
 | |
|     "selected",
 | |
|     "insensitive",
 | |
|     "inconsistent",
 | |
|     "focus",
 | |
|     "backdrop",
 | |
|     "dir(ltr)",
 | |
|     "dir(rtl)",
 | |
|     "link",
 | |
|     "visited",
 | |
|     "checked",
 | |
|     "dnd"
 | |
|   };
 | |
|   guint i;
 | |
| 
 | |
|   g_string_append_c (string, ':');
 | |
| 
 | |
|   for (i = 0; i < G_N_ELEMENTS (state_names); i++)
 | |
|     {
 | |
|       if (selector->state.state == (1 << i))
 | |
|         {
 | |
|           g_string_append (string, state_names[i]);
 | |
|           return;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|   g_assert_not_reached ();
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| match_pseudoclass_state (const GtkCssSelector *selector,
 | |
|                          const GtkCssMatcher  *matcher)
 | |
| {
 | |
|   return (_gtk_css_matcher_get_state (matcher) & selector->state.state) == selector->state.state;
 | |
| }
 | |
| 
 | |
| static guint
 | |
| hash_pseudoclass_state (const GtkCssSelector *selector)
 | |
| {
 | |
|   return selector->state.state;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int
 | |
| comp_pseudoclass_state (const GtkCssSelector *a,
 | |
| 		        const GtkCssSelector *b)
 | |
| {
 | |
|   return a->state.state - b->state.state;
 | |
| }
 | |
| 
 | |
| #define GTK_CSS_CHANGE_PSEUDOCLASS_STATE GTK_CSS_CHANGE_STATE
 | |
| DEFINE_SIMPLE_SELECTOR(pseudoclass_state, PSEUDOCLASS_STATE, print_pseudoclass_state,
 | |
|                        match_pseudoclass_state, hash_pseudoclass_state, comp_pseudoclass_state,
 | |
|                        FALSE, TRUE, FALSE)
 | |
| #undef GTK_CSS_CHANGE_PSEUDOCLASS_STATE
 | |
| 
 | |
| /* PSEUDOCLASS FOR POSITION */
 | |
| 
 | |
| static void
 | |
| print_pseudoclass_position (const GtkCssSelector *selector,
 | |
|                             GString              *string)
 | |
| {
 | |
|   switch (selector->position.type)
 | |
|     {
 | |
|     case POSITION_FORWARD:
 | |
|       if (selector->position.a == 0)
 | |
|         {
 | |
|           if (selector->position.b == 1)
 | |
|             g_string_append (string, ":first-child");
 | |
|           else
 | |
|             g_string_append_printf (string, ":nth-child(%d)", selector->position.b);
 | |
|         }
 | |
|       else if (selector->position.a == 2 && selector->position.b == 0)
 | |
|         g_string_append (string, ":nth-child(even)");
 | |
|       else if (selector->position.a == 2 && selector->position.b == 1)
 | |
|         g_string_append (string, ":nth-child(odd)");
 | |
|       else
 | |
|         {
 | |
|           g_string_append (string, ":nth-child(");
 | |
|           if (selector->position.a == 1)
 | |
|             g_string_append (string, "n");
 | |
|           else if (selector->position.a == -1)
 | |
|             g_string_append (string, "-n");
 | |
|           else
 | |
|             g_string_append_printf (string, "%dn", selector->position.a);
 | |
|           if (selector->position.b > 0)
 | |
|             g_string_append_printf (string, "+%d)", selector->position.b);
 | |
|           else if (selector->position.b < 0)
 | |
|             g_string_append_printf (string, "%d)", selector->position.b);
 | |
|           else
 | |
|             g_string_append (string, ")");
 | |
|         }
 | |
|       break;
 | |
|     case POSITION_BACKWARD:
 | |
|       if (selector->position.a == 0)
 | |
|         {
 | |
|           if (selector->position.b == 1)
 | |
|             g_string_append (string, ":last-child");
 | |
|           else
 | |
|             g_string_append_printf (string, ":nth-last-child(%d)", selector->position.b);
 | |
|         }
 | |
|       else if (selector->position.a == 2 && selector->position.b == 0)
 | |
|         g_string_append (string, ":nth-last-child(even)");
 | |
|       else if (selector->position.a == 2 && selector->position.b == 1)
 | |
|         g_string_append (string, ":nth-last-child(odd)");
 | |
|       else
 | |
|         {
 | |
|           g_string_append (string, ":nth-last-child(");
 | |
|           if (selector->position.a == 1)
 | |
|             g_string_append (string, "n");
 | |
|           else if (selector->position.a == -1)
 | |
|             g_string_append (string, "-n");
 | |
|           else
 | |
|             g_string_append_printf (string, "%dn", selector->position.a);
 | |
|           if (selector->position.b > 0)
 | |
|             g_string_append_printf (string, "+%d)", selector->position.b);
 | |
|           else if (selector->position.b < 0)
 | |
|             g_string_append_printf (string, "%d)", selector->position.b);
 | |
|           else
 | |
|             g_string_append (string, ")");
 | |
|         }
 | |
|       break;
 | |
|     case POSITION_ONLY:
 | |
|       g_string_append (string, ":only-child");
 | |
|       break;
 | |
|     case POSITION_SORTED:
 | |
|       g_string_append (string, ":sorted");
 | |
|       break;
 | |
|     default:
 | |
|       g_assert_not_reached ();
 | |
|       break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| match_pseudoclass_position (const GtkCssSelector *selector,
 | |
| 		            const GtkCssMatcher  *matcher)
 | |
| {
 | |
|   switch (selector->position.type)
 | |
|     {
 | |
|     case POSITION_FORWARD:
 | |
|       if (!_gtk_css_matcher_has_position (matcher, TRUE, selector->position.a, selector->position.b))
 | |
|         return FALSE;
 | |
|       break;
 | |
|     case POSITION_BACKWARD:
 | |
|       if (!_gtk_css_matcher_has_position (matcher, FALSE, selector->position.a, selector->position.b))
 | |
|         return FALSE;
 | |
|       break;
 | |
|     case POSITION_ONLY:
 | |
|       if (!_gtk_css_matcher_has_position (matcher, TRUE, 0, 1) ||
 | |
|           !_gtk_css_matcher_has_position (matcher, FALSE, 0, 1))
 | |
|         return FALSE;
 | |
|       break;
 | |
|     case POSITION_SORTED:
 | |
|       return FALSE;
 | |
|     default:
 | |
|       g_assert_not_reached ();
 | |
|       return FALSE;
 | |
|     }
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static guint
 | |
| hash_pseudoclass_position (const GtkCssSelector *a)
 | |
| {
 | |
|   return (((a->position.type << POSITION_NUMBER_BITS) | a->position.a) << POSITION_NUMBER_BITS) | a->position.b;
 | |
| }
 | |
| 
 | |
| static int
 | |
| comp_pseudoclass_position (const GtkCssSelector *a,
 | |
| 			   const GtkCssSelector *b)
 | |
| {
 | |
|   int diff;
 | |
|   
 | |
|   diff = a->position.type - b->position.type;
 | |
|   if (diff)
 | |
|     return diff;
 | |
| 
 | |
|   diff = a->position.a - b->position.a;
 | |
|   if (diff)
 | |
|     return diff;
 | |
| 
 | |
|   return a->position.b - b->position.b;
 | |
| }
 | |
| 
 | |
| static GtkCssChange
 | |
| change_pseudoclass_position (const GtkCssSelector *selector)
 | |
| {
 | |
|   switch (selector->position.type)
 | |
|     {
 | |
|     case POSITION_FORWARD:
 | |
|       if (selector->position.a == 0 && selector->position.b == 1)
 | |
|         return GTK_CSS_CHANGE_FIRST_CHILD;
 | |
|       else
 | |
|         return GTK_CSS_CHANGE_NTH_CHILD;
 | |
|     case POSITION_BACKWARD:
 | |
|       if (selector->position.a == 0 && selector->position.b == 1)
 | |
|         return GTK_CSS_CHANGE_LAST_CHILD;
 | |
|       else
 | |
|         return GTK_CSS_CHANGE_NTH_LAST_CHILD;
 | |
|     case POSITION_ONLY:
 | |
|       return GTK_CSS_CHANGE_FIRST_CHILD | GTK_CSS_CHANGE_LAST_CHILD;
 | |
|     default:
 | |
|       g_assert_not_reached ();
 | |
|     case POSITION_SORTED:
 | |
|       return 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| #define GTK_CSS_CHANGE_PSEUDOCLASS_POSITION change_pseudoclass_position(selector)
 | |
| DEFINE_SIMPLE_SELECTOR(pseudoclass_position, PSEUDOCLASS_POSITION, print_pseudoclass_position,
 | |
|                        match_pseudoclass_position, hash_pseudoclass_position, comp_pseudoclass_position,
 | |
|                        FALSE, TRUE, FALSE)
 | |
| #undef GTK_CSS_CHANGE_PSEUDOCLASS_POSITION
 | |
| /* API */
 | |
| 
 | |
| static guint
 | |
| gtk_css_selector_size (const GtkCssSelector *selector)
 | |
| {
 | |
|   guint size = 0;
 | |
| 
 | |
|   while (selector)
 | |
|     {
 | |
|       selector = gtk_css_selector_previous (selector);
 | |
|       size++;
 | |
|     }
 | |
| 
 | |
|   return size;
 | |
| }
 | |
| 
 | |
| static GtkCssSelector *
 | |
| gtk_css_selector_new (const GtkCssSelectorClass *class,
 | |
|                       GtkCssSelector            *selector)
 | |
| {
 | |
|   guint size;
 | |
| 
 | |
|   size = gtk_css_selector_size (selector);
 | |
|   selector = g_realloc (selector, sizeof (GtkCssSelector) * (size + 1) + sizeof (gpointer));
 | |
|   if (size == 0)
 | |
|     selector[1].class = NULL;
 | |
|   else
 | |
|     memmove (selector + 1, selector, sizeof (GtkCssSelector) * size + sizeof (gpointer));
 | |
| 
 | |
|   memset (selector, 0, sizeof (GtkCssSelector));
 | |
|   selector->class = class;
 | |
| 
 | |
|   return selector;
 | |
| }
 | |
| 
 | |
| static GtkCssSelector *
 | |
| parse_selector_class (GtkCssParser   *parser,
 | |
|                       GtkCssSelector *selector,
 | |
|                       gboolean        negate)
 | |
| {
 | |
|   char *name;
 | |
|     
 | |
|   name = _gtk_css_parser_try_name (parser, FALSE);
 | |
| 
 | |
|   if (name == NULL)
 | |
|     {
 | |
|       _gtk_css_parser_error (parser, "Expected a valid name for class");
 | |
|       if (selector)
 | |
|         _gtk_css_selector_free (selector);
 | |
|       return NULL;
 | |
|     }
 | |
| 
 | |
|   selector = gtk_css_selector_new (negate ? >K_CSS_SELECTOR_NOT_CLASS
 | |
|                                           : >K_CSS_SELECTOR_CLASS,
 | |
|                                    selector);
 | |
|   selector->style_class.style_class = g_quark_from_string (name);
 | |
| 
 | |
|   g_free (name);
 | |
| 
 | |
|   return selector;
 | |
| }
 | |
| 
 | |
| static GtkCssSelector *
 | |
| parse_selector_id (GtkCssParser   *parser,
 | |
|                    GtkCssSelector *selector,
 | |
|                    gboolean        negate)
 | |
| {
 | |
|   char *name;
 | |
|     
 | |
|   name = _gtk_css_parser_try_name (parser, FALSE);
 | |
| 
 | |
|   if (name == NULL)
 | |
|     {
 | |
|       _gtk_css_parser_error (parser, "Expected a valid name for id");
 | |
|       if (selector)
 | |
|         _gtk_css_selector_free (selector);
 | |
|       return NULL;
 | |
|     }
 | |
| 
 | |
|   selector = gtk_css_selector_new (negate ? >K_CSS_SELECTOR_NOT_ID
 | |
|                                           : >K_CSS_SELECTOR_ID,
 | |
|                                    selector);
 | |
|   selector->id.name = g_intern_string (name);
 | |
| 
 | |
|   g_free (name);
 | |
| 
 | |
|   return selector;
 | |
| }
 | |
| 
 | |
| static GtkCssSelector *
 | |
| parse_selector_pseudo_class_nth_child (GtkCssParser   *parser,
 | |
|                                        GtkCssSelector *selector,
 | |
|                                        PositionType    type,
 | |
|                                        gboolean        negate)
 | |
| {
 | |
|   int a, b;
 | |
| 
 | |
|   if (!_gtk_css_parser_try (parser, "(", TRUE))
 | |
|     {
 | |
|       _gtk_css_parser_error (parser, "Missing opening bracket for pseudo-class");
 | |
|       if (selector)
 | |
|         _gtk_css_selector_free (selector);
 | |
|       return NULL;
 | |
|     }
 | |
| 
 | |
|   if (_gtk_css_parser_try (parser, "even", TRUE))
 | |
|     {
 | |
|       a = 2;
 | |
|       b = 0;
 | |
|     }
 | |
|   else if (_gtk_css_parser_try (parser, "odd", TRUE))
 | |
|     {
 | |
|       a = 2;
 | |
|       b = 1;
 | |
|     }
 | |
|   else if (type == POSITION_FORWARD &&
 | |
|            _gtk_css_parser_try (parser, "first", TRUE))
 | |
|     {
 | |
|       a = 0;
 | |
|       b = 1;
 | |
|     }
 | |
|   else if (type == POSITION_FORWARD &&
 | |
|            _gtk_css_parser_try (parser, "last", TRUE))
 | |
|     {
 | |
|       a = 0;
 | |
|       b = 1;
 | |
|       type = POSITION_BACKWARD;
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       int multiplier;
 | |
| 
 | |
|       if (_gtk_css_parser_try (parser, "+", TRUE))
 | |
|         multiplier = 1;
 | |
|       else if (_gtk_css_parser_try (parser, "-", TRUE))
 | |
|         multiplier = -1;
 | |
|       else
 | |
|         multiplier = 1;
 | |
| 
 | |
|       if (_gtk_css_parser_try_int (parser, &a))
 | |
|         {
 | |
|           if (a < 0)
 | |
|             {
 | |
|               _gtk_css_parser_error (parser, "Expected an integer");
 | |
|               if (selector)
 | |
|                 _gtk_css_selector_free (selector);
 | |
|               return NULL;
 | |
|             }
 | |
|           a *= multiplier;
 | |
|         }
 | |
|       else if (_gtk_css_parser_has_prefix (parser, "n"))
 | |
|         {
 | |
|           a = multiplier;
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           _gtk_css_parser_error (parser, "Expected an integer");
 | |
|           if (selector)
 | |
|             _gtk_css_selector_free (selector);
 | |
|           return NULL;
 | |
|         }
 | |
| 
 | |
|       if (_gtk_css_parser_try (parser, "n", TRUE))
 | |
|         {
 | |
|           if (_gtk_css_parser_try (parser, "+", TRUE))
 | |
|             multiplier = 1;
 | |
|           else if (_gtk_css_parser_try (parser, "-", TRUE))
 | |
|             multiplier = -1;
 | |
|           else
 | |
|             multiplier = 1;
 | |
| 
 | |
|           if (_gtk_css_parser_try_int (parser, &b))
 | |
|             {
 | |
|               if (b < 0)
 | |
|                 {
 | |
|                   _gtk_css_parser_error (parser, "Expected an integer");
 | |
|                   if (selector)
 | |
|                     _gtk_css_selector_free (selector);
 | |
|                   return NULL;
 | |
|                 }
 | |
|             }
 | |
|           else
 | |
|             b = 0;
 | |
| 
 | |
|           b *= multiplier;
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           b = a;
 | |
|           a = 0;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|   if (!_gtk_css_parser_try (parser, ")", FALSE))
 | |
|     {
 | |
|       _gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class");
 | |
|       if (selector)
 | |
|         _gtk_css_selector_free (selector);
 | |
|       return NULL;
 | |
|     }
 | |
| 
 | |
|   selector = gtk_css_selector_new (negate ? >K_CSS_SELECTOR_NOT_PSEUDOCLASS_POSITION
 | |
|                                           : >K_CSS_SELECTOR_PSEUDOCLASS_POSITION,
 | |
|                                    selector);
 | |
|   selector->position.type = type;
 | |
|   selector->position.a = a;
 | |
|   selector->position.b = b;
 | |
| 
 | |
|   return selector;
 | |
| }
 | |
| 
 | |
| static GtkCssSelector *
 | |
| parse_selector_pseudo_class (GtkCssParser   *parser,
 | |
|                              GtkCssSelector *selector,
 | |
|                              gboolean        negate)
 | |
| {
 | |
|   static const struct {
 | |
|     const char    *name;
 | |
|     GtkStateFlags  state_flag;
 | |
|     PositionType   position_type;
 | |
|     int            position_a;
 | |
|     int            position_b;
 | |
|   } pseudo_classes[] = {
 | |
|     { "first-child",  0,                           POSITION_FORWARD,  0, 1 },
 | |
|     { "last-child",   0,                           POSITION_BACKWARD, 0, 1 },
 | |
|     { "only-child",   0,                           POSITION_ONLY,     0, 0 },
 | |
|     { "sorted",       0,                           POSITION_SORTED,   0, 0 },
 | |
|     { "active",       GTK_STATE_FLAG_ACTIVE, },
 | |
|     { "prelight",     GTK_STATE_FLAG_PRELIGHT, },
 | |
|     { "hover",        GTK_STATE_FLAG_PRELIGHT, },
 | |
|     { "selected",     GTK_STATE_FLAG_SELECTED, },
 | |
|     { "insensitive",  GTK_STATE_FLAG_INSENSITIVE, },
 | |
|     { "inconsistent", GTK_STATE_FLAG_INCONSISTENT, },
 | |
|     { "focused",      GTK_STATE_FLAG_FOCUSED, },
 | |
|     { "focus",        GTK_STATE_FLAG_FOCUSED, },
 | |
|     { "backdrop",     GTK_STATE_FLAG_BACKDROP, },
 | |
|     { "dir(ltr)",     GTK_STATE_FLAG_DIR_LTR, },
 | |
|     { "dir(rtl)",     GTK_STATE_FLAG_DIR_RTL, },
 | |
|     { "link",         GTK_STATE_FLAG_LINK, },
 | |
|     { "visited",      GTK_STATE_FLAG_VISITED, },
 | |
|     { "checked",      GTK_STATE_FLAG_CHECKED, },
 | |
|     { "dnd"    ,      GTK_STATE_FLAG_DND, }
 | |
|   };
 | |
|   guint i;
 | |
| 
 | |
|   if (_gtk_css_parser_try (parser, "nth-child", FALSE))
 | |
|     return parse_selector_pseudo_class_nth_child (parser, selector, POSITION_FORWARD, negate);
 | |
|   else if (_gtk_css_parser_try (parser, "nth-last-child", FALSE))
 | |
|     return parse_selector_pseudo_class_nth_child (parser, selector, POSITION_BACKWARD, negate);
 | |
| 
 | |
|   for (i = 0; i < G_N_ELEMENTS (pseudo_classes); i++)
 | |
|     {
 | |
|       if (_gtk_css_parser_try (parser, pseudo_classes[i].name, FALSE))
 | |
|         {
 | |
|           if (pseudo_classes[i].state_flag)
 | |
|             {
 | |
|               selector = gtk_css_selector_new (negate ? >K_CSS_SELECTOR_NOT_PSEUDOCLASS_STATE
 | |
|                                                       : >K_CSS_SELECTOR_PSEUDOCLASS_STATE,
 | |
|                                                selector);
 | |
|               selector->state.state = pseudo_classes[i].state_flag;
 | |
|             }
 | |
|           else
 | |
|             {
 | |
|               selector = gtk_css_selector_new (negate ? >K_CSS_SELECTOR_NOT_PSEUDOCLASS_POSITION
 | |
|                                                       : >K_CSS_SELECTOR_PSEUDOCLASS_POSITION,
 | |
|                                                selector);
 | |
|               selector->position.type = pseudo_classes[i].position_type;
 | |
|               selector->position.a = pseudo_classes[i].position_a;
 | |
|               selector->position.b = pseudo_classes[i].position_b;
 | |
|             }
 | |
|           return selector;
 | |
|         }
 | |
|     }
 | |
|       
 | |
|   _gtk_css_parser_error (parser, "Missing name of pseudo-class");
 | |
|   if (selector)
 | |
|     _gtk_css_selector_free (selector);
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| static GtkCssSelector *
 | |
| parse_selector_negation (GtkCssParser   *parser,
 | |
|                          GtkCssSelector *selector)
 | |
| {
 | |
|   char *name;
 | |
| 
 | |
|   name = _gtk_css_parser_try_ident (parser, FALSE);
 | |
|   if (name)
 | |
|     {
 | |
|       selector = gtk_css_selector_new (>K_CSS_SELECTOR_NOT_NAME,
 | |
|                                        selector);
 | |
|       selector->name.reference = get_type_reference (name);
 | |
|       g_free (name);
 | |
|     }
 | |
|   else if (_gtk_css_parser_try (parser, "*", FALSE))
 | |
|     selector = gtk_css_selector_new (>K_CSS_SELECTOR_NOT_ANY, selector);
 | |
|   else if (_gtk_css_parser_try (parser, "#", FALSE))
 | |
|     selector = parse_selector_id (parser, selector, TRUE);
 | |
|   else if (_gtk_css_parser_try (parser, ".", FALSE))
 | |
|     selector = parse_selector_class (parser, selector, TRUE);
 | |
|   else if (_gtk_css_parser_try (parser, ":", FALSE))
 | |
|     selector = parse_selector_pseudo_class (parser, selector, TRUE);
 | |
|   else
 | |
|     {
 | |
|       _gtk_css_parser_error (parser, "Not a valid selector for :not()");
 | |
|       if (selector)
 | |
|         _gtk_css_selector_free (selector);
 | |
|       return NULL;
 | |
|     }
 | |
|   
 | |
|   _gtk_css_parser_skip_whitespace (parser);
 | |
| 
 | |
|   if (!_gtk_css_parser_try (parser, ")", FALSE))
 | |
|     {
 | |
|       _gtk_css_parser_error (parser, "Missing closing bracket for :not()");
 | |
|       if (selector)
 | |
|         _gtk_css_selector_free (selector);
 | |
|       return NULL;
 | |
|     }
 | |
| 
 | |
|   return selector;
 | |
| }
 | |
| 
 | |
| static GtkCssSelector *
 | |
| parse_simple_selector (GtkCssParser   *parser,
 | |
|                        GtkCssSelector *selector)
 | |
| {
 | |
|   gboolean parsed_something = FALSE;
 | |
|   char *name;
 | |
| 
 | |
|   name = _gtk_css_parser_try_ident (parser, FALSE);
 | |
|   if (name)
 | |
|     {
 | |
|       selector = gtk_css_selector_new (>K_CSS_SELECTOR_NAME,
 | |
|                                        selector);
 | |
|       selector->name.reference = get_type_reference (name);
 | |
|       g_free (name);
 | |
|       parsed_something = TRUE;
 | |
|     }
 | |
|   else if (_gtk_css_parser_try (parser, "*", FALSE))
 | |
|     {
 | |
|       selector = gtk_css_selector_new (>K_CSS_SELECTOR_ANY, selector);
 | |
|       parsed_something = TRUE;
 | |
|     }
 | |
| 
 | |
|   do {
 | |
|       if (_gtk_css_parser_try (parser, "#", FALSE))
 | |
|         selector = parse_selector_id (parser, selector, FALSE);
 | |
|       else if (_gtk_css_parser_try (parser, ".", FALSE))
 | |
|         selector = parse_selector_class (parser, selector, FALSE);
 | |
|       else if (_gtk_css_parser_try (parser, ":not(", TRUE))
 | |
|         selector = parse_selector_negation (parser, selector);
 | |
|       else if (_gtk_css_parser_try (parser, ":", FALSE))
 | |
|         selector = parse_selector_pseudo_class (parser, selector, FALSE);
 | |
|       else if (!parsed_something)
 | |
|         {
 | |
|           _gtk_css_parser_error (parser, "Expected a valid selector");
 | |
|           if (selector)
 | |
|             _gtk_css_selector_free (selector);
 | |
|           return NULL;
 | |
|         }
 | |
|       else
 | |
|         break;
 | |
| 
 | |
|       parsed_something = TRUE;
 | |
|     }
 | |
|   while (selector && !_gtk_css_parser_is_eof (parser));
 | |
| 
 | |
|   _gtk_css_parser_skip_whitespace (parser);
 | |
| 
 | |
|   return selector;
 | |
| }
 | |
| 
 | |
| GtkCssSelector *
 | |
| _gtk_css_selector_parse (GtkCssParser *parser)
 | |
| {
 | |
|   GtkCssSelector *selector = NULL;
 | |
| 
 | |
|   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, '{'))
 | |
|     {
 | |
|       if (_gtk_css_parser_try (parser, "+", TRUE))
 | |
|         selector = gtk_css_selector_new (>K_CSS_SELECTOR_ADJACENT, selector);
 | |
|       else if (_gtk_css_parser_try (parser, "~", TRUE))
 | |
|         selector = gtk_css_selector_new (>K_CSS_SELECTOR_SIBLING, selector);
 | |
|       else if (_gtk_css_parser_try (parser, ">", TRUE))
 | |
|         selector = gtk_css_selector_new (>K_CSS_SELECTOR_CHILD, selector);
 | |
|       else
 | |
|         selector = gtk_css_selector_new (>K_CSS_SELECTOR_DESCENDANT, selector);
 | |
|     }
 | |
| 
 | |
|   return selector;
 | |
| }
 | |
| 
 | |
| void
 | |
| _gtk_css_selector_free (GtkCssSelector *selector)
 | |
| {
 | |
|   g_return_if_fail (selector != NULL);
 | |
| 
 | |
|   g_free (selector);
 | |
| }
 | |
| 
 | |
| void
 | |
| _gtk_css_selector_print (const GtkCssSelector *selector,
 | |
|                          GString *             str)
 | |
| {
 | |
|   const GtkCssSelector *previous;
 | |
| 
 | |
|   g_return_if_fail (selector != NULL);
 | |
| 
 | |
|   previous = gtk_css_selector_previous (selector);
 | |
|   if (previous)
 | |
|     _gtk_css_selector_print (previous, str);
 | |
| 
 | |
|   selector->class->print (selector, str);
 | |
| }
 | |
| 
 | |
| 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_foreach_match (const GtkCssSelector *selector,
 | |
|                                 const GtkCssMatcher  *matcher,
 | |
|                                 gpointer              unused)
 | |
| {
 | |
|   selector = gtk_css_selector_previous (selector);
 | |
| 
 | |
|   if (selector == NULL)
 | |
|     return TRUE;
 | |
| 
 | |
|   if (!gtk_css_selector_match (selector, matcher))
 | |
|     return FALSE;
 | |
| 
 | |
|   return gtk_css_selector_foreach (selector, matcher, gtk_css_selector_foreach_match, NULL);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * _gtk_css_selector_matches:
 | |
|  * @selector: the selector
 | |
|  * @path: the path to check
 | |
|  * @state: The state to match
 | |
|  *
 | |
|  * Checks if the @selector matches the given @path. If @length is
 | |
|  * smaller than the number of elements in @path, it is assumed that
 | |
|  * only the first @length element of @path are valid and the rest
 | |
|  * does not exist. This is useful for doing parent matches for the
 | |
|  * 'inherit' keyword.
 | |
|  *
 | |
|  * Returns: %TRUE if the selector matches @path
 | |
|  **/
 | |
| gboolean
 | |
| _gtk_css_selector_matches (const GtkCssSelector *selector,
 | |
|                            const GtkCssMatcher  *matcher)
 | |
| {
 | |
| 
 | |
|   g_return_val_if_fail (selector != NULL, FALSE);
 | |
|   g_return_val_if_fail (matcher != NULL, FALSE);
 | |
| 
 | |
|   update_type_references ();
 | |
| 
 | |
|   if (!gtk_css_selector_match (selector, matcher))
 | |
|     return FALSE;
 | |
| 
 | |
|   return gtk_css_selector_foreach (selector, matcher, gtk_css_selector_foreach_match, NULL);
 | |
| }
 | |
| 
 | |
| /* 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)
 | |
| {
 | |
|   for (; selector; selector = gtk_css_selector_previous (selector))
 | |
|     {
 | |
|       selector->class->add_specificity (selector, ids, classes, 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;
 | |
| }
 | |
| 
 | |
| GtkCssChange
 | |
| _gtk_css_selector_get_change (const GtkCssSelector *selector)
 | |
| {
 | |
|   if (selector == NULL)
 | |
|     return 0;
 | |
| 
 | |
|   return selector->class->get_change (selector, _gtk_css_selector_get_change (gtk_css_selector_previous (selector)));
 | |
| }
 | |
| 
 | |
| /******************** SelectorTree handling *****************/
 | |
| 
 | |
| static GHashTable *
 | |
| gtk_css_selectors_count_initial_init (void)
 | |
| {
 | |
|   return g_hash_table_new ((GHashFunc)gtk_css_selector_hash_one, (GEqualFunc)gtk_css_selector_equal);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_css_selectors_count_initial (const GtkCssSelector *selector, GHashTable *hash_one)
 | |
| {
 | |
|   if (!selector->class->is_simple)
 | |
|     {
 | |
|       guint count = GPOINTER_TO_INT (g_hash_table_lookup (hash_one, selector));
 | |
|       g_hash_table_replace (hash_one, (gpointer)selector, GUINT_TO_POINTER (count + 1));
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|   for (;
 | |
|        selector && selector->class->is_simple;
 | |
|        selector = gtk_css_selector_previous (selector))
 | |
|     {
 | |
|       guint count = GPOINTER_TO_INT (g_hash_table_lookup (hash_one, selector));
 | |
|       g_hash_table_replace (hash_one, (gpointer)selector, GUINT_TO_POINTER (count + 1));
 | |
|     }
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gtk_css_selectors_has_initial_selector (const GtkCssSelector *selector, const GtkCssSelector *initial)
 | |
| {
 | |
|   if (!selector->class->is_simple)
 | |
|     return gtk_css_selector_equal (selector, initial);
 | |
| 
 | |
|   for (;
 | |
|        selector && selector->class->is_simple;
 | |
|        selector = gtk_css_selector_previous (selector))
 | |
|     {
 | |
|       if (gtk_css_selector_equal (selector, initial))
 | |
| 	return TRUE;
 | |
|     }
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| static GtkCssSelector *
 | |
| gtk_css_selectors_skip_initial_selector (GtkCssSelector *selector, const GtkCssSelector *initial)
 | |
| {
 | |
|   GtkCssSelector *found;
 | |
|   GtkCssSelector tmp;
 | |
| 
 | |
|   /* If the initial simple selector is not first, move it there so we can skip it
 | |
|      without losing any other selectors */
 | |
|   if (!gtk_css_selector_equal (selector, initial))
 | |
|     {
 | |
|       for (found = selector; found && found->class->is_simple; found = (GtkCssSelector *)gtk_css_selector_previous (found))
 | |
| 	{
 | |
| 	  if (gtk_css_selector_equal (found, initial))
 | |
| 	    break;
 | |
| 	}
 | |
| 
 | |
|       g_assert (found != NULL && found->class->is_simple);
 | |
| 
 | |
|       tmp = *found;
 | |
|       *found = *selector;
 | |
|       *selector = tmp;
 | |
|     }
 | |
| 
 | |
|   return (GtkCssSelector *)gtk_css_selector_previous (selector);
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gtk_css_selector_tree_match_foreach (const GtkCssSelector *selector,
 | |
|                                      const GtkCssMatcher  *matcher,
 | |
|                                      gpointer              res)
 | |
| {
 | |
|   const GtkCssSelectorTree *tree = (const GtkCssSelectorTree *) selector;
 | |
|   const GtkCssSelectorTree *prev;
 | |
| 
 | |
|   if (!gtk_css_selector_match (selector, matcher))
 | |
|     return FALSE;
 | |
| 
 | |
|   gtk_css_selector_tree_found_match (tree, res);
 | |
| 
 | |
|   for (prev = gtk_css_selector_tree_get_previous (tree);
 | |
|        prev != NULL;
 | |
|        prev = gtk_css_selector_tree_get_sibling (prev))
 | |
|     gtk_css_selector_foreach (&prev->selector, matcher, gtk_css_selector_tree_match_foreach, res);
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| GPtrArray *
 | |
| _gtk_css_selector_tree_match_all (const GtkCssSelectorTree *tree,
 | |
| 				  const GtkCssMatcher *matcher)
 | |
| {
 | |
|   GPtrArray *array = NULL;
 | |
| 
 | |
|   update_type_references ();
 | |
| 
 | |
|   for (; tree != NULL;
 | |
|        tree = gtk_css_selector_tree_get_sibling (tree))
 | |
|     gtk_css_selector_foreach (&tree->selector, matcher, gtk_css_selector_tree_match_foreach, &array);
 | |
| 
 | |
|   return array;
 | |
| }
 | |
| 
 | |
| /* When checking for changes via the tree we need to know if a rule further
 | |
|    down the tree matched, because if so we need to add "our bit" to the
 | |
|    Change. For instance in a a match like *.class:active we'll
 | |
|    get a tree that first checks :active, if that matches we continue down
 | |
|    to the tree, and if we get a match we add CHANGE_CLASS. However, the
 | |
|    end of the tree where we have a match is an ANY which doesn't actually
 | |
|    modify the change, so we don't know if we have a match or not. We fix
 | |
|    this by setting GTK_CSS_CHANGE_GOT_MATCH which lets us guarantee
 | |
|    that change != 0 on any match. */
 | |
| #define GTK_CSS_CHANGE_GOT_MATCH GTK_CSS_CHANGE_RESERVED_BIT
 | |
| 
 | |
| static GtkCssChange
 | |
| gtk_css_selector_tree_collect_change (const GtkCssSelectorTree *tree)
 | |
| {
 | |
|   GtkCssChange change = 0;
 | |
|   const GtkCssSelectorTree *prev;
 | |
| 
 | |
|   for (prev = gtk_css_selector_tree_get_previous (tree);
 | |
|        prev != NULL;
 | |
|        prev = gtk_css_selector_tree_get_sibling (prev))
 | |
|     change |= gtk_css_selector_tree_collect_change (prev);
 | |
| 
 | |
|   change = tree->selector.class->get_change (&tree->selector, change);
 | |
| 
 | |
|   return change;
 | |
| }
 | |
| 
 | |
| static GtkCssChange
 | |
| gtk_css_selector_tree_get_change (const GtkCssSelectorTree *tree,
 | |
| 				  const GtkCssMatcher      *matcher)
 | |
| {
 | |
|   GtkCssChange change = 0;
 | |
|   const GtkCssSelectorTree *prev;
 | |
| 
 | |
|   if (!gtk_css_selector_match (&tree->selector, matcher))
 | |
|     return 0;
 | |
| 
 | |
|   if (!tree->selector.class->is_simple)
 | |
|     return gtk_css_selector_tree_collect_change (tree) | GTK_CSS_CHANGE_GOT_MATCH;
 | |
| 
 | |
|   for (prev = gtk_css_selector_tree_get_previous (tree);
 | |
|        prev != NULL;
 | |
|        prev = gtk_css_selector_tree_get_sibling (prev))
 | |
|     change |= gtk_css_selector_tree_get_change (prev, matcher);
 | |
| 
 | |
|   if (change || gtk_css_selector_tree_get_matches (tree))
 | |
|     change = tree->selector.class->get_change (&tree->selector, change & ~GTK_CSS_CHANGE_GOT_MATCH) | GTK_CSS_CHANGE_GOT_MATCH;
 | |
| 
 | |
|   return change;
 | |
| }
 | |
| 
 | |
| GtkCssChange
 | |
| _gtk_css_selector_tree_get_change_all (const GtkCssSelectorTree *tree,
 | |
| 				       const GtkCssMatcher *matcher)
 | |
| {
 | |
|   GtkCssChange change;
 | |
| 
 | |
|   change = 0;
 | |
| 
 | |
|   /* no need to foreach here because we abort for non-simple selectors */
 | |
|   for (; tree != NULL;
 | |
|        tree = gtk_css_selector_tree_get_sibling (tree))
 | |
|     change |= gtk_css_selector_tree_get_change (tree, matcher);
 | |
| 
 | |
|   /* Never return reserved bit set */
 | |
|   return change & ~GTK_CSS_CHANGE_RESERVED_BIT;
 | |
| }
 | |
| 
 | |
| #ifdef PRINT_TREE
 | |
| static void
 | |
| _gtk_css_selector_tree_print (const GtkCssSelectorTree *tree, GString *str, char *prefix)
 | |
| {
 | |
|   gboolean first = TRUE;
 | |
|   int len, i;
 | |
| 
 | |
|   for (; tree != NULL; tree = gtk_css_selector_tree_get_sibling (tree), first = FALSE)
 | |
|     {
 | |
|       if (!first)
 | |
| 	g_string_append (str, prefix);
 | |
| 
 | |
|       if (first)
 | |
| 	{
 | |
| 	  if (gtk_css_selector_tree_get_sibling (tree))
 | |
| 	    g_string_append (str, "─┬─");
 | |
| 	  else
 | |
| 	    g_string_append (str, "───");
 | |
| 	}
 | |
|       else
 | |
| 	{
 | |
| 	  if (gtk_css_selector_tree_get_sibling (tree))
 | |
| 	    g_string_append (str, " ├─");
 | |
| 	  else
 | |
| 	    g_string_append (str, " └─");
 | |
| 	}
 | |
| 
 | |
|       len = str->len;
 | |
|       tree->selector.class->print (&tree->selector, str);
 | |
|       len = str->len - len;
 | |
| 
 | |
|       if (gtk_css_selector_tree_get_previous (tree))
 | |
| 	{
 | |
| 	  GString *prefix2 = g_string_new (prefix);
 | |
| 
 | |
| 	  if (gtk_css_selector_tree_get_sibling (tree))
 | |
| 	    g_string_append (prefix2, " │ ");
 | |
| 	  else
 | |
| 	    g_string_append (prefix2, "   ");
 | |
| 	  for (i = 0; i < len; i++)
 | |
| 	    g_string_append_c (prefix2, ' ');
 | |
| 
 | |
| 	  _gtk_css_selector_tree_print (gtk_css_selector_tree_get_previous (tree), str, prefix2->str);
 | |
| 	  g_string_free (prefix2, TRUE);
 | |
| 	}
 | |
|       else
 | |
| 	g_string_append (str, "\n");
 | |
|     }
 | |
| }
 | |
| #endif
 | |
| 
 | |
| void
 | |
| _gtk_css_selector_tree_match_print (const GtkCssSelectorTree *tree,
 | |
| 				    GString *str)
 | |
| {
 | |
|   const GtkCssSelectorTree *iter;
 | |
| 
 | |
|   g_return_if_fail (tree != NULL);
 | |
| 
 | |
|   /* print name and * selector before others */
 | |
|   for (iter = tree; 
 | |
|        iter && iter->selector.class->is_simple;
 | |
|        iter = gtk_css_selector_tree_get_parent (iter))
 | |
|     {
 | |
|       if (iter->selector.class == >K_CSS_SELECTOR_NAME ||
 | |
|           iter->selector.class == >K_CSS_SELECTOR_ANY)
 | |
|         {
 | |
|           iter->selector.class->print (&iter->selector, str);
 | |
|         }
 | |
|     }
 | |
|   /* now print other simple selectors */
 | |
|   for (iter = tree; 
 | |
|        iter && iter->selector.class->is_simple;
 | |
|        iter = gtk_css_selector_tree_get_parent (iter))
 | |
|     {
 | |
|       if (iter->selector.class != >K_CSS_SELECTOR_NAME &&
 | |
|           iter->selector.class != >K_CSS_SELECTOR_ANY)
 | |
|         {
 | |
|           iter->selector.class->print (&iter->selector, str);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|   /* now if there's a combinator, print that one */
 | |
|   if (iter != NULL)
 | |
|     {
 | |
|       iter->selector.class->print (&iter->selector, str);
 | |
|       tree = gtk_css_selector_tree_get_parent (iter);
 | |
|       if (tree)
 | |
|         _gtk_css_selector_tree_match_print (tree, str);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| _gtk_css_selector_tree_free (GtkCssSelectorTree *tree)
 | |
| {
 | |
|   if (tree == NULL)
 | |
|     return;
 | |
| 
 | |
|   g_free (tree);
 | |
| }
 | |
| 
 | |
| 
 | |
| typedef struct {
 | |
|   gpointer match;
 | |
|   GtkCssSelector *current_selector;
 | |
|   GtkCssSelectorTree **selector_match;
 | |
| } GtkCssSelectorRuleSetInfo;
 | |
| 
 | |
| static GtkCssSelectorTree *
 | |
| get_tree (GByteArray *array, gint32 offset)
 | |
| {
 | |
|   return (GtkCssSelectorTree *) (array->data + offset);
 | |
| }
 | |
| 
 | |
| static GtkCssSelectorTree *
 | |
| alloc_tree (GByteArray *array, gint32 *offset)
 | |
| {
 | |
|   GtkCssSelectorTree tree = { { NULL} };
 | |
| 
 | |
|   *offset = array->len;
 | |
|   g_byte_array_append (array, (guint8 *)&tree, sizeof (GtkCssSelectorTree));
 | |
|   return get_tree (array, *offset);
 | |
| }
 | |
| 
 | |
| static gint32
 | |
| subdivide_infos (GByteArray *array, GList *infos, gint32 parent_offset)
 | |
| {
 | |
|   GHashTable *ht;
 | |
|   GList *l;
 | |
|   GList *matched;
 | |
|   GList *remaining;
 | |
|   gint32 tree_offset;
 | |
|   GtkCssSelectorTree *tree;
 | |
|   GtkCssSelectorRuleSetInfo *info;
 | |
|   GtkCssSelector max_selector;
 | |
|   GHashTableIter iter;
 | |
|   guint max_count;
 | |
|   gpointer key, value;
 | |
|   GPtrArray *exact_matches;
 | |
|   gint32 res;
 | |
| 
 | |
|   if (infos == NULL)
 | |
|     return GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET;
 | |
| 
 | |
|   ht = gtk_css_selectors_count_initial_init ();
 | |
| 
 | |
|   for (l = infos; l != NULL; l = l->next)
 | |
|     {
 | |
|       info = l->data;
 | |
|       gtk_css_selectors_count_initial (info->current_selector, ht);
 | |
|     }
 | |
| 
 | |
|   /* Pick the selector with highest count, and use as decision on this level
 | |
|      as that makes it possible to skip the largest amount of checks later */
 | |
| 
 | |
|   max_count = 0;
 | |
| 
 | |
|   g_hash_table_iter_init (&iter, ht);
 | |
|   while (g_hash_table_iter_next (&iter, &key, &value))
 | |
|     {
 | |
|       GtkCssSelector *selector = key;
 | |
|       if (GPOINTER_TO_UINT (value) > max_count ||
 | |
| 	  (GPOINTER_TO_UINT (value) == max_count &&
 | |
| 	  gtk_css_selector_compare_one (selector, &max_selector) < 0))
 | |
| 	{
 | |
| 	  max_count = GPOINTER_TO_UINT (value);
 | |
| 	  max_selector = *selector;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   matched = NULL;
 | |
|   remaining = NULL;
 | |
| 
 | |
|   tree = alloc_tree (array, &tree_offset);
 | |
|   tree->parent_offset = parent_offset;
 | |
|   tree->selector = max_selector;
 | |
| 
 | |
|   exact_matches = NULL;
 | |
|   for (l = infos; l != NULL; l = l->next)
 | |
|     {
 | |
|       info = l->data;
 | |
| 
 | |
|       if (gtk_css_selectors_has_initial_selector (info->current_selector, &max_selector))
 | |
| 	{
 | |
| 	  info->current_selector = gtk_css_selectors_skip_initial_selector (info->current_selector, &max_selector);
 | |
| 	  if (info->current_selector == NULL)
 | |
| 	    {
 | |
| 	      /* Matches current node */
 | |
| 	      if (exact_matches == NULL)
 | |
| 		exact_matches = g_ptr_array_new ();
 | |
| 	      g_ptr_array_add (exact_matches, info->match);
 | |
| 	      if (info->selector_match != NULL)
 | |
| 		*info->selector_match = GUINT_TO_POINTER (tree_offset);
 | |
| 	    }
 | |
| 	  else
 | |
| 	    matched = g_list_prepend (matched, info);
 | |
| 	}
 | |
|       else
 | |
| 	{
 | |
| 	  remaining = g_list_prepend (remaining, info);
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   if (exact_matches)
 | |
|     {
 | |
|       g_ptr_array_add (exact_matches, NULL); /* Null terminate */
 | |
|       res = array->len;
 | |
|       g_byte_array_append (array, (guint8 *)exact_matches->pdata,
 | |
| 			   exact_matches->len * sizeof (gpointer));
 | |
|       g_ptr_array_free (exact_matches, TRUE);
 | |
|     }
 | |
|   else
 | |
|     res = GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET;
 | |
|   get_tree (array, tree_offset)->matches_offset = res;
 | |
| 
 | |
|   res = subdivide_infos (array, matched, tree_offset);
 | |
|   get_tree (array, tree_offset)->previous_offset = res;
 | |
| 
 | |
|   res = subdivide_infos (array, remaining, parent_offset);
 | |
|   get_tree (array, tree_offset)->sibling_offset = res;
 | |
| 
 | |
|   g_list_free (matched);
 | |
|   g_list_free (remaining);
 | |
|   g_hash_table_unref (ht);
 | |
| 
 | |
|   return tree_offset;
 | |
| }
 | |
| 
 | |
| struct _GtkCssSelectorTreeBuilder {
 | |
|   GList  *infos;
 | |
| };
 | |
| 
 | |
| GtkCssSelectorTreeBuilder *
 | |
| _gtk_css_selector_tree_builder_new (void)
 | |
| {
 | |
|   return g_new0 (GtkCssSelectorTreeBuilder, 1);
 | |
| }
 | |
| 
 | |
| void
 | |
| _gtk_css_selector_tree_builder_free  (GtkCssSelectorTreeBuilder *builder)
 | |
| {
 | |
|   g_list_free_full (builder->infos, g_free);
 | |
|   g_free (builder);
 | |
| }
 | |
| 
 | |
| void
 | |
| _gtk_css_selector_tree_builder_add (GtkCssSelectorTreeBuilder *builder,
 | |
| 				    GtkCssSelector            *selectors,
 | |
| 				    GtkCssSelectorTree       **selector_match,
 | |
| 				    gpointer                   match)
 | |
| {
 | |
|   GtkCssSelectorRuleSetInfo *info = g_new0 (GtkCssSelectorRuleSetInfo, 1);
 | |
| 
 | |
|   info->match = match;
 | |
|   info->current_selector = selectors;
 | |
|   info->selector_match = selector_match;
 | |
|   builder->infos = g_list_prepend (builder->infos, info);
 | |
| }
 | |
| 
 | |
| /* Convert all offsets to node-relative */
 | |
| static void
 | |
| fixup_offsets (GtkCssSelectorTree *tree, guint8 *data)
 | |
| {
 | |
|   while (tree != NULL)
 | |
|     {
 | |
|       if (tree->parent_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
 | |
| 	tree->parent_offset -= ((guint8 *)tree - data);
 | |
| 
 | |
|       if (tree->previous_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
 | |
| 	tree->previous_offset -= ((guint8 *)tree - data);
 | |
| 
 | |
|       if (tree->sibling_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
 | |
| 	tree->sibling_offset -= ((guint8 *)tree - data);
 | |
| 
 | |
|       if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET)
 | |
| 	tree->matches_offset -= ((guint8 *)tree - data);
 | |
| 
 | |
|       fixup_offsets ((GtkCssSelectorTree *)gtk_css_selector_tree_get_previous (tree), data);
 | |
| 
 | |
|       tree = (GtkCssSelectorTree *)gtk_css_selector_tree_get_sibling (tree);
 | |
|     }
 | |
| }
 | |
| 
 | |
| GtkCssSelectorTree *
 | |
| _gtk_css_selector_tree_builder_build (GtkCssSelectorTreeBuilder *builder)
 | |
| {
 | |
|   GtkCssSelectorTree *tree;
 | |
|   GByteArray *array;
 | |
|   guint8 *data;
 | |
|   guint len;
 | |
|   GList *l;
 | |
|   GtkCssSelectorRuleSetInfo *info;
 | |
| 
 | |
|   array = g_byte_array_new ();
 | |
|   subdivide_infos (array, builder->infos, GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET);
 | |
| 
 | |
|   len = array->len;
 | |
|   data = g_byte_array_free (array, FALSE);
 | |
| 
 | |
|   /* shrink to final size */
 | |
|   data = g_realloc (data, len);
 | |
| 
 | |
|   tree = (GtkCssSelectorTree *)data;
 | |
| 
 | |
|   fixup_offsets (tree, data);
 | |
| 
 | |
|   /* Convert offsets to final pointers */
 | |
|   for (l = builder->infos; l != NULL; l = l->next)
 | |
|     {
 | |
|       info = l->data;
 | |
|       if (info->selector_match)
 | |
| 	*info->selector_match = (GtkCssSelectorTree *)(data + GPOINTER_TO_UINT (*info->selector_match));
 | |
|     }
 | |
| 
 | |
| #ifdef PRINT_TREE
 | |
|   {
 | |
|     GString *s = g_string_new ("");
 | |
|     _gtk_css_selector_tree_print (tree, s, "");
 | |
|     g_print ("%s", s->str);
 | |
|     g_string_free (s, TRUE);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   return tree;
 | |
| }
 | 
