forked from AuroraMiddleware/gtk
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:
parent
4e2d3f5d18
commit
fc88b0f47c
@ -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 \
|
||||
|
@ -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)
|
||||
@ -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
440
gtk/gtkcssselector.c
Normal 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;
|
||||
}
|
||||
|
57
gtk/gtkcssselectorprivate.h
Normal file
57
gtk/gtkcssselectorprivate.h
Normal 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__ */
|
Loading…
Reference in New Issue
Block a user