[gtk+/wip/gtkmenutrackeritem: 2/3] add GtkMenuTrackerItem



commit aad5f931929132d0eca18ec10cea91efd542a5fe
Author: Ryan Lortie <desrt desrt ca>
Date:   Wed May 8 08:20:23 2013 -0400

    add GtkMenuTrackerItem
    
    Add a new class, GtkMenuTrackerItem that represents a menu item, to be
    used with GtkMenuTracker.
    
    GtkMenuTracker's insert callback now works in terms of this new type
    (instead of passing reference to the model and an index to the item).
    
    GtkMenuShell now handles all of the binding tasks internally, mostly
    through the use of property bindings.  Having bindings for the label and
    visibility attributes, in partiular, will help with supporting upcoming
    extensions to GMenuModel.
    
    GtkModelMenu has been reduced to a helper class that has nothing to do
    with GMenuModel.  It represents something closer to an "ideal" API for
    GtkMenuItem if we didn't have compatibility concerns (eg: not emitting
    "activate" when setting toggle state, no separate subclasses per menu
    item type, supporting icons, etc.) Improvements to GtkMenuItem could
    eventually shrink the size of this class or remove the need for it
    entirely.
    
    Some GtkActionHelper functionality has been duplicated in
    GtkMenuTracker, which is suboptimal.  The duplication exists so that
    other codebases (such as Unity and gnome-shell) can reuse the
    GtkMenuTracker code, whereas GtkActionHelper is very much tied to
    GtkWidget.  Supporting binding arbitrary GtkWidgets to actions vs.
    supporting the full range of GMenuModel features for menu items turns
    out to be two overlapping but not entirely similar problems.  Some of
    the duplication (such as roles) can be removed from GtkActionHelper once
    Gtk's internal Mac OS menubar support is ported to GtkMenuTracker.
    
    The intent to reuse the code outside of Gtk is also the reason for the
    unusual treatment of the enum type introduced in this comment.

 gtk/Makefile.am          |    2 +
 gtk/gtkmenushell.c       |  135 +++++++++++---
 gtk/gtkmenutracker.c     |   22 ++-
 gtk/gtkmenutracker.h     |   12 +-
 gtk/gtkmenutrackeritem.c |  491 ++++++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkmenutrackeritem.h |   92 +++++++++
 gtk/gtkmodelmenuitem.c   |  335 +++++++++++++++-----------------
 gtk/gtkmodelmenuitem.h   |    5 +-
 8 files changed, 879 insertions(+), 215 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index dc1fd41..f87168b 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -506,6 +506,7 @@ gtk_private_h_sources =             \
        gtkmenuitemprivate.h    \
        gtkmenushellprivate.h   \
        gtkmenutracker.h        \
+       gtkmenutrackeritem.h    \
        gtkmnemonichash.h       \
        gtkmodelmenuitem.h      \
        gtkmodifierstyle.h      \
@@ -772,6 +773,7 @@ gtk_base_c_sources =                \
        gtkmenuitem.c           \
        gtkmenushell.c          \
        gtkmenutracker.c        \
+       gtkmenutrackeritem.c    \
        gtkmenutoolbutton.c     \
        gtkmessagedialog.c      \
        gtkmisc.c               \
diff --git a/gtk/gtkmenushell.c b/gtk/gtkmenushell.c
index 28b3e1a..c244ab1 100644
--- a/gtk/gtkmenushell.c
+++ b/gtk/gtkmenushell.c
@@ -52,6 +52,7 @@
 #include "gtkintl.h"
 #include "gtktypebuiltins.h"
 #include "gtkmodelmenuitem.h"
+#include "gtkwidgetprivate.h"
 
 #include "deprecated/gtktearoffmenuitem.h"
 
@@ -2028,33 +2029,30 @@ gtk_menu_shell_get_parent_shell (GtkMenuShell *menu_shell)
 }
 
 static void
-gtk_menu_shell_tracker_insert_func (gint         position,
-                                    GMenuModel  *model,
-                                    gint         item_index,
-                                    const gchar *action_namespace,
-                                    gboolean     is_separator,
-                                    gpointer     user_data)
+gtk_menu_shell_item_activate (GtkMenuItem *menuitem,
+                              gpointer     user_data)
 {
-  GtkMenuShell *menu_shell = user_data;
-  GtkWidget *item;
+  GtkMenuTrackerItem *item = user_data;
 
-  if (is_separator)
-    {
-      gchar *label;
+  gtk_menu_tracker_item_activated (item);
+}
 
-      item = gtk_separator_menu_item_new ();
+static void
+gtk_menu_shell_submenu_shown (GtkWidget *submenu,
+                              gpointer   user_data)
+{
+  GtkMenuTrackerItem *item = user_data;
 
-      if (g_menu_model_get_item_attribute (model, item_index, G_MENU_ATTRIBUTE_LABEL, "s", &label))
-        {
-          gtk_menu_item_set_label (GTK_MENU_ITEM (item), label);
-          g_free (label);
-        }
-    }
-  else
-    item = gtk_model_menu_item_new (model, item_index, action_namespace);
+  gtk_menu_tracker_item_request_submenu_shown (item, TRUE);
+}
 
-  gtk_menu_shell_insert (menu_shell, item, position);
-  gtk_widget_show (item);
+static void
+gtk_menu_shell_submenu_hidden (GtkWidget *submenu,
+                               gpointer   user_data)
+{
+  GtkMenuTrackerItem *item = user_data;
+
+  gtk_menu_tracker_item_request_submenu_shown (item, FALSE);
 }
 
 static void
@@ -2072,6 +2070,92 @@ gtk_menu_shell_tracker_remove_func (gint     position,
   gtk_widget_destroy (child);
 }
 
+static void
+gtk_menu_shell_tracker_insert_func (GtkMenuTrackerItem *item,
+                                    gint                position,
+                                    gpointer            user_data)
+{
+  GtkMenuShell *menu_shell = user_data;
+  GtkWidget *widget;
+
+  if (gtk_menu_tracker_item_get_role (item) == GTK_MENU_TRACKER_ITEM_ROLE_SEPARATOR)
+    widget = gtk_separator_menu_item_new ();
+  else
+    widget = gtk_model_menu_item_new ();
+
+  /* TODO: drop this when we have bindings that ref the source */
+  g_object_set_data_full (G_OBJECT (widget), "GtkMenuTrackerItem", g_object_ref (item), g_object_unref);
+
+  if (!GTK_IS_SEPARATOR_MENU_ITEM (widget))
+    {
+      GMenuModel *submenu;
+
+      /* We bind to "text" instead of "label" because GtkModelMenuItem
+       * uses this property (along with "icon") to control its child
+       * widget.  Once this is merged into GtkMenuItem we can go back to
+       * using "label".
+       */
+      g_object_bind_property (item, "label", widget, "text", G_BINDING_SYNC_CREATE);
+      g_object_bind_property (item, "icon", widget, "icon", G_BINDING_SYNC_CREATE);
+      g_object_bind_property (item, "sensitive", widget, "sensitive", G_BINDING_SYNC_CREATE);
+      g_object_bind_property (item, "visible", widget, "visible", G_BINDING_SYNC_CREATE);
+      g_object_bind_property (item, "role", widget, "action-role", G_BINDING_SYNC_CREATE);
+      g_object_bind_property (item, "toggled", widget, "toggled", G_BINDING_SYNC_CREATE);
+
+      g_signal_connect (widget, "activate", G_CALLBACK (gtk_menu_shell_item_activate), item);
+
+      submenu = gtk_menu_tracker_item_get_submenu (item);
+
+      if (submenu)
+        {
+          GActionObservable *observable;
+          GtkWidget *subwidget;
+          GtkMenuShell *subshell;
+
+          /* reuse the observer to reduce the amount of GActionMuxer traffic */
+          observable = gtk_menu_tracker_item_get_observable (item);
+          subwidget = gtk_menu_new ();
+          subshell = GTK_MENU_SHELL (subwidget);
+
+          /* We recurse directly here: we could use an idle instead to
+           * prevent arbitrary recursion depth.  We could also do it
+           * lazy...
+           */
+          subshell->priv->tracker = gtk_menu_tracker_new (observable, submenu, TRUE,
+                                                          gtk_menu_tracker_item_get_submenu_namespace (item),
+                                                          gtk_menu_shell_tracker_insert_func,
+                                                          gtk_menu_shell_tracker_remove_func,
+                                                          subwidget);
+          gtk_menu_item_set_submenu (GTK_MENU_ITEM (widget), subwidget);
+
+          if (gtk_menu_tracker_item_get_should_request_show (item))
+            {
+              /* We don't request show in the strictest sense of the
+               * word: we just notify when we are showing and don't
+               * bother waiting for the reply.
+               *
+               * This could be fixed one day, but it would be slightly
+               * complicated and would have a strange interaction with
+               * the submenu pop-up delay.
+               *
+               * Note: 'item' is already kept alive from above.
+               */
+              g_signal_connect (subwidget, "show", G_CALLBACK (gtk_menu_shell_submenu_shown), item);
+              g_signal_connect (subwidget, "hide", G_CALLBACK (gtk_menu_shell_submenu_hidden), item);
+            }
+        }
+    }
+  else
+    {
+      /* For separators, we bind to the "label" property in case there
+       * is a section heading.
+       */
+      g_object_bind_property (item, "label", widget, "label", G_BINDING_SYNC_CREATE);
+    }
+
+  gtk_menu_shell_insert (menu_shell, widget, position);
+}
+
 /**
  * gtk_menu_shell_bind_model:
  * @menu_shell: a #GtkMenuShell
@@ -2123,16 +2207,21 @@ gtk_menu_shell_bind_model (GtkMenuShell *menu_shell,
                            const gchar  *action_namespace,
                            gboolean      with_separators)
 {
+  GActionMuxer *muxer;
+
   g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
   g_return_if_fail (model == NULL || G_IS_MENU_MODEL (model));
 
+  muxer = _gtk_widget_get_action_muxer (GTK_WIDGET (menu_shell));
+
   g_clear_pointer (&menu_shell->priv->tracker, gtk_menu_tracker_free);
 
   while (menu_shell->priv->children)
     gtk_container_remove (GTK_CONTAINER (menu_shell), menu_shell->priv->children->data);
 
   if (model)
-    menu_shell->priv->tracker = gtk_menu_tracker_new (model, with_separators, action_namespace,
+    menu_shell->priv->tracker = gtk_menu_tracker_new (G_ACTION_OBSERVABLE (muxer),
+                                                      model, with_separators, action_namespace,
                                                       gtk_menu_shell_tracker_insert_func,
                                                       gtk_menu_shell_tracker_remove_func,
                                                       menu_shell);
diff --git a/gtk/gtkmenutracker.c b/gtk/gtkmenutracker.c
index f49457f..764b93c 100644
--- a/gtk/gtkmenutracker.c
+++ b/gtk/gtkmenutracker.c
@@ -27,6 +27,7 @@ typedef struct _GtkMenuTrackerSection GtkMenuTrackerSection;
 
 struct _GtkMenuTracker
 {
+  GActionObservable        *observable;
   GtkMenuTrackerInsertFunc  insert_func;
   GtkMenuTrackerRemoveFunc  remove_func;
   gpointer                  user_data;
@@ -159,7 +160,12 @@ gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section,
   if (should_have_separator > section->has_separator)
     {
       /* Add a separator */
-      (* tracker->insert_func) (offset, parent_model, parent_index, NULL, TRUE, tracker->user_data);
+      GtkMenuTrackerItem *item;
+
+      item = gtk_menu_tracker_item_new (tracker->observable, parent_model, parent_index, NULL, TRUE);
+      (* tracker->insert_func) (item, offset, tracker->user_data);
+      g_object_unref (item);
+
       section->has_separator = TRUE;
     }
   else if (should_have_separator < section->has_separator)
@@ -258,8 +264,13 @@ gtk_menu_tracker_add_items (GtkMenuTracker         *tracker,
         }
       else
         {
-          (* tracker->insert_func) (offset, model, position + n_items,
-                                    section->action_namespace, FALSE, tracker->user_data);
+          GtkMenuTrackerItem *item;
+
+          item = gtk_menu_tracker_item_new (tracker->observable, model, position + n_items,
+                                            section->action_namespace, FALSE);
+          (* tracker->insert_func) (item, offset, tracker->user_data);
+          g_object_unref (item);
+
           *change_point = g_slist_prepend (*change_point, NULL);
         }
     }
@@ -400,7 +411,8 @@ gtk_menu_tracker_section_new (GtkMenuTracker *tracker,
  * gtk_menu_tracker_free() is called.
  */
 GtkMenuTracker *
-gtk_menu_tracker_new (GMenuModel               *model,
+gtk_menu_tracker_new (GActionObservable        *observable,
+                      GMenuModel               *model,
                       gboolean                  with_separators,
                       const gchar              *action_namespace,
                       GtkMenuTrackerInsertFunc  insert_func,
@@ -410,6 +422,7 @@ gtk_menu_tracker_new (GMenuModel               *model,
   GtkMenuTracker *tracker;
 
   tracker = g_slice_new (GtkMenuTracker);
+  tracker->observable = g_object_ref (observable);
   tracker->insert_func = insert_func;
   tracker->remove_func = remove_func;
   tracker->user_data = user_data;
@@ -430,5 +443,6 @@ void
 gtk_menu_tracker_free (GtkMenuTracker *tracker)
 {
   gtk_menu_tracker_section_free (tracker->toplevel);
+  g_object_unref (tracker->observable);
   g_slice_free (GtkMenuTracker, tracker);
 }
diff --git a/gtk/gtkmenutracker.h b/gtk/gtkmenutracker.h
index 51810a1..51c5a7b 100644
--- a/gtk/gtkmenutracker.h
+++ b/gtk/gtkmenutracker.h
@@ -22,15 +22,12 @@
 #ifndef __GTK_MENU_TRACKER_H__
 #define __GTK_MENU_TRACKER_H__
 
-#include <gio/gio.h>
+#include <gtk/gtkmenutrackeritem.h>
 
 typedef struct _GtkMenuTracker GtkMenuTracker;
 
-typedef void         (* GtkMenuTrackerInsertFunc)       (gint                      position,
-                                                         GMenuModel               *model,
-                                                         gint                      item_index,
-                                                         const gchar              *action_namespace,
-                                                         gboolean                  is_separator,
+typedef void         (* GtkMenuTrackerInsertFunc)       (GtkMenuTrackerItem       *item,
+                                                         gint                      position,
                                                          gpointer                  user_data);
 
 typedef void         (* GtkMenuTrackerRemoveFunc)       (gint                      position,
@@ -38,7 +35,8 @@ typedef void         (* GtkMenuTrackerRemoveFunc)       (gint
 
 
 G_GNUC_INTERNAL
-GtkMenuTracker *        gtk_menu_tracker_new            (GMenuModel               *model,
+GtkMenuTracker *        gtk_menu_tracker_new            (GActionObservable        *observer,
+                                                         GMenuModel               *model,
                                                          gboolean                  with_separators,
                                                          const gchar              *action_namespace,
                                                          GtkMenuTrackerInsertFunc  insert_func,
diff --git a/gtk/gtkmenutrackeritem.c b/gtk/gtkmenutrackeritem.c
new file mode 100644
index 0000000..7712201
--- /dev/null
+++ b/gtk/gtkmenutrackeritem.c
@@ -0,0 +1,491 @@
+#include "gtkmenutrackeritem.h"
+
+typedef GObjectClass GtkMenuTrackerItemClass;
+
+struct _GtkMenuTrackerItem
+{
+  GObject parent_instance;
+
+  GActionObservable *observable;
+  gchar *action_namespace;
+  GMenuItem *item;
+  GtkMenuTrackerItemRole role : 4;
+  guint can_activate : 1;
+  guint sensitive : 1;
+  guint toggled : 1;
+};
+
+enum {
+  PROP_0,
+  PROP_LABEL,
+  PROP_ICON,
+  PROP_SENSITIVE,
+  PROP_VISIBLE,
+  PROP_ROLE,
+  PROP_TOGGLED,
+  PROP_SUBMENU,
+  PROP_SUBMENU_NAMESPACE,
+  N_PROPS
+};
+
+static GParamSpec *gtk_menu_tracker_item_pspecs[N_PROPS];
+
+static void gtk_menu_tracker_item_init_observer_iface (GActionObserverInterface *iface);
+G_DEFINE_TYPE_WITH_CODE (GtkMenuTrackerItem, gtk_menu_tracker_item, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_OBSERVER, 
gtk_menu_tracker_item_init_observer_iface))
+
+GType
+gtk_menu_tracker_item_role_get_type (void)
+{
+  static gsize gtk_menu_tracker_item_role_type;
+
+  if (g_once_init_enter (&gtk_menu_tracker_item_role_type))
+    {
+      static const GEnumValue values[] = {
+        { GTK_MENU_TRACKER_ITEM_ROLE_NORMAL, "GTK_MENU_TRACKER_ITEM_ROLE_NORMAL", "normal" },
+        { GTK_MENU_TRACKER_ITEM_ROLE_TOGGLE, "GTK_MENU_TRACKER_ITEM_ROLE_TOGGLE", "toggle" },
+        { GTK_MENU_TRACKER_ITEM_ROLE_RADIO, "GTK_MENU_TRACKER_ITEM_ROLE_RADIO", "radio" },
+        { GTK_MENU_TRACKER_ITEM_ROLE_SEPARATOR, "GTK_MENU_TRACKER_ITEM_ROLE_SEPARATOR", "separator" },
+        { 0, NULL, NULL }
+      };
+      GType type;
+
+      type = g_enum_register_static ("GtkMenuTrackerItemRole", values);
+
+      g_once_init_leave (&gtk_menu_tracker_item_role_type, type);
+    }
+
+  return gtk_menu_tracker_item_role_type;
+}
+
+static void
+gtk_menu_tracker_item_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (object);
+
+  switch (prop_id)
+    {
+    case PROP_LABEL:
+      g_value_set_string (value, gtk_menu_tracker_item_get_label (self));
+      break;
+    case PROP_ICON:
+      g_value_set_object (value, gtk_menu_tracker_item_get_icon (self));
+      break;
+    case PROP_SENSITIVE:
+      g_value_set_boolean (value, gtk_menu_tracker_item_get_sensitive (self));
+      break;
+    case PROP_VISIBLE:
+      g_value_set_boolean (value, gtk_menu_tracker_item_get_visible (self));
+      break;
+    case PROP_ROLE:
+      g_value_set_enum (value, gtk_menu_tracker_item_get_role (self));
+      break;
+    case PROP_TOGGLED:
+      g_value_set_boolean (value, gtk_menu_tracker_item_get_toggled (self));
+      break;
+    case PROP_SUBMENU:
+      g_value_set_object (value, gtk_menu_tracker_item_get_submenu (self));
+      break;
+    case PROP_SUBMENU_NAMESPACE:
+      g_value_set_string (value, gtk_menu_tracker_item_get_submenu_namespace (self));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gtk_menu_tracker_item_finalize (GObject *object)
+{
+  GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (object);
+
+  if (self->observable)
+    g_object_unref (self->observable);
+
+  g_object_unref (self->item);
+
+  G_OBJECT_CLASS (gtk_menu_tracker_item_parent_class)->finalize (object);
+}
+
+static void
+gtk_menu_tracker_item_init (GtkMenuTrackerItem * self)
+{
+}
+
+static void
+gtk_menu_tracker_item_class_init (GtkMenuTrackerItemClass *class)
+{
+  class->get_property = gtk_menu_tracker_item_get_property;
+  class->finalize = gtk_menu_tracker_item_finalize;
+
+  gtk_menu_tracker_item_pspecs[PROP_LABEL] =
+    g_param_spec_string ("label", "", "", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
+  gtk_menu_tracker_item_pspecs[PROP_ICON] =
+    g_param_spec_object ("icon", "", "", G_TYPE_ICON, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
+  gtk_menu_tracker_item_pspecs[PROP_SENSITIVE] =
+    g_param_spec_boolean ("sensitive", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
+  gtk_menu_tracker_item_pspecs[PROP_VISIBLE] =
+    g_param_spec_boolean ("visible", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
+  gtk_menu_tracker_item_pspecs[PROP_ROLE] =
+    g_param_spec_enum ("role", "", "",
+                       GTK_TYPE_MENU_TRACKER_ITEM_ROLE, GTK_MENU_TRACKER_ITEM_ROLE_NORMAL,
+                       G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
+  gtk_menu_tracker_item_pspecs[PROP_TOGGLED] =
+    g_param_spec_boolean ("toggled", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
+  gtk_menu_tracker_item_pspecs[PROP_SUBMENU] =
+    g_param_spec_object ("submenu", "", "", G_TYPE_MENU_MODEL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
+  gtk_menu_tracker_item_pspecs[PROP_SUBMENU_NAMESPACE] =
+    g_param_spec_string ("submenu-namespace", "", "", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
+
+  g_object_class_install_properties (class, N_PROPS, gtk_menu_tracker_item_pspecs);
+}
+
+static void
+gtk_menu_tracker_item_action_added (GActionObserver    *observer,
+                                    GActionObservable  *observable,
+                                    const gchar        *action_name,
+                                    const GVariantType *parameter_type,
+                                    gboolean            enabled,
+                                    GVariant           *state)
+{
+  GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer);
+  GVariant *action_target;
+
+  action_target = g_menu_item_get_attribute_value (self->item, G_MENU_ATTRIBUTE_TARGET, NULL);
+
+  self->can_activate = (action_target == NULL && parameter_type == NULL) ||
+                        (action_target != NULL && parameter_type != NULL &&
+                        g_variant_is_of_type (action_target, parameter_type));
+
+  if (!self->can_activate)
+    {
+      if (action_target)
+        g_variant_unref (action_target);
+      return;
+    }
+
+  self->sensitive = enabled;
+
+  if (action_target != NULL && state != NULL)
+    {
+      self->toggled = g_variant_equal (state, action_target);
+      self->role = GTK_MENU_TRACKER_ITEM_ROLE_RADIO;
+    }
+
+  else if (state != NULL && g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
+    {
+      self->toggled = g_variant_get_boolean (state);
+      self->role = GTK_MENU_TRACKER_ITEM_ROLE_TOGGLE;
+    }
+
+  g_object_freeze_notify (G_OBJECT (self));
+
+  if (self->sensitive)
+    g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SENSITIVE]);
+
+  if (self->toggled)
+    g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]);
+
+  if (self->role != GTK_MENU_TRACKER_ITEM_ROLE_NORMAL)
+    g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ROLE]);
+
+  g_object_thaw_notify (G_OBJECT (self));
+
+  if (action_target)
+    g_variant_unref (action_target);
+}
+
+static void
+gtk_menu_tracker_item_action_enabled_changed (GActionObserver   *observer,
+                                              GActionObservable *observable,
+                                              const gchar       *action_name,
+                                              gboolean           enabled)
+{
+  GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer);
+
+  if (!self->can_activate)
+    return;
+
+  if (self->sensitive == enabled)
+    return;
+
+  self->sensitive = enabled;
+
+  g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SENSITIVE]);
+}
+
+static void
+gtk_menu_tracker_item_action_state_changed (GActionObserver   *observer,
+                                            GActionObservable *observable,
+                                            const gchar       *action_name,
+                                            GVariant          *state)
+{
+  GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer);
+  GVariant *action_target;
+  gboolean was_toggled;
+
+  if (!self->can_activate)
+    return;
+
+  action_target = g_menu_item_get_attribute_value (self->item, G_MENU_ATTRIBUTE_TARGET, NULL);
+  was_toggled = self->toggled;
+
+  if (action_target)
+    {
+      self->toggled = g_variant_equal (state, action_target);
+      g_variant_unref (action_target);
+    }
+
+  else if (g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
+    self->toggled = g_variant_get_boolean (state);
+
+  else
+    self->toggled = FALSE;
+
+  if (self->toggled != was_toggled)
+    g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]);
+}
+
+static void
+gtk_menu_tracker_item_action_removed (GActionObserver   *observer,
+                                      GActionObservable *observable,
+                                      const gchar       *action_name)
+{
+  GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer);
+
+  if (self->can_activate)
+    return;
+
+  g_object_freeze_notify (G_OBJECT (self));
+
+  if (self->sensitive)
+    {
+      self->sensitive = FALSE;
+      g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SENSITIVE]);
+    }
+
+  if (self->toggled)
+    {
+      self->toggled = FALSE;
+      g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]);
+    }
+
+  if (self->role != GTK_MENU_TRACKER_ITEM_ROLE_NORMAL)
+    {
+      self->role = GTK_MENU_TRACKER_ITEM_ROLE_NORMAL;
+      g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ROLE]);
+    }
+
+  g_object_thaw_notify (G_OBJECT (self));
+}
+
+static void
+gtk_menu_tracker_item_init_observer_iface (GActionObserverInterface *iface)
+{
+  iface->action_added = gtk_menu_tracker_item_action_added;
+  iface->action_enabled_changed = gtk_menu_tracker_item_action_enabled_changed;
+  iface->action_state_changed = gtk_menu_tracker_item_action_state_changed;
+  iface->action_removed = gtk_menu_tracker_item_action_removed;
+}
+
+GtkMenuTrackerItem *
+gtk_menu_tracker_item_new (GActionObservable *observable,
+                           GMenuModel        *model,
+                           gint               item_index,
+                           const gchar       *action_namespace,
+                           gboolean           is_separator)
+{
+  GtkMenuTrackerItem *self;
+  const gchar *action_name;
+
+  g_return_val_if_fail (G_IS_ACTION_OBSERVABLE (observable), NULL);
+  g_return_val_if_fail (G_IS_MENU_MODEL (model), NULL);
+
+  self = g_object_new (GTK_TYPE_MENU_TRACKER_ITEM, NULL);
+  self->item = g_menu_item_new_from_model (model, item_index);
+  self->action_namespace = g_strdup (action_namespace);
+  self->observable = g_object_ref (observable);
+
+  if (!is_separator && g_menu_item_get_attribute (self->item, "action", "&s", &action_name))
+    {
+      GActionGroup *group = G_ACTION_GROUP (observable);
+      const GVariantType *parameter_type;
+      gboolean enabled;
+      GVariant *state;
+      gboolean found;
+
+      state = NULL;
+
+      if (action_namespace)
+        {
+          gchar *full_action;
+
+          full_action = g_strjoin (".", action_namespace, action_name, NULL);
+          g_action_observable_register_observer (self->observable, full_action, G_ACTION_OBSERVER (self));
+          found = g_action_group_query_action (group, full_action, &enabled, &parameter_type, NULL, NULL, 
&state);
+          g_free (full_action);
+        }
+      else
+        {
+          g_action_observable_register_observer (self->observable, action_name, G_ACTION_OBSERVER (self));
+          found = g_action_group_query_action (group, action_name, &enabled, &parameter_type, NULL, NULL, 
&state);
+        }
+
+      if (found)
+        gtk_menu_tracker_item_action_added (G_ACTION_OBSERVER (self), observable, NULL, parameter_type, 
enabled, state);
+      else
+        gtk_menu_tracker_item_action_removed (G_ACTION_OBSERVER (self), observable, NULL);
+
+      if (state)
+        g_variant_unref (state);
+    }
+  else
+    {
+      self->role = is_separator ? GTK_MENU_TRACKER_ITEM_ROLE_SEPARATOR : GTK_MENU_TRACKER_ITEM_ROLE_NORMAL;
+      self->sensitive = TRUE;
+    }
+
+  return self;
+}
+
+GActionObservable *
+gtk_menu_tracker_item_get_observable (GtkMenuTrackerItem *self)
+{
+  return self->observable;
+}
+
+const gchar *
+gtk_menu_tracker_item_get_label (GtkMenuTrackerItem *self)
+{
+  const gchar *label = NULL;
+
+  g_menu_item_get_attribute (self->item, G_MENU_ATTRIBUTE_LABEL, "&s", &label);
+
+  return label;
+}
+
+GIcon *
+gtk_menu_tracker_item_get_icon (GtkMenuTrackerItem *self)
+{
+  GVariant *icon_data;
+  GIcon *icon;
+
+  icon_data = g_menu_item_get_attribute_value (self->item, "icon", NULL);
+
+  if (icon_data == NULL)
+    return NULL;
+
+  icon = g_icon_deserialize (icon_data);
+  g_variant_unref (icon_data);
+
+  return icon;
+}
+
+gboolean
+gtk_menu_tracker_item_get_sensitive (GtkMenuTrackerItem *self)
+{
+  return self->sensitive;
+}
+
+gboolean
+gtk_menu_tracker_item_get_visible (GtkMenuTrackerItem *self)
+{
+  return TRUE;
+}
+
+GtkMenuTrackerItemRole
+gtk_menu_tracker_item_get_role (GtkMenuTrackerItem *self)
+{
+  return self->role;
+}
+
+gboolean
+gtk_menu_tracker_item_get_toggled (GtkMenuTrackerItem *self)
+{
+  return self->toggled;
+}
+
+GMenuModel *
+gtk_menu_tracker_item_get_submenu (GtkMenuTrackerItem *self)
+{
+  return g_menu_item_get_link (self->item, "submenu");
+}
+
+gchar *
+gtk_menu_tracker_item_get_submenu_namespace (GtkMenuTrackerItem *self)
+{
+  const gchar *namespace;
+
+  if (g_menu_item_get_attribute (self->item, "action-namespace", "&s", &namespace))
+    {
+      if (self->action_namespace)
+        return g_strjoin (".", self->action_namespace, namespace, NULL);
+      else
+        return g_strdup (namespace);
+    }
+  else
+    return g_strdup (self->action_namespace);
+}
+
+gboolean
+gtk_menu_tracker_item_get_should_request_show (GtkMenuTrackerItem *self)
+{
+  return g_menu_item_get_attribute (self->item, "submenu-action", "&s", NULL);
+}
+
+void
+gtk_menu_tracker_item_activated (GtkMenuTrackerItem *self)
+{
+  const gchar *action_name;
+  GVariant *action_target;
+
+  g_return_if_fail (GTK_IS_MENU_TRACKER_ITEM (self));
+
+  if (!self->can_activate)
+    return;
+
+  g_menu_item_get_attribute (self->item, G_MENU_ATTRIBUTE_ACTION, "&s", &action_name);
+  action_target = g_menu_item_get_attribute_value (self->item, G_MENU_ATTRIBUTE_TARGET, NULL);
+
+  if (self->action_namespace)
+    {
+      gchar *full_action;
+
+      full_action = g_strjoin (".", self->action_namespace, action_name, NULL);
+      g_action_group_activate_action (G_ACTION_GROUP (self->observable), full_action, action_target);
+      g_free (full_action);
+    }
+  else
+    g_action_group_activate_action (G_ACTION_GROUP (self->observable), action_name, action_target);
+
+  if (action_target)
+    g_variant_unref (action_target);
+}
+
+void
+gtk_menu_tracker_item_request_submenu_shown (GtkMenuTrackerItem *self,
+                                             gboolean            shown)
+{
+  const gchar *submenu_action;
+  GVariant *value;
+
+  if (!g_menu_item_get_attribute (self->item, "submenu-action", "&s", &submenu_action))
+    return;
+
+  value = g_variant_new_boolean (shown);
+
+  if (self->action_namespace)
+    {
+      gchar *full_action;
+
+      full_action = g_strjoin (".", self->action_namespace, submenu_action, NULL);
+      g_action_group_change_action_state (G_ACTION_GROUP (self->observable), full_action, value);
+      g_free (full_action);
+    }
+  else
+    g_action_group_change_action_state (G_ACTION_GROUP (self->observable), submenu_action, value);
+}
diff --git a/gtk/gtkmenutrackeritem.h b/gtk/gtkmenutrackeritem.h
new file mode 100644
index 0000000..928389f
--- /dev/null
+++ b/gtk/gtkmenutrackeritem.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright © 2011, 2013 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#ifndef __GTK_MENU_TRACKER_ITEM_H__
+#define __GTK_MENU_TRACKER_ITEM_H__
+
+#include "gactionobservable.h"
+
+#define GTK_TYPE_MENU_TRACKER_ITEM                          (gtk_menu_tracker_item_get_type ())
+#define GTK_MENU_TRACKER_ITEM(inst)                         (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
+                                                             GTK_TYPE_MENU_TRACKER_ITEM, GtkMenuTrackerItem))
+#define GTK_IS_MENU_TRACKER_ITEM(inst)                      (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \
+                                                             GTK_TYPE_MENU_TRACKER_ITEM))
+
+typedef struct _GtkMenuTrackerItem GtkMenuTrackerItem;
+
+#define GTK_TYPE_MENU_TRACKER_ITEM_ROLE                     (gtk_menu_tracker_item_role_get_type ())
+
+typedef enum  {
+  GTK_MENU_TRACKER_ITEM_ROLE_NORMAL,
+  GTK_MENU_TRACKER_ITEM_ROLE_TOGGLE,
+  GTK_MENU_TRACKER_ITEM_ROLE_RADIO,
+  GTK_MENU_TRACKER_ITEM_ROLE_SEPARATOR
+} GtkMenuTrackerItemRole;
+
+G_GNUC_INTERNAL
+GType                   gtk_menu_tracker_item_get_type                  (void) G_GNUC_CONST;
+
+G_GNUC_INTERNAL
+GType                   gtk_menu_tracker_item_role_get_type             (void) G_GNUC_CONST;
+
+G_GNUC_INTERNAL
+GtkMenuTrackerItem *    gtk_menu_tracker_item_new                       (GActionObservable *observable,
+                                                                         GMenuModel        *model,
+                                                                         gint               item_index,
+                                                                         const gchar       *action_namespace,
+                                                                         gboolean           is_separator);
+
+G_GNUC_INTERNAL
+GActionObservable *     gtk_menu_tracker_item_get_observable            (GtkMenuTrackerItem *self);
+
+G_GNUC_INTERNAL
+const gchar *           gtk_menu_tracker_item_get_label                 (GtkMenuTrackerItem *self);
+
+G_GNUC_INTERNAL
+GIcon *                 gtk_menu_tracker_item_get_icon                  (GtkMenuTrackerItem *self);
+
+G_GNUC_INTERNAL
+gboolean                gtk_menu_tracker_item_get_sensitive             (GtkMenuTrackerItem *self);
+
+G_GNUC_INTERNAL
+gboolean                gtk_menu_tracker_item_get_visible               (GtkMenuTrackerItem *self);
+
+G_GNUC_INTERNAL
+GtkMenuTrackerItemRole  gtk_menu_tracker_item_get_role                  (GtkMenuTrackerItem *self);
+
+G_GNUC_INTERNAL
+gboolean                gtk_menu_tracker_item_get_toggled               (GtkMenuTrackerItem *self);
+
+G_GNUC_INTERNAL
+GMenuModel *            gtk_menu_tracker_item_get_submenu               (GtkMenuTrackerItem *self);
+
+G_GNUC_INTERNAL
+gchar *                 gtk_menu_tracker_item_get_submenu_namespace     (GtkMenuTrackerItem *self);
+
+G_GNUC_INTERNAL
+gboolean                gtk_menu_tracker_item_get_should_request_show   (GtkMenuTrackerItem *self);
+
+G_GNUC_INTERNAL
+void                    gtk_menu_tracker_item_activated                 (GtkMenuTrackerItem *self);
+
+G_GNUC_INTERNAL
+void                    gtk_menu_tracker_item_request_submenu_shown     (GtkMenuTrackerItem *self,
+                                                                         gboolean            shown);
+
+#endif
diff --git a/gtk/gtkmodelmenuitem.c b/gtk/gtkmodelmenuitem.c
index 1d30d38..e8a547e 100644
--- a/gtk/gtkmodelmenuitem.c
+++ b/gtk/gtkmodelmenuitem.c
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2011 Canonical Limited
+ * Copyright © 2011, 2013 Canonical Limited
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -21,17 +21,14 @@
 
 #include "gtkmodelmenuitem.h"
 
-#include "gtkaccelmapprivate.h"
-#include "gtkactionhelper.h"
-#include "gtkwidgetprivate.h"
-#include "gtkaccellabel.h"
+#include "gtklabel.h"
 #include "gtkimage.h"
 #include "gtkbox.h"
 
 struct _GtkModelMenuItem
 {
   GtkCheckMenuItem parent_instance;
-  GtkActionHelperRole role;
+  GtkMenuTrackerItemRole role;
   gboolean has_indicator;
 };
 
@@ -39,7 +36,14 @@ typedef GtkCheckMenuItemClass GtkModelMenuItemClass;
 
 G_DEFINE_TYPE (GtkModelMenuItem, gtk_model_menu_item, GTK_TYPE_CHECK_MENU_ITEM)
 
-#define PROP_ACTION_ROLE 1
+enum
+{
+  PROP_0,
+  PROP_ACTION_ROLE,
+  PROP_ICON,
+  PROP_TEXT,
+  PROP_TOGGLED
+};
 
 static void
 gtk_model_menu_item_toggle_size_request (GtkMenuItem *menu_item,
@@ -56,6 +60,12 @@ gtk_model_menu_item_toggle_size_request (GtkMenuItem *menu_item,
 }
 
 static void
+gtk_model_menu_item_activate (GtkMenuItem *item)
+{
+  /* block the automatic toggle behaviour -- just do nothing */
+}
+
+static void
 gtk_model_menu_item_draw_indicator (GtkCheckMenuItem *check_item,
                                     cairo_t          *cr)
 {
@@ -67,186 +77,163 @@ gtk_model_menu_item_draw_indicator (GtkCheckMenuItem *check_item,
 }
 
 static void
-gtk_actionable_set_namespaced_action_name (GtkActionable *actionable,
-                                           const gchar   *namespace,
-                                           const gchar   *action_name)
+gtk_model_menu_item_set_has_indicator (GtkModelMenuItem *item,
+                                       gboolean          has_indicator)
 {
-  if (namespace)
-    {
-      gchar *name = g_strdup_printf ("%s.%s", namespace, action_name);
-      gtk_actionable_set_action_name (actionable, name);
-      g_free (name);
-    }
-  else
-    {
-      gtk_actionable_set_action_name (actionable, action_name);
-    }
-}
+  if (has_indicator == item->has_indicator)
+    return;
 
-static void
-gtk_model_menu_item_submenu_shown (GtkWidget *widget,
-                                   gpointer   user_data)
-{
-  const gchar *action_name = user_data;
-  GActionMuxer *muxer;
+  item->has_indicator = has_indicator;
 
-  muxer = _gtk_widget_get_action_muxer (widget);
-  g_action_group_change_action_state (G_ACTION_GROUP (muxer), action_name, g_variant_new_boolean (TRUE));
+  gtk_widget_queue_resize (GTK_WIDGET (item));
 }
 
 static void
-gtk_model_menu_item_submenu_hidden (GtkWidget *widget,
-                                    gpointer   user_data)
+gtk_model_menu_item_set_action_role (GtkModelMenuItem       *item,
+                                     GtkMenuTrackerItemRole  role)
 {
-  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));
-}
+  AtkObject *accessible;
+  AtkRole a11y_role;
 
-static void
-gtk_model_menu_item_setup (GtkModelMenuItem  *item,
-                           GMenuModel        *model,
-                           gint               item_index,
-                           const gchar       *action_namespace)
-{
-  GMenuAttributeIter *iter;
-  GMenuModel *submenu;
-  const gchar *key;
-  GVariant *value;
-  GtkWidget *label;
+  if (role == item->role)
+    return;
 
-  label = NULL;
+  gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (item), role == 
GTK_MENU_TRACKER_ITEM_ROLE_RADIO);
+  gtk_model_menu_item_set_has_indicator (item, role != GTK_MENU_TRACKER_ITEM_ROLE_NORMAL);
 
-  /* In the case that we have an icon, make an HBox and put it beside
-   * the label.  Otherwise, we just have a label directly.
-   */
-  if ((value = g_menu_model_get_item_attribute_value (model, item_index, "icon", NULL)))
+  accessible = gtk_widget_get_accessible (GTK_WIDGET (item));
+  switch (role)
     {
-      GIcon *icon;
-
-      icon = g_icon_deserialize (value);
+    case GTK_MENU_TRACKER_ITEM_ROLE_NORMAL:
+      a11y_role = ATK_ROLE_MENU_ITEM;
+      break;
 
-      if (icon != NULL)
-        {
-          GtkWidget *image;
-          GtkWidget *box;
+    case GTK_MENU_TRACKER_ITEM_ROLE_TOGGLE:
+      a11y_role = ATK_ROLE_CHECK_MENU_ITEM;
+      break;
 
-          box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+    case GTK_MENU_TRACKER_ITEM_ROLE_RADIO:
+      a11y_role = ATK_ROLE_RADIO_MENU_ITEM;
+      break;
 
-          image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU);
-          gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0);
-          g_object_unref (icon);
+    default:
+      g_assert_not_reached ();
+    }
 
-          label = gtk_accel_label_new ("");
-          gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
-          gtk_accel_label_set_accel_widget (GTK_ACCEL_LABEL (label), GTK_WIDGET (item));
-          gtk_box_pack_end (GTK_BOX (box), label, TRUE, TRUE, 0);
+  atk_object_set_role (accessible, a11y_role);
+}
 
-          gtk_container_add (GTK_CONTAINER (item), box);
+static void
+gtk_model_menu_item_set_icon (GtkModelMenuItem *item,
+                              GIcon            *icon)
+{
+  GtkWidget *child;
 
-          gtk_widget_show_all (box);
-        }
+  g_return_if_fail (GTK_IS_MODEL_MENU_ITEM (item));
+  g_return_if_fail (icon == NULL || G_IS_ICON (icon));
 
-      g_variant_unref (value);
-    }
+  child = gtk_bin_get_child (GTK_BIN (item));
 
-  if (label == NULL)
+  /* There are only three possibilities here:
+   *
+   *   - no child
+   *   - accel label child
+   *   - already a box
+   *
+   * Handle the no-child case by having GtkMenuItem create the accel
+   * label, then we will only have two possible cases.
+   */
+  if (child == NULL)
     {
-      /* Ensure that the GtkAccelLabel has been created... */
-      (void) gtk_menu_item_get_label (GTK_MENU_ITEM (item));
-      label = gtk_bin_get_child (GTK_BIN (item));
+      gtk_menu_item_get_label (GTK_MENU_ITEM (item));
+      child = gtk_bin_get_child (GTK_BIN (item));
+      g_assert (GTK_IS_LABEL (child));
     }
 
-  g_assert (label != NULL);
-
-  if ((submenu = g_menu_model_get_item_link (model, item_index, "submenu")))
+  /* If it is a box, make sure there are no images inside of it already.
+   */
+  if (GTK_IS_BOX (child))
     {
-      gchar *section_namespace = NULL;
-      GtkWidget *menu;
+      GList *children;
 
-      g_menu_model_get_item_attribute (model, item_index, "action-namespace", "s", &section_namespace);
-      menu = gtk_menu_new ();
-
-      if (action_namespace)
+      children = gtk_container_get_children (GTK_CONTAINER (child));
+      while (children)
         {
-          gchar *namespace = g_strjoin (".", action_namespace, section_namespace, NULL);
-          gtk_menu_shell_bind_model (GTK_MENU_SHELL (menu), submenu, namespace, TRUE);
-          g_free (namespace);
-        }
-      else
-        gtk_menu_shell_bind_model (GTK_MENU_SHELL (menu), submenu, section_namespace, TRUE);
+          if (GTK_IS_IMAGE (children->data))
+            gtk_widget_destroy (children->data);
 
-      gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu);
-
-      g_free (section_namespace);
-      g_object_unref (submenu);
+          children = g_list_delete_link (children, children);
+        }
     }
 
-  iter = g_menu_model_iterate_item_attributes (model, item_index);
-  while (g_menu_attribute_iter_get_next (iter, &key, &value))
+  /* If it is not a box, put it into a box, at the end */
+  if (!GTK_IS_BOX (child))
     {
-      if (g_str_equal (key, "label") && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
-        gtk_label_set_text_with_mnemonic (GTK_LABEL (label), g_variant_get_string (value, NULL));
+      GtkWidget *box;
 
-      else if (g_str_equal (key, "accel") && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
-        {
-          GdkModifierType modifiers;
-          guint key;
-
-          gtk_accelerator_parse (g_variant_get_string (value, NULL), &key, &modifiers);
+      box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
 
-          if (key)
-            gtk_accel_label_set_accel (GTK_ACCEL_LABEL (label), key, modifiers);
-        }
+      /* Reparent the child without destroying it */
+      g_object_ref (child);
+      gtk_container_remove (GTK_CONTAINER (item), child);
+      gtk_box_pack_end (GTK_BOX (box), child, TRUE, TRUE, 0);
+      g_object_unref (child);
 
-      else if (g_str_equal (key, "action") && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
-        gtk_actionable_set_namespaced_action_name (GTK_ACTIONABLE (item), action_namespace,
-                                                   g_variant_get_string (value, NULL));
+      gtk_container_add (GTK_CONTAINER (item), box);
+      gtk_widget_show (box);
 
-      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;
+      /* Now we have a box */
+      child = box;
+    }
 
-              if (action_namespace)
-                full_action = g_strjoin (".", action_namespace, action, NULL);
-              else
-                full_action = g_strdup (action);
+  g_assert (GTK_IS_BOX (child));
 
-              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);
-            }
-        }
+  /* child is now a box containing a label and no image.  Add the icon,
+   * if appropriate.
+   */
+  if (icon != NULL)
+    {
+      GtkWidget *image;
 
-      g_variant_unref (value);
+      image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU);
+      gtk_box_pack_start (GTK_BOX (child), image, FALSE, FALSE, 0);
+      gtk_widget_show (image);
     }
-  g_object_unref (iter);
-
-  gtk_menu_item_set_use_underline (GTK_MENU_ITEM (item), TRUE);
 }
 
 static void
-gtk_model_menu_item_set_has_indicator (GtkModelMenuItem *item,
-                                       gboolean          has_indicator)
+gtk_model_menu_item_set_text (GtkModelMenuItem *item,
+                              const gchar      *text)
 {
-  if (has_indicator == item->has_indicator)
+  GtkWidget *child;
+  GList *children;
+
+  child = gtk_bin_get_child (GTK_BIN (item));
+  if (child == NULL)
+    {
+      gtk_menu_item_get_label (GTK_MENU_ITEM (item));
+      child = gtk_bin_get_child (GTK_BIN (item));
+      g_assert (GTK_IS_LABEL (child));
+    }
+
+  if (GTK_IS_LABEL (child))
+    {
+      gtk_label_set_text_with_mnemonic (GTK_LABEL (child), text);
+      return;
+    }
+
+  if (!GTK_IS_CONTAINER (child))
     return;
 
-  item->has_indicator = has_indicator;
+  children = gtk_container_get_children (GTK_CONTAINER (child));
 
-  gtk_widget_queue_resize (GTK_WIDGET (item));
+  while (children)
+    {
+      if (GTK_IS_LABEL (children->data))
+        gtk_label_set_label (GTK_LABEL (children->data), text);
+
+      children = g_list_delete_link (children, children);
+    }
 }
 
 static void
@@ -254,40 +241,28 @@ gtk_model_menu_item_set_property (GObject *object, guint prop_id,
                                   const GValue *value, GParamSpec *pspec)
 {
   GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (object);
-  GtkActionHelperRole role;
-  AtkObject *accessible;
-  AtkRole a11y_role;
-
-  g_assert (prop_id == PROP_ACTION_ROLE);
-
-  role = g_value_get_uint (value);
-
-  if (role == item->role)
-    return;
-
-  gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (item), role == GTK_ACTION_HELPER_ROLE_RADIO);
-  gtk_model_menu_item_set_has_indicator (item, role != GTK_ACTION_HELPER_ROLE_NORMAL);
 
-  accessible = gtk_widget_get_accessible (GTK_WIDGET (item));
-  switch (role)
+  switch (prop_id)
     {
-    case GTK_ACTION_HELPER_ROLE_NORMAL:
-      a11y_role = ATK_ROLE_MENU_ITEM;
+    case PROP_ACTION_ROLE:
+      gtk_model_menu_item_set_action_role (item, g_value_get_enum (value));
       break;
 
-    case GTK_ACTION_HELPER_ROLE_TOGGLE:
-      a11y_role = ATK_ROLE_CHECK_MENU_ITEM;
+    case PROP_ICON:
+      gtk_model_menu_item_set_icon (item, g_value_get_object (value));
       break;
 
-    case GTK_ACTION_HELPER_ROLE_RADIO:
-      a11y_role = ATK_ROLE_RADIO_MENU_ITEM;
+    case PROP_TEXT:
+      gtk_model_menu_item_set_text (item, g_value_get_string (value));
+      break;
+
+    case PROP_TOGGLED:
+      _gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), g_value_get_boolean (value));
       break;
 
     default:
       g_assert_not_reached ();
     }
-
-  atk_object_set_role (accessible, a11y_role);
 }
 
 static void
@@ -305,24 +280,28 @@ gtk_model_menu_item_class_init (GtkModelMenuItemClass *class)
   check_class->draw_indicator = gtk_model_menu_item_draw_indicator;
 
   item_class->toggle_size_request = gtk_model_menu_item_toggle_size_request;
+  item_class->activate = gtk_model_menu_item_activate;
 
   object_class->set_property = gtk_model_menu_item_set_property;
 
   g_object_class_install_property (object_class, PROP_ACTION_ROLE,
-                                   g_param_spec_uint ("action-role", "action role", "action role",
-                                                      0, 2, 0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+                                   g_param_spec_enum ("action-role", "action role", "action role",
+                                                      GTK_TYPE_MENU_TRACKER_ITEM_ROLE,
+                                                      GTK_MENU_TRACKER_ITEM_ROLE_NORMAL,
+                                                      G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_ICON,
+                                   g_param_spec_object ("icon", "icon", "icon", G_TYPE_ICON,
+                                                        G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_TEXT,
+                                   g_param_spec_string ("text", "text", "text", NULL,
+                                                        G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_TOGGLED,
+                                   g_param_spec_boolean ("toggled", "toggled", "toggled", FALSE,
+                                                         G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
 }
 
 GtkWidget *
-gtk_model_menu_item_new (GMenuModel        *model,
-                         gint               item_index,
-                         const gchar       *action_namespace)
+gtk_model_menu_item_new (void)
 {
-  GtkModelMenuItem *item;
-
-  item = g_object_new (GTK_TYPE_MODEL_MENU_ITEM, NULL);
-
-  gtk_model_menu_item_setup (item, model, item_index, action_namespace);
-
-  return GTK_WIDGET (item);
+  return g_object_new (GTK_TYPE_MODEL_MENU_ITEM, NULL);
 }
diff --git a/gtk/gtkmodelmenuitem.h b/gtk/gtkmodelmenuitem.h
index 3f24163..8dbdbb0 100644
--- a/gtk/gtkmodelmenuitem.h
+++ b/gtk/gtkmodelmenuitem.h
@@ -21,6 +21,7 @@
 #define __GTK_MODEL_MENU_ITEM_H__
 
 #include <gtk/gtkcheckmenuitem.h>
+#include <gtk/gtkmenutrackeritem.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),             
         \
@@ -34,8 +35,6 @@ G_GNUC_INTERNAL
 GType                   gtk_model_menu_item_get_type                    (void) G_GNUC_CONST;
 
 G_GNUC_INTERNAL
-GtkWidget *             gtk_model_menu_item_new                         (GMenuModel        *model,
-                                                                         gint               item_index,
-                                                                         const gchar       
*action_namespace);
+GtkWidget *             gtk_model_menu_item_new                         (void);
 
 #endif /* __GTK_MODEL_MENU_ITEM_H__ */


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