diff --git a/src/client/application/application-controller.vala b/src/client/application/application-controller.vala
index 0bb01681..268d2b40 100644
--- a/src/client/application/application-controller.vala
+++ b/src/client/application/application-controller.vala
@@ -279,7 +279,6 @@ public class Application.Controller : Geary.BaseObject {
// Create the main window (must be done after creating actions.)
main_window = new MainWindow(this.application);
main_window.retry_service_problem.connect(on_retry_service_problem);
- main_window.on_shift_key.connect(on_shift_key);
main_window.notify["has-toplevel-focus"].connect(on_has_toplevel_focus);
setup_actions();
@@ -1049,9 +1048,12 @@ public class Application.Controller : Geary.BaseObject {
// Update widgets and such to match capabilities of the current folder ... sensitivity is handled
// by other utility methods
private void update_ui() {
- main_window.main_toolbar.selected_conversations = this.selected_conversations.size;
- main_window.main_toolbar.show_trash_button = current_folder_supports_trash() ||
- !(current_folder is Geary.FolderSupport.Remove);
+ this.main_window.main_toolbar.selected_conversations =
+ this.selected_conversations.size;
+ this.main_window.main_toolbar.update_trash_button(
+ !this.main_window.is_shift_down &&
+ current_folder_supports_trash()
+ );
}
private void on_folder_selected(Geary.Folder? folder) {
@@ -1424,15 +1426,6 @@ public class Application.Controller : Geary.BaseObject {
return sender.cancel_exit();
}
- private void on_shift_key(bool pressed) {
- if (main_window != null && main_window.main_toolbar != null
- && current_account != null && current_folder != null) {
- main_window.main_toolbar.show_trash_button =
- (!pressed && current_folder_supports_trash()) ||
- !(current_folder is Geary.FolderSupport.Remove);
- }
- }
-
// this signal does not necessarily indicate that the application previously didn't have
// focus and now it does
private void on_has_toplevel_focus() {
diff --git a/src/client/components/main-toolbar.vala b/src/client/components/main-toolbar.vala
index 3dc590eb..8896f1df 100644
--- a/src/client/components/main-toolbar.vala
+++ b/src/client/components/main-toolbar.vala
@@ -24,8 +24,7 @@ public class MainToolbar : Gtk.Box {
public FolderPopover move_folder_menu { get; private set; default = new FolderPopover(); }
// How many conversations are selected right now. Should automatically be updated.
public int selected_conversations { get; set; }
- // Whether to show the trash or the delete button
- public bool show_trash_button { get; set; default = true; }
+
// Folder header elements
[GtkChild]
@@ -52,6 +51,8 @@ public class MainToolbar : Gtk.Box {
[GtkChild]
private Gtk.ToggleButton find_button;
+ private bool show_trash_button = true;
+
// Load these at construction time
private Gtk.Image trash_image = new Gtk.Image.from_icon_name("user-trash-symbolic", Gtk.IconSize.MENU);
private Gtk.Image delete_image = new Gtk.Image.from_icon_name("edit-delete-symbolic", Gtk.IconSize.MENU);
@@ -84,7 +85,6 @@ public class MainToolbar : Gtk.Box {
// Setup conversation header elements
this.notify["selected-conversations"].connect(() => update_conversation_buttons());
- this.notify["show-trash-button"].connect(() => update_conversation_buttons());
this.mark_message_button.popover = new Gtk.Popover.from_model(null, mark_menu);
this.copy_message_button.popover = copy_folder_menu;
this.move_message_button.popover = move_folder_menu;
@@ -116,6 +116,11 @@ public class MainToolbar : Gtk.Box {
conversation_header.show();
}
+ public void update_trash_button(bool show_trash) {
+ this.show_trash_button = show_trash;
+ update_conversation_buttons();
+ }
+
private void set_window_buttons() {
string[] buttons = Gtk.Settings.get_default().gtk_decoration_layout.split(":");
this.show_close_button_left = this.show_close_button;
@@ -146,8 +151,8 @@ public class MainToolbar : Gtk.Box {
this.selected_conversations
);
this.archive_button.tooltip_text = ngettext(
- "Archive conversation (A)",
- "Archive conversations (A)",
+ "Archive conversation",
+ "Archive conversations",
this.selected_conversations
);
@@ -155,16 +160,16 @@ public class MainToolbar : Gtk.Box {
this.trash_delete_button.action_name = "win."+Application.Controller.ACTION_TRASH_CONVERSATION;
this.trash_delete_button.image = trash_image;
this.trash_delete_button.tooltip_text = ngettext(
- "Move conversation to Trash (Delete, Backspace)",
- "Move conversations to Trash (Delete, Backspace)",
+ "Move conversation to Trash",
+ "Move conversations to Trash",
this.selected_conversations
);
} else {
this.trash_delete_button.action_name = "win."+Application.Controller.ACTION_DELETE_CONVERSATION;
this.trash_delete_button.image = delete_image;
this.trash_delete_button.tooltip_text = ngettext(
- "Delete conversation (Shift+Delete)",
- "Delete conversations (Shift+Delete)",
+ "Delete conversation",
+ "Delete conversations",
this.selected_conversations
);
}
diff --git a/src/client/components/main-window.vala b/src/client/components/main-window.vala
index 695a6096..6d04c416 100644
--- a/src/client/components/main-window.vala
+++ b/src/client/components/main-window.vala
@@ -28,6 +28,9 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
get; private set; default = null;
}
+ /** Specifies if the Shift key is currently being held. */
+ public bool is_shift_down { get; private set; default = false; }
+
private Geary.AggregateProgressMonitor progress_monitor = new Geary.AggregateProgressMonitor();
// Used to save/load the window state between sessions.
@@ -727,7 +730,12 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
Gtk.Widget? focus = get_focus();
if (focus == null ||
(!(focus is Gtk.Entry) && !(focus is ComposerWebView))) {
- on_shift_key(event.type == Gdk.EventType.KEY_PRESS);
+ this.is_shift_down = (event.type == Gdk.EventType.KEY_PRESS);
+ this.main_toolbar.update_trash_button(
+ !this.is_shift_down &&
+ current_folder_supports_trash()
+ );
+ on_shift_key(this.is_shift_down);
}
}
}
@@ -736,6 +744,16 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
return (SimpleAction) lookup_action(name);
}
+ private bool current_folder_supports_trash() {
+ Geary.Folder? current = this.current_folder;
+ return (
+ current != null &&
+ current.special_folder_type != TRASH &&
+ !current_folder.properties.is_local_only &&
+ (current_folder as Geary.FolderSupport.Move) != null
+ );
+ }
+
private void on_scan_completed(Geary.App.ConversationMonitor monitor) {
// Done scanning. Check if we have enough messages to fill
// the conversation list; if not, trigger a load_more();
diff --git a/src/client/conversation-list/conversation-list-view.vala b/src/client/conversation-list/conversation-list-view.vala
index 9a18ee7f..6ef39aaf 100644
--- a/src/client/conversation-list/conversation-list-view.vala
+++ b/src/client/conversation-list/conversation-list-view.vala
@@ -318,8 +318,28 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
if (event.button == 3 && event.type == Gdk.EventType.BUTTON_PRESS) {
Geary.App.Conversation conversation = get_model().get_conversation_at_path(path);
- Menu context_menu_model = new Menu();
- context_menu_model.append(_("Delete conversation"), "win."+Application.Controller.ACTION_DELETE_CONVERSATION);
+ GLib.Menu context_menu_model = new GLib.Menu();
+ if (!this.main_window.is_shift_down) {
+ context_menu_model.append(
+ /// Translators: Context menu item
+ ngettext(
+ "Move conversation to _Trash",
+ "Move conversations to _Trash",
+ this.selected.size
+ ),
+ "win." + Application.Controller.ACTION_ARCHIVE_CONVERSATION
+ );
+ } else {
+ context_menu_model.append(
+ /// Translators: Context menu item
+ ngettext(
+ "_Delete conversation",
+ "_Delete conversations",
+ this.selected.size
+ ),
+ "win." + Application.Controller.ACTION_DELETE_CONVERSATION
+ );
+ }
if (conversation.is_unread())
context_menu_model.append(_("Mark as _Read"), "win."+Application.Controller.ACTION_MARK_AS_READ);
@@ -338,9 +358,18 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
actions_section.append(_("_Forward"), "win."+Application.Controller.ACTION_FORWARD_MESSAGE);
context_menu_model.append_section(null, actions_section);
- Gtk.Menu context_menu = new Gtk.Menu.from_model(context_menu_model);
- context_menu.insert_action_group("win", this.main_window);
- context_menu.popup_at_pointer(event);
+ // Use a popover rather than a regular context menu since
+ // the latter grabs the event queue, so the MainWindow
+ // will not receive events if the user releases Shift,
+ // making the trash/delete header bar state wrong.
+ Gtk.Popover context_menu = new Gtk.Popover.from_model(
+ this, context_menu_model
+ );
+ Gdk.Rectangle dest = Gdk.Rectangle();
+ dest.x = (int) event.x;
+ dest.y = (int) event.y;
+ context_menu.set_pointing_to(dest);
+ context_menu.popup();
// When the conversation under the mouse is selected, stop event propagation
return get_selection().path_is_selected(path);
diff --git a/ui/conversation-email-menus.ui b/ui/conversation-email-menus.ui
index a863c620..098e1f2a 100644
--- a/ui/conversation-email-menus.ui
+++ b/ui/conversation-email-menus.ui
@@ -46,15 +46,15 @@