[gnome-shell/wip/gtkmenutrackeritem: 6/9] remoteMenu: Port to GtkMenuTrackerItem
- From: Jasper St. Pierre <jstpierre src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell/wip/gtkmenutrackeritem: 6/9] remoteMenu: Port to GtkMenuTrackerItem
- Date: Thu, 9 May 2013 21:51:52 +0000 (UTC)
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 (>k_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 (>k_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, ¶meter_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, ¶meter_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]