[gtk+] introduce GtkModelMenuItem



commit 612e24dfc6fcd93c0b02db7ccffe51fd31cc247b
Author: Ryan Lortie <desrt desrt ca>
Date:   Thu Dec 1 20:39:11 2011 -0500

    introduce GtkModelMenuItem
    
    This GtkMenuItem subclass (and GActionObserver implementation) contains
    all the knowledge necessary for converting a GMenuModel item description
    into a GtkMenuItem.
    
    Remove much of the code that used to do this from
    gtkapplicationwindow.c.

 gtk/Makefile.am            |    2 +
 gtk/gtkapplicationwindow.c |  189 +--------------------------------
 gtk/gtkmodelmenuitem.c     |  247 ++++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkmodelmenuitem.h     |   44 ++++++++
 4 files changed, 299 insertions(+), 183 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index aad7bc7..3088bd0 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -434,6 +434,7 @@ gtk_private_h_sources =		\
 	gtkmenuitemprivate.h	\
 	gtkmenushellprivate.h	\
 	gtkmnemonichash.h	\
+	gtkmodelmenuitem.h	\
 	gtkmodifierstyle.h	\
 	gtkmodulesprivate.h	\
 	gtkmountoperationprivate.h \
@@ -635,6 +636,7 @@ gtk_base_c_sources = 		\
 	gtkmessagedialog.c	\
 	gtkmisc.c		\
 	gtkmnemonichash.c	\
+	gtkmodelmenuitem.c	\
 	gtkmodifierstyle.c	\
 	gtkmodules.c		\
 	gtkmountoperation.c	\
diff --git a/gtk/gtkapplicationwindow.c b/gtk/gtkapplicationwindow.c
index 87acbef..56733ed 100644
--- a/gtk/gtkapplicationwindow.c
+++ b/gtk/gtkapplicationwindow.c
@@ -24,6 +24,7 @@
 #include "gtkapplicationwindow.h"
 
 #include "gtkseparatormenuitem.h"
+#include "gtkmodelmenuitem.h"
 #include "gtkcheckmenuitem.h"
 #include "gtkmenubar.h"
 #include "gactionmuxer.h"
@@ -516,184 +517,6 @@ gtk_application_window_set_show_menubar (GtkApplicationWindow *window,
 
 /* GtkMenu construction {{{1 */
 
-typedef struct {
-  GActionGroup *group;
-  gchar        *name;
-  gchar        *target;
-  gulong        enabled_changed_id;
-  gulong        state_changed_id;
-  gulong        activate_handler;
-} ActionData;
-
-static void
-action_data_free (gpointer data)
-{
-  ActionData *a = data;
-
-  if (a->enabled_changed_id)
-    g_signal_handler_disconnect (a->group, a->enabled_changed_id);
-
-  if (a->state_changed_id)
-    g_signal_handler_disconnect (a->group, a->state_changed_id);
-
-  g_object_unref (a->group);
-  g_free (a->name);
-  g_free (a->target);
-
-  g_free (a);
-}
-
-static void
-enabled_changed (GActionGroup *group,
-                 const gchar  *action_name,
-                 gboolean      enabled,
-                 GtkWidget    *widget)
-{
-  gtk_widget_set_sensitive (widget, enabled);
-}
-
-static void
-item_activated (GtkWidget *w,
-                gpointer   data)
-{
-  ActionData *a;
-  GVariant *parameter;
-
-  a = g_object_get_data (G_OBJECT (w), "action");
-  if (a->target)
-    parameter = g_variant_ref_sink (g_variant_new_string (a->target));
-  else
-    parameter = NULL;
-  g_action_group_activate_action (a->group, a->name, parameter);
-  if (parameter)
-    g_variant_unref (parameter);
-}
-
-static void
-toggle_state_changed (GActionGroup     *group,
-                      const gchar      *name,
-                      GVariant         *state,
-                      GtkCheckMenuItem *w)
-{
-  ActionData *a;
-
-  a = g_object_get_data (G_OBJECT (w), "action");
-  g_signal_handler_block (w, a->activate_handler);
-  gtk_check_menu_item_set_active (w, g_variant_get_boolean (state));
-  g_signal_handler_unblock (w, a->activate_handler);
-}
-
-static void
-radio_state_changed (GActionGroup     *group,
-                     const gchar      *name,
-                     GVariant         *state,
-                     GtkCheckMenuItem *w)
-{
-  ActionData *a;
-  gboolean b;
-
-  a = g_object_get_data (G_OBJECT (w), "action");
-  g_signal_handler_block (w, a->activate_handler);
-  b = g_strcmp0 (a->target, g_variant_get_string (state, NULL)) == 0;
-  gtk_check_menu_item_set_active (w, b);
-  g_signal_handler_unblock (w, a->activate_handler);
-}
-
-static GtkWidget *
-create_menuitem_from_model (GMenuModel   *model,
-                            gint          item,
-                            GActionGroup *group)
-{
-  GtkWidget *w;
-  gchar *label;
-  gchar *action;
-  gchar *target;
-  gchar *s;
-  ActionData *a;
-  const GVariantType *type;
-  GVariant *v;
-
-  label = NULL;
-  g_menu_model_get_item_attribute (model, item, G_MENU_ATTRIBUTE_LABEL, "s", &label);
-
-  action = NULL;
-  g_menu_model_get_item_attribute (model, 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_label (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 ();
-
-  gtk_menu_item_set_use_underline (GTK_MENU_ITEM (w), TRUE);
-
-  if (action != NULL)
-    {
-      a = g_new0 (ActionData, 1);
-      a->group = g_object_ref (group);
-      a->name = g_strdup (action);
-      g_object_set_data_full (G_OBJECT (w), "action", a, action_data_free);
-
-      if (!g_action_group_get_action_enabled (group, action))
-        gtk_widget_set_sensitive (w, FALSE);
-
-      s = g_strconcat ("action-enabled-changed::", action, NULL);
-      a->enabled_changed_id = g_signal_connect (group, s,
-                                                G_CALLBACK (enabled_changed), w);
-      g_free (s);
-      a->activate_handler = g_signal_connect (w, "activate",
-                                              G_CALLBACK (item_activated), NULL);
-
-      if (type == NULL)
-        {
-          /* all set */
-        }
-      else if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
-        {
-          s = g_strconcat ("action-state-changed::", action, NULL);
-          a->state_changed_id = 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))
-        {
-          s = g_strconcat ("action-state-changed::", action, NULL);
-          a->state_changed_id = g_signal_connect (group, s,
-                                                  G_CALLBACK (radio_state_changed), w);
-          g_free (s);
-          g_menu_model_get_item_attribute (model, item, G_MENU_ATTRIBUTE_TARGET, "s", &target);
-          a->target = g_strdup (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 (target);
-        }
-      else
-        g_assert_not_reached ();
-    }
-
-  g_free (label);
-  g_free (action);
-
-  return w;
-}
-
 static void populate_menu_from_model (GtkMenuShell *menu,
                                       GMenuModel   *model,
                                       GActionGroup *group);
@@ -708,7 +531,7 @@ append_items_from_model (GtkMenuShell *menu,
   gint n;
   gint i;
   GtkWidget *w;
-  GtkWidget *menuitem;
+  GtkMenuItem *menuitem;
   GtkWidget *submenu;
   GMenuModel *m;
   gchar *label;
@@ -752,18 +575,18 @@ append_items_from_model (GtkMenuShell *menu,
           continue;
         }
 
-      menuitem = create_menuitem_from_model (model, i, group);
+      menuitem = gtk_model_menu_item_new (model, i, G_ACTION_OBSERVABLE (group));
 
       if ((m = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU)))
         {
           submenu = gtk_menu_new ();
           populate_menu_from_model (GTK_MENU_SHELL (submenu), m, group);
-          gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu);
+          gtk_menu_item_set_submenu (menuitem, submenu);
           g_object_unref (m);
         }
 
-      gtk_widget_show (menuitem);
-      gtk_menu_shell_append (menu, menuitem);
+      gtk_widget_show (GTK_WIDGET (menuitem));
+      gtk_menu_shell_append (menu, GTK_WIDGET (menuitem));
 
       *need_separator = TRUE;
     }
diff --git a/gtk/gtkmodelmenuitem.c b/gtk/gtkmodelmenuitem.c
new file mode 100644
index 0000000..c76c9db
--- /dev/null
+++ b/gtk/gtkmodelmenuitem.c
@@ -0,0 +1,247 @@
+/*
+ * Copyright  2011 Canonical Limited
+ *
+ * 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 licence, 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#include "config.h"
+
+#include "gtkmodelmenuitem.h"
+
+struct _GtkModelMenuItem
+{
+  GtkCheckMenuItem parent_instance;
+
+  GActionGroup *actions;
+  const gchar *action_name;
+  gboolean can_activate;
+  GVariant *target;
+};
+
+typedef GtkCheckMenuItemClass GtkModelMenuItemClass;
+
+static void gtk_model_menu_item_observer_iface_init (GActionObserverInterface *iface);
+G_DEFINE_TYPE_WITH_CODE (GtkModelMenuItem, gtk_model_menu_item, GTK_TYPE_CHECK_MENU_ITEM,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_OBSERVER, gtk_model_menu_item_observer_iface_init))
+
+static void
+gtk_model_menu_item_activate (GtkMenuItem *menu_item)
+{
+  GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (menu_item);
+
+  if (item->can_activate)
+    g_action_group_activate_action (item->actions, item->action_name, item->target);
+}
+
+static void
+gtk_model_menu_item_set_active (GtkModelMenuItem *item,
+                                gboolean          active)
+{
+  GtkCheckMenuItem *checkitem = GTK_CHECK_MENU_ITEM (item);
+
+  if (gtk_check_menu_item_get_active (checkitem) != active)
+    {
+      _gtk_check_menu_item_set_active (checkitem, active);
+      g_object_notify (G_OBJECT (checkitem), "active");
+      gtk_check_menu_item_toggled (checkitem);
+      gtk_widget_queue_draw (GTK_WIDGET (item));
+    }
+}
+
+static void
+gtk_model_menu_item_action_added (GActionObserver    *observer,
+                                  GActionObservable  *observable,
+                                  const gchar        *action_name,
+                                  const GVariantType *parameter_type,
+                                  gboolean            enabled,
+                                  GVariant           *state)
+{
+  GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (observer);
+
+  /* we can only activate the item if we have the correct type of parameter */
+  item->can_activate = (item->target == NULL && parameter_type == NULL) ||
+                       (item->target != NULL && parameter_type != NULL &&
+                        g_variant_is_of_type (item->target, parameter_type));
+
+  if (item->can_activate)
+    {
+      if (item->target != NULL && state != NULL)
+        {
+          /* actions with states and targets are radios */
+          gboolean selected;
+
+          selected = g_variant_equal (state, item->target);
+          gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (item), TRUE);
+          gtk_model_menu_item_set_active (item, selected);
+        }
+
+      else if (state != NULL && g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
+        {
+          /* boolean state actions without target are checks */
+          gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (item), FALSE);
+          gtk_model_menu_item_set_active (item, g_variant_get_boolean (state));
+        }
+
+      else
+        {
+          /* stateless items are just plain actions */
+          gtk_model_menu_item_set_active (item, FALSE);
+        }
+
+      gtk_widget_set_sensitive (GTK_WIDGET (item), enabled);
+    }
+}
+
+static void
+gtk_model_menu_item_action_enabled_changed (GActionObserver   *observer,
+                                            GActionObservable *observable,
+                                            const gchar       *action_name,
+                                            gboolean           enabled)
+{
+  GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (observer);
+
+  if (!item->can_activate)
+    return;
+
+  gtk_widget_set_sensitive (GTK_WIDGET (item), item->can_activate && enabled);
+}
+
+static void
+gtk_model_menu_item_action_state_changed (GActionObserver   *observer,
+                                          GActionObservable *observable,
+                                          const gchar       *action_name,
+                                          GVariant          *state)
+{
+  GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (observer);
+
+  if (!item->can_activate)
+    return;
+
+  if (item->target)
+    gtk_model_menu_item_set_active (item, g_variant_equal (state, item->target));
+
+  else if (g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
+    gtk_model_menu_item_set_active (item, g_variant_get_boolean (state));
+}
+
+static void
+gtk_model_menu_item_action_removed (GActionObserver   *observer,
+                                    GActionObservable *observable,
+                                    const gchar       *action_name)
+{
+  GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (observer);
+
+  if (!item->can_activate)
+    return;
+
+  gtk_widget_set_sensitive (GTK_WIDGET (item), FALSE);
+  gtk_model_menu_item_set_active (item, FALSE);
+}
+
+static void
+gtk_model_menu_item_setup (GtkModelMenuItem  *item,
+                           GMenuModel        *model,
+                           gint               item_index,
+                           GActionObservable *actions)
+{
+  GMenuAttributeIter *iter;
+  const gchar *key;
+  GVariant *value;
+
+  iter = g_menu_model_iterate_item_attributes (model, item_index);
+  while (g_menu_attribute_iter_get_next (iter, &key, &value))
+    {
+      if (g_str_equal (key, "label") && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
+        gtk_menu_item_set_label (GTK_MENU_ITEM (item), g_variant_get_string (value, NULL));
+
+      else if (g_str_equal (key, "action") && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
+        item->action_name = g_variant_get_string (value, NULL);
+
+      else if (g_str_equal (key, "target"))
+        item->target = g_variant_ref (value);
+
+      g_variant_unref (value);
+    }
+  g_object_unref (iter);
+
+  gtk_menu_item_set_use_underline (GTK_MENU_ITEM (item), TRUE);
+
+  if (item->action_name)
+    {
+      const GVariantType *type;
+      gboolean enabled;
+      GVariant *state;
+
+      /* observer already causes us to hold a hard ref on the group */
+      item->actions = G_ACTION_GROUP (actions);
+
+      g_action_observable_register_observer (actions, item->action_name, G_ACTION_OBSERVER (item));
+
+      if (g_action_group_query_action (G_ACTION_GROUP (actions), item->action_name, &enabled, &type, NULL, NULL, &state))
+        gtk_model_menu_item_action_added (G_ACTION_OBSERVER (item), actions, item->action_name, type, enabled, state);
+
+      else
+        gtk_widget_set_sensitive (GTK_WIDGET (item), FALSE);
+    }
+}
+
+static void
+gtk_model_menu_item_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (gtk_model_menu_item_parent_class)
+    ->finalize (object);
+}
+
+static void
+gtk_model_menu_item_init (GtkModelMenuItem *item)
+{
+}
+
+static void
+gtk_model_menu_item_observer_iface_init (GActionObserverInterface *iface)
+{
+  iface->action_added = gtk_model_menu_item_action_added;
+  iface->action_enabled_changed = gtk_model_menu_item_action_enabled_changed;
+  iface->action_state_changed = gtk_model_menu_item_action_state_changed;
+  iface->action_removed = gtk_model_menu_item_action_removed;
+}
+
+static void
+gtk_model_menu_item_class_init (GtkModelMenuItemClass *class)
+{
+  GtkMenuItemClass *item_class = GTK_MENU_ITEM_CLASS (class);
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  item_class->activate = gtk_model_menu_item_activate;
+
+  object_class->finalize = gtk_model_menu_item_finalize;
+}
+
+GtkMenuItem *
+gtk_model_menu_item_new (GMenuModel        *model,
+                         gint               item_index,
+                         GActionObservable *actions)
+{
+  GtkModelMenuItem *item;
+
+  item = g_object_new (GTK_TYPE_MODEL_MENU_ITEM, NULL);
+
+  gtk_model_menu_item_setup (item, model, item_index, actions);
+
+  return GTK_MENU_ITEM (item);
+}
diff --git a/gtk/gtkmodelmenuitem.h b/gtk/gtkmodelmenuitem.h
new file mode 100644
index 0000000..4146bf9
--- /dev/null
+++ b/gtk/gtkmodelmenuitem.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright  2011 Canonical Limited
+ *
+ * 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 licence, 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#ifndef __GTK_MODEL_MENU_ITEM_H__
+#define __GTK_MODEL_MENU_ITEM_H__
+
+#include <gtk/gactionobservable.h>
+#include <gtk/gtkcheckmenuitem.h>
+
+#define GTK_TYPE_MODEL_MENU_ITEM                            (gtk_model_menu_item_get_type ())
+#define GTK_MODEL_MENU_ITEM(inst)                           (G_TYPE_CHECK_INSTANCE_CAST ((inst),                      \
+                                                             GTK_TYPE_MODEL_MENU_ITEM, GtkModelMenuItem))
+#define GTK_IS_MODEL_MENU_ITEM(inst)                        (G_TYPE_CHECK_INSTANCE_TYPE ((inst),                      \
+                                                             GTK_TYPE_MODEL_MENU_ITEM))
+
+typedef struct _GtkModelMenuItem                            GtkModelMenuItem;
+
+G_GNUC_INTERNAL
+GType                   gtk_model_menu_item_get_type                    (void) G_GNUC_CONST;
+
+G_GNUC_INTERNAL
+GtkMenuItem *           gtk_model_menu_item_new                         (GMenuModel        *model,
+                                                                         gint               item_index,
+                                                                         GActionObservable *actions);
+
+#endif /* __GTK_MODEL_MENU_ITEM_H__ */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]