diff --git a/po/POTFILES.in b/po/POTFILES.in
index be3c3fec..8ab4fc13 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -4,6 +4,7 @@ desktop/geary-autostart.desktop.in
[type: gettext/ini]desktop/geary-attach.contract.in
src/client/accounts/account-dialog-account-list-pane.vala
src/client/accounts/account-dialog-add-edit-pane.vala
+src/client/accounts/account-dialog-edit-alternate-emails-pane.vala
src/client/accounts/account-dialog-pane.vala
src/client/accounts/account-dialog-remove-confirm-pane.vala
src/client/accounts/account-dialog-remove-fail-pane.vala
@@ -381,6 +382,7 @@ src/mailer/main.vala
[type: gettext/glade]ui/certificate_warning_dialog.glade
[type: gettext/glade]ui/composer_accelerators.ui
[type: gettext/glade]ui/composer.glade
+[type: gettext/glade]ui/edit_alternate_emails.glade
[type: gettext/glade]ui/find_bar.glade
[type: gettext/glade]ui/login.glade
[type: gettext/glade]ui/message.glade
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 102917e0..d94fcb74 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -321,6 +321,7 @@ client/application/secret-mediator.vala
client/accounts/account-dialog.vala
client/accounts/account-dialog-account-list-pane.vala
client/accounts/account-dialog-add-edit-pane.vala
+client/accounts/account-dialog-edit-alternate-emails-pane.vala
client/accounts/account-dialog-pane.vala
client/accounts/account-dialog-remove-confirm-pane.vala
client/accounts/account-dialog-remove-fail-pane.vala
diff --git a/src/client/accounts/account-dialog-add-edit-pane.vala b/src/client/accounts/account-dialog-add-edit-pane.vala
index 398c9eb5..65d30ed8 100644
--- a/src/client/accounts/account-dialog-add-edit-pane.vala
+++ b/src/client/accounts/account-dialog-add-edit-pane.vala
@@ -17,6 +17,8 @@ public class AccountDialogAddEditPane : AccountDialogPane {
public signal void size_changed();
+ public signal void edit_alternate_emails(string email_address);
+
public AccountDialogAddEditPane(Gtk.Stack stack) {
base(stack);
@@ -35,7 +37,8 @@ public class AccountDialogAddEditPane : AccountDialogPane {
ok_button.clicked.connect(on_ok);
cancel_button.clicked.connect(() => { cancel(); });
- add_edit_page.size_changed.connect(() => { size_changed(); } );
+ add_edit_page.size_changed.connect(() => { size_changed(); });
+ add_edit_page.edit_alternate_emails.connect(() => { edit_alternate_emails(add_edit_page.email_address); });
pack_start(add_edit_page);
pack_start(button_box, false, false);
diff --git a/src/client/accounts/account-dialog-edit-alternate-emails-pane.vala b/src/client/accounts/account-dialog-edit-alternate-emails-pane.vala
new file mode 100644
index 00000000..e621ee58
--- /dev/null
+++ b/src/client/accounts/account-dialog-edit-alternate-emails-pane.vala
@@ -0,0 +1,205 @@
+/* Copyright 2015 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+public class AccountDialogEditAlternateEmailsPane : AccountDialogPane {
+ private class ListItem : Gtk.Label {
+ public Geary.RFC822.MailboxAddress mailbox;
+
+ public ListItem(Geary.RFC822.MailboxAddress mailbox) {
+ this.mailbox = mailbox;
+
+ label = "%s".printf(Geary.HTML.escape_markup(mailbox.get_full_address()));
+ use_markup = true;
+ ellipsize = Pango.EllipsizeMode.END;
+ xalign = 0.0f;
+ }
+ }
+
+ public string? email { get; private set; default = null; }
+
+ public bool changed { get; private set; default = false; }
+
+ private Gtk.Label title_label;
+ private Gtk.Entry email_entry;
+ private Gtk.Button add_button;
+ private Gtk.ListBox address_listbox;
+ private Gtk.ToolButton delete_button;
+ private Gtk.Button cancel_button;
+ private Gtk.Button update_button;
+ private ListItem? selected_item = null;
+
+ private Geary.AccountInformation? account_info = null;
+ private Geary.RFC822.MailboxAddress? primary_mailbox = null;
+ private Gee.HashSet mailboxes = new Gee.HashSet();
+
+ public signal void done();
+
+ public AccountDialogEditAlternateEmailsPane(Gtk.Stack stack) {
+ base (stack);
+
+ Gtk.Builder builder = GearyApplication.instance.create_builder("edit_alternate_emails.glade");
+
+ // Primary container
+ pack_start((Gtk.Widget) builder.get_object("container"));
+
+ title_label = (Gtk.Label) builder.get_object("title_label");
+ email_entry = (Gtk.Entry) builder.get_object("email_entry");
+ add_button = (Gtk.Button) builder.get_object("add_button");
+ address_listbox = (Gtk.ListBox) builder.get_object("address_listbox");
+ delete_button = (Gtk.ToolButton) builder.get_object("delete_button");
+ cancel_button = (Gtk.Button) builder.get_object("cancel_button");
+ update_button = (Gtk.Button) builder.get_object("update_button");
+
+ // Clear text when the secondary icon (not always available) is pressed
+ email_entry.icon_release.connect((pos) => {
+ if (pos == Gtk.EntryIconPosition.SECONDARY)
+ email_entry.text = "";
+ });
+
+ email_entry.bind_property("text", add_button, "sensitive", BindingFlags.SYNC_CREATE,
+ transform_email_to_sensitive);
+ email_entry.notify["text-length"].connect(on_email_entry_text_length_changed);
+ bind_property("changed", update_button, "sensitive", BindingFlags.SYNC_CREATE);
+
+ delete_button.sensitive = false;
+
+ address_listbox.row_selected.connect(on_row_selected);
+ add_button.clicked.connect(on_add_clicked);
+ delete_button.clicked.connect(on_delete_clicked);
+ cancel_button.clicked.connect(() => { done(); });
+ update_button.clicked.connect(on_update_clicked);
+ }
+
+ private bool validate_address_text(string email_address, out Geary.RFC822.MailboxAddress? parsed) {
+ parsed = null;
+
+ Geary.RFC822.MailboxAddresses mailboxes = new Geary.RFC822.MailboxAddresses.from_rfc822_string(
+ email_address);
+ if (mailboxes.size != 1)
+ return false;
+
+ Geary.RFC822.MailboxAddress mailbox = mailboxes.get(0);
+
+ if (!mailbox.is_valid())
+ return false;
+
+ if (Geary.String.stri_equal(mailbox.address, primary_mailbox.address))
+ return false;
+
+ if (Geary.String.is_empty(mailbox.address))
+ return false;
+
+ parsed = mailbox;
+
+ return true;
+ }
+
+ private bool transform_email_to_sensitive(Binding binding, Value source, ref Value target) {
+ Geary.RFC822.MailboxAddress? parsed;
+ target = validate_address_text(email_entry.text, out parsed) && !mailboxes.contains(parsed);
+
+ return true;
+ }
+
+ private void on_email_entry_text_length_changed() {
+ bool has_text = email_entry.text_length != 0;
+
+ email_entry.secondary_icon_name = has_text ? "edit-clear-symbolic" : null;
+ email_entry.secondary_icon_sensitive = has_text;
+ email_entry.secondary_icon_activatable = has_text;
+ }
+
+ public void set_account(Geary.AccountInformation account_info) {
+ this.account_info = account_info;
+
+ email = account_info.email;
+ primary_mailbox = account_info.get_primary_mailbox_address();
+ mailboxes.clear();
+ changed = false;
+
+ // reset/clear widgets
+ title_label.label = _("Additional addresses for %s").printf(account_info.email);
+ email_entry.text = "";
+
+ // clear listbox
+ foreach (Gtk.Widget widget in address_listbox.get_children())
+ address_listbox.remove(widget);
+
+ // Add all email addresses; add_email_address() silently drops the primary address
+ foreach (Geary.RFC822.MailboxAddress mailbox in account_info.get_all_mailboxes())
+ add_mailbox(mailbox, false);
+ }
+
+ public override void present() {
+ base.present();
+
+ // because in a Gtk.Stack, need to do this manually after presenting
+ email_entry.grab_focus();
+ add_button.has_default = true;
+ }
+
+ private void add_mailbox(Geary.RFC822.MailboxAddress mailbox, bool is_change) {
+ if (mailboxes.contains(mailbox) || primary_mailbox.equal_to(mailbox))
+ return;
+
+ mailboxes.add(mailbox);
+
+ ListItem item = new ListItem(mailbox);
+ item.show_all();
+ address_listbox.add(item);
+
+ if (is_change)
+ changed = true;
+ }
+
+ private void remove_mailbox(Geary.RFC822.MailboxAddress address) {
+ if (!mailboxes.remove(address))
+ return;
+
+ foreach (Gtk.Widget widget in address_listbox.get_children()) {
+ Gtk.ListBoxRow row = (Gtk.ListBoxRow) widget;
+ ListItem item = (ListItem) row.get_child();
+
+ if (item.mailbox.equal_to(address)) {
+ address_listbox.remove(widget);
+
+ changed = true;
+
+ break;
+ }
+ }
+ }
+
+ private void on_row_selected(Gtk.ListBoxRow? row) {
+ selected_item = (row != null) ? (ListItem) row.get_child() : null;
+ delete_button.sensitive = (selected_item != null);
+ }
+
+ private void on_add_clicked() {
+ Geary.RFC822.MailboxAddress? parsed;
+ if (!validate_address_text(email_entry.text, out parsed) || parsed == null)
+ return;
+
+ add_mailbox(parsed, true);
+
+ // reset state for next input
+ email_entry.text = "";
+ email_entry.grab_focus();
+ add_button.has_default = true;
+ }
+
+ private void on_delete_clicked() {
+ if (selected_item != null)
+ remove_mailbox(selected_item.mailbox);
+ }
+
+ private void on_update_clicked() {
+ account_info.replace_alternate_mailboxes(mailboxes);
+
+ done();
+ }
+}
+
diff --git a/src/client/accounts/account-dialog.vala b/src/client/accounts/account-dialog.vala
index 1d42763c..03c6036c 100644
--- a/src/client/accounts/account-dialog.vala
+++ b/src/client/accounts/account-dialog.vala
@@ -13,6 +13,7 @@ public class AccountDialog : Gtk.Dialog {
private AccountDialogSpinnerPane spinner_pane;
private AccountDialogRemoveConfirmPane remove_confirm_pane;
private AccountDialogRemoveFailPane remove_fail_pane;
+ private AccountDialogEditAlternateEmailsPane edit_alternate_emails_pane;
private Gtk.HeaderBar headerbar = new Gtk.HeaderBar();
public AccountDialog(Gtk.Window parent) {
@@ -33,6 +34,7 @@ public class AccountDialog : Gtk.Dialog {
spinner_pane = new AccountDialogSpinnerPane(stack);
remove_confirm_pane = new AccountDialogRemoveConfirmPane(stack);
remove_fail_pane = new AccountDialogRemoveFailPane(stack);
+ edit_alternate_emails_pane = new AccountDialogEditAlternateEmailsPane(stack);
// Connect signals from pages.
account_list_pane.add_account.connect(on_add_account);
@@ -41,9 +43,11 @@ public class AccountDialog : Gtk.Dialog {
add_edit_pane.ok.connect(on_save_add_or_edit);
add_edit_pane.cancel.connect(on_cancel_back_to_list);
add_edit_pane.size_changed.connect(() => { resize(1, 1); });
+ add_edit_pane.edit_alternate_emails.connect(on_edit_alternate_emails);
remove_confirm_pane.ok.connect(on_delete_account_confirmed);
remove_confirm_pane.cancel.connect(on_cancel_back_to_list);
remove_fail_pane.ok.connect(on_cancel_back_to_list);
+ edit_alternate_emails_pane.done.connect(on_done_back_to_editor);
// Set default page.
account_list_pane.present();
@@ -132,6 +136,15 @@ public class AccountDialog : Gtk.Dialog {
}
}
+ private void on_edit_alternate_emails(string email_address) {
+ Geary.AccountInformation? account_info = get_account_info_for_email(email_address);
+ if (account_info == null)
+ return;
+
+ edit_alternate_emails_pane.set_account(account_info);
+ edit_alternate_emails_pane.present();
+ }
+
private void on_delete_account_confirmed(Geary.AccountInformation? account) {
assert(account != null); // Should not be able to happen since we checked earlier.
@@ -197,5 +210,9 @@ public class AccountDialog : Gtk.Dialog {
private void on_cancel_back_to_list() {
account_list_pane.present();
}
+
+ private void on_done_back_to_editor() {
+ add_edit_pane.present();
+ }
}
diff --git a/src/client/accounts/add-edit-page.vala b/src/client/accounts/add-edit-page.vala
index 80ec59e4..226a9905 100644
--- a/src/client/accounts/add-edit-page.vala
+++ b/src/client/accounts/add-edit-page.vala
@@ -169,6 +169,7 @@ public class AddEditPage : Gtk.Box {
private Gtk.ComboBoxText combo_service;
private Gtk.CheckButton check_remember_password;
private Gtk.CheckButton check_save_sent_mail;
+ private Gtk.Button alternate_email_button;
// Signature
private Gtk.Box composer_container;
@@ -215,6 +216,8 @@ public class AddEditPage : Gtk.Box {
public signal void size_changed();
+ public signal void edit_alternate_emails();
+
public AddEditPage() {
Object(orientation: Gtk.Orientation.VERTICAL, spacing: 4);
@@ -239,6 +242,7 @@ public class AddEditPage : Gtk.Box {
entry_password = (Gtk.Entry) builder.get_object("entry: password");
check_remember_password = (Gtk.CheckButton) builder.get_object("check: remember_password");
check_save_sent_mail = (Gtk.CheckButton) builder.get_object("check: save_sent_mail");
+ alternate_email_button = (Gtk.Button) builder.get_object("button: edit_alternate_email");
label_error = (Gtk.Label) builder.get_object("label: error");
other_info = (Gtk.Alignment) builder.get_object("container: other_info");
@@ -328,6 +332,7 @@ public class AddEditPage : Gtk.Box {
check_smtp_use_imap_credentials.toggled.connect(on_changed);
check_smtp_noauth.toggled.connect(on_changed);
check_save_drafts.toggled.connect(on_changed);
+ alternate_email_button.clicked.connect(on_alternate_email_button_clicked);
entry_email.changed.connect(on_email_changed);
entry_password.changed.connect(on_password_changed);
@@ -496,6 +501,10 @@ public class AddEditPage : Gtk.Box {
info_changed();
}
+ private void on_alternate_email_button_clicked() {
+ edit_alternate_emails();
+ }
+
// Prevent non-printable characters in nickname field.
private void on_nickname_insert_text(Gtk.Editable e, string text, int length, ref int position) {
unichar c;
@@ -696,12 +705,14 @@ public class AddEditPage : Gtk.Box {
// Updates UI based on various options.
internal void update_ui() {
base.show_all();
+
welcome_box.visible = mode == PageMode.WELCOME;
entry_nickname.visible = label_nickname.visible = mode != PageMode.WELCOME;
storage_container.visible = mode == PageMode.EDIT;
check_save_sent_mail.visible = mode == PageMode.EDIT;
check_save_drafts.visible = mode == PageMode.EDIT;
composer_container.visible = mode == PageMode.EDIT;
+ alternate_email_button.visible = mode == PageMode.EDIT;
if (get_service_provider() == Geary.ServiceProvider.OTHER) {
// Display all options for custom providers.
diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala
index 4c4bff37..e34fa536 100644
--- a/src/client/composer/composer-widget.vala
+++ b/src/client/composer/composer-widget.vala
@@ -26,6 +26,17 @@ public class ComposerWidget : Gtk.EventBox {
INLINE,
INLINE_COMPACT
}
+
+ private class FromAddressMap {
+ public Geary.Account account;
+ public Geary.RFC822.MailboxAddress? sender;
+ public Geary.RFC822.MailboxAddresses from;
+ public FromAddressMap(Geary.Account a, Geary.RFC822.MailboxAddresses f, Geary.RFC822.MailboxAddress? s = null) {
+ account = a;
+ from = f;
+ sender = s;
+ }
+ }
public const string ACTION_UNDO = "undo";
public const string ACTION_REDO = "redo";
@@ -126,7 +137,9 @@ public class ComposerWidget : Gtk.EventBox {
public Geary.Account account { get; private set; }
- public string from { get; set; }
+ public Geary.RFC822.MailboxAddress sender { get; set; }
+
+ public Geary.RFC822.MailboxAddresses from { get; set; }
public string to {
get { return to_entry.get_text(); }
@@ -205,6 +218,7 @@ public class ComposerWidget : Gtk.EventBox {
private Gtk.Label from_label;
private Gtk.Label from_single;
private Gtk.ComboBoxText from_multiple = new Gtk.ComboBoxText();
+ private Gee.ArrayList from_list = new Gee.ArrayList();
private EmailEntry to_entry;
private EmailEntry cc_entry;
private Gtk.Label bcc_label;
@@ -351,6 +365,7 @@ public class ComposerWidget : Gtk.EventBox {
// Listen to account signals to update from menu.
Geary.Engine.instance.account_available.connect(update_from_field);
Geary.Engine.instance.account_unavailable.connect(update_from_field);
+ // TODO: also listen for account updates to allow adding identities while writing an email
Gtk.ScrolledWindow scroll = new Gtk.ScrolledWindow(null, null);
scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
@@ -427,9 +442,8 @@ public class ComposerWidget : Gtk.EventBox {
add_extra_accelerators();
- from = account.information.get_from().to_rfc822_string();
+ from = account.information.get_primary_from();
update_from_field();
- from_multiple.changed.connect(on_from_changed);
if (referred != null) {
if (compose_type != ComposeType.NEW_MESSAGE) {
@@ -573,7 +587,7 @@ public class ComposerWidget : Gtk.EventBox {
chain.append(attachments_box);
box.set_focus_chain(chain);
- // If there's only one account, open the drafts manager. If there's more than one account,
+ // If there's only one From option, open the drafts manager. If there's more than one,
// the drafts manager will be opened by on_from_changed().
if (!from_multiple.visible)
open_draft_manager_async.begin(null);
@@ -675,9 +689,9 @@ public class ComposerWidget : Gtk.EventBox {
compose_type = ComposeType.REPLY_ALL;
to_entry.modified = cc_entry.modified = bcc_entry.modified = false;
- if (!Geary.RFC822.Utils.equal(to_entry.addresses, reply_to_addresses))
+ if (!to_entry.addresses.equal_to(reply_to_addresses))
to_entry.modified = true;
- if (cc != "" && !Geary.RFC822.Utils.equal(cc_entry.addresses, reply_cc_addresses))
+ if (cc != "" && !cc_entry.addresses.equal_to(reply_cc_addresses))
cc_entry.modified = true;
if (bcc != "")
bcc_entry.modified = true;
@@ -704,6 +718,33 @@ public class ComposerWidget : Gtk.EventBox {
}
}
+ private bool check_preferred_from_address(Gee.List account_addresses,
+ Geary.RFC822.MailboxAddresses? referred_addresses) {
+ if (referred_addresses != null) {
+ foreach (Geary.RFC822.MailboxAddress address in account_addresses) {
+ if (referred_addresses.get_all().contains(address)) {
+ from = new Geary.RFC822.MailboxAddresses.single(address);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void set_preferred_from_address(Geary.Email referred, ComposeType compose_type) {
+ if (compose_type == ComposeType.NEW_MESSAGE) {
+ if (referred.from != null)
+ from = referred.from;
+ } else {
+ Gee.List account_addresses = account.information.get_all_mailboxes();
+ if (!check_preferred_from_address(account_addresses, referred.to)) {
+ if (!check_preferred_from_address(account_addresses, referred.cc))
+ if (!check_preferred_from_address(account_addresses, referred.bcc))
+ check_preferred_from_address(account_addresses, referred.from);
+ }
+ }
+ }
+
private void on_load_finished(WebKit.WebFrame frame) {
if (get_realized())
on_load_finished_and_realized();
@@ -845,9 +886,8 @@ public class ComposerWidget : Gtk.EventBox {
public Geary.ComposedEmail get_composed_email(DateTime? date_override = null,
bool only_html = false) {
Geary.ComposedEmail email = new Geary.ComposedEmail(
- date_override ?? new DateTime.now_local(),
- new Geary.RFC822.MailboxAddresses.from_rfc822_string(from)
- );
+ date_override ?? new DateTime.now_local(), from);
+ email.sender = sender;
if (to_entry.addresses != null)
email.to = to_entry.addresses;
@@ -947,15 +987,16 @@ public class ComposerWidget : Gtk.EventBox {
private void add_recipients_and_ids(ComposeType type, Geary.Email referred,
bool modify_headers = true) {
- string? sender_address = account.information.get_mailbox_address().address;
+ Gee.List sender_addresses = account.information.get_all_mailboxes();
Geary.RFC822.MailboxAddresses to_addresses =
- Geary.RFC822.Utils.create_to_addresses_for_reply(referred, sender_address);
+ Geary.RFC822.Utils.create_to_addresses_for_reply(referred, sender_addresses);
Geary.RFC822.MailboxAddresses cc_addresses =
- Geary.RFC822.Utils.create_cc_addresses_for_reply_all(referred, sender_address);
+ Geary.RFC822.Utils.create_cc_addresses_for_reply_all(referred, sender_addresses);
reply_to_addresses = Geary.RFC822.Utils.merge_addresses(reply_to_addresses, to_addresses);
reply_cc_addresses = Geary.RFC822.Utils.remove_addresses(
Geary.RFC822.Utils.merge_addresses(reply_cc_addresses, cc_addresses),
reply_to_addresses);
+ set_preferred_from_address(referred, type);
if (!modify_headers)
return;
@@ -2224,7 +2265,39 @@ public class ComposerWidget : Gtk.EventBox {
}
}
+ private bool add_account_emails_to_from_list(Geary.Account account, bool set_active = false) {
+ Geary.RFC822.MailboxAddresses primary_address = new Geary.RFC822.MailboxAddresses.single(
+ account.information.get_primary_mailbox_address());
+ from_multiple.append_text(primary_address.to_rfc822_string());
+ from_list.add(new FromAddressMap(account, primary_address));
+ if (!set_active && from.equal_to(primary_address)) {
+ from_multiple.set_active(from_list.size - 1);
+ set_active = true;
+ }
+
+ if (account.information.alternate_mailboxes != null) {
+ foreach (Geary.RFC822.MailboxAddress alternate_mailbox in account.information.alternate_mailboxes) {
+ Geary.RFC822.MailboxAddresses addresses = new Geary.RFC822.MailboxAddresses.single(
+ alternate_mailbox);
+
+ // Displayed in the From dropdown to indicate an "alternate email address"
+ // for an account. The first printf argument will be the alternate email
+ // address, and the second will be the account's primary email address.
+ string display = _("%1$s via %2$s").printf(addresses.to_rfc822_string(), account.information.email);
+ from_multiple.append_text(display);
+ from_list.add(new FromAddressMap(account, addresses));
+
+ if (!set_active && from.equal_to(addresses)) {
+ from_multiple.set_active(from_list.size - 1);
+ set_active = true;
+ }
+ }
+ }
+ return set_active;
+ }
+
private void update_from_field() {
+ from_multiple.changed.disconnect(on_from_changed);
from_single.visible = from_multiple.visible = from_label.visible = false;
Gee.Map accounts;
@@ -2242,44 +2315,49 @@ public class ComposerWidget : Gtk.EventBox {
return;
// If there's only one account, show nothing. (From fields are hidden above.)
- if (accounts.size <= 1)
+ if (accounts.size < 1 || (accounts.size == 1 && Geary.traverse(
+ accounts.values).first().alternate_mailboxes == null))
return;
from_label.visible = true;
+ from_label.set_use_underline(true);
+ from_label.set_mnemonic_widget(from_multiple);
+ // Composer label (with mnemonic underscore) for the account selector
+ // when choosing what address to send a message from.
+ from_label.set_text_with_mnemonic(_("_From:"));
+
+ from_multiple.visible = true;
+ from_multiple.remove_all();
+ from_list = new Gee.ArrayList();
+
+ bool set_active = false;
if (compose_type == ComposeType.NEW_MESSAGE) {
- // For new messages, show the account combo-box.
- from_label.set_use_underline(true);
- from_label.set_mnemonic_widget(from_multiple);
- // Composer label (with mnemonic underscore) for the account selector
- // when choosing what address to send a message from.
- from_label.set_text_with_mnemonic(_("_From:"));
-
- from_multiple.visible = true;
- from_multiple.remove_all();
- foreach (Geary.AccountInformation a in accounts.values)
- from_multiple.append(a.email, a.get_mailbox_address().get_full_address());
-
- // Set the active account to the currently selected account, or failing that, set it
- // to the first account in the list.
- if (!from_multiple.set_active_id(account.information.email))
- from_multiple.set_active(0);
+ set_active = add_account_emails_to_from_list(account);
+ foreach (Geary.AccountInformation info in accounts.values) {
+ try {
+ Geary.Account a = Geary.Engine.instance.get_account_instance(info);
+ if (a != account)
+ set_active = add_account_emails_to_from_list(a, set_active);
+ } catch (Error e) {
+ debug("Error getting account in composer: %s", e.message);
+ }
+ }
} else {
- // For other types of messages, just show the from account.
- from_label.set_use_underline(false);
- // Composer label (without mnemonic underscore) for the account selector
- // when choosing what address to send a message from.
- from_label.set_text(_("From:"));
-
- from_single.label = account.information.get_mailbox_address().get_full_address();
- from_single.visible = true;
+ set_active = add_account_emails_to_from_list(account);
}
+
+ if (!set_active) {
+ // The identity or account that was active before has been removed
+ // use the best we can get now (primary address of the account or any other)
+ from_multiple.set_active(0);
+ on_from_changed();
+ }
+
+ from_multiple.changed.connect(on_from_changed);
}
private void on_from_changed() {
- if (compose_type != ComposeType.NEW_MESSAGE)
- return;
-
bool changed = false;
try {
changed = update_from_account();
@@ -2298,24 +2376,19 @@ public class ComposerWidget : Gtk.EventBox {
}
private bool update_from_account() throws Error {
- // Since we've set the combo box ID to the email addresses, we can
- // fetch that and use it to grab the account from the engine.
- string? id = from_multiple.get_active_id();
- if (id == null)
+ int index = from_multiple.get_active();
+ if (index < 0)
return false;
- // it's possible for changed signals to fire even though nothing has changed; catch that
- // here when possible to avoid a lot of extra work
- Geary.AccountInformation? new_account_info = Geary.Engine.instance.get_accounts().get(id);
- if (new_account_info == null)
- return false;
+ assert(from_list.size > index);
- Geary.Account new_account = Geary.Engine.instance.get_account_instance(new_account_info);
+ Geary.Account new_account = from_list.get(index).account;
+ from = from_list.get(index).from;
+ sender = from_list.get(index).sender;
if (new_account == account)
return false;
account = new_account;
- from = new_account_info.get_from().to_rfc822_string();
set_entry_completions();
return true;
diff --git a/src/client/conversation-list/conversation-list-store.vala b/src/client/conversation-list/conversation-list-store.vala
index 7d831364..eca2ced6 100644
--- a/src/client/conversation-list/conversation-list-store.vala
+++ b/src/client/conversation-list/conversation-list-store.vala
@@ -285,7 +285,8 @@ public class ConversationListStore : Gtk.ListStore {
private void set_row(Gtk.TreeIter iter, Geary.App.Conversation conversation, Geary.Email preview) {
FormattedConversationData conversation_data = new FormattedConversationData(conversation,
- preview, conversation_monitor.folder, conversation_monitor.folder.account.information.email);
+ preview, conversation_monitor.folder,
+ conversation_monitor.folder.account.information.get_all_mailboxes());
Gtk.TreePath? path = get_path(iter);
assert(path != null);
diff --git a/src/client/conversation-list/formatted-conversation-data.vala b/src/client/conversation-list/formatted-conversation-data.vala
index a01e26fe..4cd56d2f 100644
--- a/src/client/conversation-list/formatted-conversation-data.vala
+++ b/src/client/conversation-list/formatted-conversation-data.vala
@@ -20,22 +20,20 @@ public class FormattedConversationData : Geary.BaseObject {
private const int FONT_SIZE_PREVIEW = 8;
private class ParticipantDisplay : Geary.BaseObject, Gee.Hashable {
- public string key;
public Geary.RFC822.MailboxAddress address;
public bool is_unread;
public ParticipantDisplay(Geary.RFC822.MailboxAddress address, bool is_unread) {
- key = address.as_key();
this.address = address;
this.is_unread = is_unread;
}
- public string get_full_markup(string normalized_account_key) {
- return get_as_markup((key == normalized_account_key) ? ME : address.get_short_address());
+ public string get_full_markup(Gee.List account_mailboxes) {
+ return get_as_markup((address in account_mailboxes) ? ME : address.get_short_address());
}
- public string get_short_markup(string normalized_account_key) {
- if (key == normalized_account_key)
+ public string get_short_markup(Gee.List account_mailboxes) {
+ if (address in account_mailboxes)
return get_as_markup(ME);
string short_address = address.get_short_address().strip();
@@ -45,17 +43,17 @@ public class FormattedConversationData : Geary.BaseObject {
string[] tokens = short_address.split(", ", 2);
short_address = tokens[1].strip();
if (Geary.String.is_empty(short_address))
- return get_full_markup(normalized_account_key);
+ return get_full_markup(account_mailboxes);
}
// use first name as delimited by a space
string[] tokens = short_address.split(" ", 2);
if (tokens.length < 1)
- return get_full_markup(normalized_account_key);
+ return get_full_markup(account_mailboxes);
string first_name = tokens[0].strip();
if (Geary.String.is_empty_or_whitespace(first_name))
- return get_full_markup(normalized_account_key);
+ return get_full_markup(account_mailboxes);
return get_as_markup(first_name);
}
@@ -66,14 +64,11 @@ public class FormattedConversationData : Geary.BaseObject {
}
public bool equal_to(ParticipantDisplay other) {
- if (this == other)
- return true;
-
- return key == other.key;
+ return address.equal_to(other.address);
}
public uint hash() {
- return key.hash();
+ return address.hash();
}
}
@@ -89,17 +84,17 @@ public class FormattedConversationData : Geary.BaseObject {
public Geary.Email? preview { get; private set; default = null; }
private Geary.App.Conversation? conversation = null;
- private string? account_owner_email = null;
+ private Gee.List? account_owner_emails = null;
private bool use_to = true;
private CountBadge count_badge = new CountBadge(2);
// Creates a formatted message data from an e-mail.
public FormattedConversationData(Geary.App.Conversation conversation, Geary.Email preview,
- Geary.Folder folder, string account_owner_email) {
+ Geary.Folder folder, Gee.List account_owner_emails) {
assert(preview.fields.fulfills(ConversationListStore.REQUIRED_FIELDS));
this.conversation = conversation;
- this.account_owner_email = account_owner_email;
+ this.account_owner_emails = account_owner_emails;
use_to = (folder != null) && folder.special_folder_type.is_outgoing();
// Load preview-related data.
@@ -173,11 +168,9 @@ public class FormattedConversationData : Geary.BaseObject {
}
private string get_participants_markup(Gtk.Widget widget, bool selected) {
- if (conversation == null || account_owner_email == null)
+ if (conversation == null || account_owner_emails == null || account_owner_emails.size == 0)
return "";
- string normalized_account_owner_email = account_owner_email.normalize().casefold();
-
// Build chronological list of AuthorDisplay records, setting to unread if any message by
// that author is unread
Gee.ArrayList list = new Gee.ArrayList();
@@ -210,14 +203,14 @@ public class FormattedConversationData : Geary.BaseObject {
rgba_to_markup(get_foreground_rgba(widget, selected))));
if (list.size == 1) {
// if only one participant, use full name
- builder.append(list[0].get_full_markup(normalized_account_owner_email));
+ builder.append(list[0].get_full_markup(account_owner_emails));
} else {
bool first = true;
foreach (ParticipantDisplay participant in list) {
if (!first)
builder.append(", ");
- builder.append(participant.get_short_markup(normalized_account_owner_email));
+ builder.append(participant.get_short_markup(account_owner_emails));
first = false;
}
}
diff --git a/src/engine/api/geary-account-information.vala b/src/engine/api/geary-account-information.vala
index 7f9f49ac..2623cb09 100644
--- a/src/engine/api/geary-account-information.vala
+++ b/src/engine/api/geary-account-information.vala
@@ -10,6 +10,7 @@ public class Geary.AccountInformation : BaseObject {
private const string GROUP = "AccountInformation";
private const string REAL_NAME_KEY = "real_name";
private const string NICKNAME_KEY = "nickname";
+ private const string ALTERNATE_EMAILS_KEY = "alternate_emails";
private const string SERVICE_PROVIDER_KEY = "service_provider";
private const string ORDINAL_KEY = "ordinal";
private const string PREFETCH_PERIOD_DAYS_KEY = "prefetch_period_days";
@@ -60,11 +61,41 @@ public class Geary.AccountInformation : BaseObject {
internal File? file = null;
+ //
// IMPORTANT: When adding new properties, be sure to add them to the copy method.
+ //
+ /**
+ * User's name for the {@link primary_mailbox}.
+ */
public string real_name { get; set; }
+
+ /**
+ * User label for primary account (not transmitted on wire or used in correspondence).
+ */
public string nickname { get; set; }
+
+ /**
+ * The primary email address for the account.
+ *
+ * This the RFC822 simple mailbox style, i.e. "jim@example.com".
+ *
+ * In general, it's better to use the result of {@link get_primary_mailbox_address}, as the
+ * {@link Geary.RFC822.MailboxAddress} object is better suited for comparisons, Gee collections,
+ * validation, composing quoted strings, and so forth.
+ */
public string email { get; set; }
+
+ /**
+ * A list of additional email addresses this account accepts.
+ *
+ * Use {@link add_alternate_mailbox} or {@link replace_alternate_mailboxes} rather than edit
+ * this collection directly.
+ *
+ * @see get_all_mailboxes
+ */
+ public Gee.List? alternate_mailboxes { get; private set; }
+
public Geary.ServiceProvider service_provider { get; set; }
public int prefetch_period_days { get; set; }
@@ -148,6 +179,19 @@ public class Geary.AccountInformation : BaseObject {
} finally {
real_name = get_string_value(key_file, GROUP, REAL_NAME_KEY);
nickname = get_string_value(key_file, GROUP, NICKNAME_KEY);
+
+ // Store alternate emails in a list of case-insensitive strings
+ Gee.List alt_email_list = get_string_list_value(key_file, GROUP, ALTERNATE_EMAILS_KEY);
+ if (alt_email_list.size == 0) {
+ alternate_mailboxes = null;
+ } else {
+ foreach (string alt_email in alt_email_list) {
+ RFC822.MailboxAddresses mailboxes = new RFC822.MailboxAddresses.from_rfc822_string(alt_email);
+ foreach (RFC822.MailboxAddress mailbox in mailboxes.get_all())
+ add_alternate_mailbox(mailbox);
+ }
+ }
+
imap_credentials.user = get_string_value(key_file, GROUP, IMAP_USERNAME_KEY, email);
imap_remember_password = get_bool_value(key_file, GROUP, IMAP_REMEMBER_PASSWORD_KEY, true);
smtp_credentials.user = get_string_value(key_file, GROUP, SMTP_USERNAME_KEY, email);
@@ -231,6 +275,11 @@ public class Geary.AccountInformation : BaseObject {
real_name = from.real_name;
nickname = from.nickname;
email = from.email;
+ alternate_mailboxes = null;
+ if (from.alternate_mailboxes != null) {
+ foreach (RFC822.MailboxAddress alternate_mailbox in from.alternate_mailboxes)
+ add_alternate_mailbox(alternate_mailbox);
+ }
service_provider = from.service_provider;
prefetch_period_days = from.prefetch_period_days;
save_sent_mail = from.save_sent_mail;
@@ -258,6 +307,48 @@ public class Geary.AccountInformation : BaseObject {
email_signature = from.email_signature;
}
+ /**
+ * Return a list of the primary and all alternate email addresses.
+ */
+ public Gee.List get_all_mailboxes() {
+ Gee.ArrayList all = new Gee.ArrayList();
+
+ all.add(get_primary_mailbox_address());
+
+ if (alternate_mailboxes != null)
+ all.add_all(alternate_mailboxes);
+
+ return all;
+ }
+
+ /**
+ * Add an alternate email address to the account.
+ *
+ * Duplicates will be ignored.
+ */
+ public void add_alternate_mailbox(Geary.RFC822.MailboxAddress mailbox) {
+ if (alternate_mailboxes == null)
+ alternate_mailboxes = new Gee.ArrayList();
+
+ if (!alternate_mailboxes.contains(mailbox))
+ alternate_mailboxes.add(mailbox);
+ }
+
+ /**
+ * Replaces the list of alternate email addresses with the supplied collection.
+ *
+ * Duplicates will be ignored.
+ */
+ public void replace_alternate_mailboxes(Gee.Collection? mailboxes) {
+ alternate_mailboxes = null;
+
+ if (mailboxes == null || mailboxes.size == 0)
+ return;
+
+ foreach (RFC822.MailboxAddress mailbox in mailboxes)
+ add_alternate_mailbox(mailbox);
+ }
+
/**
* Return whether this account allows setting the save_sent_mail option.
* If not, save_sent_mail will always be true and setting it will be
@@ -712,6 +803,13 @@ public class Geary.AccountInformation : BaseObject {
key_file.set_boolean(GROUP, SAVE_SENT_MAIL_KEY, save_sent_mail);
key_file.set_boolean(GROUP, USE_EMAIL_SIGNATURE_KEY, use_email_signature);
key_file.set_string(GROUP, EMAIL_SIGNATURE_KEY, email_signature);
+ if (alternate_mailboxes != null && alternate_mailboxes.size > 0) {
+ string[] list = new string[alternate_mailboxes.size];
+ for (int ctr = 0; ctr < alternate_mailboxes.size; ctr++)
+ list[ctr] = alternate_mailboxes[ctr].to_rfc822_string();
+
+ key_file.set_string_list(GROUP, ALTERNATE_EMAILS_KEY, list);
+ }
if (service_provider == ServiceProvider.OTHER) {
key_file.set_value(GROUP, IMAP_HOST, default_imap_server_host);
@@ -797,15 +895,17 @@ public class Geary.AccountInformation : BaseObject {
/**
* Returns a MailboxAddress object for this account.
*/
- public RFC822.MailboxAddress get_mailbox_address() {
+ public RFC822.MailboxAddress get_primary_mailbox_address() {
return new RFC822.MailboxAddress(real_name, email);
}
/**
- * Returns a MailboxAddresses object with this mailbox address.
+ * Returns MailboxAddresses with the primary mailbox address.
+ *
+ * @see get_primary_mailbox_address
*/
- public RFC822.MailboxAddresses get_from() {
- return new RFC822.MailboxAddresses.single(get_mailbox_address());
+ public RFC822.MailboxAddresses get_primary_from() {
+ return new RFC822.MailboxAddresses.single(get_primary_mailbox_address());
}
public static int compare_ascending(AccountInformation a, AccountInformation b) {
diff --git a/src/engine/api/geary-composed-email.vala b/src/engine/api/geary-composed-email.vala
index 72491207..e4aa3aaa 100644
--- a/src/engine/api/geary-composed-email.vala
+++ b/src/engine/api/geary-composed-email.vala
@@ -17,6 +17,8 @@ public class Geary.ComposedEmail : BaseObject {
| Geary.Email.Field.DATE;
public DateTime date { get; set; }
+ // TODO: sender goes here, but not beyond, as it's not properly supported by GMime yet.
+ public RFC822.MailboxAddress? sender { get; set; default = null; }
public RFC822.MailboxAddresses from { get; set; }
public RFC822.MailboxAddresses? to { get; set; default = null; }
public RFC822.MailboxAddresses? cc { get; set; default = null; }
diff --git a/src/engine/imap-db/outbox/smtp-outbox-folder.vala b/src/engine/imap-db/outbox/smtp-outbox-folder.vala
index b7c8d99f..ab7ca0aa 100644
--- a/src/engine/imap-db/outbox/smtp-outbox-folder.vala
+++ b/src/engine/imap-db/outbox/smtp-outbox-folder.vala
@@ -634,7 +634,7 @@ private class Geary.SmtpOutboxFolder : Geary.AbstractLocalFolder, Geary.FolderSu
if (smtp_err == null) {
try {
- yield smtp.send_email_async(_account.information.get_mailbox_address(),
+ yield smtp.send_email_async(_account.information.get_primary_mailbox_address(),
rfc822, cancellable);
} catch (Error send_err) {
debug("SMTP send mail error: %s", send_err.message);
diff --git a/src/engine/rfc822/rfc822-mailbox-address.vala b/src/engine/rfc822/rfc822-mailbox-address.vala
index 4478cc99..20cb4575 100644
--- a/src/engine/rfc822/rfc822-mailbox-address.vala
+++ b/src/engine/rfc822/rfc822-mailbox-address.vala
@@ -4,13 +4,47 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
-public class Geary.RFC822.MailboxAddress : Geary.MessageData.SearchableMessageData, BaseObject {
+/**
+ * An immutable object containing a representation of an Internet email address.
+ *
+ * See [[https://tools.ietf.org/html/rfc2822#section-3.4]]
+ */
+
+public class Geary.RFC822.MailboxAddress : Geary.MessageData.SearchableMessageData,
+ Gee.Hashable, BaseObject {
internal delegate string ListToStringDelegate(MailboxAddress address);
+ /**
+ * The optional user-friendly name associated with the {@link MailboxAddress}.
+ *
+ * For "Dirk Gently ", this would be "Dirk Gently".
+ */
public string? name { get; private set; }
+
+ /**
+ * The routing of the message (optional, obsolete).
+ */
public string? source_route { get; private set; }
+
+ /**
+ * The mailbox (local-part) portion of the {@link MailboxAddress}.
+ *
+ * For "Dirk Gently ", this would be "dirk".
+ */
public string mailbox { get; private set; }
+
+ /**
+ * The domain portion of the {@link MailboxAddress}.
+ *
+ * For "Dirk Gently ", this would be "example.com".
+ */
public string domain { get; private set; }
+
+ /**
+ * The address specification of the {@link MailboxAddress}.
+ *
+ * For "Dirk Gently ", this would be "dirk@example.com".
+ */
public string address { get; private set; }
public MailboxAddress(string? name, string address) {
@@ -23,6 +57,9 @@ public class Geary.RFC822.MailboxAddress : Geary.MessageData.SearchableMessageDa
if (atsign > 0) {
mailbox = address.slice(0, atsign);
domain = address.slice(atsign + 1, address.length);
+ } else {
+ mailbox = "";
+ domain = "";
}
}
@@ -115,13 +152,6 @@ public class Geary.RFC822.MailboxAddress : Geary.MessageData.SearchableMessageDa
}
}
- /**
- * Returns a normalized casefolded string of the address, suitable for comparison and hashing.
- */
- public string as_key() {
- return address.normalize().casefold();
- }
-
/**
* Returns the address suitable for insertion into an RFC822 message. RFC822 quoting is
* performed if required.
@@ -141,6 +171,17 @@ public class Geary.RFC822.MailboxAddress : Geary.MessageData.SearchableMessageDa
return get_full_address();
}
+ public uint hash() {
+ return String.stri_hash(address);
+ }
+
+ /**
+ * Equality is defined as a case-insensitive comparison of the {@link address}.
+ */
+ public bool equal_to(MailboxAddress other) {
+ return this != other ? String.stri_equal(address, other.address) : true;
+ }
+
public string to_string() {
return get_full_address();
}
diff --git a/src/engine/rfc822/rfc822-mailbox-addresses.vala b/src/engine/rfc822/rfc822-mailbox-addresses.vala
index d1b99c57..c70f1ff9 100644
--- a/src/engine/rfc822/rfc822-mailbox-addresses.vala
+++ b/src/engine/rfc822/rfc822-mailbox-addresses.vala
@@ -5,7 +5,7 @@
*/
public class Geary.RFC822.MailboxAddresses : Geary.MessageData.AbstractMessageData,
- Geary.MessageData.SearchableMessageData, Geary.RFC822.MessageData {
+ Geary.MessageData.SearchableMessageData, Geary.RFC822.MessageData, Gee.Hashable {
public int size { get { return addrs.size; } }
@@ -74,11 +74,46 @@ public class Geary.RFC822.MailboxAddresses : Geary.MessageData.AbstractMessageDa
return false;
}
-
+ /**
+ * Returns the addresses suitable for insertion into an RFC822 message. RFC822 quoting is
+ * performed if required.
+ *
+ * @see RFC822.to_rfc822_string
+ */
public string to_rfc822_string() {
return MailboxAddress.list_to_string(addrs, "", (a) => a.to_rfc822_string());
}
+ public uint hash() {
+ // create sorted set to ensure ordering no matter the list's order
+ Gee.TreeSet sorted_addresses = traverse(addrs)
+ .map(m => m.address)
+ .to_tree_set(String.stri_cmp);
+
+ // xor all strings in sorted order
+ uint xor = 0;
+ foreach (string address in sorted_addresses)
+ xor ^= address.hash();
+
+ return xor;
+ }
+
+ public bool equal_to(MailboxAddresses other) {
+ if (this == other)
+ return true;
+
+ if (addrs.size != other.addrs.size)
+ return false;
+
+ Gee.HashSet first = new Gee.HashSet();
+ first.add_all(addrs);
+
+ Gee.HashSet second = new Gee.HashSet();
+ second.add_all(other.addrs);
+
+ return Collection.are_sets_equal(first, second);
+ }
+
/**
* See Geary.MessageData.SearchableMessageData.
*/
diff --git a/src/engine/rfc822/rfc822-utils.vala b/src/engine/rfc822/rfc822-utils.vala
index abed06cb..99bf3cb5 100644
--- a/src/engine/rfc822/rfc822-utils.vala
+++ b/src/engine/rfc822/rfc822-utils.vala
@@ -62,21 +62,29 @@ public string create_subject_for_forward(Geary.Email email) {
// address in the list once. Used to remove the sender's address from a list of addresses being
// created for the "reply to" recipients.
private void remove_address(Gee.List addresses,
- string address, bool empty_ok = false) {
+ RFC822.MailboxAddress address, bool empty_ok = false) {
for (int i = 0; i < addresses.size; ++i) {
- if (addresses[i].address == address && (empty_ok || addresses.size > 1))
+ if (addresses[i].equal_to(address) && (empty_ok || addresses.size > 1))
addresses.remove_at(i--);
}
}
+private bool email_is_from_sender(Geary.Email email, Gee.List? sender_addresses) {
+ if (sender_addresses == null)
+ return false;
+
+ return Geary.traverse(sender_addresses)
+ .any(a => email.from.get_all().contains(a));
+}
+
public Geary.RFC822.MailboxAddresses create_to_addresses_for_reply(Geary.Email email,
- string? sender_address = null) {
+ Gee.List< Geary.RFC822.MailboxAddress>? sender_addresses = null) {
Gee.List new_to =
new Gee.ArrayList();
// If we're replying to something we sent, send it to the same people we originally did.
// Otherwise, we'll send to the reply-to address or the from address.
- if (email.to != null && !String.is_empty(sender_address) && email.from.contains(sender_address))
+ if (email.to != null && email_is_from_sender(email, sender_addresses))
new_to.add_all(email.to.get_all());
else if (email.reply_to != null)
new_to.add_all(email.reply_to.get_all());
@@ -84,29 +92,32 @@ public Geary.RFC822.MailboxAddresses create_to_addresses_for_reply(Geary.Email e
new_to.add_all(email.from.get_all());
// Exclude the current sender. No need to receive the mail they're sending.
- if (!String.is_empty(sender_address))
- remove_address(new_to, sender_address);
+ if (sender_addresses != null) {
+ foreach (RFC822.MailboxAddress address in sender_addresses)
+ remove_address(new_to, address);
+ }
return new Geary.RFC822.MailboxAddresses(new_to);
}
public Geary.RFC822.MailboxAddresses create_cc_addresses_for_reply_all(Geary.Email email,
- string? sender_address = null) {
+ Gee.List? sender_addresses = null) {
Gee.List new_cc = new Gee.ArrayList();
// If we're replying to something we received, also add other recipients. Don't do this for
// emails we sent, since everyone we sent it to is already covered in
// create_to_addresses_for_reply().
- if (email.to != null && (String.is_empty(sender_address) ||
- !email.from.contains(sender_address)))
+ if (email.to != null && !email_is_from_sender(email, sender_addresses))
new_cc.add_all(email.to.get_all());
if (email.cc != null)
new_cc.add_all(email.cc.get_all());
// Again, exclude the current sender.
- if (!String.is_empty(sender_address))
- remove_address(new_cc, sender_address, true);
+ if (sender_addresses != null) {
+ foreach (RFC822.MailboxAddress address in sender_addresses)
+ remove_address(new_cc, address, true);
+ }
return new Geary.RFC822.MailboxAddresses(new_cc);
}
@@ -135,28 +146,11 @@ public Geary.RFC822.MailboxAddresses remove_addresses(Geary.RFC822.MailboxAddres
result.add_all(from_addresses.get_all());
if (remove_addresses != null)
foreach (Geary.RFC822.MailboxAddress address in remove_addresses)
- remove_address(result, address.address, true);
+ remove_address(result, address, true);
}
return new Geary.RFC822.MailboxAddresses(result);
}
-public bool equal(Geary.RFC822.MailboxAddresses? first, Geary.RFC822.MailboxAddresses? second) {
- bool first_empty = first == null || first.size == 0;
- bool second_empty = second == null || second.size == 0;
- if (first_empty && second_empty || first == second)
- return true;
- if (first_empty || second_empty || first.size != second.size)
- return false;
-
- Gee.HashSet first_addresses = new Gee.HashSet();
- Gee.HashSet second_addresses = new Gee.HashSet();
- foreach (Geary.RFC822.MailboxAddress a in first)
- first_addresses.add(a.as_key());
- foreach (Geary.RFC822.MailboxAddress a in second)
- second_addresses.add(a.as_key());
- return Geary.Collection.are_sets_equal(first_addresses, second_addresses);
-}
-
public string reply_references(Geary.Email source) {
// generate list for References
Gee.ArrayList list = new Gee.ArrayList();
diff --git a/src/engine/util/util-string.vala b/src/engine/util/util-string.vala
index 468e09a6..3c09bac3 100644
--- a/src/engine/util/util-string.vala
+++ b/src/engine/util/util-string.vala
@@ -46,6 +46,10 @@ public bool stri_equal(string a, string b) {
return str_equal(a.down(), b.down());
}
+public int stri_cmp(string a, string b) {
+ return strcmp(a.down(), b.down());
+}
+
// Removes redundant spaces, tabs, and newlines.
public string reduce_whitespace(string _s) {
string s = _s;
diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index bb1329f2..c4034830 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -8,6 +8,7 @@ install(FILES app_menu.interface DESTINATION ${UI_DEST})
install(FILES certificate_warning_dialog.glade DESTINATION ${UI_DEST})
install(FILES composer.glade DESTINATION ${UI_DEST})
install(FILES composer_accelerators.ui DESTINATION ${UI_DEST})
+install(FILES edit_alternate_emails.glade DESTINATION ${UI_DEST})
install(FILES find_bar.glade DESTINATION ${UI_DEST})
install(FILES login.glade DESTINATION ${UI_DEST})
install(FILES message.glade DESTINATION ${UI_DEST})
diff --git a/ui/edit_alternate_emails.glade b/ui/edit_alternate_emails.glade
new file mode 100644
index 00000000..2e268879
--- /dev/null
+++ b/ui/edit_alternate_emails.glade
@@ -0,0 +1,196 @@
+
+
+
+
+
+
diff --git a/ui/login.glade b/ui/login.glade
index 1e85ad0c..0f768acc 100644
--- a/ui/login.glade
+++ b/ui/login.glade
@@ -298,7 +298,18 @@
-
+
+ Addi_tional email addresses…
+ True
+ True
+ True
+
+
+ 1
+ 7
+ 1
+ 1
+
@@ -842,80 +853,6 @@
3
-
-
- False
- 10
- vertical
-
-
- True
- False
- 8
- 0
- 4
- 6
- Storage
-
-
-
-
-
- False
- True
- 0
-
-
-
-
- True
- False
-
-
- True
- False
- 0
- 6
- _Download mail
- True
-
-
-
- 0
- 0
- 1
- 1
-
-
-
-
- True
- False
- 0
-
-
- 1
- 0
- 1
- 1
-
-
-
-
- False
- True
- 1
-
-
-
-
- False
- True
- 4
-
-
False
@@ -1003,5 +940,79 @@
4
+
+
+ False
+ 10
+ vertical
+
+
+ True
+ False
+ 8
+ 0
+ 4
+ 6
+ Storage
+
+
+
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ False
+
+
+ True
+ False
+ 0
+ 6
+ _Download mail
+ True
+
+
+
+ 0
+ 0
+ 1
+ 1
+
+
+
+
+ True
+ False
+ 0
+
+
+ 1
+ 0
+ 1
+ 1
+
+
+
+
+ False
+ True
+ 1
+
+
+
+
+ False
+ True
+ 4
+
+