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