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
This commit is contained in:
Benjamin Otte 2011-05-14 13:27:31 +02:00
parent 4e2d3f5d18
commit fc88b0f47c
4 changed files with 641 additions and 559 deletions

View File

@ -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 \

View File

@ -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,36 +1100,17 @@ 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;
if (_gtk_css_selector_matches (info->selector, path))
{
new.style = info->style;
new.state = info->path->state;
new.state = _gtk_css_selector_get_state_flags (info->selector);
for (j = 0; j < priority_info->len; j++)
{
StylePriorityInfo *cur;
cur = &g_array_index (priority_info, StylePriorityInfo, j);
if (cur->score > new.score)
{
g_array_insert_val (priority_info, j, new);
added = TRUE;
break;
}
}
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)
{
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;
}
}
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);
else
g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free);
}
static void
@ -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)
@ -3132,110 +2819,6 @@ gtk_css_provider_get_named (const gchar *name,
return provider;
}
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)
@ -3243,7 +2826,7 @@ selector_style_info_print (const SelectorStyleInfo *info,
GList *keys, *walk;
char *s;
selector_path_print (info->path, str);
_gtk_css_selector_print (info->selector, str);
g_string_append (str, " {\n");

440
gtk/gtkcssselector.c Normal file
View File

@ -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;
}

View File

@ -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__ */