Move GAction related code from app Controller to MainWindow

This moves a large nummber of the main window's concerns to the main
window, decouping a large number of dependencies from the controller to
the main window, and enables managing action and UI state per-window.
This commit is contained in:
Michael Gratton 2019-10-05 10:27:02 +10:00 committed by Michael James Gratton
parent 09f6fc094a
commit 8e9f00295e
5 changed files with 349 additions and 693 deletions

View file

@ -14,32 +14,6 @@
*/
public class Application.Controller : Geary.BaseObject {
// Named actions.
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_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";
public const string ACTION_CONVERSATION_UP = "up-conversation";
public const string ACTION_CONVERSATION_DOWN = "down-conversation";
// Properties
public const string PROP_SELECTED_CONVERSATIONS ="selected-conversations";
@ -166,48 +140,9 @@ public class Application.Controller : Geary.BaseObject {
private Geary.Folder? previous_non_search_folder = null;
private Gee.List<string?> pending_mailtos = new Gee.ArrayList<string>();
private uint operation_count = 0;
private Geary.Revokable? revokable = null;
// Store the description for the revokable for tooltip display.
// This was previously stored within the context of undo button of the main toolbar.
private string revokable_description { get; set; }
// List of windows we're waiting to close before Geary closes.
private Gee.List<ComposerWidget> waiting_to_close = new Gee.ArrayList<ComposerWidget>();
private const ActionEntry[] win_action_entries = {
{GearyApplication.ACTION_CLOSE, on_close },
{GearyApplication.ACTION_UNDO, on_revoke },
{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 },
// 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 },
{ACTION_CONVERSATION_UP, on_conversation_up },
{ACTION_CONVERSATION_DOWN, on_conversation_down },
// 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" },
};
/**
* Constructs a new instance of the controller.
@ -277,8 +212,6 @@ public class Application.Controller : Geary.BaseObject {
main_window.retry_service_problem.connect(on_retry_service_problem);
main_window.notify["has-toplevel-focus"].connect(on_has_toplevel_focus);
setup_actions();
enable_message_buttons(false);
engine.account_available.connect(on_account_available);
@ -286,13 +219,8 @@ public class Application.Controller : Geary.BaseObject {
// Connect to various UI signals.
main_window.conversation_list_view.conversations_selected.connect(on_conversations_selected);
main_window.conversation_list_view.conversation_activated.connect(on_conversation_activated);
main_window.conversation_list_view.mark_conversations.connect(on_mark_conversations);
main_window.conversation_list_view.visible_conversations_changed.connect(on_visible_conversations_changed);
main_window.folder_list.folder_selected.connect(on_folder_selected);
main_window.folder_list.copy_conversation.connect(on_copy_conversation);
main_window.folder_list.move_conversation.connect(on_move_conversation);
main_window.main_toolbar.copy_folder_menu.folder_selected.connect(on_copy_conversation);
main_window.main_toolbar.move_folder_menu.folder_selected.connect(on_move_conversation);
main_window.search_bar.search_text_changed.connect((text) => { do_search(text); });
main_window.conversation_viewer.conversation_added.connect(
on_conversation_view_added
@ -303,9 +231,6 @@ public class Application.Controller : Geary.BaseObject {
this.main_window.conversation_list_view.grab_focus();
// initialize revokable
save_revokable(null, null);
// Migrate configuration if necessary.
try {
Migrate.xdg_config_dir(this.application.get_user_data_directory(),
@ -382,13 +307,8 @@ public class Application.Controller : Geary.BaseObject {
// Disconnect from various UI signals.
this.main_window.conversation_list_view.conversations_selected.disconnect(on_conversations_selected);
this.main_window.conversation_list_view.conversation_activated.disconnect(on_conversation_activated);
this.main_window.conversation_list_view.mark_conversations.disconnect(on_mark_conversations);
this.main_window.conversation_list_view.visible_conversations_changed.disconnect(on_visible_conversations_changed);
this.main_window.folder_list.folder_selected.disconnect(on_folder_selected);
this.main_window.folder_list.copy_conversation.disconnect(on_copy_conversation);
this.main_window.folder_list.move_conversation.disconnect(on_move_conversation);
this.main_window.main_toolbar.copy_folder_menu.folder_selected.disconnect(on_copy_conversation);
this.main_window.main_toolbar.move_folder_menu.folder_selected.disconnect(on_move_conversation);
this.main_window.conversation_viewer.conversation_added.disconnect(
on_conversation_view_added
);
@ -401,9 +321,6 @@ public class Application.Controller : Geary.BaseObject {
// be freed up
this.plugin_manager.notifications.clear_folders();
// drop the Revokable, which will commit it if necessary
save_revokable(null, null);
this.cancellable_open_account.cancel();
// Create an array of known accounts so the loops below do not
@ -554,50 +471,6 @@ public class Application.Controller : Geary.BaseObject {
}
}
private void setup_actions() {
this.main_window.add_action_entries(win_action_entries, this);
// Marking actions
//
// Unmark is the primary action
add_window_accelerators(ACTION_MARK_AS_READ, { "<Ctrl><Shift>U", "<Shift>I" });
add_window_accelerators(ACTION_MARK_AS_UNREAD, { "<Ctrl>U", "<Shift>U" });
// Ephy uses Ctrl+D for bookmarking
add_window_accelerators(ACTION_MARK_AS_STARRED, { "<Ctrl>D", "S" });
add_window_accelerators(ACTION_MARK_AS_UNSTARRED, { "<Ctrl><Shift>D", "D" });
add_window_accelerators(ACTION_MARK_AS_SPAM, { "<Ctrl>J", "exclam" }); // Exclamation mark (!)
// Replying & forwarding
add_window_accelerators(ACTION_REPLY_TO_MESSAGE, { "<Ctrl>R", "R" });
add_window_accelerators(ACTION_REPLY_ALL_MESSAGE, { "<Ctrl><Shift>R", "<Shift>R" });
add_window_accelerators(ACTION_FORWARD_MESSAGE, { "<Ctrl>L", "F" });
// Moving & labelling
add_window_accelerators(ACTION_COPY_MENU, { "<Ctrl>L", "L" });
add_window_accelerators(ACTION_MOVE_MENU, { "<Ctrl>M", "M" });
add_window_accelerators(ACTION_ARCHIVE_CONVERSATION, { "<Ctrl>K", "A", "Y" });
add_window_accelerators(ACTION_TRASH_CONVERSATION, { "Delete", "BackSpace" });
add_window_accelerators(ACTION_DELETE_CONVERSATION, { "<Shift>Delete", "<Shift>BackSpace" });
// Find & search
add_window_accelerators(ACTION_FIND_IN_CONVERSATION, { "<Ctrl>F", "slash" });
add_window_accelerators(ACTION_SEARCH, { "<Ctrl>S" });
// Zoom
add_window_accelerators(ACTION_ZOOM+("('in')"), { "<Ctrl>equal", "<Ctrl>plus" });
add_window_accelerators(ACTION_ZOOM+("('out')"), { "<Ctrl>minus" });
add_window_accelerators(ACTION_ZOOM+("('normal')"), { "<Ctrl>0" });
// Navigation
add_window_accelerators(ACTION_CONVERSATION_LIST, { "<Ctrl>B" });
add_window_accelerators(ACTION_CONVERSATION_UP, { "<Ctrl>bracketleft", "K" });
add_window_accelerators(ACTION_CONVERSATION_DOWN, { "<Ctrl>bracketright", "J" });
}
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) {
account.information.authentication_failure.connect(
on_authentication_failure
@ -1041,7 +914,7 @@ public class Application.Controller : Geary.BaseObject {
this.main_window.folder_selected(null, null);
} else if (folder != this.current_folder) {
this.main_window.conversation_viewer.show_loading();
get_window_action(ACTION_FIND_IN_CONVERSATION).set_enabled(false);
get_window_action(MainWindow.ACTION_FIND_IN_CONVERSATION).set_enabled(false);
enable_message_buttons(false);
// To prevent the user from selecting folders too quickly,
@ -1087,9 +960,6 @@ public class Application.Controller : Geary.BaseObject {
// reenter.
int mutex_token = yield select_folder_mutex.claim_async(cancellable_folder);
// clear Revokable, as Undo is only available while a folder is selected
save_revokable(null, null);
// re-enable copy/move to the last selected folder
if (current_folder != null) {
main_window.main_toolbar.copy_folder_menu.enable_disable_folder(current_folder, true);
@ -1146,7 +1016,7 @@ public class Application.Controller : Geary.BaseObject {
private void on_conversations_selected(Gee.Set<Geary.App.Conversation> selected) {
this.selected_conversations = selected;
get_window_action(ACTION_FIND_IN_CONVERSATION).set_enabled(false);
get_window_action(MainWindow.ACTION_FIND_IN_CONVERSATION).set_enabled(false);
ConversationViewer viewer = this.main_window.conversation_viewer;
if (this.current_folder != null && !this.main_window.has_composer) {
switch(selected.size) {
@ -1181,7 +1051,7 @@ public class Application.Controller : Geary.BaseObject {
viewer.load_conversation.end(ret);
enable_message_buttons(true);
get_window_action(
ACTION_FIND_IN_CONVERSATION
MainWindow.ACTION_FIND_IN_CONVERSATION
).set_enabled(true);
} catch (GLib.IOError.CANCELLED err) {
// All good
@ -1469,56 +1339,6 @@ public class Application.Controller : Geary.BaseObject {
);
}
private void mark_email(Gee.Collection<Geary.EmailIdentifier> ids,
Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove) {
if (ids.size > 0) {
Geary.App.EmailStore? store = get_email_store_for_folder(current_folder);
if (store != null) {
store.mark_email_async.begin(
ids, flags_to_add, flags_to_remove, cancellable_folder
);
}
}
}
private void on_show_mark_menu() {
bool unread_selected = false;
bool read_selected = false;
bool starred_selected = false;
bool unstarred_selected = false;
foreach (Geary.App.Conversation conversation in selected_conversations) {
if (conversation.is_unread())
unread_selected = true;
// Only check the messages that "Mark as Unread" would mark, so we
// don't add the menu option and have it not do anything.
//
// Sort by Date: field to correspond with ConversationViewer ordering
Geary.Email? latest = conversation.get_latest_sent_email(
Geary.App.Conversation.Location.IN_FOLDER_OUT_OF_FOLDER);
if (latest != null && latest.email_flags != null
&& !latest.email_flags.contains(Geary.EmailFlags.UNREAD))
read_selected = true;
if (conversation.is_flagged()) {
starred_selected = true;
} else {
unstarred_selected = true;
}
}
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<Geary.App.Conversation> visible) {
clear_new_messages("on_visible_conversations_changed", visible);
}
@ -1558,139 +1378,9 @@ public class Application.Controller : Geary.BaseObject {
}
}
private void on_mark_conversations(Gee.Collection<Geary.App.Conversation> conversations,
Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove,
bool latest_only = false) {
mark_email(get_conversation_email_ids(conversations, latest_only),
flags_to_add, flags_to_remove);
}
private void on_conversation_viewer_mark_emails(Gee.Collection<Geary.EmailIdentifier> emails,
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(SimpleAction action) {
Geary.EmailFlags flags = new Geary.EmailFlags();
flags.add(Geary.EmailFlags.UNREAD);
Gee.Collection<Geary.EmailIdentifier> ids = get_selected_email_ids(false);
mark_email(ids, null, flags);
ConversationListBox? list =
main_window.conversation_viewer.current_list;
if (list != null) {
foreach (Geary.EmailIdentifier id in ids)
list.mark_manual_read(id);
}
}
private void on_mark_as_unread(SimpleAction action) {
Geary.EmailFlags flags = new Geary.EmailFlags();
flags.add(Geary.EmailFlags.UNREAD);
Gee.Collection<Geary.EmailIdentifier> ids = get_selected_email_ids(true);
mark_email(ids, flags, null);
ConversationListBox? list =
main_window.conversation_viewer.current_list;
if (list != null) {
foreach (Geary.EmailIdentifier id in ids)
list.mark_manual_unread(id);
}
}
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(SimpleAction action) {
Geary.EmailFlags flags = new Geary.EmailFlags();
flags.add(Geary.EmailFlags.FLAGGED);
mark_email(get_selected_email_ids(false), null, flags);
}
private void on_show_move_menu(SimpleAction? action) {
this.main_window.main_toolbar.move_message_button.clicked();
}
private void on_show_copy_menu(SimpleAction? action) {
this.main_window.main_toolbar.copy_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.
try {
destination_folder = yield current_account.get_required_special_folder_async(
Geary.SpecialFolderType.SPAM, cancellable);
} catch (Error e) {
debug("Error getting spam folder: %s", e.message);
}
} else {
// Move out of spam folder, back to inbox.
destination_folder = current_account.get_special_folder(Geary.SpecialFolderType.INBOX);
}
if (destination_folder != null)
on_move_conversation(destination_folder);
}
private void on_mark_as_spam_toggle(SimpleAction action) {
mark_as_spam_toggle_async.begin(null);
}
private void copy_email(Gee.Collection<Geary.EmailIdentifier> ids,
Geary.FolderPath destination) {
if (ids.size > 0) {
Geary.App.EmailStore? store = get_email_store_for_folder(current_folder);
if (store != null) {
store.copy_email_async.begin(
ids, destination, cancellable_folder
);
}
}
}
private void on_copy_conversation(Geary.Folder destination) {
copy_email(get_selected_email_ids(false), destination.path);
}
private void on_move_conversation(Geary.Folder destination) {
// Nothing to do if nothing selected.
if (selected_conversations == null || selected_conversations.size == 0)
return;
Gee.Collection<Geary.EmailIdentifier> ids = get_selected_email_ids(false);
if (ids.size == 0)
return;
selection_operation_started();
Geary.FolderSupport.Move? supports_move = current_folder as Geary.FolderSupport.Move;
if (supports_move != null)
move_conversation_async.begin(
supports_move, ids, destination.path, cancellable_folder,
(obj, ret) => {
move_conversation_async.end(ret);
selection_operation_finished();
});
}
private async void move_conversation_async(Geary.FolderSupport.Move source_folder,
Gee.Collection<Geary.EmailIdentifier> ids,
Geary.FolderPath destination,
Cancellable? cancellable) {
try {
save_revokable(yield source_folder.move_email_async(ids, destination, cancellable),
ngettext("Moved %d message to %s", "Moved %d messages to %s", ids.size).printf(ids.size, destination.to_string()));
} catch (Error err) {
debug("%s: Unable to move %d emails: %s", source_folder.to_string(), ids.size,
err.message);
}
private void on_conversation_viewer_mark_emails(Gee.Collection<Geary.EmailIdentifier> email,
Geary.EmailFlags? to_add,
Geary.EmailFlags? to_remove) {
}
private void on_attachments_activated(Gee.Collection<Geary.Attachment> attachments) {
@ -1952,29 +1642,6 @@ public class Application.Controller : Geary.BaseObject {
return true;
}
// View contains the email from whose menu this reply or forward
// was triggered. If null, this was triggered from the headerbar
// or shortcut.
private void create_reply_forward_widget(ComposerWidget.ComposeType compose_type,
owned ConversationEmail? email_view) {
if (email_view == null) {
ConversationListBox? list_view =
main_window.conversation_viewer.current_list;
if (list_view != null) {
email_view = list_view.get_reply_target();
}
}
if (email_view != null) {
email_view.get_selection_for_quoting.begin((obj, res) => {
string? quote = email_view.get_selection_for_quoting.end(res);
create_compose_widget(compose_type, email_view.email, quote);
});
} else {
create_compose_widget(compose_type, email_view.email, null);
}
}
/**
* Creates a composer widget.
*
@ -2110,323 +1777,12 @@ public class Application.Controller : Geary.BaseObject {
}
}
private void on_close() {
this.main_window.close();
}
private void on_reply_to_message(ConversationEmail target_view) {
create_reply_forward_widget(ComposerWidget.ComposeType.REPLY, target_view);
}
private void on_reply_to_message_action(SimpleAction action) {
create_reply_forward_widget(ComposerWidget.ComposeType.REPLY, null);
}
private void on_reply_all_message(ConversationEmail target_view) {
create_reply_forward_widget(ComposerWidget.ComposeType.REPLY_ALL, target_view);
}
private void on_reply_all_message_action(SimpleAction action) {
create_reply_forward_widget(ComposerWidget.ComposeType.REPLY_ALL, null);
}
private void on_forward_message(ConversationEmail target_view) {
create_reply_forward_widget(ComposerWidget.ComposeType.FORWARD, target_view);
}
private void on_forward_message_action(SimpleAction action) {
create_reply_forward_widget(ComposerWidget.ComposeType.FORWARD, null);
}
private void on_find_in_conversation_action(SimpleAction action) {
this.main_window.conversation_viewer.enable_find();
}
private void on_search_activated(SimpleAction action) {
this.main_window.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(SimpleAction action) {
archive_or_delete_selection_async.begin(false, true, cancellable_folder,
on_archive_or_delete_selection_finished);
}
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(SimpleAction action) {
on_empty_trash_or_spam(Geary.SpecialFolderType.SPAM);
}
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
if (current_account == null)
return;
Geary.Folder? folder = current_account.get_special_folder(special_folder_type);
if (folder == null)
return;
Geary.FolderSupport.Empty? emptyable = folder as Geary.FolderSupport.Empty;
if (emptyable == null) {
debug("%s: Special folder %s (%s) does not support emptying", current_account.to_string(),
folder.path.to_string(), special_folder_type.to_string());
return;
}
ConfirmationDialog dialog = new ConfirmationDialog(main_window,
_("Empty all email from your %s folder?").printf(special_folder_type.get_display_name()),
_("This removes the email from Geary and your email server.")
+ " <b>" + _("This cannot be undone.") + "</b>",
_("Empty %s").printf(special_folder_type.get_display_name()), "destructive-action");
dialog.use_secondary_markup(true);
dialog.set_focus_response(Gtk.ResponseType.CANCEL);
if (dialog.run() == Gtk.ResponseType.OK)
empty_folder_async.begin(emptyable, cancellable_folder);
}
private async void empty_folder_async(Geary.FolderSupport.Empty emptyable, Cancellable? cancellable) {
try {
yield do_empty_folder_async(emptyable, cancellable);
} catch (Error err) {
// don't report to user if cancelled
if (err is IOError.CANCELLED)
return;
ErrorDialog dialog = new ErrorDialog(main_window,
_("Error emptying %s").printf(emptyable.get_display_name()), err.message);
dialog.run();
}
}
private async void do_empty_folder_async(Geary.FolderSupport.Empty emptyable, Cancellable? cancellable)
throws Error {
bool open = false;
try {
yield emptyable.open_async(Geary.Folder.OpenFlags.NO_DELAY, cancellable);
open = true;
yield emptyable.empty_folder_async(cancellable);
} finally {
if (open) {
try {
yield emptyable.close_async(null);
} catch (Error err) {
// ignored
}
}
}
}
private bool current_folder_supports_trash() {
return (current_folder != null && current_folder.special_folder_type != Geary.SpecialFolderType.TRASH
&& !current_folder.properties.is_local_only && current_account != null
&& (current_folder as Geary.FolderSupport.Move) != null);
}
private bool confirm_delete(int num_messages) {
ConfirmationDialog dialog = new ConfirmationDialog(main_window, ngettext(
"Do you want to permanently delete this message?",
"Do you want to permanently delete these messages?", num_messages),
null, _("Delete"), "destructive-action");
return (dialog.run() == Gtk.ResponseType.OK);
}
private async void trash_messages_async(Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable)
throws Error {
debug("Trashing selected messages");
Geary.FolderSupport.Move? supports_move = current_folder as Geary.FolderSupport.Move;
if (current_folder_supports_trash() && supports_move != null) {
Geary.FolderPath trash_path = (yield current_account.get_required_special_folder_async(
Geary.SpecialFolderType.TRASH, cancellable)).path;
save_revokable(yield supports_move.move_email_async(ids, trash_path, cancellable),
ngettext("Trashed %d message", "Trashed %d messages", ids.size).printf(ids.size));
} else {
debug("Folder %s doesn't support move or account %s doesn't have a trash folder",
current_folder.to_string(), current_account.to_string());
}
}
private async void delete_messages_async(Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable)
throws Error {
debug("Deleting selected messages");
Geary.FolderSupport.Remove? supports_remove = current_folder as Geary.FolderSupport.Remove;
if (supports_remove != null) {
if (confirm_delete(ids.size)) {
yield supports_remove.remove_email_async(ids, cancellable);
} else {
last_deleted_conversation = null;
}
} else {
debug("Folder %s doesn't support remove", current_folder.to_string());
}
}
private async void archive_or_delete_selection_async(bool archive, bool trash,
Cancellable? cancellable) throws Error {
ConversationListBox list_view =
main_window.conversation_viewer.current_list;
if (list_view != null &&
list_view.conversation == last_deleted_conversation) {
debug("Not archiving/trashing/deleting; viewed conversation is last deleted conversation");
return;
}
selection_operation_started();
last_deleted_conversation = selected_conversations.size > 0
? Geary.traverse<Geary.App.Conversation>(selected_conversations).first() : null;
Gee.Collection<Geary.EmailIdentifier> ids = get_selected_email_ids(false);
if (archive) {
debug("Archiving selected messages");
Geary.FolderSupport.Archive? supports_archive = current_folder as Geary.FolderSupport.Archive;
if (supports_archive == null) {
debug("Folder %s doesn't support archive", current_folder.to_string());
} else {
save_revokable(yield supports_archive.archive_email_async(ids, cancellable),
ngettext("Archived %d message", "Archived %d messages", ids.size).printf(ids.size));
}
return;
}
if (trash) {
yield trash_messages_async(ids, cancellable);
} else {
yield delete_messages_async(ids, cancellable);
}
}
private void on_archive_or_delete_selection_finished(Object? source, AsyncResult result) {
try {
archive_or_delete_selection_async.end(result);
} catch (Error e) {
debug("Unable to archive/trash/delete messages: %s", e.message);
}
selection_operation_finished();
}
private void save_revokable(Geary.Revokable? new_revokable, string? description) {
// disconnect old revokable & blindly commit it
if (revokable != null) {
revokable.notify[Geary.Revokable.PROP_VALID].disconnect(on_revokable_valid_changed);
revokable.notify[Geary.Revokable.PROP_IN_PROCESS].disconnect(update_revokable_action);
revokable.committed.disconnect(on_revokable_committed);
revokable.commit_async.begin();
}
// store new revokable
this.revokable = new_revokable;
this.revokable_description = description;
// connect to new revokable
if (revokable != null) {
revokable.notify[Geary.Revokable.PROP_VALID].connect(on_revokable_valid_changed);
revokable.notify[Geary.Revokable.PROP_IN_PROCESS].connect(update_revokable_action);
revokable.committed.connect(on_revokable_committed);
}
if (this.main_window != null) {
if (this.revokable != null && this.revokable_description != null) {
Components.InAppNotification ian =
new Components.InAppNotification(this.revokable_description);
ian.set_button(_("Undo"), "win." + GearyApplication.ACTION_UNDO);
this.main_window.add_notification(ian);
}
update_revokable_action();
}
}
private void update_revokable_action() {
get_window_action(GearyApplication.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)
save_revokable(null, null);
}
private void on_revokable_committed(Geary.Revokable? committed_revokable) {
if (committed_revokable == null)
return;
save_revokable(committed_revokable, this.revokable_description);
}
private void on_revoke() {
if (revokable != null && revokable.valid)
revokable.revoke_async.begin(null, on_revoke_completed);
}
private void on_revoke_completed(Object? object, AsyncResult result) {
// Don't use the "revokable" instance because it might have gone null before this callback
// was reached
Geary.Revokable? origin = object as Geary.Revokable;
if (origin == null)
return;
try {
origin.revoke_async.end(result);
} catch (Error err) {
debug("Unable to revoke operation: %s", err.message);
}
}
private void selection_operation_started() {
this.operation_count += 1;
if (this.operation_count == 1) {
this.main_window.conversation_list_view.set_changing_selection(true);
}
}
private void selection_operation_finished() {
this.operation_count -= 1;
if (this.operation_count == 0) {
this.main_window.conversation_list_view.set_changing_selection(false);
}
}
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();
}
}
private void on_conversation_list() {
this.main_window.conversation_list_view.grab_focus();
}
private void on_sent(Geary.Account account, Geary.RFC822.Message sent) {
// Translators: The label for an in-app notification. The
// string substitution is a list of recipients of the email.
@ -2478,17 +1834,9 @@ public class Application.Controller : Geary.BaseObject {
}
private void on_trash_message(ConversationEmail target_view) {
Gee.Collection<Geary.EmailIdentifier> ids =
new Gee.ArrayList<Geary.EmailIdentifier>();
ids.add(target_view.email.id);
trash_messages_async.begin(ids, cancellable_folder);
}
private void on_delete_message(ConversationEmail target_view) {
Gee.Collection<Geary.EmailIdentifier> ids =
new Gee.ArrayList<Geary.EmailIdentifier>();
ids.add(target_view.email.id);
delete_messages_async.begin(ids, cancellable_folder);
}
private void on_view_source(ConversationEmail email_view) {
@ -2526,15 +1874,15 @@ public class Application.Controller : Geary.BaseObject {
main_window.main_toolbar.selected_conversations = this.selected_conversations.size;
// Single message only buttons.
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);
get_window_action(MainWindow.ACTION_REPLY_TO_MESSAGE).set_enabled(false);
get_window_action(MainWindow.ACTION_REPLY_ALL_MESSAGE).set_enabled(false);
get_window_action(MainWindow.ACTION_FORWARD_MESSAGE).set_enabled(false);
// Mutliple message buttons.
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);
get_window_action(MainWindow.ACTION_MOVE_MENU).set_enabled(current_folder is Geary.FolderSupport.Move);
get_window_action(MainWindow.ACTION_ARCHIVE_CONVERSATION).set_enabled(current_folder is Geary.FolderSupport.Archive);
get_window_action(MainWindow.ACTION_TRASH_CONVERSATION).set_enabled(current_folder_supports_trash());
get_window_action(MainWindow.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);
@ -2549,13 +1897,13 @@ public class Application.Controller : Geary.BaseObject {
if (current_folder != null && current_folder.special_folder_type == Geary.SpecialFolderType.DRAFTS)
respond_sensitive = false;
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));
get_window_action(MainWindow.ACTION_REPLY_TO_MESSAGE).set_enabled(respond_sensitive);
get_window_action(MainWindow.ACTION_REPLY_ALL_MESSAGE).set_enabled(respond_sensitive);
get_window_action(MainWindow.ACTION_FORWARD_MESSAGE).set_enabled(respond_sensitive);
get_window_action(MainWindow.ACTION_MOVE_MENU).set_enabled(sensitive && (current_folder is Geary.FolderSupport.Move));
get_window_action(MainWindow.ACTION_ARCHIVE_CONVERSATION).set_enabled(sensitive && (current_folder is Geary.FolderSupport.Archive));
get_window_action(MainWindow.ACTION_TRASH_CONVERSATION).set_enabled(sensitive && current_folder_supports_trash());
get_window_action(MainWindow.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);
@ -2584,8 +1932,8 @@ public class Application.Controller : Geary.BaseObject {
if (selected_operations != null)
supported_operations.add_all(selected_operations.get_values());
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))));
get_window_action(MainWindow.ACTION_SHOW_MARK_MENU).set_enabled(sensitive && (typeof(Geary.FolderSupport.Mark) in supported_operations));
get_window_action(MainWindow.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.
@ -2842,12 +2190,25 @@ public class Application.Controller : Geary.BaseObject {
);
}
private void on_conversation_up() {
this.main_window.conversation_list_view.scroll(Gtk.ScrollType.STEP_UP);
private void on_reply_to_message(ConversationEmail target_view) {
target_view.get_selection_for_quoting.begin((obj, res) => {
string? quote = target_view.get_selection_for_quoting.end(res);
create_compose_widget(REPLY, target_view.email, quote);
});
}
private void on_conversation_down() {
this.main_window.conversation_list_view.scroll(Gtk.ScrollType.STEP_DOWN);
private void on_reply_all_message(ConversationEmail target_view) {
target_view.get_selection_for_quoting.begin((obj, res) => {
string? quote = target_view.get_selection_for_quoting.end(res);
create_compose_widget(REPLY_ALL, target_view.email, quote);
});
}
private void on_forward_message(ConversationEmail target_view) {
target_view.get_selection_for_quoting.begin((obj, res) => {
string? quote = target_view.get_selection_for_quoting.end(res);
create_compose_widget(FORWARD, target_view.email, quote);
});
}
private void on_save_attachments(Gee.Collection<Geary.Attachment> attachments) {

View file

@ -446,6 +446,7 @@ public class GearyApplication : Gtk.Application {
add_window_accelerators(ACTION_REDO, { "<Ctrl><Shift>Z" });
add_window_accelerators(ACTION_UNDO, { "<Ctrl>Z" });
MainWindow.add_window_accelerators(this);
ComposerWidget.add_window_accelerators(this);
Components.Inspector.add_window_accelerators(this);

View file

@ -157,7 +157,7 @@ public class MainToolbar : Gtk.Box {
);
if (this.show_trash_button) {
this.trash_delete_button.action_name = "win."+Application.Controller.ACTION_TRASH_CONVERSATION;
this.trash_delete_button.action_name = "win."+MainWindow.ACTION_TRASH_CONVERSATION;
this.trash_delete_button.image = trash_image;
this.trash_delete_button.tooltip_text = ngettext(
"Move conversation to Trash",
@ -165,7 +165,7 @@ public class MainToolbar : Gtk.Box {
this.selected_conversations
);
} else {
this.trash_delete_button.action_name = "win."+Application.Controller.ACTION_DELETE_CONVERSATION;
this.trash_delete_button.action_name = "win."+MainWindow.ACTION_DELETE_CONVERSATION;
this.trash_delete_button.image = delete_image;
this.trash_delete_button.tooltip_text = ngettext(
"Delete conversation",

View file

@ -10,16 +10,165 @@
public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
// Named actions.
public const string ACTION_ARCHIVE_CONVERSATION = "archive-conv";
public const string ACTION_CONVERSATION_DOWN = "down-conversation";
public const string ACTION_CONVERSATION_LIST = "focus-conv-list";
public const string ACTION_CONVERSATION_UP = "up-conversation";
public const string ACTION_COPY_MENU = "show-copy-menu";
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_FIND_IN_CONVERSATION = "conv-find";
public const string ACTION_FORWARD_MESSAGE = "forward-message";
public const string ACTION_MARK_AS_READ = "mark-message-read";
public const string ACTION_MARK_AS_STARRED = "mark-message-starred";
public const string ACTION_MARK_AS_UNREAD = "mark-message-unread";
public const string ACTION_MARK_AS_UNSTARRED = "mark-message-unstarred";
public const string ACTION_MOVE_MENU = "show-move-menu";
public const string ACTION_REPLY_ALL_MESSAGE = "reply-all-message";
public const string ACTION_REPLY_TO_MESSAGE = "reply-to-message";
public const string ACTION_SEARCH = "search-conv";
public const string ACTION_SHOW_MARK_MENU = "mark-message-menu";
public const string ACTION_TOGGLE_FIND = "toggle-find";
public const string ACTION_TOGGLE_SEARCH = "toggle-search";
public const string ACTION_TOGGLE_SPAM = "toggle-message-spam";
public const string ACTION_TRASH_CONVERSATION = "trash-conv";
public const string ACTION_ZOOM = "zoom";
private const int STATUS_BAR_HEIGHT = 18;
private const int UPDATE_UI_INTERVAL = 60;
private const int MIN_CONVERSATION_COUNT = 50;
private const ActionEntry[] win_action_entries = {
{GearyApplication.ACTION_CLOSE, on_close },
{GearyApplication.ACTION_UNDO, on_undo },
{GearyApplication.ACTION_REDO, on_redo },
{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 },
// Message actions
{ACTION_REPLY_TO_MESSAGE, on_reply_to_message },
{ACTION_REPLY_ALL_MESSAGE, on_reply_all_message },
{ACTION_FORWARD_MESSAGE, on_forward_message },
{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 },
{ACTION_CONVERSATION_UP, on_conversation_up },
{ACTION_CONVERSATION_DOWN, on_conversation_down },
// 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_TOGGLE_SPAM, on_mark_as_spam_toggle },
// Message viewer
{ACTION_ZOOM, on_zoom, "s" },
};
public static void add_window_accelerators(GearyApplication owner) {
// Marking actions
//
// Unmark is the primary action
owner.add_window_accelerators(
ACTION_MARK_AS_READ, { "<Ctrl><Shift>U", "<Shift>I" }
);
owner.add_window_accelerators(
ACTION_MARK_AS_UNREAD, { "<Ctrl>U", "<Shift>U" }
);
// Ephy uses Ctrl+D for bookmarking
owner.add_window_accelerators(
ACTION_MARK_AS_STARRED, { "<Ctrl>D", "S" }
);
owner.add_window_accelerators(
ACTION_MARK_AS_UNSTARRED, { "<Ctrl><Shift>D", "D" }
);
owner.add_window_accelerators(
ACTION_TOGGLE_SPAM, { "<Ctrl>J", "exclam" } // Exclamation mark (!)
);
// Replying & forwarding
owner.add_window_accelerators(
ACTION_REPLY_TO_MESSAGE, { "<Ctrl>R", "R" }
);
owner.add_window_accelerators(
ACTION_REPLY_ALL_MESSAGE, { "<Ctrl><Shift>R", "<Shift>R" }
);
owner.add_window_accelerators(
ACTION_FORWARD_MESSAGE, { "<Ctrl>L", "F" }
);
// Moving & labelling
owner.add_window_accelerators(
ACTION_COPY_MENU, { "<Ctrl>L", "L" }
);
owner.add_window_accelerators(
ACTION_MOVE_MENU, { "<Ctrl>M", "M" }
);
owner.add_window_accelerators(
ACTION_ARCHIVE_CONVERSATION, { "<Ctrl>K", "A", "Y" }
);
owner.add_window_accelerators(
ACTION_TRASH_CONVERSATION, { "Delete", "BackSpace" }
);
owner.add_window_accelerators(
ACTION_DELETE_CONVERSATION, { "<Shift>Delete", "<Shift>BackSpace" }
);
// Find & search
owner.add_window_accelerators(
ACTION_FIND_IN_CONVERSATION, { "<Ctrl>F", "slash" }
);
owner.add_window_accelerators(
ACTION_SEARCH, { "<Ctrl>S" }
);
// Zoom
owner.add_window_accelerators(
ACTION_ZOOM+("('in')"), { "<Ctrl>equal", "<Ctrl>plus" }
);
owner.add_window_accelerators(
ACTION_ZOOM+("('out')"), { "<Ctrl>minus" }
);
owner.add_window_accelerators(
ACTION_ZOOM+("('normal')"), { "<Ctrl>0" }
);
// Navigation
owner.add_window_accelerators(
ACTION_CONVERSATION_LIST, { "<Ctrl>B" }
);
owner.add_window_accelerators(
ACTION_CONVERSATION_UP, { "<Ctrl>bracketleft", "K" }
);
owner.add_window_accelerators(
ACTION_CONVERSATION_DOWN, { "<Ctrl>bracketright", "J" }
);
}
public new GearyApplication application {
get { return (GearyApplication) base.get_application(); }
set { base.set_application(value); }
}
/** Currently selected account, null if none selected */
public Geary.Account? current_account {
owned get {
Geary.Account? account = null;
if (this.current_folder != null) {
account = this.current_folder.account;
}
return account;
}
}
/** Currently selected folder, null if none selected */
public Geary.Folder? current_folder { get; private set; default = null; }
@ -110,14 +259,15 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
load_config(application.config);
restore_saved_window_state();
this.application.engine.account_available.connect(on_account_available);
this.application.engine.account_unavailable.connect(on_account_unavailable);
add_action_entries(win_action_entries, this);
set_styling();
setup_layout(application.config);
on_change_orientation();
this.application.engine.account_available.connect(on_account_available);
this.application.engine.account_unavailable.connect(on_account_unavailable);
this.update_ui_timeout = new Geary.TimeoutManager.seconds(
UPDATE_UI_INTERVAL, on_update_ui_timeout
);
@ -235,7 +385,7 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
new ComposerWindow(composer, this.application);
} else {
this.conversation_viewer.do_compose(composer);
get_action(Application.Controller.ACTION_FIND_IN_CONVERSATION).set_enabled(false);
get_action(ACTION_FIND_IN_CONVERSATION).set_enabled(false);
}
}
@ -382,6 +532,8 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
);
this.main_toolbar = new MainToolbar(config);
this.main_toolbar.move_folder_menu.folder_selected.connect(on_move_conversation);
this.main_toolbar.copy_folder_menu.folder_selected.connect(on_copy_conversation);
this.main_toolbar.bind_property("search-open", this.search_bar, "search-mode-enabled",
BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
this.main_toolbar.bind_property("find-open", this.conversation_viewer.conversation_find_bar,
@ -405,10 +557,15 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
// Search bar
this.search_bar_box.pack_start(this.search_bar, false, false, 0);
// Folder list
this.folder_list_scrolled.add(this.folder_list);
this.folder_list.move_conversation.connect(on_move_conversation);
this.folder_list.copy_conversation.connect(on_copy_conversation);
// Conversation list
this.conversation_list_scrolled.add(this.conversation_list_view);
// Conversation viewer
this.conversations_paned.pack2(this.conversation_viewer, true, true);
@ -873,4 +1030,140 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
update_ui();
}
// Action callbacks
private void on_undo() {
}
private void on_redo() {
}
private void on_close() {
close();
}
private void on_conversation_list() {
this.conversation_list_view.grab_focus();
}
private void on_find_in_conversation_action() {
this.conversation_viewer.enable_find();
}
private void on_search_activated() {
show_search_bar();
}
private void on_zoom(SimpleAction action, Variant? parameter) {
ConversationListBox? view = this.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();
}
}
private void on_reply_to_message() {
}
private void on_reply_all_message() {
}
private void on_forward_message() {
}
private void on_show_copy_menu() {
this.main_toolbar.copy_message_button.clicked();
}
private void on_show_move_menu() {
this.main_toolbar.move_message_button.clicked();
}
private void on_conversation_up() {
this.conversation_list_view.scroll(Gtk.ScrollType.STEP_UP);
}
private void on_conversation_down() {
this.conversation_list_view.scroll(Gtk.ScrollType.STEP_DOWN);
}
private void on_show_mark_menu() {
bool unread_selected = false;
bool read_selected = false;
bool starred_selected = false;
bool unstarred_selected = false;
foreach (Geary.App.Conversation conversation in
this.conversation_list_view.get_selected_conversations()) {
if (conversation.is_unread())
unread_selected = true;
// Only check the messages that "Mark as Unread" would mark, so we
// don't add the menu option and have it not do anything.
//
// Sort by Date: field to correspond with ConversationViewer ordering
Geary.Email? latest = conversation.get_latest_sent_email(
Geary.App.Conversation.Location.IN_FOLDER_OUT_OF_FOLDER);
if (latest != null && latest.email_flags != null
&& !latest.email_flags.contains(Geary.EmailFlags.UNREAD))
read_selected = true;
if (conversation.is_flagged()) {
starred_selected = true;
} else {
unstarred_selected = true;
}
}
get_action(ACTION_MARK_AS_READ).set_enabled(unread_selected);
get_action(ACTION_MARK_AS_UNREAD).set_enabled(read_selected);
get_action(ACTION_MARK_AS_STARRED).set_enabled(unstarred_selected);
get_action(ACTION_MARK_AS_UNSTARRED).set_enabled(starred_selected);
// If we're in Drafts/Outbox, we also shouldn't set a message as SPAM.
bool in_spam_folder = current_folder.special_folder_type == Geary.SpecialFolderType.SPAM;
get_action(ACTION_TOGGLE_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_mark_as_read() {
}
private void on_mark_as_unread() {
}
private void on_mark_as_starred() {
}
private void on_mark_as_unstarred() {
}
private void on_mark_as_spam_toggle() {
}
private void on_move_conversation(Geary.Folder destination) {
}
private void on_copy_conversation(Geary.Folder destination) {
}
private void on_archive_conversation() {
}
private void on_trash_conversation() {
}
private void on_delete_conversation() {
}
private void on_empty_spam() {
}
private void on_empty_trash() {
}
}

View file

@ -338,7 +338,7 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
"Move conversations to _Trash",
this.selected.size
),
"win." + Application.Controller.ACTION_ARCHIVE_CONVERSATION
"win." + MainWindow.ACTION_ARCHIVE_CONVERSATION
);
} else {
context_menu_model.append(
@ -348,25 +348,25 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
"_Delete conversations",
this.selected.size
),
"win." + Application.Controller.ACTION_DELETE_CONVERSATION
"win." + MainWindow.ACTION_DELETE_CONVERSATION
);
}
if (conversation.is_unread())
context_menu_model.append(_("Mark as _Read"), "win."+Application.Controller.ACTION_MARK_AS_READ);
context_menu_model.append(_("Mark as _Read"), "win."+MainWindow.ACTION_MARK_AS_READ);
if (conversation.has_any_read_message())
context_menu_model.append(_("Mark as _Unread"), "win."+Application.Controller.ACTION_MARK_AS_UNREAD);
context_menu_model.append(_("Mark as _Unread"), "win."+MainWindow.ACTION_MARK_AS_UNREAD);
if (conversation.is_flagged())
context_menu_model.append(_("U_nstar"), "win."+Application.Controller.ACTION_MARK_AS_UNSTARRED);
context_menu_model.append(_("U_nstar"), "win."+MainWindow.ACTION_MARK_AS_UNSTARRED);
else
context_menu_model.append(_("_Star"), "win."+Application.Controller.ACTION_MARK_AS_STARRED);
context_menu_model.append(_("_Star"), "win."+MainWindow.ACTION_MARK_AS_STARRED);
Menu actions_section = new Menu();
actions_section.append(_("_Reply"), "win."+Application.Controller.ACTION_REPLY_TO_MESSAGE);
actions_section.append(_("R_eply All"), "win."+Application.Controller.ACTION_REPLY_ALL_MESSAGE);
actions_section.append(_("_Forward"), "win."+Application.Controller.ACTION_FORWARD_MESSAGE);
actions_section.append(_("_Reply"), "win."+MainWindow.ACTION_REPLY_TO_MESSAGE);
actions_section.append(_("R_eply All"), "win."+MainWindow.ACTION_REPLY_ALL_MESSAGE);
actions_section.append(_("_Forward"), "win."+MainWindow.ACTION_FORWARD_MESSAGE);
context_menu_model.append_section(null, actions_section);
// Use a popover rather than a regular context menu since
@ -577,4 +577,5 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
return Gdk.EVENT_PROPAGATE;
}
}