[geary/wip/714104-refine-account-dialog: 69/69] Implement initial server editing and validation



commit 528b011feb4f9f7269c5d1fae569f6c62cdc6af9
Author: Michael Gratton <mike vee net>
Date:   Fri Nov 30 23:48:35 2018 +1100

    Implement initial server editing and validation

 src/client/accounts/accounts-editor-row.vala       |  41 ++-
 .../accounts/accounts-editor-servers-pane.vala     | 285 +++++++++++++++----
 src/engine/api/geary-credentials.vala              |   4 +
 src/engine/api/geary-engine.vala                   |  53 ++++
 src/engine/api/geary-service-information.vala      |  20 ++
 ui/accounts_editor_servers_pane.ui                 | 308 +++++++++++----------
 6 files changed, 512 insertions(+), 199 deletions(-)
---
diff --git a/src/client/accounts/accounts-editor-row.vala b/src/client/accounts/accounts-editor-row.vala
index 11a49f02..b0d33777 100644
--- a/src/client/accounts/accounts-editor-row.vala
+++ b/src/client/accounts/accounts-editor-row.vala
@@ -302,13 +302,40 @@ internal class Accounts.SmtpAuthComboBox : Gtk.ComboBoxText {
 }
 
 
+/**
+ * Displaying and manages validation of popover-based forms.
+ */
 internal class Accounts.EditorPopover : Gtk.Popover {
 
 
-    internal Gtk.Grid layout { get; private set; default = new Gtk.Grid(); }
+    internal Gtk.Grid layout {
+        get; private set; default = new Gtk.Grid();
+    }
+
+    internal Gee.Collection<Components.Validator> validators {
+        owned get { return this.validator_backing.read_only_view; }
+    }
 
     protected Gtk.Widget popup_focus = null;
 
+    private Gee.Collection<Components.Validator> validator_backing =
+        new Gee.LinkedList<Components.Validator>();
+
+
+    /**
+     * Emitted when a validated widget is activated all are valid.
+     *
+     * This signal will be emitted when all of the following are true:
+     *
+     * 1. At least one validator has been added to the popover
+     * 2. The user activates an entry that is being monitored by a
+     *    validator
+     * 3. The validation for the has completed (i.e. is not in
+     *    progress)
+     * 4. All validators are in the valid state
+     */
+    public override signal void valid_activated();
+
 
     public EditorPopover() {
         get_style_context().add_class("geary-editor");
@@ -352,6 +379,11 @@ internal class Accounts.EditorPopover : Gtk.Popover {
         }
     }
 
+    public void add_validator(Components.Validator validator) {
+        validator.activated.connect(on_validator_activated);
+        this.validator_backing.add(validator);
+    }
+
     public void add_labelled_row(string label, Gtk.Widget value) {
         Gtk.Label label_widget = new Gtk.Label(label);
         label_widget.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
@@ -366,6 +398,13 @@ internal class Accounts.EditorPopover : Gtk.Popover {
         destroy();
     }
 
+    private void on_validator_activated() {
+        if (Geary.traverse(this.validator_backing).all(
+                (v) => v.state == Components.Validator.Validity.VALID)) {
+            valid_activated();
+        }
+    }
+
 }
 
 
diff --git a/src/client/accounts/accounts-editor-servers-pane.vala 
b/src/client/accounts/accounts-editor-servers-pane.vala
index b10e640e..9d166f2f 100644
--- a/src/client/accounts/accounts-editor-servers-pane.vala
+++ b/src/client/accounts/accounts-editor-servers-pane.vala
@@ -17,6 +17,8 @@ internal class Accounts.EditorServersPane : Gtk.Grid, EditorPane, AccountPane {
 
     protected weak Accounts.Editor editor { get; set; }
 
+    private Geary.Engine engine;
+
     // These are copies of the originals that can be updated before
     // validating on apply, without breaking anything.
     private Geary.ServiceInformation imap_mutable;
@@ -26,6 +28,9 @@ internal class Accounts.EditorServersPane : Gtk.Grid, EditorPane, AccountPane {
     [GtkChild]
     private Gtk.HeaderBar header;
 
+    [GtkChild]
+    private Gtk.Overlay osd_overlay;
+
     [GtkChild]
     private Gtk.Grid pane_content;
 
@@ -41,6 +46,13 @@ internal class Accounts.EditorServersPane : Gtk.Grid, EditorPane, AccountPane {
     [GtkChild]
     private Gtk.ListBox sending_list;
 
+    [GtkChild]
+    private Gtk.Button apply_button;
+
+    [GtkChild]
+    private Gtk.Spinner apply_spinner;
+
+    private SaveDraftsRow save_drafts;
     private ServiceSmtpAuthRow smtp_auth;
     private ServiceLoginRow smtp_login;
 
@@ -48,12 +60,12 @@ internal class Accounts.EditorServersPane : Gtk.Grid, EditorPane, AccountPane {
     public EditorServersPane(Editor editor, Geary.AccountInformation account) {
         this.editor = editor;
         this.account = account;
-
-        this.pane_content.set_focus_vadjustment(this.pane_adjustment);
-
+        this.engine = ((GearyApplication) editor.application).engine;
         this.imap_mutable = account.imap.temp_copy();
         this.smtp_mutable = account.smtp.temp_copy();
 
+        this.pane_content.set_focus_vadjustment(this.pane_adjustment);
+
         this.details_list.set_header_func(Editor.seperator_headers);
         // Only add an account provider if it is esoteric enough.
         if (this.account.imap.mediator is GoaMediator) {
@@ -69,20 +81,23 @@ internal class Accounts.EditorServersPane : Gtk.Grid, EditorPane, AccountPane {
         service_provider.set_dim_label(true);
         service_provider.activatable = false;
         this.details_list.add(service_provider);
-        this.details_list.add(new SaveDraftsRow(this.account));
+        this.save_drafts = new SaveDraftsRow(this.account);
+        this.details_list.add(this.save_drafts);
 
         this.receiving_list.set_header_func(Editor.seperator_headers);
-        this.receiving_list.add(new ServiceHostRow(account, account.imap));
-        this.receiving_list.add(new ServiceSecurityRow(account, account.imap));
-        this.receiving_list.add(new ServiceLoginRow(account, account.imap));
+        this.receiving_list.add(new ServiceHostRow(account, this.imap_mutable));
+        this.receiving_list.add(new ServiceSecurityRow(account, this.imap_mutable));
+        this.receiving_list.add(new ServiceLoginRow(account, this.imap_mutable));
 
         this.sending_list.set_header_func(Editor.seperator_headers);
-        this.sending_list.add(new ServiceHostRow(account, account.smtp));
-        this.sending_list.add(new ServiceSecurityRow(account, account.smtp));
-        this.smtp_auth = new ServiceSmtpAuthRow(account, account.smtp);
+        this.sending_list.add(new ServiceHostRow(account, this.smtp_mutable));
+        this.sending_list.add(new ServiceSecurityRow(account, this.smtp_mutable));
+        this.smtp_auth = new ServiceSmtpAuthRow(
+            account, this.smtp_mutable, this.imap_mutable
+        );
         this.smtp_auth.value.changed.connect(on_smtp_auth_changed);
         this.sending_list.add(this.smtp_auth);
-        this.smtp_login = new ServiceLoginRow(account, account.smtp);
+        this.smtp_login = new ServiceLoginRow(account, this.smtp_mutable);
         this.sending_list.add(this.smtp_login);
 
         this.account.information_changed.connect(on_account_changed);
@@ -99,6 +114,110 @@ internal class Accounts.EditorServersPane : Gtk.Grid, EditorPane, AccountPane {
         return this.header;
     }
 
+    private async void save(GLib.Cancellable? cancellable) {
+        this.apply_button.set_sensitive(false);
+        this.apply_spinner.show();
+        this.apply_spinner.start();
+
+        // Only need to validate if a generic account
+        bool is_valid = true;
+        bool has_changed = false;
+        if (this.account.service_provider == Geary.ServiceProvider.OTHER) {
+            is_valid = yield validate(cancellable);
+
+            if (is_valid) {
+                has_changed = this.engine.update_account_service(
+                    this.account, imap_mutable
+                );
+                has_changed = this.engine.update_account_service(
+                    this.account, smtp_mutable
+                );
+            }
+        }
+
+        if (is_valid) {
+            if (this.save_drafts.value_changed) {
+                this.account.save_drafts = this.save_drafts.value.state;
+                has_changed = true;
+            }
+
+            if (has_changed) {
+                this.account.information_changed();
+            }
+
+            this.editor.pop();
+        }
+
+        this.apply_button.set_sensitive(true);
+        this.apply_spinner.stop();
+        this.apply_spinner.hide();
+    }
+
+    private async bool validate(GLib.Cancellable? cancellable) {
+        string message = "";
+        bool imap_valid = false;
+        try {
+            yield this.engine.validate_imap(
+                this.account, this.imap_mutable, cancellable
+            );
+            imap_valid = true;
+        } catch (Geary.ImapError.UNAUTHENTICATED err) {
+            debug("Error authenticating IMAP service: %s", err.message);
+            // Translators: In-app notification label
+            message = _("Check your receiving login and password");
+        } catch (GLib.Error err) {
+            debug("Error validating IMAP service: %s", err.message);
+            // Translators: In-app notification label
+            message = _("Check your receiving server details");
+        }
+
+        bool smtp_valid = false;
+        if (imap_valid) {
+            debug("Validating SMTP...");
+            try {
+                yield this.engine.validate_smtp(
+                    this.account,
+                    this.smtp_mutable,
+                    this.imap_mutable.credentials,
+                    cancellable
+                );
+                smtp_valid = true;
+            } catch (Geary.SmtpError.AUTHENTICATION_FAILED err) {
+                debug("Error authenticating SMTP service: %s", err.message);
+                // There was an SMTP auth error, but IMAP already
+                // succeeded, so the user probably needs to
+                // specify custom creds here
+                this.smtp_auth.value.source = Geary.SmtpCredentials.CUSTOM;
+                // Translators: In-app notification label
+                message = _("Check your sending login and password");
+            } catch (GLib.Error err) {
+                debug("Error validating SMTP service: %s", err.message);
+                    // Translators: In-app notification label
+                    message = _("Check your sending server details");
+            }
+        }
+
+        bool is_valid = imap_valid && smtp_valid;
+        debug("Validation complete, is valid: %s", is_valid.to_string());
+
+        if (!is_valid) {
+            add_notification(
+                new InAppNotification(
+                    // Translators: In-app notification label, the
+                    // string substitution is a more detailed reason.
+                    _("Account not updated: %s").printf(message)
+                )
+            );
+        }
+
+        return is_valid;
+    }
+
+    private void add_notification(InAppNotification notification) {
+        this.osd_overlay.add_overlay(notification);
+        notification.show();
+    }
+
     private void update_smtp_auth() {
         this.smtp_login.set_visible(
             this.smtp_auth.value.source == Geary.SmtpCredentials.CUSTOM
@@ -113,6 +232,7 @@ internal class Accounts.EditorServersPane : Gtk.Grid, EditorPane, AccountPane {
 
     [GtkCallback]
     private void on_apply_button_clicked() {
+        this.save.begin(null);
     }
 
     [GtkCallback]
@@ -228,21 +348,34 @@ private class Accounts.SaveDraftsRow :
     AccountRow<EditorServersPane,Gtk.Switch> {
 
 
+    public bool value_changed {
+        get { return this.initial_value != this.value.state; }
+    }
+
+    private bool initial_value;
+
+
     public SaveDraftsRow(Geary.AccountInformation account) {
+        Gtk.Switch value = new Gtk.Switch();
         base(
             account,
             // Translators: This label describes an account
             // preference.
             _("Save drafts on server"),
-            new Gtk.Switch()
+            value
         );
         set_activatable(false);
-
         update();
+        value.notify["active"].connect(on_activate);
     }
 
     public override void update() {
-        this.value.state = this.account.save_drafts;
+        this.initial_value = this.account.save_drafts;
+        this.value.state = this.initial_value;
+    }
+
+    private void on_activate() {
+        this.account.save_drafts = this.value.state;
     }
 
 }
@@ -279,58 +412,57 @@ private class Accounts.ServiceHostRow :
     }
 
     public override void activated(EditorServersPane pane) {
-        EditorPopover popover = new EditorPopover();
-
-        string? value = this.service.host;
+        string? text = get_host_text() ?? "";
         Gtk.Entry entry = new Gtk.Entry();
-        entry.set_text(value ?? "");
-        entry.set_placeholder_text(value ?? "");
+        entry.set_text(text);
+        entry.set_placeholder_text(text);
         entry.set_width_chars(20);
         entry.show();
 
+        EditorPopover popover = new EditorPopover();
         popover.set_relative_to(this.value);
         popover.layout.add(entry);
+        popover.add_validator(new Components.NetworkAddressValidator(entry));
+        popover.valid_activated.connect(on_popover_activate);
         popover.popup();
     }
 
     public override void update() {
-        string value = this.service.host;
+        string value = get_host_text();
         if (Geary.String.is_empty(value)) {
             value = _("None");
         }
+        this.value.set_text(value);
+    }
 
-        // Only show the port if it not the appropriate default port
-        bool custom_port = false;
-        int port = this.service.port;
-        Geary.TlsNegotiationMethod security = this.service.transport_security;
-        switch (this.service.protocol) {
-        case Geary.Protocol.IMAP:
-            if (!(port == Geary.Imap.IMAP_PORT &&
-                  (security == Geary.TlsNegotiationMethod.NONE ||
-                   security == Geary.TlsNegotiationMethod.START_TLS)) &&
-                !(port == Geary.Imap.IMAP_TLS_PORT &&
-                  security == Geary.TlsNegotiationMethod.TRANSPORT)) {
-                custom_port = true;
-            }
-            break;
-        case Geary.Protocol.SMTP:
-            if (!(port == Geary.Smtp.SMTP_PORT &&
-                  (security == Geary.TlsNegotiationMethod.NONE ||
-                   security == Geary.TlsNegotiationMethod.START_TLS)) &&
-                !(port == Geary.Smtp.SUBMISSION_PORT &&
-                  (security == Geary.TlsNegotiationMethod.NONE ||
-                   security == Geary.TlsNegotiationMethod.START_TLS)) &&
-                !(port == Geary.Smtp.SUBMISSION_TLS_PORT &&
-                  security == Geary.TlsNegotiationMethod.TRANSPORT)) {
-                custom_port = true;
+    private string? get_host_text() {
+        string? value = this.service.host ?? "";
+        if (!Geary.String.is_empty(value)) {
+            // Only show the port if it not the appropriate default port
+            uint16 port = this.service.port;
+            if (port != this.service.get_default_port()) {
+                value = "%s:%d".printf(value, this.service.port);
             }
-            break;
         }
-        if (custom_port) {
-            value = "%s:%d".printf(value, this.service.port);
+        return value;
+    }
+
+    private void on_popover_activate(EditorPopover popover) {
+        Components.NetworkAddressValidator validator =
+            (Components.NetworkAddressValidator) Geary.traverse(
+                popover.validators
+            ).first();
+
+        GLib.NetworkAddress? address = validator.validated_address;
+        if (address != null) {
+            this.service.host = address.hostname;
+            this.service.port = address.port != 0
+                ? (uint16) address.port
+                : this.service.get_default_port();
+            update();
         }
 
-        this.value.set_text(value);
+        popover.popdown();
     }
 
 }
@@ -352,7 +484,15 @@ private class Accounts.ServiceSecurityRow :
     }
 
     private void on_value_changed() {
+        // Update the port if we're currently using the default,
+        // otherwise keep the custom port as-is.
+        bool update_port = (
+            this.service.port == this.service.get_default_port()
+        );
         this.service.transport_security = this.value.method;
+        if (update_port) {
+            this.service.port = this.service.get_default_port();
+        }
     }
 
 }
@@ -377,8 +517,6 @@ private class Accounts.ServiceLoginRow :
     }
 
     public override void activated(EditorServersPane pane) {
-        EditorPopover popover = new EditorPopover();
-
         string? value = null;
         if (this.service.credentials != null) {
             value = this.service.credentials.user;
@@ -389,12 +527,19 @@ private class Accounts.ServiceLoginRow :
         entry.set_width_chars(20);
         entry.show();
 
+        EditorPopover popover = new EditorPopover();
         popover.set_relative_to(this.value);
         popover.layout.add(entry);
+        popover.add_validator(new Components.Validator(entry));
+        popover.valid_activated.connect(on_popover_activate);
         popover.popup();
     }
 
     public override void update() {
+        this.value.set_text(get_login_text());
+    }
+
+    private string? get_login_text() {
         string? label = null;
         if (this.service.credentials != null) {
             string method = "%s";
@@ -433,7 +578,16 @@ private class Accounts.ServiceLoginRow :
             // by an account's IMAP or SMTP service.
             label = _("None");
         }
-        this.value.set_text(label);
+        return label;
+    }
+
+    private void on_popover_activate(EditorPopover popover) {
+        Components.Validator validator =
+            Geary.traverse(popover.validators).first();
+       this.service.credentials =
+           this.service.credentials.copy_with_user(validator.target.text);
+        update();
+        popover.popdown();
     }
 
 }
@@ -441,12 +595,19 @@ private class Accounts.ServiceLoginRow :
 private class Accounts.ServiceSmtpAuthRow :
     ServiceRow<EditorServersPane,SmtpAuthComboBox> {
 
+
+    Geary.ServiceInformation imap_service;
+
+
     public ServiceSmtpAuthRow(Geary.AccountInformation account,
-                              Geary.ServiceInformation service) {
+                              Geary.ServiceInformation smtp_service,
+                              Geary.ServiceInformation imap_service) {
         SmtpAuthComboBox value = new SmtpAuthComboBox();
-        base(account, service, value.label, value);
+        base(account, smtp_service, value.label, value);
+        this.imap_service = imap_service;
         this.activatable = false;
         update();
+        value.source = this.service.smtp_credentials_source;
         value.changed.connect(on_value_changed);
     }
 
@@ -455,7 +616,23 @@ private class Accounts.ServiceSmtpAuthRow :
     }
 
     private void on_value_changed() {
-        this.service.smtp_credentials_source = this.value.source;
+        if (this.service.smtp_credentials_source != this.value.source) {
+            // The default SMTP port also depends on the auth method
+            // used, so also update the port here if we're currently
+            // using the default, otherwise keep the custom port
+            // as-is.
+            bool update_port = (
+                this.service.port == this.service.get_default_port()
+            );
+            this.service.smtp_credentials_source = this.value.source;
+            this.service.credentials =
+                (this.service.smtp_credentials_source != CUSTOM)
+                ? null
+                : new Geary.Credentials(Geary.Credentials.Method.PASSWORD, "");
+            if (update_port) {
+                this.service.port = this.service.get_default_port();
+            }
+        }
     }
 
 }
diff --git a/src/engine/api/geary-credentials.vala b/src/engine/api/geary-credentials.vala
index a9039aca..50a14453 100644
--- a/src/engine/api/geary-credentials.vala
+++ b/src/engine/api/geary-credentials.vala
@@ -84,6 +84,10 @@ public class Geary.Credentials : BaseObject, Gee.Hashable<Geary.Credentials> {
         return this.token != null;
     }
 
+    public Credentials copy_with_user(string user) {
+        return new Credentials(this.supported_method, user, this.token);
+    }
+
     public Credentials copy_with_token(string? token) {
         return new Credentials(this.supported_method, this.user, token);
     }
diff --git a/src/engine/api/geary-engine.vala b/src/engine/api/geary-engine.vala
index 4768d7c0..f8bafbba 100644
--- a/src/engine/api/geary-engine.vala
+++ b/src/engine/api/geary-engine.vala
@@ -433,6 +433,59 @@ public class Geary.Engine : BaseObject {
         }
     }
 
+    /**
+     * Changes the service configuration for an account.
+     *
+     * This updates an account's service configuration with the given
+     * configuration, by copying it over the account's existing
+     * configuration for that service. The corresponding {@link
+     * Account.incoming} or {@link Account.outgoing} client service
+     * will also be updated so that the new configuration will start
+     * taking effect immediately.
+     *
+     * Returns true if the account's service was updated, or false if
+     * the configuration was the same.
+     */
+    public bool update_account_service(AccountInformation account,
+                                       ServiceInformation updated) {
+        // Ensure account is closed.
+        Account? impl = this.account_instances.get(account.id);
+        if (impl == null) {
+            throw new EngineError.BAD_PARAMETERS(
+                "Account has not been added to the engine: %s", account.id
+            );
+        }
+
+        ServiceInformation? existing = null;
+        ClientService? service = null;
+        switch (updated.protocol) {
+        case Protocol.IMAP:
+            existing = account.imap;
+            service = impl.incoming;
+            break;
+        case Protocol.SMTP:
+            existing = account.smtp;
+            service = impl.outgoing;
+            break;
+        }
+
+        bool was_updated = false;
+        if (service != null) {
+            if (!existing.equal_to(updated)) {
+                existing.copy_from(updated);
+                was_updated = true;
+
+                Endpoint endpoint = get_shared_endpoint(
+                    account.service_provider, existing
+                );
+                impl.set_endpoint(service, endpoint);
+                account.information_changed();
+            }
+        }
+
+        return was_updated;
+    }
+
     private Geary.Endpoint get_shared_endpoint(ServiceProvider provider,
                                                ServiceInformation service) {
         string key = "%s/%s:%u".printf(
diff --git a/src/engine/api/geary-service-information.vala b/src/engine/api/geary-service-information.vala
index 0eb6f093..c7cdd221 100644
--- a/src/engine/api/geary-service-information.vala
+++ b/src/engine/api/geary-service-information.vala
@@ -233,6 +233,7 @@ public abstract class Geary.ServiceInformation : GLib.Object {
     }
 
 
+    /** Returns a temporary copy of this service for editing. */
     public abstract ServiceInformation temp_copy();
 
     /**
@@ -262,6 +263,25 @@ public abstract class Geary.ServiceInformation : GLib.Object {
         return port;
     }
 
+    /**
+     * Returns true if another object is equal to this one.
+     */
+    public bool equal_to(Geary.ServiceInformation other) {
+        return (
+            this == other ||
+            (this.host == other.host &&
+             this.port == other.port &&
+             this.use_starttls == other.use_starttls &&
+             this.use_ssl == other.use_ssl &&
+             (this.credentials == null && other.credentials != null ||
+              this.credentials != null && this.credentials.equal_to(other.credentials)) &&
+             this.mediator == other.mediator &&
+             this.remember_password == other.remember_password &&
+             this.smtp_noauth == other.smtp_noauth &&
+             this.smtp_use_imap_credentials == other.smtp_use_imap_credentials)
+        );
+    }
+
     public void copy_from(Geary.ServiceInformation from) {
         this.host = from.host;
         this.port = from.port;
diff --git a/ui/accounts_editor_servers_pane.ui b/ui/accounts_editor_servers_pane.ui
index f54e07f2..6b2101e4 100644
--- a/ui/accounts_editor_servers_pane.ui
+++ b/ui/accounts_editor_servers_pane.ui
@@ -2,6 +2,158 @@
 <!-- Generated with glade 3.22.1 -->
 <interface>
   <requires lib="gtk+" version="3.20"/>
+  <template class="AccountsEditorServersPane" parent="GtkGrid">
+    <property name="name">1</property>
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkOverlay" id="osd_overlay">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkScrolledWindow">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+            <property name="vadjustment">pane_adjustment</property>
+            <property name="hscrollbar_policy">never</property>
+            <property name="min_content_height">400</property>
+            <child>
+              <object class="GtkViewport">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <object class="GtkGrid" id="pane_content">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkFrame">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="hexpand">True</property>
+                        <property name="label_xalign">0</property>
+                        <property name="shadow_type">in</property>
+                        <child>
+                          <object class="GtkListBox" id="receiving_list">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="selection_mode">none</property>
+                            <signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
+                            <signal name="row-activated" handler="on_activate" swapped="no"/>
+                          </object>
+                        </child>
+                        <child type="label_item">
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">2</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">Receiving</property>
+                        <style>
+                          <class name="geary-settings-heading"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkFrame">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="hexpand">True</property>
+                        <property name="label_xalign">0</property>
+                        <property name="shadow_type">in</property>
+                        <child>
+                          <object class="GtkListBox" id="sending_list">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="selection_mode">none</property>
+                            <signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
+                            <signal name="row-activated" handler="on_activate" swapped="no"/>
+                          </object>
+                        </child>
+                        <child type="label_item">
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">4</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">Sending</property>
+                        <style>
+                          <class name="geary-settings-heading"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">3</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkFrame">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="hexpand">True</property>
+                        <property name="label_xalign">0</property>
+                        <property name="shadow_type">in</property>
+                        <child>
+                          <object class="GtkListBox" id="details_list">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="selection_mode">none</property>
+                            <signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
+                            <signal name="row-activated" handler="on_activate" 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>
+                    <style>
+                      <class name="geary-accounts-editor-pane-content"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="index">-1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">0</property>
+      </packing>
+    </child>
+    <style>
+      <class name="geary-accounts-editor-pane"/>
+    </style>
+  </template>
   <object class="GtkHeaderBar" id="header">
     <property name="visible">True</property>
     <property name="can_focus">False</property>
@@ -31,6 +183,17 @@
       <object class="GtkGrid">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
+        <property name="column_spacing">12</property>
+        <child>
+          <object class="GtkSpinner" id="apply_spinner">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
         <child>
           <object class="GtkButton" id="apply_button">
             <property name="label" translatable="yes">Apply</property>
@@ -43,7 +206,7 @@
             </style>
           </object>
           <packing>
-            <property name="left_attach">0</property>
+            <property name="left_attach">1</property>
             <property name="top_attach">0</property>
           </packing>
         </child>
@@ -59,147 +222,4 @@
     <property name="step_increment">1</property>
     <property name="page_increment">10</property>
   </object>
-  <template class="AccountsEditorServersPane" parent="GtkGrid">
-    <property name="name">1</property>
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <child>
-      <object class="GtkScrolledWindow">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="hexpand">True</property>
-        <property name="vexpand">True</property>
-        <property name="vadjustment">pane_adjustment</property>
-        <property name="hscrollbar_policy">never</property>
-        <property name="min_content_height">400</property>
-        <child>
-          <object class="GtkViewport">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <child>
-              <object class="GtkGrid" id="pane_content">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <child>
-                  <object class="GtkFrame">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="hexpand">True</property>
-                    <property name="label_xalign">0</property>
-                    <property name="shadow_type">in</property>
-                    <child>
-                      <object class="GtkListBox" id="receiving_list">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="selection_mode">none</property>
-                        <signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
-                        <signal name="row-activated" handler="on_activate" swapped="no"/>
-                      </object>
-                    </child>
-                    <child type="label_item">
-                      <placeholder/>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">2</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">Receiving</property>
-                    <style>
-                      <class name="geary-settings-heading"/>
-                    </style>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkFrame">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="hexpand">True</property>
-                    <property name="label_xalign">0</property>
-                    <property name="shadow_type">in</property>
-                    <child>
-                      <object class="GtkListBox" id="sending_list">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="selection_mode">none</property>
-                        <signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
-                        <signal name="row-activated" handler="on_activate" swapped="no"/>
-                      </object>
-                    </child>
-                    <child type="label_item">
-                      <placeholder/>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">4</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">Sending</property>
-                    <style>
-                      <class name="geary-settings-heading"/>
-                    </style>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">3</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkFrame">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="hexpand">True</property>
-                    <property name="label_xalign">0</property>
-                    <property name="shadow_type">in</property>
-                    <child>
-                      <object class="GtkListBox" id="details_list">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="selection_mode">none</property>
-                        <signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
-                        <signal name="row-activated" handler="on_activate" 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>
-                <style>
-                  <class name="geary-accounts-editor-pane-content"/>
-                </style>
-              </object>
-            </child>
-          </object>
-        </child>
-      </object>
-      <packing>
-        <property name="left_attach">0</property>
-        <property name="top_attach">0</property>
-      </packing>
-    </child>
-    <style>
-      <class name="geary-accounts-editor-pane"/>
-    </style>
-  </template>
 </interface>



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