Closes #3773. Moving and copying emails between folders is now supported.
This commit is contained in:
parent
026f048672
commit
cb2f78748b
18 changed files with 512 additions and 135 deletions
|
|
@ -1,5 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="16" height="16">
|
||||
<path fill="#797979" d="M2.5,6l5,5l5,-5z"/>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="8" height="16">
|
||||
<path d="M 0.625,4.8813569 4.0317797,13 7.438559,4.8813569 z" style="fill:#999999" />
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 288 B After Width: | Height: | Size: 229 B |
|
|
@ -171,6 +171,7 @@ client/ui/geary-login.vala
|
|||
client/ui/email-entry.vala
|
||||
client/ui/icon-factory.vala
|
||||
client/ui/folder-list.vala
|
||||
client/ui/folder-menu.vala
|
||||
client/ui/main-toolbar.vala
|
||||
client/ui/main-window.vala
|
||||
client/ui/message-list-cell-renderer.vala
|
||||
|
|
|
|||
|
|
@ -52,7 +52,9 @@ public class GearyController {
|
|||
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_COPY_MENU = "GearyCopyMenuButton";
|
||||
public const string ACTION_MOVE_MENU = "GearyMoveMenuButton";
|
||||
|
||||
private const int FETCH_EMAIL_CHUNK_COUNT = 50;
|
||||
|
||||
public MainWindow main_window { get; private set; }
|
||||
|
|
@ -82,17 +84,22 @@ public class GearyController {
|
|||
main_window = new MainWindow();
|
||||
|
||||
enable_message_buttons(false);
|
||||
|
||||
|
||||
// Connect to various UI signals.
|
||||
main_window.message_list_view.conversations_selected.connect(on_conversations_selected);
|
||||
main_window.message_list_view.load_more.connect(on_load_more);
|
||||
main_window.message_list_view.mark_conversation.connect(on_mark_conversation);
|
||||
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.message_viewer.link_selected.connect(on_link_selected);
|
||||
main_window.message_viewer.reply_to_message.connect(on_reply_to_message);
|
||||
main_window.message_viewer.reply_all_message.connect(on_reply_all_message);
|
||||
main_window.message_viewer.forward_message.connect(on_forward_message);
|
||||
main_window.message_viewer.mark_message.connect(on_message_viewer_mark_message);
|
||||
|
||||
|
||||
main_window.message_list_view.grab_focus();
|
||||
|
||||
set_busy(false);
|
||||
|
|
@ -141,8 +148,8 @@ public class GearyController {
|
|||
quit.label = _("_Quit");
|
||||
entries += quit;
|
||||
|
||||
Gtk.ActionEntry mark_menu = { ACTION_MARK_AS_MENU, null, TRANSLATABLE, null,
|
||||
null, on_show_mark_menu };
|
||||
Gtk.ActionEntry mark_menu = { ACTION_MARK_AS_MENU, null, TRANSLATABLE, null, null,
|
||||
on_show_mark_menu };
|
||||
mark_menu.label = _("_Mark as...");
|
||||
entries += mark_menu;
|
||||
|
||||
|
|
@ -166,6 +173,16 @@ public class GearyController {
|
|||
mark_unstarred.label = _("U_nstar");
|
||||
entries += mark_unstarred;
|
||||
|
||||
Gtk.ActionEntry copy_menu = { ACTION_COPY_MENU, null, TRANSLATABLE, "L", null,
|
||||
on_show_copy_menu };
|
||||
copy_menu.label = _("_Label");
|
||||
entries += copy_menu;
|
||||
|
||||
Gtk.ActionEntry move_menu = { ACTION_MOVE_MENU, null, TRANSLATABLE, "M", null,
|
||||
on_show_move_menu };
|
||||
move_menu.label = _("_Move");
|
||||
entries += move_menu;
|
||||
|
||||
Gtk.ActionEntry new_message = { ACTION_NEW_MESSAGE, null, TRANSLATABLE, "<Ctrl>N", null,
|
||||
on_new_message };
|
||||
new_message.label = _("_New Message");
|
||||
|
|
@ -595,8 +612,11 @@ public class GearyController {
|
|||
foreach (Geary.Folder folder in added) {
|
||||
if (ignored_paths != null && ignored_paths.contains(folder.get_path()))
|
||||
skipped.add(folder);
|
||||
else
|
||||
else {
|
||||
main_window.folder_list.add_folder(folder);
|
||||
main_window.main_toolbar.copy_folder_menu.add_folder(folder);
|
||||
main_window.main_toolbar.move_folder_menu.add_folder(folder);
|
||||
}
|
||||
}
|
||||
|
||||
Gee.Collection<Geary.Folder> remaining = added;
|
||||
|
|
@ -821,7 +841,51 @@ public class GearyController {
|
|||
private void on_mark_complete() {
|
||||
set_busy(false);
|
||||
}
|
||||
|
||||
|
||||
private void on_show_copy_menu() {
|
||||
main_window.main_toolbar.copy_folder_menu.show();
|
||||
}
|
||||
|
||||
private void on_show_move_menu() {
|
||||
main_window.main_toolbar.move_folder_menu.show();
|
||||
}
|
||||
|
||||
private void on_copy_conversation(Geary.Folder destination) {
|
||||
// Nothing to do if nothing selected.
|
||||
if (selected_conversations == null || selected_conversations.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Gee.List<Geary.EmailIdentifier> ids = get_selected_ids();
|
||||
if (ids.size > 0) {
|
||||
set_busy(true);
|
||||
current_folder.copy_email_async.begin(ids, destination.get_path(), cancellable_message,
|
||||
on_copy_complete);
|
||||
}
|
||||
}
|
||||
|
||||
private void on_copy_complete() {
|
||||
set_busy(false);
|
||||
}
|
||||
|
||||
private void on_move_conversation(Geary.Folder destination) {
|
||||
// Nothing to do if nothing selected.
|
||||
if (selected_conversations == null || selected_conversations.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Gee.List<Geary.EmailIdentifier> ids = get_selected_ids();
|
||||
if (ids.size > 0) {
|
||||
set_busy(true);
|
||||
current_folder.move_email_async.begin(ids, destination.get_path(), cancellable_message,
|
||||
on_move_complete);
|
||||
}
|
||||
}
|
||||
|
||||
private void on_move_complete() {
|
||||
set_busy(false);
|
||||
}
|
||||
|
||||
// Opens a link in an external browser.
|
||||
private void open_uri(string _link) {
|
||||
string link = _link;
|
||||
|
|
@ -965,7 +1029,7 @@ public class GearyController {
|
|||
open_uri(link);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Disables all single-message buttons and enables all multi-message buttons.
|
||||
public void enable_multiple_message_buttons(){
|
||||
// Single message only buttons.
|
||||
|
|
@ -976,8 +1040,10 @@ public class GearyController {
|
|||
// Mutliple message buttons.
|
||||
GearyApplication.instance.actions.get_action(ACTION_DELETE_MESSAGE).sensitive = true;
|
||||
GearyApplication.instance.actions.get_action(ACTION_MARK_AS_MENU).sensitive = true;
|
||||
GearyApplication.instance.actions.get_action(ACTION_COPY_MENU).sensitive = true;
|
||||
GearyApplication.instance.actions.get_action(ACTION_MOVE_MENU).sensitive = true;
|
||||
}
|
||||
|
||||
|
||||
// Enables or disables the message buttons on the toolbar.
|
||||
public void enable_message_buttons(bool sensitive) {
|
||||
GearyApplication.instance.actions.get_action(ACTION_REPLY_TO_MESSAGE).sensitive = sensitive;
|
||||
|
|
@ -985,6 +1051,8 @@ public class GearyController {
|
|||
GearyApplication.instance.actions.get_action(ACTION_FORWARD_MESSAGE).sensitive = sensitive;
|
||||
GearyApplication.instance.actions.get_action(ACTION_DELETE_MESSAGE).sensitive = sensitive;
|
||||
GearyApplication.instance.actions.get_action(ACTION_MARK_AS_MENU).sensitive = sensitive;
|
||||
GearyApplication.instance.actions.get_action(ACTION_COPY_MENU).sensitive = sensitive;
|
||||
GearyApplication.instance.actions.get_action(ACTION_MOVE_MENU).sensitive = sensitive;
|
||||
}
|
||||
|
||||
public void compose_mailto(string mailto) {
|
||||
|
|
|
|||
|
|
@ -5,31 +5,70 @@
|
|||
*/
|
||||
|
||||
public class FolderList : Sidebar.Tree {
|
||||
|
||||
|
||||
public const Gtk.TargetEntry[] TARGET_ENTRY_LIST = {
|
||||
{ "application/x-geary-mail", Gtk.TargetFlags.SAME_APP, 0 }
|
||||
};
|
||||
|
||||
private class SpecialFolderBranch : Sidebar.RootOnlyBranch {
|
||||
public SpecialFolderBranch(Geary.SpecialFolder special, Geary.Folder folder) {
|
||||
base(new SpecialFolderEntry(special, folder));
|
||||
}
|
||||
}
|
||||
|
||||
private class SpecialFolderEntry : Object, Sidebar.Entry, Sidebar.SelectableEntry {
|
||||
public Geary.SpecialFolder special { get; private set; }
|
||||
private class FolderEntry : Object, Sidebar.Entry, Sidebar.InternalDropTargetEntry,
|
||||
Sidebar.SelectableEntry {
|
||||
public Geary.Folder folder { get; private set; }
|
||||
|
||||
public SpecialFolderEntry(Geary.SpecialFolder special, Geary.Folder folder) {
|
||||
this.special = special;
|
||||
public FolderEntry(Geary.Folder folder) {
|
||||
this.folder = folder;
|
||||
}
|
||||
|
||||
public string get_sidebar_name() {
|
||||
return special.name;
|
||||
public virtual string get_sidebar_name() {
|
||||
return folder.get_path().basename;
|
||||
}
|
||||
|
||||
public string? get_sidebar_tooltip() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Icon? get_sidebar_icon() {
|
||||
public virtual Icon? get_sidebar_icon() {
|
||||
return IconFactory.instance.label_icon;
|
||||
}
|
||||
|
||||
public virtual string to_string() {
|
||||
return "FolderEntry: " + get_sidebar_name();
|
||||
}
|
||||
|
||||
public bool internal_drop_received(Gdk.DragContext context, Gtk.SelectionData data) {
|
||||
// Copy or move?
|
||||
Gdk.ModifierType mask;
|
||||
double[] axes = new double[2];
|
||||
context.get_device().get_state(context.get_dest_window(), axes, out mask);
|
||||
MainWindow main_window = GearyApplication.instance.get_main_window() as MainWindow;
|
||||
if ((mask & Gdk.ModifierType.CONTROL_MASK) != 0) {
|
||||
main_window.folder_list.copy_conversation(folder);
|
||||
} else {
|
||||
main_window.folder_list.move_conversation(folder);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class SpecialFolderEntry : FolderEntry {
|
||||
public Geary.SpecialFolder special { get; private set; }
|
||||
|
||||
public SpecialFolderEntry(Geary.SpecialFolder special, Geary.Folder folder) {
|
||||
base (folder);
|
||||
this.special = special;
|
||||
}
|
||||
|
||||
public override string get_sidebar_name() {
|
||||
return special.name;
|
||||
}
|
||||
|
||||
public override Icon? get_sidebar_icon() {
|
||||
switch (special.folder_type) {
|
||||
case Geary.SpecialFolderType.INBOX:
|
||||
return new ThemedIcon("mail-inbox");
|
||||
|
|
@ -57,36 +96,14 @@ public class FolderList : Sidebar.Tree {
|
|||
}
|
||||
}
|
||||
|
||||
public string to_string() {
|
||||
public override string to_string() {
|
||||
return "SpecialFolderEntry: " + get_sidebar_name();
|
||||
}
|
||||
}
|
||||
|
||||
private class FolderEntry : Object, Sidebar.Entry, Sidebar.SelectableEntry {
|
||||
public Geary.Folder folder { get; private set; }
|
||||
|
||||
public FolderEntry(Geary.Folder folder) {
|
||||
this.folder = folder;
|
||||
}
|
||||
|
||||
public string get_sidebar_name() {
|
||||
return folder.get_path().basename;
|
||||
}
|
||||
|
||||
public string? get_sidebar_tooltip() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Icon? get_sidebar_icon() {
|
||||
return IconFactory.instance.label_icon;
|
||||
}
|
||||
|
||||
public string to_string() {
|
||||
return "FolderEntry: " + get_sidebar_name();
|
||||
}
|
||||
}
|
||||
|
||||
public signal void folder_selected(Geary.Folder? folder);
|
||||
public signal void copy_conversation(Geary.Folder folder);
|
||||
public signal void move_conversation(Geary.Folder folder);
|
||||
|
||||
private Sidebar.Grouping user_folder_group;
|
||||
private Sidebar.Branch user_folder_branch;
|
||||
|
|
@ -96,13 +113,17 @@ public class FolderList : Sidebar.Tree {
|
|||
public FolderList() {
|
||||
base(new Gtk.TargetEntry[0], Gdk.DragAction.ASK, drop_handler);
|
||||
entry_selected.connect(on_entry_selected);
|
||||
|
||||
|
||||
user_folder_group = new Sidebar.Grouping("", IconFactory.instance.label_folder_icon);
|
||||
user_folder_branch = new Sidebar.Branch(user_folder_group,
|
||||
Sidebar.Branch.Options.STARTUP_OPEN_GROUPING, user_folder_comparator);
|
||||
graft(user_folder_branch, int.MAX);
|
||||
|
||||
// Set self as a drag destination.
|
||||
Gtk.drag_dest_set(this, Gtk.DestDefaults.MOTION | Gtk.DestDefaults.HIGHLIGHT,
|
||||
TARGET_ENTRY_LIST, Gdk.DragAction.COPY | Gdk.DragAction.MOVE);
|
||||
}
|
||||
|
||||
|
||||
private static int user_folder_comparator(Sidebar.Entry a, Sidebar.Entry b) {
|
||||
int result = a.get_sidebar_name().collate(b.get_sidebar_name());
|
||||
|
||||
|
|
@ -120,7 +141,7 @@ public class FolderList : Sidebar.Tree {
|
|||
folder_selected(((FolderEntry) selectable).folder);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void set_user_folders_root_name(string name) {
|
||||
user_folder_group.rename(name);
|
||||
}
|
||||
|
|
@ -162,4 +183,20 @@ public class FolderList : Sidebar.Tree {
|
|||
private Sidebar.Entry? get_entry_for_folder_path(Geary.FolderPath path) {
|
||||
return entries.get(path);
|
||||
}
|
||||
|
||||
public override bool drag_motion(Gdk.DragContext context, int x, int y, uint time) {
|
||||
// Run the base version first.
|
||||
bool ret = base.drag_motion(context, x, y, time);
|
||||
|
||||
// Update the cursor for copy or move.
|
||||
Gdk.ModifierType mask;
|
||||
double[] axes = new double[2];
|
||||
context.get_device().get_state(context.get_dest_window(), axes, out mask);
|
||||
if ((mask & Gdk.ModifierType.CONTROL_MASK) != 0) {
|
||||
Gdk.drag_status(context, Gdk.DragAction.COPY, time);
|
||||
} else {
|
||||
Gdk.drag_status(context, Gdk.DragAction.MOVE, time);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
78
src/client/ui/folder-menu.vala
Normal file
78
src/client/ui/folder-menu.vala
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/* Copyright 2011-2012 Yorba Foundation
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
public class FolderMenu {
|
||||
private Gtk.Menu? menu = null;
|
||||
private Gtk.ToggleToolButton button;
|
||||
private Gee.List<Geary.Folder> folder_list = new Gee.ArrayList<Geary.Folder>();
|
||||
|
||||
public signal void folder_selected(Geary.Folder folder);
|
||||
|
||||
public FolderMenu(Gtk.ToggleToolButton button) {
|
||||
this.button = button;
|
||||
}
|
||||
|
||||
public void add_folder(Geary.Folder folder) {
|
||||
folder_list.add(folder);
|
||||
folder_list.sort((CompareFunc) folder_sort);
|
||||
menu = null;
|
||||
}
|
||||
|
||||
public void remove_folder(Geary.Folder folder) {
|
||||
int index = folder_list.index_of(folder);
|
||||
if (index >= 0) {
|
||||
folder_list.remove_at(index);
|
||||
}
|
||||
}
|
||||
|
||||
public void show() {
|
||||
// Prevent activation loops.
|
||||
if (!button.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the menu is currently null, build it.
|
||||
if (menu == null) {
|
||||
build_menu();
|
||||
}
|
||||
|
||||
// Show the menu.
|
||||
menu.popup(null, null, menu_popup_relative, 0, 0);
|
||||
menu.select_first(true);
|
||||
}
|
||||
|
||||
private void build_menu() {
|
||||
// TODO Add fancy filter option.
|
||||
// TODO Make the menu items checkboxes instead of buttons.
|
||||
// TODO Merge the move/copy menus and just have a move/copy buttons at bottom of this menu.
|
||||
menu = new Gtk.Menu();
|
||||
foreach (Geary.Folder folder in folder_list) {
|
||||
Gtk.MenuItem menu_item = new Gtk.MenuItem.with_label(folder.get_path().to_string());
|
||||
menu_item.activate.connect(() => {
|
||||
on_menu_item_activated(folder);
|
||||
});
|
||||
menu.append(menu_item);
|
||||
}
|
||||
|
||||
// Finish setting up the menu.
|
||||
menu.attach_to_widget(button, null);
|
||||
menu.deactivate.connect(on_menu_deactivate);
|
||||
menu.show_all();
|
||||
}
|
||||
|
||||
private void on_menu_deactivate() {
|
||||
button.active = false;
|
||||
}
|
||||
|
||||
private void on_menu_item_activated(Geary.Folder folder) {
|
||||
folder_selected(folder);
|
||||
}
|
||||
|
||||
private static int folder_sort(Geary.Folder a, Geary.Folder b) {
|
||||
return a.get_path().to_string().casefold().collate(b.get_path().to_string().casefold());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -7,95 +7,72 @@
|
|||
// Draws the main toolbar.
|
||||
public class MainToolbar : Gtk.Box {
|
||||
private Gtk.Toolbar toolbar;
|
||||
private Gtk.Menu menu;
|
||||
private Gtk.Menu mark_menu;
|
||||
private Gtk.ToggleToolButton menu_button;
|
||||
private Gtk.ToggleToolButton mark_menu_button;
|
||||
|
||||
public FolderMenu copy_folder_menu { get; private set; }
|
||||
public FolderMenu move_folder_menu { get; private set; }
|
||||
|
||||
public MainToolbar() {
|
||||
Object(orientation: Gtk.Orientation.VERTICAL, spacing: 0);
|
||||
|
||||
GearyApplication.instance.load_ui_file("toolbar_mark_menu.ui");
|
||||
mark_menu = GearyApplication.instance.ui_manager.get_widget("/ui/ToolbarMarkMenu") as Gtk.Menu;
|
||||
|
||||
GearyApplication.instance.load_ui_file("toolbar_menu.ui");
|
||||
menu = GearyApplication.instance.ui_manager.get_widget("/ui/ToolbarMenu") as Gtk.Menu;
|
||||
|
||||
|
||||
Gtk.Builder builder = GearyApplication.instance.create_builder("toolbar.glade");
|
||||
toolbar = builder.get_object("toolbar") as Gtk.Toolbar;
|
||||
|
||||
Gtk.ToolButton new_message = builder.get_object(GearyController.ACTION_NEW_MESSAGE)
|
||||
as Gtk.ToolButton;
|
||||
new_message.set_related_action(GearyApplication.instance.actions.get_action(
|
||||
GearyController.ACTION_NEW_MESSAGE));
|
||||
|
||||
Gtk.ToolButton reply_to_message = builder.get_object(GearyController.ACTION_REPLY_TO_MESSAGE)
|
||||
as Gtk.ToolButton;
|
||||
reply_to_message.set_related_action(GearyApplication.instance.actions.get_action(
|
||||
GearyController.ACTION_REPLY_TO_MESSAGE));
|
||||
|
||||
Gtk.ToolButton reply_all_message = builder.get_object(GearyController.ACTION_REPLY_ALL_MESSAGE)
|
||||
as Gtk.ToolButton;
|
||||
reply_all_message.set_related_action(GearyApplication.instance.actions.get_action(
|
||||
GearyController.ACTION_REPLY_ALL_MESSAGE));
|
||||
|
||||
Gtk.ToolButton forward_message = builder.get_object(GearyController.ACTION_FORWARD_MESSAGE)
|
||||
as Gtk.ToolButton;
|
||||
forward_message.set_related_action(GearyApplication.instance.actions.get_action(
|
||||
GearyController.ACTION_FORWARD_MESSAGE));
|
||||
|
||||
Gtk.ToolButton archive_message = builder.get_object(GearyController.ACTION_DELETE_MESSAGE)
|
||||
as Gtk.ToolButton;
|
||||
archive_message.set_related_action(GearyApplication.instance.actions.get_action(
|
||||
GearyController.ACTION_DELETE_MESSAGE));
|
||||
|
||||
mark_menu_button = builder.get_object(GearyController.ACTION_MARK_AS_MENU) as Gtk.ToggleToolButton;
|
||||
mark_menu_button.set_related_action(GearyApplication.instance.actions.get_action(
|
||||
GearyController.ACTION_MARK_AS_MENU));
|
||||
mark_menu.attach_to_widget(mark_menu_button, null);
|
||||
mark_menu.deactivate.connect(on_deactivate_mark_menu);
|
||||
mark_menu_button.clicked.connect(on_show_mark_menu);
|
||||
// Setup each of the normal toolbar buttons.
|
||||
set_toolbutton_action(builder, GearyController.ACTION_NEW_MESSAGE);
|
||||
set_toolbutton_action(builder, GearyController.ACTION_REPLY_TO_MESSAGE);
|
||||
set_toolbutton_action(builder, GearyController.ACTION_REPLY_ALL_MESSAGE);
|
||||
set_toolbutton_action(builder, GearyController.ACTION_FORWARD_MESSAGE);
|
||||
set_toolbutton_action(builder, GearyController.ACTION_DELETE_MESSAGE);
|
||||
|
||||
Gtk.ToggleButton button = mark_menu_button.get_child() as Gtk.ToggleButton;
|
||||
button.remove(button.get_child());
|
||||
Gtk.Box box = new Gtk.HBox(false, 0);
|
||||
button.add(box);
|
||||
box.pack_start(new Gtk.Label(_("Mark")));
|
||||
box.pack_start(new Gtk.Image.from_icon_name("menu-down", Gtk.IconSize.LARGE_TOOLBAR));
|
||||
// Setup the folder menus (move/copy).
|
||||
Gtk.ToggleToolButton copy_menu_button = set_toolbutton_action(builder,
|
||||
GearyController.ACTION_COPY_MENU) as Gtk.ToggleToolButton;
|
||||
make_menu_dropdown_button(copy_menu_button, _("Label as"));
|
||||
copy_folder_menu = new FolderMenu(copy_menu_button);
|
||||
|
||||
Gtk.ToggleToolButton move_menu_button = set_toolbutton_action(builder,
|
||||
GearyController.ACTION_MOVE_MENU) as Gtk.ToggleToolButton;
|
||||
make_menu_dropdown_button(move_menu_button, _("Move to"));
|
||||
move_folder_menu = new FolderMenu(move_menu_button);
|
||||
|
||||
// Assemble mark menu button.
|
||||
GearyApplication.instance.load_ui_file("toolbar_mark_menu.ui");
|
||||
Gtk.Menu mark_menu = GearyApplication.instance.ui_manager.get_widget("/ui/ToolbarMarkMenu")
|
||||
as Gtk.Menu;
|
||||
Gtk.ToggleToolButton mark_menu_button = set_toolbutton_action(builder,
|
||||
GearyController.ACTION_MARK_AS_MENU) as Gtk.ToggleToolButton;
|
||||
attach_menu(mark_menu, mark_menu_button);
|
||||
make_menu_dropdown_button(mark_menu_button, _("Mark"));
|
||||
|
||||
// Setup the application menu.
|
||||
GearyApplication.instance.load_ui_file("toolbar_menu.ui");
|
||||
Gtk.Menu menu = GearyApplication.instance.ui_manager.get_widget("/ui/ToolbarMenu") as Gtk.Menu;
|
||||
attach_menu(menu, builder.get_object("menu_button") as Gtk.ToggleToolButton);
|
||||
|
||||
menu_button = builder.get_object("menu_button") as Gtk.ToggleToolButton;
|
||||
menu.attach_to_widget(menu_button, null);
|
||||
menu.deactivate.connect(on_deactivate_menu);
|
||||
menu_button.clicked.connect(on_show_menu);
|
||||
|
||||
toolbar.get_style_context().add_class("primary-toolbar");
|
||||
|
||||
add(toolbar);
|
||||
}
|
||||
|
||||
private void on_show_menu() {
|
||||
// Prevent loop
|
||||
if (!menu_button.active)
|
||||
return;
|
||||
|
||||
menu.popup(null, null, menu_popup_relative, 0, 0);
|
||||
menu.select_first(true);
|
||||
|
||||
private void attach_menu(Gtk.Menu menu, Gtk.ToggleToolButton button) {
|
||||
menu.attach_to_widget(button, null);
|
||||
menu.deactivate.connect(() => {
|
||||
button.active = false;
|
||||
});
|
||||
button.clicked.connect(() => {
|
||||
// Prevent loops.
|
||||
if (!button.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
menu.popup(null, null, menu_popup_relative, 0, 0);
|
||||
menu.select_first(true);
|
||||
});
|
||||
}
|
||||
|
||||
private void on_deactivate_menu() {
|
||||
menu_button.active = false;
|
||||
}
|
||||
|
||||
private void on_show_mark_menu() {
|
||||
// Prevent loop
|
||||
if (!mark_menu_button.active)
|
||||
return;
|
||||
|
||||
mark_menu.popup(null, null, menu_popup_relative, 0, 0);
|
||||
mark_menu.select_first(true);
|
||||
}
|
||||
|
||||
private void on_deactivate_mark_menu() {
|
||||
mark_menu_button.active = false;
|
||||
private Gtk.ToolButton set_toolbutton_action(Gtk.Builder builder, string action) {
|
||||
Gtk.ToolButton button = builder.get_object(action) as Gtk.ToolButton;
|
||||
button.set_related_action(GearyApplication.instance.actions.get_action(action));
|
||||
return button;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ public class MessageListView : Gtk.TreeView {
|
|||
|
||||
store.row_deleted.connect(on_row_deleted);
|
||||
button_press_event.connect(on_button_press);
|
||||
|
||||
// Set up drag and drop.
|
||||
Gtk.drag_source_set(this, Gdk.ModifierType.BUTTON1_MASK, FolderList.TARGET_ENTRY_LIST,
|
||||
Gdk.DragAction.COPY | Gdk.DragAction.MOVE);
|
||||
}
|
||||
|
||||
private bool on_button_press(Gdk.EventButton event) {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public interface Sidebar.DestroyableEntry : Sidebar.Entry {
|
|||
|
||||
public interface Sidebar.InternalDropTargetEntry : Sidebar.Entry {
|
||||
// Returns true if drop was successful
|
||||
public abstract bool internal_drop_received(Gtk.SelectionData data);
|
||||
public abstract bool internal_drop_received(Gdk.DragContext context, Gtk.SelectionData data);
|
||||
}
|
||||
|
||||
public interface Sidebar.InternalDragSourceEntry : Sidebar.Entry {
|
||||
|
|
|
|||
|
|
@ -1009,7 +1009,7 @@ public class Sidebar.Tree : Gtk.TreeView {
|
|||
return;
|
||||
}
|
||||
|
||||
bool success = targetable.internal_drop_received(selection_data);
|
||||
bool success = targetable.internal_drop_received(context, selection_data);
|
||||
|
||||
Gtk.drag_finish(context, success, false, time);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,3 +24,13 @@ public void menu_popup_relative(Gtk.Menu menu, out int x, out int y, out bool pu
|
|||
push_in = false;
|
||||
}
|
||||
|
||||
public void make_menu_dropdown_button(Gtk.ToggleToolButton toolbutton, string label) {
|
||||
Gtk.ToggleButton button = toolbutton.get_child() as Gtk.ToggleButton;
|
||||
button.remove(button.get_child());
|
||||
Gtk.Box box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
|
||||
box.set_homogeneous(false);
|
||||
button.add(box);
|
||||
box.pack_start(new Gtk.Label(label));
|
||||
box.pack_start(new Gtk.Image.from_icon_name("menu-down", Gtk.IconSize.SMALL_TOOLBAR));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -418,7 +418,23 @@ public interface Geary.Folder : Object {
|
|||
public abstract async void mark_email_async(Gee.List<Geary.EmailIdentifier> to_mark,
|
||||
Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove,
|
||||
Cancellable? cancellable = null) throws Error;
|
||||
|
||||
|
||||
/**
|
||||
* Copies messages into another folder.
|
||||
*
|
||||
* The Folder must be opened prior to attempting this operation.
|
||||
*/
|
||||
public abstract async void copy_email_async(Gee.List<Geary.EmailIdentifier> to_copy,
|
||||
Geary.FolderPath destination, Cancellable? cancellable = null) throws Error;
|
||||
|
||||
/**
|
||||
* Moves messages to another folder.
|
||||
*
|
||||
* The Folder must be opened prior to attempting this operation.
|
||||
*/
|
||||
public abstract async void move_email_async(Gee.List<Geary.EmailIdentifier> to_move,
|
||||
Geary.FolderPath destination, Cancellable? cancellable = null) throws Error;
|
||||
|
||||
/**
|
||||
* check_span_specifiers() verifies that the span specifiers match the requirements set by
|
||||
* list_email_async() and lazy_list_email_async(). If not, this method throws
|
||||
|
|
|
|||
|
|
@ -146,7 +146,24 @@ private class Geary.Imap.Folder : Object {
|
|||
|
||||
yield mailbox.mark_email_async(msg_set, msg_flags_add, msg_flags_remove, cancellable);
|
||||
}
|
||||
|
||||
|
||||
public async void copy_email_async(MessageSet msg_set, Geary.FolderPath destination,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
if (mailbox == null)
|
||||
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());
|
||||
|
||||
yield mailbox.copy_email_async(msg_set, destination, cancellable);
|
||||
}
|
||||
|
||||
public async void move_email_async(MessageSet msg_set, Geary.FolderPath destination,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
if (mailbox == null)
|
||||
throw new EngineError.OPEN_REQUIRED("%s not opened", to_string());
|
||||
|
||||
yield copy_email_async(msg_set, destination, cancellable);
|
||||
yield remove_email_async(msg_set, cancellable);
|
||||
}
|
||||
|
||||
public string to_string() {
|
||||
return path.to_string();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,3 +150,15 @@ public class Geary.Imap.IdleCommand : Command {
|
|||
}
|
||||
}
|
||||
|
||||
public class Geary.Imap.CopyCommand : Command {
|
||||
public const string NAME = "copy";
|
||||
public const string UID_NAME = "uid copy";
|
||||
|
||||
public CopyCommand(MessageSet message_set, string destination) {
|
||||
base (message_set.is_uid ? UID_NAME : NAME);
|
||||
|
||||
add(message_set.to_parameter());
|
||||
add(new StringParameter(destination));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -586,7 +586,17 @@ public class Geary.Imap.Mailbox : Geary.SmartReference {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async void copy_email_async(MessageSet msg_set, Geary.FolderPath destination,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
|
||||
if (context.is_closed())
|
||||
throw new ImapError.NOT_SELECTED("Mailbox %s closed", name);
|
||||
|
||||
yield context.session.send_command_async(new CopyCommand(msg_set, destination.to_string()),
|
||||
cancellable);
|
||||
}
|
||||
|
||||
public async void expunge_email_async(MessageSet? msg_set, Cancellable? cancellable = null) throws Error {
|
||||
if (context.is_closed())
|
||||
throw new ImapError.NOT_SELECTED("Mailbox %s closed", name);
|
||||
|
|
|
|||
|
|
@ -96,7 +96,13 @@ public abstract class Geary.AbstractFolder : Object, Geary.Folder {
|
|||
public abstract async void mark_email_async(
|
||||
Gee.List<Geary.EmailIdentifier> to_mark, Geary.EmailFlags? flags_to_add,
|
||||
Geary.EmailFlags? flags_to_remove, Cancellable? cancellable = null) throws Error;
|
||||
|
||||
|
||||
public abstract async void copy_email_async(Gee.List<Geary.EmailIdentifier> to_copy,
|
||||
Geary.FolderPath destination, Cancellable? cancellable = null) throws Error;
|
||||
|
||||
public abstract async void move_email_async(Gee.List<Geary.EmailIdentifier> to_move,
|
||||
Geary.FolderPath destination, Cancellable? cancellable = null) throws Error;
|
||||
|
||||
public virtual string to_string() {
|
||||
return get_path().to_string();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -877,13 +877,32 @@ private class Geary.GenericImapFolder : Geary.AbstractFolder {
|
|||
public override async void mark_email_async(Gee.List<Geary.EmailIdentifier> to_mark,
|
||||
Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
check_open("mark_email_async");
|
||||
if (!yield wait_for_remote_to_open(cancellable))
|
||||
throw new EngineError.SERVER_UNAVAILABLE("No connection to %s", remote.to_string());
|
||||
|
||||
|
||||
replay_queue.schedule(new MarkEmail(this, to_mark, flags_to_add, flags_to_remove,
|
||||
cancellable));
|
||||
}
|
||||
|
||||
|
||||
public override async void copy_email_async(Gee.List<Geary.EmailIdentifier> to_copy,
|
||||
Geary.FolderPath destination, Cancellable? cancellable = null) throws Error {
|
||||
check_open("copy_email_async");
|
||||
if (!yield wait_for_remote_to_open(cancellable))
|
||||
throw new EngineError.SERVER_UNAVAILABLE("No connection to %s", remote.to_string());
|
||||
|
||||
replay_queue.schedule(new CopyEmail(this, to_copy, destination));
|
||||
}
|
||||
|
||||
public override async void move_email_async(Gee.List<Geary.EmailIdentifier> to_move,
|
||||
Geary.FolderPath destination, Cancellable? cancellable = null) throws Error {
|
||||
check_open("move_email_async");
|
||||
if (!yield wait_for_remote_to_open(cancellable))
|
||||
throw new EngineError.SERVER_UNAVAILABLE("No connection to %s", remote.to_string());
|
||||
|
||||
replay_queue.schedule(new MoveEmail(this, to_move, destination));
|
||||
}
|
||||
|
||||
private void on_email_flags_changed(Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags> changed) {
|
||||
notify_email_flags_changed(changed);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -617,3 +617,96 @@ private class Geary.FetchEmail : Geary.SendReplayOperation {
|
|||
}
|
||||
}
|
||||
|
||||
private class Geary.CopyEmail : Geary.SendReplayOperation {
|
||||
private GenericImapFolder engine;
|
||||
private Gee.List<Geary.EmailIdentifier> to_copy;
|
||||
private Geary.FolderPath destination;
|
||||
private Cancellable? cancellable;
|
||||
|
||||
public CopyEmail(GenericImapFolder engine, Gee.List<Geary.EmailIdentifier> to_copy,
|
||||
Geary.FolderPath destination, Cancellable? cancellable = null) {
|
||||
base("CopyEmail");
|
||||
|
||||
this.engine = engine;
|
||||
|
||||
this.to_copy = to_copy;
|
||||
this.destination = destination;
|
||||
this.cancellable = cancellable;
|
||||
}
|
||||
|
||||
public override async ReplayOperation.Status replay_local_async() throws Error {
|
||||
// The local DB will be updated when the remote folder is opened and we see a new message
|
||||
// existing there.
|
||||
return ReplayOperation.Status.CONTINUE;
|
||||
}
|
||||
|
||||
public override async ReplayOperation.Status replay_remote_async() throws Error {
|
||||
yield engine.remote_folder.copy_email_async(new Imap.MessageSet.email_id_collection(to_copy),
|
||||
destination, cancellable);
|
||||
|
||||
return ReplayOperation.Status.COMPLETED;
|
||||
}
|
||||
|
||||
public override async void backout_local_async() throws Error {
|
||||
// Nothing to undo.
|
||||
}
|
||||
|
||||
public override string describe_state() {
|
||||
return "%d email IDs to %s".printf(to_copy.size, destination.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
private class Geary.MoveEmail : Geary.SendReplayOperation {
|
||||
private GenericImapFolder engine;
|
||||
private Gee.List<Geary.EmailIdentifier> to_move;
|
||||
private Geary.FolderPath destination;
|
||||
private Cancellable? cancellable;
|
||||
private int original_count = 0;
|
||||
|
||||
public MoveEmail(GenericImapFolder engine, Gee.List<Geary.EmailIdentifier> to_move,
|
||||
Geary.FolderPath destination, Cancellable? cancellable = null) {
|
||||
base("MoveEmail");
|
||||
|
||||
this.engine = engine;
|
||||
|
||||
this.to_move = to_move;
|
||||
this.destination = destination;
|
||||
this.cancellable = cancellable;
|
||||
}
|
||||
|
||||
public override async ReplayOperation.Status replay_local_async() throws Error {
|
||||
// Remove the email from the folder.
|
||||
// TODO: Use a local_folder method that operates on all messages at once
|
||||
foreach (Geary.EmailIdentifier id in to_move)
|
||||
yield engine.local_folder.mark_removed_async(id, true, cancellable);
|
||||
engine.notify_email_removed(to_move);
|
||||
|
||||
original_count = engine.remote_count;
|
||||
engine.notify_email_count_changed(original_count - to_move.size,
|
||||
Geary.Folder.CountChangeReason.REMOVED);
|
||||
|
||||
return ReplayOperation.Status.CONTINUE;
|
||||
}
|
||||
|
||||
public override async ReplayOperation.Status replay_remote_async() throws Error {
|
||||
yield engine.remote_folder.move_email_async(new Imap.MessageSet.email_id_collection(to_move),
|
||||
destination, cancellable);
|
||||
|
||||
return ReplayOperation.Status.COMPLETED;
|
||||
}
|
||||
|
||||
public override async void backout_local_async() throws Error {
|
||||
// Add the email back in.
|
||||
// TODO: Use a local_folder method that operates on all messages at once
|
||||
foreach (Geary.EmailIdentifier id in to_move)
|
||||
yield engine.local_folder.mark_removed_async(id, false, cancellable);
|
||||
|
||||
engine.notify_email_appended(to_move);
|
||||
engine.notify_email_count_changed(original_count, Geary.Folder.CountChangeReason.ADDED);
|
||||
}
|
||||
|
||||
public override string describe_state() {
|
||||
return "%d email IDs to %s".printf(to_move.size, destination.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -94,6 +94,36 @@
|
|||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleToolButton" id="GearyMoveMenuButton">
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes" context="Toggles menu for applying labels to emails.">Move the selected conversation</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="use_underline">False</property>
|
||||
<property name="icon_name">folder</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleToolButton" id="GearyCopyMenuButton">
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes" context="Toggles menu for applying labels to emails.">Label the selected conversation</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="use_underline">False</property>
|
||||
<property name="icon_name">multiple-tags</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorToolItem" id="separator2">
|
||||
<property name="use_action_appearance">False</property>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue