[geary/wip/714104-refine-account-dialog: 30/69] Implement undo-able account removal.



commit 9c8990d18d9b669caa3a7399f7e14ee907c1a48c
Author: Michael James Gratton <mike vee net>
Date:   Sun Jun 17 18:03:09 2018 +1000

    Implement undo-able account removal.

 src/client/accounts/account-dialog.vala            |   4 +-
 src/client/accounts/account-manager.vala           | 151 ++++++++++++------
 src/client/accounts/accounts-editor-list-pane.vala | 105 ++++++++++++-
 .../accounts/accounts-editor-remove-pane.vala      |  19 +--
 src/client/accounts/accounts-editor.vala           |  18 +--
 src/client/application/geary-application.vala      |   1 +
 src/client/application/geary-controller.vala       |  63 ++++++--
 ui/accounts_editor_list_pane.ui                    |  65 ++++----
 ui/accounts_editor_remove_pane.ui                  | 171 +++++++--------------
 9 files changed, 366 insertions(+), 231 deletions(-)
---
diff --git a/src/client/accounts/account-dialog.vala b/src/client/accounts/account-dialog.vala
index 18ddb668..971269c8 100644
--- a/src/client/accounts/account-dialog.vala
+++ b/src/client/accounts/account-dialog.vala
@@ -138,8 +138,8 @@ public class AccountDialog : Gtk.Dialog {
         assert(account != null); // Should not be able to happen since we checked earlier.
 
         // Remove account, then set the page back to the account list.
-        this.application.controller.remove_account_async.begin(account, null, () => {
-            account_list_pane.present(); });
+        //this.application.controller.remove_account_async.begin(account, null, () => {
+        //    account_list_pane.present(); });
     }
 
     private void on_save_add_or_edit(Geary.AccountInformation info) {
diff --git a/src/client/accounts/account-manager.vala b/src/client/accounts/account-manager.vala
index 9e654466..aa10d711 100644
--- a/src/client/accounts/account-manager.vala
+++ b/src/client/accounts/account-manager.vala
@@ -48,6 +48,7 @@ public enum CredentialsProvider {
 
 errordomain AccountError {
     INVALID,
+    LOCAL_REMOVED,
     GOA_REMOVED;
 }
 
@@ -69,6 +70,7 @@ public class AccountManager : GLib.Object {
 
 
     private const string ACCOUNT_CONFIG_GROUP = "AccountInformation";
+    private const string ACCOUNT_MANAGER_GROUP = "AccountManager";
     private const string IMAP_CONFIG_GROUP = "IMAP";
     private const string SMTP_CONFIG_GROUP = "SMTP";
 
@@ -82,6 +84,7 @@ public class AccountManager : GLib.Object {
     private const string ORDINAL_KEY = "ordinal";
     private const string PREFETCH_PERIOD_DAYS_KEY = "prefetch_period_days";
     private const string PRIMARY_EMAIL_KEY = "primary_email";
+    private const string REMOVED_KEY = "removed";
     private const string REAL_NAME_KEY = "real_name";
     private const string SAVE_DRAFTS_KEY = "save_drafts";
     private const string SAVE_SENT_MAIL_KEY = "save_sent_mail";
@@ -151,6 +154,10 @@ public class AccountManager : GLib.Object {
     private Gee.Map<string,AccountState> accounts =
         new Gee.HashMap<string,AccountState>();
 
+    private Gee.LinkedList<Geary.AccountInformation> removed =
+        new Gee.LinkedList<Geary.AccountInformation>();
+
+
     private GearyApplication application;
     private GLib.File user_config_dir;
     private GLib.File user_data_dir;
@@ -284,9 +291,6 @@ public class AccountManager : GLib.Object {
                         } else {
                             set_available(info, false);
                         }
-                    } catch (AccountError.GOA_REMOVED err) {
-                        // Since the GOA account for this account does
-                        // not longer exist, remove it.
                     } catch (GLib.Error err) {
                         report_problem(
                             new Geary.ProblemReport(
@@ -316,6 +320,73 @@ public class AccountManager : GLib.Object {
         }
     }
 
+    /**
+     * Removes an account from the manager's set of known accounts.
+     *
+     * This removes the account from the known set, marks the account
+     * as deleted, and queues it for deletion. The account will not
+     * actually be deleted until {@link expunge_accounts} is called,
+     * and until then the account can be re-added using {@link
+     * restore_account}.
+     */
+    public async void remove_account(Geary.AccountInformation account,
+                                     GLib.Cancellable cancellable)
+        throws GLib.Error {
+        this.accounts.unset(account.id);
+        this.removed.add(account);
+        yield save_account(account, cancellable);
+        account_removed(account);
+    }
+
+    /**
+     * Restores an account that has previously been removed.
+     *
+     * This restores an account previously removed via a call to
+     * {@link remove_account}, adding it back to the known set, as
+     * long as {@link expunge_accounts} has not been called since
+     * he account was removed.
+     */
+    public async void restore_account(Geary.AccountInformation account,
+                                      GLib.Cancellable cancellable)
+        throws GLib.Error {
+        if (this.removed.remove(account)) {
+            yield save_account(account, cancellable);
+            set_enabled(account, true);
+        }
+    }
+
+    /**
+     * Deletes all local data for all accounts that have been removed.
+     */
+    public async void expunge_accounts(GLib.Cancellable? cancellable)
+        throws GLib.Error {
+        while (!this.removed.is_empty && !cancellable.is_cancelled()) {
+            yield delete_account(this.removed.remove_at(0), cancellable);
+        }
+    }
+
+    public async void save_account(Geary.AccountInformation info,
+                                   GLib.Cancellable? cancellable)
+        throws GLib.Error {
+        // Ensure only one async task is saving an info at once, since
+        // at least the Engine can cause multiple saves to be called
+        // in quick succession when updating special folder config.
+        int token = yield info.write_lock.claim_async(cancellable);
+
+        GLib.Error? thrown = null;
+        try {
+            yield save_account_locked(info, cancellable);
+        } catch (GLib.Error err) {
+            thrown = err;
+        }
+
+        info.write_lock.release(ref token);
+
+        if (thrown != null) {
+            throw thrown;
+        }
+    }
+
     /**
      * Loads an account info from a config directory.
      *
@@ -438,29 +509,17 @@ public class AccountManager : GLib.Object {
 
         info.save_drafts = config.get_bool(SAVE_DRAFTS_KEY, true);
 
-        return info;
-    }
-
-    public async void save_account(Geary.AccountInformation info,
-                                   GLib.Cancellable? cancellable)
-        throws GLib.Error {
-        // Ensure only one async task is saving an info at once, since
-        // at least the Engine can cause multiple saves to be called
-        // in quick succession when updating special folder config.
-        int token = yield info.write_lock.claim_async(cancellable);
-
-        GLib.Error? thrown = null;
-        try {
-            yield save_account_locked(info, cancellable);
-        } catch (GLib.Error err) {
-            thrown = err;
+        // If the account has been removed, add it to the removed list
+        // and bail out
+        Geary.ConfigFile.Group manager_config =
+            config_file.get_group(ACCOUNT_MANAGER_GROUP);
+        if (manager_config.exists &&
+            manager_config.get_bool(REMOVED_KEY, false)) {
+            this.removed.add(info);
+            throw new AccountError.LOCAL_REMOVED("Account marked for removal");
         }
 
-        info.write_lock.release(ref token);
-
-        if (thrown != null) {
-            throw thrown;
-        }
+        return info;
     }
 
     private async void save_account_locked(Geary.AccountInformation info,
@@ -483,6 +542,16 @@ public class AccountManager : GLib.Object {
             debug("Could not load existing config file: %s", err.message);
         }
 
+        // If the account has been removed, set it as such. Otherwise
+        // ensure it is not set as such.
+        Geary.ConfigFile.Group manager_config =
+            config_file.get_group(ACCOUNT_MANAGER_GROUP);
+        if (this.removed.contains(info)) {
+            manager_config.set_bool(REMOVED_KEY, true);
+        } else if (manager_config.exists) {
+            manager_config.remove();
+        }
+
         Geary.ConfigFile.Group config = config_file.get_group(ACCOUNT_CONFIG_GROUP);
         if (info.imap is LocalServiceInformation) {
             config.set_string(
@@ -548,24 +617,9 @@ public class AccountManager : GLib.Object {
         yield config_file.save(cancellable);
     }
 
-    /**
-     * Removes an account's configuration, storage and auth tokens.
-     */
-    public async void remove_account(Geary.AccountInformation info,
-                                     GLib.Cancellable? cancellable)
+    private async void delete_account(Geary.AccountInformation info,
+                                      GLib.Cancellable? cancellable)
         throws GLib.Error {
-        if (info.data_dir != null) {
-            yield Geary.Files.recursive_delete_async(
-                info.data_dir, GLib.Priority.DEFAULT, cancellable
-            );
-        }
-
-        if (info.config_dir != null) {
-            yield Geary.Files.recursive_delete_async(
-                info.config_dir, GLib.Priority.DEFAULT, cancellable
-            );
-        }
-
         SecretMediator? mediator = info.imap.mediator as SecretMediator;
         if (mediator != null) {
             try {
@@ -584,8 +638,19 @@ public class AccountManager : GLib.Object {
             }
         }
 
-        this.accounts.unset(info.id);
-        account_removed(info);
+        if (info.data_dir != null) {
+            yield Geary.Files.recursive_delete_async(
+                info.data_dir, GLib.Priority.LOW, cancellable
+            );
+        }
+
+        // Delete config last so if there are any errors above, it
+        // will be re-tried at next startup.
+        if (info.config_dir != null) {
+            yield Geary.Files.recursive_delete_async(
+                info.config_dir, GLib.Priority.LOW, cancellable
+            );
+        }
     }
 
     private inline AccountState lookup_state(Geary.AccountInformation account) {
diff --git a/src/client/accounts/accounts-editor-list-pane.vala 
b/src/client/accounts/accounts-editor-list-pane.vala
index bc47fb5e..a1aaf0ac 100644
--- a/src/client/accounts/accounts-editor-list-pane.vala
+++ b/src/client/accounts/accounts-editor-list-pane.vala
@@ -32,9 +32,16 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
 
     private AccountManager accounts { get; private set; }
 
+    private Application.CommandStack commands {
+        get; private set; default = new Application.CommandStack();
+    }
+
     [GtkChild]
     private Gtk.HeaderBar header;
 
+    [GtkChild]
+    private Gtk.Overlay osd_overlay;
+
     [GtkChild]
     private Gtk.ListBox accounts_list;
 
@@ -59,9 +66,17 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
         this.accounts.account_added.connect(on_account_added);
         this.accounts.account_status_changed.connect(on_account_status_changed);
         this.accounts.account_removed.connect(on_account_removed);
+
+        this.commands.executed.connect(on_execute);
+        this.commands.undone.connect(on_undo);
+        this.commands.redone.connect(on_execute);
     }
 
     public override void destroy() {
+        this.commands.executed.disconnect(on_execute);
+        this.commands.undone.disconnect(on_undo);
+        this.commands.redone.disconnect(on_execute);
+
         this.accounts.account_added.disconnect(on_account_added);
         this.accounts.account_status_changed.disconnect(on_account_status_changed);
         this.accounts.account_removed.disconnect(on_account_removed);
@@ -80,14 +95,43 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
     internal void remove_account(Geary.AccountInformation account) {
         AccountListRow? row = get_account_row(account);
         if (row != null) {
-            this.accounts_list.remove(row);
+            this.commands.execute.begin(
+                new RemoveAccountCommand(account, this.accounts),
+                null
+            );
         }
     }
 
+    internal void pane_shown() {
+        update_actions();
+    }
+
+    internal void undo() {
+        this.commands.undo.begin(null);
+    }
+
+    internal void redo() {
+        this.commands.redo.begin(null);
+    }
+
     internal Gtk.HeaderBar get_header() {
         return this.header;
     }
-    
+
+    private void add_notification(InAppNotification notification) {
+        this.osd_overlay.add_overlay(notification);
+        notification.show();
+    }
+
+    private void update_actions() {
+        this.editor.get_action(GearyController.ACTION_UNDO).set_enabled(
+            this.commands.can_undo
+        );
+        this.editor.get_action(GearyController.ACTION_REDO).set_enabled(
+            this.commands.can_redo
+        );
+    }
+
     private AccountListRow? get_account_row(Geary.AccountInformation account) {
         AccountListRow? row = null;
         this.accounts_list.foreach((child) => {
@@ -113,7 +157,26 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
     }
 
     private void on_account_removed(Geary.AccountInformation account) {
-        remove_account(account);
+        AccountListRow? row = get_account_row(account);
+        if (row != null) {
+            this.accounts_list.remove(row);
+        }
+    }
+
+    private void on_execute(Application.Command command) {
+        InAppNotification ian = new InAppNotification(command.executed_label);
+        ian.set_button(_("Undo"), "win." + GearyController.ACTION_UNDO);
+        add_notification(ian);
+
+        update_actions();
+    }
+
+    private void on_undo(Application.Command command) {
+        InAppNotification ian = new InAppNotification(command.undone_label);
+        ian.set_button(_("Redo"), "win." + GearyController.ACTION_REDO);
+        add_notification(ian);
+
+        update_actions();
     }
 
     [GtkCallback]
@@ -213,3 +276,39 @@ private class Accounts.AccountListRow : EditorRow<EditorListPane> {
     }
 
 }
+
+
+internal class Accounts.RemoveAccountCommand : Application.Command {
+
+
+    private Geary.AccountInformation account;
+    private AccountManager manager;
+
+
+    public RemoveAccountCommand(Geary.AccountInformation account,
+                                AccountManager manager) {
+        this.account = account;
+        this.manager = manager;
+
+        // Translators: Notification shown after removing an
+        // account. The string substitution is the name of the
+        // account.
+        this.executed_label = _("Account “%s” removed").printf(account.nickname);
+
+        // Translators: Notification shown after removing an account
+        // is undone. The string substitution is the name of the
+        // account.
+        this.undone_label = _("Account “%s” restored").printf(account.nickname);
+    }
+
+    public async override void execute(GLib.Cancellable? cancellable)
+        throws GLib.Error {
+        yield this.manager.remove_account(this.account, cancellable);
+    }
+
+    public async override void undo(GLib.Cancellable? cancellable)
+        throws GLib.Error {
+        yield this.manager.restore_account(this.account, cancellable);
+    }
+
+}
diff --git a/src/client/accounts/accounts-editor-remove-pane.vala 
b/src/client/accounts/accounts-editor-remove-pane.vala
index 57542e57..d8976205 100644
--- a/src/client/accounts/accounts-editor-remove-pane.vala
+++ b/src/client/accounts/accounts-editor-remove-pane.vala
@@ -19,21 +19,9 @@ internal class Accounts.EditorRemovePane : Gtk.Grid, EditorPane, AccountPane {
     [GtkChild]
     private Gtk.HeaderBar header;
 
-    [GtkChild]
-    private Gtk.Stack confirm_stack;
-
     [GtkChild]
     private Gtk.Label warning_label;
 
-    [GtkChild]
-    private Gtk.Spinner remove_spinner;
-
-    [GtkChild]
-    private Gtk.Label remove_label;
-
-    [GtkChild]
-    private Gtk.Button remove_button;
-
 
     public EditorRemovePane(Editor editor, Geary.AccountInformation account) {
         this.editor = editor;
@@ -42,9 +30,6 @@ internal class Accounts.EditorRemovePane : Gtk.Grid, EditorPane, AccountPane {
         this.warning_label.set_text(
             this.warning_label.get_text().printf(account.nickname)
         );
-        this.remove_label.set_text(
-            this.remove_label.get_text().printf(account.nickname)
-        );
 
         this.account.information_changed.connect(on_account_changed);
         update_header();
@@ -64,9 +49,7 @@ internal class Accounts.EditorRemovePane : Gtk.Grid, EditorPane, AccountPane {
 
     [GtkCallback]
     private void on_remove_button_clicked() {
-        this.remove_button.set_sensitive(false);
-        this.remove_spinner.start();
-        this.confirm_stack.set_visible_child_name("remove");
+        this.editor.remove_account(this.account);
     }
 
     [GtkCallback]
diff --git a/src/client/accounts/accounts-editor.vala b/src/client/accounts/accounts-editor.vala
index b293f38e..0079fa08 100644
--- a/src/client/accounts/accounts-editor.vala
+++ b/src/client/accounts/accounts-editor.vala
@@ -27,15 +27,11 @@ public class Accounts.Editor : Gtk.Dialog {
     }
 
 
-    /** The current account being edited, if any. */
-    private Geary.AccountInformation selected_account {
-        get; private set; default = null;
-    }
-
     private SimpleActionGroup actions = new SimpleActionGroup();
 
 
     private Gtk.Stack editor_panes = new Gtk.Stack();
+    private EditorListPane editor_list_pane;
 
     private Gee.LinkedList<EditorPane> editor_pane_stack =
         new Gee.LinkedList<EditorPane>();
@@ -65,7 +61,8 @@ public class Accounts.Editor : Gtk.Dialog {
         get_action(GearyController.ACTION_UNDO).set_enabled(false);
         get_action(GearyController.ACTION_REDO).set_enabled(false);
 
-        push(new EditorListPane(this));
+        this.editor_list_pane = new EditorListPane(this);
+        push(this.editor_list_pane);
     }
 
     public override void destroy() {
@@ -104,16 +101,17 @@ public class Accounts.Editor : Gtk.Dialog {
         int prev_index = this.editor_pane_stack.index_of(current) - 1;
         EditorPane prev = this.editor_pane_stack.get(prev_index);
         this.editor_panes.set_visible_child(prev);
-
-        if (prev_index == 0) {
-            this.selected_account = null;
-        }
     }
 
     internal GLib.SimpleAction get_action(string name) {
         return (GLib.SimpleAction) this.actions.lookup_action(name);
     }
 
+    internal void remove_account(Geary.AccountInformation account) {
+        this.editor_panes.set_visible_child(this.editor_list_pane);
+        this.editor_list_pane.remove_account(account);
+    }
+
     private inline EditorPane? get_current_pane() {
         return this.editor_panes.get_visible_child() as EditorPane;
     }
diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala
index 96e831dc..a3f8149e 100644
--- a/src/client/application/geary-application.vala
+++ b/src/client/application/geary-application.vala
@@ -425,6 +425,7 @@ public class GearyApplication : Gtk.Application {
         Accounts.Editor editor = new Accounts.Editor(this, get_active_window());
         editor.run();
         editor.destroy();
+        this.controller.expunge_accounts.begin();
     }
 
     private void on_activate_compose() {
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index ea91ead0..b26c757f 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -105,6 +105,10 @@ public class GearyController : Geary.BaseObject {
     private Gee.Map<Geary.AccountInformation,AccountContext> accounts =
         new Gee.HashMap<Geary.AccountInformation,AccountContext>();
 
+    // Created when controller is opened, cancelled and nulled out
+    // when closed.
+    private GLib.Cancellable? open_cancellable = null;
+
     private Geary.Folder? current_folder = null;
     private Cancellable cancellable_folder = new Cancellable();
     private Cancellable cancellable_search = new Cancellable();
@@ -207,6 +211,8 @@ public class GearyController : Geary.BaseObject {
 
         apply_app_menu_fix();
 
+        this.open_cancellable = new GLib.Cancellable();
+
         // Listen for attempts to close the application.
         this.application.exiting.connect(on_application_exiting);
 
@@ -246,7 +252,6 @@ public class GearyController : Geary.BaseObject {
         enable_message_buttons(false);
 
         engine.account_available.connect(on_account_available);
-        engine.account_unavailable.connect(on_account_unavailable);
         engine.untrusted_host.connect(on_untrusted_host);
 
         // Connect to various UI signals.
@@ -308,6 +313,9 @@ public class GearyController : Geary.BaseObject {
         this.account_manager.account_status_changed.connect(
             on_account_status_changed
         );
+        this.account_manager.account_removed.connect(
+            on_account_removed
+        );
 
         try {
             yield this.account_manager.connect_libsecret(cancellable);
@@ -332,6 +340,10 @@ public class GearyController : Geary.BaseObject {
         } catch (Error e) {
             warning("Error opening Geary.Engine instance: %s", e.message);
         }
+
+        // Expunge any deleted accounts in the background, so we're
+        // not blocking the app continuing to open.
+        this.expunge_accounts.begin();
     }
 
     /**
@@ -339,8 +351,12 @@ public class GearyController : Geary.BaseObject {
      * re-opened.
      */
     public async void close_async() {
+        // Cancel internal processes early so they don't block
+        // shutdown
+        this.open_cancellable.cancel();
+        this.open_cancellable = null;
+
         Geary.Engine.instance.account_available.disconnect(on_account_available);
-        Geary.Engine.instance.account_unavailable.disconnect(on_account_unavailable);
         Geary.Engine.instance.untrusted_host.disconnect(on_untrusted_host);
 
         // Release folder and conversations in the main window
@@ -455,6 +471,9 @@ public class GearyController : Geary.BaseObject {
         this.account_manager.account_status_changed.disconnect(
             on_account_status_changed
         );
+        this.account_manager.account_removed.disconnect(
+            on_account_removed
+        );
         this.account_manager = null;
 
         this.application.remove_window(this.main_window);
@@ -509,16 +528,11 @@ public class GearyController : Geary.BaseObject {
         }
     }
 
-    /**
-     * Closes an account and deletes it from disk.
-     */
-    public async void remove_account_async(Geary.AccountInformation info,
-                                           Cancellable? cancellable = null) {
+    /** Expunges removed accounts while the controller remains open. */
+    internal async void expunge_accounts() {
         try {
-            yield close_account(info);
-            this.application.engine.remove_account(info);
-            yield this.account_manager.remove_account(info, cancellable);
-        } catch (Error err) {
+            yield this.account_manager.expunge_accounts(this.open_cancellable);
+        } catch (GLib.Error err) {
             report_problem(
                 new Geary.ProblemReport(Geary.ProblemType.GENERIC_ERROR, err)
             );
@@ -3129,6 +3143,29 @@ public class GearyController : Geary.BaseObject {
         }
     }
 
+    private void on_account_removed(Geary.AccountInformation removed) {
+        debug("%s: Closing account for removal", removed.id);
+        this.close_account.begin(
+            removed,
+            (obj, res) => {
+                this.close_account.end(res);
+                debug("%s: Account closed", removed.id);
+                try {
+                    this.application.engine.remove_account(removed);
+                    debug("%s: Account removed from engine", removed.id);
+                } catch (GLib.Error err) {
+                    report_problem(
+                        new Geary.AccountProblemReport(
+                            Geary.ProblemType.GENERIC_ERROR,
+                            removed,
+                            err
+                        )
+                    );
+                }
+            }
+        );
+    }
+
     private void on_scan_completed() {
         // Done scanning.  Check if we have enough messages to fill
         // the conversation list; if not, trigger a load_more();
@@ -3152,10 +3189,6 @@ public class GearyController : Geary.BaseObject {
         );
     }
 
-    private void on_account_unavailable(Geary.AccountInformation info) {
-        this.close_account.begin(info);
-    }
-
     private void on_save_attachments(Gee.Collection<Geary.Attachment> attachments) {
         GLib.Cancellable? cancellable = null;
         if (this.current_account != null) {
diff --git a/ui/accounts_editor_list_pane.ui b/ui/accounts_editor_list_pane.ui
index b34def99..db6c70d3 100644
--- a/ui/accounts_editor_list_pane.ui
+++ b/ui/accounts_editor_list_pane.ui
@@ -6,53 +6,62 @@
     <property name="visible">True</property>
     <property name="can_focus">False</property>
     <child>
-      <object class="GtkScrolledWindow">
+      <object class="GtkOverlay" id="osd_overlay">
         <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="hexpand">True</property>
-        <property name="vexpand">True</property>
-        <property name="hscrollbar_policy">never</property>
-        <property name="min_content_height">400</property>
+        <property name="can_focus">False</property>
         <child>
-          <object class="GtkViewport">
+          <object class="GtkScrolledWindow">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="can_focus">True</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+            <property name="hscrollbar_policy">never</property>
+            <property name="min_content_height">400</property>
             <child>
-              <object class="GtkGrid">
+              <object class="GtkViewport">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
                 <child>
-                  <object class="GtkFrame">
+                  <object class="GtkGrid">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
-                    <property name="hexpand">True</property>
-                    <property name="vexpand">True</property>
-                    <property name="label_xalign">0</property>
-                    <property name="shadow_type">in</property>
                     <child>
-                      <object class="GtkListBox" id="accounts_list">
-                        <property name="width_request">0</property>
+                      <object class="GtkFrame">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
-                        <property name="selection_mode">none</property>
-                        <signal name="row-activated" handler="on_accounts_list_row_activated" swapped="no"/>
+                        <property name="hexpand">True</property>
+                        <property name="vexpand">True</property>
+                        <property name="label_xalign">0</property>
+                        <property name="shadow_type">in</property>
+                        <child>
+                          <object class="GtkListBox" id="accounts_list">
+                            <property name="width_request">0</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="selection_mode">none</property>
+                            <signal name="row-activated" handler="on_accounts_list_row_activated" 
swapped="no"/>
+                          </object>
+                        </child>
+                        <child type="label_item">
+                          <placeholder/>
+                        </child>
                       </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">0</property>
+                      </packing>
                     </child>
-                    <child type="label_item">
-                      <placeholder/>
-                    </child>
+                    <style>
+                      <class name="geary-account-view"/>
+                    </style>
                   </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">0</property>
-                  </packing>
                 </child>
-                <style>
-                  <class name="geary-account-view"/>
-                </style>
               </object>
             </child>
           </object>
+          <packing>
+            <property name="index">-1</property>
+          </packing>
         </child>
       </object>
       <packing>
diff --git a/ui/accounts_editor_remove_pane.ui b/ui/accounts_editor_remove_pane.ui
index ba785dd2..e11dd170 100644
--- a/ui/accounts_editor_remove_pane.ui
+++ b/ui/accounts_editor_remove_pane.ui
@@ -3,7 +3,6 @@
 <interface>
   <requires lib="gtk+" version="3.20"/>
   <template class="AccountsEditorRemovePane" parent="GtkGrid">
-    <property name="name">1</property>
     <property name="visible">True</property>
     <property name="can_focus">False</property>
     <property name="valign">center</property>
@@ -14,116 +13,6 @@
         <property name="valign">start</property>
         <property name="vexpand">True</property>
         <property name="row_spacing">32</property>
-        <child>
-          <object class="GtkStack" id="confirm_stack">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <child>
-              <object class="GtkGrid">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="row_spacing">6</property>
-                <property name="column_spacing">18</property>
-                <child>
-                  <object class="GtkImage">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="icon_name">dialog-warning-symbolic</property>
-                    <property name="icon_size">6</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">0</property>
-                    <property name="height">2</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="warning_label">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="vexpand">True</property>
-                    <property name="label" translatable="yes" comments="This title is shown to users when 
confirming if they want to remove an account. The string substitution is replaced with the account's 
name.">Confirm removing: %s</property>
-                    <attributes>
-                      <attribute name="weight" value="bold"/>
-                    </attributes>
-                    <style>
-                      <class name="title"/>
-                    </style>
-                  </object>
-                  <packing>
-                    <property name="left_attach">1</property>
-                    <property name="top_attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="label" translatable="yes">Removing an account will delete it permanently 
from your computer. This cannot be un-done.</property>
-                    <property name="wrap">True</property>
-                    <property name="xalign">0</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">1</property>
-                    <property name="top_attach">1</property>
-                  </packing>
-                </child>
-              </object>
-              <packing>
-                <property name="name">confirm</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkGrid">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="row_spacing">6</property>
-                <property name="column_spacing">18</property>
-                <child>
-                  <object class="GtkSpinner" id="remove_spinner">
-                    <property name="width_request">16</property>
-                    <property name="height_request">16</property>
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">center</property>
-                    <property name="valign">center</property>
-                    <property name="margin_left">16</property>
-                    <property name="margin_right">16</property>
-                    <property name="active">True</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="remove_label">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="hexpand">True</property>
-                    <property name="vexpand">True</property>
-                    <property name="label" translatable="yes" comments="This title is shown to users when 
actually deleting an account. The string substitution is replaced with the account's name.">Please wait, 
removing account %s…</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">1</property>
-                    <property name="top_attach">0</property>
-                  </packing>
-                </child>
-              </object>
-              <packing>
-                <property name="name">remove</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="left_attach">0</property>
-            <property name="top_attach">0</property>
-          </packing>
-        </child>
         <child>
           <object class="GtkButtonBox">
             <property name="visible">True</property>
@@ -131,7 +20,7 @@
             <property name="layout_style">end</property>
             <child>
               <object class="GtkButton" id="remove_button">
-                <property name="label" translatable="yes" comments="This is the remove account button in the 
account settings.">Remove Permanently</property>
+                <property name="label" translatable="yes" comments="This is the remove account button in the 
account settings.">Remove Account</property>
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="receives_default">True</property>
@@ -156,6 +45,64 @@
             <property name="top_attach">1</property>
           </packing>
         </child>
+        <child>
+          <object class="GtkGrid">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="row_spacing">6</property>
+            <property name="column_spacing">18</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="icon_name">dialog-warning-symbolic</property>
+                <property name="icon_size">6</property>
+              </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">0</property>
+                <property name="height">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="warning_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="halign">start</property>
+                <property name="vexpand">True</property>
+                <property name="label" translatable="yes" comments="This title is shown to users when 
confirming if they want to remove an account. The string substitution is replaced with the account's 
name.">Confirm removing: %s</property>
+                <attributes>
+                  <attribute name="weight" value="bold"/>
+                </attributes>
+                <style>
+                  <class name="title"/>
+                </style>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="top_attach">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="halign">start</property>
+                <property name="label" translatable="yes">Removing an account will remove it from Geary and 
delete locally cached email data from your computer, but not from your service provider.</property>
+                <property name="wrap">True</property>
+                <property name="xalign">0</property>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="top_attach">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
         <style>
           <class name="geary-account-view"/>
         </style>



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