aboutsummaryrefslogtreecommitdiff
path: root/libdbusmenu-gtk
diff options
context:
space:
mode:
Diffstat (limited to 'libdbusmenu-gtk')
-rw-r--r--libdbusmenu-gtk/client.c248
-rw-r--r--libdbusmenu-gtk/client.h14
-rw-r--r--libdbusmenu-gtk/genericmenuitem.c5
-rw-r--r--libdbusmenu-gtk/menu.c6
-rw-r--r--libdbusmenu-gtk/menu.h11
-rw-r--r--libdbusmenu-gtk/menuitem.c248
-rw-r--r--libdbusmenu-gtk/menuitem.h7
7 files changed, 531 insertions, 8 deletions
diff --git a/libdbusmenu-gtk/client.c b/libdbusmenu-gtk/client.c
index 3bd0af6..bba4550 100644
--- a/libdbusmenu-gtk/client.c
+++ b/libdbusmenu-gtk/client.c
@@ -36,6 +36,13 @@ License version 3 and version 2.1 along with this program. If not, see
#include "menuitem.h"
#include "genericmenuitem.h"
+/* Private */
+struct _DbusmenuGtkClientPrivate {
+ GtkAccelGroup * agroup;
+};
+
+#define DBUSMENU_GTKCLIENT_GET_PRIVATE(o) (DBUSMENU_GTKCLIENT(o)->priv)
+
/* Prototypes */
static void dbusmenu_gtkclient_class_init (DbusmenuGtkClientClass *klass);
static void dbusmenu_gtkclient_init (DbusmenuGtkClient *self);
@@ -45,6 +52,7 @@ static void new_menuitem (DbusmenuClient * client, DbusmenuMenuitem * mi, gpoint
static void new_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position, DbusmenuGtkClient * gtkclient);
static void delete_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, DbusmenuGtkClient * gtkclient);
static void move_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint new, guint old, DbusmenuGtkClient * gtkclient);
+static void item_activate (DbusmenuClient * client, DbusmenuMenuitem * mi, guint timestamp, gpointer userdata);
static gboolean new_item_normal (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client);
static gboolean new_item_seperator (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client);
@@ -62,6 +70,8 @@ dbusmenu_gtkclient_class_init (DbusmenuGtkClientClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ g_type_class_add_private (klass, sizeof (DbusmenuGtkClientPrivate));
+
object_class->dispose = dbusmenu_gtkclient_dispose;
object_class->finalize = dbusmenu_gtkclient_finalize;
@@ -73,10 +83,18 @@ dbusmenu_gtkclient_class_init (DbusmenuGtkClientClass *klass)
static void
dbusmenu_gtkclient_init (DbusmenuGtkClient *self)
{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), DBUSMENU_GTKCLIENT_TYPE, DbusmenuGtkClientPrivate);
+
+ DbusmenuGtkClientPrivate * priv = DBUSMENU_GTKCLIENT_GET_PRIVATE(self);
+
+ priv->agroup = NULL;
+
dbusmenu_client_add_type_handler(DBUSMENU_CLIENT(self), DBUSMENU_CLIENT_TYPES_DEFAULT, new_item_normal);
dbusmenu_client_add_type_handler(DBUSMENU_CLIENT(self), DBUSMENU_CLIENT_TYPES_SEPARATOR, new_item_seperator);
+ /* TODO: I think these can be handled in the class... */
g_signal_connect(G_OBJECT(self), DBUSMENU_CLIENT_SIGNAL_NEW_MENUITEM, G_CALLBACK(new_menuitem), NULL);
+ g_signal_connect(G_OBJECT(self), DBUSMENU_CLIENT_SIGNAL_ITEM_ACTIVATE, G_CALLBACK(item_activate), NULL);
return;
}
@@ -85,6 +103,12 @@ dbusmenu_gtkclient_init (DbusmenuGtkClient *self)
static void
dbusmenu_gtkclient_dispose (GObject *object)
{
+ DbusmenuGtkClientPrivate * priv = DBUSMENU_GTKCLIENT_GET_PRIVATE(object);
+
+ if (priv->agroup != NULL) {
+ g_object_unref(priv->agroup);
+ priv->agroup = NULL;
+ }
G_OBJECT_CLASS (dbusmenu_gtkclient_parent_class)->dispose (object);
return;
@@ -99,6 +123,155 @@ dbusmenu_gtkclient_finalize (GObject *object)
return;
}
+/* Structure for passing data to swap_agroup */
+typedef struct _swap_agroup_t swap_agroup_t;
+struct _swap_agroup_t {
+ DbusmenuGtkClient * client;
+ GtkAccelGroup * old_agroup;
+ GtkAccelGroup * new_agroup;
+};
+
+/* Looks at the old version of the accelerator group and
+ the new one and makes the state proper. */
+static gboolean
+do_swap_agroup (DbusmenuMenuitem * mi, gpointer userdata) {
+ swap_agroup_t * data = (swap_agroup_t *)userdata;
+
+ /* If we don't have a shortcut we don't care */
+ if (!dbusmenu_menuitem_property_exist(mi, DBUSMENU_MENUITEM_PROP_SHORTCUT)) {
+ return FALSE;
+ }
+
+ guint key = 0;
+ GdkModifierType modifiers = 0;
+
+ dbusmenu_menuitem_property_get_shortcut(mi, &key, &modifiers);
+
+ if (key == 0) {
+ return FALSE;
+ }
+
+ #ifdef MASSIVEDEBUGGING
+ g_debug("Setting shortcut on '%s': %d %X", dbusmenu_menuitem_property_get(mi, DBUSMENU_MENUITEM_PROP_LABEL), key, modifiers);
+ #endif
+
+ GtkMenuItem * gmi = dbusmenu_gtkclient_menuitem_get(data->client, mi);
+ if (gmi == NULL) {
+ return FALSE;
+ }
+
+ const gchar * accel_path = gtk_menu_item_get_accel_path(gmi);
+
+ if (accel_path != NULL) {
+ gtk_accel_map_change_entry(accel_path, key, modifiers, TRUE /* replace */);
+ } else {
+ gchar * accel_path = g_strdup_printf("<Appmenus>/Generated/%X/%d", GPOINTER_TO_UINT(data->client), dbusmenu_menuitem_get_id(mi));
+
+ gtk_accel_map_add_entry(accel_path, key, modifiers);
+ gtk_widget_set_accel_path(GTK_WIDGET(gmi), accel_path, data->new_agroup);
+ g_free(accel_path);
+ }
+
+ GtkMenu * submenu = dbusmenu_gtkclient_menuitem_get_submenu(data->client, mi);
+ if (submenu != NULL) {
+ gtk_menu_set_accel_group(submenu, data->new_agroup);
+ }
+
+ return TRUE;
+}
+
+static void
+swap_agroup (DbusmenuMenuitem *mi, gpointer userdata) {
+ do_swap_agroup (mi, userdata);
+
+ return; /* See what I did here, Ted? :) */
+}
+
+/* Refresh the shortcut for an entry */
+static void
+refresh_shortcut (DbusmenuGtkClient * client, DbusmenuMenuitem * mi)
+{
+ g_return_if_fail(DBUSMENU_IS_GTKCLIENT(client));
+ g_return_if_fail(DBUSMENU_IS_MENUITEM(mi));
+
+ DbusmenuGtkClientPrivate * priv = DBUSMENU_GTKCLIENT_GET_PRIVATE(client);
+
+ swap_agroup_t data;
+ data.client = client;
+ data.old_agroup = priv->agroup;
+ data.new_agroup = priv->agroup;
+
+ if (do_swap_agroup(mi, &data)) {
+ guint key = 0;
+ GdkModifierType mod = 0;
+ GtkMenuItem *gmi = dbusmenu_gtkclient_menuitem_get (client, mi);
+
+ dbusmenu_menuitem_property_get_shortcut (mi, &key, &mod);
+
+ if (key != 0) {
+ gtk_widget_add_accelerator (GTK_WIDGET (gmi), "activate", priv->agroup, key, mod, GTK_ACCEL_VISIBLE);
+ }
+ }
+
+ return;
+}
+
+
+/**
+ dbusmenu_gtkclient_set_accel_group:
+ @client: To set the group on
+ @agroup: The new acceleration group
+
+ Sets the acceleration group for the menu items with accelerators
+ on this client.
+*/
+void
+dbusmenu_gtkclient_set_accel_group (DbusmenuGtkClient * client, GtkAccelGroup * agroup)
+{
+ g_return_if_fail(DBUSMENU_IS_GTKCLIENT(client));
+ g_return_if_fail(GTK_IS_ACCEL_GROUP(agroup));
+
+ DbusmenuGtkClientPrivate * priv = DBUSMENU_GTKCLIENT_GET_PRIVATE(client);
+
+ DbusmenuMenuitem * root = dbusmenu_client_get_root(DBUSMENU_CLIENT(client));
+ if (root != NULL) {
+ swap_agroup_t data;
+ data.client = client;
+ data.old_agroup = priv->agroup;
+ data.new_agroup = agroup;
+
+ dbusmenu_menuitem_foreach(root, swap_agroup, &data);
+ }
+
+ if (priv->agroup != NULL) {
+ g_object_unref(priv->agroup);
+ priv->agroup = NULL;
+ }
+
+ priv->agroup = agroup;
+
+ return;
+}
+
+/**
+ dbusmenu_gtkclient_get_accel_group:
+ @client: Client to query for an accelerator group
+
+ Gets the accel group for this client.
+
+ Return value: Either a valid group or #NULL on error or
+ none set.
+*/
+GtkAccelGroup *
+dbusmenu_gtkclient_get_accel_group (DbusmenuGtkClient * client)
+{
+ g_return_val_if_fail(DBUSMENU_IS_GTKCLIENT(client), NULL);
+
+ DbusmenuGtkClientPrivate * priv = DBUSMENU_GTKCLIENT_GET_PRIVATE(client);
+
+ return priv->agroup;
+}
+
/* Internal Functions */
static const gchar * data_menuitem = "dbusmenugtk-data-gtkmenuitem";
@@ -225,6 +398,17 @@ menu_prop_change_cb (DbusmenuMenuitem * mi, gchar * prop, GValue * value, GtkMen
return;
}
+/* Special handler for the shortcut changing as we need to have the
+ client for that one to get the accel group. */
+static void
+menu_shortcut_change_cb (DbusmenuMenuitem * mi, gchar * prop, GValue * value, DbusmenuGtkClient * client)
+{
+ if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_SHORTCUT)) {
+ refresh_shortcut(client, mi);
+ }
+ return;
+}
+
/* Call back that happens when the DbusmenuMenuitem
is destroyed. We're making sure to clean up everything
else down the pipe. */
@@ -250,6 +434,63 @@ new_menuitem (DbusmenuClient * client, DbusmenuMenuitem * mi, gpointer userdata)
return;
}
+/* Goes through the tree of items and ensure's that all the items
+ above us are also displayed. */
+static void
+activate_helper (GtkMenuShell * shell)
+{
+ if (shell == NULL) {
+ return;
+ }
+
+ if (GTK_IS_MENU(shell)) {
+ GtkWidget * attach = gtk_menu_get_attach_widget(GTK_MENU(shell));
+
+ if (attach != NULL) {
+ GtkWidget * parent = gtk_widget_get_parent(GTK_WIDGET(attach));
+
+ if (parent != NULL) {
+ if (GTK_IS_MENU(parent)) {
+ activate_helper(GTK_MENU_SHELL(parent));
+ }
+
+ /* This code is being commented out for GTK 3 because it
+ doesn't expose the right variables. We need to figure
+ this out as menus won't get grabs properly.
+ TODO FIXME HELP ARGHHHHHHHH */
+#if (HAVE_GTK3 == 0)
+ if (!GTK_MENU_SHELL (parent)->active) {
+ gtk_grab_add (parent);
+ GTK_MENU_SHELL (parent)->have_grab = TRUE;
+ GTK_MENU_SHELL (parent)->active = TRUE;
+ }
+#endif
+
+ gtk_menu_shell_select_item(GTK_MENU_SHELL(parent), attach);
+ }
+ }
+ }
+
+ return;
+}
+
+/* Signaled when we should show a menuitem at request of the application
+ that it is in. */
+static void
+item_activate (DbusmenuClient * client, DbusmenuMenuitem * mi, guint timestamp, gpointer userdata)
+{
+ gpointer pmenu = g_object_get_data(G_OBJECT(mi), data_menu);
+ if (pmenu == NULL) {
+ g_warning("Activated menu item doesn't have a menu? ID: %d", dbusmenu_menuitem_get_id(mi));
+ return;
+ }
+
+ activate_helper(GTK_MENU_SHELL(pmenu));
+ gtk_menu_shell_select_first(GTK_MENU_SHELL(pmenu), FALSE);
+
+ return;
+}
+
#ifdef MASSIVEDEBUGGING
static void
destroy_gmi (GtkMenuItem * gmi, DbusmenuMenuitem * mi)
@@ -291,6 +532,7 @@ dbusmenu_gtkclient_newitem_base (DbusmenuGtkClient * client, DbusmenuMenuitem *
/* DbusmenuMenuitem signals */
g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, G_CALLBACK(menu_prop_change_cb), gmi);
+ g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, G_CALLBACK(menu_shortcut_change_cb), client);
g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_CHILD_REMOVED, G_CALLBACK(delete_child), client);
g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_CHILD_MOVED, G_CALLBACK(move_child), client);
@@ -305,10 +547,11 @@ dbusmenu_gtkclient_newitem_base (DbusmenuGtkClient * client, DbusmenuMenuitem *
process_sensitive(item, gmi, dbusmenu_menuitem_property_get_value(item, DBUSMENU_MENUITEM_PROP_ENABLED));
process_toggle_type(item, gmi, dbusmenu_menuitem_property_get_value(item, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE));
process_toggle_state(item, gmi, dbusmenu_menuitem_property_get_value(item, DBUSMENU_MENUITEM_PROP_TOGGLE_STATE));
+ refresh_shortcut(client, item);
/* Oh, we're a child, let's deal with that */
if (parent != NULL) {
- new_child(parent, item, dbusmenu_menuitem_get_position_realized(item, parent), DBUSMENU_GTKCLIENT(client));
+ new_child(parent, item, dbusmenu_menuitem_get_position(item, parent), DBUSMENU_GTKCLIENT(client));
}
return;
@@ -322,6 +565,7 @@ new_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position, Dbus
#endif
if (dbusmenu_menuitem_get_root(mi)) { return; }
+ if (g_strcmp0(dbusmenu_menuitem_property_get(mi, DBUSMENU_MENUITEM_PROP_TYPE), DBUSMENU_CLIENT_TYPES_SEPARATOR) == 0) { return; }
gpointer ann_menu = g_object_get_data(G_OBJECT(mi), data_menu);
GtkMenu * menu = GTK_MENU(ann_menu);
@@ -335,7 +579,7 @@ new_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position, Dbus
}
GtkMenuItem * childmi = dbusmenu_gtkclient_menuitem_get(gtkclient, child);
- gtk_menu_shell_insert(GTK_MENU_SHELL(menu), GTK_WIDGET(childmi), dbusmenu_menuitem_get_position_realized(child, mi));
+ gtk_menu_shell_insert(GTK_MENU_SHELL(menu), GTK_WIDGET(childmi), position);
gtk_widget_show(GTK_WIDGET(menu));
return;
diff --git a/libdbusmenu-gtk/client.h b/libdbusmenu-gtk/client.h
index 7672bf7..c986a5d 100644
--- a/libdbusmenu-gtk/client.h
+++ b/libdbusmenu-gtk/client.h
@@ -43,6 +43,8 @@ G_BEGIN_DECLS
#define DBUSMENU_GTKCLIENT_SIGNAL_ROOT_CHANGED DBUSMENU_CLIENT_SIGNAL_ROOT_CHANGED
+typedef struct _DbusmenuGtkClientPrivate DbusmenuGtkClientPrivate;
+
/**
DbusmenuGtkClientClass:
@parent_class: #GtkMenuClass
@@ -50,6 +52,8 @@ G_BEGIN_DECLS
@reserved2: Reserved for future use.
@reserved3: Reserved for future use.
@reserved4: Reserved for future use.
+ @reserved5: Reserved for future use.
+ @reserved6: Reserved for future use.
*/
typedef struct _DbusmenuGtkClientClass DbusmenuGtkClientClass;
struct _DbusmenuGtkClientClass {
@@ -58,11 +62,13 @@ struct _DbusmenuGtkClientClass {
/* Signals */
void (*root_changed) (DbusmenuMenuitem * newroot);
- /* Reserved */
+ /*< Private >*/
void (*reserved1) (void);
void (*reserved2) (void);
void (*reserved3) (void);
void (*reserved4) (void);
+ void (*reserved5) (void);
+ void (*reserved6) (void);
};
/**
@@ -72,6 +78,9 @@ struct _DbusmenuGtkClientClass {
typedef struct _DbusmenuGtkClient DbusmenuGtkClient;
struct _DbusmenuGtkClient {
DbusmenuClient parent;
+
+ /*< Private >*/
+ DbusmenuGtkClientPrivate * priv;
};
GType dbusmenu_gtkclient_get_type (void);
@@ -79,6 +88,9 @@ DbusmenuGtkClient * dbusmenu_gtkclient_new (gchar * dbus_name, gchar * dbus_obje
GtkMenuItem * dbusmenu_gtkclient_menuitem_get (DbusmenuGtkClient * client, DbusmenuMenuitem * item);
GtkMenu * dbusmenu_gtkclient_menuitem_get_submenu (DbusmenuGtkClient * client, DbusmenuMenuitem * item);
+void dbusmenu_gtkclient_set_accel_group (DbusmenuGtkClient * client, GtkAccelGroup * agroup);
+GtkAccelGroup * dbusmenu_gtkclient_get_accel_group (DbusmenuGtkClient * client);
+
void dbusmenu_gtkclient_newitem_base (DbusmenuGtkClient * client, DbusmenuMenuitem * item, GtkMenuItem * gmi, DbusmenuMenuitem * parent);
/**
diff --git a/libdbusmenu-gtk/genericmenuitem.c b/libdbusmenu-gtk/genericmenuitem.c
index b357d82..d507487 100644
--- a/libdbusmenu-gtk/genericmenuitem.c
+++ b/libdbusmenu-gtk/genericmenuitem.c
@@ -174,6 +174,8 @@ get_hpadding (GtkWidget * widget)
static void
set_label (GtkMenuItem * menu_item, const gchar * label)
{
+ if (label == NULL) return;
+
GtkWidget * child = gtk_bin_get_child(GTK_BIN(menu_item));
GtkLabel * labelw = NULL;
gboolean suppress_update = FALSE;
@@ -207,9 +209,10 @@ set_label (GtkMenuItem * menu_item, const gchar * label)
update the one that we already have. */
if (labelw == NULL) {
/* Build it */
- labelw = GTK_LABEL(gtk_label_new(label));
+ labelw = GTK_LABEL(gtk_accel_label_new(label));
gtk_label_set_use_underline(GTK_LABEL(labelw), TRUE);
gtk_misc_set_alignment(GTK_MISC(labelw), 0.0, 0.5);
+ gtk_accel_label_set_accel_widget(GTK_ACCEL_LABEL(labelw), GTK_WIDGET(menu_item));
gtk_widget_show(GTK_WIDGET(labelw));
/* Check to see if it needs to be in the bin for this
diff --git a/libdbusmenu-gtk/menu.c b/libdbusmenu-gtk/menu.c
index 3e6f2dd..9c4f7f8 100644
--- a/libdbusmenu-gtk/menu.c
+++ b/libdbusmenu-gtk/menu.c
@@ -44,7 +44,6 @@ enum {
};
/* Private */
-typedef struct _DbusmenuGtkMenuPrivate DbusmenuGtkMenuPrivate;
struct _DbusmenuGtkMenuPrivate {
DbusmenuGtkClient * client;
@@ -52,8 +51,7 @@ struct _DbusmenuGtkMenuPrivate {
gchar * dbus_name;
};
-#define DBUSMENU_GTKMENU_GET_PRIVATE(o) \
-(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_GTKMENU_TYPE, DbusmenuGtkMenuPrivate))
+#define DBUSMENU_GTKMENU_GET_PRIVATE(o) (DBUSMENU_GTKMENU(o)->priv)
/* Prototypes */
static void dbusmenu_gtkmenu_class_init (DbusmenuGtkMenuClass *klass);
@@ -110,6 +108,8 @@ menu_focus_cb(DbusmenuGtkMenu * menu, gpointer userdata)
static void
dbusmenu_gtkmenu_init (DbusmenuGtkMenu *self)
{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), DBUSMENU_GTKMENU_TYPE, DbusmenuGtkMenuPrivate);
+
DbusmenuGtkMenuPrivate * priv = DBUSMENU_GTKMENU_GET_PRIVATE(self);
priv->client = NULL;
diff --git a/libdbusmenu-gtk/menu.h b/libdbusmenu-gtk/menu.h
index 5147d30..896e466 100644
--- a/libdbusmenu-gtk/menu.h
+++ b/libdbusmenu-gtk/menu.h
@@ -42,6 +42,8 @@ G_BEGIN_DECLS
#define DBUSMENU_IS_GTKMENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DBUSMENU_GTKMENU_TYPE))
#define DBUSMENU_GTKMENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DBUSMENU_GTKMENU_TYPE, DbusmenuGtkMenuClass))
+typedef struct _DbusmenuGtkMenuPrivate DbusmenuGtkMenuPrivate;
+
/**
DbusmenuGtkMenuClass:
@parent_class: #GtkMenuClass
@@ -49,16 +51,20 @@ G_BEGIN_DECLS
@reserved2: Reserved for future use.
@reserved3: Reserved for future use.
@reserved4: Reserved for future use.
+ @reserved5: Reserved for future use.
+ @reserved6: Reserved for future use.
*/
typedef struct _DbusmenuGtkMenuClass DbusmenuGtkMenuClass;
struct _DbusmenuGtkMenuClass {
GtkMenuClass parent_class;
- /* Reserved */
+ /*< Private >*/
void (*reserved1) (void);
void (*reserved2) (void);
void (*reserved3) (void);
void (*reserved4) (void);
+ void (*reserved5) (void);
+ void (*reserved6) (void);
};
/**
@@ -68,6 +74,9 @@ struct _DbusmenuGtkMenuClass {
typedef struct _DbusmenuGtkMenu DbusmenuGtkMenu;
struct _DbusmenuGtkMenu {
GtkMenu parent;
+
+ /*< Private >*/
+ DbusmenuGtkMenuPrivate * priv;
};
GType dbusmenu_gtkmenu_get_type (void);
diff --git a/libdbusmenu-gtk/menuitem.c b/libdbusmenu-gtk/menuitem.c
index 23ff311..adf9180 100644
--- a/libdbusmenu-gtk/menuitem.c
+++ b/libdbusmenu-gtk/menuitem.c
@@ -27,6 +27,9 @@ License version 3 and version 2.1 along with this program. If not, see
*/
#include "menuitem.h"
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#include <dbus/dbus-gtype-specialized.h>
/**
dbusmenu_menuitem_property_set_image:
@@ -128,3 +131,248 @@ dbusmenu_menuitem_property_get_image (DbusmenuMenuitem * menuitem, const gchar *
return icon;
}
+/**
+ dbusmenu_menuitem_property_set_shortcut_string:
+ @menuitem: The #DbusmenuMenuitem to set the shortcut on
+ @shortcut: String describing the shortcut
+
+ This function takes a GTK shortcut string as defined in
+ #gtk_accelerator_parse and turns that into the information
+ required to send it over DBusmenu.
+
+ Return value: Whether it was successful at setting the property.
+*/
+gboolean
+dbusmenu_menuitem_property_set_shortcut_string (DbusmenuMenuitem * menuitem, const gchar * shortcut)
+{
+ g_return_val_if_fail(DBUSMENU_IS_MENUITEM(menuitem), FALSE);
+ g_return_val_if_fail(shortcut != NULL, FALSE);
+
+ guint key = 0;
+ GdkModifierType modifier = 0;
+
+ gtk_accelerator_parse(shortcut, &key, &modifier);
+
+ if (key == 0) {
+ g_warning("Unable to parse shortcut string '%s'", shortcut);
+ return FALSE;
+ }
+
+ return dbusmenu_menuitem_property_set_shortcut(menuitem, key, modifier);
+}
+
+/**
+ dbusmenu_menuitem_property_set_shortcut:
+ @menuitem: The #DbusmenuMenuitem to set the shortcut on
+ @key: The keycode of the key to send
+ @modifier: A bitmask of modifiers used to activate the item
+
+ Takes the modifer described by @key and @modifier and places that into
+ the format sending across Dbus for shortcuts.
+
+ Return value: Whether it was successful at setting the property.
+*/
+gboolean
+dbusmenu_menuitem_property_set_shortcut (DbusmenuMenuitem * menuitem, guint key, GdkModifierType modifier)
+{
+ g_return_val_if_fail(DBUSMENU_IS_MENUITEM(menuitem), FALSE);
+ g_return_val_if_fail(gtk_accelerator_valid(key, modifier), FALSE);
+
+ GArray * array = g_array_sized_new(TRUE, TRUE, sizeof(gchar *), 4); /* Four seems like the max we'd need, plus it's still small */
+
+ const gchar * control_val = DBUSMENU_MENUITEM_SHORTCUT_CONTROL;
+ const gchar * alt_val = DBUSMENU_MENUITEM_SHORTCUT_ALT;
+ const gchar * shift_val = DBUSMENU_MENUITEM_SHORTCUT_SHIFT;
+ const gchar * super_val = DBUSMENU_MENUITEM_SHORTCUT_SUPER;
+
+ if (modifier & GDK_CONTROL_MASK) {
+ g_array_append_val(array, control_val);
+ }
+ if (modifier & GDK_MOD1_MASK) {
+ g_array_append_val(array, alt_val);
+ }
+ if (modifier & GDK_SHIFT_MASK) {
+ g_array_append_val(array, shift_val);
+ }
+ if (modifier & GDK_SUPER_MASK) {
+ g_array_append_val(array, super_val);
+ }
+
+ const gchar * keyname = gdk_keyval_name(key);
+ g_array_append_val(array, keyname);
+
+ GType type = dbus_g_type_get_collection("GPtrArray", G_TYPE_STRV);
+ GPtrArray * wrapper = (GPtrArray *)dbus_g_type_specialized_construct(type);
+
+ GValue value = {0,};
+ g_value_init(&value, type);
+ g_value_take_boxed(&value, wrapper);
+
+ DBusGTypeSpecializedAppendContext ctx;
+ dbus_g_type_specialized_init_append(&value, &ctx);
+
+ GValue strval = {0,};
+ g_value_init(&strval, G_TYPE_STRV);
+ g_value_take_boxed(&strval, array->data);
+ g_array_free(array, FALSE);
+
+ dbus_g_type_specialized_collection_append(&ctx, &strval);
+ dbus_g_type_specialized_collection_end_append(&ctx);
+
+ dbusmenu_menuitem_property_set_value(menuitem, DBUSMENU_MENUITEM_PROP_SHORTCUT, &value);
+
+ return TRUE;
+}
+
+/* Look at the closures in an accel group and find
+ the one that matches the one we've been passed */
+static gboolean
+find_closure (GtkAccelKey * key, GClosure * closure, gpointer user_data)
+{
+ return closure == user_data;
+}
+
+/**
+ dbusmenu_menuitem_property_set_shortcut_menuitem:
+ @menuitem: The #DbusmenuMenuitem to set the shortcut on
+ @gmi: A menu item to steal the shortcut off of
+
+ Takes the shortcut that is installed on a menu item and calls
+ #dbusmenu_menuitem_property_set_shortcut with it. It also sets
+ up listeners to watch it change.
+
+ Return value: Whether it was successful at setting the property.
+*/
+gboolean
+dbusmenu_menuitem_property_set_shortcut_menuitem (DbusmenuMenuitem * menuitem, const GtkMenuItem * gmi)
+{
+ g_return_val_if_fail(DBUSMENU_IS_MENUITEM(menuitem), FALSE);
+ g_return_val_if_fail(GTK_IS_MENU_ITEM(gmi), FALSE);
+
+ GClosure * closure = NULL;
+ GtkWidget *label = gtk_bin_get_child(GTK_BIN (gmi));
+
+ if (GTK_IS_ACCEL_LABEL (label))
+ {
+ g_object_get (label,
+ "accel-closure", &closure,
+ NULL);
+ }
+
+ if (closure == NULL)
+ return FALSE;
+
+ GtkAccelGroup * group = gtk_accel_group_from_accel_closure(closure);
+
+ /* Apparently this is more common than I thought. */
+ if (group == NULL) {
+ return FALSE;
+ }
+
+ GtkAccelKey * key = gtk_accel_group_find(group, find_closure, closure);
+ /* Again, not much we can do except complain loudly. */
+ g_return_val_if_fail(key != NULL, FALSE);
+
+ if (!gtk_accelerator_valid (key->accel_key, key->accel_mods))
+ return FALSE;
+
+ return dbusmenu_menuitem_property_set_shortcut(menuitem, key->accel_key, key->accel_mods);
+}
+
+/* A set of typed data for the interator */
+typedef struct _iter_data_t iter_data_t;
+struct _iter_data_t {
+ guint * key;
+ GdkModifierType * modifier;
+};
+
+/* Goes through the wrapper items. In reality we only support one
+ so it checks to see if a key is set first. But, we could possibly,
+ support more in the future. */
+static void
+_wrapper_iterator (const GValue * value, gpointer user_data)
+{
+ iter_data_t * iter_data = (iter_data_t *)user_data;
+
+ if (*iter_data->key != 0) {
+ g_warning("Shortcut is more than one entry. Which we don't currently support. Taking the first.");
+ return;
+ }
+
+ if (!G_VALUE_HOLDS(value, G_TYPE_STRV)) {
+ g_warning("Unexpected shortcut structure. Value array is: %s", G_VALUE_TYPE_NAME(value));
+ return;
+ }
+
+ gchar ** stringarray = (gchar **)g_value_get_boxed(value);
+ if (stringarray == NULL) {
+ return;
+ }
+
+ const gchar * last_string = NULL;
+ int i;
+
+ for (i = 0; stringarray[i] != NULL; i++) {
+ last_string = stringarray[i];
+
+ if (g_strcmp0(last_string, DBUSMENU_MENUITEM_SHORTCUT_CONTROL) == 0) {
+ *iter_data->modifier |= GDK_CONTROL_MASK;
+ continue;
+ }
+ if (g_strcmp0(last_string, DBUSMENU_MENUITEM_SHORTCUT_ALT) == 0) {
+ *iter_data->modifier |= GDK_MOD1_MASK;
+ continue;
+ }
+ if (g_strcmp0(last_string, DBUSMENU_MENUITEM_SHORTCUT_SHIFT) == 0) {
+ *iter_data->modifier |= GDK_SHIFT_MASK;
+ continue;
+ }
+ if (g_strcmp0(last_string, DBUSMENU_MENUITEM_SHORTCUT_SUPER) == 0) {
+ *iter_data->modifier |= GDK_SUPER_MASK;
+ continue;
+ }
+ }
+
+ if (last_string != NULL) {
+ GdkModifierType tempmod;
+ gtk_accelerator_parse(last_string, iter_data->key, &tempmod);
+ }
+
+ return;
+}
+
+/**
+ dbusmenu_menuitem_property_get_shortcut:
+ @menuitem: The #DbusmenuMenuitem to get the shortcut off
+ @key: Location to put the key value
+ @modifier: Location to put the modifier mask
+
+ This function gets a GTK shortcut as a key and a mask
+ for use to set the accelerators.
+*/
+void
+dbusmenu_menuitem_property_get_shortcut (DbusmenuMenuitem * menuitem, guint * key, GdkModifierType * modifier)
+{
+ *key = 0;
+ *modifier = 0;
+
+ g_return_if_fail(DBUSMENU_IS_MENUITEM(menuitem));
+
+ const GValue * wrapper = dbusmenu_menuitem_property_get_value(menuitem, DBUSMENU_MENUITEM_PROP_SHORTCUT);
+ if (wrapper == NULL) {
+ return;
+ }
+ if (!dbus_g_type_is_collection(G_VALUE_TYPE(wrapper))) {
+ g_warning("Unexpected shortcut structure. Wrapper is: %s", G_VALUE_TYPE_NAME(wrapper));
+ return;
+ }
+
+ iter_data_t iter_data;
+ iter_data.key = key;
+ iter_data.modifier = modifier;
+
+ dbus_g_type_collection_value_iterate(wrapper, _wrapper_iterator, &iter_data);
+
+ return;
+}
+
diff --git a/libdbusmenu-gtk/menuitem.h b/libdbusmenu-gtk/menuitem.h
index ff458de..6960f76 100644
--- a/libdbusmenu-gtk/menuitem.h
+++ b/libdbusmenu-gtk/menuitem.h
@@ -32,8 +32,15 @@ License version 3 and version 2.1 along with this program. If not, see
#include <glib.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <libdbusmenu-glib/menuitem.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
gboolean dbusmenu_menuitem_property_set_image (DbusmenuMenuitem * menuitem, const gchar * property, const GdkPixbuf * data);
GdkPixbuf * dbusmenu_menuitem_property_get_image (DbusmenuMenuitem * menuitem, const gchar * property);
+gboolean dbusmenu_menuitem_property_set_shortcut (DbusmenuMenuitem * menuitem, guint key, GdkModifierType modifier);
+gboolean dbusmenu_menuitem_property_set_shortcut_string (DbusmenuMenuitem * menuitem, const gchar * shortcut);
+gboolean dbusmenu_menuitem_property_set_shortcut_menuitem (DbusmenuMenuitem * menuitem, const GtkMenuItem * gmi);
+void dbusmenu_menuitem_property_get_shortcut (DbusmenuMenuitem * menuitem, guint * key, GdkModifierType * modifiers);
+
#endif