5 /* This utility struct is used by both the RandomMenu and MirrorMenu
6 * class implementations below.
9 GHashTable *attributes;
14 test_item_new (GHashTable *attributes,
19 item = g_slice_new (TestItem);
20 item->attributes = g_hash_table_ref (attributes);
21 item->links = g_hash_table_ref (links);
27 test_item_free (gpointer data)
29 TestItem *item = data;
31 g_hash_table_unref (item->attributes);
32 g_hash_table_unref (item->links);
34 g_slice_free (TestItem, item);
42 GMenuModel parent_instance;
48 typedef GMenuModelClass RandomMenuClass;
50 static GType random_menu_get_type (void);
51 G_DEFINE_TYPE (RandomMenu, random_menu, G_TYPE_MENU_MODEL);
54 random_menu_is_mutable (GMenuModel *model)
60 random_menu_get_n_items (GMenuModel *model)
62 RandomMenu *menu = (RandomMenu *) model;
64 return g_sequence_get_length (menu->items);
68 random_menu_get_item_attributes (GMenuModel *model,
72 RandomMenu *menu = (RandomMenu *) model;
75 item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
76 *table = g_hash_table_ref (item->attributes);
80 random_menu_get_item_links (GMenuModel *model,
84 RandomMenu *menu = (RandomMenu *) model;
87 item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
88 *table = g_hash_table_ref (item->links);
92 random_menu_finalize (GObject *object)
94 RandomMenu *menu = (RandomMenu *) object;
96 g_sequence_free (menu->items);
98 G_OBJECT_CLASS (random_menu_parent_class)
103 random_menu_init (RandomMenu *menu)
108 random_menu_class_init (GMenuModelClass *class)
110 GObjectClass *object_class = G_OBJECT_CLASS (class);
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;
117 object_class->finalize = random_menu_finalize;
120 static RandomMenu * random_menu_new (GRand *rand, gint order);
123 random_menu_change (RandomMenu *menu,
126 gint position, removes, adds;
127 GSequenceIter *point;
131 n_items = g_sequence_get_length (menu->items);
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);
139 while (removes == 0 && adds == 0);
141 point = g_sequence_get_iter_at_pos (menu->items, position + removes);
145 GSequenceIter *start;
147 start = g_sequence_get_iter_at_pos (menu->items, position);
148 g_sequence_remove_range (start, point);
151 for (i = 0; i < adds; i++)
155 GHashTable *attributes;
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);
160 if (menu->order > 0 && g_rand_boolean (rand))
163 const gchar *subtype;
165 child = random_menu_new (rand, menu->order - 1);
167 if (g_rand_boolean (rand))
169 subtype = G_MENU_LINK_SECTION;
170 /* label some section headers */
171 if (g_rand_boolean (rand))
178 /* label all submenus */
179 subtype = G_MENU_LINK_SUBMENU;
183 g_hash_table_insert (links, g_strdup (subtype), child);
186 /* label all terminals */
190 g_hash_table_insert (attributes, g_strdup ("label"), g_variant_ref_sink (g_variant_new_string (label)));
192 g_sequence_insert_before (point, test_item_new (attributes, links));
193 g_hash_table_unref (links);
194 g_hash_table_unref (attributes);
197 g_menu_model_items_changed (G_MENU_MODEL (menu), position, removes, adds);
201 random_menu_new (GRand *rand,
206 menu = g_object_new (random_menu_get_type (), NULL);
207 menu->items = g_sequence_new (test_item_free);
210 random_menu_change (menu, rand);
215 /* MirrorMenu {{{1 */
217 GMenuModel parent_instance;
219 GMenuModel *clone_of;
224 typedef GMenuModelClass MirrorMenuClass;
226 static GType mirror_menu_get_type (void);
227 G_DEFINE_TYPE (MirrorMenu, mirror_menu, G_TYPE_MENU_MODEL);
230 mirror_menu_is_mutable (GMenuModel *model)
232 MirrorMenu *menu = (MirrorMenu *) model;
234 return menu->handler_id != 0;
238 mirror_menu_get_n_items (GMenuModel *model)
240 MirrorMenu *menu = (MirrorMenu *) model;
242 return g_sequence_get_length (menu->items);
246 mirror_menu_get_item_attributes (GMenuModel *model,
250 MirrorMenu *menu = (MirrorMenu *) model;
253 item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
254 *table = g_hash_table_ref (item->attributes);
258 mirror_menu_get_item_links (GMenuModel *model,
262 MirrorMenu *menu = (MirrorMenu *) model;
265 item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
266 *table = g_hash_table_ref (item->links);
270 mirror_menu_finalize (GObject *object)
272 MirrorMenu *menu = (MirrorMenu *) object;
274 if (menu->handler_id)
275 g_signal_handler_disconnect (menu->clone_of, menu->handler_id);
277 g_sequence_free (menu->items);
278 g_object_unref (menu->clone_of);
280 G_OBJECT_CLASS (mirror_menu_parent_class)
285 mirror_menu_init (MirrorMenu *menu)
290 mirror_menu_class_init (GMenuModelClass *class)
292 GObjectClass *object_class = G_OBJECT_CLASS (class);
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;
299 object_class->finalize = mirror_menu_finalize;
302 static MirrorMenu * mirror_menu_new (GMenuModel *clone_of);
305 mirror_menu_changed (GMenuModel *model,
311 MirrorMenu *menu = user_data;
312 GSequenceIter *point;
315 g_assert (model == menu->clone_of);
317 point = g_sequence_get_iter_at_pos (menu->items, position + removed);
321 GSequenceIter *start;
323 start = g_sequence_get_iter_at_pos (menu->items, position);
324 g_sequence_remove_range (start, point);
327 for (i = position; i < position + added; i++)
329 GMenuAttributeIter *attr_iter;
330 GMenuLinkIter *link_iter;
332 GHashTable *attributes;
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);
340 attr_iter = g_menu_model_iterate_item_attributes (model, i);
341 while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
343 g_hash_table_insert (attributes, g_strdup (name), value);
345 g_object_unref (attr_iter);
347 link_iter = g_menu_model_iterate_item_links (model, i);
348 while (g_menu_link_iter_get_next (link_iter, &name, &child))
350 g_hash_table_insert (links, g_strdup (name), mirror_menu_new (child));
351 g_object_unref (child);
353 g_object_unref (link_iter);
355 g_sequence_insert_before (point, test_item_new (attributes, links));
356 g_hash_table_unref (attributes);
357 g_hash_table_unref (links);
360 g_menu_model_items_changed (G_MENU_MODEL (menu), position, removed, added);
364 mirror_menu_new (GMenuModel *clone_of)
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);
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);
379 /* check_menus_equal(), assert_menus_equal() {{{1 */
381 check_menus_equal (GMenuModel *a,
384 gboolean equal = TRUE;
388 a_n = g_menu_model_get_n_items (a);
389 b_n = g_menu_model_get_n_items (b);
394 for (i = 0; i < a_n; i++)
396 GMenuAttributeIter *attr_iter;
397 GVariant *a_value, *b_value;
398 GMenuLinkIter *link_iter;
399 GMenuModel *a_menu, *b_menu;
402 attr_iter = g_menu_model_iterate_item_attributes (a, i);
403 while (g_menu_attribute_iter_get_next (attr_iter, &name, &a_value))
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);
408 g_variant_unref (b_value);
409 g_variant_unref (a_value);
411 g_object_unref (attr_iter);
413 attr_iter = g_menu_model_iterate_item_attributes (b, i);
414 while (g_menu_attribute_iter_get_next (attr_iter, &name, &b_value))
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);
419 g_variant_unref (a_value);
420 g_variant_unref (b_value);
422 g_object_unref (attr_iter);
424 link_iter = g_menu_model_iterate_item_links (a, i);
425 while (g_menu_link_iter_get_next (link_iter, &name, &a_menu))
427 b_menu = g_menu_model_get_item_link (b, i, name);
428 equal &= b_menu && check_menus_equal (a_menu, b_menu);
430 g_object_unref (b_menu);
431 g_object_unref (a_menu);
433 g_object_unref (link_iter);
435 link_iter = g_menu_model_iterate_item_links (b, i);
436 while (g_menu_link_iter_get_next (link_iter, &name, &b_menu))
438 a_menu = g_menu_model_get_item_link (a, i, name);
439 equal &= a_menu && check_menus_equal (a_menu, b_menu);
441 g_object_unref (a_menu);
442 g_object_unref (b_menu);
444 g_object_unref (link_iter);
451 assert_menus_equal (GMenuModel *a,
454 if (!check_menus_equal (a, b))
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);
467 /* Test cases {{{1 */
471 GRand *randa, *randb;
475 seed = g_test_rand_int ();
477 randa = g_rand_new_with_seed (seed);
478 randb = g_rand_new_with_seed (seed);
480 for (i = 0; i < 500; i++)
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));
493 for (i = 0; i < 500;)
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)))
501 /* by chance, they may really be equal. double check. */
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);
511 /* make sure we get enough unequals (ie: no GRand failure) */
530 rand = g_rand_new_with_seed (g_test_rand_int ());
531 random = random_menu_new (rand, TOP_ORDER);
532 mirror = mirror_menu_new (G_MENU_MODEL (random));
534 for (i = 0; i < 500; i++)
536 assert_menus_equal (G_MENU_MODEL (random), G_MENU_MODEL (mirror));
537 random_menu_change (random, rand);
540 g_object_unref (mirror);
541 g_object_unref (random);
546 struct roundtrip_state
557 roundtrip_step (gpointer data)
559 struct roundtrip_state *state = data;
561 if (check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy)))
566 if (state->success < 100)
567 random_menu_change (state->random, state->rand);
569 g_main_loop_quit (state->loop);
571 else if (state->count == 100)
573 assert_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy));
574 g_assert_not_reached ();
579 return G_SOURCE_CONTINUE;
583 test_dbus_roundtrip (void)
585 struct roundtrip_state state;
586 GDBusConnection *bus;
588 bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
590 state.rand = g_rand_new_with_seed (g_test_rand_int ());
592 state.random = random_menu_new (state.rand, TOP_ORDER);
593 g_menu_model_dbus_export_start (bus, "/", G_MENU_MODEL (state.random), NULL);
594 state.proxy = g_menu_proxy_get (bus, g_dbus_connection_get_unique_name (bus), "/");
598 g_timeout_add (10, roundtrip_step, &state);
600 state.loop = g_main_loop_new (NULL, FALSE);
601 g_main_loop_run (state.loop);
603 g_main_loop_unref (state.loop);
604 g_object_unref (state.proxy);
605 g_menu_model_dbus_export_stop (G_MENU_MODEL (state.random));
606 g_object_unref (state.random);
607 g_rand_free (state.rand);
608 g_object_unref (bus);
612 start_element (GMarkupParseContext *context,
613 const gchar *element_name,
614 const gchar **attribute_names,
615 const gchar **attribute_values,
619 if (g_strcmp0 (element_name, "menu") == 0)
620 g_menu_markup_parser_start_menu (context, "domain", NULL);
624 end_element (GMarkupParseContext *context,
625 const gchar *element_name,
629 GMenu **menu = user_data;
631 if (g_strcmp0 (element_name, "menu") == 0)
632 *menu = g_menu_markup_parser_end_menu (context);
636 parse_menu_string (const gchar *string, GError **error)
638 const GMarkupParser parser = {
639 start_element, end_element, NULL, NULL, NULL
641 GMarkupParseContext *context;
642 GMenuModel *menu = NULL;
644 context = g_markup_parse_context_new (&parser, 0, &menu, NULL);
645 g_markup_parse_context_parse (context, string, -1, error);
646 g_markup_parse_context_free (context);
652 menu_to_string (GMenuModel *menu)
656 s = g_string_new ("<menu>\n");
657 g_menu_markup_print_string (s, menu, 2, 2);
658 g_string_append (s, "</menu>\n");
660 return g_string_free (s, FALSE);
664 test_markup_roundtrip (void)
667 "<menu id='edit-menu'>\n"
669 " <item action='undo'>\n"
670 " <attribute name='label' translatable='yes' context='Stock label'>'_Undo'</attribute>\n"
672 " <item label='Redo' action='redo'/>\n"
674 " <section></section>\n"
675 " <section label='Copy & Paste'>\n"
676 " <item label='Cut' action='cut'/>\n"
677 " <item label='Copy' action='copy'/>\n"
678 " <item label='Paste' action='paste'/>\n"
681 " <item label='Bold' action='bold'/>\n"
682 " <submenu label='Language'>\n"
683 " <item label='Latin' action='lang' target='latin'/>\n"
684 " <item label='Greek' action='lang' target='greek'/>\n"
685 " <item label='Urdu' action='lang' target='urdu'/>\n"
687 " <item name='test unusual attributes'>\n"
688 " <attribute name='action' type='s'>'quite-some-action'</attribute>\n"
689 " <attribute name='target' type='i'>36</attribute>\n"
690 " <attribute name='chocolate-thunda' type='as'>['a','b']</attribute>\n"
691 " <attribute name='thing1' type='g'>'s(uu)'</attribute>\n"
692 " <attribute name='icon' type='s'>'small blue thing'</attribute>\n"
696 GError *error = NULL;
702 a = parse_menu_string (data, &error);
703 g_assert_no_error (error);
704 g_assert (G_IS_MENU_MODEL (a));
706 /* normalized representation */
707 s = menu_to_string (a);
709 b = parse_menu_string (s, &error);
710 g_assert_no_error (error);
711 g_assert (G_IS_MENU_MODEL (b));
713 assert_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b));
715 s2 = menu_to_string (b);
717 g_assert_cmpstr (s, ==, s2);
726 test_attributes (void)
732 menu = g_menu_new ();
734 item = g_menu_item_new ("test", NULL);
735 g_menu_item_set_attribute_value (item, "boolean", g_variant_new_boolean (FALSE));
736 g_menu_item_set_attribute_value (item, "string", g_variant_new_string ("bla"));
737 g_menu_item_set_attribute_value (item, "double", g_variant_new_double (1.5));
738 v = g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three");
739 g_menu_item_set_attribute_value (item, "complex", v);
740 g_menu_item_set_attribute_value (item, "test-123", g_variant_new_string ("test-123"));
742 g_menu_append_item (menu, item);
744 g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 1);
746 v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "boolean", NULL);
747 g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN));
750 v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "string", NULL);
751 g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
754 v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "double", NULL);
755 g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_DOUBLE));
758 v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "complex", NULL);
759 g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE("a(si)")));
762 g_object_unref (menu);
773 m = G_MENU_MODEL (g_menu_new ());
774 g_menu_append (G_MENU (m), "test", NULL);
776 menu = g_menu_new ();
778 item = g_menu_item_new ("test1", NULL);
779 g_menu_item_set_link (item, "section", m);
780 g_menu_append_item (menu, item);
782 item = g_menu_item_new ("test2", NULL);
783 g_menu_item_set_link (item, "submenu", m);
784 g_menu_append_item (menu, item);
786 item = g_menu_item_new ("test3", NULL);
787 g_menu_item_set_link (item, "wallet", m);
788 g_menu_append_item (menu, item);
790 item = g_menu_item_new ("test4", NULL);
791 g_menu_item_set_link (item, "purse", m);
792 g_menu_item_set_link (item, "purse", NULL);
793 g_menu_append_item (menu, item);
795 g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 4);
797 x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 0, "section");
801 x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 1, "submenu");
805 x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 2, "wallet");
809 x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 3, "purse");
810 g_assert (x == NULL);
813 g_object_unref (menu);
821 menu = g_menu_new ();
822 g_menu_append (menu, "test", "test");
824 g_assert (g_menu_model_is_mutable (G_MENU_MODEL (menu)));
825 g_menu_freeze (menu);
826 g_assert (!g_menu_model_is_mutable (G_MENU_MODEL (menu)));
828 g_object_unref (menu);
833 main (int argc, char **argv)
835 g_test_init (&argc, &argv, NULL);
839 g_test_add_func ("/gmenu/equality", test_equality);
840 g_test_add_func ("/gmenu/random", test_random);
841 g_test_add_func ("/gmenu/bus/roundtrip", test_dbus_roundtrip);
842 g_test_add_func ("/gmenu/markup/roundtrip", test_markup_roundtrip);
843 g_test_add_func ("/gmenu/attributes", test_attributes);
844 g_test_add_func ("/gmenu/links", test_links);
845 g_test_add_func ("/gmenu/mutable", test_mutable);
847 return g_test_run ();
849 /* vim:set foldmethod=marker: */