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