From 9068d6330b5e5ddaf7080021a0d4379d761fce49 Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Thu, 11 Jul 2013 12:50:19 -0700 Subject: [PATCH] Closes #4644 Spinner fix --- src/CMakeLists.txt | 1 + src/client/geary-controller.vala | 67 ++--------- src/client/ui/main-window.vala | 16 +-- src/client/ui/monitored-spinner.vala | 40 +++++++ src/client/views/conversation-viewer.vala | 77 ++++++++++-- src/engine/app/app-conversation-monitor.vala | 2 + .../app-conversation-operation-queue.vala | 8 ++ theming/message-viewer.css | 111 ++++++++++++++++++ theming/message-viewer.html | 18 +++ 9 files changed, 262 insertions(+), 78 deletions(-) create mode 100644 src/client/ui/monitored-spinner.vala diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ec2dc00d..8edee40d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -331,6 +331,7 @@ client/ui/icon-factory.vala client/ui/main-toolbar.vala client/ui/main-window.vala client/ui/monitored-progress-bar.vala +client/ui/monitored-spinner.vala client/util/util-date.vala client/util/util-email.vala diff --git a/src/client/geary-controller.vala b/src/client/geary-controller.vala index 4e6cc51f..eb361711 100644 --- a/src/client/geary-controller.vala +++ b/src/client/geary-controller.vala @@ -73,7 +73,6 @@ public class GearyController { private Cancellable cancellable_open_account = new Cancellable(); private Gee.HashMap inbox_cancellables = new Gee.HashMap(); - private int busy_count = 0; private Gee.Set selected_conversations = new Gee.HashSet(); private Geary.Conversation? last_deleted_conversation = null; private Gee.LinkedList composer_windows = new Gee.LinkedList(); @@ -193,8 +192,6 @@ public class GearyController { main_window.conversation_list_view.grab_focus(); - set_busy(false); - // Start Geary. try { yield Geary.Engine.instance.open_async(GearyApplication.instance.get_user_data_directory(), @@ -743,8 +740,6 @@ public class GearyController { if (folder == current_folder) return; - set_busy(true); - cancel_folder(); // This function is not reentrant. It should be, because it can be @@ -763,6 +758,7 @@ public class GearyController { if (current_conversations != null) { yield current_conversations.stop_monitoring_async(!current_is_inbox, null); current_conversations = null; + main_window.set_progress_monitor(null); } else if (current_folder != null && !current_is_inbox) { yield current_folder.close_async(); } @@ -801,32 +797,21 @@ public class GearyController { clear_new_messages("do_select_folder (inbox)", null); } - 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.seed_completed.connect(on_seed_completed); main_window.conversation_list_store.set_conversation_monitor(current_conversations); main_window.conversation_list_view.set_conversation_monitor(current_conversations); + main_window.set_progress_monitor(current_conversations.progress_monitor); if (!current_conversations.is_monitoring) yield current_conversations.start_monitoring_async(conversation_cancellable); select_folder_mutex.release(ref mutex_token); - - set_busy(false); - } - - private void on_scan_started() { - set_busy(true); } private void on_scan_error(Error err) { - set_busy(false); - } - - private void on_scan_completed() { - set_busy(false); + debug("Scan error: %s", err.message); } private void on_seed_completed() { @@ -991,8 +976,6 @@ public class GearyController { Cancellable old_cancellable = cancellable_message; cancellable_message = new Cancellable(); - set_busy(false); - old_cancellable.cancel(); } @@ -1104,9 +1087,8 @@ public class GearyController { // Mark the emails. Gee.List ids = get_selected_folder_email_ids(preview_message_only); if (ids.size > 0) { - set_busy(true); supports_mark.mark_email_async.begin(ids, flags_to_add, flags_to_remove, - cancellable_message, on_mark_complete); + cancellable_message); } } @@ -1201,9 +1183,8 @@ public class GearyController { Gee.Collection ids = get_conversation_email_ids(conversation, true, only_mark_preview); if (ids.size > 0) { - set_busy(true); supports_mark.mark_email_async.begin(Geary.Collection.to_array_list(ids), - flags_to_add, flags_to_remove, cancellable_message, on_mark_complete); + flags_to_add, flags_to_remove, cancellable_message); } } @@ -1213,9 +1194,8 @@ public class GearyController { if (supports_mark == null) return; - set_busy(true); supports_mark.mark_single_email_async.begin(message.id, flags_to_add, flags_to_remove, - cancellable_message, on_mark_complete); + cancellable_message); } private void on_mark_as_read() { @@ -1246,10 +1226,6 @@ public class GearyController { mark_selected_conversations(null, flags); } - private void on_mark_complete() { - set_busy(false); - } - private void on_mark_as_spam() { Geary.Folder? destination_folder = null; if (current_folder.special_folder_type != Geary.SpecialFolderType.SPAM) { @@ -1285,15 +1261,9 @@ public class GearyController { if (supports_copy == null) return; - set_busy(true); - supports_copy.copy_email_async.begin(ids, destination.path, cancellable_message, - on_copy_complete); + supports_copy.copy_email_async.begin(ids, destination.path, cancellable_message); } - - 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.size == 0) @@ -1307,15 +1277,9 @@ public class GearyController { if (supports_move == null) return; - set_busy(true); - supports_move.move_email_async.begin(ids, destination.path, cancellable_message, - on_move_complete); + supports_move.move_email_async.begin(ids, destination.path, cancellable_message); } - - private void on_move_complete() { - set_busy(false); - } - + private void on_open_attachment(Geary.Attachment attachment) { if (GearyApplication.instance.config.ask_open_attachment) { QuestionDialog ask_to_open = new QuestionDialog.with_checkbox(main_window, @@ -1529,7 +1493,6 @@ public class GearyController { // If the user clicked the toolbar button, we want to move focus back to the message list. main_window.conversation_list_view.grab_focus(); - set_busy(true); delete_messages.begin(get_selected_folder_email_ids(), cancellable_folder, on_delete_messages_completed); } @@ -1561,8 +1524,6 @@ public class GearyController { } catch (Error err) { debug("Error, unable to delete messages: %s", err.message); } - - set_busy(false); } private void on_zoom_in() { @@ -1586,14 +1547,6 @@ public class GearyController { NotificationBubble.play_sound("message-sent-email"); } - public void set_busy(bool is_busy) { - busy_count += is_busy ? 1 : -1; - if (busy_count < 0) - busy_count = 0; - - main_window.set_busy(busy_count > 0); - } - private void on_link_selected(string link) { if (link.down().has_prefix(Geary.ComposedEmail.MAILTO_SCHEME)) { compose_mailto(link); diff --git a/src/client/ui/main-window.vala b/src/client/ui/main-window.vala index 3d8bcc8e..de3e8ea5 100644 --- a/src/client/ui/main-window.vala +++ b/src/client/ui/main-window.vala @@ -22,7 +22,7 @@ public class MainWindow : Gtk.Window { private Gtk.Paned conversations_paned = new Gtk.Paned(Gtk.Orientation.HORIZONTAL); private Gtk.ScrolledWindow conversation_list_scrolled; - private Gtk.Spinner spinner = new Gtk.Spinner(); + private MonitoredSpinner spinner = new MonitoredSpinner(); public MainWindow() { title = GearyApplication.NAME; @@ -82,15 +82,11 @@ public class MainWindow : Gtk.Window { return base.configure_event(event); } - // Displays or stops displaying busy spinner. - public void set_busy(bool is_busy) { - if (is_busy) { - spinner.start(); - spinner.show(); - } else { - spinner.stop(); - spinner.hide(); - } + /** + * Sets the progress monitor to display in the status bar. + */ + public void set_progress_monitor(Geary.ProgressMonitor? monitor) { + spinner.set_progress_monitor(monitor); } private void create_layout() { diff --git a/src/client/ui/monitored-spinner.vala b/src/client/ui/monitored-spinner.vala new file mode 100644 index 00000000..745bc94d --- /dev/null +++ b/src/client/ui/monitored-spinner.vala @@ -0,0 +1,40 @@ +/* Copyright 2013 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. + */ + +/** + * Adapts a progress spinner to automatically display progress of a Geary.ProgressMonitor. + */ +public class MonitoredSpinner : Gtk.Spinner { + private Geary.ProgressMonitor? monitor = null; + + public void set_progress_monitor(Geary.ProgressMonitor? monitor) { + if (monitor != null) { + this.monitor = monitor; + monitor.start.connect(on_start); + monitor.finish.connect(on_stop); + } else { + this.monitor = null; + stop(); + hide(); + } + } + + public override void show() { + if (monitor != null && monitor.is_in_progress) + base.show(); + } + + private void on_start() { + start(); + show(); + } + + private void on_stop() { + stop(); + hide(); + } +} + diff --git a/src/client/views/conversation-viewer.vala b/src/client/views/conversation-viewer.vala index 1fa93cc9..bf241005 100644 --- a/src/client/views/conversation-viewer.vala +++ b/src/client/views/conversation-viewer.vala @@ -16,8 +16,10 @@ public class ConversationViewer : Gtk.Box { | Geary.Email.Field.PREVIEW; private const int ATTACHMENT_PREVIEW_SIZE = 50; + private const int SELECT_CONVERSATION_TIMEOUT_MSEC = 100; private const string MESSAGE_CONTAINER_ID = "message_container"; private const string SELECTION_COUNTER_ID = "multiple_messages"; + private const string SPINNER_ID = "spinner"; private enum SearchState { // Search/find states. @@ -38,6 +40,33 @@ public class ConversationViewer : Gtk.Box { COUNT; } + // Main display mode. + private enum DisplayMode { + NONE = 0, // Nothing is shown (ni + CONVERSATION, // Email conversation + MULTISELECT, // Message indicating that <> 1 conversations are selected + LOADING, // Loading spinner + + COUNT; + + // Returns the CSS id associated with this mode's DIV container. + public string get_id() { + switch (this) { + case CONVERSATION: + return MESSAGE_CONTAINER_ID; + + case MULTISELECT: + return SELECTION_COUNTER_ID; + + case LOADING: + return SPINNER_ID; + + default: + assert_not_reached(); + } + } + } + // Fired when the user clicks a link. public signal void link_selected(string link); @@ -90,6 +119,8 @@ public class ConversationViewer : Gtk.Box { private ConversationFindBar conversation_find_bar; private Cancellable cancellable_fetch = new Cancellable(); private Geary.State.Machine fsm; + private DisplayMode display_mode = DisplayMode.NONE; + private uint select_conversation_timeout_id = 0; public ConversationViewer() { Object(orientation: Gtk.Orientation.VERTICAL, spacing: 0); @@ -175,13 +206,12 @@ public class ConversationViewer : Gtk.Box { return "message_%s".printf(id.to_string()); } - public void show_multiple_selected(uint selected_count) { + private void show_multiple_selected(uint selected_count) { // Remove any messages and hide the message container, then show the counter. clear(current_folder, current_account_information); + set_mode(DisplayMode.MULTISELECT); + try { - web_view.hide_element_by_id(MESSAGE_CONTAINER_ID); - web_view.show_element_by_id(SELECTION_COUNTER_ID); - // Update the counter's count. WebKit.DOM.HTMLElement counter = web_view.get_dom_document().get_element_by_id("selection_counter") as WebKit.DOM.HTMLElement; @@ -234,6 +264,17 @@ public class ConversationViewer : Gtk.Box { clear(current_folder, current_folder.account.information); web_view.scroll_reset(); + if (select_conversation_timeout_id != 0) + Source.remove(select_conversation_timeout_id); + + // If the load is taking too long, display a spinner. + select_conversation_timeout_id = Timeout.add(SELECT_CONVERSATION_TIMEOUT_MSEC, () => { + if (select_conversation_timeout_id != 0) + set_mode(DisplayMode.LOADING); + + return false; + }); + current_conversation = Geary.Collection.get_first(conversations); select_conversation_async.begin(current_conversation, current_folder, @@ -368,13 +409,8 @@ public class ConversationViewer : Gtk.Box { private void add_message(Geary.Email email) { // Make sure the message container is showing and the multi-message counter hidden. - try { - web_view.show_element_by_id(MESSAGE_CONTAINER_ID); - web_view.hide_element_by_id(SELECTION_COUNTER_ID); - } catch (Error e) { - debug("Error showing/hiding containers: %s", e.message); - } - + set_mode(DisplayMode.CONVERSATION); + if (messages.contains(email)) return; @@ -1756,5 +1792,24 @@ public class ConversationViewer : Gtk.Box { return SearchState.SEARCH_FOLDER; } + + // Sets the current display mode by displaying only the corresponding DIV. + private void set_mode(DisplayMode mode) { + select_conversation_timeout_id = 0; // Cancel select timers. + + display_mode = mode; + + try { + for(int i = DisplayMode.NONE + 1; i < DisplayMode.COUNT; i++) { + if ((int) mode != i) + web_view.hide_element_by_id(((DisplayMode) i).get_id()); + } + + if (mode != DisplayMode.NONE) + web_view.show_element_by_id(mode.get_id()); + } catch (Error e) { + debug("Error updating counter: %s", e.message); + } + } } diff --git a/src/engine/app/app-conversation-monitor.vala b/src/engine/app/app-conversation-monitor.vala index ceba0e22..a054a246 100644 --- a/src/engine/app/app-conversation-monitor.vala +++ b/src/engine/app/app-conversation-monitor.vala @@ -38,6 +38,8 @@ public class Geary.App.ConversationMonitor : BaseObject { } } + public Geary.ProgressMonitor progress_monitor { get { return operation_queue.progress_monitor; } } + private ConversationSet conversations = new ConversationSet(); private Geary.Email.Field required_fields; private Geary.Folder.OpenFlags open_flags; diff --git a/src/engine/app/conversation-monitor/app-conversation-operation-queue.vala b/src/engine/app/conversation-monitor/app-conversation-operation-queue.vala index 48f422c8..15928f44 100644 --- a/src/engine/app/conversation-monitor/app-conversation-operation-queue.vala +++ b/src/engine/app/conversation-monitor/app-conversation-operation-queue.vala @@ -6,6 +6,8 @@ private class Geary.App.ConversationOperationQueue : BaseObject { public bool is_processing { get; private set; default = false; } + public Geary.SimpleProgressMonitor progress_monitor { get; private set; default = + new Geary.SimpleProgressMonitor(Geary.ProgressType.ACTIVITY); } private Geary.Nonblocking.Mailbox mailbox = new Geary.Nonblocking.Mailbox(); @@ -50,7 +52,13 @@ private class Geary.App.ConversationOperationQueue : BaseObject { if (op is TerminateOperation) break; + if (!progress_monitor.is_in_progress) + progress_monitor.notify_start(); + yield op.execute_async(); + + if (mailbox.size == 0) + progress_monitor.notify_finish(); } is_processing = false; diff --git a/theming/message-viewer.css b/theming/message-viewer.css index 9c07f8a0..5a3a6de5 100644 --- a/theming/message-viewer.css +++ b/theming/message-viewer.css @@ -538,3 +538,114 @@ blockquote { .search_coloring *::selection { background-color: #00ddff; } + +#spinner { + display: none; + margin: 100px auto; + width: 128px; +} + +/* +Spinner code from CSSload.net +License: http://cssload.net/en/terms_of_use +*/ + +#spinner #floatingCirclesG { + position:relative; + width:128px; + height:128px; + -webkit-transform:scale(0.6); + transform:scale(0.6); +} + +#spinner .f_circleG { + position:absolute; + background-color:#FFFFFF; + height:23px; + width:23px; + -webkit-border-radius:12px; + -webkit-animation-name:f_fadeG; + -webkit-animation-duration:1.04s; + -webkit-animation-iteration-count:infinite; + -webkit-animation-direction:linear; + border-radius:12px; + animation-name:f_fadeG; + animation-duration:1.04s; + animation-iteration-count:infinite; + animation-direction:linear; +} + +#spinner #frotateG_01 { + left:0; + top:52px; + -webkit-animation-delay:0.39s; + animation-delay:0.39s; +} + +#spinner #frotateG_02 { + left:15px; + top:15px; + -webkit-animation-delay:0.52s; + animation-delay:0.52s; +} + +#spinner #frotateG_03 { + left:52px; + top:0; + -webkit-animation-delay:0.65s; + animation-delay:0.65s; +} + +#spinner #frotateG_04 { + right:15px; + top:15px; + -webkit-animation-delay:0.78s; + animation-delay:0.78s; +} + +#spinner #frotateG_05 { + right:0; + top:52px; + -webkit-animation-delay:0.91s; + animation-delay:0.91s; +} + +#spinner #frotateG_06 { + right:15px; + bottom:15px; + -webkit-animation-delay:1.04s; + animation-delay:1.04s; +} + +#spinner #frotateG_07 { + left:52px; + bottom:0; + -moz-animation-delay:1.17s; + -webkit-animation-delay:1.17s; + -ms-animation-delay:1.17s; + -o-animation-delay:1.17s; + animation-delay:1.17s; +} + +#spinner #frotateG_08 { + left:15px; + bottom:15px; + -moz-animation-delay:1.3s; + -webkit-animation-delay:1.3s; + -ms-animation-delay:1.3s; + -o-animation-delay:1.3s; + animation-delay:1.3s; +} + +@-webkit-keyframes f_fadeG { + 0% { + background-color:#000000 + } + + 100% { + background-color:#FFFFFF + } +} + +/* /Spinner */ + diff --git a/theming/message-viewer.html b/theming/message-viewer.html index 5b3768cc..9a44335d 100644 --- a/theming/message-viewer.html +++ b/theming/message-viewer.html @@ -34,6 +34,24 @@ +
+ +
+
+
+
+
+
+
+
+
+
+ +