aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.bzrignore4
-rw-r--r--libdbusmenu-glib/client.c7
-rw-r--r--libdbusmenu-glib/menuitem.c10
-rw-r--r--libdbusmenu-glib/server.c11
-rw-r--r--libdbusmenu-gtk/Makefile.am3
-rw-r--r--libdbusmenu-gtk/client.c28
-rw-r--r--libdbusmenu-gtk/parser.c667
-rw-r--r--libdbusmenu-gtk/parser.h37
-rw-r--r--tests/Makefile.am35
-rw-r--r--tests/test-gtk-parser.c115
10 files changed, 907 insertions, 10 deletions
diff --git a/.bzrignore b/.bzrignore
index c7cd2fc..8372b50 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -220,6 +220,10 @@ libdbusmenu-gtk/DbusmenuGtk-0.4.typelib
libdbusmenu-gtk/DbusmenuGtk-0.4.vapi
libdbusmenu-gtk/dbusmenu-gtk-0.4.pc
libdbusmenu-gtk/dbusmenu-gtk3-0.4.pc
+libdbusmenu-gtk/libdbusmenu_gtk_la-parser.lo
+test-gtk-parser
+test-gtk-parser-test
+test-gtk-parser.xml
libdbusmenu-gtk/libdbusmenu_gtk_la-serializablemenuitem.lo
docs/libdbusmenu-gtk/reference/html/DbusmenuGtkSerializableMenuItem.html
docs/libdbusmenu-gtk/reference/tmpl/serializablemenuitem.sgml
diff --git a/libdbusmenu-glib/client.c b/libdbusmenu-glib/client.c
index ea0f0f7..b196c9f 100644
--- a/libdbusmenu-glib/client.c
+++ b/libdbusmenu-glib/client.c
@@ -527,6 +527,7 @@ get_properties_callback (GObject *obj, GAsyncResult * res, gpointer user_data)
listener->callback(NULL, error, listener->user_data);
}
g_array_free(listeners, TRUE);
+ g_error_free(error);
return;
}
@@ -1150,7 +1151,6 @@ menuitem_get_properties_new_cb (GVariant * properties, GError * error, gpointer
if (error != NULL) {
g_warning("Error getting properties on a new menuitem: %s", error->message);
g_object_unref(propdata->item);
- g_free(data);
return;
}
@@ -1287,6 +1287,8 @@ about_to_show_cb (GObject * proxy, GAsyncResult * res, gpointer userdata)
g_warning("Unable to send about_to_show: %s", error->message);
/* Note: we're just ensuring only the callback gets called */
need_update = FALSE;
+ g_error_free(error);
+ error = NULL;
} else {
g_variant_get(params, "(b)", &need_update);
g_variant_unref(params);
@@ -1451,7 +1453,7 @@ parse_layout_xml(DbusmenuClient * client, xmlNodePtr node, DbusmenuMenuitem * it
/* We've got everything built up at this node and reconcilled */
/* Flush the properties requests if this is the first level */
- if (dbusmenu_menuitem_get_id(parent) == 0) {
+ if (parent != NULL && dbusmenu_menuitem_get_id(parent) == 0) {
get_properties_flush(client);
}
@@ -1563,6 +1565,7 @@ update_layout_cb (GObject * proxy, GAsyncResult * res, gpointer data)
if (error != NULL) {
g_warning("Getting layout failed: %s", error->message);
+ g_error_free(error);
return;
}
diff --git a/libdbusmenu-glib/menuitem.c b/libdbusmenu-glib/menuitem.c
index 827d6c5..b40195c 100644
--- a/libdbusmenu-glib/menuitem.c
+++ b/libdbusmenu-glib/menuitem.c
@@ -1204,11 +1204,17 @@ dbusmenu_menuitem_properties_list (DbusmenuMenuitem * mi)
return g_hash_table_get_keys(priv->properties);
}
+/* Copy the keys and make references to the variants that are
+ in the new table. They'll be free'd and unref'd when the
+ Hashtable gets destroyed. */
static void
copy_helper (gpointer in_key, gpointer in_value, gpointer in_data)
{
GHashTable * table = (GHashTable *)in_data;
- g_hash_table_insert(table, in_key, in_value);
+ gchar * key = (gchar *)in_key;
+ GVariant * value = (GVariant *)in_value;
+ g_variant_ref(value);
+ g_hash_table_insert(table, g_strdup(key), value);
return;
}
@@ -1229,7 +1235,7 @@ copy_helper (gpointer in_key, gpointer in_value, gpointer in_data)
GHashTable *
dbusmenu_menuitem_properties_copy (DbusmenuMenuitem * mi)
{
- GHashTable * ret = g_hash_table_new(g_str_hash, g_str_equal);
+ GHashTable * ret = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, _g_variant_unref);
g_return_val_if_fail(DBUSMENU_IS_MENUITEM(mi), ret);
diff --git a/libdbusmenu-glib/server.c b/libdbusmenu-glib/server.c
index 095f333..adb9f91 100644
--- a/libdbusmenu-glib/server.c
+++ b/libdbusmenu-glib/server.c
@@ -975,11 +975,17 @@ serialize_menuitem(gpointer data, gpointer user_data)
{
DbusmenuMenuitem * mi = DBUSMENU_MENUITEM(data);
GVariantBuilder * builder = (GVariantBuilder *)(user_data);
+ GVariantBuilder tuple;
+
+ g_variant_builder_init(&tuple, G_VARIANT_TYPE_TUPLE);
gint id = dbusmenu_menuitem_get_id(mi);
+ g_variant_builder_add_value(&tuple, g_variant_new_int32(id));
+
GVariant * props = dbusmenu_menuitem_properties_variant(mi);
+ g_variant_builder_add_value(&tuple, props);
- g_variant_builder_add(builder, "ia{sv}", id, props);
+ g_variant_builder_add_value(builder, g_variant_builder_end(&tuple));
return;
}
@@ -1020,7 +1026,8 @@ bus_get_children (DbusmenuServer * server, GVariant * params, GDBusMethodInvocat
g_list_foreach(children, serialize_menuitem, &builder);
- ret = g_variant_new("(a(ia{svg}))", g_variant_builder_end(&builder));
+ GVariant * end = g_variant_builder_end(&builder);
+ ret = g_variant_new_tuple(&end, 1);
} else {
GError * error = NULL;
ret = g_variant_parse(g_variant_type_new("(a(ia{sv}))"), "([(0, {})],)", NULL, NULL, &error);
diff --git a/libdbusmenu-gtk/Makefile.am b/libdbusmenu-gtk/Makefile.am
index 754ed3e..f3556e9 100644
--- a/libdbusmenu-gtk/Makefile.am
+++ b/libdbusmenu-gtk/Makefile.am
@@ -24,6 +24,7 @@ libdbusmenu_gtkinclude_HEADERS = \
client.h \
menu.h \
menuitem.h \
+ parser.h \
serializablemenuitem.h
libdbusmenu_gtk_la_SOURCES = \
@@ -35,6 +36,8 @@ libdbusmenu_gtk_la_SOURCES = \
menu.c \
menuitem.h \
menuitem.c \
+ parser.h \
+ parser.c \
serializablemenuitem.h \
serializablemenuitem.c
diff --git a/libdbusmenu-gtk/client.c b/libdbusmenu-gtk/client.c
index d957f25..7ab2fe9 100644
--- a/libdbusmenu-gtk/client.c
+++ b/libdbusmenu-gtk/client.c
@@ -42,6 +42,7 @@ struct _DbusmenuGtkClientPrivate {
};
#define DBUSMENU_GTKCLIENT_GET_PRIVATE(o) (DBUSMENU_GTKCLIENT(o)->priv)
+#define USE_FALLBACK_PROP "use-fallback"
/* Prototypes */
static void dbusmenu_gtkclient_class_init (DbusmenuGtkClientClass *klass);
@@ -737,6 +738,29 @@ new_item_seperator (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, Dbusm
return TRUE;
}
+/* A little helper so we don't generate a bunch of warnings
+ about being able to set use-fallback */
+static void
+set_use_fallback (GtkWidget * widget)
+{
+ static gboolean checked = FALSE;
+ static gboolean available = FALSE;
+
+ if (!checked) {
+ available = (g_object_class_find_property(G_OBJECT_CLASS(GTK_IMAGE_GET_CLASS(widget)), USE_FALLBACK_PROP) != NULL);
+ if (!available) {
+ g_warning("The '" USE_FALLBACK_PROP "' is not available on GtkImage so icons may not show correctly.");
+ }
+ checked = TRUE;
+ }
+
+ if (available) {
+ g_object_set(G_OBJECT(widget), USE_FALLBACK_PROP, TRUE, NULL);
+ }
+
+ return;
+}
+
/* This handler looks at property changes for items that are
image menu items. */
static void
@@ -789,7 +813,7 @@ image_property_handle (DbusmenuMenuitem * item, const gchar * property, GVariant
gtkimage = NULL;
} else if (g_strcmp0(iconname, DBUSMENU_MENUITEM_ICON_NAME_BLANK) == 0) {
gtkimage = gtk_image_new();
- g_object_set(G_OBJECT(gtkimage), "use-fallback", TRUE, NULL);
+ set_use_fallback(gtkimage);
} else {
/* Look to see if we want to have an icon with the 'ltr' or
'rtl' depending on what we're doing. */
@@ -808,7 +832,7 @@ image_property_handle (DbusmenuMenuitem * item, const gchar * property, GVariant
can just convert it to this name. */
if (gtkimage == NULL) {
gtkimage = gtk_image_new_from_icon_name(finaliconname, GTK_ICON_SIZE_MENU);
- g_object_set(G_OBJECT(gtkimage), "use-fallback", TRUE, NULL);
+ set_use_fallback(gtkimage);
} else {
gtk_image_set_from_icon_name(GTK_IMAGE(gtkimage), finaliconname, GTK_ICON_SIZE_MENU);
}
diff --git a/libdbusmenu-gtk/parser.c b/libdbusmenu-gtk/parser.c
new file mode 100644
index 0000000..ead653a
--- /dev/null
+++ b/libdbusmenu-gtk/parser.c
@@ -0,0 +1,667 @@
+/*
+Parse to take a set of GTK Menus and turn them into something that can
+be sent over the wire.
+
+Copyright 2011 Canonical Ltd.
+
+Authors:
+ Numerous (check Bazaar)
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of either or both of the following licenses:
+
+1) the GNU Lesser General Public License version 3, as published by the
+Free Software Foundation; and/or
+2) the GNU Lesser General Public License version 2.1, as published by
+the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranties of
+MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
+PURPOSE. See the applicable version of the GNU Lesser General Public
+License for more details.
+
+You should have received a copy of both the GNU Lesser General Public
+License version 3 and version 2.1 along with this program. If not, see
+<http://www.gnu.org/licenses/>
+*/
+
+#include "parser.h"
+#include "menuitem.h"
+
+#define CACHED_MENUITEM "dbusmenu-gtk-parser-cached-item"
+
+typedef struct _RecurseContext
+{
+ GtkWidget * toplevel;
+ DbusmenuMenuitem * parent;
+} RecurseContext;
+
+static void parse_menu_structure_helper (GtkWidget * widget, RecurseContext * recurse);
+static DbusmenuMenuitem * construct_dbusmenu_for_widget (GtkWidget * widget);
+static void accel_changed (GtkWidget * widget,
+ gpointer data);
+static gboolean update_stock_item (DbusmenuMenuitem * menuitem,
+ GtkWidget * widget);
+static void checkbox_toggled (GtkWidget * widget,
+ DbusmenuMenuitem * mi);
+static void update_icon_name (DbusmenuMenuitem * menuitem,
+ GtkWidget * widget);
+static GtkWidget * find_menu_label (GtkWidget * widget);
+static void label_notify_cb (GtkWidget * widget,
+ GParamSpec * pspec,
+ gpointer data);
+static void action_notify_cb (GtkAction * action,
+ GParamSpec * pspec,
+ gpointer data);
+static void item_activated (DbusmenuMenuitem * item,
+ guint timestamp,
+ gpointer user_data);
+static gboolean item_about_to_show (DbusmenuMenuitem * item,
+ gpointer user_data);
+static void widget_notify_cb (GtkWidget * widget,
+ GParamSpec * pspec,
+ gpointer data);
+static gboolean should_show_image (GtkImage * image);
+static void menuitem_notify_cb (GtkWidget * widget,
+ GParamSpec * pspec,
+ gpointer data);
+
+/**
+ dbusmenu_gtk_parse_menu_structure:
+ @widget: A #GtkMenuItem or #GtkMenuShell to turn into a #DbusmenuMenuitem
+
+ Goes through the GTK structures and turns them into the appropraite
+ Dbusmenu structures along with setting up all the relationships
+ between the objects. It also stores the dbusmenu items as a cache
+ on the GTK items so that they'll be reused if necissary.
+
+ Return value: A dbusmenu item representing the menu structure
+*/
+DbusmenuMenuitem *
+dbusmenu_gtk_parse_menu_structure (GtkWidget * widget)
+{
+ g_return_val_if_fail(GTK_IS_MENU_ITEM(widget) || GTK_IS_MENU_SHELL(widget), NULL);
+
+ RecurseContext recurse = {0};
+
+ recurse.toplevel = gtk_widget_get_toplevel(widget);
+
+ parse_menu_structure_helper(widget, &recurse);
+
+ return recurse.parent;
+}
+
+/* Called when the dbusmenu item that we're keeping around
+ is finalized */
+static void
+dbusmenu_cache_freed (gpointer data, GObject * obj)
+{
+ /* If the dbusmenu item is killed we don't need to remove
+ the weak ref as well. */
+ g_object_steal_data(G_OBJECT(data), CACHED_MENUITEM);
+ g_signal_handlers_disconnect_by_func(data, G_CALLBACK(widget_notify_cb), obj);
+ return;
+}
+
+/* Called if we replace the cache on the object with a new
+ dbusmenu menuitem */
+static void
+object_cache_freed (gpointer data)
+{
+ if (!G_IS_OBJECT(data)) return;
+ g_object_weak_unref(G_OBJECT(data), dbusmenu_cache_freed, data);
+ return;
+}
+
+static void
+parse_menu_structure_helper (GtkWidget * widget, RecurseContext * recurse)
+{
+
+ /* If this is a shell, then let's handle the items in it. */
+ if (GTK_IS_MENU_SHELL (widget)) {
+ /* Okay, this is a little janky and all.. but some applications update some
+ * menuitem properties such as sensitivity on the activate callback. This
+ * seems a little weird, but it's not our place to judge when all this code
+ * is so crazy. So we're going to get ever crazier and activate all the
+ * menus that are directly below the menubar and force the applications to
+ * update their sensitivity. The menus won't actually popup in the app
+ * window due to our gtk+ patches.
+ *
+ * Note that this will not force menuitems in submenus to be updated as well.
+ */
+ if (recurse->parent == NULL && GTK_IS_MENU_BAR(widget)) {
+ GList *children = gtk_container_get_children (GTK_CONTAINER (widget));
+
+ for (; children != NULL; children = children->next) {
+ gtk_menu_shell_activate_item (GTK_MENU_SHELL (widget),
+ children->data,
+ TRUE);
+ }
+
+ g_list_free (children);
+ }
+
+ if (recurse->parent == NULL) {
+ recurse->parent = dbusmenu_menuitem_new();
+ }
+
+ gtk_container_foreach (GTK_CONTAINER (widget),
+ (GtkCallback)parse_menu_structure_helper,
+ recurse);
+ return;
+ }
+
+ if (GTK_IS_MENU_ITEM(widget)) {
+ DbusmenuMenuitem * thisitem = NULL;
+
+ /* Check to see if we're cached already */
+ gpointer pmi = g_object_get_data(G_OBJECT(widget), CACHED_MENUITEM);
+ if (pmi != NULL) {
+ thisitem = DBUSMENU_MENUITEM(pmi);
+ g_object_ref(G_OBJECT(thisitem));
+ }
+
+ /* We don't have one, so we'll need to build it */
+ if (thisitem == NULL) {
+ thisitem = construct_dbusmenu_for_widget (widget);
+ g_object_set_data_full(G_OBJECT(widget), CACHED_MENUITEM, thisitem, object_cache_freed);
+ g_object_weak_ref(G_OBJECT(thisitem), dbusmenu_cache_freed, widget);
+
+ if (!gtk_widget_get_visible (widget)) {
+ g_signal_connect (G_OBJECT (widget),
+ "notify::visible",
+ G_CALLBACK (menuitem_notify_cb),
+ recurse->toplevel);
+ }
+
+ if (GTK_IS_TEAROFF_MENU_ITEM (widget)) {
+ dbusmenu_menuitem_property_set_bool (thisitem,
+ DBUSMENU_MENUITEM_PROP_VISIBLE,
+ FALSE);
+ }
+ }
+
+ /* Check to see if we're in our parents list of children, if we have
+ a parent. */
+ if (recurse->parent != NULL) {
+ GList * children = dbusmenu_menuitem_get_children (recurse->parent);
+ GList * peek = NULL;
+
+ if (children != NULL) {
+ peek = g_list_find (children, thisitem);
+ }
+
+ /* Oops, let's tell our parents about us */
+ if (peek == NULL) {
+ /* TODO: Should we set a weak ref on the parent? */
+ g_object_set_data (G_OBJECT (thisitem),
+ "dbusmenu-parent",
+ recurse->parent);
+ dbusmenu_menuitem_child_append (recurse->parent,
+ thisitem);
+ }
+ }
+
+ GtkWidget *menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
+ if (menu != NULL) {
+ DbusmenuMenuitem * parent_save = recurse->parent;
+ recurse->parent = thisitem;
+ parse_menu_structure_helper (menu, recurse);
+ recurse->parent = parent_save;
+ }
+
+ if (recurse->parent == NULL) {
+ recurse->parent = thisitem;
+ } else {
+ g_object_unref(thisitem);
+ }
+ }
+
+ return;
+}
+
+static DbusmenuMenuitem *
+construct_dbusmenu_for_widget (GtkWidget * widget)
+{
+ DbusmenuMenuitem *mi = dbusmenu_menuitem_new ();
+
+ if (GTK_IS_MENU_ITEM (widget))
+ {
+ gboolean visible = FALSE;
+ gboolean sensitive = FALSE;
+ if (GTK_IS_SEPARATOR_MENU_ITEM (widget))
+ {
+ dbusmenu_menuitem_property_set (mi,
+ "type",
+ "separator");
+
+ visible = gtk_widget_get_visible (widget);
+ sensitive = gtk_widget_get_sensitive (widget);
+ }
+ else
+ {
+ gboolean label_set = FALSE;
+
+ g_signal_connect (widget,
+ "accel-closures-changed",
+ G_CALLBACK (accel_changed),
+ mi);
+
+ if (GTK_IS_CHECK_MENU_ITEM (widget))
+ {
+ dbusmenu_menuitem_property_set (mi,
+ DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE,
+ gtk_check_menu_item_get_draw_as_radio (GTK_CHECK_MENU_ITEM (widget)) ? DBUSMENU_MENUITEM_TOGGLE_RADIO : DBUSMENU_MENUITEM_TOGGLE_CHECK);
+
+ dbusmenu_menuitem_property_set_int (mi,
+ DBUSMENU_MENUITEM_PROP_TOGGLE_STATE,
+ gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)) ? DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED : DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED);
+
+ g_signal_connect (widget,
+ "activate",
+ G_CALLBACK (checkbox_toggled),
+ mi);
+ }
+
+ if (GTK_IS_IMAGE_MENU_ITEM (widget))
+ {
+ GtkWidget *image;
+ GtkImageType image_type;
+
+ image = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (widget));
+
+ if (GTK_IS_IMAGE (image))
+ {
+ image_type = gtk_image_get_storage_type (GTK_IMAGE (image));
+
+ if (image_type == GTK_IMAGE_STOCK)
+ {
+ label_set = update_stock_item (mi, image);
+ }
+ else if (image_type == GTK_IMAGE_ICON_NAME)
+ {
+ update_icon_name (mi, image);
+ }
+ else if (image_type == GTK_IMAGE_PIXBUF)
+ {
+ dbusmenu_menuitem_property_set_image (mi,
+ DBUSMENU_MENUITEM_PROP_ICON_DATA,
+ gtk_image_get_pixbuf (GTK_IMAGE (image)));
+ }
+ }
+ }
+
+ GtkWidget *label = find_menu_label (widget);
+
+ dbusmenu_menuitem_property_set (mi,
+ "label",
+ label ? gtk_label_get_text (GTK_LABEL (label)) : NULL);
+
+ if (label)
+ {
+ // Sometimes, an app will directly find and modify the label
+ // (like empathy), so watch the label especially for that.
+ g_signal_connect (G_OBJECT (label),
+ "notify",
+ G_CALLBACK (label_notify_cb),
+ mi);
+ }
+
+ if (GTK_IS_ACTIVATABLE (widget))
+ {
+ GtkActivatable *activatable = GTK_ACTIVATABLE (widget);
+
+ if (gtk_activatable_get_use_action_appearance (activatable))
+ {
+ GtkAction *action = gtk_activatable_get_related_action (activatable);
+
+ if (action)
+ {
+ visible = gtk_action_is_visible (action);
+ sensitive = gtk_action_is_sensitive (action);
+
+ g_signal_connect_object (action, "notify",
+ G_CALLBACK (action_notify_cb),
+ mi,
+ G_CONNECT_AFTER);
+ }
+ }
+ }
+
+ if (!g_object_get_data (G_OBJECT (widget), "gtk-empty-menu-item") && !GTK_IS_TEAROFF_MENU_ITEM (widget))
+ {
+ visible = gtk_widget_get_visible (widget);
+ sensitive = gtk_widget_get_sensitive (widget);
+ }
+
+ dbusmenu_menuitem_property_set_shortcut_menuitem (mi, GTK_MENU_ITEM (widget));
+
+ g_signal_connect (G_OBJECT (mi),
+ DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
+ G_CALLBACK (item_activated),
+ widget);
+
+ g_signal_connect (G_OBJECT (mi),
+ DBUSMENU_MENUITEM_SIGNAL_ABOUT_TO_SHOW,
+ G_CALLBACK (item_about_to_show),
+ widget);
+ }
+
+ dbusmenu_menuitem_property_set_bool (mi,
+ DBUSMENU_MENUITEM_PROP_VISIBLE,
+ visible);
+
+ dbusmenu_menuitem_property_set_bool (mi,
+ DBUSMENU_MENUITEM_PROP_ENABLED,
+ sensitive);
+
+ g_signal_connect (widget,
+ "notify",
+ G_CALLBACK (widget_notify_cb),
+ mi);
+ }
+
+ return mi;
+
+ return NULL;
+}
+
+static void
+menuitem_notify_cb (GtkWidget *widget,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ if (pspec->name == g_intern_static_string ("visible"))
+ {
+ GtkWidget * new_toplevel = gtk_widget_get_toplevel (widget);
+ GtkWidget * old_toplevel = GTK_WIDGET(data);
+
+ if (new_toplevel == old_toplevel) {
+ /* TODO: Figure this out -> rebuild (context->bridge, window); */
+ }
+
+ /* We only care about this once, so let's disconnect now. */
+ g_signal_handlers_disconnect_by_func (widget,
+ G_CALLBACK (menuitem_notify_cb),
+ data);
+ }
+}
+
+static void
+accel_changed (GtkWidget *widget,
+ gpointer data)
+{
+ DbusmenuMenuitem *mi = (DbusmenuMenuitem *)data;
+ dbusmenu_menuitem_property_set_shortcut_menuitem (mi, GTK_MENU_ITEM (widget));
+}
+
+static gboolean
+update_stock_item (DbusmenuMenuitem *menuitem,
+ GtkWidget *widget)
+{
+ GtkStockItem stock;
+ GtkImage *image;
+
+ g_return_val_if_fail (GTK_IS_IMAGE (widget), FALSE);
+
+ image = GTK_IMAGE (widget);
+
+ if (gtk_image_get_storage_type (image) != GTK_IMAGE_STOCK)
+ return FALSE;
+
+ gchar * stock_id = NULL;
+ gtk_image_get_stock(image, &stock_id, NULL);
+
+ gtk_stock_lookup (stock_id, &stock);
+
+ if (should_show_image (image))
+ dbusmenu_menuitem_property_set (menuitem,
+ DBUSMENU_MENUITEM_PROP_ICON_NAME,
+ stock_id);
+ else
+ dbusmenu_menuitem_property_remove (menuitem,
+ DBUSMENU_MENUITEM_PROP_ICON_NAME);
+
+ const gchar *label = dbusmenu_menuitem_property_get (menuitem,
+ DBUSMENU_MENUITEM_PROP_LABEL);
+
+ if (stock.label != NULL && label != NULL)
+ {
+ dbusmenu_menuitem_property_set (menuitem,
+ DBUSMENU_MENUITEM_PROP_LABEL,
+ stock.label);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+checkbox_toggled (GtkWidget *widget, DbusmenuMenuitem *mi)
+{
+ dbusmenu_menuitem_property_set_int (mi,
+ DBUSMENU_MENUITEM_PROP_TOGGLE_STATE,
+ gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)) ? DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED : DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED);
+}
+
+static void
+update_icon_name (DbusmenuMenuitem *menuitem,
+ GtkWidget *widget)
+{
+ GtkImage *image;
+
+ g_return_if_fail (GTK_IS_IMAGE (widget));
+
+ image = GTK_IMAGE (widget);
+
+ if (gtk_image_get_storage_type (image) != GTK_IMAGE_ICON_NAME)
+ return;
+
+ if (should_show_image (image)) {
+ const gchar * icon_name = NULL;
+ gtk_image_get_icon_name(image, &icon_name, NULL);
+ dbusmenu_menuitem_property_set (menuitem,
+ DBUSMENU_MENUITEM_PROP_ICON_NAME,
+ icon_name);
+ } else {
+ dbusmenu_menuitem_property_remove (menuitem,
+ DBUSMENU_MENUITEM_PROP_ICON_NAME);
+ }
+}
+
+static GtkWidget *
+find_menu_label (GtkWidget *widget)
+{
+ GtkWidget *label = NULL;
+
+ if (GTK_IS_LABEL (widget))
+ return widget;
+
+ if (GTK_IS_CONTAINER (widget))
+ {
+ GList *children;
+ GList *l;
+
+ children = gtk_container_get_children (GTK_CONTAINER (widget));
+
+ for (l = children; l; l = l->next)
+ {
+ label = find_menu_label (l->data);
+
+ if (label)
+ break;
+ }
+
+ g_list_free (children);
+ }
+
+ return label;
+}
+
+static void
+label_notify_cb (GtkWidget *widget,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ DbusmenuMenuitem *child = (DbusmenuMenuitem *)data;
+
+ if (pspec->name == g_intern_static_string ("label"))
+ {
+ dbusmenu_menuitem_property_set (child,
+ DBUSMENU_MENUITEM_PROP_LABEL,
+ gtk_label_get_text (GTK_LABEL (widget)));
+ }
+}
+
+static void
+action_notify_cb (GtkAction *action,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ DbusmenuMenuitem *mi = (DbusmenuMenuitem *)data;
+
+ if (pspec->name == g_intern_static_string ("sensitive"))
+ {
+ dbusmenu_menuitem_property_set_bool (mi,
+ DBUSMENU_MENUITEM_PROP_ENABLED,
+ gtk_action_is_sensitive (action));
+ }
+ else if (pspec->name == g_intern_static_string ("visible"))
+ {
+ dbusmenu_menuitem_property_set_bool (mi,
+ DBUSMENU_MENUITEM_PROP_VISIBLE,
+ gtk_action_is_visible (action));
+ }
+ else if (pspec->name == g_intern_static_string ("active"))
+ {
+ dbusmenu_menuitem_property_set_int (mi,
+ DBUSMENU_MENUITEM_PROP_TOGGLE_STATE,
+ gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)) ? DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED : DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED);
+ }
+ else if (pspec->name == g_intern_static_string ("label"))
+ {
+ dbusmenu_menuitem_property_set (mi,
+ DBUSMENU_MENUITEM_PROP_LABEL,
+ gtk_action_get_label (action));
+ }
+}
+
+static void
+item_activated (DbusmenuMenuitem *item, guint timestamp, gpointer user_data)
+{
+ GtkWidget *child;
+
+ if (user_data != NULL)
+ {
+ child = (GtkWidget *)user_data;
+
+ if (GTK_IS_MENU_ITEM (child))
+ {
+ gtk_menu_item_activate (GTK_MENU_ITEM (child));
+ }
+ }
+}
+
+static gboolean
+item_about_to_show (DbusmenuMenuitem *item, gpointer user_data)
+{
+ GtkWidget *child;
+
+ if (user_data != NULL)
+ {
+ child = (GtkWidget *)user_data;
+
+ if (GTK_IS_MENU_ITEM (child))
+ {
+ // Only called for items with submens. So we activate it here in
+ // case the program dynamically creates menus (like empathy does)
+ gtk_menu_item_activate (GTK_MENU_ITEM (child));
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+widget_notify_cb (GtkWidget *widget,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ DbusmenuMenuitem *child = (DbusmenuMenuitem *)data;
+
+ if (pspec->name == g_intern_static_string ("sensitive"))
+ {
+ dbusmenu_menuitem_property_set_bool (child,
+ DBUSMENU_MENUITEM_PROP_ENABLED,
+ gtk_widget_get_sensitive (widget));
+ }
+ else if (pspec->name == g_intern_static_string ("label"))
+ {
+ dbusmenu_menuitem_property_set (child,
+ DBUSMENU_MENUITEM_PROP_LABEL,
+ gtk_menu_item_get_label (GTK_MENU_ITEM (widget)));
+ }
+ else if (pspec->name == g_intern_static_string ("visible"))
+ {
+ dbusmenu_menuitem_property_set_bool (child,
+ DBUSMENU_MENUITEM_PROP_VISIBLE,
+ gtk_widget_get_visible (widget));
+ }
+ else if (pspec->name == g_intern_static_string ("stock"))
+ {
+ update_stock_item (child, widget);
+ }
+ else if (pspec->name == g_intern_static_string ("icon-name"))
+ {
+ update_icon_name (child, widget);
+ }
+ else if (pspec->name == g_intern_static_string ("parent"))
+ {
+ /*
+ * We probably should have added a 'remove' method to the
+ * UbuntuMenuProxy early on, but it's late in the cycle now.
+ */
+ if (gtk_widget_get_parent (widget) == NULL)
+ {
+ g_signal_handlers_disconnect_by_func (widget,
+ G_CALLBACK (widget_notify_cb),
+ child);
+
+ DbusmenuMenuitem *parent = g_object_get_data (G_OBJECT (child), "dbusmenu-parent");
+
+ if (DBUSMENU_IS_MENUITEM (parent) && DBUSMENU_IS_MENUITEM (child))
+ {
+ dbusmenu_menuitem_child_delete (parent, child);
+ }
+ }
+ }
+}
+
+static gboolean
+should_show_image (GtkImage *image)
+{
+ GtkWidget *item;
+
+ item = gtk_widget_get_ancestor (GTK_WIDGET (image),
+ GTK_TYPE_IMAGE_MENU_ITEM);
+
+ if (item)
+ {
+ GtkSettings *settings;
+ gboolean gtk_menu_images;
+
+ settings = gtk_widget_get_settings (item);
+
+ g_object_get (settings, "gtk-menu-images", &gtk_menu_images, NULL);
+
+ if (gtk_menu_images)
+ return TRUE;
+
+ return gtk_image_menu_item_get_always_show_image (GTK_IMAGE_MENU_ITEM (item));
+ }
+
+ return FALSE;
+}
+
diff --git a/libdbusmenu-gtk/parser.h b/libdbusmenu-gtk/parser.h
new file mode 100644
index 0000000..a40d709
--- /dev/null
+++ b/libdbusmenu-gtk/parser.h
@@ -0,0 +1,37 @@
+/*
+Parse to take a set of GTK Menus and turn them into something that can
+be sent over the wire.
+
+Copyright 2011 Canonical Ltd.
+
+Authors:
+ Ted Gould <ted@canonical.com>
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of either or both of the following licenses:
+
+1) the GNU Lesser General Public License version 3, as published by the
+Free Software Foundation; and/or
+2) the GNU Lesser General Public License version 2.1, as published by
+the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranties of
+MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
+PURPOSE. See the applicable version of the GNU Lesser General Public
+License for more details.
+
+You should have received a copy of both the GNU Lesser General Public
+License version 3 and version 2.1 along with this program. If not, see
+<http://www.gnu.org/licenses/>
+*/
+
+#ifndef DBUSMENU_GTK_PARSER_H__
+#define DBUSMENU_GTK_PARSER_H__
+
+#include <libdbusmenu-glib/menuitem.h>
+#include <gtk/gtk.h>
+
+DbusmenuMenuitem * dbusmenu_gtk_parse_menu_structure (GtkWidget * widget);
+
+#endif /* DBUSMENU_GTK_PARSER_H__ */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 17b44d1..62142dc 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -16,7 +16,8 @@ TESTS = \
test-gtk-label \
test-gtk-shortcut \
test-gtk-reorder \
- test-gtk-submenu
+ test-gtk-submenu \
+ test-gtk-parser-test
check_PROGRAMS = \
glib-server-nomenu \
@@ -42,7 +43,8 @@ check_PROGRAMS = \
test-json-client \
test-json-server \
test-gtk-submenu-server \
- test-gtk-submenu-client
+ test-gtk-submenu-client \
+ test-gtk-parser
XVFB_RUN=". $(srcdir)/run-xvfb.sh"
@@ -384,6 +386,35 @@ test_gtk_objects_LDADD = \
$(DBUSMENUGLIB_LIBS) \
$(DBUSMENUGTK_LIBS)
+######################
+# Test GTK Parser
+######################
+
+GTK_PARSER_XML_REPORT = test-gtk-parser.xml
+
+test-gtk-parser-test: test-gtk-parser Makefile.am
+ @echo "#!/bin/bash" > $@
+ @echo $(XVFB_RUN) >> $@
+ @echo gtester --verbose -k -o $(GTK_PARSER_XML_REPORT) ./test-gtk-parser >> $@
+ @chmod +x $@
+
+test_gtk_parser_SOURCES = \
+ test-gtk-parser.c
+
+test_gtk_parser_CFLAGS = \
+ -I $(srcdir)/.. \
+ $(DBUSMENUGLIB_CFLAGS) \
+ $(DBUSMENUGTK_CFLAGS) \
+ -DSRCDIR="\"$(srcdir)\"" \
+ -Wall -Werror
+
+test_gtk_parser_LDADD = \
+ ../libdbusmenu-glib/libdbusmenu-glib.la \
+ ../libdbusmenu-gtk/libdbusmenu-gtk.la \
+ $(DBUSMENUGLIB_LIBS) \
+ $(DBUSMENUGTK_LIBS)
+
+
#########################
# Test GTK Label
#########################
diff --git a/tests/test-gtk-parser.c b/tests/test-gtk-parser.c
new file mode 100644
index 0000000..b66b46a
--- /dev/null
+++ b/tests/test-gtk-parser.c
@@ -0,0 +1,115 @@
+/*
+Testing for the various objects just by themselves.
+
+Copyright 2011 Canonical Ltd.
+
+Authors:
+ Ted Gould <ted@canonical.com>
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 3, as published
+by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranties of
+MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <libdbusmenu-glib/menuitem-private.h>
+#include <libdbusmenu-gtk/parser.h>
+
+/* Just makes sure we can connect here people */
+static void
+test_parser_runs (void)
+{
+ GtkWidget * gmi = gtk_menu_item_new_with_label("Test Item");
+ g_assert(gmi != NULL);
+ DbusmenuMenuitem * mi = dbusmenu_gtk_parse_menu_structure(gmi);
+ g_assert(mi != NULL);
+
+ g_object_unref(gmi);
+ g_object_unref(mi);
+
+ return;
+}
+
+const gchar * test_parser_children_builder =
+"<?xml version=\"1.0\"?>"
+"<interface>"
+"<requires lib=\"gtk+\" version=\"2.16\"/>"
+/* Start menu bar */
+"<object class=\"GtkMenuBar\" id=\"menubar\"><property name=\"visible\">True</property>"
+/* Child 1 */
+"<child><object class=\"GtkMenuItem\" id=\"child_one\"><property name=\"visible\">True</property><property name=\"label\">Child One</property></object></child>"
+/* Child 2 */
+"<child><object class=\"GtkMenuItem\" id=\"child_two\"><property name=\"visible\">True</property><property name=\"label\">Child Two</property></object></child>"
+/* Child 3 */
+"<child><object class=\"GtkMenuItem\" id=\"child_three\"><property name=\"visible\">True</property><property name=\"label\">Child Three</property></object></child>"
+/* Child 4 */
+"<child><object class=\"GtkMenuItem\" id=\"child_four\"><property name=\"visible\">True</property><property name=\"label\">Child Four</property></object></child>"
+/* Stop menubar */
+"</object>"
+"</interface>";
+
+/* Ensure the parser can find children */
+static void
+test_parser_children (void) {
+ GtkBuilder * builder = gtk_builder_new();
+ g_assert(builder != NULL);
+
+ GError * error = NULL;
+ gtk_builder_add_from_string(builder, test_parser_children_builder, -1, &error);
+ if (error != NULL) {
+ g_error("Unable to parse UI definition: %s", error->message);
+ g_error_free(error);
+ error = NULL;
+ }
+
+ GtkWidget * menu = GTK_WIDGET(gtk_builder_get_object(builder, "menubar"));
+ g_assert(menu != NULL);
+
+ DbusmenuMenuitem * mi = dbusmenu_gtk_parse_menu_structure(menu);
+ g_assert(mi != NULL);
+
+/*
+ GPtrArray * xmlarray = g_ptr_array_new();
+ dbusmenu_menuitem_buildxml(mi, xmlarray);
+ g_debug("XML: %s", g_strjoinv("", (gchar **)xmlarray->pdata));
+*/
+
+ GList * children = dbusmenu_menuitem_get_children(mi);
+ g_assert(children != NULL);
+
+ g_assert(g_list_length(children) == 4);
+
+ g_object_unref(mi);
+ g_object_unref(menu);
+
+ return;
+}
+
+/* Build the test suite */
+static void
+test_gtk_parser_suite (void)
+{
+ g_test_add_func ("/dbusmenu/gtk/parser/base", test_parser_runs);
+ g_test_add_func ("/dbusmenu/gtk/parser/children", test_parser_children);
+ return;
+}
+
+gint
+main (gint argc, gchar * argv[])
+{
+ gtk_init(&argc, &argv);
+ g_test_init(&argc, &argv, NULL);
+
+ /* Test suites */
+ test_gtk_parser_suite();
+
+
+ return g_test_run ();
+}