diff options
-rw-r--r-- | .bzrignore | 1 | ||||
-rw-r--r-- | libdbusmenu-glib/client.c | 161 | ||||
-rw-r--r-- | libdbusmenu-glib/client.h | 4 | ||||
-rw-r--r-- | libdbusmenu-glib/menuitem-marshal.list | 2 | ||||
-rw-r--r-- | libdbusmenu-glib/menuitem.c | 41 | ||||
-rw-r--r-- | libdbusmenu-glib/menuitem.h | 5 | ||||
-rw-r--r-- | libdbusmenu-gtk/Makefile.am | 4 | ||||
-rw-r--r-- | libdbusmenu-gtk/client.c | 243 | ||||
-rw-r--r-- | libdbusmenu-gtk/client.h | 109 | ||||
-rw-r--r-- | libdbusmenu-gtk/menu.c | 114 |
10 files changed, 564 insertions, 120 deletions
@@ -36,4 +36,5 @@ test-glib-properties-server libdbusmenu_gtk_la-menu.lo test-gtk-label-client test-gtk-label-server +libdbusmenu_gtk_la-client.lo dbusmenu.xml diff --git a/libdbusmenu-glib/client.c b/libdbusmenu-glib/client.c index 22f65fa..5c429c8 100644 --- a/libdbusmenu-glib/client.c +++ b/libdbusmenu-glib/client.c @@ -47,6 +47,8 @@ enum { /* Signals */ enum { LAYOUT_UPDATED, + ROOT_CHANGED, + NEW_MENUITEM, LAST_SIGNAL }; @@ -64,6 +66,8 @@ struct _DbusmenuClientPrivate DBusGProxy * menuproxy; DBusGProxy * propproxy; DBusGProxyCall * layoutcall; + + DBusGProxy * dbusproxy; }; #define DBUSMENU_CLIENT_GET_PRIVATE(o) \ @@ -82,7 +86,7 @@ static void id_prop_update (DBusGProxy * proxy, guint id, gchar * property, gcha static void id_update (DBusGProxy * proxy, guint id, DbusmenuClient * client); static void build_proxies (DbusmenuClient * client); static guint parse_node_get_id (xmlNodePtr node); -static DbusmenuMenuitem * parse_layout_xml(xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * parent, DBusGProxy * proxy); +static DbusmenuMenuitem * parse_layout_xml(DbusmenuClient * client, xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * parent, DBusGProxy * proxy); static void parse_layout (DbusmenuClient * client, const gchar * layout); static void update_layout_cb (DBusGProxy * proxy, DBusGProxyCall * call, void * data); static void update_layout (DbusmenuClient * client); @@ -118,6 +122,39 @@ dbusmenu_client_class_init (DbusmenuClientClass *klass) NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, G_TYPE_NONE); + /** + DbusmenuClient::root-changed: + @arg0: The #DbusmenuClient object + @arg1: The new root #DbusmenuMenuitem + + The layout has changed in a way that can not be + represented by the individual items changing as the + root of this client has changed. + */ + signals[ROOT_CHANGED] = g_signal_new(DBUSMENU_CLIENT_SIGNAL_ROOT_CHANGED, + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (DbusmenuClientClass, root_changed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, G_TYPE_OBJECT); + /** + DbusmenuClient::new-menuitem: + @arg0: The #DbusmenuClient object + @arg1: The new #DbusmenuMenuitem created + + Signaled when the client creates a new menuitem. This + doesn't mean that it's placed anywhere. The parent that + it's applied to will signal #DbusmenuMenuitem::child-added + when it gets parented. + */ + signals[NEW_MENUITEM] = g_signal_new(DBUSMENU_CLIENT_SIGNAL_NEW_MENUITEM, + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (DbusmenuClientClass, new_menuitem), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, G_TYPE_OBJECT); g_object_class_install_property (object_class, PROP_DBUSOBJECT, g_param_spec_string(DBUSMENU_CLIENT_PROP_DBUS_OBJECT, "DBus Object we represent", @@ -148,6 +185,8 @@ dbusmenu_client_init (DbusmenuClient *self) priv->propproxy = NULL; priv->layoutcall = NULL; + priv->dbusproxy = NULL; + return; } @@ -168,6 +207,10 @@ dbusmenu_client_dispose (GObject *object) g_object_unref(G_OBJECT(priv->propproxy)); priv->propproxy = NULL; } + if (priv->dbusproxy != NULL) { + g_object_unref(G_OBJECT(priv->dbusproxy)); + priv->dbusproxy = NULL; + } priv->session_bus = NULL; if (priv->root != NULL) { @@ -277,6 +320,85 @@ id_update (DBusGProxy * proxy, guint id, DbusmenuClient * client) return; } +/* Watches to see if our DBus savior comes onto the bus */ +static void +dbus_owner_change (DBusGProxy * proxy, const gchar * name, const gchar * prev, const gchar * new, DbusmenuClient * client) +{ + DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client); + + if (!(new != NULL && prev == NULL)) { + /* If it's not someone new getting on the bus, sorry we + simply just don't care. It's not that your service isn't + important to someone, just not us. You'll find the right + process someday, there's lots of processes out there. */ + return; + } + + if (g_strcmp0(new, priv->dbus_name)) { + /* Again, someone else's service. */ + return; + } + + /* Woot! A service for us to love and to hold for ever + and ever and ever! */ + return build_proxies(client); +} + +/* This function builds the DBus proxy which will look out for + the service coming up. */ +static void +build_dbus_proxy (DbusmenuClient * client) +{ + DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client); + GError * error = NULL; + + if (priv->dbusproxy != NULL) { + return; + } + + priv->dbusproxy = dbus_g_proxy_new_for_name_owner (priv->session_bus, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + &error); + if (error != NULL) { + g_debug("Oh, that's bad. That's really bad. We can't get a proxy to DBus itself? Seriously? Here's all I know: %s", error->message); + g_error_free(error); + return; + } + + dbus_g_proxy_add_signal(priv->dbusproxy, "NameOwnerChanged", + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_INVALID); + dbus_g_proxy_connect_signal(priv->dbusproxy, "NameOwnerChanged", + G_CALLBACK(dbus_owner_change), client, NULL); + + return; +} + +/* A signal handler that gets called when a proxy is destoryed a + so it needs to clean up a little. Make sure we don't think we + have a layout and setup the dbus watcher. */ +static void +proxy_destroyed (GObject * gobj_proxy, gpointer userdata) +{ + DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(userdata); + + if (priv->root != NULL) { + g_object_unref(G_OBJECT(priv->root)); + priv->root = NULL; + g_signal_emit(G_OBJECT(userdata), signals[ROOT_CHANGED], 0, NULL, TRUE); + g_signal_emit(G_OBJECT(userdata), signals[LAYOUT_UPDATED], 0, TRUE); + } + + if ((gpointer)priv->menuproxy == (gpointer)gobj_proxy) { + priv->layoutcall = NULL; + } + + build_dbus_proxy(DBUSMENU_CLIENT(userdata)); + return; +} + /* When we have a name and an object, build the two proxies and get the first version of the layout */ static void @@ -292,6 +414,7 @@ build_proxies (DbusmenuClient * client) if (error != NULL) { g_error("Unable to get session bus: %s", error->message); g_error_free(error); + build_dbus_proxy(client); return; } @@ -305,6 +428,8 @@ build_proxies (DbusmenuClient * client) g_error_free(error); return; } + g_object_add_weak_pointer(G_OBJECT(priv->propproxy), (gpointer *)&priv->propproxy); + g_signal_connect(G_OBJECT(priv->propproxy), "destroy", G_CALLBACK(proxy_destroyed), client); priv->menuproxy = dbus_g_proxy_new_for_name_owner(priv->session_bus, priv->dbus_name, @@ -316,6 +441,14 @@ build_proxies (DbusmenuClient * client) g_error_free(error); return; } + g_object_add_weak_pointer(G_OBJECT(priv->menuproxy), (gpointer *)&priv->menuproxy); + g_signal_connect(G_OBJECT(priv->menuproxy), "destroy", G_CALLBACK(proxy_destroyed), client); + + /* If we get here, we don't need the DBus proxy */ + if (priv->dbusproxy != NULL) { + g_object_unref(G_OBJECT(priv->dbusproxy)); + priv->dbusproxy = NULL; + } dbus_g_proxy_add_signal(priv->menuproxy, "LayoutUpdate", G_TYPE_INVALID); dbus_g_proxy_connect_signal(priv->menuproxy, "LayoutUpdate", G_CALLBACK(layout_update), client, NULL); @@ -383,7 +516,7 @@ menuitem_get_properties_cb (DBusGProxy * proxy, GHashTable * properties, GError /* Parse recursively through the XML and make it into objects as need be */ static DbusmenuMenuitem * -parse_layout_xml(xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * parent, DBusGProxy * proxy) +parse_layout_xml(DbusmenuClient * client, xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * parent, DBusGProxy * proxy) { guint id = parse_node_get_id(node); /* g_debug("Looking at node with id: %d", id); */ @@ -403,6 +536,7 @@ parse_layout_xml(xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * pa /* Build a new item */ item = dbusmenu_menuitem_new_with_id(id); + 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); } @@ -427,7 +561,7 @@ parse_layout_xml(xmlNodePtr node, DbusmenuMenuitem * item, DbusmenuMenuitem * pa } } - childmi = parse_layout_xml(children, childmi, item, proxy); + childmi = parse_layout_xml(client, children, childmi, item, proxy); dbusmenu_menuitem_child_add_position(item, childmi, position); } @@ -456,12 +590,18 @@ parse_layout (DbusmenuClient * client, const gchar * layout) xmlNodePtr root = xmlDocGetRootElement(xmldoc); - priv->root = parse_layout_xml(root, priv->root, NULL, priv->menuproxy); + DbusmenuMenuitem * oldroot = priv->root; + priv->root = parse_layout_xml(client, root, priv->root, NULL, priv->menuproxy); + xmlFreeDoc(xmldoc); + if (priv->root == NULL) { g_warning("Unable to parse layout on client %s object %s: %s", priv->dbus_name, priv->dbus_object, layout); } - xmlFreeDoc(xmldoc); + if (priv->root != oldroot) { + g_signal_emit(G_OBJECT(client), signals[ROOT_CHANGED], 0, priv->root, TRUE); + } + return; } @@ -500,6 +640,10 @@ update_layout (DbusmenuClient * client) { DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client); + if (priv->propproxy == NULL) { + return; + } + if (priv->layoutcall != NULL) { return; } @@ -555,7 +699,8 @@ dbusmenu_client_new (const gchar * name, const gchar * object) it could block longer. Return value: A #DbusmenuMenuitem representing the root of - menu on the server. + menu on the server. If there is no server or there is + an error receiving its layout it'll return #NULL. */ DbusmenuMenuitem * dbusmenu_client_get_root (DbusmenuClient * client) @@ -564,6 +709,10 @@ dbusmenu_client_get_root (DbusmenuClient * client) DbusmenuClientPrivate * priv = DBUSMENU_CLIENT_GET_PRIVATE(client); + if (priv->propproxy == NULL) { + return NULL; + } + if (priv->layoutcall != NULL) { /* Will end the current call and block on it's completion */ update_layout_cb(priv->propproxy, priv->layoutcall, client); diff --git a/libdbusmenu-glib/client.h b/libdbusmenu-glib/client.h index d591ebb..35f7122 100644 --- a/libdbusmenu-glib/client.h +++ b/libdbusmenu-glib/client.h @@ -44,6 +44,8 @@ G_BEGIN_DECLS #define DBUSMENU_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DBUSMENU_TYPE_CLIENT, DbusmenuClientClass)) #define DBUSMENU_CLIENT_SIGNAL_LAYOUT_UPDATED "layout-updated" +#define DBUSMENU_CLIENT_SIGNAL_ROOT_CHANGED "root-changed" +#define DBUSMENU_CLIENT_SIGNAL_NEW_MENUITEM "new-menuitem" #define DBUSMENU_CLIENT_PROP_DBUS_NAME "dbus-name" #define DBUSMENU_CLIENT_PROP_DBUS_OBJECT "dbus-object" @@ -66,6 +68,8 @@ struct _DbusmenuClientClass { GObjectClass parent_class; void (*layout_updated)(void); + void (*root_changed) (DbusmenuMenuitem * newroot); + void (*new_menuitem) (DbusmenuMenuitem * newitem); /* Reserved for future use */ void (*reserved1) (void); diff --git a/libdbusmenu-glib/menuitem-marshal.list b/libdbusmenu-glib/menuitem-marshal.list index fc0318f..a32e7e3 100644 --- a/libdbusmenu-glib/menuitem-marshal.list +++ b/libdbusmenu-glib/menuitem-marshal.list @@ -1,3 +1,5 @@ VOID: STRING, STRING +VOID: OBJECT, UINT, UINT +VOID: OBJECT, UINT VOID: OBJECT VOID: VOID diff --git a/libdbusmenu-glib/menuitem.c b/libdbusmenu-glib/menuitem.c index 95391a4..57f1832 100644 --- a/libdbusmenu-glib/menuitem.c +++ b/libdbusmenu-glib/menuitem.c @@ -58,6 +58,7 @@ enum { ITEM_ACTIVATED, CHILD_ADDED, CHILD_REMOVED, + CHILD_MOVED, LAST_SIGNAL }; @@ -129,6 +130,7 @@ dbusmenu_menuitem_class_init (DbusmenuMenuitemClass *klass) DbusmenuMenuitem::child-added: @arg0: The #DbusmenuMenuitem which is the parent. @arg1: The #DbusmenuMenuitem which is the child. + @arg2: The position that the child is being added in. Signaled when the child menuitem has been added to the parent. @@ -138,8 +140,8 @@ dbusmenu_menuitem_class_init (DbusmenuMenuitemClass *klass) G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(DbusmenuMenuitemClass, child_added), NULL, NULL, - _dbusmenu_menuitem_marshal_VOID__OBJECT, - G_TYPE_NONE, 1, G_TYPE_OBJECT); + _dbusmenu_menuitem_marshal_VOID__OBJECT_UINT, + G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_UINT); /** DbusmenuMenuitem::child-removed: @arg0: The #DbusmenuMenuitem which was the parent. @@ -157,6 +159,23 @@ dbusmenu_menuitem_class_init (DbusmenuMenuitemClass *klass) NULL, NULL, _dbusmenu_menuitem_marshal_VOID__OBJECT, G_TYPE_NONE, 1, G_TYPE_OBJECT); + /** + DbusmenuMenuitem::child-moved: + @arg0: The #DbusmenuMenuitem which is the parent. + @arg1: The #DbusmenuMenuitem which is the child. + @arg2: The position that the child is being moved to. + @arg3: The position that the child is was in. + + Signaled when the child menuitem has had it's location + in the list change. + */ + signals[CHILD_MOVED] = g_signal_new(DBUSMENU_MENUITEM_SIGNAL_CHILD_MOVED, + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(DbusmenuMenuitemClass, child_moved), + NULL, NULL, + _dbusmenu_menuitem_marshal_VOID__OBJECT_UINT_UINT, + G_TYPE_NONE, 3, G_TYPE_OBJECT, G_TYPE_UINT, G_TYPE_UINT); g_object_class_install_property (object_class, PROP_ID, g_param_spec_uint("id", "ID for the menu item", @@ -398,7 +417,7 @@ dbusmenu_menuitem_child_append (DbusmenuMenuitem * mi, DbusmenuMenuitem * child) DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi); priv->children = g_list_append(priv->children, child); - g_signal_emit(G_OBJECT(mi), signals[CHILD_ADDED], 0, child, TRUE); + g_signal_emit(G_OBJECT(mi), signals[CHILD_ADDED], 0, child, g_list_length(priv->children) - 1, TRUE); return TRUE; } @@ -420,7 +439,7 @@ dbusmenu_menuitem_child_prepend (DbusmenuMenuitem * mi, DbusmenuMenuitem * child DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi); priv->children = g_list_prepend(priv->children, child); - g_signal_emit(G_OBJECT(mi), signals[CHILD_ADDED], 0, child, TRUE); + g_signal_emit(G_OBJECT(mi), signals[CHILD_ADDED], 0, child, 0, TRUE); return TRUE; } @@ -467,7 +486,7 @@ dbusmenu_menuitem_child_add_position (DbusmenuMenuitem * mi, DbusmenuMenuitem * DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi); priv->children = g_list_insert(priv->children, child, position); - g_signal_emit(G_OBJECT(mi), signals[CHILD_ADDED], 0, child, TRUE); + g_signal_emit(G_OBJECT(mi), signals[CHILD_ADDED], 0, child, position, TRUE); return TRUE; } @@ -490,9 +509,21 @@ dbusmenu_menuitem_child_reorder(DbusmenuMenuitem * mi, DbusmenuMenuitem * child, g_return_val_if_fail(DBUSMENU_IS_MENUITEM(child), FALSE); DbusmenuMenuitemPrivate * priv = DBUSMENU_MENUITEM_GET_PRIVATE(mi); + gint oldpos = g_list_index(priv->children, child); + + if (oldpos == -1) { + g_warning("Can not reorder child that isn't actually a child."); + return FALSE; + } + if (oldpos == position) { + return TRUE; + } + priv->children = g_list_remove(priv->children, child); priv->children = g_list_insert(priv->children, child, position); + g_signal_emit(G_OBJECT(mi), signals[CHILD_MOVED], 0, child, position, oldpos, TRUE); + return TRUE; } diff --git a/libdbusmenu-glib/menuitem.h b/libdbusmenu-glib/menuitem.h index c4fcf73..bad687b 100644 --- a/libdbusmenu-glib/menuitem.h +++ b/libdbusmenu-glib/menuitem.h @@ -46,6 +46,7 @@ G_BEGIN_DECLS #define DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED "item-activated" #define DBUSMENU_MENUITEM_SIGNAL_CHILD_ADDED "child-added" #define DBUSMENU_MENUITEM_SIGNAL_CHILD_REMOVED "child-removed" +#define DBUSMENU_MENUITEM_SIGNAL_CHILD_MOVED "child-moved" /** DbusmenuMenuitem: @@ -69,6 +70,7 @@ struct _DbusmenuMenuitem @item_activated: Slot for #DbusmenuMenuitem::item-activated. @child_added: Slot for #DbusmenuMenuitem::child-added. @child_removed: Slot for #DbusmenuMenuitem::child-removed. + @child_moved: Slot for #DbusmenuMenuitem::child-moved. @buildxml: Virtual function that appends the strings required to represent this menu item in the menu XML file. @reserved1: Reserved for future use. @@ -84,8 +86,9 @@ struct _DbusmenuMenuitemClass /* Signals */ void (*property_changed) (gchar * property, gchar * value); void (*item_activated) (void); - void (*child_added) (DbusmenuMenuitem * child); + void (*child_added) (DbusmenuMenuitem * child, guint position); void (*child_removed) (DbusmenuMenuitem * child); + void (*child_moved) (DbusmenuMenuitem * child, guint newpos, guint oldpos); /* Virtual functions */ void (*buildxml) (GPtrArray * stringarray); diff --git a/libdbusmenu-gtk/Makefile.am b/libdbusmenu-gtk/Makefile.am index 831719c..c375428 100644 --- a/libdbusmenu-gtk/Makefile.am +++ b/libdbusmenu-gtk/Makefile.am @@ -8,9 +8,13 @@ lib_LTLIBRARIES = \ libdbusmenu_gtkincludedir=$(includedir)/libdbusmenu-0.1/libdbusmenu-gtk/ libdbusmenu_gtkinclude_HEADERS = \ + client.h \ menu.h libdbusmenu_gtk_la_SOURCES = \ + client.h \ + client.c \ + menu.h \ menu.c libdbusmenu_gtk_la_LDFLAGS = \ diff --git a/libdbusmenu-gtk/client.c b/libdbusmenu-gtk/client.c new file mode 100644 index 0000000..eae304f --- /dev/null +++ b/libdbusmenu-gtk/client.c @@ -0,0 +1,243 @@ +/* +A library to take the object model made consistent by libdbusmenu-glib +and visualize it in GTK. + +Copyright 2009 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/> +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gtk/gtk.h> + +#include "client.h" + +/* Prototypes */ +static void dbusmenu_gtkclient_class_init (DbusmenuGtkClientClass *klass); +static void dbusmenu_gtkclient_init (DbusmenuGtkClient *self); +static void dbusmenu_gtkclient_dispose (GObject *object); +static void dbusmenu_gtkclient_finalize (GObject *object); +static void new_menuitem (DbusmenuClient * client, DbusmenuMenuitem * mi, gpointer userdata); +static void new_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position, gpointer userdata); +static void delete_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, gpointer userdata); +static void move_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint new, guint old, gpointer userdata); + +/* GObject Stuff */ +G_DEFINE_TYPE (DbusmenuGtkClient, dbusmenu_gtkclient, DBUSMENU_TYPE_CLIENT); + +static void +dbusmenu_gtkclient_class_init (DbusmenuGtkClientClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = dbusmenu_gtkclient_dispose; + object_class->finalize = dbusmenu_gtkclient_finalize; + + return; +} + +static void +dbusmenu_gtkclient_init (DbusmenuGtkClient *self) +{ + g_signal_connect(G_OBJECT(self), DBUSMENU_CLIENT_SIGNAL_NEW_MENUITEM, G_CALLBACK(new_menuitem), NULL); + return; +} + +static void +dbusmenu_gtkclient_dispose (GObject *object) +{ + + G_OBJECT_CLASS (dbusmenu_gtkclient_parent_class)->dispose (object); + return; +} + +static void +dbusmenu_gtkclient_finalize (GObject *object) +{ + + G_OBJECT_CLASS (dbusmenu_gtkclient_parent_class)->finalize (object); + return; +} + +/* Internal Functions */ + +static const gchar * data_menuitem = "dbusmenugtk-data-gtkmenuitem"; +static const gchar * data_menu = "dbusmenugtk-data-gtkmenu"; + +/* This is the call back for the GTK widget for when it gets + clicked on by the user to send it back across the bus. */ +static gboolean +menu_pressed_cb (GtkMenuItem * gmi, DbusmenuMenuitem * mi) +{ + dbusmenu_menuitem_activate(mi); + return TRUE; +} + +/* 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")) { + gtk_menu_item_set_label(gmi, value); + gtk_widget_show(GTK_WIDGET(gmi)); + } + + return; +} + +/* Call back that happens when the DbusmenuMenuitem + is destroyed. We're making sure to clean up everything + else down the pipe. */ +static void +destoryed_dbusmenuitem_cb (gpointer udata, GObject * dbusmenuitem) +{ + /* g_debug("DbusmenuMenuitem was destroyed"); */ + gtk_widget_destroy(GTK_WIDGET(udata)); + return; +} + +/* This takes a new DbusmenuMenuitem and attaches the + various things that we need to make it work in a + GTK World. */ +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()); + + /* Attach these two */ + g_object_set_data(G_OBJECT(mi), 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), NULL); + g_signal_connect(G_OBJECT(mi), DBUSMENU_MENUITEM_SIGNAL_CHILD_REMOVED, G_CALLBACK(delete_child), NULL); + g_signal_connect(G_OBJECT(mi), DBUSMENU_MENUITEM_SIGNAL_CHILD_MOVED, G_CALLBACK(move_child), NULL); + + /* GtkMenuitem signals */ + g_signal_connect(G_OBJECT(gmi), "activate", G_CALLBACK(menu_pressed_cb), mi); + + /* Life insurance */ + g_object_weak_ref(G_OBJECT(mi), destoryed_dbusmenuitem_cb, gmi); + + return; +} + +static void +new_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position, gpointer userdata) +{ + gpointer ann_menu = g_object_get_data(G_OBJECT(mi), data_menu); + GtkMenu * menu = GTK_MENU(ann_menu); + if (menu == NULL) { + /* Oh, we don't have a submenu, build one! */ + menu = GTK_MENU(gtk_menu_new()); + g_object_set_data(G_OBJECT(mi), data_menu, menu); + + GtkMenuItem * parent = dbusmenu_gtkclient_menuitem_get (mi); + gtk_menu_item_set_submenu(parent, GTK_WIDGET(menu)); + } + + GtkMenuItem * childmi = dbusmenu_gtkclient_menuitem_get (child); + gtk_menu_shell_insert(GTK_MENU_SHELL(menu), GTK_WIDGET(childmi), position); + + return; +} + +static void +delete_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, gpointer userdata) +{ + if (g_list_length(dbusmenu_menuitem_get_children(mi)) == 0) { + gpointer ann_menu = g_object_get_data(G_OBJECT(mi), data_menu); + GtkMenu * menu = GTK_MENU(ann_menu); + + if (menu != NULL) { + gtk_widget_destroy(GTK_WIDGET(menu)); + g_object_set_data(G_OBJECT(mi), data_menu, NULL); + } + } + + return; +} + +static void +move_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint new, guint old, gpointer userdata) +{ + gpointer ann_menu = g_object_get_data(G_OBJECT(mi), data_menu); + if (ann_menu == NULL) { + g_warning("Moving a child when we don't have a submenu!"); + return; + } + + GtkMenuItem * childmi = dbusmenu_gtkclient_menuitem_get (child); + gtk_menu_reorder_child(GTK_MENU(ann_menu), GTK_WIDGET(childmi), new); + + return; +} + +/* Public API */ + +/** + dbusmenu_gtkclient_new: + @dbus_name: Name of the #DbusmenuServer on DBus + @dbus_name: Name of the object on the #DbusmenuServer + + Creates a new #DbusmenuGtkClient object and creates a #DbusmenuClient + that connects across DBus to a #DbusmenuServer. + + Return value: A new #DbusmenuGtkClient sync'd with a server +*/ +DbusmenuGtkClient * +dbusmenu_gtkclient_new (gchar * dbus_name, gchar * dbus_object) +{ + return g_object_new(DBUSMENU_GTKCLIENT_TYPE, + DBUSMENU_CLIENT_PROP_DBUS_OBJECT, dbus_object, + DBUSMENU_CLIENT_PROP_DBUS_NAME, dbus_name, + NULL); +} + +/** + dbusmenu_gtkclient_menuitem_get: + @item: #DbusmenuMenuitem to get associated #GtkMenuItem on. + + This grabs the #GtkMenuItem that is associated with the + #DbusmenuMenuitem. + + Return value: The #GtkMenuItem that can be played with. +*/ +GtkMenuItem * +dbusmenu_gtkclient_menuitem_get (DbusmenuMenuitem * item) +{ + return GTK_MENU_ITEM(g_object_get_data(G_OBJECT(item), data_menuitem)); +} + diff --git a/libdbusmenu-gtk/client.h b/libdbusmenu-gtk/client.h new file mode 100644 index 0000000..3c0117a --- /dev/null +++ b/libdbusmenu-gtk/client.h @@ -0,0 +1,109 @@ +/* +A library to take the object model made consistent by libdbusmenu-glib +and visualize it in GTK. + +Copyright 2009 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_GTKCLIENT_H__ +#define __DBUSMENU_GTKCLIENT_H__ + +#include <glib.h> +#include <glib-object.h> +#include <libdbusmenu-glib/client.h> + +G_BEGIN_DECLS + +#define DBUSMENU_GTKCLIENT_TYPE (dbusmenu_gtkclient_get_type ()) +#define DBUSMENU_GTKCLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DBUSMENU_GTKCLIENT_TYPE, DbusmenuGtkClient)) +#define DBUSMENU_GTKCLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), DBUSMENU_GTKCLIENT_TYPE, DbusmenuGtkClientClass)) +#define DBUSMENU_IS_GTKCLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DBUSMENU_GTKCLIENT_TYPE)) +#define DBUSMENU_IS_GTKCLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DBUSMENU_GTKCLIENT_TYPE)) +#define DBUSMENU_GTKCLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DBUSMENU_GTKCLIENT_TYPE, DbusmenuGtkClientClass)) + +#define DBUSMENU_GTKCLIENT_SIGNAL_ROOT_CHANGED DBUSMENU_CLIENT_SIGNAL_ROOT_CHANGED + +/** + DbusmenuGtkClientClass: + @parent_class: #GtkMenuClass + @reserved1: Reserved for future use. + @reserved2: Reserved for future use. + @reserved3: Reserved for future use. + @reserved4: Reserved for future use. +*/ +typedef struct _DbusmenuGtkClientClass DbusmenuGtkClientClass; +struct _DbusmenuGtkClientClass { + DbusmenuClientClass parent_class; + + /* Signals */ + void (*root_changed) (DbusmenuMenuitem * newroot); + + /* Reserved */ + void (*reserved1) (void); + void (*reserved2) (void); + void (*reserved3) (void); + void (*reserved4) (void); +}; + +/** + DbusmenuGtkClient: + @parent: #GtkMenu +*/ +typedef struct _DbusmenuGtkClient DbusmenuGtkClient; +struct _DbusmenuGtkClient { + DbusmenuClient parent; +}; + +GType dbusmenu_gtkclient_get_type (void); +DbusmenuGtkClient * dbusmenu_gtkclient_new (gchar * dbus_name, gchar * dbus_object); +GtkMenuItem * dbusmenu_gtkclient_menuitem_get (DbusmenuMenuitem * item); + +/** + SECTION:gtkmenu + @short_description: A GTK Menu Object that syncronizes over DBus + @stability: Unstable + @include: libdbusmenu-gtk/menu.h + + In general, this is just a #GtkMenu, why else would you care? Oh, + because this menu is created by someone else on a server that exists + on the other side of DBus. You need a #DbusmenuServer to be able + push the data into this menu. + + The first thing you need to know is how to find that #DbusmenuServer + on DBus. This involves both the DBus name and the DBus object that + the menu interface can be found on. Those two value should be set + when creating the object using dbusmenu_gtkmenu_new(). They are then + stored on two properties #DbusmenuGtkClient:dbus-name and #DbusmenuGtkClient:dbus-object. + + After creation the #DbusmenuGtkClient it will continue to keep in + synchronization with the #DbusmenuServer object across Dbus. If the + number of entries change, the menus change, if they change thier + properties change, they update in the items. All of this should + be handled transparently to the user of this object. + + TODO: Document properties. +*/ +G_END_DECLS + +#endif diff --git a/libdbusmenu-gtk/menu.c b/libdbusmenu-gtk/menu.c index 4074947..4b88f67 100644 --- a/libdbusmenu-gtk/menu.c +++ b/libdbusmenu-gtk/menu.c @@ -34,6 +34,7 @@ License version 3 and version 2.1 along with this program. If not, see #include "menu.h" #include "libdbusmenu-glib/client.h" +#include "client.h" /* Properties */ enum { @@ -45,7 +46,7 @@ enum { /* Private */ typedef struct _DbusmenuGtkMenuPrivate DbusmenuGtkMenuPrivate; struct _DbusmenuGtkMenuPrivate { - DbusmenuClient * client; + DbusmenuGtkClient * client; gchar * dbus_object; gchar * dbus_name; @@ -183,114 +184,11 @@ get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec) /* Internal Functions */ -static const gchar * data_menuitem = "dbusmenugtk-data-gtkmenuitem"; -static const gchar * data_menu = "dbusmenugtk-data-gtkmenu"; - -static gboolean -menu_pressed_cb (GtkMenuItem * gmi, DbusmenuMenuitem * mi) -{ - dbusmenu_menuitem_activate(mi); - return TRUE; -} - -static void -menu_prop_change_cb (DbusmenuMenuitem * mi, gchar * prop, gchar * value, GtkMenuItem * gmi) -{ - if (!g_strcmp0(prop, "label")) { - gtk_menu_item_set_label(gmi, value); - gtk_widget_show(GTK_WIDGET(gmi)); - } - - return; -} - -static void -destoryed_dbusmenuitem_cb (gpointer udata, GObject * dbusmenuitem) -{ - /* g_debug("DbusmenuMenuitem was destroyed"); */ - gtk_widget_destroy(GTK_WIDGET(udata)); - return; -} - -static void -connect_menuitem (DbusmenuMenuitem * mi, GtkMenuItem * gmi) -{ - g_object_set_data(G_OBJECT(mi), data_menuitem, gmi); - - g_signal_connect(G_OBJECT(mi), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, G_CALLBACK(menu_prop_change_cb), gmi); - g_signal_connect(G_OBJECT(gmi), "activate", G_CALLBACK(menu_pressed_cb), mi); - - g_object_weak_ref(G_OBJECT(mi), destoryed_dbusmenuitem_cb, gmi); - - return; -} - static void -process_dbusmenu_menuitem (DbusmenuMenuitem * mi, GtkMenu * parentmenu) -{ - gpointer unknown_menuitem = g_object_get_data(G_OBJECT(mi), data_menuitem); - if (unknown_menuitem == NULL) { - /* Oh, a virgin DbusmenuMenuitem, let's fix that. */ - GtkWidget * menuitem = gtk_menu_item_new(); - connect_menuitem(mi, GTK_MENU_ITEM(menuitem)); - unknown_menuitem = menuitem; - gtk_menu_shell_append(GTK_MENU_SHELL(parentmenu), menuitem); - } - - GList * children = dbusmenu_menuitem_get_children(mi); - if (children == NULL) { - /* If there are no children to process we are - done and we can move along */ - return; - } - - /* Phase 0: Make a submenu if we don't have one */ - gpointer unknown_menu = g_object_get_data(G_OBJECT(mi), data_menu); - if (unknown_menu == NULL) { - GtkWidget * gtkmenu = gtk_menu_new(); - g_object_ref(gtkmenu); - g_object_set_data_full(G_OBJECT(mi), data_menu, gtkmenu, g_object_unref); - unknown_menu = gtkmenu; - gtk_menu_item_set_submenu(GTK_MENU_ITEM(unknown_menuitem), gtkmenu); - gtk_widget_show(gtkmenu); - } - - /* Phase 1: Add missing children */ - GList * child = NULL; - for (child = children; child != NULL; child = g_list_next(child)) { - process_dbusmenu_menuitem(DBUSMENU_MENUITEM(child->data), GTK_MENU(unknown_menu)); - } - - /* Phase 2: Delete removed children */ - /* Actually, we don't need to do this because of the weak - reference that we've added above. When the DbusmenuMenuitem - gets destroyed it takes its GtkMenuItem with it. Bye bye. */ - - /* Phase 3: Profit! */ - return; +root_changed (void) { + /* stub */ } -/* Processing the layout being updated and handling - that and making it into a menu */ -static void -process_layout_change (DbusmenuClient * client, DbusmenuGtkMenu * gtkmenu) -{ - DbusmenuMenuitem * root = dbusmenu_client_get_root(client); - - GList * children = dbusmenu_menuitem_get_children(root); - if (children == NULL) { - return; - } - - GList * child = NULL; - for (child = children; child != NULL; child = g_list_next(child)) { - process_dbusmenu_menuitem(DBUSMENU_MENUITEM(child->data), GTK_MENU(gtkmenu)); - } - - return; -} - - /* Builds the client and connects all of the signals up for it so that it's happy-happy */ static void @@ -299,11 +197,11 @@ build_client (DbusmenuGtkMenu * self) DbusmenuGtkMenuPrivate * priv = DBUSMENU_GTKMENU_GET_PRIVATE(self); if (priv->client == NULL) { - priv->client = dbusmenu_client_new(priv->dbus_name, priv->dbus_object); + priv->client = dbusmenu_gtkclient_new(priv->dbus_name, priv->dbus_object); /* Register for layout changes, this should come after the creation of the client pulls it from DBus */ - g_signal_connect(G_OBJECT(priv->client), DBUSMENU_CLIENT_SIGNAL_LAYOUT_UPDATED, G_CALLBACK(process_layout_change), self); + g_signal_connect(G_OBJECT(priv->client), DBUSMENU_GTKCLIENT_SIGNAL_ROOT_CHANGED, G_CALLBACK(root_changed), self); } return; |