diff options
Diffstat (limited to 'libdbusmenu-gtk/parser.c')
-rw-r--r-- | libdbusmenu-gtk/parser.c | 290 |
1 files changed, 231 insertions, 59 deletions
diff --git a/libdbusmenu-gtk/parser.c b/libdbusmenu-gtk/parser.c index a7f90a2..e988c62 100644 --- a/libdbusmenu-gtk/parser.c +++ b/libdbusmenu-gtk/parser.c @@ -28,7 +28,7 @@ License version 3 and version 2.1 along with this program. If not, see #include "parser.h" #include "menuitem.h" -#include "serializablemenuitem.h" +#include "client.h" #define CACHED_MENUITEM "dbusmenu-gtk-parser-cached-item" #define PARSER_DATA "dbusmenu-gtk-parser-data" @@ -79,9 +79,17 @@ static void item_activated (DbusmenuMenuitem * item, gpointer user_data); static gboolean item_about_to_show (DbusmenuMenuitem * item, gpointer user_data); +static gboolean item_handle_event (DbusmenuMenuitem * item, + const gchar * name, + GVariant * variant, + guint timestamp, + GtkWidget * widget); static void widget_notify_cb (GtkWidget * widget, GParamSpec * pspec, gpointer data); +static void widget_add_cb (GtkWidget * widget, + GtkWidget * child, + gpointer data); static gboolean should_show_image (GtkImage * image); static void menuitem_notify_cb (GtkWidget * widget, GParamSpec * pspec, @@ -164,6 +172,8 @@ parse_data_free (gpointer data) g_signal_handlers_disconnect_matched(pdata->widget, (GSignalMatchType)G_SIGNAL_MATCH_FUNC, 0, 0, NULL, G_CALLBACK(widget_notify_cb), NULL); g_signal_handlers_disconnect_matched(pdata->widget, (GSignalMatchType)G_SIGNAL_MATCH_FUNC, + 0, 0, NULL, G_CALLBACK(widget_add_cb), NULL); + g_signal_handlers_disconnect_matched(pdata->widget, (GSignalMatchType)G_SIGNAL_MATCH_FUNC, 0, 0, NULL, G_CALLBACK(accel_changed), NULL); g_signal_handlers_disconnect_matched(pdata->widget, (GSignalMatchType)G_SIGNAL_MATCH_FUNC, 0, 0, NULL, G_CALLBACK(checkbox_toggled), NULL); @@ -260,6 +270,71 @@ new_menuitem (GtkWidget * widget) return item; } +static gboolean +toggle_widget_visibility (GtkWidget * widget) +{ + gboolean vis = gtk_widget_get_visible (widget); + gtk_widget_set_visible (widget, !vis); + gtk_widget_set_visible (widget, vis); + g_object_unref (G_OBJECT (widget)); + return FALSE; +} + +static void +watch_submenu(DbusmenuMenuitem * mi, GtkWidget * menu) +{ + g_return_if_fail(DBUSMENU_IS_MENUITEM(mi)); + g_return_if_fail(GTK_IS_MENU_SHELL(menu)); + + ParserData *pdata = (ParserData *)g_object_get_data(G_OBJECT(mi), PARSER_DATA); + + pdata->shell = menu; + g_signal_connect (G_OBJECT (menu), + "child-added", + G_CALLBACK (child_added_cb), + mi); + g_signal_connect (G_OBJECT (menu), + "child-removed", + G_CALLBACK (child_removed_cb), + mi); + g_object_add_weak_pointer(G_OBJECT (menu), (gpointer*)&pdata->shell); + + /* Some apps (notably Eclipse RCP apps) don't fill contents of submenus + until the menu is shown. So we fake that by toggling the visibility of + any submenus we come across. Further, these apps need it done with a + delay while they finish initializing, so we put the call in the idle + queue. */ + g_idle_add((GSourceFunc)toggle_widget_visibility, + g_object_ref (G_OBJECT (menu))); +} + +static void +activate_toplevel_item (GtkWidget * item) +{ + /* Make sure that we have a menu item before we start calling + functions that depend on it. This should almost always be + the case. */ + if (!GTK_IS_MENU_ITEM(item)) { + return; + } + + /* If the item is not opening a submenu we don't want to activate + it as that'd cause an action. Like opening a preferences dialog + to the user. That's not a good idea. */ + if (gtk_menu_item_get_submenu(GTK_MENU_ITEM(item)) == NULL) { + return; + } + + GtkWidget * shell = gtk_widget_get_parent (item); + if (!GTK_IS_MENU_BAR (shell)) { + return; + } + + gtk_menu_shell_activate_item (GTK_MENU_SHELL (shell), + item, + TRUE); +} + static void parse_menu_structure_helper (GtkWidget * widget, RecurseContext * recurse) { @@ -276,33 +351,14 @@ parse_menu_structure_helper (GtkWidget * widget, RecurseContext * recurse) * 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); + gtk_container_foreach (GTK_CONTAINER (widget), + (GtkCallback)activate_toplevel_item, + NULL); } 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_signal_connect (G_OBJECT (widget), - "child-removed", - G_CALLBACK (child_removed_cb), - recurse->parent); - g_object_add_weak_pointer(G_OBJECT (widget), (gpointer*)&pdata->shell); + watch_submenu(recurse->parent, widget); } gtk_container_foreach (GTK_CONTAINER (widget), @@ -438,13 +494,6 @@ sanitize_label (GtkLabel * label) 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)) { @@ -454,11 +503,11 @@ construct_dbusmenu_for_widget (GtkWidget * widget) gboolean visible = FALSE; gboolean sensitive = FALSE; - if (GTK_IS_SEPARATOR_MENU_ITEM (widget)) + if (GTK_IS_SEPARATOR_MENU_ITEM (widget) || !find_menu_label (widget)) { dbusmenu_menuitem_property_set (mi, - "type", - "separator"); + DBUSMENU_MENUITEM_PROP_TYPE, + DBUSMENU_CLIENT_TYPES_SEPARATOR); visible = gtk_widget_get_visible (widget); sensitive = gtk_widget_get_sensitive (widget); @@ -512,21 +561,18 @@ construct_dbusmenu_for_widget (GtkWidget * widget) 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. - gchar * text = sanitize_label (GTK_LABEL (label)); - dbusmenu_menuitem_property_set (mi, "label", text); - g_free (text); - - pdata->label = label; - g_signal_connect (G_OBJECT (label), - "notify", - G_CALLBACK (label_notify_cb), - mi); - g_object_add_weak_pointer(G_OBJECT (label), (gpointer*)&pdata->label); - } + // Sometimes, an app will directly find and modify the label + // (like empathy), so watch the label especially for that. + gchar * text = sanitize_label (GTK_LABEL (label)); + dbusmenu_menuitem_property_set (mi, "label", text); + g_free (text); + + pdata->label = label; + g_signal_connect (G_OBJECT (label), + "notify", + G_CALLBACK (label_notify_cb), + mi); + g_object_add_weak_pointer(G_OBJECT (label), (gpointer*)&pdata->label); if (GTK_IS_ACTIVATABLE (widget)) { @@ -554,16 +600,7 @@ construct_dbusmenu_for_widget (GtkWidget * widget) GtkWidget *submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(widget)); if (submenu) { - pdata->shell = submenu; - g_signal_connect (G_OBJECT (submenu), - "child-added", - G_CALLBACK (child_added_cb), - mi); - g_signal_connect (G_OBJECT (submenu), - "child-removed", - G_CALLBACK (child_removed_cb), - mi); - g_object_add_weak_pointer(G_OBJECT(submenu), (gpointer*)&pdata->shell); + watch_submenu(mi, submenu); } if (!g_object_get_data (G_OBJECT (widget), "gtk-empty-menu-item") && !GTK_IS_TEAROFF_MENU_ITEM (widget)) @@ -583,6 +620,11 @@ construct_dbusmenu_for_widget (GtkWidget * widget) DBUSMENU_MENUITEM_SIGNAL_ABOUT_TO_SHOW, G_CALLBACK (item_about_to_show), widget); + + g_signal_connect (G_OBJECT (mi), + DBUSMENU_MENUITEM_SIGNAL_EVENT, + G_CALLBACK (item_handle_event), + widget); } dbusmenu_menuitem_property_set_bool (mi, @@ -598,6 +640,11 @@ construct_dbusmenu_for_widget (GtkWidget * widget) G_CALLBACK (widget_notify_cb), mi); + g_signal_connect (widget, + "add", + G_CALLBACK (widget_add_cb), + mi); + return mi; } @@ -760,11 +807,48 @@ find_menu_label (GtkWidget *widget) } static void +recreate_menu_item (DbusmenuMenuitem * parent, DbusmenuMenuitem * child) +{ + if (parent == NULL) + { + /* We need a parent */ + return; + } + ParserData * pdata = g_object_get_data (G_OBJECT (child), PARSER_DATA); + /* Keep a pointer to the GtkMenuItem, as pdata->widget might be + * invalidated when we delete the DbusmenuMenuitem + */ + GtkWidget * menuitem = pdata->widget; + + dbusmenu_menuitem_child_delete (parent, child); + + RecurseContext recurse = {0}; + recurse.toplevel = gtk_widget_get_toplevel(menuitem); + recurse.parent = parent; + + parse_menu_structure_helper(menuitem, &recurse); +} + +static gboolean +recreate_menu_item_in_idle_cb (gpointer data) +{ + DbusmenuMenuitem * child = (DbusmenuMenuitem *)data; + DbusmenuMenuitem * parent = dbusmenu_menuitem_get_parent (child); + g_object_unref (child); + recreate_menu_item (parent, child); + return FALSE; +} + +static void label_notify_cb (GtkWidget *widget, GParamSpec *pspec, gpointer data) { DbusmenuMenuitem *child = (DbusmenuMenuitem *)data; + GValue prop_value = {0}; + + g_value_init (&prop_value, pspec->value_type); + g_object_get_property (G_OBJECT (widget), pspec->name, &prop_value); if (pspec->name == g_intern_static_string ("label")) { @@ -774,6 +858,27 @@ label_notify_cb (GtkWidget *widget, text); g_free (text); } + else if (pspec->name == g_intern_static_string ("parent")) + { + if (GTK_WIDGET (g_value_get_object (&prop_value)) == NULL) + { + /* This label is being removed from its GtkMenuItem. The + * menuitem becomes a separator now. As the client doesn't handle + * changing types so well, we remove the current DbusmenuMenuitem + * and add a new one. + * + * Note, we have to defer this to idle, as we are called before + * bin->child member of our old parent is invalidated. If we go ahead + * and call parse_menu_structure_helper now, the GtkMenuItem will + * still appear to have a label and we never convert it to a separator + */ + g_object_ref (child); + g_idle_add ((GSourceFunc)recreate_menu_item_in_idle_cb, child); + } + } + + g_value_unset(&prop_value); + return; } static void @@ -872,6 +977,54 @@ item_about_to_show (DbusmenuMenuitem *item, gpointer user_data) return TRUE; } +static gboolean +item_handle_event (DbusmenuMenuitem *item, const gchar *name, + GVariant *variant, guint timestamp, GtkWidget *widget) +{ + if (g_strcmp0 (name, DBUSMENU_MENUITEM_EVENT_OPENED) == 0) + { + GtkWidget *submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget)); + if (submenu != NULL) + { + // Show the submenu so the app can notice and futz with the menus as + // desired (empathy and geany do this) + gtk_widget_show (submenu); + } + } + else if (g_strcmp0 (name, DBUSMENU_MENUITEM_EVENT_CLOSED) == 0) + { + GtkWidget *submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget)); + if (submenu != NULL) + { + // Hide the submenu so the app can notice and futz with the menus as + // desired (empathy and geany do this) + gtk_widget_hide (submenu); + } + } + + return FALSE; // just pass through on everything +} + +static gboolean +handle_first_label (DbusmenuMenuitem *mi) +{ + ParserData *pdata = g_object_get_data (G_OBJECT (mi), PARSER_DATA); + if (!pdata->label) + { + /* GtkMenuItem's can start life as a separator if they have no child + * GtkLabel. In this case, we need to convert the DbusmenuMenuitem from + * a separator to a normal menuitem if the application adds a label. + * As changing types isn't handled too well by the client, we delete + * this menuitem for now and then recreate it + */ + DbusmenuMenuitem * parent = dbusmenu_menuitem_get_parent (mi); + recreate_menu_item (parent, mi); + return TRUE; + } + + return FALSE; +} + static void widget_notify_cb (GtkWidget *widget, GParamSpec *pspec, @@ -891,6 +1044,11 @@ widget_notify_cb (GtkWidget *widget, } else if (pspec->name == g_intern_static_string ("label")) { + if (handle_first_label (child)) + { + return; + } + dbusmenu_menuitem_property_set (child, DBUSMENU_MENUITEM_PROP_LABEL, g_value_get_string (&prop_value)); @@ -952,6 +1110,7 @@ widget_notify_cb (GtkWidget *widget, if (item != NULL) { GtkWidget * menu = GTK_WIDGET (g_value_get_object (&prop_value)); parse_menu_structure_helper(menu, &recurse); + watch_submenu(item, menu); } else { /* Note: it would be really odd that we wouldn't have a cached item, but we should handle that appropriately. */ @@ -962,6 +1121,15 @@ widget_notify_cb (GtkWidget *widget, g_value_unset (&prop_value); } +static void +widget_add_cb (GtkWidget *widget, + GtkWidget *child, + gpointer data) +{ + if (find_menu_label (child) != NULL) + handle_first_label (data); +} + /* A child item was added to a menu we're watching. Let's try to integrate it. */ static void child_added_cb (GtkContainer *menu, GtkWidget *widget, gpointer data) @@ -972,6 +1140,10 @@ child_added_cb (GtkContainer *menu, GtkWidget *widget, gpointer data) recurse.toplevel = gtk_widget_get_toplevel(GTK_WIDGET(menu)); recurse.parent = menuitem; + if (GTK_IS_MENU_BAR(menu)) { + activate_toplevel_item (widget); + } + parse_menu_structure_helper(widget, &recurse); } |