Start the final part of bug 713991.

* Ported Gtk.Action to GLib.Action in the GearyController.
* Removed Gtk.AccelGroups (handled through Gtk.Application now).
* Got rid of Gtk.UiManager (now all is done through Gtk.Builder).
* Throw away workaround for conflicting Gtk.Actions in ComposerContainer.
* Aggregate zoom in/out/normal into one parameterized zoom action.

Signed-off-by: Niels De Graef <nielsdegraef@gmail.com>
This commit is contained in:
Niels De Graef 2017-05-15 23:21:32 +02:00 committed by Michael James Gratton
parent 466ef79da0
commit 114ed09dda
15 changed files with 388 additions and 668 deletions

View file

@ -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

View file

@ -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.
*/

View file

@ -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<Geary.Endpoint> validating_endpoints = new Gee.HashSet<Geary.Endpoint>();
private Geary.Revokable? revokable = null;
// 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 = {
{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, "<Ctrl>I",
null, on_mark_as_read };
mark_read.label = _("Mark as _Read");
entries += mark_read;
add_accelerator("<Shift>I", ACTION_MARK_AS_READ);
Gtk.ActionEntry mark_unread = { ACTION_MARK_AS_UNREAD, "mail-mark-unread", TRANSLATABLE,
"<Ctrl>U", null, on_mark_as_unread };
mark_unread.label = _("Mark as _Unread");
entries += mark_unread;
add_accelerator("<Shift>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, "<Ctrl>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, "<Ctrl>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"), "<Ctrl>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"),
"<Ctrl><Shift>R", _("Reply all (Ctrl+Shift+R, Shift+R)"),
on_reply_all_message_action };
entries += reply_all_message;
add_accelerator("<Shift>R", ACTION_REPLY_ALL_MESSAGE);
Gtk.ActionEntry forward_message = { ACTION_FORWARD_MESSAGE, null, _("_Forward"), "<Ctrl>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, "<Ctrl>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, "<Shift>Delete", null, on_delete_conversation };
delete_conversation.label = DELETE_CONVERSATION_LABEL;
delete_conversation.tooltip = DELETE_CONVERSATION_TOOLTIP_SINGLE;
entries += delete_conversation;
add_accelerator("<Shift>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, "<Ctrl>Z", null, on_revoke };
entries += undo;
Gtk.ActionEntry zoom_in = { ACTION_ZOOM_IN, null, null, "<Ctrl>equal",
null, on_zoom_in };
entries += zoom_in;
add_accelerator("equal", ACTION_ZOOM_IN);
Gtk.ActionEntry zoom_out = { ACTION_ZOOM_OUT, null, null, "<Ctrl>minus",
null, on_zoom_out };
entries += zoom_out;
add_accelerator("minus", ACTION_ZOOM_OUT);
Gtk.ActionEntry zoom_normal = { ACTION_ZOOM_NORMAL, null, null, "<Ctrl>0",
null, on_zoom_normal };
entries += zoom_normal;
add_accelerator("0", ACTION_ZOOM_NORMAL);
Gtk.ActionEntry search = {
ACTION_SEARCH, null, null, "<Ctrl>S", null,
() => { show_search_bar(); }
};
entries += search;
Gtk.ActionEntry conversation_list = { ACTION_CONVERSATION_LIST, null, null, "<Ctrl>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, { "<Ctrl>I", "<Shift>I" });
add_window_accelerators(ACTION_MARK_AS_UNREAD, { "<Ctrl>U", "<Shift>U" });
add_window_accelerators(ACTION_MARK_AS_STARRED, { "S" });
add_window_accelerators(ACTION_MARK_AS_UNSTARRED, { "D" });
add_window_accelerators(ACTION_MARK_AS_SPAM, { "<Ctrl>J", "exclam" }); // Exclamation mark (!)
add_window_accelerators(ACTION_MARK_AS_NOT_SPAM, { "<Ctrl>J", "exclam" });
add_window_accelerators(ACTION_COPY_MENU, { "L" });
add_window_accelerators(ACTION_MOVE_MENU, { "M" });
add_window_accelerators(ACTION_NEW_MESSAGE, { "<Ctrl>N", "N" });
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" });
add_window_accelerators(ACTION_FIND_IN_CONVERSATION, { "<Ctrl>F", "slash" });
add_window_accelerators(ACTION_ARCHIVE_CONVERSATION, { "A" });
add_window_accelerators(ACTION_TRASH_CONVERSATION, { "Delete", "BackSpace" });
add_window_accelerators(ACTION_DELETE_CONVERSATION, { "<Shift>Delete", "<Shift>BackSpace" });
add_window_accelerators(ACTION_UNDO, { "<Ctrl>Z" });
add_window_accelerators(ACTION_ZOOM+("('in')"), { "<Ctrl>equal", "equal" });
add_window_accelerators(ACTION_ZOOM+("('out')"), { "<Ctrl>minus", "minus" });
add_window_accelerators(ACTION_ZOOM+("('normal')"), { "<Ctrl>0", "0" });
add_window_accelerators(ACTION_SEARCH, { "<Ctrl>S" });
add_window_accelerators(ACTION_CONVERSATION_LIST, { "<Ctrl>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<Geary.App.Conversation> 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<Geary.App.Conversation> 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<Geary.EmailIdentifier> 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<Geary.EmailIdentifier> 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<Geary.EmailIdentifier> 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<Geary.EmailIdentifier, Type>? 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.

View file

@ -79,6 +79,8 @@ public class FolderPopover : Gtk.Popover {
label.set_halign(Gtk.Align.START);
row.add(label);
row.show_all();
return row;
}

View file

@ -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);
}
}
}

View file

@ -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",

View file

@ -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<string, string>? 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);

View file

@ -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<Geary.App.Conversation>? current_visible_conversations = null;
private Geary.Scheduler.Scheduled? scheduled_update_visible_conversations = null;
private Gtk.Menu? context_menu = null;
private Gee.Set<Geary.App.Conversation> selected = new Gee.HashSet<Geary.App.Conversation>();
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<Geary.App.Conversation> 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;
}

View file

@ -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) {

View file

@ -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"
)

View file

@ -1,17 +0,0 @@
<ui>
<accelerator action="GearyArchiveConversation" />
<accelerator action="GearyConversationList" />
<accelerator action="GearyCopyMenuButton" />
<accelerator action="GearyDeleteConversation" />
<accelerator action="GearyFindInConversation" />
<accelerator action="GearyForwardMessage" />
<accelerator action="GearyMoveMenuButton" />
<accelerator action="GearyNewMessage" />
<accelerator action="GearyReplyAllMessage" />
<accelerator action="GearyReplyToMessage" />
<accelerator action="GearySearch" />
<accelerator action="GearyTrashConversation" />
<accelerator action="GearyZoomIn" />
<accelerator action="GearyZoomNormal" />
<accelerator action="GearyZoomOut" />
</ui>

40
ui/main-toolbar-menus.ui Normal file
View file

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<menu id="empty_menu">
<item>
<attribute name="label" translatable="yes">Empty _Spam…</attribute>
<attribute name="action">win.empty-spam</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Empty _Trash…</attribute>
<attribute name="action">win.empty-trash</attribute>
</item>
</menu>
<menu id="mark_message_menu">
<item>
<attribute name="label" translatable="yes">Mark as _Read</attribute>
<attribute name="action">win.mark-message-read</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Mark as _Unread</attribute>
<attribute name="action">win.mark-message-unread</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Star</attribute>
<attribute name="action">win.mark-message-starred</attribute>
</item>
<item>
<attribute name="label" translatable="yes">U_nstar</attribute>
<attribute name="action">win.mark-message-unstarred</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Mark as S_pam</attribute>
<attribute name="action">win.mark-message-spam</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Mark as not S_pam</attribute>
<attribute name="action">win.mark-message-not-spam</attribute>
</item>
</menu>
</interface>

View file

@ -20,6 +20,7 @@
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="always_show_image">True</property>
<property name="action_name">win.new-message</property>
<child>
<object class="GtkImage" id="compose_new_message_image">
<property name="icon_name">text-editor-symbolic</property>
@ -43,6 +44,7 @@
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="always_show_image">True</property>
<property name="tooltip_text" translatable="yes">Toggle search bar</property>
<child>
<object class="GtkImage" id="search_conversations_image">
<property name="icon_name">preferences-system-search-symbolic</property>
@ -94,6 +96,8 @@
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="always_show_image">True</property>
<property name="tooltip_text" translatable="yes">Reply</property>
<property name="action_name">win.reply-to-message</property>
<child>
<object class="GtkImage" id="reply_sender_image">
<property name="icon_name">mail-reply-sender-symbolic</property>
@ -107,6 +111,8 @@
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="always_show_image">True</property>
<property name="tooltip_text" translatable="yes">Reply All</property>
<property name="action_name">win.reply-all-message</property>
<child>
<object class="GtkImage" id="reply_all_image">
<property name="icon_name">mail-reply-all-symbolic</property>
@ -120,6 +126,8 @@
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="always_show_image">True</property>
<property name="tooltip_text" translatable="yes">Forward</property>
<property name="action_name">win.forward-message</property>
<child>
<object class="GtkImage" id="forward_image">
<property name="icon_name">mail-forward-symbolic</property>
@ -143,6 +151,7 @@
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="always_show_image">True</property>
<property name="action_name">win.mark-message-menu</property>
<child>
<object class="GtkImage" id="mark_message_image">
<property name="icon_name">marker-symbolic</property>
@ -151,7 +160,7 @@
</object>
</child>
<child>
<object class="GtkMenuButton" id="copy_message_button">
<object class="GtkMenuButton" id="move_message_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
@ -164,7 +173,7 @@
</object>
</child>
<child>
<object class="GtkMenuButton" id="move_message_button">
<object class="GtkMenuButton" id="copy_message_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
@ -184,6 +193,7 @@
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="always_show_image">True</property>
<property name="tooltip_text" translatable="yes">Toggle find bar</property>
<child>
<object class="GtkImage" id="find_image">
<property name="icon_name">preferences-system-search-symbolic</property>
@ -200,9 +210,10 @@
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="always_show_image">True</property>
<property name="action_name">win.undo</property>
<child>
<object class="GtkImage" id="undo_image">
<property name="icon_name">preferences-system-search-symbolic</property>
<property name="icon_name">edit-undo-symbolic</property>
</object>
</child>
</object>
@ -224,6 +235,12 @@
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="always_show_image">True</property>
<property name="action_name">win.archive-conv</property>
<child>
<object class="GtkImage" id="archive_image">
<property name="icon_name">mail-archive-symbolic</property>
</object>
</child>
</object>
</child>
<child>
@ -232,6 +249,12 @@
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="always_show_image">True</property>
<property name="action_name">win.trash-conv</property>
<child>
<object class="GtkImage" id="trash_delete_image">
<property name="icon_name">user-trash-symbolic</property>
</object>
</child>
</object>
</child>
</object>

View file

@ -1,7 +0,0 @@
<ui>
<popup name="ToolbarEmptyMenu">
<menuitem name="EmptySpam" action="GearyEmptySpam" />
<menuitem name="EmptyTrash" action="GearyEmptyTrash" />
</popup>
</ui>

View file

@ -1,10 +0,0 @@
<ui>
<popup name="ToolbarMarkMenu">
<menuitem name="MarkAsRead" action="GearyMarkAsRead" />
<menuitem name="MarkAsUnread" action="GearyMarkAsUnread" />
<menuitem name="MarkAsStarred" action="GearyMarkAsStarred" />
<menuitem name="MarkAsUnStarred" action="GearyMarkAsUnStarred" />
<menuitem name="MarkAsSpam" action="GearyMarkAsSpam" />
</popup>
</ui>