]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssselector.c
css: Rewrite selectors
[~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 struct _GtkCssSelector
25 {
26   GtkCssSelector *  previous;        /* link to next element in selector or NULL if last */
27   GtkCssCombinator  combine;         /* how to combine with the previous element */
28   const char *      name;            /* quarked name of element we match or NULL if any */
29   GType             type;            /* cache for type belonging to name - G_TYPE_INVALID if uncached */
30   GQuark *          ids;             /* 0-terminated list of required ids or NULL if none */
31   GQuark *          classes;         /* 0-terminated list of required classes or NULL if none */
32   GtkRegionFlags    pseudo_classes;  /* required pseudo classes */
33   GtkStateFlags     state;           /* required state flags (currently not checked when matching) */
34 };
35
36 GtkCssSelector *
37 _gtk_css_selector_new (GtkCssSelector         *previous,
38                        GtkCssCombinator        combine,
39                        const char *            name,
40                        GQuark *                ids,
41                        GQuark *                classes,
42                        GtkRegionFlags          pseudo_classes,
43                        GtkStateFlags           state)
44 {
45   GtkCssSelector *selector;
46
47   selector = g_slice_new0 (GtkCssSelector);
48   selector->previous = previous;
49   selector->combine = combine;
50   selector->name = name ? g_quark_to_string (g_quark_from_string (name)) : NULL;
51   selector->type = G_TYPE_INVALID;
52   selector->ids = ids;
53   selector->classes = classes;
54   selector->pseudo_classes = pseudo_classes;
55   selector->state = state;
56
57   return selector;
58 }
59
60 void
61 _gtk_css_selector_free (GtkCssSelector *selector)
62 {
63   g_return_if_fail (selector != NULL);
64
65   if (selector->previous)
66     _gtk_css_selector_free (selector->previous);
67
68   g_free (selector->ids);
69   g_free (selector->classes);
70
71   g_slice_free (GtkCssSelector, selector);
72 }
73
74 void
75 _gtk_css_selector_print (const GtkCssSelector *selector,
76                          GString *             str)
77 {
78   if (selector->previous)
79     {
80       _gtk_css_selector_print (selector->previous, str);
81       switch (selector->combine)
82         {
83           case GTK_CSS_COMBINE_DESCANDANT:
84             g_string_append (str, " ");
85             break;
86           case GTK_CSS_COMBINE_CHILD:
87             g_string_append (str, " > ");
88             break;
89           default:
90             g_assert_not_reached ();
91         }
92     }
93
94   if (selector->name)
95     g_string_append (str, selector->name);
96   else if (selector->ids == NULL && 
97            selector->classes == NULL && 
98            selector->pseudo_classes == 0 &&
99            selector->state == 0)
100     g_string_append (str, "*");
101
102   if (selector->ids)
103     {
104       GQuark *id;
105
106       for (id = selector->ids; *id != 0; id++)
107         {
108           g_string_append_c (str, '#');
109           g_string_append (str, g_quark_to_string (*id));
110         }
111     }
112
113   if (selector->classes)
114     {
115       GQuark *class;
116
117       for (class = selector->classes; *class != 0; class++)
118         {
119           g_string_append_c (str, '.');
120           g_string_append (str, g_quark_to_string (*class));
121         }
122     }
123
124   if (selector->pseudo_classes)
125     {
126       static const char * flag_names[] = {
127         "nth-child(even)",
128         "nth-child(odd)",
129         "first-child",
130         "last-child",
131         "sorted"
132       };
133       guint i;
134
135       for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
136         {
137           if (selector->pseudo_classes & (1 << i))
138             {
139               g_string_append_c (str, ':');
140               g_string_append (str, flag_names[i]);
141             }
142         }
143     }
144
145   if (selector->state)
146     {
147       static const char * state_names[] = {
148         "active",
149         "hover",
150         "selected",
151         "insensitive",
152         "inconsistent",
153         "focus"
154       };
155       guint i;
156
157       for (i = 0; i < G_N_ELEMENTS (state_names); i++)
158         {
159           if (selector->state & (1 << i))
160             {
161               g_string_append_c (str, ':');
162               g_string_append (str, state_names[i]);
163             }
164         }
165     }
166 }
167
168 char *
169 _gtk_css_selector_to_string (const GtkCssSelector *selector)
170 {
171   GString *string;
172
173   g_return_val_if_fail (selector != NULL, NULL);
174
175   string = g_string_new (NULL);
176
177   _gtk_css_selector_print (selector, string);
178
179   return g_string_free (string, FALSE);
180 }
181
182 static gboolean
183 gtk_css_selector_matches_type (const GtkCssSelector      *selector,
184                                /* const */ GtkWidgetPath *path,
185                                guint                      id)
186 {
187   if (selector->name == NULL)
188     return TRUE;
189
190   if (selector->pseudo_classes)
191     return FALSE;
192
193   /* ugh, assigning to a const variable */
194   if (selector->type == G_TYPE_INVALID)
195     ((GtkCssSelector *) selector)->type = g_type_from_name (selector->name);
196
197   if (selector->type == G_TYPE_INVALID)
198     return FALSE;
199
200   return g_type_is_a (gtk_widget_path_iter_get_object_type (path, id), selector->type);
201 }
202
203 static gboolean
204 gtk_css_selector_matches_region (const GtkCssSelector      *selector,
205                                  /* const */ GtkWidgetPath *path,
206                                  guint                      id,
207                                  const char *               region)
208 {
209   GtkRegionFlags flags;
210
211   if (selector->name == NULL)
212     return TRUE;
213   
214   if (selector->name != region)
215     return FALSE;
216
217   if (!gtk_widget_path_iter_has_region (path, id, region, &flags))
218     {
219       /* This function must be called with existing regions */
220       g_assert_not_reached ();
221     }
222
223   return (selector->pseudo_classes & flags) == selector->pseudo_classes;
224 }
225
226 static gboolean
227 gtk_css_selector_matches_rest (const GtkCssSelector      *selector,
228                                /* const */ GtkWidgetPath *path,
229                                guint                      id)
230 {
231   if (selector->ids)
232     {
233       GQuark *name;
234       
235       for (name = selector->ids; *name; name++)
236         {
237           if (!gtk_widget_path_iter_has_qname (path, id, *name))
238             return FALSE;
239         }
240     }
241
242   if (selector->classes)
243     {
244       GQuark *class;
245       
246       for (class = selector->classes; *class; class++)
247         {
248           if (!gtk_widget_path_iter_has_qclass (path, id, *class))
249             return FALSE;
250         }
251     }
252
253   return TRUE;
254 }
255
256 static gboolean
257 gtk_css_selector_matches_previous (const GtkCssSelector      *selector,
258                                    /* const */ GtkWidgetPath *path,
259                                    guint                      id,
260                                    GSList                    *regions);
261
262 static gboolean
263 gtk_css_selector_matches_from (const GtkCssSelector      *selector,
264                                /* const */ GtkWidgetPath *path,
265                                guint                      id,
266                                GSList                    *regions)
267 {
268   GSList *l;
269
270   if (!gtk_css_selector_matches_rest (selector, path, id))
271     return FALSE;
272
273   for (l = regions; l; l = l->next)
274     {
275       const char *region = l->data;
276
277       if (gtk_css_selector_matches_region (selector, path, id, region))
278         {
279           GSList *remaining;
280           gboolean match;
281
282           remaining = g_slist_copy (regions);
283           remaining = g_slist_remove (remaining, region);
284           match = gtk_css_selector_matches_previous (selector,
285                                                      path,
286                                                      id,
287                                                      remaining);
288           g_slist_free (remaining);
289           if (match)
290             return TRUE;
291         }
292     }
293
294   if (gtk_css_selector_matches_type (selector, path, id))
295     {
296       GSList *regions;
297       gboolean match;
298       
299       if (id <= 0)
300         return selector->previous == NULL;
301
302       regions = gtk_widget_path_iter_list_regions (path, id - 1);
303       match = gtk_css_selector_matches_previous (selector,
304                                                  path,
305                                                  id - 1,
306                                                  regions);
307       g_slist_free (regions);
308       return match;
309     }
310
311   return FALSE;
312 }
313
314 static gboolean
315 gtk_css_selector_matches_previous (const GtkCssSelector      *selector,
316                                    /* const */ GtkWidgetPath *path,
317                                    guint                      id,
318                                    GSList                    *regions)
319 {
320   if (!selector->previous)
321     return TRUE;
322
323   if (gtk_css_selector_matches_from (selector->previous,
324                                      path,
325                                      id,
326                                      regions))
327     return TRUE;
328
329   if (selector->combine == GTK_CSS_COMBINE_DESCANDANT)
330     {
331       /* with this magic we run the loop while id >= 0 */
332       while (id-- != 0)
333         {
334           GSList *list;
335           gboolean match;
336
337           list = gtk_widget_path_iter_list_regions (path, id);
338           match = gtk_css_selector_matches_from (selector->previous,
339                                                  path,
340                                                  id,
341                                                  list);
342           g_slist_free (list);
343           if (match)
344             return TRUE;
345         }
346     }
347
348   return FALSE;
349 }
350
351 gboolean
352 _gtk_css_selector_matches (const GtkCssSelector      *selector,
353                            /* const */ GtkWidgetPath *path)
354 {
355   GSList *list;
356   guint length;
357   gboolean match;
358
359   g_return_val_if_fail (selector != NULL, FALSE);
360   g_return_val_if_fail (path != NULL, FALSE);
361
362   length = gtk_widget_path_length (path);
363   if (length == 0)
364     return FALSE;
365
366   list = gtk_widget_path_iter_list_regions (path, length - 1);
367   match = gtk_css_selector_matches_from (selector,
368                                          path,
369                                          length - 1,
370                                          list);
371   g_slist_free (list);
372   return match;
373 }
374
375 static guint
376 count_bits (guint v)
377 {
378   /* http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel */
379   v = v - ((v >> 1) & 0x55555555);
380   v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
381   return (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
382 }
383
384 /* Computes specificity according to CSS 2.1.
385  * The arguments must be initialized to 0 */
386 static void
387 _gtk_css_selector_get_specificity (const GtkCssSelector *selector,
388                                    guint                *ids,
389                                    guint                *classes,
390                                    guint                *elements)
391 {
392   GQuark *count;
393
394   if (selector->previous)
395     _gtk_css_selector_get_specificity (selector->previous, ids, classes, elements);
396
397   if (selector->ids)
398     for (count = selector->ids; *count; count++)
399       (*ids)++;
400
401   if (selector->classes)
402     for (count = selector->classes; *count; count++)
403       (*classes)++;
404   
405   *classes += count_bits (selector->state) + count_bits (selector->pseudo_classes);
406
407   if (selector->name)
408     (*elements)++;
409 }
410
411 int
412 _gtk_css_selector_compare (const GtkCssSelector *a,
413                            const GtkCssSelector *b)
414 {
415   guint a_ids = 0, a_classes = 0, a_elements = 0;
416   guint b_ids = 0, b_classes = 0, b_elements = 0;
417   int compare;
418
419   _gtk_css_selector_get_specificity (a, &a_ids, &a_classes, &a_elements);
420   _gtk_css_selector_get_specificity (b, &b_ids, &b_classes, &b_elements);
421
422   compare = a_ids - b_ids;
423   if (compare)
424     return compare;
425
426   compare = a_classes - b_classes;
427   if (compare)
428     return compare;
429
430   return a_elements - b_elements;
431 }
432
433 GtkStateFlags
434 _gtk_css_selector_get_state_flags (GtkCssSelector *selector)
435 {
436   g_return_val_if_fail (selector != NULL, 0);
437
438   return selector->state;
439 }
440