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 @@
- _Trash + to the trash folder --> + Move message to _Trash eml.trash_msg
- _Delete… + _Delete message… eml.delete_msg