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 <locale.h>
21
22#include <gtk/gtk.h>
23
24static void
25inc_counter (gpointer data)
26{
27 guint *counter = data;
28
29 *counter += 1;
30}
31
32static void
33test_property (void)
34{
35 GValue value = G_VALUE_INIT;
36 GtkExpression *expr;
37 GtkExpressionWatch *watch;
38 GtkStringFilter *filter;
39 guint counter = 0;
40 gboolean ret;
41
42 filter = gtk_string_filter_new (NULL);
43 expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, NULL, property_name: "search");
44 watch = gtk_expression_watch (self: expr, this_: filter, notify: inc_counter, user_data: &counter, NULL);
45
46 ret = gtk_expression_evaluate (self: expr, this_: filter, value: &value);
47 g_assert_true (ret);
48 g_assert_cmpstr (g_value_get_string (&value), ==, NULL);
49 g_value_unset (value: &value);
50
51 gtk_string_filter_set_search (self: filter, search: "Hello World");
52 g_assert_cmpint (counter, ==, 1);
53 counter = 0;
54
55 ret = gtk_expression_evaluate (self: expr, this_: filter , value: &value);
56 g_assert_true (ret);
57 g_assert_cmpstr (g_value_get_string (&value), ==, "Hello World");
58 g_value_unset (value: &value);
59
60 gtk_expression_watch_unwatch (watch);
61 g_assert_cmpint (counter, ==, 0);
62
63 gtk_expression_unref (self: expr);
64 g_object_unref (object: filter);
65}
66
67static void
68test_interface_property (void)
69{
70 GtkExpression *expr;
71
72 expr = gtk_property_expression_new (GTK_TYPE_ORIENTABLE, NULL, property_name: "orientation");
73 g_assert_cmpstr (gtk_property_expression_get_pspec (expr)->name, ==, "orientation");
74 gtk_expression_unref (self: expr);
75}
76
77static char *
78print_filter_info (GtkStringFilter *filter,
79 const char *search,
80 gboolean ignore_case,
81 GtkStringFilterMatchMode match_mode)
82{
83 g_assert_cmpstr (search, ==, gtk_string_filter_get_search (filter));
84 g_assert_cmpint (ignore_case, ==, gtk_string_filter_get_ignore_case (filter));
85 g_assert_cmpint (match_mode, ==, gtk_string_filter_get_match_mode (filter));
86
87 return g_strdup (str: "OK");
88}
89
90static void
91test_cclosure (void)
92{
93 GValue value = G_VALUE_INIT;
94 GtkExpression *expr, *pexpr[3];
95 GtkExpressionWatch *watch;
96 GtkStringFilter *filter;
97 guint counter = 0;
98 gboolean ret;
99
100 filter = GTK_STRING_FILTER (ptr: gtk_string_filter_new (NULL));
101 pexpr[0] = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, NULL, property_name: "search");
102 pexpr[1] = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, NULL, property_name: "ignore-case");
103 pexpr[2] = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, NULL, property_name: "match-mode");
104 expr = gtk_cclosure_expression_new (G_TYPE_STRING,
105 NULL,
106 n_params: 3,
107 params: pexpr,
108 G_CALLBACK (print_filter_info),
109 NULL,
110 NULL);
111 watch = gtk_expression_watch (self: expr, this_: filter, notify: inc_counter, user_data: &counter, NULL);
112
113 ret = gtk_expression_evaluate (self: expr, this_: filter, value: &value);
114 g_assert_true (ret);
115 g_assert_cmpstr (g_value_get_string (&value), ==, "OK");
116 g_value_unset (value: &value);
117
118 gtk_string_filter_set_search (self: filter, search: "Hello World");
119 g_assert_cmpint (counter, ==, 1);
120 ret = gtk_expression_evaluate (self: expr, this_: filter , value: &value);
121 g_assert_true (ret);
122 g_assert_cmpstr (g_value_get_string (&value), ==, "OK");
123 g_value_unset (value: &value);
124
125 gtk_string_filter_set_ignore_case (self: filter, FALSE);
126 g_assert_cmpint (counter, ==, 2);
127 ret = gtk_expression_evaluate (self: expr, this_: filter , value: &value);
128 g_assert_true (ret);
129 g_assert_cmpstr (g_value_get_string (&value), ==, "OK");
130 g_value_unset (value: &value);
131
132 gtk_string_filter_set_search (self: filter, search: "Hello");
133 gtk_string_filter_set_ignore_case (self: filter, TRUE);
134 gtk_string_filter_set_match_mode (self: filter, mode: GTK_STRING_FILTER_MATCH_MODE_EXACT);
135 g_assert_cmpint (counter, ==, 5);
136 ret = gtk_expression_evaluate (self: expr, this_: filter , value: &value);
137 g_assert_true (ret);
138 g_assert_cmpstr (g_value_get_string (&value), ==, "OK");
139 g_value_unset (value: &value);
140
141 gtk_expression_watch_unwatch (watch);
142 g_assert_cmpint (counter, ==, 5);
143
144 gtk_expression_unref (self: expr);
145 g_object_unref (object: filter);
146}
147
148static char *
149make_string (void)
150{
151 return g_strdup (str: "Hello");
152}
153
154static void
155test_closure (void)
156{
157 GValue value = G_VALUE_INIT;
158 GtkExpression *expr;
159 GClosure *closure;
160 gboolean ret;
161
162 closure = g_cclosure_new (G_CALLBACK (make_string), NULL, NULL);
163 expr = gtk_closure_expression_new (G_TYPE_STRING, closure, n_params: 0, NULL);
164 ret = gtk_expression_evaluate (self: expr, NULL, value: &value);
165 g_assert_true (ret);
166 g_assert_cmpstr (g_value_get_string (&value), ==, "Hello");
167 g_value_unset (value: &value);
168
169 gtk_expression_unref (self: expr);
170}
171
172static void
173test_constant (void)
174{
175 GtkExpression *expr;
176 GValue value = G_VALUE_INIT;
177 const GValue *v;
178 gboolean res;
179
180 expr = gtk_constant_expression_new (G_TYPE_INT, 22);
181 g_assert_cmpint (gtk_expression_get_value_type (expr), ==, G_TYPE_INT);
182 g_assert_true (gtk_expression_is_static (expr));
183
184 res = gtk_expression_evaluate (self: expr, NULL, value: &value);
185 g_assert_true (res);
186 g_assert_cmpint (g_value_get_int (&value), ==, 22);
187
188 v = gtk_constant_expression_get_value (expression: expr);
189 g_assert_cmpint (g_value_get_int (v), ==, 22);
190
191 gtk_expression_unref (self: expr);
192}
193
194/* Test that object expressions fail to evaluate when
195 * the object is gone.
196 */
197static void
198test_object (void)
199{
200 GtkExpression *expr;
201 GObject *obj;
202 GValue value = G_VALUE_INIT;
203 gboolean res;
204 GObject *o;
205
206 obj = G_OBJECT (gtk_string_filter_new (NULL));
207
208 expr = gtk_object_expression_new (object: obj);
209 g_assert_true (!gtk_expression_is_static (expr));
210 g_assert_cmpint (gtk_expression_get_value_type (expr), ==, GTK_TYPE_STRING_FILTER);
211
212 res = gtk_expression_evaluate (self: expr, NULL, value: &value);
213 g_assert_true (res);
214 g_assert_true (g_value_get_object (&value) == obj);
215 g_value_unset (value: &value);
216
217 o = gtk_object_expression_get_object (expression: expr);
218 g_assert_true (o == obj);
219
220 g_clear_object (&obj);
221 res = gtk_expression_evaluate (self: expr, NULL, value: &value);
222 g_assert_false (res);
223
224 gtk_expression_unref (self: expr);
225}
226
227/* Some basic tests that nested expressions work; in particular test
228 * that watching works when things change deeper in the expression tree
229 *
230 * The setup we use is GtkFilterListModel -> GtkFilter -> "search" property,
231 * which gives us an expression tree like
232 *
233 * GtkPropertyExpression "search"
234 * -> GtkPropertyExpression "filter"
235 * -> GtkObjectExpression listmodel
236 *
237 * We test setting both the search property and the filter property.
238 */
239static void
240test_nested (void)
241{
242 GtkExpression *list_expr;
243 GtkExpression *filter_expr;
244 GtkExpression *expr;
245 GtkStringFilter *filter;
246 GListModel *list;
247 GtkFilterListModel *filtered;
248 GValue value = G_VALUE_INIT;
249 gboolean res;
250 GtkExpressionWatch *watch;
251 guint counter = 0;
252
253 filter = gtk_string_filter_new (NULL);
254 gtk_string_filter_set_search (self: filter, search: "word");
255 list = G_LIST_MODEL (ptr: g_list_store_new (G_TYPE_OBJECT));
256 filtered = gtk_filter_list_model_new (model: list, g_object_ref (GTK_FILTER (filter)));
257
258 list_expr = gtk_object_expression_new (G_OBJECT (filtered));
259 filter_expr = gtk_property_expression_new (GTK_TYPE_FILTER_LIST_MODEL, expression: list_expr, property_name: "filter");
260 expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, expression: filter_expr, property_name: "search");
261
262 g_assert_true (!gtk_expression_is_static (expr));
263 g_assert_cmpint (gtk_expression_get_value_type (expr), ==, G_TYPE_STRING);
264
265 res = gtk_expression_evaluate (self: expr, NULL, value: &value);
266 g_assert_true (res);
267 g_assert_cmpstr (g_value_get_string (&value), ==, "word");
268 g_value_unset (value: &value);
269
270 watch = gtk_expression_watch (self: expr, NULL, notify: inc_counter, user_data: &counter, NULL);
271 gtk_string_filter_set_search (self: GTK_STRING_FILTER (ptr: filter), search: "salad");
272 g_assert_cmpint (counter, ==, 1);
273 counter = 0;
274
275 res = gtk_expression_evaluate (self: expr, NULL, value: &value);
276 g_assert_true (res);
277 g_assert_cmpstr (g_value_get_string (&value), ==, "salad");
278 g_value_unset (value: &value);
279
280 gtk_filter_list_model_set_filter (self: filtered, filter: GTK_FILTER (ptr: filter));
281 g_assert_cmpint (counter, ==, 0);
282
283 g_clear_object (&filter);
284 filter = gtk_string_filter_new (NULL);
285 gtk_string_filter_set_search (self: filter, search: "salad");
286 gtk_filter_list_model_set_filter (self: filtered, filter: GTK_FILTER (ptr: filter));
287 g_assert_cmpint (counter, ==, 1);
288 counter = 0;
289
290 res = gtk_expression_evaluate (self: expr, NULL, value: &value);
291 g_assert_true (res);
292 g_assert_cmpstr (g_value_get_string (&value), ==, "salad");
293 g_value_unset (value: &value);
294
295 gtk_string_filter_set_search (self: filter, search: "bar");
296 g_assert_cmpint (counter, ==, 1);
297 counter = 0;
298
299 res = gtk_expression_evaluate (self: expr, NULL, value: &value);
300 g_assert_true (res);
301 g_assert_cmpstr (g_value_get_string (&value), ==, "bar");
302 g_value_unset (value: &value);
303
304 gtk_filter_list_model_set_filter (self: filtered, NULL);
305 g_assert_cmpint (counter, ==, 1);
306 counter = 0;
307
308 res = gtk_expression_evaluate (self: expr, NULL, value: &value);
309 g_assert_false (res);
310
311 gtk_expression_watch_unwatch (watch);
312 g_assert_cmpint (counter, ==, 0);
313
314 g_object_unref (object: filtered);
315 gtk_expression_unref (self: expr);
316}
317
318/* This test uses the same setup as the last test, but
319 * passes the filter as the "this" object when creating
320 * the watch.
321 *
322 * So when we set a new filter and the old one gets desroyed,
323 * the watch should invalidate itself because its this object
324 * is gone.
325 */
326static void
327test_nested_this_destroyed (void)
328{
329 GtkExpression *list_expr;
330 GtkExpression *filter_expr;
331 GtkExpression *expr;
332 GtkStringFilter *filter;
333 GListModel *list;
334 GtkFilterListModel *filtered;
335 GValue value = G_VALUE_INIT;
336 gboolean res;
337 GtkExpressionWatch *watch;
338 guint counter = 0;
339
340 filter = gtk_string_filter_new (NULL);
341 gtk_string_filter_set_search (self: filter, search: "word");
342 list = G_LIST_MODEL (ptr: g_list_store_new (G_TYPE_OBJECT));
343 filtered = gtk_filter_list_model_new (model: list, g_object_ref (GTK_FILTER (filter)));
344
345 list_expr = gtk_object_expression_new (G_OBJECT (filtered));
346 filter_expr = gtk_property_expression_new (GTK_TYPE_FILTER_LIST_MODEL, expression: list_expr, property_name: "filter");
347 expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, expression: filter_expr, property_name: "search");
348
349 watch = gtk_expression_watch (self: expr, this_: filter, notify: inc_counter, user_data: &counter, NULL);
350 gtk_expression_watch_ref (watch);
351 res = gtk_expression_watch_evaluate (watch, value: &value);
352 g_assert_true (res);
353 g_assert_cmpstr (g_value_get_string (&value), ==, "word");
354 g_value_unset (value: &value);
355
356 g_clear_object (&filter);
357 g_assert_cmpint (counter, ==, 0);
358
359 filter = gtk_string_filter_new (NULL);
360 gtk_string_filter_set_search (self: filter, search: "salad");
361 gtk_filter_list_model_set_filter (self: filtered, filter: GTK_FILTER (ptr: filter));
362 g_assert_cmpint (counter, ==, 1);
363 counter = 0;
364
365 res = gtk_expression_watch_evaluate (watch, value: &value);
366 g_assert_false (res);
367
368 gtk_string_filter_set_search (self: filter, search: "bar");
369 g_assert_cmpint (counter, ==, 0);
370
371 gtk_filter_list_model_set_filter (self: filtered, NULL);
372 g_assert_cmpint (counter, ==, 0);
373
374 res = gtk_expression_watch_evaluate (watch, value: &value);
375 g_assert_false (res);
376 g_assert_false (G_IS_VALUE (&value));
377
378 /* We unwatch on purpose here to make sure it doesn't do bad things. */
379 gtk_expression_watch_unwatch (watch);
380 gtk_expression_watch_unref (watch);
381 g_assert_cmpint (counter, ==, 0);
382
383 g_object_unref (object: filtered);
384 g_object_unref (object: filter);
385 gtk_expression_unref (self: expr);
386}
387
388/* Test that property expressions fail to evaluate if the
389 * expression evaluates to an object of the wrong type
390 */
391static void
392test_type_mismatch (void)
393{
394 GtkFilter *filter;
395 GtkExpression *expr;
396 GValue value = G_VALUE_INIT;
397 gboolean res;
398
399 filter = GTK_FILTER (ptr: gtk_any_filter_new ());
400
401 expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, expression: gtk_constant_expression_new (GTK_TYPE_ANY_FILTER, filter), property_name: "search");
402
403 res = gtk_expression_evaluate (self: expr, NULL, value: &value);
404 g_assert_false (res);
405 g_assert_false (G_IS_VALUE (&value));
406
407 gtk_expression_unref (self: expr);
408 g_object_unref (object: filter);
409}
410
411/* Some basic tests around 'this' */
412static void
413test_this (void)
414{
415 GtkStringFilter *filter;
416 GtkStringFilter *filter2;
417 GtkExpression *expr;
418 GValue value = G_VALUE_INIT;
419 gboolean res;
420
421 expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, NULL, property_name: "search");
422
423 filter = gtk_string_filter_new (NULL);
424 gtk_string_filter_set_search (self: filter, search: "word");
425
426 filter2 = gtk_string_filter_new (NULL);
427 gtk_string_filter_set_search (self: filter2, search: "sausage");
428
429 res = gtk_expression_evaluate (self: expr, this_: filter, value: &value);
430 g_assert_true (res);
431 g_assert_cmpstr (g_value_get_string (&value), ==, "word");
432 g_value_unset (value: &value);
433
434 res = gtk_expression_evaluate (self: expr, this_: filter2, value: &value);
435 g_assert_true (res);
436 g_assert_cmpstr (g_value_get_string (&value), ==, "sausage");
437 g_value_unset (value: &value);
438
439 gtk_expression_unref (self: expr);
440 g_object_unref (object: filter2);
441 g_object_unref (object: filter);
442}
443
444/* Check that even for static expressions, watches can be created
445 * and destroying the "this" argument does invalidate the
446 * expression.
447 */
448static void
449test_constant_watch_this_destroyed (void)
450{
451 GtkExpression *expr;
452 GObject *this;
453 guint counter = 0;
454
455 this = g_object_new (G_TYPE_OBJECT, NULL);
456 expr = gtk_constant_expression_new (G_TYPE_INT, 42);
457 gtk_expression_watch (self: expr, this_: this, notify: inc_counter, user_data: &counter, NULL);
458 g_assert_cmpint (counter, ==, 0);
459
460 g_clear_object (&this);
461 g_assert_cmpint (counter, ==, 1);
462
463 gtk_expression_unref (self: expr);
464}
465
466/* Basic test of gtk_expression_bind */
467static void
468test_bind (void)
469{
470 GtkStringFilter *target;
471 GtkStringFilter *source;
472 GtkExpression *expr;
473 GtkExpressionWatch *watch;
474 GValue value = G_VALUE_INIT;
475 gboolean res;
476
477 expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, NULL, property_name: "search");
478
479 target = gtk_string_filter_new (NULL);
480 gtk_string_filter_set_search (self: target, search: "word");
481 g_assert_cmpstr (gtk_string_filter_get_search (target), ==, "word");
482
483 source = gtk_string_filter_new (NULL);
484 gtk_string_filter_set_search (self: source, search: "sausage");
485
486 watch = gtk_expression_bind (self: expr, target, property: "search", this_: source);
487 gtk_expression_watch_ref (watch);
488 g_assert_cmpstr (gtk_string_filter_get_search (target), ==, "sausage");
489
490 gtk_string_filter_set_search (self: source, search: "salad");
491 g_assert_cmpstr (gtk_string_filter_get_search (target), ==, "salad");
492 res = gtk_expression_watch_evaluate (watch, value: &value);
493 g_assert_true (res);
494 g_assert_cmpstr (g_value_get_string (&value), ==, "salad");
495 g_value_unset (value: &value);
496
497 g_object_unref (object: source);
498 g_assert_cmpstr (gtk_string_filter_get_search (target), ==, "salad");
499 res = gtk_expression_watch_evaluate (watch, value: &value);
500 g_assert_false (res);
501 g_assert_false (G_IS_VALUE (&value));
502
503 g_object_unref (object: target);
504 gtk_expression_watch_unref (watch);
505}
506
507/* Another test of bind, this time we watch ourselves */
508static void
509test_bind_self (void)
510{
511 GtkStringFilter *filter;
512 GtkExpression *expr;
513
514 expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER,
515 NULL,
516 property_name: "ignore-case");
517
518 filter = gtk_string_filter_new (NULL);
519 gtk_string_filter_set_search (self: filter, search: "word");
520 g_assert_cmpstr (gtk_string_filter_get_search (filter), ==, "word");
521
522 gtk_expression_bind (self: expr, target: filter, property: "search", this_: filter);
523 g_assert_cmpstr (gtk_string_filter_get_search (filter), ==, "TRUE");
524
525 g_object_unref (object: filter);
526}
527
528/* Test bind does the right memory management if the target's
529 * dispose() kills the source */
530static void
531test_bind_child (void)
532{
533 GtkStringFilter *filter;
534 GtkFilterListModel *child, *target;
535 GtkExpression *expr;
536
537 expr = gtk_property_expression_new (GTK_TYPE_FILTER_LIST_MODEL,
538 NULL,
539 property_name: "filter");
540
541 filter = gtk_string_filter_new (NULL);
542 child = gtk_filter_list_model_new (NULL, filter: GTK_FILTER (ptr: filter));
543 target = gtk_filter_list_model_new (model: G_LIST_MODEL (ptr: child), NULL);
544
545 gtk_expression_bind (self: expr, target, property: "filter", this_: child);
546 g_assert_true (gtk_filter_list_model_get_filter (child) == gtk_filter_list_model_get_filter (target));
547
548 filter = gtk_string_filter_new (NULL);
549 gtk_filter_list_model_set_filter (self: child, filter: GTK_FILTER (ptr: filter));
550 g_assert_true (GTK_FILTER (filter) == gtk_filter_list_model_get_filter (target));
551 g_assert_true (gtk_filter_list_model_get_filter (child) == gtk_filter_list_model_get_filter (target));
552 g_object_unref (object: filter);
553
554 g_object_unref (object: target);
555}
556
557/* Another test of gtk_expression_bind that exercises the subwatch code paths */
558static void
559test_nested_bind (void)
560{
561 GtkStringFilter *filter;
562 GtkStringFilter *filter2;
563 GtkStringFilter *filter3;
564 GListModel *list;
565 GtkFilterListModel *filtered;
566 GtkExpression *expr;
567 GtkExpression *filter_expr;
568 gboolean res;
569 GValue value = G_VALUE_INIT;
570
571 filter2 = gtk_string_filter_new (NULL);
572 gtk_string_filter_set_search (self: filter2, search: "sausage");
573
574 list = G_LIST_MODEL (ptr: g_list_store_new (G_TYPE_OBJECT));
575 filtered = gtk_filter_list_model_new (model: list, g_object_ref (GTK_FILTER (filter2)));
576
577 filter_expr = gtk_property_expression_new (GTK_TYPE_FILTER_LIST_MODEL,
578 expression: gtk_object_expression_new (G_OBJECT (filtered)),
579 property_name: "filter");
580 expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, expression: gtk_expression_ref (self: filter_expr), property_name: "search");
581
582 filter = gtk_string_filter_new (NULL);
583 gtk_string_filter_set_search (self: filter, search: "word");
584 g_assert_cmpstr (gtk_string_filter_get_search (filter), ==, "word");
585
586 gtk_expression_bind (self: gtk_expression_ref (self: expr), target: filter, property: "search", NULL);
587
588 gtk_string_filter_set_search (self: filter2, search: "sausage");
589 g_assert_cmpstr (gtk_string_filter_get_search (filter), ==, "sausage");
590
591 filter3 = gtk_string_filter_new (NULL);
592 gtk_string_filter_set_search (self: filter3, search: "banana");
593 gtk_filter_list_model_set_filter (self: filtered, filter: GTK_FILTER (ptr: filter3));
594
595 /* check that the expressions evaluate correctly */
596 res = gtk_expression_evaluate (self: filter_expr, NULL, value: &value);
597 g_assert_true (res);
598 g_assert_true (g_value_get_object (&value) == filter3);
599 g_value_unset (value: &value);
600
601 res = gtk_expression_evaluate (self: expr, NULL, value: &value);
602 g_assert_true (res);
603 g_assert_cmpstr (g_value_get_string (&value), ==, "banana");
604 g_value_unset (value: &value);
605
606 /* and the bind too */
607 g_assert_cmpstr (gtk_string_filter_get_search (filter), ==, "banana");
608
609 g_object_unref (object: filter);
610 g_object_unref (object: filter2);
611 g_object_unref (object: filter3);
612 g_object_unref (object: filtered);
613
614 gtk_expression_unref (self: expr);
615 gtk_expression_unref (self: filter_expr);
616}
617
618static char *
619some_cb (gpointer this,
620 const char *search,
621 gboolean ignore_case,
622 gpointer data)
623{
624 if (!search)
625 return NULL;
626
627 if (ignore_case)
628 return g_utf8_strdown (str: search, len: -1);
629 else
630 return g_strdup (str: search);
631}
632
633/* Test that things work as expected when the same object is used multiple times in an
634 * expression or its subexpressions.
635 */
636static void
637test_double_bind (void)
638{
639 GtkStringFilter *filter1;
640 GtkStringFilter *filter2;
641 GtkExpression *expr;
642 GtkExpression *filter_expr;
643 GtkExpression *params[2];
644
645 filter1 = gtk_string_filter_new (NULL);
646 filter2 = gtk_string_filter_new (NULL);
647
648 filter_expr = gtk_object_expression_new (G_OBJECT (filter1));
649
650 params[0] = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, expression: gtk_expression_ref (self: filter_expr), property_name: "search");
651 params[1] = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, expression: gtk_expression_ref (self: filter_expr), property_name: "ignore-case");
652 expr = gtk_cclosure_expression_new (G_TYPE_STRING,
653 NULL,
654 n_params: 2, params,
655 callback_func: (GCallback)some_cb,
656 NULL, NULL);
657
658 gtk_expression_bind (self: gtk_expression_ref (self: expr), target: filter2, property: "search", NULL);
659
660 gtk_string_filter_set_search (self: filter1, search: "Banana");
661 g_assert_cmpstr (gtk_string_filter_get_search (filter2), ==, "banana");
662
663 gtk_string_filter_set_ignore_case (self: filter1, FALSE);
664 g_assert_cmpstr (gtk_string_filter_get_search (filter2), ==, "Banana");
665
666 gtk_expression_unref (self: expr);
667 gtk_expression_unref (self: filter_expr);
668
669 g_object_unref (object: filter1);
670 g_object_unref (object: filter2);
671}
672
673/* Test that having multiple binds on the same object works. */
674static void
675test_binds (void)
676{
677 GtkStringFilter *filter1;
678 GtkStringFilter *filter2;
679 GtkStringFilter *filter3;
680 GtkExpression *expr;
681 GtkExpression *expr2;
682 GtkExpression *filter1_expr;
683 GtkExpression *filter2_expr;
684 GtkExpression *params[2];
685
686 filter1 = gtk_string_filter_new (NULL);
687 filter2 = gtk_string_filter_new (NULL);
688 filter3 = gtk_string_filter_new (NULL);
689
690 filter1_expr = gtk_object_expression_new (G_OBJECT (filter1));
691 filter2_expr = gtk_object_expression_new (G_OBJECT (filter2));
692
693 params[0] = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, expression: gtk_expression_ref (self: filter1_expr), property_name: "search");
694 params[1] = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, expression: gtk_expression_ref (self: filter2_expr), property_name: "ignore-case");
695 expr = gtk_cclosure_expression_new (G_TYPE_STRING,
696 NULL,
697 n_params: 2, params,
698 callback_func: (GCallback)some_cb,
699 NULL, NULL);
700
701 expr2 = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, expression: gtk_expression_ref (self: filter2_expr), property_name: "ignore-case");
702
703 g_assert_true (gtk_property_expression_get_expression (expr2) == filter2_expr);
704 g_assert_cmpstr (gtk_property_expression_get_pspec (expr2)->name, ==, "ignore-case");
705
706 gtk_expression_bind (self: gtk_expression_ref (self: expr), target: filter3, property: "search", NULL);
707 gtk_expression_bind (self: gtk_expression_ref (self: expr2), target: filter3, property: "ignore-case", NULL);
708
709 gtk_string_filter_set_search (self: filter1, search: "Banana");
710 g_assert_cmpstr (gtk_string_filter_get_search (filter3), ==, "banana");
711 g_assert_true (gtk_string_filter_get_ignore_case (filter3));
712
713 gtk_string_filter_set_ignore_case (self: filter2, FALSE);
714 g_assert_cmpstr (gtk_string_filter_get_search (filter3), ==, "Banana");
715 g_assert_false (gtk_string_filter_get_ignore_case (filter3));
716
717 /* invalidate the first bind */
718 g_object_unref (object: filter1);
719
720 gtk_string_filter_set_ignore_case (self: filter2, TRUE);
721 g_assert_cmpstr (gtk_string_filter_get_search (filter3), ==, "Banana");
722 g_assert_true (gtk_string_filter_get_ignore_case (filter3));
723
724 gtk_expression_unref (self: expr);
725 gtk_expression_unref (self: expr2);
726 gtk_expression_unref (self: filter1_expr);
727 gtk_expression_unref (self: filter2_expr);
728
729 g_object_unref (object: filter2);
730 g_object_unref (object: filter3);
731}
732
733/* test that binds work ok with object expressions */
734static void
735test_bind_object (void)
736{
737 GtkStringFilter *filter;
738 GListStore *store;
739 GtkFilterListModel *model;
740 GtkExpression *expr;
741
742 filter = gtk_string_filter_new (NULL);
743 store = g_list_store_new (G_TYPE_OBJECT);
744 model = gtk_filter_list_model_new (model: G_LIST_MODEL (ptr: store), NULL);
745
746 expr = gtk_object_expression_new (G_OBJECT (filter));
747
748 gtk_expression_bind (self: gtk_expression_ref (self: expr), target: model, property: "filter", NULL);
749
750 g_assert_true (gtk_filter_list_model_get_filter (model) == GTK_FILTER (filter));
751
752 g_object_unref (object: filter);
753
754 g_assert_true (gtk_filter_list_model_get_filter (model) == GTK_FILTER (filter));
755
756 gtk_expression_unref (self: expr);
757 g_object_unref (object: model);
758}
759
760static void
761test_value (void)
762{
763 GValue value = G_VALUE_INIT;
764 GtkExpression *expr;
765
766 expr = gtk_constant_expression_new (G_TYPE_INT, 22);
767
768 g_value_init (value: &value, GTK_TYPE_EXPRESSION);
769 gtk_value_take_expression (value: &value, expression: expr);
770 g_assert_true (G_VALUE_TYPE (&value) == GTK_TYPE_EXPRESSION);
771
772 expr = gtk_value_dup_expression (value: &value);
773 gtk_expression_unref (self: expr);
774
775 expr = gtk_constant_expression_new (G_TYPE_INT, 23);
776 gtk_value_set_expression (value: &value, expression: expr);
777 gtk_expression_unref (self: expr);
778
779 g_value_unset (value: &value);
780}
781
782int
783main (int argc, char *argv[])
784{
785 gtk_test_init (argcp: &argc, argvp: &argv, NULL);
786 setlocale (LC_ALL, locale: "C");
787
788 g_test_add_func (testpath: "/expression/property", test_func: test_property);
789 g_test_add_func (testpath: "/expression/interface-property", test_func: test_interface_property);
790 g_test_add_func (testpath: "/expression/cclosure", test_func: test_cclosure);
791 g_test_add_func (testpath: "/expression/closure", test_func: test_closure);
792 g_test_add_func (testpath: "/expression/constant", test_func: test_constant);
793 g_test_add_func (testpath: "/expression/constant-watch-this-destroyed", test_func: test_constant_watch_this_destroyed);
794 g_test_add_func (testpath: "/expression/object", test_func: test_object);
795 g_test_add_func (testpath: "/expression/nested", test_func: test_nested);
796 g_test_add_func (testpath: "/expression/nested-this-destroyed", test_func: test_nested_this_destroyed);
797 g_test_add_func (testpath: "/expression/type-mismatch", test_func: test_type_mismatch);
798 g_test_add_func (testpath: "/expression/this", test_func: test_this);
799 g_test_add_func (testpath: "/expression/bind", test_func: test_bind);
800 g_test_add_func (testpath: "/expression/bind-self", test_func: test_bind_self);
801 g_test_add_func (testpath: "/expression/bind-child", test_func: test_bind_child);
802 g_test_add_func (testpath: "/expression/nested-bind", test_func: test_nested_bind);
803 g_test_add_func (testpath: "/expression/double-bind", test_func: test_double_bind);
804 g_test_add_func (testpath: "/expression/binds", test_func: test_binds);
805 g_test_add_func (testpath: "/expression/bind-object", test_func: test_bind_object);
806 g_test_add_func (testpath: "/expression/value", test_func: test_value);
807
808 return g_test_run ();
809}
810

source code of gtk/testsuite/gtk/expression.c