1 | /* GTK - The GIMP Toolkit |
2 | * Copyright (C) 2014 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 "gtkcssnodeprivate.h" |
21 | |
22 | #include "gtkcssstaticstyleprivate.h" |
23 | #include "gtkcssanimatedstyleprivate.h" |
24 | #include "gtkcssstylepropertyprivate.h" |
25 | #include "gtkintl.h" |
26 | #include "gtkmarshalers.h" |
27 | #include "gtksettingsprivate.h" |
28 | #include "gtktypebuiltins.h" |
29 | #include "gtkprivate.h" |
30 | #include "gdkprofilerprivate.h" |
31 | |
32 | /* |
33 | * CSS nodes are the backbone of the GtkStyleContext implementation and |
34 | * replace the role that GtkWidgetPath played in the past. A CSS node has |
35 | * an element name and a state, and can have an id and style classes, which |
36 | * is what is needed to determine the matching CSS selectors. CSS nodes have |
37 | * a 'visible' property, which makes it possible to temporarily 'hide' them |
38 | * from CSS matching - e.g. an invisible node will not affect :nth-child |
39 | * matching and so forth. |
40 | * |
41 | * The API to manage states, names, ids and classes of CSS nodes is: |
42 | * - gtk_css_node_get/set_state. States are represented as GtkStateFlags |
43 | * - gtk_css_node_get/set_name. Names are represented as interned strings |
44 | * - gtk_css_node_get/set_id. Ids are represented as interned strings |
45 | * - gtk_css_node_add/remove/has_class and gtk_css_node_list_classes. Style |
46 | * classes are represented as quarks. |
47 | * |
48 | * CSS nodes are organized in a dom-like tree, and there is API to navigate |
49 | * and manipulate this tree: |
50 | * - gtk_css_node_set_parent |
51 | * - gtk_css_node_insert_before/after |
52 | * - gtk_css_node_get_parent |
53 | * - gtk_css_node_get_first/last_child |
54 | * - gtk_css_node_get_previous/next_sibling |
55 | * Note that parents keep a reference on their children in this tree. |
56 | * |
57 | * Every widget has one or more CSS nodes - the first one gets created |
58 | * automatically by GtkStyleContext. To set the name of the main node, |
59 | * call gtk_widget_class_set_css_name() in class_init(). Widget implementations |
60 | * can and should add subnodes as suitable. |
61 | * |
62 | * Best practice is: |
63 | * - For permanent subnodes, create them in init(), and keep a pointer |
64 | * to the node (you don't have to keep a reference, cleanup will be |
65 | * automatic by means of the parent node getting cleaned up by the |
66 | * style context). |
67 | * - For transient nodes, create/destroy them when the conditions that |
68 | * warrant their existence change. |
69 | * - Keep the state of all your nodes up-to-date. This probably requires |
70 | * a ::state-flags-changed (and possibly ::direction-changed) handler, |
71 | * as well as code to update the state in other places. Note that GTK |
72 | * does this automatically for the widget's main CSS node. |
73 | * - The sibling ordering in the CSS node tree is supposed to correspond |
74 | * to the visible order of content: top-to-bottom and left-to-right. |
75 | * Reorder your nodes to maintain this correlation. In particular for |
76 | * horizontally laid out widgets, this will require listening to |
77 | * ::direction-changed. |
78 | * - The draw function should just use gtk_style_context_save_to_node() to |
79 | * 'switch' to the right node, not make any other changes to the style |
80 | * context. |
81 | * |
82 | * A noteworthy difference between gtk_style_context_save() and |
83 | * gtk_style_context_save_to_node() is that the former inherits all the |
84 | * style classes from the main CSS node, which often leads to unintended |
85 | * inheritance. |
86 | */ |
87 | |
88 | /* When these change we do a full restyling. Otherwise we try to figure out |
89 | * if we need to change things. */ |
90 | #define GTK_CSS_RADICAL_CHANGE (GTK_CSS_CHANGE_ID | GTK_CSS_CHANGE_NAME | GTK_CSS_CHANGE_CLASS | \ |
91 | GTK_CSS_CHANGE_PARENT_ID | GTK_CSS_CHANGE_PARENT_NAME | GTK_CSS_CHANGE_PARENT_CLASS | \ |
92 | GTK_CSS_CHANGE_SOURCE | GTK_CSS_CHANGE_PARENT_STYLE) |
93 | |
94 | /* When these change, we need to recompute the change flags for the new style |
95 | * since they may have changed. |
96 | */ |
97 | #define GTK_CSS_CHANGE_NEEDS_RECOMPUTE (GTK_CSS_RADICAL_CHANGE & ~GTK_CSS_CHANGE_PARENT_STYLE) |
98 | |
99 | G_DEFINE_TYPE (GtkCssNode, gtk_css_node, G_TYPE_OBJECT) |
100 | |
101 | enum { |
102 | NODE_ADDED, |
103 | NODE_REMOVED, |
104 | STYLE_CHANGED, |
105 | LAST_SIGNAL |
106 | }; |
107 | |
108 | enum { |
109 | PROP_0, |
110 | PROP_CLASSES, |
111 | PROP_ID, |
112 | PROP_NAME, |
113 | PROP_STATE, |
114 | PROP_VISIBLE, |
115 | NUM_PROPERTIES |
116 | }; |
117 | |
118 | static guint cssnode_signals[LAST_SIGNAL] = { 0 }; |
119 | static GParamSpec *cssnode_properties[NUM_PROPERTIES]; |
120 | |
121 | static GtkStyleProvider * |
122 | gtk_css_node_get_style_provider_or_null (GtkCssNode *cssnode) |
123 | { |
124 | return GTK_CSS_NODE_GET_CLASS (cssnode)->get_style_provider (cssnode); |
125 | } |
126 | |
127 | static int invalidated_nodes; |
128 | static int created_styles; |
129 | static guint invalidated_nodes_counter; |
130 | static guint created_styles_counter; |
131 | |
132 | static void |
133 | gtk_css_node_set_invalid (GtkCssNode *node, |
134 | gboolean invalid) |
135 | { |
136 | if (node->invalid == invalid) |
137 | return; |
138 | |
139 | node->invalid = invalid; |
140 | |
141 | if (invalid) |
142 | invalidated_nodes++; |
143 | |
144 | if (node->visible) |
145 | { |
146 | if (node->parent) |
147 | { |
148 | if (invalid) |
149 | gtk_css_node_set_invalid (node: node->parent, TRUE); |
150 | } |
151 | else |
152 | { |
153 | if (invalid) |
154 | GTK_CSS_NODE_GET_CLASS (node)->queue_validate (node); |
155 | else |
156 | GTK_CSS_NODE_GET_CLASS (node)->dequeue_validate (node); |
157 | } |
158 | } |
159 | } |
160 | |
161 | static void |
162 | gtk_css_node_get_property (GObject *object, |
163 | guint property_id, |
164 | GValue *value, |
165 | GParamSpec *pspec) |
166 | { |
167 | GtkCssNode *cssnode = GTK_CSS_NODE (object); |
168 | |
169 | switch (property_id) |
170 | { |
171 | case PROP_CLASSES: |
172 | g_value_take_boxed (value, v_boxed: gtk_css_node_get_classes (cssnode)); |
173 | break; |
174 | |
175 | case PROP_ID: |
176 | g_value_set_string (value, v_string: g_quark_to_string (quark: gtk_css_node_get_id (cssnode))); |
177 | break; |
178 | |
179 | case PROP_NAME: |
180 | g_value_set_string (value, v_string: g_quark_to_string (quark: gtk_css_node_get_name (cssnode))); |
181 | break; |
182 | |
183 | case PROP_STATE: |
184 | g_value_set_flags (value, v_flags: gtk_css_node_get_state (cssnode)); |
185 | break; |
186 | |
187 | case PROP_VISIBLE: |
188 | g_value_set_boolean (value, v_boolean: gtk_css_node_get_visible (cssnode)); |
189 | break; |
190 | |
191 | default: |
192 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
193 | } |
194 | } |
195 | |
196 | static void |
197 | gtk_css_node_set_property (GObject *object, |
198 | guint property_id, |
199 | const GValue *value, |
200 | GParamSpec *pspec) |
201 | { |
202 | GtkCssNode *cssnode = GTK_CSS_NODE (object); |
203 | |
204 | switch (property_id) |
205 | { |
206 | case PROP_CLASSES: |
207 | gtk_css_node_set_classes (cssnode, classes: g_value_get_boxed (value)); |
208 | break; |
209 | |
210 | case PROP_ID: |
211 | gtk_css_node_set_id (cssnode, id: g_quark_from_string (string: g_value_get_string (value))); |
212 | break; |
213 | |
214 | case PROP_NAME: |
215 | gtk_css_node_set_name (cssnode, name: g_quark_from_string (string: g_value_get_string (value))); |
216 | break; |
217 | |
218 | case PROP_STATE: |
219 | gtk_css_node_set_state (cssnode, state_flags: g_value_get_flags (value)); |
220 | break; |
221 | |
222 | case PROP_VISIBLE: |
223 | gtk_css_node_set_visible (cssnode, visible: g_value_get_boolean (value)); |
224 | break; |
225 | |
226 | default: |
227 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
228 | } |
229 | } |
230 | |
231 | static void |
232 | gtk_css_node_dispose (GObject *object) |
233 | { |
234 | GtkCssNode *cssnode = GTK_CSS_NODE (object); |
235 | |
236 | while (cssnode->first_child) |
237 | { |
238 | gtk_css_node_set_parent (cssnode: cssnode->first_child, NULL); |
239 | } |
240 | |
241 | gtk_css_node_set_invalid (node: cssnode, FALSE); |
242 | |
243 | g_clear_pointer (&cssnode->cache, gtk_css_node_style_cache_unref); |
244 | |
245 | G_OBJECT_CLASS (gtk_css_node_parent_class)->dispose (object); |
246 | } |
247 | |
248 | static void |
249 | gtk_css_node_finalize (GObject *object) |
250 | { |
251 | GtkCssNode *cssnode = GTK_CSS_NODE (object); |
252 | |
253 | if (cssnode->style) |
254 | g_object_unref (object: cssnode->style); |
255 | gtk_css_node_declaration_unref (decl: cssnode->decl); |
256 | |
257 | G_OBJECT_CLASS (gtk_css_node_parent_class)->finalize (object); |
258 | } |
259 | |
260 | static gboolean |
261 | gtk_css_node_is_first_child (GtkCssNode *node) |
262 | { |
263 | GtkCssNode *iter; |
264 | |
265 | for (iter = node->previous_sibling; |
266 | iter != NULL; |
267 | iter = iter->previous_sibling) |
268 | { |
269 | if (iter->visible) |
270 | return FALSE; |
271 | } |
272 | return TRUE; |
273 | } |
274 | |
275 | static gboolean |
276 | gtk_css_node_is_last_child (GtkCssNode *node) |
277 | { |
278 | GtkCssNode *iter; |
279 | |
280 | for (iter = node->next_sibling; |
281 | iter != NULL; |
282 | iter = iter->next_sibling) |
283 | { |
284 | if (iter->visible) |
285 | return FALSE; |
286 | } |
287 | return TRUE; |
288 | } |
289 | |
290 | static gboolean |
291 | may_use_global_parent_cache (GtkCssNode *node) |
292 | { |
293 | GtkStyleProvider *provider; |
294 | GtkCssNode *parent; |
295 | |
296 | parent = gtk_css_node_get_parent (cssnode: node); |
297 | if (parent == NULL) |
298 | return FALSE; |
299 | |
300 | provider = gtk_css_node_get_style_provider_or_null (cssnode: node); |
301 | if (provider != NULL && provider != gtk_css_node_get_style_provider (cssnode: parent)) |
302 | return FALSE; |
303 | |
304 | return TRUE; |
305 | } |
306 | |
307 | static GtkCssStyle * |
308 | lookup_in_global_parent_cache (GtkCssNode *node, |
309 | const GtkCssNodeDeclaration *decl) |
310 | { |
311 | GtkCssNode *parent; |
312 | |
313 | parent = node->parent; |
314 | |
315 | if (parent == NULL || |
316 | !may_use_global_parent_cache (node)) |
317 | return NULL; |
318 | |
319 | if (parent->cache == NULL) |
320 | return NULL; |
321 | |
322 | g_assert (node->cache == NULL); |
323 | node->cache = gtk_css_node_style_cache_lookup (parent: parent->cache, |
324 | decl, |
325 | is_first: gtk_css_node_is_first_child (node), |
326 | is_last: gtk_css_node_is_last_child (node)); |
327 | if (node->cache == NULL) |
328 | return NULL; |
329 | |
330 | return gtk_css_node_style_cache_get_style (cache: node->cache); |
331 | } |
332 | |
333 | static void |
334 | store_in_global_parent_cache (GtkCssNode *node, |
335 | const GtkCssNodeDeclaration *decl, |
336 | GtkCssStyle *style) |
337 | { |
338 | GtkCssNode *parent; |
339 | |
340 | g_assert (GTK_IS_CSS_STATIC_STYLE (style)); |
341 | |
342 | parent = node->parent; |
343 | |
344 | if (parent == NULL || |
345 | !may_use_global_parent_cache (node)) |
346 | return; |
347 | |
348 | if (parent->cache == NULL) |
349 | parent->cache = gtk_css_node_style_cache_new (style: parent->style); |
350 | |
351 | node->cache = gtk_css_node_style_cache_insert (parent: parent->cache, |
352 | decl: (GtkCssNodeDeclaration *) decl, |
353 | is_first: gtk_css_node_is_first_child (node), |
354 | is_last: gtk_css_node_is_last_child (node), |
355 | style); |
356 | } |
357 | |
358 | static GtkCssStyle * |
359 | gtk_css_node_create_style (GtkCssNode *cssnode, |
360 | const GtkCountingBloomFilter *filter, |
361 | GtkCssChange change) |
362 | { |
363 | const GtkCssNodeDeclaration *decl; |
364 | GtkCssStyle *style; |
365 | GtkCssChange style_change; |
366 | |
367 | decl = gtk_css_node_get_declaration (cssnode); |
368 | |
369 | style = lookup_in_global_parent_cache (node: cssnode, decl); |
370 | if (style) |
371 | return g_object_ref (style); |
372 | |
373 | created_styles++; |
374 | |
375 | if (change & GTK_CSS_CHANGE_NEEDS_RECOMPUTE) |
376 | { |
377 | /* Need to recompute the change flags */ |
378 | style_change = 0; |
379 | } |
380 | else |
381 | { |
382 | style_change = gtk_css_static_style_get_change (style: gtk_css_style_get_static_style (style: cssnode->style)); |
383 | } |
384 | |
385 | style = gtk_css_static_style_new_compute (provider: gtk_css_node_get_style_provider (cssnode), |
386 | filter, |
387 | node: cssnode, |
388 | change: style_change); |
389 | |
390 | store_in_global_parent_cache (node: cssnode, decl, style); |
391 | |
392 | return style; |
393 | } |
394 | |
395 | static gboolean |
396 | should_create_transitions (GtkCssChange change) |
397 | { |
398 | return (change & GTK_CSS_CHANGE_ANIMATIONS) == 0; |
399 | } |
400 | |
401 | static gboolean |
402 | gtk_css_style_needs_recreation (GtkCssStyle *style, |
403 | GtkCssChange change) |
404 | { |
405 | gtk_internal_return_val_if_fail (GTK_IS_CSS_STATIC_STYLE (style), TRUE); |
406 | |
407 | /* Try to avoid invalidating if we can */ |
408 | if (change & GTK_CSS_RADICAL_CHANGE) |
409 | return TRUE; |
410 | |
411 | if (gtk_css_static_style_get_change (GTK_CSS_STATIC_STYLE (style)) & change) |
412 | return TRUE; |
413 | else |
414 | return FALSE; |
415 | } |
416 | |
417 | static GtkCssStyle * |
418 | gtk_css_node_real_update_style (GtkCssNode *cssnode, |
419 | const GtkCountingBloomFilter *filter, |
420 | GtkCssChange change, |
421 | gint64 timestamp, |
422 | GtkCssStyle *style) |
423 | { |
424 | GtkCssStyle *static_style, *new_static_style, *new_style; |
425 | |
426 | static_style = GTK_CSS_STYLE (gtk_css_style_get_static_style (style)); |
427 | |
428 | if (gtk_css_style_needs_recreation (style: static_style, change)) |
429 | new_static_style = gtk_css_node_create_style (cssnode, filter, change); |
430 | else |
431 | new_static_style = g_object_ref (static_style); |
432 | |
433 | if (new_static_style != static_style || (change & GTK_CSS_CHANGE_ANIMATIONS)) |
434 | { |
435 | GtkCssNode *parent = gtk_css_node_get_parent (cssnode); |
436 | new_style = gtk_css_animated_style_new (base_style: new_static_style, |
437 | parent_style: parent ? gtk_css_node_get_style (cssnode: parent) : NULL, |
438 | timestamp, |
439 | provider: gtk_css_node_get_style_provider (cssnode), |
440 | previous_style: should_create_transitions (change) ? style : NULL); |
441 | |
442 | /* Clear the cache again, the static style we looked up above |
443 | * may have populated it. */ |
444 | g_clear_pointer (&cssnode->cache, gtk_css_node_style_cache_unref); |
445 | } |
446 | else if (static_style != style && (change & GTK_CSS_CHANGE_TIMESTAMP)) |
447 | { |
448 | new_style = gtk_css_animated_style_new_advance (GTK_CSS_ANIMATED_STYLE (style), |
449 | base: static_style, |
450 | timestamp); |
451 | } |
452 | else |
453 | { |
454 | new_style = g_object_ref (style); |
455 | } |
456 | |
457 | if (!gtk_css_style_is_static (style: new_style)) |
458 | gtk_css_node_set_invalid (node: cssnode, TRUE); |
459 | |
460 | g_object_unref (object: new_static_style); |
461 | |
462 | return new_style; |
463 | } |
464 | |
465 | static void |
466 | gtk_css_node_real_queue_validate (GtkCssNode *node) |
467 | { |
468 | } |
469 | |
470 | static void |
471 | gtk_css_node_real_dequeue_validate (GtkCssNode *node) |
472 | { |
473 | } |
474 | |
475 | static void |
476 | gtk_css_node_real_validate (GtkCssNode *node) |
477 | { |
478 | } |
479 | |
480 | static GtkStyleProvider * |
481 | gtk_css_node_real_get_style_provider (GtkCssNode *cssnode) |
482 | { |
483 | return NULL; |
484 | } |
485 | |
486 | static GdkFrameClock * |
487 | gtk_css_node_real_get_frame_clock (GtkCssNode *cssnode) |
488 | { |
489 | return NULL; |
490 | } |
491 | |
492 | static void |
493 | gtk_css_node_real_node_removed (GtkCssNode *parent, |
494 | GtkCssNode *node, |
495 | GtkCssNode *previous) |
496 | { |
497 | if (node->previous_sibling) |
498 | node->previous_sibling->next_sibling = node->next_sibling; |
499 | else |
500 | node->parent->first_child = node->next_sibling; |
501 | |
502 | if (node->next_sibling) |
503 | node->next_sibling->previous_sibling = node->previous_sibling; |
504 | else |
505 | node->parent->last_child = node->previous_sibling; |
506 | |
507 | node->previous_sibling = NULL; |
508 | node->next_sibling = NULL; |
509 | node->parent = NULL; |
510 | } |
511 | |
512 | static void |
513 | gtk_css_node_real_node_added (GtkCssNode *parent, |
514 | GtkCssNode *node, |
515 | GtkCssNode *new_previous) |
516 | { |
517 | if (new_previous) |
518 | { |
519 | node->previous_sibling = new_previous; |
520 | node->next_sibling = new_previous->next_sibling; |
521 | new_previous->next_sibling = node; |
522 | } |
523 | else |
524 | { |
525 | node->next_sibling = parent->first_child; |
526 | parent->first_child = node; |
527 | } |
528 | |
529 | if (node->next_sibling) |
530 | node->next_sibling->previous_sibling = node; |
531 | else |
532 | parent->last_child = node; |
533 | |
534 | node->parent = parent; |
535 | } |
536 | |
537 | static void |
538 | gtk_css_node_real_style_changed (GtkCssNode *cssnode, |
539 | GtkCssStyleChange *change) |
540 | { |
541 | g_set_object (&cssnode->style, gtk_css_style_change_get_new_style (change)); |
542 | } |
543 | |
544 | static void |
545 | gtk_css_node_class_init (GtkCssNodeClass *klass) |
546 | { |
547 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
548 | |
549 | object_class->get_property = gtk_css_node_get_property; |
550 | object_class->set_property = gtk_css_node_set_property; |
551 | object_class->dispose = gtk_css_node_dispose; |
552 | object_class->finalize = gtk_css_node_finalize; |
553 | |
554 | klass->update_style = gtk_css_node_real_update_style; |
555 | klass->validate = gtk_css_node_real_validate; |
556 | klass->queue_validate = gtk_css_node_real_queue_validate; |
557 | klass->dequeue_validate = gtk_css_node_real_dequeue_validate; |
558 | klass->get_style_provider = gtk_css_node_real_get_style_provider; |
559 | klass->get_frame_clock = gtk_css_node_real_get_frame_clock; |
560 | |
561 | klass->node_added = gtk_css_node_real_node_added; |
562 | klass->node_removed = gtk_css_node_real_node_removed; |
563 | klass->style_changed = gtk_css_node_real_style_changed; |
564 | |
565 | cssnode_signals[NODE_ADDED] = |
566 | g_signal_new (I_("node-added" ), |
567 | G_TYPE_FROM_CLASS (object_class), |
568 | signal_flags: G_SIGNAL_RUN_LAST, |
569 | G_STRUCT_OFFSET (GtkCssNodeClass, node_added), |
570 | NULL, NULL, |
571 | c_marshaller: _gtk_marshal_VOID__OBJECT_OBJECT, |
572 | G_TYPE_NONE, n_params: 2, |
573 | GTK_TYPE_CSS_NODE, GTK_TYPE_CSS_NODE); |
574 | g_signal_set_va_marshaller (signal_id: cssnode_signals[NODE_ADDED], |
575 | G_TYPE_FROM_CLASS (klass), |
576 | va_marshaller: _gtk_marshal_VOID__OBJECT_OBJECTv); |
577 | |
578 | cssnode_signals[NODE_REMOVED] = |
579 | g_signal_new (I_("node-removed" ), |
580 | G_TYPE_FROM_CLASS (object_class), |
581 | signal_flags: G_SIGNAL_RUN_LAST, |
582 | G_STRUCT_OFFSET (GtkCssNodeClass, node_removed), |
583 | NULL, NULL, |
584 | c_marshaller: _gtk_marshal_VOID__OBJECT_OBJECT, |
585 | G_TYPE_NONE, n_params: 2, |
586 | GTK_TYPE_CSS_NODE, GTK_TYPE_CSS_NODE); |
587 | g_signal_set_va_marshaller (signal_id: cssnode_signals[NODE_REMOVED], |
588 | G_TYPE_FROM_CLASS (klass), |
589 | va_marshaller: _gtk_marshal_VOID__OBJECT_OBJECTv); |
590 | |
591 | cssnode_signals[STYLE_CHANGED] = |
592 | g_signal_new (I_("style-changed" ), |
593 | G_TYPE_FROM_CLASS (object_class), |
594 | signal_flags: G_SIGNAL_RUN_LAST, |
595 | G_STRUCT_OFFSET (GtkCssNodeClass, style_changed), |
596 | NULL, NULL, |
597 | NULL, |
598 | G_TYPE_NONE, n_params: 1, |
599 | G_TYPE_POINTER); |
600 | |
601 | cssnode_properties[PROP_CLASSES] = |
602 | g_param_spec_boxed (name: "classes" , P_("Style Classes" ), P_("List of classes" ), |
603 | G_TYPE_STRV, |
604 | flags: G_PARAM_READWRITE |
605 | | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); |
606 | cssnode_properties[PROP_ID] = |
607 | g_param_spec_string (name: "id" , P_("ID" ), P_("Unique ID" ), |
608 | NULL, |
609 | flags: G_PARAM_READWRITE |
610 | | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); |
611 | cssnode_properties[PROP_NAME] = |
612 | g_param_spec_string (name: "name" , P_("Name" ), blurb: "Name identifying the type of node" , |
613 | NULL, |
614 | flags: G_PARAM_READWRITE |
615 | | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); |
616 | cssnode_properties[PROP_STATE] = |
617 | g_param_spec_flags (name: "state" , P_("State" ), P_("State flags" ), |
618 | flags_type: GTK_TYPE_STATE_FLAGS, |
619 | default_value: 0, |
620 | flags: G_PARAM_READWRITE |
621 | | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); |
622 | cssnode_properties[PROP_VISIBLE] = |
623 | g_param_spec_boolean (name: "visible" , P_("Visible" ), P_("If other nodes can see this node" ), |
624 | TRUE, |
625 | flags: G_PARAM_READWRITE |
626 | | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); |
627 | |
628 | g_object_class_install_properties (oclass: object_class, n_pspecs: NUM_PROPERTIES, pspecs: cssnode_properties); |
629 | |
630 | if (invalidated_nodes_counter == 0) |
631 | { |
632 | invalidated_nodes_counter = gdk_profiler_define_int_counter ("invalidated-nodes" , "CSS Node Invalidations" ); |
633 | created_styles_counter = gdk_profiler_define_int_counter ("created-styles" , "CSS Style Creations" ); |
634 | } |
635 | } |
636 | |
637 | static void |
638 | gtk_css_node_init (GtkCssNode *cssnode) |
639 | { |
640 | cssnode->decl = gtk_css_node_declaration_new (); |
641 | |
642 | cssnode->style = g_object_ref (gtk_css_static_style_get_default ()); |
643 | |
644 | cssnode->visible = TRUE; |
645 | } |
646 | |
647 | /** |
648 | * gtk_css_node_new: |
649 | * |
650 | * Creates a new CSS node. |
651 | * |
652 | * Returns: (transfer full): the new CSS node |
653 | */ |
654 | GtkCssNode * |
655 | gtk_css_node_new (void) |
656 | { |
657 | return g_object_new (GTK_TYPE_CSS_NODE, NULL); |
658 | } |
659 | |
660 | static GdkFrameClock * |
661 | gtk_css_node_get_frame_clock_or_null (GtkCssNode *cssnode) |
662 | { |
663 | while (cssnode->parent) |
664 | cssnode = cssnode->parent; |
665 | |
666 | return GTK_CSS_NODE_GET_CLASS (cssnode)->get_frame_clock (cssnode); |
667 | } |
668 | |
669 | static gint64 |
670 | gtk_css_node_get_timestamp (GtkCssNode *cssnode) |
671 | { |
672 | GdkFrameClock *frameclock; |
673 | |
674 | frameclock = gtk_css_node_get_frame_clock_or_null (cssnode); |
675 | if (frameclock == NULL) |
676 | return 0; |
677 | |
678 | return gdk_frame_clock_get_frame_time (frame_clock: frameclock); |
679 | } |
680 | |
681 | static void |
682 | gtk_css_node_parent_was_unset (GtkCssNode *node) |
683 | { |
684 | if (node->visible && node->invalid) |
685 | GTK_CSS_NODE_GET_CLASS (node)->queue_validate (node); |
686 | } |
687 | |
688 | static void |
689 | gtk_css_node_parent_will_be_set (GtkCssNode *node) |
690 | { |
691 | if (node->visible && node->invalid) |
692 | GTK_CSS_NODE_GET_CLASS (node)->dequeue_validate (node); |
693 | } |
694 | |
695 | static void |
696 | gtk_css_node_invalidate_style (GtkCssNode *cssnode) |
697 | { |
698 | if (cssnode->style_is_invalid) |
699 | return; |
700 | |
701 | cssnode->style_is_invalid = TRUE; |
702 | gtk_css_node_set_invalid (node: cssnode, TRUE); |
703 | |
704 | if (cssnode->first_child) |
705 | gtk_css_node_invalidate_style (cssnode: cssnode->first_child); |
706 | |
707 | if (cssnode->next_sibling) |
708 | gtk_css_node_invalidate_style (cssnode: cssnode->next_sibling); |
709 | } |
710 | |
711 | static void |
712 | gtk_css_node_reposition (GtkCssNode *node, |
713 | GtkCssNode *new_parent, |
714 | GtkCssNode *previous) |
715 | { |
716 | GtkCssNode *old_parent; |
717 | |
718 | g_assert (! (new_parent == NULL && previous != NULL)); |
719 | |
720 | old_parent = node->parent; |
721 | /* Take a reference here so the whole function has a reference */ |
722 | g_object_ref (node); |
723 | |
724 | if (node->visible) |
725 | { |
726 | if (node->next_sibling) |
727 | gtk_css_node_invalidate (cssnode: node->next_sibling, |
728 | GTK_CSS_CHANGE_ANY_SIBLING |
729 | | GTK_CSS_CHANGE_NTH_CHILD |
730 | | (node->previous_sibling ? 0 : GTK_CSS_CHANGE_FIRST_CHILD)); |
731 | else if (node->previous_sibling) |
732 | gtk_css_node_invalidate (cssnode: node->previous_sibling, GTK_CSS_CHANGE_LAST_CHILD); |
733 | } |
734 | |
735 | if (old_parent != NULL) |
736 | { |
737 | g_signal_emit (instance: old_parent, signal_id: cssnode_signals[NODE_REMOVED], detail: 0, node, node->previous_sibling); |
738 | if (old_parent->first_child && node->visible) |
739 | gtk_css_node_invalidate (cssnode: old_parent->first_child, GTK_CSS_CHANGE_NTH_LAST_CHILD); |
740 | } |
741 | |
742 | if (old_parent != new_parent) |
743 | { |
744 | if (old_parent == NULL) |
745 | { |
746 | gtk_css_node_parent_will_be_set (node); |
747 | } |
748 | else |
749 | { |
750 | g_object_unref (object: node); |
751 | } |
752 | |
753 | if (gtk_css_node_get_style_provider_or_null (cssnode: node) == NULL) |
754 | gtk_css_node_invalidate_style_provider (cssnode: node); |
755 | gtk_css_node_invalidate (cssnode: node, GTK_CSS_CHANGE_TIMESTAMP | GTK_CSS_CHANGE_ANIMATIONS); |
756 | |
757 | if (new_parent) |
758 | { |
759 | g_object_ref (node); |
760 | |
761 | if (node->pending_changes) |
762 | new_parent->needs_propagation = TRUE; |
763 | if (node->invalid && node->visible) |
764 | gtk_css_node_set_invalid (node: new_parent, TRUE); |
765 | } |
766 | else |
767 | { |
768 | gtk_css_node_parent_was_unset (node); |
769 | } |
770 | } |
771 | |
772 | if (new_parent) |
773 | { |
774 | g_signal_emit (instance: new_parent, signal_id: cssnode_signals[NODE_ADDED], detail: 0, node, previous); |
775 | if (node->visible) |
776 | gtk_css_node_invalidate (cssnode: new_parent->first_child, GTK_CSS_CHANGE_NTH_LAST_CHILD); |
777 | } |
778 | |
779 | if (node->visible) |
780 | { |
781 | if (node->next_sibling) |
782 | { |
783 | if (node->previous_sibling == NULL) |
784 | gtk_css_node_invalidate (cssnode: node->next_sibling, GTK_CSS_CHANGE_FIRST_CHILD); |
785 | else |
786 | gtk_css_node_invalidate_style (cssnode: node->next_sibling); |
787 | } |
788 | else if (node->previous_sibling) |
789 | { |
790 | gtk_css_node_invalidate (cssnode: node->previous_sibling, GTK_CSS_CHANGE_LAST_CHILD); |
791 | } |
792 | } |
793 | else |
794 | { |
795 | if (node->next_sibling) |
796 | gtk_css_node_invalidate_style (cssnode: node->next_sibling); |
797 | } |
798 | |
799 | gtk_css_node_invalidate (cssnode: node, change: (old_parent != new_parent ? GTK_CSS_CHANGE_ANY_PARENT : 0) |
800 | | GTK_CSS_CHANGE_ANY_SIBLING |
801 | | GTK_CSS_CHANGE_NTH_CHILD |
802 | | (node->previous_sibling ? 0 : GTK_CSS_CHANGE_FIRST_CHILD) |
803 | | (node->next_sibling ? 0 : GTK_CSS_CHANGE_LAST_CHILD)); |
804 | |
805 | g_object_unref (object: node); |
806 | } |
807 | |
808 | void |
809 | gtk_css_node_set_parent (GtkCssNode *node, |
810 | GtkCssNode *parent) |
811 | { |
812 | if (node->parent == parent) |
813 | return; |
814 | |
815 | gtk_css_node_reposition (node, new_parent: parent, previous: parent ? parent->last_child : NULL); |
816 | } |
817 | |
818 | /* If previous_sibling is NULL, insert at the beginning */ |
819 | void |
820 | gtk_css_node_insert_after (GtkCssNode *parent, |
821 | GtkCssNode *cssnode, |
822 | GtkCssNode *previous_sibling) |
823 | { |
824 | g_return_if_fail (previous_sibling == NULL || previous_sibling->parent == parent); |
825 | g_return_if_fail (cssnode != previous_sibling); |
826 | |
827 | if (cssnode->previous_sibling == previous_sibling && |
828 | cssnode->parent == parent) |
829 | return; |
830 | |
831 | gtk_css_node_reposition (node: cssnode, |
832 | new_parent: parent, |
833 | previous: previous_sibling); |
834 | } |
835 | |
836 | /* If next_sibling is NULL, insert at the end */ |
837 | void |
838 | gtk_css_node_insert_before (GtkCssNode *parent, |
839 | GtkCssNode *cssnode, |
840 | GtkCssNode *next_sibling) |
841 | { |
842 | g_return_if_fail (next_sibling == NULL || next_sibling->parent == parent); |
843 | g_return_if_fail (cssnode != next_sibling); |
844 | |
845 | if (cssnode->next_sibling == next_sibling && |
846 | cssnode->parent == parent) |
847 | return; |
848 | |
849 | gtk_css_node_reposition (node: cssnode, |
850 | new_parent: parent, |
851 | previous: next_sibling ? next_sibling->previous_sibling : parent->last_child); |
852 | } |
853 | |
854 | GtkCssNode * |
855 | gtk_css_node_get_parent (GtkCssNode *cssnode) |
856 | { |
857 | return cssnode->parent; |
858 | } |
859 | |
860 | GtkCssNode * |
861 | gtk_css_node_get_first_child (GtkCssNode *cssnode) |
862 | { |
863 | return cssnode->first_child; |
864 | } |
865 | |
866 | GtkCssNode * |
867 | gtk_css_node_get_last_child (GtkCssNode *cssnode) |
868 | { |
869 | return cssnode->last_child; |
870 | } |
871 | |
872 | GtkCssNode * |
873 | gtk_css_node_get_previous_sibling (GtkCssNode *cssnode) |
874 | { |
875 | return cssnode->previous_sibling; |
876 | } |
877 | |
878 | GtkCssNode * |
879 | gtk_css_node_get_next_sibling (GtkCssNode *cssnode) |
880 | { |
881 | return cssnode->next_sibling; |
882 | } |
883 | |
884 | static gboolean |
885 | gtk_css_node_set_style (GtkCssNode *cssnode, |
886 | GtkCssStyle *style) |
887 | { |
888 | GtkCssStyleChange change; |
889 | gboolean style_changed; |
890 | |
891 | if (cssnode->style == style) |
892 | return FALSE; |
893 | |
894 | gtk_css_style_change_init (change: &change, old_style: cssnode->style, new_style: style); |
895 | |
896 | style_changed = gtk_css_style_change_has_change (change: &change); |
897 | if (style_changed) |
898 | { |
899 | g_signal_emit (instance: cssnode, signal_id: cssnode_signals[STYLE_CHANGED], detail: 0, &change); |
900 | } |
901 | else if (GTK_IS_CSS_ANIMATED_STYLE (cssnode->style) || GTK_IS_CSS_ANIMATED_STYLE (style)) |
902 | { |
903 | /* This is when animations are starting/stopping but they didn't change any CSS this frame */ |
904 | g_set_object (&cssnode->style, style); |
905 | } |
906 | else if (gtk_css_static_style_get_change (style: gtk_css_style_get_static_style (style: cssnode->style)) != |
907 | gtk_css_static_style_get_change (style: gtk_css_style_get_static_style (style))) |
908 | { |
909 | /* This is when we recomputed the change flags but the style didn't change */ |
910 | g_set_object (&cssnode->style, style); |
911 | } |
912 | |
913 | gtk_css_style_change_finish (change: &change); |
914 | |
915 | return style_changed; |
916 | } |
917 | |
918 | static void |
919 | gtk_css_node_propagate_pending_changes (GtkCssNode *cssnode, |
920 | gboolean style_changed) |
921 | { |
922 | GtkCssChange change, child_change; |
923 | GtkCssNode *child; |
924 | |
925 | change = _gtk_css_change_for_child (match: cssnode->pending_changes); |
926 | if (style_changed) |
927 | change |= GTK_CSS_CHANGE_PARENT_STYLE; |
928 | |
929 | if (!cssnode->needs_propagation && change == 0) |
930 | return; |
931 | |
932 | for (child = gtk_css_node_get_first_child (cssnode); |
933 | child; |
934 | child = gtk_css_node_get_next_sibling (cssnode: child)) |
935 | { |
936 | child_change = child->pending_changes; |
937 | gtk_css_node_invalidate (cssnode: child, change); |
938 | if (child->visible) |
939 | change |= _gtk_css_change_for_sibling (match: child_change); |
940 | } |
941 | |
942 | cssnode->needs_propagation = FALSE; |
943 | } |
944 | |
945 | static gboolean |
946 | gtk_css_node_needs_new_style (GtkCssNode *cssnode) |
947 | { |
948 | return cssnode->style_is_invalid || cssnode->needs_propagation; |
949 | } |
950 | |
951 | static void |
952 | gtk_css_node_do_ensure_style (GtkCssNode *cssnode, |
953 | const GtkCountingBloomFilter *filter, |
954 | gint64 current_time) |
955 | { |
956 | gboolean style_changed; |
957 | |
958 | if (cssnode->style_is_invalid) |
959 | { |
960 | GtkCssStyle *new_style; |
961 | |
962 | g_clear_pointer (&cssnode->cache, gtk_css_node_style_cache_unref); |
963 | |
964 | new_style = GTK_CSS_NODE_GET_CLASS (cssnode)->update_style (cssnode, |
965 | filter, |
966 | cssnode->pending_changes, |
967 | current_time, |
968 | cssnode->style); |
969 | |
970 | style_changed = gtk_css_node_set_style (cssnode, style: new_style); |
971 | g_object_unref (object: new_style); |
972 | } |
973 | else |
974 | { |
975 | style_changed = FALSE; |
976 | } |
977 | |
978 | gtk_css_node_propagate_pending_changes (cssnode, style_changed); |
979 | |
980 | cssnode->pending_changes = 0; |
981 | cssnode->style_is_invalid = FALSE; |
982 | } |
983 | |
984 | static void |
985 | gtk_css_node_ensure_style (GtkCssNode *cssnode, |
986 | const GtkCountingBloomFilter *filter, |
987 | gint64 current_time) |
988 | { |
989 | GtkCssNode *sibling; |
990 | |
991 | if (!gtk_css_node_needs_new_style (cssnode)) |
992 | return; |
993 | |
994 | if (cssnode->parent) |
995 | gtk_css_node_ensure_style (cssnode: cssnode->parent, filter, current_time); |
996 | |
997 | /* Ensure all siblings before this have a valid style, in order |
998 | * starting at the first that needs it. */ |
999 | sibling = cssnode; |
1000 | while (sibling->style_is_invalid && |
1001 | sibling->previous_sibling != NULL && |
1002 | gtk_css_node_needs_new_style (cssnode: sibling->previous_sibling)) |
1003 | sibling = sibling->previous_sibling; |
1004 | |
1005 | while (sibling != cssnode) |
1006 | { |
1007 | gtk_css_node_do_ensure_style (cssnode: sibling, filter, current_time); |
1008 | sibling = sibling->next_sibling; |
1009 | } |
1010 | |
1011 | gtk_css_node_do_ensure_style (cssnode, filter, current_time); |
1012 | } |
1013 | |
1014 | GtkCssStyle * |
1015 | gtk_css_node_get_style (GtkCssNode *cssnode) |
1016 | { |
1017 | if (gtk_css_node_needs_new_style (cssnode)) |
1018 | { |
1019 | gint64 timestamp = gtk_css_node_get_timestamp (cssnode); |
1020 | |
1021 | gtk_css_node_ensure_style (cssnode, NULL, current_time: timestamp); |
1022 | } |
1023 | |
1024 | return cssnode->style; |
1025 | } |
1026 | |
1027 | void |
1028 | gtk_css_node_set_visible (GtkCssNode *cssnode, |
1029 | gboolean visible) |
1030 | { |
1031 | GtkCssNode *iter; |
1032 | |
1033 | if (cssnode->visible == visible) |
1034 | return; |
1035 | |
1036 | cssnode->visible = visible; |
1037 | g_object_notify_by_pspec (G_OBJECT (cssnode), pspec: cssnode_properties[PROP_VISIBLE]); |
1038 | |
1039 | if (cssnode->invalid) |
1040 | { |
1041 | if (cssnode->visible) |
1042 | { |
1043 | if (cssnode->parent) |
1044 | gtk_css_node_set_invalid (node: cssnode->parent, TRUE); |
1045 | else |
1046 | GTK_CSS_NODE_GET_CLASS (cssnode)->queue_validate (cssnode); |
1047 | } |
1048 | else |
1049 | { |
1050 | if (cssnode->parent == NULL) |
1051 | GTK_CSS_NODE_GET_CLASS (cssnode)->dequeue_validate (cssnode); |
1052 | } |
1053 | } |
1054 | |
1055 | if (cssnode->next_sibling) |
1056 | { |
1057 | gtk_css_node_invalidate (cssnode: cssnode->next_sibling, GTK_CSS_CHANGE_ANY_SIBLING | GTK_CSS_CHANGE_NTH_CHILD); |
1058 | if (gtk_css_node_is_first_child (node: cssnode)) |
1059 | { |
1060 | for (iter = cssnode->next_sibling; |
1061 | iter != NULL; |
1062 | iter = iter->next_sibling) |
1063 | { |
1064 | gtk_css_node_invalidate (cssnode: iter, GTK_CSS_CHANGE_FIRST_CHILD); |
1065 | if (iter->visible) |
1066 | break; |
1067 | } |
1068 | } |
1069 | } |
1070 | |
1071 | if (cssnode->previous_sibling) |
1072 | { |
1073 | if (gtk_css_node_is_last_child (node: cssnode)) |
1074 | { |
1075 | for (iter = cssnode->previous_sibling; |
1076 | iter != NULL; |
1077 | iter = iter->previous_sibling) |
1078 | { |
1079 | gtk_css_node_invalidate (cssnode: iter, GTK_CSS_CHANGE_LAST_CHILD); |
1080 | if (iter->visible) |
1081 | break; |
1082 | } |
1083 | } |
1084 | gtk_css_node_invalidate (cssnode: cssnode->parent->first_child, GTK_CSS_CHANGE_NTH_LAST_CHILD); |
1085 | } |
1086 | } |
1087 | |
1088 | gboolean |
1089 | gtk_css_node_get_visible (GtkCssNode *cssnode) |
1090 | { |
1091 | return cssnode->visible; |
1092 | } |
1093 | |
1094 | void |
1095 | gtk_css_node_set_name (GtkCssNode *cssnode, |
1096 | GQuark name) |
1097 | { |
1098 | if (gtk_css_node_declaration_set_name (decl: &cssnode->decl, name)) |
1099 | { |
1100 | gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_NAME); |
1101 | g_object_notify_by_pspec (G_OBJECT (cssnode), pspec: cssnode_properties[PROP_NAME]); |
1102 | } |
1103 | } |
1104 | |
1105 | GQuark |
1106 | gtk_css_node_get_name (GtkCssNode *cssnode) |
1107 | { |
1108 | return gtk_css_node_declaration_get_name (decl: cssnode->decl); |
1109 | } |
1110 | |
1111 | void |
1112 | gtk_css_node_set_id (GtkCssNode *cssnode, |
1113 | GQuark id) |
1114 | { |
1115 | if (gtk_css_node_declaration_set_id (decl: &cssnode->decl, id)) |
1116 | { |
1117 | gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_ID); |
1118 | g_object_notify_by_pspec (G_OBJECT (cssnode), pspec: cssnode_properties[PROP_ID]); |
1119 | } |
1120 | } |
1121 | |
1122 | GQuark |
1123 | gtk_css_node_get_id (GtkCssNode *cssnode) |
1124 | { |
1125 | return gtk_css_node_declaration_get_id (decl: cssnode->decl); |
1126 | } |
1127 | |
1128 | void |
1129 | gtk_css_node_set_state (GtkCssNode *cssnode, |
1130 | GtkStateFlags state_flags) |
1131 | { |
1132 | GtkStateFlags old_state; |
1133 | |
1134 | old_state = gtk_css_node_declaration_get_state (decl: cssnode->decl); |
1135 | |
1136 | if (gtk_css_node_declaration_set_state (decl: &cssnode->decl, flags: state_flags)) |
1137 | { |
1138 | GtkStateFlags states = old_state ^ state_flags; |
1139 | GtkCssChange change = 0; |
1140 | |
1141 | if (states & GTK_STATE_FLAG_PRELIGHT) |
1142 | change |= GTK_CSS_CHANGE_HOVER; |
1143 | if (states & GTK_STATE_FLAG_INSENSITIVE) |
1144 | change |= GTK_CSS_CHANGE_DISABLED; |
1145 | if (states & GTK_STATE_FLAG_BACKDROP) |
1146 | change |= GTK_CSS_CHANGE_BACKDROP; |
1147 | if (states & GTK_STATE_FLAG_SELECTED) |
1148 | change |= GTK_CSS_CHANGE_SELECTED; |
1149 | if (states & ~(GTK_STATE_FLAG_PRELIGHT | |
1150 | GTK_STATE_FLAG_INSENSITIVE | |
1151 | GTK_STATE_FLAG_BACKDROP | |
1152 | GTK_STATE_FLAG_SELECTED)) |
1153 | change |= GTK_CSS_CHANGE_STATE; |
1154 | |
1155 | gtk_css_node_invalidate (cssnode, change); |
1156 | g_object_notify_by_pspec (G_OBJECT (cssnode), pspec: cssnode_properties[PROP_STATE]); |
1157 | } |
1158 | } |
1159 | |
1160 | GtkStateFlags |
1161 | gtk_css_node_get_state (GtkCssNode *cssnode) |
1162 | { |
1163 | return gtk_css_node_declaration_get_state (decl: cssnode->decl); |
1164 | } |
1165 | |
1166 | static void |
1167 | gtk_css_node_clear_classes (GtkCssNode *cssnode) |
1168 | { |
1169 | if (gtk_css_node_declaration_clear_classes (decl: &cssnode->decl)) |
1170 | { |
1171 | gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_CLASS); |
1172 | g_object_notify_by_pspec (G_OBJECT (cssnode), pspec: cssnode_properties[PROP_CLASSES]); |
1173 | } |
1174 | } |
1175 | |
1176 | void |
1177 | gtk_css_node_set_classes (GtkCssNode *cssnode, |
1178 | const char **classes) |
1179 | { |
1180 | guint i; |
1181 | |
1182 | g_object_freeze_notify (G_OBJECT (cssnode)); |
1183 | |
1184 | gtk_css_node_clear_classes (cssnode); |
1185 | |
1186 | if (classes) |
1187 | { |
1188 | for (i = 0; classes[i] != NULL; i++) |
1189 | { |
1190 | gtk_css_node_add_class (cssnode, style_class: g_quark_from_string (string: classes[i])); |
1191 | } |
1192 | } |
1193 | |
1194 | g_object_thaw_notify (G_OBJECT (cssnode)); |
1195 | } |
1196 | |
1197 | char ** |
1198 | gtk_css_node_get_classes (GtkCssNode *cssnode) |
1199 | { |
1200 | const GQuark *classes; |
1201 | char **result; |
1202 | guint n_classes, i, j; |
1203 | |
1204 | classes = gtk_css_node_declaration_get_classes (decl: cssnode->decl, n_classes: &n_classes); |
1205 | result = g_new (char *, n_classes + 1); |
1206 | |
1207 | for (i = n_classes, j = 0; i-- > 0; ++j) |
1208 | { |
1209 | result[j] = g_strdup (str: g_quark_to_string (quark: classes[i])); |
1210 | } |
1211 | |
1212 | result[n_classes] = NULL; |
1213 | return result; |
1214 | } |
1215 | |
1216 | void |
1217 | gtk_css_node_add_class (GtkCssNode *cssnode, |
1218 | GQuark style_class) |
1219 | { |
1220 | if (gtk_css_node_declaration_add_class (decl: &cssnode->decl, class_quark: style_class)) |
1221 | { |
1222 | gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_CLASS); |
1223 | g_object_notify_by_pspec (G_OBJECT (cssnode), pspec: cssnode_properties[PROP_CLASSES]); |
1224 | } |
1225 | } |
1226 | |
1227 | void |
1228 | gtk_css_node_remove_class (GtkCssNode *cssnode, |
1229 | GQuark style_class) |
1230 | { |
1231 | if (gtk_css_node_declaration_remove_class (decl: &cssnode->decl, class_quark: style_class)) |
1232 | { |
1233 | gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_CLASS); |
1234 | g_object_notify_by_pspec (G_OBJECT (cssnode), pspec: cssnode_properties[PROP_CLASSES]); |
1235 | } |
1236 | } |
1237 | |
1238 | gboolean |
1239 | gtk_css_node_has_class (GtkCssNode *cssnode, |
1240 | GQuark style_class) |
1241 | { |
1242 | return gtk_css_node_declaration_has_class (decl: cssnode->decl, class_quark: style_class); |
1243 | } |
1244 | |
1245 | const GQuark * |
1246 | gtk_css_node_list_classes (GtkCssNode *cssnode, |
1247 | guint *n_classes) |
1248 | { |
1249 | return gtk_css_node_declaration_get_classes (decl: cssnode->decl, n_classes); |
1250 | } |
1251 | |
1252 | const GtkCssNodeDeclaration * |
1253 | gtk_css_node_get_declaration (GtkCssNode *cssnode) |
1254 | { |
1255 | return cssnode->decl; |
1256 | } |
1257 | |
1258 | void |
1259 | gtk_css_node_invalidate_style_provider (GtkCssNode *cssnode) |
1260 | { |
1261 | GtkCssNode *child; |
1262 | |
1263 | gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_SOURCE); |
1264 | |
1265 | for (child = cssnode->first_child; |
1266 | child; |
1267 | child = child->next_sibling) |
1268 | { |
1269 | if (gtk_css_node_get_style_provider_or_null (cssnode: child) == NULL) |
1270 | gtk_css_node_invalidate_style_provider (cssnode: child); |
1271 | } |
1272 | } |
1273 | |
1274 | static void |
1275 | gtk_css_node_invalidate_timestamp (GtkCssNode *cssnode) |
1276 | { |
1277 | GtkCssNode *child; |
1278 | |
1279 | if (!cssnode->invalid) |
1280 | return; |
1281 | |
1282 | if (!gtk_css_style_is_static (style: cssnode->style)) |
1283 | gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_TIMESTAMP); |
1284 | |
1285 | for (child = cssnode->first_child; child; child = child->next_sibling) |
1286 | { |
1287 | gtk_css_node_invalidate_timestamp (cssnode: child); |
1288 | } |
1289 | } |
1290 | |
1291 | void |
1292 | gtk_css_node_invalidate_frame_clock (GtkCssNode *cssnode, |
1293 | gboolean just_timestamp) |
1294 | { |
1295 | /* frame clock is handled by the top level */ |
1296 | if (cssnode->parent) |
1297 | return; |
1298 | |
1299 | gtk_css_node_invalidate_timestamp (cssnode); |
1300 | |
1301 | if (!just_timestamp) |
1302 | gtk_css_node_invalidate (cssnode, GTK_CSS_CHANGE_ANIMATIONS); |
1303 | } |
1304 | |
1305 | void |
1306 | gtk_css_node_invalidate (GtkCssNode *cssnode, |
1307 | GtkCssChange change) |
1308 | { |
1309 | if (!cssnode->invalid) |
1310 | change &= ~GTK_CSS_CHANGE_TIMESTAMP; |
1311 | |
1312 | if (change == 0) |
1313 | return; |
1314 | |
1315 | cssnode->pending_changes |= change; |
1316 | |
1317 | if (cssnode->parent) |
1318 | cssnode->parent->needs_propagation = TRUE; |
1319 | gtk_css_node_invalidate_style (cssnode); |
1320 | } |
1321 | |
1322 | static void |
1323 | gtk_css_node_validate_internal (GtkCssNode *cssnode, |
1324 | GtkCountingBloomFilter *filter, |
1325 | gint64 timestamp) |
1326 | { |
1327 | GtkCssNode *child; |
1328 | gboolean bloomed = FALSE; |
1329 | |
1330 | if (!cssnode->invalid) |
1331 | return; |
1332 | |
1333 | gtk_css_node_ensure_style (cssnode, filter, current_time: timestamp); |
1334 | |
1335 | /* need to set to FALSE then to TRUE here to make it chain up */ |
1336 | gtk_css_node_set_invalid (node: cssnode, FALSE); |
1337 | if (!gtk_css_style_is_static (style: cssnode->style)) |
1338 | gtk_css_node_set_invalid (node: cssnode, TRUE); |
1339 | |
1340 | GTK_CSS_NODE_GET_CLASS (cssnode)->validate (cssnode); |
1341 | |
1342 | for (child = gtk_css_node_get_first_child (cssnode); |
1343 | child; |
1344 | child = gtk_css_node_get_next_sibling (cssnode: child)) |
1345 | { |
1346 | if (!child->visible) |
1347 | continue; |
1348 | |
1349 | if (!bloomed) |
1350 | { |
1351 | gtk_css_node_declaration_add_bloom_hashes (decl: cssnode->decl, filter); |
1352 | bloomed = TRUE; |
1353 | } |
1354 | |
1355 | gtk_css_node_validate_internal (cssnode: child, filter, timestamp); |
1356 | } |
1357 | |
1358 | if (bloomed) |
1359 | gtk_css_node_declaration_remove_bloom_hashes (decl: cssnode->decl, filter); |
1360 | } |
1361 | |
1362 | void |
1363 | gtk_css_node_validate (GtkCssNode *cssnode) |
1364 | { |
1365 | GtkCountingBloomFilter filter = GTK_COUNTING_BLOOM_FILTER_INIT; |
1366 | gint64 timestamp; |
1367 | gint64 before G_GNUC_UNUSED; |
1368 | |
1369 | before = GDK_PROFILER_CURRENT_TIME; |
1370 | |
1371 | g_assert (cssnode->parent == NULL); |
1372 | |
1373 | timestamp = gtk_css_node_get_timestamp (cssnode); |
1374 | |
1375 | gtk_css_node_validate_internal (cssnode, filter: &filter, timestamp); |
1376 | |
1377 | if (GDK_PROFILER_IS_RUNNING) |
1378 | { |
1379 | gdk_profiler_end_mark (before, "css validation" , "" ); |
1380 | gdk_profiler_set_int_counter (invalidated_nodes_counter, invalidated_nodes); |
1381 | gdk_profiler_set_int_counter (created_styles_counter, created_styles); |
1382 | invalidated_nodes = 0; |
1383 | created_styles = 0; |
1384 | } |
1385 | } |
1386 | |
1387 | GtkStyleProvider * |
1388 | gtk_css_node_get_style_provider (GtkCssNode *cssnode) |
1389 | { |
1390 | GtkStyleProvider *result; |
1391 | |
1392 | result = gtk_css_node_get_style_provider_or_null (cssnode); |
1393 | if (result) |
1394 | return result; |
1395 | |
1396 | if (cssnode->parent) |
1397 | return gtk_css_node_get_style_provider (cssnode: cssnode->parent); |
1398 | |
1399 | return GTK_STYLE_PROVIDER (_gtk_settings_get_style_cascade (gtk_settings_get_default (), 1)); |
1400 | } |
1401 | |
1402 | void |
1403 | gtk_css_node_print (GtkCssNode *cssnode, |
1404 | GtkStyleContextPrintFlags flags, |
1405 | GString *string, |
1406 | guint indent) |
1407 | { |
1408 | gboolean need_newline = FALSE; |
1409 | |
1410 | g_string_append_printf (string, format: "%*s" , indent, "" ); |
1411 | |
1412 | if (!cssnode->visible) |
1413 | g_string_append_c (string, '['); |
1414 | |
1415 | gtk_css_node_declaration_print (decl: cssnode->decl, string); |
1416 | |
1417 | if (!cssnode->visible) |
1418 | g_string_append_c (string, ']'); |
1419 | |
1420 | if (flags & GTK_STYLE_CONTEXT_PRINT_SHOW_CHANGE) |
1421 | { |
1422 | GtkCssStyle *style = gtk_css_node_get_style (cssnode); |
1423 | GtkCssChange change; |
1424 | |
1425 | change = gtk_css_static_style_get_change (style: gtk_css_style_get_static_style (style)); |
1426 | g_string_append (string, val: " " ); |
1427 | gtk_css_change_print (change, string); |
1428 | } |
1429 | |
1430 | g_string_append_c (string, '\n'); |
1431 | |
1432 | if (flags & GTK_STYLE_CONTEXT_PRINT_SHOW_STYLE) |
1433 | need_newline = gtk_css_style_print (style: gtk_css_node_get_style (cssnode), string, indent: indent + 2, TRUE); |
1434 | |
1435 | if (flags & GTK_STYLE_CONTEXT_PRINT_RECURSE) |
1436 | { |
1437 | GtkCssNode *node; |
1438 | |
1439 | if (need_newline && gtk_css_node_get_first_child (cssnode)) |
1440 | g_string_append_c (string, '\n'); |
1441 | |
1442 | for (node = gtk_css_node_get_first_child (cssnode); node; node = gtk_css_node_get_next_sibling (cssnode: node)) |
1443 | gtk_css_node_print (cssnode: node, flags, string, indent: indent + 2); |
1444 | } |
1445 | } |
1446 | |