From 8841c04e2256679e3fad84bb2d56adc1c410db71 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 30 Oct 2011 12:35:12 -0400 Subject: [PATCH] Some GMenu test code This is some test code for constructing GtkMenus from GMenus. --- tests/Makefile.am | 5 +- tests/testgmenu.c | 728 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 732 insertions(+), 1 deletion(-) create mode 100644 tests/testgmenu.c diff --git a/tests/Makefile.am b/tests/Makefile.am index 89552d2dd..6aa01124f 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -121,7 +121,8 @@ noinst_PROGRAMS = $(TEST_PROGS) \ testpixbuf-save \ testpixbuf-color \ testpixbuf-scale \ - widget-factory + widget-factory \ + testgmenu if USE_X11 noinst_PROGRAMS += testerrors @@ -237,6 +238,7 @@ testpixbuf_save_DEPENDENCIES = $(TEST_DEPS) testpixbuf_color_DEPENDENCIES = $(TEST_DEPS) testpixbuf_scale_DEPENDENCIES = $(TEST_DEPS) widget_factory_DEPENDENCIES = $(TEST_DEPS) +testgmenu_DEPENDENCIES = $(TEST_DEPS) flicker_LDADD = $(LDADDS) simple_LDADD = $(LDADDS) @@ -336,6 +338,7 @@ testpixbuf_save_LDADD = $(LDADDS) testpixbuf_color_LDADD = $(LDADDS) testpixbuf_scale_LDADD = $(LDADDS) widget_factory_LDADD = $(LDADDS) +testgmenu_LDADD = $(LDADDS) testboxcss_SOURCES = \ diff --git a/tests/testgmenu.c b/tests/testgmenu.c new file mode 100644 index 000000000..e67d1f87a --- /dev/null +++ b/tests/testgmenu.c @@ -0,0 +1,728 @@ +/* testgmenu.c + * Copyright (C) 2011 Red Hat, Inc. + * Written by Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +/* GtkMenu construction {{{1 */ + +static void +enabled_changed (GActionGroup *group, + const gchar *action_name, + gboolean enabled, + GtkWidget *widget) +{ + gtk_widget_set_sensitive (widget, enabled); +} + +typedef struct { + GActionGroup *group; + const gchar *name; + const gchar *target; +} Activation; + +static void +activate_item (GtkWidget *w, gpointer data) +{ + Activation *a; + + a = g_object_get_data (G_OBJECT (w), "activation"); + + g_action_group_activate_action (a->group, a->name, NULL); +} + +static void +toggle_item_toggled (GtkCheckMenuItem *w, gpointer data) +{ + Activation *a; + gboolean b; + + a = g_object_get_data (G_OBJECT (w), "activation"); + b = gtk_check_menu_item_get_active (w); + g_action_group_change_action_state (a->group, a->name, + g_variant_new_boolean (b)); +} + +static void +toggle_state_changed (GActionGroup *group, + const gchar *name, + GVariant *state, + GtkCheckMenuItem *w) +{ + gtk_check_menu_item_set_active (w, g_variant_get_boolean (state)); +} + +static void +radio_item_toggled (GtkCheckMenuItem *w, gpointer data) +{ + Activation *a; + GVariant *v; + + a = g_object_get_data (G_OBJECT (w), "activation"); + /*g_print ("Radio item %s toggled\n", a->name);*/ + if (gtk_check_menu_item_get_active (w)) + g_action_group_change_action_state (a->group, a->name, + g_variant_new_string (a->target)); + else + { + v = g_action_group_get_action_state (a->group, a->name); + if (g_strcmp0 (g_variant_get_string (v, NULL), a->target) == 0) + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w), TRUE); + g_variant_unref (v); + } +} + +static void +radio_state_changed (GActionGroup *group, + const gchar *name, + GVariant *state, + GtkCheckMenuItem *w) +{ + Activation *a; + gboolean b; + + /*g_print ("Radio state changed %s\n", name);*/ + a = g_object_get_data (G_OBJECT (w), "activation"); + b = g_strcmp0 (a->target, g_variant_get_string (state, NULL)) == 0; + + gtk_check_menu_item_set_active (w, b); +} + +static GtkWidget * +create_menuitem_from_model (GMenuModelItem *item, + GActionGroup *group) +{ + GtkWidget *w; + gchar *label; + gchar *action; + gchar *target; + gchar *s; + Activation *a; + const GVariantType *type; + GVariant *v; + + g_menu_model_item_get_attribute (item, G_MENU_ATTRIBUTE_LABEL, "s", &label); + + action = NULL; + g_menu_model_item_get_attribute (item, G_MENU_ATTRIBUTE_ACTION, "s", &action); + + if (action != NULL) + type = g_action_group_get_action_state_type (group, action); + else + type = NULL; + + if (type == NULL) + w = gtk_menu_item_new_with_mnemonic (label); + else if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN)) + w = gtk_check_menu_item_new_with_label (label); + else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING)) + { + w = gtk_check_menu_item_new_with_label (label); + gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (w), TRUE); + } + else + g_assert_not_reached (); + + if (action != NULL) + { + if (!g_action_group_get_action_enabled (group, action)) + gtk_widget_set_sensitive (w, FALSE); + + s = g_strconcat ("action-enabled-changed::", action, NULL); + g_signal_connect (group, s, G_CALLBACK (enabled_changed), w); + g_free (s); + + a = g_new0 (Activation, 1); + a->group = group; + a->name = action; + g_object_set_data_full (G_OBJECT (w), "activation", a, g_free); + + if (type == NULL) + g_signal_connect (w, "activate", G_CALLBACK (activate_item), NULL); + else if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN)) + { + g_signal_connect (w, "toggled", G_CALLBACK (toggle_item_toggled), NULL); + s = g_strconcat ("action-state-changed::", action, NULL); + g_signal_connect (group, s, G_CALLBACK (toggle_state_changed), w); + g_free (s); + v = g_action_group_get_action_state (group, action); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w), + g_variant_get_boolean (v)); + g_variant_unref (v); + } + else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING)) + { + g_signal_connect (w, "toggled", G_CALLBACK (radio_item_toggled), NULL); + s = g_strconcat ("action-state-changed::", action, NULL); + g_signal_connect (group, s, G_CALLBACK (radio_state_changed), w); + g_free (s); + g_menu_model_item_get_attribute (item, G_MENU_ATTRIBUTE_TARGET, "s", &target); + a->target = target; + v = g_action_group_get_action_state (group, action); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w), + g_strcmp0 (g_variant_get_string (v, NULL), target) == 0); + g_variant_unref (v); + } + } + + g_free (label); + + return w; +} + +static GtkWidget *create_menu_from_model (GMenuModel *model, + GActionGroup *group); + +static void +append_items_from_model (GtkWidget *menu, + GMenuModel *model, + GActionGroup *group, + gboolean *need_separator) +{ + gint n; + gint i; + GtkWidget *w; + GtkWidget *menuitem; + GMenuModelItem item; + GMenuModel *m; + + n = g_menu_model_get_n_items (model); + + if (*need_separator && n > 0) + { + /* TODO section heading */ + w = gtk_separator_menu_item_new (); + gtk_widget_show (w); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), w); + + *need_separator = FALSE; + } + + for (i = 0; i < n; i++) + { + g_menu_model_get_item (model, i, &item); + if ((m = g_menu_model_item_get_link (&item, G_MENU_LINK_SECTION))) + { + append_items_from_model (menu, m, group, need_separator); + continue; + } + + menuitem = create_menuitem_from_model (&item, group); + + if ((m = g_menu_model_item_get_link (&item, G_MENU_LINK_SUBMENU))) + gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), create_menu_from_model (m, group)); + + gtk_widget_show (menuitem); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + + *need_separator = TRUE; + } +} + +static GtkWidget * +create_menu_from_model (GMenuModel *model, + GActionGroup *group) +{ + GtkWidget *w; + gboolean need_separator; + + w = gtk_menu_new (); + need_separator = FALSE; + append_items_from_model (w, model, group, &need_separator); + + return w; +} + +/* The example menu {{{1 */ + +static const gchar menu_markup[] = + "\n" + "
\n" + " \n" + " \n" + "
\n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "
\n"; + +static void +start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + if (strcmp (element_name, "menu") == 0) + g_menu_markup_parser_start_menu (context, NULL); +} + +static void +end_element (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + GMenu **menu = user_data; + + if (strcmp (element_name, "menu") == 0) + *menu = g_menu_markup_parser_end_menu (context); +} + +static const GMarkupParser parser = { + start_element, end_element, NULL, NULL, NULL +}; + +static GMenuModel * +get_model (void) +{ + GMarkupParseContext *context; + GMenu *menu = NULL; + GError *error = NULL; + + context = g_markup_parse_context_new (&parser, 0, &menu, NULL); + if (!g_markup_parse_context_parse (context, menu_markup, -1, &error)) + { + g_warning ("menu parsing failed: %s\n", error->message); + exit (1); + } + g_markup_parse_context_free (context); + g_assert (menu); + + return G_MENU_MODEL (menu); +} + +/* The example actions {{{1 */ + +static void +activate_action (GSimpleAction *action, GVariant *parameter, gpointer user_data) +{ + g_print ("Action %s activated\n", g_action_get_name (G_ACTION (action))); +} + +static void +toggle_changed (GSimpleAction *action, GVariant *value, gpointer user_data) +{ + g_print ("Toggle action %s state changed to %d\n", + g_action_get_name (G_ACTION (action)), + g_variant_get_boolean (value)); + + g_simple_action_set_state (action, value); +} + +static void +radio_changed (GSimpleAction *action, GVariant *value, gpointer user_data) +{ + g_print ("Radio action %s state changed to %s\n", + g_action_get_name (G_ACTION (action)), + g_variant_get_string (value, NULL)); + + g_simple_action_set_state (action, value); +} + +static GActionEntry actions[] = { + { "undo", activate_action, NULL, NULL, NULL }, + { "redo", activate_action, NULL, NULL, NULL }, + { "cut", activate_action, NULL, NULL, NULL }, + { "copy", activate_action, NULL, NULL, NULL }, + { "paste", activate_action, NULL, NULL, NULL }, + { "bold", NULL, NULL, "true", toggle_changed }, + { "lang", NULL, NULL, "'latin'", radio_changed }, +}; + +static GActionGroup * +get_group (void) +{ + GSimpleActionGroup *group; + + group = g_simple_action_group_new (); + + g_simple_action_group_add_entries (group, actions, G_N_ELEMENTS (actions), NULL); + + return G_ACTION_GROUP (group); +} + +/* The action treeview {{{1 */ + +static void +enabled_cell_func (GtkTreeViewColumn *column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + GActionGroup *group = data; + gchar *name; + gboolean enabled; + + gtk_tree_model_get (model, iter, 0, &name, -1); + enabled = g_action_group_get_action_enabled (group, name); + g_free (name); + + gtk_cell_renderer_toggle_set_active (GTK_CELL_RENDERER_TOGGLE (cell), enabled); +} + +static void +state_cell_func (GtkTreeViewColumn *column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + GActionGroup *group = data; + gchar *name; + GVariant *state; + + gtk_tree_model_get (model, iter, 0, &name, -1); + state = g_action_group_get_action_state (group, name); + g_free (name); + + gtk_cell_renderer_set_visible (cell, FALSE); + g_object_set (cell, "mode", GTK_CELL_RENDERER_MODE_INERT, NULL); + + if (state && + g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN) && + GTK_IS_CELL_RENDERER_TOGGLE (cell)) + { + gtk_cell_renderer_set_visible (cell, TRUE); + g_object_set (cell, "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL); + gtk_cell_renderer_toggle_set_active (GTK_CELL_RENDERER_TOGGLE (cell), + g_variant_get_boolean (state)); + } + else if (state && + g_variant_is_of_type (state, G_VARIANT_TYPE_STRING) && + GTK_IS_CELL_RENDERER_COMBO (cell)) + { + gtk_cell_renderer_set_visible (cell, TRUE); + g_object_set (cell, "mode", GTK_CELL_RENDERER_MODE_EDITABLE, NULL); + g_object_set (cell, "text", g_variant_get_string (state, NULL), NULL); + } + + if (state) + g_variant_unref (state); +} + +static void +enabled_cell_toggled (GtkCellRendererToggle *cell, + const gchar *path_str, + GtkTreeModel *model) +{ + GActionGroup *group; + GAction *action; + gchar *name; + GtkTreePath *path; + GtkTreeIter iter; + gboolean enabled; + + group = g_object_get_data (G_OBJECT (model), "group"); + path = gtk_tree_path_new_from_string (path_str); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, 0, &name, -1); + + enabled = g_action_group_get_action_enabled (group, name); + action = g_simple_action_group_lookup (G_SIMPLE_ACTION_GROUP (group), name); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !enabled); + + gtk_tree_model_row_changed (model, path, &iter); + + g_free (name); + gtk_tree_path_free (path); +} + +static void +state_cell_toggled (GtkCellRendererToggle *cell, + const gchar *path_str, + GtkTreeModel *model) +{ + GActionGroup *group; + GAction *action; + gchar *name; + GtkTreePath *path; + GtkTreeIter iter; + GVariant *state; + + group = g_object_get_data (G_OBJECT (model), "group"); + path = gtk_tree_path_new_from_string (path_str); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, 0, &name, -1); + + state = g_action_group_get_action_state (group, name); + action = g_simple_action_group_lookup (G_SIMPLE_ACTION_GROUP (group), name); + if (state && g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN)) + { + gboolean b; + + b = g_variant_get_boolean (state); + g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (!b)); + } + else + { + /* nothing to do */ + } + + gtk_tree_model_row_changed (model, path, &iter); + + g_free (name); + gtk_tree_path_free (path); + if (state) + g_variant_unref (state); +} + +static void +state_cell_edited (GtkCellRendererCombo *cell, + const gchar *path_str, + const gchar *new_text, + GtkTreeModel *model) +{ + GActionGroup *group; + GAction *action; + gchar *name; + GtkTreePath *path; + GtkTreeIter iter; + + group = g_object_get_data (G_OBJECT (model), "group"); + path = gtk_tree_path_new_from_string (path_str); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, 0, &name, -1); + action = g_simple_action_group_lookup (G_SIMPLE_ACTION_GROUP (group), name); + g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_string (new_text)); + + gtk_tree_model_row_changed (model, path, &iter); + + g_free (name); + gtk_tree_path_free (path); +} + +static GtkWidget * +create_action_treeview (GActionGroup *group) +{ + GtkWidget *tv; + GtkListStore *store; + GtkListStore *values; + GtkTreeIter iter; + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + gchar **actions; + gint i; + + store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING); + actions = g_action_group_list_actions (group); + for (i = 0; actions[i]; i++) + { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, actions[i], -1); + } + g_strfreev (actions); + g_object_set_data (G_OBJECT (store), "group", group); + + tv = gtk_tree_view_new (); + + g_signal_connect_swapped (group, "action-enabled-changed", + G_CALLBACK (gtk_widget_queue_draw), tv); + g_signal_connect_swapped (group, "action-state-changed", + G_CALLBACK (gtk_widget_queue_draw), tv); + + gtk_tree_view_set_model (GTK_TREE_VIEW (tv), GTK_TREE_MODEL (store)); + + cell = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("Action", cell, + "text", 0, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column); + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, "Enabled"); + cell = gtk_cell_renderer_toggle_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_set_cell_data_func (column, cell, enabled_cell_func, group, NULL); + g_signal_connect (cell, "toggled", G_CALLBACK (enabled_cell_toggled), store); + gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column); + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, "State"); + cell = gtk_cell_renderer_toggle_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_set_cell_data_func (column, cell, state_cell_func, group, NULL); + g_signal_connect (cell, "toggled", G_CALLBACK (state_cell_toggled), store); + cell = gtk_cell_renderer_combo_new (); + values = gtk_list_store_new (1, G_TYPE_STRING); + gtk_list_store_append (values, &iter); + gtk_list_store_set (values, &iter, 0, "latin", -1); + gtk_list_store_append (values, &iter); + gtk_list_store_set (values, &iter, 0, "greek", -1); + gtk_list_store_append (values, &iter); + gtk_list_store_set (values, &iter, 0, "urdu", -1); + g_object_set (cell, + "has-entry", FALSE, + "model", values, + "text-column", 0, + "editable", TRUE, + NULL); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_set_cell_data_func (column, cell, state_cell_func, group, NULL); + g_signal_connect (cell, "edited", G_CALLBACK (state_cell_edited), store); + gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column); + + return tv; +} + +/* The menu button {{{1 */ + +static void +button_clicked (GtkButton *button, gpointer data) +{ + GMenuModel *model; + GActionGroup *group; + GtkWidget *menu; + + menu = g_object_get_data (G_OBJECT (button), "menu"); + if (!menu) + { + model = g_object_get_data (G_OBJECT (button), "model"); + group = g_object_get_data (G_OBJECT (button), "group"); + menu = create_menu_from_model (model, group); + g_object_set_data_full (G_OBJECT (button), "menu", menu, (GDestroyNotify)gtk_widget_destroy); + } + + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 0, 0); +} + +static void +items_changed (GMenuModel *model, + gint position, + gint removed, + gint added, + GtkButton *button) +{ + g_object_set_data (G_OBJECT (button), "menu", NULL); +} + +static GtkWidget * +create_menu_button (GMenuModel *model, GActionGroup *group) +{ + GtkWidget *button; + + button = gtk_button_new_with_label ("Click here"); + g_object_set_data (G_OBJECT (button), "model", model); + g_object_set_data (G_OBJECT (button), "group", group); + + g_signal_connect (button, "clicked", G_CALLBACK (button_clicked), NULL); + g_signal_connect (model, "items-changed", G_CALLBACK (items_changed), button); + + return button; +} + +/* main {{{1 */ + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *box; + GtkWidget *button; + GtkWidget *tv; + GMenuModel *model; + GActionGroup *group; + GDBusConnection *bus; + GError *error = NULL; + gboolean do_export = FALSE; + gboolean do_import = FALSE; + GOptionEntry entries[] = { + { "export", 0, 0, G_OPTION_ARG_NONE, &do_export, "Export actions and menus over D-Bus", NULL }, + { "import", 0, 0, G_OPTION_ARG_NONE, &do_import, "Use exported actions and menus", NULL }, + { NULL, } + }; + + gtk_init_with_args (&argc, &argv, NULL, entries, NULL, NULL); + + if (do_export && do_import) + { + g_error ("can't have it both ways\n"); + exit (1); + } + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add (GTK_CONTAINER (window), box); + + bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); + + if (do_import) + { + g_print ("Getting menus from the bus...\n"); + model = (GMenuModel*)g_menu_proxy_get (bus, "org.gtk.TestMenus", "/path"); + g_print ("Getting actions from the bus...\n"); + group = (GActionGroup*)g_dbus_action_group_new_sync (bus, "org.gtk.TestMenus", "/path", 0, NULL, NULL); + } + else + { + group = get_group (); + model = get_model (); + + tv = create_action_treeview (group); + gtk_container_add (GTK_CONTAINER (box), tv); + } + + if (do_export) + { + g_print ("Exporting menus on the bus...\n"); + if (!g_menu_exporter_export (bus, "/path", model, &error)) + { + g_warning ("Menu export failed: %s", error->message); + exit (1); + } + g_print ("Exporting actions on the bus...\n"); + if (!g_action_group_exporter_export (bus, "/path", group, &error)) + { + g_warning ("Action export failed: %s", error->message); + exit (1); + } + g_bus_own_name_on_connection (bus, "org.gtk.TestMenus", + 0, NULL, NULL, NULL, NULL); + } + else + { + button = create_menu_button (model, group); + gtk_container_add (GTK_CONTAINER (box), button); + } + + gtk_widget_show_all (window); + + gtk_main (); + + return 0; +} + +/* Epilogue {{{1 */ +/* vim:set foldmethod=marker: */ -- 2.43.2