X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkcssselector.c;h=216090faaddccaeba251026712d5066ddac2c63e;hb=b2340254109d8599244c875fe7bc5e234f64da08;hp=5851af68fc6d3505d3b71cf20e5215cb60568ba2;hpb=e9adaabc5e28cd032b80457953ae1f12f5776c73;p=~andy%2Fgtk diff --git a/gtk/gtkcssselector.c b/gtk/gtkcssselector.c index 5851af68f..216090faa 100644 --- a/gtk/gtkcssselector.c +++ b/gtk/gtkcssselector.c @@ -12,49 +12,1930 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * License along with this library. If not, see . */ #include "config.h" #include "gtkcssselectorprivate.h" +#include +#include + +#include "gtkcssprovider.h" #include "gtkstylecontextprivate.h" -struct _GtkCssSelector +/* 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 { + const char *name; + + void (* print) (const GtkCssSelector *selector, + GString *string); + gboolean (* match) (const GtkCssSelector *selector, + const GtkCssMatcher *matcher); + 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 +{ + const GtkCssSelectorClass *class; /* type of check this selector does */ + gconstpointer data; /* data for matching: + - interned string for CLASS, NAME and ID + - 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) +{ + if (selector == NULL) + return TRUE; + + return selector->class->match (selector, matcher); +} + +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); +} + +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 +gtk_css_selector_descendant_print (const GtkCssSelector *selector, + GString *string) +{ + g_string_append_c (string, ' '); +} + +static gboolean +gtk_css_selector_descendant_match (const GtkCssSelector *selector, + const GtkCssMatcher *matcher) +{ + GtkCssMatcher ancestor; + + while (_gtk_css_matcher_get_parent (&ancestor, matcher)) + { + matcher = &ancestor; + + if (gtk_css_selector_match (gtk_css_selector_previous (selector), matcher)) + return TRUE; + } + + 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, 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_match, + gtk_css_selector_descendant_tree_match, + gtk_css_selector_descendant_get_change, + gtk_css_selector_descendant_tree_get_change, + gtk_css_selector_descendant_compare_one, + FALSE, FALSE, FALSE, FALSE, FALSE +}; + +/* CHILD */ + +static void +gtk_css_selector_child_print (const GtkCssSelector *selector, + GString *string) +{ + g_string_append (string, " > "); +} + +static gboolean +gtk_css_selector_child_match (const GtkCssSelector *selector, + const GtkCssMatcher *matcher) +{ + GtkCssMatcher parent; + + if (!_gtk_css_matcher_get_parent (&parent, matcher)) + return FALSE; + + 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, 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 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, + gtk_css_selector_child_tree_get_change, + gtk_css_selector_child_compare_one, + FALSE, FALSE, FALSE, FALSE, FALSE +}; + +/* SIBLING */ + +static void +gtk_css_selector_sibling_print (const GtkCssSelector *selector, + GString *string) +{ + g_string_append (string, " ~ "); +} + +static gboolean +gtk_css_selector_sibling_match (const GtkCssSelector *selector, + const GtkCssMatcher *matcher) +{ + GtkCssMatcher previous; + + while (_gtk_css_matcher_get_previous (&previous, matcher)) + { + matcher = &previous; + + if (gtk_css_selector_match (gtk_css_selector_previous (selector), matcher)) + return TRUE; + } + + 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, 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 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, + gtk_css_selector_sibling_tree_get_change, + gtk_css_selector_sibling_compare_one, + FALSE, FALSE, FALSE, FALSE, FALSE +}; + +/* ADJACENT */ + +static void +gtk_css_selector_adjacent_print (const GtkCssSelector *selector, + GString *string) +{ + g_string_append (string, " + "); +} + +static gboolean +gtk_css_selector_adjacent_match (const GtkCssSelector *selector, + const GtkCssMatcher *matcher) +{ + GtkCssMatcher previous; + + if (!_gtk_css_matcher_get_previous (&previous, matcher)) + return FALSE; + + 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, 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 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, + gtk_css_selector_adjacent_tree_get_change, + gtk_css_selector_adjacent_compare_one, + FALSE, FALSE, FALSE, FALSE, FALSE +}; + +/* ANY */ + +static void +gtk_css_selector_any_print (const GtkCssSelector *selector, + GString *string) +{ + g_string_append_c (string, '*'); +} + +static gboolean +gtk_css_selector_any_match (const GtkCssSelector *selector, + const GtkCssMatcher *matcher) +{ + const GtkCssSelector *previous = gtk_css_selector_previous (selector); + + if (previous && + previous->class == >K_CSS_SELECTOR_DESCENDANT && + _gtk_css_matcher_has_regions (matcher)) + { + if (gtk_css_selector_match (gtk_css_selector_previous (previous), matcher)) + return TRUE; + } + + 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, GtkCssChange previous_change) +{ + return previous_change; +} + +static int +gtk_css_selector_any_compare_one (const GtkCssSelector *a, + const GtkCssSelector *b) +{ + 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, + 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, ((TypeReference *)selector->data)->name); +} + +static gboolean +gtk_css_selector_name_match (const GtkCssSelector *selector, + const GtkCssMatcher *matcher) +{ + 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, GtkCssChange previous_change) +{ + 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, + gtk_css_selector_name_tree_get_change, + gtk_css_selector_name_compare_one, + FALSE, FALSE, TRUE, TRUE, FALSE +}; + +/* REGION */ + +static void +gtk_css_selector_region_print (const GtkCssSelector *selector, + GString *string) +{ + g_string_append (string, selector->data); +} + +static gboolean +gtk_css_selector_region_match (const GtkCssSelector *selector, + const GtkCssMatcher *matcher) +{ + const GtkCssSelector *previous; + + if (!_gtk_css_matcher_has_region (matcher, selector->data, 0)) + 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; + + 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, GtkCssChange previous_change) +{ + GtkCssChange change; + + 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, + gtk_css_selector_region_tree_get_change, + gtk_css_selector_region_compare_one, + FALSE, FALSE, TRUE, TRUE, TRUE +}; + +/* CLASS */ + +static void +gtk_css_selector_class_print (const GtkCssSelector *selector, + GString *string) +{ + g_string_append_c (string, '.'); + 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, 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_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 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, + gtk_css_selector_class_tree_get_change, + gtk_css_selector_class_compare_one, + FALSE, TRUE, FALSE, TRUE, FALSE +}; + +/* ID */ + +static void +gtk_css_selector_id_print (const GtkCssSelector *selector, + GString *string) +{ + g_string_append_c (string, '#'); + g_string_append (string, selector->data); +} + +static gboolean +gtk_css_selector_id_match (const GtkCssSelector *selector, + const GtkCssMatcher *matcher) +{ + if (!_gtk_css_matcher_has_id (matcher, selector->data)) + return FALSE; + + 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, 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 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, + gtk_css_selector_id_tree_get_change, + gtk_css_selector_id_compare_one, + TRUE, FALSE, FALSE, TRUE, FALSE +}; + +/* PSEUDOCLASS FOR STATE */ + +static void +gtk_css_selector_pseudoclass_state_print (const GtkCssSelector *selector, + GString *string) +{ + static const char * state_names[] = { + "active", + "hover", + "selected", + "insensitive", + "inconsistent", + "focus", + "backdrop", + "dir(ltr)", + "dir(rtl)" + }; + guint i, state; + + state = GPOINTER_TO_UINT (selector->data); + g_string_append_c (string, ':'); + + for (i = 0; i < G_N_ELEMENTS (state_names); i++) + { + if (state == (1 << i)) + { + g_string_append (string, state_names[i]); + return; + } + } + + g_assert_not_reached (); +} + +static gboolean +gtk_css_selector_pseudoclass_state_match (const GtkCssSelector *selector, + const GtkCssMatcher *matcher) +{ + GtkStateFlags state = GPOINTER_TO_UINT (selector->data); + + if ((_gtk_css_matcher_get_state (matcher) & state) != state) + return FALSE; + + 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_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 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, + 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) +{ + PositionType type; + int a, b; + + decode_position (selector, &type, &a, &b); + switch (type) + { + case POSITION_FORWARD: + if (a == 0) + { + 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; + } +} + +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 +gtk_css_selector_pseudoclass_position_match_for_region (const GtkCssSelector *selector, + const GtkCssMatcher *matcher) +{ + GtkRegionFlags selector_flags; + const GtkCssSelector *previous; + + 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; + + 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 change; +} + +static GtkCssChange +gtk_css_selector_pseudoclass_position_tree_get_change (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher) +{ + const GtkCssSelectorTree *prev; + GtkCssChange change, previous_change; + + 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_REGION) + change |= gtk_css_selector_pseudoclass_position_tree_get_change_for_region (tree, prev, matcher); + } + + if (!get_position_match (&tree->selector, matcher)) + return change; + + if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET) + change |= GTK_CSS_CHANGE_POSITION | 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_REGION) + previous_change |= gtk_css_selector_tree_get_change (prev, 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, 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 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, + gtk_css_selector_pseudoclass_position_tree_get_change, + gtk_css_selector_pseudoclass_position_compare_one, + FALSE, TRUE, FALSE, TRUE, TRUE +}; + +/* 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, + gconstpointer data) +{ + 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)); + + selector->class = class; + selector->data = data; + + return selector; +} + +static GtkCssSelector * +parse_selector_class (GtkCssParser *parser, GtkCssSelector *selector) +{ + 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 (>K_CSS_SELECTOR_CLASS, + selector, + GUINT_TO_POINTER (g_quark_from_string (name))); + + g_free (name); + + return selector; +} + +static GtkCssSelector * +parse_selector_id (GtkCssParser *parser, GtkCssSelector *selector) +{ + 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 (>K_CSS_SELECTOR_ID, + selector, + g_intern_string (name)); + + g_free (name); + + return selector; +} + +static GtkCssSelector * +parse_selector_pseudo_class_nth_child (GtkCssParser *parser, + GtkCssSelector *selector, + PositionType type) +{ + 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 (>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 (_gtk_css_parser_try (parser, pseudo_classes[i].name, FALSE)) + { + if (pseudo_classes[i].state_flag) + selector = gtk_css_selector_new (>K_CSS_SELECTOR_PSEUDOCLASS_STATE, + selector, + GUINT_TO_POINTER (pseudo_classes[i].state_flag)); + else + selector = gtk_css_selector_new (>K_CSS_SELECTOR_PSEUDOCLASS_POSITION, + selector, + encode_position (pseudo_classes[i].position_type, + pseudo_classes[i].position_a, + 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 * +try_parse_name (GtkCssParser *parser, + GtkCssSelector *selector) +{ + 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 (>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)) + selector = gtk_css_selector_new (>K_CSS_SELECTOR_ANY, selector, NULL); + + return selector; +} + +static GtkCssSelector * +parse_simple_selector (GtkCssParser *parser, + GtkCssSelector *selector) { - GtkCssSelector * previous; /* link to next element in selector or NULL if last */ - GtkCssCombinator combine; /* how to combine with the previous element */ - const char * name; /* quarked name of element we match or NULL if any */ - GType type; /* cache for type belonging to name - G_TYPE_INVALID if uncached */ - GQuark * ids; /* 0-terminated list of required ids or NULL if none */ - GQuark * classes; /* 0-terminated list of required classes or NULL if none */ - GtkRegionFlags pseudo_classes; /* required pseudo classes */ - GtkStateFlags state; /* required state flags (currently not checked when matching) */ -}; + guint size = gtk_css_selector_size (selector); + + selector = try_parse_name (parser, selector); + + do { + if (_gtk_css_parser_try (parser, "#", FALSE)) + selector = parse_selector_id (parser, selector); + else if (_gtk_css_parser_try (parser, ".", FALSE)) + selector = parse_selector_class (parser, selector); + else if (_gtk_css_parser_try (parser, ":", FALSE)) + selector = parse_selector_pseudo_class (parser, selector); + else if (gtk_css_selector_size (selector) == size) + { + _gtk_css_parser_error (parser, "Expected a valid selector"); + if (selector) + _gtk_css_selector_free (selector); + return NULL; + } + else + break; + } + while (selector && !_gtk_css_parser_is_eof (parser)); + + _gtk_css_parser_skip_whitespace (parser); + + return selector; +} GtkCssSelector * -_gtk_css_selector_new (GtkCssSelector *previous, - GtkCssCombinator combine, - const char * name, - GQuark * ids, - GQuark * classes, - GtkRegionFlags pseudo_classes, - GtkStateFlags state) -{ - GtkCssSelector *selector; - - selector = g_slice_new0 (GtkCssSelector); - selector->previous = previous; - selector->combine = combine; - selector->name = name ? g_quark_to_string (g_quark_from_string (name)) : NULL; - selector->type = !name || _gtk_style_context_check_region_name (name) ? G_TYPE_NONE : G_TYPE_INVALID; - selector->ids = ids; - selector->classes = classes; - selector->pseudo_classes = pseudo_classes; - selector->state = state; +_gtk_css_selector_parse (GtkCssParser *parser) +{ + GtkCssSelector *selector = NULL; + + while ((selector = parse_simple_selector (parser, selector)) && + !_gtk_css_parser_is_eof (parser) && + !_gtk_css_parser_begins_with (parser, ',') && + !_gtk_css_parser_begins_with (parser, '{')) + { + if (_gtk_css_parser_try (parser, "+", TRUE)) + selector = gtk_css_selector_new (>K_CSS_SELECTOR_ADJACENT, selector, NULL); + else if (_gtk_css_parser_try (parser, "~", TRUE)) + selector = gtk_css_selector_new (>K_CSS_SELECTOR_SIBLING, selector, NULL); + else if (_gtk_css_parser_try (parser, ">", TRUE)) + selector = gtk_css_selector_new (>K_CSS_SELECTOR_CHILD, selector, NULL); + else + selector = gtk_css_selector_new (>K_CSS_SELECTOR_DESCENDANT, selector, NULL); + } return selector; } @@ -64,109 +1945,22 @@ _gtk_css_selector_free (GtkCssSelector *selector) { g_return_if_fail (selector != NULL); - if (selector->previous) - _gtk_css_selector_free (selector->previous); - - g_free (selector->ids); - g_free (selector->classes); - - g_slice_free (GtkCssSelector, selector); + g_free (selector); } void _gtk_css_selector_print (const GtkCssSelector *selector, GString * str) { - if (selector->previous) - { - _gtk_css_selector_print (selector->previous, str); - switch (selector->combine) - { - case GTK_CSS_COMBINE_DESCANDANT: - g_string_append (str, " "); - break; - case GTK_CSS_COMBINE_CHILD: - g_string_append (str, " > "); - break; - default: - g_assert_not_reached (); - } - } - - if (selector->name) - g_string_append (str, selector->name); - else if (selector->ids == NULL && - selector->classes == NULL && - selector->pseudo_classes == 0 && - selector->state == 0) - g_string_append (str, "*"); - - if (selector->ids) - { - GQuark *id; - - for (id = selector->ids; *id != 0; id++) - { - g_string_append_c (str, '#'); - g_string_append (str, g_quark_to_string (*id)); - } - } - - if (selector->classes) - { - GQuark *class; - - for (class = selector->classes; *class != 0; class++) - { - g_string_append_c (str, '.'); - g_string_append (str, g_quark_to_string (*class)); - } - } - - if (selector->pseudo_classes) - { - static const char * flag_names[] = { - "nth-child(even)", - "nth-child(odd)", - "first-child", - "last-child", - "only-child", - "sorted" - }; - guint i; + const GtkCssSelector *previous; - for (i = 0; i < G_N_ELEMENTS (flag_names); i++) - { - if (selector->pseudo_classes & (1 << i)) - { - g_string_append_c (str, ':'); - g_string_append (str, flag_names[i]); - } - } - } + g_return_if_fail (selector != NULL); - if (selector->state) - { - static const char * state_names[] = { - "active", - "hover", - "selected", - "insensitive", - "inconsistent", - "focus", - "backdrop" - }; - guint i; + previous = gtk_css_selector_previous (selector); + if (previous) + _gtk_css_selector_print (previous, str); - for (i = 0; i < G_N_ELEMENTS (state_names); i++) - { - if (selector->state & (1 << i)) - { - g_string_append_c (str, ':'); - g_string_append (str, state_names[i]); - } - } - } + selector->class->print (selector, str); } char * @@ -183,314 +1977,518 @@ _gtk_css_selector_to_string (const GtkCssSelector *selector) return g_string_free (string, FALSE); } -static GtkRegionFlags -compute_region_flags_for_index (const GtkWidgetPath *path, - guint id) + +GtkCssChange +_gtk_css_selector_tree_match_get_change (const GtkCssSelectorTree *tree) { - const GtkWidgetPath *siblings; - guint sibling_id, n_siblings; - GtkRegionFlags flags; - - siblings = gtk_widget_path_iter_get_siblings (path, id); - if (siblings == NULL) - return 0; + GtkCssChange change = 0; - sibling_id = gtk_widget_path_iter_get_sibling_index (path, id); - n_siblings = gtk_widget_path_length (siblings); + update_type_references (); - flags = (sibling_id % 2) ? GTK_REGION_EVEN : GTK_REGION_ODD; - if (sibling_id == 0) - flags |= GTK_REGION_FIRST; - if (sibling_id + 1 == n_siblings) - flags |= GTK_REGION_LAST; - if (n_siblings == 1) - flags |= GTK_REGION_ONLY; + while (tree) + { + change = tree->selector.class->get_change (&tree->selector, change); + tree = gtk_css_selector_tree_get_parent (tree); + } - return flags; + return change; } -static gboolean -gtk_css_selector_matches_type (const GtkCssSelector *selector, - const GtkWidgetPath *path, - guint id) +/** + * _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) { - if (selector->pseudo_classes) - { - GtkRegionFlags flags = compute_region_flags_for_index (path, id); - if ((selector->pseudo_classes & flags) != selector->pseudo_classes) - return FALSE; + 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); +} + +/* 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)) + { + const GtkCssSelectorClass *klass = selector->class; + + if (klass->increase_id_specificity) + (*ids)++; + if (klass->increase_class_specificity) + (*classes)++; + if (klass->increase_element_specificity) + (*elements)++; } +} - if (selector->name == NULL) - return TRUE; +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; - if (selector->type == G_TYPE_NONE) - return FALSE; + _gtk_css_selector_get_specificity (a, &a_ids, &a_classes, &a_elements); + _gtk_css_selector_get_specificity (b, &b_ids, &b_classes, &b_elements); - /* ugh, assigning to a const variable */ - if (selector->type == G_TYPE_INVALID) - ((GtkCssSelector *) selector)->type = g_type_from_name (selector->name); + compare = a_ids - b_ids; + if (compare) + return compare; - if (selector->type == G_TYPE_INVALID) - return FALSE; + compare = a_classes - b_classes; + if (compare) + return compare; - return g_type_is_a (gtk_widget_path_iter_get_object_type (path, id), selector->type); + return a_elements - b_elements; } -static gboolean -gtk_css_selector_matches_region (const GtkCssSelector *selector, - const GtkWidgetPath *path, - guint id, - const char * region) -{ - GtkRegionFlags flags; - if (selector->name == NULL) - return TRUE; - - if (selector->name != region) - return FALSE; +/******************** 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); +} - if (!gtk_widget_path_iter_has_region (path, id, region, &flags)) +static void +gtk_css_selectors_count_initial (const GtkCssSelector *selector, GHashTable *hash) +{ + if (!selector->class->is_simple || selector->class->must_keep_order) { - /* This function must be called with existing regions */ - g_assert_not_reached (); + guint count = GPOINTER_TO_INT (g_hash_table_lookup (hash, selector)); + g_hash_table_replace (hash, (gpointer)selector, GUINT_TO_POINTER (count + 1)); + return; } - return (selector->pseudo_classes & flags) == selector->pseudo_classes; + 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_selector_matches_rest (const GtkCssSelector *selector, - const GtkWidgetPath *path, - guint id) +gtk_css_selectors_has_initial_selector (const GtkCssSelector *selector, const GtkCssSelector *initial) { - if (selector->ids) + 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)) { - GQuark *name; - - for (name = selector->ids; *name; name++) - { - if (!gtk_widget_path_iter_has_qname (path, id, *name)) - return FALSE; - } + if (gtk_css_selector_equal (selector, initial)) + return TRUE; } - if (selector->classes) + 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)) { - GQuark *class; - - for (class = selector->classes; *class; class++) - { - if (!gtk_widget_path_iter_has_qclass (path, id, *class)) - return FALSE; - } + 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 TRUE; + return (GtkCssSelector *)gtk_css_selector_previous (selector); } -static gboolean -gtk_css_selector_matches_previous (const GtkCssSelector *selector, - const GtkWidgetPath *path, - guint id, - GSList *regions); +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_matches_from (const GtkCssSelector *selector, - const GtkWidgetPath *path, - guint id, - GSList *regions) +GPtrArray * +_gtk_css_selector_tree_match_all (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher) { - GSList *l; + GHashTable *res; + GPtrArray *array; + GHashTableIter iter; + gpointer key; - if (!gtk_css_selector_matches_rest (selector, path, id)) - return FALSE; + update_type_references (); - for (l = regions; l; l = l->next) - { - const char *region = l->data; + res = g_hash_table_new (g_direct_hash, g_direct_equal); - if (gtk_css_selector_matches_region (selector, path, id, region)) - { - GSList *remaining; - gboolean match; - - remaining = g_slist_copy (regions); - remaining = g_slist_remove (remaining, region); - match = gtk_css_selector_matches_previous (selector, - path, - id, - remaining); - g_slist_free (remaining); - if (match) - return TRUE; - } - } + for (; tree != NULL; + tree = gtk_css_selector_tree_get_sibling (tree)) + gtk_css_selector_tree_match (tree, matcher, res); - if (gtk_css_selector_matches_type (selector, path, id)) - { - GSList *regions; - gboolean match; - - if (id <= 0) - return selector->previous == NULL; + array = g_ptr_array_sized_new (g_hash_table_size (res)); - regions = gtk_widget_path_iter_list_regions (path, id - 1); - match = gtk_css_selector_matches_previous (selector, - path, - id - 1, - regions); - g_slist_free (regions); - return match; - } + g_hash_table_iter_init (&iter, res); + while (g_hash_table_iter_next (&iter, &key, NULL)) + g_ptr_array_add (array, key); - return FALSE; + g_hash_table_destroy (res); + + qsort (array->pdata, array->len, sizeof (gpointer), direct_ptr_compare); + + return array; } -static gboolean -gtk_css_selector_matches_previous (const GtkCssSelector *selector, - const GtkWidgetPath *path, - guint id, - GSList *regions) +GtkCssChange +_gtk_css_selector_tree_get_change_all (const GtkCssSelectorTree *tree, + const GtkCssMatcher *matcher) { - if (!selector->previous) - return TRUE; + GtkCssChange change; - if (gtk_css_selector_matches_from (selector->previous, - path, - id, - regions)) - return TRUE; + change = 0; - if (selector->combine == GTK_CSS_COMBINE_DESCANDANT) + 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) { - /* with this magic we run the loop while id >= 0 */ - while (id-- != 0) - { - GSList *list; - gboolean match; - - list = gtk_widget_path_iter_list_regions (path, id); - match = gtk_css_selector_matches_from (selector->previous, - path, - id, - list); - g_slist_free (list); - if (match) - return TRUE; - } + 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 - return FALSE; +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); } -/** - * _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 GtkWidgetPath *path, - GtkStateFlags state) +void +_gtk_css_selector_tree_free (GtkCssSelectorTree *tree) { - GSList *list; - gboolean match; - guint length; + if (tree == NULL) + return; - g_return_val_if_fail (selector != NULL, FALSE); - g_return_val_if_fail (path != NULL, FALSE); + g_free (tree); +} - if ((selector->state & state) != selector->state) - return FALSE; - length = gtk_widget_path_length (path); - if (length == 0) - return FALSE; +typedef struct { + gpointer match; + GtkCssSelector *current_selector; + GtkCssSelectorTree **selector_match; +} GtkCssSelectorRuleSetInfo; - list = gtk_widget_path_iter_list_regions (path, length - 1); - match = gtk_css_selector_matches_from (selector, - path, - length - 1, - list); - g_slist_free (list); - return match; +static GtkCssSelectorTree * +get_tree (GByteArray *array, gint32 offset) +{ + return (GtkCssSelectorTree *) (array->data + offset); } -static guint -count_bits (guint v) +static GtkCssSelectorTree * +alloc_tree (GByteArray *array, gint32 *offset) { - /* http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel */ - v = v - ((v >> 1) & 0x55555555); - v = (v & 0x33333333) + ((v >> 2) & 0x33333333); - return (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24; + GtkCssSelectorTree tree = { { NULL} }; + + *offset = array->len; + g_byte_array_append (array, (guint8 *)&tree, sizeof (GtkCssSelectorTree)); + return get_tree (array, *offset); } -/* 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) +static gint32 +subdivide_infos (GByteArray *array, GList *infos, gint32 parent_offset) { - GQuark *count; + 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); + } - if (selector->previous) - _gtk_css_selector_get_specificity (selector->previous, ids, classes, elements); + /* 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 */ - if (selector->ids) - for (count = selector->ids; *count; count++) - (*ids)++; + max_count = 0; - if (selector->classes) - for (count = selector->classes; *count; count++) - (*classes)++; - - *classes += count_bits (selector->state) + count_bits (selector->pseudo_classes); + 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; - if (selector->name) - (*elements)++; + g_list_free (matched); + g_list_free (remaining); + g_hash_table_unref (ht); + + return tree_offset; } -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; +struct _GtkCssSelectorTreeBuilder { + GList *infos; +}; - _gtk_css_selector_get_specificity (a, &a_ids, &a_classes, &a_elements); - _gtk_css_selector_get_specificity (b, &b_ids, &b_classes, &b_elements); +GtkCssSelectorTreeBuilder * +_gtk_css_selector_tree_builder_new (void) +{ + return g_new0 (GtkCssSelectorTreeBuilder, 1); +} - compare = a_ids - b_ids; - if (compare) - return compare; +void +_gtk_css_selector_tree_builder_free (GtkCssSelectorTreeBuilder *builder) +{ + g_list_free_full (builder->infos, g_free); + g_free (builder); +} - compare = a_classes - b_classes; - if (compare) - return compare; +void +_gtk_css_selector_tree_builder_add (GtkCssSelectorTreeBuilder *builder, + GtkCssSelector *selectors, + GtkCssSelectorTree **selector_match, + gpointer match) +{ + GtkCssSelectorRuleSetInfo *info = g_new0 (GtkCssSelectorRuleSetInfo, 1); - return a_elements - b_elements; + info->match = match; + info->current_selector = selectors; + info->selector_match = selector_match; + builder->infos = g_list_prepend (builder->infos, info); } -GtkStateFlags -_gtk_css_selector_get_state_flags (GtkCssSelector *selector) +/* Convert all offsets to node-relative */ +static void +fixup_offsets (GtkCssSelectorTree *tree, guint8 *data) { - g_return_val_if_fail (selector != NULL, 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); - return selector->state; + 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; +}