+
+/******************** SelectorTree handling *****************/
+
+static GHashTable *
+gtk_css_selectors_count_initial_init (void)
+{
+ return g_hash_table_new ((GHashFunc)gtk_css_selector_hash, (GEqualFunc)gtk_css_selector_equal);
+}
+
+static void
+gtk_css_selectors_count_initial (const GtkCssSelector *selector, GHashTable *hash)
+{
+ if (!selector->class->is_simple)
+ {
+ guint count = GPOINTER_TO_INT (g_hash_table_lookup (hash, selector));
+ g_hash_table_replace (hash, (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, selector));
+ g_hash_table_replace (hash, (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 int
+direct_ptr_compare (const void *_a, const void *_b)
+{
+ gpointer *a = (gpointer *)_a;
+ gpointer *b = (gpointer *)_b;
+ if (*a < *b)
+ return -1;
+ else if (*a == *b)
+ return 0;
+ return 1;
+}
+
+GPtrArray *
+_gtk_css_selector_tree_match_all (GtkCssSelectorTree *tree,
+ const GtkCssMatcher *matcher)
+{
+ GHashTable *res;
+ GPtrArray *array;
+ GHashTableIter iter;
+ gpointer key;
+
+ res = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ for (; tree != NULL; tree = tree->siblings)
+ gtk_css_selector_tree_match (tree, matcher, res);
+
+ array = g_ptr_array_sized_new (g_hash_table_size (res));
+
+ g_hash_table_iter_init (&iter, res);
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ g_ptr_array_add (array, key);
+
+ g_hash_table_destroy (res);
+
+ qsort (array->pdata, array->len, sizeof (gpointer), direct_ptr_compare);
+
+ return array;
+}
+
+#ifdef PRINT_TREE
+static void
+_gtk_css_selector_tree_print (GtkCssSelectorTree *tree, GString *str, char *prefix)
+{
+ gboolean first = TRUE;
+ int len, i;
+
+ for (; tree != NULL; tree = tree->siblings, first = FALSE)
+ {
+ if (!first)
+ g_string_append (str, prefix);
+
+ if (first)
+ {
+ if (tree->siblings)
+ g_string_append (str, "─┬─");
+ else
+ g_string_append (str, "───");
+ }
+ else
+ {
+ if (tree->siblings)
+ g_string_append (str, " ├─");
+ else
+ g_string_append (str, " └─");
+ }
+
+ len = str->len;
+ tree->selector.class->print (&tree->selector, str);
+ len = str->len - len;
+
+ if (tree->previous)
+ {
+ GString *prefix2 = g_string_new (prefix);
+
+ if (tree->siblings)
+ 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 (tree->previous, str, prefix2->str);
+ g_string_free (prefix2, TRUE);
+ }
+ else
+ g_string_append (str, "\n");
+ }
+}
+#endif
+
+void
+_gtk_css_selector_tree_free (GtkCssSelectorTree *tree)
+{
+ if (tree == NULL)
+ return;
+
+ _gtk_css_selector_tree_free (tree->siblings);
+ _gtk_css_selector_tree_free (tree->previous);
+
+ g_free (tree);
+}
+
+
+typedef struct {
+ GtkCssSelector **ruleset;
+ GtkCssSelector *current_selector;
+} GtkCssSelectorRuleSetInfo;
+
+
+static GtkCssSelectorTree *
+subdivide_infos (GList *infos)
+{
+ GHashTable *ht = gtk_css_selectors_count_initial_init ();
+ GList *l;
+ GList *matched;
+ GList *remaining;
+ GtkCssSelectorTree *tree;
+ GtkCssSelectorRuleSetInfo *info;
+ GtkCssSelector *max_selector;
+ GHashTableIter iter;
+ guint max_count;
+ gpointer key, value;
+ GPtrArray *exact_matches;
+
+ if (infos == NULL)
+ return NULL;
+
+ 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;
+ max_selector = NULL;
+
+ g_hash_table_iter_init (&iter, ht);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ if (GPOINTER_TO_UINT (value) > max_count)
+ {
+ max_count = GPOINTER_TO_UINT (value);
+ max_selector = key;
+ }
+ }
+
+ matched = NULL;
+ remaining = NULL;
+
+ tree = g_new0 (GtkCssSelectorTree, 1);
+ 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, &tree->selector))
+ {
+ info->current_selector = gtk_css_selectors_skip_initial_selector (info->current_selector, &tree->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->ruleset);
+ }
+ 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 */
+ tree->matches = g_ptr_array_free (exact_matches, FALSE);
+ }
+
+ if (matched)
+ tree->previous = subdivide_infos (matched);
+
+ if (remaining)
+ tree->siblings = subdivide_infos (remaining);
+
+ return tree;
+}
+
+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,
+ gpointer match)
+{
+ GtkCssSelectorRuleSetInfo *info = g_new0 (GtkCssSelectorRuleSetInfo, 1);
+
+ info->ruleset = match;
+ info->current_selector = selectors;
+ builder->infos = g_list_prepend (builder->infos, info);
+}
+
+GtkCssSelectorTree *
+_gtk_css_selector_tree_builder_build (GtkCssSelectorTreeBuilder *builder)
+{
+ GtkCssSelectorTree *tree;
+
+ tree = subdivide_infos (builder->infos);
+
+#ifdef PRINT_TREE
+ {
+ GString *s = g_string_new ("");
+ _gtk_css_selector_tree_print (tree, s, "");
+ g_print ("%s", s->str);
+ g_string_free (s);
+ }
+#endif
+
+ return tree;
+}