gtk2/gtk/gtkcssselector.c
Benjamin Otte 506639add1 cssselector: The if was the wrong way around
We want to keep matching as long as selectors are simple. Otherwise we'd
never be matching, because the first selector is always simple. Oops.
2015-01-05 21:20:26 +01:00

2112 lines
59 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"
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 exissts.
* 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
gtk_css_selector_tree_found_match (const GtkCssSelectorTree *tree,
GHashTable *res)
{
int i;
gpointer *matches;
matches = gtk_css_selector_tree_get_matches (tree);
if (matches)
{
for (i = 0; matches[i] != NULL; i++)
g_hash_table_insert (res, matches[i], 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
};
/* DESCENDANT FOR REGION */
static void
gtk_css_selector_descendant_for_region_print (const GtkCssSelector *selector,
GString *string)
{
g_string_append_c (string, ' ');
}
static gboolean
gtk_css_selector_descendant_for_region_foreach_matcher (const GtkCssSelector *selector,
const GtkCssMatcher *matcher,
GtkCssSelectorForeachFunc func,
gpointer data)
{
GtkCssMatcher ancestor;
if (_gtk_css_matcher_has_regions (matcher))
{
if (func (selector, matcher, data))
return TRUE;
}
while (_gtk_css_matcher_get_parent (&ancestor, matcher))
{
matcher = &ancestor;
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_descendant_for_region_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
{
return previous_change | _gtk_css_change_for_child (previous_change);
}
static const GtkCssSelectorClass GTK_CSS_SELECTOR_DESCENDANT_FOR_REGION = {
"descendant_for_region",
gtk_css_selector_descendant_for_region_print,
gtk_css_selector_descendant_for_region_foreach_matcher,
gtk_css_selector_default_match_one,
gtk_css_selector_descendant_for_region_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_type (matcher, 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)
/* REGION */
static void
gtk_css_selector_region_print (const GtkCssSelector *selector,
GString *string)
{
char *region_names[] = {
"even",
"odd",
"first-child",
"last-child",
"only-child",
"sorted"
};
guint i;
g_string_append (string, selector->region.name);
for (i = 0; i < G_N_ELEMENTS (region_names); i++)
{
if (selector->region.flags & (1 << i))
{
g_string_append_c (string, ':');
g_string_append (string, region_names[i]);
}
}
}
static gboolean
gtk_css_selector_region_match_one (const GtkCssSelector *selector,
const GtkCssMatcher *matcher)
{
return _gtk_css_matcher_has_region (matcher, selector->region.name, selector->region.flags);
}
static GtkCssChange
gtk_css_selector_region_get_change (const GtkCssSelector *selector, GtkCssChange previous_change)
{
return previous_change | GTK_CSS_CHANGE_REGION;
}
static guint
count_bits (guint n)
{
guint result = 0;
for (result = 0; n != 0; result++)
n &= n - 1;
return result;
}
static void
gtk_css_selector_region_add_specificity (const GtkCssSelector *selector,
guint *ids,
guint *classes,
guint *elements)
{
(*elements)++;
(*classes) += count_bits (selector->region.flags);
}
static guint
gtk_css_selector_region_hash_one (const GtkCssSelector *a)
{
return g_str_hash (a->region.name) ^ a->region.flags;
}
static int
gtk_css_selector_region_compare_one (const GtkCssSelector *a,
const GtkCssSelector *b)
{
int diff;
diff = strcmp (a->region.name, b->region.name);
if (diff)
return diff;
return a->region.flags - b->region.flags;
}
static const GtkCssSelectorClass GTK_CSS_SELECTOR_REGION = {
"region",
gtk_css_selector_region_print,
gtk_css_selector_default_foreach_matcher,
gtk_css_selector_region_match_one,
gtk_css_selector_region_get_change,
gtk_css_selector_region_add_specificity,
gtk_css_selector_region_hash_one,
gtk_css_selector_region_compare_one,
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 g_str_hash (g_quark_to_string (a->style_class.style_class));
}
static int
comp_class (const GtkCssSelector *a,
const GtkCssSelector *b)
{
return strcmp (g_quark_to_string (a->style_class.style_class),
g_quark_to_string (b->style_class.style_class));
}
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 g_str_hash (a->id.name);
}
static int
comp_id (const GtkCssSelector *a,
const GtkCssSelector *b)
{
return strcmp (a->id.name, b->id.name);
}
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"
};
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;
}
#define GTK_CSS_CHANGE_PSEUDOCLASS_POSITION GTK_CSS_CHANGE_POSITION
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 ? &GTK_CSS_SELECTOR_NOT_CLASS
: &GTK_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 ? &GTK_CSS_SELECTOR_NOT_ID
: &GTK_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 ? &GTK_CSS_SELECTOR_NOT_PSEUDOCLASS_POSITION
: &GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION,
selector);
selector->position.type = type;
selector->position.a = a;
selector->position.b = b;
return selector;
}
static GtkRegionFlags
try_parse_selector_region_pseudo_class (GtkCssParser *parser)
{
static const struct {
const char *name;
GtkRegionFlags flags;
} region_flags[] = {
{ "even", GTK_REGION_EVEN },
{ "odd", GTK_REGION_ODD },
{ "first-child", GTK_REGION_FIRST },
{ "last-child", GTK_REGION_LAST },
{ "only-child", GTK_REGION_ONLY },
{ "sorted", GTK_REGION_SORTED }
};
guint i;
for (i = 0; i < G_N_ELEMENTS (region_flags); i++)
{
if (_gtk_css_parser_try (parser, region_flags[i].name, FALSE))
return region_flags[i].flags;
}
return 0;
}
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, }
};
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 ? &GTK_CSS_SELECTOR_NOT_PSEUDOCLASS_STATE
: &GTK_CSS_SELECTOR_PSEUDOCLASS_STATE,
selector);
selector->state.state = pseudo_classes[i].state_flag;
}
else
{
selector = gtk_css_selector_new (negate ? &GTK_CSS_SELECTOR_NOT_PSEUDOCLASS_POSITION
: &GTK_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 (&GTK_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 (&GTK_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;
guint region_offset = 0;
char *name;
name = _gtk_css_parser_try_ident (parser, FALSE);
if (name)
{
if (_gtk_style_context_check_region_name (name))
{
selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_REGION,
selector);
selector->region.name = g_intern_string (name);
region_offset = gtk_css_selector_size (selector);
}
else
{
selector = gtk_css_selector_new (&GTK_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 (&GTK_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))
{
GtkRegionFlags region_flags;
if (region_offset &&
(region_flags = try_parse_selector_region_pseudo_class (parser)))
{
selector[gtk_css_selector_size (selector) - region_offset].region.flags |= region_flags;
}
else
{
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);
/* This is the big region hack where we change the descendant matcher
* to a version that respects regions.
*/
if (selector)
{
if ((selector[0].class == &GTK_CSS_SELECTOR_ANY || selector[0].class == &GTK_CSS_SELECTOR_REGION)
&& selector[1].class == &GTK_CSS_SELECTOR_DESCENDANT)
selector[1].class = &GTK_CSS_SELECTOR_DESCENDANT_FOR_REGION;
}
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 (&GTK_CSS_SELECTOR_ADJACENT, selector);
else if (_gtk_css_parser_try (parser, "~", TRUE))
selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_SIBLING, selector);
else if (_gtk_css_parser_try (parser, ">", TRUE))
selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_CHILD, selector);
else
selector = gtk_css_selector_new (&GTK_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 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;
}
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)
{
GHashTable *res;
GPtrArray *array;
GHashTableIter iter;
gpointer key;
update_type_references ();
res = g_hash_table_new (g_direct_hash, g_direct_equal);
for (; tree != NULL;
tree = gtk_css_selector_tree_get_sibling (tree))
gtk_css_selector_foreach (&tree->selector, matcher, gtk_css_selector_tree_match_foreach, 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;
}
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);
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);
change = tree->selector.class->get_change (&tree->selector, change);
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;
}
#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 *parent;
g_return_if_fail (tree != NULL);
tree->selector.class->print (&tree->selector, str);
parent = gtk_css_selector_tree_get_parent (tree);
if (parent != NULL)
_gtk_css_selector_tree_match_print (parent, 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;
}