Rename GMenuProxy to GDBusMenuModel
[platform/upstream/glib.git] / gio / tests / gmenumodel.c
1 #include <gio/gio.h>
2
3 /* TestItem {{{1 */
4
5 /* This utility struct is used by both the RandomMenu and MirrorMenu
6  * class implementations below.
7  */
8 typedef struct {
9   GHashTable *attributes;
10   GHashTable *links;
11 } TestItem;
12
13 static TestItem *
14 test_item_new (GHashTable *attributes,
15                GHashTable *links)
16 {
17   TestItem *item;
18
19   item = g_slice_new (TestItem);
20   item->attributes = g_hash_table_ref (attributes);
21   item->links = g_hash_table_ref (links);
22
23   return item;
24 }
25
26 static void
27 test_item_free (gpointer data)
28 {
29   TestItem *item = data;
30
31   g_hash_table_unref (item->attributes);
32   g_hash_table_unref (item->links);
33
34   g_slice_free (TestItem, item);
35 }
36
37 /* RandomMenu {{{1 */
38 #define MAX_ITEMS 5
39 #define TOP_ORDER 4
40
41 typedef struct {
42   GMenuModel parent_instance;
43
44   GSequence *items;
45   gint order;
46 } RandomMenu;
47
48 typedef GMenuModelClass RandomMenuClass;
49
50 static GType random_menu_get_type (void);
51 G_DEFINE_TYPE (RandomMenu, random_menu, G_TYPE_MENU_MODEL);
52
53 static gboolean
54 random_menu_is_mutable (GMenuModel *model)
55 {
56   return TRUE;
57 }
58
59 static gint
60 random_menu_get_n_items (GMenuModel *model)
61 {
62   RandomMenu *menu = (RandomMenu *) model;
63
64   return g_sequence_get_length (menu->items);
65 }
66
67 static void
68 random_menu_get_item_attributes (GMenuModel  *model,
69                                  gint         position,
70                                  GHashTable **table)
71 {
72   RandomMenu *menu = (RandomMenu *) model;
73   TestItem *item;
74
75   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
76   *table = g_hash_table_ref (item->attributes);
77 }
78
79 static void
80 random_menu_get_item_links (GMenuModel  *model,
81                             gint         position,
82                             GHashTable **table)
83 {
84   RandomMenu *menu = (RandomMenu *) model;
85   TestItem *item;
86
87   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
88   *table = g_hash_table_ref (item->links);
89 }
90
91 static void
92 random_menu_finalize (GObject *object)
93 {
94   RandomMenu *menu = (RandomMenu *) object;
95
96   g_sequence_free (menu->items);
97
98   G_OBJECT_CLASS (random_menu_parent_class)
99     ->finalize (object);
100 }
101
102 static void
103 random_menu_init (RandomMenu *menu)
104 {
105 }
106
107 static void
108 random_menu_class_init (GMenuModelClass *class)
109 {
110   GObjectClass *object_class = G_OBJECT_CLASS (class);
111
112   class->is_mutable = random_menu_is_mutable;
113   class->get_n_items = random_menu_get_n_items;
114   class->get_item_attributes = random_menu_get_item_attributes;
115   class->get_item_links = random_menu_get_item_links;
116
117   object_class->finalize = random_menu_finalize;
118 }
119
120 static RandomMenu * random_menu_new (GRand *rand, gint order);
121
122 static void
123 random_menu_change (RandomMenu *menu,
124                     GRand      *rand)
125 {
126   gint position, removes, adds;
127   GSequenceIter *point;
128   gint n_items;
129   gint i;
130
131   n_items = g_sequence_get_length (menu->items);
132
133   do
134     {
135       position = g_rand_int_range (rand, 0, n_items + 1);
136       removes = g_rand_int_range (rand, 0, n_items - position + 1);
137       adds = g_rand_int_range (rand, 0, MAX_ITEMS - (n_items - removes) + 1);
138     }
139   while (removes == 0 && adds == 0);
140
141   point = g_sequence_get_iter_at_pos (menu->items, position + removes);
142
143   if (removes)
144     {
145       GSequenceIter *start;
146
147       start = g_sequence_get_iter_at_pos (menu->items, position);
148       g_sequence_remove_range (start, point);
149     }
150
151   for (i = 0; i < adds; i++)
152     {
153       const gchar *label;
154       GHashTable *links;
155       GHashTable *attributes;
156
157       attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
158       links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref);
159
160       if (menu->order > 0 && g_rand_boolean (rand))
161         {
162           RandomMenu *child;
163           const gchar *subtype;
164
165           child = random_menu_new (rand, menu->order - 1);
166
167           if (g_rand_boolean (rand))
168             {
169               subtype = G_MENU_LINK_SECTION;
170               /* label some section headers */
171               if (g_rand_boolean (rand))
172                 label = "Section";
173               else
174                 label = NULL;
175             }
176           else
177             {
178               /* label all submenus */
179               subtype = G_MENU_LINK_SUBMENU;
180               label = "Submenu";
181             }
182
183           g_hash_table_insert (links, g_strdup (subtype), child);
184         }
185       else
186         /* label all terminals */
187         label = "Menu Item";
188
189       if (label)
190         g_hash_table_insert (attributes, g_strdup ("label"), g_variant_ref_sink (g_variant_new_string (label)));
191
192       g_sequence_insert_before (point, test_item_new (attributes, links));
193       g_hash_table_unref (links);
194       g_hash_table_unref (attributes);
195     }
196
197   g_menu_model_items_changed (G_MENU_MODEL (menu), position, removes, adds);
198 }
199
200 static RandomMenu *
201 random_menu_new (GRand *rand,
202                  gint   order)
203 {
204   RandomMenu *menu;
205
206   menu = g_object_new (random_menu_get_type (), NULL);
207   menu->items = g_sequence_new (test_item_free);
208   menu->order = order;
209
210   random_menu_change (menu, rand);
211
212   return menu;
213 }
214
215 /* MirrorMenu {{{1 */
216 typedef struct {
217   GMenuModel parent_instance;
218
219   GMenuModel *clone_of;
220   GSequence *items;
221   gulong handler_id;
222 } MirrorMenu;
223
224 typedef GMenuModelClass MirrorMenuClass;
225
226 static GType mirror_menu_get_type (void);
227 G_DEFINE_TYPE (MirrorMenu, mirror_menu, G_TYPE_MENU_MODEL);
228
229 static gboolean
230 mirror_menu_is_mutable (GMenuModel *model)
231 {
232   MirrorMenu *menu = (MirrorMenu *) model;
233
234   return menu->handler_id != 0;
235 }
236
237 static gint
238 mirror_menu_get_n_items (GMenuModel *model)
239 {
240   MirrorMenu *menu = (MirrorMenu *) model;
241
242   return g_sequence_get_length (menu->items);
243 }
244
245 static void
246 mirror_menu_get_item_attributes (GMenuModel  *model,
247                                  gint         position,
248                                  GHashTable **table)
249 {
250   MirrorMenu *menu = (MirrorMenu *) model;
251   TestItem *item;
252
253   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
254   *table = g_hash_table_ref (item->attributes);
255 }
256
257 static void
258 mirror_menu_get_item_links (GMenuModel  *model,
259                             gint         position,
260                             GHashTable **table)
261 {
262   MirrorMenu *menu = (MirrorMenu *) model;
263   TestItem *item;
264
265   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
266   *table = g_hash_table_ref (item->links);
267 }
268
269 static void
270 mirror_menu_finalize (GObject *object)
271 {
272   MirrorMenu *menu = (MirrorMenu *) object;
273
274   if (menu->handler_id)
275     g_signal_handler_disconnect (menu->clone_of, menu->handler_id);
276
277   g_sequence_free (menu->items);
278   g_object_unref (menu->clone_of);
279
280   G_OBJECT_CLASS (mirror_menu_parent_class)
281     ->finalize (object);
282 }
283
284 static void
285 mirror_menu_init (MirrorMenu *menu)
286 {
287 }
288
289 static void
290 mirror_menu_class_init (GMenuModelClass *class)
291 {
292   GObjectClass *object_class = G_OBJECT_CLASS (class);
293
294   class->is_mutable = mirror_menu_is_mutable;
295   class->get_n_items = mirror_menu_get_n_items;
296   class->get_item_attributes = mirror_menu_get_item_attributes;
297   class->get_item_links = mirror_menu_get_item_links;
298
299   object_class->finalize = mirror_menu_finalize;
300 }
301
302 static MirrorMenu * mirror_menu_new (GMenuModel *clone_of);
303
304 static void
305 mirror_menu_changed (GMenuModel *model,
306                      gint        position,
307                      gint        removed,
308                      gint        added,
309                      gpointer    user_data)
310 {
311   MirrorMenu *menu = user_data;
312   GSequenceIter *point;
313   gint i;
314
315   g_assert (model == menu->clone_of);
316
317   point = g_sequence_get_iter_at_pos (menu->items, position + removed);
318
319   if (removed)
320     {
321       GSequenceIter *start;
322
323       start = g_sequence_get_iter_at_pos (menu->items, position);
324       g_sequence_remove_range (start, point);
325     }
326
327   for (i = position; i < position + added; i++)
328     {
329       GMenuAttributeIter *attr_iter;
330       GMenuLinkIter *link_iter;
331       GHashTable *links;
332       GHashTable *attributes;
333       const gchar *name;
334       GMenuModel *child;
335       GVariant *value;
336
337       attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
338       links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref);
339
340       attr_iter = g_menu_model_iterate_item_attributes (model, i);
341       while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
342         {
343           g_hash_table_insert (attributes, g_strdup (name), value);
344         }
345       g_object_unref (attr_iter);
346
347       link_iter = g_menu_model_iterate_item_links (model, i);
348       while (g_menu_link_iter_get_next (link_iter, &name, &child))
349         {
350           g_hash_table_insert (links, g_strdup (name), mirror_menu_new (child));
351           g_object_unref (child);
352         }
353       g_object_unref (link_iter);
354
355       g_sequence_insert_before (point, test_item_new (attributes, links));
356       g_hash_table_unref (attributes);
357       g_hash_table_unref (links);
358     }
359
360   g_menu_model_items_changed (G_MENU_MODEL (menu), position, removed, added);
361 }
362
363 static MirrorMenu *
364 mirror_menu_new (GMenuModel *clone_of)
365 {
366   MirrorMenu *menu;
367
368   menu = g_object_new (mirror_menu_get_type (), NULL);
369   menu->items = g_sequence_new (test_item_free);
370   menu->clone_of = g_object_ref (clone_of);
371
372   if (g_menu_model_is_mutable (clone_of))
373     menu->handler_id = g_signal_connect (clone_of, "items-changed", G_CALLBACK (mirror_menu_changed), menu);
374   mirror_menu_changed (clone_of, 0, 0, g_menu_model_get_n_items (clone_of), menu);
375
376   return menu;
377 }
378
379 /* check_menus_equal(), assert_menus_equal() {{{1 */
380 static gboolean
381 check_menus_equal (GMenuModel *a,
382                    GMenuModel *b)
383 {
384   gboolean equal = TRUE;
385   gint a_n, b_n;
386   gint i;
387
388   a_n = g_menu_model_get_n_items (a);
389   b_n = g_menu_model_get_n_items (b);
390
391   if (a_n != b_n)
392     return FALSE;
393
394   for (i = 0; i < a_n; i++)
395     {
396       GMenuAttributeIter *attr_iter;
397       GVariant *a_value, *b_value;
398       GMenuLinkIter *link_iter;
399       GMenuModel *a_menu, *b_menu;
400       const gchar *name;
401
402       attr_iter = g_menu_model_iterate_item_attributes (a, i);
403       while (g_menu_attribute_iter_get_next (attr_iter, &name, &a_value))
404         {
405           b_value = g_menu_model_get_item_attribute_value (b, i, name, NULL);
406           equal &= b_value && g_variant_equal (a_value, b_value);
407           if (b_value)
408             g_variant_unref (b_value);
409           g_variant_unref (a_value);
410         }
411       g_object_unref (attr_iter);
412
413       attr_iter = g_menu_model_iterate_item_attributes (b, i);
414       while (g_menu_attribute_iter_get_next (attr_iter, &name, &b_value))
415         {
416           a_value = g_menu_model_get_item_attribute_value (a, i, name, NULL);
417           equal &= a_value && g_variant_equal (a_value, b_value);
418           if (a_value)
419             g_variant_unref (a_value);
420           g_variant_unref (b_value);
421         }
422       g_object_unref (attr_iter);
423
424       link_iter = g_menu_model_iterate_item_links (a, i);
425       while (g_menu_link_iter_get_next (link_iter, &name, &a_menu))
426         {
427           b_menu = g_menu_model_get_item_link (b, i, name);
428           equal &= b_menu && check_menus_equal (a_menu, b_menu);
429           if (b_menu)
430             g_object_unref (b_menu);
431           g_object_unref (a_menu);
432         }
433       g_object_unref (link_iter);
434
435       link_iter = g_menu_model_iterate_item_links (b, i);
436       while (g_menu_link_iter_get_next (link_iter, &name, &b_menu))
437         {
438           a_menu = g_menu_model_get_item_link (a, i, name);
439           equal &= a_menu && check_menus_equal (a_menu, b_menu);
440           if (a_menu)
441             g_object_unref (a_menu);
442           g_object_unref (b_menu);
443         }
444       g_object_unref (link_iter);
445     }
446
447   return equal;
448 }
449
450 static void
451 assert_menus_equal (GMenuModel *a,
452                     GMenuModel *b)
453 {
454   if (!check_menus_equal (a, b))
455     {
456       GString *string;
457
458       string = g_string_new ("\n  <a>\n");
459       g_menu_markup_print_string (string, G_MENU_MODEL (a), 4, 2);
460       g_string_append (string, "  </a>\n\n-------------\n  <b>\n");
461       g_menu_markup_print_string (string, G_MENU_MODEL (b), 4, 2);
462       g_string_append (string, "  </b>\n");
463       g_error ("%s", string->str);
464     }
465 }
466
467 /* Test cases {{{1 */
468 static void
469 test_equality (void)
470 {
471   GRand *randa, *randb;
472   guint32 seed;
473   gint i;
474
475   seed = g_test_rand_int ();
476
477   randa = g_rand_new_with_seed (seed);
478   randb = g_rand_new_with_seed (seed);
479
480   for (i = 0; i < 500; i++)
481     {
482       RandomMenu *a, *b;
483
484       a = random_menu_new (randa, TOP_ORDER);
485       b = random_menu_new (randb, TOP_ORDER);
486       assert_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b));
487       g_object_unref (b);
488       g_object_unref (a);
489     }
490
491   g_rand_int (randa);
492
493   for (i = 0; i < 500;)
494     {
495       RandomMenu *a, *b;
496
497       a = random_menu_new (randa, TOP_ORDER);
498       b = random_menu_new (randb, TOP_ORDER);
499       if (check_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b)))
500         {
501           /* by chance, they may really be equal.  double check. */
502           GString *as, *bs;
503
504           as = g_menu_markup_print_string (NULL, G_MENU_MODEL (a), 4, 2);
505           bs = g_menu_markup_print_string (NULL, G_MENU_MODEL (b), 4, 2);
506           g_assert_cmpstr (as->str, ==, bs->str);
507           g_string_free (bs, TRUE);
508           g_string_free (as, TRUE);
509
510           /* we're here because randa and randb just generated equal
511            * menus.  they may do it again, so throw away randb and make
512            * a fresh one.
513            */
514           g_rand_free (randb);
515           randb = g_rand_new_with_seed (g_rand_int (randa));
516         }
517       else
518         /* make sure we get enough unequals (ie: no GRand failure) */
519         i++;
520
521       g_object_unref (b);
522       g_object_unref (a);
523     }
524
525   g_rand_free (randb);
526   g_rand_free (randa);
527 }
528
529 static void
530 test_random (void)
531 {
532   RandomMenu *random;
533   MirrorMenu *mirror;
534   GRand *rand;
535   gint i;
536
537   rand = g_rand_new_with_seed (g_test_rand_int ());
538   random = random_menu_new (rand, TOP_ORDER);
539   mirror = mirror_menu_new (G_MENU_MODEL (random));
540
541   for (i = 0; i < 500; i++)
542     {
543       assert_menus_equal (G_MENU_MODEL (random), G_MENU_MODEL (mirror));
544       random_menu_change (random, rand);
545     }
546
547   g_object_unref (mirror);
548   g_object_unref (random);
549
550   g_rand_free (rand);
551 }
552
553 struct roundtrip_state
554 {
555   RandomMenu *random;
556   MirrorMenu *proxy_mirror;
557   GDBusMenuModel *proxy;
558   GMainLoop *loop;
559   GRand *rand;
560   gint success;
561   gint count;
562 };
563
564 static gboolean
565 roundtrip_step (gpointer data)
566 {
567   struct roundtrip_state *state = data;
568
569   if (check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy)) &&
570       check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy_mirror)))
571     {
572       state->success++;
573       state->count = 0;
574
575       if (state->success < 100)
576         random_menu_change (state->random, state->rand);
577       else
578         g_main_loop_quit (state->loop);
579     }
580   else if (state->count == 100)
581     {
582       assert_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy));
583       g_assert_not_reached ();
584     }
585   else
586     state->count++;
587
588   return G_SOURCE_CONTINUE;
589 }
590
591 static void
592 test_dbus_roundtrip (void)
593 {
594   struct roundtrip_state state;
595   GDBusConnection *bus;
596   guint export_id;
597   guint id;
598
599   bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
600
601   state.rand = g_rand_new_with_seed (g_test_rand_int ());
602
603   state.random = random_menu_new (state.rand, 2);
604   export_id = g_dbus_connection_export_menu_model (bus, "/", G_MENU_MODEL (state.random), NULL);
605   state.proxy = g_dbus_menu_model_get (bus, g_dbus_connection_get_unique_name (bus), "/");
606   state.proxy_mirror = mirror_menu_new (G_MENU_MODEL (state.proxy));
607   state.count = 0;
608   state.success = 0;
609
610   id = g_timeout_add (10, roundtrip_step, &state);
611
612   state.loop = g_main_loop_new (NULL, FALSE);
613   g_main_loop_run (state.loop);
614
615   g_main_loop_unref (state.loop);
616   g_source_remove (id);
617   g_object_unref (state.proxy);
618   g_dbus_connection_unexport_menu_model (bus, export_id);
619   g_object_unref (state.random);
620   g_object_unref (state.proxy_mirror);
621   g_rand_free (state.rand);
622   g_object_unref (bus);
623 }
624
625 static gint items_changed_count;
626
627 static void
628 items_changed (GMenuModel *model,
629                gint        position,
630                gint        removed,
631                gint        added,
632                gpointer    data)
633 {
634   items_changed_count++;
635 }
636
637 static gboolean
638 stop_loop (gpointer data)
639 {
640   GMainLoop *loop = data;
641
642   g_main_loop_quit (loop);
643
644   return G_SOURCE_REMOVE;
645 }
646
647 static void
648 test_dbus_subscriptions (void)
649 {
650   GDBusConnection *bus;
651   GMenu *menu;
652   GDBusMenuModel *proxy;
653   GMainLoop *loop;
654   GError *error = NULL;
655   guint export_id;
656
657   loop = g_main_loop_new (NULL, FALSE);
658
659   bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
660
661   menu = g_menu_new ();
662
663   export_id = g_dbus_connection_export_menu_model (bus, "/", G_MENU_MODEL (menu), &error);
664   g_assert_no_error (error);
665
666   proxy = g_dbus_menu_model_get (bus, g_dbus_connection_get_unique_name (bus), "/");
667   items_changed_count = 0;
668   g_signal_connect (proxy, "items-changed",
669                     G_CALLBACK (items_changed), NULL);
670
671   g_menu_append (menu, "item1", NULL);
672   g_menu_append (menu, "item2", NULL);
673   g_menu_append (menu, "item3", NULL);
674
675   g_assert_cmpint (items_changed_count, ==, 0);
676
677   g_timeout_add (100, stop_loop, loop);
678   g_main_loop_run (loop);
679
680   g_menu_model_get_n_items (G_MENU_MODEL (proxy));
681
682   g_timeout_add (100, stop_loop, loop);
683   g_main_loop_run (loop);
684
685   g_assert_cmpint (items_changed_count, ==, 1);
686   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 3);
687
688   g_timeout_add (100, stop_loop, loop);
689   g_main_loop_run (loop);
690
691   g_menu_append (menu, "item4", NULL);
692   g_menu_append (menu, "item5", NULL);
693   g_menu_append (menu, "item6", NULL);
694   g_menu_remove (menu, 0);
695   g_menu_remove (menu, 0);
696
697   g_timeout_add (200, stop_loop, loop);
698   g_main_loop_run (loop);
699
700   g_assert_cmpint (items_changed_count, ==, 6);
701
702   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 4);
703   g_object_unref (proxy);
704
705   g_timeout_add (100, stop_loop, loop);
706   g_main_loop_run (loop);
707
708   g_menu_remove (menu, 0);
709   g_menu_remove (menu, 0);
710
711   g_timeout_add (100, stop_loop, loop);
712   g_main_loop_run (loop);
713
714   g_assert_cmpint (items_changed_count, ==, 6);
715
716   g_dbus_connection_unexport_menu_model (bus, export_id);
717   g_object_unref (menu);
718
719   g_main_loop_unref (loop);
720 }
721
722 static gpointer
723 do_modify (gpointer data)
724 {
725   RandomMenu *menu = data;
726   GRand *rand;
727   gint i;
728
729   rand = g_rand_new_with_seed (g_test_rand_int ());
730
731   for (i = 0; i < 10000; i++)
732     {
733       random_menu_change (menu, rand);
734     }
735
736   return NULL;
737 }
738
739 static gpointer
740 do_export (gpointer data)
741 {
742   GMenuModel *menu = data;
743   gint i;
744   GDBusConnection *bus;
745   gchar *path;
746   GError *error = NULL;
747   guint id;
748
749   bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
750   path = g_strdup_printf ("/%p", data);
751
752   for (i = 0; i < 10000; i++)
753     {
754       id = g_dbus_connection_export_menu_model (bus, path, menu, &error);
755       g_assert_no_error (error);
756       g_dbus_connection_unexport_menu_model (bus, id);
757     }
758
759   g_free (path);
760
761   g_object_unref (bus);
762
763   return NULL;
764 }
765
766 static void
767 test_dbus_threaded (void)
768 {
769   RandomMenu *menu[10];
770   GThread *call[10];
771   GThread *export[10];
772   gint i;
773
774   for (i = 0; i < 10; i++)
775     {
776       menu[i] = random_menu_new (g_rand_new_with_seed (g_test_rand_int ()), 2);
777       call[i] = g_thread_new ("call", do_modify, menu[i]);
778       export[i] = g_thread_new ("export", do_export, menu[i]);
779     }
780
781   for (i = 0; i < 10; i++)
782     {
783       g_thread_join (call[i]);
784       g_thread_join (export[i]);
785     }
786
787   for (i = 0; i < 10; i++)
788     g_object_unref (menu[i]);
789 }
790
791 static void
792 start_element (GMarkupParseContext *context,
793                const gchar         *element_name,
794                const gchar        **attribute_names,
795                const gchar        **attribute_values,
796                gpointer             user_data,
797                GError             **error)
798 {
799   if (g_strcmp0 (element_name, "menu") == 0)
800     g_menu_markup_parser_start_menu (context, "domain", NULL);
801 }
802
803 static void
804 end_element (GMarkupParseContext *context,
805              const gchar         *element_name,
806              gpointer             user_data,
807              GError             **error)
808 {
809   GMenu **menu = user_data;
810
811   if (g_strcmp0 (element_name, "menu") == 0)
812     *menu = g_menu_markup_parser_end_menu (context);
813 }
814
815 static GMenuModel *
816 parse_menu_string (const gchar *string, GError **error)
817 {
818   const GMarkupParser parser = {
819     start_element, end_element, NULL, NULL, NULL
820   };
821   GMarkupParseContext *context;
822   GMenuModel *menu = NULL;
823
824   context = g_markup_parse_context_new (&parser, 0, &menu, NULL);
825   g_markup_parse_context_parse (context, string, -1, error);
826   g_markup_parse_context_free (context);
827
828   return menu;
829 }
830
831 static gchar *
832 menu_to_string (GMenuModel *menu)
833 {
834   GString *s;
835
836   s = g_string_new ("<menu>\n");
837   g_menu_markup_print_string (s, menu, 2, 2);
838   g_string_append (s, "</menu>\n");
839
840   return g_string_free (s, FALSE);
841 }
842
843 static void
844 test_markup_roundtrip (void)
845 {
846   const gchar data[] =
847   "<menu id='edit-menu'>\n"
848   "  <section>\n"
849   "    <item action='undo'>\n"
850   "      <attribute name='label' translatable='yes' context='Stock label'>'_Undo'</attribute>\n"
851   "    </item>\n"
852   "    <item label='Redo' action='redo'/>\n"
853   "  </section>\n"
854   "  <section></section>\n"
855   "  <section label='Copy &amp; Paste'>\n"
856   "    <item label='Cut' action='cut'/>\n"
857   "    <item label='Copy' action='copy'/>\n"
858   "    <item label='Paste' action='paste'/>\n"
859   "  </section>\n"
860   "  <section>\n"
861   "    <item label='Bold' action='bold'/>\n"
862   "    <submenu label='Language'>\n"
863   "      <item label='Latin' action='lang' target='latin'/>\n"
864   "      <item label='Greek' action='lang' target='greek'/>\n"
865   "      <item label='Urdu'  action='lang' target='urdu'/>\n"
866   "    </submenu>\n"
867   "    <item name='test unusual attributes'>\n"
868   "      <attribute name='action' type='s'>'quite-some-action'</attribute>\n"
869   "      <attribute name='target' type='i'>36</attribute>\n"
870   "      <attribute name='chocolate-thunda' type='as'>['a','b']</attribute>\n"
871   "      <attribute name='thing1' type='g'>'s(uu)'</attribute>\n"
872   "      <attribute name='icon' type='s'>'small blue thing'</attribute>\n"
873   "   </item>\n"
874   "  </section>\n"
875   "</menu>\n";
876   GError *error = NULL;
877   GMenuModel *a;
878   GMenuModel *b;
879   gchar *s;
880   gchar *s2;
881
882   a = parse_menu_string (data, &error);
883   g_assert_no_error (error);
884   g_assert (G_IS_MENU_MODEL (a));
885
886   /* normalized representation */
887   s = menu_to_string (a);
888
889   b = parse_menu_string (s, &error);
890   g_assert_no_error (error);
891   g_assert (G_IS_MENU_MODEL (b));
892
893   assert_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b));
894
895   s2 = menu_to_string (b);
896
897   g_assert_cmpstr (s, ==, s2);
898
899   g_object_unref (a);
900   g_object_unref (b);
901   g_free (s);
902   g_free (s2);
903 }
904
905 static void
906 test_attributes (void)
907 {
908   GMenu *menu;
909   GMenuItem *item;
910   GVariant *v;
911
912   menu = g_menu_new ();
913
914   item = g_menu_item_new ("test", NULL);
915   g_menu_item_set_attribute_value (item, "boolean", g_variant_new_boolean (FALSE));
916   g_menu_item_set_attribute_value (item, "string", g_variant_new_string ("bla"));
917   g_menu_item_set_attribute_value (item, "double", g_variant_new_double (1.5));
918   v = g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three");
919   g_menu_item_set_attribute_value (item, "complex", v);
920   g_menu_item_set_attribute_value (item, "test-123", g_variant_new_string ("test-123"));
921
922   g_menu_append_item (menu, item);
923
924   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 1);
925
926   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "boolean", NULL);
927   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN));
928   g_variant_unref (v);
929
930   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "string", NULL);
931   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
932   g_variant_unref (v);
933
934   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "double", NULL);
935   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_DOUBLE));
936   g_variant_unref (v);
937
938   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "complex", NULL);
939   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE("a(si)")));
940   g_variant_unref (v);
941
942   g_object_unref (menu);
943 }
944
945 static void
946 test_links (void)
947 {
948   GMenu *menu;
949   GMenuModel *m;
950   GMenuModel *x;
951   GMenuItem *item;
952
953   m = G_MENU_MODEL (g_menu_new ());
954   g_menu_append (G_MENU (m), "test", NULL);
955
956   menu = g_menu_new ();
957
958   item = g_menu_item_new ("test1", NULL);
959   g_menu_item_set_link (item, "section", m);
960   g_menu_append_item (menu, item);
961
962   item = g_menu_item_new ("test2", NULL);
963   g_menu_item_set_link (item, "submenu", m);
964   g_menu_append_item (menu, item);
965
966   item = g_menu_item_new ("test3", NULL);
967   g_menu_item_set_link (item, "wallet", m);
968   g_menu_append_item (menu, item);
969
970   item = g_menu_item_new ("test4", NULL);
971   g_menu_item_set_link (item, "purse", m);
972   g_menu_item_set_link (item, "purse", NULL);
973   g_menu_append_item (menu, item);
974
975   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 4);
976
977   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 0, "section");
978   g_assert (x == m);
979   g_object_unref (x);
980
981   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 1, "submenu");
982   g_assert (x == m);
983   g_object_unref (x);
984
985   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 2, "wallet");
986   g_assert (x == m);
987   g_object_unref (x);
988
989   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 3, "purse");
990   g_assert (x == NULL);
991
992   g_object_unref (m);
993   g_object_unref (menu);
994 }
995
996 static void
997 test_mutable (void)
998 {
999   GMenu *menu;
1000
1001   menu = g_menu_new ();
1002   g_menu_append (menu, "test", "test");
1003
1004   g_assert (g_menu_model_is_mutable (G_MENU_MODEL (menu)));
1005   g_menu_freeze (menu);
1006   g_assert (!g_menu_model_is_mutable (G_MENU_MODEL (menu)));
1007
1008   g_object_unref (menu);
1009 }
1010
1011 static void
1012 test_misc (void)
1013 {
1014   /* trying to use most of the GMenu api for constructing the
1015    * same menu two different ways
1016    */
1017   GMenu *a, *m, *m2;
1018   GMenuModel *b;
1019   GMenuItem *item;
1020   const gchar *s;
1021
1022   a = g_menu_new ();
1023   item = g_menu_item_new ("test1", "action1::target1");
1024   g_menu_prepend_item (a, item);
1025   g_object_unref (item);
1026
1027   m = g_menu_new ();
1028   g_menu_prepend (m, "test2a", "action2");
1029   g_menu_append (m, "test2c", NULL);
1030   g_menu_insert (m, 1, "test2b", NULL);
1031
1032   item = g_menu_item_new_submenu ("test2", G_MENU_MODEL (m));
1033   g_menu_append_item (a, item);
1034   g_object_unref (item);
1035   g_object_unref (m);
1036
1037   m = g_menu_new ();
1038
1039   m2 = g_menu_new ();
1040   g_menu_append (m2, "x", NULL);
1041   g_menu_prepend_section (m, "test3a", G_MENU_MODEL (m2));
1042   g_object_unref (m2);
1043
1044   item = g_menu_item_new_section ("test3", G_MENU_MODEL (m));
1045   g_menu_insert_item (a, -1, item);
1046   g_object_unref (item);
1047   g_object_unref (m);
1048
1049   s = ""
1050 "<menu>"
1051 "  <item target='target1' action='action1' label='test1'/>"
1052 "  <item label='test2'>"
1053 "    <link name='submenu'>"
1054 "      <item action='action2' label='test2a'/>"
1055 "      <item label='test2b'/>"
1056 "      <item label='test2c'/>"
1057 "    </link>"
1058 "  </item>"
1059 "  <item label='test3'>"
1060 "    <link name='section'>"
1061 "      <item label='test3a'>"
1062 "        <link name='section'>"
1063 "          <item label='x'/>"
1064 "        </link>"
1065 "      </item>"
1066 "    </link>"
1067 "  </item>"
1068 "</menu>";
1069
1070   b = parse_menu_string (s, NULL);
1071
1072   assert_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b));
1073   g_object_unref (a);
1074   g_object_unref (b);
1075 }
1076
1077  /* Epilogue {{{1 */
1078 int
1079 main (int argc, char **argv)
1080 {
1081   g_test_init (&argc, &argv, NULL);
1082
1083   g_type_init ();
1084
1085   g_test_add_func ("/gmenu/equality", test_equality);
1086   g_test_add_func ("/gmenu/random", test_random);
1087   g_test_add_func ("/gmenu/dbus/roundtrip", test_dbus_roundtrip);
1088   g_test_add_func ("/gmenu/dbus/subscriptions", test_dbus_subscriptions);
1089   g_test_add_func ("/gmenu/dbus/threaded", test_dbus_threaded);
1090   g_test_add_func ("/gmenu/markup/roundtrip", test_markup_roundtrip);
1091   g_test_add_func ("/gmenu/attributes", test_attributes);
1092   g_test_add_func ("/gmenu/links", test_links);
1093   g_test_add_func ("/gmenu/mutable", test_mutable);
1094   g_test_add_func ("/gmenu/misc", test_misc);
1095
1096   return g_test_run ();
1097 }
1098 /* vim:set foldmethod=marker: */