/* Copyright (C) 2019 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include static int win_activated; static int box_activated; static void win_activate (GSimpleAction *action, GVariant *parameter, gpointer user_data) { win_activated++; } static void box_activate (GSimpleAction *action, GVariant *parameter, gpointer user_data) { box_activated++; } /* Test that inheriting actions along the widget * hierarchy works as expected. Since GtkWidget does * not expose the actions, we test this by observing * the effect of activating them. */ static void test_action (void) { GtkWidget *window; GtkWidget *box; GtkWidget *button; GSimpleActionGroup *win_actions; GSimpleActionGroup *box_actions; GActionEntry win_entries[] = { { "action", win_activate, NULL, NULL, NULL }, }; GActionEntry box_entries[] = { { "action", box_activate, NULL, NULL, NULL }, }; window = gtk_window_new (); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); button = gtk_button_new (); gtk_container_add (GTK_CONTAINER (window), box); gtk_container_add (GTK_CONTAINER (box), button); win_actions = g_simple_action_group_new (); g_action_map_add_action_entries (G_ACTION_MAP (win_actions), win_entries, G_N_ELEMENTS (win_entries), NULL); box_actions = g_simple_action_group_new (); g_action_map_add_action_entries (G_ACTION_MAP (box_actions), box_entries, G_N_ELEMENTS (box_entries), NULL); gtk_widget_insert_action_group (window, "win", G_ACTION_GROUP (win_actions)); gtk_widget_insert_action_group (box, "box", G_ACTION_GROUP (box_actions)); g_assert_cmpint (win_activated, ==, 0); g_assert_cmpint (box_activated, ==, 0); gtk_widget_activate_action (button, "win.action", NULL); g_assert_cmpint (win_activated, ==, 1); g_assert_cmpint (box_activated, ==, 0); gtk_widget_activate_action (box, "win.action", NULL); g_assert_cmpint (win_activated, ==, 2); g_assert_cmpint (box_activated, ==, 0); gtk_widget_activate_action (button, "box.action", NULL); g_assert_cmpint (win_activated, ==, 2); g_assert_cmpint (box_activated, ==, 1); gtk_widget_activate_action (window, "box.action", NULL); g_assert_cmpint (win_activated, ==, 2); g_assert_cmpint (box_activated, ==, 1); gtk_widget_destroy (window); g_object_unref (win_actions); g_object_unref (box_actions); } static int cut_activated; static int copy_activated; static int paste_activated; static int visibility_changed; static void cut_activate (GSimpleAction *action, GVariant *parameter, gpointer user_data) { cut_activated++; } static void copy_activate (GSimpleAction *action, GVariant *parameter, gpointer user_data) { copy_activated++; } static void paste_activate (GSimpleAction *action, GVariant *parameter, gpointer user_data) { paste_activated++; } static void visibility_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_data) { visibility_changed++; } /* Spot-check that GtkText has the class actions * for the context menu. Here we test that the clipboard * actions are present, and that toggling visibility * via the action works. */ static void test_text (void) { GtkWidget *box; GtkWidget *text; GSimpleActionGroup *clipboard_actions; GActionEntry clipboard_entries[] = { { "cut", cut_activate, NULL, NULL, NULL }, { "copy", copy_activate, NULL, NULL, NULL }, { "paste", paste_activate, NULL, NULL, NULL }, }; box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); text = gtk_text_new (); gtk_container_add (GTK_CONTAINER (box), text); clipboard_actions = g_simple_action_group_new (); g_action_map_add_action_entries (G_ACTION_MAP (clipboard_actions), clipboard_entries, G_N_ELEMENTS (clipboard_entries), NULL); gtk_widget_insert_action_group (box, "clipboard", G_ACTION_GROUP (clipboard_actions)); gtk_widget_activate_action (text, "cut.clipboard", NULL); gtk_widget_activate_action (text, "copy.clipboard", NULL); gtk_widget_activate_action (text, "paste.clipboard", NULL); g_assert_cmpint (cut_activated, ==, 0); g_assert_cmpint (copy_activated, ==, 0); g_assert_cmpint (paste_activated, ==, 0); g_signal_connect (text, "notify::visibility", G_CALLBACK (visibility_changed_cb), NULL); gtk_widget_activate_action (text, "misc.toggle-visibility", NULL); g_assert_cmpint (visibility_changed, ==, 1); gtk_widget_destroy (box); g_object_unref (clipboard_actions); } /* Test that inheritance works for individual actions * even if they are in groups with the same prefix. * This is a change from the way things work in GTK3. */ static void test_overlap (void) { GtkWidget *window; GtkWidget *box; GActionEntry win_entries[] = { { "win", win_activate, NULL, NULL, NULL }, }; GActionEntry box_entries[] = { { "box", box_activate, NULL, NULL, NULL }, }; GSimpleActionGroup *win_actions; GSimpleActionGroup *box_actions; window = gtk_window_new (); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_container_add (GTK_CONTAINER (window), box); win_actions = g_simple_action_group_new (); g_action_map_add_action_entries (G_ACTION_MAP (win_actions), win_entries, G_N_ELEMENTS (win_entries), NULL); box_actions = g_simple_action_group_new (); g_action_map_add_action_entries (G_ACTION_MAP (box_actions), box_entries, G_N_ELEMENTS (box_entries), NULL); gtk_widget_insert_action_group (window, "actions", G_ACTION_GROUP (win_actions)); gtk_widget_insert_action_group (box, "actions", G_ACTION_GROUP (box_actions)); win_activated = 0; box_activated = 0; gtk_widget_activate_action (box, "actions.win", NULL); g_assert_cmpint (win_activated, ==, 1); g_assert_cmpint (box_activated, ==, 0); gtk_widget_activate_action (box, "actions.box", NULL); g_assert_cmpint (win_activated, ==, 1); g_assert_cmpint (box_activated, ==, 1); gtk_widget_destroy (window); g_object_unref (win_actions); g_object_unref (box_actions); } static int toggled; static int act1; static int act2; static void visibility_toggled (GObject *object, GParamSpec *pspec, gpointer data) { toggled++; } static void activate1 (GSimpleAction *action, GVariant *parameter, gpointer user_data) { act1++; } static void activate2 (GSimpleAction *action, GVariant *parameter, gpointer user_data) { act2++; } /* Test that overlap also works as expected between * class action and inserted groups. Class actions * take precedence over inserted groups in the same * muxer, but inheritance works as normal between * muxers. */ static void test_overlap2 (void) { GtkWidget *text; GtkWidget *child; GSimpleActionGroup *group1; GSimpleActionGroup *group2; GActionEntry entries1[] = { { "toggle-visibility", activate1, NULL, NULL, NULL }, }; GActionEntry entries2[] = { { "toggle-visibility", activate2, NULL, NULL, NULL }, }; text = gtk_text_new (); g_signal_connect (text, "notify::visibility", G_CALLBACK (visibility_toggled), NULL); child = gtk_label_new (""); gtk_widget_set_parent (child, text); g_assert_cmpint (toggled, ==, 0); g_assert_cmpint (act1, ==, 0); g_assert_cmpint (act2, ==, 0); gtk_widget_activate_action (child, "misc.toggle-visibility", NULL); g_assert_cmpint (toggled, ==, 1); g_assert_cmpint (act1, ==, 0); g_assert_cmpint (act2, ==, 0); group1 = g_simple_action_group_new (); g_action_map_add_action_entries (G_ACTION_MAP (group1), entries1, G_N_ELEMENTS (entries1), NULL); gtk_widget_insert_action_group (text, "misc", G_ACTION_GROUP (group1)); gtk_widget_activate_action (child, "misc.toggle-visibility", NULL); g_assert_cmpint (toggled, ==, 2); g_assert_cmpint (act1, ==, 0); g_assert_cmpint (act2, ==, 0); group2 = g_simple_action_group_new (); g_action_map_add_action_entries (G_ACTION_MAP (group2), entries2, G_N_ELEMENTS (entries2), NULL); gtk_widget_insert_action_group (child, "misc", G_ACTION_GROUP (group2)); gtk_widget_activate_action (child, "misc.toggle-visibility", NULL); g_assert_cmpint (toggled, ==, 2); g_assert_cmpint (act1, ==, 0); g_assert_cmpint (act2, ==, 1); gtk_widget_destroy (text); g_object_unref (group1); g_object_unref (group2); } /* Test that gtk_widget_class_query_action * yields the expected results */ static void test_introspection (void) { GtkWidgetClass *class = g_type_class_ref (GTK_TYPE_TEXT); guint i; GType owner; const char *name; const GVariantType *params; const char *property; struct { GType owner; const char *name; const char *params; const char *property; } expected[] = { { GTK_TYPE_TEXT, "misc.toggle-visibility", NULL, "visibility" }, { GTK_TYPE_TEXT, "misc.insert-emoji", NULL, NULL }, { GTK_TYPE_TEXT, "selection.select-all", NULL, NULL }, { GTK_TYPE_TEXT, "selection.delete", NULL, NULL }, { GTK_TYPE_TEXT, "clipboard.paste", NULL, NULL }, { GTK_TYPE_TEXT, "clipboard.copy", NULL, NULL }, { GTK_TYPE_TEXT, "clipboard.cut", NULL, NULL }, { GTK_TYPE_TEXT, "menu.popup", NULL, NULL }, { GTK_TYPE_TEXT, "text.redo", NULL, NULL }, { GTK_TYPE_TEXT, "text.undo", NULL, NULL }, }; i = 0; while (gtk_widget_class_query_action (class, i, &owner, &name, ¶ms, &property)) { g_assert (expected[i].owner == owner); g_assert_cmpstr (expected[i].name, ==, name); g_assert_cmpstr (expected[i].params, ==, params ? g_variant_type_peek_string (params) : NULL); g_assert_cmpstr (expected[i].property, ==, property); i++; } g_assert (i == G_N_ELEMENTS (expected)); g_type_class_unref (class); } /* Test that disabled actions don't get activated */ static void test_enabled (void) { GtkWidget *text; text = gtk_text_new (); g_signal_connect (text, "notify::visibility", G_CALLBACK (visibility_toggled), NULL); toggled = 0; gtk_widget_activate_action (text, "misc.toggle-visibility", NULL); g_assert_cmpint (toggled, ==, 1); gtk_widget_action_set_enabled (text, "misc.toggle-visibility", FALSE); gtk_widget_activate_action (text, "misc.toggle-visibility", NULL); g_assert_cmpint (toggled, ==, 1); gtk_widget_destroy (text); } int main (int argc, char *argv[]) { gtk_test_init (&argc, &argv); g_test_add_func ("/action/inheritance", test_action); g_test_add_func ("/action/text", test_text); g_test_add_func ("/action/overlap", test_overlap); g_test_add_func ("/action/overlap2", test_overlap2); g_test_add_func ("/action/introspection", test_introspection); g_test_add_func ("/action/enabled", test_enabled); return g_test_run(); }