]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssselector.c
Change FSF Address
[~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, see <http://www.gnu.org/licenses/>.
16  */
17
18 #include "config.h"
19
20 #include "gtkcssselectorprivate.h"
21
22 #include "gtkstylecontextprivate.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 = !name || _gtk_style_context_check_region_name (name) ? G_TYPE_NONE : 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         "only-child",
132         "sorted"
133       };
134       guint i;
135
136       for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
137         {
138           if (selector->pseudo_classes & (1 << i))
139             {
140               g_string_append_c (str, ':');
141               g_string_append (str, flag_names[i]);
142             }
143         }
144     }
145
146   if (selector->state)
147     {
148       static const char * state_names[] = {
149         "active",
150         "hover",
151         "selected",
152         "insensitive",
153         "inconsistent",
154         "focus",
155         "backdrop"
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   if (n_siblings == 1)
205     flags |= GTK_REGION_ONLY;
206
207   return flags;
208 }
209
210 static gboolean
211 gtk_css_selector_matches_type (const GtkCssSelector      *selector,
212                                const GtkWidgetPath       *path,
213                                guint                      id)
214 {
215   if (selector->pseudo_classes)
216     {
217       GtkRegionFlags flags = compute_region_flags_for_index (path, id);
218
219       if ((selector->pseudo_classes & flags) != selector->pseudo_classes)
220         return FALSE;
221     }
222
223   if (selector->name == NULL)
224     return TRUE;
225
226   if (selector->type == G_TYPE_NONE)
227     return FALSE;
228
229   /* ugh, assigning to a const variable */
230   if (selector->type == G_TYPE_INVALID)
231     ((GtkCssSelector *) selector)->type = g_type_from_name (selector->name);
232
233   if (selector->type == G_TYPE_INVALID)
234     return FALSE;
235
236   return g_type_is_a (gtk_widget_path_iter_get_object_type (path, id), selector->type);
237 }
238
239 static gboolean
240 gtk_css_selector_matches_region (const GtkCssSelector      *selector,
241                                  const GtkWidgetPath       *path,
242                                  guint                      id,
243                                  const char *               region)
244 {
245   GtkRegionFlags flags;
246
247   if (selector->name == NULL)
248     return TRUE;
249   
250   if (selector->name != region)
251     return FALSE;
252
253   if (!gtk_widget_path_iter_has_region (path, id, region, &flags))
254     {
255       /* This function must be called with existing regions */
256       g_assert_not_reached ();
257     }
258
259   return (selector->pseudo_classes & flags) == selector->pseudo_classes;
260 }
261
262 static gboolean
263 gtk_css_selector_matches_rest (const GtkCssSelector      *selector,
264                                const GtkWidgetPath       *path,
265                                guint                      id)
266 {
267   if (selector->ids)
268     {
269       GQuark *name;
270       
271       for (name = selector->ids; *name; name++)
272         {
273           if (!gtk_widget_path_iter_has_qname (path, id, *name))
274             return FALSE;
275         }
276     }
277
278   if (selector->classes)
279     {
280       GQuark *class;
281       
282       for (class = selector->classes; *class; class++)
283         {
284           if (!gtk_widget_path_iter_has_qclass (path, id, *class))
285             return FALSE;
286         }
287     }
288
289   return TRUE;
290 }
291
292 static gboolean
293 gtk_css_selector_matches_previous (const GtkCssSelector      *selector,
294                                    const GtkWidgetPath       *path,
295                                    guint                      id,
296                                    GSList                    *regions);
297
298 static gboolean
299 gtk_css_selector_matches_from (const GtkCssSelector      *selector,
300                                const GtkWidgetPath       *path,
301                                guint                      id,
302                                GSList                    *regions)
303 {
304   GSList *l;
305
306   if (!gtk_css_selector_matches_rest (selector, path, id))
307     return FALSE;
308
309   for (l = regions; l; l = l->next)
310     {
311       const char *region = l->data;
312
313       if (gtk_css_selector_matches_region (selector, path, id, region))
314         {
315           GSList *remaining;
316           gboolean match;
317
318           remaining = g_slist_copy (regions);
319           remaining = g_slist_remove (remaining, region);
320           match = gtk_css_selector_matches_previous (selector,
321                                                      path,
322                                                      id,
323                                                      remaining);
324           g_slist_free (remaining);
325           if (match)
326             return TRUE;
327         }
328     }
329
330   if (gtk_css_selector_matches_type (selector, path, id))
331     {
332       GSList *regions;
333       gboolean match;
334       
335       if (id <= 0)
336         return selector->previous == NULL;
337
338       regions = gtk_widget_path_iter_list_regions (path, id - 1);
339       match = gtk_css_selector_matches_previous (selector,
340                                                  path,
341                                                  id - 1,
342                                                  regions);
343       g_slist_free (regions);
344       return match;
345     }
346
347   return FALSE;
348 }
349
350 static gboolean
351 gtk_css_selector_matches_previous (const GtkCssSelector      *selector,
352                                    const GtkWidgetPath       *path,
353                                    guint                      id,
354                                    GSList                    *regions)
355 {
356   if (!selector->previous)
357     return TRUE;
358
359   if (gtk_css_selector_matches_from (selector->previous,
360                                      path,
361                                      id,
362                                      regions))
363     return TRUE;
364
365   if (selector->combine == GTK_CSS_COMBINE_DESCANDANT)
366     {
367       /* with this magic we run the loop while id >= 0 */
368       while (id-- != 0)
369         {
370           GSList *list;
371           gboolean match;
372
373           list = gtk_widget_path_iter_list_regions (path, id);
374           match = gtk_css_selector_matches_from (selector->previous,
375                                                  path,
376                                                  id,
377                                                  list);
378           g_slist_free (list);
379           if (match)
380             return TRUE;
381         }
382     }
383
384   return FALSE;
385 }
386
387 /**
388  * _gtk_css_selector_matches:
389  * @selector: the selector
390  * @path: the path to check
391  * @state: The state to match
392  *
393  * Checks if the @selector matches the given @path. If @length is
394  * smaller than the number of elements in @path, it is assumed that
395  * only the first @length element of @path are valid and the rest
396  * does not exist. This is useful for doing parent matches for the
397  * 'inherit' keyword.
398  *
399  * Returns: %TRUE if the selector matches @path
400  **/
401 gboolean
402 _gtk_css_selector_matches (const GtkCssSelector      *selector,
403                            const GtkWidgetPath       *path,
404                            GtkStateFlags              state)
405 {
406   GSList *list;
407   gboolean match;
408   guint length;
409
410   g_return_val_if_fail (selector != NULL, FALSE);
411   g_return_val_if_fail (path != NULL, FALSE);
412
413   if ((selector->state & state) != selector->state)
414     return FALSE;
415
416   length = gtk_widget_path_length (path);
417   if (length == 0)
418     return FALSE;
419
420   list = gtk_widget_path_iter_list_regions (path, length - 1);
421   match = gtk_css_selector_matches_from (selector,
422                                          path,
423                                          length - 1,
424                                          list);
425   g_slist_free (list);
426   return match;
427 }
428
429 static guint
430 count_bits (guint v)
431 {
432   /* http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel */
433   v = v - ((v >> 1) & 0x55555555);
434   v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
435   return (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
436 }
437
438 /* Computes specificity according to CSS 2.1.
439  * The arguments must be initialized to 0 */
440 static void
441 _gtk_css_selector_get_specificity (const GtkCssSelector *selector,
442                                    guint                *ids,
443                                    guint                *classes,
444                                    guint                *elements)
445 {
446   GQuark *count;
447
448   if (selector->previous)
449     _gtk_css_selector_get_specificity (selector->previous, ids, classes, elements);
450
451   if (selector->ids)
452     for (count = selector->ids; *count; count++)
453       (*ids)++;
454
455   if (selector->classes)
456     for (count = selector->classes; *count; count++)
457       (*classes)++;
458   
459   *classes += count_bits (selector->state) + count_bits (selector->pseudo_classes);
460
461   if (selector->name)
462     (*elements)++;
463 }
464
465 int
466 _gtk_css_selector_compare (const GtkCssSelector *a,
467                            const GtkCssSelector *b)
468 {
469   guint a_ids = 0, a_classes = 0, a_elements = 0;
470   guint b_ids = 0, b_classes = 0, b_elements = 0;
471   int compare;
472
473   _gtk_css_selector_get_specificity (a, &a_ids, &a_classes, &a_elements);
474   _gtk_css_selector_get_specificity (b, &b_ids, &b_classes, &b_elements);
475
476   compare = a_ids - b_ids;
477   if (compare)
478     return compare;
479
480   compare = a_classes - b_classes;
481   if (compare)
482     return compare;
483
484   return a_elements - b_elements;
485 }
486
487 GtkStateFlags
488 _gtk_css_selector_get_state_flags (GtkCssSelector *selector)
489 {
490   g_return_val_if_fail (selector != NULL, 0);
491
492   return selector->state;
493 }
494