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