Closes #4644 Spinner fix

This commit is contained in:
Eric Gregory 2013-07-11 12:50:19 -07:00
parent 1054b99264
commit 9068d6330b
9 changed files with 262 additions and 78 deletions

View file

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

View file

@ -73,7 +73,6 @@ public class GearyController {
private Cancellable cancellable_open_account = new Cancellable();
private Gee.HashMap<Geary.Account, Cancellable> inbox_cancellables
= new Gee.HashMap<Geary.Account, Cancellable>();
private int busy_count = 0;
private Gee.Set<Geary.Conversation> selected_conversations = new Gee.HashSet<Geary.Conversation>();
private Geary.Conversation? last_deleted_conversation = null;
private Gee.LinkedList<ComposerWindow> composer_windows = new Gee.LinkedList<ComposerWindow>();
@ -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<Geary.EmailIdentifier> 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<Geary.EmailIdentifier> 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<Geary.EmailIdentifier>(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);

View file

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

View file

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

View file

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

View file

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

View file

@ -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<ConversationOperation> mailbox
= new Geary.Nonblocking.Mailbox<ConversationOperation>();
@ -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;

View file

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

View file

@ -34,6 +34,24 @@
</div>
<div id="link_warning_template" class="link_warning">
<img class="close_link_warning button" />
</div>
<div id="spinner">
<!--
Spinner code from CSSLoad.net
License: http://cssload.net/en/terms_of_use
-->
<div id="floatingCirclesG">
<div class="f_circleG" id="frotateG_01"></div>
<div class="f_circleG" id="frotateG_02"></div>
<div class="f_circleG" id="frotateG_03"></div>
<div class="f_circleG" id="frotateG_04"></div>
<div class="f_circleG" id="frotateG_05"></div>
<div class="f_circleG" id="frotateG_06"></div>
<div class="f_circleG" id="frotateG_07"></div>
<div class="f_circleG" id="frotateG_08"></div>
</div>
</div>
</body>
</html>