]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssselector.c
css: Don't query type for regions
[~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 gboolean
185 gtk_css_selector_matches_type (const GtkCssSelector      *selector,
186                                /* const */ GtkWidgetPath *path,
187                                guint                      id)
188 {
189   if (selector->name == NULL)
190     return TRUE;
191
192   if (selector->pseudo_classes)
193     return FALSE;
194
195   if (selector->type == G_TYPE_NONE)
196     return FALSE;
197
198   /* ugh, assigning to a const variable */
199   if (selector->type == G_TYPE_INVALID)
200     ((GtkCssSelector *) selector)->type = g_type_from_name (selector->name);
201
202   if (selector->type == G_TYPE_INVALID)
203     return FALSE;
204
205   return g_type_is_a (gtk_widget_path_iter_get_object_type (path, id), selector->type);
206 }
207
208 static gboolean
209 gtk_css_selector_matches_region (const GtkCssSelector      *selector,
210                                  /* const */ GtkWidgetPath *path,
211                                  guint                      id,
212                                  const char *               region)
213 {
214   GtkRegionFlags flags;
215
216   if (selector->name == NULL)
217     return TRUE;
218   
219   if (selector->name != region)
220     return FALSE;
221
222   if (!gtk_widget_path_iter_has_region (path, id, region, &flags))
223     {
224       /* This function must be called with existing regions */
225       g_assert_not_reached ();
226     }
227
228   return (selector->pseudo_classes & flags) == selector->pseudo_classes;
229 }
230
231 static gboolean
232 gtk_css_selector_matches_rest (const GtkCssSelector      *selector,
233                                /* const */ GtkWidgetPath *path,
234                                guint                      id)
235 {
236   if (selector->ids)
237     {
238       GQuark *name;
239       
240       for (name = selector->ids; *name; name++)
241         {
242           if (!gtk_widget_path_iter_has_qname (path, id, *name))
243             return FALSE;
244         }
245     }
246
247   if (selector->classes)
248     {
249       GQuark *class;
250       
251       for (class = selector->classes; *class; class++)
252         {
253           if (!gtk_widget_path_iter_has_qclass (path, id, *class))
254             return FALSE;
255         }
256     }
257
258   return TRUE;
259 }
260
261 static gboolean
262 gtk_css_selector_matches_previous (const GtkCssSelector      *selector,
263                                    /* const */ GtkWidgetPath *path,
264                                    guint                      id,
265                                    GSList                    *regions);
266
267 static gboolean
268 gtk_css_selector_matches_from (const GtkCssSelector      *selector,
269                                /* const */ GtkWidgetPath *path,
270                                guint                      id,
271                                GSList                    *regions)
272 {
273   GSList *l;
274
275   if (!gtk_css_selector_matches_rest (selector, path, id))
276     return FALSE;
277
278   for (l = regions; l; l = l->next)
279     {
280       const char *region = l->data;
281
282       if (gtk_css_selector_matches_region (selector, path, id, region))
283         {
284           GSList *remaining;
285           gboolean match;
286
287           remaining = g_slist_copy (regions);
288           remaining = g_slist_remove (remaining, region);
289           match = gtk_css_selector_matches_previous (selector,
290                                                      path,
291                                                      id,
292                                                      remaining);
293           g_slist_free (remaining);
294           if (match)
295             return TRUE;
296         }
297     }
298
299   if (gtk_css_selector_matches_type (selector, path, id))
300     {
301       GSList *regions;
302       gboolean match;
303       
304       if (id <= 0)
305         return selector->previous == NULL;
306
307       regions = gtk_widget_path_iter_list_regions (path, id - 1);
308       match = gtk_css_selector_matches_previous (selector,
309                                                  path,
310                                                  id - 1,
311                                                  regions);
312       g_slist_free (regions);
313       return match;
314     }
315
316   return FALSE;
317 }
318
319 static gboolean
320 gtk_css_selector_matches_previous (const GtkCssSelector      *selector,
321                                    /* const */ GtkWidgetPath *path,
322                                    guint                      id,
323                                    GSList                    *regions)
324 {
325   if (!selector->previous)
326     return TRUE;
327
328   if (gtk_css_selector_matches_from (selector->previous,
329                                      path,
330                                      id,
331                                      regions))
332     return TRUE;
333
334   if (selector->combine == GTK_CSS_COMBINE_DESCANDANT)
335     {
336       /* with this magic we run the loop while id >= 0 */
337       while (id-- != 0)
338         {
339           GSList *list;
340           gboolean match;
341
342           list = gtk_widget_path_iter_list_regions (path, id);
343           match = gtk_css_selector_matches_from (selector->previous,
344                                                  path,
345                                                  id,
346                                                  list);
347           g_slist_free (list);
348           if (match)
349             return TRUE;
350         }
351     }
352
353   return FALSE;
354 }
355
356 gboolean
357 _gtk_css_selector_matches (const GtkCssSelector      *selector,
358                            /* const */ GtkWidgetPath *path)
359 {
360   GSList *list;
361   guint length;
362   gboolean match;
363
364   g_return_val_if_fail (selector != NULL, FALSE);
365   g_return_val_if_fail (path != NULL, FALSE);
366
367   length = gtk_widget_path_length (path);
368   if (length == 0)
369     return FALSE;
370
371   list = gtk_widget_path_iter_list_regions (path, length - 1);
372   match = gtk_css_selector_matches_from (selector,
373                                          path,
374                                          length - 1,
375                                          list);
376   g_slist_free (list);
377   return match;
378 }
379
380 static guint
381 count_bits (guint v)
382 {
383   /* http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel */
384   v = v - ((v >> 1) & 0x55555555);
385   v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
386   return (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
387 }
388
389 /* Computes specificity according to CSS 2.1.
390  * The arguments must be initialized to 0 */
391 static void
392 _gtk_css_selector_get_specificity (const GtkCssSelector *selector,
393                                    guint                *ids,
394                                    guint                *classes,
395                                    guint                *elements)
396 {
397   GQuark *count;
398
399   if (selector->previous)
400     _gtk_css_selector_get_specificity (selector->previous, ids, classes, elements);
401
402   if (selector->ids)
403     for (count = selector->ids; *count; count++)
404       (*ids)++;
405
406   if (selector->classes)
407     for (count = selector->classes; *count; count++)
408       (*classes)++;
409   
410   *classes += count_bits (selector->state) + count_bits (selector->pseudo_classes);
411
412   if (selector->name)
413     (*elements)++;
414 }
415
416 int
417 _gtk_css_selector_compare (const GtkCssSelector *a,
418                            const GtkCssSelector *b)
419 {
420   guint a_ids = 0, a_classes = 0, a_elements = 0;
421   guint b_ids = 0, b_classes = 0, b_elements = 0;
422   int compare;
423
424   _gtk_css_selector_get_specificity (a, &a_ids, &a_classes, &a_elements);
425   _gtk_css_selector_get_specificity (b, &b_ids, &b_classes, &b_elements);
426
427   compare = a_ids - b_ids;
428   if (compare)
429     return compare;
430
431   compare = a_classes - b_classes;
432   if (compare)
433     return compare;
434
435   return a_elements - b_elements;
436 }
437
438 GtkStateFlags
439 _gtk_css_selector_get_state_flags (GtkCssSelector *selector)
440 {
441   g_return_val_if_fail (selector != NULL, 0);
442
443   return selector->state;
444 }
445