X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkcssselector.c;h=216090faaddccaeba251026712d5066ddac2c63e;hb=HEAD;hp=369d62070569bb48aed92181b028a86934f7541b;hpb=b0b6c8ad4b9120bdc30d2fd81d572dfb060f4ed1;p=~andy%2Fgtk diff --git a/gtk/gtkcssselector.c b/gtk/gtkcssselector.c index 369d62070..216090faa 100644 --- a/gtk/gtkcssselector.c +++ b/gtk/gtkcssselector.c @@ -19,11 +19,23 @@ #include "gtkcssselectorprivate.h" +#include #include #include "gtkcssprovider.h" #include "gtkstylecontextprivate.h" +/* 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 + typedef struct _GtkCssSelectorClass GtkCssSelectorClass; struct _GtkCssSelectorClass { @@ -33,11 +45,21 @@ struct _GtkCssSelectorClass { GString *string); gboolean (* match) (const GtkCssSelector *selector, const GtkCssMatcher *matcher); - GtkCssChange (* get_change) (const GtkCssSelector *selector); + void (* tree_match) (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher, + GHashTable *res); + GtkCssChange (* get_change) (const GtkCssSelector *selector, + GtkCssChange previous_change); + GtkCssChange (* tree_get_change) (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher); + int (* compare_one) (const GtkCssSelector *a, + const GtkCssSelector *b); guint increase_id_specificity :1; guint increase_class_specificity :1; guint increase_element_specificity :1; + guint is_simple :1; + guint must_keep_order :1; /* Due to region weirdness these must be kept before a DESCENDANT, so don't reorder */ }; struct _GtkCssSelector @@ -48,6 +70,76 @@ struct _GtkCssSelector - GUINT_TO_POINTER() for PSEUDOCLASS_REGION/STATE */ }; +#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->data == b->data; +} + +static guint +gtk_css_selector_hash (const GtkCssSelector *selector) +{ + return GPOINTER_TO_UINT (selector->class) ^ GPOINTER_TO_UINT (selector->data); +} + +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 void +gtk_css_selector_tree_match (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher, + GHashTable *res) +{ + if (tree == NULL) + return; + + tree->selector.class->tree_match (tree, matcher, res); +} + +static GtkCssChange +gtk_css_selector_tree_get_change (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher) +{ + if (tree == NULL) + return 0; + + return tree->selector.class->tree_get_change (tree, matcher); +} + static gboolean gtk_css_selector_match (const GtkCssSelector *selector, const GtkCssMatcher *matcher) @@ -58,15 +150,15 @@ gtk_css_selector_match (const GtkCssSelector *selector, return selector->class->match (selector, matcher); } -static GtkCssChange -gtk_css_selector_get_change (const GtkCssSelector *selector) +static int +gtk_css_selector_compare_one (const GtkCssSelector *a, const GtkCssSelector *b) { - if (selector == NULL) - return 0; - - return selector->class->get_change (selector); + 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) { @@ -75,6 +167,62 @@ gtk_css_selector_previous (const GtkCssSelector *selector) 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); +} + +static void +gtk_css_selector_tree_match_previous (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher, + GHashTable *res) +{ + const GtkCssSelectorTree *prev; + + for (prev = gtk_css_selector_tree_get_previous (tree); + prev != NULL; + prev = gtk_css_selector_tree_get_sibling (prev)) + gtk_css_selector_tree_match (prev, matcher, res); +} + +static GtkCssChange +gtk_css_selector_tree_get_previous_change (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher) +{ + GtkCssChange previous_change = 0; + const GtkCssSelectorTree *prev; + + for (prev = gtk_css_selector_tree_get_previous (tree); + prev != NULL; + prev = gtk_css_selector_tree_get_sibling (prev)) + previous_change |= gtk_css_selector_tree_get_change (prev, matcher); + + return previous_change; +} + /* DESCENDANT */ static void @@ -101,18 +249,75 @@ gtk_css_selector_descendant_match (const GtkCssSelector *selector, return FALSE; } +static void +gtk_css_selector_descendant_tree_match (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher, + GHashTable *res) +{ + GtkCssMatcher ancestor; + + while (_gtk_css_matcher_get_parent (&ancestor, matcher)) + { + matcher = &ancestor; + + gtk_css_selector_tree_match_previous (tree, matcher, res); + + /* 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; + } +} + +static GtkCssChange +gtk_css_selector_descendant_tree_get_change (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher) +{ + GtkCssMatcher ancestor; + GtkCssChange change, previous_change; + + change = 0; + previous_change = 0; + while (_gtk_css_matcher_get_parent (&ancestor, matcher)) + { + matcher = &ancestor; + + previous_change |= gtk_css_selector_tree_get_previous_change (tree, matcher); + + /* 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; + } + + if (previous_change != 0) + change |= _gtk_css_change_for_child (previous_change) | GTK_CSS_CHANGE_GOT_MATCH; + + return change; +} + +static int +gtk_css_selector_descendant_compare_one (const GtkCssSelector *a, + const GtkCssSelector *b) +{ + return 0; +} + static GtkCssChange -gtk_css_selector_descendant_get_change (const GtkCssSelector *selector) +gtk_css_selector_descendant_get_change (const GtkCssSelector *selector, GtkCssChange previous_change) { - return _gtk_css_change_for_child (gtk_css_selector_get_change (gtk_css_selector_previous (selector))); + 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_match, + gtk_css_selector_descendant_tree_match, gtk_css_selector_descendant_get_change, - FALSE, FALSE, FALSE + gtk_css_selector_descendant_tree_get_change, + gtk_css_selector_descendant_compare_one, + FALSE, FALSE, FALSE, FALSE, FALSE }; /* CHILD */ @@ -136,18 +341,62 @@ gtk_css_selector_child_match (const GtkCssSelector *selector, return gtk_css_selector_match (gtk_css_selector_previous (selector), &parent); } +static void +gtk_css_selector_child_tree_match (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher, + GHashTable *res) +{ + GtkCssMatcher parent; + + if (!_gtk_css_matcher_get_parent (&parent, matcher)) + return; + + gtk_css_selector_tree_match_previous (tree, &parent, res); +} + + +static GtkCssChange +gtk_css_selector_child_tree_get_change (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher) +{ + GtkCssMatcher parent; + GtkCssChange change, previous_change; + + if (!_gtk_css_matcher_get_parent (&parent, matcher)) + return 0; + + change = 0; + + previous_change = gtk_css_selector_tree_get_previous_change (tree, &parent); + + if (previous_change != 0) + change |= _gtk_css_change_for_child (previous_change) | GTK_CSS_CHANGE_GOT_MATCH; + + return change; +} + static GtkCssChange -gtk_css_selector_child_get_change (const GtkCssSelector *selector) +gtk_css_selector_child_get_change (const GtkCssSelector *selector, GtkCssChange previous_change) +{ + return _gtk_css_change_for_child (previous_change); +} + +static int +gtk_css_selector_child_compare_one (const GtkCssSelector *a, + const GtkCssSelector *b) { - return _gtk_css_change_for_child (gtk_css_selector_get_change (gtk_css_selector_previous (selector))); + return 0; } static const GtkCssSelectorClass GTK_CSS_SELECTOR_CHILD = { "child", gtk_css_selector_child_print, gtk_css_selector_child_match, + gtk_css_selector_child_tree_match, gtk_css_selector_child_get_change, - FALSE, FALSE, FALSE + gtk_css_selector_child_tree_get_change, + gtk_css_selector_child_compare_one, + FALSE, FALSE, FALSE, FALSE, FALSE }; /* SIBLING */ @@ -176,18 +425,77 @@ gtk_css_selector_sibling_match (const GtkCssSelector *selector, return FALSE; } +static void +gtk_css_selector_sibling_tree_match (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher, + GHashTable *res) +{ + GtkCssMatcher previous; + + while (_gtk_css_matcher_get_previous (&previous, matcher)) + { + matcher = &previous; + + gtk_css_selector_tree_match_previous (tree, matcher, res); + + /* 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; + } +} + +static GtkCssChange +gtk_css_selector_sibling_tree_get_change (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher) +{ + GtkCssMatcher previous; + GtkCssChange change, previous_change; + + change = 0; + + previous_change = 0; + while (_gtk_css_matcher_get_previous (&previous, matcher)) + { + matcher = &previous; + + previous_change |= gtk_css_selector_tree_get_previous_change (tree, matcher); + + /* 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; + } + + if (previous_change != 0) + change |= _gtk_css_change_for_sibling (previous_change) | GTK_CSS_CHANGE_GOT_MATCH; + + return change; +} + static GtkCssChange -gtk_css_selector_sibling_get_change (const GtkCssSelector *selector) +gtk_css_selector_sibling_get_change (const GtkCssSelector *selector, GtkCssChange previous_change) +{ + return _gtk_css_change_for_sibling (previous_change); +} + +static int +gtk_css_selector_sibling_compare_one (const GtkCssSelector *a, + const GtkCssSelector *b) { - return _gtk_css_change_for_sibling (gtk_css_selector_get_change (gtk_css_selector_previous (selector))); + return 0; } + static const GtkCssSelectorClass GTK_CSS_SELECTOR_SIBLING = { "sibling", gtk_css_selector_sibling_print, gtk_css_selector_sibling_match, + gtk_css_selector_sibling_tree_match, gtk_css_selector_sibling_get_change, - FALSE, FALSE, FALSE + gtk_css_selector_sibling_tree_get_change, + gtk_css_selector_sibling_compare_one, + FALSE, FALSE, FALSE, FALSE, FALSE }; /* ADJACENT */ @@ -211,18 +519,63 @@ gtk_css_selector_adjacent_match (const GtkCssSelector *selector, return gtk_css_selector_match (gtk_css_selector_previous (selector), &previous); } +static void +gtk_css_selector_adjacent_tree_match (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher, + GHashTable *res) +{ + GtkCssMatcher previous; + + if (!_gtk_css_matcher_get_previous (&previous, matcher)) + return; + + matcher = &previous; + + gtk_css_selector_tree_match_previous (tree, matcher, res); +} + +static GtkCssChange +gtk_css_selector_adjacent_tree_get_change (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher) +{ + GtkCssMatcher previous; + GtkCssChange change, previous_change; + + if (!_gtk_css_matcher_get_previous (&previous, matcher)) + return 0; + + change = 0; + + previous_change = gtk_css_selector_tree_get_previous_change (tree, &previous); + + if (previous_change != 0) + change |= _gtk_css_change_for_sibling (previous_change) | GTK_CSS_CHANGE_GOT_MATCH; + + return change; +} + static GtkCssChange -gtk_css_selector_adjacent_get_change (const GtkCssSelector *selector) +gtk_css_selector_adjacent_get_change (const GtkCssSelector *selector, GtkCssChange previous_change) +{ + return _gtk_css_change_for_sibling (previous_change); +} + +static int +gtk_css_selector_adjacent_compare_one (const GtkCssSelector *a, + const GtkCssSelector *b) { - return _gtk_css_change_for_sibling (gtk_css_selector_get_change (gtk_css_selector_previous (selector))); + return 0; } static const GtkCssSelectorClass GTK_CSS_SELECTOR_ADJACENT = { "adjacent", gtk_css_selector_adjacent_print, gtk_css_selector_adjacent_match, + gtk_css_selector_adjacent_tree_match, gtk_css_selector_adjacent_get_change, - FALSE, FALSE, FALSE + gtk_css_selector_adjacent_tree_get_change, + gtk_css_selector_adjacent_compare_one, + FALSE, FALSE, FALSE, FALSE, FALSE }; /* ANY */ @@ -251,51 +604,219 @@ gtk_css_selector_any_match (const GtkCssSelector *selector, return gtk_css_selector_match (previous, matcher); } +static void +gtk_css_selector_any_tree_match (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher, + GHashTable *res) +{ + const GtkCssSelectorTree *prev; + + 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)) + { + if (prev->selector.class == >K_CSS_SELECTOR_DESCENDANT && + _gtk_css_matcher_has_regions (matcher)) + gtk_css_selector_tree_match_previous (prev, matcher, res); + + gtk_css_selector_tree_match (prev, matcher, res); + } +} + +static GtkCssChange +gtk_css_selector_any_tree_get_change (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher) +{ + const GtkCssSelectorTree *prev; + GtkCssChange change, previous_change; + + change = 0; + + if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET) + change |= GTK_CSS_CHANGE_GOT_MATCH; + + previous_change = 0; + for (prev = gtk_css_selector_tree_get_previous (tree); + prev != NULL; + prev = gtk_css_selector_tree_get_sibling (prev)) + { + if (prev->selector.class == >K_CSS_SELECTOR_DESCENDANT && + _gtk_css_matcher_has_regions (matcher)) + previous_change |= gtk_css_selector_tree_get_previous_change (prev, matcher); + + previous_change |= gtk_css_selector_tree_get_change (prev, matcher); + } + + if (previous_change != 0) + change |= previous_change | GTK_CSS_CHANGE_GOT_MATCH; + + return change; +} + + static GtkCssChange -gtk_css_selector_any_get_change (const GtkCssSelector *selector) +gtk_css_selector_any_get_change (const GtkCssSelector *selector, GtkCssChange previous_change) +{ + return previous_change; +} + +static int +gtk_css_selector_any_compare_one (const GtkCssSelector *a, + const GtkCssSelector *b) { - return gtk_css_selector_get_change (gtk_css_selector_previous (selector)); + return 0; } static const GtkCssSelectorClass GTK_CSS_SELECTOR_ANY = { "any", gtk_css_selector_any_print, gtk_css_selector_any_match, + gtk_css_selector_any_tree_match, gtk_css_selector_any_get_change, - FALSE, FALSE, FALSE + gtk_css_selector_any_tree_get_change, + gtk_css_selector_any_compare_one, + FALSE, FALSE, FALSE, TRUE, TRUE }; /* NAME */ +typedef struct { + GType type; + const char *name; +} TypeReference; + +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 gtk_css_selector_name_print (const GtkCssSelector *selector, GString *string) { - g_string_append (string, selector->data); + g_string_append (string, ((TypeReference *)selector->data)->name); } static gboolean gtk_css_selector_name_match (const GtkCssSelector *selector, const GtkCssMatcher *matcher) { - if (!_gtk_css_matcher_has_name (matcher, selector->data)) + if (!_gtk_css_matcher_has_type (matcher, ((TypeReference *)selector->data)->type)) return FALSE; return gtk_css_selector_match (gtk_css_selector_previous (selector), matcher); } +static void +gtk_css_selector_name_tree_match (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher, + GHashTable *res) +{ + if (!_gtk_css_matcher_has_type (matcher, ((TypeReference *)tree->selector.data)->type)) + return; + + gtk_css_selector_tree_found_match (tree, res); + + gtk_css_selector_tree_match_previous (tree, matcher, res); +} + +static GtkCssChange +gtk_css_selector_name_tree_get_change (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher) +{ + GtkCssChange change, previous_change; + + if (!_gtk_css_matcher_has_type (matcher, ((TypeReference *)tree->selector.data)->type)) + return 0; + + change = 0; + + if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET) + change |= GTK_CSS_CHANGE_NAME | GTK_CSS_CHANGE_GOT_MATCH; + + previous_change = gtk_css_selector_tree_get_previous_change (tree, matcher); + + if (previous_change) + change |= previous_change | GTK_CSS_CHANGE_NAME | GTK_CSS_CHANGE_GOT_MATCH; + + return change; +} + + static GtkCssChange -gtk_css_selector_name_get_change (const GtkCssSelector *selector) +gtk_css_selector_name_get_change (const GtkCssSelector *selector, GtkCssChange previous_change) { - return gtk_css_selector_get_change (gtk_css_selector_previous (selector)) | GTK_CSS_CHANGE_NAME; + return previous_change | GTK_CSS_CHANGE_NAME; +} + +static int +gtk_css_selector_name_compare_one (const GtkCssSelector *a, + const GtkCssSelector *b) +{ + return strcmp (((TypeReference *)a->data)->name, + ((TypeReference *)b->data)->name); } static const GtkCssSelectorClass GTK_CSS_SELECTOR_NAME = { "name", gtk_css_selector_name_print, gtk_css_selector_name_match, + gtk_css_selector_name_tree_match, gtk_css_selector_name_get_change, - FALSE, FALSE, TRUE + gtk_css_selector_name_tree_get_change, + gtk_css_selector_name_compare_one, + FALSE, FALSE, TRUE, TRUE, FALSE }; /* REGION */ @@ -324,24 +845,93 @@ gtk_css_selector_region_match (const GtkCssSelector *selector, return gtk_css_selector_match (previous, matcher); } +static void +gtk_css_selector_region_tree_match (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher, + GHashTable *res) +{ + const GtkCssSelectorTree *prev; + + if (!_gtk_css_matcher_has_region (matcher, tree->selector.data, 0)) + return; + + 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)) + { + if (prev->selector.class == >K_CSS_SELECTOR_DESCENDANT) + gtk_css_selector_tree_match_previous (prev, matcher, res); + + gtk_css_selector_tree_match (prev, matcher, res); + } +} + +static GtkCssChange +gtk_css_selector_region_tree_get_change (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher) +{ + const GtkCssSelectorTree *prev; + GtkCssChange change, previous_change; + + if (!_gtk_css_matcher_has_region (matcher, tree->selector.data, 0)) + return 0; + + change = 0; + + if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET) + change |= GTK_CSS_CHANGE_REGION | GTK_CSS_CHANGE_GOT_MATCH; + + previous_change = 0; + for (prev = gtk_css_selector_tree_get_previous (tree); + prev != NULL; + prev = gtk_css_selector_tree_get_sibling (prev)) + { + if (prev->selector.class == >K_CSS_SELECTOR_DESCENDANT) + previous_change |= gtk_css_selector_tree_get_previous_change (prev, matcher); + + previous_change |= gtk_css_selector_tree_get_change (prev, matcher); + } + + if (previous_change != 0) + { + previous_change |= GTK_CSS_CHANGE_REGION; + previous_change |= _gtk_css_change_for_child (previous_change); + change |= previous_change | GTK_CSS_CHANGE_GOT_MATCH; + } + + return change; +} + static GtkCssChange -gtk_css_selector_region_get_change (const GtkCssSelector *selector) +gtk_css_selector_region_get_change (const GtkCssSelector *selector, GtkCssChange previous_change) { GtkCssChange change; - change = gtk_css_selector_get_change (gtk_css_selector_previous (selector)); + change = previous_change; change |= GTK_CSS_CHANGE_REGION; change |= _gtk_css_change_for_child (change); return change; } +static int +gtk_css_selector_region_compare_one (const GtkCssSelector *a, + const GtkCssSelector *b) +{ + return strcmp (a->data, b->data); +} + static const GtkCssSelectorClass GTK_CSS_SELECTOR_REGION = { "region", gtk_css_selector_region_print, gtk_css_selector_region_match, + gtk_css_selector_region_tree_match, gtk_css_selector_region_get_change, - FALSE, FALSE, TRUE + gtk_css_selector_region_tree_get_change, + gtk_css_selector_region_compare_one, + FALSE, FALSE, TRUE, TRUE, TRUE }; /* CLASS */ @@ -351,31 +941,77 @@ gtk_css_selector_class_print (const GtkCssSelector *selector, GString *string) { g_string_append_c (string, '.'); - g_string_append (string, selector->data); + g_string_append (string, g_quark_to_string (GPOINTER_TO_UINT (selector->data))); } static gboolean gtk_css_selector_class_match (const GtkCssSelector *selector, const GtkCssMatcher *matcher) { - if (!_gtk_css_matcher_has_class (matcher, selector->data)) + if (!_gtk_css_matcher_has_class (matcher, GPOINTER_TO_UINT (selector->data))) return FALSE; return gtk_css_selector_match (gtk_css_selector_previous (selector), matcher); } +static void +gtk_css_selector_class_tree_match (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher, + GHashTable *res) +{ + if (!_gtk_css_matcher_has_class (matcher, GPOINTER_TO_UINT (tree->selector.data))) + return; + + gtk_css_selector_tree_found_match (tree, res); + + gtk_css_selector_tree_match_previous (tree, matcher, res); +} + static GtkCssChange -gtk_css_selector_class_get_change (const GtkCssSelector *selector) +gtk_css_selector_class_tree_get_change (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher) +{ + GtkCssChange change, previous_change; + + if (!_gtk_css_matcher_has_class (matcher, GPOINTER_TO_UINT (tree->selector.data))) + return 0; + + change = 0; + + if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET) + change |= GTK_CSS_CHANGE_CLASS | GTK_CSS_CHANGE_GOT_MATCH; + + previous_change = gtk_css_selector_tree_get_previous_change (tree, matcher); + + if (previous_change != 0) + change |= previous_change | GTK_CSS_CHANGE_CLASS | GTK_CSS_CHANGE_GOT_MATCH; + + return change; +} + +static GtkCssChange +gtk_css_selector_class_get_change (const GtkCssSelector *selector, GtkCssChange previous_change) +{ + return previous_change | GTK_CSS_CHANGE_CLASS; +} + +static int +gtk_css_selector_class_compare_one (const GtkCssSelector *a, + const GtkCssSelector *b) { - return gtk_css_selector_get_change (gtk_css_selector_previous (selector)) | GTK_CSS_CHANGE_CLASS; + return strcmp (g_quark_to_string (GPOINTER_TO_UINT (a->data)), + g_quark_to_string (GPOINTER_TO_UINT (b->data))); } static const GtkCssSelectorClass GTK_CSS_SELECTOR_CLASS = { "class", gtk_css_selector_class_print, gtk_css_selector_class_match, + gtk_css_selector_class_tree_match, gtk_css_selector_class_get_change, - FALSE, TRUE, FALSE + gtk_css_selector_class_tree_get_change, + gtk_css_selector_class_compare_one, + FALSE, TRUE, FALSE, TRUE, FALSE }; /* ID */ @@ -398,18 +1034,64 @@ gtk_css_selector_id_match (const GtkCssSelector *selector, return gtk_css_selector_match (gtk_css_selector_previous (selector), matcher); } +static void +gtk_css_selector_id_tree_match (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher, + GHashTable *res) +{ + if (!_gtk_css_matcher_has_id (matcher, tree->selector.data)) + return; + + gtk_css_selector_tree_found_match (tree, res); + + gtk_css_selector_tree_match_previous (tree, matcher, res); +} + +static GtkCssChange +gtk_css_selector_id_tree_get_change (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher) +{ + GtkCssChange change, previous_change; + + if (!_gtk_css_matcher_has_id (matcher, tree->selector.data)) + return 0; + + change = 0; + + if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET) + change |= GTK_CSS_CHANGE_ID | GTK_CSS_CHANGE_GOT_MATCH; + + previous_change = gtk_css_selector_tree_get_previous_change (tree, matcher); + + if (previous_change != 0) + change |= previous_change | GTK_CSS_CHANGE_ID | GTK_CSS_CHANGE_GOT_MATCH; + + return change; +} + static GtkCssChange -gtk_css_selector_id_get_change (const GtkCssSelector *selector) +gtk_css_selector_id_get_change (const GtkCssSelector *selector, GtkCssChange previous_change) +{ + return previous_change | GTK_CSS_CHANGE_ID; +} + + +static int +gtk_css_selector_id_compare_one (const GtkCssSelector *a, + const GtkCssSelector *b) { - return gtk_css_selector_get_change (gtk_css_selector_previous (selector)) | GTK_CSS_CHANGE_ID; + return strcmp (a->data, b->data); } static const GtkCssSelectorClass GTK_CSS_SELECTOR_ID = { "id", gtk_css_selector_id_print, gtk_css_selector_id_match, + gtk_css_selector_id_tree_match, gtk_css_selector_id_get_change, - TRUE, FALSE, FALSE + gtk_css_selector_id_tree_get_change, + gtk_css_selector_id_compare_one, + TRUE, FALSE, FALSE, TRUE, FALSE }; /* PSEUDOCLASS FOR STATE */ @@ -425,7 +1107,9 @@ gtk_css_selector_pseudoclass_state_print (const GtkCssSelector *selector, "insensitive", "inconsistent", "focus", - "backdrop" + "backdrop", + "dir(ltr)", + "dir(rtl)" }; guint i, state; @@ -456,49 +1140,243 @@ gtk_css_selector_pseudoclass_state_match (const GtkCssSelector *selector, return gtk_css_selector_match (gtk_css_selector_previous (selector), matcher); } +static void +gtk_css_selector_pseudoclass_state_tree_match (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher, + GHashTable *res) +{ + GtkStateFlags state = GPOINTER_TO_UINT (tree->selector.data); + + if ((_gtk_css_matcher_get_state (matcher) & state) != state) + return; + + gtk_css_selector_tree_found_match (tree, res); + + gtk_css_selector_tree_match_previous (tree, matcher, res); +} + static GtkCssChange -gtk_css_selector_pseudoclass_state_get_change (const GtkCssSelector *selector) +gtk_css_selector_pseudoclass_state_tree_get_change (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher) +{ + GtkStateFlags state = GPOINTER_TO_UINT (tree->selector.data); + GtkCssChange change, previous_change; + + if ((_gtk_css_matcher_get_state (matcher) & state) != state) + return 0; + + change = 0; + + if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET) + change |= GTK_CSS_CHANGE_STATE | GTK_CSS_CHANGE_GOT_MATCH; + + previous_change = gtk_css_selector_tree_get_previous_change (tree, matcher); + + if (previous_change != 0) + change |= previous_change | GTK_CSS_CHANGE_STATE | GTK_CSS_CHANGE_GOT_MATCH; + + return change; +} + +static GtkCssChange +gtk_css_selector_pseudoclass_state_get_change (const GtkCssSelector *selector, GtkCssChange previous_change) +{ + return previous_change | GTK_CSS_CHANGE_STATE; +} + +static int +gtk_css_selector_pseudoclass_state_compare_one (const GtkCssSelector *a, + const GtkCssSelector *b) { - return gtk_css_selector_get_change (gtk_css_selector_previous (selector)) | GTK_CSS_CHANGE_STATE; + return GPOINTER_TO_UINT (a->data) - GPOINTER_TO_UINT (b->data); } static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_STATE = { "pseudoclass-state", gtk_css_selector_pseudoclass_state_print, gtk_css_selector_pseudoclass_state_match, + gtk_css_selector_pseudoclass_state_tree_match, gtk_css_selector_pseudoclass_state_get_change, - FALSE, TRUE, FALSE + gtk_css_selector_pseudoclass_state_tree_get_change, + gtk_css_selector_pseudoclass_state_compare_one, + FALSE, TRUE, FALSE, TRUE, FALSE }; /* PSEUDOCLASS FOR POSITION */ +typedef enum { + POSITION_FORWARD, + POSITION_BACKWARD, + POSITION_ONLY, + POSITION_SORTED +} PositionType; +#define POSITION_TYPE_BITS 2 +#define POSITION_NUMBER_BITS ((sizeof (gpointer) * 8 - POSITION_TYPE_BITS) / 2) + +static gconstpointer +encode_position (PositionType type, + int a, + int b) +{ + union { + gconstpointer p; + struct { + gssize type :POSITION_TYPE_BITS; + gssize a :POSITION_NUMBER_BITS; + gssize b :POSITION_NUMBER_BITS; + } data; + } result; + G_STATIC_ASSERT (sizeof (gconstpointer) == sizeof (result)); + + g_assert (type < (1 << POSITION_TYPE_BITS)); + + result.data.type = type; + result.data.a = a; + result.data.b = b; + + return result.p; +} + +static void +decode_position (const GtkCssSelector *selector, + PositionType *type, + int *a, + int *b) +{ + union { + gconstpointer p; + struct { + gssize type :POSITION_TYPE_BITS; + gssize a :POSITION_NUMBER_BITS; + gssize b :POSITION_NUMBER_BITS; + } data; + } result; + G_STATIC_ASSERT (sizeof (gconstpointer) == sizeof (result)); + + result.p = selector->data; + + *type = result.data.type & ((1 << POSITION_TYPE_BITS) - 1); + *a = result.data.a; + *b = result.data.b; +} + static void gtk_css_selector_pseudoclass_position_print (const GtkCssSelector *selector, GString *string) { - static const char * flag_names[] = { - "nth-child(even)", - "nth-child(odd)", - "first-child", - "last-child", - "only-child", - "sorted" - }; - guint i, state; + PositionType type; + int a, b; - state = GPOINTER_TO_UINT (selector->data); - g_string_append_c (string, ':'); - - for (i = 0; i < G_N_ELEMENTS (flag_names); i++) + decode_position (selector, &type, &a, &b); + switch (type) { - if (state == (1 << i)) + case POSITION_FORWARD: + if (a == 0) { - g_string_append (string, flag_names[i]); - return; + if (b == 1) + g_string_append (string, ":first-child"); + else + g_string_append_printf (string, ":nth-child(%d)", b); + } + else if (a == 2 && b == 0) + g_string_append (string, ":nth-child(even)"); + else if (a == 2 && b == 1) + g_string_append (string, ":nth-child(odd)"); + else + { + g_string_append (string, ":nth-child("); + if (a == 1) + g_string_append (string, "n"); + else if (a == -1) + g_string_append (string, "-n"); + else + g_string_append_printf (string, "%dn", a); + if (b > 0) + g_string_append_printf (string, "+%d)", b); + else if (b < 0) + g_string_append_printf (string, "%d)", b); + else + g_string_append (string, ")"); + } + break; + case POSITION_BACKWARD: + if (a == 0) + { + if (b == 1) + g_string_append (string, ":last-child"); + else + g_string_append_printf (string, ":nth-last-child(%d)", b); + } + else if (a == 2 && b == 0) + g_string_append (string, ":nth-last-child(even)"); + else if (a == 2 && b == 1) + g_string_append (string, ":nth-last-child(odd)"); + else + { + g_string_append (string, ":nth-last-child("); + if (a == 1) + g_string_append (string, "n"); + else if (a == -1) + g_string_append (string, "-n"); + else + g_string_append_printf (string, "%dn", a); + if (b > 0) + g_string_append_printf (string, "+%d)", b); + else if (b < 0) + g_string_append_printf (string, "%d)", 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; } +} - g_assert_not_reached (); +static gboolean +get_selector_flags_for_position_region_match (const GtkCssSelector *selector, GtkRegionFlags *selector_flags) +{ + PositionType type; + int a, b; + + decode_position (selector, &type, &a, &b); + switch (type) + { + case POSITION_FORWARD: + if (a == 0 && b == 1) + *selector_flags = GTK_REGION_FIRST; + else if (a == 2 && b == 0) + *selector_flags = GTK_REGION_EVEN; + else if (a == 2 && b == 1) + *selector_flags = GTK_REGION_ODD; + else + return FALSE; + break; + case POSITION_BACKWARD: + if (a == 0 && b == 1) + *selector_flags = GTK_REGION_LAST; + else + return FALSE; + break; + case POSITION_ONLY: + *selector_flags = GTK_REGION_ONLY; + break; + case POSITION_SORTED: + *selector_flags = GTK_REGION_SORTED; + break; + default: + g_assert_not_reached (); + break; + } + + return TRUE; } static gboolean @@ -507,84 +1385,219 @@ gtk_css_selector_pseudoclass_position_match_for_region (const GtkCssSelector *se { GtkRegionFlags selector_flags; const GtkCssSelector *previous; - - selector_flags = GPOINTER_TO_UINT (selector->data); + + if (!get_selector_flags_for_position_region_match (selector, &selector_flags)) + return FALSE; + selector = gtk_css_selector_previous (selector); if (!_gtk_css_matcher_has_region (matcher, selector->data, selector_flags)) return FALSE; - previous = gtk_css_selector_previous (selector); - if (previous && previous->class == >K_CSS_SELECTOR_DESCENDANT && - gtk_css_selector_match (gtk_css_selector_previous (previous), matcher)) - return TRUE; + previous = gtk_css_selector_previous (selector); + if (previous && previous->class == >K_CSS_SELECTOR_DESCENDANT && + gtk_css_selector_match (gtk_css_selector_previous (previous), matcher)) + return TRUE; + + return gtk_css_selector_match (previous, matcher); +} + +static gboolean +get_position_match (const GtkCssSelector *selector, + const GtkCssMatcher *matcher) +{ + PositionType type; + int a, b; + + decode_position (selector, &type, &a, &b); + switch (type) + { + case POSITION_FORWARD: + if (!_gtk_css_matcher_has_position (matcher, TRUE, a, b)) + return FALSE; + break; + case POSITION_BACKWARD: + if (!_gtk_css_matcher_has_position (matcher, FALSE, a, 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 gboolean +gtk_css_selector_pseudoclass_position_match (const GtkCssSelector *selector, + const GtkCssMatcher *matcher) +{ + const GtkCssSelector *previous; + + previous = gtk_css_selector_previous (selector); + if (previous && previous->class == >K_CSS_SELECTOR_REGION) + return gtk_css_selector_pseudoclass_position_match_for_region (selector, matcher); + + if (!get_position_match (selector, matcher)) + return FALSE; + + return gtk_css_selector_match (previous, matcher); +} + +static void +gtk_css_selector_pseudoclass_position_tree_match_for_region (const GtkCssSelectorTree *tree, + const GtkCssSelectorTree *prev, + const GtkCssMatcher *matcher, + GHashTable *res) +{ + const GtkCssSelectorTree *prev2; + GtkRegionFlags selector_flags; + + if (!get_selector_flags_for_position_region_match (&tree->selector, &selector_flags)) + return; + + if (!_gtk_css_matcher_has_region (matcher, prev->selector.data, selector_flags)) + return; + + gtk_css_selector_tree_found_match (prev, res); + + for (prev2 = gtk_css_selector_tree_get_previous (prev); + prev2 != NULL; + prev2 = gtk_css_selector_tree_get_sibling (prev2)) + { + if (prev2->selector.class == >K_CSS_SELECTOR_DESCENDANT) + gtk_css_selector_tree_match (gtk_css_selector_tree_get_previous (prev2), matcher, res); + gtk_css_selector_tree_match (prev2, matcher, res); + } +} + +static void +gtk_css_selector_pseudoclass_position_tree_match (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher, + GHashTable *res) +{ + const GtkCssSelectorTree *prev; + + for (prev = gtk_css_selector_tree_get_previous (tree); + prev != NULL; + prev = gtk_css_selector_tree_get_sibling (prev)) + { + if (prev->selector.class == >K_CSS_SELECTOR_REGION) + gtk_css_selector_pseudoclass_position_tree_match_for_region (tree, prev, matcher, res); + } + + if (!get_position_match (&tree->selector, matcher)) + return; + + 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)) + { + if (prev->selector.class != >K_CSS_SELECTOR_REGION) + gtk_css_selector_tree_match (prev, matcher, res); + } +} + +static GtkCssChange +gtk_css_selector_pseudoclass_position_tree_get_change_for_region (const GtkCssSelectorTree *tree, + const GtkCssSelectorTree *prev, + const GtkCssMatcher *matcher) +{ + const GtkCssSelectorTree *prev2; + GtkRegionFlags selector_flags; + GtkCssChange change, previous_change; + + if (!get_selector_flags_for_position_region_match (&tree->selector, &selector_flags)) + return 0; + + if (!_gtk_css_matcher_has_region (matcher, prev->selector.data, selector_flags)) + return 0; + + change = 0; + if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET) + change |= GTK_CSS_CHANGE_POSITION | GTK_CSS_CHANGE_GOT_MATCH; + + previous_change = 0; + for (prev2 = gtk_css_selector_tree_get_previous (prev); + prev2 != NULL; + prev2 = gtk_css_selector_tree_get_sibling (prev2)) + { + if (prev2->selector.class == >K_CSS_SELECTOR_DESCENDANT) + previous_change |= gtk_css_selector_tree_get_change (gtk_css_selector_tree_get_previous (prev2), matcher); + previous_change |= gtk_css_selector_tree_get_change (prev2, matcher); + } + + if (previous_change != 0) + change |= previous_change | GTK_CSS_CHANGE_POSITION | GTK_CSS_CHANGE_GOT_MATCH; - return gtk_css_selector_match (previous, matcher); + return change; } -static gboolean -gtk_css_selector_pseudoclass_position_match (const GtkCssSelector *selector, - const GtkCssMatcher *matcher) +static GtkCssChange +gtk_css_selector_pseudoclass_position_tree_get_change (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher) { - GtkRegionFlags region; - guint sibling, n_siblings; - const GtkCssSelector *previous; + const GtkCssSelectorTree *prev; + GtkCssChange change, previous_change; - previous = gtk_css_selector_previous (selector); - if (previous && previous->class == >K_CSS_SELECTOR_REGION) - return gtk_css_selector_pseudoclass_position_match_for_region (selector, matcher); + change = 0; - n_siblings = _gtk_css_matcher_get_n_siblings (matcher); - if (n_siblings == 0) - return FALSE; - sibling = _gtk_css_matcher_get_sibling_index (matcher); + for (prev = gtk_css_selector_tree_get_previous (tree); + prev != NULL; + prev = gtk_css_selector_tree_get_sibling (prev)) + { + if (prev->selector.class == >K_CSS_SELECTOR_REGION) + change |= gtk_css_selector_pseudoclass_position_tree_get_change_for_region (tree, prev, matcher); + } + + if (!get_position_match (&tree->selector, matcher)) + return change; - region = GPOINTER_TO_UINT (selector->data); + if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET) + change |= GTK_CSS_CHANGE_POSITION | GTK_CSS_CHANGE_GOT_MATCH; - switch (region) + previous_change = 0; + for (prev = gtk_css_selector_tree_get_previous (tree); prev != NULL; prev = gtk_css_selector_tree_get_sibling (prev)) { - case GTK_REGION_EVEN: - if (!(sibling % 2)) - return FALSE; - break; - case GTK_REGION_ODD: - if (sibling % 2) - return FALSE; - break; - case GTK_REGION_FIRST: - if (sibling != 0) - return FALSE; - break; - case GTK_REGION_LAST: - if (sibling + 1 != n_siblings) - return FALSE; - break; - case GTK_REGION_ONLY: - if (n_siblings != 1) - return FALSE; - break; - case GTK_REGION_SORTED: - return FALSE; - default: - g_assert_not_reached (); - return FALSE; + if (prev->selector.class != >K_CSS_SELECTOR_REGION) + previous_change |= gtk_css_selector_tree_get_change (prev, matcher); } - return gtk_css_selector_match (previous, matcher); + if (previous_change != 0) + change |= previous_change | GTK_CSS_CHANGE_POSITION | GTK_CSS_CHANGE_GOT_MATCH; + + return change; } static GtkCssChange -gtk_css_selector_pseudoclass_position_get_change (const GtkCssSelector *selector) +gtk_css_selector_pseudoclass_position_get_change (const GtkCssSelector *selector, GtkCssChange previous_change) +{ + return previous_change | GTK_CSS_CHANGE_POSITION; +} + +static int +gtk_css_selector_pseudoclass_position_compare_one (const GtkCssSelector *a, + const GtkCssSelector *b) { - return gtk_css_selector_get_change (gtk_css_selector_previous (selector)) | GTK_CSS_CHANGE_POSITION; + return GPOINTER_TO_UINT (a->data) - GPOINTER_TO_UINT (b->data); } static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION = { "pseudoclass-position", gtk_css_selector_pseudoclass_position_print, gtk_css_selector_pseudoclass_position_match, + gtk_css_selector_pseudoclass_position_tree_match, gtk_css_selector_pseudoclass_position_get_change, - FALSE, TRUE, FALSE + gtk_css_selector_pseudoclass_position_tree_get_change, + gtk_css_selector_pseudoclass_position_compare_one, + FALSE, TRUE, FALSE, TRUE, TRUE }; /* API */ @@ -640,7 +1653,7 @@ parse_selector_class (GtkCssParser *parser, GtkCssSelector *selector) selector = gtk_css_selector_new (>K_CSS_SELECTOR_CLASS, selector, - g_intern_string (name)); + GUINT_TO_POINTER (g_quark_from_string (name))); g_free (name); @@ -672,125 +1685,178 @@ parse_selector_id (GtkCssParser *parser, GtkCssSelector *selector) } static GtkCssSelector * -parse_selector_pseudo_class (GtkCssParser *parser, - GtkCssSelector *selector) +parse_selector_pseudo_class_nth_child (GtkCssParser *parser, + GtkCssSelector *selector, + PositionType type) { - struct { - const char *name; - GtkRegionFlags region_flag; - GtkStateFlags state_flag; - } pseudo_classes[] = { - { "first-child", GTK_REGION_FIRST, 0 }, - { "last-child", GTK_REGION_LAST, 0 }, - { "only-child", GTK_REGION_ONLY, 0 }, - { "sorted", GTK_REGION_SORTED, 0 }, - { "active", 0, GTK_STATE_FLAG_ACTIVE }, - { "prelight", 0, GTK_STATE_FLAG_PRELIGHT }, - { "hover", 0, GTK_STATE_FLAG_PRELIGHT }, - { "selected", 0, GTK_STATE_FLAG_SELECTED }, - { "insensitive", 0, GTK_STATE_FLAG_INSENSITIVE }, - { "inconsistent", 0, GTK_STATE_FLAG_INCONSISTENT }, - { "focused", 0, GTK_STATE_FLAG_FOCUSED }, - { "focus", 0, GTK_STATE_FLAG_FOCUSED }, - { "backdrop", 0, GTK_STATE_FLAG_BACKDROP }, - { NULL, } - }, nth_child_classes[] = { - { "first", GTK_REGION_FIRST, 0 }, - { "last", GTK_REGION_LAST, 0 }, - { "even", GTK_REGION_EVEN, 0 }, - { "odd", GTK_REGION_ODD, 0 }, - { NULL, } - }, *classes; - guint i; - char *name; - GError *error; + int a, b; - name = _gtk_css_parser_try_ident (parser, FALSE); - if (name == NULL) + if (!_gtk_css_parser_try (parser, "(", TRUE)) { - _gtk_css_parser_error (parser, "Missing name of pseudo-class"); + _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, "(", TRUE)) + if (_gtk_css_parser_try (parser, "even", TRUE)) + { + a = 2; + b = 0; + } + else if (_gtk_css_parser_try (parser, "odd", TRUE)) { - char *function = name; + 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; - name = _gtk_css_parser_try_ident (parser, TRUE); - if (!_gtk_css_parser_try (parser, ")", FALSE)) + 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")) { - _gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class"); + a = multiplier; + } + else + { + _gtk_css_parser_error (parser, "Expected an integer"); if (selector) _gtk_css_selector_free (selector); return NULL; } - if (g_ascii_strcasecmp (function, "nth-child") != 0) + if (_gtk_css_parser_try (parser, "n", TRUE)) { - error = g_error_new (GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE, - "Unknown pseudo-class '%s(%s)'", function, name ? name : ""); - _gtk_css_parser_take_error (parser, error); - g_free (function); - g_free (name); - if (selector) - _gtk_css_selector_free (selector); - return NULL; + 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; } - - g_free (function); - - if (name == NULL) + else { - error = g_error_new (GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE, - "Unknown pseudo-class 'nth-child(%s)'", name); - _gtk_css_parser_take_error (parser, error); - if (selector) - _gtk_css_selector_free (selector); - return NULL; + b = a; + a = 0; } + } - classes = nth_child_classes; + 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; } - else - classes = pseudo_classes; - for (i = 0; classes[i].name != NULL; i++) + selector = gtk_css_selector_new (>K_CSS_SELECTOR_PSEUDOCLASS_POSITION, + selector, + encode_position (type, a, b)); + + return selector; +} + +static GtkCssSelector * +parse_selector_pseudo_class (GtkCssParser *parser, + GtkCssSelector *selector) +{ + 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, } + }; + guint i; + + if (_gtk_css_parser_try (parser, "nth-child", FALSE)) + return parse_selector_pseudo_class_nth_child (parser, selector, POSITION_FORWARD); + else if (_gtk_css_parser_try (parser, "nth-last-child", FALSE)) + return parse_selector_pseudo_class_nth_child (parser, selector, POSITION_BACKWARD); + + for (i = 0; i < G_N_ELEMENTS (pseudo_classes); i++) { - if (g_ascii_strcasecmp (name, classes[i].name) == 0) + if (_gtk_css_parser_try (parser, pseudo_classes[i].name, FALSE)) { - g_free (name); - - if (classes[i].region_flag) - selector = gtk_css_selector_new (>K_CSS_SELECTOR_PSEUDOCLASS_POSITION, + if (pseudo_classes[i].state_flag) + selector = gtk_css_selector_new (>K_CSS_SELECTOR_PSEUDOCLASS_STATE, selector, - GUINT_TO_POINTER (classes[i].region_flag)); + GUINT_TO_POINTER (pseudo_classes[i].state_flag)); else - selector = gtk_css_selector_new (>K_CSS_SELECTOR_PSEUDOCLASS_STATE, + selector = gtk_css_selector_new (>K_CSS_SELECTOR_PSEUDOCLASS_POSITION, selector, - GUINT_TO_POINTER (classes[i].state_flag)); - + encode_position (pseudo_classes[i].position_type, + pseudo_classes[i].position_a, + pseudo_classes[i].position_b)); return selector; } } - - if (classes == nth_child_classes) - error = g_error_new (GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE, - "Unknown pseudo-class 'nth-child(%s)'", name); - else - error = g_error_new (GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE, - "Unknown pseudo-class '%s'", name); - - _gtk_css_parser_take_error (parser, error); - g_free (name); + + _gtk_css_parser_error (parser, "Missing name of pseudo-class"); if (selector) _gtk_css_selector_free (selector); - return NULL; } @@ -803,11 +1869,14 @@ try_parse_name (GtkCssParser *parser, name = _gtk_css_parser_try_ident (parser, FALSE); if (name) { - selector = gtk_css_selector_new (_gtk_style_context_check_region_name (name) - ? >K_CSS_SELECTOR_REGION - : >K_CSS_SELECTOR_NAME, - selector, - g_intern_string (name)); + if (_gtk_style_context_check_region_name (name)) + selector = gtk_css_selector_new (>K_CSS_SELECTOR_REGION, + selector, + g_intern_string (name)); + else + selector = gtk_css_selector_new (>K_CSS_SELECTOR_NAME, + selector, + get_type_reference (name)); g_free (name); } else if (_gtk_css_parser_try (parser, "*", FALSE)) @@ -908,12 +1977,21 @@ _gtk_css_selector_to_string (const GtkCssSelector *selector) return g_string_free (string, FALSE); } + GtkCssChange -_gtk_css_selector_get_change (const GtkCssSelector *selector) +_gtk_css_selector_tree_match_get_change (const GtkCssSelectorTree *tree) { - g_return_val_if_fail (selector != NULL, 0); + GtkCssChange change = 0; + + update_type_references (); + + while (tree) + { + change = tree->selector.class->get_change (&tree->selector, change); + tree = gtk_css_selector_tree_get_parent (tree); + } - return gtk_css_selector_get_change (selector); + return change; } /** @@ -938,6 +2016,8 @@ _gtk_css_selector_matches (const GtkCssSelector *selector, g_return_val_if_fail (selector != NULL, FALSE); g_return_val_if_fail (matcher != NULL, FALSE); + update_type_references (); + return gtk_css_selector_match (selector, matcher); } @@ -984,16 +2064,431 @@ _gtk_css_selector_compare (const GtkCssSelector *a, return a_elements - b_elements; } -GtkStateFlags -_gtk_css_selector_get_state_flags (const GtkCssSelector *selector) + +/******************** 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 || selector->class->must_keep_order) + { + 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->class->must_keep_order; + 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 || selector->class->must_keep_order) + return gtk_css_selector_equal (selector, initial); + + for (; + selector && selector->class->is_simple && !selector->class->must_keep_order; + 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 (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_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; +} + +GtkCssChange +_gtk_css_selector_tree_get_change_all (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher) +{ + GtkCssChange change; + + change = 0; + + 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 (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 (gtk_css_selector_tree_get_previous (tree)) + { + 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 (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) { - GtkStateFlags state = 0; + 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); - g_return_val_if_fail (selector != NULL, 0); + if (tree->sibling_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET) + tree->sibling_offset -= ((guint8 *)tree - data); - for (; selector && selector->class == >K_CSS_SELECTOR_NAME; selector = gtk_css_selector_previous (selector)) - state |= GPOINTER_TO_UINT (selector->data); + if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET) + tree->matches_offset -= ((guint8 *)tree - data); - return state; + 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; +}