From: Ryan Lortie Date: Fri, 24 Aug 2012 18:11:37 +0000 (-0400) Subject: GtkModelMenuItem: add a submenu action attribute X-Git-Url: http://pileus.org/git/?p=~andy%2Fgtk;a=commitdiff_plain;h=338b5f6c2dc6cdb73ec3dfb6973b2269d4664b80 GtkModelMenuItem: add a submenu action attribute Add support for a stateful action associated with a submenu. The action state is set to TRUE when the menu is shown and FALSE when it is unshown. This is useful to avoid unnecessary processing for menus that have frequently-changing content. A possible future feature is to add support for asynchronously filling the initial state of the menu by waiting until the action actually emits its state-change signal to TRUE before showing the menu. A silly example has been added to Bloatpad to demonstrate the new feature. https://bugzilla.gnome.org/show_bug.cgi?id=682630 --- diff --git a/examples/bloatpad.c b/examples/bloatpad.c index 537b9d709..fc8e263e1 100644 --- a/examples/bloatpad.c +++ b/examples/bloatpad.c @@ -191,7 +191,14 @@ bloat_pad_open (GApplication *application, new_window (application, files[i]); } -typedef GtkApplication BloatPad; +typedef struct +{ + GtkApplication parent_instance; + + GMenu *time; + guint timeout; +} BloatPad; + typedef GtkApplicationClass BloatPadClass; G_DEFINE_TYPE (BloatPad, bloat_pad, GTK_TYPE_APPLICATION) @@ -234,15 +241,65 @@ quit_activated (GSimpleAction *action, g_application_quit (app); } +static gboolean +update_time (gpointer user_data) +{ + BloatPad *bloatpad = user_data; + GDateTime *now; + gchar *time; + + while (g_menu_model_get_n_items (G_MENU_MODEL (bloatpad->time))) + g_menu_remove (bloatpad->time, 0); + + g_message ("Updating the time menu (which should be open now)..."); + + now = g_date_time_new_now_local (); + time = g_date_time_format (now, "%c"); + g_menu_append (bloatpad->time, time, NULL); + g_date_time_unref (now); + g_free (time); + + return G_SOURCE_CONTINUE; +} + +static void +time_active_changed (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + BloatPad *bloatpad = user_data; + + if (g_variant_get_boolean (state)) + { + if (!bloatpad->timeout) + { + bloatpad->timeout = g_timeout_add (1000, update_time, bloatpad); + update_time (bloatpad); + } + } + else + { + if (bloatpad->timeout) + { + g_source_remove (bloatpad->timeout); + bloatpad->timeout = 0; + } + } + + g_simple_action_set_state (action, state); +} + static GActionEntry app_entries[] = { { "new", new_activated, NULL, NULL, NULL }, { "about", about_activated, NULL, NULL, NULL }, { "quit", quit_activated, NULL, NULL, NULL }, + { "time-active", NULL, NULL, "false", time_active_changed } }; static void bloat_pad_startup (GApplication *application) { + BloatPad *bloatpad = (BloatPad*) application; GtkBuilder *builder; G_APPLICATION_CLASS (bloat_pad_parent_class) @@ -301,13 +358,33 @@ bloat_pad_startup (GApplication *application) " " " " " " + " " + " Time" + " app.time-active" + " " " " "", -1, NULL); gtk_application_set_app_menu (GTK_APPLICATION (application), G_MENU_MODEL (gtk_builder_get_object (builder, "app-menu"))); gtk_application_set_menubar (GTK_APPLICATION (application), G_MENU_MODEL (gtk_builder_get_object (builder, "menubar"))); + bloatpad->time = G_MENU (gtk_builder_get_object (builder, "time-menu")); g_object_unref (builder); } +static void +bloat_pad_shutdown (GApplication *application) +{ + BloatPad *bloatpad = (BloatPad *) application; + + if (bloatpad->timeout) + { + g_source_remove (bloatpad->timeout); + bloatpad->timeout = 0; + } + + G_APPLICATION_CLASS (bloat_pad_parent_class) + ->shutdown (application); +} + static void bloat_pad_init (BloatPad *app) { @@ -320,6 +397,7 @@ bloat_pad_class_init (BloatPadClass *class) GObjectClass *object_class = G_OBJECT_CLASS (class); application_class->startup = bloat_pad_startup; + application_class->shutdown = bloat_pad_shutdown; application_class->activate = bloat_pad_activate; application_class->open = bloat_pad_open; @@ -330,7 +408,7 @@ bloat_pad_class_init (BloatPadClass *class) BloatPad * bloat_pad_new (void) { - GtkApplication *bloat_pad; + BloatPad *bloat_pad; g_type_init (); diff --git a/gtk/gtkmodelmenuitem.c b/gtk/gtkmodelmenuitem.c index d2c533ce1..450cd9a4b 100644 --- a/gtk/gtkmodelmenuitem.c +++ b/gtk/gtkmodelmenuitem.c @@ -24,6 +24,7 @@ #include "gtkaccelmapprivate.h" #include "gtkactionhelper.h" #include "gtkmodelmenu.h" +#include "gtkwidgetprivate.h" struct _GtkModelMenuItem { @@ -80,6 +81,28 @@ gtk_actionable_set_namespaced_action_name (GtkActionable *actionable, } } +static void +gtk_model_menu_item_submenu_shown (GtkWidget *widget, + gpointer user_data) +{ + const gchar *action_name = user_data; + GActionMuxer *muxer; + + muxer = _gtk_widget_get_action_muxer (widget); + g_action_group_change_action_state (G_ACTION_GROUP (muxer), action_name, g_variant_new_boolean (TRUE)); +} + +static void +gtk_model_menu_item_submenu_hidden (GtkWidget *widget, + gpointer user_data) +{ + const gchar *action_name = user_data; + GActionMuxer *muxer; + + muxer = _gtk_widget_get_action_muxer (widget); + g_action_group_change_action_state (G_ACTION_GROUP (muxer), action_name, g_variant_new_boolean (FALSE)); +} + static void gtk_model_menu_item_setup (GtkModelMenuItem *item, GMenuModel *model, @@ -129,6 +152,28 @@ gtk_model_menu_item_setup (GtkModelMenuItem *item, else if (g_str_equal (key, "target")) gtk_actionable_set_action_target_value (GTK_ACTIONABLE (item), value); + else if (g_str_equal (key, "submenu-action") && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) + { + GtkWidget *submenu; + + submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (item)); + + if (submenu != NULL) + { + const gchar *action = g_variant_get_string (value, NULL); + gchar *full_action; + + if (action_namespace) + full_action = g_strjoin (".", action_namespace, action, NULL); + else + full_action = g_strdup (action); + + g_object_set_data_full (G_OBJECT (submenu), "gtkmodelmenu-visibility-action", full_action, g_free); + g_signal_connect (submenu, "show", G_CALLBACK (gtk_model_menu_item_submenu_shown), full_action); + g_signal_connect (submenu, "hide", G_CALLBACK (gtk_model_menu_item_submenu_hidden), full_action); + } + } + g_variant_unref (value); } g_object_unref (iter);