[gtk+] css: Rewrite selectors
- From: Benjamin Otte <otte src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk+] css: Rewrite selectors
- Date: Wed, 18 May 2011 20:30:30 +0000 (UTC)
commit fc88b0f47c7dc198cc6674cb8819f5b43e45ef3c
Author: Benjamin Otte <otte redhat com>
Date: Sat May 14 13:27:31 2011 +0200
css: Rewrite selectors
Selectors now go into their own C file. The new selectors are modeled a
lot closer to the CSS spec. In particular the specificity computation
matches CSS 2.1 exactly.
For details about the why, see also:
http://mail.gnome.org/archives/gtk-devel-list/2011-May/msg00061.html
https://bugzilla.gnome.org/show_bug.cgi?id=649798
gtk/Makefile.am | 2 +
gtk/gtkcssprovider.c | 701 +++++++++----------------------------------
gtk/gtkcssselector.c | 440 +++++++++++++++++++++++++++
gtk/gtkcssselectorprivate.h | 57 ++++
4 files changed, 641 insertions(+), 559 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index f675830..4111938 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -388,6 +388,7 @@ gtk_private_h_sources = \
gtkcellareaboxcontextprivate.h \
gtkcssparserprivate.h \
gtkcssproviderprivate.h \
+ gtkcssselectorprivate.h \
gtkcssstringfuncsprivate.h \
gtkcustompaperunixdialog.h \
gtkdndcursors.h \
@@ -515,6 +516,7 @@ gtk_base_c_sources = \
gtkcontainer.c \
gtkcssparser.c \
gtkcssprovider.c \
+ gtkcssselector.c \
gtkcssstringfuncs.c \
gtkdialog.c \
gtkdrawingarea.c \
diff --git a/gtk/gtkcssprovider.c b/gtk/gtkcssprovider.c
index 234bcc4..191502a 100644
--- a/gtk/gtkcssprovider.c
+++ b/gtk/gtkcssprovider.c
@@ -28,6 +28,7 @@
#include "gtkcssproviderprivate.h"
#include "gtkcssparserprivate.h"
+#include "gtkcssselectorprivate.h"
#include "gtkcssstringfuncsprivate.h"
#include "gtksymboliccolor.h"
#include "gtkstyleprovider.h"
@@ -733,57 +734,14 @@
* </refsect2>
*/
-typedef struct SelectorElement SelectorElement;
-typedef struct SelectorPath SelectorPath;
typedef struct SelectorStyleInfo SelectorStyleInfo;
typedef struct _GtkCssScanner GtkCssScanner;
-typedef enum SelectorElementType SelectorElementType;
-typedef enum CombinatorType CombinatorType;
typedef enum ParserScope ParserScope;
typedef enum ParserSymbol ParserSymbol;
-enum SelectorElementType {
- SELECTOR_TYPE_NAME,
- SELECTOR_NAME,
- SELECTOR_GTYPE,
- SELECTOR_REGION,
- SELECTOR_CLASS,
- SELECTOR_GLOB
-};
-
-enum CombinatorType {
- COMBINATOR_DESCENDANT, /* No direct relation needed */
- COMBINATOR_CHILD /* Direct child */
-};
-
-struct SelectorElement
-{
- SelectorElementType elem_type;
- CombinatorType combinator;
-
- union
- {
- GQuark name;
- GType type;
-
- struct
- {
- GQuark name;
- GtkRegionFlags flags;
- } region;
- };
-};
-
-struct SelectorPath
-{
- GSList *elements;
- GtkStateFlags state;
- guint ref_count;
-};
-
struct SelectorStyleInfo
{
- SelectorPath *path;
+ GtkCssSelector *selector;
GHashTable *style;
};
@@ -949,145 +907,13 @@ gtk_css_provider_take_error_full (GtkCssProvider *provider,
g_error_free (error);
}
-static SelectorPath *
-selector_path_new (void)
-{
- SelectorPath *path;
-
- path = g_slice_new0 (SelectorPath);
- path->ref_count = 1;
-
- return path;
-}
-
-static SelectorPath *
-selector_path_ref (SelectorPath *path)
-{
- path->ref_count++;
- return path;
-}
-
-static void
-selector_path_unref (SelectorPath *path)
-{
- path->ref_count--;
-
- if (path->ref_count > 0)
- return;
-
- while (path->elements)
- {
- g_slice_free (SelectorElement, path->elements->data);
- path->elements = g_slist_delete_link (path->elements, path->elements);
- }
-
- g_slice_free (SelectorPath, path);
-}
-
-static void
-selector_path_prepend_type (SelectorPath *path,
- const gchar *type_name)
-{
- SelectorElement *elem;
- GType type;
-
- elem = g_slice_new (SelectorElement);
- elem->combinator = COMBINATOR_DESCENDANT;
- type = g_type_from_name (type_name);
-
- if (type == G_TYPE_INVALID)
- {
- elem->elem_type = SELECTOR_TYPE_NAME;
- elem->name = g_quark_from_string (type_name);
- }
- else
- {
- elem->elem_type = SELECTOR_GTYPE;
- elem->type = type;
- }
-
- path->elements = g_slist_prepend (path->elements, elem);
-}
-
-static void
-selector_path_prepend_glob (SelectorPath *path)
-{
- SelectorElement *elem;
-
- elem = g_slice_new (SelectorElement);
- elem->elem_type = SELECTOR_GLOB;
- elem->combinator = COMBINATOR_DESCENDANT;
-
- path->elements = g_slist_prepend (path->elements, elem);
-}
-
-static void
-selector_path_prepend_region (SelectorPath *path,
- const gchar *name,
- GtkRegionFlags flags)
-{
- SelectorElement *elem;
-
- elem = g_slice_new (SelectorElement);
- elem->combinator = COMBINATOR_DESCENDANT;
- elem->elem_type = SELECTOR_REGION;
-
- elem->region.name = g_quark_from_string (name);
- elem->region.flags = flags;
-
- path->elements = g_slist_prepend (path->elements, elem);
-}
-
-static void
-selector_path_prepend_name (SelectorPath *path,
- const gchar *name)
-{
- SelectorElement *elem;
-
- elem = g_slice_new (SelectorElement);
- elem->combinator = COMBINATOR_DESCENDANT;
- elem->elem_type = SELECTOR_NAME;
-
- elem->name = g_quark_from_string (name);
-
- path->elements = g_slist_prepend (path->elements, elem);
-}
-
-static void
-selector_path_prepend_class (SelectorPath *path,
- const gchar *name)
-{
- SelectorElement *elem;
-
- elem = g_slice_new (SelectorElement);
- elem->combinator = COMBINATOR_DESCENDANT;
- elem->elem_type = SELECTOR_CLASS;
-
- elem->name = g_quark_from_string (name);
-
- path->elements = g_slist_prepend (path->elements, elem);
-}
-
-static void
-selector_path_prepend_combinator (SelectorPath *path,
- CombinatorType combinator)
-{
- SelectorElement *elem;
-
- g_assert (path->elements != NULL);
-
- /* It is actually stored in the last element */
- elem = path->elements->data;
- elem->combinator = combinator;
-}
-
static SelectorStyleInfo *
-selector_style_info_new (SelectorPath *path)
+selector_style_info_new (GtkCssSelector *selector)
{
SelectorStyleInfo *info;
info = g_slice_new0 (SelectorStyleInfo);
- info->path = selector_path_ref (path);
+ info->selector = selector;
return info;
}
@@ -1098,8 +924,8 @@ selector_style_info_free (SelectorStyleInfo *info)
if (info->style)
g_hash_table_unref (info->style);
- if (info->path)
- selector_path_unref (info->path);
+ if (info->selector)
+ _gtk_css_selector_free (info->selector);
g_slice_free (SelectorStyleInfo, info);
}
@@ -1132,8 +958,7 @@ gtk_css_scanner_reset (GtkCssScanner *scanner)
g_slist_free (scanner->state);
scanner->state = NULL;
- g_slist_foreach (scanner->cur_selectors, (GFunc) selector_path_unref, NULL);
- g_slist_free (scanner->cur_selectors);
+ g_slist_free_full (scanner->cur_selectors, (GDestroyNotify) _gtk_css_selector_free);
scanner->cur_selectors = NULL;
if (scanner->cur_properties)
@@ -1252,196 +1077,10 @@ gtk_css_provider_init (GtkCssProvider *css_provider)
(GDestroyNotify) gtk_symbolic_color_unref);
}
-typedef struct ComparePathData ComparePathData;
-
-struct ComparePathData
-{
- guint64 score;
- SelectorPath *path;
- GSList *iter;
-};
-
-static gboolean
-compare_selector_element (GtkWidgetPath *path,
- guint index,
- SelectorElement *elem,
- guint8 *score)
-{
- *score = 0;
-
- if (elem->elem_type == SELECTOR_TYPE_NAME)
- {
- const gchar *type_name;
- GType resolved_type;
-
- /* Resolve the type name */
- type_name = g_quark_to_string (elem->name);
- resolved_type = g_type_from_name (type_name);
-
- if (resolved_type == G_TYPE_INVALID)
- {
- /* Type couldn't be resolved, so the selector
- * clearly doesn't affect the given widget path
- */
- return FALSE;
- }
-
- elem->elem_type = SELECTOR_GTYPE;
- elem->type = resolved_type;
- }
-
- if (elem->elem_type == SELECTOR_GTYPE)
- {
- GType type;
-
- type = gtk_widget_path_iter_get_object_type (path, index);
-
- if (!g_type_is_a (type, elem->type))
- return FALSE;
-
- if (type == elem->type)
- *score |= 0xF;
- else
- {
- guint diff = g_type_depth (type) - g_type_depth (elem->type);
-
- if (G_UNLIKELY (diff > 0xE))
- {
- g_warning ("Hierarchy is higher than expected.");
- diff = 0xE;
- }
-
- *score = 0XF - diff;
- }
-
- return TRUE;
- }
- else if (elem->elem_type == SELECTOR_REGION)
- {
- GtkRegionFlags flags;
-
- if (!gtk_widget_path_iter_has_qregion (path, index,
- elem->region.name,
- &flags))
- return FALSE;
-
- if (elem->region.flags != 0 &&
- (flags & elem->region.flags) == 0)
- return FALSE;
-
- *score = 0xF;
- return TRUE;
- }
- else if (elem->elem_type == SELECTOR_GLOB)
- {
- /* Treat as lowest matching type */
- *score = 1;
- return TRUE;
- }
- else if (elem->elem_type == SELECTOR_NAME)
- {
- if (!gtk_widget_path_iter_has_qname (path, index, elem->name))
- return FALSE;
-
- *score = 0xF;
- return TRUE;
- }
- else if (elem->elem_type == SELECTOR_CLASS)
- {
- if (!gtk_widget_path_iter_has_qclass (path, index, elem->name))
- return FALSE;
-
- *score = 0xF;
- return TRUE;
- }
-
- return FALSE;
-}
-
-static guint64
-compare_selector (GtkWidgetPath *path,
- SelectorPath *selector)
-{
- GSList *elements = selector->elements;
- gboolean match = TRUE, first = TRUE, first_match = FALSE;
- guint64 score = 0;
- gint i;
-
- i = gtk_widget_path_length (path) - 1;
-
- while (elements && match && i >= 0)
- {
- SelectorElement *elem;
- guint8 elem_score;
-
- elem = elements->data;
-
- match = compare_selector_element (path, i, elem, &elem_score);
-
- if (match && first)
- first_match = TRUE;
-
- /* Only move on to the next index if there is no match
- * with the current element (whether to continue or not
- * handled right after in the combinator check), or a
- * GType or glob has just been matched.
- *
- * Region and widget names do not trigger this because
- * the next element in the selector path could also be
- * related to the same index.
- */
- if (!match ||
- (elem->elem_type == SELECTOR_GTYPE ||
- elem->elem_type == SELECTOR_GLOB))
- i--;
-
- if (!match &&
- elem->elem_type != SELECTOR_TYPE_NAME &&
- elem->combinator == COMBINATOR_DESCENDANT)
- {
- /* With descendant combinators there may
- * be intermediate chidren in the hierarchy
- */
- match = TRUE;
- }
- else if (match)
- elements = elements->next;
-
- if (match)
- {
- /* Only 4 bits are actually used */
- score <<= 4;
- score |= elem_score;
- }
-
- first = FALSE;
- }
-
- /* If there are pending selector
- * elements to compare, it's not
- * a match.
- */
- if (elements)
- match = FALSE;
-
- if (!match)
- score = 0;
- else if (first_match)
- {
- /* Assign more weight to these selectors
- * that matched right from the first element.
- */
- score <<= 4;
- }
-
- return score;
-}
-
typedef struct StylePriorityInfo StylePriorityInfo;
struct StylePriorityInfo
{
- guint64 score;
GHashTable *style;
GtkStateFlags state;
};
@@ -1452,7 +1091,7 @@ css_provider_get_selectors (GtkCssProvider *css_provider,
{
GtkCssProviderPrivate *priv;
GArray *priority_info;
- guint i, j;
+ guint i;
priv = css_provider->priv;
priority_info = g_array_new (FALSE, FALSE, sizeof (StylePriorityInfo));
@@ -1461,35 +1100,16 @@ css_provider_get_selectors (GtkCssProvider *css_provider,
{
SelectorStyleInfo *info;
StylePriorityInfo new;
- gboolean added = FALSE;
- guint64 score;
info = g_ptr_array_index (priv->selectors_info, i);
- score = compare_selector (path, info->path);
-
- if (score <= 0)
- continue;
-
- new.score = score;
- new.style = info->style;
- new.state = info->path->state;
- for (j = 0; j < priority_info->len; j++)
+ if (_gtk_css_selector_matches (info->selector, path))
{
- StylePriorityInfo *cur;
+ new.style = info->style;
+ new.state = _gtk_css_selector_get_state_flags (info->selector);
- cur = &g_array_index (priority_info, StylePriorityInfo, j);
-
- if (cur->score > new.score)
- {
- g_array_insert_val (priority_info, j, new);
- added = TRUE;
- break;
- }
+ g_array_append_val (priority_info, new);
}
-
- if (!added)
- g_array_append_val (priority_info, new);
}
return priority_info;
@@ -1728,18 +1348,24 @@ css_provider_commit (GtkCssProvider *css_provider,
priv = css_provider->priv;
if (g_hash_table_size (properties) == 0)
- return;
+ {
+ g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free);
+ g_hash_table_unref (properties);
+ return;
+ }
for (l = selectors; l; l = l->next)
{
- SelectorPath *path = l->data;
+ GtkCssSelector *selector = l->data;
SelectorStyleInfo *info;
- info = selector_style_info_new (path);
+ info = selector_style_info_new (selector);
selector_style_info_set_style (info, properties);
g_ptr_array_add (priv->selectors_info, info);
}
+
+ g_hash_table_unref (properties);
}
static void
@@ -1990,7 +1616,8 @@ parse_at_keyword (GtkCssScanner *scanner)
}
static gboolean
-parse_selector_pseudo_class (GtkCssScanner *scanner, SelectorPath *path)
+parse_selector_pseudo_class (GtkCssScanner *scanner,
+ GtkStateFlags *flags_to_modify)
{
struct {
const char *name;
@@ -2011,7 +1638,15 @@ parse_selector_pseudo_class (GtkCssScanner *scanner, SelectorPath *path)
{
if (_gtk_css_parser_try (scanner->parser, classes[i].name, FALSE))
{
- path->state |= classes[i].flag;
+ if (*flags_to_modify & classes[i].flag)
+ {
+ gtk_css_provider_error (scanner->provider,
+ scanner,
+ GTK_CSS_PROVIDER_ERROR,
+ GTK_CSS_PROVIDER_ERROR_SYNTAX,
+ "Duplicate pseudo-class %s in selector", classes[i].name);
+ }
+ *flags_to_modify |= classes[i].flag;
return TRUE;
}
}
@@ -2025,9 +1660,12 @@ parse_selector_pseudo_class (GtkCssScanner *scanner, SelectorPath *path)
}
static gboolean
-parse_selector_class (GtkCssScanner *scanner, SelectorPath *path)
+parse_selector_class (GtkCssScanner *scanner, GArray *classes)
{
- char *name = _gtk_css_parser_try_name (scanner->parser, FALSE);
+ GQuark qname;
+ char *name;
+
+ name = _gtk_css_parser_try_name (scanner->parser, FALSE);
if (name == NULL)
{
@@ -2035,19 +1673,23 @@ parse_selector_class (GtkCssScanner *scanner, SelectorPath *path)
scanner,
GTK_CSS_PROVIDER_ERROR,
GTK_CSS_PROVIDER_ERROR_SYNTAX,
- "Expected a valid name");
+ "Expected a valid name for class");
return FALSE;
}
- selector_path_prepend_combinator (path, COMBINATOR_CHILD);
- selector_path_prepend_class (path, name);
+ qname = g_quark_from_string (name);
+ g_array_append_val (classes, qname);
+ g_free (name);
return TRUE;
}
static gboolean
-parse_selector_name (GtkCssScanner *scanner, SelectorPath *path)
+parse_selector_name (GtkCssScanner *scanner, GArray *names)
{
- char *name = _gtk_css_parser_try_name (scanner->parser, FALSE);
+ GQuark qname;
+ char *name;
+
+ name = _gtk_css_parser_try_name (scanner->parser, FALSE);
if (name == NULL)
{
@@ -2055,19 +1697,20 @@ parse_selector_name (GtkCssScanner *scanner, SelectorPath *path)
scanner,
GTK_CSS_PROVIDER_ERROR,
GTK_CSS_PROVIDER_ERROR_SYNTAX,
- "Expected a valid name");
+ "Expected a valid name for id");
return FALSE;
}
- selector_path_prepend_combinator (path, COMBINATOR_CHILD);
- selector_path_prepend_name (path, name);
+ qname = g_quark_from_string (name);
+ g_array_append_val (names, qname);
+ g_free (name);
return TRUE;
}
static gboolean
parse_selector_pseudo_class_for_region (GtkCssScanner *scanner,
- SelectorPath *path,
- GtkRegionFlags *flags_to_modify)
+ GtkRegionFlags *flags_to_modify,
+ GtkStateFlags *state_to_modify)
{
struct {
const char *name;
@@ -2094,7 +1737,7 @@ parse_selector_pseudo_class_for_region (GtkCssScanner *scanner,
}
if (!_gtk_css_parser_try (scanner->parser, "nth-child(", TRUE))
- return parse_selector_pseudo_class (scanner, path);
+ return parse_selector_pseudo_class (scanner, state_to_modify);
for (i = 0; i < G_N_ELEMENTS (nth_child); i++)
{
@@ -2129,60 +1772,54 @@ parse_selector_pseudo_class_for_region (GtkCssScanner *scanner,
}
static gboolean
-parse_simple_selector (GtkCssScanner *scanner, SelectorPath *path)
+parse_simple_selector (GtkCssScanner *scanner,
+ char **name,
+ GArray *ids,
+ GArray *classes,
+ GtkRegionFlags *pseudo_classes,
+ GtkStateFlags *state)
{
- char *name;
gboolean parsed_something;
- name = _gtk_css_parser_try_ident (scanner->parser, FALSE);
- if (name)
+ *name = _gtk_css_parser_try_ident (scanner->parser, FALSE);
+ if (*name)
{
- if (_gtk_style_context_check_region_name (name))
+ if (_gtk_style_context_check_region_name (*name))
{
- GtkRegionFlags flags;
-
- flags = 0;
-
while (_gtk_css_parser_try (scanner->parser, ":", FALSE))
{
- if (!parse_selector_pseudo_class_for_region (scanner, path, &flags))
+ if (!parse_selector_pseudo_class_for_region (scanner, pseudo_classes, state))
{
g_free (name);
return FALSE;
}
}
- selector_path_prepend_region (path, name, flags);
- g_free (name);
_gtk_css_parser_skip_whitespace (scanner->parser);
return TRUE;
}
- else
- {
- selector_path_prepend_type (path, name);
- parsed_something = TRUE;
- }
+
+ parsed_something = TRUE;
}
else
{
parsed_something = _gtk_css_parser_try (scanner->parser, "*", FALSE);
- selector_path_prepend_glob (path);
}
do {
if (_gtk_css_parser_try (scanner->parser, "#", FALSE))
{
- if (!parse_selector_name (scanner, path))
+ if (!parse_selector_name (scanner, ids))
return FALSE;
}
else if (_gtk_css_parser_try (scanner->parser, ".", FALSE))
{
- if (!parse_selector_class (scanner, path))
+ if (!parse_selector_class (scanner, classes))
return FALSE;
}
else if (_gtk_css_parser_try (scanner->parser, ":", FALSE))
{
- if (!parse_selector_pseudo_class (scanner, path))
+ if (!parse_selector_pseudo_class (scanner, state))
return FALSE;
}
else if (!parsed_something)
@@ -2205,29 +1842,48 @@ parse_simple_selector (GtkCssScanner *scanner, SelectorPath *path)
return TRUE;
}
-static SelectorPath *
+static GtkCssSelector *
parse_selector (GtkCssScanner *scanner)
{
- SelectorPath *path;
-
- path = selector_path_new ();
+ GtkCssSelector *selector = NULL;
do {
- if (!parse_simple_selector (scanner, path))
+ char *name = NULL;
+ GArray *ids = g_array_new (TRUE, FALSE, sizeof (GQuark));
+ GArray *classes = g_array_new (TRUE, FALSE, sizeof (GQuark));
+ GtkRegionFlags pseudo_classes = 0;
+ GtkStateFlags state = 0;
+ GtkCssCombinator combine = GTK_CSS_COMBINE_DESCANDANT;
+
+ if (selector)
{
- selector_path_unref (path);
+ if (_gtk_css_parser_try (scanner->parser, ">", TRUE))
+ combine = GTK_CSS_COMBINE_CHILD;
+ }
+
+ if (!parse_simple_selector (scanner, &name, ids, classes, &pseudo_classes, &state))
+ {
+ g_array_free (ids, TRUE);
+ g_array_free (classes, TRUE);
+ if (selector)
+ _gtk_css_selector_free (selector);
return NULL;
}
- if (_gtk_css_parser_try (scanner->parser, ">", TRUE))
- selector_path_prepend_combinator (path, COMBINATOR_CHILD);
+ selector = _gtk_css_selector_new (selector,
+ combine,
+ name,
+ (GQuark *) g_array_free (ids, ids->len == 0),
+ (GQuark *) g_array_free (classes, classes->len == 0),
+ pseudo_classes,
+ state);
+ g_free (name);
}
- while (path->state == 0 &&
- !_gtk_css_parser_is_eof (scanner->parser) &&
+ while (!_gtk_css_parser_is_eof (scanner->parser) &&
!_gtk_css_parser_begins_with (scanner->parser, ',') &&
!_gtk_css_parser_begins_with (scanner->parser, '{'));
- return path;
+ return selector;
}
static GSList *
@@ -2236,16 +1892,16 @@ parse_selector_list (GtkCssScanner *scanner)
GSList *selectors = NULL;
do {
- SelectorPath *path = parse_selector (scanner);
+ GtkCssSelector *select = parse_selector (scanner);
- if (path == NULL)
+ if (select == NULL)
{
- g_slist_free_full (selectors, (GDestroyNotify) selector_path_unref);
+ g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free);
_gtk_css_parser_resync (scanner->parser, FALSE, 0);
return NULL;
}
- selectors = g_slist_prepend (selectors, path);
+ selectors = g_slist_prepend (selectors, select);
}
while (_gtk_css_parser_try (scanner->parser, ",", TRUE));
@@ -2412,7 +2068,7 @@ parse_ruleset (GtkCssScanner *scanner)
GTK_CSS_PROVIDER_ERROR_SYNTAX,
"expected '{' after selectors");
_gtk_css_parser_resync (scanner->parser, FALSE, 0);
- g_slist_free_full (selectors, (GDestroyNotify) selector_path_unref);
+ g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free);
return;
}
@@ -2428,7 +2084,7 @@ parse_ruleset (GtkCssScanner *scanner)
if (!_gtk_css_parser_is_eof (scanner->parser))
{
_gtk_css_parser_resync (scanner->parser, FALSE, 0);
- g_slist_free_full (selectors, (GDestroyNotify) selector_path_unref);
+ g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free);
if (properties)
g_hash_table_unref (properties);
return;
@@ -2436,11 +2092,9 @@ parse_ruleset (GtkCssScanner *scanner)
}
if (properties)
- {
- css_provider_commit (scanner->provider, selectors, properties);
- g_hash_table_unref (properties);
- }
- g_slist_free_full (selectors, (GDestroyNotify) selector_path_unref);
+ css_provider_commit (scanner->provider, selectors, properties);
+ else
+ g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free);
}
static void
@@ -2453,7 +2107,7 @@ parse_statement (GtkCssScanner *scanner)
}
static void
-parse_stylesheet (GtkCssScanner *scanner)
+parse_stylesheet (GtkCssScanner *scanner)
{
_gtk_css_parser_skip_whitespace (scanner->parser);
@@ -2467,6 +2121,36 @@ parse_stylesheet (GtkCssScanner *scanner)
}
}
+static int
+gtk_css_provider_compare_rule (gconstpointer a_,
+ gconstpointer b_)
+{
+ const SelectorStyleInfo *a = *(const SelectorStyleInfo **) a_;
+ const SelectorStyleInfo *b = *(const SelectorStyleInfo **) b_;
+ int compare;
+
+ compare = _gtk_css_selector_compare (a->selector, b->selector);
+ if (compare != 0)
+ return compare;
+
+ /* compare pointers in array to ensure a stable sort */
+ if (a_ < b_)
+ return -1;
+
+ if (a_ > b_)
+ return 1;
+
+ return 0;
+}
+
+static void
+gtk_css_provider_postprocess (GtkCssProvider *css_provider)
+{
+ GtkCssProviderPrivate *priv = css_provider->priv;
+
+ g_ptr_array_sort (priv->selectors_info, gtk_css_provider_compare_rule);
+}
+
static gboolean
gtk_css_provider_load_internal (GtkCssProvider *css_provider,
GtkCssScanner *parent,
@@ -2528,6 +2212,9 @@ gtk_css_provider_load_internal (GtkCssProvider *css_provider,
parse_stylesheet (scanner);
gtk_css_scanner_destroy (scanner);
+
+ if (parent == NULL)
+ gtk_css_provider_postprocess (css_provider);
}
if (error)
@@ -3133,117 +2820,13 @@ gtk_css_provider_get_named (const gchar *name,
}
static void
-selector_path_print (const SelectorPath *path,
- GString * str)
-{
- GSList *walk, *reverse;
-
- reverse = g_slist_copy (path->elements);
- reverse = g_slist_reverse (reverse);
-
- for (walk = reverse; walk; walk = walk->next)
- {
- SelectorElement *elem = walk->data;
-
- switch (elem->elem_type)
- {
- case SELECTOR_TYPE_NAME:
- case SELECTOR_NAME:
- g_string_append (str, g_quark_to_string (elem->name));
- break;
- case SELECTOR_GTYPE:
- g_string_append (str, g_type_name (elem->type));
- break;
- case SELECTOR_REGION:
- g_string_append (str, g_quark_to_string (elem->region.name));
- if (elem->region.flags)
- {
- char * flag_names[] = {
- "nth-child(even)",
- "nth-child(odd)",
- "first-child",
- "last-child",
- "sorted"
- };
- guint i;
-
- for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
- {
- if (elem->region.flags & (1 << i))
- {
- g_string_append_c (str, ':');
- g_string_append (str, flag_names[i]);
- }
- }
- }
- break;
- case SELECTOR_CLASS:
- g_string_append_c (str, '.');
- g_string_append (str, g_quark_to_string (elem->name));
- break;
- case SELECTOR_GLOB:
- if (walk->next == NULL ||
- elem->combinator != COMBINATOR_CHILD ||
- ((SelectorElement *) walk->next->data)->elem_type != SELECTOR_CLASS)
- g_string_append (str, "*");
- break;
- default:
- g_assert_not_reached ();
- }
-
- if (walk->next)
- {
- switch (elem->combinator)
- {
- case COMBINATOR_DESCENDANT:
- if (elem->elem_type != SELECTOR_CLASS ||
- ((SelectorElement *) walk->next->data)->elem_type != SELECTOR_CLASS)
- g_string_append_c (str, ' ');
- break;
- case COMBINATOR_CHILD:
- if (((SelectorElement *) walk->next->data)->elem_type != SELECTOR_CLASS)
- g_string_append (str, " > ");
- break;
- default:
- g_assert_not_reached ();
- }
- }
-
- }
-
- if (path->state)
- {
- char * state_names[] = {
- "active",
- "hover",
- "selected",
- "insensitive",
- "inconsistent",
- "focus"
- };
- guint i;
-
- for (i = 0; i < G_N_ELEMENTS (state_names); i++)
- {
- if (path->state & (1 << i))
- {
- g_string_append_c (str, ':');
- g_string_append (str, state_names[i]);
- }
- }
- }
-
- g_slist_free (reverse);
-}
-
-static void
selector_style_info_print (const SelectorStyleInfo *info,
GString *str)
{
GList *keys, *walk;
char *s;
- selector_path_print (info->path, str);
+ _gtk_css_selector_print (info->selector, str);
g_string_append (str, " {\n");
diff --git a/gtk/gtkcssselector.c b/gtk/gtkcssselector.c
new file mode 100644
index 0000000..799f3a0
--- /dev/null
+++ b/gtk/gtkcssselector.c
@@ -0,0 +1,440 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2011 Benjamin Otte <otte gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "gtkcssselectorprivate.h"
+
+struct _GtkCssSelector
+{
+ 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) */
+};
+
+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 = G_TYPE_INVALID;
+ selector->ids = ids;
+ selector->classes = classes;
+ selector->pseudo_classes = pseudo_classes;
+ selector->state = state;
+
+ return selector;
+}
+
+void
+_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);
+}
+
+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",
+ "sorted"
+ };
+ guint i;
+
+ 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]);
+ }
+ }
+ }
+
+ if (selector->state)
+ {
+ static const char * state_names[] = {
+ "active",
+ "hover",
+ "selected",
+ "insensitive",
+ "inconsistent",
+ "focus"
+ };
+ guint i;
+
+ 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]);
+ }
+ }
+ }
+}
+
+char *
+_gtk_css_selector_to_string (const GtkCssSelector *selector)
+{
+ GString *string;
+
+ g_return_val_if_fail (selector != NULL, NULL);
+
+ string = g_string_new (NULL);
+
+ _gtk_css_selector_print (selector, string);
+
+ return g_string_free (string, FALSE);
+}
+
+static gboolean
+gtk_css_selector_matches_type (const GtkCssSelector *selector,
+ /* const */ GtkWidgetPath *path,
+ guint id)
+{
+ if (selector->name == NULL)
+ return TRUE;
+
+ if (selector->pseudo_classes)
+ return FALSE;
+
+ /* ugh, assigning to a const variable */
+ if (selector->type == G_TYPE_INVALID)
+ ((GtkCssSelector *) selector)->type = g_type_from_name (selector->name);
+
+ if (selector->type == G_TYPE_INVALID)
+ return FALSE;
+
+ return g_type_is_a (gtk_widget_path_iter_get_object_type (path, id), selector->type);
+}
+
+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;
+
+ if (!gtk_widget_path_iter_has_region (path, id, region, &flags))
+ {
+ /* This function must be called with existing regions */
+ g_assert_not_reached ();
+ }
+
+ return (selector->pseudo_classes & flags) == selector->pseudo_classes;
+}
+
+static gboolean
+gtk_css_selector_matches_rest (const GtkCssSelector *selector,
+ /* const */ GtkWidgetPath *path,
+ guint id)
+{
+ if (selector->ids)
+ {
+ GQuark *name;
+
+ for (name = selector->ids; *name; name++)
+ {
+ if (!gtk_widget_path_iter_has_qname (path, id, *name))
+ return FALSE;
+ }
+ }
+
+ if (selector->classes)
+ {
+ GQuark *class;
+
+ for (class = selector->classes; *class; class++)
+ {
+ if (!gtk_widget_path_iter_has_qclass (path, id, *class))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gtk_css_selector_matches_previous (const GtkCssSelector *selector,
+ /* const */ GtkWidgetPath *path,
+ guint id,
+ GSList *regions);
+
+static gboolean
+gtk_css_selector_matches_from (const GtkCssSelector *selector,
+ /* const */ GtkWidgetPath *path,
+ guint id,
+ GSList *regions)
+{
+ GSList *l;
+
+ if (!gtk_css_selector_matches_rest (selector, path, id))
+ return FALSE;
+
+ for (l = regions; l; l = l->next)
+ {
+ const char *region = l->data;
+
+ 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;
+ }
+ }
+
+ if (gtk_css_selector_matches_type (selector, path, id))
+ {
+ GSList *regions;
+ gboolean match;
+
+ if (id <= 0)
+ return selector->previous == NULL;
+
+ 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;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gtk_css_selector_matches_previous (const GtkCssSelector *selector,
+ /* const */ GtkWidgetPath *path,
+ guint id,
+ GSList *regions)
+{
+ if (!selector->previous)
+ return TRUE;
+
+ if (gtk_css_selector_matches_from (selector->previous,
+ path,
+ id,
+ regions))
+ return TRUE;
+
+ if (selector->combine == GTK_CSS_COMBINE_DESCANDANT)
+ {
+ /* 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;
+ }
+ }
+
+ return FALSE;
+}
+
+gboolean
+_gtk_css_selector_matches (const GtkCssSelector *selector,
+ /* const */ GtkWidgetPath *path)
+{
+ GSList *list;
+ guint length;
+ gboolean match;
+
+ g_return_val_if_fail (selector != NULL, FALSE);
+ g_return_val_if_fail (path != NULL, FALSE);
+
+ length = gtk_widget_path_length (path);
+ if (length == 0)
+ return FALSE;
+
+ 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 guint
+count_bits (guint v)
+{
+ /* 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;
+}
+
+/* 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)
+{
+ GQuark *count;
+
+ if (selector->previous)
+ _gtk_css_selector_get_specificity (selector->previous, ids, classes, elements);
+
+ if (selector->ids)
+ for (count = selector->ids; *count; count++)
+ (*ids)++;
+
+ if (selector->classes)
+ for (count = selector->classes; *count; count++)
+ (*classes)++;
+
+ *classes += count_bits (selector->state) + count_bits (selector->pseudo_classes);
+
+ if (selector->name)
+ (*elements)++;
+}
+
+int
+_gtk_css_selector_compare (const GtkCssSelector *a,
+ const GtkCssSelector *b)
+{
+ guint a_ids = 0, a_classes = 0, a_elements = 0;
+ guint b_ids = 0, b_classes = 0, b_elements = 0;
+ int compare;
+
+ _gtk_css_selector_get_specificity (a, &a_ids, &a_classes, &a_elements);
+ _gtk_css_selector_get_specificity (b, &b_ids, &b_classes, &b_elements);
+
+ compare = a_ids - b_ids;
+ if (compare)
+ return compare;
+
+ compare = a_classes - b_classes;
+ if (compare)
+ return compare;
+
+ return a_elements - b_elements;
+}
+
+GtkStateFlags
+_gtk_css_selector_get_state_flags (GtkCssSelector *selector)
+{
+ g_return_val_if_fail (selector != NULL, 0);
+
+ return selector->state;
+}
+
diff --git a/gtk/gtkcssselectorprivate.h b/gtk/gtkcssselectorprivate.h
new file mode 100644
index 0000000..70c5e60
--- /dev/null
+++ b/gtk/gtkcssselectorprivate.h
@@ -0,0 +1,57 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2011 Benjamin Otte <otte gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GTK_CSS_SELECTOR_PRIVATE_H__
+#define __GTK_CSS_SELECTOR_PRIVATE_H__
+
+#include <gtk/gtkenums.h>
+#include <gtk/gtkwidgetpath.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ GTK_CSS_COMBINE_DESCANDANT,
+ GTK_CSS_COMBINE_CHILD
+} GtkCssCombinator;
+
+typedef struct _GtkCssSelector GtkCssSelector;
+
+GtkCssSelector * _gtk_css_selector_new (GtkCssSelector *previous,
+ GtkCssCombinator combine,
+ const char * name,
+ GQuark * ids,
+ GQuark * classes,
+ GtkRegionFlags pseudo_classes,
+ GtkStateFlags state);
+void _gtk_css_selector_free (GtkCssSelector *selector);
+
+char * _gtk_css_selector_to_string (const GtkCssSelector *selector);
+void _gtk_css_selector_print (const GtkCssSelector *selector,
+ GString *str);
+
+GtkStateFlags _gtk_css_selector_get_state_flags (GtkCssSelector *selector);
+
+gboolean _gtk_css_selector_matches (const GtkCssSelector *selector,
+ /* const */ GtkWidgetPath *path);
+int _gtk_css_selector_compare (const GtkCssSelector *a,
+ const GtkCssSelector *b);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_SELECTOR_PRIVATE_H__ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]