1 | /* |
2 | * Copyright © 2019 Benjamin Otte |
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.1 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 | * Authors: Benjamin Otte <otte@gnome.org> |
18 | */ |
19 | |
20 | #include "config.h" |
21 | |
22 | #include "gtkexpression.h" |
23 | |
24 | #include "gtkprivate.h" |
25 | |
26 | #include <gobject/gvaluecollector.h> |
27 | |
28 | /** |
29 | * GtkExpression: (ref-func gtk_expression_ref) (unref-func gtk_expression_unref) (set-value-func gtk_value_set_expression) (get-value-func gtk_value_get_expression) |
30 | * |
31 | * `GtkExpression` provides a way to describe references to values. |
32 | * |
33 | * An important aspect of expressions is that the value can be obtained |
34 | * from a source that is several steps away. For example, an expression |
35 | * may describe ‘the value of property A of `object1`, which is itself the |
36 | * value of a property of `object2`’. And `object1` may not even exist yet |
37 | * at the time that the expression is created. This is contrast to `GObject` |
38 | * property bindings, which can only create direct connections between |
39 | * the properties of two objects that must both exist for the duration |
40 | * of the binding. |
41 | * |
42 | * An expression needs to be "evaluated" to obtain the value that it currently |
43 | * refers to. An evaluation always happens in the context of a current object |
44 | * called `this` (it mirrors the behavior of object-oriented languages), |
45 | * which may or may not influence the result of the evaluation. Use |
46 | * [method@Gtk.Expression.evaluate] for evaluating an expression. |
47 | * |
48 | * Various methods for defining expressions exist, from simple constants via |
49 | * [ctor@Gtk.ConstantExpression.new] to looking up properties in a `GObject` |
50 | * (even recursively) via [ctor@Gtk.PropertyExpression.new] or providing |
51 | * custom functions to transform and combine expressions via |
52 | * [ctor@Gtk.ClosureExpression.new]. |
53 | * |
54 | * Here is an example of a complex expression: |
55 | * |
56 | * ```c |
57 | * color_expr = gtk_property_expression_new (GTK_TYPE_LIST_ITEM, |
58 | * NULL, "item"); |
59 | * expression = gtk_property_expression_new (GTK_TYPE_COLOR, |
60 | * color_expr, "name"); |
61 | * ``` |
62 | * |
63 | * when evaluated with `this` being a `GtkListItem`, it will obtain the |
64 | * "item" property from the `GtkListItem`, and then obtain the "name" property |
65 | * from the resulting object (which is assumed to be of type `GTK_TYPE_COLOR`). |
66 | * |
67 | * A more concise way to describe this would be |
68 | * |
69 | * ``` |
70 | * this->item->name |
71 | * ``` |
72 | * |
73 | * The most likely place where you will encounter expressions is in the context |
74 | * of list models and list widgets using them. For example, `GtkDropDown` is |
75 | * evaluating a `GtkExpression` to obtain strings from the items in its model |
76 | * that it can then use to match against the contents of its search entry. |
77 | * `GtkStringFilter` is using a `GtkExpression` for similar reasons. |
78 | * |
79 | * By default, expressions are not paying attention to changes and evaluation is |
80 | * just a snapshot of the current state at a given time. To get informed about |
81 | * changes, an expression needs to be "watched" via a [struct@Gtk.ExpressionWatch], |
82 | * which will cause a callback to be called whenever the value of the expression may |
83 | * have changed; [method@Gtk.Expression.watch] starts watching an expression, and |
84 | * [method@Gtk.ExpressionWatch.unwatch] stops. |
85 | * |
86 | * Watches can be created for automatically updating the property of an object, |
87 | * similar to GObject's `GBinding` mechanism, by using [method@Gtk.Expression.bind]. |
88 | * |
89 | * ## GtkExpression in GObject properties |
90 | * |
91 | * In order to use a `GtkExpression` as a `GObject` property, you must use the |
92 | * [id@gtk_param_spec_expression] when creating a `GParamSpec` to install in the |
93 | * `GObject` class being defined; for instance: |
94 | * |
95 | * ```c |
96 | * obj_props[PROP_EXPRESSION] = |
97 | * gtk_param_spec_expression ("expression", |
98 | * "Expression", |
99 | * "The expression used by the widget", |
100 | * G_PARAM_READWRITE | |
101 | * G_PARAM_STATIC_STRINGS | |
102 | * G_PARAM_EXPLICIT_NOTIFY); |
103 | * ``` |
104 | * |
105 | * When implementing the `GObjectClass.set_property` and `GObjectClass.get_property` |
106 | * virtual functions, you must use [id@gtk_value_get_expression], to retrieve the |
107 | * stored `GtkExpression` from the `GValue` container, and [id@gtk_value_set_expression], |
108 | * to store the `GtkExpression` into the `GValue`; for instance: |
109 | * |
110 | * ```c |
111 | * // in set_property()... |
112 | * case PROP_EXPRESSION: |
113 | * foo_widget_set_expression (foo, gtk_value_get_expression (value)); |
114 | * break; |
115 | * |
116 | * // in get_property()... |
117 | * case PROP_EXPRESSION: |
118 | * gtk_value_set_expression (value, foo->expression); |
119 | * break; |
120 | * ``` |
121 | * |
122 | * ## GtkExpression in .ui files |
123 | * |
124 | * `GtkBuilder` has support for creating expressions. The syntax here can be used where |
125 | * a `GtkExpression` object is needed like in a `<property>` tag for an expression |
126 | * property, or in a `<binding name="property">` tag to bind a property to an expression. |
127 | * |
128 | * To create a property expression, use the `<lookup>` element. It can have a `type` |
129 | * attribute to specify the object type, and a `name` attribute to specify the property |
130 | * to look up. The content of `<lookup>` can either be an element specfiying the expression |
131 | * to use the object, or a string that specifies the name of the object to use. |
132 | * |
133 | * Example: |
134 | * |
135 | * ```xml |
136 | * <lookup name='search'>string_filter</lookup> |
137 | * ``` |
138 | * |
139 | * To create a constant expression, use the `<constant>` element. If the type attribute |
140 | * is specified, the element content is interpreted as a value of that type. Otherwise, |
141 | * it is assumed to be an object. For instance: |
142 | * |
143 | * ```xml |
144 | * <constant>string_filter</constant> |
145 | * <constant type='gchararray'>Hello, world</constant> |
146 | * ``` |
147 | * |
148 | * To create a closure expression, use the `<closure>` element. The `type` and `function` |
149 | * attributes specify what function to use for the closure, the content of the element |
150 | * contains the expressions for the parameters. For instance: |
151 | * |
152 | * ```xml |
153 | * <closure type='gchararray' function='combine_args_somehow'> |
154 | * <constant type='gchararray'>File size:</constant> |
155 | * <lookup type='GFile' name='size'>myfile</lookup> |
156 | * </closure> |
157 | * ``` |
158 | */ |
159 | |
160 | |
161 | typedef struct _GtkExpressionClass GtkExpressionClass; |
162 | typedef struct _GtkExpressionSubWatch GtkExpressionSubWatch; |
163 | typedef struct _GtkExpressionTypeInfo GtkExpressionTypeInfo; |
164 | |
165 | struct _GtkExpression |
166 | { |
167 | GTypeInstance parent_instance; |
168 | |
169 | gatomicrefcount ref_count; |
170 | |
171 | GType value_type; |
172 | |
173 | GtkExpression *owner; |
174 | }; |
175 | |
176 | struct _GtkExpressionClass |
177 | { |
178 | GTypeClass parent_class; |
179 | |
180 | void (* finalize) (GtkExpression *expr); |
181 | gboolean (* is_static) (GtkExpression *expr); |
182 | gboolean (* evaluate) (GtkExpression *expr, |
183 | gpointer this, |
184 | GValue *value); |
185 | |
186 | gsize (* watch_size) (GtkExpression *expr); |
187 | void (* watch) (GtkExpression *self, |
188 | GtkExpressionSubWatch *watch, |
189 | gpointer this_, |
190 | GtkExpressionNotify notify, |
191 | gpointer user_data); |
192 | void (* unwatch) (GtkExpression *self, |
193 | GtkExpressionSubWatch *watch); |
194 | }; |
195 | |
196 | struct _GtkExpressionTypeInfo |
197 | { |
198 | gsize instance_size; |
199 | |
200 | void (* instance_init) (GtkExpression *expr); |
201 | void (* finalize) (GtkExpression *expr); |
202 | gboolean (* is_static) (GtkExpression *expr); |
203 | gboolean (* evaluate) (GtkExpression *expr, |
204 | gpointer this, |
205 | GValue *value); |
206 | |
207 | gsize (* watch_size) (GtkExpression *expr); |
208 | void (* watch) (GtkExpression *self, |
209 | GtkExpressionSubWatch *watch, |
210 | gpointer this_, |
211 | GtkExpressionNotify notify, |
212 | gpointer user_data); |
213 | void (* unwatch) (GtkExpression *self, |
214 | GtkExpressionSubWatch *watch); |
215 | }; |
216 | |
217 | /** |
218 | * GtkExpressionWatch: |
219 | * |
220 | * An opaque structure representing a watched `GtkExpression`. |
221 | * |
222 | * The contents of `GtkExpressionWatch` should only be accessed through the |
223 | * provided API. |
224 | */ |
225 | struct _GtkExpressionWatch |
226 | { |
227 | GtkExpression *expression; |
228 | GObject *this; |
229 | GDestroyNotify user_destroy; |
230 | GtkExpressionNotify notify; |
231 | gpointer user_data; |
232 | guchar sub[0]; |
233 | }; |
234 | |
235 | G_DEFINE_BOXED_TYPE (GtkExpressionWatch, gtk_expression_watch, |
236 | gtk_expression_watch_ref, |
237 | gtk_expression_watch_unref) |
238 | |
239 | #define GTK_EXPRESSION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_EXPRESSION, GtkExpressionClass)) |
240 | |
241 | /*< private > |
242 | * GTK_DEFINE_EXPRESSION_TYPE: |
243 | * @TypeName: the type name, in camel case |
244 | * @type_name: the type name, in snake case |
245 | * @type_info: the address of the `GtkExpressionTypeInfo` for the expression type |
246 | * |
247 | * Registers a new `GtkExpression` subclass with the given @TypeName and @type_info. |
248 | * |
249 | * Similarly to %G_DEFINE_TYPE, this macro will generate a `get_type()` |
250 | * function that registers the event type. |
251 | * |
252 | * You can specify code to be run after the type registration; the `GType` of |
253 | * the event is available in the `gtk_define_expression_type_id` variable. |
254 | */ |
255 | #define GTK_DEFINE_EXPRESSION_TYPE(TypeName, type_name, type_info) \ |
256 | GType \ |
257 | type_name ## _get_type (void) \ |
258 | { \ |
259 | static gsize gtk_define_expression_type_id__volatile; \ |
260 | if (g_once_init_enter (>k_define_expression_type_id__volatile)) \ |
261 | { \ |
262 | GType gtk_define_expression_type_id = \ |
263 | gtk_expression_type_register_static (g_intern_static_string (#TypeName), type_info); \ |
264 | g_once_init_leave (>k_define_expression_type_id__volatile, gtk_define_expression_type_id); \ |
265 | } \ |
266 | return gtk_define_expression_type_id__volatile; \ |
267 | } |
268 | |
269 | #define GTK_EXPRESSION_SUPER(expr) \ |
270 | ((GtkExpressionClass *) g_type_class_peek (g_type_parent (G_TYPE_FROM_INSTANCE (expr)))) |
271 | |
272 | /* {{{ GtkExpression internals */ |
273 | |
274 | static void |
275 | value_expression_init (GValue *value) |
276 | { |
277 | value->data[0].v_pointer = NULL; |
278 | } |
279 | |
280 | static void |
281 | value_expression_free_value (GValue *value) |
282 | { |
283 | if (value->data[0].v_pointer != NULL) |
284 | gtk_expression_unref (self: value->data[0].v_pointer); |
285 | } |
286 | |
287 | static void |
288 | value_expression_copy_value (const GValue *src, |
289 | GValue *dst) |
290 | { |
291 | if (src->data[0].v_pointer != NULL) |
292 | dst->data[0].v_pointer = gtk_expression_ref (self: src->data[0].v_pointer); |
293 | else |
294 | dst->data[0].v_pointer = NULL; |
295 | } |
296 | |
297 | static gpointer |
298 | value_expression_peek_pointer (const GValue *value) |
299 | { |
300 | return value->data[0].v_pointer; |
301 | } |
302 | |
303 | static char * |
304 | value_expression_collect_value (GValue *value, |
305 | guint n_collect_values, |
306 | GTypeCValue *collect_values, |
307 | guint collect_flags) |
308 | { |
309 | GtkExpression *expression = collect_values[0].v_pointer; |
310 | |
311 | if (expression == NULL) |
312 | { |
313 | value->data[0].v_pointer = NULL; |
314 | return NULL; |
315 | } |
316 | |
317 | if (expression->parent_instance.g_class == NULL) |
318 | return g_strconcat (string1: "invalid unclassed GtkExpression pointer for " |
319 | "value type '" , |
320 | G_VALUE_TYPE_NAME (value), |
321 | "'" , |
322 | NULL); |
323 | |
324 | value->data[0].v_pointer = gtk_expression_ref (self: expression); |
325 | |
326 | return NULL; |
327 | } |
328 | |
329 | static char * |
330 | value_expression_lcopy_value (const GValue *value, |
331 | guint n_collect_values, |
332 | GTypeCValue *collect_values, |
333 | guint collect_flags) |
334 | { |
335 | GtkExpression **expression_p = collect_values[0].v_pointer; |
336 | |
337 | if (expression_p == NULL) |
338 | return g_strconcat (string1: "value location for '" , |
339 | G_VALUE_TYPE_NAME (value), |
340 | "' passed as NULL" , |
341 | NULL); |
342 | |
343 | if (value->data[0].v_pointer == NULL) |
344 | *expression_p = NULL; |
345 | else if (collect_flags & G_VALUE_NOCOPY_CONTENTS) |
346 | *expression_p = value->data[0].v_pointer; |
347 | else |
348 | *expression_p = gtk_expression_ref (self: value->data[0].v_pointer); |
349 | |
350 | return NULL; |
351 | } |
352 | |
353 | /** |
354 | * gtk_value_set_expression: |
355 | * @value: a `GValue` initialized with type `GTK_TYPE_EXPRESSION` |
356 | * @expression: a `GtkExpression` |
357 | * |
358 | * Stores the given `GtkExpression` inside `value`. |
359 | * |
360 | * The `GValue` will acquire a reference to the `expression`. |
361 | */ |
362 | void |
363 | gtk_value_set_expression (GValue *value, |
364 | GtkExpression *expression) |
365 | { |
366 | g_return_if_fail (G_VALUE_HOLDS (value, GTK_TYPE_EXPRESSION)); |
367 | |
368 | GtkExpression *old_expression = value->data[0].v_pointer; |
369 | |
370 | if (expression != NULL) |
371 | { |
372 | g_return_if_fail (GTK_IS_EXPRESSION (expression)); |
373 | |
374 | value->data[0].v_pointer = gtk_expression_ref (self: expression); |
375 | } |
376 | else |
377 | { |
378 | value->data[0].v_pointer = NULL; |
379 | } |
380 | |
381 | if (old_expression != NULL) |
382 | gtk_expression_unref (self: old_expression); |
383 | } |
384 | |
385 | /** |
386 | * gtk_value_take_expression: |
387 | * @value: a `GValue` initialized with type `GTK_TYPE_EXPRESSION` |
388 | * @expression: (transfer full) (nullable): a `GtkExpression` |
389 | * |
390 | * Stores the given `GtkExpression` inside `value`. |
391 | * |
392 | * This function transfers the ownership of the `expression` to the `GValue`. |
393 | */ |
394 | void |
395 | gtk_value_take_expression (GValue *value, |
396 | GtkExpression *expression) |
397 | { |
398 | g_return_if_fail (G_VALUE_HOLDS (value, GTK_TYPE_EXPRESSION)); |
399 | |
400 | GtkExpression *old_expression = value->data[0].v_pointer; |
401 | |
402 | if (expression != NULL) |
403 | { |
404 | g_return_if_fail (GTK_IS_EXPRESSION (expression)); |
405 | |
406 | value->data[0].v_pointer = expression; |
407 | } |
408 | else |
409 | { |
410 | value->data[0].v_pointer = NULL; |
411 | } |
412 | |
413 | if (old_expression != NULL) |
414 | gtk_expression_unref (self: old_expression); |
415 | } |
416 | |
417 | /** |
418 | * gtk_value_get_expression: |
419 | * @value: a `GValue` initialized with type `GTK_TYPE_EXPRESSION` |
420 | * |
421 | * Retrieves the `GtkExpression` stored inside the given `value`. |
422 | * |
423 | * Returns: (transfer none) (nullable): a `GtkExpression` |
424 | */ |
425 | GtkExpression * |
426 | gtk_value_get_expression (const GValue *value) |
427 | { |
428 | g_return_val_if_fail (G_VALUE_HOLDS (value, GTK_TYPE_EXPRESSION), NULL); |
429 | |
430 | return value->data[0].v_pointer; |
431 | } |
432 | |
433 | /** |
434 | * gtk_value_dup_expression: |
435 | * @value: a `GValue` initialized with type `GTK_TYPE_EXPRESSION` |
436 | * |
437 | * Retrieves the `GtkExpression` stored inside the given `value`, and acquires |
438 | * a reference to it. |
439 | * |
440 | * Returns: (transfer full) (nullable): a `GtkExpression` |
441 | */ |
442 | GtkExpression * |
443 | gtk_value_dup_expression (const GValue *value) |
444 | { |
445 | g_return_val_if_fail (G_VALUE_HOLDS (value, GTK_TYPE_EXPRESSION), NULL); |
446 | |
447 | if (value->data[0].v_pointer == NULL) |
448 | return NULL; |
449 | |
450 | GtkExpression *expression = value->data[0].v_pointer; |
451 | |
452 | return gtk_expression_ref (self: expression); |
453 | } |
454 | |
455 | static void |
456 | param_expression_init (GParamSpec *pspec) |
457 | { |
458 | } |
459 | |
460 | static void |
461 | param_expression_set_default (GParamSpec *pspec, |
462 | GValue *value) |
463 | { |
464 | value->data[0].v_pointer = NULL; |
465 | } |
466 | |
467 | static gboolean |
468 | param_expression_validate (GParamSpec *pspec, |
469 | GValue *value) |
470 | { |
471 | GtkParamSpecExpression *espec = GTK_PARAM_SPEC_EXPRESSION (pspec); |
472 | GtkExpression *expr = value->data[0].v_pointer; |
473 | guint changed = 0; |
474 | |
475 | if (expr != NULL && |
476 | !g_value_type_compatible (G_TYPE_FROM_INSTANCE (expr), G_PARAM_SPEC_VALUE_TYPE (espec))) |
477 | { |
478 | gtk_expression_unref (self: expr); |
479 | value->data[0].v_pointer = NULL; |
480 | changed++; |
481 | } |
482 | |
483 | return changed; |
484 | } |
485 | |
486 | static int |
487 | param_expression_values_cmp (GParamSpec *pspec, |
488 | const GValue *value1, |
489 | const GValue *value2) |
490 | { |
491 | guint8 *p1 = value1->data[0].v_pointer; |
492 | guint8 *p2 = value2->data[0].v_pointer; |
493 | |
494 | return p1 < p2 ? -1 : p1 > p2; |
495 | } |
496 | |
497 | GType |
498 | gtk_param_expression_get_type (void) |
499 | { |
500 | static gsize param_expression_type__volatile; |
501 | |
502 | if (g_once_init_enter (¶m_expression_type__volatile)) |
503 | { |
504 | const GParamSpecTypeInfo pspec_info = { |
505 | sizeof (GtkParamSpecExpression), |
506 | 16, |
507 | param_expression_init, |
508 | GTK_TYPE_EXPRESSION, |
509 | NULL, |
510 | param_expression_set_default, |
511 | param_expression_validate, |
512 | param_expression_values_cmp, |
513 | }; |
514 | |
515 | GType param_expression_type = |
516 | g_param_type_register_static (name: g_intern_static_string (string: "GtkParamSpecExpression" ), |
517 | pspec_info: &pspec_info); |
518 | |
519 | g_once_init_leave (¶m_expression_type__volatile, param_expression_type); |
520 | } |
521 | |
522 | return param_expression_type__volatile; |
523 | } |
524 | |
525 | /** |
526 | * gtk_param_spec_expression: |
527 | * @name: canonical name of the property |
528 | * @nick: a user-readable name for the property |
529 | * @blurb: a user-readable description of the property |
530 | * @flags: flags for the property |
531 | * |
532 | * Creates a new `GParamSpec` instance for a property holding a `GtkExpression`. |
533 | * |
534 | * See `g_param_spec_internal()` for details on the property strings. |
535 | * |
536 | * Returns: (transfer full): a newly created property specification |
537 | */ |
538 | GParamSpec * |
539 | gtk_param_spec_expression (const char *name, |
540 | const char *nick, |
541 | const char *blurb, |
542 | GParamFlags flags) |
543 | { |
544 | GParamSpec *pspec = g_param_spec_internal (GTK_TYPE_PARAM_SPEC_EXPRESSION, |
545 | name, nick, blurb, |
546 | flags); |
547 | |
548 | pspec->value_type = GTK_TYPE_EXPRESSION; |
549 | |
550 | return pspec; |
551 | } |
552 | |
553 | /* }}} */ |
554 | |
555 | /* {{{ GtkExpression internals */ |
556 | |
557 | static void |
558 | gtk_expression_real_finalize (GtkExpression *self) |
559 | { |
560 | g_type_free_instance (instance: (GTypeInstance *) self); |
561 | } |
562 | |
563 | static gsize |
564 | gtk_expression_real_watch_size (GtkExpression *self) |
565 | { |
566 | return 0; |
567 | } |
568 | |
569 | static void |
570 | gtk_expression_real_watch (GtkExpression *self, |
571 | GtkExpressionSubWatch *watch, |
572 | gpointer this_, |
573 | GtkExpressionNotify notify, |
574 | gpointer user_data) |
575 | { |
576 | } |
577 | |
578 | static void |
579 | gtk_expression_real_unwatch (GtkExpression *self, |
580 | GtkExpressionSubWatch *watch) |
581 | { |
582 | } |
583 | |
584 | static gsize |
585 | gtk_expression_watch_size (GtkExpression *self) |
586 | { |
587 | return GTK_EXPRESSION_GET_CLASS (self)->watch_size (self); |
588 | } |
589 | |
590 | static void |
591 | gtk_expression_class_init (GtkExpressionClass *klass) |
592 | { |
593 | klass->finalize = gtk_expression_real_finalize; |
594 | klass->watch_size = gtk_expression_real_watch_size; |
595 | klass->watch = gtk_expression_real_watch; |
596 | klass->unwatch = gtk_expression_real_unwatch; |
597 | } |
598 | |
599 | static void |
600 | gtk_expression_init (GtkExpression *self) |
601 | { |
602 | g_atomic_ref_count_init (arc: &self->ref_count); |
603 | } |
604 | |
605 | GType |
606 | gtk_expression_get_type (void) |
607 | { |
608 | static gsize expression_type__volatile; |
609 | |
610 | if (g_once_init_enter (&expression_type__volatile)) |
611 | { |
612 | static const GTypeFundamentalInfo finfo = { |
613 | (G_TYPE_FLAG_CLASSED | |
614 | G_TYPE_FLAG_INSTANTIATABLE | |
615 | G_TYPE_FLAG_DERIVABLE | |
616 | G_TYPE_FLAG_DEEP_DERIVABLE), |
617 | }; |
618 | |
619 | static const GTypeValueTable value_table = { |
620 | value_expression_init, |
621 | value_expression_free_value, |
622 | value_expression_copy_value, |
623 | value_expression_peek_pointer, |
624 | "p" , |
625 | value_expression_collect_value, |
626 | "p" , |
627 | value_expression_lcopy_value, |
628 | }; |
629 | |
630 | const GTypeInfo event_info = { |
631 | /* Class */ |
632 | sizeof (GtkExpressionClass), |
633 | (GBaseInitFunc) NULL, |
634 | (GBaseFinalizeFunc) NULL, |
635 | (GClassInitFunc) gtk_expression_class_init, |
636 | (GClassFinalizeFunc) NULL, |
637 | NULL, |
638 | |
639 | /* Instance */ |
640 | sizeof (GtkExpression), |
641 | 0, |
642 | (GInstanceInitFunc) gtk_expression_init, |
643 | |
644 | /* GValue */ |
645 | &value_table, |
646 | }; |
647 | |
648 | GType expression_type = |
649 | g_type_register_fundamental (type_id: g_type_fundamental_next (), |
650 | type_name: g_intern_static_string (string: "GtkExpression" ), |
651 | info: &event_info, finfo: &finfo, |
652 | flags: G_TYPE_FLAG_ABSTRACT); |
653 | |
654 | g_once_init_leave (&expression_type__volatile, expression_type); |
655 | } |
656 | |
657 | return expression_type__volatile; |
658 | } |
659 | |
660 | static void |
661 | gtk_expression_generic_class_init (gpointer g_class, |
662 | gpointer class_data) |
663 | { |
664 | GtkExpressionTypeInfo *info = class_data; |
665 | GtkExpressionClass *expression_class = g_class; |
666 | |
667 | /* Mandatory */ |
668 | expression_class->is_static = info->is_static; |
669 | expression_class->evaluate = info->evaluate; |
670 | |
671 | /* Optional */ |
672 | if (info->finalize != NULL) |
673 | expression_class->finalize = info->finalize; |
674 | if (info->watch_size != NULL) |
675 | expression_class->watch_size = info->watch_size; |
676 | if (info->watch != NULL) |
677 | expression_class->watch = info->watch; |
678 | if (info->unwatch != NULL) |
679 | expression_class->unwatch = info->unwatch; |
680 | |
681 | g_free (mem: info); |
682 | } |
683 | |
684 | static GType |
685 | gtk_expression_type_register_static (const char *type_name, |
686 | const GtkExpressionTypeInfo *type_info) |
687 | { |
688 | GTypeInfo info; |
689 | |
690 | info.class_size = sizeof (GtkExpressionClass); |
691 | info.base_init = NULL; |
692 | info.base_finalize = NULL; |
693 | info.class_init = gtk_expression_generic_class_init; |
694 | info.class_finalize = NULL; |
695 | info.class_data = g_memdup2 (mem: type_info, byte_size: sizeof (GtkExpressionTypeInfo)); |
696 | |
697 | info.instance_size = type_info->instance_size; |
698 | info.n_preallocs = 0; |
699 | info.instance_init = (GInstanceInitFunc) type_info->instance_init; |
700 | info.value_table = NULL; |
701 | |
702 | return g_type_register_static (GTK_TYPE_EXPRESSION, type_name, info: &info, flags: 0); |
703 | } |
704 | |
705 | /*< private > |
706 | * gtk_expression_alloc: |
707 | * @expression_type: the type of expression to create |
708 | * @value_type: the type of the value returned by this expression |
709 | * |
710 | * Returns: (transfer full): the newly created `GtkExpression` |
711 | */ |
712 | static gpointer |
713 | gtk_expression_alloc (GType expression_type, |
714 | GType value_type) |
715 | { |
716 | GtkExpression *self; |
717 | |
718 | self = (GtkExpression *) g_type_create_instance (type: expression_type); |
719 | |
720 | self->value_type = value_type; |
721 | |
722 | return self; |
723 | } |
724 | |
725 | static void |
726 | gtk_expression_subwatch_init (GtkExpression *self, |
727 | GtkExpressionSubWatch *watch, |
728 | gpointer this, |
729 | GtkExpressionNotify notify, |
730 | gpointer user_data) |
731 | { |
732 | GTK_EXPRESSION_GET_CLASS (self)->watch (self, watch, this, notify, user_data); |
733 | } |
734 | |
735 | static void |
736 | gtk_expression_subwatch_finish (GtkExpression *self, |
737 | GtkExpressionSubWatch *watch) |
738 | { |
739 | GTK_EXPRESSION_GET_CLASS (self)->unwatch (self, watch); |
740 | } |
741 | |
742 | /* }}} */ |
743 | |
744 | /* {{{ GtkConstantExpression */ |
745 | |
746 | /** |
747 | * GtkConstantExpression: |
748 | * |
749 | * A constant value in a `GtkExpression`. |
750 | */ |
751 | struct _GtkConstantExpression |
752 | { |
753 | GtkExpression parent; |
754 | |
755 | GValue value; |
756 | }; |
757 | |
758 | static void |
759 | gtk_constant_expression_finalize (GtkExpression *expr) |
760 | { |
761 | GtkConstantExpression *self = (GtkConstantExpression *) expr; |
762 | |
763 | g_value_unset (value: &self->value); |
764 | |
765 | GTK_EXPRESSION_SUPER (expr)->finalize (expr); |
766 | } |
767 | |
768 | static gboolean |
769 | gtk_constant_expression_is_static (GtkExpression *expr) |
770 | { |
771 | return TRUE; |
772 | } |
773 | |
774 | static gboolean |
775 | gtk_constant_expression_evaluate (GtkExpression *expr, |
776 | gpointer this, |
777 | GValue *value) |
778 | { |
779 | GtkConstantExpression *self = (GtkConstantExpression *) expr; |
780 | |
781 | g_value_init (value, G_VALUE_TYPE (&self->value)); |
782 | g_value_copy (src_value: &self->value, dest_value: value); |
783 | return TRUE; |
784 | } |
785 | |
786 | static const GtkExpressionTypeInfo gtk_constant_expression_info = |
787 | { |
788 | sizeof (GtkConstantExpression), |
789 | NULL, |
790 | gtk_constant_expression_finalize, |
791 | gtk_constant_expression_is_static, |
792 | gtk_constant_expression_evaluate, |
793 | NULL, |
794 | NULL, |
795 | NULL, |
796 | }; |
797 | |
798 | GTK_DEFINE_EXPRESSION_TYPE (GtkConstantExpression, |
799 | gtk_constant_expression, |
800 | >k_constant_expression_info) |
801 | |
802 | /** |
803 | * gtk_constant_expression_new: |
804 | * @value_type: The type of the object |
805 | * @...: arguments to create the object from |
806 | * |
807 | * Creates a `GtkExpression` that evaluates to the |
808 | * object given by the arguments. |
809 | * |
810 | * Returns: (transfer full) (type GtkConstantExpression): a new `GtkExpression` |
811 | */ |
812 | GtkExpression * |
813 | gtk_constant_expression_new (GType value_type, |
814 | ...) |
815 | { |
816 | GValue value = G_VALUE_INIT; |
817 | GtkExpression *result; |
818 | va_list args; |
819 | char *error; |
820 | |
821 | va_start (args, value_type); |
822 | G_VALUE_COLLECT_INIT (&value, value_type, |
823 | args, G_VALUE_NOCOPY_CONTENTS, |
824 | &error); |
825 | if (error) |
826 | { |
827 | g_critical ("%s: %s" , G_STRLOC, error); |
828 | g_free (mem: error); |
829 | /* we purposely leak the value here, it might not be |
830 | * in a sane state if an error condition occurred |
831 | */ |
832 | return NULL; |
833 | } |
834 | |
835 | result = gtk_constant_expression_new_for_value (value: &value); |
836 | |
837 | g_value_unset (value: &value); |
838 | va_end (args); |
839 | |
840 | return result; |
841 | } |
842 | |
843 | /** |
844 | * gtk_constant_expression_new_for_value: (constructor) |
845 | * @value: a `GValue` |
846 | * |
847 | * Creates an expression that always evaluates to the given `value`. |
848 | * |
849 | * Returns: (transfer full) (type GtkConstantExpression): a new `GtkExpression` |
850 | **/ |
851 | GtkExpression * |
852 | gtk_constant_expression_new_for_value (const GValue *value) |
853 | { |
854 | GtkExpression *result; |
855 | GtkConstantExpression *self; |
856 | |
857 | g_return_val_if_fail (G_IS_VALUE (value), NULL); |
858 | |
859 | result = gtk_expression_alloc (GTK_TYPE_CONSTANT_EXPRESSION, G_VALUE_TYPE (value)); |
860 | self = (GtkConstantExpression *) result; |
861 | |
862 | g_value_init (value: &self->value, G_VALUE_TYPE (value)); |
863 | g_value_copy (src_value: value, dest_value: &self->value); |
864 | |
865 | return result; |
866 | } |
867 | |
868 | /** |
869 | * gtk_constant_expression_get_value: |
870 | * @expression: (type GtkConstantExpression): a constant `GtkExpression` |
871 | * |
872 | * Gets the value that a constant expression evaluates to. |
873 | * |
874 | * Returns: (transfer none): the value |
875 | */ |
876 | const GValue * |
877 | gtk_constant_expression_get_value (GtkExpression *expression) |
878 | { |
879 | GtkConstantExpression *self = (GtkConstantExpression *) expression; |
880 | |
881 | g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (expression, GTK_TYPE_CONSTANT_EXPRESSION), NULL); |
882 | |
883 | return &self->value; |
884 | } |
885 | |
886 | /* }}} */ |
887 | |
888 | /* {{{ GtkObjectExpression */ |
889 | |
890 | typedef struct _GtkObjectExpressionWatch GtkObjectExpressionWatch; |
891 | |
892 | /** |
893 | * GtkObjectExpression: |
894 | * |
895 | * A `GObject` value in a `GtkExpression`. |
896 | */ |
897 | struct _GtkObjectExpression |
898 | { |
899 | GtkExpression parent; |
900 | |
901 | GObject *object; |
902 | GSList *watches; |
903 | }; |
904 | |
905 | struct _GtkObjectExpressionWatch |
906 | { |
907 | GtkExpressionNotify notify; |
908 | gpointer user_data; |
909 | }; |
910 | |
911 | static void |
912 | gtk_object_expression_weak_ref_cb (gpointer data, |
913 | GObject *object) |
914 | { |
915 | GtkObjectExpression *self = (GtkObjectExpression *) data; |
916 | GSList *l; |
917 | |
918 | self->object = NULL; |
919 | |
920 | for (l = self->watches; l; l = l->next) |
921 | { |
922 | GtkObjectExpressionWatch *owatch = l->data; |
923 | |
924 | owatch->notify (owatch->user_data); |
925 | } |
926 | } |
927 | |
928 | static void |
929 | gtk_object_expression_finalize (GtkExpression *expr) |
930 | { |
931 | GtkObjectExpression *self = (GtkObjectExpression *) expr; |
932 | |
933 | if (self->object) |
934 | g_object_weak_unref (object: self->object, notify: gtk_object_expression_weak_ref_cb, data: self); |
935 | |
936 | g_assert (self->watches == NULL); |
937 | |
938 | GTK_EXPRESSION_SUPER (expr)->finalize (expr); |
939 | } |
940 | |
941 | static gboolean |
942 | gtk_object_expression_is_static (GtkExpression *expr) |
943 | { |
944 | return FALSE; |
945 | } |
946 | |
947 | static gboolean |
948 | gtk_object_expression_evaluate (GtkExpression *expr, |
949 | gpointer this, |
950 | GValue *value) |
951 | { |
952 | GtkObjectExpression *self = (GtkObjectExpression *) expr; |
953 | |
954 | if (self->object == NULL) |
955 | return FALSE; |
956 | |
957 | g_value_init (value, g_type: gtk_expression_get_value_type (self: expr)); |
958 | g_value_set_object (value, v_object: self->object); |
959 | return TRUE; |
960 | } |
961 | |
962 | static gsize |
963 | gtk_object_expression_watch_size (GtkExpression *expr) |
964 | { |
965 | return sizeof (GtkObjectExpressionWatch); |
966 | } |
967 | |
968 | static void |
969 | gtk_object_expression_watch (GtkExpression *expr, |
970 | GtkExpressionSubWatch *watch, |
971 | gpointer this_, |
972 | GtkExpressionNotify notify, |
973 | gpointer user_data) |
974 | { |
975 | GtkObjectExpression *self = (GtkObjectExpression *) expr; |
976 | GtkObjectExpressionWatch *owatch = (GtkObjectExpressionWatch *) watch; |
977 | |
978 | owatch->notify = notify; |
979 | owatch->user_data = user_data; |
980 | self->watches = g_slist_prepend (list: self->watches, data: owatch); |
981 | } |
982 | |
983 | static void |
984 | gtk_object_expression_unwatch (GtkExpression *expr, |
985 | GtkExpressionSubWatch *watch) |
986 | { |
987 | GtkObjectExpression *self = (GtkObjectExpression *) expr; |
988 | |
989 | self->watches = g_slist_remove (list: self->watches, data: watch); |
990 | } |
991 | |
992 | static const GtkExpressionTypeInfo gtk_object_expression_info = |
993 | { |
994 | sizeof (GtkObjectExpression), |
995 | NULL, |
996 | gtk_object_expression_finalize, |
997 | gtk_object_expression_is_static, |
998 | gtk_object_expression_evaluate, |
999 | gtk_object_expression_watch_size, |
1000 | gtk_object_expression_watch, |
1001 | gtk_object_expression_unwatch |
1002 | }; |
1003 | |
1004 | GTK_DEFINE_EXPRESSION_TYPE (GtkObjectExpression, |
1005 | gtk_object_expression, |
1006 | >k_object_expression_info) |
1007 | |
1008 | /** |
1009 | * gtk_object_expression_new: (constructor) |
1010 | * @object: (transfer none): object to watch |
1011 | * |
1012 | * Creates an expression evaluating to the given `object` with a weak reference. |
1013 | * |
1014 | * Once the `object` is disposed, it will fail to evaluate. |
1015 | * |
1016 | * This expression is meant to break reference cycles. |
1017 | * |
1018 | * If you want to keep a reference to `object`, use [ctor@Gtk.ConstantExpression.new]. |
1019 | * |
1020 | * Returns: (type GtkObjectExpression) (transfer full): a new `GtkExpression` |
1021 | **/ |
1022 | GtkExpression * |
1023 | gtk_object_expression_new (GObject *object) |
1024 | { |
1025 | GtkExpression *result; |
1026 | GtkObjectExpression *self; |
1027 | |
1028 | g_return_val_if_fail (G_IS_OBJECT (object), NULL); |
1029 | |
1030 | result = gtk_expression_alloc (GTK_TYPE_OBJECT_EXPRESSION, G_OBJECT_TYPE (object)); |
1031 | self = (GtkObjectExpression *) result; |
1032 | |
1033 | self->object = object; |
1034 | g_object_weak_ref (object, notify: gtk_object_expression_weak_ref_cb, data: self); |
1035 | |
1036 | return result; |
1037 | } |
1038 | |
1039 | /** |
1040 | * gtk_object_expression_get_object: |
1041 | * @expression: (type GtkObjectExpression): an object `GtkExpression` |
1042 | * |
1043 | * Gets the object that the expression evaluates to. |
1044 | * |
1045 | * Returns: (transfer none) (nullable): the object, or `NULL` |
1046 | */ |
1047 | GObject * |
1048 | gtk_object_expression_get_object (GtkExpression *expression) |
1049 | { |
1050 | GtkObjectExpression *self = (GtkObjectExpression *) expression; |
1051 | |
1052 | g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (expression, GTK_TYPE_OBJECT_EXPRESSION), NULL); |
1053 | |
1054 | return self->object; |
1055 | } |
1056 | |
1057 | /* }}} */ |
1058 | |
1059 | /* {{{ GtkPropertyExpression */ |
1060 | |
1061 | /** |
1062 | * GtkPropertyExpression: |
1063 | * |
1064 | * A `GObject` property value in a `GtkExpression`. |
1065 | */ |
1066 | struct _GtkPropertyExpression |
1067 | { |
1068 | GtkExpression parent; |
1069 | |
1070 | GtkExpression *expr; |
1071 | |
1072 | GParamSpec *pspec; |
1073 | }; |
1074 | |
1075 | static void |
1076 | gtk_property_expression_finalize (GtkExpression *expr) |
1077 | { |
1078 | GtkPropertyExpression *self = (GtkPropertyExpression *) expr; |
1079 | |
1080 | g_clear_pointer (&self->expr, gtk_expression_unref); |
1081 | |
1082 | GTK_EXPRESSION_SUPER (expr)->finalize (expr); |
1083 | } |
1084 | |
1085 | static gboolean |
1086 | gtk_property_expression_is_static (GtkExpression *expr) |
1087 | { |
1088 | return FALSE; |
1089 | } |
1090 | |
1091 | static GObject * |
1092 | gtk_property_expression_get_object (GtkPropertyExpression *self, |
1093 | gpointer this) |
1094 | { |
1095 | GValue expr_value = G_VALUE_INIT; |
1096 | GObject *object; |
1097 | |
1098 | if (self->expr == NULL) |
1099 | { |
1100 | if (this) |
1101 | return g_object_ref (this); |
1102 | else |
1103 | return NULL; |
1104 | } |
1105 | |
1106 | if (!gtk_expression_evaluate (self: self->expr, this_: this, value: &expr_value)) |
1107 | return NULL; |
1108 | |
1109 | if (!G_VALUE_HOLDS_OBJECT (&expr_value)) |
1110 | { |
1111 | g_value_unset (value: &expr_value); |
1112 | return NULL; |
1113 | } |
1114 | |
1115 | object = g_value_dup_object (value: &expr_value); |
1116 | g_value_unset (value: &expr_value); |
1117 | if (object == NULL) |
1118 | return NULL; |
1119 | |
1120 | if (!G_TYPE_CHECK_INSTANCE_TYPE (object, self->pspec->owner_type)) |
1121 | { |
1122 | g_object_unref (object); |
1123 | return NULL; |
1124 | } |
1125 | |
1126 | return object; |
1127 | } |
1128 | |
1129 | static gboolean |
1130 | gtk_property_expression_evaluate (GtkExpression *expr, |
1131 | gpointer this, |
1132 | GValue *value) |
1133 | { |
1134 | GtkPropertyExpression *self = (GtkPropertyExpression *) expr; |
1135 | GObject *object; |
1136 | |
1137 | object = gtk_property_expression_get_object (self, this); |
1138 | if (object == NULL) |
1139 | return FALSE; |
1140 | |
1141 | g_object_get_property (object, property_name: self->pspec->name, value); |
1142 | g_object_unref (object); |
1143 | return TRUE; |
1144 | } |
1145 | |
1146 | typedef struct _GtkPropertyExpressionWatch GtkPropertyExpressionWatch; |
1147 | |
1148 | struct _GtkPropertyExpressionWatch |
1149 | { |
1150 | GtkExpressionNotify notify; |
1151 | gpointer user_data; |
1152 | |
1153 | GtkPropertyExpression *expr; |
1154 | gpointer this; |
1155 | GClosure *closure; |
1156 | guchar sub[0]; |
1157 | }; |
1158 | |
1159 | static void |
1160 | gtk_property_expression_watch_destroy_closure (GtkPropertyExpressionWatch *pwatch) |
1161 | { |
1162 | if (pwatch->closure == NULL) |
1163 | return; |
1164 | |
1165 | g_closure_invalidate (closure: pwatch->closure); |
1166 | g_closure_unref (closure: pwatch->closure); |
1167 | pwatch->closure = NULL; |
1168 | } |
1169 | |
1170 | static void |
1171 | gtk_property_expression_watch_notify_cb (GObject *object, |
1172 | GParamSpec *pspec, |
1173 | GtkPropertyExpressionWatch *pwatch) |
1174 | { |
1175 | pwatch->notify (pwatch->user_data); |
1176 | } |
1177 | |
1178 | static void |
1179 | gtk_property_expression_watch_create_closure (GtkPropertyExpressionWatch *pwatch) |
1180 | { |
1181 | GObject *object; |
1182 | |
1183 | object = gtk_property_expression_get_object (self: pwatch->expr, this: pwatch->this); |
1184 | if (object == NULL) |
1185 | return; |
1186 | |
1187 | pwatch->closure = g_cclosure_new (G_CALLBACK (gtk_property_expression_watch_notify_cb), user_data: pwatch, NULL); |
1188 | if (!g_signal_connect_closure_by_id (instance: object, |
1189 | signal_id: g_signal_lookup (name: "notify" , G_OBJECT_TYPE (object)), |
1190 | detail: g_quark_from_string (string: pwatch->expr->pspec->name), |
1191 | closure: g_closure_ref (closure: pwatch->closure), |
1192 | FALSE)) |
1193 | { |
1194 | g_assert_not_reached (); |
1195 | } |
1196 | |
1197 | g_object_unref (object); |
1198 | } |
1199 | |
1200 | static void |
1201 | gtk_property_expression_watch_expr_notify_cb (gpointer data) |
1202 | { |
1203 | GtkPropertyExpressionWatch *pwatch = data; |
1204 | |
1205 | gtk_property_expression_watch_destroy_closure (pwatch); |
1206 | gtk_property_expression_watch_create_closure (pwatch); |
1207 | pwatch->notify (pwatch->user_data); |
1208 | } |
1209 | |
1210 | static gsize |
1211 | gtk_property_expression_watch_size (GtkExpression *expr) |
1212 | { |
1213 | GtkPropertyExpression *self = (GtkPropertyExpression *) expr; |
1214 | gsize result; |
1215 | |
1216 | result = sizeof (GtkPropertyExpressionWatch); |
1217 | if (self->expr) |
1218 | result += gtk_expression_watch_size (self: self->expr); |
1219 | |
1220 | return result; |
1221 | } |
1222 | |
1223 | static void |
1224 | gtk_property_expression_watch (GtkExpression *expr, |
1225 | GtkExpressionSubWatch *watch, |
1226 | gpointer this_, |
1227 | GtkExpressionNotify notify, |
1228 | gpointer user_data) |
1229 | { |
1230 | GtkPropertyExpressionWatch *pwatch = (GtkPropertyExpressionWatch *) watch; |
1231 | GtkPropertyExpression *self = (GtkPropertyExpression *) expr; |
1232 | |
1233 | pwatch->notify = notify; |
1234 | pwatch->user_data = user_data; |
1235 | pwatch->expr = self; |
1236 | pwatch->this = this_; |
1237 | if (self->expr && !gtk_expression_is_static (self: self->expr)) |
1238 | { |
1239 | gtk_expression_subwatch_init (self: self->expr, |
1240 | watch: (GtkExpressionSubWatch *) pwatch->sub, |
1241 | this: this_, |
1242 | notify: gtk_property_expression_watch_expr_notify_cb, |
1243 | user_data: pwatch); |
1244 | } |
1245 | |
1246 | gtk_property_expression_watch_create_closure (pwatch); |
1247 | } |
1248 | |
1249 | static void |
1250 | gtk_property_expression_unwatch (GtkExpression *expr, |
1251 | GtkExpressionSubWatch *watch) |
1252 | { |
1253 | GtkPropertyExpressionWatch *pwatch = (GtkPropertyExpressionWatch *) watch; |
1254 | GtkPropertyExpression *self = (GtkPropertyExpression *) expr; |
1255 | |
1256 | gtk_property_expression_watch_destroy_closure (pwatch); |
1257 | |
1258 | if (self->expr && !gtk_expression_is_static (self: self->expr)) |
1259 | gtk_expression_subwatch_finish (self: self->expr, watch: (GtkExpressionSubWatch *) pwatch->sub); |
1260 | } |
1261 | |
1262 | static const GtkExpressionTypeInfo gtk_property_expression_info = |
1263 | { |
1264 | sizeof (GtkPropertyExpression), |
1265 | NULL, |
1266 | gtk_property_expression_finalize, |
1267 | gtk_property_expression_is_static, |
1268 | gtk_property_expression_evaluate, |
1269 | gtk_property_expression_watch_size, |
1270 | gtk_property_expression_watch, |
1271 | gtk_property_expression_unwatch |
1272 | }; |
1273 | |
1274 | GTK_DEFINE_EXPRESSION_TYPE (GtkPropertyExpression, |
1275 | gtk_property_expression, |
1276 | >k_property_expression_info) |
1277 | |
1278 | /** |
1279 | * gtk_property_expression_new: (constructor) |
1280 | * @this_type: The type to expect for the this type |
1281 | * @expression: (nullable) (transfer full): Expression to |
1282 | * evaluate to get the object to query or `NULL` to |
1283 | * query the `this` object |
1284 | * @property_name: name of the property |
1285 | * |
1286 | * Creates an expression that looks up a property. |
1287 | * |
1288 | * The object to use is found by evaluating the `expression`, |
1289 | * or using the `this` argument when `expression` is `NULL`. |
1290 | * |
1291 | * If the resulting object conforms to `this_type`, its property named |
1292 | * `property_name` will be queried. Otherwise, this expression's |
1293 | * evaluation will fail. |
1294 | * |
1295 | * The given `this_type` must have a property with `property_name`. |
1296 | * |
1297 | * Returns: (type GtkPropertyExpression) (transfer full): a new `GtkExpression` |
1298 | **/ |
1299 | GtkExpression * |
1300 | gtk_property_expression_new (GType this_type, |
1301 | GtkExpression *expression, |
1302 | const char *property_name) |
1303 | { |
1304 | GParamSpec *pspec; |
1305 | |
1306 | if (g_type_fundamental (type_id: this_type) == G_TYPE_OBJECT) |
1307 | { |
1308 | GObjectClass *class = g_type_class_ref (type: this_type); |
1309 | pspec = g_object_class_find_property (oclass: class, property_name); |
1310 | g_type_class_unref (g_class: class); |
1311 | } |
1312 | else if (g_type_fundamental (type_id: this_type) == G_TYPE_INTERFACE) |
1313 | { |
1314 | GTypeInterface *iface = g_type_default_interface_ref (g_type: this_type); |
1315 | pspec = g_object_interface_find_property (g_iface: iface, property_name); |
1316 | g_type_default_interface_unref (g_iface: iface); |
1317 | } |
1318 | else |
1319 | { |
1320 | g_critical ("Type `%s` does not support properties" , g_type_name (this_type)); |
1321 | return NULL; |
1322 | } |
1323 | |
1324 | if (pspec == NULL) |
1325 | { |
1326 | g_critical ("Type `%s` does not have a property named `%s`" , g_type_name (this_type), property_name); |
1327 | return NULL; |
1328 | } |
1329 | |
1330 | return gtk_property_expression_new_for_pspec (expression, pspec); |
1331 | } |
1332 | |
1333 | /** |
1334 | * gtk_property_expression_new_for_pspec: (constructor) |
1335 | * @expression: (nullable) (transfer full): Expression to |
1336 | * evaluate to get the object to query or `NULL` to |
1337 | * query the `this` object |
1338 | * @pspec: the `GParamSpec` for the property to query |
1339 | * |
1340 | * Creates an expression that looks up a property. |
1341 | * |
1342 | * The object to use is found by evaluating the `expression`, |
1343 | * or using the `this` argument when `expression` is `NULL`. |
1344 | * |
1345 | * If the resulting object conforms to `this_type`, its |
1346 | * property specified by `pspec` will be queried. |
1347 | * Otherwise, this expression's evaluation will fail. |
1348 | * |
1349 | * Returns: (type GtkPropertyExpression) (transfer full): a new `GtkExpression` |
1350 | **/ |
1351 | GtkExpression * |
1352 | gtk_property_expression_new_for_pspec (GtkExpression *expression, |
1353 | GParamSpec *pspec) |
1354 | { |
1355 | GtkExpression *result; |
1356 | GtkPropertyExpression *self; |
1357 | |
1358 | result = gtk_expression_alloc (GTK_TYPE_PROPERTY_EXPRESSION, value_type: pspec->value_type); |
1359 | self = (GtkPropertyExpression *) result; |
1360 | |
1361 | self->pspec = pspec; |
1362 | self->expr = expression; |
1363 | |
1364 | return result; |
1365 | } |
1366 | |
1367 | /** |
1368 | * gtk_property_expression_get_expression: |
1369 | * @expression: (type GtkPropertyExpression): a property `GtkExpression` |
1370 | * |
1371 | * Gets the expression specifying the object of |
1372 | * a property expression. |
1373 | * |
1374 | * Returns: (transfer none) (nullable): the object expression |
1375 | */ |
1376 | GtkExpression * |
1377 | gtk_property_expression_get_expression (GtkExpression *expression) |
1378 | { |
1379 | GtkPropertyExpression *self = (GtkPropertyExpression *) expression; |
1380 | |
1381 | g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (expression, GTK_TYPE_PROPERTY_EXPRESSION), NULL); |
1382 | |
1383 | return self->expr; |
1384 | } |
1385 | |
1386 | /** |
1387 | * gtk_property_expression_get_pspec: |
1388 | * @expression: (type GtkPropertyExpression): a property `GtkExpression` |
1389 | * |
1390 | * Gets the `GParamSpec` specifying the property of |
1391 | * a property expression. |
1392 | * |
1393 | * Returns: (transfer none): the `GParamSpec` for the property |
1394 | */ |
1395 | GParamSpec * |
1396 | gtk_property_expression_get_pspec (GtkExpression *expression) |
1397 | { |
1398 | GtkPropertyExpression *self = (GtkPropertyExpression *) expression; |
1399 | |
1400 | g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (expression, GTK_TYPE_PROPERTY_EXPRESSION), NULL); |
1401 | |
1402 | return self->pspec; |
1403 | } |
1404 | |
1405 | /* }}} */ |
1406 | |
1407 | /* {{{ GtkClosureExpression */ |
1408 | |
1409 | /** |
1410 | * GtkClosureExpression: |
1411 | * |
1412 | * An expression using a custom `GClosure` to compute the value from |
1413 | * its parameters. |
1414 | */ |
1415 | struct _GtkClosureExpression |
1416 | { |
1417 | GtkExpression parent; |
1418 | |
1419 | GClosure *closure; |
1420 | guint n_params; |
1421 | GtkExpression **params; |
1422 | }; |
1423 | |
1424 | static void |
1425 | gtk_closure_expression_finalize (GtkExpression *expr) |
1426 | { |
1427 | GtkClosureExpression *self = (GtkClosureExpression *) expr; |
1428 | guint i; |
1429 | |
1430 | for (i = 0; i < self->n_params; i++) |
1431 | { |
1432 | gtk_expression_unref (self: self->params[i]); |
1433 | } |
1434 | g_free (mem: self->params); |
1435 | |
1436 | g_closure_unref (closure: self->closure); |
1437 | |
1438 | GTK_EXPRESSION_SUPER (expr)->finalize (expr); |
1439 | } |
1440 | |
1441 | static gboolean |
1442 | gtk_closure_expression_is_static (GtkExpression *expr) |
1443 | { |
1444 | GtkClosureExpression *self = (GtkClosureExpression *) expr; |
1445 | guint i; |
1446 | |
1447 | for (i = 0; i < self->n_params; i++) |
1448 | { |
1449 | if (!gtk_expression_is_static (self: self->params[i])) |
1450 | return FALSE; |
1451 | } |
1452 | |
1453 | return TRUE; |
1454 | } |
1455 | |
1456 | static gboolean |
1457 | gtk_closure_expression_evaluate (GtkExpression *expr, |
1458 | gpointer this, |
1459 | GValue *value) |
1460 | { |
1461 | GtkClosureExpression *self = (GtkClosureExpression *) expr; |
1462 | GValue *instance_and_params; |
1463 | gboolean result = TRUE; |
1464 | guint i; |
1465 | |
1466 | instance_and_params = g_alloca (sizeof (GValue) * (self->n_params + 1)); |
1467 | memset (s: instance_and_params, c: 0, n: sizeof (GValue) * (self->n_params + 1)); |
1468 | |
1469 | for (i = 0; i < self->n_params; i++) |
1470 | { |
1471 | if (!gtk_expression_evaluate (self: self->params[i], this_: this, value: &instance_and_params[i + 1])) |
1472 | { |
1473 | result = FALSE; |
1474 | goto out; |
1475 | } |
1476 | } |
1477 | if (this) |
1478 | g_value_init_from_instance (value: instance_and_params, instance: this); |
1479 | else |
1480 | g_value_init (value: instance_and_params, G_TYPE_OBJECT); |
1481 | |
1482 | g_value_init (value, g_type: expr->value_type); |
1483 | g_closure_invoke (closure: self->closure, |
1484 | return_value: value, |
1485 | n_param_values: self->n_params + 1, |
1486 | param_values: instance_and_params, |
1487 | NULL); |
1488 | |
1489 | out: |
1490 | for (i = 0; i < self->n_params + 1; i++) |
1491 | g_value_unset (value: &instance_and_params[i]); |
1492 | |
1493 | return result; |
1494 | } |
1495 | |
1496 | typedef struct _GtkClosureExpressionWatch GtkClosureExpressionWatch; |
1497 | struct _GtkClosureExpressionWatch |
1498 | { |
1499 | GtkExpressionNotify notify; |
1500 | gpointer user_data; |
1501 | |
1502 | guchar sub[0]; |
1503 | }; |
1504 | |
1505 | static void |
1506 | gtk_closure_expression_watch_notify_cb (gpointer data) |
1507 | { |
1508 | GtkClosureExpressionWatch *cwatch = data; |
1509 | |
1510 | cwatch->notify (cwatch->user_data); |
1511 | } |
1512 | |
1513 | static gsize |
1514 | gtk_closure_expression_watch_size (GtkExpression *expr) |
1515 | { |
1516 | GtkClosureExpression *self = (GtkClosureExpression *) expr; |
1517 | gsize size; |
1518 | guint i; |
1519 | |
1520 | size = sizeof (GtkClosureExpressionWatch); |
1521 | |
1522 | for (i = 0; i < self->n_params; i++) |
1523 | { |
1524 | if (gtk_expression_is_static (self: self->params[i])) |
1525 | continue; |
1526 | |
1527 | size += gtk_expression_watch_size (self: self->params[i]); |
1528 | } |
1529 | |
1530 | return size; |
1531 | } |
1532 | |
1533 | static void |
1534 | gtk_closure_expression_watch (GtkExpression *expr, |
1535 | GtkExpressionSubWatch *watch, |
1536 | gpointer this_, |
1537 | GtkExpressionNotify notify, |
1538 | gpointer user_data) |
1539 | { |
1540 | GtkClosureExpressionWatch *cwatch = (GtkClosureExpressionWatch *) watch; |
1541 | GtkClosureExpression *self = (GtkClosureExpression *) expr; |
1542 | guchar *sub; |
1543 | guint i; |
1544 | |
1545 | cwatch->notify = notify; |
1546 | cwatch->user_data = user_data; |
1547 | |
1548 | sub = cwatch->sub; |
1549 | for (i = 0; i < self->n_params; i++) |
1550 | { |
1551 | if (gtk_expression_is_static (self: self->params[i])) |
1552 | continue; |
1553 | |
1554 | gtk_expression_subwatch_init (self: self->params[i], |
1555 | watch: (GtkExpressionSubWatch *) sub, |
1556 | this: this_, |
1557 | notify: gtk_closure_expression_watch_notify_cb, |
1558 | user_data: watch); |
1559 | sub += gtk_expression_watch_size (self: self->params[i]); |
1560 | } |
1561 | } |
1562 | |
1563 | static void |
1564 | gtk_closure_expression_unwatch (GtkExpression *expr, |
1565 | GtkExpressionSubWatch *watch) |
1566 | { |
1567 | GtkClosureExpressionWatch *cwatch = (GtkClosureExpressionWatch *) watch; |
1568 | GtkClosureExpression *self = (GtkClosureExpression *) expr; |
1569 | guchar *sub; |
1570 | guint i; |
1571 | |
1572 | sub = cwatch->sub; |
1573 | for (i = 0; i < self->n_params; i++) |
1574 | { |
1575 | if (gtk_expression_is_static (self: self->params[i])) |
1576 | continue; |
1577 | |
1578 | gtk_expression_subwatch_finish (self: self->params[i], |
1579 | watch: (GtkExpressionSubWatch *) sub); |
1580 | sub += gtk_expression_watch_size (self: self->params[i]); |
1581 | } |
1582 | } |
1583 | |
1584 | static const GtkExpressionTypeInfo gtk_closure_expression_info = |
1585 | { |
1586 | sizeof (GtkClosureExpression), |
1587 | NULL, |
1588 | gtk_closure_expression_finalize, |
1589 | gtk_closure_expression_is_static, |
1590 | gtk_closure_expression_evaluate, |
1591 | gtk_closure_expression_watch_size, |
1592 | gtk_closure_expression_watch, |
1593 | gtk_closure_expression_unwatch |
1594 | }; |
1595 | |
1596 | GTK_DEFINE_EXPRESSION_TYPE (GtkClosureExpression, |
1597 | gtk_closure_expression, |
1598 | >k_closure_expression_info) |
1599 | |
1600 | /** |
1601 | * gtk_closure_expression_new: (constructor) |
1602 | * @value_type: the type of the value that this expression evaluates to |
1603 | * @closure: closure to call when evaluating this expression. If closure is floating, it is adopted |
1604 | * @n_params: the number of params needed for evaluating `closure` |
1605 | * @params: (nullable) (array length=n_params) (transfer full): expressions for each parameter |
1606 | * |
1607 | * Creates a `GtkExpression` that calls `closure` when it is evaluated. |
1608 | * |
1609 | * `closure` is called with the `this` object and the results of evaluating |
1610 | * the `params` expressions. |
1611 | * |
1612 | * Returns: (transfer full) (type GtkClosureExpression): a new `GtkExpression` |
1613 | */ |
1614 | GtkExpression * |
1615 | gtk_closure_expression_new (GType value_type, |
1616 | GClosure *closure, |
1617 | guint n_params, |
1618 | GtkExpression **params) |
1619 | { |
1620 | GtkExpression *result; |
1621 | GtkClosureExpression *self; |
1622 | guint i; |
1623 | |
1624 | g_return_val_if_fail (closure != NULL, NULL); |
1625 | g_return_val_if_fail (n_params == 0 || params != NULL, NULL); |
1626 | |
1627 | result = gtk_expression_alloc (GTK_TYPE_CLOSURE_EXPRESSION, value_type); |
1628 | self = (GtkClosureExpression *) result; |
1629 | |
1630 | self->closure = g_closure_ref (closure); |
1631 | g_closure_sink (closure); |
1632 | if (G_CLOSURE_NEEDS_MARSHAL (closure)) |
1633 | g_closure_set_marshal (closure, marshal: g_cclosure_marshal_generic); |
1634 | |
1635 | self->n_params = n_params; |
1636 | self->params = g_new (GtkExpression *, n_params); |
1637 | for (i = 0; i < n_params; i++) |
1638 | self->params[i] = params[i]; |
1639 | |
1640 | return result; |
1641 | } |
1642 | |
1643 | /* }}} */ |
1644 | |
1645 | /* {{{ GtkCClosureExpression */ |
1646 | |
1647 | /** |
1648 | * GtkCClosureExpression: |
1649 | * |
1650 | * A variant of `GtkClosureExpression` using a C closure. |
1651 | */ |
1652 | struct _GtkCClosureExpression |
1653 | { |
1654 | GtkClosureExpression parent; |
1655 | }; |
1656 | |
1657 | static const GtkExpressionTypeInfo gtk_cclosure_expression_info = |
1658 | { |
1659 | sizeof (GtkClosureExpression), |
1660 | NULL, |
1661 | gtk_closure_expression_finalize, |
1662 | gtk_closure_expression_is_static, |
1663 | gtk_closure_expression_evaluate, |
1664 | gtk_closure_expression_watch_size, |
1665 | gtk_closure_expression_watch, |
1666 | gtk_closure_expression_unwatch |
1667 | }; |
1668 | |
1669 | GTK_DEFINE_EXPRESSION_TYPE (GtkCClosureExpression, |
1670 | gtk_cclosure_expression, |
1671 | >k_cclosure_expression_info) |
1672 | |
1673 | /** |
1674 | * gtk_cclosure_expression_new: (constructor) |
1675 | * @value_type: the type of the value that this expression evaluates to |
1676 | * @marshal: (scope call) (nullable): marshaller used for creating a closure |
1677 | * @n_params: the number of params needed for evaluating @closure |
1678 | * @params: (array length=n_params) (transfer full): expressions for each parameter |
1679 | * @callback_func: (scope notified) (closure user_data) (destroy user_destroy): callback used for creating a closure |
1680 | * @user_data: (nullable): user data used for creating a closure |
1681 | * @user_destroy: (nullable): destroy notify for @user_data |
1682 | * |
1683 | * Creates a `GtkExpression` that calls `callback_func` when it is evaluated. |
1684 | * |
1685 | * This function is a variant of [ctor@Gtk.ClosureExpression.new] that |
1686 | * creates a `GClosure` by calling g_cclosure_new() with the given |
1687 | * `callback_func`, `user_data` and `user_destroy`. |
1688 | * |
1689 | * Returns: (transfer full) (type GtkCClosureExpression): a new `GtkExpression` |
1690 | */ |
1691 | GtkExpression * |
1692 | gtk_cclosure_expression_new (GType value_type, |
1693 | GClosureMarshal marshal, |
1694 | guint n_params, |
1695 | GtkExpression **params, |
1696 | GCallback callback_func, |
1697 | gpointer user_data, |
1698 | GClosureNotify user_destroy) |
1699 | { |
1700 | GtkExpression *result; |
1701 | GtkClosureExpression *self; |
1702 | GClosure *closure; |
1703 | guint i; |
1704 | |
1705 | g_return_val_if_fail (callback_func != NULL, NULL); |
1706 | g_return_val_if_fail (n_params == 0 || params != NULL, NULL); |
1707 | |
1708 | result = gtk_expression_alloc (GTK_TYPE_CCLOSURE_EXPRESSION, value_type); |
1709 | self = (GtkClosureExpression *) result; |
1710 | |
1711 | closure = g_cclosure_new (callback_func, user_data, destroy_data: user_destroy); |
1712 | if (marshal) |
1713 | g_closure_set_marshal (closure, marshal); |
1714 | |
1715 | self->closure = g_closure_ref (closure); |
1716 | g_closure_sink (closure); |
1717 | if (G_CLOSURE_NEEDS_MARSHAL (closure)) |
1718 | g_closure_set_marshal (closure, marshal: g_cclosure_marshal_generic); |
1719 | |
1720 | self->n_params = n_params; |
1721 | self->params = g_new (GtkExpression *, n_params); |
1722 | for (i = 0; i < n_params; i++) |
1723 | self->params[i] = params[i]; |
1724 | |
1725 | return result; |
1726 | } |
1727 | |
1728 | /* }}} */ |
1729 | |
1730 | /* {{{ GtkExpression public API */ |
1731 | |
1732 | /** |
1733 | * gtk_expression_ref: |
1734 | * @self: a `GtkExpression` |
1735 | * |
1736 | * Acquires a reference on the given `GtkExpression`. |
1737 | * |
1738 | * Returns: (transfer full): the `GtkExpression` with an additional reference |
1739 | */ |
1740 | GtkExpression * |
1741 | gtk_expression_ref (GtkExpression *self) |
1742 | { |
1743 | g_return_val_if_fail (GTK_IS_EXPRESSION (self), NULL); |
1744 | |
1745 | g_atomic_ref_count_inc (arc: &self->ref_count); |
1746 | |
1747 | return self; |
1748 | } |
1749 | |
1750 | /** |
1751 | * gtk_expression_unref: |
1752 | * @self: (transfer full): a `GtkExpression` |
1753 | * |
1754 | * Releases a reference on the given `GtkExpression`. |
1755 | * |
1756 | * If the reference was the last, the resources associated to the `self` are |
1757 | * freed. |
1758 | */ |
1759 | void |
1760 | gtk_expression_unref (GtkExpression *self) |
1761 | { |
1762 | g_return_if_fail (GTK_IS_EXPRESSION (self)); |
1763 | |
1764 | if (g_atomic_ref_count_dec (arc: &self->ref_count)) |
1765 | GTK_EXPRESSION_GET_CLASS (self)->finalize (self); |
1766 | } |
1767 | |
1768 | /** |
1769 | * gtk_expression_get_value_type: |
1770 | * @self: a `GtkExpression` |
1771 | * |
1772 | * Gets the `GType` that this expression evaluates to. |
1773 | * |
1774 | * This type is constant and will not change over the lifetime |
1775 | * of this expression. |
1776 | * |
1777 | * Returns: The type returned from [method@Gtk.Expression.evaluate] |
1778 | */ |
1779 | GType |
1780 | gtk_expression_get_value_type (GtkExpression *self) |
1781 | { |
1782 | g_return_val_if_fail (GTK_IS_EXPRESSION (self), G_TYPE_INVALID); |
1783 | |
1784 | return self->value_type; |
1785 | } |
1786 | |
1787 | /** |
1788 | * gtk_expression_evaluate: |
1789 | * @self: a `GtkExpression` |
1790 | * @this_: (transfer none) (type GObject) (nullable): the this argument for the evaluation |
1791 | * @value: an empty `GValue` |
1792 | * |
1793 | * Evaluates the given expression and on success stores the result |
1794 | * in @value. |
1795 | * |
1796 | * The `GType` of `value` will be the type given by |
1797 | * [method@Gtk.Expression.get_value_type]. |
1798 | * |
1799 | * It is possible that expressions cannot be evaluated - for example |
1800 | * when the expression references objects that have been destroyed or |
1801 | * set to `NULL`. In that case `value` will remain empty and `FALSE` |
1802 | * will be returned. |
1803 | * |
1804 | * Returns: `TRUE` if the expression could be evaluated |
1805 | **/ |
1806 | gboolean |
1807 | gtk_expression_evaluate (GtkExpression *self, |
1808 | gpointer this_, |
1809 | GValue *value) |
1810 | { |
1811 | g_return_val_if_fail (GTK_IS_EXPRESSION (self), FALSE); |
1812 | g_return_val_if_fail (this_ == NULL || G_IS_OBJECT (this_), FALSE); |
1813 | g_return_val_if_fail (value != NULL, FALSE); |
1814 | |
1815 | return GTK_EXPRESSION_GET_CLASS (self)->evaluate (self, this_, value); |
1816 | } |
1817 | |
1818 | /** |
1819 | * gtk_expression_is_static: |
1820 | * @self: a `GtkExpression` |
1821 | * |
1822 | * Checks if the expression is static. |
1823 | * |
1824 | * A static expression will never change its result when |
1825 | * [method@Gtk.Expression.evaluate] is called on it with the same arguments. |
1826 | * |
1827 | * That means a call to [method@Gtk.Expression.watch] is not necessary because |
1828 | * it will never trigger a notify. |
1829 | * |
1830 | * Returns: `TRUE` if the expression is static |
1831 | **/ |
1832 | gboolean |
1833 | gtk_expression_is_static (GtkExpression *self) |
1834 | { |
1835 | g_return_val_if_fail (GTK_IS_EXPRESSION (self), FALSE); |
1836 | |
1837 | return GTK_EXPRESSION_GET_CLASS (self)->is_static (self); |
1838 | } |
1839 | |
1840 | static gboolean |
1841 | gtk_expression_watch_is_watching (GtkExpressionWatch *watch) |
1842 | { |
1843 | return watch->expression != NULL; |
1844 | } |
1845 | |
1846 | static void |
1847 | gtk_expression_watch_this_cb (gpointer data, |
1848 | GObject *this) |
1849 | { |
1850 | GtkExpressionWatch *watch = data; |
1851 | |
1852 | watch->this = NULL; |
1853 | |
1854 | watch->notify (watch->user_data); |
1855 | gtk_expression_watch_unwatch (watch); |
1856 | } |
1857 | |
1858 | static void |
1859 | gtk_expression_watch_cb (gpointer data) |
1860 | { |
1861 | GtkExpressionWatch *watch = data; |
1862 | |
1863 | if (!gtk_expression_watch_is_watching (watch)) |
1864 | return; |
1865 | |
1866 | watch->notify (watch->user_data); |
1867 | } |
1868 | |
1869 | /** |
1870 | * gtk_expression_watch: |
1871 | * @self: a `GtkExpression` |
1872 | * @this_: (transfer none) (type GObject) (nullable): the `this` argument to |
1873 | * watch |
1874 | * @notify: (closure user_data): callback to invoke when the expression changes |
1875 | * @user_data: user data to pass to the `notify` callback |
1876 | * @user_destroy: destroy notify for `user_data` |
1877 | * |
1878 | * Watch the given `expression` for changes. |
1879 | * |
1880 | * The @notify function will be called whenever the evaluation of `self` |
1881 | * may have changed. |
1882 | * |
1883 | * GTK cannot guarantee that the evaluation did indeed change when the @notify |
1884 | * gets invoked, but it guarantees the opposite: When it did in fact change, |
1885 | * the @notify will be invoked. |
1886 | * |
1887 | * Returns: (transfer none): The newly installed watch. Note that the only |
1888 | * reference held to the watch will be released when the watch is unwatched |
1889 | * which can happen automatically, and not just via |
1890 | * [method@Gtk.ExpressionWatch.unwatch]. You should call [method@Gtk.ExpressionWatch.ref] |
1891 | * if you want to keep the watch around. |
1892 | **/ |
1893 | GtkExpressionWatch * |
1894 | gtk_expression_watch (GtkExpression *self, |
1895 | gpointer this_, |
1896 | GtkExpressionNotify notify, |
1897 | gpointer user_data, |
1898 | GDestroyNotify user_destroy) |
1899 | { |
1900 | GtkExpressionWatch *watch; |
1901 | |
1902 | g_return_val_if_fail (self != NULL, NULL); |
1903 | g_return_val_if_fail (this_ == NULL || G_IS_OBJECT (this_), NULL); |
1904 | g_return_val_if_fail (notify != NULL, NULL); |
1905 | |
1906 | watch = g_atomic_rc_box_alloc0 (block_size: sizeof (GtkExpressionWatch) + gtk_expression_watch_size (self)); |
1907 | |
1908 | watch->expression = gtk_expression_ref (self); |
1909 | watch->this = this_; |
1910 | if (this_) |
1911 | g_object_weak_ref (object: this_, notify: gtk_expression_watch_this_cb, data: watch); |
1912 | watch->notify = notify; |
1913 | watch->user_data = user_data; |
1914 | watch->user_destroy = user_destroy; |
1915 | |
1916 | gtk_expression_subwatch_init (self, |
1917 | watch: (GtkExpressionSubWatch *) watch->sub, |
1918 | this: this_, |
1919 | notify: gtk_expression_watch_cb, |
1920 | user_data: watch); |
1921 | |
1922 | return watch; |
1923 | } |
1924 | |
1925 | /** |
1926 | * gtk_expression_watch_ref: |
1927 | * @watch: a `GtkExpressionWatch` |
1928 | * |
1929 | * Acquires a reference on the given `GtkExpressionWatch`. |
1930 | * |
1931 | * Returns: (transfer full): the `GtkExpressionWatch` with an additional reference |
1932 | */ |
1933 | GtkExpressionWatch * |
1934 | gtk_expression_watch_ref (GtkExpressionWatch *watch) |
1935 | { |
1936 | return g_atomic_rc_box_acquire (watch); |
1937 | } |
1938 | |
1939 | static void |
1940 | gtk_expression_watch_finalize (gpointer data) |
1941 | { |
1942 | GtkExpressionWatch *watch G_GNUC_UNUSED = data; |
1943 | |
1944 | g_assert (!gtk_expression_watch_is_watching (data)); |
1945 | } |
1946 | |
1947 | /** |
1948 | * gtk_expression_watch_unref: |
1949 | * @watch: (transfer full): a `GtkExpressionWatch` |
1950 | * |
1951 | * Releases a reference on the given `GtkExpressionWatch`. |
1952 | * |
1953 | * If the reference was the last, the resources associated to `self` are |
1954 | * freed. |
1955 | */ |
1956 | void |
1957 | gtk_expression_watch_unref (GtkExpressionWatch *watch) |
1958 | { |
1959 | g_atomic_rc_box_release_full (mem_block: watch, clear_func: gtk_expression_watch_finalize); |
1960 | } |
1961 | |
1962 | /** |
1963 | * gtk_expression_watch_unwatch: |
1964 | * @watch: (transfer none): watch to release |
1965 | * |
1966 | * Stops watching an expression. |
1967 | * |
1968 | * See [method@Gtk.Expression.watch] for how the watch |
1969 | * was established. |
1970 | */ |
1971 | void |
1972 | gtk_expression_watch_unwatch (GtkExpressionWatch *watch) |
1973 | { |
1974 | if (!gtk_expression_watch_is_watching (watch)) |
1975 | return; |
1976 | |
1977 | gtk_expression_subwatch_finish (self: watch->expression, watch: (GtkExpressionSubWatch *) watch->sub); |
1978 | |
1979 | if (watch->this) |
1980 | g_object_weak_unref (object: watch->this, notify: gtk_expression_watch_this_cb, data: watch); |
1981 | |
1982 | if (watch->user_destroy) |
1983 | watch->user_destroy (watch->user_data); |
1984 | |
1985 | g_clear_pointer (&watch->expression, gtk_expression_unref); |
1986 | |
1987 | gtk_expression_watch_unref (watch); |
1988 | } |
1989 | |
1990 | /** |
1991 | * gtk_expression_watch_evaluate: |
1992 | * @watch: a `GtkExpressionWatch` |
1993 | * @value: an empty `GValue` to be set |
1994 | * |
1995 | * Evaluates the watched expression and on success stores the result |
1996 | * in `value`. |
1997 | * |
1998 | * This is equivalent to calling [method@Gtk.Expression.evaluate] with the |
1999 | * expression and this pointer originally used to create `watch`. |
2000 | * |
2001 | * Returns: `TRUE` if the expression could be evaluated and `value` was set |
2002 | **/ |
2003 | gboolean |
2004 | gtk_expression_watch_evaluate (GtkExpressionWatch *watch, |
2005 | GValue *value) |
2006 | { |
2007 | g_return_val_if_fail (watch != NULL, FALSE); |
2008 | |
2009 | if (!gtk_expression_watch_is_watching (watch)) |
2010 | return FALSE; |
2011 | |
2012 | return gtk_expression_evaluate (self: watch->expression, this_: watch->this, value); |
2013 | } |
2014 | |
2015 | typedef struct { |
2016 | GtkExpressionWatch *watch; |
2017 | GObject *target; |
2018 | GParamSpec *pspec; |
2019 | } GtkExpressionBind; |
2020 | |
2021 | static void |
2022 | invalidate_binds (gpointer unused, |
2023 | GObject *object) |
2024 | { |
2025 | GSList *l, *binds; |
2026 | |
2027 | binds = g_object_get_data (object, key: "gtk-expression-binds" ); |
2028 | for (l = binds; l; l = l->next) |
2029 | { |
2030 | GtkExpressionBind *bind = l->data; |
2031 | |
2032 | /* This guarantees we neither try to update bindings |
2033 | * (which would wreck havoc because the object is |
2034 | * dispose()ing itself) nor try to destroy bindings |
2035 | * anymore, so destruction can be done in free_binds(). |
2036 | */ |
2037 | bind->target = NULL; |
2038 | } |
2039 | } |
2040 | |
2041 | static void |
2042 | free_binds (gpointer data) |
2043 | { |
2044 | GSList *l; |
2045 | |
2046 | for (l = data; l; l = l->next) |
2047 | { |
2048 | GtkExpressionBind *bind = l->data; |
2049 | |
2050 | g_assert (bind->target == NULL); |
2051 | if (bind->watch) |
2052 | gtk_expression_watch_unwatch (watch: bind->watch); |
2053 | g_slice_free (GtkExpressionBind, bind); |
2054 | } |
2055 | g_slist_free (list: data); |
2056 | } |
2057 | |
2058 | static void |
2059 | gtk_expression_bind_free (gpointer data) |
2060 | { |
2061 | GtkExpressionBind *bind = data; |
2062 | |
2063 | if (bind->target) |
2064 | { |
2065 | GSList *binds; |
2066 | binds = g_object_steal_data (object: bind->target, key: "gtk-expression-binds" ); |
2067 | binds = g_slist_remove (list: binds, data: bind); |
2068 | if (binds) |
2069 | g_object_set_data_full (object: bind->target, key: "gtk-expression-binds" , data: binds, destroy: free_binds); |
2070 | else |
2071 | g_object_weak_unref (object: bind->target, notify: invalidate_binds, NULL); |
2072 | |
2073 | g_slice_free (GtkExpressionBind, bind); |
2074 | } |
2075 | else |
2076 | { |
2077 | /* If a bind gets unwatched after invalidate_binds() but |
2078 | * before free_binds(), we end up here. This can happen if |
2079 | * the bind was watching itself or if the target's dispose() |
2080 | * function freed the object that was watched. |
2081 | * We make sure we don't destroy the binding or free_binds() will do |
2082 | * bad stuff, but we clear the watch, so free_binds() won't try to |
2083 | * unwatch() it. |
2084 | */ |
2085 | bind->watch = NULL; |
2086 | } |
2087 | } |
2088 | |
2089 | static void |
2090 | gtk_expression_bind_notify (gpointer data) |
2091 | { |
2092 | GValue value = G_VALUE_INIT; |
2093 | GtkExpressionBind *bind = data; |
2094 | |
2095 | if (bind->target == NULL) |
2096 | return; |
2097 | |
2098 | if (!gtk_expression_watch_evaluate (watch: bind->watch, value: &value)) |
2099 | return; |
2100 | |
2101 | g_object_set_property (object: bind->target, property_name: bind->pspec->name, value: &value); |
2102 | g_value_unset (value: &value); |
2103 | } |
2104 | |
2105 | /** |
2106 | * gtk_expression_bind: |
2107 | * @self: (transfer full): a `GtkExpression` |
2108 | * @target: (transfer none) (type GObject): the target object to bind to |
2109 | * @property: name of the property on `target` to bind to |
2110 | * @this_: (transfer none) (type GObject) (nullable): the this argument for |
2111 | * the evaluation of `self` |
2112 | * |
2113 | * Bind `target`'s property named `property` to `self`. |
2114 | * |
2115 | * The value that `self` evaluates to is set via `g_object_set()` on |
2116 | * `target`. This is repeated whenever `self` changes to ensure that |
2117 | * the object's property stays synchronized with `self`. |
2118 | * |
2119 | * If `self`'s evaluation fails, `target`'s `property` is not updated. |
2120 | * You can ensure that this doesn't happen by using a fallback |
2121 | * expression. |
2122 | * |
2123 | * Note that this function takes ownership of `self`. If you want |
2124 | * to keep it around, you should [method@Gtk.Expression.ref] it beforehand. |
2125 | * |
2126 | * Returns: (transfer none): a `GtkExpressionWatch` |
2127 | **/ |
2128 | GtkExpressionWatch * |
2129 | gtk_expression_bind (GtkExpression *self, |
2130 | gpointer target, |
2131 | const char *property, |
2132 | gpointer this_) |
2133 | { |
2134 | GtkExpressionBind *bind; |
2135 | GParamSpec *pspec; |
2136 | GSList *binds; |
2137 | |
2138 | g_return_val_if_fail (GTK_IS_EXPRESSION (self), NULL); |
2139 | g_return_val_if_fail (G_IS_OBJECT (target), NULL); |
2140 | g_return_val_if_fail (property != NULL, NULL); |
2141 | g_return_val_if_fail (this_ == NULL || G_IS_OBJECT (this_), NULL); |
2142 | |
2143 | pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (target), property_name: property); |
2144 | if (G_UNLIKELY (pspec == NULL)) |
2145 | { |
2146 | g_critical ("%s: Class '%s' has no property named '%s'" , |
2147 | G_STRFUNC, G_OBJECT_TYPE_NAME (target), property); |
2148 | return NULL; |
2149 | } |
2150 | if (G_UNLIKELY ((pspec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) != G_PARAM_WRITABLE)) |
2151 | { |
2152 | g_critical ("%s: property '%s' of class '%s' is not writable" , |
2153 | G_STRFUNC, pspec->name, G_OBJECT_TYPE_NAME (target)); |
2154 | return NULL; |
2155 | } |
2156 | |
2157 | bind = g_slice_new0 (GtkExpressionBind); |
2158 | binds = g_object_steal_data (object: target, key: "gtk-expression-binds" ); |
2159 | if (binds == NULL) |
2160 | g_object_weak_ref (object: target, notify: invalidate_binds, NULL); |
2161 | bind->target = target; |
2162 | bind->pspec = pspec; |
2163 | bind->watch = gtk_expression_watch (self, |
2164 | this_, |
2165 | notify: gtk_expression_bind_notify, |
2166 | user_data: bind, |
2167 | user_destroy: gtk_expression_bind_free); |
2168 | binds = g_slist_prepend (list: binds, data: bind); |
2169 | g_object_set_data_full (object: target, key: "gtk-expression-binds" , data: binds, destroy: free_binds); |
2170 | |
2171 | gtk_expression_unref (self); |
2172 | |
2173 | gtk_expression_bind_notify (data: bind); |
2174 | |
2175 | return bind->watch; |
2176 | } |
2177 | |
2178 | /* }}} */ |
2179 | |