#include <gio/gio.h>
+#include "gdbus-sessionbus.h"
+
+/* Markup printing {{{1 */
+
+/* This used to be part of GLib, but it was removed before the stable
+ * release because it wasn't generally useful. We want it here, though.
+ */
+static void
+indent_string (GString *string,
+ gint indent)
+{
+ while (indent--)
+ g_string_append_c (string, ' ');
+}
+
+static GString *
+g_menu_markup_print_string (GString *string,
+ GMenuModel *model,
+ gint indent,
+ gint tabstop)
+{
+ gboolean need_nl = FALSE;
+ gint i, n;
+
+ if G_UNLIKELY (string == NULL)
+ string = g_string_new (NULL);
+
+ n = g_menu_model_get_n_items (model);
+
+ for (i = 0; i < n; i++)
+ {
+ GMenuAttributeIter *attr_iter;
+ GMenuLinkIter *link_iter;
+ GString *contents;
+ GString *attrs;
+
+ attr_iter = g_menu_model_iterate_item_attributes (model, i);
+ link_iter = g_menu_model_iterate_item_links (model, i);
+ contents = g_string_new (NULL);
+ attrs = g_string_new (NULL);
+
+ while (g_menu_attribute_iter_next (attr_iter))
+ {
+ const char *name = g_menu_attribute_iter_get_name (attr_iter);
+ GVariant *value = g_menu_attribute_iter_get_value (attr_iter);
+
+ if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
+ {
+ gchar *str;
+ str = g_markup_printf_escaped (" %s='%s'", name, g_variant_get_string (value, NULL));
+ g_string_append (attrs, str);
+ g_free (str);
+ }
+
+ else
+ {
+ gchar *printed;
+ gchar *str;
+ const gchar *type;
+
+ printed = g_variant_print (value, TRUE);
+ type = g_variant_type_peek_string (g_variant_get_type (value));
+ str = g_markup_printf_escaped ("<attribute name='%s' type='%s'>%s</attribute>\n", name, type, printed);
+ indent_string (contents, indent + tabstop);
+ g_string_append (contents, str);
+ g_free (printed);
+ g_free (str);
+ }
+
+ g_variant_unref (value);
+ }
+ g_object_unref (attr_iter);
+
+ while (g_menu_link_iter_next (link_iter))
+ {
+ const gchar *name = g_menu_link_iter_get_name (link_iter);
+ GMenuModel *menu = g_menu_link_iter_get_value (link_iter);
+ gchar *str;
+
+ if (contents->str[0])
+ g_string_append_c (contents, '\n');
+
+ str = g_markup_printf_escaped ("<link name='%s'>\n", name);
+ indent_string (contents, indent + tabstop);
+ g_string_append (contents, str);
+ g_free (str);
+
+ g_menu_markup_print_string (contents, menu, indent + 2 * tabstop, tabstop);
+
+ indent_string (contents, indent + tabstop);
+ g_string_append (contents, "</link>\n");
+ g_object_unref (menu);
+ }
+ g_object_unref (link_iter);
+
+ if (contents->str[0])
+ {
+ indent_string (string, indent);
+ g_string_append_printf (string, "<item%s>\n", attrs->str);
+ g_string_append (string, contents->str);
+ indent_string (string, indent);
+ g_string_append (string, "</item>\n");
+ need_nl = TRUE;
+ }
+
+ else
+ {
+ if (need_nl)
+ g_string_append_c (string, '\n');
+
+ indent_string (string, indent);
+ g_string_append_printf (string, "<item%s/>\n", attrs->str);
+ need_nl = FALSE;
+ }
+
+ g_string_free (contents, TRUE);
+ g_string_free (attrs, TRUE);
+ }
+
+ return string;
+}
+
/* TestItem {{{1 */
/* This utility struct is used by both the RandomMenu and MirrorMenu
}
}
+static void
+assert_menuitem_equal (GMenuItem *item,
+ GMenuModel *model,
+ gint index)
+{
+ GMenuAttributeIter *attr_iter;
+ GMenuLinkIter *link_iter;
+ const gchar *name;
+ GVariant *value;
+ GMenuModel *linked_model;
+
+ /* NOTE we can't yet test whether item has attributes or links that
+ * are not in the model, because there's no iterator API for menu
+ * items */
+
+ attr_iter = g_menu_model_iterate_item_attributes (model, index);
+ while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
+ {
+ GVariant *item_value;
+
+ item_value = g_menu_item_get_attribute_value (item, name, g_variant_get_type (value));
+ g_assert (item_value && g_variant_equal (item_value, value));
+
+ g_variant_unref (item_value);
+ g_variant_unref (value);
+ }
+
+ link_iter = g_menu_model_iterate_item_links (model, index);
+ while (g_menu_link_iter_get_next (link_iter, &name, &linked_model))
+ {
+ GMenuModel *item_linked_model;
+
+ item_linked_model = g_menu_item_get_link (item, name);
+ g_assert (linked_model == item_linked_model);
+
+ g_object_unref (item_linked_model);
+ g_object_unref (linked_model);
+ }
+
+ g_object_unref (attr_iter);
+ g_object_unref (link_iter);
+}
+
/* Test cases {{{1 */
static void
test_equality (void)
g_assert_cmpstr (as->str, ==, bs->str);
g_string_free (bs, TRUE);
g_string_free (as, TRUE);
+
+ /* we're here because randa and randb just generated equal
+ * menus. they may do it again, so throw away randb and make
+ * a fresh one.
+ */
+ g_rand_free (randb);
+ randb = g_rand_new_with_seed (g_rand_int (randa));
}
else
/* make sure we get enough unequals (ie: no GRand failure) */
struct roundtrip_state
{
RandomMenu *random;
- GMenuProxy *proxy;
+ MirrorMenu *proxy_mirror;
+ GDBusMenuModel *proxy;
GMainLoop *loop;
GRand *rand;
gint success;
{
struct roundtrip_state *state = data;
- if (check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy)))
+ if (check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy)) &&
+ check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy_mirror)))
{
state->success++;
state->count = 0;
{
struct roundtrip_state state;
GDBusConnection *bus;
+ guint export_id;
+ guint id;
bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
state.rand = g_rand_new_with_seed (g_test_rand_int ());
- state.random = random_menu_new (state.rand, TOP_ORDER);
- g_menu_model_dbus_export_start (bus, "/", G_MENU_MODEL (state.random), NULL);
- state.proxy = g_menu_proxy_get (bus, g_dbus_connection_get_unique_name (bus), "/");
+ state.random = random_menu_new (state.rand, 2);
+ export_id = g_dbus_connection_export_menu_model (bus, "/", G_MENU_MODEL (state.random), NULL);
+ state.proxy = g_dbus_menu_model_get (bus, g_dbus_connection_get_unique_name (bus), "/");
+ state.proxy_mirror = mirror_menu_new (G_MENU_MODEL (state.proxy));
state.count = 0;
state.success = 0;
- g_timeout_add (10, roundtrip_step, &state);
+ id = g_timeout_add (10, roundtrip_step, &state);
state.loop = g_main_loop_new (NULL, FALSE);
g_main_loop_run (state.loop);
g_main_loop_unref (state.loop);
+ g_source_remove (id);
g_object_unref (state.proxy);
- g_menu_model_dbus_export_stop (G_MENU_MODEL (state.random));
+ g_dbus_connection_unexport_menu_model (bus, export_id);
g_object_unref (state.random);
+ g_object_unref (state.proxy_mirror);
g_rand_free (state.rand);
g_object_unref (bus);
}
+static gint items_changed_count;
+
static void
-start_element (GMarkupParseContext *context,
- const gchar *element_name,
- const gchar **attribute_names,
- const gchar **attribute_values,
- gpointer user_data,
- GError **error)
+items_changed (GMenuModel *model,
+ gint position,
+ gint removed,
+ gint added,
+ gpointer data)
{
- if (g_strcmp0 (element_name, "menu") == 0)
- g_menu_markup_parser_start_menu (context, "domain", NULL);
+ items_changed_count++;
}
-static void
-end_element (GMarkupParseContext *context,
- const gchar *element_name,
- gpointer user_data,
- GError **error)
+static gboolean
+stop_loop (gpointer data)
{
- GMenu **menu = user_data;
+ GMainLoop *loop = data;
+
+ g_main_loop_quit (loop);
- if (g_strcmp0 (element_name, "menu") == 0)
- *menu = g_menu_markup_parser_end_menu (context);
+ return G_SOURCE_REMOVE;
}
-static GMenuModel *
-parse_menu_string (const gchar *string, GError **error)
+static void
+test_dbus_subscriptions (void)
{
- const GMarkupParser parser = {
- start_element, end_element, NULL, NULL, NULL
- };
- GMarkupParseContext *context;
- GMenuModel *menu = NULL;
+ GDBusConnection *bus;
+ GMenu *menu;
+ GDBusMenuModel *proxy;
+ GMainLoop *loop;
+ GError *error = NULL;
+ guint export_id;
- context = g_markup_parse_context_new (&parser, 0, &menu, NULL);
- g_markup_parse_context_parse (context, string, -1, error);
- g_markup_parse_context_free (context);
+ loop = g_main_loop_new (NULL, FALSE);
- return menu;
+ bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+
+ menu = g_menu_new ();
+
+ export_id = g_dbus_connection_export_menu_model (bus, "/", G_MENU_MODEL (menu), &error);
+ g_assert_no_error (error);
+
+ proxy = g_dbus_menu_model_get (bus, g_dbus_connection_get_unique_name (bus), "/");
+ items_changed_count = 0;
+ g_signal_connect (proxy, "items-changed",
+ G_CALLBACK (items_changed), NULL);
+
+ g_menu_append (menu, "item1", NULL);
+ g_menu_append (menu, "item2", NULL);
+ g_menu_append (menu, "item3", NULL);
+
+ g_assert_cmpint (items_changed_count, ==, 0);
+
+ g_timeout_add (100, stop_loop, loop);
+ g_main_loop_run (loop);
+
+ g_menu_model_get_n_items (G_MENU_MODEL (proxy));
+
+ g_timeout_add (100, stop_loop, loop);
+ g_main_loop_run (loop);
+
+ g_assert_cmpint (items_changed_count, ==, 1);
+ g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 3);
+
+ g_timeout_add (100, stop_loop, loop);
+ g_main_loop_run (loop);
+
+ g_menu_append (menu, "item4", NULL);
+ g_menu_append (menu, "item5", NULL);
+ g_menu_append (menu, "item6", NULL);
+ g_menu_remove (menu, 0);
+ g_menu_remove (menu, 0);
+
+ g_timeout_add (200, stop_loop, loop);
+ g_main_loop_run (loop);
+
+ g_assert_cmpint (items_changed_count, ==, 6);
+
+ g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 4);
+ g_object_unref (proxy);
+
+ g_timeout_add (100, stop_loop, loop);
+ g_main_loop_run (loop);
+
+ g_menu_remove (menu, 0);
+ g_menu_remove (menu, 0);
+
+ g_timeout_add (100, stop_loop, loop);
+ g_main_loop_run (loop);
+
+ g_assert_cmpint (items_changed_count, ==, 6);
+
+ g_dbus_connection_unexport_menu_model (bus, export_id);
+ g_object_unref (menu);
+
+ g_main_loop_unref (loop);
+ g_object_unref (bus);
}
-static gchar *
-menu_to_string (GMenuModel *menu)
+static gpointer
+do_modify (gpointer data)
{
- GString *s;
+ RandomMenu *menu = data;
+ GRand *rand;
+ gint i;
+
+ rand = g_rand_new_with_seed (g_test_rand_int ());
- s = g_string_new ("<menu>\n");
- g_menu_markup_print_string (s, menu, 2, 2);
- g_string_append (s, "</menu>\n");
+ for (i = 0; i < 10000; i++)
+ {
+ random_menu_change (menu, rand);
+ }
- return g_string_free (s, FALSE);
+ return NULL;
}
-static void
-test_markup_roundtrip (void)
-{
- const gchar data[] =
- "<menu id='edit-menu'>\n"
- " <section>\n"
- " <item action='undo'>\n"
- " <attribute name='label' translatable='yes' context='Stock label'>'_Undo'</attribute>\n"
- " </item>\n"
- " <item label='Redo' action='redo'/>\n"
- " </section>\n"
- " <section></section>\n"
- " <section label='Copy & Paste'>\n"
- " <item label='Cut' action='cut'/>\n"
- " <item label='Copy' action='copy'/>\n"
- " <item label='Paste' action='paste'/>\n"
- " </section>\n"
- " <section>\n"
- " <item label='Bold' action='bold'/>\n"
- " <submenu label='Language'>\n"
- " <item label='Latin' action='lang' target='latin'/>\n"
- " <item label='Greek' action='lang' target='greek'/>\n"
- " <item label='Urdu' action='lang' target='urdu'/>\n"
- " </submenu>\n"
- " <item name='test unusual attributes'>\n"
- " <attribute name='action' type='s'>'quite-some-action'</attribute>\n"
- " <attribute name='target' type='i'>36</attribute>\n"
- " <attribute name='chocolate-thunda' type='as'>['a','b']</attribute>\n"
- " <attribute name='thing1' type='g'>'s(uu)'</attribute>\n"
- " <attribute name='icon' type='s'>'small blue thing'</attribute>\n"
- " </item>\n"
- " </section>\n"
- "</menu>\n";
+static gpointer
+do_export (gpointer data)
+{
+ GMenuModel *menu = data;
+ gint i;
+ GDBusConnection *bus;
+ gchar *path;
GError *error = NULL;
- GMenuModel *a;
- GMenuModel *b;
- gchar *s;
- gchar *s2;
+ guint id;
- a = parse_menu_string (data, &error);
- g_assert_no_error (error);
- g_assert (G_IS_MENU_MODEL (a));
+ bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+ path = g_strdup_printf ("/%p", data);
- /* normalized representation */
- s = menu_to_string (a);
+ for (i = 0; i < 10000; i++)
+ {
+ id = g_dbus_connection_export_menu_model (bus, path, menu, &error);
+ g_assert_no_error (error);
+ g_dbus_connection_unexport_menu_model (bus, id);
+ while (g_main_context_iteration (NULL, FALSE));
+ }
- b = parse_menu_string (s, &error);
- g_assert_no_error (error);
- g_assert (G_IS_MENU_MODEL (b));
+ g_free (path);
- assert_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b));
+ g_object_unref (bus);
- s2 = menu_to_string (b);
+ return NULL;
+}
- g_assert_cmpstr (s, ==, s2);
+static void
+test_dbus_threaded (void)
+{
+ RandomMenu *menu[10];
+ GThread *call[10];
+ GThread *export[10];
+ gint i;
- g_object_unref (a);
- g_object_unref (b);
- g_free (s);
- g_free (s2);
+ for (i = 0; i < 10; i++)
+ {
+ menu[i] = random_menu_new (g_rand_new_with_seed (g_test_rand_int ()), 2);
+ call[i] = g_thread_new ("call", do_modify, menu[i]);
+ export[i] = g_thread_new ("export", do_export, menu[i]);
+ }
+
+ for (i = 0; i < 10; i++)
+ {
+ g_thread_join (call[i]);
+ g_thread_join (export[i]);
+ }
+
+ for (i = 0; i < 10; i++)
+ g_object_unref (menu[i]);
}
static void
item = g_menu_item_new ("test", NULL);
g_menu_item_set_attribute_value (item, "boolean", g_variant_new_boolean (FALSE));
g_menu_item_set_attribute_value (item, "string", g_variant_new_string ("bla"));
- g_menu_item_set_attribute_value (item, "double", g_variant_new_double (1.5));
+
+ g_menu_item_set_attribute (item, "double", "d", 1.5);
v = g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three");
g_menu_item_set_attribute_value (item, "complex", v);
g_menu_item_set_attribute_value (item, "test-123", g_variant_new_string ("test-123"));
g_menu_append_item (menu, item);
+ g_menu_item_set_attribute (item, "double", "d", G_PI);
+
g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 1);
v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "boolean", NULL);
g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE("a(si)")));
g_variant_unref (v);
+ g_menu_remove_all (menu);
+
g_object_unref (menu);
+ g_object_unref (item);
}
static void
menu = g_menu_new ();
- item = g_menu_item_new ("test1", NULL);
- g_menu_item_set_link (item, "section", m);
- g_menu_append_item (menu, item);
-
item = g_menu_item_new ("test2", NULL);
g_menu_item_set_link (item, "submenu", m);
- g_menu_append_item (menu, item);
+ g_menu_prepend_item (menu, item);
+
+ item = g_menu_item_new ("test1", NULL);
+ g_menu_item_set_link (item, "section", m);
+ g_menu_insert_item (menu, 0, item);
item = g_menu_item_new ("test3", NULL);
g_menu_item_set_link (item, "wallet", m);
- g_menu_append_item (menu, item);
+ g_menu_insert_item (menu, 1000, item);
item = g_menu_item_new ("test4", NULL);
g_menu_item_set_link (item, "purse", m);
g_object_unref (menu);
}
+static void
+test_convenience (void)
+{
+ GMenu *m1, *m2;
+ GMenu *sub;
+ GMenuItem *item;
+
+ m1 = g_menu_new ();
+ m2 = g_menu_new ();
+ sub = g_menu_new ();
+
+ g_menu_prepend (m1, "label1", "do::something");
+ g_menu_insert (m2, 0, "label1", "do::something");
+
+ g_menu_append (m1, "label2", "do::somethingelse");
+ g_menu_insert (m2, -1, "label2", "do::somethingelse");
+
+ g_menu_insert_section (m1, 10, "label3", G_MENU_MODEL (sub));
+ item = g_menu_item_new_section ("label3", G_MENU_MODEL (sub));
+ g_menu_insert_item (m2, 10, item);
+ g_object_unref (item);
+
+ g_menu_prepend_section (m1, "label4", G_MENU_MODEL (sub));
+ g_menu_insert_section (m2, 0, "label4", G_MENU_MODEL (sub));
+
+ g_menu_append_section (m1, "label5", G_MENU_MODEL (sub));
+ g_menu_insert_section (m2, -1, "label5", G_MENU_MODEL (sub));
+
+ g_menu_insert_submenu (m1, 5, "label6", G_MENU_MODEL (sub));
+ item = g_menu_item_new_submenu ("label6", G_MENU_MODEL (sub));
+ g_menu_insert_item (m2, 5, item);
+ g_object_unref (item);
+
+ g_menu_prepend_submenu (m1, "label7", G_MENU_MODEL (sub));
+ g_menu_insert_submenu (m2, 0, "label7", G_MENU_MODEL (sub));
+
+ g_menu_append_submenu (m1, "label8", G_MENU_MODEL (sub));
+ g_menu_insert_submenu (m2, -1, "label8", G_MENU_MODEL (sub));
+
+ assert_menus_equal (G_MENU_MODEL (m1), G_MENU_MODEL (m2));
+
+ g_object_unref (m1);
+ g_object_unref (m2);
+}
+
+static void
+test_menuitem (void)
+{
+ GMenu *menu;
+ GMenu *submenu;
+ GMenuItem *item;
+ GIcon *icon;
+ gboolean b;
+ gchar *s;
+
+ menu = g_menu_new ();
+ submenu = g_menu_new ();
+
+ item = g_menu_item_new ("label", "action");
+ g_menu_item_set_attribute (item, "attribute", "b", TRUE);
+ g_menu_item_set_link (item, G_MENU_LINK_SUBMENU, G_MENU_MODEL (submenu));
+ g_menu_append_item (menu, item);
+
+ icon = g_themed_icon_new ("bla");
+ g_menu_item_set_icon (item, icon);
+ g_object_unref (icon);
+
+ g_assert (g_menu_item_get_attribute (item, "attribute", "b", &b));
+ g_assert (b);
+
+ g_menu_item_set_action_and_target (item, "action", "(bs)", TRUE, "string");
+ g_assert (g_menu_item_get_attribute (item, "target", "(bs)", &b, &s));
+ g_assert (b);
+ g_assert_cmpstr (s, ==, "string");
+ g_free (s);
+
+ g_object_unref (item);
+
+ item = g_menu_item_new_from_model (G_MENU_MODEL (menu), 0);
+ assert_menuitem_equal (item, G_MENU_MODEL (menu), 0);
+ g_object_unref (item);
+
+ g_object_unref (menu);
+ g_object_unref (submenu);
+}
+
/* Epilogue {{{1 */
int
main (int argc, char **argv)
{
+ gboolean ret;
+
g_test_init (&argc, &argv, NULL);
- g_type_init ();
+ session_bus_up ();
g_test_add_func ("/gmenu/equality", test_equality);
g_test_add_func ("/gmenu/random", test_random);
- g_test_add_func ("/gmenu/bus/roundtrip", test_dbus_roundtrip);
- g_test_add_func ("/gmenu/markup/roundtrip", test_markup_roundtrip);
+ g_test_add_func ("/gmenu/dbus/roundtrip", test_dbus_roundtrip);
+ g_test_add_func ("/gmenu/dbus/subscriptions", test_dbus_subscriptions);
+ g_test_add_func ("/gmenu/dbus/threaded", test_dbus_threaded);
g_test_add_func ("/gmenu/attributes", test_attributes);
g_test_add_func ("/gmenu/links", test_links);
g_test_add_func ("/gmenu/mutable", test_mutable);
+ g_test_add_func ("/gmenu/convenience", test_convenience);
+ g_test_add_func ("/gmenu/menuitem", test_menuitem);
+
+ ret = g_test_run ();
+
+ session_bus_down ();
- return g_test_run ();
+ return ret;
}
/* vim:set foldmethod=marker: */