diff --git a/po/POTFILES.in b/po/POTFILES.in index 50387e02..f93c1690 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -401,6 +401,7 @@ src/engine/util/util-time.vala src/engine/util/util-timeout-manager.vala src/engine/util/util-trillian.vala src/mailer/main.vala +ui/accounts_editor.ui ui/accounts_editor_add_pane.ui ui/accounts_editor_edit_pane.ui ui/accounts_editor_list_pane.ui diff --git a/src/client/accounts/accounts-editor-add-pane.vala b/src/client/accounts/accounts-editor-add-pane.vala index cee7d989..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 + * Copyright 2018-2019 Michael Gratton * * 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; @@ -27,9 +38,6 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane { [GtkChild] private Gtk.HeaderBar header; - [GtkChild] - private Gtk.Overlay osd_overlay; - [GtkChild] private Gtk.Grid pane_content; @@ -54,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; @@ -148,16 +159,8 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane { return this.header; } - private void add_notification(InAppNotification notification) { - this.osd_overlay.add_overlay(notification); - notification.show(); - } - 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 = ""; @@ -190,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(); @@ -218,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(); @@ -260,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. @@ -271,7 +277,7 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane { if (to_focus != null) { to_focus.grab_focus(); } - add_notification( + this.editor.add_notification( new InAppNotification( // Translators: In-app notification label, the // string substitution is a more detailed reason. @@ -365,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) { @@ -407,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 66439e62..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 + * Copyright 2018-2019 Michael Gratton * * 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; @@ -154,7 +166,7 @@ internal class Accounts.EditorEditPane : /** {@inheritDoc} */ protected void command_executed() { - update_command_actions(); + this.editor.update_command_actions(); Application.Command next_undo = this.commands.peek_undo(); this.undo_button.set_tooltip_text( @@ -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 { 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 { ); this.activatable = false; this.commands = commands; + this.cancellable = cancellable; update(); @@ -295,7 +311,7 @@ private class Accounts.DisplayNameRow : AccountRow { // account. _("Change account name back to ā€œ%sā€") ), - null + this.cancellable ); } @@ -335,7 +351,7 @@ private class Accounts.AddMailboxRow : AddRow { ) ) ), - null + pane.op_cancellable ); popover.popdown(); }); @@ -376,13 +392,14 @@ private class Accounts.MailboxRow : AccountRow { 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 7d181d15..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 + * Copyright 2018-2019 Michael Gratton * * 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 { @@ -54,9 +62,6 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane { [GtkChild] private Gtk.HeaderBar header; - [GtkChild] - private Gtk.Overlay osd_overlay; - [GtkChild] private Gtk.Grid pane_content; @@ -110,7 +115,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane { this.commands.executed.connect(on_execute); this.commands.undone.connect(on_undo); this.commands.redone.connect(on_execute); - + connect_command_signals(); update_welcome_panel(); } @@ -118,6 +123,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane { this.commands.executed.disconnect(on_execute); this.commands.undone.disconnect(on_undo); this.commands.redone.disconnect(on_execute); + disconnect_command_signals(); this.accounts.account_added.disconnect(on_account_added); this.accounts.account_status_changed.disconnect(on_account_status_changed); @@ -146,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 ); } } @@ -164,20 +170,6 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane { this.accounts_list.add(row); } - 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 void update_welcome_panel() { if (this.show_welcome) { // No accounts are available, so show only the welcome @@ -224,7 +216,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane { new ReorderAccountCommand( (AccountListRow) source, new_position, this.accounts ), - null + this.op_cancellable ); } @@ -233,7 +225,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane { new ReorderAccountCommand( (AccountListRow) source, target.get_index(), this.accounts ), - null + this.op_cancellable ); } @@ -249,20 +241,16 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane { if (command.executed_label != null) { InAppNotification ian = new InAppNotification(command.executed_label); ian.set_button(_("Undo"), "win." + GearyController.ACTION_UNDO); - add_notification(ian); + this.editor.add_notification(ian); } - - update_actions(); } private void on_undo(Application.Command command) { if (command.undone_label != null) { InAppNotification ian = new InAppNotification(command.undone_label); ian.set_button(_("Redo"), "win." + GearyController.ACTION_REDO); - add_notification(ian); + this.editor.add_notification(ian); } - - update_actions(); } [GtkCallback] @@ -326,7 +314,7 @@ private class Accounts.AccountListRow : AccountRow { // 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); @@ -379,7 +367,7 @@ private class Accounts.AccountListRow : AccountRow { case DISABLED: this.set_tooltip_text( // Translators: Tooltip for accounts that have been - // loaded by disabled by the user. + // loaded but disabled by the user. _("This account has been disabled") ); break; @@ -466,7 +454,7 @@ private class Accounts.AddServiceProviderRow : EditorRow { 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 + * Copyright 2018-2019 Michael Gratton * * 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 2502196a..38a4b3c0 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 + * Copyright 2018-2019 Michael Gratton * * 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 @@ -44,9 +55,6 @@ internal class Accounts.EditorServersPane : [GtkChild] private Gtk.HeaderBar header; - [GtkChild] - private Gtk.Overlay osd_overlay; - [GtkChild] private Gtk.Grid pane_content; @@ -105,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 @@ -113,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); @@ -136,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); @@ -179,7 +227,7 @@ internal class Accounts.EditorServersPane : /** {@inheritDoc} */ protected void command_executed() { - update_command_actions(); + this.editor.update_command_actions(); this.apply_button.set_sensitive(this.commands.can_undo); } @@ -188,17 +236,16 @@ 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 + // Only need to validate if a generic, local account since + // other account types have read-only incoming/outgoing + // settings bool is_valid = true; bool has_changed = false; - if (this.account.service_provider == Geary.ServiceProvider.OTHER) { + if (this.account.service_provider == Geary.ServiceProvider.OTHER && + !this.editor.accounts.is_goa_account(this.account)) { is_valid = yield validate(cancellable); - if (is_valid) { has_changed |= yield update_service( this.account.incoming, this.incoming_mutable, cancellable @@ -209,6 +256,8 @@ internal class Accounts.EditorServersPane : } } + this.is_operation_running = false; + if (is_valid) { if (this.save_drafts.value_changed) { has_changed = true; @@ -229,14 +278,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( @@ -247,6 +292,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 @@ -272,6 +320,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 @@ -282,8 +333,8 @@ internal class Accounts.EditorServersPane : bool is_valid = imap_valid && smtp_valid; debug("Validation complete, is valid: %s", is_valid.to_string()); - if (!is_valid) { - add_notification( + if (!is_valid && message != null) { + this.editor.add_notification( new InAppNotification( // Translators: In-app notification label, the // string substitution is a more detailed reason. @@ -329,11 +380,6 @@ internal class Accounts.EditorServersPane : return has_changed; } - private void add_notification(InAppNotification notification) { - this.osd_overlay.add_overlay(notification); - notification.show(); - } - private void add_row(Gtk.ListBox list, EditorRow row) { list.add(row); ValidatingRow? validating = row as ValidatingRow; @@ -350,6 +396,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()); } @@ -362,12 +415,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] @@ -455,7 +512,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); @@ -484,9 +541,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, @@ -497,6 +557,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); @@ -513,7 +574,7 @@ private class Accounts.SaveDraftsRow : new Application.PropertyCommand( this.account, "save_drafts", this.value.state ), - null + this.cancellable ); } } @@ -540,11 +601,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: @@ -562,6 +625,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); @@ -596,8 +660,8 @@ private class Accounts.ServiceHostRow : this.service, "port", port ) }), - null - ); + this.cancellable + ); } } @@ -621,16 +685,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); } @@ -663,7 +730,7 @@ private class Accounts.ServiceSecurityRow : ) }); } - this.commands.execute.begin(cmd, null); + this.commands.execute.begin(cmd, this.cancellable); } } @@ -685,12 +752,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, @@ -702,6 +771,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; @@ -745,7 +815,7 @@ private class Accounts.ServiceLoginRow : }); } - this.commands.execute.begin(cmd, null); + this.commands.execute.begin(cmd, this.cancellable); } } @@ -803,11 +873,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, @@ -818,6 +890,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; @@ -841,7 +914,7 @@ private class Accounts.ServicePasswordRow : "credentials", this.service.credentials.copy_with_token(this.value.text) ), - null + this.cancellable ); } } @@ -860,18 +933,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); @@ -919,7 +995,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 4080164d..7b6f0a04 100644 --- a/src/client/accounts/accounts-editor.vala +++ b/src/client/accounts/accounts-editor.vala @@ -1,5 +1,5 @@ /* - * Copyright 2018 Michael Gratton + * Copyright 2018-2019 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -7,7 +7,14 @@ /** * The main account editor window. + * + * The editor is a dialog window that manages a stack of {@link + * EditorPane} instances. Each pane handles a specific task (listing + * accounts, adding a new account, editing an existing one, etc.). The + * editor displaying panes as needed, and provides some common command + * management, account management and other common code for the panes. */ +[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor.ui")] public class Accounts.Editor : Gtk.Dialog { @@ -32,7 +39,12 @@ public class Accounts.Editor : Gtk.Dialog { private SimpleActionGroup actions = new SimpleActionGroup(); - private Gtk.Stack editor_panes = new Gtk.Stack(); + [GtkChild] + private Gtk.Overlay notifications_pane; + + [GtkChild] + private Gtk.Stack editor_panes; + private EditorListPane editor_list_pane; private Gee.LinkedList editor_pane_stack = @@ -41,51 +53,73 @@ public class Accounts.Editor : Gtk.Dialog { public Editor(GearyApplication application, Gtk.Window parent) { this.application = application; + this.transient_for = parent; + + // Can't set this in Glade 3.22.1 :( + this.get_content_area().border_width = 0; + this.accounts = application.controller.account_manager; - set_default_size(700, 450); - set_icon_name(GearyApplication.APP_ID); - set_modal(true); - set_title(_("Accounts")); - set_transient_for(parent); - - get_content_area().border_width = 0; - get_content_area().add(this.editor_panes); - - this.editor_panes.set_transition_type( - Gtk.StackTransitionType.SLIDE_LEFT_RIGHT - ); - this.editor_panes.notify["visible-child"].connect_after(on_pane_changed); - this.editor_panes.show(); - this.actions.add_action_entries(ACTION_ENTRIES, this); insert_action_group("win", this.actions); - get_action(GearyController.ACTION_UNDO).set_enabled(false); - get_action(GearyController.ACTION_REDO).set_enabled(false); - this.editor_list_pane = new EditorListPane(this); push(this.editor_list_pane); + + update_command_actions(); } public override bool key_press_event(Gdk.EventKey event) { 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) { @@ -95,11 +129,9 @@ public class Accounts.Editor : Gtk.Dialog { return ret; } - public override void destroy() { - this.editor_panes.notify["visible-child"].disconnect(on_pane_changed); - base.destroy(); - } - + /** + * Adds and shows a new pane in the editor. + */ internal void push(EditorPane pane) { // Since we keep old, already-popped panes around (see pop for // details), when a new pane is pushed on they need to be @@ -111,15 +143,15 @@ public class Accounts.Editor : Gtk.Dialog { this.editor_panes.remove(old); } - get_action(GearyController.ACTION_UNDO).set_enabled(false); - get_action(GearyController.ACTION_REDO).set_enabled(false); - // Now push the new pane on this.editor_pane_stack.add(pane); this.editor_panes.add(pane); this.editor_panes.set_visible_child(pane); } + /** + * Removes the current pane from the editor, showing the last one. + */ internal void pop() { // One can't simply remove old panes fro the GTK stack since // there won't be any transition between them - the old one @@ -131,19 +163,40 @@ public class Accounts.Editor : Gtk.Dialog { this.editor_panes.set_visible_child(prev); } - internal GLib.SimpleAction get_action(string name) { - return (GLib.SimpleAction) this.actions.lookup_action(name); + /** Displays an in-app notification in the dialog. */ + internal void add_notification(InAppNotification notification) { + this.notifications_pane.add_overlay(notification); + notification.show(); } + /** Removes an account from the editor. */ internal void remove_account(Geary.AccountInformation account) { this.editor_panes.set_visible_child(this.editor_list_pane); this.editor_list_pane.remove_account(account); } + /** Updates the state of the editor's undo and redo actions. */ + internal void update_command_actions() { + bool can_undo = false; + bool can_redo = false; + CommandPane? pane = get_current_pane() as CommandPane; + if (pane != null) { + can_undo = pane.commands.can_undo; + can_redo = pane.commands.can_redo; + } + + get_action(GearyController.ACTION_UNDO).set_enabled(can_undo); + get_action(GearyController.ACTION_REDO).set_enabled(can_redo); + } + private inline EditorPane? get_current_pane() { return this.editor_panes.get_visible_child() as EditorPane; } + private inline GLib.SimpleAction get_action(string name) { + return (GLib.SimpleAction) this.actions.lookup_action(name); + } + private void on_undo() { CommandPane? pane = get_current_pane() as CommandPane; if (pane != null) { @@ -158,6 +211,7 @@ public class Accounts.Editor : Gtk.Dialog { } } + [GtkCallback] private void on_pane_changed() { EditorPane? visible = get_current_pane(); Gtk.Widget? header = null; @@ -171,11 +225,7 @@ public class Accounts.Editor : Gtk.Dialog { header = visible.get_header(); } set_titlebar(header); - - CommandPane? commands = visible as CommandPane; - if (commands != null) { - commands.update_command_actions(); - } + update_command_actions(); } } @@ -205,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(); + } + } } @@ -279,18 +361,6 @@ internal interface Accounts.CommandPane : EditorPane { this.commands.redo.begin(null); } - /** - * Updates the state of the editor's undo and redo actions. - */ - internal virtual void update_command_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 - ); - } - /** * Connects to command stack signals. * @@ -316,10 +386,10 @@ internal interface Accounts.CommandPane : EditorPane { /** * Called when a command is executed, undone or redone. * - * By default, calls {@link update_command_actions}. + * By default, calls {@link Accounts.Editor.update_command_actions}. */ protected virtual void command_executed() { - update_command_actions(); + this.editor.update_command_actions(); } private void on_command() { diff --git a/src/engine/imap/transport/imap-client-session.vala b/src/engine/imap/transport/imap-client-session.vala index ef3d44c7..4613a0d6 100644 --- a/src/engine/imap/transport/imap-client-session.vala +++ b/src/engine/imap/transport/imap-client-session.vala @@ -881,14 +881,16 @@ public class Geary.Imap.ClientSession : BaseObject { } if (login_err != null) { - // Throw an error indicating auth failed here, unless the - // response code indicated that the server is merely - // unavailable, then don't since the creds might actually - // be fine. - ResponseCode? code = cmd.status.response_code; + // Throw an error indicating auth failed here, unless + // there is a status response and it indicates that the + // server is merely reporting login as being unavailable, + // then don't since the creds might actually be fine. ResponseCodeType? code_type = null; - if (code != null) { - code_type = code.get_response_code_type(); + if (cmd.status != null) { + ResponseCode? code = cmd.status.response_code; + if (code != null) { + code_type = code.get_response_code_type(); + } } if (code_type == null || diff --git a/ui/accounts_editor.ui b/ui/accounts_editor.ui new file mode 100644 index 00000000..22d41852 --- /dev/null +++ b/ui/accounts_editor.ui @@ -0,0 +1,64 @@ + + + + + + diff --git a/ui/accounts_editor_add_pane.ui b/ui/accounts_editor_add_pane.ui index 4e4921f3..8d942c33 100644 --- a/ui/accounts_editor_add_pane.ui +++ b/ui/accounts_editor_add_pane.ui @@ -81,27 +81,65 @@ True False - + True - False + True + True + True + pane_adjustment + never + in - + True - True - True - True - pane_adjustment - never - in + False + none - + True False - none - + True False + 0 + in + + + True + False + none + + + + + + 0 + 0 + + + + + True + False + + + True + False + start + Receiving + + + + + + + 0 + 0 + + True @@ -109,7 +147,7 @@ 0 in - + True False none @@ -117,52 +155,52 @@ + + 0 + 1 + + + + + 0 + 1 + + + + + True + False + + + True + False + start + Sending + + + + + 0 0 - + True False + 0 + in - + True False - start - Receiving - - - - + none + - - 0 - 0 - - - - - True - False - 0 - in - - - True - False - none - - - - - - 0 - 1 - @@ -170,65 +208,18 @@ 1 - - - True - False - - - True - False - start - Sending - - - - - - - 0 - 0 - - - - - True - False - 0 - in - - - True - False - none - - - - - - 0 - 1 - - - - - 0 - 2 - - - + + 0 + 2 + + - - -1 - diff --git a/ui/accounts_editor_list_pane.ui b/ui/accounts_editor_list_pane.ui index 36a4d3d3..85e7944a 100644 --- a/ui/accounts_editor_list_pane.ui +++ b/ui/accounts_editor_list_pane.ui @@ -18,171 +18,160 @@ True False - + True - False + True True True + pane_adjustment + never + 400 - + True - True - True - True - pane_adjustment - never - 400 + False - + True False - + True False + center + 12 - + True False - center - 12 - - - True - False - 64 - org.gnome.Geary - True - - - 0 - 0 - 2 - - - - - True - False - start - start - To get started, select an email provider below. - - - 1 - 1 - - - - - True - False - start - end - Welcome to Geary - - - - - - 1 - 0 - - - + 64 + org.gnome.Geary + True 0 0 + 2 - + True False + start start - True - True - 0 - in - - - True - False - none - - - - - - - + To get started, select an email provider below. - 0 + 1 1 - + True False start - Add an account + end + Welcome to Geary - - 0 - 2 - - - - - True - False - start - True - True - 0 - in - - - 0 - True - False - start - none - - - - - - - - - - 0 - 3 + 1 + 0 + + 0 + 0 + + + + True + False + start + True + True + 0 + in + + + True + False + none + + + + + + + + + + 0 + 1 + + + + + True + False + start + Add an account + + + + + + + 0 + 2 + + + + + True + False + start + True + True + 0 + in + + + 0 + True + False + start + none + + + + + + + + + + 0 + 3 + + + - - -1 - diff --git a/ui/accounts_editor_servers_pane.ui b/ui/accounts_editor_servers_pane.ui index d49704be..1a1234a3 100644 --- a/ui/accounts_editor_servers_pane.ui +++ b/ui/accounts_editor_servers_pane.ui @@ -76,142 +76,133 @@ True False - + True - False + True + True + True + pane_adjustment + never + 400 - + True - True - True - True - pane_adjustment - never - 400 + False - + True False - + True False + True + 0 + in - + True False - True - 0 - in - - - True - False - none - - - - - - - + none + + - - 0 - 2 - - - - True - False - start - Receiving - - - - 0 - 1 - - - - - True - False - True - 0 - in - - - True - False - none - - - - - - - - - - 0 - 4 - - - - - True - False - start - Sending - - - - 0 - 3 - - - - - True - False - True - 0 - in - - - True - False - none - - - - - - - - - - 0 - 0 - + + + + + 0 + 2 + + + + + True + False + start + Receiving + + 0 + 1 + + + + True + False + True + 0 + in + + + True + False + none + + + + + + + + + + 0 + 4 + + + + + True + False + start + Sending + + + + 0 + 3 + + + + + True + False + True + 0 + in + + + True + False + none + + + + + + + + + + 0 + 0 + + + - - -1 - diff --git a/ui/org.gnome.Geary.gresource.xml b/ui/org.gnome.Geary.gresource.xml index 38fce5d8..9aa268bb 100644 --- a/ui/org.gnome.Geary.gresource.xml +++ b/ui/org.gnome.Geary.gresource.xml @@ -1,6 +1,7 @@ + accounts_editor.ui accounts_editor_add_pane.ui accounts_editor_edit_pane.ui accounts_editor_list_pane.ui