selector: Rewrite position tracking

We now track the position as a (type,a,b) tuple where the numbers make
up the an + b formula from CSS3 nth-child.

Also, the get_sibling() and get_sibling_index() vfuncs were replaced by
a has_position() vfunc. This is mostly so that the matcher can always
return TRUE. And I need that for the everything matcher.
This commit is contained in:
Benjamin Otte 2012-03-18 02:11:44 +01:00
parent 8dbe8c8349
commit 3bdde54aaf
3 changed files with 349 additions and 165 deletions

View File

@ -146,22 +146,33 @@ gtk_css_matcher_widget_path_has_region (const GtkCssMatcher *matcher,
return TRUE;
}
static guint
gtk_css_matcher_widget_path_get_sibling_index (const GtkCssMatcher *matcher)
{
return matcher->path.sibling_index;
}
static guint
gtk_css_matcher_widget_path_get_n_siblings (const GtkCssMatcher *matcher)
static gboolean
gtk_css_matcher_widget_path_has_position (const GtkCssMatcher *matcher,
gboolean forward,
int a,
int b)
{
const GtkWidgetPath *siblings;
int x;
siblings = gtk_widget_path_iter_get_siblings (matcher->path.path, matcher->path.index);
if (!siblings)
return 0;
return FALSE;
return gtk_widget_path_length (siblings);
if (forward)
x = matcher->path.sibling_index + 1;
else
x = gtk_widget_path_length (siblings) - matcher->path.sibling_index;
x -= b;
if (a == 0)
return x == 0;
if (x % a)
return FALSE;
return x / a > 0;
}
static const GtkCssMatcherClass GTK_CSS_MATCHER_WIDGET_PATH = {
@ -173,8 +184,7 @@ static const GtkCssMatcherClass GTK_CSS_MATCHER_WIDGET_PATH = {
gtk_css_matcher_widget_path_has_id,
gtk_css_matcher_widget_path_has_regions,
gtk_css_matcher_widget_path_has_region,
gtk_css_matcher_widget_path_get_sibling_index,
gtk_css_matcher_widget_path_get_n_siblings
gtk_css_matcher_widget_path_has_position,
};
void

View File

@ -44,8 +44,10 @@ struct _GtkCssMatcherClass {
gboolean (* has_region) (const GtkCssMatcher *matcher,
const char *region,
GtkRegionFlags flags);
guint (* get_sibling_index) (const GtkCssMatcher *matcher);
guint (* get_n_siblings) (const GtkCssMatcher *matcher);
gboolean (* has_position) (const GtkCssMatcher *matcher,
gboolean forward,
int a,
int b);
};
struct _GtkCssMatcherWidgetPath {
@ -122,15 +124,12 @@ _gtk_css_matcher_has_region (const GtkCssMatcher *matcher,
}
static inline guint
_gtk_css_matcher_get_sibling_index (const GtkCssMatcher *matcher)
_gtk_css_matcher_has_position (const GtkCssMatcher *matcher,
gboolean forward,
int a,
int b)
{
return matcher->klass->get_sibling_index (matcher);
}
static inline guint
_gtk_css_matcher_get_n_siblings (const GtkCssMatcher *matcher)
{
return matcher->klass->get_n_siblings (matcher);
return matcher->klass->has_position (matcher, forward, a, b);
}

View File

@ -472,33 +472,140 @@ static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_STATE = {
/* PSEUDOCLASS FOR POSITION */
typedef enum {
POSITION_FORWARD,
POSITION_BACKWARD,
POSITION_ONLY,
POSITION_SORTED
} PositionType;
#define POSITION_TYPE_BITS 2
#define POSITION_NUMBER_BITS ((sizeof (gpointer) * 8 - POSITION_TYPE_BITS) / 2)
static gconstpointer
encode_position (PositionType type,
int a,
int b)
{
union {
gconstpointer p;
struct {
gssize type :POSITION_TYPE_BITS;
gssize a :POSITION_NUMBER_BITS;
gssize b :POSITION_NUMBER_BITS;
} data;
} result;
G_STATIC_ASSERT (sizeof (gconstpointer) == sizeof (result));
g_assert (type < (1 << POSITION_TYPE_BITS));
result.data.type = type;
result.data.a = a;
result.data.b = b;
return result.p;
}
static void
decode_position (const GtkCssSelector *selector,
PositionType *type,
int *a,
int *b)
{
union {
gconstpointer p;
struct {
gssize type :POSITION_TYPE_BITS;
gssize a :POSITION_NUMBER_BITS;
gssize b :POSITION_NUMBER_BITS;
} data;
} result;
G_STATIC_ASSERT (sizeof (gconstpointer) == sizeof (result));
result.p = selector->data;
*type = result.data.type & ((1 << POSITION_TYPE_BITS) - 1);
*a = result.data.a;
*b = result.data.b;
}
static void
gtk_css_selector_pseudoclass_position_print (const GtkCssSelector *selector,
GString *string)
{
static const char * flag_names[] = {
"nth-child(even)",
"nth-child(odd)",
"first-child",
"last-child",
"only-child",
"sorted"
};
guint i, state;
PositionType type;
int a, b;
state = GPOINTER_TO_UINT (selector->data);
g_string_append_c (string, ':');
for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
decode_position (selector, &type, &a, &b);
switch (type)
{
if (state == (1 << i))
case POSITION_FORWARD:
if (a == 0)
{
g_string_append (string, flag_names[i]);
return;
if (b == 1)
g_string_append (string, ":first-child");
else
g_string_append_printf (string, ":nth-child(%d)", b);
}
else if (a == 2 && b == 0)
g_string_append (string, ":nth-child(even)");
else if (a == 2 && b == 1)
g_string_append (string, ":nth-child(odd)");
else
{
g_string_append (string, ":nth-child(");
if (a == 1)
g_string_append (string, "n");
else if (a == -1)
g_string_append (string, "-n");
else
g_string_append_printf (string, "%dn", a);
if (b > 0)
g_string_append_printf (string, "+%d)", b);
else if (b < 0)
g_string_append_printf (string, "%d)", b);
else
g_string_append (string, ")");
}
break;
case POSITION_BACKWARD:
if (a == 0)
{
if (b == 1)
g_string_append (string, ":last-child");
else
g_string_append_printf (string, ":nth-last-child(%d)", b);
}
else if (a == 2 && b == 0)
g_string_append (string, ":nth-last-child(even)");
else if (a == 2 && b == 1)
g_string_append (string, ":nth-last-child(odd)");
else
{
g_string_append (string, ":nth-last-child(");
if (a == 1)
g_string_append (string, "n");
else if (a == -1)
g_string_append (string, "-n");
else
g_string_append_printf (string, "%dn", a);
if (b > 0)
g_string_append_printf (string, "+%d)", b);
else if (b < 0)
g_string_append_printf (string, "%d)", b);
else
g_string_append (string, ")");
}
break;
case POSITION_ONLY:
g_string_append (string, ":only-child");
break;
case POSITION_SORTED:
g_string_append (string, ":sorted");
break;
default:
g_assert_not_reached ();
break;
}
g_assert_not_reached ();
}
static gboolean
@ -507,8 +614,38 @@ gtk_css_selector_pseudoclass_position_match_for_region (const GtkCssSelector *se
{
GtkRegionFlags selector_flags;
const GtkCssSelector *previous;
selector_flags = GPOINTER_TO_UINT (selector->data);
PositionType type;
int a, b;
decode_position (selector, &type, &a, &b);
switch (type)
{
case POSITION_FORWARD:
if (a == 0 && b == 1)
selector_flags = GTK_REGION_FIRST;
else if (a == 2 && b == 0)
selector_flags = GTK_REGION_EVEN;
else if (a == 2 && b == 1)
selector_flags = GTK_REGION_ODD;
else
return FALSE;
break;
case POSITION_BACKWARD:
if (a == 0 && b == 1)
selector_flags = GTK_REGION_LAST;
else
return FALSE;
break;
case POSITION_ONLY:
selector_flags = GTK_REGION_ONLY;
break;
case POSITION_SORTED:
selector_flags = GTK_REGION_SORTED;
break;
default:
g_assert_not_reached ();
break;
}
selector = gtk_css_selector_previous (selector);
if (!_gtk_css_matcher_has_region (matcher, selector->data, selector_flags))
@ -526,44 +663,31 @@ static gboolean
gtk_css_selector_pseudoclass_position_match (const GtkCssSelector *selector,
const GtkCssMatcher *matcher)
{
GtkRegionFlags region;
guint sibling, n_siblings;
const GtkCssSelector *previous;
PositionType type;
int a, b;
previous = gtk_css_selector_previous (selector);
if (previous && previous->class == &GTK_CSS_SELECTOR_REGION)
return gtk_css_selector_pseudoclass_position_match_for_region (selector, matcher);
n_siblings = _gtk_css_matcher_get_n_siblings (matcher);
if (n_siblings == 0)
return FALSE;
sibling = _gtk_css_matcher_get_sibling_index (matcher);
region = GPOINTER_TO_UINT (selector->data);
switch (region)
decode_position (selector, &type, &a, &b);
switch (type)
{
case GTK_REGION_EVEN:
if (!(sibling % 2))
case POSITION_FORWARD:
if (!_gtk_css_matcher_has_position (matcher, TRUE, a, b))
return FALSE;
break;
case GTK_REGION_ODD:
if (sibling % 2)
case POSITION_BACKWARD:
if (!_gtk_css_matcher_has_position (matcher, FALSE, a, b))
return FALSE;
break;
case GTK_REGION_FIRST:
if (sibling != 0)
case POSITION_ONLY:
if (!_gtk_css_matcher_has_position (matcher, TRUE, 0, 1) ||
!_gtk_css_matcher_has_position (matcher, FALSE, 0, 1))
return FALSE;
break;
case GTK_REGION_LAST:
if (sibling + 1 != n_siblings)
return FALSE;
break;
case GTK_REGION_ONLY:
if (n_siblings != 1)
return FALSE;
break;
case GTK_REGION_SORTED:
case POSITION_SORTED:
return FALSE;
default:
g_assert_not_reached ();
@ -672,125 +796,176 @@ parse_selector_id (GtkCssParser *parser, GtkCssSelector *selector)
}
static GtkCssSelector *
parse_selector_pseudo_class (GtkCssParser *parser,
GtkCssSelector *selector)
parse_selector_pseudo_class_nth_child (GtkCssParser *parser,
GtkCssSelector *selector,
PositionType type)
{
struct {
const char *name;
GtkRegionFlags region_flag;
GtkStateFlags state_flag;
} pseudo_classes[] = {
{ "first-child", GTK_REGION_FIRST, 0 },
{ "last-child", GTK_REGION_LAST, 0 },
{ "only-child", GTK_REGION_ONLY, 0 },
{ "sorted", GTK_REGION_SORTED, 0 },
{ "active", 0, GTK_STATE_FLAG_ACTIVE },
{ "prelight", 0, GTK_STATE_FLAG_PRELIGHT },
{ "hover", 0, GTK_STATE_FLAG_PRELIGHT },
{ "selected", 0, GTK_STATE_FLAG_SELECTED },
{ "insensitive", 0, GTK_STATE_FLAG_INSENSITIVE },
{ "inconsistent", 0, GTK_STATE_FLAG_INCONSISTENT },
{ "focused", 0, GTK_STATE_FLAG_FOCUSED },
{ "focus", 0, GTK_STATE_FLAG_FOCUSED },
{ "backdrop", 0, GTK_STATE_FLAG_BACKDROP },
{ NULL, }
}, nth_child_classes[] = {
{ "first", GTK_REGION_FIRST, 0 },
{ "last", GTK_REGION_LAST, 0 },
{ "even", GTK_REGION_EVEN, 0 },
{ "odd", GTK_REGION_ODD, 0 },
{ NULL, }
}, *classes;
guint i;
char *name;
GError *error;
int a, b;
name = _gtk_css_parser_try_ident (parser, FALSE);
if (name == NULL)
if (!_gtk_css_parser_try (parser, "(", TRUE))
{
_gtk_css_parser_error (parser, "Missing name of pseudo-class");
_gtk_css_parser_error (parser, "Missing opening bracket for pseudo-class");
if (selector)
_gtk_css_selector_free (selector);
return NULL;
}
if (_gtk_css_parser_try (parser, "(", TRUE))
if (_gtk_css_parser_try (parser, "even", TRUE))
{
char *function = name;
name = _gtk_css_parser_try_ident (parser, TRUE);
if (!_gtk_css_parser_try (parser, ")", FALSE))
{
_gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class");
if (selector)
_gtk_css_selector_free (selector);
return NULL;
}
if (g_ascii_strcasecmp (function, "nth-child") != 0)
{
error = g_error_new (GTK_CSS_PROVIDER_ERROR,
GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
"Unknown pseudo-class '%s(%s)'", function, name ? name : "");
_gtk_css_parser_take_error (parser, error);
g_free (function);
g_free (name);
if (selector)
_gtk_css_selector_free (selector);
return NULL;
}
g_free (function);
if (name == NULL)
{
error = g_error_new (GTK_CSS_PROVIDER_ERROR,
GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
"Unknown pseudo-class 'nth-child(%s)'", name);
_gtk_css_parser_take_error (parser, error);
if (selector)
_gtk_css_selector_free (selector);
return NULL;
}
classes = nth_child_classes;
a = 2;
b = 0;
}
else if (_gtk_css_parser_try (parser, "odd", TRUE))
{
a = 2;
b = 1;
}
else if (type == POSITION_FORWARD &&
_gtk_css_parser_try (parser, "first", TRUE))
{
a = 0;
b = 1;
}
else if (type == POSITION_FORWARD &&
_gtk_css_parser_try (parser, "last", TRUE))
{
a = 0;
b = 1;
type = POSITION_BACKWARD;
}
else
classes = pseudo_classes;
for (i = 0; classes[i].name != NULL; i++)
{
if (g_ascii_strcasecmp (name, classes[i].name) == 0)
{
g_free (name);
int multiplier;
if (classes[i].region_flag)
selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION,
selector,
GUINT_TO_POINTER (classes[i].region_flag));
if (_gtk_css_parser_try (parser, "+", TRUE))
multiplier = 1;
else if (_gtk_css_parser_try (parser, "-", TRUE))
multiplier = -1;
else
multiplier = 1;
if (_gtk_css_parser_try_int (parser, &a))
{
if (a < 0)
{
_gtk_css_parser_error (parser, "Expected an integer");
if (selector)
_gtk_css_selector_free (selector);
return NULL;
}
a *= multiplier;
}
else if (_gtk_css_parser_has_prefix (parser, "n"))
{
a = multiplier;
}
else
{
_gtk_css_parser_error (parser, "Expected an integer");
if (selector)
_gtk_css_selector_free (selector);
return NULL;
}
if (_gtk_css_parser_try (parser, "n", TRUE))
{
if (_gtk_css_parser_try (parser, "+", TRUE))
multiplier = 1;
else if (_gtk_css_parser_try (parser, "-", TRUE))
multiplier = -1;
else
multiplier = 1;
if (_gtk_css_parser_try_int (parser, &b))
{
if (b < 0)
{
_gtk_css_parser_error (parser, "Expected an integer");
if (selector)
_gtk_css_selector_free (selector);
return NULL;
}
}
else
b = 0;
b *= multiplier;
}
else
{
b = a;
a = 0;
}
}
if (!_gtk_css_parser_try (parser, ")", FALSE))
{
_gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class");
if (selector)
_gtk_css_selector_free (selector);
return NULL;
}
selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION,
selector,
encode_position (type, a, b));
return selector;
}
static GtkCssSelector *
parse_selector_pseudo_class (GtkCssParser *parser,
GtkCssSelector *selector)
{
static const struct {
const char *name;
GtkStateFlags state_flag;
PositionType position_type;
int position_a;
int position_b;
} pseudo_classes[] = {
{ "first-child", 0, POSITION_FORWARD, 0, 1 },
{ "last-child", 0, POSITION_BACKWARD, 0, 1 },
{ "only-child", 0, POSITION_ONLY, 0, 0 },
{ "sorted", 0, POSITION_SORTED, 0, 0 },
{ "active", GTK_STATE_FLAG_ACTIVE, },
{ "prelight", GTK_STATE_FLAG_PRELIGHT, },
{ "hover", GTK_STATE_FLAG_PRELIGHT, },
{ "selected", GTK_STATE_FLAG_SELECTED, },
{ "insensitive", GTK_STATE_FLAG_INSENSITIVE, },
{ "inconsistent", GTK_STATE_FLAG_INCONSISTENT, },
{ "focused", GTK_STATE_FLAG_FOCUSED, },
{ "focus", GTK_STATE_FLAG_FOCUSED, },
{ "backdrop", GTK_STATE_FLAG_BACKDROP, }
};
guint i;
if (_gtk_css_parser_try (parser, "nth-child", FALSE))
return parse_selector_pseudo_class_nth_child (parser, selector, POSITION_FORWARD);
else if (_gtk_css_parser_try (parser, "nth-last-child", FALSE))
return parse_selector_pseudo_class_nth_child (parser, selector, POSITION_BACKWARD);
for (i = 0; i < G_N_ELEMENTS (pseudo_classes); i++)
{
if (_gtk_css_parser_try (parser, pseudo_classes[i].name, FALSE))
{
if (pseudo_classes[i].state_flag)
selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_PSEUDOCLASS_STATE,
selector,
GUINT_TO_POINTER (classes[i].state_flag));
GUINT_TO_POINTER (pseudo_classes[i].state_flag));
else
selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_PSEUDOCLASS_POSITION,
selector,
encode_position (pseudo_classes[i].position_type,
pseudo_classes[i].position_a,
pseudo_classes[i].position_b));
return selector;
}
}
if (classes == nth_child_classes)
error = g_error_new (GTK_CSS_PROVIDER_ERROR,
GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
"Unknown pseudo-class 'nth-child(%s)'", name);
else
error = g_error_new (GTK_CSS_PROVIDER_ERROR,
GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
"Unknown pseudo-class '%s'", name);
_gtk_css_parser_take_error (parser, error);
g_free (name);
_gtk_css_parser_error (parser, "Missing name of pseudo-class");
if (selector)
_gtk_css_selector_free (selector);
return NULL;
}