/* Copyright 2011 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 MainWindow : Gtk.Window { private const int MESSAGE_LIST_WIDTH = 250; private const int FETCH_EMAIL_CHUNK_COUNT = 50; private MainToolbar main_toolbar; private MessageListStore message_list_store = new MessageListStore(); private MessageListView message_list_view; private FolderListStore folder_list_store = new FolderListStore(); private FolderListView folder_list_view; private MessageViewer message_viewer = new MessageViewer(); private Geary.EngineAccount? account = null; private Geary.Folder? current_folder = null; private Geary.Conversations? current_conversations = null; private bool second_list_pass_required = false; private int window_width; private int window_height; private bool window_maximized; private Gtk.HPaned folder_paned = new Gtk.HPaned(); private Gtk.HPaned messages_paned = new Gtk.HPaned(); private Cancellable cancellable_folder = new Cancellable(); private Cancellable cancellable_message = new Cancellable(); public MainWindow() { title = GearyApplication.NAME; message_list_view = new MessageListView(message_list_store); message_list_view.conversation_selected.connect(on_conversation_selected); message_list_view.load_more.connect(on_load_more); folder_list_view = new FolderListView(folder_list_store); folder_list_view.folder_selected.connect(on_folder_selected); add_accel_group(GearyApplication.instance.ui_manager.get_accel_group()); create_layout(); } ~MainWindow() { if (account != null) account.folders_added_removed.disconnect(on_folders_added_removed); } public void start(Geary.EngineAccount account) { this.account = account; account.folders_added_removed.connect(on_folders_added_removed); folder_list_store.set_user_folders_root_name(account.get_user_folders_label()); do_start.begin(); } private async void do_start() { try { // add all the special folders, which are assumed to always exist Geary.SpecialFolderMap? special_folders = account.get_special_folder_map(); if (special_folders != null) { foreach (Geary.SpecialFolder special_folder in special_folders.get_all()) { Geary.Folder folder = yield account.fetch_folder_async(special_folder.path); folder_list_store.add_special_folder(special_folder, folder); } // If inbox is specified, select that Geary.SpecialFolder? inbox = special_folders.get_folder(Geary.SpecialFolderType.INBOX); if (inbox != null) folder_list_view.select_path(inbox.path); } // pull down the root-level user folders Gee.Collection folders = yield account.list_folders_async(null); if (folders != null) on_folders_added_removed(folders, null); else debug("no folders"); } catch (Error err) { warning("%s", err.message); } } public override void show_all() { set_default_size(GearyApplication.instance.config.window_width, GearyApplication.instance.config.window_height); if (GearyApplication.instance.config.window_maximize) maximize(); folder_paned.set_position(GearyApplication.instance.config.folder_list_pane_position); messages_paned.set_position(GearyApplication.instance.config.messages_pane_position); base.show_all(); } public override void destroy() { // Save window dimensions. GearyApplication.instance.config.window_width = window_width; GearyApplication.instance.config.window_height = window_height; GearyApplication.instance.config.window_maximize = window_maximized; // Save pane positions. GearyApplication.instance.config.folder_list_pane_position = folder_paned.get_position(); GearyApplication.instance.config.messages_pane_position = messages_paned.get_position(); GearyApplication.instance.exit(); base.destroy(); } public override bool configure_event(Gdk.EventConfigure event) { // Get window dimensions. window_maximized = (get_window().get_state() == Gdk.WindowState.MAXIMIZED); if (!window_maximized) get_size(out window_width, out window_height); return base.configure_event(event); } private void create_layout() { Gtk.VBox main_layout = new Gtk.VBox(false, 0); // Toolbar. main_toolbar = new MainToolbar(); main_layout.pack_start(main_toolbar, false, false, 0); // folder list Gtk.ScrolledWindow folder_list_scrolled = new Gtk.ScrolledWindow(null, null); folder_list_scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); folder_list_scrolled.add(folder_list_view); // message list Gtk.ScrolledWindow message_list_scrolled = new Gtk.ScrolledWindow(null, null); message_list_scrolled.set_size_request(MESSAGE_LIST_WIDTH, -1); message_list_scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC); message_list_scrolled.add(message_list_view); // message viewer Gtk.ScrolledWindow message_viewer_scrolled = new Gtk.ScrolledWindow(null, null); message_viewer_scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC); message_viewer_scrolled.add(message_viewer); // three-pane display: message list left of current message on bottom separated by // grippable messages_paned.pack1(message_list_scrolled, false, false); messages_paned.pack2(message_viewer_scrolled, true, true); // three-pane display: folder list on left and messages on right separated by grippable folder_paned.pack1(folder_list_scrolled, false, false); folder_paned.pack2(messages_paned, true, false); main_layout.pack_end(folder_paned, true, true, 0); add(main_layout); } private void on_folder_selected(Geary.Folder? folder) { if (folder == null) { debug("no folder selected"); message_list_store.clear(); return; } debug("Folder %s selected", folder.to_string()); do_select_folder.begin(folder, on_select_folder_completed); } private async void do_select_folder(Geary.Folder folder) throws Error { cancel_folder(); message_list_store.clear(); if (current_folder != null) { yield current_folder.close_async(); } current_folder = folder; yield current_folder.open_async(true, cancellable_folder); current_conversations = new Geary.Conversations(current_folder, MessageListStore.REQUIRED_FIELDS); current_conversations.monitor_new_messages(cancellable_folder); current_conversations.scan_started.connect(on_scan_started); current_conversations.scan_error.connect(on_scan_error); current_conversations.scan_completed.connect(on_scan_completed); current_conversations.conversations_added.connect(on_conversations_added); current_conversations.conversation_appended.connect(on_conversation_appended); current_conversations.conversation_trimmed.connect(on_conversation_trimmed); current_conversations.conversation_removed.connect(on_conversation_removed); current_conversations.updated_placeholders.connect(on_updated_placeholders); // Do a quick-list of the messages (which should return what's in the local store) if // supported by the Folder, followed by a complete list if needed second_list_pass_required = current_folder.get_supported_list_flags().is_all_set(Geary.Folder.ListFlags.FAST); // Load all conversations from the DB. current_conversations.lazy_load(-1, -1, Geary.Folder.ListFlags.FAST, cancellable_folder); } public void on_scan_started(Geary.EmailIdentifier? id, int low, int count) { debug("on scan started. id = %s low = %d count = %d", id != null ? id.to_string() : "(null)", low, count); message_list_view.enable_load_more = false; } public void on_scan_error(Error err) { debug("Scan error: %s", err.message); } public void on_scan_completed() { debug("on scan completed"); do_fetch_previews.begin(cancellable_folder); message_list_view.enable_load_more = true; } public void on_conversations_added(Gee.Collection conversations) { debug("on conversation added"); foreach (Geary.Conversation c in conversations) { if (!message_list_store.has_conversation(c)) message_list_store.append_conversation(c); } } public void on_conversation_appended(Geary.Conversation conversation, Gee.Collection email) { message_list_store.update_conversation(conversation); } public void on_conversation_trimmed(Geary.Conversation conversation, Geary.Email email) { message_list_store.update_conversation(conversation); } public void on_conversation_removed(Geary.Conversation conversation) { message_list_store.remove_conversation(conversation); } public void on_updated_placeholders(Geary.Conversation conversation, Gee.Collection email) { message_list_store.update_conversation(conversation); } private void on_load_more() { debug("on_load_more"); message_list_view.enable_load_more = false; Geary.EmailIdentifier? low_id = message_list_store.get_email_id_lowest(); if (low_id == null) return; current_conversations.load_by_id_async.begin(low_id, - FETCH_EMAIL_CHUNK_COUNT, Geary.Folder.ListFlags.EXCLUDING_ID, cancellable_folder, on_load_more_completed); } private void on_load_more_completed(Object? source, AsyncResult result) { debug("on load more completed"); try { current_conversations.load_by_id_async.end(result); } catch (Error err) { debug("Error, unable to load conversations: %s", err.message); } } private async void do_fetch_previews(Cancellable? cancellable) throws Error { int count = message_list_store.get_count(); for (int ctr = 0; ctr < count; ctr++) { Geary.Email? email = message_list_store.get_newest_message_at_index(ctr); if (email == null) continue; Geary.Email? body = yield current_folder.fetch_email_async(email.id, Geary.Email.Field.HEADER | Geary.Email.Field.BODY | Geary.Email.Field.ENVELOPE | Geary.Email.Field.PROPERTIES, cancellable); message_list_store.set_preview_at_index(ctr, body); } // with all the previews fetched, now go back and do a full list (if required) if (second_list_pass_required) { second_list_pass_required = false; debug("Doing second list pass now"); current_conversations.lazy_load(-1, FETCH_EMAIL_CHUNK_COUNT, Geary.Folder.ListFlags.NONE, cancellable); } } private void on_select_folder_completed(Object? source, AsyncResult result) { try { do_select_folder.end(result); } catch (Error err) { debug("Unable to select folder: %s", err.message); } } private void on_conversation_selected(Geary.Conversation? conversation) { cancel_message(); message_viewer.clear(); if (conversation != null) do_select_message.begin(conversation, cancellable_message, on_select_message_completed); } private async void do_select_message(Geary.Conversation conversation, Cancellable? cancellable = null) throws Error { if (current_folder == null) { debug("Conversation selected with no folder selected"); return; } foreach (Geary.Email email in conversation.get_pool_sorted(compare_email)) { Geary.Email full_email = yield current_folder.fetch_email_async(email.id, MessageViewer.REQUIRED_FIELDS, cancellable); if (cancellable.is_cancelled()) break; message_viewer.add_message(full_email); } } private void on_select_message_completed(Object? source, AsyncResult result) { try { do_select_message.end(result); } catch (Error err) { if (!(err is IOError.CANCELLED)) debug("Unable to select message: %s", err.message); } } private void on_folders_added_removed(Gee.Collection? added, Gee.Collection? removed) { if (added != null && added.size > 0) { Gee.Set? ignored_paths = account.get_ignored_paths(); Gee.ArrayList skipped = new Gee.ArrayList(); foreach (Geary.Folder folder in added) { if (ignored_paths != null && ignored_paths.contains(folder.get_path())) skipped.add(folder); else folder_list_store.add_user_folder(folder); } Gee.Collection remaining = added; if (skipped.size > 0) { remaining = new Gee.ArrayList(); remaining.add_all(added); remaining.remove_all(skipped); } search_folders_for_children.begin(remaining); } } private async void search_folders_for_children(Gee.Collection folders) { Gee.ArrayList accumulator = new Gee.ArrayList(); foreach (Geary.Folder folder in folders) { try { Gee.Collection children = yield account.list_folders_async( folder.get_path(), null); accumulator.add_all(children); } catch (Error err) { debug("Unable to list children of %s: %s", folder.to_string(), err.message); } } if (accumulator.size > 0) on_folders_added_removed(accumulator, null); } public void debug_print_selected() { if (message_viewer.messages.size == 0) { debug("Nothing to print"); return; } debug("---------------------------"); foreach (Geary.Email e in message_viewer.messages) { debug("Message: %s", e.id.to_string()); if (e.header != null) debug("\n%s", e.header.buffer.to_utf8()); else debug("No message data."); debug("---------------------------"); } } private void cancel_folder() { Cancellable old_cancellable = cancellable_folder; cancellable_folder = new Cancellable(); cancel_message(); old_cancellable.cancel(); } private void cancel_message() { Cancellable old_cancellable = cancellable_message; cancellable_message = new Cancellable(); old_cancellable.cancel(); } }