]> Pileus Git - ~andy/gtk/commitdiff
GtkModelMenuItem: add a submenu action attribute
authorRyan Lortie <desrt@desrt.ca>
Fri, 24 Aug 2012 18:11:37 +0000 (14:11 -0400)
committerRyan Lortie <desrt@desrt.ca>
Mon, 17 Sep 2012 16:31:21 +0000 (12:31 -0400)
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

examples/bloatpad.c
gtk/gtkmodelmenuitem.c

index 537b9d7091d798d53d3aaba972c7c2d75e75e46b..fc8e263e1c006aac961778812c51a95ca44a01b1 100644 (file)
@@ -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)
                                "        </item>"
                                "      </section>"
                                "    </submenu>"
+                               "    <submenu id='time-menu'>"
+                               "      <attribute name='label' translatable='yes'>Time</attribute>"
+                               "      <attribute name='submenu-action'>app.time-active</attribute>"
+                               "    </submenu>"
                                "  </menu>"
                                "</interface>", -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 ();
 
index d2c533ce129de64e0b84e197f25cf290174c1274..450cd9a4bf81b66343b5f0e03dd8b8d64011b186 100644 (file)
@@ -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);