[balsa/gtk4: 5/248] various: Port to new LibBalsaVfs API




commit d97c0bc5bc4b6591164ba96f2d98587f65ebf283
Author: Peter Bloomfield <PeterBloomfield bellsouth net>
Date:   Wed Apr 29 15:52:40 2020 -0400

    various: Port to new LibBalsaVfs API

 libbalsa/libbalsa-vfs.c           |  11 +-
 src/balsa-index.c                 |  53 ++++++---
 src/balsa-mblist.c                | 134 +++++----------------
 src/balsa-mblist.h                |   5 +-
 src/balsa-message.c               | 124 ++++++++++++--------
 src/balsa-mime-widget-callbacks.c |   9 +-
 src/balsa-mime-widget-callbacks.h |   2 +-
 src/balsa-mime-widget-message.c   |  39 +++++--
 src/balsa-mime-widget-text.c      |  72 ++++++++++--
 src/message-window.c              |  48 +++++---
 src/sendmsg-window.c              | 238 ++++++++++++++++++++------------------
 11 files changed, 413 insertions(+), 322 deletions(-)
---
diff --git a/libbalsa/libbalsa-vfs.c b/libbalsa/libbalsa-vfs.c
index 0ff43cfd6..ddcb74be7 100644
--- a/libbalsa/libbalsa-vfs.c
+++ b/libbalsa/libbalsa-vfs.c
@@ -534,7 +534,7 @@ libbalsa_vfs_launch_app(LibbalsaVfs * file, const gchar *app_name, GError **err)
 {
     GList *app_list;
     GList *list;
-    GAppInfo *app;
+    GAppInfo *app = NULL;
     GList * args;
     gboolean result;
 
@@ -543,13 +543,15 @@ libbalsa_vfs_launch_app(LibbalsaVfs * file, const gchar *app_name, GError **err)
 
     app_list = g_app_info_get_all();
     for (list = app_list; list != NULL; list = list->next) {
-        app = G_APP_INFO(list->data);
-        if (strcmp(app_name, g_app_info_get_name(app)) == 0)
+        GAppInfo *this_app = G_APP_INFO(list->data);
+        if (strcmp(app_name, g_app_info_get_name(this_app)) == 0) {
+            app = g_object_ref(this_app);
             break;
+        }
     }
     g_list_free_full(app_list, g_object_unref);
 
-    if (list == NULL) {
+    if (app == NULL) {
         g_set_error(err, LIBBALSA_VFS_ERROR_QUARK, -1,
                     _("Cannot launch, missing application"));
         return FALSE;
@@ -558,6 +560,7 @@ libbalsa_vfs_launch_app(LibbalsaVfs * file, const gchar *app_name, GError **err)
     args = g_list_prepend(NULL, file->gio_gfile);
     result = g_app_info_launch(app, args, NULL, err);
     g_list_free(args);
+    g_object_unref(app);
 
     return result;
 }
diff --git a/src/balsa-index.c b/src/balsa-index.c
index 70b721993..3b9b9d920 100644
--- a/src/balsa-index.c
+++ b/src/balsa-index.c
@@ -46,6 +46,7 @@
 #include "sendmsg-window.h"
 #include "store-address.h"
 
+#include "application-helpers.h"
 #include "filter-funcs.h"
 #include "misc.h"
 #include <glib/gi18n.h>
@@ -1945,21 +1946,6 @@ bi_toggle_new_cb(gpointer user_data)
                             LIBBALSA_MESSAGE_FLAG_NEW);
 }
 
-
-static void
-mru_menu_cb(const gchar * url, BalsaIndex * index)
-{
-    LibBalsaMailbox *mailbox = balsa_find_mailbox_by_url(url);
-
-    g_return_if_fail(mailbox != NULL);
-
-    if (balsa_mailbox_node_get_mailbox(index->mailbox_node) != mailbox) {
-        GArray *selected = balsa_index_selected_msgnos_new(index);
-        balsa_index_transfer(index, selected, mailbox, FALSE);
-        balsa_index_selected_msgnos_free(index, selected);
-    }
-}
-
 /*
  * bndx_popup_menu_create: create the popup menu at init time
  */
@@ -2046,6 +2032,22 @@ bndx_popup_menu_create(BalsaIndex * index)
  * below the headers of the tree-view.
  */
 
+static void
+move_to_change_state(GSimpleAction *action,
+                     GVariant      *parameter,
+                     gpointer       user_data)
+{
+    BalsaIndex *index = user_data;
+    const gchar *url = g_variant_get_string(parameter, NULL);
+    LibBalsaMailbox *mailbox = balsa_find_mailbox_by_url(url);
+
+    if (balsa_mailbox_node_get_mailbox(index->mailbox_node) != mailbox) {
+        GArray *selected = balsa_index_selected_msgnos_new(index);
+        balsa_index_transfer(index, selected, mailbox, FALSE);
+        balsa_index_selected_msgnos_free(index, selected);
+    }
+}
+
 static void
 bndx_do_popup(BalsaIndex * index, const GdkEvent *event)
 {
@@ -2059,6 +2061,11 @@ bndx_do_popup(BalsaIndex * index, const GdkEvent *event)
     GArray *selected = balsa_index_selected_msgnos_new(index);
     guint i;
     gboolean readonly;
+    GSimpleActionGroup *simple;
+    static const GActionEntry bndx_popup_entries[] = {
+        {"move-to", libbalsa_radio_activated, "s", "''", move_to_change_state},
+    };
+    GMenu *mru_menu;
 
     g_debug("%s:%s", __FILE__, __func__);
 
@@ -2093,11 +2100,21 @@ bndx_do_popup(BalsaIndex * index, const GdkEvent *event)
     gtk_widget_set_sensitive(index->move_to_item,
                              any && !readonly);
 
-    submenu =
+    simple = g_simple_action_group_new();
+    g_action_map_add_action_entries(G_ACTION_MAP(simple),
+                                    bndx_popup_entries,
+                                    G_N_ELEMENTS(bndx_popup_entries),
+                                    index);
+    gtk_widget_insert_action_group(menu, "bndx-popup", G_ACTION_GROUP(simple));
+    g_object_unref(simple);
+
+    mru_menu =
         balsa_mblist_mru_menu(GTK_WINDOW
                               (gtk_widget_get_toplevel(GTK_WIDGET(index))),
-                              &balsa_app.folder_mru,
-                              G_CALLBACK(mru_menu_cb), index);
+                              &balsa_app.folder_mru, "bndx-popup.move-to");
+    submenu = gtk_menu_new_from_model(G_MENU_MODEL(mru_menu));
+    g_object_unref(mru_menu);
+
     gtk_menu_item_set_submenu(GTK_MENU_ITEM(index->move_to_item),
                               submenu);
 
diff --git a/src/balsa-mblist.c b/src/balsa-mblist.c
index 9bb94f654..f62106d62 100644
--- a/src/balsa-mblist.c
+++ b/src/balsa-mblist.c
@@ -1667,26 +1667,14 @@ struct _BalsaMBListMRUEntry {
     GCallback user_func;
     gpointer user_data;
     gchar *url;
-    GCallback setup_cb;
 };
 typedef struct _BalsaMBListMRUEntry BalsaMBListMRUEntry;
 
 /* The callback that's passed in must fit this prototype, although it's
  * cast as a GCallback */
 typedef void (*MRUCallback) (gchar * url, gpointer user_data);
-/* Callback used internally for letting the option menu know that the
- * option menu needs to be set up */
-typedef void (*MRUSetup) (gpointer user_data);
 
 /* Forward references */
-static GtkWidget *bmbl_mru_menu(GtkWindow * window, GList ** url_list,
-                                GCallback user_func, gpointer user_data,
-                                gboolean allow_empty, GCallback setup_cb);
-static BalsaMBListMRUEntry *bmbl_mru_new(GList ** url_list,
-                                         GCallback user_func,
-                                         gpointer user_data,
-                                         gchar * url);
-static void bmbl_mru_free(BalsaMBListMRUEntry * mru);
 static void bmbl_mru_activate_cb(GtkWidget * widget, gpointer data);
 static void bmbl_mru_show_tree(GtkWidget * widget, gpointer data);
 static void bmbl_mru_selected_cb(GtkTreeSelection * selection,
@@ -1701,113 +1689,56 @@ static void bmbl_mru_activated_cb(GtkTreeView * tree_view,
  *
  * window:      parent window for the `Other...' dialog;
  * url_list:    pointer to a list of urls;
- * user_func:   called when an item is selected, with the url and
- *              the user_data as arguments;
- * user_data:   passed to the user_func callback.
+ * action:      the action to be taken on selecting an item
  *
- * Returns a pointer to a GtkMenu.
+ * Returns a pointer to a GMenu.
  *
  * Takes a list of urls and creates a menu with an entry for each one
  * that resolves to a mailbox, labeled with the mailbox name, with a
- * last entry that pops up the whole mailbox tree. When an item is
- * clicked, user_func is called with the url and user_data as
- * arguments, and the url_list is updated.
+ * last entry that pops up the whole mailbox tree.
  */
-GtkWidget *
-balsa_mblist_mru_menu(GtkWindow * window, GList ** url_list,
-                      GCallback user_func, gpointer user_data)
+GMenu *
+balsa_mblist_mru_menu(GtkWindow * window,
+                      GList ** url_list,
+                      const gchar *action)
 {
-    g_return_val_if_fail(url_list != NULL, NULL);
-    return bmbl_mru_menu(window, url_list, user_func, user_data, FALSE,
-                         NULL);
-}
-
-/*
- * bmbl_mru_menu:
- *
- * window, url_list, user_func, user_data:
- *              as for balsa_mblist_mru_menu;
- * allow_empty: if TRUE, a list with an empty url
- *              will be allowed into the menu;
- * setup_cb:    called when the tree has been displayed, to allow the
- *              display to be reset.
- *
- * Returns the GtkMenu.
- */
-static GtkWidget *
-bmbl_mru_menu(GtkWindow * window, GList ** url_list,
-              GCallback user_func, gpointer user_data,
-              gboolean allow_empty, GCallback setup_cb)
-{
-    GtkWidget *menu = gtk_menu_new();
-    GtkWidget *item;
+    GMenu *menu;
+    GMenu *other_menu;
     GList *list;
-    BalsaMBListMRUEntry *mru;
+    GMenuItem *other_item;
+
+    g_return_val_if_fail(GTK_IS_WINDOW(window), NULL);
+    g_return_val_if_fail(url_list != NULL, NULL);
+    g_return_val_if_fail(action != NULL, NULL);
 
-    for (list = *url_list; list; list = g_list_next(list)) {
+    menu = g_menu_new();
+    for (list = *url_list; list != NULL; list = list->next) {
         gchar *url = list->data;
         LibBalsaMailbox *mailbox = balsa_find_mailbox_by_url(url);
 
-        if (mailbox || (allow_empty && !*url)) {
-            mru = bmbl_mru_new(url_list, user_func, user_data, url);
-            item =
-                gtk_menu_item_new_with_label(mailbox ? libbalsa_mailbox_get_name(mailbox) : "");
-            gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
-            g_signal_connect_data(item, "activate",
-                                  G_CALLBACK(bmbl_mru_activate_cb), mru,
-                                  (GClosureNotify) bmbl_mru_free,
-                                  (GConnectFlags) 0);
+        if (mailbox != NULL) {
+            const gchar *name = libbalsa_mailbox_get_name(mailbox);
+            GMenuItem *item;
+
+            item = g_menu_item_new(name, NULL);
+            g_menu_item_set_action_and_target(item, action, "s", url);
+            g_menu_append_item(menu, item);
+            g_object_unref(item);
         }
     }
 
-    gtk_menu_shell_append(GTK_MENU_SHELL(menu),
-                          gtk_separator_menu_item_new());
-
-    mru = bmbl_mru_new(url_list, user_func, user_data, NULL);
-    mru->window = window;
-    mru->setup_cb = setup_cb;
-    item = gtk_menu_item_new_with_mnemonic(_("_Other…"));
-    gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
-    g_signal_connect_data(item, "activate",
-                          G_CALLBACK(bmbl_mru_show_tree), mru,
-                          (GClosureNotify) g_free, (GConnectFlags) 0);
+    other_item = g_menu_item_new(_("_Other…"), NULL);
+    g_menu_item_set_action_and_target(other_item, action, "s", "");
+    other_menu = g_menu_new();
+    g_menu_append_item(other_menu, other_item);
+    g_object_unref(other_item);
 
-    gtk_widget_show_all(menu);
+    g_menu_append_section(menu, NULL, G_MENU_MODEL(other_menu));
+    g_object_unref(other_menu);
 
     return menu;
 }
 
-/*
- * bmbl_mru_new:
- *
- * url_list, user_func, user_data:
- *              as for balsa_mblist_mru_menu;
- * url:         url of a mailbox.
- *
- * Returns a newly allocated BalsaMBListMRUEntry structure, initialized
- * with the data.
- */
-static BalsaMBListMRUEntry *
-bmbl_mru_new(GList ** url_list, GCallback user_func, gpointer user_data,
-             gchar * url)
-{
-    BalsaMBListMRUEntry *mru = g_new(BalsaMBListMRUEntry, 1);
-
-    mru->url_list = url_list;
-    mru->user_func = user_func;
-    mru->user_data = user_data;
-    mru->url = g_strdup(url);
-
-    return mru;
-}
-
-static void
-bmbl_mru_free(BalsaMBListMRUEntry * mru)
-{
-    g_free(mru->url);
-    g_free(mru);
-}
-
 /*
  * bmbl_mru_activate_cb:
  *
@@ -1904,8 +1835,6 @@ bmbl_mru_show_tree(GtkWidget * widget, gpointer data)
 
     gtk_dialog_run(GTK_DIALOG(dialog));
     gtk_widget_destroy(dialog);
-    if (mru->setup_cb)
-        ((MRUSetup) mru->setup_cb) (mru->user_data);
 }
 
 /*
@@ -2110,7 +2039,6 @@ bmbl_mru_combo_box_changed(GtkComboBox * combo_box,
     mru.window = mro->window;
     mru.url_list = mro->url_list;
     mru.user_func = NULL;
-    mru.setup_cb = NULL;
     mru.user_data = combo_box;
     mru.url = NULL;
     bmbl_mru_show_tree(NULL, &mru);
diff --git a/src/balsa-mblist.h b/src/balsa-mblist.h
index 9b0f1d69e..ee6b4f6a2 100644
--- a/src/balsa-mblist.h
+++ b/src/balsa-mblist.h
@@ -50,8 +50,9 @@ BalsaMailboxNode *balsa_mblist_get_selected_node(BalsaMBList * mblist);
 BalsaMailboxNode *balsa_mblist_get_node_by_mailbox(BalsaMBList * mblist,
                                                    LibBalsaMailbox *
                                                    mailbox);
-GtkWidget *balsa_mblist_mru_menu(GtkWindow * window, GList ** url_list,
-                                 GCallback user_func, gpointer user_data);
+GMenu *balsa_mblist_mru_menu(GtkWindow * window,
+                             GList ** url_list,
+                             const gchar *action);
 void balsa_mblist_mru_add(GList ** url_list, const gchar * url);
 void balsa_mblist_mru_drop(GList ** url_list, const gchar * url);
 GtkWidget *balsa_mblist_mru_option_menu(GtkWindow * window, 
diff --git a/src/balsa-message.c b/src/balsa-message.c
index f2584d66b..e5625e729 100644
--- a/src/balsa-message.c
+++ b/src/balsa-message.c
@@ -28,6 +28,7 @@
 #include <ctype.h>
 #include <sys/utsname.h>
 
+#include "application-helpers.h"
 #include "balsa-app.h"
 #include "balsa-icons.h"
 #include "mime.h"
@@ -122,7 +123,7 @@ static void tree_button_press_cb(GtkGestureMultiPress *multi_press_gesture,
 static void part_info_init(BalsaMessage * balsa_message, BalsaPartInfo * info);
 static void part_context_save_all_cb(GtkWidget * menu_item, GList * info_list);
 static void part_context_dump_all_cb(GtkWidget * menu_item, GList * info_list);
-static void part_create_menu (BalsaPartInfo* info);
+static void part_create_menu (BalsaMessage *balsa_message, BalsaPartInfo* info);
 
 /* stuff needed for sending Message Disposition Notifications */
 static void handle_mdn_request(GtkWindow *parent, LibBalsaMessage *message,
@@ -932,16 +933,8 @@ tree_mult_selection_popup(BalsaMessage     *balsa_message,
     selected = g_list_length(balsa_message->save_all_list);
     if (selected == 1) {
         BalsaPartInfo *info = BALSA_PART_INFO(balsa_message->save_all_list->data);
-        if (info->popup_menu) {
-            if (event != NULL) {
-                gtk_menu_popup_at_pointer(GTK_MENU(info->popup_menu), event);
-            } else {
-                gtk_menu_popup_at_widget(GTK_MENU(info->popup_menu),
-                                         GTK_WIDGET(balsa_message),
-                                         GDK_GRAVITY_CENTER, GDK_GRAVITY_CENTER,
-                                         NULL);
-            }
-        }
+        if (info->popup_menu != NULL)
+            gtk_popover_popup(GTK_POPOVER(info->popup_menu));
         g_list_free(balsa_message->save_all_list);
         balsa_message->save_all_list = NULL;
     } else if (selected > 1) {
@@ -1034,11 +1027,9 @@ tree_button_press_cb(GtkGestureMultiPress *multi_press_gesture,
                                      FALSE);
             if (gtk_tree_model_get_iter (model, &iter, path)) {
                 gtk_tree_model_get(model, &iter, PART_INFO_COLUMN, &info, -1);
-                if (info) {
-                    if (info->popup_menu) {
-                        gtk_menu_popup_at_pointer(GTK_MENU(info->popup_menu),
-                                                  (GdkEvent *) event);
-                    }
+                if (info != NULL) {
+                    if (info->popup_menu != NULL)
+                        gtk_popover_popup(GTK_POPOVER(info->popup_menu));
                     g_object_unref(info);
                 }
             }
@@ -1305,10 +1296,6 @@ display_headers(BalsaMessage * balsa_message)
 static void
 part_info_init(BalsaMessage * balsa_message, BalsaPartInfo * info)
 {
-    g_return_if_fail(balsa_message != NULL);
-    g_return_if_fail(info != NULL);
-    g_return_if_fail(info->body != NULL);
-
     info->mime_widget =
         g_object_ref_sink(balsa_mime_widget_new(balsa_message, info->body, info->popup_menu));
 }
@@ -1465,7 +1452,7 @@ display_part(BalsaMessage * balsa_message, LibBalsaMessageBody * body,
            g_free(menu_label);
        }
 
-        part_create_menu (info);
+        part_create_menu(balsa_message, info);
         info->path = gtk_tree_model_get_path(model, iter);
 
         /* add to the tree view */
@@ -1626,7 +1613,43 @@ balsa_message_copy_part(const gchar *url, LibBalsaMessageBody *part)
 }
 
 static void
-part_create_menu (BalsaPartInfo* info)
+save_cb(GSimpleAction *action,
+        GVariant      *parameter,
+        gpointer       user_data)
+{
+    BalsaPartInfo *info = user_data;
+
+    balsa_mime_widget_ctx_menu_save(GTK_WIDGET(info->mime_widget), info->body);
+}
+
+static void
+open_with_change_state(GSimpleAction *action,
+                       GVariant      *parameter,
+                       gpointer       user_data)
+{
+    const gchar *app = g_variant_get_string(parameter, NULL);
+    BalsaPartInfo *info = user_data;
+
+    balsa_mime_widget_ctx_menu_cb(app, info->body);
+
+    g_simple_action_set_state(action, parameter);
+}
+
+static void
+copy_part_change_state(GSimpleAction *action,
+                       GVariant      *parameter,
+                       gpointer       user_data)
+{
+    const gchar *url = g_variant_get_string(parameter, NULL);
+    BalsaPartInfo *info = user_data;
+
+    balsa_message_copy_part(url, info->body);
+
+    g_simple_action_set_state(action, parameter);
+}
+
+static void
+part_create_menu(BalsaMessage *balsa_message, BalsaPartInfo *info)
 /* Remarks: Will add items in the following order:
             1) Default application according to GnomeVFS.
             2) GNOME MIME/GnomeVFS key values that don't match default
@@ -1634,40 +1657,48 @@ part_create_menu (BalsaPartInfo* info)
             3) GnomeVFS shortlist applications, with the default one (sometimes
                included on shortlist, sometimes not) excluded. */
 {
-    GtkWidget* menu_item;
-    gchar* content_type;
-
-    info->popup_menu = gtk_menu_new ();
-    g_object_ref_sink(info->popup_menu);
+    GSimpleActionGroup *simple;
+    static const GActionEntry part_menu_entries[] = {
+        {"save", save_cb},
+        {"open-with", libbalsa_radio_activated, "s", "''", open_with_change_state},
+        {"copy-part", libbalsa_radio_activated, "s", "''", copy_part_change_state}
+    };
+    GMenu *menu;
+    gchar *content_type;
+
+    simple = g_simple_action_group_new();
+    g_action_map_add_action_entries(G_ACTION_MAP(simple),
+                                    part_menu_entries,
+                                    G_N_ELEMENTS(part_menu_entries),
+                                    info);
+    gtk_widget_insert_action_group(GTK_WIDGET(balsa_message),
+                                   "part-menu",
+                                   G_ACTION_GROUP(simple));
+    g_object_unref(simple);
+
+    menu = g_menu_new();
 
     content_type = libbalsa_message_body_get_mime_type (info->body);
-    libbalsa_vfs_fill_menu_by_content_type(GTK_MENU(info->popup_menu),
+    libbalsa_vfs_fill_menu_by_content_type(menu,
                                           content_type,
-                                          G_CALLBACK (balsa_mime_widget_ctx_menu_cb),
-                                          (gpointer)info->body);
+                                           "part-menu.open-with");
 
-    menu_item = gtk_menu_item_new_with_mnemonic (_("_Save…"));
-    g_signal_connect (menu_item, "activate",
-                      G_CALLBACK (balsa_mime_widget_ctx_menu_save), (gpointer) info->body);
-    gtk_menu_shell_append (GTK_MENU_SHELL (info->popup_menu), menu_item);
+    g_menu_append(menu, _("Save…"), "part-menu.save");
 
     if (strcmp(content_type, "message/rfc822") == 0) {
-        GtkWidget *submenu;
-
-        menu_item =
-            gtk_menu_item_new_with_mnemonic(_("_Copy to folder…"));
-        gtk_menu_shell_append(GTK_MENU_SHELL(info->popup_menu), menu_item);
+        GMenu *submenu;
 
         submenu =
-            balsa_mblist_mru_menu(GTK_WINDOW(gtk_widget_get_toplevel(info->popup_menu)),
-                                  &balsa_app.folder_mru,
-                                  G_CALLBACK(balsa_message_copy_part),
-                                  info->body);
-        gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu);
+            balsa_mblist_mru_menu(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(balsa_message))),
+                                  &balsa_app.folder_mru, "part-menu.copy-part");
+
+        g_menu_append_submenu(menu, _("_Copy to folder…"), G_MENU_MODEL(submenu));
     }
 
-    gtk_widget_show_all (info->popup_menu);
-    g_free (content_type);
+    g_free(content_type);
+
+    info->popup_menu =
+        gtk_popover_new_from_model(GTK_WIDGET(info->mime_widget), G_MENU_MODEL(menu));
 }
 
 static void
@@ -1695,7 +1726,6 @@ balsa_part_info_dispose(GObject * object)
     BalsaPartInfo *info = (BalsaPartInfo *) object;
 
     g_clear_object(&info->mime_widget);
-    g_clear_object(&info->popup_menu);
 
     G_OBJECT_CLASS(balsa_part_info_parent_class)->dispose(object);
 }
diff --git a/src/balsa-mime-widget-callbacks.c b/src/balsa-mime-widget-callbacks.c
index 895e5a844..d97482dc6 100644
--- a/src/balsa-mime-widget-callbacks.c
+++ b/src/balsa-mime-widget-callbacks.c
@@ -37,16 +37,15 @@
 
 
 void
-balsa_mime_widget_ctx_menu_cb(GtkWidget * menu_item,
-                             LibBalsaMessageBody * mime_body)
+balsa_mime_widget_ctx_menu_cb(const gchar         *app,
+                             LibBalsaMessageBody *mime_body)
 {
     GError *err = NULL;
     gboolean result;
 
     g_return_if_fail(mime_body != NULL);
-    result = libbalsa_vfs_launch_app_for_body(mime_body,
-                                              G_OBJECT(menu_item),
-                                              &err);
+
+    result = libbalsa_vfs_launch_app_for_body(mime_body, app, &err);
     if (!result)
         balsa_information(LIBBALSA_INFORMATION_WARNING,
                           _("Could not launch application: %s"),
diff --git a/src/balsa-mime-widget-callbacks.h b/src/balsa-mime-widget-callbacks.h
index c819aa4e4..868fbf4a1 100644
--- a/src/balsa-mime-widget-callbacks.h
+++ b/src/balsa-mime-widget-callbacks.h
@@ -27,7 +27,7 @@
 G_BEGIN_DECLS
 
 
-void balsa_mime_widget_ctx_menu_cb(GtkWidget * menu_item, LibBalsaMessageBody * mime_body);
+void balsa_mime_widget_ctx_menu_cb(const gchar * app, LibBalsaMessageBody * mime_body);
 void balsa_mime_widget_ctx_menu_save(GtkWidget * parent_widget,
                                      LibBalsaMessageBody * mime_body);
 gboolean balsa_mime_widget_key_pressed(GtkEventControllerKey *controller,
diff --git a/src/balsa-mime-widget-message.c b/src/balsa-mime-widget-message.c
index 79812591e..6189116e6 100644
--- a/src/balsa-mime-widget-message.c
+++ b/src/balsa-mime-widget-message.c
@@ -25,6 +25,7 @@
 #include <string.h>
 #include <gtk/gtk.h>
 
+#include "application-helpers.h"
 #include "balsa-app.h"
 #include "balsa-icons.h"
 #include "send.h"
@@ -425,8 +426,6 @@ balsa_mime_widget_new_message_tl(BalsaMessage * bm,
 }
 
 
-/* Callback for the "realized" signal; set header frame and text base
- * color when first realized. */
 #define BALSA_MESSAGE_GRID "balsa-message-grid"
 #define bm_header_widget_get_grid(header_widget) \
     g_object_get_data(G_OBJECT(header_widget), BALSA_MESSAGE_GRID)
@@ -438,9 +437,25 @@ bm_header_ctx_menu_reply(GtkWidget * menu_item,
     sendmsg_window_reply_embedded(part, SEND_REPLY);
 }
 
+static void
+copy_change_state(GSimpleAction *action,
+                  GVariant      *parameter,
+                  gpointer       user_data)
+{
+    const gchar *url = g_variant_get_string(parameter, NULL);
+    LibBalsaMessageBody *part = user_data;
+
+    balsa_message_copy_part(url, part);
+}
+
 static void
 bm_header_extend_popup(GtkWidget * widget, GtkMenu * menu, gpointer arg)
 {
+    GSimpleActionGroup *simple;
+    static const GActionEntry header_popup_entries[] = {
+        {"copy", libbalsa_radio_activated, "s", "''", copy_change_state},
+    };
+    GMenu *mru_menu;
     GtkWidget *menu_item, *submenu;
     GtkWidget *separator = gtk_separator_menu_item_new();
 
@@ -453,16 +468,26 @@ bm_header_extend_popup(GtkWidget * widget, GtkMenu * menu, gpointer arg)
     gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
     gtk_widget_show(menu_item);
 
+    simple = g_simple_action_group_new();
+    g_action_map_add_action_entries(G_ACTION_MAP(simple),
+                                    header_popup_entries,
+                                    G_N_ELEMENTS(header_popup_entries),
+                                    arg);
+    gtk_widget_insert_action_group(widget,
+                                   "header-popup",
+                                   G_ACTION_GROUP(simple));
+    g_object_unref(simple);
+
+    mru_menu =
+        balsa_mblist_mru_menu(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
+                              &balsa_app.folder_mru, "header-popup.copy");
+    submenu = gtk_menu_new_from_model(G_MENU_MODEL(mru_menu));
+    g_object_unref(mru_menu);
 
     menu_item = gtk_menu_item_new_with_mnemonic(_("_Copy to folder…"));
     gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
     gtk_widget_show(menu_item);
 
-    submenu =
-        balsa_mblist_mru_menu(GTK_WINDOW
-                              (gtk_widget_get_toplevel(widget)),
-                              &balsa_app.folder_mru,
-                              G_CALLBACK(balsa_message_copy_part), arg);
     gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item),
                               submenu);
     gtk_widget_show_all(submenu);
diff --git a/src/balsa-mime-widget-text.c b/src/balsa-mime-widget-text.c
index 1fdd35273..f1bc81a86 100644
--- a/src/balsa-mime-widget-text.c
+++ b/src/balsa-mime-widget-text.c
@@ -22,6 +22,7 @@
 #endif                          /* HAVE_CONFIG_H */
 #include "balsa-mime-widget-text.h"
 
+#include "application-helpers.h"
 #include <string.h>
 #include <stdlib.h>
 #include "balsa-app.h"
@@ -600,12 +601,29 @@ text_view_url_popup(GtkWidget *widget, GtkMenu *menu, message_url_t *url)
     return TRUE;
 }
 
+static void
+open_with_change_state(GSimpleAction *action,
+                       GVariant      *parameter,
+                       gpointer       user_data)
+{
+    const gchar *app = g_variant_get_string(parameter, NULL);
+    LibBalsaMessageBody *part = user_data;
+
+    balsa_mime_widget_ctx_menu_cb(app, part);
+}
+
 static void
 text_view_populate_popup(GtkWidget *widget, GtkMenu *menu,
                          gpointer user_data)
 {
     BalsaMimeWidgetText *mwt = user_data;
     GtkWidget *menu_item;
+    GSimpleActionGroup *simple;
+    static const GActionEntry text_view_popup_entries[] = {
+        {"open-with", libbalsa_radio_activated, "s", "''", open_with_change_state},
+    };
+    GMenu *open_menu;
+    GtkWidget *submenu;
 
     gtk_widget_hide(GTK_WIDGET(menu));
     gtk_container_foreach(GTK_CONTAINER(menu),
@@ -618,9 +636,27 @@ text_view_populate_popup(GtkWidget *widget, GtkMenu *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);
+
+    /* Set up the "open-with" action: */
+    simple = g_simple_action_group_new();
+    g_action_map_add_action_entries(G_ACTION_MAP(simple),
+                                    text_view_popup_entries,
+                                    G_N_ELEMENTS(text_view_popup_entries),
+                                    mwt->mime_body);
+    gtk_widget_insert_action_group(GTK_WIDGET(menu),
+                                   "text-view-popup",
+                                   G_ACTION_GROUP(simple));
+    g_object_unref(simple);
+
+    open_menu = g_menu_new();
+    libbalsa_vfs_fill_menu_by_content_type(open_menu, "text/plain",
+                                           "text-view-popup.open-with");
+    submenu = gtk_menu_new_from_model(G_MENU_MODEL(open_menu));
+    g_object_unref(open_menu);
+
+    menu_item = gtk_menu_item_new_with_label(_("Open…"));
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu);
+    gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
 
     menu_item = gtk_menu_item_new_with_label(_("Save…"));
     g_signal_connect(menu_item, "activate",
@@ -1110,6 +1146,12 @@ bmwt_html_populate_popup_menu(BalsaMessage * bm,
 {
     GtkWidget *menuitem;
     gpointer mime_body = g_object_get_data(G_OBJECT(html), "mime-body");
+    GSimpleActionGroup *simple;
+    static const GActionEntry text_view_popup_entries[] = {
+        {"open-with", libbalsa_radio_activated, "s", "''", open_with_change_state},
+    };
+    GMenu *open_menu;
+    GtkWidget *submenu;
 
     menuitem = gtk_menu_item_new_with_label(_("Zoom In"));
     g_signal_connect_swapped(menuitem, "activate",
@@ -1139,10 +1181,26 @@ bmwt_html_populate_popup_menu(BalsaMessage * bm,
         gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
     }
 
-    libbalsa_vfs_fill_menu_by_content_type(GTK_MENU(menu), "text/html",
-                                           G_CALLBACK
-                                          (balsa_mime_widget_ctx_menu_cb),
-                                           mime_body);
+    /* Set up the "open-with" action: */
+    simple = g_simple_action_group_new();
+    g_action_map_add_action_entries(G_ACTION_MAP(simple),
+                                    text_view_popup_entries,
+                                    G_N_ELEMENTS(text_view_popup_entries),
+                                    mime_body);
+    gtk_widget_insert_action_group(GTK_WIDGET(menu),
+                                   "text-view-popup",
+                                   G_ACTION_GROUP(simple));
+    g_object_unref(simple);
+
+    open_menu = g_menu_new();
+    libbalsa_vfs_fill_menu_by_content_type(open_menu, "text/plain",
+                                           "text-view-popup.open-with");
+    submenu = gtk_menu_new_from_model(G_MENU_MODEL(open_menu));
+    g_object_unref(open_menu);
+
+    menuitem = gtk_menu_item_new_with_label(_("Open…"));
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
+    gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
 
     menuitem = gtk_menu_item_new_with_label(_("Save…"));
     g_signal_connect(menuitem, "activate",
diff --git a/src/message-window.c b/src/message-window.c
index 933774c3c..303bcfa17 100644
--- a/src/message-window.c
+++ b/src/message-window.c
@@ -602,8 +602,6 @@ message_window_move_message(MessageWindow * mw, LibBalsaMailbox * mailbox)
     guint msgno;
     LibBalsaMessage *original = mw->message;
 
-    g_return_if_fail(mailbox != NULL);
-
     /* Transferring to same mailbox? */
     if (libbalsa_message_get_mailbox(mw->message) == mailbox)
         return;
@@ -628,15 +626,6 @@ message_window_move_message(MessageWindow * mw, LibBalsaMailbox * mailbox)
         gtk_widget_destroy(mw->window);
 }
 
-static void
-mw_mru_menu_cb(gchar * url, gpointer data)
-{
-    LibBalsaMailbox *mailbox = balsa_find_mailbox_by_url(url);
-    MessageWindow *mw = data;
-
-    message_window_move_message(mw, mailbox);
-}
-
 static void
 mw_next_message_activated(GSimpleAction * action, GVariant * parameter,
                           gpointer data)
@@ -805,6 +794,18 @@ message_window_add_action_entries(GActionMap * action_map)
                                     G_N_ELEMENTS(win_entries), action_map);
 }
 
+static void
+move_to_change_state(GSimpleAction *action,
+                     GVariant      *parameter,
+                     gpointer       user_data)
+{
+    MessageWindow *mw = user_data;
+    const gchar *url = g_variant_get_string(parameter, NULL);
+    LibBalsaMailbox *mailbox = balsa_find_mailbox_by_url(url);
+
+    message_window_move_message(mw, mailbox);
+}
+
 void
 message_window_new(LibBalsaMailbox * mailbox, guint msgno)
 {
@@ -820,6 +821,11 @@ message_window_new(LibBalsaMailbox * mailbox, guint msgno)
         { "none", "selected", "all" };
     const gchar resource_path[] = "/org/desktop/Balsa/message-window.ui";
     GAction *action;
+    GSimpleActionGroup *simple;
+    static const GActionEntry message_window_entries[] = {
+        {"move-to", libbalsa_radio_activated, "s", "''", move_to_change_state},
+    };
+    GMenu *mru_menu;
 
     if (!mailbox || !msgno)
        return;
@@ -884,9 +890,23 @@ message_window_new(LibBalsaMailbox * mailbox, guint msgno)
     g_signal_connect(mailbox, "message_expunged",
                      G_CALLBACK(mw_expunged_cb), mw);
 
-    submenu = balsa_mblist_mru_menu(GTK_WINDOW(window),
-                                    &balsa_app.folder_mru,
-                                    G_CALLBACK(mw_mru_menu_cb), mw);
+    /* Set up the "move-to" action */
+    simple = g_simple_action_group_new();
+    g_action_map_add_action_entries(G_ACTION_MAP(simple),
+                                    message_window_entries,
+                                    G_N_ELEMENTS(message_window_entries),
+                                    mw);
+    gtk_widget_insert_action_group(GTK_WIDGET(mw->window),
+                                   "message-window",
+                                   G_ACTION_GROUP(simple));
+    g_object_unref(simple);
+
+    mru_menu =
+        balsa_mblist_mru_menu(GTK_WINDOW(window),
+                              &balsa_app.folder_mru, "message-window.move-to");
+    submenu = gtk_menu_new_from_model(G_MENU_MODEL(mru_menu));
+    g_object_unref(mru_menu);
+
     gtk_container_foreach(GTK_CONTAINER(menubar), mw_menubar_foreach,
                           &move_menu);
     gtk_menu_item_set_submenu(GTK_MENU_ITEM(move_menu), submenu);
diff --git a/src/sendmsg-window.c b/src/sendmsg-window.c
index efe6f2dd7..2adc9ecd4 100644
--- a/src/sendmsg-window.c
+++ b/src/sendmsg-window.c
@@ -1277,15 +1277,16 @@ update_bsmsg_identity(BalsaSendmsg* bsmsg, LibBalsaIdentity* ident)
 
 /* remove_attachment - right mouse button callback */
 static void
-remove_attachment(GtkWidget * menu_item, BalsaAttachInfo *info)
+remove_attachment(GSimpleAction *action,
+                  GVariant      *parameter,
+                  gpointer       user_data)
 {
+    BalsaAttachInfo *info = user_data;
     GtkTreeIter iter;
     GtkTreeModel *model;
     GtkTreeSelection *selection;
     BalsaAttachInfo *test_info;
 
-    g_return_if_fail(info->bm != NULL);
-
     /* get the selected element */
     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(info->bm->tree_view));
     if (!gtk_tree_selection_get_selected(selection, &model, &iter))
@@ -1304,30 +1305,19 @@ remove_attachment(GtkWidget * menu_item, BalsaAttachInfo *info)
     gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
 }
 
-static void
-set_attach_menu_sensitivity(GtkWidget * widget, gpointer data)
-{
-    gint mode =
-        GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "new-mode"));
-
-    if (mode)
-        gtk_widget_set_sensitive(widget, mode != GPOINTER_TO_INT(data));
-}
-
 /* change attachment mode - right mouse button callback */
 static void
-change_attach_mode(GtkWidget * menu_item, BalsaAttachInfo *info)
+change_attach_mode(GSimpleAction *action,
+                   GVariant      *parameter,
+                   gpointer       user_data)
 {
-    gint new_mode =
-        GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item),
-                                          "new-mode"));
+    gint new_mode = g_variant_get_int32(parameter);
+    BalsaAttachInfo *info = user_data;
     GtkTreeIter iter;
     GtkTreeModel *model;
     GtkTreeSelection *selection;
     BalsaAttachInfo *test_info;
 
-    g_return_if_fail(info->bm != NULL);
-
     /* get the selected element */
     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(info->bm->tree_view));
     if (!gtk_tree_selection_get_selected(selection, &model, &iter))
@@ -1336,8 +1326,8 @@ change_attach_mode(GtkWidget * menu_item, BalsaAttachInfo *info)
     /* make sure we got the right element */
     gtk_tree_model_get(model, &iter, ATTACH_INFO_COLUMN, &test_info, -1);
     if (test_info != info) {
-       if (test_info)
-           g_object_unref(test_info);
+        g_object_unref(test_info);
+
        return;
     }
     g_object_unref(test_info);
@@ -1347,7 +1337,7 @@ change_attach_mode(GtkWidget * menu_item, BalsaAttachInfo *info)
        GtkWidget *extbody_dialog, *parent;
        gint result;
 
-       parent = gtk_widget_get_toplevel(menu_item);
+       parent = gtk_widget_get_toplevel(info->bm->window);
        extbody_dialog =
            gtk_message_dialog_new(GTK_WINDOW(parent),
                                   GTK_DIALOG_DESTROY_WITH_PARENT,
@@ -1369,8 +1359,15 @@ change_attach_mode(GtkWidget * menu_item, BalsaAttachInfo *info)
                             _("Attach as Reference?"));
        result = gtk_dialog_run(GTK_DIALOG(extbody_dialog));
        gtk_widget_destroy(extbody_dialog);
-       if (result != GTK_RESPONSE_YES)
+       if (result != GTK_RESPONSE_YES) {
+#if GTK_CHECK_VERSION(3, 22, 0)
+            gtk_popover_popdown(GTK_POPOVER(info->popup_menu));
+#else                           /*GTK_CHECK_VERSION(3, 22, 0) */
+            gtk_widget_hide(info->popup_menu);
+#endif                          /*GTK_CHECK_VERSION(3, 22, 0) */
+
            return;
+        }
     }
 
     /* change the attachment mode */
@@ -1378,44 +1375,54 @@ change_attach_mode(GtkWidget * menu_item, BalsaAttachInfo *info)
     gtk_list_store_set(GTK_LIST_STORE(model), &iter, ATTACH_MODE_COLUMN,
                       info->mode, -1);
 
-    /* set the menu's sensitivities */
-    gtk_container_forall(GTK_CONTAINER(gtk_widget_get_parent(menu_item)),
-                        set_attach_menu_sensitivity,
-                         GINT_TO_POINTER(info->mode));
+#if GTK_CHECK_VERSION(3, 22, 0)
+    gtk_popover_popdown(GTK_POPOVER(info->popup_menu));
+#else                           /*GTK_CHECK_VERSION(3, 22, 0) */
+    gtk_widget_hide(info->popup_menu);
+#endif                          /*GTK_CHECK_VERSION(3, 22, 0) */
+
+    g_simple_action_set_state(action, parameter);
 }
 
 
 /* attachment vfs menu - right mouse button callback */
 static void
-attachment_menu_vfs_cb(GtkWidget * menu_item, BalsaAttachInfo * info)
+attachment_menu_vfs_cb(GSimpleAction *action,
+                       GVariant      *parameter,
+                       gpointer       user_data)
 {
+    const gchar *app = g_variant_get_string(parameter, NULL);
+    BalsaAttachInfo *info = user_data;
     GError *err = NULL;
     gboolean result;
 
-    g_return_if_fail(info != NULL);
-
-    result = libbalsa_vfs_launch_app(info->file_uri,
-                                     G_OBJECT(menu_item),
-                                     &err);
+    result = libbalsa_vfs_launch_app(info->file_uri, app, &err);
     if (!result)
         balsa_information(LIBBALSA_INFORMATION_WARNING,
                           _("Could not launch application: %s"),
                           err ? err->message : "Unknown error");
     g_clear_error(&err);
+
+#if GTK_CHECK_VERSION(3, 22, 0)
+    gtk_popover_popdown(GTK_POPOVER(info->popup_menu));
+#else                           /*GTK_CHECK_VERSION(3, 22, 0) */
+    gtk_widget_hide(info->popup_menu);
+#endif                          /*GTK_CHECK_VERSION(3, 22, 0) */
 }
 
 
 /* URL external body - right mouse button callback */
 static void
-on_open_url_cb(GtkWidget * menu_item, BalsaAttachInfo * info)
+on_open_url_cb(GSimpleAction *action,
+               GVariant      *parameter,
+               gpointer       user_data)
 {
+    BalsaAttachInfo *info = user_data;
     GtkWidget *toplevel;
     GError *err = NULL;
     const gchar * uri;
 
-    g_return_if_fail(info != NULL);
     uri = libbalsa_vfs_get_uri(info->file_uri);
-    g_return_if_fail(uri != NULL);
 
     g_debug("open URL %s", uri);
     toplevel = gtk_widget_get_toplevel(info->bm->window);
@@ -1639,6 +1646,7 @@ get_fwd_mail_headers(const gchar *mailfile)
 /* add_attachment:
    adds given filename (uri format) to the list.
 */
+
 gboolean
 add_attachment(BalsaSendmsg * bsmsg, const gchar *filename,
                gboolean is_a_temp_file, const gchar *forced_mime_type)
@@ -1652,8 +1660,14 @@ add_attachment(BalsaSendmsg * bsmsg, const gchar *filename,
     gchar *utf8name;
     GError *err = NULL;
     GdkPixbuf *pixbuf;
-    GtkWidget *menu_item;
     gchar *content_desc;
+    GSimpleActionGroup *simple;
+    static GActionEntry attachment_entries[] = {
+        {"new-mode", libbalsa_radio_activated, "i", "1", change_attach_mode},
+        {"remove", remove_attachment},
+        {"launch-app", libbalsa_radio_activated, "s", "''", attachment_menu_vfs_cb}
+    };
+    GMenu *menu;
 
     g_debug("Trying to attach '%s'", filename);
     if (!(file_uri = libbalsa_vfs_new_from_uri(filename))) {
@@ -1743,49 +1757,47 @@ add_attachment(BalsaSendmsg * bsmsg, const gchar *filename,
     attach_data->mode = LIBBALSA_ATTACH_AS_ATTACHMENT;
 
     /* build the attachment's popup menu */
-    attach_data->popup_menu = gtk_menu_new();
+    simple = g_simple_action_group_new();
+    g_action_map_add_action_entries(G_ACTION_MAP(simple),
+                                    attachment_entries,
+                                    G_N_ELEMENTS(attachment_entries),
+                                    attach_data);
+
+    gtk_widget_insert_action_group(bsmsg->window,
+                                   "attachment",
+                                   G_ACTION_GROUP(simple));
+    g_object_unref(simple);
+
+    menu = g_menu_new();
 
     /* only real text/... and image/... parts may be inlined */
     if (can_inline) {
-       menu_item =
-           gtk_menu_item_new_with_label(_(attach_modes
-                                           [LIBBALSA_ATTACH_AS_INLINE]));
-       g_object_set_data(G_OBJECT(menu_item), "new-mode",
-                         GINT_TO_POINTER(LIBBALSA_ATTACH_AS_INLINE));
-       g_signal_connect(menu_item, "activate",
-                        G_CALLBACK(change_attach_mode),
-                        (gpointer)attach_data);
-       gtk_menu_shell_append(GTK_MENU_SHELL(attach_data->popup_menu),
-                             menu_item);
+        GMenuItem *menu_item =
+            g_menu_item_new(_(attach_modes[LIBBALSA_ATTACH_AS_INLINE]), NULL);
+        g_menu_item_set_action_and_target(menu_item, "attachment.new-mode", "i",
+                                          LIBBALSA_ATTACH_AS_INLINE);
+        g_menu_append_item(menu, menu_item);
+        g_object_unref(menu_item);
     }
 
     /* all real files can be attachments */
     if (can_inline || !is_a_temp_file) {
-       menu_item =
-           gtk_menu_item_new_with_label(_(attach_modes
-                                           [LIBBALSA_ATTACH_AS_ATTACHMENT]));
-       gtk_widget_set_sensitive(menu_item, FALSE);
-       g_object_set_data(G_OBJECT(menu_item), "new-mode",
-                         GINT_TO_POINTER(LIBBALSA_ATTACH_AS_ATTACHMENT));
-       g_signal_connect(menu_item, "activate",
-                        G_CALLBACK(change_attach_mode),
-                        (gpointer)attach_data);
-       gtk_menu_shell_append(GTK_MENU_SHELL(attach_data->popup_menu),
-                             menu_item);
+        GMenuItem *menu_item =
+            g_menu_item_new(_(attach_modes[LIBBALSA_ATTACH_AS_ATTACHMENT]), NULL);
+        g_menu_item_set_action_and_target(menu_item, "attachment.new-mode", "i",
+                                          LIBBALSA_ATTACH_AS_ATTACHMENT);
+        g_menu_append_item(menu, menu_item);
+        g_object_unref(menu_item);
     }
 
     /* real files may be references (external body) */
     if (!is_a_temp_file) {
-       menu_item =
-           gtk_menu_item_new_with_label(_(attach_modes
-                                           [LIBBALSA_ATTACH_AS_EXTBODY]));
-       g_object_set_data(G_OBJECT(menu_item), "new-mode",
-                         GINT_TO_POINTER(LIBBALSA_ATTACH_AS_EXTBODY));
-       g_signal_connect(menu_item, "activate",
-                        G_CALLBACK(change_attach_mode),
-                        (gpointer)attach_data);
-       gtk_menu_shell_append(GTK_MENU_SHELL(attach_data->popup_menu),
-                             menu_item);
+        GMenuItem *menu_item =
+            g_menu_item_new(_(attach_modes[LIBBALSA_ATTACH_AS_EXTBODY]), NULL);
+        g_menu_item_set_action_and_target(menu_item, "attachment.new-mode", "i",
+                                          LIBBALSA_ATTACH_AS_EXTBODY);
+        g_menu_append_item(menu, menu_item);
+        g_object_unref(menu_item);
     }
 
     if (can_inline || !is_a_temp_file) {
@@ -1795,13 +1807,7 @@ add_attachment(BalsaSendmsg * bsmsg, const gchar *filename,
     }
 
     /* an attachment can be removed */
-    menu_item =
-       gtk_menu_item_new_with_label(_("Remove"));
-    g_signal_connect(menu_item, "activate",
-                    G_CALLBACK(remove_attachment),
-                    (gpointer)attach_data);
-    gtk_menu_shell_append(GTK_MENU_SHELL(attach_data->popup_menu),
-                         menu_item);
+    g_menu_append(menu, _("Remove"), "attachment.remove");
 
     /* Insert another separator */
     menu_item = gtk_separator_menu_item_new();
@@ -1809,12 +1815,14 @@ add_attachment(BalsaSendmsg * bsmsg, const gchar *filename,
 
     /* add the usual vfs menu so the user can inspect what (s)he actually
        attached... (only for non-message attachments) */
-    if (!is_fwd_message)
-       libbalsa_vfs_fill_menu_by_content_type(GTK_MENU(attach_data->popup_menu),
-                                              content_type,
-                                              G_CALLBACK(attachment_menu_vfs_cb),
-                                              (gpointer)attach_data);
-    gtk_widget_show_all(attach_data->popup_menu);
+    if (!is_fwd_message) {
+       libbalsa_vfs_fill_menu_by_content_type(menu, content_type,
+                                               "attachment.launch-app");
+    }
+
+    attach_data->popup_menu =
+        gtk_popover_new_from_model(bsmsg->text, G_MENU_MODEL(menu));
+    g_object_unref(menu);
 
     /* append to the list store */
     content_desc =libbalsa_vfs_content_description(content_type);
@@ -1845,7 +1853,13 @@ add_urlref_attachment(BalsaSendmsg * bsmsg, const gchar *url)
     GtkTreeIter iter;
     BalsaAttachInfo *attach_data;
     GdkPixbuf * pixbuf;
-    GtkWidget *menu_item;
+    GSimpleActionGroup *simple;
+    static GActionEntry attachment_entries[] = {
+        {"remove", remove_attachment},
+        {"open", libbalsa_radio_activated, "s", "''", on_open_url_cb}
+    };
+    GMenu *menu;
+    GMenu *open_menu;
 
     g_debug("Trying to attach '%s'", url);
 
@@ -1870,26 +1884,30 @@ add_urlref_attachment(BalsaSendmsg * bsmsg, const gchar *url)
     attach_data->file_uri = libbalsa_vfs_new_from_uri(url);
 
     /* build the attachment's popup menu - may only be removed */
-    attach_data->popup_menu = gtk_menu_new();
-    menu_item =
-       gtk_menu_item_new_with_label(_("Remove"));
-    g_signal_connect(menu_item, "activate",
-                    G_CALLBACK(remove_attachment),
-                    (gpointer)attach_data);
-    gtk_menu_shell_append(GTK_MENU_SHELL(attach_data->popup_menu),
-                         menu_item);
+    simple = g_simple_action_group_new();
+    g_action_map_add_action_entries(G_ACTION_MAP(simple),
+                                    attachment_entries,
+                                    G_N_ELEMENTS(attachment_entries),
+                                    attach_data);
+    gtk_widget_insert_action_group(bsmsg->window,
+                                   "urlref-attachment",
+                                   G_ACTION_GROUP(simple));
+    g_object_unref(simple);
+
+    menu = g_menu_new();
+    g_menu_append(menu, _("Remove"), "urlref-attachment.remove");
 
     /* add a separator and the usual vfs menu so the user can inspect what
        (s)he actually attached... (only for non-message attachments) */
-    gtk_menu_shell_append(GTK_MENU_SHELL(attach_data->popup_menu),
-                         gtk_separator_menu_item_new());
-    menu_item =
-       gtk_menu_item_new_with_label(_("Open…"));
-    g_signal_connect(menu_item, "activate",
-                    G_CALLBACK(on_open_url_cb),
-                    (gpointer)attach_data);
-    gtk_menu_shell_append(GTK_MENU_SHELL(attach_data->popup_menu),
-                         menu_item);
+    open_menu = g_menu_new();
+    g_menu_append(open_menu, _("Open…"), "urlref-attachment.open");
+
+    g_menu_append_section(menu, NULL, G_MENU_MODEL(open_menu));
+    g_object_unref(open_menu);
+
+    attach_data->popup_menu =
+        gtk_popover_new_from_model(bsmsg->window, G_MENU_MODEL(menu));
+    g_object_unref(menu);
     gtk_widget_show_all(attach_data->popup_menu);
 
     /* append to the list store */
@@ -2396,11 +2414,9 @@ attachment_button_press_cb(GtkGestureMultiPress *multi_press,
            BalsaAttachInfo *attach_info;
 
            gtk_tree_model_get(model, &iter, ATTACH_INFO_COLUMN, &attach_info, -1);
-           if (attach_info) {
-               if (attach_info->popup_menu) {
-                    gtk_menu_popup_at_pointer(GTK_MENU(attach_info->popup_menu),
-                                              (GdkEvent *) event);
-                }
+           if (attach_info != NULL) {
+               if (attach_info->popup_menu != NULL)
+                    gtk_popover_popup(GTK_POPOVER(attach_info->popup_menu));
                g_object_unref(attach_info);
            }
         }
@@ -2421,13 +2437,9 @@ attachment_popup_cb(GtkWidget *widget, gpointer user_data)
        return FALSE;
 
     gtk_tree_model_get(model, &iter, ATTACH_INFO_COLUMN, &attach_info, -1);
-    if (attach_info) {
-       if (attach_info->popup_menu) {
-            gtk_menu_popup_at_widget(GTK_MENU(attach_info->popup_menu),
-                                     GTK_WIDGET(widget),
-                                     GDK_GRAVITY_CENTER, GDK_GRAVITY_CENTER,
-                                     NULL);
-        }
+    if (attach_info != NULL) {
+       if (attach_info->popup_menu != NULL)
+            gtk_popover_popup(GTK_POPOVER(attach_info->popup_menu));
        g_object_unref(attach_info);
     }
 
@@ -6092,8 +6104,6 @@ sw_entry_helper(GSimpleAction      * action,
         gtk_widget_hide(entry[1]);
     }
 
-    g_simple_action_set_state(G_SIMPLE_ACTION(action), state);
-
     if (bsmsg->update_config) { /* then save the config */
         GString *str = g_string_new(NULL);
         unsigned i;


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