diff options
Diffstat (limited to 'libdbusmenu-gtk')
-rw-r--r-- | libdbusmenu-gtk/Makefile.am | 107 | ||||
-rw-r--r-- | libdbusmenu-gtk/client.c | 434 | ||||
-rw-r--r-- | libdbusmenu-gtk/client.h | 14 | ||||
-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 | 41 | ||||
-rw-r--r-- | libdbusmenu-gtk/dbusmenu-gtk3-0.4.pc.in | 14 | ||||
-rw-r--r-- | libdbusmenu-gtk/genericmenuitem.c | 116 | ||||
-rw-r--r-- | libdbusmenu-gtk/menu.c | 83 | ||||
-rw-r--r-- | libdbusmenu-gtk/menu.h | 11 | ||||
-rw-r--r-- | libdbusmenu-gtk/menuitem.c | 227 | ||||
-rw-r--r-- | libdbusmenu-gtk/menuitem.h | 15 | ||||
-rw-r--r-- | libdbusmenu-gtk/parser.c | 887 | ||||
-rw-r--r-- | libdbusmenu-gtk/parser.h | 37 | ||||
-rw-r--r-- | libdbusmenu-gtk/serializablemenuitem.c | 288 | ||||
-rw-r--r-- | libdbusmenu-gtk/serializablemenuitem.h | 115 |
15 files changed, 2175 insertions, 218 deletions
diff --git a/libdbusmenu-gtk/Makefile.am b/libdbusmenu-gtk/Makefile.am index 2be63b7..50a8f2c 100644 --- a/libdbusmenu-gtk/Makefile.am +++ b/libdbusmenu-gtk/Makefile.am @@ -1,18 +1,31 @@ CLEANFILES = -EXTRA_DIST = \ - dbusmenu-gtk.pc.in +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 -lib_LTLIBRARIES = \ - libdbusmenu-gtk.la +EXTRA_DIST = \ + dbusmenu-gtk-0.4.pc.in \ + dbusmenu-gtk3-0.4.pc.in -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 \ + serializablemenuitem.h libdbusmenu_gtk_la_SOURCES = \ client.h \ @@ -22,7 +35,11 @@ libdbusmenu_gtk_la_SOURCES = \ menu.h \ menu.c \ menuitem.h \ - menuitem.c + menuitem.c \ + parser.h \ + parser.c \ + serializablemenuitem.h \ + serializablemenuitem.c libdbusmenu_gtk_la_LDFLAGS = \ -version-info $(LIBDBUSMENU_CURRENT):$(LIBDBUSMENU_REVISION):$(LIBDBUSMENU_AGE) \ @@ -36,7 +53,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 +73,49 @@ 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/, $(introspection_sources)) + $(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/, $(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_SCANNERFLAGS = $(INTROSPECTION_SCANNER_ARGS) +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_SCANNERFLAGS = $(DbusmenuGtk_0_4_gir_SCANNERFLAGS) +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 +134,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 3bd0af6..50978ff 100644 --- a/libdbusmenu-gtk/client.c +++ b/libdbusmenu-gtk/client.c @@ -36,6 +36,14 @@ License version 3 and version 2.1 along with this program. If not, see #include "menuitem.h" #include "genericmenuitem.h" +/* Private */ +struct _DbusmenuGtkClientPrivate { + GtkAccelGroup * agroup; +}; + +#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); static void dbusmenu_gtkclient_init (DbusmenuGtkClient *self); @@ -45,13 +53,14 @@ static void new_menuitem (DbusmenuClient * client, DbusmenuMenuitem * mi, gpoint static void new_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position, DbusmenuGtkClient * gtkclient); 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 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); @@ -62,6 +71,8 @@ dbusmenu_gtkclient_class_init (DbusmenuGtkClientClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + g_type_class_add_private (klass, sizeof (DbusmenuGtkClientPrivate)); + object_class->dispose = dbusmenu_gtkclient_dispose; object_class->finalize = dbusmenu_gtkclient_finalize; @@ -73,10 +84,18 @@ 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; + 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); + /* 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); return; } @@ -85,6 +104,12 @@ dbusmenu_gtkclient_init (DbusmenuGtkClient *self) static void dbusmenu_gtkclient_dispose (GObject *object) { + DbusmenuGtkClientPrivate * priv = DBUSMENU_GTKCLIENT_GET_PRIVATE(object); + + if (priv->agroup != NULL) { + g_object_unref(priv->agroup); + priv->agroup = NULL; + } G_OBJECT_CLASS (dbusmenu_gtkclient_parent_class)->dispose (object); return; @@ -99,6 +124,156 @@ dbusmenu_gtkclient_finalize (GObject *object) return; } +/* Structure for passing data to swap_agroup */ +typedef struct _swap_agroup_t swap_agroup_t; +struct _swap_agroup_t { + DbusmenuGtkClient * client; + GtkAccelGroup * old_agroup; + GtkAccelGroup * new_agroup; +}; + +/* Looks at the old version of the accelerator group and + the new one and makes the state proper. */ +static gboolean +do_swap_agroup (DbusmenuMenuitem * mi, gpointer userdata) { + swap_agroup_t * data = (swap_agroup_t *)userdata; + + /* If we don't have a shortcut we don't care */ + if (!dbusmenu_menuitem_property_exist(mi, DBUSMENU_MENUITEM_PROP_SHORTCUT)) { + return FALSE; + } + + guint key = 0; + GdkModifierType modifiers = 0; + + dbusmenu_menuitem_property_get_shortcut(mi, &key, &modifiers); + + if (key == 0) { + return FALSE; + } + + #ifdef MASSIVEDEBUGGING + g_debug("Setting shortcut on '%s': %d %X", dbusmenu_menuitem_property_get(mi, DBUSMENU_MENUITEM_PROP_LABEL), key, modifiers); + #endif + + GtkMenuItem * gmi = dbusmenu_gtkclient_menuitem_get(data->client, mi); + if (gmi == NULL) { + return FALSE; + } + + const gchar * accel_path = gtk_menu_item_get_accel_path(gmi); + + if (accel_path != NULL) { + gtk_accel_map_change_entry(accel_path, key, modifiers, TRUE /* replace */); + } else { + gchar * accel_path = g_strdup_printf("<Appmenus>/Generated/%X/%d", GPOINTER_TO_UINT(data->client), dbusmenu_menuitem_get_id(mi)); + + gtk_accel_map_add_entry(accel_path, key, modifiers); + gtk_widget_set_accel_path(GTK_WIDGET(gmi), accel_path, data->new_agroup); + g_free(accel_path); + } + + GtkMenu * submenu = dbusmenu_gtkclient_menuitem_get_submenu(data->client, mi); + if (submenu != NULL) { + gtk_menu_set_accel_group(submenu, data->new_agroup); + } + + return TRUE; +} + +static void +swap_agroup (DbusmenuMenuitem *mi, gpointer userdata) { + do_swap_agroup (mi, userdata); + + return; /* See what I did here, Ted? :) */ +} + +/* Refresh the shortcut for an entry */ +static void +refresh_shortcut (DbusmenuGtkClient * client, DbusmenuMenuitem * mi) +{ + g_return_if_fail(DBUSMENU_IS_GTKCLIENT(client)); + g_return_if_fail(DBUSMENU_IS_MENUITEM(mi)); + + DbusmenuGtkClientPrivate * priv = DBUSMENU_GTKCLIENT_GET_PRIVATE(client); + + swap_agroup_t data; + data.client = client; + data.old_agroup = priv->agroup; + data.new_agroup = priv->agroup; + + if (do_swap_agroup(mi, &data)) { + guint key = 0; + GdkModifierType mod = 0; + GtkMenuItem *gmi = dbusmenu_gtkclient_menuitem_get (client, mi); + + dbusmenu_menuitem_property_get_shortcut (mi, &key, &mod); + + if (key != 0) { + gtk_widget_add_accelerator (GTK_WIDGET (gmi), "activate", priv->agroup, key, mod, GTK_ACCEL_VISIBLE); + } + } + + return; +} + + +/** + * 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) +{ + g_return_if_fail(DBUSMENU_IS_GTKCLIENT(client)); + g_return_if_fail(GTK_IS_ACCEL_GROUP(agroup)); + + DbusmenuGtkClientPrivate * priv = DBUSMENU_GTKCLIENT_GET_PRIVATE(client); + + DbusmenuMenuitem * root = dbusmenu_client_get_root(DBUSMENU_CLIENT(client)); + if (root != NULL) { + swap_agroup_t data; + data.client = client; + data.old_agroup = priv->agroup; + data.new_agroup = agroup; + + dbusmenu_menuitem_foreach(root, swap_agroup, &data); + } + + if (priv->agroup != NULL) { + g_object_unref(priv->agroup); + priv->agroup = NULL; + } + + 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: (transfer none): Either a valid group or #NULL on error or + * none set. + */ +GtkAccelGroup * +dbusmenu_gtkclient_get_accel_group (DbusmenuGtkClient * client) +{ + g_return_val_if_fail(DBUSMENU_IS_GTKCLIENT(client), NULL); + + DbusmenuGtkClientPrivate * priv = DBUSMENU_GTKCLIENT_GET_PRIVATE(client); + + return priv->agroup; +} + /* Internal Functions */ static const gchar * data_menuitem = "dbusmenugtk-data-gtkmenuitem"; @@ -110,10 +285,8 @@ 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()); + GVariant * variant = g_variant_new("i", 0); + dbusmenu_menuitem_handle_event(mi, "clicked", variant, gtk_get_current_event_time()); } else { /* TODO: We need to stop the display of the submenu until this callback returns. */ @@ -124,7 +297,7 @@ menu_pressed_cb (GtkMenuItem * gmi, DbusmenuMenuitem * mi) /* 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) { @@ -141,7 +314,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) { @@ -153,26 +326,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); @@ -182,17 +350,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; @@ -208,23 +373,34 @@ process_toggle_state (DbusmenuMenuitem * mi, GtkMenuItem * gmi, const GValue * v /* 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, GtkMenuItem * gmi) { 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); } return; } +/* 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, GVariant * value, DbusmenuGtkClient * client) +{ + if (!g_strcmp0(prop, DBUSMENU_MENUITEM_PROP_SHORTCUT)) { + refresh_shortcut(client, mi); + } + return; +} + /* Call back that happens when the DbusmenuMenuitem is destroyed. We're making sure to clean up everything else down the pipe. */ @@ -250,6 +426,63 @@ new_menuitem (DbusmenuClient * client, DbusmenuMenuitem * mi, gpointer userdata) return; } +/* Goes through the tree of items and ensure's that all the items + above us are also displayed. */ +static void +activate_helper (GtkMenuShell * shell) +{ + if (shell == NULL) { + return; + } + + if (GTK_IS_MENU(shell)) { + GtkWidget * attach = gtk_menu_get_attach_widget(GTK_MENU(shell)); + + if (attach != NULL) { + GtkWidget * parent = gtk_widget_get_parent(GTK_WIDGET(attach)); + + if (parent != NULL) { + if (GTK_IS_MENU(parent)) { + 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); + } + } + } + + return; +} + +/* Signaled when we should show a menuitem at request of the application + that it is in. */ +static void +item_activate (DbusmenuClient * client, DbusmenuMenuitem * mi, guint timestamp, gpointer userdata) +{ + gpointer pmenu = g_object_get_data(G_OBJECT(mi), data_menu); + if (pmenu == NULL) { + g_warning("Activated menu item doesn't have a menu? ID: %d", dbusmenu_menuitem_get_id(mi)); + return; + } + + activate_helper(GTK_MENU_SHELL(pmenu)); + gtk_menu_shell_select_first(GTK_MENU_SHELL(pmenu), FALSE); + + return; +} + #ifdef MASSIVEDEBUGGING static void destroy_gmi (GtkMenuItem * gmi, DbusmenuMenuitem * mi) @@ -260,21 +493,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) { @@ -291,6 +524,7 @@ dbusmenu_gtkclient_newitem_base (DbusmenuGtkClient * client, DbusmenuMenuitem * /* 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_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); @@ -301,14 +535,15 @@ dbusmenu_gtkclient_newitem_base (DbusmenuGtkClient * client, DbusmenuMenuitem * 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)); + refresh_shortcut(client, item); /* Oh, we're a child, let's deal with that */ if (parent != NULL) { - new_child(parent, item, dbusmenu_menuitem_get_position_realized(item, parent), DBUSMENU_GTKCLIENT(client)); + new_child(parent, item, dbusmenu_menuitem_get_position(item, parent), DBUSMENU_GTKCLIENT(client)); } return; @@ -322,6 +557,7 @@ new_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position, Dbus #endif if (dbusmenu_menuitem_get_root(mi)) { return; } + 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); @@ -335,7 +571,7 @@ new_child (DbusmenuMenuitem * mi, DbusmenuMenuitem * child, guint position, Dbus } GtkMenuItem * childmi = dbusmenu_gtkclient_menuitem_get(gtkclient, child); - gtk_menu_shell_insert(GTK_MENU_SHELL(menu), GTK_WIDGET(childmi), dbusmenu_menuitem_get_position_realized(child, mi)); + gtk_menu_shell_insert(GTK_MENU_SHELL(menu), GTK_WIDGET(childmi), position); gtk_widget_show(GTK_WIDGET(menu)); return; @@ -381,15 +617,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) { @@ -400,15 +636,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) { @@ -424,13 +660,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) @@ -449,7 +685,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); @@ -467,11 +703,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, @@ -484,7 +720,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); @@ -502,10 +738,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 && @@ -514,11 +773,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 */ @@ -555,6 +813,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. */ @@ -573,6 +832,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); } diff --git a/libdbusmenu-gtk/client.h b/libdbusmenu-gtk/client.h index 7672bf7..c986a5d 100644 --- a/libdbusmenu-gtk/client.h +++ b/libdbusmenu-gtk/client.h @@ -43,6 +43,8 @@ G_BEGIN_DECLS #define DBUSMENU_GTKCLIENT_SIGNAL_ROOT_CHANGED DBUSMENU_CLIENT_SIGNAL_ROOT_CHANGED +typedef struct _DbusmenuGtkClientPrivate DbusmenuGtkClientPrivate; + /** DbusmenuGtkClientClass: @parent_class: #GtkMenuClass @@ -50,6 +52,8 @@ G_BEGIN_DECLS @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. */ typedef struct _DbusmenuGtkClientClass DbusmenuGtkClientClass; struct _DbusmenuGtkClientClass { @@ -58,11 +62,13 @@ 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); }; /** @@ -72,6 +78,9 @@ struct _DbusmenuGtkClientClass { typedef struct _DbusmenuGtkClient DbusmenuGtkClient; struct _DbusmenuGtkClient { DbusmenuClient parent; + + /*< Private >*/ + DbusmenuGtkClientPrivate * priv; }; GType dbusmenu_gtkclient_get_type (void); @@ -79,6 +88,9 @@ DbusmenuGtkClient * dbusmenu_gtkclient_new (gchar * dbus_name, gchar * dbus_obje GtkMenuItem * dbusmenu_gtkclient_menuitem_get (DbusmenuGtkClient * client, DbusmenuMenuitem * item); GtkMenu * dbusmenu_gtkclient_menuitem_get_submenu (DbusmenuGtkClient * client, DbusmenuMenuitem * item); +void dbusmenu_gtkclient_set_accel_group (DbusmenuGtkClient * client, GtkAccelGroup * agroup); +GtkAccelGroup * dbusmenu_gtkclient_get_accel_group (DbusmenuGtkClient * client); + void dbusmenu_gtkclient_newitem_base (DbusmenuGtkClient * client, DbusmenuMenuitem * item, GtkMenuItem * gmi, DbusmenuMenuitem * parent); /** diff --git a/libdbusmenu-gtk/dbusmenu-gtk.pc.in b/libdbusmenu-gtk/dbusmenu-gtk-0.4.pc.in index df4cb36..8784556 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 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..f2fe5be --- /dev/null +++ b/libdbusmenu-gtk/dbusmenu-gtk.h @@ -0,0 +1,41 @@ +/* +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> +#include <libdbusmenu-gtk/serializablemenuitem.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..804b13e --- /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 +Libs: -L${libdir} -ldbusmenu-gtk3 + +Name: libdbusmenu-gtk3 +Description: libdbusmenu-gtk3. +Version: @VERSION@ + diff --git a/libdbusmenu-gtk/genericmenuitem.c b/libdbusmenu-gtk/genericmenuitem.c index 8f40d93..2fd6fba 100644 --- a/libdbusmenu-gtk/genericmenuitem.c +++ b/libdbusmenu-gtk/genericmenuitem.c @@ -51,7 +51,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 +58,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 +87,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; @@ -121,6 +127,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 +147,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. */ @@ -158,6 +176,8 @@ get_hpadding (GtkWidget * widget) static void set_label (GtkMenuItem * menu_item, const gchar * label) { + if (label == NULL) return; + GtkWidget * child = gtk_bin_get_child(GTK_BIN(menu_item)); GtkLabel * labelw = NULL; gboolean suppress_update = FALSE; @@ -191,9 +211,10 @@ set_label (GtkMenuItem * menu_item, const gchar * label) update the one that we already have. */ if (labelw == NULL) { /* Build it */ - labelw = GTK_LABEL(gtk_label_new(label)); + labelw = GTK_LABEL(gtk_accel_label_new(label)); gtk_label_set_use_underline(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_widget_show(GTK_WIDGET(labelw)); /* Check to see if it needs to be in the bin for this @@ -257,12 +278,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) @@ -272,7 +293,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: @@ -281,14 +301,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); @@ -301,14 +317,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) { @@ -319,38 +335,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; } @@ -367,11 +377,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) @@ -429,13 +439,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) diff --git a/libdbusmenu-gtk/menu.c b/libdbusmenu-gtk/menu.c index 4fa546b..c2720ac 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); @@ -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,41 @@ 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; +} + /* 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); + + 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 +398,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 +417,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..896e466 100644 --- a/libdbusmenu-gtk/menu.h +++ b/libdbusmenu-gtk/menu.h @@ -42,6 +42,8 @@ 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 @@ -49,16 +51,20 @@ G_BEGIN_DECLS @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. */ 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); }; /** @@ -68,6 +74,9 @@ struct _DbusmenuGtkMenuClass { typedef struct _DbusmenuGtkMenu DbusmenuGtkMenu; struct _DbusmenuGtkMenu { GtkMenu parent; + + /*< Private >*/ + DbusmenuGtkMenuPrivate * priv; }; GType dbusmenu_gtkmenu_get_type (void); diff --git a/libdbusmenu-gtk/menuitem.c b/libdbusmenu-gtk/menuitem.c index 23ff311..508b43f 100644 --- a/libdbusmenu-gtk/menuitem.c +++ b/libdbusmenu-gtk/menuitem.c @@ -27,19 +27,21 @@ 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> /** - 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) @@ -75,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) { @@ -128,3 +130,184 @@ dbusmenu_menuitem_property_get_image (DbusmenuMenuitem * menuitem, const gchar * 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. + */ +gboolean +dbusmenu_menuitem_property_set_shortcut_string (DbusmenuMenuitem * menuitem, const gchar * shortcut) +{ + g_return_val_if_fail(DBUSMENU_IS_MENUITEM(menuitem), FALSE); + g_return_val_if_fail(shortcut != NULL, FALSE); + + guint key = 0; + GdkModifierType modifier = 0; + + gtk_accelerator_parse(shortcut, &key, &modifier); + + if (key == 0) { + g_warning("Unable to parse shortcut string '%s'", shortcut); + return FALSE; + } + + return dbusmenu_menuitem_property_set_shortcut(menuitem, key, modifier); +} + +/** + * 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); + + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); + + if (modifier & GDK_CONTROL_MASK) { + g_variant_builder_add(&builder, "s", DBUSMENU_MENUITEM_SHORTCUT_CONTROL); + } + if (modifier & GDK_MOD1_MASK) { + g_variant_builder_add(&builder, "s", DBUSMENU_MENUITEM_SHORTCUT_ALT); + } + if (modifier & GDK_SHIFT_MASK) { + g_variant_builder_add(&builder, "s", DBUSMENU_MENUITEM_SHORTCUT_SHIFT); + } + if (modifier & GDK_SUPER_MASK) { + g_variant_builder_add(&builder, "s", DBUSMENU_MENUITEM_SHORTCUT_SUPER); + } + + const gchar * keyname = gdk_keyval_name(key); + g_variant_builder_add(&builder, "s", keyname); + + 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 dbusmenu_menuitem_property_set_variant(menuitem, DBUSMENU_MENUITEM_PROP_SHORTCUT, outsidevariant); +} + +/* Look at the closures in an accel group and find + the one that matches the one we've been passed */ +static gboolean +find_closure (GtkAccelKey * key, GClosure * closure, gpointer user_data) +{ + return closure == 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. + */ +gboolean +dbusmenu_menuitem_property_set_shortcut_menuitem (DbusmenuMenuitem * menuitem, const GtkMenuItem * gmi) +{ + g_return_val_if_fail(DBUSMENU_IS_MENUITEM(menuitem), FALSE); + g_return_val_if_fail(GTK_IS_MENU_ITEM(gmi), FALSE); + + GClosure * closure = NULL; + GtkWidget *label = gtk_bin_get_child(GTK_BIN (gmi)); + + if (GTK_IS_ACCEL_LABEL (label)) + { + g_object_get (label, + "accel-closure", &closure, + NULL); + } + + if (closure == NULL) + return FALSE; + + GtkAccelGroup * group = gtk_accel_group_from_accel_closure(closure); + + /* Apparently this is more common than I thought. */ + if (group == NULL) { + return FALSE; + } + + GtkAccelKey * key = gtk_accel_group_find(group, find_closure, closure); + /* Again, not much we can do except complain loudly. */ + g_return_val_if_fail(key != NULL, FALSE); + + if (!gtk_accelerator_valid (key->accel_key, key->accel_mods)) + return FALSE; + + return dbusmenu_menuitem_property_set_shortcut(menuitem, key->accel_key, key->accel_mods); +} + +/** + * 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) +{ + *key = 0; + *modifier = 0; + + g_return_if_fail(DBUSMENU_IS_MENUITEM(menuitem)); + + GVariant * wrapper = dbusmenu_menuitem_property_get_variant(menuitem, DBUSMENU_MENUITEM_PROP_SHORTCUT); + if (wrapper == NULL) { + return; + } + + if (g_variant_n_children(wrapper) != 1) { + g_warning("Unable to parse shortcut, too many keys"); + g_variant_unref(wrapper); + return; + } + + GVariantIter iter; + g_variant_iter_init(&iter, g_variant_get_child_value(wrapper, 0)); + gchar * string; + + while(g_variant_iter_next(&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); + } + + g_free(string); + } + + return; +} + diff --git a/libdbusmenu-gtk/menuitem.h b/libdbusmenu-gtk/menuitem.h index ff458de..6f009df 100644 --- a/libdbusmenu-gtk/menuitem.h +++ b/libdbusmenu-gtk/menuitem.h @@ -26,14 +26,25 @@ 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> #include <libdbusmenu-glib/menuitem.h> +#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 * modifier); + +G_END_DECLS + #endif diff --git a/libdbusmenu-gtk/parser.c b/libdbusmenu-gtk/parser.c new file mode 100644 index 0000000..f516dde --- /dev/null +++ b/libdbusmenu-gtk/parser.c @@ -0,0 +1,887 @@ +/* +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 "serializablemenuitem.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 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 void widget_notify_cb (GtkWidget * widget, + GParamSpec * pspec, + gpointer data); +static gboolean should_show_image (GtkImage * image); +static void menuitem_notify_cb (GtkWidget * widget, + GParamSpec * pspec, + gpointer data); + +/** + * dbusmenu_gtk_parse_menu_structure: + * @widget: A #GtkMenuItem or #GtkMenuShell to turn into a #DbusmenuMenuitem + * + * Goes through the GTK structures and turns them into the appropraite + * Dbusmenu structures along with setting up all the relationships + * between the objects. It also stores the dbusmenu items as a cache + * on the GTK items so that they'll be reused if necissary. + * + * Return value: (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); + + RecurseContext recurse = {0}; + + recurse.toplevel = gtk_widget_get_toplevel(widget); + + parse_menu_structure_helper(widget, &recurse); + + return recurse.parent; +} + +/* Called when the dbusmenu item that we're keeping around + is finalized */ +static void +dbusmenu_cache_freed (gpointer data, GObject * obj) +{ + /* If the dbusmenu item is killed we don't need to remove + the weak ref as well. */ + g_object_steal_data(G_OBJECT(data), CACHED_MENUITEM); + + ParserData *pdata = (ParserData *)g_object_get_data(G_OBJECT(obj), PARSER_DATA); + + if (pdata != NULL && pdata->label != NULL) { + g_signal_handlers_disconnect_by_func(pdata->label, G_CALLBACK(label_notify_cb), obj); + g_object_remove_weak_pointer(G_OBJECT(pdata->label), (gpointer*)&pdata->label); + } + + if (pdata != NULL && pdata->action != NULL) { + g_signal_handlers_disconnect_by_func(pdata->action, G_CALLBACK(action_notify_cb), obj); + g_object_remove_weak_pointer(G_OBJECT(pdata->action), (gpointer*)&pdata->action); + } + + if (pdata != NULL && pdata->widget != NULL) { + g_signal_handlers_disconnect_by_func(pdata->widget, G_CALLBACK(widget_notify_cb), obj); + g_object_remove_weak_pointer(G_OBJECT(pdata->widget), (gpointer*)&pdata->widget); + } + + if (pdata != NULL && pdata->shell != NULL) { + g_signal_handlers_disconnect_by_func(pdata->shell, G_CALLBACK(child_added_cb), obj); + g_object_remove_weak_pointer(G_OBJECT(pdata->shell), (gpointer*)&pdata->shell); + } + + if (pdata != NULL && pdata->image != NULL) { + g_signal_handlers_disconnect_by_func(pdata->image, G_CALLBACK(image_notify_cb), obj); + g_object_remove_weak_pointer(G_OBJECT(pdata->image), (gpointer*)&pdata->image); + } + + return; +} + +/* Called if we replace the cache on the object with a new + dbusmenu menuitem */ +static void +object_cache_freed (gpointer data) +{ + // TODO: make this have access to both data and obj so we can call these + //if (!G_IS_OBJECT(obj)) return; + //g_object_weak_unref(G_OBJECT(obj), dbusmenu_cache_freed, data); + //dbusmenu_cache_freed(data, obj); + + g_signal_handlers_disconnect_by_func(gtk_icon_theme_get_default(), G_CALLBACK(theme_changed_cb), data); + + return; +} + +/* 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, g_free); + + g_object_set_data_full(G_OBJECT(widget), CACHED_MENUITEM, item, object_cache_freed); + g_object_weak_ref(G_OBJECT(item), dbusmenu_cache_freed, widget); + + return item; +} + +static void +parse_menu_structure_helper (GtkWidget * widget, RecurseContext * recurse) +{ + + /* If this is a shell, then let's handle the items in it. */ + if (GTK_IS_MENU_SHELL (widget)) { + /* Okay, this is a little janky and all.. but some applications update some + * menuitem properties such as sensitivity on the activate callback. This + * seems a little weird, but it's not our place to judge when all this code + * is so crazy. So we're going to get ever crazier and activate all the + * menus that are directly below the menubar and force the applications to + * update their sensitivity. The menus won't actually popup in the app + * window due to our gtk+ patches. + * + * Note that this will not force menuitems in submenus to be updated as well. + */ + if (recurse->parent == NULL && GTK_IS_MENU_BAR(widget)) { + GList *children = gtk_container_get_children (GTK_CONTAINER (widget)); + GList *iter; + + for (iter = children; iter != NULL; iter = iter->next) { + gtk_menu_shell_activate_item (GTK_MENU_SHELL (widget), + iter->data, + TRUE); + } + + g_list_free (children); + } + + if (recurse->parent == NULL) { + recurse->parent = new_menuitem(widget); + } + + ParserData *pdata = (ParserData *)g_object_get_data(G_OBJECT(recurse->parent), PARSER_DATA); + + pdata->shell = widget; + g_signal_connect (G_OBJECT (widget), + "child-added", + G_CALLBACK (child_added_cb), + recurse->parent); + g_object_add_weak_pointer(G_OBJECT (widget), (gpointer*)&pdata->shell); + + 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) { + /* TODO: Should we set a weak ref on the parent? */ + g_object_set_data (G_OBJECT (thisitem), + "dbusmenu-parent", + recurse->parent); + 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; +} + +/* 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 subclass of our serializable menu item then we can + use its own build function */ + if (DBUSMENU_IS_GTK_SERIALIZABLE_MENU_ITEM(widget)) { + DbusmenuGtkSerializableMenuItem * smi = DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM(widget); + return dbusmenu_gtk_serializable_menu_item_build_menuitem(smi); + } + + /* 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)) + { + dbusmenu_menuitem_property_set (mi, + "type", + "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); + + if (label) + { + // Sometimes, an app will directly find and modify the label + // (like empathy), so watch the label especially for that. + dbusmenu_menuitem_property_set (mi, + "label", + gtk_label_get_text (GTK_LABEL (label))); + + 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); + } + } + } + + if (!g_object_get_data (G_OBJECT (widget), "gtk-empty-menu-item") && !GTK_IS_TEAROFF_MENU_ITEM (widget)) + { + visible = gtk_widget_get_visible (widget); + sensitive = gtk_widget_get_sensitive (widget); + } + + dbusmenu_menuitem_property_set_shortcut_menuitem (mi, GTK_MENU_ITEM (widget)); + + g_signal_connect (G_OBJECT (mi), + DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, + G_CALLBACK (item_activated), + widget); + + g_signal_connect (G_OBJECT (mi), + DBUSMENU_MENUITEM_SIGNAL_ABOUT_TO_SHOW, + G_CALLBACK (item_about_to_show), + widget); + } + + dbusmenu_menuitem_property_set_bool (mi, + DBUSMENU_MENUITEM_PROP_VISIBLE, + visible); + + dbusmenu_menuitem_property_set_bool (mi, + DBUSMENU_MENUITEM_PROP_ENABLED, + sensitive); + + pdata->widget = widget; + g_signal_connect (widget, + "notify", + G_CALLBACK (widget_notify_cb), + mi); + g_object_add_weak_pointer(G_OBJECT (widget), (gpointer*)&pdata->widget); + + 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_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 +label_notify_cb (GtkWidget *widget, + GParamSpec *pspec, + gpointer data) +{ + DbusmenuMenuitem *child = (DbusmenuMenuitem *)data; + + if (pspec->name == g_intern_static_string ("label")) + { + dbusmenu_menuitem_property_set (child, + DBUSMENU_MENUITEM_PROP_LABEL, + gtk_label_get_text (GTK_LABEL (widget))); + } +} + +static void +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")) + { + dbusmenu_menuitem_property_set (mi, + DBUSMENU_MENUITEM_PROP_LABEL, + gtk_action_get_label (action)); + } +} + +static void +item_activated (DbusmenuMenuitem *item, guint timestamp, gpointer user_data) +{ + GtkWidget *child; + + if (user_data != NULL) + { + child = (GtkWidget *)user_data; + + if (GTK_IS_MENU_ITEM (child)) + { + gtk_menu_item_activate (GTK_MENU_ITEM (child)); + } + } +} + +static gboolean +item_about_to_show (DbusmenuMenuitem *item, gpointer user_data) +{ + GtkWidget *child; + + if (user_data != NULL) + { + child = (GtkWidget *)user_data; + + if (GTK_IS_MENU_ITEM (child)) + { + // Only called for items with submens. So we activate it here in + // case the program dynamically creates menus (like empathy does) + gtk_menu_item_activate (GTK_MENU_ITEM (child)); + } + } + + return TRUE; +} + +static void +widget_notify_cb (GtkWidget *widget, + GParamSpec *pspec, + gpointer data) +{ + DbusmenuMenuitem *child = (DbusmenuMenuitem *)data; + + if (pspec->name == g_intern_static_string ("sensitive")) + { + dbusmenu_menuitem_property_set_bool (child, + DBUSMENU_MENUITEM_PROP_ENABLED, + gtk_widget_get_sensitive (widget)); + } + else if (pspec->name == g_intern_static_string ("label")) + { + dbusmenu_menuitem_property_set (child, + DBUSMENU_MENUITEM_PROP_LABEL, + gtk_menu_item_get_label (GTK_MENU_ITEM (widget))); + } + else if (pspec->name == g_intern_static_string ("visible")) + { + dbusmenu_menuitem_property_set_bool (child, + DBUSMENU_MENUITEM_PROP_VISIBLE, + gtk_widget_get_visible (widget)); + } + else if (pspec->name == g_intern_static_string ("image") || + pspec->name == g_intern_static_string ("always-show-image")) + { + GtkWidget *image; + image = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (widget)); + 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_get_parent (widget) == NULL) + { + g_signal_handlers_disconnect_by_func (widget, + G_CALLBACK (widget_notify_cb), + child); + + DbusmenuMenuitem *parent = g_object_get_data (G_OBJECT (child), "dbusmenu-parent"); + + if (DBUSMENU_IS_MENUITEM (parent) && DBUSMENU_IS_MENUITEM (child)) + { + dbusmenu_menuitem_child_delete (parent, child); + } + } + } + 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_menu_item_get_submenu (GTK_MENU_ITEM (widget)); + parse_menu_structure_helper(menu, &recurse); + } 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)); + } + } +} + +/* 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; + + parse_menu_structure_helper(widget, &recurse); +} + +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..a40d709 --- /dev/null +++ b/libdbusmenu-gtk/parser.h @@ -0,0 +1,37 @@ +/* +Parse to take a set of GTK Menus and turn them into something that can +be sent over the wire. + +Copyright 2011 Canonical Ltd. + +Authors: + Ted Gould <ted@canonical.com> + +This program is free software: you can redistribute it and/or modify it +under the terms of either or both of the following licenses: + +1) the GNU Lesser General Public License version 3, as published by the +Free Software Foundation; and/or +2) the GNU Lesser General Public License version 2.1, as published by +the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranties of +MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR +PURPOSE. See the applicable version of the GNU Lesser General Public +License for more details. + +You should have received a copy of both the GNU Lesser General Public +License version 3 and version 2.1 along with this program. If not, see +<http://www.gnu.org/licenses/> +*/ + +#ifndef DBUSMENU_GTK_PARSER_H__ +#define DBUSMENU_GTK_PARSER_H__ + +#include <libdbusmenu-glib/menuitem.h> +#include <gtk/gtk.h> + +DbusmenuMenuitem * dbusmenu_gtk_parse_menu_structure (GtkWidget * widget); + +#endif /* DBUSMENU_GTK_PARSER_H__ */ diff --git a/libdbusmenu-gtk/serializablemenuitem.c b/libdbusmenu-gtk/serializablemenuitem.c new file mode 100644 index 0000000..29f83a8 --- /dev/null +++ b/libdbusmenu-gtk/serializablemenuitem.c @@ -0,0 +1,288 @@ +/* +An object to act as a base class for easy GTK widgets that can be +transfered over dbusmenu. + +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/> +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "client.h" +#include "serializablemenuitem.h" + +/* + DbusmenuGtkSerializableMenuItemPrivate: + @mi: Menuitem to watch the property changes from +*/ +struct _DbusmenuGtkSerializableMenuItemPrivate { + DbusmenuMenuitem * mi; +}; + +/* Properties */ +enum { + PROP_0, + PROP_MENUITEM +}; + +/* Private macro, only used in object init */ +#define DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_GET_PRIVATE(o) \ +(G_TYPE_INSTANCE_GET_PRIVATE ((o), DBUSMENU_TYPE_GTK_SERIALIZABLE_MENU_ITEM, DbusmenuGtkSerializableMenuItemPrivate)) + +/* Function prototypes */ +static void dbusmenu_gtk_serializable_menu_item_class_init (DbusmenuGtkSerializableMenuItemClass *klass); +static void dbusmenu_gtk_serializable_menu_item_init (DbusmenuGtkSerializableMenuItem *self); +static void dbusmenu_gtk_serializable_menu_item_dispose (GObject *object); +static void dbusmenu_gtk_serializable_menu_item_finalize (GObject *object); +static void set_property (GObject * obj, + guint id, + const GValue * value, + GParamSpec * pspec); +static void get_property (GObject * obj, + guint id, + GValue * value, + GParamSpec * pspec); + +/* GObject boiler plate */ +G_DEFINE_TYPE (DbusmenuGtkSerializableMenuItem, dbusmenu_gtk_serializable_menu_item, GTK_TYPE_MENU_ITEM); + +/* Initialize the stuff in the class structure */ +static void +dbusmenu_gtk_serializable_menu_item_class_init (DbusmenuGtkSerializableMenuItemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (DbusmenuGtkSerializableMenuItemPrivate)); + + object_class->dispose = dbusmenu_gtk_serializable_menu_item_dispose; + object_class->finalize = dbusmenu_gtk_serializable_menu_item_finalize; + object_class->set_property = set_property; + object_class->get_property = get_property; + + g_object_class_install_property (object_class, PROP_MENUITEM, + g_param_spec_object(DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_PROP_MENUITEM, "DBusmenu Menuitem attached to item", + "A menuitem who's properties are being watched and where changes should be watched for updates. It is the responsibility of subclasses to set up the signal handlers for those property changes.", + DBUSMENU_TYPE_MENUITEM, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + return; +} + +/* Initialize the object structures and private structure */ +static void +dbusmenu_gtk_serializable_menu_item_init (DbusmenuGtkSerializableMenuItem *self) +{ + self->priv = DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_GET_PRIVATE(self); + + self->priv->mi = NULL; + + return; +} + +/* Free all references to objects */ +static void +dbusmenu_gtk_serializable_menu_item_dispose (GObject *object) +{ + DbusmenuGtkSerializableMenuItem * smi = DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM(object); + g_return_if_fail(smi != NULL); + + if (smi->priv->mi != NULL) { + g_object_unref(G_OBJECT(smi->priv->mi)); + smi->priv->mi = NULL; + } + + + G_OBJECT_CLASS (dbusmenu_gtk_serializable_menu_item_parent_class)->dispose (object); + return; +} + +/* Free memory */ +static void +dbusmenu_gtk_serializable_menu_item_finalize (GObject *object) +{ + + + + G_OBJECT_CLASS (dbusmenu_gtk_serializable_menu_item_parent_class)->finalize (object); + return; +} + +/* Set an object property */ +static void +set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec) +{ + DbusmenuGtkSerializableMenuItem * smi = DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM(obj); + + switch (id) { + case PROP_MENUITEM: + smi->priv->mi = g_value_get_object(value); + break; + default: + g_return_if_reached(); + break; + } + + return; +} + +/* Get an object property */ +static void +get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec) +{ + DbusmenuGtkSerializableMenuItem * smi = DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM(obj); + + switch (id) { + case PROP_MENUITEM: + g_value_set_object(value, smi->priv->mi); + break; + default: + g_return_if_reached(); + break; + } + + return; +} + +/** + * dbusmenu_gtk_serializable_menu_item_build_menuitem: + * @smi: #DbusmenuGtkSerializableMenuItem to build a #DbusmenuMenuitem mirroring + * + * This function is for menu items that are instanciated from + * GTK and have their properites set using GTK functions. This + * builds a #DbusmenuMenuitem that then has the properties that + * should be sent over the bus to create a new item of this + * type on the other side. + * + * Return value: (transfer full): A #DbusmenuMenuitem who's values will be + * set by this object. + */ +DbusmenuMenuitem * +dbusmenu_gtk_serializable_menu_item_build_menuitem (DbusmenuGtkSerializableMenuItem * smi) +{ + g_return_val_if_fail(DBUSMENU_IS_GTK_SERIALIZABLE_MENU_ITEM(smi), NULL); + + DbusmenuGtkSerializableMenuItemClass * klass = DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_GET_CLASS(smi); + if (klass->build_dbusmenu_menuitem != NULL) { + return klass->build_dbusmenu_menuitem(smi); + } + + return NULL; +} + +/* Callback to the generic type handler */ +typedef struct _type_handler_t type_handler_t; +struct _type_handler_t { + DbusmenuGtkSerializableMenuItemClass * class; + GType type; +}; + +/* Handle the type with this item. */ +static gboolean +type_handler (DbusmenuMenuitem * newitem, DbusmenuMenuitem * parent, DbusmenuClient * client, gpointer user_data) +{ + type_handler_t * th = (type_handler_t *)user_data; + + DbusmenuGtkSerializableMenuItem * smi = DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM(g_object_new(th->type, NULL)); + g_return_val_if_fail(smi != NULL, FALSE); + + dbusmenu_gtk_serializable_menu_item_set_menuitem(smi, newitem); + dbusmenu_gtkclient_newitem_base(DBUSMENU_GTKCLIENT(client), newitem, GTK_MENU_ITEM(smi), parent); + + return TRUE; +} + +/* Destruction is inevitable */ +static void +type_destroy_handler (DbusmenuClient * client, const gchar * type, gpointer user_data) +{ + g_return_if_fail(user_data != NULL); + type_handler_t * th = (type_handler_t *)user_data; + g_type_class_unref(th->class); + g_free(user_data); + return; +} + +/** + * dbusmenu_gtk_serializable_menu_item_register_to_client: + * @client: #DbusmenuClient that we should register a type at. + * @item_type: The #GType of a class that is a subclass of #DbusmenuGtkSerializableMenuItem + * + * Registers a generic handler for dealing with all subclasses of + * #DbusmenuGtkSerializableMenuItem. This handler responds to the callback, + * creates a new object and attaches it to the appropriate #DbusmenuMenuitem + * object. + */ +void +dbusmenu_gtk_serializable_menu_item_register_to_client (DbusmenuClient * client, GType item_type) +{ + g_return_if_fail(g_type_is_a(item_type, DBUSMENU_TYPE_GTK_SERIALIZABLE_MENU_ITEM)); + + gpointer type_class = g_type_class_ref(item_type); + g_return_if_fail(type_class != NULL); + + DbusmenuGtkSerializableMenuItemClass * class = DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_CLASS(type_class); + + if (class->get_type_string == NULL) { + g_type_class_unref(type_class); + g_error("No 'get_type_string' in subclass of DbusmenuGtkSerializableMenuItem"); + return; + } + + /* Register type */ + type_handler_t * th = g_new0(type_handler_t, 1); + th->class = class; + th->type = item_type; + if (!dbusmenu_client_add_type_handler_full(client, class->get_type_string(), type_handler, th, type_destroy_handler)) { + type_destroy_handler(client, class->get_type_string(), th); + } + + /* Register defaults */ + /* TODO: Need API on another branch */ + + return; +} + +/** + * dbusmenu_gtk_serializable_menu_item_set_menuitem: + * @smi: #DbusmenuGtkSerializableMenuItem to set the @DbusmenuGtkSerializableMenuItem::dbusmenu-menuitem of + * @mi: Menuitem to get the properties from + * + * This function is used on the server side to signal to the object + * that it should get its' property change events from @mi instead + * of expecting calls to its' API. A call to this function sets the + * property and subclasses should listen to the notify signal to + * pick up this property being set. + */ +void +dbusmenu_gtk_serializable_menu_item_set_menuitem (DbusmenuGtkSerializableMenuItem * smi, DbusmenuMenuitem * mi) +{ + g_return_if_fail(DBUSMENU_IS_GTK_SERIALIZABLE_MENU_ITEM(smi)); + g_return_if_fail(mi != NULL); + + smi->priv->mi = mi; + g_object_notify(G_OBJECT(smi), DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_PROP_MENUITEM); + + return; +} diff --git a/libdbusmenu-gtk/serializablemenuitem.h b/libdbusmenu-gtk/serializablemenuitem.h new file mode 100644 index 0000000..db28a24 --- /dev/null +++ b/libdbusmenu-gtk/serializablemenuitem.h @@ -0,0 +1,115 @@ +/* +An object to act as a base class for easy GTK widgets that can be +transfered over dbusmenu. + +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_SERIALIZABLE_MENU_ITEM_H__ +#define DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_H__ 1 + +#include <glib.h> +#include <glib-object.h> +#include <gtk/gtk.h> +#include <libdbusmenu-glib/menuitem.h> +#include <libdbusmenu-glib/client.h> + +G_BEGIN_DECLS + +#define DBUSMENU_TYPE_GTK_SERIALIZABLE_MENU_ITEM (dbusmenu_gtk_serializable_menu_item_get_type ()) +#define DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DBUSMENU_TYPE_GTK_SERIALIZABLE_MENU_ITEM, DbusmenuGtkSerializableMenuItem)) +#define DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), DBUSMENU_TYPE_GTK_SERIALIZABLE_MENU_ITEM, DbusmenuGtkSerializableMenuItemClass)) +#define DBUSMENU_IS_GTK_SERIALIZABLE_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DBUSMENU_TYPE_GTK_SERIALIZABLE_MENU_ITEM)) +#define DBUSMENU_IS_GTK_SERIALIZABLE_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DBUSMENU_TYPE_GTK_SERIALIZABLE_MENU_ITEM)) +#define DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DBUSMENU_TYPE_GTK_SERIALIZABLE_MENU_ITEM, DbusmenuGtkSerializableMenuItemClass)) + +#define DBUSMENU_GTK_SERIALIZABLE_MENU_ITEM_PROP_MENUITEM "dbusmenu-menuitem" + +typedef struct _DbusmenuGtkSerializableMenuItem DbusmenuGtkSerializableMenuItem; +typedef struct _DbusmenuGtkSerializableMenuItemClass DbusmenuGtkSerializableMenuItemClass; +typedef struct _DbusmenuGtkSerializableMenuItemPrivate DbusmenuGtkSerializableMenuItemPrivate; + +/** + DbusmenuGtkSerializableMenuItemClass: + @parent_class: Inherit from GtkMenuItem + @get_type_string: Static function to get a string describing this type + @get_default_properties: Return a hashtable of defaults for the menu item type + @build_dbusmenu_menuitem: Build a menuitem that can be sent over dbus + @_dbusmenu_gtk_serializable_menu_item_reserved1: Reserved for future use. + @_dbusmenu_gtk_serializable_menu_item_reserved2: Reserved for future use. + @_dbusmenu_gtk_serializable_menu_item_reserved3: Reserved for future use. + @_dbusmenu_gtk_serializable_menu_item_reserved4: Reserved for future use. + @_dbusmenu_gtk_serializable_menu_item_reserved5: Reserved for future use. + @_dbusmenu_gtk_serializable_menu_item_reserved6: Reserved for future use. +*/ +struct _DbusmenuGtkSerializableMenuItemClass { + GtkMenuItemClass parent_class; + + /* Subclassable functions */ + const gchar * (*get_type_string) (void); + GHashTable * (*get_default_properties) (void); + + DbusmenuMenuitem * (*build_dbusmenu_menuitem) (DbusmenuGtkSerializableMenuItem * smi); + + /* Signals */ + + + + /* Empty Space */ + /*< Private >*/ + void (*_dbusmenu_gtk_serializable_menu_item_reserved1) (void); + void (*_dbusmenu_gtk_serializable_menu_item_reserved2) (void); + void (*_dbusmenu_gtk_serializable_menu_item_reserved3) (void); + void (*_dbusmenu_gtk_serializable_menu_item_reserved4) (void); + void (*_dbusmenu_gtk_serializable_menu_item_reserved5) (void); + void (*_dbusmenu_gtk_serializable_menu_item_reserved6) (void); +}; + +/** + DbusmenuGtkSerializableMenuItem: + @parent: Inherit from GtkMenuItem + @priv: Blind structure of private variables + + The Serializable Menuitem provides a way for menu items to be created + that can easily be picked up by the Dbusmenu GTK Parser. This way + you can create custom items, and transport them across dbusmenu to + your menus or the appmenu on the other side of the bus. By providing + these function the parser has enough information to both serialize, and + deserialize on the other side, the menuitem you've so carefully created. +*/ +struct _DbusmenuGtkSerializableMenuItem { + GtkMenuItem parent; + + DbusmenuGtkSerializableMenuItemPrivate * priv; +}; + +GType dbusmenu_gtk_serializable_menu_item_get_type (void); + +DbusmenuMenuitem * dbusmenu_gtk_serializable_menu_item_build_menuitem (DbusmenuGtkSerializableMenuItem * smi); +void dbusmenu_gtk_serializable_menu_item_register_to_client (DbusmenuClient * client, GType item_type); +void dbusmenu_gtk_serializable_menu_item_set_menuitem (DbusmenuGtkSerializableMenuItem * smi, DbusmenuMenuitem * mi); + +G_END_DECLS + +#endif |