[balsa/wip/gtk4] GtkTextView has no populate-menu signal



commit 6d4bc7acdcbdde2e0555e63062ce707028f21dfa
Author: Peter Bloomfield <PeterBloomfield bellsouth net>
Date:   Thu Sep 26 22:09:58 2019 -0400

    GtkTextView has no populate-menu signal
    
    The replacement is gtk_text_view_set_extra_menu(), which takes a GMenu
    argument, so we have to set up functions to provide one.

 libbalsa/libbalsa-vfs.c           | 123 ++++++++++++++++++++++---
 libbalsa/libbalsa-vfs.h           |   9 ++
 src/balsa-mime-widget-callbacks.c |  19 ++++
 src/balsa-mime-widget-callbacks.h |   1 +
 src/balsa-mime-widget-text.c      | 187 +++++++++++++++++++++++++-------------
 5 files changed, 263 insertions(+), 76 deletions(-)
---
diff --git a/libbalsa/libbalsa-vfs.c b/libbalsa/libbalsa-vfs.c
index 0d95ff044..7a15ce850 100644
--- a/libbalsa/libbalsa-vfs.c
+++ b/libbalsa/libbalsa-vfs.c
@@ -579,29 +579,66 @@ libbalsa_vfs_file_unlink(const LibbalsaVfs * file, GError **err)
     return result;
 }
 
-
-gboolean
-libbalsa_vfs_launch_app(const LibbalsaVfs * file, GObject * object, GError **err)
+static gboolean
+launch_app(const LibbalsaVfs *file,
+           GAppInfo          *app,
+           GError           **err)
 {
-    GAppInfo *app;
+    gboolean result = FALSE;
     GList * args;
-    gboolean result;
 
-    g_return_val_if_fail(file != NULL, FALSE);
-    g_return_val_if_fail(object != NULL, FALSE);
-
-    app = G_APP_INFO(g_object_get_data(object, LIBBALSA_VFS_MIME_ACTION));
-    if (!app) {
+    if (app == NULL) {
         g_set_error(err, LIBBALSA_VFS_ERROR_QUARK, -1,
                     _("Cannot launch, missing application"));
-        return FALSE;
+        return result;
     }
+
     args = g_list_prepend(NULL, file->priv->gio_gfile);
     result = g_app_info_launch(app, args, NULL, err);
     g_list_free(args);
+
     return result;
 }
 
+gboolean
+libbalsa_vfs_launch_app(const LibbalsaVfs * file, GObject * object, GError **err)
+{
+    GAppInfo *app;
+
+    g_return_val_if_fail(file != NULL, FALSE);
+    g_return_val_if_fail(object != NULL, FALSE);
+
+    app = G_APP_INFO(g_object_get_data(object, LIBBALSA_VFS_MIME_ACTION));
+
+    return launch_app(file, app, err);
+}
+
+gboolean
+libbalsa_vfs_launch_app_gmenu(const LibbalsaVfs *file,
+                              const gchar       *app_id,
+                              GError           **err)
+{
+    GList *apps;
+    GList *app_l;
+    GAppInfo *app = NULL;
+
+    g_return_val_if_fail(file != NULL, FALSE);
+    g_return_val_if_fail(app_id != NULL, FALSE);
+
+    apps = g_app_info_get_all();
+
+    for (app_l = apps; app_l != NULL; app_l = app_l->next) {
+        if (g_strcmp0(g_app_info_get_id(app_l->data), app_id) == 0)
+            break;
+    }
+
+    if (app_l != NULL)
+        app = g_object_ref(app_l->data);
+
+    g_list_free_full(apps, g_object_unref);
+
+    return launch_app(file, app, err);
+}
 
 gboolean
 libbalsa_vfs_launch_app_for_body(LibBalsaMessageBody * mime_body,
@@ -626,6 +663,29 @@ libbalsa_vfs_launch_app_for_body(LibBalsaMessageBody * mime_body,
     return result;
 }
 
+gboolean
+libbalsa_vfs_launch_app_for_body_gmenu(const gchar         *app_id,
+                                       LibBalsaMessageBody *mime_body,
+                                       GError             **err)
+{
+    gchar *uri;
+    LibbalsaVfs * file;
+    gboolean result;
+
+    g_return_val_if_fail(mime_body != NULL, FALSE);
+
+    if (!libbalsa_message_body_save_temporary(mime_body, err))
+        return FALSE;
+
+    uri = g_filename_to_uri(mime_body->temp_filename, NULL, NULL);
+    file = libbalsa_vfs_new_from_uri(uri);
+    g_free(uri);
+    result = libbalsa_vfs_launch_app_gmenu(file, app_id, err);
+    g_object_unref(file);
+
+    return result;
+}
+
 
 gchar *
 libbalsa_vfs_content_description(const gchar * mime_type)
@@ -654,6 +714,7 @@ libbalsa_vfs_content_type_of_buffer(const guchar * buffer,
 }
 
 
+/* fill the passed menu with vfs items */
 static void 
 gio_add_vfs_menu_item(GtkMenu * menu, GAppInfo *app, GCallback callback,
                       gpointer data)
@@ -670,7 +731,6 @@ gio_add_vfs_menu_item(GtkMenu * menu, GAppInfo *app, GCallback callback,
 }
 
 
-/* fill the passed menu with vfs items */
 void
 libbalsa_vfs_fill_menu_by_content_type(GtkMenu * menu,
                                       const gchar * content_type,
@@ -697,6 +757,45 @@ libbalsa_vfs_fill_menu_by_content_type(GtkMenu * menu,
     g_list_free_full(app_list, g_object_unref);
 }
 
+
+/* fill the passed GMenu with vfs items */
+static void
+gio_add_vfs_gmenu_item(GMenu * menu, const gchar *action, GAppInfo *app)
+{
+    gchar *menu_label =
+        g_strdup_printf(_("Open with %s"), g_app_info_get_display_name(app));
+    gchar *detailed_action =
+        g_strdup_printf("%s::%s", action, g_app_info_get_id(app));
+
+    g_menu_append(menu, menu_label, detailed_action);
+    g_free(detailed_action);
+    g_free(menu_label);
+}
+
+void
+libbalsa_vfs_fill_gmenu_by_content_type(GMenu * menu,
+                                        const gchar * action,
+                                        const gchar * content_type)
+{
+    GList* list;
+    GAppInfo *def_app;
+    GList *app_list;
+
+    if ((def_app = g_app_info_get_default_for_type(content_type, FALSE)))
+        gio_add_vfs_gmenu_item(menu, action, def_app);
+
+    app_list = g_app_info_get_all_for_type(content_type);
+    for (list = app_list; list != NULL; list = list->next) {
+        GAppInfo *app = G_APP_INFO(list->data);
+
+        if (app && g_app_info_should_show(app) &&
+            (!def_app || !g_app_info_equal(app, def_app)))
+            gio_add_vfs_gmenu_item(menu, action, app);
+    }
+    g_clear_object(&def_app);
+    g_list_free_full(app_list, g_object_unref);
+}
+
 GtkWidget *
 libbalsa_vfs_mime_button(LibBalsaMessageBody * mime_body,
                          const gchar * content_type,
diff --git a/libbalsa/libbalsa-vfs.h b/libbalsa/libbalsa-vfs.h
index 4a0ad6e5c..b7af02a75 100644
--- a/libbalsa/libbalsa-vfs.h
+++ b/libbalsa/libbalsa-vfs.h
@@ -71,13 +71,22 @@ gint     libbalsa_vfs_file_unlink(const LibbalsaVfs *file,
 gboolean libbalsa_vfs_launch_app(const LibbalsaVfs *file,
                                  GObject           *object,
                                  GError           **err);
+gboolean libbalsa_vfs_launch_app_gmenu(const LibbalsaVfs *file,
+                                       const gchar       *app_id,
+                                       GError           **err);
 gboolean libbalsa_vfs_launch_app_for_body(LibBalsaMessageBody *mime_body,
                                           GObject             *object,
                                           GError             **err);
+gboolean libbalsa_vfs_launch_app_for_body_gmenu(const gchar         *app_id,
+                                                LibBalsaMessageBody *mime_body,
+                                                GError             **err);
 void libbalsa_vfs_fill_menu_by_content_type(GtkMenu     *menu,
                                             const gchar *content_type,
                                             GCallback    callback,
                                             gpointer     data);
+void libbalsa_vfs_fill_gmenu_by_content_type(GMenu       *menu,
+                                             const gchar *action,
+                                             const gchar *content_type);
 GtkWidget *libbalsa_vfs_mime_button(LibBalsaMessageBody *mime_body,
                                     const gchar         *content_type,
                                     GCallback            callback,
diff --git a/src/balsa-mime-widget-callbacks.c b/src/balsa-mime-widget-callbacks.c
index a8f5ae150..0373def35 100644
--- a/src/balsa-mime-widget-callbacks.c
+++ b/src/balsa-mime-widget-callbacks.c
@@ -55,6 +55,25 @@ balsa_mime_widget_ctx_menu_cb(GtkWidget * menu_item,
 }
 
 
+void
+balsa_mime_widget_ctx_gmenu_cb(const gchar         *app_id,
+                               LibBalsaMessageBody *mime_body)
+{
+    GError *err = NULL;
+    gboolean result;
+
+    g_return_if_fail(mime_body != NULL);
+    result = libbalsa_vfs_launch_app_for_body_gmenu(app_id,
+                                                    mime_body,
+                                                    &err);
+    if (!result)
+        balsa_information(LIBBALSA_INFORMATION_WARNING,
+                          _("Could not launch application: %s"),
+                          err ? err->message : "Unknown error");
+    g_clear_error(&err);
+}
+
+
 /** Pops up a "save part" dialog for a message part.
 
     @param parent_widget the widget located in the window that is to
diff --git a/src/balsa-mime-widget-callbacks.h b/src/balsa-mime-widget-callbacks.h
index dfa7a64ea..5145368a3 100644
--- a/src/balsa-mime-widget-callbacks.h
+++ b/src/balsa-mime-widget-callbacks.h
@@ -28,6 +28,7 @@ G_BEGIN_DECLS
 
 
 void balsa_mime_widget_ctx_menu_cb(GtkWidget * menu_item, LibBalsaMessageBody * mime_body);
+void balsa_mime_widget_ctx_gmenu_cb(const gchar *app_id, LibBalsaMessageBody *mime_body);
 void balsa_mime_widget_ctx_menu_save(GtkWidget * parent_widget,
                                      LibBalsaMessageBody * mime_body);
 gboolean balsa_mime_widget_key_press_event(GtkEventControllerKey *key_controller,
diff --git a/src/balsa-mime-widget-text.c b/src/balsa-mime-widget-text.c
index 93c790b8c..d729d539a 100644
--- a/src/balsa-mime-widget-text.c
+++ b/src/balsa-mime-widget-text.c
@@ -33,6 +33,7 @@
 #include "balsa-mime-widget.h"
 #include "balsa-mime-widget-callbacks.h"
 #include "balsa-cite-bar.h"
+#include "libbalsa/application-helpers.h"
 
 #if HAVE_GTKSOURCEVIEW
 #include <gtksourceview/gtksource.h>
@@ -43,8 +44,6 @@ static GtkWidget * create_text_widget(const char * content_type);
 static void bm_modify_font_from_string(GtkWidget * widget, const char *font);
 static GtkTextTag * quote_tag(GtkTextBuffer * buffer, gint level, gint margin);
 static void fix_text_widget(GtkWidget *widget, gpointer data);
-static void text_view_populate_popup(GtkWidget *widget, GtkMenu *menu,
-                                     gpointer user_data);
 
 #ifdef HAVE_HTML_WIDGET
 static BalsaMimeWidget *bm_widget_new_html(BalsaMessage * bm,
@@ -123,6 +122,12 @@ static void fill_text_buf_cited(BalsaMimeWidgetText *mwt,
                                 const gchar         *text_body,
                                 gboolean             is_flowed,
                                 gboolean             is_plain);
+static gboolean text_view_url_popup(GtkWidget           *widget,
+                                    BalsaMimeWidgetText *mwt);
+static void mwt_set_extra_menu(GtkWidget           *widget,
+                               BalsaMimeWidgetText *mwt);
+static void structured_phrases_toggle(BalsaMimeWidgetText *mwt,
+                                      gboolean             new_hl);
 
 
 #define PHRASE_HIGHLIGHT_ON    1
@@ -171,9 +176,74 @@ balsa_mime_widget_text_class_init(BalsaMimeWidgetTextClass * klass)
     object_class->finalize = balsa_mime_widget_text_finalize;
 }
 
+static void
+bmwt_launch_app_change_state(GSimpleAction * action,
+                             GVariant      * state,
+                             gpointer        user_data)
+{
+    BalsaMimeWidgetText *mwt = user_data;
+    const gchar *app_id;
+
+    app_id = g_variant_get_string(state, NULL);
+    balsa_mime_widget_ctx_gmenu_cb(app_id, mwt->mime_body);
+
+    g_simple_action_set_state(action, state);
+}
+
+static void
+bmwt_save_activated(GSimpleAction * action,
+                    GVariant      * state,
+                    gpointer        user_data)
+{
+    BalsaMimeWidgetText *mwt = user_data;
+
+    balsa_mime_widget_ctx_menu_save(GTK_WIDGET(mwt), mwt->mime_body);
+
+    g_simple_action_set_state(action, state);
+}
+
+static void
+bmwt_highlight_change_state(GSimpleAction * action,
+                            GVariant      * state,
+                            gpointer        user_data)
+{
+    BalsaMimeWidgetText *mwt = user_data;
+    gboolean new_hl;
+
+    new_hl = g_variant_get_boolean(state);
+    structured_phrases_toggle(mwt, new_hl);
+
+    g_simple_action_set_state(action, state);
+}
+
+static GActionEntry win_entries[] = {
+    {
+        "launch-app",
+        libbalsa_radio_activated,
+        "s",
+        "''",
+        bmwt_launch_app_change_state},
+    {
+        "save-part",
+        bmwt_save_activated},
+    {
+        "highlight-structured-phrases",
+        libbalsa_toggle_activated,
+        NULL,
+        "true",
+        bmwt_highlight_change_state}
+};
+
 static void
 balsa_mime_widget_text_init(BalsaMimeWidgetText * self)
 {
+    GSimpleActionGroup *simple = g_simple_action_group_new();
+
+    g_action_map_add_action_entries(G_ACTION_MAP(simple),
+                                    win_entries, G_N_ELEMENTS(win_entries),
+                                    self);
+    gtk_widget_insert_action_group(GTK_WIDGET(self), "win", G_ACTION_GROUP(simple));
+    g_object_unref(simple);
 }
 
 /*
@@ -265,7 +335,12 @@ balsa_mime_widget_new_text(BalsaMessage * bm, LibBalsaMessageBody * mime_body,
 
     /* create the mime object and the text/source view widget */
     mwt = g_object_new(BALSA_TYPE_MIME_WIDGET_TEXT, NULL);
+    mwt->mime_body = mime_body;
+
     mwt->text_widget = widget = create_text_widget(content_type);
+    g_signal_connect(widget, "popup-menu",
+                     G_CALLBACK(text_view_url_popup), mwt);
+    mwt_set_extra_menu(widget, mwt);
 
     /* configure text or source view */
     gtk_text_view_set_editable(GTK_TEXT_VIEW(widget), FALSE);
@@ -293,10 +368,6 @@ balsa_mime_widget_new_text(BalsaMessage * bm, LibBalsaMessageBody * mime_body,
                     G_CALLBACK(balsa_mime_widget_key_press_event), bm);
     gtk_widget_add_controller(widget, controller);
 
-    mwt->mime_body = mime_body;
-    g_signal_connect(G_OBJECT(widget), "populate-popup",
-                    G_CALLBACK(text_view_populate_popup), mwt);
-
     buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
 
     fill_text_buf_cited(mwt, widget, ptr,
@@ -462,8 +533,7 @@ quote_tag(GtkTextBuffer * buffer, gint level, gint margin)
     return tag;
 }
 
-/* set the gtk_text widget's cursor to a vertical bar
-   fix event mask so that pointer motions are reported (if necessary) */
+/* set the gtk_text widget's cursor to a vertical bar */
 static void
 fix_text_widget(GtkWidget *widget, gpointer data)
 {
@@ -477,26 +547,15 @@ fix_text_widget(GtkWidget *widget, gpointer data)
 }
 
 static void
-gtk_widget_destroy_insensitive(GtkWidget * widget)
-{
-    if (!gtk_widget_get_sensitive(widget) ||
-       GTK_IS_SEPARATOR_MENU_ITEM(widget))
-       gtk_widget_destroy(widget);
-}
-
-static void
-structured_phrases_toggle(GtkCheckMenuItem *checkmenuitem,
-                         gpointer          user_data)
+structured_phrases_toggle(BalsaMimeWidgetText *mwt,
+                          gboolean             new_hl)
 {
-    BalsaMimeWidgetText *mwt = user_data;
     GtkTextView * text_view;
     GtkTextTagTable * table;
     GtkTextTag * tag;
-    gboolean new_hl;
 
     text_view = GTK_TEXT_VIEW(mwt->text_widget);
     table = gtk_text_buffer_get_tag_table(gtk_text_view_get_buffer(text_view));
-    new_hl = gtk_check_menu_item_get_active(checkmenuitem);
     if (!table || mwt->phrase_hl == 0 ||
        (mwt->phrase_hl == PHRASE_HIGHLIGHT_ON && new_hl) ||
        (mwt->phrase_hl == PHRASE_HIGHLIGHT_OFF && !new_hl))
@@ -543,72 +602,72 @@ url_send_cb(GtkWidget * menu_item, message_url_t * uri)
 }
 
 static gboolean
-text_view_url_popup(GtkWidget *widget, GtkMenu *menu, message_url_t *url)
+text_view_url_popup(GtkWidget           *widget,
+                    BalsaMimeWidgetText *mwt)
 {
+    message_url_t *url = mwt->current_url;
+    GtkMenu *menu;
     GtkWidget *menu_item;
+    GdkEvent *event;
 
     /* check if we are over an url */
     if (url == NULL)
        return FALSE;
 
     /* build a popup to copy or open the URL */
-    gtk_container_foreach(GTK_CONTAINER(menu),
-                          (GtkCallback)gtk_widget_destroy, NULL);
+    menu = (GtkMenu *) gtk_menu_new();
 
-    menu_item = gtk_menu_item_new_with_label (_("Copy link"));
-    g_signal_connect (G_OBJECT (menu_item), "activate",
-                      G_CALLBACK (url_copy_cb), (gpointer)url);
-    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+    menu_item = gtk_menu_item_new_with_label(_("Copy link"));
+    g_signal_connect(menu_item, "activate",
+                     G_CALLBACK(url_copy_cb), url);
+    gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
 
-    menu_item = gtk_menu_item_new_with_label (_("Open link"));
-    g_signal_connect (G_OBJECT (menu_item), "activate",
-                      G_CALLBACK (url_open_cb), (gpointer)url);
-    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+    menu_item = gtk_menu_item_new_with_label(_("Open link"));
+    g_signal_connect(menu_item, "activate",
+                     G_CALLBACK(url_open_cb), url);
+    gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
 
-    menu_item = gtk_menu_item_new_with_label (_("Send link…"));
-    g_signal_connect (G_OBJECT (menu_item), "activate",
-                      G_CALLBACK (url_send_cb), (gpointer)url);
+    menu_item = gtk_menu_item_new_with_label(_("Send link…"));
+    g_signal_connect(menu_item, "activate",
+                     G_CALLBACK(url_send_cb), url);
     gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
 
+    event = gtk_get_current_event();
+    if (event != NULL) {
+        gtk_menu_popup_at_pointer(GTK_MENU(menu), event);
+        g_object_unref(event);
+    } else {
+        gtk_menu_popup_at_widget(GTK_MENU(menu), widget,
+                                 GDK_GRAVITY_CENTER, GDK_GRAVITY_CENTER,
+                                 NULL);
+    }
+
     return TRUE;
 }
 
 static void
-text_view_populate_popup(GtkWidget *widget, GtkMenu *menu,
-                         gpointer user_data)
+mwt_set_extra_menu(GtkWidget           *widget,
+                   BalsaMimeWidgetText *mwt)
 {
-    BalsaMimeWidgetText *mwt = user_data;
-    GtkWidget *menu_item;
+    GMenu *menu = g_menu_new();
 
-    gtk_widget_hide(GTK_WIDGET(menu));
-    gtk_container_foreach(GTK_CONTAINER(menu),
-                          (GtkCallback) gtk_widget_hide, NULL);
-    if (text_view_url_popup(widget, menu, mwt->current_url))
-       return;
+    libbalsa_vfs_fill_gmenu_by_content_type(menu,
+                                            "launch-app",
+                                            "text/plain");
 
-    gtk_container_foreach(GTK_CONTAINER(menu),
-                          (GtkCallback)gtk_widget_destroy_insensitive, NULL);
-    gtk_menu_shell_append(GTK_MENU_SHELL(menu),
-                         gtk_separator_menu_item_new ());
-    libbalsa_vfs_fill_menu_by_content_type(menu, "text/plain",
-                                          G_CALLBACK (balsa_mime_widget_ctx_menu_cb),
-                                          (gpointer)mwt->mime_body);
-
-    menu_item = gtk_menu_item_new_with_label (_("Save…"));
-    g_signal_connect (G_OBJECT (menu_item), "activate",
-                      G_CALLBACK (balsa_mime_widget_ctx_menu_save), mwt->mime_body);
-    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+    g_menu_append(menu, _("Save…"), "save-part");
 
     if (mwt->phrase_hl != 0) {
-       gtk_menu_shell_append(GTK_MENU_SHELL(menu),
-                             gtk_separator_menu_item_new ());
-       menu_item = gtk_check_menu_item_new_with_label (_("Highlight structured phrases"));
-       gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM(menu_item),
-                                        mwt->phrase_hl == PHRASE_HIGHLIGHT_ON);
-       g_signal_connect (G_OBJECT (menu_item), "toggled",
-                         G_CALLBACK (structured_phrases_toggle), mwt);
-       gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+        GMenu *section = g_menu_new();
+
+        g_menu_append(section, _("Highlight structured phrases"),
+                      "highlight-structured-phrases");
+
+       g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
     }
+
+    gtk_text_view_set_extra_menu(GTK_TEXT_VIEW(widget), G_MENU_MODEL(menu));
+    g_object_unref(menu);
 }
 
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]