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 <stdlib.h> |
23 | #include <string.h> |
24 | |
25 | #include "gtkcssprovider.h" |
26 | #include "gtkstylecontextprivate.h" |
27 | |
28 | #include <errno.h> |
29 | #if defined(_MSC_VER) && _MSC_VER >= 1500 |
30 | # include <intrin.h> |
31 | #endif |
32 | |
33 | /* |
34 | * @GTK_CSS_SELECTOR_CATEGORY_SIMPLE: A simple selector |
35 | * @GTK_CSS_SELECTOR_CATEGORY_SIMPLE_RADICAL: A simple selector that matches |
36 | * what change tracking considers a "radical change" |
37 | * @GTK_CSS_SELECTOR_SIBLING: A selector matching siblings |
38 | * @GTK_CSS_SELECTOR_CATEGORY_PARENT: A selector matching a parent or other |
39 | * ancestor |
40 | * |
41 | * Categorize the selectors. This helps in various loops when matching. |
42 | */ |
43 | typedef enum { |
44 | GTK_CSS_SELECTOR_CATEGORY_SIMPLE, |
45 | GTK_CSS_SELECTOR_CATEGORY_SIMPLE_RADICAL, |
46 | GTK_CSS_SELECTOR_CATEGORY_PARENT, |
47 | GTK_CSS_SELECTOR_CATEGORY_SIBLING, |
48 | } GtkCssSelectorCategory; |
49 | |
50 | typedef struct _GtkCssSelectorClass GtkCssSelectorClass; |
51 | |
52 | struct _GtkCssSelectorClass { |
53 | const char *name; |
54 | GtkCssSelectorCategory category; |
55 | |
56 | void (* print) (const GtkCssSelector *selector, |
57 | GString *string); |
58 | /* NULL or an iterator that returns the next node or %NULL if there are no |
59 | * more nodes. |
60 | * Call it like: |
61 | * for (iter = gtk_css_selector_iterator (node, NULL); |
62 | * iter; |
63 | * iter = gtk_css_selector_iterator (node, iter)) |
64 | * { |
65 | * do_stuff(); |
66 | * } |
67 | */ |
68 | GtkCssNode * (* iterator) (const GtkCssSelector *selector, |
69 | GtkCssNode *node, |
70 | GtkCssNode *current); |
71 | gboolean (* match_one) (const GtkCssSelector *selector, |
72 | GtkCssNode *node); |
73 | GtkCssChange (* get_change) (const GtkCssSelector *selector, |
74 | GtkCssChange previous_change); |
75 | void (* add_specificity) (const GtkCssSelector *selector, |
76 | guint *ids, |
77 | guint *classes, |
78 | guint *elements); |
79 | guint (* hash_one) (const GtkCssSelector *selector); |
80 | int (* compare_one) (const GtkCssSelector *a, |
81 | const GtkCssSelector *b); |
82 | }; |
83 | |
84 | typedef enum { |
85 | POSITION_FORWARD, |
86 | POSITION_BACKWARD, |
87 | POSITION_ONLY, |
88 | } PositionType; |
89 | #define POSITION_TYPE_BITS 4 |
90 | #define POSITION_NUMBER_BITS ((sizeof (gpointer) * 8 - POSITION_TYPE_BITS) / 2) |
91 | |
92 | union _GtkCssSelector |
93 | { |
94 | const GtkCssSelectorClass *class; /* type of check this selector does */ |
95 | struct { |
96 | const GtkCssSelectorClass *class; |
97 | GQuark name; |
98 | } id; |
99 | struct { |
100 | const GtkCssSelectorClass *class; |
101 | GQuark style_class; |
102 | } style_class; |
103 | struct { |
104 | const GtkCssSelectorClass *class; |
105 | GQuark name; |
106 | } name; |
107 | struct { |
108 | const GtkCssSelectorClass *class; |
109 | GtkStateFlags state; |
110 | } state; |
111 | struct { |
112 | const GtkCssSelectorClass *class; |
113 | PositionType type :POSITION_TYPE_BITS; |
114 | gssize a :POSITION_NUMBER_BITS; |
115 | gssize b :POSITION_NUMBER_BITS; |
116 | } position; |
117 | }; |
118 | |
119 | #define GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET G_MAXINT32 |
120 | struct _GtkCssSelectorTree |
121 | { |
122 | GtkCssSelector selector; |
123 | gint32 parent_offset; |
124 | gint32 previous_offset; |
125 | gint32 sibling_offset; |
126 | gint32 matches_offset; /* pointers that we return as matches if selector matches */ |
127 | }; |
128 | |
129 | static gboolean |
130 | gtk_css_selector_equal (const GtkCssSelector *a, |
131 | const GtkCssSelector *b) |
132 | { |
133 | return |
134 | a->class == b->class && |
135 | a->class->compare_one (a, b) == 0; |
136 | } |
137 | |
138 | static guint |
139 | gtk_css_selector_hash_one (const GtkCssSelector *selector) |
140 | { |
141 | return selector->class->hash_one (selector); |
142 | } |
143 | |
144 | static inline gpointer * |
145 | gtk_css_selector_tree_get_matches (const GtkCssSelectorTree *tree) |
146 | { |
147 | if (tree->matches_offset == GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET) |
148 | return NULL; |
149 | |
150 | return (gpointer *) ((guint8 *)tree + tree->matches_offset); |
151 | } |
152 | |
153 | static void |
154 | gtk_css_selector_matches_insert_sorted (GtkCssSelectorMatches *matches, |
155 | gpointer data) |
156 | { |
157 | guint i; |
158 | |
159 | for (i = 0; i < gtk_css_selector_matches_get_size (self: matches); i++) |
160 | { |
161 | gpointer elem = gtk_css_selector_matches_get (self: matches, pos: i); |
162 | |
163 | if (data == elem) |
164 | return; |
165 | |
166 | if (data < elem) |
167 | break; |
168 | } |
169 | |
170 | gtk_css_selector_matches_splice (self: matches, pos: i, removed: 0, FALSE, additions: (gpointer[1]) { data }, added: 1); |
171 | } |
172 | |
173 | static inline gboolean |
174 | gtk_css_selector_match_one (const GtkCssSelector *selector, |
175 | GtkCssNode *node) |
176 | { |
177 | return selector->class->match_one (selector, node); |
178 | } |
179 | |
180 | static inline GtkCssNode * |
181 | gtk_css_selector_iterator (const GtkCssSelector *selector, |
182 | GtkCssNode *node, |
183 | GtkCssNode *current) |
184 | { |
185 | return selector->class->iterator (selector, node, current); |
186 | } |
187 | |
188 | static int |
189 | gtk_css_selector_compare_one (const GtkCssSelector *a, const GtkCssSelector *b) |
190 | { |
191 | if (a->class != b->class) |
192 | return strcmp (s1: a->class->name, s2: b->class->name); |
193 | else |
194 | return a->class->compare_one (a, b); |
195 | } |
196 | |
197 | static const GtkCssSelector * |
198 | gtk_css_selector_previous (const GtkCssSelector *selector) |
199 | { |
200 | selector = selector + 1; |
201 | |
202 | return selector->class ? selector : NULL; |
203 | } |
204 | |
205 | static inline const GtkCssSelectorTree * |
206 | gtk_css_selector_tree_at_offset (const GtkCssSelectorTree *tree, |
207 | gint32 offset) |
208 | { |
209 | if (offset == GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET) |
210 | return NULL; |
211 | |
212 | return (GtkCssSelectorTree *) ((guint8 *)tree + offset); |
213 | } |
214 | |
215 | static inline const GtkCssSelectorTree * |
216 | gtk_css_selector_tree_get_parent (const GtkCssSelectorTree *tree) |
217 | { |
218 | return gtk_css_selector_tree_at_offset (tree, offset: tree->parent_offset); |
219 | } |
220 | |
221 | static inline const GtkCssSelectorTree * |
222 | gtk_css_selector_tree_get_previous (const GtkCssSelectorTree *tree) |
223 | { |
224 | return gtk_css_selector_tree_at_offset (tree, offset: tree->previous_offset); |
225 | } |
226 | |
227 | static inline const GtkCssSelectorTree * |
228 | gtk_css_selector_tree_get_sibling (const GtkCssSelectorTree *tree) |
229 | { |
230 | return gtk_css_selector_tree_at_offset (tree, offset: tree->sibling_offset); |
231 | } |
232 | |
233 | /* DEFAULTS */ |
234 | |
235 | static void |
236 | gtk_css_selector_default_add_specificity (const GtkCssSelector *selector, |
237 | guint *ids, |
238 | guint *classes, |
239 | guint *elements) |
240 | { |
241 | /* no specificity changes */ |
242 | } |
243 | |
244 | static GtkCssNode * |
245 | gtk_css_selector_default_iterator (const GtkCssSelector *selector, |
246 | GtkCssNode *node, |
247 | GtkCssNode *current) |
248 | { |
249 | if (current) |
250 | return NULL; |
251 | else |
252 | return node; |
253 | } |
254 | |
255 | static gboolean |
256 | gtk_css_selector_default_match_one (const GtkCssSelector *selector, |
257 | GtkCssNode *node) |
258 | { |
259 | return TRUE; |
260 | } |
261 | |
262 | static guint |
263 | gtk_css_selector_default_hash_one (const GtkCssSelector *selector) |
264 | { |
265 | return GPOINTER_TO_UINT (selector->class); |
266 | } |
267 | |
268 | static int |
269 | gtk_css_selector_default_compare_one (const GtkCssSelector *a, |
270 | const GtkCssSelector *b) |
271 | { |
272 | return 0; |
273 | } |
274 | |
275 | /* DESCENDANT */ |
276 | |
277 | static void |
278 | gtk_css_selector_descendant_print (const GtkCssSelector *selector, |
279 | GString *string) |
280 | { |
281 | g_string_append_c (string, ' '); |
282 | } |
283 | |
284 | static GtkCssNode * |
285 | gtk_css_selector_descendant_iterator (const GtkCssSelector *selector, |
286 | GtkCssNode *node, |
287 | GtkCssNode *current) |
288 | { |
289 | return gtk_css_node_get_parent (cssnode: current ? current : node); |
290 | } |
291 | |
292 | static GtkCssChange |
293 | gtk_css_selector_descendant_get_change (const GtkCssSelector *selector, GtkCssChange previous_change) |
294 | { |
295 | return _gtk_css_change_for_child (match: previous_change); |
296 | } |
297 | |
298 | static const GtkCssSelectorClass GTK_CSS_SELECTOR_DESCENDANT = { |
299 | "descendant" , |
300 | GTK_CSS_SELECTOR_CATEGORY_PARENT, |
301 | gtk_css_selector_descendant_print, |
302 | gtk_css_selector_descendant_iterator, |
303 | gtk_css_selector_default_match_one, |
304 | gtk_css_selector_descendant_get_change, |
305 | gtk_css_selector_default_add_specificity, |
306 | gtk_css_selector_default_hash_one, |
307 | gtk_css_selector_default_compare_one, |
308 | }; |
309 | |
310 | /* CHILD */ |
311 | |
312 | static void |
313 | gtk_css_selector_child_print (const GtkCssSelector *selector, |
314 | GString *string) |
315 | { |
316 | g_string_append (string, val: " > " ); |
317 | } |
318 | |
319 | static GtkCssNode * |
320 | gtk_css_selector_child_iterator (const GtkCssSelector *selector, |
321 | GtkCssNode *node, |
322 | GtkCssNode *current) |
323 | { |
324 | if (current == NULL) |
325 | return gtk_css_node_get_parent (cssnode: node); |
326 | |
327 | return NULL; |
328 | } |
329 | |
330 | static GtkCssChange |
331 | gtk_css_selector_child_get_change (const GtkCssSelector *selector, GtkCssChange previous_change) |
332 | { |
333 | return _gtk_css_change_for_child (match: previous_change); |
334 | } |
335 | |
336 | static const GtkCssSelectorClass GTK_CSS_SELECTOR_CHILD = { |
337 | "child" , |
338 | GTK_CSS_SELECTOR_CATEGORY_PARENT, |
339 | gtk_css_selector_child_print, |
340 | gtk_css_selector_child_iterator, |
341 | gtk_css_selector_default_match_one, |
342 | gtk_css_selector_child_get_change, |
343 | gtk_css_selector_default_add_specificity, |
344 | gtk_css_selector_default_hash_one, |
345 | gtk_css_selector_default_compare_one, |
346 | }; |
347 | |
348 | /* SIBLING */ |
349 | |
350 | static void |
351 | gtk_css_selector_sibling_print (const GtkCssSelector *selector, |
352 | GString *string) |
353 | { |
354 | g_string_append (string, val: " ~ " ); |
355 | } |
356 | |
357 | static GtkCssNode * |
358 | get_previous_visible_sibling (GtkCssNode *node) |
359 | { |
360 | do { |
361 | node = gtk_css_node_get_previous_sibling (cssnode: node); |
362 | } while (node && !gtk_css_node_get_visible (cssnode: node)); |
363 | |
364 | return node; |
365 | } |
366 | |
367 | static GtkCssNode * |
368 | get_next_visible_sibling (GtkCssNode *node) |
369 | { |
370 | do { |
371 | node = gtk_css_node_get_next_sibling (cssnode: node); |
372 | } while (node && !gtk_css_node_get_visible (cssnode: node)); |
373 | |
374 | return node; |
375 | } |
376 | |
377 | static GtkCssNode * |
378 | gtk_css_selector_sibling_iterator (const GtkCssSelector *selector, |
379 | GtkCssNode *node, |
380 | GtkCssNode *current) |
381 | { |
382 | return get_previous_visible_sibling (node: current ? current : node); |
383 | } |
384 | |
385 | static GtkCssChange |
386 | gtk_css_selector_sibling_get_change (const GtkCssSelector *selector, GtkCssChange previous_change) |
387 | { |
388 | return _gtk_css_change_for_sibling (match: previous_change); |
389 | } |
390 | |
391 | static const GtkCssSelectorClass GTK_CSS_SELECTOR_SIBLING = { |
392 | "sibling" , |
393 | GTK_CSS_SELECTOR_CATEGORY_SIBLING, |
394 | gtk_css_selector_sibling_print, |
395 | gtk_css_selector_sibling_iterator, |
396 | gtk_css_selector_default_match_one, |
397 | gtk_css_selector_sibling_get_change, |
398 | gtk_css_selector_default_add_specificity, |
399 | gtk_css_selector_default_hash_one, |
400 | gtk_css_selector_default_compare_one, |
401 | }; |
402 | |
403 | /* ADJACENT */ |
404 | |
405 | static void |
406 | gtk_css_selector_adjacent_print (const GtkCssSelector *selector, |
407 | GString *string) |
408 | { |
409 | g_string_append (string, val: " + " ); |
410 | } |
411 | |
412 | static GtkCssNode * |
413 | gtk_css_selector_adjacent_iterator (const GtkCssSelector *selector, |
414 | GtkCssNode *node, |
415 | GtkCssNode *current) |
416 | { |
417 | if (current == NULL) |
418 | return get_previous_visible_sibling (node); |
419 | |
420 | return NULL; |
421 | } |
422 | |
423 | static GtkCssChange |
424 | gtk_css_selector_adjacent_get_change (const GtkCssSelector *selector, GtkCssChange previous_change) |
425 | { |
426 | return _gtk_css_change_for_sibling (match: previous_change); |
427 | } |
428 | |
429 | static const GtkCssSelectorClass GTK_CSS_SELECTOR_ADJACENT = { |
430 | "adjacent" , |
431 | GTK_CSS_SELECTOR_CATEGORY_SIBLING, |
432 | gtk_css_selector_adjacent_print, |
433 | gtk_css_selector_adjacent_iterator, |
434 | gtk_css_selector_default_match_one, |
435 | gtk_css_selector_adjacent_get_change, |
436 | gtk_css_selector_default_add_specificity, |
437 | gtk_css_selector_default_hash_one, |
438 | gtk_css_selector_default_compare_one, |
439 | }; |
440 | |
441 | /* SIMPLE SELECTOR DEFINE */ |
442 | |
443 | #define DEFINE_SIMPLE_SELECTOR(n, \ |
444 | c, \ |
445 | print_func, \ |
446 | match_func, \ |
447 | hash_func, \ |
448 | comp_func, \ |
449 | increase_id_specificity, \ |
450 | increase_class_specificity, \ |
451 | increase_element_specificity, \ |
452 | ignore_for_change) \ |
453 | static void \ |
454 | gtk_css_selector_ ## n ## _print (const GtkCssSelector *selector, \ |
455 | GString *string) \ |
456 | { \ |
457 | print_func (selector, string); \ |
458 | } \ |
459 | \ |
460 | static void \ |
461 | gtk_css_selector_not_ ## n ## _print (const GtkCssSelector *selector, \ |
462 | GString *string) \ |
463 | { \ |
464 | g_string_append (string, ":not("); \ |
465 | print_func (selector, string); \ |
466 | g_string_append (string, ")"); \ |
467 | } \ |
468 | \ |
469 | static gboolean \ |
470 | gtk_css_selector_not_ ## n ## _match_one (const GtkCssSelector *selector, \ |
471 | GtkCssNode *node) \ |
472 | { \ |
473 | return !match_func (selector, node); \ |
474 | } \ |
475 | \ |
476 | static GtkCssChange \ |
477 | gtk_css_selector_ ## n ## _get_change (const GtkCssSelector *selector, GtkCssChange previous_change) \ |
478 | { \ |
479 | return previous_change | GTK_CSS_CHANGE_ ## c; \ |
480 | } \ |
481 | \ |
482 | static void \ |
483 | gtk_css_selector_ ## n ## _add_specificity (const GtkCssSelector *selector, \ |
484 | guint *ids, \ |
485 | guint *classes, \ |
486 | guint *elements) \ |
487 | { \ |
488 | if (increase_id_specificity) \ |
489 | { \ |
490 | (*ids)++; \ |
491 | } \ |
492 | if (increase_class_specificity) \ |
493 | { \ |
494 | (*classes)++; \ |
495 | } \ |
496 | if (increase_element_specificity) \ |
497 | { \ |
498 | (*elements)++; \ |
499 | } \ |
500 | } \ |
501 | \ |
502 | static const GtkCssSelectorClass GTK_CSS_SELECTOR_ ## c = { \ |
503 | G_STRINGIFY(n), \ |
504 | ignore_for_change ? GTK_CSS_SELECTOR_CATEGORY_SIMPLE : GTK_CSS_SELECTOR_CATEGORY_SIMPLE_RADICAL, \ |
505 | gtk_css_selector_ ## n ## _print, \ |
506 | gtk_css_selector_default_iterator, \ |
507 | match_func, \ |
508 | gtk_css_selector_ ## n ## _get_change, \ |
509 | gtk_css_selector_ ## n ## _add_specificity, \ |
510 | hash_func, \ |
511 | comp_func, \ |
512 | };\ |
513 | \ |
514 | static const GtkCssSelectorClass GTK_CSS_SELECTOR_NOT_ ## c = { \ |
515 | "not_" G_STRINGIFY(n), \ |
516 | GTK_CSS_SELECTOR_CATEGORY_SIMPLE, \ |
517 | gtk_css_selector_not_ ## n ## _print, \ |
518 | gtk_css_selector_default_iterator, \ |
519 | gtk_css_selector_not_ ## n ## _match_one, \ |
520 | gtk_css_selector_ ## n ## _get_change, \ |
521 | gtk_css_selector_ ## n ## _add_specificity, \ |
522 | hash_func, \ |
523 | comp_func, \ |
524 | }; |
525 | |
526 | /* ANY */ |
527 | |
528 | static void |
529 | print_any (const GtkCssSelector *selector, |
530 | GString *string) |
531 | { |
532 | g_string_append_c (string, '*'); |
533 | } |
534 | |
535 | static gboolean |
536 | match_any (const GtkCssSelector *selector, |
537 | GtkCssNode *node) |
538 | { |
539 | return TRUE; |
540 | } |
541 | |
542 | #undef GTK_CSS_CHANGE_ANY |
543 | #define GTK_CSS_CHANGE_ANY 0 |
544 | DEFINE_SIMPLE_SELECTOR(any, ANY, print_any, match_any, |
545 | gtk_css_selector_default_hash_one, gtk_css_selector_default_compare_one, |
546 | FALSE, FALSE, FALSE, TRUE) |
547 | #undef GTK_CSS_CHANGE_ANY |
548 | |
549 | /* NAME */ |
550 | |
551 | static void |
552 | print_name (const GtkCssSelector *selector, |
553 | GString *string) |
554 | { |
555 | g_string_append (string, val: g_quark_to_string (quark: selector->name.name)); |
556 | } |
557 | |
558 | static gboolean |
559 | match_name (const GtkCssSelector *selector, |
560 | GtkCssNode *node) |
561 | { |
562 | return gtk_css_node_get_name (cssnode: node) == selector->name.name; |
563 | } |
564 | |
565 | static guint |
566 | hash_name (const GtkCssSelector *a) |
567 | { |
568 | return gtk_css_hash_name (name: a->name.name); |
569 | } |
570 | |
571 | static int |
572 | comp_name (const GtkCssSelector *a, |
573 | const GtkCssSelector *b) |
574 | { |
575 | return a->name.name - b->name.name; |
576 | } |
577 | |
578 | DEFINE_SIMPLE_SELECTOR(name, NAME, print_name, match_name, hash_name, comp_name, FALSE, FALSE, TRUE, FALSE) |
579 | |
580 | /* CLASS */ |
581 | |
582 | static void |
583 | print_class (const GtkCssSelector *selector, |
584 | GString *string) |
585 | { |
586 | g_string_append_c (string, '.'); |
587 | g_string_append (string, val: g_quark_to_string (quark: selector->style_class.style_class)); |
588 | } |
589 | |
590 | static gboolean |
591 | match_class (const GtkCssSelector *selector, |
592 | GtkCssNode *node) |
593 | { |
594 | return gtk_css_node_has_class (cssnode: node, style_class: selector->style_class.style_class); |
595 | } |
596 | |
597 | static guint |
598 | hash_class (const GtkCssSelector *a) |
599 | { |
600 | return gtk_css_hash_class (klass: a->style_class.style_class); |
601 | } |
602 | |
603 | static int |
604 | comp_class (const GtkCssSelector *a, |
605 | const GtkCssSelector *b) |
606 | { |
607 | if (a->style_class.style_class < b->style_class.style_class) |
608 | return -1; |
609 | if (a->style_class.style_class > b->style_class.style_class) |
610 | return 1; |
611 | else |
612 | return 0; |
613 | } |
614 | |
615 | DEFINE_SIMPLE_SELECTOR(class, CLASS, print_class, match_class, hash_class, comp_class, FALSE, TRUE, FALSE, FALSE) |
616 | |
617 | /* ID */ |
618 | |
619 | static void |
620 | print_id (const GtkCssSelector *selector, |
621 | GString *string) |
622 | { |
623 | g_string_append_c (string, '#'); |
624 | g_string_append (string, val: g_quark_to_string (quark: selector->id.name)); |
625 | } |
626 | |
627 | static gboolean |
628 | match_id (const GtkCssSelector *selector, |
629 | GtkCssNode *node) |
630 | { |
631 | return gtk_css_node_get_id (cssnode: node) == selector->id.name; |
632 | } |
633 | |
634 | static guint |
635 | hash_id (const GtkCssSelector *a) |
636 | { |
637 | return gtk_css_hash_id (id: a->id.name); |
638 | } |
639 | |
640 | static int |
641 | comp_id (const GtkCssSelector *a, |
642 | const GtkCssSelector *b) |
643 | { |
644 | return a->id.name - b->id.name; |
645 | } |
646 | |
647 | DEFINE_SIMPLE_SELECTOR(id, ID, print_id, match_id, hash_id, comp_id, TRUE, FALSE, FALSE, FALSE) |
648 | |
649 | /* PSEUDOCLASS FOR STATE */ |
650 | static void |
651 | print_pseudoclass_state (const GtkCssSelector *selector, |
652 | GString *string) |
653 | { |
654 | g_string_append_c (string, ':'); |
655 | g_string_append (string, val: gtk_css_pseudoclass_name (flags: selector->state.state)); |
656 | } |
657 | |
658 | static gboolean |
659 | match_pseudoclass_state (const GtkCssSelector *selector, |
660 | GtkCssNode *node) |
661 | { |
662 | return (gtk_css_node_get_state (cssnode: node) & selector->state.state) == selector->state.state; |
663 | } |
664 | |
665 | static guint |
666 | hash_pseudoclass_state (const GtkCssSelector *selector) |
667 | { |
668 | return selector->state.state; |
669 | } |
670 | |
671 | |
672 | static int |
673 | comp_pseudoclass_state (const GtkCssSelector *a, |
674 | const GtkCssSelector *b) |
675 | { |
676 | return a->state.state - b->state.state; |
677 | } |
678 | |
679 | static GtkCssChange |
680 | change_pseudoclass_state (const GtkCssSelector *selector) |
681 | { |
682 | GtkStateFlags states = selector->state.state; |
683 | GtkCssChange change = 0; |
684 | |
685 | if (states & GTK_STATE_FLAG_PRELIGHT) |
686 | change |= GTK_CSS_CHANGE_HOVER; |
687 | if (states & GTK_STATE_FLAG_INSENSITIVE) |
688 | change |= GTK_CSS_CHANGE_DISABLED; |
689 | if (states & GTK_STATE_FLAG_BACKDROP) |
690 | change |= GTK_CSS_CHANGE_BACKDROP; |
691 | if (states & GTK_STATE_FLAG_SELECTED) |
692 | change |= GTK_CSS_CHANGE_SELECTED; |
693 | if (states & ~(GTK_STATE_FLAG_PRELIGHT | |
694 | GTK_STATE_FLAG_INSENSITIVE | |
695 | GTK_STATE_FLAG_BACKDROP | |
696 | GTK_STATE_FLAG_SELECTED)) |
697 | change |= GTK_CSS_CHANGE_STATE; |
698 | |
699 | return change; |
700 | } |
701 | |
702 | #define GTK_CSS_CHANGE_PSEUDOCLASS_STATE change_pseudoclass_state (selector) |
703 | DEFINE_SIMPLE_SELECTOR(pseudoclass_state, PSEUDOCLASS_STATE, print_pseudoclass_state, |
704 | match_pseudoclass_state, hash_pseudoclass_state, comp_pseudoclass_state, |
705 | FALSE, TRUE, FALSE, TRUE) |
706 | #undef GTK_CSS_CHANGE_PSEUDOCLASS_STATE |
707 | |
708 | /* PSEUDOCLASS FOR POSITION */ |
709 | |
710 | static void |
711 | print_pseudoclass_position (const GtkCssSelector *selector, |
712 | GString *string) |
713 | { |
714 | switch (selector->position.type) |
715 | { |
716 | case POSITION_FORWARD: |
717 | if (selector->position.a == 0) |
718 | { |
719 | if (selector->position.b == 1) |
720 | g_string_append (string, val: ":first-child" ); |
721 | else |
722 | g_string_append_printf (string, format: ":nth-child(%d)" , selector->position.b); |
723 | } |
724 | else if (selector->position.a == 2 && selector->position.b == 0) |
725 | g_string_append (string, val: ":nth-child(even)" ); |
726 | else if (selector->position.a == 2 && selector->position.b == 1) |
727 | g_string_append (string, val: ":nth-child(odd)" ); |
728 | else |
729 | { |
730 | g_string_append (string, val: ":nth-child(" ); |
731 | if (selector->position.a == 1) |
732 | g_string_append (string, val: "n" ); |
733 | else if (selector->position.a == -1) |
734 | g_string_append (string, val: "-n" ); |
735 | else |
736 | g_string_append_printf (string, format: "%dn" , selector->position.a); |
737 | if (selector->position.b > 0) |
738 | g_string_append_printf (string, format: "+%d)" , selector->position.b); |
739 | else if (selector->position.b < 0) |
740 | g_string_append_printf (string, format: "%d)" , selector->position.b); |
741 | else |
742 | g_string_append (string, val: ")" ); |
743 | } |
744 | break; |
745 | case POSITION_BACKWARD: |
746 | if (selector->position.a == 0) |
747 | { |
748 | if (selector->position.b == 1) |
749 | g_string_append (string, val: ":last-child" ); |
750 | else |
751 | g_string_append_printf (string, format: ":nth-last-child(%d)" , selector->position.b); |
752 | } |
753 | else if (selector->position.a == 2 && selector->position.b == 0) |
754 | g_string_append (string, val: ":nth-last-child(even)" ); |
755 | else if (selector->position.a == 2 && selector->position.b == 1) |
756 | g_string_append (string, val: ":nth-last-child(odd)" ); |
757 | else |
758 | { |
759 | g_string_append (string, val: ":nth-last-child(" ); |
760 | if (selector->position.a == 1) |
761 | g_string_append (string, val: "n" ); |
762 | else if (selector->position.a == -1) |
763 | g_string_append (string, val: "-n" ); |
764 | else |
765 | g_string_append_printf (string, format: "%dn" , selector->position.a); |
766 | if (selector->position.b > 0) |
767 | g_string_append_printf (string, format: "+%d)" , selector->position.b); |
768 | else if (selector->position.b < 0) |
769 | g_string_append_printf (string, format: "%d)" , selector->position.b); |
770 | else |
771 | g_string_append (string, val: ")" ); |
772 | } |
773 | break; |
774 | case POSITION_ONLY: |
775 | g_string_append (string, val: ":only-child" ); |
776 | break; |
777 | default: |
778 | g_assert_not_reached (); |
779 | break; |
780 | } |
781 | } |
782 | |
783 | static gboolean |
784 | match_position (GtkCssNode *node, |
785 | GtkCssNode *(* prev_node_func) (GtkCssNode *), |
786 | int a, |
787 | int b) |
788 | { |
789 | int pos, x; |
790 | |
791 | /* special-case the common "first-child" and "last-child" */ |
792 | if (a == 0) |
793 | { |
794 | while (b > 0 && node != NULL) |
795 | { |
796 | b--; |
797 | node = prev_node_func (node); |
798 | } |
799 | |
800 | return b == 0 && node == NULL; |
801 | } |
802 | |
803 | /* count nodes */ |
804 | for (pos = 0; node != NULL; pos++) |
805 | node = prev_node_func (node); |
806 | |
807 | /* solve pos = a * X + b |
808 | * and return TRUE if X is integer >= 0 */ |
809 | x = pos - b; |
810 | |
811 | if (x % a) |
812 | return FALSE; |
813 | |
814 | return x / a >= 0; |
815 | } |
816 | |
817 | static gboolean |
818 | match_pseudoclass_position (const GtkCssSelector *selector, |
819 | GtkCssNode *node) |
820 | { |
821 | switch (selector->position.type) |
822 | { |
823 | case POSITION_FORWARD: |
824 | if (!match_position (node, prev_node_func: get_previous_visible_sibling, a: selector->position.a, b: selector->position.b)) |
825 | return FALSE; |
826 | break; |
827 | case POSITION_BACKWARD: |
828 | if (!match_position (node, prev_node_func: get_next_visible_sibling, a: selector->position.a, b: selector->position.b)) |
829 | return FALSE; |
830 | break; |
831 | case POSITION_ONLY: |
832 | if (get_previous_visible_sibling (node) || |
833 | get_next_visible_sibling (node)) |
834 | return FALSE; |
835 | break; |
836 | default: |
837 | g_assert_not_reached (); |
838 | return FALSE; |
839 | } |
840 | |
841 | return TRUE; |
842 | } |
843 | |
844 | static guint |
845 | hash_pseudoclass_position (const GtkCssSelector *a) |
846 | { |
847 | return (guint)(((((gulong)a->position.type) << POSITION_NUMBER_BITS) | a->position.a) << POSITION_NUMBER_BITS) | a->position.b; |
848 | } |
849 | |
850 | static int |
851 | comp_pseudoclass_position (const GtkCssSelector *a, |
852 | const GtkCssSelector *b) |
853 | { |
854 | int diff; |
855 | |
856 | diff = a->position.type - b->position.type; |
857 | if (diff) |
858 | return diff; |
859 | |
860 | diff = a->position.a - b->position.a; |
861 | if (diff) |
862 | return diff; |
863 | |
864 | return a->position.b - b->position.b; |
865 | } |
866 | |
867 | static GtkCssChange |
868 | change_pseudoclass_position (const GtkCssSelector *selector) |
869 | { |
870 | switch (selector->position.type) |
871 | { |
872 | case POSITION_FORWARD: |
873 | if (selector->position.a == 0 && selector->position.b == 1) |
874 | return GTK_CSS_CHANGE_FIRST_CHILD; |
875 | else |
876 | return GTK_CSS_CHANGE_NTH_CHILD; |
877 | case POSITION_BACKWARD: |
878 | if (selector->position.a == 0 && selector->position.b == 1) |
879 | return GTK_CSS_CHANGE_LAST_CHILD; |
880 | else |
881 | return GTK_CSS_CHANGE_NTH_LAST_CHILD; |
882 | case POSITION_ONLY: |
883 | return GTK_CSS_CHANGE_FIRST_CHILD | GTK_CSS_CHANGE_LAST_CHILD; |
884 | default: |
885 | g_assert_not_reached (); |
886 | return 0; |
887 | } |
888 | } |
889 | |
890 | #define GTK_CSS_CHANGE_PSEUDOCLASS_POSITION change_pseudoclass_position(selector) |
891 | DEFINE_SIMPLE_SELECTOR(pseudoclass_position, PSEUDOCLASS_POSITION, print_pseudoclass_position, |
892 | match_pseudoclass_position, hash_pseudoclass_position, comp_pseudoclass_position, |
893 | FALSE, TRUE, FALSE, TRUE) |
894 | #undef GTK_CSS_CHANGE_PSEUDOCLASS_POSITION |
895 | /* API */ |
896 | |
897 | static guint |
898 | gtk_css_selector_size (const GtkCssSelector *selector) |
899 | { |
900 | guint size = 0; |
901 | |
902 | while (selector) |
903 | { |
904 | selector = gtk_css_selector_previous (selector); |
905 | size++; |
906 | } |
907 | |
908 | return size; |
909 | } |
910 | |
911 | static GtkCssSelector * |
912 | gtk_css_selector_new (const GtkCssSelectorClass *class, |
913 | GtkCssSelector *selector) |
914 | { |
915 | guint size; |
916 | |
917 | size = gtk_css_selector_size (selector); |
918 | selector = g_realloc (mem: selector, n_bytes: sizeof (GtkCssSelector) * (size + 1) + sizeof (gpointer)); |
919 | if (size == 0) |
920 | selector[1].class = NULL; |
921 | else |
922 | memmove (dest: selector + 1, src: selector, n: sizeof (GtkCssSelector) * size + sizeof (gpointer)); |
923 | |
924 | memset (s: selector, c: 0, n: sizeof (GtkCssSelector)); |
925 | selector->class = class; |
926 | |
927 | return selector; |
928 | } |
929 | |
930 | static GtkCssSelector * |
931 | gtk_css_selector_parse_selector_class (GtkCssParser *parser, |
932 | GtkCssSelector *selector, |
933 | gboolean negate) |
934 | { |
935 | const GtkCssToken *token; |
936 | |
937 | gtk_css_parser_consume_token (self: parser); |
938 | for (token = gtk_css_parser_peek_token (self: parser); |
939 | gtk_css_token_is (token, GTK_CSS_TOKEN_COMMENT); |
940 | token = gtk_css_parser_peek_token (self: parser)) |
941 | { |
942 | gtk_css_parser_consume_token (self: parser); |
943 | } |
944 | |
945 | if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT)) |
946 | { |
947 | selector = gtk_css_selector_new (class: negate ? >K_CSS_SELECTOR_NOT_CLASS |
948 | : >K_CSS_SELECTOR_CLASS, |
949 | selector); |
950 | selector->style_class.style_class = g_quark_from_string (string: token->string.string); |
951 | gtk_css_parser_consume_token (self: parser); |
952 | return selector; |
953 | } |
954 | else |
955 | { |
956 | gtk_css_parser_error_syntax (self: parser, format: "No class name after '.' in selector" ); |
957 | if (selector) |
958 | _gtk_css_selector_free (selector); |
959 | return NULL; |
960 | } |
961 | } |
962 | |
963 | static gboolean |
964 | string_has_number (const char *string, |
965 | const char *prefix, |
966 | int *number) |
967 | { |
968 | gsize len = strlen (s: prefix); |
969 | char *end; |
970 | |
971 | if (g_ascii_strncasecmp (s1: string, s2: prefix, n: len) != 0) |
972 | return FALSE; |
973 | |
974 | errno = 0; |
975 | *number = strtoul (nptr: string + len, endptr: &end, base: 10); |
976 | if (*end != '\0' || errno != 0) |
977 | return FALSE; |
978 | |
979 | return TRUE; |
980 | } |
981 | |
982 | static gboolean |
983 | parse_plus_b (GtkCssParser *parser, |
984 | gboolean negate, |
985 | int *b) |
986 | { |
987 | const GtkCssToken *token; |
988 | gboolean has_seen_sign; |
989 | |
990 | token = gtk_css_parser_get_token (self: parser); |
991 | |
992 | if (negate) |
993 | { |
994 | has_seen_sign = TRUE; |
995 | } |
996 | else |
997 | { |
998 | if (gtk_css_token_is_delim (token, delim: '+')) |
999 | { |
1000 | gtk_css_parser_consume_token (self: parser); |
1001 | has_seen_sign = TRUE; |
1002 | } |
1003 | else if (gtk_css_token_is_delim (token, delim: '-')) |
1004 | { |
1005 | gtk_css_parser_consume_token (self: parser); |
1006 | negate = TRUE; |
1007 | has_seen_sign = TRUE; |
1008 | } |
1009 | else |
1010 | { |
1011 | has_seen_sign = FALSE; |
1012 | } |
1013 | } |
1014 | |
1015 | token = gtk_css_parser_get_token (self: parser); |
1016 | if (!has_seen_sign && gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER)) |
1017 | { |
1018 | *b = token->number.number; |
1019 | gtk_css_parser_consume_token (self: parser); |
1020 | return TRUE; |
1021 | } |
1022 | else if (has_seen_sign && gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER)) |
1023 | { |
1024 | *b = token->number.number; |
1025 | if (negate) |
1026 | *b = - *b; |
1027 | gtk_css_parser_consume_token (self: parser); |
1028 | return TRUE; |
1029 | } |
1030 | else if (!has_seen_sign) |
1031 | { |
1032 | *b = 0; |
1033 | return TRUE; |
1034 | } |
1035 | |
1036 | gtk_css_parser_error_syntax (self: parser, format: "Not a valid an+b type" ); |
1037 | return FALSE; |
1038 | } |
1039 | |
1040 | static gboolean |
1041 | parse_n_plus_b (GtkCssParser *parser, |
1042 | int before, |
1043 | int *a, |
1044 | int *b) |
1045 | { |
1046 | const GtkCssToken *token; |
1047 | |
1048 | token = gtk_css_parser_get_token (self: parser); |
1049 | |
1050 | if (gtk_css_token_is_ident (token, ident: "n" )) |
1051 | { |
1052 | *a = before; |
1053 | gtk_css_parser_consume_token (self: parser); |
1054 | return parse_plus_b (parser, FALSE, b); |
1055 | } |
1056 | else if (gtk_css_token_is_ident (token, ident: "n-" )) |
1057 | { |
1058 | *a = before; |
1059 | gtk_css_parser_consume_token (self: parser); |
1060 | return parse_plus_b (parser, TRUE, b); |
1061 | } |
1062 | else if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) && |
1063 | string_has_number (string: token->string.string, prefix: "n-" , number: b)) |
1064 | { |
1065 | *a = before; |
1066 | *b = -*b; |
1067 | gtk_css_parser_consume_token (self: parser); |
1068 | return TRUE; |
1069 | } |
1070 | else |
1071 | { |
1072 | *b = before; |
1073 | *a = 0; |
1074 | return TRUE; |
1075 | } |
1076 | } |
1077 | |
1078 | static gboolean |
1079 | parse_a_n_plus_b (GtkCssParser *parser, |
1080 | int seen_sign, |
1081 | int *a, |
1082 | int *b) |
1083 | { |
1084 | const GtkCssToken *token; |
1085 | |
1086 | token = gtk_css_parser_get_token (self: parser); |
1087 | |
1088 | if (!seen_sign && gtk_css_token_is_ident (token, ident: "even" )) |
1089 | { |
1090 | *a = 2; |
1091 | *b = 0; |
1092 | gtk_css_parser_consume_token (self: parser); |
1093 | return TRUE; |
1094 | } |
1095 | else if (!seen_sign && gtk_css_token_is_ident (token, ident: "odd" )) |
1096 | { |
1097 | *a = 2; |
1098 | *b = 1; |
1099 | gtk_css_parser_consume_token (self: parser); |
1100 | return TRUE; |
1101 | } |
1102 | else if (!seen_sign && gtk_css_token_is_delim (token, delim: '+')) |
1103 | { |
1104 | gtk_css_parser_consume_token (self: parser); |
1105 | return parse_a_n_plus_b (parser, seen_sign: 1, a, b); |
1106 | } |
1107 | else if (!seen_sign && gtk_css_token_is_delim (token, delim: '-')) |
1108 | { |
1109 | gtk_css_parser_consume_token (self: parser); |
1110 | return parse_a_n_plus_b (parser, seen_sign: -1, a, b); |
1111 | } |
1112 | else if ((!seen_sign && gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER)) || |
1113 | gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER)) |
1114 | { |
1115 | int x = token->number.number * (seen_sign ? seen_sign : 1); |
1116 | gtk_css_parser_consume_token (self: parser); |
1117 | |
1118 | return parse_n_plus_b (parser, before: x , a, b); |
1119 | } |
1120 | else if (((!seen_sign && gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION)) || |
1121 | gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION)) && |
1122 | g_ascii_strcasecmp (s1: token->dimension.dimension, s2: "n" ) == 0) |
1123 | { |
1124 | *a = token->dimension.value * (seen_sign ? seen_sign : 1); |
1125 | gtk_css_parser_consume_token (self: parser); |
1126 | return parse_plus_b (parser, FALSE, b); |
1127 | } |
1128 | else if (((!seen_sign && gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION)) || |
1129 | gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION)) && |
1130 | g_ascii_strcasecmp (s1: token->dimension.dimension, s2: "n-" ) == 0) |
1131 | { |
1132 | *a = token->dimension.value * (seen_sign ? seen_sign : 1); |
1133 | gtk_css_parser_consume_token (self: parser); |
1134 | return parse_plus_b (parser, TRUE, b); |
1135 | } |
1136 | else if (((!seen_sign && gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNED_INTEGER_DIMENSION)) || |
1137 | gtk_css_token_is (token, GTK_CSS_TOKEN_SIGNLESS_INTEGER_DIMENSION)) && |
1138 | string_has_number (string: token->dimension.dimension, prefix: "n-" , number: b)) |
1139 | { |
1140 | *a = token->dimension.value * (seen_sign ? seen_sign : 1); |
1141 | *b = -*b; |
1142 | gtk_css_parser_consume_token (self: parser); |
1143 | return TRUE; |
1144 | } |
1145 | else if (!seen_sign && gtk_css_token_is_ident (token, ident: "-n" )) |
1146 | { |
1147 | *a = -1; |
1148 | gtk_css_parser_consume_token (self: parser); |
1149 | return parse_plus_b (parser, FALSE, b); |
1150 | } |
1151 | else if (!seen_sign && gtk_css_token_is_ident (token, ident: "-n-" )) |
1152 | { |
1153 | *a = -1; |
1154 | gtk_css_parser_consume_token (self: parser); |
1155 | return parse_plus_b (parser, TRUE, b); |
1156 | } |
1157 | else if (!seen_sign && |
1158 | gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) && |
1159 | string_has_number (string: token->string.string, prefix: "-n-" , number: b)) |
1160 | { |
1161 | *a = -1; |
1162 | *b = -*b; |
1163 | gtk_css_parser_consume_token (self: parser); |
1164 | return TRUE; |
1165 | } |
1166 | else if (gtk_css_token_is_ident (token, ident: "n" ) || |
1167 | gtk_css_token_is_ident (token, ident: "n-" )) |
1168 | { |
1169 | return parse_n_plus_b (parser, before: seen_sign ? seen_sign : 1, a, b); |
1170 | } |
1171 | else if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) && |
1172 | string_has_number (string: token->string.string, prefix: "n-" , number: b)) |
1173 | { |
1174 | *a = seen_sign ? seen_sign : 1; |
1175 | *b = -*b; |
1176 | gtk_css_parser_consume_token (self: parser); |
1177 | return TRUE; |
1178 | } |
1179 | else if (!seen_sign && gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) && |
1180 | string_has_number (string: token->string.string, prefix: "-n-" , number: b)) |
1181 | { |
1182 | *a = -1; |
1183 | *b = -*b; |
1184 | gtk_css_parser_consume_token (self: parser); |
1185 | return TRUE; |
1186 | } |
1187 | |
1188 | gtk_css_parser_error_syntax (self: parser, format: "Not a valid an+b type" ); |
1189 | return FALSE; |
1190 | } |
1191 | |
1192 | static guint |
1193 | parse_a_n_plus_b_arg (GtkCssParser *parser, |
1194 | guint arg, |
1195 | gpointer data) |
1196 | { |
1197 | int *ab = data; |
1198 | |
1199 | if (!parse_a_n_plus_b (parser, FALSE, a: &ab[0], b: &ab[1])) |
1200 | return 0; |
1201 | |
1202 | return 1; |
1203 | } |
1204 | |
1205 | static guint |
1206 | parse_dir_arg (GtkCssParser *parser, |
1207 | guint arg, |
1208 | gpointer data) |
1209 | { |
1210 | GtkStateFlags *flag = data; |
1211 | |
1212 | if (gtk_css_parser_try_ident (self: parser, ident: "ltr" )) |
1213 | { |
1214 | *flag = GTK_STATE_FLAG_DIR_LTR; |
1215 | return 1; |
1216 | } |
1217 | else if (gtk_css_parser_try_ident (self: parser, ident: "rtl" )) |
1218 | { |
1219 | *flag = GTK_STATE_FLAG_DIR_RTL; |
1220 | return 1; |
1221 | } |
1222 | else |
1223 | { |
1224 | gtk_css_parser_error_value (self: parser, format: "Expected \"ltr\" or \"rtl\"" ); |
1225 | return 0; |
1226 | } |
1227 | } |
1228 | |
1229 | static guint |
1230 | parse_identifier_arg (GtkCssParser *parser, |
1231 | guint arg, |
1232 | gpointer data) |
1233 | { |
1234 | const char *ident = data; |
1235 | |
1236 | if (!gtk_css_parser_try_ident (self: parser, ident)) |
1237 | { |
1238 | gtk_css_parser_error_value (self: parser, format: "Expected \"%s\"" , ident); |
1239 | return 0; |
1240 | } |
1241 | |
1242 | return 1; |
1243 | } |
1244 | |
1245 | static GtkCssSelector * |
1246 | gtk_css_selector_parse_selector_pseudo_class (GtkCssParser *parser, |
1247 | GtkCssSelector *selector, |
1248 | gboolean negate) |
1249 | { |
1250 | GtkCssLocation start_location; |
1251 | const GtkCssToken *token; |
1252 | |
1253 | start_location = *gtk_css_parser_get_start_location (self: parser); |
1254 | gtk_css_parser_consume_token (self: parser); |
1255 | for (token = gtk_css_parser_peek_token (self: parser); |
1256 | gtk_css_token_is (token, GTK_CSS_TOKEN_COMMENT); |
1257 | token = gtk_css_parser_peek_token (self: parser)) |
1258 | { |
1259 | gtk_css_parser_consume_token (self: parser); |
1260 | } |
1261 | |
1262 | if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT)) |
1263 | { |
1264 | static const struct { |
1265 | const char *name; |
1266 | GtkStateFlags state_flag; |
1267 | PositionType position_type; |
1268 | int position_a; |
1269 | int position_b; |
1270 | } pseudo_classes[] = { |
1271 | { "first-child" , 0, POSITION_FORWARD, 0, 1 }, |
1272 | { "last-child" , 0, POSITION_BACKWARD, 0, 1 }, |
1273 | { "only-child" , 0, POSITION_ONLY, 0, 0 }, |
1274 | { "active" , GTK_STATE_FLAG_ACTIVE, }, |
1275 | { "hover" , GTK_STATE_FLAG_PRELIGHT, }, |
1276 | { "selected" , GTK_STATE_FLAG_SELECTED, }, |
1277 | { "disabled" , GTK_STATE_FLAG_INSENSITIVE, }, |
1278 | { "indeterminate" , GTK_STATE_FLAG_INCONSISTENT, }, |
1279 | { "focus" , GTK_STATE_FLAG_FOCUSED, }, |
1280 | { "backdrop" , GTK_STATE_FLAG_BACKDROP, }, |
1281 | { "link" , GTK_STATE_FLAG_LINK, }, |
1282 | { "visited" , GTK_STATE_FLAG_VISITED, }, |
1283 | { "checked" , GTK_STATE_FLAG_CHECKED, }, |
1284 | { "focus-visible" , GTK_STATE_FLAG_FOCUS_VISIBLE, }, |
1285 | { "focus-within" , GTK_STATE_FLAG_FOCUS_WITHIN, }, |
1286 | }; |
1287 | guint i; |
1288 | |
1289 | for (i = 0; i < G_N_ELEMENTS (pseudo_classes); i++) |
1290 | { |
1291 | if (g_ascii_strcasecmp (s1: pseudo_classes[i].name, s2: token->string.string) == 0) |
1292 | { |
1293 | if (pseudo_classes[i].state_flag) |
1294 | { |
1295 | selector = gtk_css_selector_new (class: negate ? >K_CSS_SELECTOR_NOT_PSEUDOCLASS_STATE |
1296 | : >K_CSS_SELECTOR_PSEUDOCLASS_STATE, |
1297 | selector); |
1298 | selector->state.state = pseudo_classes[i].state_flag; |
1299 | } |
1300 | else |
1301 | { |
1302 | selector = gtk_css_selector_new (class: negate ? >K_CSS_SELECTOR_NOT_PSEUDOCLASS_POSITION |
1303 | : >K_CSS_SELECTOR_PSEUDOCLASS_POSITION, |
1304 | selector); |
1305 | selector->position.type = pseudo_classes[i].position_type; |
1306 | selector->position.a = pseudo_classes[i].position_a; |
1307 | selector->position.b = pseudo_classes[i].position_b; |
1308 | } |
1309 | gtk_css_parser_consume_token (self: parser); |
1310 | return selector; |
1311 | } |
1312 | } |
1313 | |
1314 | gtk_css_parser_error (self: parser, |
1315 | code: GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE, |
1316 | start: &start_location, |
1317 | end: gtk_css_parser_get_end_location (self: parser), |
1318 | format: "Unknown name of pseudo-class" ); |
1319 | if (selector) |
1320 | _gtk_css_selector_free (selector); |
1321 | return NULL; |
1322 | } |
1323 | else if (gtk_css_token_is (token, GTK_CSS_TOKEN_FUNCTION)) |
1324 | { |
1325 | if (gtk_css_token_is_function (token, ident: "nth-child" )) |
1326 | { |
1327 | int ab[2]; |
1328 | |
1329 | if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: parse_a_n_plus_b_arg, data: ab)) |
1330 | { |
1331 | if (selector) |
1332 | _gtk_css_selector_free (selector); |
1333 | return NULL; |
1334 | } |
1335 | |
1336 | selector = gtk_css_selector_new (class: negate ? >K_CSS_SELECTOR_NOT_PSEUDOCLASS_POSITION |
1337 | : >K_CSS_SELECTOR_PSEUDOCLASS_POSITION, |
1338 | selector); |
1339 | selector->position.type = POSITION_FORWARD; |
1340 | selector->position.a = ab[0]; |
1341 | selector->position.b = ab[1]; |
1342 | } |
1343 | else if (gtk_css_token_is_function (token, ident: "nth-last-child" )) |
1344 | { |
1345 | int ab[2]; |
1346 | |
1347 | if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: parse_a_n_plus_b_arg, data: ab)) |
1348 | { |
1349 | if (selector) |
1350 | _gtk_css_selector_free (selector); |
1351 | return NULL; |
1352 | } |
1353 | |
1354 | selector = gtk_css_selector_new (class: negate ? >K_CSS_SELECTOR_NOT_PSEUDOCLASS_POSITION |
1355 | : >K_CSS_SELECTOR_PSEUDOCLASS_POSITION, |
1356 | selector); |
1357 | selector->position.type = POSITION_BACKWARD; |
1358 | selector->position.a = ab[0]; |
1359 | selector->position.b = ab[1]; |
1360 | } |
1361 | else if (gtk_css_token_is_function (token, ident: "not" )) |
1362 | { |
1363 | if (negate) |
1364 | { |
1365 | gtk_css_parser_error_syntax (self: parser, format: "Nesting of :not() not allowed" ); |
1366 | if (selector) |
1367 | _gtk_css_selector_free (selector); |
1368 | return NULL; |
1369 | } |
1370 | else |
1371 | { |
1372 | gtk_css_parser_start_block (self: parser); |
1373 | token = gtk_css_parser_get_token (self: parser); |
1374 | |
1375 | if (gtk_css_token_is_delim (token, delim: '*')) |
1376 | { |
1377 | selector = gtk_css_selector_new (class: >K_CSS_SELECTOR_NOT_ANY, selector); |
1378 | gtk_css_parser_consume_token (self: parser); |
1379 | } |
1380 | else if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT)) |
1381 | { |
1382 | selector = gtk_css_selector_new (class: >K_CSS_SELECTOR_NOT_NAME, selector); |
1383 | selector->name.name = g_quark_from_string (string: token->string.string); |
1384 | gtk_css_parser_consume_token (self: parser); |
1385 | } |
1386 | else if (gtk_css_token_is (token, GTK_CSS_TOKEN_HASH_ID)) |
1387 | { |
1388 | selector = gtk_css_selector_new (class: >K_CSS_SELECTOR_NOT_ID, selector); |
1389 | selector->id.name = g_quark_from_string (string: token->string.string); |
1390 | gtk_css_parser_consume_token (self: parser); |
1391 | } |
1392 | else if (gtk_css_token_is_delim (token, delim: '.')) |
1393 | { |
1394 | selector = gtk_css_selector_parse_selector_class (parser, selector, TRUE); |
1395 | } |
1396 | else if (gtk_css_token_is (token, GTK_CSS_TOKEN_COLON)) |
1397 | { |
1398 | selector = gtk_css_selector_parse_selector_pseudo_class (parser, selector, TRUE); |
1399 | } |
1400 | else |
1401 | { |
1402 | gtk_css_parser_error_syntax (self: parser, format: "Invalid contents of :not() selector" ); |
1403 | gtk_css_parser_end_block (self: parser); |
1404 | if (selector) |
1405 | _gtk_css_selector_free (selector); |
1406 | selector = NULL; |
1407 | return NULL; |
1408 | } |
1409 | |
1410 | token = gtk_css_parser_get_token (self: parser); |
1411 | if (!gtk_css_token_is (token, GTK_CSS_TOKEN_EOF)) |
1412 | { |
1413 | gtk_css_parser_error_syntax (self: parser, format: "Invalid contents of :not() selector" ); |
1414 | gtk_css_parser_end_block (self: parser); |
1415 | if (selector) |
1416 | _gtk_css_selector_free (selector); |
1417 | selector = NULL; |
1418 | return NULL; |
1419 | } |
1420 | gtk_css_parser_end_block (self: parser); |
1421 | } |
1422 | } |
1423 | else if (gtk_css_token_is_function (token, ident: "dir" )) |
1424 | { |
1425 | GtkStateFlags flag; |
1426 | |
1427 | if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: parse_dir_arg, data: &flag)) |
1428 | { |
1429 | if (selector) |
1430 | _gtk_css_selector_free (selector); |
1431 | return NULL; |
1432 | } |
1433 | |
1434 | selector = gtk_css_selector_new (class: negate ? >K_CSS_SELECTOR_NOT_PSEUDOCLASS_STATE |
1435 | : >K_CSS_SELECTOR_PSEUDOCLASS_STATE, |
1436 | selector); |
1437 | selector->state.state = flag; |
1438 | } |
1439 | else if (gtk_css_token_is_function (token, ident: "drop" )) |
1440 | { |
1441 | if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: parse_identifier_arg, data: (gpointer) "active" )) |
1442 | { |
1443 | if (selector) |
1444 | _gtk_css_selector_free (selector); |
1445 | return NULL; |
1446 | } |
1447 | selector = gtk_css_selector_new (class: negate ? >K_CSS_SELECTOR_NOT_PSEUDOCLASS_STATE |
1448 | : >K_CSS_SELECTOR_PSEUDOCLASS_STATE, |
1449 | selector); |
1450 | selector->state.state = GTK_STATE_FLAG_DROP_ACTIVE; |
1451 | } |
1452 | else |
1453 | { |
1454 | gtk_css_parser_error (self: parser, |
1455 | code: GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE, |
1456 | start: &start_location, |
1457 | end: gtk_css_parser_get_end_location (self: parser), |
1458 | format: "Unknown pseudoclass" ); |
1459 | if (selector) |
1460 | _gtk_css_selector_free (selector); |
1461 | return NULL; |
1462 | } |
1463 | } |
1464 | else |
1465 | { |
1466 | gtk_css_parser_error (self: parser, |
1467 | code: GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE, |
1468 | start: &start_location, |
1469 | end: gtk_css_parser_get_end_location (self: parser), |
1470 | format: "Unknown pseudoclass" ); |
1471 | if (selector) |
1472 | _gtk_css_selector_free (selector); |
1473 | return NULL; |
1474 | } |
1475 | |
1476 | return selector; |
1477 | } |
1478 | |
1479 | static GtkCssSelector * |
1480 | gtk_css_selector_parse_simple_selector (GtkCssParser *parser, |
1481 | GtkCssSelector *selector) |
1482 | { |
1483 | gboolean parsed_something = FALSE; |
1484 | const GtkCssToken *token; |
1485 | |
1486 | do { |
1487 | for (token = gtk_css_parser_peek_token (self: parser); |
1488 | gtk_css_token_is (token, GTK_CSS_TOKEN_COMMENT); |
1489 | token = gtk_css_parser_peek_token (self: parser)) |
1490 | { |
1491 | gtk_css_parser_consume_token (self: parser); |
1492 | } |
1493 | |
1494 | if (!parsed_something && gtk_css_token_is_delim (token, delim: '*')) |
1495 | { |
1496 | selector = gtk_css_selector_new (class: >K_CSS_SELECTOR_ANY, selector); |
1497 | gtk_css_parser_consume_token (self: parser); |
1498 | } |
1499 | else if (!parsed_something && gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT)) |
1500 | { |
1501 | selector = gtk_css_selector_new (class: >K_CSS_SELECTOR_NAME, selector); |
1502 | selector->name.name = g_quark_from_string (string: token->string.string); |
1503 | gtk_css_parser_consume_token (self: parser); |
1504 | } |
1505 | else if (gtk_css_token_is (token, GTK_CSS_TOKEN_HASH_ID)) |
1506 | { |
1507 | selector = gtk_css_selector_new (class: >K_CSS_SELECTOR_ID, selector); |
1508 | selector->id.name = g_quark_from_string (string: token->string.string); |
1509 | gtk_css_parser_consume_token (self: parser); |
1510 | } |
1511 | else if (gtk_css_token_is_delim (token, delim: '.')) |
1512 | { |
1513 | selector = gtk_css_selector_parse_selector_class (parser, selector, FALSE); |
1514 | } |
1515 | else if (gtk_css_token_is (token, GTK_CSS_TOKEN_COLON)) |
1516 | { |
1517 | selector = gtk_css_selector_parse_selector_pseudo_class (parser, selector, FALSE); |
1518 | } |
1519 | else |
1520 | { |
1521 | if (!parsed_something) |
1522 | { |
1523 | gtk_css_parser_error_syntax (self: parser, format: "Expected a valid selector" ); |
1524 | if (selector) |
1525 | _gtk_css_selector_free (selector); |
1526 | selector = NULL; |
1527 | } |
1528 | break; |
1529 | } |
1530 | |
1531 | parsed_something = TRUE; |
1532 | } |
1533 | while (TRUE); |
1534 | |
1535 | return selector; |
1536 | } |
1537 | |
1538 | GtkCssSelector * |
1539 | _gtk_css_selector_parse (GtkCssParser *parser) |
1540 | { |
1541 | GtkCssSelector *selector = NULL; |
1542 | const GtkCssToken *token; |
1543 | |
1544 | while (TRUE) |
1545 | { |
1546 | gboolean seen_whitespace = FALSE; |
1547 | |
1548 | /* skip all whitespace and comments */ |
1549 | gtk_css_parser_get_token (self: parser); |
1550 | |
1551 | selector = gtk_css_selector_parse_simple_selector (parser, selector); |
1552 | if (selector == NULL) |
1553 | return NULL; |
1554 | |
1555 | for (token = gtk_css_parser_peek_token (self: parser); |
1556 | gtk_css_token_is (token, GTK_CSS_TOKEN_COMMENT) || |
1557 | gtk_css_token_is (token, GTK_CSS_TOKEN_WHITESPACE); |
1558 | token = gtk_css_parser_peek_token (self: parser)) |
1559 | { |
1560 | seen_whitespace |= gtk_css_token_is (token, GTK_CSS_TOKEN_WHITESPACE); |
1561 | gtk_css_parser_consume_token (self: parser); |
1562 | } |
1563 | |
1564 | if (gtk_css_token_is_delim (token, delim: '+')) |
1565 | { |
1566 | selector = gtk_css_selector_new (class: >K_CSS_SELECTOR_ADJACENT, selector); |
1567 | gtk_css_parser_consume_token (self: parser); |
1568 | } |
1569 | else if (gtk_css_token_is_delim (token, delim: '~')) |
1570 | { |
1571 | selector = gtk_css_selector_new (class: >K_CSS_SELECTOR_SIBLING, selector); |
1572 | gtk_css_parser_consume_token (self: parser); |
1573 | } |
1574 | else if (gtk_css_token_is_delim (token, delim: '>')) |
1575 | { |
1576 | selector = gtk_css_selector_new (class: >K_CSS_SELECTOR_CHILD, selector); |
1577 | gtk_css_parser_consume_token (self: parser); |
1578 | } |
1579 | else if (gtk_css_token_is (token, GTK_CSS_TOKEN_EOF) || |
1580 | gtk_css_token_is (token, GTK_CSS_TOKEN_COMMA) || |
1581 | gtk_css_token_is (token, GTK_CSS_TOKEN_OPEN_CURLY)) |
1582 | { |
1583 | break; |
1584 | } |
1585 | else if (seen_whitespace) |
1586 | { |
1587 | selector = gtk_css_selector_new (class: >K_CSS_SELECTOR_DESCENDANT, selector); |
1588 | } |
1589 | else |
1590 | { |
1591 | gtk_css_parser_error_syntax (self: parser, format: "Expected a valid selector" ); |
1592 | _gtk_css_selector_free (selector); |
1593 | return NULL; |
1594 | } |
1595 | } |
1596 | |
1597 | return selector; |
1598 | } |
1599 | |
1600 | void |
1601 | _gtk_css_selector_free (GtkCssSelector *selector) |
1602 | { |
1603 | g_return_if_fail (selector != NULL); |
1604 | |
1605 | g_free (mem: selector); |
1606 | } |
1607 | |
1608 | void |
1609 | _gtk_css_selector_print (const GtkCssSelector *selector, |
1610 | GString * str) |
1611 | { |
1612 | const GtkCssSelector *previous; |
1613 | |
1614 | g_return_if_fail (selector != NULL); |
1615 | |
1616 | previous = gtk_css_selector_previous (selector); |
1617 | if (previous) |
1618 | _gtk_css_selector_print (selector: previous, str); |
1619 | |
1620 | selector->class->print (selector, str); |
1621 | } |
1622 | |
1623 | char * |
1624 | _gtk_css_selector_to_string (const GtkCssSelector *selector) |
1625 | { |
1626 | GString *string; |
1627 | |
1628 | g_return_val_if_fail (selector != NULL, NULL); |
1629 | |
1630 | string = g_string_new (NULL); |
1631 | |
1632 | _gtk_css_selector_print (selector, str: string); |
1633 | |
1634 | return g_string_free (string, FALSE); |
1635 | } |
1636 | |
1637 | /** |
1638 | * gtk_css_selector_matches: |
1639 | * @selector: the selector |
1640 | * @node: The node to match |
1641 | * |
1642 | * Checks if the @selector matches the given @node. |
1643 | * |
1644 | * Returns: %TRUE if the selector matches @node |
1645 | **/ |
1646 | gboolean |
1647 | gtk_css_selector_matches (const GtkCssSelector *selector, |
1648 | GtkCssNode *node) |
1649 | { |
1650 | GtkCssNode *child; |
1651 | const GtkCssSelector *prev; |
1652 | |
1653 | g_return_val_if_fail (selector != NULL, FALSE); |
1654 | g_return_val_if_fail (node != NULL, FALSE); |
1655 | |
1656 | if (!gtk_css_selector_match_one (selector, node)) |
1657 | return FALSE; |
1658 | |
1659 | prev = gtk_css_selector_previous (selector); |
1660 | if (prev == NULL) |
1661 | return TRUE; |
1662 | |
1663 | for (child = gtk_css_selector_iterator (selector, node, NULL); |
1664 | child; |
1665 | child = gtk_css_selector_iterator (selector, node, current: child)) |
1666 | { |
1667 | if (gtk_css_selector_matches (selector: prev, node: child)) |
1668 | return TRUE; |
1669 | } |
1670 | |
1671 | return FALSE; |
1672 | } |
1673 | |
1674 | /* Computes specificity according to CSS 2.1. |
1675 | * The arguments must be initialized to 0 */ |
1676 | static void |
1677 | _gtk_css_selector_get_specificity (const GtkCssSelector *selector, |
1678 | guint *ids, |
1679 | guint *classes, |
1680 | guint *elements) |
1681 | { |
1682 | for (; selector; selector = gtk_css_selector_previous (selector)) |
1683 | { |
1684 | selector->class->add_specificity (selector, ids, classes, elements); |
1685 | } |
1686 | } |
1687 | |
1688 | int |
1689 | _gtk_css_selector_compare (const GtkCssSelector *a, |
1690 | const GtkCssSelector *b) |
1691 | { |
1692 | guint a_ids = 0, a_classes = 0, a_elements = 0; |
1693 | guint b_ids = 0, b_classes = 0, b_elements = 0; |
1694 | int compare; |
1695 | |
1696 | _gtk_css_selector_get_specificity (selector: a, ids: &a_ids, classes: &a_classes, elements: &a_elements); |
1697 | _gtk_css_selector_get_specificity (selector: b, ids: &b_ids, classes: &b_classes, elements: &b_elements); |
1698 | |
1699 | compare = a_ids - b_ids; |
1700 | if (compare) |
1701 | return compare; |
1702 | |
1703 | compare = a_classes - b_classes; |
1704 | if (compare) |
1705 | return compare; |
1706 | |
1707 | return a_elements - b_elements; |
1708 | } |
1709 | |
1710 | GtkCssChange |
1711 | _gtk_css_selector_get_change (const GtkCssSelector *selector) |
1712 | { |
1713 | if (selector == NULL) |
1714 | return 0; |
1715 | |
1716 | return selector->class->get_change (selector, _gtk_css_selector_get_change (selector: gtk_css_selector_previous (selector))); |
1717 | } |
1718 | |
1719 | /******************** SelectorTree handling *****************/ |
1720 | |
1721 | static gboolean |
1722 | gtk_css_selector_is_simple (const GtkCssSelector *selector) |
1723 | { |
1724 | switch (selector->class->category) |
1725 | { |
1726 | case GTK_CSS_SELECTOR_CATEGORY_SIMPLE: |
1727 | case GTK_CSS_SELECTOR_CATEGORY_SIMPLE_RADICAL: |
1728 | return TRUE; |
1729 | case GTK_CSS_SELECTOR_CATEGORY_PARENT: |
1730 | case GTK_CSS_SELECTOR_CATEGORY_SIBLING: |
1731 | return FALSE; |
1732 | default: |
1733 | g_assert_not_reached (); |
1734 | return FALSE; |
1735 | } |
1736 | } |
1737 | |
1738 | static GHashTable * |
1739 | gtk_css_selectors_count_initial_init (void) |
1740 | { |
1741 | return g_hash_table_new (hash_func: (GHashFunc)gtk_css_selector_hash_one, key_equal_func: (GEqualFunc)gtk_css_selector_equal); |
1742 | } |
1743 | |
1744 | static void |
1745 | gtk_css_selectors_count_initial (const GtkCssSelector *selector, GHashTable *hash_one) |
1746 | { |
1747 | if (!gtk_css_selector_is_simple (selector)) |
1748 | { |
1749 | guint count = GPOINTER_TO_INT (g_hash_table_lookup (hash_one, selector)); |
1750 | g_hash_table_replace (hash_table: hash_one, key: (gpointer)selector, GUINT_TO_POINTER (count + 1)); |
1751 | return; |
1752 | } |
1753 | |
1754 | for (; |
1755 | selector && gtk_css_selector_is_simple (selector); |
1756 | selector = gtk_css_selector_previous (selector)) |
1757 | { |
1758 | guint count = GPOINTER_TO_INT (g_hash_table_lookup (hash_one, selector)); |
1759 | g_hash_table_replace (hash_table: hash_one, key: (gpointer)selector, GUINT_TO_POINTER (count + 1)); |
1760 | } |
1761 | } |
1762 | |
1763 | static gboolean |
1764 | gtk_css_selectors_has_initial_selector (const GtkCssSelector *selector, const GtkCssSelector *initial) |
1765 | { |
1766 | if (!gtk_css_selector_is_simple (selector)) |
1767 | return gtk_css_selector_equal (a: selector, b: initial); |
1768 | |
1769 | for (; |
1770 | selector && gtk_css_selector_is_simple (selector); |
1771 | selector = gtk_css_selector_previous (selector)) |
1772 | { |
1773 | if (gtk_css_selector_equal (a: selector, b: initial)) |
1774 | return TRUE; |
1775 | } |
1776 | |
1777 | return FALSE; |
1778 | } |
1779 | |
1780 | static GtkCssSelector * |
1781 | gtk_css_selectors_skip_initial_selector (GtkCssSelector *selector, const GtkCssSelector *initial) |
1782 | { |
1783 | GtkCssSelector *found; |
1784 | GtkCssSelector tmp; |
1785 | |
1786 | /* If the initial simple selector is not first, move it there so we can skip it |
1787 | without losing any other selectors */ |
1788 | if (!gtk_css_selector_equal (a: selector, b: initial)) |
1789 | { |
1790 | for (found = selector; found && gtk_css_selector_is_simple (selector: found); found = (GtkCssSelector *)gtk_css_selector_previous (selector: found)) |
1791 | { |
1792 | if (gtk_css_selector_equal (a: found, b: initial)) |
1793 | break; |
1794 | } |
1795 | |
1796 | g_assert (found != NULL && gtk_css_selector_is_simple (found)); |
1797 | |
1798 | tmp = *found; |
1799 | *found = *selector; |
1800 | *selector = tmp; |
1801 | } |
1802 | |
1803 | return (GtkCssSelector *)gtk_css_selector_previous (selector); |
1804 | } |
1805 | |
1806 | /* When checking for changes via the tree we need to know if a rule further |
1807 | down the tree matched, because if so we need to add "our bit" to the |
1808 | Change. For instance in a match like *.class:active we'll |
1809 | get a tree that first checks :active, if that matches we continue down |
1810 | to the tree, and if we get a match we add CHANGE_CLASS. However, the |
1811 | end of the tree where we have a match is an ANY which doesn't actually |
1812 | modify the change, so we don't know if we have a match or not. We fix |
1813 | this by setting GTK_CSS_CHANGE_GOT_MATCH which lets us guarantee |
1814 | that change != 0 on any match. */ |
1815 | #define GTK_CSS_CHANGE_GOT_MATCH GTK_CSS_CHANGE_RESERVED_BIT |
1816 | |
1817 | /* The code for collecting matches assumes that the name, id and classes |
1818 | * of a node remain unchanged, and anything else can change. This needs to |
1819 | * be kept in sync with the definition of 'radical change' in gtkcssnode.c. |
1820 | */ |
1821 | |
1822 | static GtkCssChange |
1823 | gtk_css_selector_tree_get_change (const GtkCssSelectorTree *tree, |
1824 | const GtkCountingBloomFilter *filter, |
1825 | GtkCssNode *node, |
1826 | gboolean skipping) |
1827 | { |
1828 | GtkCssChange change = 0; |
1829 | const GtkCssSelectorTree *prev; |
1830 | |
1831 | switch (tree->selector.class->category) |
1832 | { |
1833 | case GTK_CSS_SELECTOR_CATEGORY_SIMPLE: |
1834 | break; |
1835 | case GTK_CSS_SELECTOR_CATEGORY_SIMPLE_RADICAL: |
1836 | if (skipping) |
1837 | break; |
1838 | if (node) |
1839 | { |
1840 | if (!tree->selector.class->match_one (&tree->selector, node)) |
1841 | return 0; |
1842 | } |
1843 | else if (filter) |
1844 | { |
1845 | if (!gtk_counting_bloom_filter_may_contain (self: filter, |
1846 | hash: gtk_css_selector_hash_one (selector: &tree->selector))) |
1847 | return 0; |
1848 | } |
1849 | break; |
1850 | case GTK_CSS_SELECTOR_CATEGORY_PARENT: |
1851 | skipping = FALSE; |
1852 | node = NULL; |
1853 | break; |
1854 | case GTK_CSS_SELECTOR_CATEGORY_SIBLING: |
1855 | skipping = TRUE; |
1856 | node = NULL; |
1857 | break; |
1858 | default: |
1859 | g_assert_not_reached (); |
1860 | return 0; |
1861 | } |
1862 | |
1863 | for (prev = gtk_css_selector_tree_get_previous (tree); |
1864 | prev != NULL; |
1865 | prev = gtk_css_selector_tree_get_sibling (tree: prev)) |
1866 | change |= gtk_css_selector_tree_get_change (tree: prev, filter, node, skipping); |
1867 | |
1868 | if (change || gtk_css_selector_tree_get_matches (tree)) |
1869 | change = tree->selector.class->get_change (&tree->selector, change & ~GTK_CSS_CHANGE_GOT_MATCH) | GTK_CSS_CHANGE_GOT_MATCH; |
1870 | |
1871 | return change; |
1872 | } |
1873 | |
1874 | static void |
1875 | gtk_css_selector_tree_found_match (const GtkCssSelectorTree *tree, |
1876 | GtkCssSelectorMatches *results) |
1877 | { |
1878 | int i; |
1879 | gpointer *matches; |
1880 | |
1881 | matches = gtk_css_selector_tree_get_matches (tree); |
1882 | if (!matches) |
1883 | return; |
1884 | |
1885 | for (i = 0; matches[i] != NULL; i++) |
1886 | gtk_css_selector_matches_insert_sorted (matches: results, data: matches[i]); |
1887 | } |
1888 | |
1889 | static gboolean |
1890 | gtk_css_selector_tree_match (const GtkCssSelectorTree *tree, |
1891 | const GtkCountingBloomFilter *filter, |
1892 | gboolean match_filter, |
1893 | GtkCssNode *node, |
1894 | GtkCssSelectorMatches *results) |
1895 | { |
1896 | const GtkCssSelectorTree *prev; |
1897 | GtkCssNode *child; |
1898 | |
1899 | if (match_filter && tree->selector.class->category == GTK_CSS_SELECTOR_CATEGORY_SIMPLE_RADICAL && |
1900 | !gtk_counting_bloom_filter_may_contain (self: filter, hash: gtk_css_selector_hash_one (selector: &tree->selector))) |
1901 | return FALSE; |
1902 | |
1903 | if (!gtk_css_selector_match_one (selector: &tree->selector, node)) |
1904 | return TRUE; |
1905 | |
1906 | gtk_css_selector_tree_found_match (tree, results); |
1907 | |
1908 | if (filter && !gtk_css_selector_is_simple (selector: &tree->selector)) |
1909 | match_filter = tree->selector.class->category == GTK_CSS_SELECTOR_CATEGORY_PARENT; |
1910 | |
1911 | for (prev = gtk_css_selector_tree_get_previous (tree); |
1912 | prev != NULL; |
1913 | prev = gtk_css_selector_tree_get_sibling (tree: prev)) |
1914 | { |
1915 | for (child = gtk_css_selector_iterator (selector: &tree->selector, node, NULL); |
1916 | child; |
1917 | child = gtk_css_selector_iterator (selector: &tree->selector, node, current: child)) |
1918 | { |
1919 | if (!gtk_css_selector_tree_match (tree: prev, filter, match_filter, node: child, results)) |
1920 | break; |
1921 | } |
1922 | } |
1923 | |
1924 | return TRUE; |
1925 | } |
1926 | |
1927 | void |
1928 | _gtk_css_selector_tree_match_all (const GtkCssSelectorTree *tree, |
1929 | const GtkCountingBloomFilter *filter, |
1930 | GtkCssNode *node, |
1931 | GtkCssSelectorMatches *out_tree_rules) |
1932 | { |
1933 | const GtkCssSelectorTree *iter; |
1934 | |
1935 | for (iter = tree; |
1936 | iter != NULL; |
1937 | iter = gtk_css_selector_tree_get_sibling (tree: iter)) |
1938 | { |
1939 | gtk_css_selector_tree_match (tree: iter, filter, FALSE, node, results: out_tree_rules); |
1940 | } |
1941 | } |
1942 | |
1943 | gboolean |
1944 | _gtk_css_selector_tree_is_empty (const GtkCssSelectorTree *tree) |
1945 | { |
1946 | return tree == NULL; |
1947 | } |
1948 | |
1949 | GtkCssChange |
1950 | gtk_css_selector_tree_get_change_all (const GtkCssSelectorTree *tree, |
1951 | const GtkCountingBloomFilter *filter, |
1952 | GtkCssNode *node) |
1953 | { |
1954 | GtkCssChange change = 0; |
1955 | |
1956 | for (; tree != NULL; |
1957 | tree = gtk_css_selector_tree_get_sibling (tree)) |
1958 | change |= gtk_css_selector_tree_get_change (tree, filter, node, FALSE); |
1959 | |
1960 | /* Never return reserved bit set */ |
1961 | return change & ~GTK_CSS_CHANGE_RESERVED_BIT; |
1962 | } |
1963 | |
1964 | #ifdef PRINT_TREE |
1965 | static void |
1966 | _gtk_css_selector_tree_print (const GtkCssSelectorTree *tree, GString *str, const char *prefix) |
1967 | { |
1968 | gboolean first = TRUE; |
1969 | int len, i; |
1970 | gpointer *matches; |
1971 | |
1972 | for (; tree != NULL; tree = gtk_css_selector_tree_get_sibling (tree), first = FALSE) |
1973 | { |
1974 | if (!first) |
1975 | g_string_append (str, prefix); |
1976 | |
1977 | if (first) |
1978 | { |
1979 | if (gtk_css_selector_tree_get_sibling (tree)) |
1980 | g_string_append (str, "─┬─" ); |
1981 | else |
1982 | g_string_append (str, "───" ); |
1983 | } |
1984 | else |
1985 | { |
1986 | if (gtk_css_selector_tree_get_sibling (tree)) |
1987 | g_string_append (str, " ├─" ); |
1988 | else |
1989 | g_string_append (str, " └─" ); |
1990 | } |
1991 | |
1992 | len = str->len; |
1993 | tree->selector.class->print (&tree->selector, str); |
1994 | matches = gtk_css_selector_tree_get_matches (tree); |
1995 | if (matches) |
1996 | { |
1997 | int n; |
1998 | for (n = 0; matches[n] != NULL; n++) ; |
1999 | if (n == 1) |
2000 | g_string_append (str, " (1 match)" ); |
2001 | else |
2002 | g_string_append_printf (str, " (%d matches)" , n); |
2003 | } |
2004 | len = str->len - len; |
2005 | |
2006 | if (gtk_css_selector_tree_get_previous (tree)) |
2007 | { |
2008 | GString *prefix2 = g_string_new (prefix); |
2009 | |
2010 | if (gtk_css_selector_tree_get_sibling (tree)) |
2011 | g_string_append (prefix2, " │ " ); |
2012 | else |
2013 | g_string_append (prefix2, " " ); |
2014 | for (i = 0; i < len; i++) |
2015 | g_string_append_c (prefix2, ' '); |
2016 | |
2017 | _gtk_css_selector_tree_print (gtk_css_selector_tree_get_previous (tree), str, prefix2->str); |
2018 | g_string_free (prefix2, TRUE); |
2019 | } |
2020 | else |
2021 | g_string_append (str, "\n" ); |
2022 | } |
2023 | } |
2024 | #endif |
2025 | |
2026 | void |
2027 | _gtk_css_selector_tree_match_print (const GtkCssSelectorTree *tree, |
2028 | GString *str) |
2029 | { |
2030 | const GtkCssSelectorTree *iter; |
2031 | |
2032 | g_return_if_fail (tree != NULL); |
2033 | |
2034 | /* print name and * selector before others */ |
2035 | for (iter = tree; |
2036 | iter && gtk_css_selector_is_simple (selector: &iter->selector); |
2037 | iter = gtk_css_selector_tree_get_parent (tree: iter)) |
2038 | { |
2039 | if (iter->selector.class == >K_CSS_SELECTOR_NAME || |
2040 | iter->selector.class == >K_CSS_SELECTOR_ANY) |
2041 | { |
2042 | iter->selector.class->print (&iter->selector, str); |
2043 | } |
2044 | } |
2045 | /* now print other simple selectors */ |
2046 | for (iter = tree; |
2047 | iter && gtk_css_selector_is_simple (selector: &iter->selector); |
2048 | iter = gtk_css_selector_tree_get_parent (tree: iter)) |
2049 | { |
2050 | if (iter->selector.class != >K_CSS_SELECTOR_NAME && |
2051 | iter->selector.class != >K_CSS_SELECTOR_ANY) |
2052 | { |
2053 | iter->selector.class->print (&iter->selector, str); |
2054 | } |
2055 | } |
2056 | |
2057 | /* now if there's a combinator, print that one */ |
2058 | if (iter != NULL) |
2059 | { |
2060 | iter->selector.class->print (&iter->selector, str); |
2061 | tree = gtk_css_selector_tree_get_parent (tree: iter); |
2062 | if (tree) |
2063 | _gtk_css_selector_tree_match_print (tree, str); |
2064 | } |
2065 | } |
2066 | |
2067 | void |
2068 | _gtk_css_selector_tree_free (GtkCssSelectorTree *tree) |
2069 | { |
2070 | if (tree == NULL) |
2071 | return; |
2072 | |
2073 | g_free (mem: tree); |
2074 | } |
2075 | |
2076 | |
2077 | typedef struct { |
2078 | gpointer match; |
2079 | GtkCssSelector *current_selector; |
2080 | GtkCssSelectorTree **selector_match; |
2081 | } GtkCssSelectorRuleSetInfo; |
2082 | |
2083 | static GtkCssSelectorTree * |
2084 | get_tree (GByteArray *array, gint32 offset) |
2085 | { |
2086 | return (GtkCssSelectorTree *) (array->data + offset); |
2087 | } |
2088 | |
2089 | static GtkCssSelectorTree * |
2090 | alloc_tree (GByteArray *array, gint32 *offset) |
2091 | { |
2092 | GtkCssSelectorTree tree = { { NULL} }; |
2093 | |
2094 | *offset = array->len; |
2095 | g_byte_array_append (array, data: (guint8 *)&tree, len: sizeof (GtkCssSelectorTree)); |
2096 | return get_tree (array, offset: *offset); |
2097 | } |
2098 | |
2099 | static gint32 |
2100 | subdivide_infos (GByteArray *array, |
2101 | GtkCssSelectorRuleSetInfo **infos, |
2102 | guint n_infos, |
2103 | gint32 parent_offset) |
2104 | { |
2105 | GtkCssSelectorRuleSetInfo **matched_infos; |
2106 | guint n_matched = 0; |
2107 | GtkCssSelectorRuleSetInfo **remaining_infos; |
2108 | guint n_remaining = 0; |
2109 | GHashTable *ht; |
2110 | gint32 tree_offset; |
2111 | GtkCssSelectorTree *tree; |
2112 | GtkCssSelector max_selector; |
2113 | GHashTableIter iter; |
2114 | guint max_count; |
2115 | gpointer key, value; |
2116 | GtkCssSelectorMatches exact_matches; |
2117 | gint32 res; |
2118 | guint i; |
2119 | |
2120 | if (n_infos == 0) |
2121 | return GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET; |
2122 | |
2123 | ht = gtk_css_selectors_count_initial_init (); |
2124 | |
2125 | for (i = 0; i < n_infos; i++) |
2126 | { |
2127 | const GtkCssSelectorRuleSetInfo *info = infos[i]; |
2128 | gtk_css_selectors_count_initial (selector: info->current_selector, hash_one: ht); |
2129 | } |
2130 | |
2131 | /* Pick the selector with highest count, and use as decision on this level |
2132 | as that makes it possible to skip the largest amount of checks later */ |
2133 | |
2134 | max_count = 0; |
2135 | |
2136 | g_hash_table_iter_init (iter: &iter, hash_table: ht); |
2137 | while (g_hash_table_iter_next (iter: &iter, key: &key, value: &value)) |
2138 | { |
2139 | GtkCssSelector *selector = key; |
2140 | if (GPOINTER_TO_UINT (value) > max_count || |
2141 | (GPOINTER_TO_UINT (value) == max_count && |
2142 | gtk_css_selector_compare_one (a: selector, b: &max_selector) < 0)) |
2143 | { |
2144 | max_count = GPOINTER_TO_UINT (value); |
2145 | max_selector = *selector; |
2146 | } |
2147 | } |
2148 | |
2149 | tree = alloc_tree (array, offset: &tree_offset); |
2150 | tree->parent_offset = parent_offset; |
2151 | tree->selector = max_selector; |
2152 | |
2153 | /* Allocate maximum for both of them */ |
2154 | /* TODO: Potentially dangerous? */ |
2155 | matched_infos = g_alloca (sizeof (GtkCssSelectorRuleSetInfo *) * n_infos); |
2156 | remaining_infos = g_alloca (sizeof (GtkCssSelectorRuleSetInfo *) * n_infos); |
2157 | |
2158 | gtk_css_selector_matches_init (self: &exact_matches); |
2159 | for (i = 0; i < n_infos; i++) |
2160 | { |
2161 | GtkCssSelectorRuleSetInfo *info = infos[i]; |
2162 | |
2163 | if (gtk_css_selectors_has_initial_selector (selector: info->current_selector, initial: &max_selector)) |
2164 | { |
2165 | info->current_selector = gtk_css_selectors_skip_initial_selector (selector: info->current_selector, initial: &max_selector); |
2166 | if (info->current_selector == NULL) |
2167 | { |
2168 | /* Matches current node */ |
2169 | gtk_css_selector_matches_append (self: &exact_matches, value: info->match); |
2170 | if (info->selector_match != NULL) |
2171 | *info->selector_match = GUINT_TO_POINTER (tree_offset); |
2172 | } |
2173 | else |
2174 | { |
2175 | matched_infos[n_matched] = info; |
2176 | n_matched++; |
2177 | } |
2178 | } |
2179 | else |
2180 | { |
2181 | remaining_infos[n_remaining] = info; |
2182 | n_remaining++; |
2183 | } |
2184 | } |
2185 | |
2186 | if (!gtk_css_selector_matches_is_empty (self: &exact_matches)) |
2187 | { |
2188 | gtk_css_selector_matches_append (self: &exact_matches, NULL); /* Null terminate */ |
2189 | res = array->len; |
2190 | g_byte_array_append (array, data: (guint8 *) gtk_css_selector_matches_get_data (self: &exact_matches), |
2191 | len: gtk_css_selector_matches_get_size (self: &exact_matches) * sizeof (gpointer)); |
2192 | } |
2193 | else |
2194 | res = GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET; |
2195 | gtk_css_selector_matches_clear (self: &exact_matches); |
2196 | get_tree (array, offset: tree_offset)->matches_offset = res; |
2197 | |
2198 | res = subdivide_infos (array, infos: matched_infos, n_infos: n_matched, parent_offset: tree_offset); |
2199 | get_tree (array, offset: tree_offset)->previous_offset = res; |
2200 | |
2201 | res = subdivide_infos (array, infos: remaining_infos, n_infos: n_remaining, parent_offset); |
2202 | get_tree (array, offset: tree_offset)->sibling_offset = res; |
2203 | |
2204 | g_hash_table_unref (hash_table: ht); |
2205 | |
2206 | return tree_offset; |
2207 | } |
2208 | |
2209 | struct _GtkCssSelectorTreeBuilder { |
2210 | GArray *infos; |
2211 | }; |
2212 | |
2213 | GtkCssSelectorTreeBuilder * |
2214 | _gtk_css_selector_tree_builder_new (void) |
2215 | { |
2216 | GtkCssSelectorTreeBuilder *builder = g_new0 (GtkCssSelectorTreeBuilder, 1); |
2217 | |
2218 | builder->infos = g_array_new (FALSE, TRUE, element_size: sizeof (GtkCssSelectorRuleSetInfo)); |
2219 | |
2220 | return builder; |
2221 | } |
2222 | |
2223 | void |
2224 | _gtk_css_selector_tree_builder_free (GtkCssSelectorTreeBuilder *builder) |
2225 | { |
2226 | g_array_free (array: builder->infos, TRUE); |
2227 | g_free (mem: builder); |
2228 | } |
2229 | |
2230 | void |
2231 | _gtk_css_selector_tree_builder_add (GtkCssSelectorTreeBuilder *builder, |
2232 | GtkCssSelector *selectors, |
2233 | GtkCssSelectorTree **selector_match, |
2234 | gpointer match) |
2235 | { |
2236 | GtkCssSelectorRuleSetInfo *info; |
2237 | |
2238 | g_array_set_size (array: builder->infos, length: builder->infos->len + 1); |
2239 | info = &g_array_index (builder->infos, GtkCssSelectorRuleSetInfo, builder->infos->len - 1); |
2240 | info->match = match; |
2241 | info->current_selector = selectors; |
2242 | info->selector_match = selector_match; |
2243 | } |
2244 | |
2245 | /* Convert all offsets to node-relative */ |
2246 | static void |
2247 | fixup_offsets (GtkCssSelectorTree *tree, guint8 *data) |
2248 | { |
2249 | while (tree != NULL) |
2250 | { |
2251 | if (tree->parent_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET) |
2252 | tree->parent_offset -= ((guint8 *)tree - data); |
2253 | |
2254 | if (tree->previous_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET) |
2255 | tree->previous_offset -= ((guint8 *)tree - data); |
2256 | |
2257 | if (tree->sibling_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET) |
2258 | tree->sibling_offset -= ((guint8 *)tree - data); |
2259 | |
2260 | if (tree->matches_offset != GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET) |
2261 | tree->matches_offset -= ((guint8 *)tree - data); |
2262 | |
2263 | fixup_offsets (tree: (GtkCssSelectorTree *)gtk_css_selector_tree_get_previous (tree), data); |
2264 | |
2265 | tree = (GtkCssSelectorTree *)gtk_css_selector_tree_get_sibling (tree); |
2266 | } |
2267 | } |
2268 | |
2269 | GtkCssSelectorTree * |
2270 | _gtk_css_selector_tree_builder_build (GtkCssSelectorTreeBuilder *builder) |
2271 | { |
2272 | GtkCssSelectorTree *tree; |
2273 | GByteArray *array; |
2274 | guint8 *data; |
2275 | guint len; |
2276 | guint i; |
2277 | GtkCssSelectorRuleSetInfo **infos_array; |
2278 | |
2279 | array = g_byte_array_new (); |
2280 | |
2281 | infos_array = g_alloca (sizeof (GtkCssSelectorRuleSetInfo *) * builder->infos->len); |
2282 | for (i = 0; i < builder->infos->len; i++) |
2283 | infos_array[i] = &g_array_index (builder->infos, GtkCssSelectorRuleSetInfo, i); |
2284 | |
2285 | subdivide_infos (array, infos: infos_array, n_infos: builder->infos->len, GTK_CSS_SELECTOR_TREE_EMPTY_OFFSET); |
2286 | |
2287 | len = array->len; |
2288 | data = g_byte_array_free (array, FALSE); |
2289 | |
2290 | /* shrink to final size */ |
2291 | data = g_realloc (mem: data, n_bytes: len); |
2292 | |
2293 | tree = (GtkCssSelectorTree *)data; |
2294 | |
2295 | fixup_offsets (tree, data); |
2296 | |
2297 | /* Convert offsets to final pointers */ |
2298 | for (i = 0; i < builder->infos->len; i++) |
2299 | { |
2300 | GtkCssSelectorRuleSetInfo *info = &g_array_index (builder->infos, GtkCssSelectorRuleSetInfo, i); |
2301 | |
2302 | if (info->selector_match) |
2303 | *info->selector_match = (GtkCssSelectorTree *)(data + GPOINTER_TO_UINT (*info->selector_match)); |
2304 | } |
2305 | |
2306 | |
2307 | #ifdef PRINT_TREE |
2308 | { |
2309 | GString *s = g_string_new ("" ); |
2310 | _gtk_css_selector_tree_print (tree, s, "" ); |
2311 | g_print ("%s" , s->str); |
2312 | g_string_free (s, TRUE); |
2313 | } |
2314 | #endif |
2315 | |
2316 | return tree; |
2317 | } |
2318 | |