aboutsummaryrefslogtreecommitdiff
path: root/libdbusmenu-gtk/client.c
diff options
context:
space:
mode:
Diffstat (limited to 'libdbusmenu-gtk/client.c')
-rw-r--r--libdbusmenu-gtk/client.c521
1 files changed, 428 insertions, 93 deletions
diff --git a/libdbusmenu-gtk/client.c b/libdbusmenu-gtk/client.c
index 7ab2fe9..1051f20 100644
--- a/libdbusmenu-gtk/client.c
+++ b/libdbusmenu-gtk/client.c
@@ -31,16 +31,21 @@ License version 3 and version 2.1 along with this program. If not, see
#endif
#include <gtk/gtk.h>
+#include <glib.h>
#include "client.h"
#include "menuitem.h"
#include "genericmenuitem.h"
+#include "genericmenuitem-enum-types.h"
/* Private */
struct _DbusmenuGtkClientPrivate {
+ GStrv old_themedirs;
GtkAccelGroup * agroup;
};
+GHashTable * theme_dir_db = NULL;
+
#define DBUSMENU_GTKCLIENT_GET_PRIVATE(o) (DBUSMENU_GTKCLIENT(o)->priv)
#define USE_FALLBACK_PROP "use-fallback"
@@ -54,6 +59,9 @@ static void new_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint po
static void delete_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, DbusmenuGtkClient * gtkclient);
static void move_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint new, guint old, DbusmenuGtkClient * gtkclient);
static void item_activate (DbusmenuClient * client, DbusmenuMenuitem * mi, guint timestamp, gpointer userdata);
+static void theme_dir_changed (DbusmenuClient * client, GStrv theme_dirs, gpointer userdata);
+static void remove_theme_dirs (GtkIconTheme * theme, GStrv dirs);
+static void event_result (DbusmenuClient * client, DbusmenuMenuitem * mi, const gchar * event, GVariant * variant, guint timestamp, GError * error);
static gboolean new_item_normal (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client, gpointer user_data);
static gboolean new_item_seperator (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client, gpointer user_data);
@@ -89,6 +97,23 @@ dbusmenu_gtkclient_init (DbusmenuGtkClient *self)
DbusmenuGtkClientPrivate * priv = DBUSMENU_GTKCLIENT_GET_PRIVATE(self);
priv->agroup = NULL;
+ priv->old_themedirs = NULL;
+
+ /* We either build the theme db or we get a reference
+ to it. This way when all clients die the hashtable
+ will be free'd as well. */
+ if (theme_dir_db == NULL) {
+ theme_dir_db = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
+ /* NOTE: We're adding an extra ref here because there
+ is no way to clear the pointer when the hash table
+ dies, so it's safer to keep the hash table around
+ forever than not know if it's free'd or not. Patch
+ submitted to GLib. */
+ g_hash_table_ref(theme_dir_db);
+ } else {
+ g_hash_table_ref(theme_dir_db);
+ }
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);
@@ -96,6 +121,10 @@ dbusmenu_gtkclient_init (DbusmenuGtkClient *self)
/* TODO: I think these can be handled in the class... */
g_signal_connect(G_OBJECT(self), DBUSMENU_CLIENT_SIGNAL_NEW_MENUITEM, G_CALLBACK(new_menuitem), NULL);
g_signal_connect(G_OBJECT(self), DBUSMENU_CLIENT_SIGNAL_ITEM_ACTIVATE, G_CALLBACK(item_activate), NULL);
+ g_signal_connect(G_OBJECT(self), DBUSMENU_CLIENT_SIGNAL_ICON_THEME_DIRS_CHANGED, G_CALLBACK(theme_dir_changed), NULL);
+ g_signal_connect(G_OBJECT(self), DBUSMENU_CLIENT_SIGNAL_EVENT_RESULT, G_CALLBACK(event_result), NULL);
+
+ theme_dir_changed(DBUSMENU_CLIENT(self), dbusmenu_client_get_icon_paths(DBUSMENU_CLIENT(self)), NULL);
return;
}
@@ -111,6 +140,18 @@ dbusmenu_gtkclient_dispose (GObject *object)
priv->agroup = NULL;
}
+ if (priv->old_themedirs) {
+ remove_theme_dirs(gtk_icon_theme_get_default(), priv->old_themedirs);
+ g_strfreev(priv->old_themedirs);
+ priv->old_themedirs = NULL;
+ }
+
+ if (theme_dir_db != NULL) {
+ g_hash_table_unref(theme_dir_db);
+ } else {
+ g_assert_not_reached();
+ }
+
G_OBJECT_CLASS (dbusmenu_gtkclient_parent_class)->dispose (object);
return;
}
@@ -124,6 +165,141 @@ dbusmenu_gtkclient_finalize (GObject *object)
return;
}
+/* Add a theme directory to the table and the theme's list of available
+ themes to use. */
+static void
+theme_dir_ref (GtkIconTheme * theme, GHashTable * db, const gchar * dir)
+{
+ g_return_if_fail(db != NULL);
+ g_return_if_fail(theme != NULL);
+ g_return_if_fail(dir != NULL);
+
+ int count = 0;
+ if ((count = GPOINTER_TO_INT(g_hash_table_lookup(db, dir))) != 0) {
+ /* It exists so what we need to do is increase the ref
+ count of this dir. */
+ count++;
+ } else {
+ /* It doesn't exist, so we need to add it to the table
+ and to the search path. */
+ gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), dir);
+ g_debug("\tAppending search path: %s", dir);
+ count = 1;
+ }
+
+ g_hash_table_insert(db, g_strdup(dir), GINT_TO_POINTER(count));
+
+ return;
+}
+
+/* Unreference the theme directory, and if it's count goes to zero then
+ we need to remove it from the search path. */
+static void
+theme_dir_unref (GtkIconTheme * theme, GHashTable * db, const gchar * dir)
+{
+ g_return_if_fail(db != NULL);
+ g_return_if_fail(theme != NULL);
+ g_return_if_fail(dir != NULL);
+
+ /* Grab the count for this dir */
+ int count = GPOINTER_TO_INT(g_hash_table_lookup(db, dir));
+
+ /* Is this a simple deprecation, if so, we can just lower the
+ number and move on. */
+ if (count > 1) {
+ count--;
+ g_hash_table_insert(db, g_strdup(dir), GINT_TO_POINTER(count));
+ return;
+ }
+
+ /* Try to remove it from the hash table, this makes sure
+ that it existed */
+ if (!g_hash_table_remove(db, dir)) {
+ g_warning("Unref'd a directory that wasn't in the theme dir hash table.");
+ return;
+ }
+
+ gchar ** paths;
+ gint path_count;
+
+ gtk_icon_theme_get_search_path(theme, &paths, &path_count);
+
+ gint i;
+ gboolean found = FALSE;
+ for (i = 0; i < path_count; i++) {
+ if (found) {
+ /* If we've already found the right entry */
+ paths[i - 1] = paths[i];
+ } else {
+ /* We're still looking, is this the one? */
+ if (!g_strcmp0(paths[i], dir)) {
+ found = TRUE;
+ /* We're freeing this here as it won't be captured by the
+ g_strfreev() below as it's out of the array. */
+ g_free(paths[i]);
+ }
+ }
+ }
+
+ /* If we found one we need to reset the path to
+ accomidate the changes */
+ if (found) {
+ paths[path_count - 1] = NULL; /* Clear the last one */
+ gtk_icon_theme_set_search_path(theme, (const gchar **)paths, path_count - 1);
+ }
+
+ g_strfreev(paths);
+
+ return;
+}
+
+/* Unregister this list of theme directories */
+static void
+remove_theme_dirs (GtkIconTheme * theme, GStrv dirs)
+{
+ g_return_if_fail(GTK_ICON_THEME(theme));
+ g_return_if_fail(dirs != NULL);
+
+ int dir;
+
+ for (dir = 0; dirs[dir] != NULL; dir++) {
+ theme_dir_unref(theme, theme_dir_db, dirs[dir]);
+ }
+
+ return;
+}
+
+/* Called when the theme directories are changed by the
+ server part of things. */
+static void
+theme_dir_changed (DbusmenuClient * client, GStrv theme_dirs, gpointer userdata)
+{
+ DbusmenuGtkClientPrivate * priv = DBUSMENU_GTKCLIENT_GET_PRIVATE(client);
+ GtkIconTheme * theme = gtk_icon_theme_get_default();
+
+ /* Ref the new directories */
+ if (theme_dirs != NULL) {
+ int dir;
+ for (dir = 0; theme_dirs[dir] != NULL; dir++) {
+ theme_dir_ref(theme, theme_dir_db, theme_dirs[dir]);
+ }
+ }
+
+ /* Unref the old ones */
+ if (priv->old_themedirs) {
+ remove_theme_dirs(theme, priv->old_themedirs);
+ g_strfreev(priv->old_themedirs);
+ priv->old_themedirs = NULL;
+ }
+
+ /* Copy the new to the old */
+ if (theme_dirs != NULL) {
+ priv->old_themedirs = g_strdupv(theme_dirs);
+ }
+
+ return;
+}
+
/* Structure for passing data to swap_agroup */
typedef struct _swap_agroup_t swap_agroup_t;
struct _swap_agroup_t {
@@ -219,13 +395,13 @@ refresh_shortcut (DbusmenuGtkClient * client, DbusmenuMenuitem * mi)
/**
- dbusmenu_gtkclient_set_accel_group:
- @client: To set the group on
- @agroup: The new acceleration group
-
- Sets the acceleration group for the menu items with accelerators
- on this client.
-*/
+ * dbusmenu_gtkclient_set_accel_group:
+ * @client: To set the group on
+ * @agroup: The new acceleration group
+ *
+ * Sets the acceleration group for the menu items with accelerators
+ * on this client.
+ */
void
dbusmenu_gtkclient_set_accel_group (DbusmenuGtkClient * client, GtkAccelGroup * agroup)
{
@@ -256,14 +432,14 @@ dbusmenu_gtkclient_set_accel_group (DbusmenuGtkClient * client, GtkAccelGroup *
}
/**
- dbusmenu_gtkclient_get_accel_group:
- @client: Client to query for an accelerator group
-
- Gets the accel group for this client.
-
- Return value: Either a valid group or #NULL on error or
- none set.
-*/
+ * dbusmenu_gtkclient_get_accel_group:
+ * @client: Client to query for an accelerator group
+ *
+ * Gets the accel group for this client.
+ *
+ * Return value: (transfer none): Either a valid group or #NULL on error or
+ * none set.
+ */
GtkAccelGroup *
dbusmenu_gtkclient_get_accel_group (DbusmenuGtkClient * client)
{
@@ -276,8 +452,97 @@ dbusmenu_gtkclient_get_accel_group (DbusmenuGtkClient * client)
/* Internal Functions */
-static const gchar * data_menuitem = "dbusmenugtk-data-gtkmenuitem";
-static const gchar * data_menu = "dbusmenugtk-data-gtkmenu";
+static const gchar * data_menuitem = "dbusmenugtk-data-gtkmenuitem";
+static const gchar * data_menu = "dbusmenugtk-data-gtkmenu";
+static const gchar * data_activating = "dbusmenugtk-data-activating";
+static const gchar * data_idle_close_id = "dbusmenugtk-data-idle-close-id";
+static const gchar * data_delayed_close = "dbusmenugtk-data-delayed-close";
+
+static void
+menu_item_start_activating(DbusmenuMenuitem * mi)
+{
+ /* Mark this item and all it's parents as activating */
+ DbusmenuMenuitem * parent = mi;
+ do {
+ g_object_set_data(G_OBJECT(parent), data_activating,
+ GINT_TO_POINTER(TRUE));
+ } while ((parent = dbusmenu_menuitem_get_parent (parent)) != NULL);
+
+ GVariant * variant = g_variant_new("i", 0);
+ dbusmenu_menuitem_handle_event(mi, DBUSMENU_MENUITEM_EVENT_ACTIVATED, variant, gtk_get_current_event_time());
+}
+
+static gboolean
+menu_item_is_activating(DbusmenuMenuitem * mi)
+{
+ return GPOINTER_TO_INT(g_object_get_data(G_OBJECT(mi), data_activating));
+}
+
+static void
+menu_item_stop_activating(DbusmenuMenuitem * mi)
+{
+ if (!menu_item_is_activating(mi))
+ return;
+
+ /* Mark this item and all it's parents as not activating and finally
+ send their queued close event. */
+ g_object_set_data(G_OBJECT(mi), data_activating, GINT_TO_POINTER(FALSE));
+
+ /* There is one master root parent that we don't care about, so stop
+ right before it */
+ DbusmenuMenuitem * parent = dbusmenu_menuitem_get_parent (mi);
+ while (dbusmenu_menuitem_get_parent (parent) != NULL &&
+ menu_item_is_activating(parent)) {
+ /* Now clean up the activating flag */
+ g_object_set_data(G_OBJECT(parent), data_activating,
+ GINT_TO_POINTER(FALSE));
+
+ gboolean should_close = FALSE;
+
+ /* Note that dbus might be fast enough to have already
+ processed the app's reply before close_in_idle() is called.
+ So to avoid that, we shut down any pending close_in_idle call */
+ guint id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(parent),
+ data_idle_close_id));
+ if (id > 0) {
+ g_source_remove(id);
+ g_object_set_data(G_OBJECT(parent), data_idle_close_id,
+ GINT_TO_POINTER(0));
+ should_close = TRUE;
+ }
+
+ gboolean delayed = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(mi),
+ data_delayed_close));
+ if (delayed) {
+ g_object_set_data(G_OBJECT(mi), data_delayed_close,
+ GINT_TO_POINTER(FALSE));
+ should_close = TRUE;
+ }
+
+ /* And finally send a delayed closed event if one would have
+ happened */
+ if (should_close) {
+ dbusmenu_menuitem_handle_event(parent,
+ DBUSMENU_MENUITEM_EVENT_CLOSED,
+ NULL,
+ gtk_get_current_event_time());
+ }
+
+ parent = dbusmenu_menuitem_get_parent (parent);
+ }
+}
+
+static void
+event_result (DbusmenuClient * client, DbusmenuMenuitem * mi,
+ const gchar * event, GVariant * variant, guint timestamp,
+ GError * error)
+{
+ if (g_strcmp0(event, DBUSMENU_MENUITEM_EVENT_ACTIVATED) == 0) {
+ menu_item_stop_activating(mi);
+ }
+
+ return;
+}
/* 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. */
@@ -285,8 +550,7 @@ static gboolean
menu_pressed_cb (GtkMenuItem * gmi, DbusmenuMenuitem * mi)
{
if (gtk_menu_item_get_submenu(gmi) == NULL) {
- GVariant * variant = g_variant_new("i", 0);
- dbusmenu_menuitem_handle_event(mi, "clicked", variant, gtk_get_current_event_time());
+ menu_item_start_activating(mi);
} else {
/* TODO: We need to stop the display of the submenu
until this callback returns. */
@@ -295,6 +559,44 @@ menu_pressed_cb (GtkMenuItem * gmi, DbusmenuMenuitem * mi)
return TRUE;
}
+static gboolean
+close_in_idle (DbusmenuMenuitem * mi)
+{
+ /* Don't send closed signal if we also sent activating signal.
+ We'd just be asking for race conditions. We'll send closed
+ when done with activation. */
+ if (!menu_item_is_activating(mi))
+ dbusmenu_menuitem_handle_event(mi, DBUSMENU_MENUITEM_EVENT_CLOSED, NULL, gtk_get_current_event_time());
+ else
+ g_object_set_data(G_OBJECT(mi), data_delayed_close, GINT_TO_POINTER(TRUE));
+
+ g_object_set_data(G_OBJECT(mi), data_idle_close_id, GINT_TO_POINTER(0));
+ return FALSE;
+}
+
+static void
+submenu_notify_visible_cb (GtkWidget * menu, GParamSpec * pspec, DbusmenuMenuitem * mi)
+{
+ if (gtk_widget_get_visible (menu)) {
+ menu_item_stop_activating(mi); /* just in case */
+ dbusmenu_menuitem_handle_event(mi, DBUSMENU_MENUITEM_EVENT_OPENED, NULL, gtk_get_current_event_time());
+ } else {
+ /* Try to close in the idle loop because we actually get a menu
+ close notification before we get notified that a menu item
+ was clicked. We want to give that clicked signal some
+ time, so we wait until all queued signals are handled before
+ continuing. (our handling of the closed signal depends on
+ whether the user clicked an item or not) */
+ guint id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(mi),
+ data_idle_close_id));
+ if (id == 0) {
+ id = g_idle_add((GSourceFunc)close_in_idle, mi);
+ g_object_set_data(G_OBJECT(mi), data_idle_close_id,
+ GINT_TO_POINTER(id));
+ }
+ }
+}
+
/* Process the visible property */
static void
process_visible (DbusmenuMenuitem * mi, GtkMenuItem * gmi, GVariant * value)
@@ -370,11 +672,58 @@ process_toggle_state (DbusmenuMenuitem * mi, GtkMenuItem * gmi, GVariant * varia
return;
}
+/* Submenu processing */
+static void
+process_submenu (DbusmenuMenuitem * mi, GtkMenuItem * gmi, GVariant * variant, DbusmenuGtkClient * gtkclient)
+{
+ const gchar * submenu = NULL;
+ if (variant != NULL) {
+ submenu = g_variant_get_string(variant, NULL);
+ }
+
+ if (g_strcmp0(submenu, DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU) != 0) {
+ /* This is the only case we're really supporting right now,
+ so if it's not this, we want to clean up. */
+ /* We're just going to warn for now. */
+ gpointer pmenu = g_object_get_data(G_OBJECT(mi), data_menu);
+ if (pmenu != NULL) {
+ g_warning("The child-display variable is set to '%s' but there's a menu, odd?", submenu);
+ }
+ } else {
+ /* We need to build a menu for these guys to live in. */
+ GtkMenu * menu = GTK_MENU(gtk_menu_new());
+ g_object_ref_sink(menu);
+ g_object_set_data_full(G_OBJECT(mi), data_menu, menu, g_object_unref);
+
+ gtk_menu_item_set_submenu(gmi, GTK_WIDGET(menu));
+
+ g_signal_connect(menu, "notify::visible", G_CALLBACK(submenu_notify_visible_cb), mi);
+ }
+
+ return;
+}
+
+/* Process the disposition changing */
+static void
+process_disposition (DbusmenuMenuitem * mi, GtkMenuItem * gmi, GVariant * variant, DbusmenuGtkClient * gtkclient)
+{
+ /* We can only handle generic menu items here. Perhaps someone else
+ will find the value useful. Not us. */
+ if (!IS_GENERICMENUITEM(gmi)) {
+ return;
+ }
+
+ genericmenuitem_set_disposition(GENERICMENUITEM(gmi), genericmenuitem_disposition_get_value_from_nick(g_variant_get_string(variant, NULL)));
+ 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, GVariant * variant, GtkMenuItem * gmi)
+menu_prop_change_cb (DbusmenuMenuitem * mi, gchar * prop, GVariant * variant, DbusmenuGtkClient * gtkclient)
{
+ GtkMenuItem * gmi = dbusmenu_gtkclient_menuitem_get(gtkclient, mi);
+
if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_LABEL)) {
gtk_menu_item_set_label(gmi, variant == NULL ? NULL : g_variant_get_string(variant, NULL));
} else if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_VISIBLE)) {
@@ -385,6 +734,10 @@ menu_prop_change_cb (DbusmenuMenuitem * mi, gchar * prop, GVariant * variant, Gt
process_toggle_type(mi, gmi, variant);
} else if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_TOGGLE_STATE)) {
process_toggle_state(mi, gmi, variant);
+ } else if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY)) {
+ process_submenu(mi, gmi, variant, gtkclient);
+ } else if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_DISPOSITION)) {
+ process_disposition(mi, gmi, variant, gtkclient);
}
return;
@@ -401,19 +754,6 @@ menu_shortcut_change_cb (DbusmenuMenuitem * mi, gchar * prop, GVariant * value,
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)
-{
- #ifdef MASSIVEDEBUGGING
- g_debug("DbusmenuMenuitem was destroyed");
- #endif
- gtk_widget_destroy(GTK_WIDGET(udata));
- return;
-}
-
/* The new menuitem signal only happens if we don't have a type handler
for the type of the item. This should be an error condition and we're
printing out a message. */
@@ -493,21 +833,21 @@ destroy_gmi (GtkMenuItem * gmi, DbusmenuMenuitem * mi)
#endif
/**
- dbusmenu_gtkclient_newitem_base:
- @client: The client handling everything on this connection
- @item: The #DbusmenuMenuitem to attach the GTK-isms to
- @gmi: A #GtkMenuItem representing the GTK world's view of this menuitem
- @parent: The parent #DbusmenuMenuitem
-
- This function provides some of the basic connectivity for being in
- the GTK world. Things like visibility and sensitivity of the item are
- handled here so that the subclasses don't have to. If you're building
- your on GTK menu item you can use this function to apply those basic
- attributes so that you don't have to deal with them either.
-
- This also handles passing the "activate" signal back to the
- #DbusmenuMenuitem side of thing.
-*/
+ * dbusmenu_gtkclient_newitem_base:
+ * @client: The client handling everything on this connection
+ * @item: The #DbusmenuMenuitem to attach the GTK-isms to
+ * @gmi: A #GtkMenuItem representing the GTK world's view of this menuitem
+ * @parent: The parent #DbusmenuMenuitem
+ *
+ * This function provides some of the basic connectivity for being in
+ * the GTK world. Things like visibility and sensitivity of the item are
+ * handled here so that the subclasses don't have to. If you're building
+ * your on GTK menu item you can use this function to apply those basic
+ * attributes so that you don't have to deal with them either.
+ *
+ * This also handles passing the "activate" signal back to the
+ * #DbusmenuMenuitem side of thing.
+ */
void
dbusmenu_gtkclient_newitem_base (DbusmenuGtkClient * client, DbusmenuMenuitem * item, GtkMenuItem * gmi, DbusmenuMenuitem * parent)
{
@@ -516,14 +856,11 @@ dbusmenu_gtkclient_newitem_base (DbusmenuGtkClient * client, DbusmenuMenuitem *
#endif
/* Attach these two */
- g_object_set_data(G_OBJECT(item), data_menuitem, gmi);
- g_object_ref(G_OBJECT(gmi));
- #ifdef MASSIVEDEBUGGING
- g_signal_connect(G_OBJECT(gmi), "destroy", G_CALLBACK(destroy_gmi), item);
- #endif
+ g_object_ref_sink(G_OBJECT(gmi));
+ g_object_set_data_full(G_OBJECT(item), data_menuitem, gmi, (GDestroyNotify)gtk_widget_destroy);
/* DbusmenuMenuitem signals */
- g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, G_CALLBACK(menu_prop_change_cb), gmi);
+ g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, G_CALLBACK(menu_prop_change_cb), client);
g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, G_CALLBACK(menu_shortcut_change_cb), client);
g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_CHILD_REMOVED, G_CALLBACK(delete_child), client);
g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_CHILD_MOVED, G_CALLBACK(move_child), client);
@@ -531,14 +868,13 @@ dbusmenu_gtkclient_newitem_base (DbusmenuGtkClient * client, DbusmenuMenuitem *
/* GtkMenuitem signals */
g_signal_connect(G_OBJECT(gmi), "activate", G_CALLBACK(menu_pressed_cb), item);
- /* Life insurance */
- g_object_weak_ref(G_OBJECT(item), destoryed_dbusmenuitem_cb, gmi);
-
/* Check our set of props to see if any are set already */
process_visible(item, gmi, dbusmenu_menuitem_property_get_variant(item, DBUSMENU_MENUITEM_PROP_VISIBLE));
process_sensitive(item, gmi, dbusmenu_menuitem_property_get_variant(item, DBUSMENU_MENUITEM_PROP_ENABLED));
process_toggle_type(item, gmi, dbusmenu_menuitem_property_get_variant(item, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE));
process_toggle_state(item, gmi, dbusmenu_menuitem_property_get_variant(item, DBUSMENU_MENUITEM_PROP_TOGGLE_STATE));
+ process_submenu(item, gmi, dbusmenu_menuitem_property_get_variant(item, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY), client);
+ process_disposition(item, gmi, dbusmenu_menuitem_property_get_variant(item, DBUSMENU_MENUITEM_PROP_DISPOSITION), client);
refresh_shortcut(client, item);
/* Oh, we're a child, let's deal with that */
@@ -560,19 +896,15 @@ new_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position, Dbus
if (g_strcmp0(dbusmenu_menuitem_property_get(mi, DBUSMENU_MENUITEM_PROP_TYPE), DBUSMENU_CLIENT_TYPES_SEPARATOR) == 0) { return; }
gpointer ann_menu = g_object_get_data(G_OBJECT(mi), data_menu);
- GtkMenu * menu = GTK_MENU(ann_menu);
- 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);
+ if (ann_menu == NULL) {
+ g_warning("Children but no menu, someone's been naughty with their '" DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY "' property: '%s'", dbusmenu_menuitem_property_get(mi, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY));
+ return;
+ }
- GtkMenuItem * parent = dbusmenu_gtkclient_menuitem_get(gtkclient, mi);
- gtk_menu_item_set_submenu(parent, GTK_WIDGET(menu));
- }
+ GtkMenu * menu = GTK_MENU(ann_menu);
GtkMenuItem * childmi = dbusmenu_gtkclient_menuitem_get(gtkclient, child);
gtk_menu_shell_insert(GTK_MENU_SHELL(menu), GTK_WIDGET(childmi), position);
- gtk_widget_show(GTK_WIDGET(menu));
return;
}
@@ -589,7 +921,7 @@ delete_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, DbusmenuGtkClient
if (menu != NULL) {
gtk_widget_destroy(GTK_WIDGET(menu));
- g_object_set_data(G_OBJECT(mi), data_menu, NULL);
+ g_object_steal_data(G_OBJECT(mi), data_menu);
}
}
@@ -617,15 +949,15 @@ move_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint new, guint ol
/* 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
-*/
+ * dbusmenu_gtkclient_new:
+ * @dbus_name: Name of the #DbusmenuServer on DBus
+ * @dbus_object: 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)
{
@@ -636,15 +968,15 @@ dbusmenu_gtkclient_new (gchar * dbus_name, gchar * dbus_object)
}
/**
- dbusmenu_gtkclient_menuitem_get:
- @client: A #DbusmenuGtkClient with the item in it.
- @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.
-*/
+ * dbusmenu_gtkclient_menuitem_get:
+ * @client: A #DbusmenuGtkClient with the item in it.
+ * @item: #DbusmenuMenuitem to get associated #GtkMenuItem on.
+ *
+ * This grabs the #GtkMenuItem that is associated with the
+ * #DbusmenuMenuitem.
+ *
+ * Return value: (transfer none): The #GtkMenuItem that can be played with.
+ */
GtkMenuItem *
dbusmenu_gtkclient_menuitem_get (DbusmenuGtkClient * client, DbusmenuMenuitem * item)
{
@@ -660,13 +992,13 @@ dbusmenu_gtkclient_menuitem_get (DbusmenuGtkClient * client, DbusmenuMenuitem *
}
/**
- dbusmenu_gtkclient_menuitem_get_submenu:
- @client: A #DbusmenuGtkClient with the item in it.
- @item: #DbusmenuMenuitem to get associated #GtkMenu on.
-
- This grabs the submenu associated with the menuitem.
-
- Return value: The #GtkMenu if there is one.
+ * dbusmenu_gtkclient_menuitem_get_submenu:
+ * @client: A #DbusmenuGtkClient with the item in it.
+ * @item: #DbusmenuMenuitem to get associated #GtkMenu on.
+ *
+ * This grabs the submenu associated with the menuitem.
+ *
+ * Return value: (transfer none): The #GtkMenu if there is one.
*/
GtkMenu *
dbusmenu_gtkclient_menuitem_get_submenu (DbusmenuGtkClient * client, DbusmenuMenuitem * item)
@@ -693,9 +1025,9 @@ new_item_normal (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, Dbusmenu
GtkMenuItem * gmi;
gmi = GTK_MENU_ITEM(g_object_new(GENERICMENUITEM_TYPE, NULL));
- gtk_menu_item_set_label(gmi, dbusmenu_menuitem_property_get(newitem, DBUSMENU_MENUITEM_PROP_LABEL));
if (gmi != NULL) {
+ gtk_menu_item_set_label(gmi, dbusmenu_menuitem_property_get(newitem, DBUSMENU_MENUITEM_PROP_LABEL));
dbusmenu_gtkclient_newitem_base(DBUSMENU_GTKCLIENT(client), newitem, gmi, parent);
} else {
return FALSE;
@@ -870,6 +1202,9 @@ image_property_handle (DbusmenuMenuitem * item, const gchar * property, GVariant
} else {
gtk_image_set_from_pixbuf(GTK_IMAGE(gtkimage), image);
}
+ if (image) {
+ g_object_unref(image);
+ }
}
}