diff --git a/po/POTFILES.in b/po/POTFILES.in index 345dcbff..dea321b7 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -384,7 +384,6 @@ src/engine/util/util-time.vala src/engine/util/util-timeout-manager.vala src/engine/util/util-trillian.vala src/mailer/main.vala -[type: gettext/glade]ui/accelerators.ui [type: gettext/glade]ui/account_cannot_remove.glade [type: gettext/glade]ui/account_list.glade [type: gettext/glade]ui/account_spinner.glade @@ -407,10 +406,9 @@ src/mailer/main.vala [type: gettext/glade]ui/gtk/menus.ui [type: gettext/glade]ui/login.glade [type: gettext/glade]ui/main-toolbar.ui +[type: gettext/glade]ui/main-toolbar-menus.ui [type: gettext/glade]ui/main-window.ui [type: gettext/glade]ui/password-dialog.glade [type: gettext/glade]ui/preferences-dialog.ui [type: gettext/glade]ui/remove_confirm.glade -[type: gettext/glade]ui/toolbar_empty_menu.ui -[type: gettext/glade]ui/toolbar_mark_menu.ui [type: gettext/glade]ui/upgrade_dialog.glade diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala index 857cffbc..8c51c3e1 100644 --- a/src/client/application/geary-application.vala +++ b/src/client/application/geary-application.vala @@ -113,14 +113,6 @@ public class GearyApplication : Gtk.Application { get { return Args.hidden_startup || this.config.startup_notifications; } } - public Gtk.ActionGroup actions { - get; private set; default = new Gtk.ActionGroup("GearyActionGroup"); - } - - public Gtk.UIManager ui_manager { - get; private set; default = new Gtk.UIManager(); - } - private string bin; private File exec_dir; private bool exiting_fired = false; @@ -260,14 +252,6 @@ public class GearyApplication : Gtk.Application { is_destroyed = true; } - // NOTE: This assert()'s if the Gtk.Action is not present in the default action group - public Gtk.Action get_action(string name) { - Gtk.Action? action = actions.get_action(name); - assert(action != null); - - return action; - } - public File get_user_data_directory() { return File.new_for_path(Environment.get_user_data_dir()).get_child("geary"); } @@ -350,30 +334,6 @@ public class GearyApplication : Gtk.Application { return GioUtil.create_builder(name); } - /** - * Loads a GResource as a string. - * - * @deprecated Use {@link GioUtil.read_resource} instead. - */ - [Deprecated] - public string read_resource(string name) throws Error { - return GioUtil.read_resource(name); - } - - /** - * Loads a UI GResource into the UI manager. - */ - [Deprecated] - public void load_ui_resource(string name) { - try { - this.ui_manager.add_ui_from_resource("/org/gnome/Geary/" + name); - } catch(GLib.Error error) { - critical("Unable to load \"%s\" for Gtk.UIManager: %s".printf( - name, error.message - )); - } - } - /** * Displays a URI on the current active window, if any. */ diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala index b2635306..040dede0 100644 --- a/src/client/application/geary-controller.vala +++ b/src/client/application/geary-controller.vala @@ -21,67 +21,40 @@ extern bool gcr_trust_remove_pinned_certificate(Gcr.Certificate cert, string pur */ public class GearyController : Geary.BaseObject { // Named actions. - // - // NOTE: Some actions with accelerators need to also be added to ui/accelerators.ui - public const string ACTION_NEW_MESSAGE = "GearyNewMessage"; - public const string ACTION_REPLY_TO_MESSAGE = "GearyReplyToMessage"; - public const string ACTION_REPLY_ALL_MESSAGE = "GearyReplyAllMessage"; - public const string ACTION_FORWARD_MESSAGE = "GearyForwardMessage"; - public const string ACTION_ARCHIVE_CONVERSATION = "GearyArchiveConversation"; - public const string ACTION_TRASH_CONVERSATION = "GearyTrashConversation"; - public const string ACTION_DELETE_CONVERSATION = "GearyDeleteConversation"; - public const string ACTION_EMPTY_SPAM = "GearyEmptySpam"; - public const string ACTION_EMPTY_TRASH = "GearyEmptyTrash"; - public const string ACTION_UNDO = "GearyUndo"; - public const string ACTION_FIND_IN_CONVERSATION = "GearyFindInConversation"; - public const string ACTION_ZOOM_IN = "GearyZoomIn"; - public const string ACTION_ZOOM_OUT = "GearyZoomOut"; - public const string ACTION_ZOOM_NORMAL = "GearyZoomNormal"; - public const string ACTION_MARK_AS_MENU = "GearyMarkAsMenuButton"; - public const string ACTION_MARK_AS_READ = "GearyMarkAsRead"; - public const string ACTION_MARK_AS_UNREAD = "GearyMarkAsUnread"; - public const string ACTION_MARK_AS_STARRED = "GearyMarkAsStarred"; - public const string ACTION_MARK_AS_UNSTARRED = "GearyMarkAsUnStarred"; - public const string ACTION_MARK_AS_SPAM = "GearyMarkAsSpam"; - public const string ACTION_COPY_MENU = "GearyCopyMenuButton"; - public const string ACTION_MOVE_MENU = "GearyMoveMenuButton"; - public const string ACTION_SEARCH = "GearySearch"; - public const string ACTION_CONVERSATION_LIST = "GearyConversationList"; - public const string ACTION_TOGGLE_SEARCH = "GearyToggleSearch"; - public const string ACTION_TOGGLE_FIND = "GearyToggleFind"; + public const string ACTION_NEW_MESSAGE = "new-message"; + public const string ACTION_REPLY_TO_MESSAGE = "reply-to-message"; + public const string ACTION_REPLY_ALL_MESSAGE = "reply-all-message"; + public const string ACTION_FORWARD_MESSAGE = "forward-message"; + public const string ACTION_ARCHIVE_CONVERSATION = "archive-conv"; + public const string ACTION_TRASH_CONVERSATION = "trash-conv"; + public const string ACTION_DELETE_CONVERSATION = "delete-conv"; + public const string ACTION_EMPTY_SPAM = "empty-spam"; + public const string ACTION_EMPTY_TRASH = "empty-trash"; + public const string ACTION_UNDO = "undo"; + public const string ACTION_FIND_IN_CONVERSATION = "conv-find"; + public const string ACTION_ZOOM = "zoom"; + public const string ACTION_SHOW_MARK_MENU = "mark-message-menu"; + public const string ACTION_MARK_AS_READ = "mark-message-read"; + public const string ACTION_MARK_AS_UNREAD = "mark-message-unread"; + public const string ACTION_MARK_AS_STARRED = "mark-message-starred"; + public const string ACTION_MARK_AS_UNSTARRED = "mark-message-unstarred"; + public const string ACTION_MARK_AS_SPAM = "mark-message-spam"; + public const string ACTION_MARK_AS_NOT_SPAM = "mark-message-not-spam"; + public const string ACTION_COPY_MENU = "show-copy-menu"; + public const string ACTION_MOVE_MENU = "show-move-menu"; + public const string ACTION_SEARCH = "search-conv"; + public const string ACTION_CONVERSATION_LIST = "focus-conv-list"; + public const string ACTION_TOGGLE_SEARCH = "toggle-search"; + public const string ACTION_TOGGLE_FIND = "toggle-find"; + // Properties public const string PROP_CURRENT_CONVERSATION ="current-conversations"; - + public const string PROP_SELECTED_CONVERSATIONS ="selected-conversations"; + public const int MIN_CONVERSATION_COUNT = 50; - - private const string DELETE_CONVERSATION_LABEL = _("Delete conversation"); - private const string DELETE_CONVERSATION_TOOLTIP_SINGLE = _("Delete conversation (Shift+Delete)"); - private const string DELETE_CONVERSATION_TOOLTIP_MULTIPLE = _("Delete conversations (Shift+Delete)"); - private const string DELETE_CONVERSATION_ICON_NAME = "edit-delete-symbolic"; - - // This refers to the action ("move email to the trash"), not the Trash folder itself - private const string TRASH_CONVERSATION_TOOLTIP_SINGLE = _("Move conversation to Trash (Delete, Backspace)"); - private const string TRASH_CONVERSATION_TOOLTIP_MULTIPLE = _("Move conversations to Trash (Delete, Backspace)"); - private const string TRASH_CONVERSATION_ICON_NAME = "user-trash-symbolic"; - - // This refers to the action ("archive an email"), not the Archive folder itself - private const string ARCHIVE_CONVERSATION_LABEL = _("_Archive"); - private const string ARCHIVE_CONVERSATION_TOOLTIP_SINGLE = _("Archive conversation (A)"); - private const string ARCHIVE_CONVERSATION_TOOLTIP_MULTIPLE = _("Archive conversations (A)"); - private const string ARCHIVE_CONVERSATION_ICON_NAME = "mail-archive-symbolic"; - - private const string MARK_AS_SPAM_LABEL = _("Mark as S_pam"); - private const string MARK_AS_NOT_SPAM_LABEL = _("Mark as not S_pam"); - - private const string MARK_MESSAGE_MENU_TOOLTIP_SINGLE = _("Mark conversation"); - private const string MARK_MESSAGE_MENU_TOOLTIP_MULTIPLE = _("Mark conversations"); - private const string LABEL_MESSAGE_TOOLTIP_SINGLE = _("Add label to conversation"); - private const string LABEL_MESSAGE_TOOLTIP_MULTIPLE = _("Add label to conversations"); - private const string MOVE_MESSAGE_TOOLTIP_SINGLE = _("Move conversation"); - private const string MOVE_MESSAGE_TOOLTIP_MULTIPLE = _("Move conversations"); - + private const int SELECT_FOLDER_TIMEOUT_USEC = 100 * 1000; - + private const string PROP_ATTEMPT_OPEN_ACCOUNT = "attempt-open-account"; public weak GearyApplication application { get; private set; } // circular ref @@ -126,10 +99,39 @@ public class GearyController : Geary.BaseObject { private Geary.Nonblocking.Mutex untrusted_host_prompt_mutex = new Geary.Nonblocking.Mutex(); private Gee.HashSet validating_endpoints = new Gee.HashSet(); private Geary.Revokable? revokable = null; - + // List of windows we're waiting to close before Geary closes. private Gee.List waiting_to_close = new Gee.ArrayList(); - + + private const ActionEntry[] win_action_entries = { + {ACTION_NEW_MESSAGE, on_new_message }, + {ACTION_CONVERSATION_LIST, on_conversation_list }, + {ACTION_FIND_IN_CONVERSATION, on_find_in_conversation_action }, + {ACTION_SEARCH, on_search_activated }, + {ACTION_EMPTY_SPAM, on_empty_spam }, + {ACTION_EMPTY_TRASH, on_empty_trash }, + {ACTION_UNDO, on_revoke }, + // Message actions + {ACTION_REPLY_TO_MESSAGE, on_reply_to_message_action }, + {ACTION_REPLY_ALL_MESSAGE, on_reply_all_message_action }, + {ACTION_FORWARD_MESSAGE, on_forward_message_action }, + {ACTION_ARCHIVE_CONVERSATION, on_archive_conversation }, + {ACTION_TRASH_CONVERSATION, on_trash_conversation }, + {ACTION_DELETE_CONVERSATION, on_delete_conversation }, + {ACTION_COPY_MENU, on_show_copy_menu }, + {ACTION_MOVE_MENU, on_show_move_menu }, + // Message marking actions + {ACTION_SHOW_MARK_MENU, on_show_mark_menu }, + {ACTION_MARK_AS_READ, on_mark_as_read }, + {ACTION_MARK_AS_UNREAD, on_mark_as_unread }, + {ACTION_MARK_AS_STARRED, on_mark_as_starred }, + {ACTION_MARK_AS_UNSTARRED, on_mark_as_unstarred }, + {ACTION_MARK_AS_SPAM, on_mark_as_spam_toggle }, + {ACTION_MARK_AS_NOT_SPAM, on_mark_as_spam_toggle }, + // Message viewer + {ACTION_ZOOM, on_zoom, "s" }, + }; + /** * Fired when the currently selected account has changed. */ @@ -176,10 +178,6 @@ public class GearyController : Geary.BaseObject { apply_app_menu_fix(); - // Setup actions. - setup_actions(); - this.application.load_ui_resource("accelerators.ui"); - // Listen for attempts to close the application. this.application.exiting.connect(on_application_exiting); @@ -224,9 +222,11 @@ public class GearyController : Geary.BaseObject { main_window = new MainWindow(this.application); main_window.on_shift_key.connect(on_shift_key); main_window.notify["has-toplevel-focus"].connect(on_has_toplevel_focus); - + + setup_actions(); + enable_message_buttons(false); - + Geary.Engine.instance.account_available.connect(on_account_available); Geary.Engine.instance.account_unavailable.connect(on_account_unavailable); Geary.Engine.instance.untrusted_host.connect(on_untrusted_host); @@ -414,11 +414,6 @@ public class GearyController : Geary.BaseObject { } } - private void add_accelerator(string accelerator, string action) { - GtkUtil.add_accelerator(this.application.ui_manager, this.application.actions, - accelerator, action); - } - // Fix for clients having both: // * disabled Gtk/ShellShowsAppMenu setting // * no 'menu' setting in Gtk/DecorationLayout @@ -428,13 +423,10 @@ public class GearyController : Geary.BaseObject { if (settings == null) { warning("Couldn't fetch Gtk default settings"); - return ; + return; } - string? decoration_layout = settings.gtk_decoration_layout; - - if (decoration_layout == null) decoration_layout = ""; - + string decoration_layout = settings.gtk_decoration_layout ?? ""; if (!decoration_layout.contains("menu")) { string prefix = "menu:"; if (decoration_layout.contains(":")) { @@ -444,177 +436,35 @@ public class GearyController : Geary.BaseObject { } } - private Gtk.ActionEntry[] create_actions() { - Gtk.ActionEntry[] entries = new Gtk.ActionEntry[0]; - - Gtk.ActionEntry mark_menu = { ACTION_MARK_AS_MENU, null, TRANSLATABLE, null, _("Mark conversation"), - on_show_mark_menu }; - mark_menu.label = _("_Mark as…"); - mark_menu.tooltip = MARK_MESSAGE_MENU_TOOLTIP_SINGLE; - entries += mark_menu; - - Gtk.ActionEntry mark_read = { ACTION_MARK_AS_READ, "mail-mark-read", TRANSLATABLE, "I", - null, on_mark_as_read }; - mark_read.label = _("Mark as _Read"); - entries += mark_read; - add_accelerator("I", ACTION_MARK_AS_READ); - - Gtk.ActionEntry mark_unread = { ACTION_MARK_AS_UNREAD, "mail-mark-unread", TRANSLATABLE, - "U", null, on_mark_as_unread }; - mark_unread.label = _("Mark as _Unread"); - entries += mark_unread; - add_accelerator("U", ACTION_MARK_AS_UNREAD); - - Gtk.ActionEntry mark_starred = { ACTION_MARK_AS_STARRED, "star-symbolic", TRANSLATABLE, "S", null, - on_mark_as_starred }; - mark_starred.label = _("_Star"); - entries += mark_starred; - - Gtk.ActionEntry mark_unstarred = { ACTION_MARK_AS_UNSTARRED, "non-starred", TRANSLATABLE, "D", - null, on_mark_as_unstarred }; - mark_unstarred.label = _("U_nstar"); - entries += mark_unstarred; - - Gtk.ActionEntry mark_spam = { ACTION_MARK_AS_SPAM, null, TRANSLATABLE, "J", null, - on_mark_as_spam }; - mark_spam.label = MARK_AS_SPAM_LABEL; - entries += mark_spam; - add_accelerator("exclam", ACTION_MARK_AS_SPAM); // Exclamation mark (!) - - Gtk.ActionEntry copy_menu = { ACTION_COPY_MENU, null, TRANSLATABLE, "L", - _("Add label"), null }; - copy_menu.label = _("_Label"); - entries += copy_menu; - - Gtk.ActionEntry move_menu = { ACTION_MOVE_MENU, null, TRANSLATABLE, "M", _("Move conversation"), null }; - move_menu.label = _("_Move"); - entries += move_menu; - - Gtk.ActionEntry new_message = { ACTION_NEW_MESSAGE, null, null, "N", - _("Compose new message (Ctrl+N, N)"), on_new_message }; - entries += new_message; - add_accelerator("N", ACTION_NEW_MESSAGE); - - Gtk.ActionEntry reply_to_message = { ACTION_REPLY_TO_MESSAGE, null, _("_Reply"), "R", - _("Reply (Ctrl+R, R)"), on_reply_to_message_action }; - entries += reply_to_message; - add_accelerator("R", ACTION_REPLY_TO_MESSAGE); - - Gtk.ActionEntry reply_all_message = { ACTION_REPLY_ALL_MESSAGE, null, _("R_eply All"), - "R", _("Reply all (Ctrl+Shift+R, Shift+R)"), - on_reply_all_message_action }; - entries += reply_all_message; - add_accelerator("R", ACTION_REPLY_ALL_MESSAGE); - - Gtk.ActionEntry forward_message = { ACTION_FORWARD_MESSAGE, null, _("_Forward"), "L", - _("Forward (Ctrl+L, F)"), on_forward_message_action }; - entries += forward_message; - add_accelerator("F", ACTION_FORWARD_MESSAGE); - - Gtk.ActionEntry find_in_conversation = { ACTION_FIND_IN_CONVERSATION, null, null, "F", - null, on_find_in_conversation_action }; - entries += find_in_conversation; - add_accelerator("slash", ACTION_FIND_IN_CONVERSATION); - - Gtk.ActionEntry archive_conversation = { ACTION_ARCHIVE_CONVERSATION, ARCHIVE_CONVERSATION_ICON_NAME, - ARCHIVE_CONVERSATION_LABEL, "A", null, on_archive_conversation }; - archive_conversation.tooltip = ARCHIVE_CONVERSATION_TOOLTIP_SINGLE; - entries += archive_conversation; - - // although this action changes according to the account's capabilities, set to Delete - // until they're known so the "translatable" string doesn't first appear - Gtk.ActionEntry trash_conversation = { ACTION_TRASH_CONVERSATION, TRASH_CONVERSATION_ICON_NAME, - null, "Delete", null, on_trash_conversation }; - trash_conversation.tooltip = TRASH_CONVERSATION_TOOLTIP_SINGLE; - entries += trash_conversation; - add_accelerator("BackSpace", ACTION_TRASH_CONVERSATION); - - Gtk.ActionEntry delete_conversation = { ACTION_DELETE_CONVERSATION, DELETE_CONVERSATION_ICON_NAME, - null, "Delete", null, on_delete_conversation }; - delete_conversation.label = DELETE_CONVERSATION_LABEL; - delete_conversation.tooltip = DELETE_CONVERSATION_TOOLTIP_SINGLE; - entries += delete_conversation; - add_accelerator("BackSpace", ACTION_DELETE_CONVERSATION); - - Gtk.ActionEntry empty_spam = { ACTION_EMPTY_SPAM, null, null, null, null, on_empty_spam }; - empty_spam.label = _("Empty _Spam…"); - entries += empty_spam; - - Gtk.ActionEntry empty_trash = { ACTION_EMPTY_TRASH, null, null, null, null, on_empty_trash }; - empty_trash.label = _("Empty _Trash…"); - entries += empty_trash; - - Gtk.ActionEntry undo = { ACTION_UNDO, "edit-undo-symbolic", null, "Z", null, on_revoke }; - entries += undo; - - Gtk.ActionEntry zoom_in = { ACTION_ZOOM_IN, null, null, "equal", - null, on_zoom_in }; - entries += zoom_in; - add_accelerator("equal", ACTION_ZOOM_IN); - - Gtk.ActionEntry zoom_out = { ACTION_ZOOM_OUT, null, null, "minus", - null, on_zoom_out }; - entries += zoom_out; - add_accelerator("minus", ACTION_ZOOM_OUT); - - Gtk.ActionEntry zoom_normal = { ACTION_ZOOM_NORMAL, null, null, "0", - null, on_zoom_normal }; - entries += zoom_normal; - add_accelerator("0", ACTION_ZOOM_NORMAL); - - Gtk.ActionEntry search = { - ACTION_SEARCH, null, null, "S", null, - () => { show_search_bar(); } - }; - entries += search; - - Gtk.ActionEntry conversation_list = { ACTION_CONVERSATION_LIST, null, null, "B", null, on_conversation_list }; - entries += conversation_list; - - // No callback is connected, since we bind the toggle button to the search bar visibility - Gtk.ActionEntry toggle_search = { ACTION_TOGGLE_SEARCH, null, null, null, - _("Toggle search bar"), null }; - entries += toggle_search; - - // No callback is connected, since we bind the toggle button to the find bar visibility - Gtk.ActionEntry toggle_find = { ACTION_TOGGLE_FIND, null, null, null, - _("Toggle find bar"), null }; - entries += toggle_find; - - return entries; - } - - private Gtk.ToggleActionEntry[] create_toggle_actions() { - Gtk.ToggleActionEntry[] entries = new Gtk.ToggleActionEntry[0]; - - return entries; - } - private void setup_actions() { - const string[] important_actions = { - ACTION_NEW_MESSAGE, - ACTION_REPLY_TO_MESSAGE, - ACTION_REPLY_ALL_MESSAGE, - ACTION_FORWARD_MESSAGE, - ACTION_ARCHIVE_CONVERSATION, - ACTION_TRASH_CONVERSATION, - ACTION_DELETE_CONVERSATION, - }; - Gtk.ActionGroup action_group = this.application.actions; + this.main_window.add_action_entries(win_action_entries, this); - Gtk.ActionEntry[] action_entries = create_actions(); - action_group.add_actions(action_entries, this); - foreach (Gtk.ActionEntry e in action_entries) { - Gtk.Action action = action_group.get_action(e.name); - assert(action != null); - - if (e.name in important_actions) - action.is_important = true; - } + add_window_accelerators(ACTION_MARK_AS_READ, { "I", "I" }); + add_window_accelerators(ACTION_MARK_AS_UNREAD, { "U", "U" }); + add_window_accelerators(ACTION_MARK_AS_STARRED, { "S" }); + add_window_accelerators(ACTION_MARK_AS_UNSTARRED, { "D" }); + add_window_accelerators(ACTION_MARK_AS_SPAM, { "J", "exclam" }); // Exclamation mark (!) + add_window_accelerators(ACTION_MARK_AS_NOT_SPAM, { "J", "exclam" }); + add_window_accelerators(ACTION_COPY_MENU, { "L" }); + add_window_accelerators(ACTION_MOVE_MENU, { "M" }); + add_window_accelerators(ACTION_NEW_MESSAGE, { "N", "N" }); + add_window_accelerators(ACTION_REPLY_TO_MESSAGE, { "R", "R" }); + add_window_accelerators(ACTION_REPLY_ALL_MESSAGE, { "R", "R" }); + add_window_accelerators(ACTION_FORWARD_MESSAGE, { "L", "F" }); + add_window_accelerators(ACTION_FIND_IN_CONVERSATION, { "F", "slash" }); + add_window_accelerators(ACTION_ARCHIVE_CONVERSATION, { "A" }); + add_window_accelerators(ACTION_TRASH_CONVERSATION, { "Delete", "BackSpace" }); + add_window_accelerators(ACTION_DELETE_CONVERSATION, { "Delete", "BackSpace" }); + add_window_accelerators(ACTION_UNDO, { "Z" }); + add_window_accelerators(ACTION_ZOOM+("('in')"), { "equal", "equal" }); + add_window_accelerators(ACTION_ZOOM+("('out')"), { "minus", "minus" }); + add_window_accelerators(ACTION_ZOOM+("('normal')"), { "0", "0" }); + add_window_accelerators(ACTION_SEARCH, { "S" }); + add_window_accelerators(ACTION_CONVERSATION_LIST, { "B" }); + } - Gtk.ToggleActionEntry[] toggle_action_entries = create_toggle_actions(); - action_group.add_toggle_actions(toggle_action_entries, this); - this.application.ui_manager.insert_action_group(action_group, 0); + private void add_window_accelerators(string action, string[] accelerators, Variant? param = null) { + this.application.set_accels_for_action("win."+action, accelerators); } private void open_account(Geary.Account account) { @@ -1330,11 +1180,9 @@ public class GearyController : Geary.BaseObject { // Update widgets and such to match capabilities of the current folder ... sensitivity is handled // by other utility methods private void update_ui() { - update_tooltips(); - main_window.main_toolbar.update_trash_button( - current_folder_supports_trash() || - !(current_folder is Geary.FolderSupport.Remove) - ); + 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); } private void on_folder_selected(Geary.Folder? folder) { @@ -1346,9 +1194,7 @@ public class GearyController : Geary.BaseObject { folder_selected(null); } else if (folder != this.current_folder) { this.main_window.conversation_viewer.show_loading(); - this.application.get_action( - ACTION_FIND_IN_CONVERSATION - ).set_sensitive(false); + get_window_action(ACTION_FIND_IN_CONVERSATION).set_enabled(false); enable_message_buttons(false); // To prevent the user from selecting folders too quickly, @@ -1531,7 +1377,7 @@ public class GearyController : Geary.BaseObject { private void on_indicator_activated_composer(uint32 timestamp) { on_indicator_activated_application(timestamp); - on_new_message(); + on_new_message(null); } private void on_indicator_activated_inbox(Geary.Folder folder, uint32 timestamp) { @@ -1554,9 +1400,7 @@ public class GearyController : Geary.BaseObject { private void on_conversations_selected(Gee.Set selected) { this.selected_conversations = selected; - this.application.get_action( - ACTION_FIND_IN_CONVERSATION - ).set_sensitive(false); + get_window_action(ACTION_FIND_IN_CONVERSATION).set_enabled(false); ConversationViewer viewer = this.main_window.conversation_viewer; if (this.current_folder != null && !viewer.is_composer_visible) { switch(selected.size) { @@ -1575,9 +1419,7 @@ public class GearyController : Geary.BaseObject { try { viewer.load_conversation.end(ret); enable_message_buttons(true); - this.application.get_action( - ACTION_FIND_IN_CONVERSATION - ).set_sensitive(true); + get_window_action(ACTION_FIND_IN_CONVERSATION).set_enabled(true); } catch (Error err) { debug("Unable to load conversation: %s", err.message); @@ -1754,8 +1596,9 @@ public class GearyController : Geary.BaseObject { 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.update_trash_button( - (!pressed && current_folder_supports_trash()) || !(current_folder is Geary.FolderSupport.Remove)); + main_window.main_toolbar.show_trash_button = + (!pressed && current_folder_supports_trash()) || + !(current_folder is Geary.FolderSupport.Remove); } } @@ -1832,30 +1675,19 @@ public class GearyController : Geary.BaseObject { unstarred_selected = true; } } - var actions = this.application.actions; - actions.get_action(ACTION_MARK_AS_READ).set_visible(unread_selected); - actions.get_action(ACTION_MARK_AS_UNREAD).set_visible(read_selected); - actions.get_action(ACTION_MARK_AS_STARRED).set_visible(unstarred_selected); - actions.get_action(ACTION_MARK_AS_UNSTARRED).set_visible(starred_selected); - - if (current_folder.special_folder_type != Geary.SpecialFolderType.DRAFTS && - current_folder.special_folder_type != Geary.SpecialFolderType.OUTBOX) { - if (current_folder.special_folder_type == Geary.SpecialFolderType.SPAM) { - // We're in the spam folder. - actions.get_action(ACTION_MARK_AS_SPAM).sensitive = true; - actions.get_action(ACTION_MARK_AS_SPAM).label = MARK_AS_NOT_SPAM_LABEL; - } else { - // We're not in the spam folder, but we are in a folder that allows mark-as-spam. - actions.get_action(ACTION_MARK_AS_SPAM).sensitive = true; - actions.get_action(ACTION_MARK_AS_SPAM).label = MARK_AS_SPAM_LABEL; - } - } else { - // We're in Drafts/Outbox, so gray-out the option. - actions.get_action(ACTION_MARK_AS_SPAM).sensitive = false; - actions.get_action(ACTION_MARK_AS_SPAM).label = MARK_AS_SPAM_LABEL; - } + get_window_action(ACTION_MARK_AS_READ).set_enabled(unread_selected); + get_window_action(ACTION_MARK_AS_UNREAD).set_enabled(read_selected); + get_window_action(ACTION_MARK_AS_STARRED).set_enabled(unstarred_selected); + get_window_action(ACTION_MARK_AS_UNSTARRED).set_enabled(starred_selected); + + bool in_spam_folder = current_folder.special_folder_type == Geary.SpecialFolderType.SPAM; + get_window_action(ACTION_MARK_AS_NOT_SPAM).set_enabled(in_spam_folder); + // If we're in Drafts/Outbox, we also shouldn't set a message as SPAM. + get_window_action(ACTION_MARK_AS_SPAM).set_enabled(!in_spam_folder && + current_folder.special_folder_type != Geary.SpecialFolderType.DRAFTS && + current_folder.special_folder_type != Geary.SpecialFolderType.OUTBOX); } - + private void on_visible_conversations_changed(Gee.Set visible) { clear_new_messages("on_visible_conversations_changed", visible); } @@ -1900,11 +1732,11 @@ public class GearyController : Geary.BaseObject { Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove) { mark_email(emails, flags_to_add, flags_to_remove); } - - private void on_mark_as_read() { + + private void on_mark_as_read(SimpleAction action) { Geary.EmailFlags flags = new Geary.EmailFlags(); flags.add(Geary.EmailFlags.UNREAD); - + Gee.ArrayList ids = get_selected_email_ids(false); mark_email(ids, null, flags); @@ -1916,10 +1748,10 @@ public class GearyController : Geary.BaseObject { } } - private void on_mark_as_unread() { + private void on_mark_as_unread(SimpleAction action) { Geary.EmailFlags flags = new Geary.EmailFlags(); flags.add(Geary.EmailFlags.UNREAD); - + Gee.ArrayList ids = get_selected_email_ids(true); mark_email(ids, flags, null); @@ -1931,19 +1763,27 @@ public class GearyController : Geary.BaseObject { } } - private void on_mark_as_starred() { + private void on_mark_as_starred(SimpleAction action) { Geary.EmailFlags flags = new Geary.EmailFlags(); flags.add(Geary.EmailFlags.FLAGGED); mark_email(get_selected_email_ids(true), flags, null); } - private void on_mark_as_unstarred() { + private void on_mark_as_unstarred(SimpleAction action) { Geary.EmailFlags flags = new Geary.EmailFlags(); flags.add(Geary.EmailFlags.FLAGGED); mark_email(get_selected_email_ids(false), null, flags); } - - private async void mark_as_spam_async(Cancellable? cancellable) { + + private void on_show_move_menu(SimpleAction? action) { + this.main_window.main_toolbar.copy_message_button.clicked(); + } + + private void on_show_copy_menu(SimpleAction? action) { + this.main_window.main_toolbar.move_message_button.clicked(); + } + + private async void mark_as_spam_toggle_async(Cancellable? cancellable) { Geary.Folder? destination_folder = null; if (current_folder.special_folder_type != Geary.SpecialFolderType.SPAM) { // Move to spam folder. @@ -1961,15 +1801,15 @@ public class GearyController : Geary.BaseObject { debug("Error getting inbox folder: %s", e.message); } } - + if (destination_folder != null) on_move_conversation(destination_folder); } - - private void on_mark_as_spam() { - mark_as_spam_async.begin(null); + + private void on_mark_as_spam_toggle(SimpleAction action) { + mark_as_spam_toggle_async.begin(null); } - + private void copy_email(Gee.Collection ids, Geary.FolderPath destination) { if (ids.size > 0) { @@ -2284,9 +2124,7 @@ public class GearyController : Geary.BaseObject { if (widget.state == ComposerWidget.ComposerState.NEW || widget.state == ComposerWidget.ComposerState.PANED) { main_window.conversation_viewer.do_compose(widget); - this.application.get_action( - ACTION_FIND_IN_CONVERSATION - ).set_sensitive(false); + get_window_action(ACTION_FIND_IN_CONVERSATION).set_enabled(false); } else { main_window.conversation_viewer.do_compose_embedded( widget, @@ -2401,7 +2239,7 @@ public class GearyController : Geary.BaseObject { } } - private void on_new_message() { + private void on_new_message(SimpleAction? action) { create_compose_widget(ComposerWidget.ComposeType.NEW_MESSAGE); } @@ -2409,7 +2247,7 @@ public class GearyController : Geary.BaseObject { create_reply_forward_widget(ComposerWidget.ComposeType.REPLY, target_view); } - private void on_reply_to_message_action() { + private void on_reply_to_message_action(SimpleAction action) { create_reply_forward_widget(ComposerWidget.ComposeType.REPLY, null); } @@ -2417,7 +2255,7 @@ public class GearyController : Geary.BaseObject { create_reply_forward_widget(ComposerWidget.ComposeType.REPLY_ALL, target_view); } - private void on_reply_all_message_action() { + private void on_reply_all_message_action(SimpleAction action) { create_reply_forward_widget(ComposerWidget.ComposeType.REPLY_ALL, null); } @@ -2425,37 +2263,41 @@ public class GearyController : Geary.BaseObject { create_reply_forward_widget(ComposerWidget.ComposeType.FORWARD, target_view); } - private void on_forward_message_action() { + private void on_forward_message_action(SimpleAction action) { create_reply_forward_widget(ComposerWidget.ComposeType.FORWARD, null); } - private void on_find_in_conversation_action() { + private void on_find_in_conversation_action(SimpleAction action) { this.main_window.conversation_viewer.conversation_find_bar.set_search_mode(true); } - private void on_archive_conversation() { + private void on_search_activated(SimpleAction action) { + show_search_bar(); + } + + private void on_archive_conversation(SimpleAction action) { archive_or_delete_selection_async.begin(true, false, cancellable_folder, on_archive_or_delete_selection_finished); } - - private void on_trash_conversation() { + + private void on_trash_conversation(SimpleAction action) { archive_or_delete_selection_async.begin(false, true, cancellable_folder, on_archive_or_delete_selection_finished); } - - private void on_delete_conversation() { + + private void on_delete_conversation(SimpleAction action) { archive_or_delete_selection_async.begin(false, false, cancellable_folder, on_archive_or_delete_selection_finished); } - - private void on_empty_spam() { + + private void on_empty_spam(SimpleAction action) { on_empty_trash_or_spam(Geary.SpecialFolderType.SPAM); } - - private void on_empty_trash() { + + private void on_empty_trash(SimpleAction action) { on_empty_trash_or_spam(Geary.SpecialFolderType.TRASH); } - + private void on_empty_trash_or_spam(Geary.SpecialFolderType special_folder_type) { // Account must be in place, must have the specified special folder type, and that folder // must support Empty in order for this command to proceed @@ -2636,17 +2478,18 @@ public class GearyController : Geary.BaseObject { revokable.committed.connect(on_revokable_committed); } - Gtk.Action undo_action = this.application.get_action(ACTION_UNDO); - undo_action.tooltip = (revokable != null && description != null) ? description : _("Undo (Ctrl+Z)"); + if (revokable != null && description != null) + this.main_window.main_toolbar.undo_tooltip = description; + else + this.main_window.main_toolbar.undo_tooltip = _("Undo (Ctrl+Z)"); update_revokable_action(); } - + private void update_revokable_action() { - Gtk.Action undo_action = this.application.get_action(ACTION_UNDO); - undo_action.sensitive = revokable != null && revokable.valid && !revokable.in_process; + get_window_action(ACTION_UNDO).set_enabled(this.revokable != null && this.revokable.valid && !this.revokable.in_process); } - + private void on_revokable_valid_changed() { // remove revokable if it goes invalid if (revokable != null && !revokable.valid) @@ -2658,10 +2501,9 @@ public class GearyController : Geary.BaseObject { return; // use existing description - Gtk.Action undo_action = this.application.get_action(ACTION_UNDO); - save_revokable(committed_revokable, undo_action.tooltip); + save_revokable(committed_revokable, this.main_window.main_toolbar.undo_tooltip); } - + private void on_revoke() { if (revokable != null && revokable.valid) revokable.revoke_async.begin(null, on_revoke_completed); @@ -2681,27 +2523,16 @@ public class GearyController : Geary.BaseObject { } } - private void on_zoom_in() { - ConversationListBox? view = - main_window.conversation_viewer.current_list; - if (view != null) { - view.zoom_in(); - } - } - - private void on_zoom_out() { - ConversationListBox? view = - main_window.conversation_viewer.current_list; - if (view != null) { - view.zoom_out(); - } - } - - private void on_zoom_normal() { - ConversationListBox? view = - main_window.conversation_viewer.current_list; - if (view != null) { - view.zoom_reset(); + private void on_zoom(SimpleAction action, Variant? parameter) { + ConversationListBox? view = main_window.conversation_viewer.current_list; + if (view != null && parameter != null) { + string zoom_action = parameter.get_string(); + if (zoom_action == "in") + view.zoom_in(); + else if (zoom_action == "out") + view.zoom_out(); + else + view.zoom_reset(); } } @@ -2775,24 +2606,24 @@ public class GearyController : Geary.BaseObject { } } + private SimpleAction get_window_action(string action_name) { + return (SimpleAction) this.main_window.lookup_action(action_name); + } + // Disables all single-message buttons and enables all multi-message buttons. public void enable_multiple_message_buttons() { - update_tooltips(); + main_window.main_toolbar.selected_conversations = this.selected_conversations.size; // Single message only buttons. - this.application.actions.get_action(ACTION_REPLY_TO_MESSAGE).sensitive = false; - this.application.actions.get_action(ACTION_REPLY_ALL_MESSAGE).sensitive = false; - this.application.actions.get_action(ACTION_FORWARD_MESSAGE).sensitive = false; + get_window_action(ACTION_REPLY_TO_MESSAGE).set_enabled(false); + get_window_action(ACTION_REPLY_ALL_MESSAGE).set_enabled(false); + get_window_action(ACTION_FORWARD_MESSAGE).set_enabled(false); // Mutliple message buttons. - this.application.actions.get_action(ACTION_MOVE_MENU).sensitive = - (current_folder is Geary.FolderSupport.Move); - this.application.actions.get_action(ACTION_ARCHIVE_CONVERSATION).sensitive = - (current_folder is Geary.FolderSupport.Archive); - this.application.actions.get_action(ACTION_TRASH_CONVERSATION).sensitive = - current_folder_supports_trash(); - this.application.actions.get_action(ACTION_DELETE_CONVERSATION).sensitive = - (current_folder is Geary.FolderSupport.Remove); + get_window_action(ACTION_MOVE_MENU).set_enabled(current_folder is Geary.FolderSupport.Move); + get_window_action(ACTION_ARCHIVE_CONVERSATION).set_enabled(current_folder is Geary.FolderSupport.Archive); + get_window_action(ACTION_TRASH_CONVERSATION).set_enabled(current_folder_supports_trash()); + get_window_action(ACTION_DELETE_CONVERSATION).set_enabled(current_folder is Geary.FolderSupport.Remove); cancel_context_dependent_buttons(); enable_context_dependent_buttons_async.begin(true, cancellable_context_dependent_buttons); @@ -2800,29 +2631,25 @@ public class GearyController : Geary.BaseObject { // Enables or disables the message buttons on the toolbar. public void enable_message_buttons(bool sensitive) { - update_tooltips(); - + main_window.main_toolbar.selected_conversations = this.selected_conversations.size; + // No reply/forward in drafts folder. bool respond_sensitive = sensitive; if (current_folder != null && current_folder.special_folder_type == Geary.SpecialFolderType.DRAFTS) respond_sensitive = false; - this.application.actions.get_action(ACTION_REPLY_TO_MESSAGE).sensitive = respond_sensitive; - this.application.actions.get_action(ACTION_REPLY_ALL_MESSAGE).sensitive = respond_sensitive; - this.application.actions.get_action(ACTION_FORWARD_MESSAGE).sensitive = respond_sensitive; - this.application.actions.get_action(ACTION_MOVE_MENU).sensitive = - sensitive && (current_folder is Geary.FolderSupport.Move); - this.application.actions.get_action(ACTION_ARCHIVE_CONVERSATION).sensitive = sensitive - && (current_folder is Geary.FolderSupport.Archive); - this.application.actions.get_action(ACTION_TRASH_CONVERSATION).sensitive = sensitive - && current_folder_supports_trash(); - this.application.actions.get_action(ACTION_DELETE_CONVERSATION).sensitive = sensitive - && (current_folder is Geary.FolderSupport.Remove); + get_window_action(ACTION_REPLY_TO_MESSAGE).set_enabled(respond_sensitive); + get_window_action(ACTION_REPLY_ALL_MESSAGE).set_enabled(respond_sensitive); + get_window_action(ACTION_FORWARD_MESSAGE).set_enabled(respond_sensitive); + get_window_action(ACTION_MOVE_MENU).set_enabled(sensitive && (current_folder is Geary.FolderSupport.Move)); + get_window_action(ACTION_ARCHIVE_CONVERSATION).set_enabled(sensitive && (current_folder is Geary.FolderSupport.Archive)); + get_window_action(ACTION_TRASH_CONVERSATION).set_enabled(sensitive && current_folder_supports_trash()); + get_window_action(ACTION_DELETE_CONVERSATION).set_enabled(sensitive && (current_folder is Geary.FolderSupport.Remove)); cancel_context_dependent_buttons(); enable_context_dependent_buttons_async.begin(sensitive, cancellable_context_dependent_buttons); } - + private async void enable_context_dependent_buttons_async(bool sensitive, Cancellable? cancellable) { Gee.MultiMap? selected_operations = null; try { @@ -2846,29 +2673,8 @@ public class GearyController : Geary.BaseObject { if (selected_operations != null) supported_operations.add_all(selected_operations.get_values()); - this.application.actions.get_action(ACTION_MARK_AS_MENU).sensitive = - sensitive && (supported_operations.contains(typeof(Geary.FolderSupport.Mark))); - this.application.actions.get_action(ACTION_COPY_MENU).sensitive = - sensitive && (supported_operations.contains(typeof(Geary.FolderSupport.Copy))); - } - - // Updates tooltip text depending on number of conversations selected. - private void update_tooltips() { - bool single = selected_conversations.size == 1; - - this.application.actions.get_action(ACTION_MARK_AS_MENU).tooltip = single ? - MARK_MESSAGE_MENU_TOOLTIP_SINGLE : MARK_MESSAGE_MENU_TOOLTIP_MULTIPLE; - this.application.actions.get_action(ACTION_COPY_MENU).tooltip = single ? - LABEL_MESSAGE_TOOLTIP_SINGLE : LABEL_MESSAGE_TOOLTIP_MULTIPLE; - this.application.actions.get_action(ACTION_MOVE_MENU).tooltip = single ? - MOVE_MESSAGE_TOOLTIP_SINGLE : MOVE_MESSAGE_TOOLTIP_MULTIPLE; - - this.application.actions.get_action(ACTION_ARCHIVE_CONVERSATION).tooltip = single ? - ARCHIVE_CONVERSATION_TOOLTIP_SINGLE : ARCHIVE_CONVERSATION_TOOLTIP_MULTIPLE; - this.application.actions.get_action(ACTION_TRASH_CONVERSATION).tooltip = single ? - TRASH_CONVERSATION_TOOLTIP_SINGLE : TRASH_CONVERSATION_TOOLTIP_MULTIPLE; - this.application.actions.get_action(ACTION_DELETE_CONVERSATION).tooltip = single ? - DELETE_CONVERSATION_TOOLTIP_SINGLE : DELETE_CONVERSATION_TOOLTIP_MULTIPLE; + get_window_action(ACTION_SHOW_MARK_MENU).set_enabled(sensitive && (typeof(Geary.FolderSupport.Mark) in supported_operations)); + get_window_action(ACTION_COPY_MENU).set_enabled(sensitive && (supported_operations.contains(typeof(Geary.FolderSupport.Copy)))); } // Returns a list of composer windows for an account, or null if none. diff --git a/src/client/components/folder-popover.vala b/src/client/components/folder-popover.vala index 9b377c8f..99326abf 100644 --- a/src/client/components/folder-popover.vala +++ b/src/client/components/folder-popover.vala @@ -79,6 +79,8 @@ public class FolderPopover : Gtk.Popover { label.set_halign(Gtk.Align.START); row.add(label); + row.show_all(); + return row; } diff --git a/src/client/components/main-toolbar.vala b/src/client/components/main-toolbar.vala index 3c351137..67fc5d5c 100644 --- a/src/client/components/main-toolbar.vala +++ b/src/client/components/main-toolbar.vala @@ -1,4 +1,4 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* Copyright 2017 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -7,24 +7,35 @@ // Draws the main toolbar. [GtkTemplate (ui = "/org/gnome/Geary/main-toolbar.ui")] public class MainToolbar : Gtk.Box { - private Gtk.ActionGroup action_group; - public FolderPopover copy_folder_menu { get; private set; default = new FolderPopover(); } - public FolderPopover move_folder_menu { get; private set; default = new FolderPopover(); } + // How wide the left pane should be. Auto-synced with our settings + public int left_pane_width { get; set; } + // Used to form the title of the folder header public string account { get; set; } public string folder { get; set; } + // Close button settings public bool show_close_button { get; set; default = false; } public bool show_close_button_left { get; private set; default = true; } public bool show_close_button_right { get; private set; default = true; } + // Search and find bar public bool search_open { get; set; default = false; } public bool find_open { get; set; default = false; } - public int left_pane_width { get; set; } + // Copy and Move popovers + public FolderPopover copy_folder_menu { get; private set; default = new FolderPopover(); } + 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; } + // The tooltip of the Undo-button + public string undo_tooltip { + owned get { return this.undo_button.tooltip_text; } + set { this.undo_button.tooltip_text = value; } + } // Folder header elements [GtkChild] private Gtk.HeaderBar folder_header; [GtkChild] - private Gtk.Button compose_new_message_button; - [GtkChild] private Gtk.MenuButton empty_menu_button; [GtkChild] private Gtk.ToggleButton search_conversations_button; @@ -34,90 +45,85 @@ public class MainToolbar : Gtk.Box { [GtkChild] private Gtk.HeaderBar conversation_header; [GtkChild] - private Gtk.Button reply_sender_button; - [GtkChild] - private Gtk.Button reply_all_button; - [GtkChild] - private Gtk.Button forward_button; - [GtkChild] private Gtk.MenuButton mark_message_button; [GtkChild] - private Gtk.MenuButton copy_message_button; + public Gtk.MenuButton copy_message_button; [GtkChild] - private Gtk.MenuButton move_message_button; + public Gtk.MenuButton move_message_button; [GtkChild] private Gtk.Button archive_button; [GtkChild] private Gtk.Button trash_delete_button; [GtkChild] - private Gtk.Button undo_button; - [GtkChild] private Gtk.ToggleButton find_button; - public MainToolbar(Configuration config) { - this.action_group = GearyApplication.instance.actions; + // Other + [GtkChild] + private Gtk.Button undo_button; + // 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); + + // Tooltips + private const string DELETE_CONVERSATION_TOOLTIP_SINGLE = _("Delete conversation (Shift+Delete)"); + private const string DELETE_CONVERSATION_TOOLTIP_MULTIPLE = _("Delete conversations (Shift+Delete)"); + private const string TRASH_CONVERSATION_TOOLTIP_SINGLE = _("Move conversation to Trash (Delete, Backspace)"); + private const string TRASH_CONVERSATION_TOOLTIP_MULTIPLE = _("Move conversations to Trash (Delete, Backspace)"); + private const string ARCHIVE_CONVERSATION_TOOLTIP_SINGLE = _("Archive conversation (A)"); + private const string ARCHIVE_CONVERSATION_TOOLTIP_MULTIPLE = _("Archive conversations (A)"); + private const string MARK_MESSAGE_MENU_TOOLTIP_SINGLE = _("Mark conversation"); + private const string MARK_MESSAGE_MENU_TOOLTIP_MULTIPLE = _("Mark conversations"); + private const string LABEL_MESSAGE_TOOLTIP_SINGLE = _("Add label to conversation"); + private const string LABEL_MESSAGE_TOOLTIP_MULTIPLE = _("Add label to conversations"); + private const string MOVE_MESSAGE_TOOLTIP_SINGLE = _("Move conversation"); + private const string MOVE_MESSAGE_TOOLTIP_MULTIPLE = _("Move conversations"); + + public MainToolbar(Configuration config) { // Instead of putting a separator between the two headerbars, as other applications do, // we put a separator at the right end of the left headerbar. This greatly improves // the appearance under the Ambiance theme (see bug #746171). To get this separator to // line up with the handle of the pane, we need to extend the width of the left-hand // headerbar a bit. Six pixels is right both for Adwaita and Ambiance. - GearyApplication.instance.config.bind(Configuration.MESSAGES_PANE_POSITION_KEY, - this, "left-pane-width", SettingsBindFlags.GET); - this.bind_property("left-pane-width", folder_header, "width-request", + config.bind(Configuration.MESSAGES_PANE_POSITION_KEY, this, "left-pane-width", + SettingsBindFlags.GET); + this.bind_property("left-pane-width", this.folder_header, "width-request", BindingFlags.SYNC_CREATE, (binding, source_value, ref target_value) => { target_value = left_pane_width + 6; return true; }); if (config.desktop_environment != Configuration.DesktopEnvironment.UNITY) { - this.bind_property("account", folder_header, "title", BindingFlags.SYNC_CREATE); - this.bind_property("folder", folder_header, "subtitle", BindingFlags.SYNC_CREATE); + this.bind_property("account", this.folder_header, "title", BindingFlags.SYNC_CREATE); + this.bind_property("folder", this.folder_header, "subtitle", BindingFlags.SYNC_CREATE); } - this.bind_property("show-close-button-left", folder_header, "show-close-button", + this.bind_property("show-close-button-left", this.folder_header, "show-close-button", BindingFlags.SYNC_CREATE); - this.bind_property("show-close-button-right", conversation_header, "show-close-button", + this.bind_property("show-close-button-right", this.conversation_header, "show-close-button", BindingFlags.SYNC_CREATE); // Assemble the empty/mark menus - GearyApplication.instance.load_ui_resource("toolbar_empty_menu.ui"); - Gtk.Menu empty_menu = (Gtk.Menu) GearyApplication.instance.ui_manager.get_widget("/ui/ToolbarEmptyMenu"); - GearyApplication.instance.load_ui_resource("toolbar_mark_menu.ui"); - Gtk.Menu mark_menu = (Gtk.Menu) GearyApplication.instance.ui_manager.get_widget("/ui/ToolbarMarkMenu"); + Gtk.Builder builder = new Gtk.Builder.from_resource("/org/gnome/Geary/main-toolbar-menus.ui"); + MenuModel empty_menu = (MenuModel) builder.get_object("empty_menu"); + MenuModel mark_menu = (MenuModel) builder.get_object("mark_message_menu"); // Setup folder header elements - setup_button(compose_new_message_button, GearyController.ACTION_NEW_MESSAGE); - empty_menu_button.popup = empty_menu; - - setup_button(search_conversations_button, GearyController.ACTION_TOGGLE_SEARCH); - this.bind_property("search-open", search_conversations_button, "active", + this.empty_menu_button.popover = new Gtk.Popover.from_model(null, empty_menu); + this.bind_property("search-open", this.search_conversations_button, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); // Setup conversation header elements - setup_button(reply_sender_button, GearyController.ACTION_REPLY_TO_MESSAGE); - setup_button(reply_all_button, GearyController.ACTION_REPLY_ALL_MESSAGE); - setup_button(forward_button, GearyController.ACTION_FORWARD_MESSAGE); + 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; - setup_menu_button(mark_message_button, mark_menu, GearyController.ACTION_MARK_AS_MENU); - setup_popover_button(copy_message_button, copy_folder_menu, GearyController.ACTION_COPY_MENU); - setup_popover_button(move_message_button, move_folder_menu, GearyController.ACTION_MOVE_MENU); - - setup_button(archive_button, GearyController.ACTION_ARCHIVE_CONVERSATION, true); - setup_button(trash_delete_button, GearyController.ACTION_TRASH_CONVERSATION); - setup_button(undo_button, GearyController.ACTION_UNDO); - - setup_button(find_button, GearyController.ACTION_TOGGLE_FIND); - this.bind_property("find-open", find_button, "active", + this.bind_property("find-open", this.find_button, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); Gtk.Settings.get_default().notify["gtk-decoration-layout"].connect(set_window_buttons); - realize.connect(set_window_buttons); - } - - public void update_trash_button(bool is_trash) { - string action_name = (is_trash ? GearyController.ACTION_TRASH_CONVERSATION - : GearyController.ACTION_DELETE_CONVERSATION); - setup_button(trash_delete_button, action_name, false); + this.realize.connect(set_window_buttons); } public void set_conversation_header(Gtk.HeaderBar header) { @@ -152,71 +158,33 @@ public class MainToolbar : Gtk.Box { conversation_header.decoration_layout = ":" + buttons[1]; } - private void setup_button(Gtk.Button b, string action_name, bool show_label = false) { - Gtk.Action related_action = action_group.get_action(action_name); - b.focus_on_click = false; - b.use_underline = true; - b.tooltip_text = related_action.tooltip; - related_action.notify["tooltip"].connect(() => { b.tooltip_text = related_action.tooltip; }); - b.related_action = related_action; + // Updates tooltip text depending on number of conversations selected. + private void update_conversation_buttons() { + this.mark_message_button.tooltip_text = ngettext(MARK_MESSAGE_MENU_TOOLTIP_SINGLE, + MARK_MESSAGE_MENU_TOOLTIP_MULTIPLE, + this.selected_conversations); + this.copy_message_button.tooltip_text = ngettext(LABEL_MESSAGE_TOOLTIP_SINGLE, + LABEL_MESSAGE_TOOLTIP_MULTIPLE, + this.selected_conversations); + this.move_message_button.tooltip_text = ngettext(MOVE_MESSAGE_TOOLTIP_SINGLE, + MOVE_MESSAGE_TOOLTIP_MULTIPLE, + this.selected_conversations); + this.archive_button.tooltip_text = ngettext(ARCHIVE_CONVERSATION_TOOLTIP_SINGLE, + ARCHIVE_CONVERSATION_TOOLTIP_MULTIPLE, + this.selected_conversations); - // Load icon by name with this fallback order: specified icon name, the action's icon name, - // the action's stock ID ... although stock IDs are being deprecated, that's how we specify - // the icon in the GtkActionEntry (also being deprecated) and GTK+ 3.14 doesn't support that - // any longer - string? icon_to_load = b.related_action.icon_name; - if (icon_to_load == null) - icon_to_load = b.related_action.stock_id; - - // set pixel size to force GTK+ to load our images from our installed directory, not the theme - // directory - if (icon_to_load != null) { - Gtk.Image image = new Gtk.Image.from_icon_name(icon_to_load, Gtk.IconSize.MENU); - image.set_pixel_size(16); - b.image = image; - } - - b.always_show_image = true; - - if (show_label) - b.label = related_action.label; - else - b.label = null; - } - - /** - * Given an icon, menu, and action, creates a button that triggers the menu and the action. - */ - private void setup_menu_button(Gtk.MenuButton b, Gtk.Menu menu, string action_name) { - setup_button(b, action_name); - menu.foreach(GtkUtil.show_menuitem_accel_labels); - b.popup = menu; - - if (b.related_action != null) { - b.related_action.activate.connect(() => { - b.clicked(); - }); - // Null out the action since by connecting it to clicked - // above, invoking would cause an infinite loop otherwise. - b.related_action = null; - } - } - - /** - * Given an icon, popover, and action, creates a button that triggers the popover and the action. - */ - private void setup_popover_button(Gtk.MenuButton b, Gtk.Popover popover, string action_name) { - setup_button(b, action_name); - b.popover = popover; - b.clicked.connect(() => popover.show_all()); - - if (b.related_action != null) { - b.related_action.activate.connect(() => { - b.clicked(); - }); - // Null out the action since by connecting it to clicked - // above, invoking would cause an infinite loop otherwise. - b.related_action = null; + if (this.show_trash_button) { + this.trash_delete_button.action_name = "win."+GearyController.ACTION_TRASH_CONVERSATION; + this.trash_delete_button.image = trash_image; + this.trash_delete_button.tooltip_text = ngettext(TRASH_CONVERSATION_TOOLTIP_SINGLE, + TRASH_CONVERSATION_TOOLTIP_MULTIPLE, + this.selected_conversations); + } else { + this.trash_delete_button.action_name = "win."+GearyController.ACTION_DELETE_CONVERSATION; + this.trash_delete_button.image = delete_image; + this.trash_delete_button.tooltip_text = ngettext(DELETE_CONVERSATION_TOOLTIP_SINGLE, + DELETE_CONVERSATION_TOOLTIP_MULTIPLE, + this.selected_conversations); } } } diff --git a/src/client/components/main-window.vala b/src/client/components/main-window.vala index d0171071..13fedf7c 100644 --- a/src/client/components/main-window.vala +++ b/src/client/components/main-window.vala @@ -29,7 +29,7 @@ public class MainWindow : Gtk.ApplicationWindow { public FolderList.Tree folder_list { get; private set; default = new FolderList.Tree(); } public MainToolbar main_toolbar { get; private set; } public SearchBar search_bar { get; private set; default = new SearchBar(); } - public ConversationListView conversation_list_view { get; private set; default = new ConversationListView(); } + public ConversationListView conversation_list_view { get; private set; } public ConversationViewer conversation_viewer { get; private set; default = new ConversationViewer(); } public StatusBar status_bar { get; private set; default = new StatusBar(); } private MonitoredSpinner spinner = new MonitoredSpinner(); @@ -61,8 +61,6 @@ public class MainWindow : Gtk.ApplicationWindow { load_config(application.config); restore_saved_window_state(); - add_accel_group(application.ui_manager.get_accel_group()); - application.controller.notify[GearyController.PROP_CURRENT_CONVERSATION] .connect(on_conversation_monitor_changed); application.controller.folder_selected.connect(on_folder_selected); @@ -163,6 +161,8 @@ public class MainWindow : Gtk.ApplicationWindow { } private void setup_layout(Configuration config) { + // ConversationListView + this.conversation_list_view = new ConversationListView(this); // Toolbar this.main_toolbar = new MainToolbar(config); this.main_toolbar.bind_property("search-open", this.search_bar, "search-mode-enabled", diff --git a/src/client/composer/composer-container.vala b/src/client/composer/composer-container.vala index da82724d..ba831e64 100644 --- a/src/client/composer/composer-container.vala +++ b/src/client/composer/composer-container.vala @@ -12,12 +12,6 @@ public interface ComposerContainer { // The ComposerWidget-child. internal abstract ComposerWidget composer { get; set; } - // Workaround to retrieve all Gtk.Actions with conflicting accelerators - protected const string[] conflicting_actions = { - GearyController.ACTION_MARK_AS_UNREAD, - GearyController.ACTION_FORWARD_MESSAGE - }; - // We use old_accelerators to keep track of the accelerators we temporarily disabled. protected abstract Gee.MultiMap? old_accelerators { get; set; } @@ -80,10 +74,6 @@ public interface ComposerContainer { } } - // Very stupid workaround while we still use Gtk.Actions in the GearyController - foreach (string conflicting_action in conflicting_actions) - app.actions.get_action(conflicting_action).disconnect_accelerator(); - // Now add our actions to the window and their accelerators foreach (string action in ComposerWidget.action_accelerators.get_keys()) { this.top_window.add_action(composer.get_action(action)); @@ -99,10 +89,6 @@ public interface ComposerContainer { foreach (string action in ComposerWidget.action_accelerators.get_keys()) GearyApplication.instance.set_accels_for_action("win." + action, {}); - // Very stupid workaround while we still use Gtk.Actions in the GearyController - foreach (string conflicting_action in conflicting_actions) - GearyApplication.instance.actions.get_action(conflicting_action).connect_accelerator(); - foreach (string action in old_accelerators.get_keys()) foreach (string accelerator in this.old_accelerators[action]) restore_conflicting_accelerator(action, accelerator); diff --git a/src/client/conversation-list/conversation-list-view.vala b/src/client/conversation-list/conversation-list-view.vala index bad27448..abacb34e 100644 --- a/src/client/conversation-list/conversation-list-view.vala +++ b/src/client/conversation-list/conversation-list-view.vala @@ -6,9 +6,12 @@ public class ConversationListView : Gtk.TreeView { const int LOAD_MORE_HEIGHT = 100; - + + // Used to be able to refer to the action names of the MainWindow + private MainWindow main_window; + private bool enable_load_more = true; - + // Used to avoid repeated calls to load_more(). Contains the last "upper" bound of the // scroll adjustment seen at the call to load_more(). private double last_upper = -1.0; @@ -16,7 +19,6 @@ public class ConversationListView : Gtk.TreeView { private Geary.App.ConversationMonitor? conversation_monitor; private Gee.Set? current_visible_conversations = null; private Geary.Scheduler.Scheduled? scheduled_update_visible_conversations = null; - private Gtk.Menu? context_menu = null; private Gee.Set selected = new Gee.HashSet(); private Geary.IdleManager selection_update; private bool suppress_selection = false; @@ -36,9 +38,10 @@ public class ConversationListView : Gtk.TreeView { public signal void visible_conversations_changed(Gee.Set visible); - public ConversationListView() { + public ConversationListView(MainWindow parent) { set_show_expanders(false); set_headers_visible(false); + this.main_window = parent; append_column(create_column(ConversationListStore.Column.CONVERSATION_DATA, new ConversationListCellRenderer(), ConversationListStore.Column.CONVERSATION_DATA.to_string(), @@ -281,47 +284,36 @@ public class ConversationListView : Gtk.TreeView { if (event.button == 3 && event.type == Gdk.EventType.BUTTON_PRESS) { Geary.App.Conversation conversation = get_model().get_conversation_at_path(path); - - string?[] action_names = {}; - action_names += GearyController.ACTION_DELETE_CONVERSATION; - + + Menu context_menu_model = new Menu(); + context_menu_model.append(_("Delete conversation"), "win."+GearyController.ACTION_DELETE_CONVERSATION); + if (conversation.is_unread()) - action_names += GearyController.ACTION_MARK_AS_READ; - + context_menu_model.append(_("Mark as _Read"), "win."+GearyController.ACTION_MARK_AS_READ); + if (conversation.has_any_read_message()) - action_names += GearyController.ACTION_MARK_AS_UNREAD; - + context_menu_model.append(_("Mark as _Unread"), "win."+GearyController.ACTION_MARK_AS_UNREAD); + if (conversation.is_flagged()) - action_names += GearyController.ACTION_MARK_AS_UNSTARRED; + context_menu_model.append(_("U_nstar"), "win."+GearyController.ACTION_MARK_AS_UNSTARRED); else - action_names += GearyController.ACTION_MARK_AS_STARRED; - - // treat null as separator - action_names += null; - action_names += GearyController.ACTION_REPLY_TO_MESSAGE; - action_names += GearyController.ACTION_REPLY_ALL_MESSAGE; - action_names += GearyController.ACTION_FORWARD_MESSAGE; - - context_menu = new Gtk.Menu(); - foreach (string? action_name in action_names) { - if (action_name == null) { - context_menu.add(new Gtk.SeparatorMenuItem()); - - continue; - } - - Gtk.Action? menu_action = GearyApplication.instance.actions.get_action(action_name); - if (menu_action != null) - context_menu.add(menu_action.create_menu_item()); - } - + context_menu_model.append(_("_Star"), "win."+GearyController.ACTION_MARK_AS_STARRED); + + Menu actions_section = new Menu(); + actions_section.append(_("_Reply"), "win."+GearyController.ACTION_REPLY_TO_MESSAGE); + actions_section.append(_("R_eply All"), "win."+GearyController.ACTION_REPLY_ALL_MESSAGE); + actions_section.append(_("_Forward"), "win."+GearyController.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.show_all(); context_menu.popup(null, null, null, event.button, event.time); - + // When the conversation under the mouse is selected, stop event propagation return get_selection().path_is_selected(path); } - + return false; } diff --git a/src/client/util/util-gtk.vala b/src/client/util/util-gtk.vala index 84fcabed..489d217e 100644 --- a/src/client/util/util-gtk.vala +++ b/src/client/util/util-gtk.vala @@ -35,25 +35,6 @@ public void add_proxy_menu(Gtk.ToolItem tool_item, string label, Gtk.Menu proxy_ }); } -public void add_accelerator(Gtk.UIManager ui_manager, Gtk.ActionGroup action_group, - string accelerator, string action) { - // Parse the accelerator. - uint key = 0; - Gdk.ModifierType modifiers = 0; - Gtk.accelerator_parse(accelerator, out key, out modifiers); - if (key == 0) { - debug("Failed to parse accelerator '%s'", accelerator); - return; - } - - // Connect the accelerator to the action. - ui_manager.get_accel_group().connect(key, modifiers, Gtk.AccelFlags.VISIBLE, - (group, obj, key, modifiers) => { - action_group.get_action(action).activate(); - return true; - }); -} - public void show_menuitem_accel_labels(Gtk.Widget widget) { Gtk.MenuItem? item = widget as Gtk.MenuItem; if (item == null) { diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index dc410911..05a74e74 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -1,6 +1,5 @@ set(RESOURCE_LIST - STRIPBLANKS "accelerators.ui" STRIPBLANKS "account_cannot_remove.glade" STRIPBLANKS "account_list.glade" STRIPBLANKS "account_spinner.glade" @@ -29,12 +28,11 @@ set(RESOURCE_LIST STRIPBLANKS "gtk/menus.ui" STRIPBLANKS "login.glade" STRIPBLANKS "main-toolbar.ui" + STRIPBLANKS "main-toolbar-menus.ui" STRIPBLANKS "main-window.ui" STRIPBLANKS "password-dialog.glade" STRIPBLANKS "preferences-dialog.ui" STRIPBLANKS "remove_confirm.glade" - STRIPBLANKS "toolbar_empty_menu.ui" - STRIPBLANKS "toolbar_mark_menu.ui" STRIPBLANKS "upgrade_dialog.glade" "geary.css" ) diff --git a/ui/accelerators.ui b/ui/accelerators.ui deleted file mode 100644 index 470696b5..00000000 --- a/ui/accelerators.ui +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/ui/main-toolbar-menus.ui b/ui/main-toolbar-menus.ui new file mode 100644 index 00000000..dea30ccb --- /dev/null +++ b/ui/main-toolbar-menus.ui @@ -0,0 +1,40 @@ + + + + + Empty _Spam… + win.empty-spam + + + Empty _Trash… + win.empty-trash + + + + + + Mark as _Read + win.mark-message-read + + + Mark as _Unread + win.mark-message-unread + + + _Star + win.mark-message-starred + + + U_nstar + win.mark-message-unstarred + + + Mark as S_pam + win.mark-message-spam + + + Mark as not S_pam + win.mark-message-not-spam + + + diff --git a/ui/main-toolbar.ui b/ui/main-toolbar.ui index 074710c6..48cd8a03 100644 --- a/ui/main-toolbar.ui +++ b/ui/main-toolbar.ui @@ -20,6 +20,7 @@ True False True + win.new-message text-editor-symbolic @@ -43,6 +44,7 @@ True False True + Toggle search bar preferences-system-search-symbolic @@ -94,6 +96,8 @@ True False True + Reply + win.reply-to-message mail-reply-sender-symbolic @@ -107,6 +111,8 @@ True False True + Reply All + win.reply-all-message mail-reply-all-symbolic @@ -120,6 +126,8 @@ True False True + Forward + win.forward-message mail-forward-symbolic @@ -143,6 +151,7 @@ True False True + win.mark-message-menu marker-symbolic @@ -151,7 +160,7 @@ - + True True False @@ -164,7 +173,7 @@ - + True True False @@ -184,6 +193,7 @@ True False True + Toggle find bar preferences-system-search-symbolic @@ -200,9 +210,10 @@ True False True + win.undo - preferences-system-search-symbolic + edit-undo-symbolic @@ -224,6 +235,12 @@ True False True + win.archive-conv + + + mail-archive-symbolic + + @@ -232,6 +249,12 @@ True False True + win.trash-conv + + + user-trash-symbolic + + diff --git a/ui/toolbar_empty_menu.ui b/ui/toolbar_empty_menu.ui deleted file mode 100644 index cfc89bb5..00000000 --- a/ui/toolbar_empty_menu.ui +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/ui/toolbar_mark_menu.ui b/ui/toolbar_mark_menu.ui deleted file mode 100644 index 662b27b2..00000000 --- a/ui/toolbar_mark_menu.ui +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - -