diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala index ab6d779d..7141878f 100644 --- a/src/client/composer/composer-widget.vala +++ b/src/client/composer/composer-widget.vala @@ -28,6 +28,10 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { private const string DEFAULT_TITLE = _("New Message"); + /** For providing feedback on slow operation for large inline image paste. */ + private const int SHOW_BACKGROUND_WORK_TIMEOUT_MSEC = 500; + private const int PULSE_TIMEOUT_MSEC = 250; + public enum ComposeType { NEW_MESSAGE, REPLY, @@ -386,6 +390,9 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { [GtkChild] private Gtk.Label info_label; + [GtkChild] + private Gtk.ProgressBar background_progress; + private GLib.SimpleActionGroup composer_actions = new GLib.SimpleActionGroup(); private GLib.SimpleActionGroup editor_actions = new GLib.SimpleActionGroup(); @@ -468,6 +475,12 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { private Application.Client application; + // Timeout for showing the slow image paste pulsing bar + private Geary.TimeoutManager show_background_work_timeout = null; + + // Timer for pulsing progress bar + private Geary.TimeoutManager background_work_pulse; + public Widget(Application.Client application, Geary.Account initial_account, @@ -593,6 +606,14 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { this.editor.mouse_target_changed.connect(on_mouse_target_changed); this.editor.selection_changed.connect(on_selection_changed); + this.show_background_work_timeout = new Geary.TimeoutManager.milliseconds( + SHOW_BACKGROUND_WORK_TIMEOUT_MSEC, this.on_background_work_timeout + ); + this.background_work_pulse = new Geary.TimeoutManager.milliseconds( + PULSE_TIMEOUT_MSEC, this.background_progress.pulse + ); + this.background_work_pulse.repetition = FOREVER; + load_entry_completions(); } @@ -885,6 +906,9 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { on_account_unavailable ); + this.show_background_work_timeout.reset(); + this.background_work_pulse.reset(); + base.destroy(); } @@ -2036,30 +2060,36 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { private void paste_image() { // The slow operations here are creating the PNG and, to a lesser extent, // requesting the image from the clipboard - this.container.top_window.application.mark_busy(); + this.show_background_work_timeout.start(); get_clipboard(Gdk.SELECTION_CLIPBOARD).request_image((clipboard, pixbuf) => { if (pixbuf != null) { - try { - uint8[] buffer; - pixbuf.save_to_buffer(out buffer, "png"); - Geary.Memory.ByteBuffer byte_buffer = new Geary.Memory.ByteBuffer(buffer, buffer.length); + MemoryOutputStream os = new MemoryOutputStream(null); + pixbuf.save_to_stream_async.begin(os, "png", null, (obj, res) => { + try { + pixbuf.save_to_stream_async.end(res); + os.close(); - GLib.DateTime time_now = new GLib.DateTime.now(); - string filename = PASTED_IMAGE_FILENAME_TEMPLATE.printf(time_now.hash()); + Geary.Memory.ByteBuffer byte_buffer = new Geary.Memory.ByteBuffer.from_memory_output_stream(os); - string unique_filename; - add_inline_part(byte_buffer, filename, out unique_filename); - this.editor.insert_image( - Components.WebView.INTERNAL_URL_PREFIX + unique_filename - ); - } catch (Error error) { - warning("Failed to paste image %s", error.message); - } + GLib.DateTime time_now = new GLib.DateTime.now(); + string filename = PASTED_IMAGE_FILENAME_TEMPLATE.printf(time_now.hash()); + + string unique_filename; + add_inline_part(byte_buffer, filename, out unique_filename); + this.editor.insert_image( + Components.WebView.INTERNAL_URL_PREFIX + unique_filename + ); + } catch (Error err) { + warning("Failed to convert pasted clipboard image to PNG"); + } + + stop_background_work_pulse(); + }); } else { warning("Failed to get image from clipboard"); + stop_background_work_pulse(); } - this.container.top_window.application.unmark_busy(); }); } @@ -2807,4 +2837,18 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { Components.WebView.INTERNAL_URL_PREFIX + unique_filename ); } + + /** Shows and starts pulsing the progress meter. */ + private void on_background_work_timeout() { + this.background_progress.fraction = 0.0; + this.background_work_pulse.start(); + this.background_progress.show(); + } + + /** Hides and stops pulsing the progress meter. */ + private void stop_background_work_pulse() { + this.background_progress.hide(); + this.background_work_pulse.reset(); + this.show_background_work_timeout.reset(); + } } diff --git a/ui/composer-widget.ui b/ui/composer-widget.ui index b568c422..b4821843 100644 --- a/ui/composer-widget.ui +++ b/ui/composer-widget.ui @@ -850,6 +850,16 @@ + + + False + start + + + True