← Back to team overview

ayatana-commits team mailing list archive

[Branch ~dbusmenu-team/dbusmenu/trunk] Rev 6: Merging in the branch to add a simple type system to dbusmenu and use it to make a better GTK lib...

 

Merge authors:
  Ted Gould (ted)
Related merge proposals:
  https://code.launchpad.net/~ted/dbusmenu/types/+merge/10772
  proposed by: Ted Gould (ted)
  review: Approve - Neil J. Patel (njpatel)
------------------------------------------------------------
revno: 6 [merge]
tags: 0.0.2
committer: Ted Gould <ted@xxxxxxxxxxxxx>
branch nick: libdbusmenu
timestamp: Thu 2009-08-27 09:42:59 -0500
message:
  Merging in the branch to add a simple type system to dbusmenu and use it to make a better GTK library, including showing and hiding items.
modified:
  configure.ac
  libdbusmenu-glib/client.c
  libdbusmenu-glib/client.h
  libdbusmenu-glib/menuitem.c
  libdbusmenu-glib/menuitem.h
  libdbusmenu-gtk/client.c
  libdbusmenu-gtk/client.h
  libdbusmenu-gtk/menu.c
  libdbusmenu-gtk/menu.h
  po/Makefile.in.in
  tests/test-gtk-label.json


--
lp:dbusmenu
https://code.launchpad.net/~dbusmenu-team/dbusmenu/trunk

Your team ayatana-commits is subscribed to branch lp:dbusmenu.
To unsubscribe from this branch go to https://code.launchpad.net/~dbusmenu-team/dbusmenu/trunk/+edit-subscription.
=== modified file 'configure.ac'
--- configure.ac	2009-06-24 17:35:35 +0000
+++ configure.ac	2009-08-26 21:31:40 +0000
@@ -5,7 +5,7 @@
 AC_PREREQ(2.53)
 
 AM_CONFIG_HEADER(config.h)
-AM_INIT_AUTOMAKE(libdbusmenu, 0.0.1)
+AM_INIT_AUTOMAKE(libdbusmenu, 0.0.2)
 
 AM_MAINTAINER_MODE
 
@@ -19,6 +19,8 @@
 AC_SUBST(VERSION)
 AC_CONFIG_MACRO_DIR([m4])
 
+m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
+
 ###########################
 # Dependencies - GLib
 ###########################

=== modified file 'libdbusmenu-glib/client.c'
--- libdbusmenu-glib/client.c	2009-07-20 21:34:39 +0000
+++ libdbusmenu-glib/client.c	2009-08-27 13:36:58 +0000
@@ -68,6 +68,16 @@
 	DBusGProxyCall * layoutcall;
 
 	DBusGProxy * dbusproxy;
+
+	GHashTable * type_handlers;
+};
+
+typedef struct _newItemPropData newItemPropData;
+struct _newItemPropData
+{
+	DbusmenuClient * client;
+	DbusmenuMenuitem * item;
+	DbusmenuMenuitem * parent;
 };
 
 #define DBUSMENU_CLIENT_GET_PRIVATE(o) \
@@ -187,6 +197,9 @@
 
 	priv->dbusproxy = NULL;
 
+	priv->type_handlers = g_hash_table_new_full(g_str_hash, g_str_equal,
+	                                            g_free, NULL);
+
 	return;
 }
 
@@ -230,6 +243,10 @@
 	g_free(priv->dbus_name);
 	g_free(priv->dbus_object);
 
+	if (priv->type_handlers != NULL) {
+		g_hash_table_destroy(priv->type_handlers);
+	}
+
 	G_OBJECT_CLASS (dbusmenu_client_parent_class)->finalize (object);
 	return;
 }
@@ -515,6 +532,45 @@
 	return;
 }
 
+/* This is a different get properites call back that also sends
+   new signals.  It basically is a small wrapper around the original. */
+static void
+menuitem_get_properties_new_cb (DBusGProxy * proxy, GHashTable * properties, GError * error, gpointer data)
+{
+	g_return_if_fail(data != NULL);
+
+	newItemPropData * propdata = (newItemPropData *)data;
+	DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(propdata->client);
+
+	menuitem_get_properties_cb (proxy, properties, error, propdata->item);
+
+	gboolean handled = FALSE;
+
+	const gchar * type;
+	DbusmenuClientTypeHandler newfunc = NULL;
+	
+	type = dbusmenu_menuitem_property_get(propdata->item, "type");
+	if (type != NULL) {
+		newfunc = g_hash_table_lookup(priv->type_handlers, type);
+	} else {
+		newfunc = g_hash_table_lookup(priv->type_handlers, DBUSMENU_CLIENT_TYPES_DEFAULT);
+	}
+
+	if (newfunc != NULL) {
+		handled = newfunc(propdata->item, propdata->parent, propdata->client);
+	}
+
+	g_signal_emit(G_OBJECT(propdata->item), DBUSMENU_MENUITEM_SIGNAL_REALIZED_ID, 0, TRUE);
+
+	if (!handled) {
+		g_signal_emit(G_OBJECT(propdata->client), signals[NEW_MENUITEM], 0, propdata->item, TRUE);
+	}
+
+	g_free(propdata);
+
+	return;
+}
+
 static void
 menuitem_call_cb (DBusGProxy * proxy, GError * error, gpointer userdata)
 {
@@ -562,9 +618,19 @@
 			dbusmenu_menuitem_set_root(item, TRUE);
 		}
 		g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, G_CALLBACK(menuitem_activate), client);
-		g_signal_emit(G_OBJECT(client), signals[NEW_MENUITEM], 0, item, TRUE);
+
 		/* Get the properties queued up for this item */
-		org_freedesktop_dbusmenu_get_properties_async(proxy, id, menuitem_get_properties_cb, item);
+		/* Not happy about this, but I need these :( */
+		newItemPropData * propdata = g_new0(newItemPropData, 1);
+		if (propdata != NULL) {
+			propdata->client  = client;
+			propdata->item    = item;
+			propdata->parent  = parent;
+
+			org_freedesktop_dbusmenu_get_properties_async(proxy, id, menuitem_get_properties_new_cb, propdata);
+		} else {
+			g_warning("Unable to allocate memory to get properties for menuitem.  This menuitem will never be realized.");
+		}
 	} 
 
 	xmlNodePtr children;
@@ -744,3 +810,47 @@
 
 	return priv->root;
 }
+
+/**
+	dbusmenu_client_add_type_handler:
+	@client: Client where we're getting types coming in
+	@type: A text string that will be matched with the 'type'
+	    property on incoming menu items
+	@newfunc: The function that will be executed with those new
+	    items when they come in.
+
+	This function connects into the type handling of the #DbusmenuClient.
+	Every new menuitem that comes in immediately gets asked for it's
+	properties.  When we get those properties we check the 'type'
+	property and look to see if it matches a handler that is known
+	by the client.  If so, the @newfunc function is executed on that
+	#DbusmenuMenuitem.  If not, then the DbusmenuClient::new-menuitem
+	signal is sent.
+
+	In the future the known types will be sent to the server so that it
+	can make choices about the menu item types availble.
+
+	Return value: If registering the new type was successful.
+*/
+gboolean
+dbusmenu_client_add_type_handler (DbusmenuClient * client, const gchar * type, DbusmenuClientTypeHandler newfunc)
+{
+	g_return_val_if_fail(DBUSMENU_IS_CLIENT(client), FALSE);
+	g_return_val_if_fail(type != NULL, FALSE);
+
+	DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client);
+
+	if (priv->type_handlers == NULL) {
+		g_warning("Type handlers hashtable not built");
+		return FALSE;
+	}
+
+	gpointer value = g_hash_table_lookup(priv->type_handlers, type);
+	if (value != NULL) {
+		g_warning("Type '%s' already had a registered handler.", type);
+		return FALSE;
+	}
+
+	g_hash_table_insert(priv->type_handlers, g_strdup(type), newfunc);
+	return TRUE;
+}

=== modified file 'libdbusmenu-glib/client.h'
--- libdbusmenu-glib/client.h	2009-06-23 20:44:29 +0000
+++ libdbusmenu-glib/client.h	2009-08-26 20:15:13 +0000
@@ -50,10 +50,15 @@
 #define DBUSMENU_CLIENT_PROP_DBUS_NAME     "dbus-name"
 #define DBUSMENU_CLIENT_PROP_DBUS_OBJECT   "dbus-object"
 
+#define DBUSMENU_CLIENT_TYPES_DEFAULT      "menuitem"
+#define DBUSMENU_CLIENT_TYPES_SEPARATOR    "separator"
+#define DBUSMENU_CLIENT_TYPES_IMAGE        "imageitem"
+
 /**
 	DbusmenuClientClass:
 	@parent_class: #GObjectClass
 	@layout_updated: Slot for #DbusmenuClient::layout-updated.
+	@new_menuitem: Slot for #DbusmenuClient::new-menuitem.
 	@reserved1: Reserved for future use.
 	@reserved2: Reserved for future use.
 	@reserved3: Reserved for future use.
@@ -90,9 +95,15 @@
 	GObject parent;
 };
 
-GType                dbusmenu_client_get_type   (void);
-DbusmenuClient *     dbusmenu_client_new        (const gchar * name, const gchar * object);
-DbusmenuMenuitem *   dbusmenu_client_get_root   (DbusmenuClient * client);
+typedef gboolean (*DbusmenuClientTypeHandler) (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client);
+
+GType                dbusmenu_client_get_type          (void);
+DbusmenuClient *     dbusmenu_client_new               (const gchar * name,
+                                                        const gchar * object);
+DbusmenuMenuitem *   dbusmenu_client_get_root          (DbusmenuClient * client);
+gboolean             dbusmenu_client_add_type_handler  (DbusmenuClient * client,
+                                                        const gchar * type,
+                                                        DbusmenuClientTypeHandler newfunc);
 
 /**
 	SECTION:client

=== modified file 'libdbusmenu-glib/menuitem.c'
--- libdbusmenu-glib/menuitem.c	2009-06-26 18:57:59 +0000
+++ libdbusmenu-glib/menuitem.c	2009-08-25 16:16:26 +0000
@@ -61,6 +61,7 @@
 	CHILD_ADDED,
 	CHILD_REMOVED,
 	CHILD_MOVED,
+	REALIZED,
 	LAST_SIGNAL
 };
 
@@ -178,6 +179,22 @@
 	                                           NULL, NULL,
 	                                           _dbusmenu_menuitem_marshal_VOID__OBJECT_UINT_UINT,
 	                                           G_TYPE_NONE, 3, G_TYPE_OBJECT, G_TYPE_UINT, G_TYPE_UINT);
+	/**
+		DbusmenuMenuitem::realized:
+		@arg0: The #DbusmenuMenuitem object.
+
+		Emitted when the initial request for properties
+		is complete on the item.  If there is a type
+		handler configured for the "type" parameter
+		that will be executed before this is signaled.
+	*/
+	signals[REALIZED] =           g_signal_new(DBUSMENU_MENUITEM_SIGNAL_REALIZED,
+	                                           G_TYPE_FROM_CLASS(klass),
+	                                           G_SIGNAL_RUN_LAST,
+	                                           G_STRUCT_OFFSET(DbusmenuMenuitemClass, realized),
+	                                           NULL, NULL,
+	                                           _dbusmenu_menuitem_marshal_VOID__VOID,
+	                                           G_TYPE_NONE, 0, G_TYPE_NONE);
 
 	g_object_class_install_property (object_class, PROP_ID,
 	                                 g_param_spec_uint("id", "ID for the menu item",

=== modified file 'libdbusmenu-glib/menuitem.h'
--- libdbusmenu-glib/menuitem.h	2009-06-26 17:49:52 +0000
+++ libdbusmenu-glib/menuitem.h	2009-08-26 20:18:50 +0000
@@ -47,6 +47,13 @@
 #define DBUSMENU_MENUITEM_SIGNAL_CHILD_ADDED         "child-added"
 #define DBUSMENU_MENUITEM_SIGNAL_CHILD_REMOVED       "child-removed"
 #define DBUSMENU_MENUITEM_SIGNAL_CHILD_MOVED         "child-moved"
+#define DBUSMENU_MENUITEM_SIGNAL_REALIZED            "realized"
+#define DBUSMENU_MENUITEM_SIGNAL_REALIZED_ID         (g_signal_lookup(DBUSMENU_MENUITEM_SIGNAL_REALIZED, DBUSMENU_TYPE_MENUITEM))
+
+#define DBUSMENU_MENUITEM_PROP_VISIBLE               "visible"
+#define DBUSMENU_MENUITEM_PROP_LABEL                 "label"
+#define DBUSMENU_MENUITEM_PROP_ICON                  "icon"
+#define DBUSMENU_MENUITEM_PROP_ICON_DATA             "icon-data"
 
 /**
 	DbusmenuMenuitem:
@@ -71,6 +78,7 @@
 	@child_added: Slot for #DbusmenuMenuitem::child-added.
 	@child_removed: Slot for #DbusmenuMenuitem::child-removed.
 	@child_moved: Slot for #DbusmenuMenuitem::child-moved.
+	@realized: Slot for #DbusmenuMenuitem::realized.
 	@buildxml: Virtual function that appends the strings required
 	           to represent this menu item in the menu XML file.
 	@reserved1: Reserved for future use.
@@ -89,6 +97,7 @@
 	void (*child_added) (DbusmenuMenuitem * child, guint position);
 	void (*child_removed) (DbusmenuMenuitem * child);
 	void (*child_moved) (DbusmenuMenuitem * child, guint newpos, guint oldpos);
+	void (*realized) (void);
 
 	/* Virtual functions */
 	void (*buildxml) (GPtrArray * stringarray);
@@ -96,7 +105,7 @@
 	void (*reserved1) (void);
 	void (*reserved2) (void);
 	void (*reserved3) (void);
-	void (*reserved4) (void);
+	/* void (*reserved4) (void); -- realized, realloc when bumping lib version */
 };
 
 GType dbusmenu_menuitem_get_type (void);

=== modified file 'libdbusmenu-gtk/client.c'
--- libdbusmenu-gtk/client.c	2009-06-26 17:53:15 +0000
+++ libdbusmenu-gtk/client.c	2009-08-27 13:45:49 +0000
@@ -44,6 +44,10 @@
 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 gboolean new_item_normal     (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client);
+static gboolean new_item_seperator  (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client);
+static gboolean new_item_image      (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client);
+
 /* GObject Stuff */
 G_DEFINE_TYPE (DbusmenuGtkClient, dbusmenu_gtkclient, DBUSMENU_TYPE_CLIENT);
 
@@ -61,7 +65,12 @@
 static void
 dbusmenu_gtkclient_init (DbusmenuGtkClient *self)
 {
+	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);
+	dbusmenu_client_add_type_handler(DBUSMENU_CLIENT(self), DBUSMENU_CLIENT_TYPES_IMAGE,     new_item_image);
+
 	g_signal_connect(G_OBJECT(self), DBUSMENU_CLIENT_SIGNAL_NEW_MENUITEM, G_CALLBACK(new_menuitem), NULL);
+
 	return;
 }
 
@@ -95,14 +104,27 @@
 	return TRUE;
 }
 
+/* Process the visible property */
+static void
+process_visible (GtkMenuItem * gmi, const gchar * value)
+{
+	if (value == NULL || !g_strcmp0(value, "true")) {
+		gtk_widget_show(GTK_WIDGET(gmi));
+	} else {
+		gtk_widget_hide(GTK_WIDGET(gmi));
+	}
+	return;
+}
+
 /* Whenever we have a property change on a DbusmenuMenuitem
    we need to be responsive to that. */
 static void
 menu_prop_change_cb (DbusmenuMenuitem * mi, gchar * prop, gchar * value, GtkMenuItem * gmi)
 {
-	if (!g_strcmp0(prop, "label")) {
+	if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_LABEL)) {
 		gtk_menu_item_set_label(gmi, value);
-		gtk_widget_show(GTK_WIDGET(gmi));
+	} else if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_VISIBLE)) {
+		process_visible(gmi, value);
 	}
 
 	return;
@@ -125,31 +147,34 @@
 static void
 new_menuitem (DbusmenuClient * client, DbusmenuMenuitem * mi, gpointer userdata)
 {
-	gpointer ann_mi = g_object_get_data(G_OBJECT(mi), data_menuitem);
-	GtkMenuItem * gmi = GTK_MENU_ITEM(ann_mi);
-
-	if (gmi != NULL) {
-		/* It's possible we've already been looked at, that's
-		   okay, but we can just ignore this signal then. */
-		return;
-	}
-
-	gmi = GTK_MENU_ITEM(gtk_menu_item_new());
-
+	g_warning("Got new menuitem signal, which means they want something");
+	g_warning("  that I simply don't have.");
+
+	return;
+}
+
+void
+dbusmenu_gtkclient_newitem_base (DbusmenuGtkClient * client, DbusmenuMenuitem * item, GtkMenuItem * gmi, DbusmenuMenuitem * parent)
+{
 	/* Attach these two */
-	g_object_set_data(G_OBJECT(mi), data_menuitem, gmi);
+	g_object_set_data(G_OBJECT(item), data_menuitem, gmi);
 
 	/* DbusmenuMenuitem signals */
-	g_signal_connect(G_OBJECT(mi), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, G_CALLBACK(menu_prop_change_cb), gmi);
-	g_signal_connect(G_OBJECT(mi), DBUSMENU_MENUITEM_SIGNAL_CHILD_ADDED,   G_CALLBACK(new_child),    client);
-	g_signal_connect(G_OBJECT(mi), DBUSMENU_MENUITEM_SIGNAL_CHILD_REMOVED, G_CALLBACK(delete_child), client);
-	g_signal_connect(G_OBJECT(mi), DBUSMENU_MENUITEM_SIGNAL_CHILD_MOVED,   G_CALLBACK(move_child),   client);
+	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_CHILD_REMOVED, G_CALLBACK(delete_child), client);
+	g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_CHILD_MOVED,   G_CALLBACK(move_child),   client);
 
 	/* GtkMenuitem signals */
-	g_signal_connect(G_OBJECT(gmi), "activate", G_CALLBACK(menu_pressed_cb), mi);
+	g_signal_connect(G_OBJECT(gmi), "activate", G_CALLBACK(menu_pressed_cb), item);
 
 	/* Life insurance */
-	g_object_weak_ref(G_OBJECT(mi), destoryed_dbusmenuitem_cb, gmi);
+	g_object_weak_ref(G_OBJECT(item), destoryed_dbusmenuitem_cb, gmi);
+
+	process_visible(gmi, dbusmenu_menuitem_property_get(item, DBUSMENU_MENUITEM_PROP_VISIBLE));
+
+	if (parent != NULL) {
+		new_child(parent, item, dbusmenu_menuitem_get_position(item, parent), DBUSMENU_GTKCLIENT(client));
+	}
 
 	return;
 }
@@ -251,10 +276,55 @@
 
 	GtkMenuItem * mi = GTK_MENU_ITEM(g_object_get_data(G_OBJECT(item), data_menuitem));
 	if (mi == NULL) {
-		new_menuitem(DBUSMENU_CLIENT(client), item, NULL);
+		// new_menuitem(DBUSMENU_CLIENT(client), item, NULL);
+		g_warning("GTK not updated");
 		mi = GTK_MENU_ITEM(g_object_get_data(G_OBJECT(item), data_menuitem));
 	}
 
 	return mi;
 }
 
+static gboolean
+new_item_normal (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client)
+{
+	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(newitem), FALSE);
+	g_return_val_if_fail(DBUSMENU_IS_GTKCLIENT(client), FALSE);
+	/* Note: not checking parent, it's reasonable for it to be NULL */
+
+	GtkMenuItem * gmi;
+	gmi = GTK_MENU_ITEM(gtk_menu_item_new_with_label(dbusmenu_menuitem_property_get(newitem, DBUSMENU_MENUITEM_PROP_LABEL)));
+
+	if (gmi != NULL) {
+		dbusmenu_gtkclient_newitem_base(DBUSMENU_GTKCLIENT(client), newitem, gmi, parent);
+	} else {
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+new_item_seperator (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client)
+{
+	g_return_val_if_fail(DBUSMENU_IS_MENUITEM(newitem), FALSE);
+	g_return_val_if_fail(DBUSMENU_IS_GTKCLIENT(client), FALSE);
+	/* Note: not checking parent, it's reasonable for it to be NULL */
+
+	GtkMenuItem * gmi;
+	gmi = GTK_MENU_ITEM(gtk_separator_menu_item_new());
+
+	if (gmi != NULL) {
+		dbusmenu_gtkclient_newitem_base(DBUSMENU_GTKCLIENT(client), newitem, gmi, parent);
+	} else {
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+new_item_image (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client)
+{
+
+	return TRUE;
+}

=== modified file 'libdbusmenu-gtk/client.h'
--- libdbusmenu-gtk/client.h	2009-06-25 20:38:16 +0000
+++ libdbusmenu-gtk/client.h	2009-08-26 21:58:46 +0000
@@ -79,6 +79,8 @@
 DbusmenuGtkClient * dbusmenu_gtkclient_new (gchar * dbus_name, gchar * dbus_object);
 GtkMenuItem * dbusmenu_gtkclient_menuitem_get (DbusmenuGtkClient * client, DbusmenuMenuitem * item);
 
+void dbusmenu_gtkclient_newitem_base (DbusmenuGtkClient * client, DbusmenuMenuitem * item, GtkMenuItem * gmi, DbusmenuMenuitem * parent);
+
 /**
 	SECTION:gtkmenu
 	@short_description: A GTK Menu Object that syncronizes over DBus

=== modified file 'libdbusmenu-gtk/menu.c'
--- libdbusmenu-gtk/menu.c	2009-06-26 18:40:04 +0000
+++ libdbusmenu-gtk/menu.c	2009-08-27 13:49:11 +0000
@@ -64,6 +64,7 @@
 static void get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec);
 /* Internal */
 static void build_client (DbusmenuGtkMenu * self);
+static void child_realized (DbusmenuMenuitem * child, gpointer userdata);
 
 /* GObject Stuff */
 G_DEFINE_TYPE (DbusmenuGtkMenu, dbusmenu_gtkmenu, GTK_TYPE_MENU);
@@ -188,9 +189,7 @@
 root_child_added (DbusmenuMenuitem * root, DbusmenuMenuitem * child, guint position, DbusmenuGtkMenu * menu)
 {
 	g_debug("Root new child");
-	DbusmenuGtkMenuPrivate * priv = DBUSMENU_GTKMENU_GET_PRIVATE(menu);
-	gtk_menu_shell_insert(GTK_MENU_SHELL(menu), GTK_WIDGET(dbusmenu_gtkclient_menuitem_get(priv->client, child)), position);
-	gtk_widget_show(GTK_WIDGET(menu));
+	g_signal_connect(G_OBJECT(child), DBUSMENU_MENUITEM_SIGNAL_REALIZED, G_CALLBACK(child_realized), menu);
 	return;
 }
 
@@ -214,6 +213,18 @@
 }
 
 static void
+child_realized (DbusmenuMenuitem * child, gpointer userdata)
+{
+	g_return_if_fail(DBUSMENU_IS_GTKMENU(userdata));
+
+	DbusmenuGtkMenu * menu = DBUSMENU_GTKMENU(userdata);
+	DbusmenuGtkMenuPrivate * priv = DBUSMENU_GTKMENU_GET_PRIVATE(menu);
+
+	gtk_menu_append(menu, GTK_WIDGET(dbusmenu_gtkclient_menuitem_get(priv->client, child)));
+	return;
+}
+
+static void
 root_changed (DbusmenuGtkClient * client, DbusmenuMenuitem * newroot, DbusmenuGtkMenu * menu) {
 	if (newroot == NULL) {
 		gtk_widget_hide(GTK_WIDGET(menu));
@@ -227,7 +238,8 @@
 	GList * child = NULL;
 	guint count = 0;
 	for (child = dbusmenu_menuitem_get_children(newroot); child != NULL; child = g_list_next(child)) {
-		gtk_menu_append(menu, GTK_WIDGET(dbusmenu_gtkclient_menuitem_get(client, child->data)));
+		/* gtk_menu_append(menu, GTK_WIDGET(dbusmenu_gtkclient_menuitem_get(client, child->data))); */
+		g_signal_connect(G_OBJECT(child->data), DBUSMENU_MENUITEM_SIGNAL_REALIZED, G_CALLBACK(child_realized), menu);
 		count++;
 	}
 
@@ -279,3 +291,19 @@
 	                    NULL);
 }
 
+/**
+	dbusmenu_gtkmenu_get_client:
+	@menu: The #DbusmenuGtkMenu to get the client from
+
+	An accessor for the client that this menu is using to
+	communicate with the server.
+
+	Return value: A valid #DbusmenuGtkClient or NULL on error.
+*/
+DbusmenuGtkClient *
+dbusmenu_gtkmenu_get_client (DbusmenuGtkMenu * menu)
+{
+	g_return_val_if_fail(DBUSMENU_IS_GTKMENU(menu), NULL);
+	DbusmenuGtkMenuPrivate * priv = DBUSMENU_GTKMENU_GET_PRIVATE(menu);
+	return priv->client;
+}

=== modified file 'libdbusmenu-gtk/menu.h'
--- libdbusmenu-gtk/menu.h	2009-06-23 21:51:28 +0000
+++ libdbusmenu-gtk/menu.h	2009-08-26 22:22:50 +0000
@@ -31,6 +31,7 @@
 
 #include <glib.h>
 #include <glib-object.h>
+#include "client.h"
 
 G_BEGIN_DECLS
 
@@ -71,6 +72,7 @@
 
 GType dbusmenu_gtkmenu_get_type (void);
 DbusmenuGtkMenu * dbusmenu_gtkmenu_new (gchar * dbus_name, gchar * dbus_object);
+DbusmenuGtkClient * dbusmenu_gtkmenu_get_client (DbusmenuGtkMenu * menu);
 
 /**
 	SECTION:gtkmenu

=== modified file 'po/Makefile.in.in'
--- po/Makefile.in.in	2009-03-25 17:33:11 +0000
+++ po/Makefile.in.in	2009-08-20 17:55:36 +0000
@@ -56,7 +56,7 @@
 
 PO_LINGUAS=$(shell if test -r $(srcdir)/LINGUAS; then grep -v "^\#" $(srcdir)/LINGUAS; else echo "$(ALL_LINGUAS)"; fi)
 
-USER_LINGUAS=$(shell if test -n "$(LINGUAS)"; then LLINGUAS="$(LINGUAS)"; ALINGUAS="$(ALL_LINGUAS)"; for lang in $$LLINGUAS; do if test -n "`grep '^$$lang$$' $(srcdir)/LINGUAS 2>/dev/null`" -o -n "`echo $$ALINGUAS|tr ' ' '\n'|grep '^$$lang$$'`"; then printf "$$lang "; fi; done; fi)
+USER_LINGUAS=$(shell if test -n "$(LINGUAS)"; then LLINGUAS="$(LINGUAS)"; ALINGUAS="$(ALL_LINGUAS)"; for lang in $$LLINGUAS; do if test -n "`grep \^$$lang$$ $(srcdir)/LINGUAS 2>/dev/null`" -o -n "`echo $$ALINGUAS|tr ' ' '\n'|grep \^$$lang$$`"; then printf "$$lang "; fi; done; fi)
 
 USE_LINGUAS=$(shell if test -n "$(USER_LINGUAS)" -o -n "$(LINGUAS)"; then LLINGUAS="$(USER_LINGUAS)"; else if test -n "$(PO_LINGUAS)"; then LLINGUAS="$(PO_LINGUAS)"; else LLINGUAS="$(ALL_LINGUAS)"; fi; fi; for lang in $$LLINGUAS; do printf "$$lang "; done)
 

=== modified file 'tests/test-gtk-label.json'
--- tests/test-gtk-label.json	2009-06-17 19:59:57 +0000
+++ tests/test-gtk-label.json	2009-08-26 20:32:37 +0000
@@ -1,155 +1,155 @@
 [
-	{"id": 1,
+	{"id": 1, "type": "menuitem",
 	 "label": "value1",
 	 "submenu": [
-	 	{"id": 30,
+	 	{"id": 30, "type": "menuitem",
 		 "label": "value30"},
-	 	{"id": 31,
+	 	{"id": 31, "type": "menuitem",
 		 "label": "value31"},
-	 	{"id": 32,
+	 	{"id": 32, "type": "menuitem",
 		 "label": "value32"},
-	 	{"id": 33,
+	 	{"id": 33, "type": "menuitem",
 		 "label": "value33"},
-	 	{"id": 34,
+	 	{"id": 34, "type": "menuitem",
 		 "label": "value34"},
-	 	{"id": 35,
+	 	{"id": 35, "type": "menuitem",
 		 "label": "value35"},
-	 	{"id": 36,
+	 	{"id": 36, "type": "menuitem",
 		 "label": "value36"},
-	 	{"id": 37,
+	 	{"id": 37, "type": "menuitem",
 		 "label": "value37"},
-	 	{"id": 38,
+	 	{"id": 38, "type": "menuitem",
 		 "label": "value38"},
-	 	{"id": 39,
+	 	{"id": 39, "type": "menuitem",
 		 "label": "value39"}
 	 ]
 	},
-	{"id": 2,
+	{"id": 2, "type": "menuitem",
 	 "label": "value2",
 	 "submenu": [
-	 	{"id": 20,
+	 	{"id": 20, "type": "menuitem",
 		 "label": "value20"},
-	 	{"id": 21,
+	 	{"id": 21, "type": "separator",
 		 "label": "value21"},
-	 	{"id": 22,
+	 	{"id": 22, "type": "menuitem",
 		 "label": "value22"},
-	 	{"id": 23,
+	 	{"id": 23, "type": "separator",
 		 "label": "value23"},
-	 	{"id": 24,
+	 	{"id": 24, "type": "menuitem",
 		 "label": "value24"},
-	 	{"id": 25,
+	 	{"id": 25, "type": "separator",
 		 "label": "value25"},
-	 	{"id": 26,
+	 	{"id": 26, "type": "menuitem",
 		 "label": "value26"},
-	 	{"id": 27,
+	 	{"id": 27, "type": "separator",
 		 "label": "value27"},
-	 	{"id": 28,
+	 	{"id": 28, "type": "menuitem",
 		 "label": "value28"},
-	 	{"id": 29,
+	 	{"id": 29, "type": "menuitem", "visible": "false",
 		 "label": "value29"}
 	 ]
 	},
-	{"id": 3,
+	{"id": 3, "type": "menuitem",
 	 "label": "a super long label that is really of unreasonable length but we should make sure it makes it across the bus",
 	 "not.a.value": "A useless value",
 	 "submenu": [
-	 	{"id": 10,
+	 	{"id": 10, "type": "menuitem",
 		 "label": "value10"},
-	 	{"id": 11,
+	 	{"id": 11, "type": "menuitem",
 		 "label": "value11"},
-	 	{"id": 12,
+	 	{"id": 12, "type": "menuitem",
 		 "label": "value12"},
-	 	{"id": 13,
+	 	{"id": 13, "type": "menuitem",
 		 "label": "value13"},
-	 	{"id": 14,
+	 	{"id": 14, "type": "menuitem",
 		 "label": "value14"},
-	 	{"id": 15,
+	 	{"id": 15, "type": "menuitem",
 		 "label": "value15"},
-	 	{"id": 16,
+	 	{"id": 16, "type": "menuitem",
 		 "label": "value16"},
-	 	{"id": 17,
+	 	{"id": 17, "type": "menuitem",
 		 "label": "value17"},
-	 	{"id": 18,
+	 	{"id": 18, "type": "menuitem",
 		 "label": "value18"},
-	 	{"id": 19,
+	 	{"id": 19, "type": "menuitem",
 		 "label": "value19"}
 	 ]
 	},
-	{"id": 4,
+	{"id": 4, "type": "menuitem",
 	 "label": "value2",
 	 "submenu": [
-	 	{"id": 5,
+	 	{"id": 5, "type": "menuitem",
 		 "label": "value5",
 		 "submenu": [
-			{"id": 10,
+			{"id": 10, "type": "menuitem",
 			 "label": "value10"},
-			{"id": 11,
+			{"id": 11, "type": "menuitem",
 			 "label": "value11"},
-			{"id": 12,
+			{"id": 12, "type": "menuitem",
 			 "label": "value12"},
-			{"id": 13,
+			{"id": 13, "type": "menuitem",
 			 "label": "value13"},
-			{"id": 14,
+			{"id": 14, "type": "menuitem",
 			 "label": "value14"},
-			{"id": 15,
+			{"id": 15, "type": "menuitem",
 			 "label": "value15"},
-			{"id": 16,
+			{"id": 16, "type": "menuitem",
 			 "label": "value16"},
-			{"id": 17,
+			{"id": 17, "type": "menuitem",
 			 "label": "value17"},
-			{"id": 18,
+			{"id": 18, "type": "menuitem",
 			 "label": "value18"},
-			{"id": 19,
+			{"id": 19, "type": "menuitem",
 			 "label": "value19"}
 		 ]
 		},
-	 	{"id": 6,
+	 	{"id": 6, "type": "menuitem",
 		 "label": "value6",
 		 "submenu": [
-			{"id": 20,
+			{"id": 20, "type": "menuitem",
 			 "label": "value20"},
-			{"id": 21,
+			{"id": 21, "type": "menuitem",
 			 "label": "value21"},
-			{"id": 22,
+			{"id": 22, "type": "menuitem",
 			 "label": "value22"},
-			{"id": 23,
+			{"id": 23, "type": "menuitem",
 			 "label": "value23"},
-			{"id": 24,
+			{"id": 24, "type": "menuitem",
 			 "label": "value24"},
-			{"id": 25,
+			{"id": 25, "type": "menuitem",
 			 "label": "value25"},
-			{"id": 26,
+			{"id": 26, "type": "menuitem",
 			 "label": "value26"},
-			{"id": 27,
+			{"id": 27, "type": "menuitem",
 			 "label": "value27"},
-			{"id": 28,
+			{"id": 28, "type": "menuitem",
 			 "label": "value28"},
-			{"id": 29,
+			{"id": 29, "type": "menuitem",
 			 "label": "value29"}
 		 ]
 		},
-	 	{"id": 7,
+	 	{"id": 7, "type": "menuitem",
 		 "label": "value7",
 		 "submenu": [
-			{"id": 30,
+			{"id": 30, "type": "menuitem",
 			 "label": "value30"},
-			{"id": 31,
+			{"id": 31, "type": "menuitem",
 			 "label": "value31"},
-			{"id": 32,
+			{"id": 32, "type": "menuitem",
 			 "label": "value32"},
-			{"id": 33,
+			{"id": 33, "type": "menuitem",
 			 "label": "value33"},
-			{"id": 34,
+			{"id": 34, "type": "menuitem",
 			 "label": "value34"},
-			{"id": 35,
+			{"id": 35, "type": "menuitem",
 			 "label": "value35"},
-			{"id": 36,
+			{"id": 36, "type": "menuitem",
 			 "label": "value36"},
-			{"id": 37,
+			{"id": 37, "type": "menuitem",
 			 "label": "value37"},
-			{"id": 38,
+			{"id": 38, "type": "menuitem",
 			 "label": "value38"},
-			{"id": 39,
+			{"id": 39, "type": "menuitem",
 			 "label": "value39"}
 		 ]
 		},