[geary/wip/account-editor-refinements-v1: 5/7] Allow adding/modifying an account to be cancelled when in progress



commit cc07e542acfac540b1e7be5ac8a69b552e9ec045
Author: Michael Gratton <mike vee net>
Date:   Wed Jan 9 12:23:12 2019 +1100

    Allow adding/modifying an account to be cancelled when in progress
    
    This adds a cancellable property to each account pane and uses it for
    all I/O operations, and allows it to be cancelled and pane state to be
    updated when either Esc is pressed, or Cancel is clicked on the servers
    pane.

 src/client/accounts/accounts-editor-add-pane.vala  |  42 ++++--
 src/client/accounts/accounts-editor-edit-pane.vala |  41 ++++--
 src/client/accounts/accounts-editor-list-pane.vala |  26 ++--
 .../accounts/accounts-editor-remove-pane.vala      |  10 +-
 .../accounts/accounts-editor-servers-pane.vala     | 154 ++++++++++++++++-----
 src/client/accounts/accounts-editor.vala           |  81 +++++++++--
 6 files changed, 278 insertions(+), 76 deletions(-)
---
diff --git a/src/client/accounts/accounts-editor-add-pane.vala 
b/src/client/accounts/accounts-editor-add-pane.vala
index 3d7a5610..3c5468df 100644
--- a/src/client/accounts/accounts-editor-add-pane.vala
+++ b/src/client/accounts/accounts-editor-add-pane.vala
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018 Michael Gratton <mike vee net>
+ * Copyright 2018-2019 Michael Gratton <mike vee net>
  *
  * This software is licensed under the GNU Lesser General Public License
  * (version 2.1 or later).  See the COPYING file in this distribution.
@@ -16,6 +16,17 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
         get { return this.real_name.value; }
     }
 
+    /** {@inheritDoc} */
+    internal bool is_operation_running {
+        get { return !this.sensitive; }
+        protected set { update_operation_ui(value); }
+    }
+
+    /** {@inheritDoc} */
+    internal GLib.Cancellable? op_cancellable {
+        get; protected set; default = new GLib.Cancellable();
+    }
+
     protected weak Accounts.Editor editor { get; set; }
 
     private Geary.ServiceProvider provider;
@@ -51,6 +62,9 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
     [GtkChild]
     private Gtk.Button create_button;
 
+    [GtkChild]
+    private Gtk.Button back_button;
+
     [GtkChild]
     private Gtk.Spinner create_spinner;
 
@@ -146,10 +160,7 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
     }
 
     private async void validate_account(GLib.Cancellable? cancellable) {
-        this.create_spinner.show();
-        this.create_spinner.start();
-        this.create_button.set_sensitive(false);
-        this.set_sensitive(false);
+        this.is_operation_running = true;
 
         bool is_valid = false;
         string message = "";
@@ -182,6 +193,9 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
                 to_focus = this.imap_login.value;
                 // Translators: In-app notification label
                 message = _("Check your receiving login and password");
+            } catch (GLib.IOError.CANCELLED err) {
+                // Nothing to do here, someone just cancelled
+                debug("IMAP validation was cancelled: %s", err.message);
             } catch (GLib.Error err) {
                 debug("Error validating IMAP service: %s", err.message);
                 this.imap_tls.show();
@@ -210,6 +224,9 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
                     to_focus = this.smtp_login.value;
                     // Translators: In-app notification label
                     message = _("Check your sending login and password");
+                } catch (GLib.IOError.CANCELLED err) {
+                    // Nothing to do here, someone just cancelled
+                    debug("SMTP validation was cancelled: %s", err.message);
                 } catch (GLib.Error err) {
                     debug("Error validating SMTP service: %s", err.message);
                     this.smtp_tls.show();
@@ -252,10 +269,7 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
             }
         }
 
-        this.create_spinner.stop();
-        this.create_spinner.hide();
-        this.create_button.set_sensitive(true);
-        this.set_sensitive(true);
+        this.is_operation_running = false;
 
         // Focus and pop up the notification after re-sensitising
         // so it actually succeeds.
@@ -357,6 +371,14 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
         this.controls_valid = controls_valid;
     }
 
+    private void update_operation_ui(bool is_running) {
+        this.create_spinner.visible = is_running;
+        this.create_spinner.active = is_running;
+        this.create_button.sensitive = !is_running;
+        this.back_button.sensitive = !is_running;
+        this.sensitive = !is_running;
+    }
+
     private void on_validated(Components.Validator.Trigger reason) {
         check_validation();
         if (this.controls_valid && reason == Components.Validator.Trigger.ACTIVATED) {
@@ -399,7 +421,7 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
 
     [GtkCallback]
     private void on_create_button_clicked() {
-        this.validate_account.begin(null);
+        this.validate_account.begin(this.op_cancellable);
     }
 
     [GtkCallback]
diff --git a/src/client/accounts/accounts-editor-edit-pane.vala 
b/src/client/accounts/accounts-editor-edit-pane.vala
index a86ff117..d84727f5 100644
--- a/src/client/accounts/accounts-editor-edit-pane.vala
+++ b/src/client/accounts/accounts-editor-edit-pane.vala
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018 Michael Gratton <mike vee net>
+ * Copyright 2018-2019 Michael Gratton <mike vee net>
  *
  * This software is licensed under the GNU Lesser General Public License
  * (version 2.1 or later).  See the COPYING file in this distribution.
@@ -26,6 +26,14 @@ internal class Accounts.EditorEditPane :
         get; protected set; default = new Application.CommandStack();
     }
 
+    /** {@inheritDoc} */
+    internal bool is_operation_running { get; protected set; default = false; }
+
+    /** {@inheritDoc} */
+    internal GLib.Cancellable? op_cancellable {
+        get; protected set; default = null;
+    }
+
     /** {@inheritDoc} */
     protected weak Accounts.Editor editor { get; set; }
 
@@ -67,7 +75,9 @@ internal class Accounts.EditorEditPane :
         this.pane_content.set_focus_vadjustment(this.pane_adjustment);
 
         this.details_list.set_header_func(Editor.seperator_headers);
-        this.details_list.add(new DisplayNameRow(account, this.commands));
+        this.details_list.add(
+            new DisplayNameRow(account, this.commands, this.op_cancellable)
+        );
 
         this.senders_list.set_header_func(Editor.seperator_headers);
         foreach (Geary.RFC822.MailboxAddress sender in
@@ -85,7 +95,9 @@ internal class Accounts.EditorEditPane :
         this.signature_preview.content_loaded.connect(() => {
                 // Only enable editability after the content has fully
                 // loaded to avoid the WebProcess crashing.
-                this.signature_preview.set_editable.begin(true, null);
+                this.signature_preview.set_editable.begin(
+                    true, this.op_cancellable
+                );
             });
         this.signature_preview.document_modified.connect(() => {
                 this.signature_changed = true;
@@ -101,7 +113,7 @@ internal class Accounts.EditorEditPane :
                         new SignatureChangedCommand(
                             this.signature_preview, account
                         ),
-                        null
+                        this.op_cancellable
                     );
                 }
                 return Gdk.EVENT_PROPAGATE;
@@ -175,7 +187,7 @@ internal class Accounts.EditorEditPane :
                 this.account,
                 this.senders_list
             ),
-            null
+            this.op_cancellable
         );
     }
 
@@ -187,7 +199,7 @@ internal class Accounts.EditorEditPane :
                 this.account,
                 this.senders_list
             ),
-            null
+            this.op_cancellable
         );
     }
 
@@ -253,9 +265,12 @@ private class Accounts.DisplayNameRow : AccountRow<EditorEditPane,Gtk.Entry> {
 
 
     private Application.CommandStack commands;
+    private GLib.Cancellable? cancellable;
+
 
     public DisplayNameRow(Geary.AccountInformation account,
-                          Application.CommandStack commands) {
+                          Application.CommandStack commands,
+                          GLib.Cancellable? cancellable) {
         base(
             account,
             // Translators: Label in the account editor for the user's
@@ -265,6 +280,7 @@ private class Accounts.DisplayNameRow : AccountRow<EditorEditPane,Gtk.Entry> {
         );
         this.activatable = false;
         this.commands = commands;
+        this.cancellable = cancellable;
 
         update();
 
@@ -295,7 +311,7 @@ private class Accounts.DisplayNameRow : AccountRow<EditorEditPane,Gtk.Entry> {
                     // account.
                     _("Change account name back to ā€œ%sā€")
                 ),
-                null
+                this.cancellable
             );
         }
 
@@ -335,7 +351,7 @@ private class Accounts.AddMailboxRow : AddRow<EditorEditPane> {
                             )
                         )
                     ),
-                    null
+                    pane.op_cancellable
                 );
                 popover.popdown();
             });
@@ -376,13 +392,14 @@ private class Accounts.MailboxRow : AccountRow<EditorEditPane,Gtk.Label> {
                             popover.address
                         )
                     ),
-                    null
+                    pane.op_cancellable
                 );
                 popover.popdown();
             });
         popover.remove_clicked.connect(() => {
                 pane.commands.execute.begin(
-                    new RemoveMailboxCommand(this), null
+                    new RemoveMailboxCommand(this),
+                    pane.op_cancellable
                 );
                 popover.popdown();
             });
@@ -788,7 +805,7 @@ private class Accounts.EmailPrefetchRow :
                             get_label(this.account.prefetch_period_days)
                         )
                     ),
-                    null
+                    pane.op_cancellable
                 );
             });
     }
diff --git a/src/client/accounts/accounts-editor-list-pane.vala 
b/src/client/accounts/accounts-editor-list-pane.vala
index 5aa0975b..3cc59888 100644
--- a/src/client/accounts/accounts-editor-list-pane.vala
+++ b/src/client/accounts/accounts-editor-list-pane.vala
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018 Michael Gratton <mike vee net>
+ * Copyright 2018-2019 Michael Gratton <mike vee net>
  *
  * This software is licensed under the GNU Lesser General Public License
  * (version 2.1 or later).  See the COPYING file in this distribution.
@@ -28,21 +28,29 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
     }
 
 
-    /** {@iinheritDoc} */
+    /** {@inheritDoc} */
     internal Gtk.Widget initial_widget {
         get {
             return this.show_welcome ? this.service_list : this.accounts_list;
         }
     }
 
-    /** {@iinheritDoc} */
+    /** {@inheritDoc} */
     internal Application.CommandStack commands {
         get; protected set; default = new Application.CommandStack();
     }
 
+    /** {@inheritDoc} */
+    internal bool is_operation_running { get; protected set; default = false; }
+
+    /** {@inheritDoc} */
+    internal GLib.Cancellable? op_cancellable {
+        get; protected set; default = null;
+    }
+
     internal Manager accounts { get; private set; }
 
-    /** {@iinheritDoc} */
+    /** {@inheritDoc} */
     protected weak Accounts.Editor editor { get; set; }
 
     private bool show_welcome {
@@ -144,7 +152,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
         if (row != null) {
             this.commands.execute.begin(
                 new RemoveAccountCommand(account, this.accounts),
-                null
+                this.op_cancellable
             );
         }
     }
@@ -208,7 +216,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
             new ReorderAccountCommand(
                 (AccountListRow) source, new_position, this.accounts
             ),
-            null
+            this.op_cancellable
         );
     }
 
@@ -217,7 +225,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
             new ReorderAccountCommand(
                 (AccountListRow) source, target.get_index(), this.accounts
             ),
-            null
+            this.op_cancellable
         );
     }
 
@@ -306,7 +314,7 @@ private class Accounts.AccountListRow : AccountRow<EditorListPane,Gtk.Grid> {
             // GOA account but it's disabled, so just take people
             // directly to the GOA panel
             manager.show_goa_account.begin(
-                account, null,
+                account, pane.op_cancellable,
                 (obj, res) => {
                     try {
                         manager.show_goa_account.end(res);
@@ -446,7 +454,7 @@ private class Accounts.AddServiceProviderRow : EditorRow<EditorListPane> {
 
     public override void activated(EditorListPane pane) {
         pane.accounts.add_goa_account.begin(
-            this.provider, null,
+            this.provider, pane.op_cancellable,
             (obj, res) => {
                 bool add_local = false;
                 try {
diff --git a/src/client/accounts/accounts-editor-remove-pane.vala 
b/src/client/accounts/accounts-editor-remove-pane.vala
index 7698f14c..2a6844cd 100644
--- a/src/client/accounts/accounts-editor-remove-pane.vala
+++ b/src/client/accounts/accounts-editor-remove-pane.vala
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018 Michael Gratton <mike vee net>
+ * Copyright 2018-2019 Michael Gratton <mike vee net>
  *
  * This software is licensed under the GNU Lesser General Public License
  * (version 2.1 or later).  See the COPYING file in this distribution.
@@ -23,6 +23,14 @@ internal class Accounts.EditorRemovePane : Gtk.Grid, EditorPane, AccountPane {
         get { return this.remove_button; }
     }
 
+    /** {@inheritDoc} */
+    internal bool is_operation_running { get; protected set; default = false; }
+
+    /** {@inheritDoc} */
+    internal GLib.Cancellable? op_cancellable {
+        get; protected set; default = null;
+    }
+
     [GtkChild]
     private Gtk.HeaderBar header;
 
diff --git a/src/client/accounts/accounts-editor-servers-pane.vala 
b/src/client/accounts/accounts-editor-servers-pane.vala
index 1718ea03..49004cc2 100644
--- a/src/client/accounts/accounts-editor-servers-pane.vala
+++ b/src/client/accounts/accounts-editor-servers-pane.vala
@@ -1,6 +1,6 @@
 /*
  * Copyright 2016 Software Freedom Conservancy Inc.
- * Copyright 2018 Michael Gratton <mike vee net>
+ * Copyright 2018-2019 Michael Gratton <mike vee net>
  *
  * This software is licensed under the GNU Lesser General Public License
  * (version 2.1 or later). See the COPYING file in this distribution.
@@ -30,6 +30,17 @@ internal class Accounts.EditorServersPane :
         get { return this.details_list; }
     }
 
+    /** {@inheritDoc} */
+    internal bool is_operation_running {
+        get { return !this.sensitive; }
+        protected set { update_operation_ui(value); }
+    }
+
+    /** {@inheritDoc} */
+    internal GLib.Cancellable? op_cancellable {
+        get; protected set; default = new GLib.Cancellable();
+    }
+
     private Geary.Engine engine;
 
     // These are copies of the originals that can be updated before
@@ -102,7 +113,9 @@ internal class Accounts.EditorServersPane :
         service_provider.activatable = false;
         add_row(this.details_list, service_provider);
 
-        this.save_drafts = new SaveDraftsRow(this.account, this.commands);
+        this.save_drafts = new SaveDraftsRow(
+            this.account, this.commands, this.op_cancellable
+        );
         add_row(this.details_list, this.save_drafts);
 
         // Receiving
@@ -110,19 +123,36 @@ internal class Accounts.EditorServersPane :
         this.receiving_list.set_header_func(Editor.seperator_headers);
         add_row(
             this.receiving_list,
-            new ServiceHostRow(account, this.incoming_mutable, this.commands)
+            new ServiceHostRow(
+                account,
+                this.incoming_mutable,
+                this.commands,
+                this.op_cancellable
+            )
         );
         add_row(
             this.receiving_list,
-            new ServiceSecurityRow(account, this.incoming_mutable, this.commands)
+            new ServiceSecurityRow(
+                account,
+                this.incoming_mutable,
+                this.commands,
+                this.op_cancellable
+            )
         );
 
         this.incoming_password = new ServicePasswordRow(
-            account, this.incoming_mutable, this.commands
+            account,
+            this.incoming_mutable,
+            this.commands,
+            this.op_cancellable
         );
 
         this.incoming_login = new ServiceLoginRow(
-            account, this.incoming_mutable, this.commands, this.incoming_password
+            account,
+            this.incoming_mutable,
+            this.commands,
+            this.op_cancellable,
+            this.incoming_password
         );
 
         add_row(this.receiving_list, this.incoming_login);
@@ -133,24 +163,45 @@ internal class Accounts.EditorServersPane :
         this.sending_list.set_header_func(Editor.seperator_headers);
         add_row(
             this.sending_list,
-            new ServiceHostRow(account, this.outgoing_mutable, this.commands)
+            new ServiceHostRow(
+                account,
+                this.outgoing_mutable,
+                this.commands,
+                this.op_cancellable
+            )
         );
         add_row(
             this.sending_list,
-            new ServiceSecurityRow(account, this.outgoing_mutable, this.commands)
+            new ServiceSecurityRow(
+                account,
+                this.outgoing_mutable,
+                this.commands,
+                this.op_cancellable
+            )
         );
         this.outgoing_auth = new ServiceOutgoingAuthRow(
-            account, this.outgoing_mutable, this.incoming_mutable, this.commands
+            account,
+            this.outgoing_mutable,
+            this.incoming_mutable,
+            this.commands,
+            this.op_cancellable
         );
         this.outgoing_auth.value.changed.connect(on_outgoing_auth_changed);
         add_row(this.sending_list, this.outgoing_auth);
 
         this.outgoing_password = new ServicePasswordRow(
-            account, this.outgoing_mutable, this.commands
+            account,
+            this.outgoing_mutable,
+            this.commands,
+            this.op_cancellable
         );
 
         this.outgoing_login = new ServiceLoginRow(
-            account, this.outgoing_mutable, this.commands, this.outgoing_password
+            account,
+            this.outgoing_mutable,
+            this.commands,
+            this.op_cancellable,
+            this.outgoing_password
         );
 
         add_row(this.sending_list, this.outgoing_login);
@@ -185,10 +236,7 @@ internal class Accounts.EditorServersPane :
     }
 
     private async void save(GLib.Cancellable? cancellable) {
-        this.apply_button.set_sensitive(false);
-        this.apply_spinner.show();
-        this.apply_spinner.start();
-        this.set_sensitive(false);
+        this.is_operation_running = true;
 
         // Only need to validate if a generic account
         bool is_valid = true;
@@ -206,6 +254,8 @@ internal class Accounts.EditorServersPane :
             }
         }
 
+        this.is_operation_running = false;
+
         if (is_valid) {
             if (this.save_drafts.value_changed) {
                 has_changed = true;
@@ -226,14 +276,10 @@ internal class Accounts.EditorServersPane :
             // updated already by the command
             this.account.save_drafts = this.save_drafts.initial_value;
         }
-
-        this.apply_spinner.stop();
-        this.apply_spinner.hide();
-        this.set_sensitive(true);
     }
 
     private async bool validate(GLib.Cancellable? cancellable) {
-        string message = "";
+        string? message = null;
         bool imap_valid = false;
         try {
             yield this.engine.validate_imap(
@@ -244,6 +290,9 @@ internal class Accounts.EditorServersPane :
             debug("Error authenticating IMAP service: %s", err.message);
             // Translators: In-app notification label
             message = _("Check your receiving login and password");
+        } catch (GLib.IOError.CANCELLED err) {
+            // Nothing to do here, someone just cancelled
+            debug("IMAP validation was cancelled: %s", err.message);
         } catch (GLib.Error err) {
             debug("Error validating IMAP service: %s", err.message);
             // Translators: In-app notification label
@@ -269,6 +318,9 @@ internal class Accounts.EditorServersPane :
                 this.outgoing_auth.value.source = Geary.Credentials.Requirement.CUSTOM;
                 // Translators: In-app notification label
                 message = _("Check your sending login and password");
+            } catch (GLib.IOError.CANCELLED err) {
+                // Nothing to do here, someone just cancelled
+                debug("SMTP validation was cancelled: %s", err.message);
             } catch (GLib.Error err) {
                 debug("Error validating SMTP service: %s", err.message);
                     // Translators: In-app notification label
@@ -279,7 +331,7 @@ internal class Accounts.EditorServersPane :
         bool is_valid = imap_valid && smtp_valid;
         debug("Validation complete, is valid: %s", is_valid.to_string());
 
-        if (!is_valid) {
+        if (!is_valid && message != null) {
             this.editor.add_notification(
                 new InAppNotification(
                     // Translators: In-app notification label, the
@@ -342,6 +394,13 @@ internal class Accounts.EditorServersPane :
         );
     }
 
+    private void update_operation_ui(bool is_running) {
+        this.apply_spinner.visible = is_running;
+        this.apply_spinner.active = is_running;
+        this.apply_button.sensitive = !is_running;
+        this.sensitive = !is_running;
+    }
+
     private void on_validator_changed() {
         this.apply_button.set_sensitive(is_valid());
     }
@@ -354,12 +413,16 @@ internal class Accounts.EditorServersPane :
 
     [GtkCallback]
     private void on_cancel_button_clicked() {
-        this.editor.pop();
+        if (this.is_operation_running) {
+            cancel_operation();
+        } else {
+            this.editor.pop();
+        }
     }
 
     [GtkCallback]
     private void on_apply_button_clicked() {
-        this.save.begin(null);
+        this.save.begin(this.op_cancellable);
     }
 
     [GtkCallback]
@@ -447,7 +510,7 @@ private class Accounts.AccountProviderRow :
     public override void activated(EditorServersPane pane) {
         if (this.accounts.is_goa_account(this.account)) {
             this.accounts.show_goa_account.begin(
-                account, null,
+                account, pane.op_cancellable,
                 (obj, res) => {
                     try {
                         this.accounts.show_goa_account.end(res);
@@ -476,9 +539,12 @@ private class Accounts.SaveDraftsRow :
     public bool initial_value { get; private set; }
 
     private Application.CommandStack commands;
+    private GLib.Cancellable? cancellable;
+
 
     public SaveDraftsRow(Geary.AccountInformation account,
-                         Application.CommandStack commands) {
+                         Application.CommandStack commands,
+                         GLib.Cancellable? cancellable) {
         Gtk.Switch value = new Gtk.Switch();
         base(
             account,
@@ -489,6 +555,7 @@ private class Accounts.SaveDraftsRow :
         );
         update();
         this.commands = commands;
+        this.cancellable = cancellable;
         this.activatable = false;
         this.initial_value = this.account.save_drafts;
         this.account.notify["save-drafts"].connect(on_account_changed);
@@ -505,7 +572,7 @@ private class Accounts.SaveDraftsRow :
                 new Application.PropertyCommand<bool>(
                     this.account, "save_drafts", this.value.state
                 ),
-                null
+                this.cancellable
             );
         }
     }
@@ -532,11 +599,13 @@ private class Accounts.ServiceHostRow :
     }
 
     private Application.CommandStack commands;
+    private GLib.Cancellable? cancellable;
 
 
     public ServiceHostRow(Geary.AccountInformation account,
                           Geary.ServiceInformation service,
-                          Application.CommandStack commands) {
+                          Application.CommandStack commands,
+                          GLib.Cancellable? cancellable) {
         string label = "";
         switch (service.protocol) {
         case Geary.Protocol.IMAP:
@@ -554,6 +623,7 @@ private class Accounts.ServiceHostRow :
 
         base(account, service, label, new Gtk.Entry());
         this.commands = commands;
+        this.cancellable = cancellable;
         this.activatable = false;
         this.validator = new Components.NetworkAddressValidator(this.value);
 
@@ -588,8 +658,8 @@ private class Accounts.ServiceHostRow :
                             this.service, "port", port
                         )
                 }),
-                null
-                );
+                this.cancellable
+            );
         }
     }
 
@@ -613,16 +683,19 @@ private class Accounts.ServiceSecurityRow :
 
 
     private Application.CommandStack commands;
+    private GLib.Cancellable? cancellable;
 
 
     public ServiceSecurityRow(Geary.AccountInformation account,
                               Geary.ServiceInformation service,
-                              Application.CommandStack commands) {
+                              Application.CommandStack commands,
+                              GLib.Cancellable? cancellable) {
         TlsComboBox value = new TlsComboBox();
         base(account, service, value.label, value);
         update();
 
         this.commands = commands;
+        this.cancellable = cancellable;
         this.activatable = false;
         value.changed.connect(on_value_changed);
     }
@@ -655,7 +728,7 @@ private class Accounts.ServiceSecurityRow :
                      )
                     });
             }
-            this.commands.execute.begin(cmd, null);
+            this.commands.execute.begin(cmd, this.cancellable);
         }
     }
 
@@ -677,12 +750,14 @@ private class Accounts.ServiceLoginRow :
     }
 
     private Application.CommandStack commands;
+    private GLib.Cancellable? cancellable;
     private ServicePasswordRow? password_row;
 
 
     public ServiceLoginRow(Geary.AccountInformation account,
                            Geary.ServiceInformation service,
                            Application.CommandStack commands,
+                           GLib.Cancellable? cancellable,
                            ServicePasswordRow? password_row = null) {
         base(
             account,
@@ -694,6 +769,7 @@ private class Accounts.ServiceLoginRow :
         );
 
         this.commands = commands;
+        this.cancellable = cancellable;
         this.activatable = false;
         this.validator = new Components.Validator(this.value);
         this.password_row = password_row;
@@ -737,7 +813,7 @@ private class Accounts.ServiceLoginRow :
                     });
             }
 
-            this.commands.execute.begin(cmd, null);
+            this.commands.execute.begin(cmd, this.cancellable);
         }
     }
 
@@ -795,11 +871,13 @@ private class Accounts.ServicePasswordRow :
     }
 
     private Application.CommandStack commands;
+    private GLib.Cancellable? cancellable;
 
 
     public ServicePasswordRow(Geary.AccountInformation account,
                               Geary.ServiceInformation service,
-                              Application.CommandStack commands) {
+                              Application.CommandStack commands,
+                              GLib.Cancellable? cancellable) {
         base(
             account,
             service,
@@ -810,6 +888,7 @@ private class Accounts.ServicePasswordRow :
         );
 
         this.commands = commands;
+        this.cancellable = cancellable;
         this.activatable = false;
         this.value.visibility = false;
         this.value.input_purpose = Gtk.InputPurpose.PASSWORD;
@@ -833,7 +912,7 @@ private class Accounts.ServicePasswordRow :
                     "credentials",
                     this.service.credentials.copy_with_token(this.value.text)
                 ),
-                null
+                this.cancellable
             );
         }
     }
@@ -852,18 +931,21 @@ private class Accounts.ServiceOutgoingAuthRow :
 
 
     private Application.CommandStack commands;
+    private GLib.Cancellable? cancellable;
     private Geary.ServiceInformation imap_service;
 
 
     public ServiceOutgoingAuthRow(Geary.AccountInformation account,
                                   Geary.ServiceInformation smtp_service,
                                   Geary.ServiceInformation imap_service,
-                                  Application.CommandStack commands) {
+                                  Application.CommandStack commands,
+                                  GLib.Cancellable? cancellable) {
         OutgoingAuthComboBox value = new OutgoingAuthComboBox();
         base(account, smtp_service, value.label, value);
         update();
 
         this.commands = commands;
+        this.cancellable = cancellable;
         this.imap_service = imap_service;
         this.activatable = false;
         value.changed.connect(on_value_changed);
@@ -911,7 +993,7 @@ private class Accounts.ServiceOutgoingAuthRow :
                 );
             }
 
-            this.commands.execute.begin(seq, null);
+            this.commands.execute.begin(seq, this.cancellable);
         }
     }
 
diff --git a/src/client/accounts/accounts-editor.vala b/src/client/accounts/accounts-editor.vala
index ca8753fc..7b6f0a04 100644
--- a/src/client/accounts/accounts-editor.vala
+++ b/src/client/accounts/accounts-editor.vala
@@ -73,20 +73,53 @@ public class Accounts.Editor : Gtk.Dialog {
         bool ret = Gdk.EVENT_PROPAGATE;
 
         // Allow the user to use Esc, Back and Alt+arrow keys to
-        // navigate between panes.
-        if (get_current_pane() != this.editor_list_pane) {
+        // navigate between panes. If a pane is executing a long
+        // running operation, only allow Esc and use it to cancel the
+        // operation instead.
+        EditorPane? current_pane = get_current_pane();
+        if (current_pane != null &&
+            current_pane != this.editor_list_pane) {
             Gdk.ModifierType state = (
                 event.state & Gtk.accelerator_get_default_mod_mask()
             );
             bool is_ltr = (get_direction() == Gtk.TextDirection.LTR);
-            if (event.keyval == Gdk.Key.Escape ||
-                event.keyval == Gdk.Key.Back ||
-                (state == Gdk.ModifierType.MOD1_MASK &&
-                 (is_ltr && event.keyval == Gdk.Key.Left) ||
-                 (!is_ltr && event.keyval == Gdk.Key.Right))) {
-                pop();
+
+            switch (event.keyval) {
+            case Gdk.Key.Escape:
+                if (current_pane.is_operation_running) {
+                    current_pane.cancel_operation();
+                } else {
+                    pop();
+                }
                 ret = Gdk.EVENT_STOP;
+                break;
+
+            case Gdk.Key.Back:
+                if (!current_pane.is_operation_running) {
+                    pop();
+                    ret = Gdk.EVENT_STOP;
+                }
+                break;
+
+            case Gdk.Key.Left:
+                if (!current_pane.is_operation_running &&
+                    state == Gdk.ModifierType.MOD1_MASK &&
+                    is_ltr) {
+                    pop();
+                    ret = Gdk.EVENT_STOP;
+                }
+                break;
+
+            case Gdk.Key.Right:
+                if (!current_pane.is_operation_running &&
+                    state == Gdk.ModifierType.MOD1_MASK &&
+                    !is_ltr) {
+                    pop();
+                    ret = Gdk.EVENT_STOP;
+                }
+                break;
             }
+
         }
 
         if (ret != Gdk.EVENT_STOP) {
@@ -222,9 +255,41 @@ internal interface Accounts.EditorPane : Gtk.Grid {
     /** The editor displaying this pane. */
     internal abstract Gtk.Widget initial_widget { get; }
 
+    /**
+     * Determines if a long running operation is being executed.
+     *
+     * @see cancel_operation
+     */
+    internal abstract bool is_operation_running { get; protected set; }
+
+    /**
+     * Long running operation cancellable.
+     *
+     * This cancellable must be passed to any long-running operations
+     * involving I/O. If not null and operation is cancelled, the
+     * value should be cancelled and replaced with a new instance.
+     *
+     * @see cancel_operation
+     */
+    internal abstract GLib.Cancellable? op_cancellable { get; protected set; }
+
     /** The GTK header bar to display for this pane. */
     internal abstract Gtk.HeaderBar get_header();
 
+    /**
+     * Cancels this pane's current operation, any.
+     *
+     * Sets {@link is_operation_running} to false and if {@link
+     * op_cancellable} is not null, it is cancelled and replaced with
+     * a new instance.
+     */
+    internal void cancel_operation() {
+        this.is_operation_running = false;
+        if (this.op_cancellable != null) {
+            this.op_cancellable.cancel();
+            this.op_cancellable = new GLib.Cancellable();
+        }
+    }
 }
 
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]