]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssselector.c
d300d41baaedd128811c7c1ba30dcc78c47b7ce1
[~andy/gtk] / gtk / gtkcssselector.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2011 Benjamin Otte <otte@gnome.org>
3  *
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.
8  *
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.
13  *
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.
18  */
19
20 #include "config.h"
21
22 #include "gtkcssselectorprivate.h"
23
24 #include "gtkstylecontextprivate.h"
25
26 struct _GtkCssSelector
27 {
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) */
36 };
37
38 GtkCssSelector *
39 _gtk_css_selector_new (GtkCssSelector         *previous,
40                        GtkCssCombinator        combine,
41                        const char *            name,
42                        GQuark *                ids,
43                        GQuark *                classes,
44                        GtkRegionFlags          pseudo_classes,
45                        GtkStateFlags           state)
46 {
47   GtkCssSelector *selector;
48
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;
54   selector->ids = ids;
55   selector->classes = classes;
56   selector->pseudo_classes = pseudo_classes;
57   selector->state = state;
58
59   return selector;
60 }
61
62 void
63 _gtk_css_selector_free (GtkCssSelector *selector)
64 {
65   g_return_if_fail (selector != NULL);
66
67   if (selector->previous)
68     _gtk_css_selector_free (selector->previous);
69
70   g_free (selector->ids);
71   g_free (selector->classes);
72
73   g_slice_free (GtkCssSelector, selector);
74 }
75
76 void
77 _gtk_css_selector_print (const GtkCssSelector *selector,
78                          GString *             str)
79 {
80   if (selector->previous)
81     {
82       _gtk_css_selector_print (selector->previous, str);
83       switch (selector->combine)
84         {
85           case GTK_CSS_COMBINE_DESCANDANT:
86             g_string_append (str, " ");
87             break;
88           case GTK_CSS_COMBINE_CHILD:
89             g_string_append (str, " > ");
90             break;
91           default:
92             g_assert_not_reached ();
93         }
94     }
95
96   if (selector->name)
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, "*");
103
104   if (selector->ids)
105     {
106       GQuark *id;
107
108       for (id = selector->ids; *id != 0; id++)
109         {
110           g_string_append_c (str, '#');
111           g_string_append (str, g_quark_to_string (*id));
112         }
113     }
114
115   if (selector->classes)
116     {
117       GQuark *class;
118
119       for (class = selector->classes; *class != 0; class++)
120         {
121           g_string_append_c (str, '.');
122           g_string_append (str, g_quark_to_string (*class));
123         }
124     }
125
126   if (selector->pseudo_classes)
127     {
128       static const char * flag_names[] = {
129         "nth-child(even)",
130         "nth-child(odd)",
131         "first-child",
132         "last-child",
133         "sorted"
134       };
135       guint i;
136
137       for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
138         {
139           if (selector->pseudo_classes & (1 << i))
140             {
141               g_string_append_c (str, ':');
142               g_string_append (str, flag_names[i]);
143             }
144         }
145     }
146
147   if (selector->state)
148     {
149       static const char * state_names[] = {
150         "active",
151         "hover",
152         "selected",
153         "insensitive",
154         "inconsistent",
155         "focus"
156       };
157       guint i;
158
159       for (i = 0; i < G_N_ELEMENTS (state_names); i++)
160         {
161           if (selector->state & (1 << i))
162             {
163               g_string_append_c (str, ':');
164               g_string_append (str, state_names[i]);
165             }
166         }
167     }
168 }
169
170 char *
171 _gtk_css_selector_to_string (const GtkCssSelector *selector)
172 {
173   GString *string;
174
175   g_return_val_if_fail (selector != NULL, NULL);
176
177   string = g_string_new (NULL);
178
179   _gtk_css_selector_print (selector, string);
180
181   return g_string_free (string, FALSE);
182 }
183
184 static GtkRegionFlags
185 compute_region_flags_for_index (const GtkWidgetPath *path,
186                                 guint                id)
187 {
188   const GtkWidgetPath *siblings;
189   guint sibling_id, n_siblings;
190   GtkRegionFlags flags;
191   
192   siblings = gtk_widget_path_iter_get_siblings (path, id);
193   if (siblings == NULL)
194     return 0;
195
196   sibling_id = gtk_widget_path_iter_get_sibling_index (path, id);
197   n_siblings = gtk_widget_path_length (siblings);
198
199   flags = (sibling_id % 2) ? GTK_REGION_EVEN : GTK_REGION_ODD;
200   if (sibling_id == 0)
201     flags |= GTK_REGION_FIRST;
202   if (sibling_id + 1 == n_siblings)
203     flags |= GTK_REGION_LAST;
204
205   return flags;
206 }
207
208 static gboolean
209 gtk_css_selector_matches_type (const GtkCssSelector      *selector,
210                                const GtkWidgetPath       *path,
211                                guint                      id)
212 {
213   if (selector->pseudo_classes)
214     {
215       GtkRegionFlags flags = compute_region_flags_for_index (path, id);
216
217       if ((selector->pseudo_classes & flags) != selector->pseudo_classes)
218         return FALSE;
219     }
220
221   if (selector->name == NULL)
222     return TRUE;
223
224   if (selector->type == G_TYPE_NONE)
225     return FALSE;
226
227   /* ugh, assigning to a const variable */
228   if (selector->type == G_TYPE_INVALID)
229     ((GtkCssSelector *) selector)->type = g_type_from_name (selector->name);
230
231   if (selector->type == G_TYPE_INVALID)
232     return FALSE;
233
234   return g_type_is_a (gtk_widget_path_iter_get_object_type (path, id), selector->type);
235 }
236
237 static gboolean
238 gtk_css_selector_matches_region (const GtkCssSelector      *selector,
239                                  const GtkWidgetPath       *path,
240                                  guint                      id,
241                                  const char *               region)
242 {
243   GtkRegionFlags flags;
244
245   if (selector->name == NULL)
246     return TRUE;
247   
248   if (selector->name != region)
249     return FALSE;
250
251   if (!gtk_widget_path_iter_has_region (path, id, region, &flags))
252     {
253       /* This function must be called with existing regions */
254       g_assert_not_reached ();
255     }
256
257   return (selector->pseudo_classes & flags) == selector->pseudo_classes;
258 }
259
260 static gboolean
261 gtk_css_selector_matches_rest (const GtkCssSelector      *selector,
262                                const GtkWidgetPath       *path,
263                                guint                      id)
264 {
265   if (selector->ids)
266     {
267       GQuark *name;
268       
269       for (name = selector->ids; *name; name++)
270         {
271           if (!gtk_widget_path_iter_has_qname (path, id, *name))
272             return FALSE;
273         }
274     }
275
276   if (selector->classes)
277     {
278       GQuark *class;
279       
280       for (class = selector->classes; *class; class++)
281         {
282           if (!gtk_widget_path_iter_has_qclass (path, id, *class))
283             return FALSE;
284         }
285     }
286
287   return TRUE;
288 }
289
290 static gboolean
291 gtk_css_selector_matches_previous (const GtkCssSelector      *selector,
292                                    const GtkWidgetPath       *path,
293                                    guint                      id,
294                                    GSList                    *regions);
295
296 static gboolean
297 gtk_css_selector_matches_from (const GtkCssSelector      *selector,
298                                const GtkWidgetPath       *path,
299                                guint                      id,
300                                GSList                    *regions)
301 {
302   GSList *l;
303
304   if (!gtk_css_selector_matches_rest (selector, path, id))
305     return FALSE;
306
307   for (l = regions; l; l = l->next)
308     {
309       const char *region = l->data;
310
311       if (gtk_css_selector_matches_region (selector, path, id, region))
312         {
313           GSList *remaining;
314           gboolean match;
315
316           remaining = g_slist_copy (regions);
317           remaining = g_slist_remove (remaining, region);
318           match = gtk_css_selector_matches_previous (selector,
319                                                      path,
320                                                      id,
321                                                      remaining);
322           g_slist_free (remaining);
323           if (match)
324             return TRUE;
325         }
326     }
327
328   if (gtk_css_selector_matches_type (selector, path, id))
329     {
330       GSList *regions;
331       gboolean match;
332       
333       if (id <= 0)
334         return selector->previous == NULL;
335
336       regions = gtk_widget_path_iter_list_regions (path, id - 1);
337       match = gtk_css_selector_matches_previous (selector,
338                                                  path,
339                                                  id - 1,
340                                                  regions);
341       g_slist_free (regions);
342       return match;
343     }
344
345   return FALSE;
346 }
347
348 static gboolean
349 gtk_css_selector_matches_previous (const GtkCssSelector      *selector,
350                                    const GtkWidgetPath       *path,
351                                    guint                      id,
352                                    GSList                    *regions)
353 {
354   if (!selector->previous)
355     return TRUE;
356
357   if (gtk_css_selector_matches_from (selector->previous,
358                                      path,
359                                      id,
360                                      regions))
361     return TRUE;
362
363   if (selector->combine == GTK_CSS_COMBINE_DESCANDANT)
364     {
365       /* with this magic we run the loop while id >= 0 */
366       while (id-- != 0)
367         {
368           GSList *list;
369           gboolean match;
370
371           list = gtk_widget_path_iter_list_regions (path, id);
372           match = gtk_css_selector_matches_from (selector->previous,
373                                                  path,
374                                                  id,
375                                                  list);
376           g_slist_free (list);
377           if (match)
378             return TRUE;
379         }
380     }
381
382   return FALSE;
383 }
384
385 /**
386  * _gtk_css_selector_matches:
387  * @selector: the selector
388  * @path: the path to check
389  * @length: How many elements of the path are to be used
390  *
391  * Checks if the @selector matches the given @path. If @length is
392  * smaller than the number of elements in @path, it is assumed that
393  * only the first @length element of @path are valid and the rest
394  * does not exist. This is useful for doing parent matches for the
395  * 'inherit' keyword.
396  *
397  * Returns: %TRUE if the selector matches @path
398  **/
399 gboolean
400 _gtk_css_selector_matches (const GtkCssSelector      *selector,
401                            const GtkWidgetPath       *path,
402                            guint                      length)
403 {
404   GSList *list;
405   gboolean match;
406
407   g_return_val_if_fail (selector != NULL, FALSE);
408   g_return_val_if_fail (path != NULL, FALSE);
409   g_return_val_if_fail (length <= gtk_widget_path_length (path), FALSE);
410
411   if (length == 0)
412     return FALSE;
413
414   list = gtk_widget_path_iter_list_regions (path, length - 1);
415   match = gtk_css_selector_matches_from (selector,
416                                          path,
417                                          length - 1,
418                                          list);
419   g_slist_free (list);
420   return match;
421 }
422
423 static guint
424 count_bits (guint v)
425 {
426   /* http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel */
427   v = v - ((v >> 1) & 0x55555555);
428   v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
429   return (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
430 }
431
432 /* Computes specificity according to CSS 2.1.
433  * The arguments must be initialized to 0 */
434 static void
435 _gtk_css_selector_get_specificity (const GtkCssSelector *selector,
436                                    guint                *ids,
437                                    guint                *classes,
438                                    guint                *elements)
439 {
440   GQuark *count;
441
442   if (selector->previous)
443     _gtk_css_selector_get_specificity (selector->previous, ids, classes, elements);
444
445   if (selector->ids)
446     for (count = selector->ids; *count; count++)
447       (*ids)++;
448
449   if (selector->classes)
450     for (count = selector->classes; *count; count++)
451       (*classes)++;
452   
453   *classes += count_bits (selector->state) + count_bits (selector->pseudo_classes);
454
455   if (selector->name)
456     (*elements)++;
457 }
458
459 int
460 _gtk_css_selector_compare (const GtkCssSelector *a,
461                            const GtkCssSelector *b)
462 {
463   guint a_ids = 0, a_classes = 0, a_elements = 0;
464   guint b_ids = 0, b_classes = 0, b_elements = 0;
465   int compare;
466
467   _gtk_css_selector_get_specificity (a, &a_ids, &a_classes, &a_elements);
468   _gtk_css_selector_get_specificity (b, &b_ids, &b_classes, &b_elements);
469
470   compare = a_ids - b_ids;
471   if (compare)
472     return compare;
473
474   compare = a_classes - b_classes;
475   if (compare)
476     return compare;
477
478   return a_elements - b_elements;
479 }
480
481 GtkStateFlags
482 _gtk_css_selector_get_state_flags (GtkCssSelector *selector)
483 {
484   g_return_val_if_fail (selector != NULL, 0);
485
486   return selector->state;
487 }
488