1 | #include <stdlib.h> |
2 | #include <gstdio.h> |
3 | #include <glib-object.h> |
4 | |
5 | typedef struct { |
6 | GTypeInterface g_iface; |
7 | } FooInterface; |
8 | |
9 | GType foo_get_type (void); |
10 | |
11 | G_DEFINE_INTERFACE (Foo, foo, G_TYPE_OBJECT) |
12 | |
13 | static void |
14 | foo_default_init (FooInterface *iface) |
15 | { |
16 | } |
17 | |
18 | typedef struct { |
19 | GObject parent; |
20 | } Baa; |
21 | |
22 | typedef struct { |
23 | GObjectClass parent_class; |
24 | } BaaClass; |
25 | |
26 | static void |
27 | baa_init_foo (FooInterface *iface) |
28 | { |
29 | } |
30 | |
31 | GType baa_get_type (void); |
32 | |
33 | G_DEFINE_TYPE_WITH_CODE (Baa, baa, G_TYPE_OBJECT, |
34 | G_IMPLEMENT_INTERFACE (foo_get_type (), baa_init_foo)) |
35 | |
36 | static void |
37 | baa_init (Baa *baa) |
38 | { |
39 | } |
40 | |
41 | static void |
42 | baa_class_init (BaaClass *class) |
43 | { |
44 | } |
45 | |
46 | typedef struct _BindingSource |
47 | { |
48 | GObject parent_instance; |
49 | |
50 | gint foo; |
51 | gint bar; |
52 | gdouble double_value; |
53 | gboolean toggle; |
54 | gpointer item; |
55 | } BindingSource; |
56 | |
57 | typedef struct _BindingSourceClass |
58 | { |
59 | GObjectClass parent_class; |
60 | } BindingSourceClass; |
61 | |
62 | enum |
63 | { |
64 | PROP_SOURCE_0, |
65 | |
66 | PROP_SOURCE_FOO, |
67 | PROP_SOURCE_BAR, |
68 | PROP_SOURCE_DOUBLE_VALUE, |
69 | PROP_SOURCE_TOGGLE, |
70 | PROP_SOURCE_OBJECT |
71 | }; |
72 | |
73 | static GType binding_source_get_type (void); |
74 | G_DEFINE_TYPE (BindingSource, binding_source, G_TYPE_OBJECT) |
75 | |
76 | static void |
77 | binding_source_set_property (GObject *gobject, |
78 | guint prop_id, |
79 | const GValue *value, |
80 | GParamSpec *pspec) |
81 | { |
82 | BindingSource *source = (BindingSource *) gobject; |
83 | |
84 | switch (prop_id) |
85 | { |
86 | case PROP_SOURCE_FOO: |
87 | source->foo = g_value_get_int (value); |
88 | break; |
89 | |
90 | case PROP_SOURCE_BAR: |
91 | source->bar = g_value_get_int (value); |
92 | break; |
93 | |
94 | case PROP_SOURCE_DOUBLE_VALUE: |
95 | source->double_value = g_value_get_double (value); |
96 | break; |
97 | |
98 | case PROP_SOURCE_TOGGLE: |
99 | source->toggle = g_value_get_boolean (value); |
100 | break; |
101 | |
102 | case PROP_SOURCE_OBJECT: |
103 | source->item = g_value_get_object (value); |
104 | break; |
105 | |
106 | default: |
107 | G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); |
108 | } |
109 | } |
110 | |
111 | static void |
112 | binding_source_get_property (GObject *gobject, |
113 | guint prop_id, |
114 | GValue *value, |
115 | GParamSpec *pspec) |
116 | { |
117 | BindingSource *source = (BindingSource *) gobject; |
118 | |
119 | switch (prop_id) |
120 | { |
121 | case PROP_SOURCE_FOO: |
122 | g_value_set_int (value, v_int: source->foo); |
123 | break; |
124 | |
125 | case PROP_SOURCE_BAR: |
126 | g_value_set_int (value, v_int: source->bar); |
127 | break; |
128 | |
129 | case PROP_SOURCE_DOUBLE_VALUE: |
130 | g_value_set_double (value, v_double: source->double_value); |
131 | break; |
132 | |
133 | case PROP_SOURCE_TOGGLE: |
134 | g_value_set_boolean (value, v_boolean: source->toggle); |
135 | break; |
136 | |
137 | case PROP_SOURCE_OBJECT: |
138 | g_value_set_object (value, v_object: source->item); |
139 | break; |
140 | |
141 | default: |
142 | G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); |
143 | } |
144 | } |
145 | |
146 | static void |
147 | binding_source_class_init (BindingSourceClass *klass) |
148 | { |
149 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
150 | |
151 | gobject_class->set_property = binding_source_set_property; |
152 | gobject_class->get_property = binding_source_get_property; |
153 | |
154 | g_object_class_install_property (oclass: gobject_class, property_id: PROP_SOURCE_FOO, |
155 | pspec: g_param_spec_int (name: "foo" , nick: "Foo" , blurb: "Foo" , |
156 | minimum: -1, maximum: 100, |
157 | default_value: 0, |
158 | flags: G_PARAM_READWRITE)); |
159 | g_object_class_install_property (oclass: gobject_class, property_id: PROP_SOURCE_BAR, |
160 | pspec: g_param_spec_int (name: "bar" , nick: "Bar" , blurb: "Bar" , |
161 | minimum: -1, maximum: 100, |
162 | default_value: 0, |
163 | flags: G_PARAM_READWRITE)); |
164 | g_object_class_install_property (oclass: gobject_class, property_id: PROP_SOURCE_DOUBLE_VALUE, |
165 | pspec: g_param_spec_double (name: "double-value" , nick: "Value" , blurb: "Value" , |
166 | minimum: -100.0, maximum: 200.0, |
167 | default_value: 0.0, |
168 | flags: G_PARAM_READWRITE)); |
169 | g_object_class_install_property (oclass: gobject_class, property_id: PROP_SOURCE_TOGGLE, |
170 | pspec: g_param_spec_boolean (name: "toggle" , nick: "Toggle" , blurb: "Toggle" , |
171 | FALSE, |
172 | flags: G_PARAM_READWRITE)); |
173 | g_object_class_install_property (oclass: gobject_class, property_id: PROP_SOURCE_OBJECT, |
174 | pspec: g_param_spec_object (name: "object" , nick: "Object" , blurb: "Object" , |
175 | G_TYPE_OBJECT, |
176 | flags: G_PARAM_READWRITE)); |
177 | } |
178 | |
179 | static void |
180 | binding_source_init (BindingSource *self) |
181 | { |
182 | } |
183 | |
184 | typedef struct _BindingTarget |
185 | { |
186 | GObject parent_instance; |
187 | |
188 | gint bar; |
189 | gdouble double_value; |
190 | gboolean toggle; |
191 | gpointer foo; |
192 | } BindingTarget; |
193 | |
194 | typedef struct _BindingTargetClass |
195 | { |
196 | GObjectClass parent_class; |
197 | } BindingTargetClass; |
198 | |
199 | enum |
200 | { |
201 | PROP_TARGET_0, |
202 | |
203 | PROP_TARGET_BAR, |
204 | PROP_TARGET_DOUBLE_VALUE, |
205 | PROP_TARGET_TOGGLE, |
206 | PROP_TARGET_FOO |
207 | }; |
208 | |
209 | static GType binding_target_get_type (void); |
210 | G_DEFINE_TYPE (BindingTarget, binding_target, G_TYPE_OBJECT) |
211 | |
212 | static void |
213 | binding_target_set_property (GObject *gobject, |
214 | guint prop_id, |
215 | const GValue *value, |
216 | GParamSpec *pspec) |
217 | { |
218 | BindingTarget *target = (BindingTarget *) gobject; |
219 | |
220 | switch (prop_id) |
221 | { |
222 | case PROP_TARGET_BAR: |
223 | target->bar = g_value_get_int (value); |
224 | break; |
225 | |
226 | case PROP_TARGET_DOUBLE_VALUE: |
227 | target->double_value = g_value_get_double (value); |
228 | break; |
229 | |
230 | case PROP_TARGET_TOGGLE: |
231 | target->toggle = g_value_get_boolean (value); |
232 | break; |
233 | |
234 | case PROP_TARGET_FOO: |
235 | target->foo = g_value_get_object (value); |
236 | break; |
237 | |
238 | default: |
239 | G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); |
240 | } |
241 | } |
242 | |
243 | static void |
244 | binding_target_get_property (GObject *gobject, |
245 | guint prop_id, |
246 | GValue *value, |
247 | GParamSpec *pspec) |
248 | { |
249 | BindingTarget *target = (BindingTarget *) gobject; |
250 | |
251 | switch (prop_id) |
252 | { |
253 | case PROP_TARGET_BAR: |
254 | g_value_set_int (value, v_int: target->bar); |
255 | break; |
256 | |
257 | case PROP_TARGET_DOUBLE_VALUE: |
258 | g_value_set_double (value, v_double: target->double_value); |
259 | break; |
260 | |
261 | case PROP_TARGET_TOGGLE: |
262 | g_value_set_boolean (value, v_boolean: target->toggle); |
263 | break; |
264 | |
265 | case PROP_TARGET_FOO: |
266 | g_value_set_object (value, v_object: target->foo); |
267 | break; |
268 | |
269 | default: |
270 | G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); |
271 | } |
272 | } |
273 | |
274 | static void |
275 | binding_target_class_init (BindingTargetClass *klass) |
276 | { |
277 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
278 | |
279 | gobject_class->set_property = binding_target_set_property; |
280 | gobject_class->get_property = binding_target_get_property; |
281 | |
282 | g_object_class_install_property (oclass: gobject_class, property_id: PROP_TARGET_BAR, |
283 | pspec: g_param_spec_int (name: "bar" , nick: "Bar" , blurb: "Bar" , |
284 | minimum: -1, maximum: 100, |
285 | default_value: 0, |
286 | flags: G_PARAM_READWRITE)); |
287 | g_object_class_install_property (oclass: gobject_class, property_id: PROP_TARGET_DOUBLE_VALUE, |
288 | pspec: g_param_spec_double (name: "double-value" , nick: "Value" , blurb: "Value" , |
289 | minimum: -100.0, maximum: 200.0, |
290 | default_value: 0.0, |
291 | flags: G_PARAM_READWRITE)); |
292 | g_object_class_install_property (oclass: gobject_class, property_id: PROP_TARGET_TOGGLE, |
293 | pspec: g_param_spec_boolean (name: "toggle" , nick: "Toggle" , blurb: "Toggle" , |
294 | FALSE, |
295 | flags: G_PARAM_READWRITE)); |
296 | g_object_class_install_property (oclass: gobject_class, property_id: PROP_TARGET_FOO, |
297 | pspec: g_param_spec_object (name: "foo" , nick: "Foo" , blurb: "Foo" , |
298 | object_type: foo_get_type (), |
299 | flags: G_PARAM_READWRITE)); |
300 | } |
301 | |
302 | static void |
303 | binding_target_init (BindingTarget *self) |
304 | { |
305 | } |
306 | |
307 | static gboolean |
308 | celsius_to_fahrenheit (GBinding *binding, |
309 | const GValue *from_value, |
310 | GValue *to_value, |
311 | gpointer user_data G_GNUC_UNUSED) |
312 | { |
313 | gdouble celsius, fahrenheit; |
314 | |
315 | g_assert_true (G_VALUE_HOLDS (from_value, G_TYPE_DOUBLE)); |
316 | g_assert_true (G_VALUE_HOLDS (to_value, G_TYPE_DOUBLE)); |
317 | |
318 | celsius = g_value_get_double (value: from_value); |
319 | fahrenheit = (9 * celsius / 5) + 32.0; |
320 | |
321 | if (g_test_verbose ()) |
322 | g_printerr (format: "Converting %.2fC to %.2fF\n" , celsius, fahrenheit); |
323 | |
324 | g_value_set_double (value: to_value, v_double: fahrenheit); |
325 | |
326 | return TRUE; |
327 | } |
328 | |
329 | static gboolean |
330 | fahrenheit_to_celsius (GBinding *binding, |
331 | const GValue *from_value, |
332 | GValue *to_value, |
333 | gpointer user_data G_GNUC_UNUSED) |
334 | { |
335 | gdouble celsius, fahrenheit; |
336 | |
337 | g_assert_true (G_VALUE_HOLDS (from_value, G_TYPE_DOUBLE)); |
338 | g_assert_true (G_VALUE_HOLDS (to_value, G_TYPE_DOUBLE)); |
339 | |
340 | fahrenheit = g_value_get_double (value: from_value); |
341 | celsius = 5 * (fahrenheit - 32.0) / 9; |
342 | |
343 | if (g_test_verbose ()) |
344 | g_printerr (format: "Converting %.2fF to %.2fC\n" , fahrenheit, celsius); |
345 | |
346 | g_value_set_double (value: to_value, v_double: celsius); |
347 | |
348 | return TRUE; |
349 | } |
350 | |
351 | static void |
352 | binding_default (void) |
353 | { |
354 | BindingSource *source = g_object_new (object_type: binding_source_get_type (), NULL); |
355 | BindingTarget *target = g_object_new (object_type: binding_target_get_type (), NULL); |
356 | GObject *tmp; |
357 | GBinding *binding; |
358 | |
359 | binding = g_object_bind_property (source, source_property: "foo" , |
360 | target, target_property: "bar" , |
361 | flags: G_BINDING_DEFAULT); |
362 | |
363 | g_object_add_weak_pointer (G_OBJECT (binding), weak_pointer_location: (gpointer *) &binding); |
364 | tmp = g_binding_dup_source (binding); |
365 | g_assert_nonnull (tmp); |
366 | g_assert_true ((BindingSource *) tmp == source); |
367 | g_object_unref (object: tmp); |
368 | tmp = g_binding_dup_target (binding); |
369 | g_assert_nonnull (tmp); |
370 | g_assert_true ((BindingTarget *) tmp == target); |
371 | g_object_unref (object: tmp); |
372 | g_assert_cmpstr (g_binding_get_source_property (binding), ==, "foo" ); |
373 | g_assert_cmpstr (g_binding_get_target_property (binding), ==, "bar" ); |
374 | g_assert_cmpint (g_binding_get_flags (binding), ==, G_BINDING_DEFAULT); |
375 | |
376 | g_object_set (object: source, first_property_name: "foo" , 42, NULL); |
377 | g_assert_cmpint (source->foo, ==, target->bar); |
378 | |
379 | g_object_set (object: target, first_property_name: "bar" , 47, NULL); |
380 | g_assert_cmpint (source->foo, !=, target->bar); |
381 | |
382 | g_object_unref (object: binding); |
383 | |
384 | g_object_set (object: source, first_property_name: "foo" , 0, NULL); |
385 | g_assert_cmpint (source->foo, !=, target->bar); |
386 | |
387 | g_object_unref (object: source); |
388 | g_object_unref (object: target); |
389 | g_assert_null (binding); |
390 | } |
391 | |
392 | static void |
393 | binding_canonicalisation (void) |
394 | { |
395 | BindingSource *source = g_object_new (object_type: binding_source_get_type (), NULL); |
396 | BindingTarget *target = g_object_new (object_type: binding_target_get_type (), NULL); |
397 | GBinding *binding; |
398 | GObject *tmp; |
399 | |
400 | g_test_summary (summary: "Test that bindings set up with non-canonical property names work" ); |
401 | |
402 | binding = g_object_bind_property (source, source_property: "double_value" , |
403 | target, target_property: "double_value" , |
404 | flags: G_BINDING_DEFAULT); |
405 | |
406 | g_object_add_weak_pointer (G_OBJECT (binding), weak_pointer_location: (gpointer *) &binding); |
407 | tmp = g_binding_dup_source (binding); |
408 | g_assert_nonnull (tmp); |
409 | g_assert_true ((BindingSource *) tmp == source); |
410 | g_object_unref (object: tmp); |
411 | tmp = g_binding_dup_target (binding); |
412 | g_assert_nonnull (tmp); |
413 | g_assert_true ((BindingTarget *) tmp == target); |
414 | g_object_unref (object: tmp); |
415 | g_assert_cmpstr (g_binding_get_source_property (binding), ==, "double-value" ); |
416 | g_assert_cmpstr (g_binding_get_target_property (binding), ==, "double-value" ); |
417 | g_assert_cmpint (g_binding_get_flags (binding), ==, G_BINDING_DEFAULT); |
418 | |
419 | g_object_set (object: source, first_property_name: "double-value" , 24.0, NULL); |
420 | g_assert_cmpfloat (target->double_value, ==, source->double_value); |
421 | |
422 | g_object_set (object: target, first_property_name: "double-value" , 69.0, NULL); |
423 | g_assert_cmpfloat (source->double_value, !=, target->double_value); |
424 | |
425 | g_object_unref (object: target); |
426 | g_object_unref (object: source); |
427 | g_assert_null (binding); |
428 | } |
429 | |
430 | static void |
431 | binding_bidirectional (void) |
432 | { |
433 | BindingSource *source = g_object_new (object_type: binding_source_get_type (), NULL); |
434 | BindingTarget *target = g_object_new (object_type: binding_target_get_type (), NULL); |
435 | GBinding *binding; |
436 | |
437 | binding = g_object_bind_property (source, source_property: "foo" , |
438 | target, target_property: "bar" , |
439 | flags: G_BINDING_BIDIRECTIONAL); |
440 | g_object_add_weak_pointer (G_OBJECT (binding), weak_pointer_location: (gpointer *) &binding); |
441 | |
442 | g_object_set (object: source, first_property_name: "foo" , 42, NULL); |
443 | g_assert_cmpint (source->foo, ==, target->bar); |
444 | |
445 | g_object_set (object: target, first_property_name: "bar" , 47, NULL); |
446 | g_assert_cmpint (source->foo, ==, target->bar); |
447 | |
448 | g_object_unref (object: binding); |
449 | |
450 | g_object_set (object: source, first_property_name: "foo" , 0, NULL); |
451 | g_assert_cmpint (source->foo, !=, target->bar); |
452 | |
453 | g_object_unref (object: source); |
454 | g_object_unref (object: target); |
455 | g_assert_null (binding); |
456 | } |
457 | |
458 | static void |
459 | data_free (gpointer data) |
460 | { |
461 | gboolean *b = data; |
462 | |
463 | *b = TRUE; |
464 | } |
465 | |
466 | static void |
467 | binding_transform_default (void) |
468 | { |
469 | BindingSource *source = g_object_new (object_type: binding_source_get_type (), NULL); |
470 | BindingTarget *target = g_object_new (object_type: binding_target_get_type (), NULL); |
471 | GBinding *binding; |
472 | gpointer src, trg; |
473 | gchar *src_prop, *trg_prop; |
474 | GBindingFlags flags; |
475 | |
476 | binding = g_object_bind_property (source, source_property: "foo" , |
477 | target, target_property: "double-value" , |
478 | flags: G_BINDING_BIDIRECTIONAL); |
479 | |
480 | g_object_add_weak_pointer (G_OBJECT (binding), weak_pointer_location: (gpointer *) &binding); |
481 | |
482 | g_object_get (object: binding, |
483 | first_property_name: "source" , &src, |
484 | "source-property" , &src_prop, |
485 | "target" , &trg, |
486 | "target-property" , &trg_prop, |
487 | "flags" , &flags, |
488 | NULL); |
489 | g_assert_true (src == source); |
490 | g_assert_true (trg == target); |
491 | g_assert_cmpstr (src_prop, ==, "foo" ); |
492 | g_assert_cmpstr (trg_prop, ==, "double-value" ); |
493 | g_assert_cmpint (flags, ==, G_BINDING_BIDIRECTIONAL); |
494 | g_object_unref (object: src); |
495 | g_object_unref (object: trg); |
496 | g_free (mem: src_prop); |
497 | g_free (mem: trg_prop); |
498 | |
499 | g_object_set (object: source, first_property_name: "foo" , 24, NULL); |
500 | g_assert_cmpfloat (target->double_value, ==, 24.0); |
501 | |
502 | g_object_set (object: target, first_property_name: "double-value" , 69.0, NULL); |
503 | g_assert_cmpint (source->foo, ==, 69); |
504 | |
505 | g_object_unref (object: target); |
506 | g_object_unref (object: source); |
507 | g_assert_null (binding); |
508 | } |
509 | |
510 | static void |
511 | binding_transform (void) |
512 | { |
513 | BindingSource *source = g_object_new (object_type: binding_source_get_type (), NULL); |
514 | BindingTarget *target = g_object_new (object_type: binding_target_get_type (), NULL); |
515 | GBinding *binding G_GNUC_UNUSED; |
516 | gboolean unused_data = FALSE; |
517 | |
518 | binding = g_object_bind_property_full (source, source_property: "double-value" , |
519 | target, target_property: "double-value" , |
520 | flags: G_BINDING_BIDIRECTIONAL, |
521 | transform_to: celsius_to_fahrenheit, |
522 | transform_from: fahrenheit_to_celsius, |
523 | user_data: &unused_data, notify: data_free); |
524 | |
525 | g_object_set (object: source, first_property_name: "double-value" , 24.0, NULL); |
526 | g_assert_cmpfloat (target->double_value, ==, ((9 * 24.0 / 5) + 32.0)); |
527 | |
528 | g_object_set (object: target, first_property_name: "double-value" , 69.0, NULL); |
529 | g_assert_cmpfloat (source->double_value, ==, (5 * (69.0 - 32.0) / 9)); |
530 | |
531 | g_object_unref (object: source); |
532 | g_object_unref (object: target); |
533 | |
534 | g_assert_true (unused_data); |
535 | } |
536 | |
537 | static void |
538 | binding_transform_closure (void) |
539 | { |
540 | BindingSource *source = g_object_new (object_type: binding_source_get_type (), NULL); |
541 | BindingTarget *target = g_object_new (object_type: binding_target_get_type (), NULL); |
542 | GBinding *binding G_GNUC_UNUSED; |
543 | gboolean unused_data_1 = FALSE, unused_data_2 = FALSE; |
544 | GClosure *c2f_clos, *f2c_clos; |
545 | |
546 | c2f_clos = g_cclosure_new (G_CALLBACK (celsius_to_fahrenheit), user_data: &unused_data_1, destroy_data: (GClosureNotify) data_free); |
547 | |
548 | f2c_clos = g_cclosure_new (G_CALLBACK (fahrenheit_to_celsius), user_data: &unused_data_2, destroy_data: (GClosureNotify) data_free); |
549 | |
550 | binding = g_object_bind_property_with_closures (source, source_property: "double-value" , |
551 | target, target_property: "double-value" , |
552 | flags: G_BINDING_BIDIRECTIONAL, |
553 | transform_to: c2f_clos, |
554 | transform_from: f2c_clos); |
555 | |
556 | g_object_set (object: source, first_property_name: "double-value" , 24.0, NULL); |
557 | g_assert_cmpfloat (target->double_value, ==, ((9 * 24.0 / 5) + 32.0)); |
558 | |
559 | g_object_set (object: target, first_property_name: "double-value" , 69.0, NULL); |
560 | g_assert_cmpfloat (source->double_value, ==, (5 * (69.0 - 32.0) / 9)); |
561 | |
562 | g_object_unref (object: source); |
563 | g_object_unref (object: target); |
564 | |
565 | g_assert_true (unused_data_1); |
566 | g_assert_true (unused_data_2); |
567 | } |
568 | |
569 | static void |
570 | binding_chain (void) |
571 | { |
572 | BindingSource *a = g_object_new (object_type: binding_source_get_type (), NULL); |
573 | BindingSource *b = g_object_new (object_type: binding_source_get_type (), NULL); |
574 | BindingSource *c = g_object_new (object_type: binding_source_get_type (), NULL); |
575 | GBinding *binding_1, *binding_2; |
576 | |
577 | g_test_bug_base (uri_pattern: "http://bugzilla.gnome.org/" ); |
578 | g_test_bug (bug_uri_snippet: "621782" ); |
579 | |
580 | /* A -> B, B -> C */ |
581 | binding_1 = g_object_bind_property (source: a, source_property: "foo" , target: b, target_property: "foo" , flags: G_BINDING_BIDIRECTIONAL); |
582 | g_object_add_weak_pointer (G_OBJECT (binding_1), weak_pointer_location: (gpointer *) &binding_1); |
583 | |
584 | binding_2 = g_object_bind_property (source: b, source_property: "foo" , target: c, target_property: "foo" , flags: G_BINDING_BIDIRECTIONAL); |
585 | g_object_add_weak_pointer (G_OBJECT (binding_2), weak_pointer_location: (gpointer *) &binding_2); |
586 | |
587 | /* verify the chain */ |
588 | g_object_set (object: a, first_property_name: "foo" , 42, NULL); |
589 | g_assert_cmpint (a->foo, ==, b->foo); |
590 | g_assert_cmpint (b->foo, ==, c->foo); |
591 | |
592 | /* unbind A -> B and B -> C */ |
593 | g_object_unref (object: binding_1); |
594 | g_assert_null (binding_1); |
595 | g_object_unref (object: binding_2); |
596 | g_assert_null (binding_2); |
597 | |
598 | /* bind A -> C directly */ |
599 | binding_2 = g_object_bind_property (source: a, source_property: "foo" , target: c, target_property: "foo" , flags: G_BINDING_BIDIRECTIONAL); |
600 | |
601 | /* verify the chain is broken */ |
602 | g_object_set (object: a, first_property_name: "foo" , 47, NULL); |
603 | g_assert_cmpint (a->foo, !=, b->foo); |
604 | g_assert_cmpint (a->foo, ==, c->foo); |
605 | |
606 | g_object_unref (object: a); |
607 | g_object_unref (object: b); |
608 | g_object_unref (object: c); |
609 | } |
610 | |
611 | static void |
612 | binding_sync_create (void) |
613 | { |
614 | BindingSource *source = g_object_new (object_type: binding_source_get_type (), |
615 | first_property_name: "foo" , 42, |
616 | NULL); |
617 | BindingTarget *target = g_object_new (object_type: binding_target_get_type (), |
618 | first_property_name: "bar" , 47, |
619 | NULL); |
620 | GBinding *binding; |
621 | |
622 | binding = g_object_bind_property (source, source_property: "foo" , |
623 | target, target_property: "bar" , |
624 | flags: G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); |
625 | |
626 | g_assert_cmpint (source->foo, ==, 42); |
627 | g_assert_cmpint (target->bar, ==, 42); |
628 | |
629 | g_object_set (object: source, first_property_name: "foo" , 47, NULL); |
630 | g_assert_cmpint (source->foo, ==, target->bar); |
631 | |
632 | g_object_unref (object: binding); |
633 | |
634 | g_object_set (object: target, first_property_name: "bar" , 49, NULL); |
635 | |
636 | binding = g_object_bind_property (source, source_property: "foo" , |
637 | target, target_property: "bar" , |
638 | flags: G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); |
639 | g_assert_cmpint (source->foo, ==, 47); |
640 | g_assert_cmpint (target->bar, ==, 47); |
641 | |
642 | g_object_unref (object: source); |
643 | g_object_unref (object: target); |
644 | } |
645 | |
646 | static void |
647 | binding_invert_boolean (void) |
648 | { |
649 | BindingSource *source = g_object_new (object_type: binding_source_get_type (), |
650 | first_property_name: "toggle" , TRUE, |
651 | NULL); |
652 | BindingTarget *target = g_object_new (object_type: binding_target_get_type (), |
653 | first_property_name: "toggle" , FALSE, |
654 | NULL); |
655 | GBinding *binding; |
656 | |
657 | binding = g_object_bind_property (source, source_property: "toggle" , |
658 | target, target_property: "toggle" , |
659 | flags: G_BINDING_BIDIRECTIONAL | G_BINDING_INVERT_BOOLEAN); |
660 | |
661 | g_assert_true (source->toggle); |
662 | g_assert_false (target->toggle); |
663 | |
664 | g_object_set (object: source, first_property_name: "toggle" , FALSE, NULL); |
665 | g_assert_false (source->toggle); |
666 | g_assert_true (target->toggle); |
667 | |
668 | g_object_set (object: target, first_property_name: "toggle" , FALSE, NULL); |
669 | g_assert_true (source->toggle); |
670 | g_assert_false (target->toggle); |
671 | |
672 | g_object_unref (object: binding); |
673 | g_object_unref (object: source); |
674 | g_object_unref (object: target); |
675 | } |
676 | |
677 | static void |
678 | binding_same_object (void) |
679 | { |
680 | BindingSource *source = g_object_new (object_type: binding_source_get_type (), |
681 | first_property_name: "foo" , 100, |
682 | "bar" , 50, |
683 | NULL); |
684 | GBinding *binding; |
685 | |
686 | binding = g_object_bind_property (source, source_property: "foo" , |
687 | target: source, target_property: "bar" , |
688 | flags: G_BINDING_BIDIRECTIONAL); |
689 | g_object_add_weak_pointer (G_OBJECT (binding), weak_pointer_location: (gpointer *) &binding); |
690 | |
691 | g_object_set (object: source, first_property_name: "foo" , 10, NULL); |
692 | g_assert_cmpint (source->foo, ==, 10); |
693 | g_assert_cmpint (source->bar, ==, 10); |
694 | g_object_set (object: source, first_property_name: "bar" , 30, NULL); |
695 | g_assert_cmpint (source->foo, ==, 30); |
696 | g_assert_cmpint (source->bar, ==, 30); |
697 | |
698 | g_object_unref (object: source); |
699 | g_assert_null (binding); |
700 | } |
701 | |
702 | static void |
703 | binding_unbind (void) |
704 | { |
705 | BindingSource *source = g_object_new (object_type: binding_source_get_type (), NULL); |
706 | BindingTarget *target = g_object_new (object_type: binding_target_get_type (), NULL); |
707 | GBinding *binding; |
708 | |
709 | binding = g_object_bind_property (source, source_property: "foo" , |
710 | target, target_property: "bar" , |
711 | flags: G_BINDING_DEFAULT); |
712 | g_object_add_weak_pointer (G_OBJECT (binding), weak_pointer_location: (gpointer *) &binding); |
713 | |
714 | g_object_set (object: source, first_property_name: "foo" , 42, NULL); |
715 | g_assert_cmpint (source->foo, ==, target->bar); |
716 | |
717 | g_object_set (object: target, first_property_name: "bar" , 47, NULL); |
718 | g_assert_cmpint (source->foo, !=, target->bar); |
719 | |
720 | g_binding_unbind (binding); |
721 | g_assert_null (binding); |
722 | |
723 | g_object_set (object: source, first_property_name: "foo" , 0, NULL); |
724 | g_assert_cmpint (source->foo, !=, target->bar); |
725 | |
726 | g_object_unref (object: source); |
727 | g_object_unref (object: target); |
728 | |
729 | |
730 | /* g_binding_unbind() has a special case for this */ |
731 | source = g_object_new (object_type: binding_source_get_type (), NULL); |
732 | binding = g_object_bind_property (source, source_property: "foo" , |
733 | target: source, target_property: "bar" , |
734 | flags: G_BINDING_DEFAULT); |
735 | g_object_add_weak_pointer (G_OBJECT (binding), weak_pointer_location: (gpointer *) &binding); |
736 | |
737 | g_binding_unbind (binding); |
738 | g_assert_null (binding); |
739 | |
740 | g_object_unref (object: source); |
741 | } |
742 | |
743 | /* When source or target die, so does the binding if there is no other ref */ |
744 | static void |
745 | binding_unbind_weak (void) |
746 | { |
747 | GBinding *binding; |
748 | BindingSource *source; |
749 | BindingTarget *target; |
750 | |
751 | /* first source, then target */ |
752 | source = g_object_new (object_type: binding_source_get_type (), NULL); |
753 | target = g_object_new (object_type: binding_target_get_type (), NULL); |
754 | binding = g_object_bind_property (source, source_property: "foo" , |
755 | target, target_property: "bar" , |
756 | flags: G_BINDING_DEFAULT); |
757 | g_object_add_weak_pointer (G_OBJECT (binding), weak_pointer_location: (gpointer *) &binding); |
758 | g_assert_nonnull (binding); |
759 | g_object_unref (object: source); |
760 | g_assert_null (binding); |
761 | g_object_unref (object: target); |
762 | g_assert_null (binding); |
763 | |
764 | /* first target, then source */ |
765 | source = g_object_new (object_type: binding_source_get_type (), NULL); |
766 | target = g_object_new (object_type: binding_target_get_type (), NULL); |
767 | binding = g_object_bind_property (source, source_property: "foo" , |
768 | target, target_property: "bar" , |
769 | flags: G_BINDING_DEFAULT); |
770 | g_object_add_weak_pointer (G_OBJECT (binding), weak_pointer_location: (gpointer *) &binding); |
771 | g_assert_nonnull (binding); |
772 | g_object_unref (object: target); |
773 | g_assert_null (binding); |
774 | g_object_unref (object: source); |
775 | g_assert_null (binding); |
776 | |
777 | /* target and source are the same */ |
778 | source = g_object_new (object_type: binding_source_get_type (), NULL); |
779 | binding = g_object_bind_property (source, source_property: "foo" , |
780 | target: source, target_property: "bar" , |
781 | flags: G_BINDING_DEFAULT); |
782 | g_object_add_weak_pointer (G_OBJECT (binding), weak_pointer_location: (gpointer *) &binding); |
783 | g_assert_nonnull (binding); |
784 | g_object_unref (object: source); |
785 | g_assert_null (binding); |
786 | } |
787 | |
788 | /* Test that every call to unbind() after the first is a noop */ |
789 | static void |
790 | binding_unbind_multiple (void) |
791 | { |
792 | BindingSource *source = g_object_new (object_type: binding_source_get_type (), NULL); |
793 | BindingTarget *target = g_object_new (object_type: binding_target_get_type (), NULL); |
794 | GBinding *binding; |
795 | guint i; |
796 | |
797 | g_test_bug (bug_uri_snippet: "1373" ); |
798 | |
799 | binding = g_object_bind_property (source, source_property: "foo" , |
800 | target, target_property: "bar" , |
801 | flags: G_BINDING_DEFAULT); |
802 | g_object_ref (binding); |
803 | g_object_add_weak_pointer (G_OBJECT (binding), weak_pointer_location: (gpointer *) &binding); |
804 | g_assert_nonnull (binding); |
805 | |
806 | /* this shouldn't crash */ |
807 | for (i = 0; i < 50; i++) |
808 | { |
809 | g_binding_unbind (binding); |
810 | g_assert_nonnull (binding); |
811 | } |
812 | |
813 | g_object_unref (object: binding); |
814 | g_assert_null (binding); |
815 | |
816 | g_object_unref (object: source); |
817 | g_object_unref (object: target); |
818 | } |
819 | |
820 | static void |
821 | binding_fail (void) |
822 | { |
823 | BindingSource *source = g_object_new (object_type: binding_source_get_type (), NULL); |
824 | BindingTarget *target = g_object_new (object_type: binding_target_get_type (), NULL); |
825 | GBinding *binding; |
826 | |
827 | /* double -> boolean is not supported */ |
828 | binding = g_object_bind_property (source, source_property: "double-value" , |
829 | target, target_property: "toggle" , |
830 | flags: G_BINDING_DEFAULT); |
831 | g_object_add_weak_pointer (G_OBJECT (binding), weak_pointer_location: (gpointer *) &binding); |
832 | |
833 | g_test_expect_message (log_domain: "GLib-GObject" , log_level: G_LOG_LEVEL_WARNING, |
834 | pattern: "*Unable to convert*double*boolean*" ); |
835 | g_object_set (object: source, first_property_name: "double-value" , 1.0, NULL); |
836 | g_test_assert_expected_messages (); |
837 | |
838 | g_object_unref (object: source); |
839 | g_object_unref (object: target); |
840 | g_assert_null (binding); |
841 | } |
842 | |
843 | static gboolean |
844 | transform_to_func (GBinding *binding, |
845 | const GValue *value_a, |
846 | GValue *value_b, |
847 | gpointer user_data) |
848 | { |
849 | if (g_value_type_compatible (G_VALUE_TYPE (value_a), G_VALUE_TYPE (value_b))) |
850 | { |
851 | g_value_copy (src_value: value_a, dest_value: value_b); |
852 | return TRUE; |
853 | } |
854 | |
855 | if (g_value_type_transformable (G_VALUE_TYPE (value_a), G_VALUE_TYPE (value_b))) |
856 | { |
857 | if (g_value_transform (src_value: value_a, dest_value: value_b)) |
858 | return TRUE; |
859 | } |
860 | |
861 | return FALSE; |
862 | } |
863 | |
864 | static void |
865 | binding_interface (void) |
866 | { |
867 | BindingSource *source = g_object_new (object_type: binding_source_get_type (), NULL); |
868 | BindingTarget *target = g_object_new (object_type: binding_target_get_type (), NULL); |
869 | GObject *baa; |
870 | GBinding *binding; |
871 | GClosure *transform_to; |
872 | |
873 | /* binding a generic object property to an interface-valued one */ |
874 | binding = g_object_bind_property (source, source_property: "object" , |
875 | target, target_property: "foo" , |
876 | flags: G_BINDING_DEFAULT); |
877 | |
878 | baa = g_object_new (object_type: baa_get_type (), NULL); |
879 | g_object_set (object: source, first_property_name: "object" , baa, NULL); |
880 | g_object_unref (object: baa); |
881 | |
882 | g_binding_unbind (binding); |
883 | |
884 | /* the same, with a generic marshaller */ |
885 | transform_to = g_cclosure_new (G_CALLBACK (transform_to_func), NULL, NULL); |
886 | g_closure_set_marshal (closure: transform_to, marshal: g_cclosure_marshal_generic); |
887 | binding = g_object_bind_property_with_closures (source, source_property: "object" , |
888 | target, target_property: "foo" , |
889 | flags: G_BINDING_DEFAULT, |
890 | transform_to, |
891 | NULL); |
892 | |
893 | baa = g_object_new (object_type: baa_get_type (), NULL); |
894 | g_object_set (object: source, first_property_name: "object" , baa, NULL); |
895 | g_object_unref (object: baa); |
896 | |
897 | g_binding_unbind (binding); |
898 | |
899 | g_object_unref (object: source); |
900 | g_object_unref (object: target); |
901 | } |
902 | |
903 | typedef struct { |
904 | GThread *thread; |
905 | GBinding *binding; |
906 | GMutex *lock; |
907 | GCond *cond; |
908 | gboolean *wait; |
909 | gint *count; /* (atomic) */ |
910 | } ConcurrentUnbindData; |
911 | |
912 | static gpointer |
913 | concurrent_unbind_func (gpointer data) |
914 | { |
915 | ConcurrentUnbindData *unbind_data = data; |
916 | |
917 | g_mutex_lock (mutex: unbind_data->lock); |
918 | g_atomic_int_inc (unbind_data->count); |
919 | while (*unbind_data->wait) |
920 | g_cond_wait (cond: unbind_data->cond, mutex: unbind_data->lock); |
921 | g_mutex_unlock (mutex: unbind_data->lock); |
922 | g_binding_unbind (binding: unbind_data->binding); |
923 | g_object_unref (object: unbind_data->binding); |
924 | |
925 | return NULL; |
926 | } |
927 | |
928 | static void |
929 | binding_concurrent_unbind (void) |
930 | { |
931 | guint i, j; |
932 | |
933 | g_test_summary (summary: "Test that unbinding from multiple threads concurrently works correctly" ); |
934 | |
935 | for (i = 0; i < 50; i++) |
936 | { |
937 | BindingSource *source = g_object_new (object_type: binding_source_get_type (), NULL); |
938 | BindingTarget *target = g_object_new (object_type: binding_target_get_type (), NULL); |
939 | GBinding *binding; |
940 | GQueue threads = G_QUEUE_INIT; |
941 | GMutex lock; |
942 | GCond cond; |
943 | gboolean wait = TRUE; |
944 | gint count = 0; /* (atomic) */ |
945 | ConcurrentUnbindData *data; |
946 | |
947 | g_mutex_init (mutex: &lock); |
948 | g_cond_init (cond: &cond); |
949 | |
950 | binding = g_object_bind_property (source, source_property: "foo" , |
951 | target, target_property: "bar" , |
952 | flags: G_BINDING_BIDIRECTIONAL); |
953 | g_object_ref (binding); |
954 | |
955 | for (j = 0; j < 10; j++) |
956 | { |
957 | data = g_new0 (ConcurrentUnbindData, 1); |
958 | |
959 | data->binding = g_object_ref (binding); |
960 | data->lock = &lock; |
961 | data->cond = &cond; |
962 | data->wait = &wait; |
963 | data->count = &count; |
964 | |
965 | data->thread = g_thread_new (name: "binding-concurrent" , func: concurrent_unbind_func, data); |
966 | g_queue_push_tail (queue: &threads, data); |
967 | } |
968 | |
969 | /* wait until all threads are started */ |
970 | while (g_atomic_int_get (&count) < 10) |
971 | g_thread_yield (); |
972 | |
973 | g_mutex_lock (mutex: &lock); |
974 | wait = FALSE; |
975 | g_cond_broadcast (cond: &cond); |
976 | g_mutex_unlock (mutex: &lock); |
977 | |
978 | while ((data = g_queue_pop_head (queue: &threads))) |
979 | { |
980 | g_thread_join (thread: data->thread); |
981 | g_free (mem: data); |
982 | } |
983 | |
984 | g_mutex_clear (mutex: &lock); |
985 | g_cond_clear (cond: &cond); |
986 | |
987 | g_object_unref (object: binding); |
988 | g_object_unref (object: source); |
989 | g_object_unref (object: target); |
990 | } |
991 | } |
992 | |
993 | typedef struct { |
994 | GObject *object; |
995 | GMutex *lock; |
996 | GCond *cond; |
997 | gint *count; /* (atomic) */ |
998 | gboolean *wait; |
999 | } ConcurrentFinalizeData; |
1000 | |
1001 | static gpointer |
1002 | concurrent_finalize_func (gpointer data) |
1003 | { |
1004 | ConcurrentFinalizeData *finalize_data = data; |
1005 | |
1006 | g_mutex_lock (mutex: finalize_data->lock); |
1007 | g_atomic_int_inc (finalize_data->count); |
1008 | while (*finalize_data->wait) |
1009 | g_cond_wait (cond: finalize_data->cond, mutex: finalize_data->lock); |
1010 | g_mutex_unlock (mutex: finalize_data->lock); |
1011 | g_object_unref (object: finalize_data->object); |
1012 | g_free (mem: finalize_data); |
1013 | |
1014 | return NULL; |
1015 | } |
1016 | |
1017 | static void |
1018 | binding_concurrent_finalizing (void) |
1019 | { |
1020 | guint i; |
1021 | |
1022 | g_test_summary (summary: "Test that finalizing source/target from multiple threads concurrently works correctly" ); |
1023 | |
1024 | for (i = 0; i < 50; i++) |
1025 | { |
1026 | BindingSource *source = g_object_new (object_type: binding_source_get_type (), NULL); |
1027 | BindingTarget *target = g_object_new (object_type: binding_target_get_type (), NULL); |
1028 | GBinding *binding; |
1029 | GMutex lock; |
1030 | GCond cond; |
1031 | gboolean wait = TRUE; |
1032 | ConcurrentFinalizeData *data; |
1033 | GThread *source_thread, *target_thread; |
1034 | gint count = 0; /* (atomic) */ |
1035 | |
1036 | g_mutex_init (mutex: &lock); |
1037 | g_cond_init (cond: &cond); |
1038 | |
1039 | binding = g_object_bind_property (source, source_property: "foo" , |
1040 | target, target_property: "bar" , |
1041 | flags: G_BINDING_BIDIRECTIONAL); |
1042 | g_object_ref (binding); |
1043 | |
1044 | data = g_new0 (ConcurrentFinalizeData, 1); |
1045 | data->object = (GObject *) source; |
1046 | data->wait = &wait; |
1047 | data->lock = &lock; |
1048 | data->cond = &cond; |
1049 | data->count = &count; |
1050 | source_thread = g_thread_new (name: "binding-concurrent" , func: concurrent_finalize_func, data); |
1051 | |
1052 | data = g_new0 (ConcurrentFinalizeData, 1); |
1053 | data->object = (GObject *) target; |
1054 | data->wait = &wait; |
1055 | data->lock = &lock; |
1056 | data->cond = &cond; |
1057 | data->count = &count; |
1058 | target_thread = g_thread_new (name: "binding-concurrent" , func: concurrent_finalize_func, data); |
1059 | |
1060 | /* wait until all threads are started */ |
1061 | while (g_atomic_int_get (&count) < 2) |
1062 | g_thread_yield (); |
1063 | |
1064 | g_mutex_lock (mutex: &lock); |
1065 | wait = FALSE; |
1066 | g_cond_broadcast (cond: &cond); |
1067 | g_mutex_unlock (mutex: &lock); |
1068 | |
1069 | g_thread_join (thread: source_thread); |
1070 | g_thread_join (thread: target_thread); |
1071 | |
1072 | g_mutex_clear (mutex: &lock); |
1073 | g_cond_clear (cond: &cond); |
1074 | |
1075 | g_object_unref (object: binding); |
1076 | } |
1077 | } |
1078 | |
1079 | int |
1080 | main (int argc, char *argv[]) |
1081 | { |
1082 | g_test_init (argc: &argc, argv: &argv, NULL); |
1083 | |
1084 | g_test_bug_base (uri_pattern: "https://gitlab.gnome.org/GNOME/glib/issues/" ); |
1085 | |
1086 | g_test_add_func (testpath: "/binding/default" , test_func: binding_default); |
1087 | g_test_add_func (testpath: "/binding/canonicalisation" , test_func: binding_canonicalisation); |
1088 | g_test_add_func (testpath: "/binding/bidirectional" , test_func: binding_bidirectional); |
1089 | g_test_add_func (testpath: "/binding/transform" , test_func: binding_transform); |
1090 | g_test_add_func (testpath: "/binding/transform-default" , test_func: binding_transform_default); |
1091 | g_test_add_func (testpath: "/binding/transform-closure" , test_func: binding_transform_closure); |
1092 | g_test_add_func (testpath: "/binding/chain" , test_func: binding_chain); |
1093 | g_test_add_func (testpath: "/binding/sync-create" , test_func: binding_sync_create); |
1094 | g_test_add_func (testpath: "/binding/invert-boolean" , test_func: binding_invert_boolean); |
1095 | g_test_add_func (testpath: "/binding/same-object" , test_func: binding_same_object); |
1096 | g_test_add_func (testpath: "/binding/unbind" , test_func: binding_unbind); |
1097 | g_test_add_func (testpath: "/binding/unbind-weak" , test_func: binding_unbind_weak); |
1098 | g_test_add_func (testpath: "/binding/unbind-multiple" , test_func: binding_unbind_multiple); |
1099 | g_test_add_func (testpath: "/binding/fail" , test_func: binding_fail); |
1100 | g_test_add_func (testpath: "/binding/interface" , test_func: binding_interface); |
1101 | g_test_add_func (testpath: "/binding/concurrent-unbind" , test_func: binding_concurrent_unbind); |
1102 | g_test_add_func (testpath: "/binding/concurrent-finalizing" , test_func: binding_concurrent_finalizing); |
1103 | |
1104 | return g_test_run (); |
1105 | } |
1106 | |