diff options
Diffstat (limited to 'libdbusmenu-gtk')
-rw-r--r-- | libdbusmenu-gtk/Makefile.am | 120 | ||||
-rw-r--r-- | libdbusmenu-gtk/client.c | 635 | ||||
-rw-r--r-- | libdbusmenu-gtk/client.h | 49 | ||||
-rw-r--r-- | libdbusmenu-gtk/dbusmenu-gtk-0.4.pc.in (renamed from libdbusmenu-gtk/dbusmenu-gtk.pc.in) | 4 | ||||
-rw-r--r-- | libdbusmenu-gtk/dbusmenu-gtk.h | 40 | ||||
-rw-r--r-- | libdbusmenu-gtk/dbusmenu-gtk3-0.4.pc.in | 14 | ||||
-rw-r--r-- | libdbusmenu-gtk/genericmenuitem-enum-types.c.in | 116 | ||||
-rw-r--r-- | libdbusmenu-gtk/genericmenuitem-enum-types.h.in | 65 | ||||
-rw-r--r-- | libdbusmenu-gtk/genericmenuitem.c | 232 | ||||
-rw-r--r-- | libdbusmenu-gtk/genericmenuitem.h | 71 | ||||
-rw-r--r-- | libdbusmenu-gtk/menu.c | 99 | ||||
-rw-r--r-- | libdbusmenu-gtk/menu.h | 40 | ||||
-rw-r--r-- | libdbusmenu-gtk/menuitem.c | 286 | ||||
-rw-r--r-- | libdbusmenu-gtk/menuitem.h | 21 | ||||
-rw-r--r-- | libdbusmenu-gtk/parser.c | 1211 | ||||
-rw-r--r-- | libdbusmenu-gtk/parser.h | 52 |
16 files changed, 2577 insertions, 478 deletions
diff --git a/libdbusmenu-gtk/Makefile.am b/libdbusmenu-gtk/Makefile.am index 2be63b7..4ec464b 100644 --- a/libdbusmenu-gtk/Makefile.am +++ b/libdbusmenu-gtk/Makefile.am @@ -1,28 +1,60 @@ -CLEANFILES = +if USE_GTK3 +VER=3 +GTKGIR=Gtk-3.0 +GTKVALA=gtk+-3.0 +lib_LTLIBRARIES = libdbusmenu-gtk3.la +else +VER= +GTKGIR=Gtk-2.0 +GTKVALA=gtk+-2.0 +lib_LTLIBRARIES = libdbusmenu-gtk.la +endif +BUILT_SOURCES = +CLEANFILES = +DISTCLEANFILES = EXTRA_DIST = \ - dbusmenu-gtk.pc.in + dbusmenu-gtk-0.4.pc.in \ + dbusmenu-gtk3-0.4.pc.in + +############## +# Enum Stuff +############## + +include $(top_srcdir)/Makefile.am.enum + +glib_enum_h = genericmenuitem-enum-types.h +glib_enum_c = genericmenuitem-enum-types.c +glib_enum_headers = $(srcdir)/genericmenuitem.h + -lib_LTLIBRARIES = \ - libdbusmenu-gtk.la +##################### +# Include Directory +##################### -libdbusmenu_gtkincludedir=$(includedir)/libdbusmenu-0.1/libdbusmenu-gtk/ +libdbusmenu_gtkincludedir=$(includedir)/libdbusmenu-0.4/libdbusmenu-gtk$(VER)/ libdbusmenu_gtkinclude_HEADERS = \ + dbusmenu-gtk.h \ client.h \ menu.h \ - menuitem.h + menuitem.h \ + parser.h libdbusmenu_gtk_la_SOURCES = \ client.h \ client.c \ genericmenuitem.h \ genericmenuitem.c \ + genericmenuitem-enum-types.h \ + genericmenuitem-enum-types.c \ menu.h \ menu.c \ menuitem.h \ - menuitem.c + menuitem.c \ + parser.h \ + parser.c libdbusmenu_gtk_la_LDFLAGS = \ -version-info $(LIBDBUSMENU_CURRENT):$(LIBDBUSMENU_REVISION):$(LIBDBUSMENU_AGE) \ @@ -36,7 +68,18 @@ libdbusmenu_gtk_la_LIBADD = \ ../libdbusmenu-glib/libdbusmenu-glib.la \ $(DBUSMENUGTK_LIBS) -pkgconfig_DATA = dbusmenu-gtk.pc +# We duplicate these here because Automake won't let us use $(VER) on the left hand side. +# Since we carefully use $(VER) in the right hand side above, we can assign the same values. +# Only one version of the library is every compiled at the same time, so it is safe to reuse +# the right hand sides like this. +libdbusmenu_gtk3includedir = $(libdbusmenu_gtkincludedir) +libdbusmenu_gtk3include_HEADERS = $(libdbusmenu_gtkinclude_HEADERS) +libdbusmenu_gtk3_la_SOURCES = $(libdbusmenu_gtk_la_SOURCES) +libdbusmenu_gtk3_la_LDFLAGS = $(libdbusmenu_gtk_la_LDFLAGS) +libdbusmenu_gtk3_la_CFLAGS = $(libdbusmenu_gtk_la_CFLAGS) +libdbusmenu_gtk3_la_LIBADD = $(libdbusmenu_gtk_la_LIBADD) + +pkgconfig_DATA = dbusmenu-gtk$(VER)-0.4.pc pkgconfigdir = $(libdir)/pkgconfig ######################### @@ -45,26 +88,47 @@ pkgconfigdir = $(libdir)/pkgconfig -include $(INTROSPECTION_MAKEFILE) INTROSPECTION_GIRS = -INTROSPECTION_SCANNER_ARGS = \ - --add-include-path=$(srcdir) \ + +if INTROSPECTION_TEN +INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir) \ + --warn-all \ + --add-include-path=$(top_builddir)/libdbusmenu-glib \ + $(addprefix --c-include=libdbusmenu-gtk/, $(libdbusmenu_gtkinclude_HEADERS)) \ + --symbol-prefix=dbusmenu \ + --identifier-prefix=DbusmenuGtk +else +INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir) \ + --warn-all \ --add-include-path=$(top_builddir)/libdbusmenu-glib \ - $(addprefix --c-include=libdbusmenu-gtk/, $(introspection_sources)) + $(addprefix --c-include=libdbusmenu-gtk/, $(libdbusmenu_gtkinclude_HEADERS)) +endif + INTROSPECTION_COMPILER_ARGS = --includedir=$(builddir) --includedir=$(top_builddir)/libdbusmenu-glib if HAVE_INTROSPECTION -introspection_sources = $(libdbusmenu_gtkinclude_HEADERS) +introspection_sources = $(filter-out genericmenuitem%, $(libdbusmenu_gtkinclude_HEADERS) $(libdbusmenu_gtk_la_SOURCES)) -DbusmenuGtk-0.2.gir: libdbusmenu-gtk.la -DbusmenuGtk_0_2_gir_INCLUDES = \ +DbusmenuGtk$(VER)-0.4.gir: libdbusmenu-gtk$(VER).la +DbusmenuGtk_0_4_gir_INCLUDES = \ GObject-2.0 \ - Gtk-2.0 \ - Dbusmenu-Glib-0.2 -DbusmenuGtk_0_2_gir_CFLAGS = $(DBUSMENUGTK_CFLAGS) -I$(top_srcdir) -DbusmenuGtk_0_2_gir_LIBS = libdbusmenu-gtk.la -DbusmenuGtk_0_2_gir_FILES = $(addprefix $(srcdir)/, $(introspection_sources)) - -INTROSPECTION_GIRS += DbusmenuGtk-0.2.gir + $(GTKGIR) \ + Dbusmenu-0.4 +DbusmenuGtk_0_4_gir_CFLAGS = $(DBUSMENUGTK_CFLAGS) -I$(top_srcdir) +DbusmenuGtk_0_4_gir_LIBS = libdbusmenu-gtk$(VER).la +DbusmenuGtk_0_4_gir_FILES = $(addprefix $(srcdir)/, $(introspection_sources)) +DbusmenuGtk_0_4_gir_NAMESPACE = DbusmenuGtk$(VER) +DbusmenuGtk_0_4_gir_EXPORT_PACKAGES = dbusmenu-gtk$(VER)-0.4 + +# We duplicate these for the same reason as libdbusmenu_gtk3includedir above +DbusmenuGtk3_0_4_gir_INCLUDES = $(DbusmenuGtk_0_4_gir_INCLUDES) +DbusmenuGtk3_0_4_gir_CFLAGS = $(DbusmenuGtk_0_4_gir_CFLAGS) +DbusmenuGtk3_0_4_gir_LIBS = $(DbusmenuGtk_0_4_gir_LIBS) +DbusmenuGtk3_0_4_gir_FILES = $(DbusmenuGtk_0_4_gir_FILES) +DbusmenuGtk3_0_4_gir_NAMESPACE = $(DbusmenuGtk_0_4_gir_NAMESPACE) +DbusmenuGtk3_0_4_gir_EXPORT_PACKAGES = $(DbusmenuGtk_0_4_gir_EXPORT_PACKAGES) + +INTROSPECTION_GIRS += DbusmenuGtk$(VER)-0.4.gir girdir = $(datadir)/gir-1.0 gir_DATA = $(INTROSPECTION_GIRS) @@ -83,24 +147,24 @@ endif if HAVE_INTROSPECTION vapidir = $(datadir)/vala/vapi -vapi_DATA = DbusmenuGtk-0.2.vapi +vapi_DATA = DbusmenuGtk$(VER)-0.4.vapi -DbusmenuGtk-0.2.vapi: DbusmenuGtk-0.2.tmp.gir Makefile.am - $(VALA_API_GEN) --library=DbusmenuGtk-0.2 \ +DbusmenuGtk$(VER)-0.4.vapi: DbusmenuGtk$(VER)-0.4.tmp.gir Makefile.am + $(VALA_API_GEN) --library=DbusmenuGtk$(VER)-0.4 \ --pkg gdk-pixbuf-2.0 \ - --pkg gtk+-2.0 \ + --pkg $(GTKVALA) \ --pkg atk \ - --pkg Dbusmenu-Glib-0.2 \ + --pkg Dbusmenu-0.4 \ --vapidir=$(top_builddir)/libdbusmenu-glib \ $< -DbusmenuGtk-0.2.tmp.gir: DbusmenuGtk-0.2.gir +DbusmenuGtk$(VER)-0.4.tmp.gir: DbusmenuGtk$(VER)-0.4.gir $(SED) \ -e "s|GdkPixbuf.Pixbuf|Gdk.Pixbuf|g" \ -e "s|Atk.ImplementorIface|Atk.Implementor|g" \ $< > $@ -CLEANFILES += $(vapi_DATA) DbusmenuGtk-0.2.tmp.gir +CLEANFILES += $(vapi_DATA) DbusmenuGtk$(VER)-0.4.tmp.gir endif diff --git a/libdbusmenu-gtk/client.c b/libdbusmenu-gtk/client.c index 6970d59..1051f20 100644 --- a/libdbusmenu-gtk/client.c +++ b/libdbusmenu-gtk/client.c @@ -31,19 +31,23 @@ 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 */ -typedef struct _DbusmenuGtkClientPrivate DbusmenuGtkClientPrivate; struct _DbusmenuGtkClientPrivate { + GStrv old_themedirs; GtkAccelGroup * agroup; }; -#define DBUSMENU_GTKCLIENT_GET_PRIVATE(o) \ -(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_GTKCLIENT_TYPE, DbusmenuGtkClientPrivate)) +GHashTable * theme_dir_db = NULL; + +#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); @@ -55,13 +59,16 @@ 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); -static gboolean new_item_seperator (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client); +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); -static void process_visible (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * value); -static void process_sensitive (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * value); -static void image_property_handle (DbusmenuMenuitem * item, const gchar * property, const GValue * invalue, gpointer userdata); +static void process_visible (DbusmenuMenuitem * mi, GtkMenuItem * gmi, GVariant * value); +static void process_sensitive (DbusmenuMenuitem * mi, GtkMenuItem * gmi, GVariant * value); +static void image_property_handle (DbusmenuMenuitem * item, const gchar * property, GVariant * invalue, gpointer userdata); /* GObject Stuff */ G_DEFINE_TYPE (DbusmenuGtkClient, dbusmenu_gtkclient, DBUSMENU_TYPE_CLIENT); @@ -85,9 +92,28 @@ dbusmenu_gtkclient_class_init (DbusmenuGtkClientClass *klass) static void dbusmenu_gtkclient_init (DbusmenuGtkClient *self) { + self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), DBUSMENU_GTKCLIENT_TYPE, DbusmenuGtkClientPrivate); + DbusmenuGtkClientPrivate * priv = DBUSMENU_GTKCLIENT_GET_PRIVATE(self); priv->agroup = NULL; + 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); @@ -95,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; } @@ -110,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; } @@ -123,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 { @@ -218,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) { @@ -249,19 +426,20 @@ dbusmenu_gtkclient_set_accel_group (DbusmenuGtkClient * client, GtkAccelGroup * } priv->agroup = agroup; + g_object_ref(priv->agroup); return; } /** - dbusmenu_gtkclient_get_accel_group: - @client: Client to query for an accelerator group - - Gets the accel group for this client. - - Return value: Either a valid group or #NULL on error or - none set. -*/ + * 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) { @@ -274,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. */ @@ -283,10 +550,7 @@ static gboolean menu_pressed_cb (GtkMenuItem * gmi, DbusmenuMenuitem * mi) { if (gtk_menu_item_get_submenu(gmi) == NULL) { - GValue value = {0}; - g_value_init(&value, G_TYPE_INT); - g_value_set_int(&value, 0); - dbusmenu_menuitem_handle_event(mi, "clicked", &value, 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,9 +559,47 @@ 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, const GValue * value) +process_visible (DbusmenuMenuitem * mi, GtkMenuItem * gmi, GVariant * value) { gboolean val = TRUE; if (value != NULL) { @@ -314,7 +616,7 @@ process_visible (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * value) /* Process the sensitive property */ static void -process_sensitive (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * value) +process_sensitive (DbusmenuMenuitem * mi, GtkMenuItem * gmi, GVariant * value) { gboolean val = TRUE; if (value != NULL) { @@ -326,26 +628,21 @@ process_sensitive (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * valu /* Process the sensitive property */ static void -process_toggle_type (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * value) +process_toggle_type (DbusmenuMenuitem * mi, GtkMenuItem * gmi, GVariant * variant) { if (!IS_GENERICMENUITEM(gmi)) return; - if (value == NULL) return; + if (variant == NULL) return; GenericmenuitemCheckType type = GENERICMENUITEM_CHECK_TYPE_NONE; - GValue strvalue = {0}; - g_value_init(&strvalue, G_TYPE_STRING); - - if (value != NULL && g_value_transform(value, &strvalue)) { - const gchar * strval = g_value_get_string(&strvalue); + if (variant != NULL) { + const gchar * strval = g_variant_get_string(variant, NULL); if (!g_strcmp0(strval, DBUSMENU_MENUITEM_TOGGLE_CHECK)) { type = GENERICMENUITEM_CHECK_TYPE_CHECKBOX; } else if (!g_strcmp0(strval, DBUSMENU_MENUITEM_TOGGLE_RADIO)) { type = GENERICMENUITEM_CHECK_TYPE_RADIO; } - - g_value_unset(&strvalue); } genericmenuitem_set_check_type(GENERICMENUITEM(gmi), type); @@ -355,17 +652,14 @@ process_toggle_type (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * va /* Process the sensitive property */ static void -process_toggle_state (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * value) +process_toggle_state (DbusmenuMenuitem * mi, GtkMenuItem * gmi, GVariant * variant) { if (!IS_GENERICMENUITEM(gmi)) return; GenericmenuitemState state = GENERICMENUITEM_STATE_UNCHECKED; - GValue intvalue = {0}; - g_value_init(&intvalue, G_TYPE_INT); - - if (value != NULL && g_value_transform(value, &intvalue)) { - int val = g_value_get_int(&intvalue); + if (variant != NULL) { + int val = g_variant_get_int32(variant); if (val == DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED) { state = GENERICMENUITEM_STATE_CHECKED; @@ -378,21 +672,72 @@ process_toggle_state (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * v 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, GValue * value, 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, g_value_get_string(value)); + gtk_menu_item_set_label(gmi, variant == NULL ? NULL : g_variant_get_string(variant, NULL)); } else if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_VISIBLE)) { - process_visible(mi, gmi, value); + process_visible(mi, gmi, variant); } else if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_ENABLED)) { - process_sensitive(mi, gmi, value); + process_sensitive(mi, gmi, variant); } else if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE)) { - process_toggle_type(mi, gmi, value); + process_toggle_type(mi, gmi, variant); } else if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_TOGGLE_STATE)) { - process_toggle_state(mi, gmi, value); + 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,7 +746,7 @@ menu_prop_change_cb (DbusmenuMenuitem * mi, gchar * prop, GValue * value, GtkMen /* Special handler for the shortcut changing as we need to have the client for that one to get the accel group. */ static void -menu_shortcut_change_cb (DbusmenuMenuitem * mi, gchar * prop, GValue * value, DbusmenuGtkClient * client) +menu_shortcut_change_cb (DbusmenuMenuitem * mi, gchar * prop, GVariant * value, DbusmenuGtkClient * client) { if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_SHORTCUT)) { refresh_shortcut(client, mi); @@ -409,19 +754,6 @@ menu_shortcut_change_cb (DbusmenuMenuitem * mi, gchar * prop, GValue * value, Db 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. */ @@ -454,11 +786,17 @@ activate_helper (GtkMenuShell * shell) activate_helper(GTK_MENU_SHELL(parent)); } + /* This code is being commented out for GTK 3 because it + doesn't expose the right variables. We need to figure + this out as menus won't get grabs properly. + TODO FIXME HELP ARGHHHHHHHH */ +#if (HAVE_GTK3 == 0) if (!GTK_MENU_SHELL (parent)->active) { gtk_grab_add (parent); GTK_MENU_SHELL (parent)->have_grab = TRUE; GTK_MENU_SHELL (parent)->active = TRUE; } +#endif gtk_menu_shell_select_item(GTK_MENU_SHELL(parent), attach); } @@ -495,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) { @@ -518,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); @@ -533,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_value(item, DBUSMENU_MENUITEM_PROP_VISIBLE)); - process_sensitive(item, gmi, dbusmenu_menuitem_property_get_value(item, DBUSMENU_MENUITEM_PROP_ENABLED)); - process_toggle_type(item, gmi, dbusmenu_menuitem_property_get_value(item, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE)); - process_toggle_state(item, gmi, dbusmenu_menuitem_property_get_value(item, DBUSMENU_MENUITEM_PROP_TOGGLE_STATE)); + 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 */ @@ -562,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; } @@ -591,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); } } @@ -619,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) { @@ -638,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) { @@ -662,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) @@ -687,7 +1017,7 @@ dbusmenu_gtkclient_menuitem_get_submenu (DbusmenuGtkClient * client, DbusmenuMen /* The base type handler that builds a plain ol' GtkMenuItem to represent, well, the GtkMenuItem */ static gboolean -new_item_normal (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client) +new_item_normal (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client, gpointer user_data) { g_return_val_if_fail(DBUSMENU_IS_MENUITEM(newitem), FALSE); g_return_val_if_fail(DBUSMENU_IS_GTKCLIENT(client), FALSE); @@ -695,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; @@ -705,11 +1035,11 @@ new_item_normal (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, Dbusmenu image_property_handle(newitem, DBUSMENU_MENUITEM_PROP_ICON_NAME, - dbusmenu_menuitem_property_get_value(newitem, DBUSMENU_MENUITEM_PROP_ICON_NAME), + dbusmenu_menuitem_property_get_variant(newitem, DBUSMENU_MENUITEM_PROP_ICON_NAME), client); image_property_handle(newitem, DBUSMENU_MENUITEM_PROP_ICON_DATA, - dbusmenu_menuitem_property_get_value(newitem, DBUSMENU_MENUITEM_PROP_ICON_DATA), + dbusmenu_menuitem_property_get_variant(newitem, DBUSMENU_MENUITEM_PROP_ICON_DATA), client); g_signal_connect(G_OBJECT(newitem), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, @@ -722,7 +1052,7 @@ new_item_normal (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, Dbusmenu /* Type handler for the seperators where it builds a GtkSeparator to act as the GtkMenuItem */ static gboolean -new_item_seperator (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client) +new_item_seperator (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client, gpointer user_data) { g_return_val_if_fail(DBUSMENU_IS_MENUITEM(newitem), FALSE); g_return_val_if_fail(DBUSMENU_IS_GTKCLIENT(client), FALSE); @@ -740,10 +1070,33 @@ 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 -image_property_handle (DbusmenuMenuitem * item, const gchar * property, const GValue * invalue, gpointer userdata) +image_property_handle (DbusmenuMenuitem * item, const gchar * property, GVariant * variant, gpointer userdata) { /* We're only looking at these two properties here */ if (g_strcmp0(property, DBUSMENU_MENUITEM_PROP_ICON_NAME) != 0 && @@ -752,11 +1105,10 @@ image_property_handle (DbusmenuMenuitem * item, const gchar * property, const GV } const gchar * value = NULL; - - if (invalue != NULL && G_VALUE_TYPE(invalue) == G_TYPE_STRING) { - value = g_value_get_string(invalue); + if (variant != NULL) { + value = g_variant_get_string(variant, NULL); } - + if (value == NULL || value[0] == '\0') { /* This means that we're unsetting a value. */ /* Try to use the other one */ @@ -793,6 +1145,7 @@ image_property_handle (DbusmenuMenuitem * item, const gchar * property, const GV gtkimage = NULL; } else if (g_strcmp0(iconname, DBUSMENU_MENUITEM_ICON_NAME_BLANK) == 0) { gtkimage = gtk_image_new(); + 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. */ @@ -811,6 +1164,7 @@ image_property_handle (DbusmenuMenuitem * item, const gchar * property, const GV can just convert it to this name. */ if (gtkimage == NULL) { gtkimage = gtk_image_new_from_icon_name(finaliconname, GTK_ICON_SIZE_MENU); + set_use_fallback(gtkimage); } else { gtk_image_set_from_icon_name(GTK_IMAGE(gtkimage), finaliconname, GTK_ICON_SIZE_MENU); } @@ -848,6 +1202,9 @@ image_property_handle (DbusmenuMenuitem * item, const gchar * property, const GV } else { gtk_image_set_from_pixbuf(GTK_IMAGE(gtkimage), image); } + if (image) { + g_object_unref(image); + } } } diff --git a/libdbusmenu-gtk/client.h b/libdbusmenu-gtk/client.h index a7c96ee..75b59a0 100644 --- a/libdbusmenu-gtk/client.h +++ b/libdbusmenu-gtk/client.h @@ -41,16 +41,28 @@ G_BEGIN_DECLS #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)) +/** + * DBUSMENU_GTKCLIENT_SIGNAL_ROOT_CHANGED: + * + * String to attach to signal #DbusmenuClient::root-changed + */ #define DBUSMENU_GTKCLIENT_SIGNAL_ROOT_CHANGED DBUSMENU_CLIENT_SIGNAL_ROOT_CHANGED +typedef struct _DbusmenuGtkClientPrivate DbusmenuGtkClientPrivate; + /** - DbusmenuGtkClientClass: - @parent_class: #GtkMenuClass - @reserved1: Reserved for future use. - @reserved2: Reserved for future use. - @reserved3: Reserved for future use. - @reserved4: Reserved for future use. -*/ + * DbusmenuGtkClientClass: + * @parent_class: #GtkMenuClass + * @root_changed: Slot for signal #DbusmenuGtkClient::root-changed + * @reserved1: Reserved for future use. + * @reserved2: Reserved for future use. + * @reserved3: Reserved for future use. + * @reserved4: Reserved for future use. + * @reserved5: Reserved for future use. + * @reserved6: Reserved for future use. + * + * Functions and signal slots for using a #DbusmenuGtkClient + */ typedef struct _DbusmenuGtkClientClass DbusmenuGtkClientClass; struct _DbusmenuGtkClientClass { DbusmenuClientClass parent_class; @@ -58,20 +70,27 @@ struct _DbusmenuGtkClientClass { /* Signals */ void (*root_changed) (DbusmenuMenuitem * newroot); - /* Reserved */ + /*< Private >*/ void (*reserved1) (void); void (*reserved2) (void); void (*reserved3) (void); void (*reserved4) (void); + void (*reserved5) (void); + void (*reserved6) (void); }; /** - DbusmenuGtkClient: - @parent: #GtkMenu -*/ + * DbusmenuGtkClient: + * + * A subclass of #DbusmenuClient to add functionality with regarding + * building GTK items out of the abstract tree. + */ typedef struct _DbusmenuGtkClient DbusmenuGtkClient; struct _DbusmenuGtkClient { DbusmenuClient parent; + + /*< Private >*/ + DbusmenuGtkClientPrivate * priv; }; GType dbusmenu_gtkclient_get_type (void); @@ -85,10 +104,10 @@ GtkAccelGroup * dbusmenu_gtkclient_get_accel_group (DbusmenuGtkClient * client); 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 + SECTION:client + @short_description: A subclass of #DbusmenuClient adding GTK level features @stability: Unstable - @include: libdbusmenu-gtk/menu.h + @include: libdbusmenu-gtk/client.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 @@ -106,8 +125,6 @@ void dbusmenu_gtkclient_newitem_base (DbusmenuGtkClient * client, DbusmenuMenuit 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 diff --git a/libdbusmenu-gtk/dbusmenu-gtk.pc.in b/libdbusmenu-gtk/dbusmenu-gtk-0.4.pc.in index df4cb36..9a1b460 100644 --- a/libdbusmenu-gtk/dbusmenu-gtk.pc.in +++ b/libdbusmenu-gtk/dbusmenu-gtk-0.4.pc.in @@ -4,8 +4,8 @@ libdir=@libdir@ bindir=@bindir@ includedir=@includedir@ -Cflags: -I${includedir}/libdbusmenu-0.1 -Requires: dbus-glib-1 dbusmenu-glib +Cflags: -I${includedir}/libdbusmenu-0.4 +Requires: dbusmenu-glib-0.4 gdk-pixbuf-2.0 gtk+-2.0 Libs: -L${libdir} -ldbusmenu-gtk Name: libdbusmenu-gtk diff --git a/libdbusmenu-gtk/dbusmenu-gtk.h b/libdbusmenu-gtk/dbusmenu-gtk.h new file mode 100644 index 0000000..de63c61 --- /dev/null +++ b/libdbusmenu-gtk/dbusmenu-gtk.h @@ -0,0 +1,40 @@ +/* +A library to communicate a menu object set accross DBus and +track updates and maintain consistency. + +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_H__ +#define __DBUSMENU_GTK_H__ + +/* Start with the glib stuff */ +#include <libdbusmenu-glib/dbusmenu-glib.h> + +/* Now get the GTK stuff */ +#include <libdbusmenu-gtk/client.h> +#include <libdbusmenu-gtk/menu.h> +#include <libdbusmenu-gtk/menuitem.h> + +#endif /* __DBUSMENU_GLIB_H__ */ diff --git a/libdbusmenu-gtk/dbusmenu-gtk3-0.4.pc.in b/libdbusmenu-gtk/dbusmenu-gtk3-0.4.pc.in new file mode 100644 index 0000000..c297db3 --- /dev/null +++ b/libdbusmenu-gtk/dbusmenu-gtk3-0.4.pc.in @@ -0,0 +1,14 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +bindir=@bindir@ +includedir=@includedir@ + +Cflags: -I${includedir}/libdbusmenu-0.4 +Requires: dbusmenu-glib-0.4 gdk-pixbuf-2.0 gtk+-3.0 +Libs: -L${libdir} -ldbusmenu-gtk3 + +Name: libdbusmenu-gtk3 +Description: libdbusmenu-gtk3. +Version: @VERSION@ + diff --git a/libdbusmenu-gtk/genericmenuitem-enum-types.c.in b/libdbusmenu-gtk/genericmenuitem-enum-types.c.in new file mode 100644 index 0000000..8b2c046 --- /dev/null +++ b/libdbusmenu-gtk/genericmenuitem-enum-types.c.in @@ -0,0 +1,116 @@ +/*** BEGIN file-header ***/ +/* +Enums from the dbusmenu headers + +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/> +*/ + +#include "genericmenuitem-enum-types.h" + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +#include "@basename@" +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +/** + @enum_name@_get_type: + + Builds a GLib type for the #@EnumName@ enumeration. + + Return value: A unique #GType for the #@EnumName@ enum. +*/ +GType +@enum_name@_get_type (void) +{ + static GType etype = 0; + if (G_UNLIKELY(etype == 0)) { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL} + }; + + etype = g_@type@_register_static (g_intern_static_string("@EnumName@"), values); + } + + return etype; +} + +/** + @enum_name@_get_nick: + @value: The value of @EnumName@ to get the nick of + + Looks up in the enum table for the nick of @value. + + Return value: The nick for the given value or #NULL on error +*/ +const gchar * +@enum_name@_get_nick (@EnumName@ value) +{ + GEnumClass * class = G_ENUM_CLASS(g_type_class_ref(@enum_name@_get_type())); + g_return_val_if_fail(class != NULL, NULL); + + const gchar * ret = NULL; + GEnumValue * val = g_enum_get_value(class, value); + if (val != NULL) { + ret = val->value_nick; + } + + g_type_class_unref(class); + return ret; +} + +/** + @enum_name@_get_value_from_nick: + @nick: The enum nick to lookup + + Looks up in the enum table for the value of @nick. + + Return value: The value for the given @nick +*/ +@EnumName@ +@enum_name@_get_value_from_nick (const gchar * nick) +{ + GEnumClass * class = G_ENUM_CLASS(g_type_class_ref(@enum_name@_get_type())); + g_return_val_if_fail(class != NULL, 0); + + @EnumName@ ret = 0; + GEnumValue * val = g_enum_get_value_by_nick(class, nick); + if (val != NULL) { + ret = val->value; + } + + g_type_class_unref(class); + return ret; +} + + +/*** END value-tail ***/ diff --git a/libdbusmenu-gtk/genericmenuitem-enum-types.h.in b/libdbusmenu-gtk/genericmenuitem-enum-types.h.in new file mode 100644 index 0000000..5758438 --- /dev/null +++ b/libdbusmenu-gtk/genericmenuitem-enum-types.h.in @@ -0,0 +1,65 @@ +/*** BEGIN file-header ***/ +/* +Enums from the dbusmenu headers + +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_ENUM_TYPES_H__ +#define __DBUSMENU_ENUM_TYPES_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +/*** END file-header ***/ + +/*** BEGIN file-tail ***/ + +G_END_DECLS + +#endif /* __DBUSMENU_ENUM_TYPES_H__ */ +/*** END file-tail ***/ + +/*** BEGIN file-production ***/ +/* Enumerations from file: "@filename@" */ +#include "@basename@" + +/*** END file-production ***/ + +/*** BEGIN value-header ***/ + +GType @enum_name@_get_type (void) G_GNUC_CONST; +const gchar * @enum_name@_get_nick (@EnumName@ value) G_GNUC_CONST; +@EnumName@ @enum_name@_get_value_from_nick (const gchar * nick) G_GNUC_CONST; + +/** + DBUSMENU_TYPE_@ENUMSHORT@: + + Gets the #GType value for the type associated with the + #@EnumName@ enumerated type. +*/ +#define DBUSMENU_TYPE_@ENUMSHORT@ (@enum_name@_get_type()) + +/*** END value-header ***/ diff --git a/libdbusmenu-gtk/genericmenuitem.c b/libdbusmenu-gtk/genericmenuitem.c index 30b072f..5488f93 100644 --- a/libdbusmenu-gtk/genericmenuitem.c +++ b/libdbusmenu-gtk/genericmenuitem.c @@ -30,9 +30,11 @@ License version 3 and version 2.1 along with this program. If not, see #include "config.h" #endif +#include <gdk/gdk.h> + #include "genericmenuitem.h" -/** +/* GenericmenuitemPrivate: @check_type: What type of check we have, or none at all. @state: What the state of our check is. @@ -40,6 +42,7 @@ License version 3 and version 2.1 along with this program. If not, see struct _GenericmenuitemPrivate { GenericmenuitemCheckType check_type; GenericmenuitemState state; + GenericmenuitemDisposition disposition; }; /* Private macro */ @@ -51,7 +54,6 @@ static void genericmenuitem_class_init (GenericmenuitemClass *klass); static void genericmenuitem_init (Genericmenuitem *self); static void genericmenuitem_dispose (GObject *object); static void genericmenuitem_finalize (GObject *object); -static void draw_indicator (GtkCheckMenuItem *check_menu_item, GdkRectangle *area); static void set_label (GtkMenuItem * menu_item, const gchar * label); static const gchar * get_label (GtkMenuItem * menu_item); static void activate (GtkMenuItem * menu_item); @@ -59,8 +61,14 @@ static void activate (GtkMenuItem * menu_item); /* GObject stuff */ G_DEFINE_TYPE (Genericmenuitem, genericmenuitem, GTK_TYPE_CHECK_MENU_ITEM); -/* Globals */ +#if HAVE_GTK3 +static void draw_indicator (GtkCheckMenuItem *check_menu_item, cairo_t *cr); +static void (*parent_draw_indicator) (GtkCheckMenuItem *check_menu_item, cairo_t *cr) = NULL; +#else +static void draw_indicator (GtkCheckMenuItem *check_menu_item, GdkRectangle *area); static void (*parent_draw_indicator) (GtkCheckMenuItem *check_menu_item, GdkRectangle *area) = NULL; +#endif +static void (*parent_menuitem_activate) (GtkMenuItem * mi) = NULL; /* Initializing all of the classes. Most notably we're disabling the drawing of the check early. */ @@ -82,6 +90,7 @@ genericmenuitem_class_init (GenericmenuitemClass *klass) GtkMenuItemClass * menuitem_class = GTK_MENU_ITEM_CLASS (klass); menuitem_class->set_label = set_label; menuitem_class->get_label = get_label; + parent_menuitem_activate = menuitem_class->activate; menuitem_class->activate = activate; return; @@ -96,6 +105,7 @@ genericmenuitem_init (Genericmenuitem *self) self->priv->check_type = GENERICMENUITEM_CHECK_TYPE_NONE; self->priv->state = GENERICMENUITEM_STATE_UNCHECKED; + self->priv->disposition = GENERICMENUITEM_DISPOSITION_NORMAL; return; } @@ -121,6 +131,17 @@ genericmenuitem_finalize (GObject *object) /* Checks to see if we should be drawing a little box at all. If we should be, let's do that, otherwise we're going suppress the box drawing. */ +#if HAVE_GTK3 +static void +draw_indicator (GtkCheckMenuItem *check_menu_item, cairo_t *cr) +{ + Genericmenuitem * self = GENERICMENUITEM(check_menu_item); + if (self->priv->check_type != GENERICMENUITEM_CHECK_TYPE_NONE) { + parent_draw_indicator(check_menu_item, cr); + } + return; +} +#else static void draw_indicator (GtkCheckMenuItem *check_menu_item, GdkRectangle *area) { @@ -130,6 +151,7 @@ draw_indicator (GtkCheckMenuItem *check_menu_item, GdkRectangle *area) } return; } +#endif /* A small helper to look through the widgets in the box and find the one that is the label. */ @@ -147,18 +169,61 @@ set_label_helper (GtkWidget * widget, gpointer data) style. It should be considered for caching when optimizing. */ static gint -get_hpadding (GtkWidget * widget) +get_toggle_space (GtkWidget * widget) { gint padding = 0; - gtk_widget_style_get(widget, "horizontal-padding", &padding, NULL); + gtk_widget_style_get(widget, "toggle-spacing", &padding, NULL); return padding; } +/* Get the value to put in the span for the disposition */ +static gchar * +get_text_color (GenericmenuitemDisposition disposition, GtkWidget * widget) +{ + struct {const gchar * color_name; const gchar * default_color;} values[] = { + /* NORMAL */ { NULL, NULL}, + /* INFO */ { "informational-color", "blue"}, + /* WARN */ { "warning-color", "orange"}, + /* ALERT */ { "error-color", "red"} + }; + +#if GTK_CHECK_VERSION(3, 0, 0) + GtkStyleContext * context = gtk_widget_get_style_context(widget); + GdkRGBA color; + + if (gtk_style_context_lookup_color(context, values[disposition].color_name, &color)) { + return g_strdup_printf("rgb(%d, %d, %d)", (gint)(color.red * 255), (gint)(color.green * 255), (gint)(color.blue * 255)); + } +#endif + + return g_strdup(values[disposition].default_color); +} + /* Set the label on the item */ static void -set_label (GtkMenuItem * menu_item, const gchar * label) +set_label (GtkMenuItem * menu_item, const gchar * in_label) { - if (label == NULL) return; + if (in_label == NULL) return; + + /* Build a label that might include the colors of the disposition + so that it gets rendered in the menuitem. */ + gchar * local_label = NULL; + switch (GENERICMENUITEM(menu_item)->priv->disposition) { + case GENERICMENUITEM_DISPOSITION_NORMAL: + local_label = g_strdup(in_label); + break; + case GENERICMENUITEM_DISPOSITION_INFORMATIONAL: + case GENERICMENUITEM_DISPOSITION_WARNING: + case GENERICMENUITEM_DISPOSITION_ALERT: { + gchar * color = get_text_color(GENERICMENUITEM(menu_item)->priv->disposition, GTK_WIDGET(menu_item)); + local_label = g_markup_printf_escaped("<span fgcolor=\"%s\">%s</span>", color, in_label); + g_free(color); + break; + } + default: + g_warn_if_reached(); + break; + } GtkWidget * child = gtk_bin_get_child(GTK_BIN(menu_item)); GtkLabel * labelw = NULL; @@ -176,10 +241,10 @@ set_label (GtkMenuItem * menu_item, const gchar * label) /* We need to put the child into a new box and make the box the child of the menu item. Basically we're inserting a box in the middle. */ - GtkWidget * hbox = gtk_hbox_new(FALSE, 0); + GtkWidget * hbox = gtk_hbox_new(FALSE, get_toggle_space(GTK_WIDGET(menu_item))); g_object_ref(child); gtk_container_remove(GTK_CONTAINER(menu_item), child); - gtk_box_pack_start(GTK_BOX(hbox), child, FALSE, FALSE, get_hpadding(GTK_WIDGET(menu_item))); + gtk_box_pack_start(GTK_BOX(hbox), child, FALSE, FALSE, 0); gtk_container_add(GTK_CONTAINER(menu_item), hbox); gtk_widget_show(hbox); g_object_unref(child); @@ -193,10 +258,12 @@ set_label (GtkMenuItem * menu_item, const gchar * label) update the one that we already have. */ if (labelw == NULL) { /* Build it */ - labelw = GTK_LABEL(gtk_accel_label_new(label)); + labelw = GTK_LABEL(gtk_accel_label_new(local_label)); gtk_label_set_use_underline(GTK_LABEL(labelw), TRUE); + gtk_label_set_use_markup(GTK_LABEL(labelw), TRUE); gtk_misc_set_alignment(GTK_MISC(labelw), 0.0, 0.5); gtk_accel_label_set_accel_widget(GTK_ACCEL_LABEL(labelw), GTK_WIDGET(menu_item)); + gtk_label_set_markup_with_mnemonic(labelw, local_label); gtk_widget_show(GTK_WIDGET(labelw)); /* Check to see if it needs to be in the bin for this @@ -204,17 +271,17 @@ set_label (GtkMenuItem * menu_item, const gchar * label) if (child == NULL) { gtk_container_add(GTK_CONTAINER(menu_item), GTK_WIDGET(labelw)); } else { - gtk_box_pack_end(GTK_BOX(child), GTK_WIDGET(labelw), TRUE, TRUE, get_hpadding(GTK_WIDGET(menu_item))); + gtk_box_pack_end(GTK_BOX(child), GTK_WIDGET(labelw), TRUE, TRUE, 0); } } else { /* Oh, just an update. No biggie. */ - if (!g_strcmp0(label, gtk_label_get_label(labelw))) { + if (!g_strcmp0(local_label, gtk_label_get_label(labelw))) { /* The only reason to suppress the update is if we had a label and the value was the same as the one we're getting in. */ suppress_update = TRUE; } else { - gtk_label_set_label(labelw, label); + gtk_label_set_markup_with_mnemonic(labelw, local_label); } } @@ -223,6 +290,12 @@ set_label (GtkMenuItem * menu_item, const gchar * label) g_object_notify(G_OBJECT(menu_item), "label"); } + /* Clean up this */ + if (local_label != NULL) { + g_free(local_label); + local_label = NULL; + } + return; } @@ -260,12 +333,12 @@ activate (GtkMenuItem * menu_item) } /** - genericmenuitem_set_check_type: - @item: #Genericmenuitem to set the type on - @check_type: Which type of check should be displayed - - This function changes the type of the checkmark that - appears in the left hand gutter for the menuitem. + * genericmenuitem_set_check_type: + * @item: #Genericmenuitem to set the type on + * @check_type: Which type of check should be displayed + * + * This function changes the type of the checkmark that + * appears in the left hand gutter for the menuitem. */ void genericmenuitem_set_check_type (Genericmenuitem * item, GenericmenuitemCheckType check_type) @@ -275,7 +348,6 @@ genericmenuitem_set_check_type (Genericmenuitem * item, GenericmenuitemCheckType } item->priv->check_type = check_type; - GValue value = {0}; switch (item->priv->check_type) { case GENERICMENUITEM_CHECK_TYPE_NONE: @@ -284,14 +356,10 @@ genericmenuitem_set_check_type (Genericmenuitem * item, GenericmenuitemCheckType check on the item. */ break; case GENERICMENUITEM_CHECK_TYPE_CHECKBOX: - g_value_init(&value, G_TYPE_BOOLEAN); - g_value_set_boolean(&value, FALSE); - g_object_set_property(G_OBJECT(item), "draw-as-radio", &value); + gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(item), FALSE); break; case GENERICMENUITEM_CHECK_TYPE_RADIO: - g_value_init(&value, G_TYPE_BOOLEAN); - g_value_set_boolean(&value, TRUE); - g_object_set_property(G_OBJECT(item), "draw-as-radio", &value); + gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(item), TRUE); break; default: g_warning("Generic Menuitem invalid check type: %d", check_type); @@ -304,14 +372,14 @@ genericmenuitem_set_check_type (Genericmenuitem * item, GenericmenuitemCheckType } /** - genericmenuitem_set_state: - @item: #Genericmenuitem to set the type on - @check_type: What is the state of the check - - Sets the state of the check in the menu item. It does - not require, but isn't really useful if the type of - check that the menuitem is set to #GENERICMENUITEM_CHECK_TYPE_NONE. -*/ + * genericmenuitem_set_state: + * @item: #Genericmenuitem to set the type on + * @check_type: What is the state of the check + * + * Sets the state of the check in the menu item. It does + * not require, but isn't really useful if the type of + * check that the menuitem is set to #GENERICMENUITEM_CHECK_TYPE_NONE. + */ void genericmenuitem_set_state (Genericmenuitem * item, GenericmenuitemState state) { @@ -322,38 +390,32 @@ genericmenuitem_set_state (Genericmenuitem * item, GenericmenuitemState state) item->priv->state = state; GtkCheckMenuItem * check = GTK_CHECK_MENU_ITEM(item); - - gboolean old_active = check->active; - gboolean old_inconsist = check->inconsistent; + gboolean goal_active = FALSE; switch (item->priv->state) { case GENERICMENUITEM_STATE_UNCHECKED: - check->active = FALSE; - check->inconsistent = FALSE; + goal_active = FALSE; + gtk_check_menu_item_set_inconsistent (check, FALSE); break; case GENERICMENUITEM_STATE_CHECKED: - check->active = TRUE; - check->inconsistent = FALSE; + goal_active = TRUE; + gtk_check_menu_item_set_inconsistent (check, FALSE); break; case GENERICMENUITEM_STATE_INDETERMINATE: - check->active = TRUE; - check->inconsistent = TRUE; + goal_active = TRUE; + gtk_check_menu_item_set_inconsistent (check, TRUE); break; default: g_warning("Generic Menuitem invalid check state: %d", state); return; } - if (old_active != check->active) { - g_object_notify(G_OBJECT(item), "active"); - } - - if (old_inconsist != check->inconsistent) { - g_object_notify(G_OBJECT(item), "inconsistent"); + if (goal_active != gtk_check_menu_item_get_active(check)) { + if (parent_menuitem_activate != NULL) { + parent_menuitem_activate(GTK_MENU_ITEM(check)); + } } - gtk_widget_queue_draw(GTK_WIDGET(item)); - return; } @@ -370,11 +432,11 @@ set_image_helper (GtkWidget * widget, gpointer data) } /** - genericmenuitem_set_image: - @item: A #Genericmenuitem - @image: The image to set as the image of @item - - Sets the image of the menu item. + * genericmenuitem_set_image: + * @item: A #Genericmenuitem + * @image: The image to set as the image of @item + * + * Sets the image of the menu item. */ void genericmenuitem_set_image (Genericmenuitem * menu_item, GtkWidget * image) @@ -394,10 +456,10 @@ genericmenuitem_set_image (Genericmenuitem * menu_item, GtkWidget * image) /* We need to put the child into a new box and make the box the child of the menu item. Basically we're inserting a box in the middle. */ - GtkWidget * hbox = gtk_hbox_new(FALSE, 0); + GtkWidget * hbox = gtk_hbox_new(FALSE, get_toggle_space(GTK_WIDGET(menu_item))); g_object_ref(child); gtk_container_remove(GTK_CONTAINER(menu_item), child); - gtk_box_pack_end(GTK_BOX(hbox), child, TRUE, TRUE, get_hpadding(GTK_WIDGET(menu_item))); + gtk_box_pack_end(GTK_BOX(hbox), child, TRUE, TRUE, 0); gtk_container_add(GTK_CONTAINER(menu_item), hbox); gtk_widget_show(hbox); g_object_unref(child); @@ -422,7 +484,7 @@ genericmenuitem_set_image (Genericmenuitem * menu_item, GtkWidget * image) if (child == NULL) { gtk_container_add(GTK_CONTAINER(menu_item), GTK_WIDGET(image)); } else { - gtk_box_pack_start(GTK_BOX(child), GTK_WIDGET(image), FALSE, FALSE, get_hpadding(GTK_WIDGET(menu_item))); + gtk_box_pack_start(GTK_BOX(child), GTK_WIDGET(image), FALSE, FALSE, 0); } gtk_widget_show(image); @@ -432,13 +494,13 @@ genericmenuitem_set_image (Genericmenuitem * menu_item, GtkWidget * image) } /** - genericmenuitem_get_image: - @item: A #Genericmenuitem - - Returns the image if there is one. - - Return value: A pointer to the image of the item or #NULL - if there isn't one. + * genericmenuitem_get_image: + * @item: A #Genericmenuitem + * + * Returns the image if there is one. + * + * Return value: (transfer none): A pointer to the image of the item or #NULL + * if there isn't one. */ GtkWidget * genericmenuitem_get_image (Genericmenuitem * menu_item) @@ -459,3 +521,41 @@ genericmenuitem_get_image (Genericmenuitem * menu_item) return imagew; } + +/** + * genericmenuitem_set_disposition: + * @item: A #Genericmenuitem + * @disposition: The disposition of the item + * + * Sets the disposition of the menuitem. + */ +void +genericmenuitem_set_disposition (Genericmenuitem * item, GenericmenuitemDisposition disposition) +{ + g_return_if_fail(IS_GENERICMENUITEM(item)); + + if (item->priv->disposition == disposition) + return; + + item->priv->disposition = disposition; + + set_label(GTK_MENU_ITEM(item), get_label(GTK_MENU_ITEM(item))); + + return; +} + +/** + * genericmenuitem_get_disposition: + * @item: A #Genericmenuitem + * + * Gets the disposition of the menuitem. + * + * Return value: The disposition of the menuitem. + */ +GenericmenuitemDisposition +genericmenuitem_get_disposition (Genericmenuitem * item) +{ + g_return_val_if_fail(IS_GENERICMENUITEM(item), GENERICMENUITEM_DISPOSITION_NORMAL); + + return item->priv->disposition; +} diff --git a/libdbusmenu-gtk/genericmenuitem.h b/libdbusmenu-gtk/genericmenuitem.h index 3c4af0c..0b7df55 100644 --- a/libdbusmenu-gtk/genericmenuitem.h +++ b/libdbusmenu-gtk/genericmenuitem.h @@ -42,13 +42,11 @@ G_BEGIN_DECLS #define IS_GENERICMENUITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GENERICMENUITEM_TYPE)) #define GENERICMENUITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GENERICMENUITEM_TYPE, GenericmenuitemClass)) -typedef struct _Genericmenuitem Genericmenuitem; -typedef struct _GenericmenuitemClass GenericmenuitemClass; -typedef struct _GenericmenuitemPrivate GenericmenuitemPrivate; -typedef enum _GenericmenuitemCheckType GenericmenuitemCheckType; -typedef enum _GenericmenuitemState GenericmenuitemState; +typedef struct _Genericmenuitem Genericmenuitem; +typedef struct _GenericmenuitemClass GenericmenuitemClass; +typedef struct _GenericmenuitemPrivate GenericmenuitemPrivate; -/** +/* GenericmenuitemClass: @parent_class: Our parent #GtkCheckMenuItemClass */ @@ -56,7 +54,7 @@ struct _GenericmenuitemClass { GtkCheckMenuItemClass parent_class; }; -/** +/* Genericmenuitem: @parent: Our parent #GtkCheckMenuItem */ @@ -65,26 +63,61 @@ struct _Genericmenuitem { GenericmenuitemPrivate * priv; }; -enum _GenericmenuitemCheckType { +/** + * GenericmenuitemCheckType: + * @GENERICMENUITEM_CHECK_TYPE_NONE: No check + * @GENERICMENUITEM_CHECK_TYPE_CHECKBOX: Nice little check + * @GENERICMENUITEM_CHECK_TYPE_RADIO: Radio button + * + * Tracks what type of checkmark should be shown on the item + */ +typedef enum { /*< prefix=GENERICMENUITEM_CHECK_TYPE >*/ GENERICMENUITEM_CHECK_TYPE_NONE, GENERICMENUITEM_CHECK_TYPE_CHECKBOX, GENERICMENUITEM_CHECK_TYPE_RADIO -}; +} GenericmenuitemCheckType; -enum _GenericmenuitemState { +/** + * GenericmenuitemState: + * @GENERICMENUITEM_STATE_UNCHECKED: No check visisble + * @GENERICMENUITEM_STATE_CHECKED: Check visible + * @GENERICMENUITEM_STATE_INDETERMINATE: We have no clue + * + * What the state of the check mark on the item is + */ +typedef enum { /*< prefix=GENERICMENUITEM_STATE >*/ GENERICMENUITEM_STATE_UNCHECKED, GENERICMENUITEM_STATE_CHECKED, GENERICMENUITEM_STATE_INDETERMINATE -}; +} GenericmenuitemState; -GType genericmenuitem_get_type (void); -void genericmenuitem_set_check_type (Genericmenuitem * item, - GenericmenuitemCheckType check_type); -void genericmenuitem_set_state (Genericmenuitem * item, - GenericmenuitemState state); -void genericmenuitem_set_image (Genericmenuitem * item, - GtkWidget * image); -GtkWidget * genericmenuitem_get_image (Genericmenuitem * item); +/** + * GenericmenuitemDisposition: + * @GENERICMENUITEM_DISPOSITION_NORMAL: Normal state + * @GENERICMENUITEM_DISPOSITION_INFORMATIONAL: Item is informational + * @GENERICMENUITEM_DISPOSITION_WARNING: Oh, you should watch out for this one + * @GENERICMENUITEM_DISPOSITION_ALERT: Boom! + * + * What the disposition of the menu item is + */ +typedef enum { /*< prefix=GENERICMENUITEM_DISPOSITION >*/ + GENERICMENUITEM_DISPOSITION_NORMAL, + GENERICMENUITEM_DISPOSITION_INFORMATIONAL, + GENERICMENUITEM_DISPOSITION_WARNING, + GENERICMENUITEM_DISPOSITION_ALERT +} GenericmenuitemDisposition; + +GType genericmenuitem_get_type (void); +void genericmenuitem_set_check_type (Genericmenuitem * item, + GenericmenuitemCheckType check_type); +void genericmenuitem_set_state (Genericmenuitem * item, + GenericmenuitemState state); +void genericmenuitem_set_image (Genericmenuitem * item, + GtkWidget * image); +GtkWidget * genericmenuitem_get_image (Genericmenuitem * item); +void genericmenuitem_set_disposition (Genericmenuitem * item, + GenericmenuitemDisposition disposition); +GenericmenuitemDisposition genericmenuitem_get_disposition (Genericmenuitem * item); G_END_DECLS diff --git a/libdbusmenu-gtk/menu.c b/libdbusmenu-gtk/menu.c index 4fa546b..236a596 100644 --- a/libdbusmenu-gtk/menu.c +++ b/libdbusmenu-gtk/menu.c @@ -44,16 +44,15 @@ enum { }; /* Private */ -typedef struct _DbusmenuGtkMenuPrivate DbusmenuGtkMenuPrivate; struct _DbusmenuGtkMenuPrivate { DbusmenuGtkClient * client; + DbusmenuMenuitem * root; gchar * dbus_object; gchar * dbus_name; }; -#define DBUSMENU_GTKMENU_GET_PRIVATE(o) \ -(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_GTKMENU_TYPE, DbusmenuGtkMenuPrivate)) +#define DBUSMENU_GTKMENU_GET_PRIVATE(o) (DBUSMENU_GTKMENU(o)->priv) /* Prototypes */ static void dbusmenu_gtkmenu_class_init (DbusmenuGtkMenuClass *klass); @@ -65,6 +64,8 @@ static void get_property (GObject * obj, guint id, GValue * value, GParamSpec * /* Internal */ static void build_client (DbusmenuGtkMenu * self); static void child_realized (DbusmenuMenuitem * child, gpointer userdata); +static void remove_child_signals (gpointer data, gpointer user_data); +static void root_changed (DbusmenuGtkClient * client, DbusmenuMenuitem * newroot, DbusmenuGtkMenu * menu); /* GObject Stuff */ G_DEFINE_TYPE (DbusmenuGtkMenu, dbusmenu_gtkmenu, GTK_TYPE_MENU); @@ -102,7 +103,7 @@ menu_focus_cb(DbusmenuGtkMenu * menu, gpointer userdata) if (priv->client != NULL) { /* TODO: We should stop the display of the menu until the about to show call returns. */ - dbusmenu_client_send_about_to_show(DBUSMENU_CLIENT(priv->client), 0, NULL, NULL); + dbusmenu_menuitem_send_about_to_show(priv->root, NULL, NULL); } return; } @@ -110,6 +111,8 @@ menu_focus_cb(DbusmenuGtkMenu * menu, gpointer userdata) static void dbusmenu_gtkmenu_init (DbusmenuGtkMenu *self) { + self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), DBUSMENU_GTKMENU_TYPE, DbusmenuGtkMenuPrivate); + DbusmenuGtkMenuPrivate * priv = DBUSMENU_GTKMENU_GET_PRIVATE(self); priv->client = NULL; @@ -127,6 +130,12 @@ dbusmenu_gtkmenu_dispose (GObject *object) { DbusmenuGtkMenuPrivate * priv = DBUSMENU_GTKMENU_GET_PRIVATE(object); + /* Remove signals from the root */ + if (priv->root != NULL) { + /* This will clear the root */ + root_changed(priv->client, NULL, DBUSMENU_GTKMENU(object)); + } + if (priv->client != NULL) { g_object_unref(G_OBJECT(priv->client)); priv->client = NULL; @@ -237,7 +246,7 @@ root_child_added (DbusmenuMenuitem * root, DbusmenuMenuitem * child, guint posit GtkMenuItem * mi = dbusmenu_gtkclient_menuitem_get(priv->client, child); if (mi != NULL) { GtkWidget * item = GTK_WIDGET(mi); - gtk_menu_insert(GTK_MENU(menu), item, dbusmenu_menuitem_get_position_realized(child, root)); + gtk_menu_shell_insert(GTK_MENU_SHELL(menu), item, dbusmenu_menuitem_get_position_realized(child, root)); #ifdef MASSIVEDEBUGGING menu_pos_t menu_pos; menu_pos.mi = mi; @@ -271,6 +280,10 @@ root_child_delete (DbusmenuMenuitem * root, DbusmenuMenuitem * child, DbusmenuGt #ifdef MASSIVEDEBUGGING g_debug("Root child deleted"); #endif + + /* Remove signal for realized */ + remove_child_signals(child, menu); + DbusmenuGtkMenuPrivate * priv = DBUSMENU_GTKMENU_GET_PRIVATE(menu); GtkWidget * item = GTK_WIDGET(dbusmenu_gtkclient_menuitem_get(priv->client, child)); if (item != NULL) { @@ -299,7 +312,7 @@ child_realized (DbusmenuMenuitem * child, gpointer userdata) GtkWidget * child_widget = GTK_WIDGET(dbusmenu_gtkclient_menuitem_get(priv->client, child)); if (child_widget != NULL) { - gtk_menu_append(menu, child_widget); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), child_widget); gtk_menu_reorder_child(GTK_MENU(menu), child_widget, dbusmenu_menuitem_get_position_realized(child, dbusmenu_client_get_root(DBUSMENU_CLIENT(priv->client)))); } else { g_warning("Child is realized, but doesn't have a GTK Widget!"); @@ -308,15 +321,55 @@ child_realized (DbusmenuMenuitem * child, gpointer userdata) return; } +/* Remove any signals we attached to children -- just realized right now */ +static void +remove_child_signals (gpointer data, gpointer user_data) +{ + g_signal_handlers_disconnect_by_func(G_OBJECT(data), child_realized, user_data); + return; +} + +/* Handler for all of the menu items on a root change to ensure that + the menus are hidden before we start going and deleting things. */ +static void +popdown_all (DbusmenuMenuitem * mi, gpointer user_data) +{ + GtkMenu * menu = dbusmenu_gtkclient_menuitem_get_submenu(DBUSMENU_GTKCLIENT(user_data), mi); + if (menu != NULL) { + gtk_menu_popdown(menu); + } + return; +} + /* When the root menuitem changes we need to resetup things so that we're back in the game. */ static void root_changed (DbusmenuGtkClient * client, DbusmenuMenuitem * newroot, DbusmenuGtkMenu * menu) { + DbusmenuGtkMenuPrivate * priv = DBUSMENU_GTKMENU_GET_PRIVATE(menu); + + /* Clear out our interest in the old root */ + if (priv->root != NULL) { + GList * children = dbusmenu_menuitem_get_children(priv->root); + g_list_foreach(children, remove_child_signals, menu); + + g_signal_handlers_disconnect_by_func(G_OBJECT(priv->root), root_child_added, menu); + g_signal_handlers_disconnect_by_func(G_OBJECT(priv->root), root_child_moved, menu); + g_signal_handlers_disconnect_by_func(G_OBJECT(priv->root), root_child_delete, menu); + + dbusmenu_menuitem_foreach(priv->root, popdown_all, client); + + g_object_unref(priv->root); + priv->root = NULL; + } + if (newroot == NULL) { gtk_widget_hide(GTK_WIDGET(menu)); return; } + priv->root = newroot; + g_object_ref(priv->root); + g_signal_connect(G_OBJECT(newroot), DBUSMENU_MENUITEM_SIGNAL_CHILD_ADDED, G_CALLBACK(root_child_added), menu); g_signal_connect(G_OBJECT(newroot), DBUSMENU_MENUITEM_SIGNAL_CHILD_MOVED, G_CALLBACK(root_child_moved), menu); g_signal_connect(G_OBJECT(newroot), DBUSMENU_MENUITEM_SIGNAL_CHILD_REMOVED, G_CALLBACK(root_child_delete), menu); @@ -359,15 +412,15 @@ build_client (DbusmenuGtkMenu * self) /* Public API */ /** - dbusmenu_gtkmenu_new: - @dbus_name: Name of the #DbusmenuServer on DBus - @dbus_name: Name of the object on the #DbusmenuServer - - Creates a new #DbusmenuGtkMenu object and creates a #DbusmenuClient - that connects across DBus to a #DbusmenuServer. - - Return value: A new #DbusmenuGtkMenu sync'd with a server -*/ + * dbusmenu_gtkmenu_new: + * @dbus_name: Name of the #DbusmenuServer on DBus + * @dbus_object: Name of the object on the #DbusmenuServer + * + * Creates a new #DbusmenuGtkMenu object and creates a #DbusmenuClient + * that connects across DBus to a #DbusmenuServer. + * + * Return value: A new #DbusmenuGtkMenu sync'd with a server + */ DbusmenuGtkMenu * dbusmenu_gtkmenu_new (gchar * dbus_name, gchar * dbus_object) { @@ -378,14 +431,14 @@ dbusmenu_gtkmenu_new (gchar * dbus_name, gchar * dbus_object) } /** - 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. -*/ + * 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: (transfer none): A valid #DbusmenuGtkClient or NULL on error. + */ DbusmenuGtkClient * dbusmenu_gtkmenu_get_client (DbusmenuGtkMenu * menu) { diff --git a/libdbusmenu-gtk/menu.h b/libdbusmenu-gtk/menu.h index 5147d30..c413ab8 100644 --- a/libdbusmenu-gtk/menu.h +++ b/libdbusmenu-gtk/menu.h @@ -42,32 +42,46 @@ G_BEGIN_DECLS #define DBUSMENU_IS_GTKMENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DBUSMENU_GTKMENU_TYPE)) #define DBUSMENU_GTKMENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DBUSMENU_GTKMENU_TYPE, DbusmenuGtkMenuClass)) +typedef struct _DbusmenuGtkMenuPrivate DbusmenuGtkMenuPrivate; + /** - DbusmenuGtkMenuClass: - @parent_class: #GtkMenuClass - @reserved1: Reserved for future use. - @reserved2: Reserved for future use. - @reserved3: Reserved for future use. - @reserved4: Reserved for future use. -*/ + * DbusmenuGtkMenuClass: + * @parent_class: #GtkMenuClass + * @reserved1: Reserved for future use. + * @reserved2: Reserved for future use. + * @reserved3: Reserved for future use. + * @reserved4: Reserved for future use. + * @reserved5: Reserved for future use. + * @reserved6: Reserved for future use. + * + * All of the subclassable functions and signal slots for a + * #DbusmenuGtkMenu. + */ typedef struct _DbusmenuGtkMenuClass DbusmenuGtkMenuClass; struct _DbusmenuGtkMenuClass { GtkMenuClass parent_class; - /* Reserved */ + /*< Private >*/ void (*reserved1) (void); void (*reserved2) (void); void (*reserved3) (void); void (*reserved4) (void); + void (*reserved5) (void); + void (*reserved6) (void); }; /** - DbusmenuGtkMenu: - @parent: #GtkMenu -*/ + * DbusmenuGtkMenu: + * + * A #GtkMenu that is built using an abstract tree built from + * a #DbusmenuGtkClient. + */ typedef struct _DbusmenuGtkMenu DbusmenuGtkMenu; struct _DbusmenuGtkMenu { GtkMenu parent; + + /*< Private >*/ + DbusmenuGtkMenuPrivate * priv; }; GType dbusmenu_gtkmenu_get_type (void); @@ -75,7 +89,7 @@ DbusmenuGtkMenu * dbusmenu_gtkmenu_new (gchar * dbus_name, gchar * dbus_object); DbusmenuGtkClient * dbusmenu_gtkmenu_get_client (DbusmenuGtkMenu * menu); /** - SECTION:gtkmenu + SECTION:menu @short_description: A GTK Menu Object that syncronizes over DBus @stability: Unstable @include: libdbusmenu-gtk/menu.h @@ -96,8 +110,6 @@ DbusmenuGtkClient * dbusmenu_gtkmenu_get_client (DbusmenuGtkMenu * menu); 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 diff --git a/libdbusmenu-gtk/menuitem.c b/libdbusmenu-gtk/menuitem.c index 5846aa7..0f511bc 100644 --- a/libdbusmenu-gtk/menuitem.c +++ b/libdbusmenu-gtk/menuitem.c @@ -29,20 +29,19 @@ License version 3 and version 2.1 along with this program. If not, see #include "menuitem.h" #include <gdk/gdk.h> #include <gtk/gtk.h> -#include <dbus/dbus-gtype-specialized.h> /** - dbusmenu_menuitem_property_set_image: - @menuitem: The #DbusmenuMenuitem to set the property on. - @property: Name of the property to set. - @data: The image to place on the property. - - This function takes the pixbuf that is stored in @data and - turns it into a base64 encoded PNG so that it can be placed - onto a standard #DbusmenuMenuitem property. - - Return value: Whether the function was able to set the property - or not. + * dbusmenu_menuitem_property_set_image: + * @menuitem: The #DbusmenuMenuitem to set the property on. + * @property: Name of the property to set. + * @data: The image to place on the property. + * + * This function takes the pixbuf that is stored in @data and + * turns it into a base64 encoded PNG so that it can be placed + * onto a standard #DbusmenuMenuitem property. + * + * Return value: Whether the function was able to set the property + * or not. */ gboolean dbusmenu_menuitem_property_set_image (DbusmenuMenuitem * menuitem, const gchar * property, const GdkPixbuf * data) @@ -78,17 +77,17 @@ dbusmenu_menuitem_property_set_image (DbusmenuMenuitem * menuitem, const gchar * } /** - dbusmenu_menuitem_property_get_image: - @menuitem: The #DbusmenuMenuite to look for the property on - @property: The name of the property to look for. - - This function looks on the menu item for a property by the - name of @property. If one exists it tries to turn it into - a #GdkPixbuf. It assumes that the property is a base64 encoded - PNG file like the one created by #dbusmenu_menuite_property_set_image. - - Return value: A pixbuf or #NULL to signal error. -*/ + * dbusmenu_menuitem_property_get_image: + * @menuitem: The #DbusmenuMenuitem to look for the property on + * @property: The name of the property to look for. + * + * This function looks on the menu item for a property by the + * name of @property. If one exists it tries to turn it into + * a #GdkPixbuf. It assumes that the property is a base64 encoded + * PNG file like the one created by #dbusmenu_menuite_property_set_image. + * + * Return value: (transfer full): A pixbuf or #NULL to signal error. + */ GdkPixbuf * dbusmenu_menuitem_property_get_image (DbusmenuMenuitem * menuitem, const gchar * property) { @@ -120,28 +119,23 @@ dbusmenu_menuitem_property_get_image (DbusmenuMenuitem * menuitem, const gchar * g_error_free(error); } - error = NULL; - g_input_stream_close(input, NULL, &error); - if (error != NULL) { - g_warning("Unable to close input stream: %s", error->message); - g_error_free(error); - } + g_object_unref(input); g_free(icondata); return icon; } /** - dbusmenu_menuitem_property_set_shortcut_string: - @menuitem: The #DbusmenuMenuitem to set the shortcut on - @shortcut: String describing the shortcut - - This function takes a GTK shortcut string as defined in - #gtk_accelerator_parse and turns that into the information - required to send it over DBusmenu. - - Return value: Whether it was successful at setting the property. -*/ + * dbusmenu_menuitem_property_set_shortcut_string: + * @menuitem: The #DbusmenuMenuitem to set the shortcut on + * @shortcut: String describing the shortcut + * + * This function takes a GTK shortcut string as defined in + * #gtk_accelerator_parse and turns that into the information + * required to send it over DBusmenu. + * + * Return value: Whether it was successful at setting the property. + */ gboolean dbusmenu_menuitem_property_set_shortcut_string (DbusmenuMenuitem * menuitem, const gchar * shortcut) { @@ -162,66 +156,49 @@ dbusmenu_menuitem_property_set_shortcut_string (DbusmenuMenuitem * menuitem, con } /** - dbusmenu_menuitem_property_set_shortcut: - @menuitem: The #DbusmenuMenuitem to set the shortcut on - @key: The keycode of the key to send - @modifier: A bitmask of modifiers used to activate the item - - Takes the modifer described by @key and @modifier and places that into - the format sending across Dbus for shortcuts. - - Return value: Whether it was successful at setting the property. -*/ + * dbusmenu_menuitem_property_set_shortcut: + * @menuitem: The #DbusmenuMenuitem to set the shortcut on + * @key: The keycode of the key to send + * @modifier: A bitmask of modifiers used to activate the item + * + * Takes the modifer described by @key and @modifier and places that into + * the format sending across Dbus for shortcuts. + * + * Return value: Whether it was successful at setting the property. + */ gboolean dbusmenu_menuitem_property_set_shortcut (DbusmenuMenuitem * menuitem, guint key, GdkModifierType modifier) { g_return_val_if_fail(DBUSMENU_IS_MENUITEM(menuitem), FALSE); g_return_val_if_fail(gtk_accelerator_valid(key, modifier), FALSE); - GArray * array = g_array_sized_new(TRUE, TRUE, sizeof(gchar *), 4); /* Four seems like the max we'd need, plus it's still small */ + const gchar * keyname = gdk_keyval_name(key); + g_return_val_if_fail(keyname != NULL, FALSE); - const gchar * control_val = DBUSMENU_MENUITEM_SHORTCUT_CONTROL; - const gchar * alt_val = DBUSMENU_MENUITEM_SHORTCUT_ALT; - const gchar * shift_val = DBUSMENU_MENUITEM_SHORTCUT_SHIFT; - const gchar * super_val = DBUSMENU_MENUITEM_SHORTCUT_SUPER; + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); if (modifier & GDK_CONTROL_MASK) { - g_array_append_val(array, control_val); + g_variant_builder_add(&builder, "s", DBUSMENU_MENUITEM_SHORTCUT_CONTROL); } if (modifier & GDK_MOD1_MASK) { - g_array_append_val(array, alt_val); + g_variant_builder_add(&builder, "s", DBUSMENU_MENUITEM_SHORTCUT_ALT); } if (modifier & GDK_SHIFT_MASK) { - g_array_append_val(array, shift_val); + g_variant_builder_add(&builder, "s", DBUSMENU_MENUITEM_SHORTCUT_SHIFT); } if (modifier & GDK_SUPER_MASK) { - g_array_append_val(array, super_val); + g_variant_builder_add(&builder, "s", DBUSMENU_MENUITEM_SHORTCUT_SUPER); } - const gchar * keyname = gdk_keyval_name(key); - g_array_append_val(array, keyname); - - GType type = dbus_g_type_get_collection("GPtrArray", G_TYPE_STRV); - GPtrArray * wrapper = (GPtrArray *)dbus_g_type_specialized_construct(type); - - GValue value = {0,}; - g_value_init(&value, type); - g_value_take_boxed(&value, wrapper); - - DBusGTypeSpecializedAppendContext ctx; - dbus_g_type_specialized_init_append(&value, &ctx); - - GValue strval = {0,}; - g_value_init(&strval, G_TYPE_STRV); - g_value_take_boxed(&strval, array->data); - g_array_free(array, FALSE); - - dbus_g_type_specialized_collection_append(&ctx, &strval); - dbus_g_type_specialized_collection_end_append(&ctx); + g_variant_builder_add(&builder, "s", keyname); - dbusmenu_menuitem_property_set_value(menuitem, DBUSMENU_MENUITEM_PROP_SHORTCUT, &value); + GVariant * inside = g_variant_builder_end(&builder); + g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); + g_variant_builder_add_value(&builder, inside); + GVariant * outsidevariant = g_variant_builder_end(&builder); - return TRUE; + return dbusmenu_menuitem_property_set_variant(menuitem, DBUSMENU_MENUITEM_PROP_SHORTCUT, outsidevariant); } /* Look at the closures in an accel group and find @@ -233,16 +210,16 @@ find_closure (GtkAccelKey * key, GClosure * closure, gpointer user_data) } /** - dbusmenu_menuitem_property_set_shortcut_menuitem: - @menuitem: The #DbusmenuMenuitem to set the shortcut on - @gmi: A menu item to steal the shortcut off of - - Takes the shortcut that is installed on a menu item and calls - #dbusmenu_menuitem_property_set_shortcut with it. It also sets - up listeners to watch it change. - - Return value: Whether it was successful at setting the property. -*/ + * dbusmenu_menuitem_property_set_shortcut_menuitem: + * @menuitem: The #DbusmenuMenuitem to set the shortcut on + * @gmi: A menu item to steal the shortcut off of + * + * Takes the shortcut that is installed on a menu item and calls + * #dbusmenu_menuitem_property_set_shortcut with it. It also sets + * up listeners to watch it change. + * + * Return value: Whether it was successful at setting the property. + */ gboolean dbusmenu_menuitem_property_set_shortcut_menuitem (DbusmenuMenuitem * menuitem, const GtkMenuItem * gmi) { @@ -250,7 +227,7 @@ dbusmenu_menuitem_property_set_shortcut_menuitem (DbusmenuMenuitem * menuitem, c g_return_val_if_fail(GTK_IS_MENU_ITEM(gmi), FALSE); GClosure * closure = NULL; - GtkWidget *label = GTK_BIN (gmi)->child; + GtkWidget *label = gtk_bin_get_child(GTK_BIN (gmi)); if (GTK_IS_ACCEL_LABEL (label)) { @@ -259,8 +236,15 @@ dbusmenu_menuitem_property_set_shortcut_menuitem (DbusmenuMenuitem * menuitem, c NULL); } - if (closure == NULL) - return FALSE; + if (closure == NULL) { + /* As a fallback, check for a closure in the related menu item. This + actually happens with SWT menu items. */ + GList * closures = gtk_widget_list_accel_closures (GTK_WIDGET (gmi)); + if (closures == NULL) + return FALSE; + closure = closures->data; + g_list_free (closures); + } GtkAccelGroup * group = gtk_accel_group_from_accel_closure(closure); @@ -279,99 +263,65 @@ dbusmenu_menuitem_property_set_shortcut_menuitem (DbusmenuMenuitem * menuitem, c return dbusmenu_menuitem_property_set_shortcut(menuitem, key->accel_key, key->accel_mods); } -/* A set of typed data for the interator */ -typedef struct _iter_data_t iter_data_t; -struct _iter_data_t { - guint * key; - GdkModifierType * modifier; -}; - -/* Goes through the wrapper items. In reality we only support one - so it checks to see if a key is set first. But, we could possibly, - support more in the future. */ -static void -_wrapper_iterator (const GValue * value, gpointer user_data) +/** + * dbusmenu_menuitem_property_get_shortcut: + * @menuitem: The #DbusmenuMenuitem to get the shortcut off + * @key: (out): Location to put the key value + * @modifier: (out): Location to put the modifier mask + * + * This function gets a GTK shortcut as a key and a mask + * for use to set the accelerators. + */ +void +dbusmenu_menuitem_property_get_shortcut (DbusmenuMenuitem * menuitem, guint * key, GdkModifierType * modifier) { - iter_data_t * iter_data = (iter_data_t *)user_data; - - if (*iter_data->key != 0) { - g_warning("Shortcut is more than one entry. Which we don't currently support. Taking the first."); - return; - } - - if (!G_VALUE_HOLDS(value, G_TYPE_STRV)) { - g_warning("Unexpected shortcut structure. Value array is: %s", G_VALUE_TYPE_NAME(value)); - return; - } + guint dummykey; + GdkModifierType dummymodifier; - gchar ** stringarray = (gchar **)g_value_get_boxed(value); - if (stringarray == NULL) { - return; + if (key == NULL) { + key = &dummykey; } - const gchar * last_string = NULL; - int i; - - for (i = 0; stringarray[i] != NULL; i++) { - last_string = stringarray[i]; - - if (g_strcmp0(last_string, DBUSMENU_MENUITEM_SHORTCUT_CONTROL) == 0) { - *iter_data->modifier |= GDK_CONTROL_MASK; - continue; - } - if (g_strcmp0(last_string, DBUSMENU_MENUITEM_SHORTCUT_ALT) == 0) { - *iter_data->modifier |= GDK_MOD1_MASK; - continue; - } - if (g_strcmp0(last_string, DBUSMENU_MENUITEM_SHORTCUT_SHIFT) == 0) { - *iter_data->modifier |= GDK_SHIFT_MASK; - continue; - } - if (g_strcmp0(last_string, DBUSMENU_MENUITEM_SHORTCUT_SUPER) == 0) { - *iter_data->modifier |= GDK_SUPER_MASK; - continue; - } + if (modifier == NULL) { + modifier = &dummymodifier; } - if (last_string != NULL) { - GdkModifierType tempmod; - gtk_accelerator_parse(last_string, iter_data->key, &tempmod); - } - - return; -} - -/** - dbusmenu_menuitem_property_get_shortcut: - @menuitem: The #DbusmenuMenuitem to get the shortcut off - @key: Location to put the key value - @modifier: Location to put the modifier mask - - This function gets a GTK shortcut as a key and a mask - for use to set the accelerators. -*/ -void -dbusmenu_menuitem_property_get_shortcut (DbusmenuMenuitem * menuitem, guint * key, GdkModifierType * modifier) -{ *key = 0; *modifier = 0; g_return_if_fail(DBUSMENU_IS_MENUITEM(menuitem)); - const GValue * wrapper = dbusmenu_menuitem_property_get_value(menuitem, DBUSMENU_MENUITEM_PROP_SHORTCUT); + GVariant * wrapper = dbusmenu_menuitem_property_get_variant(menuitem, DBUSMENU_MENUITEM_PROP_SHORTCUT); if (wrapper == NULL) { return; } - if (!dbus_g_type_is_collection(G_VALUE_TYPE(wrapper))) { - g_warning("Unexpected shortcut structure. Wrapper is: %s", G_VALUE_TYPE_NAME(wrapper)); + + if (g_variant_n_children(wrapper) != 1) { + g_warning("Unable to parse shortcut, too many keys"); return; } - iter_data_t iter_data; - iter_data.key = key; - iter_data.modifier = modifier; + GVariantIter iter; + GVariant * child = g_variant_get_child_value(wrapper, 0); + g_variant_iter_init(&iter, child); + gchar * string; + + while(g_variant_iter_loop(&iter, "s", &string)) { + if (g_strcmp0(string, DBUSMENU_MENUITEM_SHORTCUT_CONTROL) == 0) { + *modifier |= GDK_CONTROL_MASK; + } else if (g_strcmp0(string, DBUSMENU_MENUITEM_SHORTCUT_ALT) == 0) { + *modifier |= GDK_MOD1_MASK; + } else if (g_strcmp0(string, DBUSMENU_MENUITEM_SHORTCUT_SHIFT) == 0) { + *modifier |= GDK_SHIFT_MASK; + } else if (g_strcmp0(string, DBUSMENU_MENUITEM_SHORTCUT_SUPER) == 0) { + *modifier |= GDK_SUPER_MASK; + } else { + GdkModifierType tempmod; + gtk_accelerator_parse(string, key, &tempmod); + } + } - dbus_g_type_collection_value_iterate(wrapper, _wrapper_iterator, &iter_data); + g_variant_unref(child); return; } diff --git a/libdbusmenu-gtk/menuitem.h b/libdbusmenu-gtk/menuitem.h index 6960f76..41f2187 100644 --- a/libdbusmenu-gtk/menuitem.h +++ b/libdbusmenu-gtk/menuitem.h @@ -26,8 +26,8 @@ License version 3 and version 2.1 along with this program. If not, see <http://www.gnu.org/licenses/> */ -#ifndef __DBUSMENU_GTKMENUITEM_H__ -#define __DBUSMENU_GTKMENUITEM_H__ 1 +#ifndef DBUSMENU_GTK_MENUITEM_H__ +#define DBUSMENU_GTK_MENUITEM_H__ 1 #include <glib.h> #include <gdk-pixbuf/gdk-pixbuf.h> @@ -35,12 +35,27 @@ License version 3 and version 2.1 along with this program. If not, see #include <gdk/gdk.h> #include <gtk/gtk.h> +G_BEGIN_DECLS + gboolean dbusmenu_menuitem_property_set_image (DbusmenuMenuitem * menuitem, const gchar * property, const GdkPixbuf * data); GdkPixbuf * dbusmenu_menuitem_property_get_image (DbusmenuMenuitem * menuitem, const gchar * property); gboolean dbusmenu_menuitem_property_set_shortcut (DbusmenuMenuitem * menuitem, guint key, GdkModifierType modifier); gboolean dbusmenu_menuitem_property_set_shortcut_string (DbusmenuMenuitem * menuitem, const gchar * shortcut); gboolean dbusmenu_menuitem_property_set_shortcut_menuitem (DbusmenuMenuitem * menuitem, const GtkMenuItem * gmi); -void dbusmenu_menuitem_property_get_shortcut (DbusmenuMenuitem * menuitem, guint * key, GdkModifierType * modifiers); +void dbusmenu_menuitem_property_get_shortcut (DbusmenuMenuitem * menuitem, guint * key, GdkModifierType * modifier); + +/** + SECTION:menuitem + @short_description: Helpers for #DbusmenuMenuitem properties that require a GTK dependency + @stability: Unstable + @include: libdbusmenu-gtk/menuitem.h + + Some property helpers can't be done without picking up a GTK+ + dependency. So those sit in libdbusmenu-gtk but have very similar + prototypes to the code in libdbusmenu-glib so your code will + look consistent, just with the extra depedency. +*/ +G_END_DECLS #endif diff --git a/libdbusmenu-gtk/parser.c b/libdbusmenu-gtk/parser.c new file mode 100644 index 0000000..3243c81 --- /dev/null +++ b/libdbusmenu-gtk/parser.c @@ -0,0 +1,1211 @@ +/* +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" +#include "client.h" + +#define CACHED_MENUITEM "dbusmenu-gtk-parser-cached-item" +#define PARSER_DATA "dbusmenu-gtk-parser-data" + +typedef struct _ParserData +{ + GtkWidget *label; + GtkAction *action; + GtkWidget *widget; + GtkWidget *shell; + GtkWidget *image; +} ParserData; + +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 void checkbox_toggled (GtkWidget * widget, + DbusmenuMenuitem * mi); +static void update_icon (DbusmenuMenuitem * menuitem, + GtkImage * image); +static GtkWidget * find_menu_label (GtkWidget * widget); +static void label_notify_cb (GtkWidget * widget, + GParamSpec * pspec, + gpointer data); +static void image_notify_cb (GtkWidget * widget, + GParamSpec * pspec, + gpointer data); +static void action_notify_cb (GtkAction * action, + GParamSpec * pspec, + gpointer data); +static void child_added_cb (GtkContainer * menu, + GtkWidget * widget, + gpointer data); +static void child_removed_cb (GtkContainer * menu, + GtkWidget * widget, + gpointer data); +static void theme_changed_cb (GtkIconTheme * theme, + 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 gboolean item_handle_event (DbusmenuMenuitem * item, + const gchar * name, + GVariant * variant, + guint timestamp, + GtkWidget * widget); +static void widget_notify_cb (GtkWidget * widget, + GParamSpec * pspec, + gpointer data); +static void widget_add_cb (GtkWidget * widget, + GtkWidget * child, + 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: (transfer full): 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); + + DbusmenuMenuitem * returnval = NULL; + gpointer data = g_object_get_data(G_OBJECT(widget), CACHED_MENUITEM); + + if (data == NULL) { + RecurseContext recurse = {0}; + + recurse.toplevel = gtk_widget_get_toplevel(widget); + + parse_menu_structure_helper(widget, &recurse); + + returnval = recurse.parent; + } else { + returnval = DBUSMENU_MENUITEM(data); + g_object_ref(G_OBJECT(returnval)); + } + + return returnval; +} + +/** + * dbusmenu_gtk_parse_get_cached_item: + * @widget: A #GtkMenuItem that may have a cached #DbusmenuMenuitem from the parser + * + * The Dbusmenu GTK parser adds cached items on the various + * menu items throughout the tree. Sometimes it can be useful + * to get that cached item to use directly. This function + * will retrieve it for you. + * + * Return value: (transfer none): A pointer to the cached item + * or NULL if it isn't there. + */ +DbusmenuMenuitem * +dbusmenu_gtk_parse_get_cached_item (GtkWidget * widget) +{ + if (!GTK_IS_MENU_ITEM(widget)) { + return NULL; + } + return DBUSMENU_MENUITEM(g_object_get_data(G_OBJECT(widget), CACHED_MENUITEM)); +} + +static void +parse_data_free (gpointer data) +{ + ParserData *pdata = (ParserData *)data; + + if (pdata != NULL && pdata->label != NULL) { + g_signal_handlers_disconnect_matched(pdata->label, (GSignalMatchType)G_SIGNAL_MATCH_FUNC, + 0, 0, NULL, G_CALLBACK(label_notify_cb), NULL); + g_object_remove_weak_pointer(G_OBJECT(pdata->label), (gpointer*)&pdata->label); + } + + if (pdata != NULL && pdata->action != NULL) { + g_signal_handlers_disconnect_matched(pdata->action, (GSignalMatchType)G_SIGNAL_MATCH_FUNC, + 0, 0, NULL, G_CALLBACK(action_notify_cb), NULL); + g_object_remove_weak_pointer(G_OBJECT(pdata->action), (gpointer*)&pdata->action); + } + + if (pdata != NULL && pdata->widget != NULL) { + g_signal_handlers_disconnect_matched(pdata->widget, (GSignalMatchType)G_SIGNAL_MATCH_FUNC, + 0, 0, NULL, G_CALLBACK(widget_notify_cb), NULL); + g_signal_handlers_disconnect_matched(pdata->widget, (GSignalMatchType)G_SIGNAL_MATCH_FUNC, + 0, 0, NULL, G_CALLBACK(widget_add_cb), NULL); + g_signal_handlers_disconnect_matched(pdata->widget, (GSignalMatchType)G_SIGNAL_MATCH_FUNC, + 0, 0, NULL, G_CALLBACK(accel_changed), NULL); + g_signal_handlers_disconnect_matched(pdata->widget, (GSignalMatchType)G_SIGNAL_MATCH_FUNC, + 0, 0, NULL, G_CALLBACK(checkbox_toggled), NULL); + g_signal_handlers_disconnect_matched(pdata->widget, (GSignalMatchType)G_SIGNAL_MATCH_FUNC, + 0, 0, NULL, G_CALLBACK(menuitem_notify_cb), NULL); + g_object_remove_weak_pointer(G_OBJECT(pdata->widget), (gpointer*)&pdata->widget); + } + + if (pdata != NULL && pdata->shell != NULL) { + g_signal_handlers_disconnect_matched(pdata->shell, (GSignalMatchType)G_SIGNAL_MATCH_FUNC, + 0, 0, NULL, G_CALLBACK(child_added_cb), NULL); + g_signal_handlers_disconnect_matched(pdata->shell, (GSignalMatchType)G_SIGNAL_MATCH_FUNC, + 0, 0, NULL, G_CALLBACK(child_removed_cb), NULL); + g_object_remove_weak_pointer(G_OBJECT(pdata->shell), (gpointer*)&pdata->shell); + } + + if (pdata != NULL && pdata->image != NULL) { + g_signal_handlers_disconnect_matched(pdata->image, (GSignalMatchType)G_SIGNAL_MATCH_FUNC, + 0, 0, NULL, G_CALLBACK(image_notify_cb), NULL); + g_object_remove_weak_pointer(G_OBJECT(pdata->image), (gpointer*)&pdata->image); + } + + g_free(pdata); + + return; +} + +static void +widget_freed (gpointer data, GObject * obj) +{ + g_signal_handlers_disconnect_by_func(gtk_icon_theme_get_default(), G_CALLBACK(theme_changed_cb), obj); + + return; +} + +/* Called when the dbusmenu item that we're keeping around + is finalized */ +static void +dbusmenu_item_freed (gpointer data, GObject * obj) +{ + ParserData *pdata = (ParserData *)g_object_get_data(G_OBJECT(obj), PARSER_DATA); + + if (pdata != NULL && pdata->widget != NULL) { + g_signal_handlers_disconnect_by_func(gtk_icon_theme_get_default(), G_CALLBACK(theme_changed_cb), pdata->widget); + g_object_steal_data(G_OBJECT(pdata->widget), CACHED_MENUITEM); + g_object_weak_unref(G_OBJECT(pdata->widget), widget_freed, NULL); + } +} + +/* Gets the positon of the child with its' parent if it has one. + Returns -1 if the position is unable to be calculated. */ +static gint +get_child_position (GtkWidget * child) +{ + GtkWidget * parent = gtk_widget_get_parent (child); + if (parent == NULL || !GTK_IS_CONTAINER (parent)) + return -1; + + GList * children = gtk_container_get_children (GTK_CONTAINER (parent)); + GList * iter; + gint position = 0; + + for (iter = children; iter != NULL; iter = iter->next) { + if (iter->data == child) + break; + ++position; + } + + g_list_free (children); + + if (iter == NULL) + return -1; + else + return position; +} + +/* Creates a new menu item that is attached to the widget and has + the data linkages hooked up. Also allocates the ParserData */ +static DbusmenuMenuitem * +new_menuitem (GtkWidget * widget) +{ + DbusmenuMenuitem * item = dbusmenu_menuitem_new(); + + ParserData *pdata = g_new0 (ParserData, 1); + g_object_set_data_full(G_OBJECT(item), PARSER_DATA, pdata, parse_data_free); + + g_object_weak_ref(G_OBJECT(item), dbusmenu_item_freed, NULL); + g_object_weak_ref(G_OBJECT(widget), widget_freed, NULL); + + pdata->widget = widget; + g_object_add_weak_pointer(G_OBJECT (widget), (gpointer*)&pdata->widget); + g_object_set_data(G_OBJECT(widget), CACHED_MENUITEM, item); + + return item; +} + +static gboolean +toggle_widget_visibility (GtkWidget * widget) +{ + gboolean vis = gtk_widget_get_visible (widget); + gtk_widget_set_visible (widget, !vis); + gtk_widget_set_visible (widget, vis); + g_object_unref (G_OBJECT (widget)); + return FALSE; +} + +static void +watch_submenu(DbusmenuMenuitem * mi, GtkWidget * menu) +{ + ParserData *pdata = (ParserData *)g_object_get_data(G_OBJECT(mi), PARSER_DATA); + + pdata->shell = menu; + g_signal_connect (G_OBJECT (menu), + "child-added", + G_CALLBACK (child_added_cb), + mi); + g_signal_connect (G_OBJECT (menu), + "child-removed", + G_CALLBACK (child_removed_cb), + mi); + g_object_add_weak_pointer(G_OBJECT (menu), (gpointer*)&pdata->shell); + + /* Some apps (notably Eclipse RCP apps) don't fill contents of submenus + until the menu is shown. So we fake that by toggling the visibility of + any submenus we come across. Further, these apps need it done with a + delay while they finish initializing, so we put the call in the idle + queue. */ + g_idle_add((GSourceFunc)toggle_widget_visibility, + g_object_ref (G_OBJECT (menu))); +} + +static void +activate_toplevel_item (GtkWidget * item) +{ + /* Make sure that we have a menu item before we start calling + functions that depend on it. This should almost always be + the case. */ + if (!GTK_IS_MENU_ITEM(item)) { + return; + } + + /* If the item is not opening a submenu we don't want to activate + it as that'd cause an action. Like opening a preferences dialog + to the user. That's not a good idea. */ + if (gtk_menu_item_get_submenu(GTK_MENU_ITEM(item)) == NULL) { + return; + } + + GtkWidget * shell = gtk_widget_get_parent (item); + if (!GTK_IS_MENU_BAR (shell)) { + return; + } + + gtk_menu_shell_activate_item (GTK_MENU_SHELL (shell), + item, + TRUE); +} + +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)) { + gtk_container_foreach (GTK_CONTAINER (widget), + (GtkCallback)activate_toplevel_item, + NULL); + } + + if (recurse->parent == NULL) { + recurse->parent = new_menuitem(widget); + watch_submenu(recurse->parent, widget); + } + + 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); + + 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) { + if (dbusmenu_menuitem_get_parent(thisitem) != NULL) { + dbusmenu_menuitem_unparent(thisitem); + } + + gint pos = get_child_position (widget); + if (pos >= 0) + dbusmenu_menuitem_child_add_position (recurse->parent, + thisitem, + pos); + else + 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 gchar * +sanitize_label_text (const gchar * label) +{ + /* Label contains underscores, which we like, and pango markup, + which we don't. */ + gchar * sanitized = NULL; + GError * error = NULL; + + if (label == NULL) { + return NULL; + } + + if (pango_parse_markup (label, -1, 0, NULL, &sanitized, NULL, &error)) { + return sanitized; + } + + if (error != NULL) { + g_warning ("Could not parse '%s': %s", label, error->message); + g_error_free (error); + } + return g_strdup (label); +} + +static gchar * +sanitize_label (GtkLabel * label) +{ + gchar * text; + + if (gtk_label_get_use_markup (label)) { + text = sanitize_label_text (gtk_label_get_label (label)); + } + else { + text = g_strdup (gtk_label_get_label (label)); + } + + if (!gtk_label_get_use_underline (label)) { + /* Insert extra underscores */ + GRegex * regex = g_regex_new ("_", 0, 0, NULL); + gchar * escaped = g_regex_replace_literal (regex, text, -1, 0, "__", 0, NULL); + + g_regex_unref (regex); + g_free (text); + + text = escaped; + } + + return text; +} + +/* Turn a widget into a dbusmenu item depending on the type of GTK + object that it is. */ +static DbusmenuMenuitem * +construct_dbusmenu_for_widget (GtkWidget * widget) +{ + /* If it's a standard GTK Menu Item we need to do some of our own work */ + if (GTK_IS_MENU_ITEM (widget)) + { + DbusmenuMenuitem *mi = new_menuitem(widget); + + ParserData *pdata = (ParserData *)g_object_get_data(G_OBJECT(mi), PARSER_DATA); + + gboolean visible = FALSE; + gboolean sensitive = FALSE; + if (GTK_IS_SEPARATOR_MENU_ITEM (widget) || !find_menu_label (widget)) + { + dbusmenu_menuitem_property_set (mi, + DBUSMENU_MENUITEM_PROP_TYPE, + DBUSMENU_CLIENT_TYPES_SEPARATOR); + + visible = gtk_widget_get_visible (widget); + sensitive = gtk_widget_get_sensitive (widget); + } + else + { + 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; + + image = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (widget)); + + if (GTK_IS_IMAGE (image)) + { + update_icon (mi, GTK_IMAGE (image)); + + /* Watch for theme changes because if gicon changes, we want to send a + different pixbuf. */ + g_signal_connect(G_OBJECT(gtk_icon_theme_get_default()), + "changed", G_CALLBACK(theme_changed_cb), widget); + + pdata->image = image; + g_signal_connect (G_OBJECT (image), + "notify", + G_CALLBACK (image_notify_cb), + mi); + g_object_add_weak_pointer(G_OBJECT (image), (gpointer*)&pdata->image); + } + } + + GtkWidget *label = find_menu_label (widget); + + // Sometimes, an app will directly find and modify the label + // (like empathy), so watch the label especially for that. + gchar * text = sanitize_label (GTK_LABEL (label)); + dbusmenu_menuitem_property_set (mi, "label", text); + g_free (text); + + pdata->label = label; + g_signal_connect (G_OBJECT (label), + "notify", + G_CALLBACK (label_notify_cb), + mi); + g_object_add_weak_pointer(G_OBJECT (label), (gpointer*)&pdata->label); + + 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); + + pdata->action = action; + g_signal_connect_object (action, "notify", + G_CALLBACK (action_notify_cb), + mi, + G_CONNECT_AFTER); + g_object_add_weak_pointer(G_OBJECT (action), (gpointer*)&pdata->action); + } + } + } + + GtkWidget *submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(widget)); + if (submenu) + { + watch_submenu(mi, submenu); + } + + 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); + + g_signal_connect (G_OBJECT (mi), + DBUSMENU_MENUITEM_SIGNAL_EVENT, + G_CALLBACK (item_handle_event), + 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); + + g_signal_connect (widget, + "add", + G_CALLBACK (widget_add_cb), + mi); + + return mi; + } + + /* If it's none of those we're going to just create a + generic menuitem as a place holder for it. */ + return new_menuitem(widget); +} + +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 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 (DbusmenuMenuitem *menuitem, GtkImage *image) +{ + GdkPixbuf * pixbuf = NULL; + const gchar * icon_name = NULL; + GtkStockItem stock; + GIcon * gicon; + GtkIconInfo * info; + gint width; + + if (image != NULL && should_show_image (image)) { + switch (gtk_image_get_storage_type (image)) { + case GTK_IMAGE_EMPTY: + break; + + case GTK_IMAGE_PIXBUF: + pixbuf = g_object_ref (gtk_image_get_pixbuf (image)); + break; + + case GTK_IMAGE_ICON_NAME: + gtk_image_get_icon_name (image, &icon_name, NULL); + break; + + case GTK_IMAGE_STOCK: + gtk_image_get_stock (image, (gchar **) &icon_name, NULL); + if (gtk_stock_lookup (icon_name, &stock)) { + /* Now set label too */ + const gchar * label = NULL; + 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); + } + } + break; + + case GTK_IMAGE_GICON: + /* Load up a pixbuf and send that over. We don't bother differentiating + between icon-name gicons and pixbuf gicons because even when given a + icon-name gicon, there's no easy way to lookup which icon-name among + its set is present and should be used among the icon themes available. + So instead, we render to a pixbuf and watch icon theme changes. */ + gtk_image_get_gicon (image, &gicon, NULL); + gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, NULL); + info = gtk_icon_theme_lookup_by_gicon (gtk_icon_theme_get_default (), + gicon, width, + GTK_ICON_LOOKUP_FORCE_SIZE); + if (info != NULL) { + pixbuf = gtk_icon_info_load_icon (info, NULL); + gtk_icon_info_free (info); + } + break; + + default: + g_debug ("Could not handle image type %i\n", gtk_image_get_storage_type (image)); + break; + } + } + + if (icon_name != NULL) { + dbusmenu_menuitem_property_set (menuitem, + DBUSMENU_MENUITEM_PROP_ICON_NAME, + icon_name); + dbusmenu_menuitem_property_remove (menuitem, + DBUSMENU_MENUITEM_PROP_ICON_DATA); + } + else if (pixbuf != NULL) { + dbusmenu_menuitem_property_remove (menuitem, + DBUSMENU_MENUITEM_PROP_ICON_NAME); + dbusmenu_menuitem_property_set_image (menuitem, + DBUSMENU_MENUITEM_PROP_ICON_DATA, + pixbuf); + } + else { + dbusmenu_menuitem_property_remove (menuitem, + DBUSMENU_MENUITEM_PROP_ICON_NAME); + dbusmenu_menuitem_property_remove (menuitem, + DBUSMENU_MENUITEM_PROP_ICON_DATA); + } + + if (pixbuf != NULL) { + g_object_unref (pixbuf); + } +} + +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 +recreate_menu_item (DbusmenuMenuitem * parent, DbusmenuMenuitem * child) +{ + if (parent == NULL) + { + /* We need a parent */ + return; + } + ParserData * pdata = g_object_get_data (G_OBJECT (child), PARSER_DATA); + /* Keep a pointer to the GtkMenuItem, as pdata->widget might be + * invalidated when we delete the DbusmenuMenuitem + */ + GtkWidget * menuitem = pdata->widget; + + dbusmenu_menuitem_child_delete (parent, child); + + RecurseContext recurse = {0}; + recurse.toplevel = gtk_widget_get_toplevel(menuitem); + recurse.parent = parent; + + parse_menu_structure_helper(menuitem, &recurse); +} + +static gboolean +recreate_menu_item_in_idle_cb (gpointer data) +{ + DbusmenuMenuitem * child = (DbusmenuMenuitem *)data; + DbusmenuMenuitem * parent = dbusmenu_menuitem_get_parent (child); + g_object_unref (child); + recreate_menu_item (parent, child); + return FALSE; +} + +static void +label_notify_cb (GtkWidget *widget, + GParamSpec *pspec, + gpointer data) +{ + DbusmenuMenuitem *child = (DbusmenuMenuitem *)data; + GValue prop_value = {0}; + + g_value_init (&prop_value, pspec->value_type); + g_object_get_property (G_OBJECT (widget), pspec->name, &prop_value); + + if (pspec->name == g_intern_static_string ("label")) + { + gchar * text = sanitize_label (GTK_LABEL (widget)); + dbusmenu_menuitem_property_set (child, + DBUSMENU_MENUITEM_PROP_LABEL, + text); + g_free (text); + } + else if (pspec->name == g_intern_static_string ("parent")) + { + if (GTK_WIDGET (g_value_get_object (&prop_value)) == NULL) + { + /* This label is being removed from its GtkMenuItem. The + * menuitem becomes a separator now. As the client doesn't handle + * changing types so well, we remove the current DbusmenuMenuitem + * and add a new one. + * + * Note, we have to defer this to idle, as we are called before + * bin->child member of our old parent is invalidated. If we go ahead + * and call parse_menu_structure_helper now, the GtkMenuItem will + * still appear to have a label and we never convert it to a separator + */ + g_object_ref (child); + g_idle_add ((GSourceFunc)recreate_menu_item_in_idle_cb, child); + } + } + + g_value_unset(&prop_value); + return; +} + +static void +image_notify_cb (GtkWidget *widget, + GParamSpec *pspec, + gpointer data) +{ + DbusmenuMenuitem *mi = (DbusmenuMenuitem *)data; + + if (pspec->name == g_intern_static_string ("file") || + pspec->name == g_intern_static_string ("gicon") || + pspec->name == g_intern_static_string ("icon-name") || + pspec->name == g_intern_static_string ("icon-set") || + pspec->name == g_intern_static_string ("image") || + pspec->name == g_intern_static_string ("mask") || + pspec->name == g_intern_static_string ("pixbuf") || + pspec->name == g_intern_static_string ("pixbuf-animation") || + pspec->name == g_intern_static_string ("pixmap") || + pspec->name == g_intern_static_string ("stock") || + pspec->name == g_intern_static_string ("storage-type")) + { + update_icon (mi, GTK_IMAGE (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")) + { + gchar * text = sanitize_label_text (gtk_action_get_label (action)); + dbusmenu_menuitem_property_set (mi, + DBUSMENU_MENUITEM_PROP_LABEL, + text); + g_free (text); + } +} + +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)) + { + gdk_threads_enter (); + gtk_menu_item_activate (GTK_MENU_ITEM (child)); + gdk_threads_leave (); + } + } +} + +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 gboolean +item_handle_event (DbusmenuMenuitem *item, const gchar *name, + GVariant *variant, guint timestamp, GtkWidget *widget) +{ + if (g_strcmp0 (name, DBUSMENU_MENUITEM_EVENT_OPENED) == 0) + { + GtkWidget *submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget)); + if (submenu != NULL) + { + // Show the submenu so the app can notice and futz with the menus as + // desired (empathy and geany do this) + gtk_widget_show (submenu); + } + } + else if (g_strcmp0 (name, DBUSMENU_MENUITEM_EVENT_CLOSED) == 0) + { + GtkWidget *submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget)); + if (submenu != NULL) + { + // Hide the submenu so the app can notice and futz with the menus as + // desired (empathy and geany do this) + gtk_widget_hide (submenu); + } + } + + return FALSE; // just pass through on everything +} + +static gboolean +handle_first_label (DbusmenuMenuitem *mi) +{ + ParserData *pdata = g_object_get_data (G_OBJECT (mi), PARSER_DATA); + if (!pdata->label) + { + /* GtkMenuItem's can start life as a separator if they have no child + * GtkLabel. In this case, we need to convert the DbusmenuMenuitem from + * a separator to a normal menuitem if the application adds a label. + * As changing types isn't handled too well by the client, we delete + * this menuitem for now and then recreate it + */ + DbusmenuMenuitem * parent = dbusmenu_menuitem_get_parent (mi); + recreate_menu_item (parent, mi); + return TRUE; + } + + return FALSE; +} + +static void +widget_notify_cb (GtkWidget *widget, + GParamSpec *pspec, + gpointer data) +{ + DbusmenuMenuitem *child = (DbusmenuMenuitem *)data; + GValue prop_value = {0}; + + g_value_init (&prop_value, pspec->value_type); + g_object_get_property (G_OBJECT (widget), pspec->name, &prop_value); + + if (pspec->name == g_intern_static_string ("sensitive")) + { + dbusmenu_menuitem_property_set_bool (child, + DBUSMENU_MENUITEM_PROP_ENABLED, + g_value_get_boolean (&prop_value)); + } + else if (pspec->name == g_intern_static_string ("label")) + { + if (handle_first_label (child)) + { + return; + } + + dbusmenu_menuitem_property_set (child, + DBUSMENU_MENUITEM_PROP_LABEL, + g_value_get_string (&prop_value)); + } + else if (pspec->name == g_intern_static_string ("visible")) + { + dbusmenu_menuitem_property_set_bool (child, + DBUSMENU_MENUITEM_PROP_VISIBLE, + g_value_get_boolean (&prop_value)); + } + else if (pspec->name == g_intern_static_string ("image") || + pspec->name == g_intern_static_string ("always-show-image")) + { + GtkWidget *image; + image = GTK_WIDGET (g_value_get_object (&prop_value)); + update_icon (child, GTK_IMAGE (image)); + } + 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 (g_value_get_object (&prop_value)) == NULL) + { + g_signal_handlers_disconnect_by_func (widget, + G_CALLBACK (widget_notify_cb), + child); + + DbusmenuMenuitem *parent = dbusmenu_menuitem_get_parent (child); + + if (DBUSMENU_IS_MENUITEM (parent) && DBUSMENU_IS_MENUITEM (child)) + { + dbusmenu_menuitem_child_delete (parent, child); + } + } + } + else if (pspec->name == g_intern_static_string ("submenu")) + { + /* The underlying submenu got swapped out. Let's see what it is now. */ + /* First, delete any children that may exist currently. */ + DbusmenuMenuitem * item = DBUSMENU_MENUITEM(g_object_get_data(G_OBJECT(widget), CACHED_MENUITEM)); + if (item != NULL) + { + GList * children = dbusmenu_menuitem_take_children (item); + GList * child = children; + while (child != NULL) { + g_object_unref (G_OBJECT(child->data)); + child = child->next; + } + g_list_free(children); + } + + /* Now parse new submenu. */ + RecurseContext recurse = {0}; + recurse.toplevel = gtk_widget_get_toplevel(widget); + recurse.parent = item; + + if (item != NULL) { + GtkWidget * menu = GTK_WIDGET (g_value_get_object (&prop_value)); + parse_menu_structure_helper(menu, &recurse); + watch_submenu(item, menu); + } else { + /* Note: it would be really odd that we wouldn't have a cached + item, but we should handle that appropriately. */ + parse_menu_structure_helper(widget, &recurse); + g_object_unref(G_OBJECT(recurse.parent)); + } + } + g_value_unset (&prop_value); +} + +static void +widget_add_cb (GtkWidget *widget, + GtkWidget *child, + gpointer data) +{ + if (find_menu_label (child) != NULL) + handle_first_label (data); +} + +/* A child item was added to a menu we're watching. Let's try to integrate it. */ +static void +child_added_cb (GtkContainer *menu, GtkWidget *widget, gpointer data) +{ + DbusmenuMenuitem *menuitem = (DbusmenuMenuitem *)data; + + RecurseContext recurse = {0}; + recurse.toplevel = gtk_widget_get_toplevel(GTK_WIDGET(menu)); + recurse.parent = menuitem; + + if (GTK_IS_MENU_BAR(menu)) { + activate_toplevel_item (widget); + } + + parse_menu_structure_helper(widget, &recurse); +} + +/* A child item was added to a menu we're watching. Let's try to integrate it. */ +static void +child_removed_cb (GtkContainer *menu, GtkWidget *widget, gpointer data) +{ + gpointer pmi = g_object_get_data(G_OBJECT(widget), CACHED_MENUITEM); + if (pmi == NULL) { + return; + } + + DbusmenuMenuitem * child = DBUSMENU_MENUITEM(pmi); + + pmi = g_object_get_data(G_OBJECT(menu), CACHED_MENUITEM); + if (pmi == NULL) { + return; + } + + DbusmenuMenuitem * parent = DBUSMENU_MENUITEM(pmi); + + dbusmenu_menuitem_child_delete(parent, child); + return; +} + +static void +theme_changed_cb (GtkIconTheme *theme, gpointer data) +{ + GtkWidget *image; + + image = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (data)); + + gpointer pmi = g_object_get_data(G_OBJECT(data), CACHED_MENUITEM); + if (pmi != NULL) { + update_icon(DBUSMENU_MENUITEM(pmi), GTK_IMAGE(image)); + } + + /* Switch signal to new theme */ + g_signal_handlers_disconnect_by_func(theme, G_CALLBACK(theme_changed_cb), data); + g_signal_connect(gtk_icon_theme_get_default(), "changed", G_CALLBACK(theme_changed_cb), data); +} + +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", >k_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..97fa9c6 --- /dev/null +++ b/libdbusmenu-gtk/parser.h @@ -0,0 +1,52 @@ +/* +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> + +G_BEGIN_DECLS + +DbusmenuMenuitem * dbusmenu_gtk_parse_menu_structure (GtkWidget * widget); +DbusmenuMenuitem * dbusmenu_gtk_parse_get_cached_item (GtkWidget * widget); + +/** + SECTION:parser + @short_description: A parser of in-memory GTK menu trees + @stability: Unstable + @include: libdbusmenu-gtk/parser.h + + The parser will take a GTK menu tree and attach it to a Dbusmenu menu + tree. Along with setting up all the signals for updates and destruction. + The returned item would be the root item of the given tree. +*/ +G_END_DECLS + +#endif /* DBUSMENU_GTK_PARSER_H__ */ |