1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 2011 Benjamin Otte <otte@gnome.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
22 #include "gtkcssselectorprivate.h"
24 #include "gtkstylecontextprivate.h"
26 struct _GtkCssSelector
28 GtkCssSelector * previous; /* link to next element in selector or NULL if last */
29 GtkCssCombinator combine; /* how to combine with the previous element */
30 const char * name; /* quarked name of element we match or NULL if any */
31 GType type; /* cache for type belonging to name - G_TYPE_INVALID if uncached */
32 GQuark * ids; /* 0-terminated list of required ids or NULL if none */
33 GQuark * classes; /* 0-terminated list of required classes or NULL if none */
34 GtkRegionFlags pseudo_classes; /* required pseudo classes */
35 GtkStateFlags state; /* required state flags (currently not checked when matching) */
39 _gtk_css_selector_new (GtkCssSelector *previous,
40 GtkCssCombinator combine,
44 GtkRegionFlags pseudo_classes,
47 GtkCssSelector *selector;
49 selector = g_slice_new0 (GtkCssSelector);
50 selector->previous = previous;
51 selector->combine = combine;
52 selector->name = name ? g_quark_to_string (g_quark_from_string (name)) : NULL;
53 selector->type = !name || _gtk_style_context_check_region_name (name) ? G_TYPE_NONE : G_TYPE_INVALID;
55 selector->classes = classes;
56 selector->pseudo_classes = pseudo_classes;
57 selector->state = state;
63 _gtk_css_selector_free (GtkCssSelector *selector)
65 g_return_if_fail (selector != NULL);
67 if (selector->previous)
68 _gtk_css_selector_free (selector->previous);
70 g_free (selector->ids);
71 g_free (selector->classes);
73 g_slice_free (GtkCssSelector, selector);
77 _gtk_css_selector_print (const GtkCssSelector *selector,
80 if (selector->previous)
82 _gtk_css_selector_print (selector->previous, str);
83 switch (selector->combine)
85 case GTK_CSS_COMBINE_DESCANDANT:
86 g_string_append (str, " ");
88 case GTK_CSS_COMBINE_CHILD:
89 g_string_append (str, " > ");
92 g_assert_not_reached ();
97 g_string_append (str, selector->name);
98 else if (selector->ids == NULL &&
99 selector->classes == NULL &&
100 selector->pseudo_classes == 0 &&
101 selector->state == 0)
102 g_string_append (str, "*");
108 for (id = selector->ids; *id != 0; id++)
110 g_string_append_c (str, '#');
111 g_string_append (str, g_quark_to_string (*id));
115 if (selector->classes)
119 for (class = selector->classes; *class != 0; class++)
121 g_string_append_c (str, '.');
122 g_string_append (str, g_quark_to_string (*class));
126 if (selector->pseudo_classes)
128 static const char * flag_names[] = {
138 for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
140 if (selector->pseudo_classes & (1 << i))
142 g_string_append_c (str, ':');
143 g_string_append (str, flag_names[i]);
150 static const char * state_names[] = {
160 for (i = 0; i < G_N_ELEMENTS (state_names); i++)
162 if (selector->state & (1 << i))
164 g_string_append_c (str, ':');
165 g_string_append (str, state_names[i]);
172 _gtk_css_selector_to_string (const GtkCssSelector *selector)
176 g_return_val_if_fail (selector != NULL, NULL);
178 string = g_string_new (NULL);
180 _gtk_css_selector_print (selector, string);
182 return g_string_free (string, FALSE);
185 static GtkRegionFlags
186 compute_region_flags_for_index (const GtkWidgetPath *path,
189 const GtkWidgetPath *siblings;
190 guint sibling_id, n_siblings;
191 GtkRegionFlags flags;
193 siblings = gtk_widget_path_iter_get_siblings (path, id);
194 if (siblings == NULL)
197 sibling_id = gtk_widget_path_iter_get_sibling_index (path, id);
198 n_siblings = gtk_widget_path_length (siblings);
200 flags = (sibling_id % 2) ? GTK_REGION_EVEN : GTK_REGION_ODD;
202 flags |= GTK_REGION_FIRST;
203 if (sibling_id + 1 == n_siblings)
204 flags |= GTK_REGION_LAST;
206 flags |= GTK_REGION_ONLY;
212 gtk_css_selector_matches_type (const GtkCssSelector *selector,
213 const GtkWidgetPath *path,
216 if (selector->pseudo_classes)
218 GtkRegionFlags flags = compute_region_flags_for_index (path, id);
220 if ((selector->pseudo_classes & flags) != selector->pseudo_classes)
224 if (selector->name == NULL)
227 if (selector->type == G_TYPE_NONE)
230 /* ugh, assigning to a const variable */
231 if (selector->type == G_TYPE_INVALID)
232 ((GtkCssSelector *) selector)->type = g_type_from_name (selector->name);
234 if (selector->type == G_TYPE_INVALID)
237 return g_type_is_a (gtk_widget_path_iter_get_object_type (path, id), selector->type);
241 gtk_css_selector_matches_region (const GtkCssSelector *selector,
242 const GtkWidgetPath *path,
246 GtkRegionFlags flags;
248 if (selector->name == NULL)
251 if (selector->name != region)
254 if (!gtk_widget_path_iter_has_region (path, id, region, &flags))
256 /* This function must be called with existing regions */
257 g_assert_not_reached ();
260 return (selector->pseudo_classes & flags) == selector->pseudo_classes;
264 gtk_css_selector_matches_rest (const GtkCssSelector *selector,
265 const GtkWidgetPath *path,
272 for (name = selector->ids; *name; name++)
274 if (!gtk_widget_path_iter_has_qname (path, id, *name))
279 if (selector->classes)
283 for (class = selector->classes; *class; class++)
285 if (!gtk_widget_path_iter_has_qclass (path, id, *class))
294 gtk_css_selector_matches_previous (const GtkCssSelector *selector,
295 const GtkWidgetPath *path,
300 gtk_css_selector_matches_from (const GtkCssSelector *selector,
301 const GtkWidgetPath *path,
307 if (!gtk_css_selector_matches_rest (selector, path, id))
310 for (l = regions; l; l = l->next)
312 const char *region = l->data;
314 if (gtk_css_selector_matches_region (selector, path, id, region))
319 remaining = g_slist_copy (regions);
320 remaining = g_slist_remove (remaining, region);
321 match = gtk_css_selector_matches_previous (selector,
325 g_slist_free (remaining);
331 if (gtk_css_selector_matches_type (selector, path, id))
337 return selector->previous == NULL;
339 regions = gtk_widget_path_iter_list_regions (path, id - 1);
340 match = gtk_css_selector_matches_previous (selector,
344 g_slist_free (regions);
352 gtk_css_selector_matches_previous (const GtkCssSelector *selector,
353 const GtkWidgetPath *path,
357 if (!selector->previous)
360 if (gtk_css_selector_matches_from (selector->previous,
366 if (selector->combine == GTK_CSS_COMBINE_DESCANDANT)
368 /* with this magic we run the loop while id >= 0 */
374 list = gtk_widget_path_iter_list_regions (path, id);
375 match = gtk_css_selector_matches_from (selector->previous,
389 * _gtk_css_selector_matches:
390 * @selector: the selector
391 * @path: the path to check
392 * @length: How many elements of the path are to be used
394 * Checks if the @selector matches the given @path. If @length is
395 * smaller than the number of elements in @path, it is assumed that
396 * only the first @length element of @path are valid and the rest
397 * does not exist. This is useful for doing parent matches for the
400 * Returns: %TRUE if the selector matches @path
403 _gtk_css_selector_matches (const GtkCssSelector *selector,
404 const GtkWidgetPath *path,
410 g_return_val_if_fail (selector != NULL, FALSE);
411 g_return_val_if_fail (path != NULL, FALSE);
412 g_return_val_if_fail (length <= gtk_widget_path_length (path), FALSE);
417 list = gtk_widget_path_iter_list_regions (path, length - 1);
418 match = gtk_css_selector_matches_from (selector,
429 /* http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel */
430 v = v - ((v >> 1) & 0x55555555);
431 v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
432 return (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
435 /* Computes specificity according to CSS 2.1.
436 * The arguments must be initialized to 0 */
438 _gtk_css_selector_get_specificity (const GtkCssSelector *selector,
445 if (selector->previous)
446 _gtk_css_selector_get_specificity (selector->previous, ids, classes, elements);
449 for (count = selector->ids; *count; count++)
452 if (selector->classes)
453 for (count = selector->classes; *count; count++)
456 *classes += count_bits (selector->state) + count_bits (selector->pseudo_classes);
463 _gtk_css_selector_compare (const GtkCssSelector *a,
464 const GtkCssSelector *b)
466 guint a_ids = 0, a_classes = 0, a_elements = 0;
467 guint b_ids = 0, b_classes = 0, b_elements = 0;
470 _gtk_css_selector_get_specificity (a, &a_ids, &a_classes, &a_elements);
471 _gtk_css_selector_get_specificity (b, &b_ids, &b_classes, &b_elements);
473 compare = a_ids - b_ids;
477 compare = a_classes - b_classes;
481 return a_elements - b_elements;
485 _gtk_css_selector_get_state_flags (GtkCssSelector *selector)
487 g_return_val_if_fail (selector != NULL, 0);
489 return selector->state;