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 \
|
gtkcellareaboxcontextprivate.h \
|
||||||
gtkcssparserprivate.h \
|
gtkcssparserprivate.h \
|
||||||
gtkcssproviderprivate.h \
|
gtkcssproviderprivate.h \
|
||||||
|
gtkcssselectorprivate.h \
|
||||||
gtkcssstringfuncsprivate.h \
|
gtkcssstringfuncsprivate.h \
|
||||||
gtkcustompaperunixdialog.h \
|
gtkcustompaperunixdialog.h \
|
||||||
gtkdndcursors.h \
|
gtkdndcursors.h \
|
||||||
@ -515,6 +516,7 @@ gtk_base_c_sources = \
|
|||||||
gtkcontainer.c \
|
gtkcontainer.c \
|
||||||
gtkcssparser.c \
|
gtkcssparser.c \
|
||||||
gtkcssprovider.c \
|
gtkcssprovider.c \
|
||||||
|
gtkcssselector.c \
|
||||||
gtkcssstringfuncs.c \
|
gtkcssstringfuncs.c \
|
||||||
gtkdialog.c \
|
gtkdialog.c \
|
||||||
gtkdrawingarea.c \
|
gtkdrawingarea.c \
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include "gtkcssproviderprivate.h"
|
#include "gtkcssproviderprivate.h"
|
||||||
|
|
||||||
#include "gtkcssparserprivate.h"
|
#include "gtkcssparserprivate.h"
|
||||||
|
#include "gtkcssselectorprivate.h"
|
||||||
#include "gtkcssstringfuncsprivate.h"
|
#include "gtkcssstringfuncsprivate.h"
|
||||||
#include "gtksymboliccolor.h"
|
#include "gtksymboliccolor.h"
|
||||||
#include "gtkstyleprovider.h"
|
#include "gtkstyleprovider.h"
|
||||||
@ -733,57 +734,14 @@
|
|||||||
* </refsect2>
|
* </refsect2>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
typedef struct SelectorElement SelectorElement;
|
|
||||||
typedef struct SelectorPath SelectorPath;
|
|
||||||
typedef struct SelectorStyleInfo SelectorStyleInfo;
|
typedef struct SelectorStyleInfo SelectorStyleInfo;
|
||||||
typedef struct _GtkCssScanner GtkCssScanner;
|
typedef struct _GtkCssScanner GtkCssScanner;
|
||||||
typedef enum SelectorElementType SelectorElementType;
|
|
||||||
typedef enum CombinatorType CombinatorType;
|
|
||||||
typedef enum ParserScope ParserScope;
|
typedef enum ParserScope ParserScope;
|
||||||
typedef enum ParserSymbol ParserSymbol;
|
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
|
struct SelectorStyleInfo
|
||||||
{
|
{
|
||||||
SelectorPath *path;
|
GtkCssSelector *selector;
|
||||||
GHashTable *style;
|
GHashTable *style;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -949,145 +907,13 @@ gtk_css_provider_take_error_full (GtkCssProvider *provider,
|
|||||||
g_error_free (error);
|
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 *
|
static SelectorStyleInfo *
|
||||||
selector_style_info_new (SelectorPath *path)
|
selector_style_info_new (GtkCssSelector *selector)
|
||||||
{
|
{
|
||||||
SelectorStyleInfo *info;
|
SelectorStyleInfo *info;
|
||||||
|
|
||||||
info = g_slice_new0 (SelectorStyleInfo);
|
info = g_slice_new0 (SelectorStyleInfo);
|
||||||
info->path = selector_path_ref (path);
|
info->selector = selector;
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
@ -1098,8 +924,8 @@ selector_style_info_free (SelectorStyleInfo *info)
|
|||||||
if (info->style)
|
if (info->style)
|
||||||
g_hash_table_unref (info->style);
|
g_hash_table_unref (info->style);
|
||||||
|
|
||||||
if (info->path)
|
if (info->selector)
|
||||||
selector_path_unref (info->path);
|
_gtk_css_selector_free (info->selector);
|
||||||
|
|
||||||
g_slice_free (SelectorStyleInfo, info);
|
g_slice_free (SelectorStyleInfo, info);
|
||||||
}
|
}
|
||||||
@ -1132,8 +958,7 @@ gtk_css_scanner_reset (GtkCssScanner *scanner)
|
|||||||
g_slist_free (scanner->state);
|
g_slist_free (scanner->state);
|
||||||
scanner->state = NULL;
|
scanner->state = NULL;
|
||||||
|
|
||||||
g_slist_foreach (scanner->cur_selectors, (GFunc) selector_path_unref, NULL);
|
g_slist_free_full (scanner->cur_selectors, (GDestroyNotify) _gtk_css_selector_free);
|
||||||
g_slist_free (scanner->cur_selectors);
|
|
||||||
scanner->cur_selectors = NULL;
|
scanner->cur_selectors = NULL;
|
||||||
|
|
||||||
if (scanner->cur_properties)
|
if (scanner->cur_properties)
|
||||||
@ -1252,196 +1077,10 @@ gtk_css_provider_init (GtkCssProvider *css_provider)
|
|||||||
(GDestroyNotify) gtk_symbolic_color_unref);
|
(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;
|
typedef struct StylePriorityInfo StylePriorityInfo;
|
||||||
|
|
||||||
struct StylePriorityInfo
|
struct StylePriorityInfo
|
||||||
{
|
{
|
||||||
guint64 score;
|
|
||||||
GHashTable *style;
|
GHashTable *style;
|
||||||
GtkStateFlags state;
|
GtkStateFlags state;
|
||||||
};
|
};
|
||||||
@ -1452,7 +1091,7 @@ css_provider_get_selectors (GtkCssProvider *css_provider,
|
|||||||
{
|
{
|
||||||
GtkCssProviderPrivate *priv;
|
GtkCssProviderPrivate *priv;
|
||||||
GArray *priority_info;
|
GArray *priority_info;
|
||||||
guint i, j;
|
guint i;
|
||||||
|
|
||||||
priv = css_provider->priv;
|
priv = css_provider->priv;
|
||||||
priority_info = g_array_new (FALSE, FALSE, sizeof (StylePriorityInfo));
|
priority_info = g_array_new (FALSE, FALSE, sizeof (StylePriorityInfo));
|
||||||
@ -1461,35 +1100,16 @@ css_provider_get_selectors (GtkCssProvider *css_provider,
|
|||||||
{
|
{
|
||||||
SelectorStyleInfo *info;
|
SelectorStyleInfo *info;
|
||||||
StylePriorityInfo new;
|
StylePriorityInfo new;
|
||||||
gboolean added = FALSE;
|
|
||||||
guint64 score;
|
|
||||||
|
|
||||||
info = g_ptr_array_index (priv->selectors_info, i);
|
info = g_ptr_array_index (priv->selectors_info, i);
|
||||||
score = compare_selector (path, info->path);
|
|
||||||
|
|
||||||
if (score <= 0)
|
if (_gtk_css_selector_matches (info->selector, path))
|
||||||
continue;
|
|
||||||
|
|
||||||
new.score = score;
|
|
||||||
new.style = info->style;
|
|
||||||
new.state = info->path->state;
|
|
||||||
|
|
||||||
for (j = 0; j < priority_info->len; j++)
|
|
||||||
{
|
{
|
||||||
StylePriorityInfo *cur;
|
new.style = info->style;
|
||||||
|
new.state = _gtk_css_selector_get_state_flags (info->selector);
|
||||||
|
|
||||||
cur = &g_array_index (priority_info, StylePriorityInfo, j);
|
g_array_append_val (priority_info, new);
|
||||||
|
|
||||||
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;
|
return priority_info;
|
||||||
@ -1728,18 +1348,24 @@ css_provider_commit (GtkCssProvider *css_provider,
|
|||||||
priv = css_provider->priv;
|
priv = css_provider->priv;
|
||||||
|
|
||||||
if (g_hash_table_size (properties) == 0)
|
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)
|
for (l = selectors; l; l = l->next)
|
||||||
{
|
{
|
||||||
SelectorPath *path = l->data;
|
GtkCssSelector *selector = l->data;
|
||||||
SelectorStyleInfo *info;
|
SelectorStyleInfo *info;
|
||||||
|
|
||||||
info = selector_style_info_new (path);
|
info = selector_style_info_new (selector);
|
||||||
selector_style_info_set_style (info, properties);
|
selector_style_info_set_style (info, properties);
|
||||||
|
|
||||||
g_ptr_array_add (priv->selectors_info, info);
|
g_ptr_array_add (priv->selectors_info, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_hash_table_unref (properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -1990,7 +1616,8 @@ parse_at_keyword (GtkCssScanner *scanner)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
parse_selector_pseudo_class (GtkCssScanner *scanner, SelectorPath *path)
|
parse_selector_pseudo_class (GtkCssScanner *scanner,
|
||||||
|
GtkStateFlags *flags_to_modify)
|
||||||
{
|
{
|
||||||
struct {
|
struct {
|
||||||
const char *name;
|
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))
|
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;
|
return TRUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2025,9 +1660,12 @@ parse_selector_pseudo_class (GtkCssScanner *scanner, SelectorPath *path)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
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)
|
if (name == NULL)
|
||||||
{
|
{
|
||||||
@ -2035,19 +1673,23 @@ parse_selector_class (GtkCssScanner *scanner, SelectorPath *path)
|
|||||||
scanner,
|
scanner,
|
||||||
GTK_CSS_PROVIDER_ERROR,
|
GTK_CSS_PROVIDER_ERROR,
|
||||||
GTK_CSS_PROVIDER_ERROR_SYNTAX,
|
GTK_CSS_PROVIDER_ERROR_SYNTAX,
|
||||||
"Expected a valid name");
|
"Expected a valid name for class");
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
selector_path_prepend_combinator (path, COMBINATOR_CHILD);
|
qname = g_quark_from_string (name);
|
||||||
selector_path_prepend_class (path, name);
|
g_array_append_val (classes, qname);
|
||||||
|
g_free (name);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
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)
|
if (name == NULL)
|
||||||
{
|
{
|
||||||
@ -2055,19 +1697,20 @@ parse_selector_name (GtkCssScanner *scanner, SelectorPath *path)
|
|||||||
scanner,
|
scanner,
|
||||||
GTK_CSS_PROVIDER_ERROR,
|
GTK_CSS_PROVIDER_ERROR,
|
||||||
GTK_CSS_PROVIDER_ERROR_SYNTAX,
|
GTK_CSS_PROVIDER_ERROR_SYNTAX,
|
||||||
"Expected a valid name");
|
"Expected a valid name for id");
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
selector_path_prepend_combinator (path, COMBINATOR_CHILD);
|
qname = g_quark_from_string (name);
|
||||||
selector_path_prepend_name (path, name);
|
g_array_append_val (names, qname);
|
||||||
|
g_free (name);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
parse_selector_pseudo_class_for_region (GtkCssScanner *scanner,
|
parse_selector_pseudo_class_for_region (GtkCssScanner *scanner,
|
||||||
SelectorPath *path,
|
GtkRegionFlags *flags_to_modify,
|
||||||
GtkRegionFlags *flags_to_modify)
|
GtkStateFlags *state_to_modify)
|
||||||
{
|
{
|
||||||
struct {
|
struct {
|
||||||
const char *name;
|
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))
|
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++)
|
for (i = 0; i < G_N_ELEMENTS (nth_child); i++)
|
||||||
{
|
{
|
||||||
@ -2129,60 +1772,54 @@ parse_selector_pseudo_class_for_region (GtkCssScanner *scanner,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
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;
|
gboolean parsed_something;
|
||||||
|
|
||||||
name = _gtk_css_parser_try_ident (scanner->parser, FALSE);
|
*name = _gtk_css_parser_try_ident (scanner->parser, FALSE);
|
||||||
if (name)
|
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))
|
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);
|
g_free (name);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selector_path_prepend_region (path, name, flags);
|
|
||||||
g_free (name);
|
|
||||||
_gtk_css_parser_skip_whitespace (scanner->parser);
|
_gtk_css_parser_skip_whitespace (scanner->parser);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
parsed_something = TRUE;
|
||||||
selector_path_prepend_type (path, name);
|
|
||||||
parsed_something = TRUE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
parsed_something = _gtk_css_parser_try (scanner->parser, "*", FALSE);
|
parsed_something = _gtk_css_parser_try (scanner->parser, "*", FALSE);
|
||||||
selector_path_prepend_glob (path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if (_gtk_css_parser_try (scanner->parser, "#", FALSE))
|
if (_gtk_css_parser_try (scanner->parser, "#", FALSE))
|
||||||
{
|
{
|
||||||
if (!parse_selector_name (scanner, path))
|
if (!parse_selector_name (scanner, ids))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
else if (_gtk_css_parser_try (scanner->parser, ".", FALSE))
|
else if (_gtk_css_parser_try (scanner->parser, ".", FALSE))
|
||||||
{
|
{
|
||||||
if (!parse_selector_class (scanner, path))
|
if (!parse_selector_class (scanner, classes))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
else if (_gtk_css_parser_try (scanner->parser, ":", 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;
|
return FALSE;
|
||||||
}
|
}
|
||||||
else if (!parsed_something)
|
else if (!parsed_something)
|
||||||
@ -2205,29 +1842,48 @@ parse_simple_selector (GtkCssScanner *scanner, SelectorPath *path)
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static SelectorPath *
|
static GtkCssSelector *
|
||||||
parse_selector (GtkCssScanner *scanner)
|
parse_selector (GtkCssScanner *scanner)
|
||||||
{
|
{
|
||||||
SelectorPath *path;
|
GtkCssSelector *selector = NULL;
|
||||||
|
|
||||||
path = selector_path_new ();
|
|
||||||
|
|
||||||
do {
|
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;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_gtk_css_parser_try (scanner->parser, ">", TRUE))
|
selector = _gtk_css_selector_new (selector,
|
||||||
selector_path_prepend_combinator (path, COMBINATOR_CHILD);
|
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 &&
|
while (!_gtk_css_parser_is_eof (scanner->parser) &&
|
||||||
!_gtk_css_parser_is_eof (scanner->parser) &&
|
|
||||||
!_gtk_css_parser_begins_with (scanner->parser, ',') &&
|
!_gtk_css_parser_begins_with (scanner->parser, ',') &&
|
||||||
!_gtk_css_parser_begins_with (scanner->parser, '{'));
|
!_gtk_css_parser_begins_with (scanner->parser, '{'));
|
||||||
|
|
||||||
return path;
|
return selector;
|
||||||
}
|
}
|
||||||
|
|
||||||
static GSList *
|
static GSList *
|
||||||
@ -2236,16 +1892,16 @@ parse_selector_list (GtkCssScanner *scanner)
|
|||||||
GSList *selectors = NULL;
|
GSList *selectors = NULL;
|
||||||
|
|
||||||
do {
|
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);
|
_gtk_css_parser_resync (scanner->parser, FALSE, 0);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectors = g_slist_prepend (selectors, path);
|
selectors = g_slist_prepend (selectors, select);
|
||||||
}
|
}
|
||||||
while (_gtk_css_parser_try (scanner->parser, ",", TRUE));
|
while (_gtk_css_parser_try (scanner->parser, ",", TRUE));
|
||||||
|
|
||||||
@ -2412,7 +2068,7 @@ parse_ruleset (GtkCssScanner *scanner)
|
|||||||
GTK_CSS_PROVIDER_ERROR_SYNTAX,
|
GTK_CSS_PROVIDER_ERROR_SYNTAX,
|
||||||
"expected '{' after selectors");
|
"expected '{' after selectors");
|
||||||
_gtk_css_parser_resync (scanner->parser, FALSE, 0);
|
_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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2428,7 +2084,7 @@ parse_ruleset (GtkCssScanner *scanner)
|
|||||||
if (!_gtk_css_parser_is_eof (scanner->parser))
|
if (!_gtk_css_parser_is_eof (scanner->parser))
|
||||||
{
|
{
|
||||||
_gtk_css_parser_resync (scanner->parser, FALSE, 0);
|
_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)
|
if (properties)
|
||||||
g_hash_table_unref (properties);
|
g_hash_table_unref (properties);
|
||||||
return;
|
return;
|
||||||
@ -2436,11 +2092,9 @@ parse_ruleset (GtkCssScanner *scanner)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (properties)
|
if (properties)
|
||||||
{
|
css_provider_commit (scanner->provider, selectors, properties);
|
||||||
css_provider_commit (scanner->provider, selectors, properties);
|
else
|
||||||
g_hash_table_unref (properties);
|
g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free);
|
||||||
}
|
|
||||||
g_slist_free_full (selectors, (GDestroyNotify) selector_path_unref);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -2453,7 +2107,7 @@ parse_statement (GtkCssScanner *scanner)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
parse_stylesheet (GtkCssScanner *scanner)
|
parse_stylesheet (GtkCssScanner *scanner)
|
||||||
{
|
{
|
||||||
_gtk_css_parser_skip_whitespace (scanner->parser);
|
_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
|
static gboolean
|
||||||
gtk_css_provider_load_internal (GtkCssProvider *css_provider,
|
gtk_css_provider_load_internal (GtkCssProvider *css_provider,
|
||||||
GtkCssScanner *parent,
|
GtkCssScanner *parent,
|
||||||
@ -2528,6 +2212,9 @@ gtk_css_provider_load_internal (GtkCssProvider *css_provider,
|
|||||||
parse_stylesheet (scanner);
|
parse_stylesheet (scanner);
|
||||||
|
|
||||||
gtk_css_scanner_destroy (scanner);
|
gtk_css_scanner_destroy (scanner);
|
||||||
|
|
||||||
|
if (parent == NULL)
|
||||||
|
gtk_css_provider_postprocess (css_provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error)
|
if (error)
|
||||||
@ -3132,110 +2819,6 @@ gtk_css_provider_get_named (const gchar *name,
|
|||||||
return provider;
|
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
|
static void
|
||||||
selector_style_info_print (const SelectorStyleInfo *info,
|
selector_style_info_print (const SelectorStyleInfo *info,
|
||||||
GString *str)
|
GString *str)
|
||||||
@ -3243,7 +2826,7 @@ selector_style_info_print (const SelectorStyleInfo *info,
|
|||||||
GList *keys, *walk;
|
GList *keys, *walk;
|
||||||
char *s;
|
char *s;
|
||||||
|
|
||||||
selector_path_print (info->path, str);
|
_gtk_css_selector_print (info->selector, str);
|
||||||
|
|
||||||
g_string_append (str, " {\n");
|
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