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 
24 static void
inc_counter(gpointer data)25 inc_counter (gpointer data)
26 {
27   guint *counter = data;
28 
29   *counter += 1;
30 }
31 
32 static void
test_property(void)33 test_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, "search");
44   watch = gtk_expression_watch (expr, filter, inc_counter, &counter, NULL);
45 
46   ret = gtk_expression_evaluate (expr, filter, &value);
47   g_assert_true (ret);
48   g_assert_cmpstr (g_value_get_string (&value), ==, NULL);
49   g_value_unset (&value);
50 
51   gtk_string_filter_set_search (filter, "Hello World");
52   g_assert_cmpint (counter, ==, 1);
53   counter = 0;
54 
55   ret = gtk_expression_evaluate (expr, filter , &value);
56   g_assert_true (ret);
57   g_assert_cmpstr (g_value_get_string (&value), ==, "Hello World");
58   g_value_unset (&value);
59 
60   gtk_expression_watch_unwatch (watch);
61   g_assert_cmpint (counter, ==, 0);
62 
63   gtk_expression_unref (expr);
64   g_object_unref (filter);
65 }
66 
67 static void
test_interface_property(void)68 test_interface_property (void)
69 {
70   GtkExpression *expr;
71 
72   expr = gtk_property_expression_new (GTK_TYPE_ORIENTABLE, NULL, "orientation");
73   g_assert_cmpstr (gtk_property_expression_get_pspec (expr)->name, ==, "orientation");
74   gtk_expression_unref (expr);
75 }
76 
77 static char *
print_filter_info(GtkStringFilter * filter,const char * search,gboolean ignore_case,GtkStringFilterMatchMode match_mode)78 print_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 ("OK");
88 }
89 
90 static void
test_cclosure(void)91 test_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 (gtk_string_filter_new (NULL));
101   pexpr[0] = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, NULL, "search");
102   pexpr[1] = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, NULL, "ignore-case");
103   pexpr[2] = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, NULL, "match-mode");
104   expr = gtk_cclosure_expression_new (G_TYPE_STRING,
105                                       NULL,
106                                       3,
107                                       pexpr,
108                                       G_CALLBACK (print_filter_info),
109                                       NULL,
110                                       NULL);
111   watch = gtk_expression_watch (expr, filter, inc_counter, &counter, NULL);
112 
113   ret = gtk_expression_evaluate (expr, filter, &value);
114   g_assert_true (ret);
115   g_assert_cmpstr (g_value_get_string (&value), ==, "OK");
116   g_value_unset (&value);
117 
118   gtk_string_filter_set_search (filter, "Hello World");
119   g_assert_cmpint (counter, ==, 1);
120   ret = gtk_expression_evaluate (expr, filter , &value);
121   g_assert_true (ret);
122   g_assert_cmpstr (g_value_get_string (&value), ==, "OK");
123   g_value_unset (&value);
124 
125   gtk_string_filter_set_ignore_case (filter, FALSE);
126   g_assert_cmpint (counter, ==, 2);
127   ret = gtk_expression_evaluate (expr, filter , &value);
128   g_assert_true (ret);
129   g_assert_cmpstr (g_value_get_string (&value), ==, "OK");
130   g_value_unset (&value);
131 
132   gtk_string_filter_set_search (filter, "Hello");
133   gtk_string_filter_set_ignore_case (filter, TRUE);
134   gtk_string_filter_set_match_mode (filter, GTK_STRING_FILTER_MATCH_MODE_EXACT);
135   g_assert_cmpint (counter, ==, 5);
136   ret = gtk_expression_evaluate (expr, filter , &value);
137   g_assert_true (ret);
138   g_assert_cmpstr (g_value_get_string (&value), ==, "OK");
139   g_value_unset (&value);
140 
141   gtk_expression_watch_unwatch (watch);
142   g_assert_cmpint (counter, ==, 5);
143 
144   gtk_expression_unref (expr);
145   g_object_unref (filter);
146 }
147 
148 static char *
make_string(void)149 make_string (void)
150 {
151   return g_strdup ("Hello");
152 }
153 
154 static void
test_closure(void)155 test_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, 0, NULL);
164   ret = gtk_expression_evaluate (expr, NULL, &value);
165   g_assert_true (ret);
166   g_assert_cmpstr (g_value_get_string (&value), ==, "Hello");
167   g_value_unset (&value);
168 
169   gtk_expression_unref (expr);
170 }
171 
172 static void
test_constant(void)173 test_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 (expr, NULL, &value);
185   g_assert_true (res);
186   g_assert_cmpint (g_value_get_int (&value), ==, 22);
187 
188   v = gtk_constant_expression_get_value (expr);
189   g_assert_cmpint (g_value_get_int (v), ==, 22);
190 
191   gtk_expression_unref (expr);
192 }
193 
194 /* Test that object expressions fail to evaluate when
195  * the object is gone.
196  */
197 static void
test_object(void)198 test_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 (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 (expr, NULL, &value);
213   g_assert_true (res);
214   g_assert_true (g_value_get_object (&value) == obj);
215   g_value_unset (&value);
216 
217   o = gtk_object_expression_get_object (expr);
218   g_assert_true (o == obj);
219 
220   g_clear_object (&obj);
221   res = gtk_expression_evaluate (expr, NULL, &value);
222   g_assert_false (res);
223 
224   gtk_expression_unref (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  */
239 static void
test_nested(void)240 test_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 (filter, "word");
255   list = G_LIST_MODEL (g_list_store_new (G_TYPE_OBJECT));
256   filtered = gtk_filter_list_model_new (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, list_expr, "filter");
260   expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, filter_expr, "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 (expr, NULL, &value);
266   g_assert_true (res);
267   g_assert_cmpstr (g_value_get_string (&value), ==, "word");
268   g_value_unset (&value);
269 
270   watch = gtk_expression_watch (expr, NULL, inc_counter, &counter, NULL);
271   gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "salad");
272   g_assert_cmpint (counter, ==, 1);
273   counter = 0;
274 
275   res = gtk_expression_evaluate (expr, NULL, &value);
276   g_assert_true (res);
277   g_assert_cmpstr (g_value_get_string (&value), ==, "salad");
278   g_value_unset (&value);
279 
280   gtk_filter_list_model_set_filter (filtered, GTK_FILTER (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 (filter, "salad");
286   gtk_filter_list_model_set_filter (filtered, GTK_FILTER (filter));
287   g_assert_cmpint (counter, ==, 1);
288   counter = 0;
289 
290   res = gtk_expression_evaluate (expr, NULL, &value);
291   g_assert_true (res);
292   g_assert_cmpstr (g_value_get_string (&value), ==, "salad");
293   g_value_unset (&value);
294 
295   gtk_string_filter_set_search (filter, "bar");
296   g_assert_cmpint (counter, ==, 1);
297   counter = 0;
298 
299   res = gtk_expression_evaluate (expr, NULL, &value);
300   g_assert_true (res);
301   g_assert_cmpstr (g_value_get_string (&value), ==, "bar");
302   g_value_unset (&value);
303 
304   gtk_filter_list_model_set_filter (filtered, NULL);
305   g_assert_cmpint (counter, ==, 1);
306   counter = 0;
307 
308   res = gtk_expression_evaluate (expr, NULL, &value);
309   g_assert_false (res);
310 
311   gtk_expression_watch_unwatch (watch);
312   g_assert_cmpint (counter, ==, 0);
313 
314   g_object_unref (filtered);
315   gtk_expression_unref (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  */
326 static void
test_nested_this_destroyed(void)327 test_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 (filter, "word");
342   list = G_LIST_MODEL (g_list_store_new (G_TYPE_OBJECT));
343   filtered = gtk_filter_list_model_new (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, list_expr, "filter");
347   expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, filter_expr, "search");
348 
349   watch = gtk_expression_watch (expr, filter, inc_counter, &counter, NULL);
350   gtk_expression_watch_ref (watch);
351   res = gtk_expression_watch_evaluate (watch, &value);
352   g_assert_true (res);
353   g_assert_cmpstr (g_value_get_string (&value), ==, "word");
354   g_value_unset (&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 (filter, "salad");
361   gtk_filter_list_model_set_filter (filtered, GTK_FILTER (filter));
362   g_assert_cmpint (counter, ==, 1);
363   counter = 0;
364 
365   res = gtk_expression_watch_evaluate (watch, &value);
366   g_assert_false (res);
367 
368   gtk_string_filter_set_search (filter, "bar");
369   g_assert_cmpint (counter, ==, 0);
370 
371   gtk_filter_list_model_set_filter (filtered, NULL);
372   g_assert_cmpint (counter, ==, 0);
373 
374   res = gtk_expression_watch_evaluate (watch, &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 (filtered);
384   g_object_unref (filter);
385   gtk_expression_unref (expr);
386 }
387 
388 /* Test that property expressions fail to evaluate if the
389  * expression evaluates to an object of the wrong type
390  */
391 static void
test_type_mismatch(void)392 test_type_mismatch (void)
393 {
394   GtkFilter *filter;
395   GtkExpression *expr;
396   GValue value = G_VALUE_INIT;
397   gboolean res;
398 
399   filter = GTK_FILTER (gtk_any_filter_new ());
400 
401   expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, gtk_constant_expression_new (GTK_TYPE_ANY_FILTER, filter), "search");
402 
403   res = gtk_expression_evaluate (expr, NULL, &value);
404   g_assert_false (res);
405   g_assert_false (G_IS_VALUE (&value));
406 
407   gtk_expression_unref (expr);
408   g_object_unref (filter);
409 }
410 
411 /* Some basic tests around 'this' */
412 static void
test_this(void)413 test_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, "search");
422 
423   filter = gtk_string_filter_new (NULL);
424   gtk_string_filter_set_search (filter, "word");
425 
426   filter2 = gtk_string_filter_new (NULL);
427   gtk_string_filter_set_search (filter2, "sausage");
428 
429   res = gtk_expression_evaluate (expr, filter, &value);
430   g_assert_true (res);
431   g_assert_cmpstr (g_value_get_string (&value), ==, "word");
432   g_value_unset (&value);
433 
434   res = gtk_expression_evaluate (expr, filter2, &value);
435   g_assert_true (res);
436   g_assert_cmpstr (g_value_get_string (&value), ==, "sausage");
437   g_value_unset (&value);
438 
439   gtk_expression_unref (expr);
440   g_object_unref (filter2);
441   g_object_unref (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  */
448 static void
test_constant_watch_this_destroyed(void)449 test_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 (expr, this, inc_counter, &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 (expr);
464 }
465 
466 /* Basic test of gtk_expression_bind */
467 static void
test_bind(void)468 test_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, "search");
478 
479   target = gtk_string_filter_new (NULL);
480   gtk_string_filter_set_search (target, "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 (source, "sausage");
485 
486   watch = gtk_expression_bind (expr, target, "search", 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 (source, "salad");
491   g_assert_cmpstr (gtk_string_filter_get_search (target), ==, "salad");
492   res = gtk_expression_watch_evaluate (watch, &value);
493   g_assert_true (res);
494   g_assert_cmpstr (g_value_get_string (&value), ==, "salad");
495   g_value_unset (&value);
496 
497   g_object_unref (source);
498   g_assert_cmpstr (gtk_string_filter_get_search (target), ==, "salad");
499   res = gtk_expression_watch_evaluate (watch, &value);
500   g_assert_false (res);
501   g_assert_false (G_IS_VALUE (&value));
502 
503   g_object_unref (target);
504   gtk_expression_watch_unref (watch);
505 }
506 
507 /* Another test of bind, this time we watch ourselves */
508 static void
test_bind_self(void)509 test_bind_self (void)
510 {
511   GtkStringFilter *filter;
512   GtkExpression *expr;
513 
514   expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER,
515                                       NULL,
516                                       "ignore-case");
517 
518   filter = gtk_string_filter_new (NULL);
519   gtk_string_filter_set_search (filter, "word");
520   g_assert_cmpstr (gtk_string_filter_get_search (filter), ==, "word");
521 
522   gtk_expression_bind (expr, filter, "search", filter);
523   g_assert_cmpstr (gtk_string_filter_get_search (filter), ==, "TRUE");
524 
525   g_object_unref (filter);
526 }
527 
528 /* Test bind does the right memory management if the target's
529  * dispose() kills the source */
530 static void
test_bind_child(void)531 test_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                                       "filter");
540 
541   filter = gtk_string_filter_new (NULL);
542   child = gtk_filter_list_model_new (NULL, GTK_FILTER (filter));
543   target = gtk_filter_list_model_new (G_LIST_MODEL (child), NULL);
544 
545   gtk_expression_bind (expr, target, "filter", 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 (child, GTK_FILTER (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 (filter);
553 
554   g_object_unref (target);
555 }
556 
557 /* Another test of gtk_expression_bind that exercises the subwatch code paths */
558 static void
test_nested_bind(void)559 test_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 (filter2, "sausage");
573 
574   list = G_LIST_MODEL (g_list_store_new (G_TYPE_OBJECT));
575   filtered = gtk_filter_list_model_new (list, g_object_ref (GTK_FILTER (filter2)));
576 
577   filter_expr = gtk_property_expression_new (GTK_TYPE_FILTER_LIST_MODEL,
578                                              gtk_object_expression_new (G_OBJECT (filtered)),
579                                              "filter");
580   expr = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, gtk_expression_ref (filter_expr), "search");
581 
582   filter = gtk_string_filter_new (NULL);
583   gtk_string_filter_set_search (filter, "word");
584   g_assert_cmpstr (gtk_string_filter_get_search (filter), ==, "word");
585 
586   gtk_expression_bind (gtk_expression_ref (expr), filter, "search", NULL);
587 
588   gtk_string_filter_set_search (filter2, "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 (filter3, "banana");
593   gtk_filter_list_model_set_filter (filtered, GTK_FILTER (filter3));
594 
595   /* check that the expressions evaluate correctly */
596   res = gtk_expression_evaluate (filter_expr, NULL, &value);
597   g_assert_true (res);
598   g_assert_true (g_value_get_object (&value) == filter3);
599   g_value_unset (&value);
600 
601   res = gtk_expression_evaluate (expr, NULL, &value);
602   g_assert_true (res);
603   g_assert_cmpstr (g_value_get_string (&value), ==, "banana");
604   g_value_unset (&value);
605 
606   /* and the bind too */
607   g_assert_cmpstr (gtk_string_filter_get_search (filter), ==, "banana");
608 
609   g_object_unref (filter);
610   g_object_unref (filter2);
611   g_object_unref (filter3);
612   g_object_unref (filtered);
613 
614   gtk_expression_unref (expr);
615   gtk_expression_unref (filter_expr);
616 }
617 
618 static char *
some_cb(gpointer this,const char * search,gboolean ignore_case,gpointer data)619 some_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 (search, -1);
629   else
630     return g_strdup (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  */
636 static void
test_double_bind(void)637 test_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, gtk_expression_ref (filter_expr), "search");
651   params[1] = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, gtk_expression_ref (filter_expr), "ignore-case");
652   expr = gtk_cclosure_expression_new (G_TYPE_STRING,
653                                       NULL,
654                                       2, params,
655                                       (GCallback)some_cb,
656                                       NULL, NULL);
657 
658   gtk_expression_bind (gtk_expression_ref (expr), filter2, "search", NULL);
659 
660   gtk_string_filter_set_search (filter1, "Banana");
661   g_assert_cmpstr (gtk_string_filter_get_search (filter2), ==, "banana");
662 
663   gtk_string_filter_set_ignore_case (filter1, FALSE);
664   g_assert_cmpstr (gtk_string_filter_get_search (filter2), ==, "Banana");
665 
666   gtk_expression_unref (expr);
667   gtk_expression_unref (filter_expr);
668 
669   g_object_unref (filter1);
670   g_object_unref (filter2);
671 }
672 
673 /* Test that having multiple binds on the same object works. */
674 static void
test_binds(void)675 test_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, gtk_expression_ref (filter1_expr), "search");
694   params[1] = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, gtk_expression_ref (filter2_expr), "ignore-case");
695   expr = gtk_cclosure_expression_new (G_TYPE_STRING,
696                                       NULL,
697                                       2, params,
698                                       (GCallback)some_cb,
699                                       NULL, NULL);
700 
701   expr2 = gtk_property_expression_new (GTK_TYPE_STRING_FILTER, gtk_expression_ref (filter2_expr), "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 (gtk_expression_ref (expr), filter3, "search", NULL);
707   gtk_expression_bind (gtk_expression_ref (expr2), filter3, "ignore-case", NULL);
708 
709   gtk_string_filter_set_search (filter1, "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 (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 (filter1);
719 
720   gtk_string_filter_set_ignore_case (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 (expr);
725   gtk_expression_unref (expr2);
726   gtk_expression_unref (filter1_expr);
727   gtk_expression_unref (filter2_expr);
728 
729   g_object_unref (filter2);
730   g_object_unref (filter3);
731 }
732 
733 /* test that binds work ok with object expressions */
734 static void
test_bind_object(void)735 test_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 (G_LIST_MODEL (store), NULL);
745 
746   expr = gtk_object_expression_new (G_OBJECT (filter));
747 
748   gtk_expression_bind (gtk_expression_ref (expr), model, "filter", NULL);
749 
750   g_assert_true (gtk_filter_list_model_get_filter (model) == GTK_FILTER (filter));
751 
752   g_object_unref (filter);
753 
754   g_assert_true (gtk_filter_list_model_get_filter (model) == GTK_FILTER (filter));
755 
756   gtk_expression_unref (expr);
757   g_object_unref (model);
758 }
759 
760 static void
test_value(void)761 test_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, GTK_TYPE_EXPRESSION);
769   gtk_value_take_expression (&value, expr);
770   g_assert_true (G_VALUE_TYPE (&value) == GTK_TYPE_EXPRESSION);
771 
772   expr = gtk_value_dup_expression (&value);
773   gtk_expression_unref (expr);
774 
775   expr = gtk_constant_expression_new (G_TYPE_INT, 23);
776   gtk_value_set_expression (&value, expr);
777   gtk_expression_unref (expr);
778 
779   g_value_unset (&value);
780 }
781 
782 int
main(int argc,char * argv[])783 main (int argc, char *argv[])
784 {
785   gtk_test_init (&argc, &argv, NULL);
786   setlocale (LC_ALL, "C");
787 
788   g_test_add_func ("/expression/property", test_property);
789   g_test_add_func ("/expression/interface-property", test_interface_property);
790   g_test_add_func ("/expression/cclosure", test_cclosure);
791   g_test_add_func ("/expression/closure", test_closure);
792   g_test_add_func ("/expression/constant", test_constant);
793   g_test_add_func ("/expression/constant-watch-this-destroyed", test_constant_watch_this_destroyed);
794   g_test_add_func ("/expression/object", test_object);
795   g_test_add_func ("/expression/nested", test_nested);
796   g_test_add_func ("/expression/nested-this-destroyed", test_nested_this_destroyed);
797   g_test_add_func ("/expression/type-mismatch", test_type_mismatch);
798   g_test_add_func ("/expression/this", test_this);
799   g_test_add_func ("/expression/bind", test_bind);
800   g_test_add_func ("/expression/bind-self", test_bind_self);
801   g_test_add_func ("/expression/bind-child", test_bind_child);
802   g_test_add_func ("/expression/nested-bind", test_nested_bind);
803   g_test_add_func ("/expression/double-bind", test_double_bind);
804   g_test_add_func ("/expression/binds", test_binds);
805   g_test_add_func ("/expression/bind-object", test_bind_object);
806   g_test_add_func ("/expression/value", test_value);
807 
808   return g_test_run ();
809 }
810