From f754d3df8290204643ae8abab156102019eebfb1 Mon Sep 17 00:00:00 2001 From: Michael James Gratton Date: Thu, 14 Jun 2018 22:27:45 +1000 Subject: [PATCH] Introduce some common interfaces for account editor panes. This allows the panes themselves to manage their own headers, command stacks, and so on. Break out the account list pane out so it's handled in the same way as the others. --- po/POTFILES.in | 3 +- .../accounts/accounts-editor-edit-pane.vala | 136 +++++-- .../accounts/accounts-editor-list-pane.vala | 215 +++++++++++ .../accounts/accounts-editor-remove-pane.vala | 32 +- src/client/accounts/accounts-editor-row.vala | 13 +- .../accounts-editor-servers-pane.vala | 54 ++- src/client/accounts/accounts-editor.vala | 340 +++++------------- src/client/meson.build | 1 + ui/accounts_editor.ui | 179 --------- ui/accounts_editor_edit_pane.ui | 64 ++++ ui/accounts_editor_list_pane.ui | 71 ++++ ui/accounts_editor_remove_pane.ui | 33 ++ ui/accounts_editor_servers_pane.ui | 52 +++ ui/org.gnome.Geary.gresource.xml | 2 +- 14 files changed, 713 insertions(+), 482 deletions(-) create mode 100644 src/client/accounts/accounts-editor-list-pane.vala delete mode 100644 ui/accounts_editor.ui create mode 100644 ui/accounts_editor_list_pane.ui diff --git a/po/POTFILES.in b/po/POTFILES.in index b1ee0817..fdf925a6 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -19,6 +19,7 @@ src/client/accounts/account-manager.vala src/client/accounts/account-spinner-page.vala src/client/accounts/accounts-editor.vala src/client/accounts/accounts-editor-edit-pane.vala +src/client/accounts/accounts-editor-list-pane.vala src/client/accounts/accounts-editor-remove-pane.vala src/client/accounts/accounts-editor-row.vala src/client/accounts/accounts-editor-servers-pane.vala @@ -412,8 +413,8 @@ src/mailer/main.vala ui/account_cannot_remove.glade ui/account_list.glade ui/account_spinner.glade -ui/accounts_editor.ui ui/accounts_editor_edit_pane.ui +ui/accounts_editor_list_pane.ui ui/accounts_editor_remove_pane.ui ui/accounts_editor_servers_pane.ui ui/certificate_warning_dialog.glade diff --git a/src/client/accounts/accounts-editor-edit-pane.vala b/src/client/accounts/accounts-editor-edit-pane.vala index 260e9aa5..7d7758df 100644 --- a/src/client/accounts/accounts-editor-edit-pane.vala +++ b/src/client/accounts/accounts-editor-edit-pane.vala @@ -6,17 +6,23 @@ */ /** - * The main account editor window. + * An account editor pane for editing a specific account's preferences. */ [GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_edit_pane.ui")] -public class Accounts.EditorEditPane : Gtk.Grid { +internal class Accounts.EditorEditPane : Gtk.Grid, EditorPane, AccountPane { - /** The editor this pane belongs to. */ - internal weak Editor editor; // circular ref + internal Geary.AccountInformation account { get ; protected set; } - /** The account being displayed by this pane. */ - internal Geary.AccountInformation account; + /** Command stack for edit pane user commands. */ + internal Application.CommandStack commands { + get; private set; default = new Application.CommandStack(); + } + + protected weak Accounts.Editor editor { get; set; } + + [GtkChild] + private Gtk.HeaderBar header; [GtkChild] private Gtk.ListBox details_list; @@ -33,9 +39,11 @@ public class Accounts.EditorEditPane : Gtk.Grid { [GtkChild] private Gtk.ListBox settings_list; + [GtkChild] + private Gtk.Button undo_button; - public EditorEditPane(Editor editor, - Geary.AccountInformation account) { + + public EditorEditPane(Editor editor, Geary.AccountInformation account) { this.editor = editor; this.account = account; @@ -47,7 +55,7 @@ public class Accounts.EditorEditPane : Gtk.Grid { in account.get_sender_mailboxes()) { this.senders_list.add(new MailboxRow(account, sender)); } - this.senders_list.add(new AddMailboxRow(this)); + this.senders_list.add(new AddMailboxRow()); this.signature_preview = new ClientWebView( ((GearyApplication) editor.application).config @@ -77,7 +85,7 @@ public class Accounts.EditorEditPane : Gtk.Grid { // view no longer the focus widget if (!this.signature_preview.is_focus && this.signature_changed) { - editor.commands.execute.begin( + this.commands.execute.begin( new SignatureChangedCommand( this.signature_preview, account ), @@ -95,7 +103,22 @@ public class Accounts.EditorEditPane : Gtk.Grid { this.signature_frame.add(this.signature_preview); this.settings_list.set_header_func(Editor.seperator_headers); - this.settings_list.add(new EmailPrefetchRow(editor, this.account)); + this.settings_list.add(new EmailPrefetchRow(this)); + + this.account.information_changed.connect(on_account_changed); + update_header(); + + this.commands.executed.connect(on_command); + this.commands.undone.connect(on_command); + this.commands.redone.connect(on_command); + } + + ~EditorEditPane() { + this.account.information_changed.disconnect(on_account_changed); + + this.commands.executed.disconnect(on_command); + this.commands.undone.disconnect(on_command); + this.commands.redone.disconnect(on_command); } internal string? get_default_name() { @@ -111,11 +134,50 @@ public class Accounts.EditorEditPane : Gtk.Grid { return name; } + internal Gtk.HeaderBar get_header() { + return this.header; + } + + internal void pane_shown() { + update_actions(); + } + + internal void undo() { + this.commands.undo.begin(null); + } + + internal void redo() { + this.commands.redo.begin(null); + } + + 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 + ); + + Application.Command next_undo = this.commands.peek_undo(); + this.undo_button.set_tooltip_text( + (next_undo != null && next_undo.undo_label != null) + ? next_undo.undo_label : "" + ); + } + + private void on_account_changed() { + update_header(); + } + + private void on_command() { + update_actions(); + } + [GtkCallback] private void on_setting_activated(Gtk.ListBoxRow row) { - EditorRow? setting = row as EditorRow; + EditorRow? setting = row as EditorRow; if (setting != null) { - setting.activated(this.editor); + setting.activated(this); } } @@ -129,10 +191,15 @@ public class Accounts.EditorEditPane : Gtk.Grid { this.editor.push(new EditorRemovePane(this.editor, this.account)); } + [GtkCallback] + private void on_back_button_clicked() { + this.editor.pop(); + } + } -private class Accounts.NicknameRow : AccountRow { +private class Accounts.NicknameRow : AccountRow { public NicknameRow(Geary.AccountInformation account) { @@ -146,7 +213,7 @@ private class Accounts.NicknameRow : AccountRow { update(); } - public override void activated(Accounts.Editor editor) { + public override void activated(EditorEditPane pane) { EditorPopover popover = new EditorPopover(); string? value = this.account.nickname; @@ -155,11 +222,11 @@ private class Accounts.NicknameRow : AccountRow { entry.set_placeholder_text(value ?? ""); entry.set_width_chars(20); entry.activate.connect(() => { - editor.commands.execute.begin( + pane.commands.execute.begin( new PropertyCommand( this.account, this.account, - "nickname", + Geary.AccountInformation.PROP_NICKNAME, entry.get_text(), // Translators: Tooltip used to undo changing // the name of an account. The string @@ -192,30 +259,25 @@ private class Accounts.NicknameRow : AccountRow { } -private class Accounts.AddMailboxRow : AddRow { +private class Accounts.AddMailboxRow : AddRow { - private EditorEditPane edit_pane; - - - public AddMailboxRow(EditorEditPane edit_pane) { - this.edit_pane = edit_pane; - + public AddMailboxRow() { // Translators: Tooltip for adding a new email sender/from // address's address to an account this.set_tooltip_text(_("Add a new sender email address")); } - public override void activated(Accounts.Editor editor) { + public override void activated(EditorEditPane pane) { MailboxEditorPopover popover = new MailboxEditorPopover( - this.edit_pane.get_default_name() ?? "", "", false + pane.get_default_name() ?? "", "", false ); popover.activated.connect(() => { - editor.commands.execute.begin( + pane.commands.execute.begin( new AppendMailboxCommand( (Gtk.ListBox) get_parent(), new MailboxRow( - this.edit_pane.account, + pane.account, new Geary.RFC822.MailboxAddress( popover.display_name, popover.address @@ -233,7 +295,7 @@ private class Accounts.AddMailboxRow : AddRow { } -private class Accounts.MailboxRow : AccountRow { +private class Accounts.MailboxRow : AccountRow { internal Geary.RFC822.MailboxAddress mailbox; @@ -247,14 +309,14 @@ private class Accounts.MailboxRow : AccountRow { update(); } - public override void activated(Accounts.Editor editor) { + public override void activated(EditorEditPane pane) { MailboxEditorPopover popover = new MailboxEditorPopover( this.mailbox.name ?? "", this.mailbox.address, this.account.get_sender_mailboxes().size > 1 ); popover.activated.connect(() => { - editor.commands.execute.begin( + pane.commands.execute.begin( new UpdateMailboxCommand( this, new Geary.RFC822.MailboxAddress( @@ -267,7 +329,7 @@ private class Accounts.MailboxRow : AccountRow { popover.popdown(); }); popover.remove_clicked.connect(() => { - editor.commands.execute.begin( + pane.commands.execute.begin( new RemoveMailboxCommand(this), null ); popover.popdown(); @@ -603,7 +665,8 @@ internal class Accounts.SignatureChangedCommand : Application.Command { } -private class Accounts.EmailPrefetchRow : AccountRow { +private class Accounts.EmailPrefetchRow : + AccountRow { private static bool row_separator(Gtk.TreeModel model, Gtk.TreeIter iter) { @@ -613,10 +676,9 @@ private class Accounts.EmailPrefetchRow : AccountRow { } - public EmailPrefetchRow(Accounts.Editor editor, - Geary.AccountInformation account) { + public EmailPrefetchRow(EditorEditPane pane) { base( - account, + pane.account, // Translators: This label describes the account // preference for the length of time (weeks, months or // years) that past email should be downloaded. @@ -642,7 +704,7 @@ private class Accounts.EmailPrefetchRow : AccountRow { update(); this.value.changed.connect(() => { - editor.commands.execute.begin( + pane.commands.execute.begin( new PropertyCommand( this.account, this.account, diff --git a/src/client/accounts/accounts-editor-list-pane.vala b/src/client/accounts/accounts-editor-list-pane.vala new file mode 100644 index 00000000..bc47fb5e --- /dev/null +++ b/src/client/accounts/accounts-editor-list-pane.vala @@ -0,0 +1,215 @@ +/* + * Copyright 2018 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. + */ + +/** + * An account editor pane for listing all known accounts. + */ +[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_list_pane.ui")] +internal class Accounts.EditorListPane : Gtk.Grid, EditorPane { + + + private static int ordinal_sort(Gtk.ListBoxRow a, Gtk.ListBoxRow b) { + AccountListRow? account_a = a as AccountListRow; + AccountListRow? account_b = b as AccountListRow; + + if (account_a == null) { + return (account_b == null) ? 0 : 1; + } else if (account_b == null) { + return -1; + } + + return Geary.AccountInformation.compare_ascending( + account_a.account, account_b.account + ); + } + + + protected weak Accounts.Editor editor { get; set; } + + private AccountManager accounts { get; private set; } + + [GtkChild] + private Gtk.HeaderBar header; + + [GtkChild] + private Gtk.ListBox accounts_list; + + private Gee.Map edit_pane_cache = + new Gee.HashMap(); + + + public EditorListPane(Editor editor) { + this.editor = editor; + this.accounts = + ((GearyApplication) editor.application).controller.account_manager; + + this.accounts_list.set_header_func(Editor.seperator_headers); + this.accounts_list.set_sort_func(ordinal_sort); + + foreach (Geary.AccountInformation account in this.accounts.iterable()) { + add_account(account, this.accounts.get_status(account)); + } + + this.accounts_list.add(new AddRow()); + + 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); + } + + public override void destroy() { + 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); + + this.edit_pane_cache.clear(); + base.destroy(); + } + + /** Adds a new account to the list. */ + internal void add_account(Geary.AccountInformation account, + AccountManager.Status status) { + this.accounts_list.add(new AccountListRow(account, status)); + } + + /** Removes an account from the list. */ + internal void remove_account(Geary.AccountInformation account) { + AccountListRow? row = get_account_row(account); + if (row != null) { + this.accounts_list.remove(row); + } + } + + internal Gtk.HeaderBar get_header() { + return this.header; + } + + private AccountListRow? get_account_row(Geary.AccountInformation account) { + AccountListRow? row = null; + this.accounts_list.foreach((child) => { + AccountListRow? account_row = child as AccountListRow; + if (account_row != null && account_row.account == account) { + row = account_row; + } + }); + return row; + } + + private void on_account_added(Geary.AccountInformation account, + AccountManager.Status status) { + add_account(account, status); + } + + private void on_account_status_changed(Geary.AccountInformation account, + AccountManager.Status status) { + AccountListRow? row = get_account_row(account); + if (row != null) { + row.update(status); + } + } + + private void on_account_removed(Geary.AccountInformation account) { + remove_account(account); + } + + [GtkCallback] + private void on_accounts_list_row_activated(Gtk.ListBoxRow activated) { + AccountListRow? row = activated as AccountListRow; + if (row != null) { + Geary.AccountInformation account = row.account; + EditorEditPane? edit_pane = this.edit_pane_cache.get(account); + if (edit_pane == null) { + edit_pane = new EditorEditPane(this.editor, account); + this.edit_pane_cache.set(account, edit_pane); + } + this.editor.push(edit_pane); + } + } + +} + + +private class Accounts.AccountListRow : EditorRow { + + + internal Geary.AccountInformation account; + + private Gtk.Image unavailable_icon = new Gtk.Image.from_icon_name( + "dialog-warning-symbolic", Gtk.IconSize.BUTTON + ); + private Gtk.Label account_name = new Gtk.Label(""); + private Gtk.Label account_details = new Gtk.Label(""); + + + public AccountListRow(Geary.AccountInformation account, + AccountManager.Status status) { + this.account = account; + + this.account_name.show(); + this.account_name.set_hexpand(true); + this.account_name.halign = Gtk.Align.START; + + this.account_details.show(); + + this.layout.add(this.unavailable_icon); + this.layout.add(this.account_name); + this.layout.add(this.account_details); + + update(status); + } + + public void update(AccountManager.Status status) { + if (status != AccountManager.Status.UNAVAILABLE) { + this.unavailable_icon.hide(); + this.set_tooltip_text(""); + } else { + this.unavailable_icon.show(); + this.set_tooltip_text( + _("This account has encountered a problem and is unavailable") + ); + } + + string name = this.account.nickname; + if (Geary.String.is_empty(name)) { + name = account.primary_mailbox.to_address_display("", ""); + } + this.account_name.set_text(name); + + string? details = this.account.service_label; + switch (account.service_provider) { + case Geary.ServiceProvider.GMAIL: + details = _("GMail"); + break; + + case Geary.ServiceProvider.OUTLOOK: + details = _("Outlook.com"); + break; + + case Geary.ServiceProvider.YAHOO: + details = _("Yahoo"); + break; + } + this.account_details.set_text(details); + + if (status == AccountManager.Status.ENABLED) { + this.account_name.get_style_context().remove_class( + Gtk.STYLE_CLASS_DIM_LABEL + ); + this.account_details.get_style_context().remove_class( + Gtk.STYLE_CLASS_DIM_LABEL + ); + } else { + this.account_name.get_style_context().add_class( + Gtk.STYLE_CLASS_DIM_LABEL + ); + this.account_details.get_style_context().add_class( + Gtk.STYLE_CLASS_DIM_LABEL + ); + } + } + +} diff --git a/src/client/accounts/accounts-editor-remove-pane.vala b/src/client/accounts/accounts-editor-remove-pane.vala index 13b63649..57542e57 100644 --- a/src/client/accounts/accounts-editor-remove-pane.vala +++ b/src/client/accounts/accounts-editor-remove-pane.vala @@ -6,14 +6,18 @@ */ /** - * The main account editor window. + * An account editor pane for removing an account from the client. */ [GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_remove_pane.ui")] -public class Accounts.EditorRemovePane : Gtk.Grid { +internal class Accounts.EditorRemovePane : Gtk.Grid, EditorPane, AccountPane { - private weak Editor editor; // circular ref - private Geary.AccountInformation account; + internal Geary.AccountInformation account { get ; protected set; } + + protected weak Accounts.Editor editor { get; set; } + + [GtkChild] + private Gtk.HeaderBar header; [GtkChild] private Gtk.Stack confirm_stack; @@ -41,6 +45,21 @@ public class Accounts.EditorRemovePane : Gtk.Grid { this.remove_label.set_text( this.remove_label.get_text().printf(account.nickname) ); + + this.account.information_changed.connect(on_account_changed); + update_header(); + } + + ~EditorRemovePane() { + this.account.information_changed.disconnect(on_account_changed); + } + + internal Gtk.HeaderBar get_header() { + return this.header; + } + + private void on_account_changed() { + update_header(); } [GtkCallback] @@ -50,4 +69,9 @@ public class Accounts.EditorRemovePane : Gtk.Grid { this.confirm_stack.set_visible_child_name("remove"); } + [GtkCallback] + private void on_back_button_clicked() { + this.editor.pop(); + } + } diff --git a/src/client/accounts/accounts-editor-row.vala b/src/client/accounts/accounts-editor-row.vala index 38718b29..38ac0719 100644 --- a/src/client/accounts/accounts-editor-row.vala +++ b/src/client/accounts/accounts-editor-row.vala @@ -6,7 +6,7 @@ */ -internal class Accounts.EditorRow : Gtk.ListBoxRow { +internal class Accounts.EditorRow : Gtk.ListBoxRow { protected Gtk.Grid layout { get; private set; default = new Gtk.Grid(); } @@ -22,14 +22,14 @@ internal class Accounts.EditorRow : Gtk.ListBoxRow { this.show(); } - public virtual void activated(Accounts.Editor editor) { + public virtual void activated(PaneType pane) { // No-op by default } } -internal class Accounts.LabelledEditorRow : EditorRow { +internal class Accounts.LabelledEditorRow : EditorRow { protected Gtk.Label label { get; private set; default = new Gtk.Label(""); } @@ -64,7 +64,7 @@ internal class Accounts.LabelledEditorRow : EditorRow { } -internal class Accounts.AddRow : EditorRow { +internal class Accounts.AddRow : EditorRow { public AddRow() { @@ -81,7 +81,8 @@ internal class Accounts.AddRow : EditorRow { } -internal abstract class Accounts.AccountRow : LabelledEditorRow { +internal abstract class Accounts.AccountRow : + LabelledEditorRow { internal Geary.AccountInformation account { get; private set; } @@ -108,7 +109,7 @@ internal abstract class Accounts.AccountRow : LabelledEditorRow { } -private abstract class Accounts.ServiceRow : AccountRow { +private abstract class Accounts.ServiceRow : AccountRow { internal Geary.ServiceInformation service { get; private set; } diff --git a/src/client/accounts/accounts-editor-servers-pane.vala b/src/client/accounts/accounts-editor-servers-pane.vala index 7a25d695..8da25e90 100644 --- a/src/client/accounts/accounts-editor-servers-pane.vala +++ b/src/client/accounts/accounts-editor-servers-pane.vala @@ -7,14 +7,18 @@ */ /** - * The main account editor window. + * An account editor pane for editing server details for an account. */ [GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_servers_pane.ui")] -public class Accounts.EditorServersPane : Gtk.Grid { +internal class Accounts.EditorServersPane : Gtk.Grid, EditorPane, AccountPane { - private weak Editor editor; // circular ref - private Geary.AccountInformation account; + internal Geary.AccountInformation account { get ; protected set; } + + protected weak Accounts.Editor editor { get; set; } + + [GtkChild] + private Gtk.HeaderBar header; [GtkChild] private Gtk.ListBox details_list; @@ -43,6 +47,17 @@ public class Accounts.EditorServersPane : Gtk.Grid { this.sending_list.set_header_func(Editor.seperator_headers); build_service(account.smtp, this.sending_list); + + this.account.information_changed.connect(on_account_changed); + update_header(); + } + + ~EditorServersPane() { + this.account.information_changed.disconnect(on_account_changed); + } + + internal Gtk.HeaderBar get_header() { + return this.header; } private void build_service(Geary.ServiceInformation service, @@ -52,10 +67,24 @@ public class Accounts.EditorServersPane : Gtk.Grid { settings_list.add(new ServiceAuthRow(this.account, service)); } + [GtkCallback] + private void on_cancel_button_clicked() { + this.editor.pop(); + } + + [GtkCallback] + private void on_apply_button_clicked() { + } + + private void on_account_changed() { + update_header(); + } + } -private class Accounts.ServiceProviderRow : AccountRow { +private class Accounts.ServiceProviderRow : + AccountRow { public ServiceProviderRow(Geary.AccountInformation account) { @@ -96,7 +125,8 @@ private class Accounts.ServiceProviderRow : AccountRow { } -private class Accounts.AccountProviderRow : AccountRow { +private class Accounts.AccountProviderRow : + AccountRow { public AccountProviderRow(Geary.AccountInformation account) { @@ -129,7 +159,8 @@ private class Accounts.AccountProviderRow : AccountRow { } -private class Accounts.SaveDraftsRow : AccountRow { +private class Accounts.SaveDraftsRow : + AccountRow { public SaveDraftsRow(Geary.AccountInformation account) { @@ -151,7 +182,8 @@ private class Accounts.SaveDraftsRow : AccountRow { } -private class Accounts.ServiceHostRow : ServiceRow { +private class Accounts.ServiceHostRow : + ServiceRow { public ServiceHostRow(Geary.AccountInformation account, Geary.ServiceInformation service) { @@ -191,7 +223,8 @@ private class Accounts.ServiceHostRow : ServiceRow { } -private class Accounts.ServiceSecurityRow : ServiceRow { +private class Accounts.ServiceSecurityRow : + ServiceRow { private const string INSECURE_ICON = "channel-insecure-symbolic"; private const string SECURE_ICON = "channel-secure-symbolic"; @@ -246,7 +279,8 @@ private class Accounts.ServiceSecurityRow : ServiceRow { } -private class Accounts.ServiceAuthRow : ServiceRow { +private class Accounts.ServiceAuthRow : + ServiceRow { public ServiceAuthRow(Geary.AccountInformation account, Geary.ServiceInformation service) { diff --git a/src/client/accounts/accounts-editor.vala b/src/client/accounts/accounts-editor.vala index 48102c6b..b293f38e 100644 --- a/src/client/accounts/accounts-editor.vala +++ b/src/client/accounts/accounts-editor.vala @@ -8,7 +8,6 @@ /** * The main account editor window. */ -[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor.ui")] public class Accounts.Editor : Gtk.Dialog { @@ -27,123 +26,73 @@ public class Accounts.Editor : Gtk.Dialog { } } - private static int ordinal_sort(Gtk.ListBoxRow a, Gtk.ListBoxRow b) { - AccountListRow? account_a = a as AccountListRow; - AccountListRow? account_b = b as AccountListRow; - - if (account_a == null) { - return (account_b == null) ? 0 : 1; - } else if (account_b == null) { - return -1; - } - - return Geary.AccountInformation.compare_ascending( - account_a.account, account_b.account - ); - } - - - /** The command stack for this pane. */ - internal Application.CommandStack commands { - get; private set; default = new Application.CommandStack(); - } /** The current account being edited, if any. */ private Geary.AccountInformation selected_account { get; private set; default = null; } - private AccountManager accounts; - private SimpleActionGroup actions = new SimpleActionGroup(); - [GtkChild] - private Gtk.HeaderBar default_header; - [GtkChild] - private Gtk.Stack editor_panes; + private Gtk.Stack editor_panes = new Gtk.Stack(); - [GtkChild] - private Gtk.Button back_button; - - [GtkChild] - private Gtk.Button undo_button; - - [GtkChild] - private Gtk.Grid list_pane; - - [GtkChild] - private Gtk.ListBox accounts_list; - - private Gee.LinkedList editor_pane_stack = - new Gee.LinkedList(); + private Gee.LinkedList editor_pane_stack = + new Gee.LinkedList(); public Editor(GearyApplication application, Gtk.Window parent) { this.application = application; - 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(on_pane_changed); + this.editor_panes.show(); this.actions.add_action_entries(ACTION_ENTRIES, this); insert_action_group("win", this.actions); - set_titlebar(this.default_header); - set_transient_for(parent); - //set_modal(true); - set_modal(false); - - // XXX Glade 3.22 won't let us set this - get_content_area().border_width = 2; - - this.accounts_list.set_header_func(seperator_headers); - this.accounts_list.set_sort_func(ordinal_sort); - - this.editor_pane_stack.add(list_pane); - - foreach (Geary.AccountInformation account in accounts.iterable()) { - add_account(account, accounts.get_status(account)); - } - - this.accounts_list.add(new AddRow()); - - 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_command); - this.commands.undone.connect(on_command); - this.commands.redone.connect(on_command); - get_action(GearyController.ACTION_UNDO).set_enabled(false); get_action(GearyController.ACTION_REDO).set_enabled(false); + + push(new EditorListPane(this)); } - ~Editor() { - this.commands.executed.disconnect(on_command); - this.commands.undone.disconnect(on_command); - this.commands.redone.disconnect(on_command); - - 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); + public override void destroy() { + this.editor_panes.notify["visible-child"].disconnect(on_pane_changed); + base.destroy(); } - internal void push(Gtk.Widget child) { - // Since keep old, already-popped panes around (see pop for + 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 // truncated. - Gtk.Widget current = this.editor_panes.get_visible_child(); + EditorPane current = get_current_pane(); int target_length = this.editor_pane_stack.index_of(current) + 1; while (target_length < this.editor_pane_stack.size) { - Gtk.Widget old = this.editor_pane_stack.remove_at(target_length); + EditorPane old = this.editor_pane_stack.remove_at(target_length); 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(child); - this.editor_panes.add(child); - this.editor_panes.set_visible_child(child); - this.back_button.show(); - this.undo_button.show(); + this.editor_pane_stack.add(pane); + this.editor_panes.add(pane); + this.editor_panes.set_visible_child(pane); + + pane.pane_shown(); } internal void pop() { @@ -151,192 +100,95 @@ public class Accounts.Editor : Gtk.Dialog { // there won't be any transition between them - the old one // will simply disappear. So we need to keep old, popped panes // around until a new one is pushed on. - // - // XXX work out a way to reuse the old ones if we go back to - // them? - Gtk.Widget current = this.editor_panes.get_visible_child(); - int next = this.editor_pane_stack.index_of(current) - 1; + EditorPane current = get_current_pane(); + 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); - this.editor_panes.set_visible_child(this.editor_pane_stack.get(next)); - - // Don't carry commands over from one pane to another - this.commands.clear(); - get_action(GearyController.ACTION_UNDO).set_enabled(false); - get_action(GearyController.ACTION_REDO).set_enabled(false); - - if (next == 0) { + if (prev_index == 0) { this.selected_account = null; - this.back_button.hide(); - this.undo_button.hide(); } } - private void add_account(Geary.AccountInformation account, - AccountManager.Status status) { - this.accounts_list.add(new AccountListRow(account, status)); - } - - private void show_account(Geary.AccountInformation account) { - this.selected_account = account; - push(new EditorEditPane(this, account)); - } - - private AccountListRow? get_account_row(Geary.AccountInformation account) { - AccountListRow? row = null; - this.accounts_list.foreach((child) => { - AccountListRow? account_row = child as AccountListRow; - if (account_row != null && account_row.account == account) { - row = account_row; - } - }); - return row; - } - - private inline GLib.SimpleAction get_action(string name) { + internal GLib.SimpleAction get_action(string name) { return (GLib.SimpleAction) this.actions.lookup_action(name); } - private void on_account_added(Geary.AccountInformation account, - AccountManager.Status status) { - add_account(account, status); - } - - private void on_account_status_changed(Geary.AccountInformation account, - AccountManager.Status status) { - AccountListRow? row = get_account_row(account); - if (row != null) { - row.update(status); - } - } - - private void on_account_removed(Geary.AccountInformation account) { - AccountListRow? row = get_account_row(account); - if (row != null) { - this.accounts_list.remove(row); - } - - if (this.selected_account == account) { - while (this.editor_panes.get_visible_child() != this.list_pane) { - pop(); - } - } + private inline EditorPane? get_current_pane() { + return this.editor_panes.get_visible_child() as EditorPane; } private void on_undo() { - this.commands.undo.begin(null); + get_current_pane().undo(); } private void on_redo() { - this.commands.redo.begin(null); + get_current_pane().redo(); } - private void on_command() { - get_action(GearyController.ACTION_UNDO).set_enabled( - this.commands.can_undo - ); - get_action(GearyController.ACTION_REDO).set_enabled( - this.commands.can_redo - ); - - Application.Command next_undo = this.commands.peek_undo(); - this.undo_button.set_tooltip_text( - (next_undo != null && next_undo.undo_label != null) - ? next_undo.undo_label : "" - ); - } - - [GtkCallback] - private void on_accounts_list_row_activated(Gtk.ListBoxRow activated) { - AccountListRow? row = activated as AccountListRow; - if (row != null) { - show_account(row.account); - } - } - - [GtkCallback] - private void on_back_button_clicked() { - pop(); + private void on_pane_changed() { + EditorPane? visible = get_current_pane(); + set_titlebar(visible != null ? visible.get_header() : null); } } -private class Accounts.AccountListRow : EditorRow { + +// XXX I'd really like to make EditorPane an abstract class, +// AccountPane an abstract class extending that, and the four concrete +// panes extend those, but the GTK+ Builder XML template system +// requires a template class to designate its immediate parent +// class. I.e. if accounts-editor-list-pane.ui specifies GtkGrid as +// the parent of EditorListPane, then it much exactly be that and not +// an instance of EditorPane, even if that extends GtkGrid. As a +// result, both EditorPane and AccountPane must both be interfaces so +// that the concrete pane classes can derive from GtkGrid directly, +// and everything becomes horrible. See GTK+ Issue #1151: +// https://gitlab.gnome.org/GNOME/gtk/issues/1151 + +/** + * Base interface for panes that can be shown by the accounts editor. + */ +internal interface Accounts.EditorPane : Gtk.Grid { - internal Geary.AccountInformation account; - - private Gtk.Image unavailable_icon = new Gtk.Image.from_icon_name( - "dialog-warning-symbolic", Gtk.IconSize.BUTTON - ); - private Gtk.Label account_name = new Gtk.Label(""); - private Gtk.Label account_details = new Gtk.Label(""); + /** The editor displaying this pane. */ + protected abstract weak Accounts.Editor editor { get; set; } - public AccountListRow(Geary.AccountInformation account, - AccountManager.Status status) { - this.account = account; + /** The GTK header bar to display for this pane. */ + internal abstract Gtk.HeaderBar get_header(); - this.account_name.show(); - this.account_name.set_hexpand(true); - this.account_name.halign = Gtk.Align.START; - - this.account_details.show(); - - this.layout.add(this.unavailable_icon); - this.layout.add(this.account_name); - this.layout.add(this.account_details); - - update(status); + /** Notifies the pane that it has been displayed in the dialog. */ + internal virtual void pane_shown() { + // no-op by default } - public void update(AccountManager.Status status) { - if (status != AccountManager.Status.UNAVAILABLE) { - this.unavailable_icon.hide(); - this.set_tooltip_text(""); - } else { - this.unavailable_icon.show(); - this.set_tooltip_text( - _("This account has encountered a problem and is unavailable") - ); - } + /** Un-does the last user action, if enabled and supported. */ + internal virtual void undo() { + // no-op by default + } - string name = this.account.nickname; - if (Geary.String.is_empty(name)) { - name = account.primary_mailbox.to_address_display("", ""); - } - this.account_name.set_text(name); - - string? details = this.account.service_label; - switch (account.service_provider) { - case Geary.ServiceProvider.GMAIL: - details = _("GMail"); - break; - - case Geary.ServiceProvider.OUTLOOK: - details = _("Outlook.com"); - break; - - case Geary.ServiceProvider.YAHOO: - details = _("Yahoo"); - break; - } - this.account_details.set_text(details); - - if (status == AccountManager.Status.ENABLED) { - this.account_name.get_style_context().remove_class( - Gtk.STYLE_CLASS_DIM_LABEL - ); - this.account_details.get_style_context().remove_class( - Gtk.STYLE_CLASS_DIM_LABEL - ); - } else { - this.account_name.get_style_context().add_class( - Gtk.STYLE_CLASS_DIM_LABEL - ); - this.account_details.get_style_context().add_class( - Gtk.STYLE_CLASS_DIM_LABEL - ); - } + /** Re-does the last user action, if enabled and supported. */ + internal virtual void redo() { + // no-op by default + } + +} + + +/** + * Base class for editor panes that display a specific account + */ +internal interface Accounts.AccountPane : Gtk.Grid, EditorPane { + + + /** Account being displayed by this pane. */ + internal abstract Geary.AccountInformation account { get; protected set; } + + + protected void update_header() { + get_header().subtitle = this.account.nickname; } } diff --git a/src/client/meson.build b/src/client/meson.build index 62caec66..90c95751 100644 --- a/src/client/meson.build +++ b/src/client/meson.build @@ -22,6 +22,7 @@ geary_client_vala_sources = files( 'accounts/account-spinner-page.vala', 'accounts/accounts-editor.vala', 'accounts/accounts-editor-edit-pane.vala', + 'accounts/accounts-editor-list-pane.vala', 'accounts/accounts-editor-remove-pane.vala', 'accounts/accounts-editor-row.vala', 'accounts/accounts-editor-servers-pane.vala', diff --git a/ui/accounts_editor.ui b/ui/accounts_editor.ui deleted file mode 100644 index f957839e..00000000 --- a/ui/accounts_editor.ui +++ /dev/null @@ -1,179 +0,0 @@ - - - - - - - True - False - Accounts - False - True - - - True - False - - - True - True - - - - True - False - True - go-previous-symbolic - - - - - 0 - 0 - - - - - - - True - False - - - True - True - win.undo - - - True - False - True - edit-undo-symbolic - - - - - 0 - 0 - - - - - end - 1 - - - - diff --git a/ui/accounts_editor_edit_pane.ui b/ui/accounts_editor_edit_pane.ui index e38c0fa0..83a616eb 100644 --- a/ui/accounts_editor_edit_pane.ui +++ b/ui/accounts_editor_edit_pane.ui @@ -223,4 +223,68 @@ + + True + False + Edit Account + Account Name + False + True + + + True + False + + + True + True + True + + + + True + False + True + go-previous-symbolic + + + + + 0 + 0 + + + + + + + True + False + + + True + True + True + win.undo + + + True + False + True + edit-undo-symbolic + + + + + 0 + 0 + + + + + end + 1 + + + diff --git a/ui/accounts_editor_list_pane.ui b/ui/accounts_editor_list_pane.ui new file mode 100644 index 00000000..b34def99 --- /dev/null +++ b/ui/accounts_editor_list_pane.ui @@ -0,0 +1,71 @@ + + + + + + + True + False + Accounts + False + True + + diff --git a/ui/accounts_editor_remove_pane.ui b/ui/accounts_editor_remove_pane.ui index 33ddb612..ba785dd2 100644 --- a/ui/accounts_editor_remove_pane.ui +++ b/ui/accounts_editor_remove_pane.ui @@ -166,4 +166,37 @@ + + True + False + Remove account + Account name + True + + + True + False + + + True + True + True + + + + True + False + True + go-previous-symbolic + + + + + 0 + 0 + + + + + diff --git a/ui/accounts_editor_servers_pane.ui b/ui/accounts_editor_servers_pane.ui index 9b56ad9f..8dfa598a 100644 --- a/ui/accounts_editor_servers_pane.ui +++ b/ui/accounts_editor_servers_pane.ui @@ -135,4 +135,56 @@ + + True + False + Server Settings + Account Name + True + + + True + False + + + Cancel + True + True + True + + + + 0 + 0 + + + + + + + True + False + + + Apply + True + True + True + + + + + 0 + 0 + + + + + end + 1 + + + diff --git a/ui/org.gnome.Geary.gresource.xml b/ui/org.gnome.Geary.gresource.xml index 011ec159..ba31d74f 100644 --- a/ui/org.gnome.Geary.gresource.xml +++ b/ui/org.gnome.Geary.gresource.xml @@ -4,8 +4,8 @@ account_cannot_remove.glade account_list.glade account_spinner.glade - accounts_editor.ui accounts_editor_edit_pane.ui + accounts_editor_list_pane.ui accounts_editor_remove_pane.ui accounts_editor_servers_pane.ui certificate_warning_dialog.glade