[gnome-shell/wip/gtkmenutrackeritem: 6/9] remoteMenu: Port to GtkMenuTrackerItem



commit 71464904c178aadb4e868c06320f829cbc3bde39
Author: Jasper St. Pierre <jstpierre mecheye net>
Date:   Thu May 9 12:28:44 2013 -0400

    remoteMenu: Port to GtkMenuTrackerItem
    
    This removes a lot more code and punts it to Ryan to maintain.
    Currently, our implementation of submenus will be gone, but this
    will be fixed in a few commits.

 js/ui/remoteMenu.js      |  222 +++++--------------
 src/Makefile.am          |    4 +
 src/gtkmenutracker.c     |   22 ++-
 src/gtkmenutracker.h     |   14 +-
 src/gtkmenutrackeritem.c |  535 ++++++++++++++++++++++++++++++++++++++++++++++
 src/gtkmenutrackeritem.h |   80 +++++++
 src/shell-menu-tracker.c |   24 ++-
 src/shell-menu-tracker.h |   12 +-
 8 files changed, 721 insertions(+), 192 deletions(-)
---
diff --git a/js/ui/remoteMenu.js b/js/ui/remoteMenu.js
index 89157bf..dd98602 100644
--- a/js/ui/remoteMenu.js
+++ b/js/ui/remoteMenu.js
@@ -1,114 +1,81 @@
 // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
 
 const GLib = imports.gi.GLib;
+const GObject = imports.gi.GObject;
 const Gio = imports.gi.Gio;
 const Lang = imports.lang;
 const Shell = imports.gi.Shell;
+const ShellMenu = imports.gi.ShellMenu;
 const St = imports.gi.St;
 
 const PopupMenu = imports.ui.popupMenu;
 
-/**
- * RemoteMenu:
- *
- * A PopupMenu that tracks a GMenuModel and shows its actions
- * (exposed by GApplication/GActionGroup)
- */
-const RemoteMenu = new Lang.Class({
-    Name: 'RemoteMenu',
-    Extends: PopupMenu.PopupMenu,
+const RemoteMenuItemMapper = new Lang.Class({
+    Name: 'RemoteMenuItemMapper',
 
-    _init: function(sourceActor, model, actionGroup) {
-        this.parent(sourceActor, 0.0, St.Side.TOP);
+    _init: function(trackerItem) {
+        this._trackerItem = trackerItem;
 
-        this.model = model;
-        this.actionGroup = actionGroup;
+        this.menuItem = new PopupMenu.PopupBaseMenuItem();
+        this._label = new St.Label();
+        this.menuItem.addActor(this._label);
+        this.menuItem.actor.label_actor = this._label;
 
-        this._actions = {};
-        this._trackMenu(model, this);
+        this.menuItem.connect('activate', Lang.bind(this, function() {
+            this._trackerItem.activated();
+        }));
 
-        this._actionStateChangeId = this.actionGroup.connect('action-state-changed', Lang.bind(this, 
this._actionStateChanged));
-        this._actionEnableChangeId = this.actionGroup.connect('action-enabled-changed', Lang.bind(this, 
this._actionEnabledChanged));
-    },
+        this._trackerItem.bind_property('visible', this.menuItem.actor, 'visible', 
GObject.BindingFlags.SYNC_CREATE);
 
-    destroy: function() {
-        if (this._actionStateChangeId) {
-            this.actionGroup.disconnect(this._actionStateChangeId);
-            this._actionStateChangeId = 0;
-        }
+        this._trackerItem.connect('notify::label', Lang.bind(this, this._updateLabel));
+        this._trackerItem.connect('notify::sensitive', Lang.bind(this, this._updateSensitivity));
+        this._trackerItem.connect('notify::role', Lang.bind(this, this._updateDecoration));
+        this._trackerItem.connect('notify::toggled', Lang.bind(this, this._updateDecoration));
 
-        if (this._actionEnableChangeId) {
-            this.actionGroup.disconnect(this._actionEnableChangeId);
-            this._actionEnableChangeId = 0;
-        }
+        this._updateLabel();
+        this._updateSensitivity();
+        this._updateDecoration();
+    },
 
-        this.parent();
+    _updateLabel: function() {
+        let label = this._trackerItem.label;
+        // remove all underscores that are not followed by another underscore
+        label = label.replace(/_([^_])/, '$1');
+        this._label.text = label;
     },
 
-    _actionAdded: function(model, item, index) {
-        let action_id = item.action_id;
-
-        if (!this._actions[action_id])
-            this._actions[action_id] = { enabled: this.actionGroup.get_action_enabled(action_id),
-                                         state: this.actionGroup.get_action_state(action_id),
-                                         items: [ ],
-                                       };
-        let action = this._actions[action_id];
-        let target, destroyId, specificSignalId;
-
-        if (action.state) {
-            // Docs have get_state_hint(), except that the DBus protocol
-            // has no provision for it (so ShellApp does not implement it,
-            // and neither GApplication), and g_action_get_state_hint()
-            // always returns null
-            // Funny :)
-
-            switch (String.fromCharCode(action.state.classify())) {
-            case 'b':
-                action.items.push(item);
-                item.setOrnament(action.state.get_boolean() ?
-                                 PopupMenu.Ornament.CHECK :
-                                 PopupMenu.Ornament.NONE);
-                specificSignalId = item.connect('activate', Lang.bind(this, function(item) {
-                    this.actionGroup.activate_action(action_id, null);
-                }));
-                break;
-            case 's':
-                action.items.push(item);
-                item._remoteTarget = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, 
null).deep_unpack();
-                item.setOrnament(action.state.deep_unpack() == item._remoteTarget ?
-                                 PopupMenu.Ornament.DOT :
-                                 PopupMenu.Ornament.NONE);
-                specificSignalId = item.connect('activate', Lang.bind(this, function(item) {
-                    this.actionGroup.activate_action(action_id, GLib.Variant.new_string(item._remoteTarget));
-                }));
-                break;
-            default:
-                log('Action "%s" has state of type %s, which is not supported'.format(action_id, 
action.state.get_type_string()));
-                return;
-            }
-        } else {
-            target = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null);
-            action.items.push(item);
-            specificSignalId = item.connect('activate', Lang.bind(this, function() {
-                this.actionGroup.activate_action(action_id, target);
-            }));
-        }
+    _updateSensitivity: function() {
+        this.menuItem.setSensitive(this._trackerItem.sensitive);
+    },
 
-        item.actor.reactive = item.actor.can_focus = action.enabled;
+    _updateDecoration: function() {
+        let ornamentForRole = {};
+        ornamentForRole[ShellMenu.MenuTrackerItemRole.RADIO] = PopupMenu.Ornament.DOT;
+        ornamentForRole[ShellMenu.MenuTrackerItemRole.CHECK] = PopupMenu.Ornament.CHECK;
 
-        destroyId = item.connect('destroy', Lang.bind(this, function() {
-            item.disconnect(destroyId);
-            item.disconnect(specificSignalId);
+        let ornament = PopupMenu.Ornament.NONE;
+        if (this._trackerItem.toggled)
+            ornament = ornamentForRole[this._trackerItem.role];
 
-            let pos = action.items.indexOf(item);
-            if (pos != -1)
-                action.items.splice(pos, 1);
-        }));
+        this.menuItem.setOrnament(ornament);
+    },
+});
+
+const RemoteMenu = new Lang.Class({
+    Name: 'RemoteMenu',
+    Extends: PopupMenu.PopupMenu,
+
+    _init: function(sourceActor, model, actionGroup) {
+        this.parent(sourceActor, 0.0, St.Side.TOP);
+
+        this._model = model;
+        this._actionGroup = actionGroup;
+        this._trackMenu(model, this);
     },
 
     _trackMenu: function(model, item) {
-        item._tracker = Shell.MenuTracker.new(model,
+        item._tracker = Shell.MenuTracker.new(this._actionGroup,
+                                              model,
                                               null, /* action namespace */
                                               Lang.bind(this, this._insertItem, item),
                                               Lang.bind(this, this._removeItem, item));
@@ -119,86 +86,15 @@ const RemoteMenu = new Lang.Class({
         });
     },
 
-    _createMenuItem: function(model, index) {
-        let labelValue = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_LABEL, null);
-        let label = labelValue ? labelValue.deep_unpack() : '';
-        // remove all underscores that are not followed by another underscore
-        label = label.replace(/_([^_])/, '$1');
-
-        let submenuModel = model.get_item_link(index, Gio.MENU_LINK_SUBMENU);
-        if (submenuModel) {
-            let item = new PopupMenu.PopupSubMenuMenuItem(label);
-            this._trackMenu(submenuModel, item.menu);
-            return item;
-        }
-
-        let item = new PopupMenu.PopupMenuItem(label);
-        let action_id = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_ACTION, null).deep_unpack();
-        item.actor.can_focus = item.actor.reactive = false;
-
-        item.action_id = action_id;
-
-        if (this.actionGroup.has_action(action_id)) {
-            this._actionAdded(model, item, index);
-            return item;
-        }
-
-        let signalId = this.actionGroup.connect('action-added', Lang.bind(this, function(actionGroup, 
actionName) {
-            actionGroup.disconnect(signalId);
-            if (this._actions[actionName]) return;
-
-            this._actionAdded(model, item, index);
-        }));
-
-        return item;
-    },
-
-    _actionStateChanged: function(actionGroup, action_id) {
-        let action = this._actions[action_id];
-        if (!action)
-            return;
-
-        action.state = actionGroup.get_action_state(action_id);
-        if (action.items.length) {
-            switch (String.fromCharCode(action.state.classify())) {
-            case 'b':
-                for (let i = 0; i < action.items.length; i++)
-                    action.items[i].setOrnament(action.state.get_boolean() ?
-                                                Ornament.CHECK : Ornament.NONE);
-                break;
-            case 'd':
-                for (let i = 0; i < action.items.length; i++)
-                    action.items[i].setValue(action.state.get_double());
-                break;
-            case 's':
-                for (let i = 0; i < action.items.length; i++)
-                    action.items[i].setOrnament(action.items[i]._remoteTarget == action.state.deep_unpack() ?
-                                                Ornament.DOT : Ornament.NONE);
-            }
-        }
-    },
-
-    _actionEnabledChanged: function(actionGroup, action_id) {
-        let action = this._actions[action_id];
-        if (!action)
-            return;
-
-        action.enabled = actionGroup.get_action_enabled(action_id);
-        if (action.items.length) {
-            for (let i = 0; i < action.items.length; i++) {
-                let item = action.items[i];
-                item.actor.reactive = item.actor.can_focus = action.enabled;
-            }
-        }
-    },
-
-    _insertItem: function(position, model, item_index, action_namespace, is_separator, target) {
+    _insertItem: function(trackerItem, position, target) {
         let item;
 
-        if (is_separator)
+        if (trackerItem.get_is_separator()) {
             item = new PopupMenu.PopupSeparatorMenuItem();
-        else
-            item = this._createMenuItem(model, item_index);
+        } else {
+            let mapper = new RemoteMenuItemMapper(trackerItem);
+            item = mapper.menuItem;
+        }
 
         target.addMenuItem(item, position);
     },
diff --git a/src/Makefile.am b/src/Makefile.am
index ec0028f..0e51ae2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -136,6 +136,8 @@ shell_private_sources =             \
        gtkactionobservable.c           \
        gtkactionobserver.h             \
        gtkactionobserver.c             \
+       gtkmenutrackeritem.c            \
+       gtkmenutrackeritem.h            \
        gtkmenutracker.c                \
        gtkmenutracker.h                \
        $(NULL)
@@ -302,6 +304,8 @@ ShellMenu_0_1_gir_FILES = \
        gtkactionobservable.c           \
        gtkactionobserver.h             \
        gtkactionobserver.c             \
+       gtkmenutrackeritem.c            \
+       gtkmenutrackeritem.h            \
        $(NULL)
 ShellMenu_0_1_gir_SCANNERFLAGS = --namespace=ShellMenu --identifier-prefix=Gtk
 INTROSPECTION_GIRS += ShellMenu-0.1.gir
diff --git a/src/gtkmenutracker.c b/src/gtkmenutracker.c
index f49457f..5dedc3b 100644
--- a/src/gtkmenutracker.c
+++ b/src/gtkmenutracker.c
@@ -27,6 +27,7 @@ typedef struct _GtkMenuTrackerSection GtkMenuTrackerSection;
 
 struct _GtkMenuTracker
 {
+  GtkActionObservable      *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 (GtkActionObservable      *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/src/gtkmenutracker.h b/src/gtkmenutracker.h
index 51810a1..b6c06ae 100644
--- a/src/gtkmenutracker.h
+++ b/src/gtkmenutracker.h
@@ -22,30 +22,26 @@
 #ifndef __GTK_MENU_TRACKER_H__
 #define __GTK_MENU_TRACKER_H__
 
-#include <gio/gio.h>
+#include "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,
                                                          gpointer                  user_data);
 
 
-G_GNUC_INTERNAL
-GtkMenuTracker *        gtk_menu_tracker_new            (GMenuModel               *model,
+GtkMenuTracker *        gtk_menu_tracker_new            (GtkActionObservable      *observer,
+                                                         GMenuModel               *model,
                                                          gboolean                  with_separators,
                                                          const gchar              *action_namespace,
                                                          GtkMenuTrackerInsertFunc  insert_func,
                                                          GtkMenuTrackerRemoveFunc  remove_func,
                                                          gpointer                  user_data);
 
-G_GNUC_INTERNAL
 void                    gtk_menu_tracker_free           (GtkMenuTracker           *tracker);
 
 #endif /* __GTK_MENU_TRACKER_H__ */
diff --git a/src/gtkmenutrackeritem.c b/src/gtkmenutrackeritem.c
new file mode 100644
index 0000000..df9ef85
--- /dev/null
+++ b/src/gtkmenutrackeritem.c
@@ -0,0 +1,535 @@
+#include "gtkmenutrackeritem.h"
+
+typedef GObjectClass GtkMenuTrackerItemClass;
+
+struct _GtkMenuTrackerItem
+{
+  GObject parent_instance;
+
+  GtkActionObservable *observable;
+  gchar *action_namespace;
+  GMenuItem *item;
+  GtkMenuTrackerItemRole role : 4;
+  guint is_separator : 1;
+  guint can_activate : 1;
+  guint sensitive : 1;
+  guint toggled : 1;
+};
+
+enum {
+  PROP_0,
+  PROP_IS_SEPARATOR,
+  PROP_LABEL,
+  PROP_ICON,
+  PROP_SENSITIVE,
+  PROP_VISIBLE,
+  PROP_ROLE,
+  PROP_TOGGLED,
+  PROP_ACCEL,
+  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 (GtkActionObserverInterface *iface);
+G_DEFINE_TYPE_WITH_CODE (GtkMenuTrackerItem, gtk_menu_tracker_item, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GTK_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_CHECK, "GTK_MENU_TRACKER_ITEM_ROLE_CHECK", "check" },
+        { GTK_MENU_TRACKER_ITEM_ROLE_RADIO, "GTK_MENU_TRACKER_ITEM_ROLE_RADIO", "radio" },
+        { 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_IS_SEPARATOR:
+      g_value_set_boolean (value, gtk_menu_tracker_item_get_is_separator (self));
+      break;
+    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_ACCEL:
+      g_value_set_string (value, gtk_menu_tracker_item_get_accel (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);
+
+  g_free (self->action_namespace);
+
+  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_IS_SEPARATOR] =
+    g_param_spec_boolean ("is-separator", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
+  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_ACCEL] =
+    g_param_spec_string ("accel", "", "", NULL, 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 (GtkActionObserver   *observer,
+                                    GtkActionObservable *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_CHECK;
+    }
+
+  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 (GtkActionObserver   *observer,
+                                              GtkActionObservable *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 (GtkActionObserver   *observer,
+                                            GtkActionObservable *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 (GtkActionObserver   *observer,
+                                      GtkActionObservable *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 (GtkActionObserverInterface *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 (GtkActionObservable *observable,
+                           GMenuModel          *model,
+                           gint                 item_index,
+                           const gchar         *action_namespace,
+                           gboolean             is_separator)
+{
+  GtkMenuTrackerItem *self;
+  const gchar *action_name;
+
+  g_return_val_if_fail (GTK_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);
+  self->is_separator = is_separator;
+
+  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);
+          gtk_action_observable_register_observer (self->observable, full_action, GTK_ACTION_OBSERVER 
(self));
+          found = g_action_group_query_action (group, full_action, &enabled, &parameter_type, NULL, NULL, 
&state);
+          g_free (full_action);
+        }
+      else
+        {
+          gtk_action_observable_register_observer (self->observable, action_name, GTK_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 (GTK_ACTION_OBSERVER (self), observable, NULL,
+                                            parameter_type, enabled, state);
+      else
+        gtk_menu_tracker_item_action_removed (GTK_ACTION_OBSERVER (self), observable, NULL);
+
+      if (state)
+        g_variant_unref (state);
+    }
+  else
+    self->sensitive = TRUE;
+
+  return self;
+}
+
+/**
+ * gtk_menu_tracker_item_get_observable:
+ *
+ * Returns: (transfer none):
+ */
+GtkActionObservable *
+gtk_menu_tracker_item_get_observable (GtkMenuTrackerItem *self)
+{
+  return self->observable;
+}
+
+gboolean
+gtk_menu_tracker_item_get_is_separator (GtkMenuTrackerItem *self)
+{
+  return self->is_separator;
+}
+
+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;
+}
+
+/**
+ * gtk_menu_tracker_item_get_icon:
+ *
+ * Returns: (transfer full):
+ */
+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;
+}
+
+const gchar *
+gtk_menu_tracker_item_get_accel (GtkMenuTrackerItem *self)
+{
+  const gchar *accel = NULL;
+
+  g_menu_item_get_attribute (self->item, "accel", "&s", &accel);
+
+  return accel;
+}
+
+/**
+ * gtk_menu_tracker_item_get_submenu:
+ *
+ * Returns: (transfer full):
+ */
+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/src/gtkmenutrackeritem.h b/src/gtkmenutrackeritem.h
new file mode 100644
index 0000000..c82e444
--- /dev/null
+++ b/src/gtkmenutrackeritem.h
@@ -0,0 +1,80 @@
+/*
+ * 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 "gtkactionobservable.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_CHECK,
+  GTK_MENU_TRACKER_ITEM_ROLE_RADIO,
+} GtkMenuTrackerItemRole;
+
+GType                   gtk_menu_tracker_item_get_type                  (void) G_GNUC_CONST;
+
+GType                   gtk_menu_tracker_item_role_get_type             (void) G_GNUC_CONST;
+
+GtkMenuTrackerItem *    gtk_menu_tracker_item_new                       (GtkActionObservable *observable,
+                                                                         GMenuModel          *model,
+                                                                         gint                 item_index,
+                                                                         const gchar         
*action_namespace,
+                                                                         gboolean             is_separator);
+
+GtkActionObservable *   gtk_menu_tracker_item_get_observable            (GtkMenuTrackerItem *self);
+
+gboolean                gtk_menu_tracker_item_get_is_separator          (GtkMenuTrackerItem *self);
+
+const gchar *           gtk_menu_tracker_item_get_label                 (GtkMenuTrackerItem *self);
+
+GIcon *                 gtk_menu_tracker_item_get_icon                  (GtkMenuTrackerItem *self);
+
+gboolean                gtk_menu_tracker_item_get_sensitive             (GtkMenuTrackerItem *self);
+
+gboolean                gtk_menu_tracker_item_get_visible               (GtkMenuTrackerItem *self);
+
+GtkMenuTrackerItemRole  gtk_menu_tracker_item_get_role                  (GtkMenuTrackerItem *self);
+
+gboolean                gtk_menu_tracker_item_get_toggled               (GtkMenuTrackerItem *self);
+
+const gchar *           gtk_menu_tracker_item_get_accel                 (GtkMenuTrackerItem *self);
+
+GMenuModel *            gtk_menu_tracker_item_get_submenu               (GtkMenuTrackerItem *self);
+
+gchar *                 gtk_menu_tracker_item_get_submenu_namespace     (GtkMenuTrackerItem *self);
+
+gboolean                gtk_menu_tracker_item_get_should_request_show   (GtkMenuTrackerItem *self);
+
+void                    gtk_menu_tracker_item_activated                 (GtkMenuTrackerItem *self);
+
+void                    gtk_menu_tracker_item_request_submenu_shown     (GtkMenuTrackerItem *self,
+                                                                         gboolean            shown);
+
+#endif
diff --git a/src/shell-menu-tracker.c b/src/shell-menu-tracker.c
index 788605e..ac1c8b2 100644
--- a/src/shell-menu-tracker.c
+++ b/src/shell-menu-tracker.c
@@ -25,6 +25,12 @@
 #include "shell-menu-tracker.h"
 #include "gtkmenutracker.h"
 
+/**
+ * SECTION:shell-menu-tracker
+ * @short_description: a simple wrapper around #GtkMenuTracker
+ *                     to make it bindable.
+ */
+
 struct _ShellMenuTracker
 {
   guint ref_count;
@@ -40,17 +46,12 @@ struct _ShellMenuTracker
 };
 
 static void
-shell_menu_tracker_insert_func (gint position,
-                                GMenuModel *model,
-                                gint item_index,
-                                const gchar *action_namespace,
-                                gboolean is_separator,
+shell_menu_tracker_insert_func (GtkMenuTrackerItem *item,
+                                gint position,
                                 gpointer user_data)
 {
   ShellMenuTracker *tracker = (ShellMenuTracker *) user_data;
-  tracker->insert_func (position, model, item_index,
-                        action_namespace, is_separator,
-                        tracker->insert_user_data);
+  tracker->insert_func (item, position, tracker->insert_user_data);
 }
 
 static void
@@ -63,6 +64,7 @@ shell_menu_tracker_remove_func (gint position,
 
 /**
  * shell_menu_tracker_new:
+ * @observable:
  * @model:
  * @action_namespace: (allow-none):
  * @insert_func:
@@ -73,7 +75,8 @@ shell_menu_tracker_remove_func (gint position,
  * @remove_notify:
  */
 ShellMenuTracker *
-shell_menu_tracker_new (GMenuModel                 *model,
+shell_menu_tracker_new (GtkActionObservable        *observable,
+                        GMenuModel                 *model,
                         const gchar                *action_namespace,
                         ShellMenuTrackerInsertFunc  insert_func,
                         gpointer                    insert_user_data,
@@ -92,7 +95,8 @@ shell_menu_tracker_new (GMenuModel                 *model,
   tracker->remove_user_data = remove_user_data;
   tracker->remove_notify = remove_notify;
 
-  tracker->tracker = gtk_menu_tracker_new (model,
+  tracker->tracker = gtk_menu_tracker_new (observable,
+                                           model,
                                            TRUE, /* with separators */
                                            action_namespace,
                                            shell_menu_tracker_insert_func,
diff --git a/src/shell-menu-tracker.h b/src/shell-menu-tracker.h
index 9af602e..df1482a 100644
--- a/src/shell-menu-tracker.h
+++ b/src/shell-menu-tracker.h
@@ -25,21 +25,21 @@
 
 #include <gio/gio.h>
 
+#include "gtkmenutrackeritem.h"
+
 typedef struct _ShellMenuTracker ShellMenuTracker;
 
 GType shell_menu_tracker_get_type (void) G_GNUC_CONST;
 
-typedef void         (* ShellMenuTrackerInsertFunc)       (gint                      position,
-                                                           GMenuModel               *model,
-                                                           gint                      item_index,
-                                                           const gchar              *action_namespace,
-                                                           gboolean                  is_separator,
+typedef void         (* ShellMenuTrackerInsertFunc)       (GtkMenuTrackerItem       *item,
+                                                           gint                      position,
                                                            gpointer                  user_data);
 
 typedef void         (* ShellMenuTrackerRemoveFunc)       (gint                      position,
                                                            gpointer                  user_data);
 
-ShellMenuTracker * shell_menu_tracker_new (GMenuModel                 *model,
+ShellMenuTracker * shell_menu_tracker_new (GtkActionObservable        *observable,
+                                           GMenuModel                 *model,
                                            const gchar                *action_namespace,
                                            ShellMenuTrackerInsertFunc  insert_func,
                                            gpointer                    insert_user_data,



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