Closes #6248 Account nicknames

This commit is contained in:
Eric Gregory 2013-01-31 14:54:03 -08:00
parent a9dc52b658
commit e18bef9638
6 changed files with 135 additions and 19 deletions

View file

@ -6,8 +6,13 @@
// List of accounts. Used with AccountDialog.
public class AccountDialogAccountListPane : Gtk.Box {
public enum Column {
ACCOUNT_NICKNAME = 0,
ACCOUNT_ADDRESS;
}
private Gtk.TreeView list_view;
private Gtk.ListStore list_model = new Gtk.ListStore(1, typeof (string));
private Gtk.ListStore list_model = new Gtk.ListStore(2, typeof (string), typeof (string));
private Gtk.Action edit_action;
public signal void add_account();
@ -21,13 +26,19 @@ public class AccountDialogAccountListPane : Gtk.Box {
Gtk.Builder builder = GearyApplication.instance.create_builder("account_list.glade");
pack_end((Gtk.Box) builder.get_object("container"));
list_view = (Gtk.TreeView) builder.get_object("account_list");
list_view.set_model(list_model);
list_view.insert_column_with_attributes (-1, "Account", new Gtk.CellRendererText (), "text", 0);
Gtk.ActionGroup actions = (Gtk.ActionGroup) builder.get_object("account list actions");
edit_action = actions.get_action("edit_account");
// Set up list.
list_view = (Gtk.TreeView) builder.get_object("account_list");
list_view.set_model(list_model);
list_view.insert_column_with_attributes(-1, "Nickname", new Gtk.CellRendererText(), "text",
Column.ACCOUNT_NICKNAME);
list_view.get_column(Column.ACCOUNT_NICKNAME).set_expand(true);
list_view.insert_column_with_attributes(-1, "Address", new Gtk.CellRendererText(), "text",
Column.ACCOUNT_ADDRESS);
list_view.get_column(Column.ACCOUNT_ADDRESS).set_expand(true);
// Hook up signals.
actions.get_action("close").activate.connect(() => { close(); });
actions.get_action("add_account").activate.connect(() => { add_account(); });
@ -43,8 +54,12 @@ public class AccountDialogAccountListPane : Gtk.Box {
// Add email accounts to the list.
try {
foreach (string address in Geary.Engine.instance.get_accounts().keys)
add_account_to_list(address);
Gee.Map<string, Geary.AccountInformation> all_accounts =
Geary.Engine.instance.get_accounts();
foreach (Geary.AccountInformation account in all_accounts.values) {
on_account_added(account);
}
} catch (Error e) {
debug("Error enumerating accounts: %s", e.message);
}
@ -89,7 +104,7 @@ public class AccountDialogAccountListPane : Gtk.Box {
return null;
string? account = null;
list_model.get(iter, 0, out account);
list_model.get(iter, Column.ACCOUNT_ADDRESS, out account);
return account;
}
@ -102,19 +117,22 @@ public class AccountDialogAccountListPane : Gtk.Box {
if (iter != null)
return; // Already listed.
add_account_to_list(account.email);
add_account_to_list(account.nickname, account.email);
account.notify.connect(on_account_changed);
}
private void on_account_removed(Geary.AccountInformation account) {
remove_account_from_list(account.email);
account.notify.disconnect(on_account_changed);
}
// Adds an account to the list.
// Note: does NOT check if the account is already listed.
private void add_account_to_list(string address) {
private void add_account_to_list(string nickname, string address) {
Gtk.TreeIter iter;
list_model.append(out iter);
list_model.set(iter, 0, address);
list_model.set(iter, Column.ACCOUNT_NICKNAME, nickname);
list_model.set(iter, Column.ACCOUNT_ADDRESS, address);
}
// Removes an account on the list.
@ -126,6 +144,17 @@ public class AccountDialogAccountListPane : Gtk.Box {
list_model.remove(iter);
}
private void on_account_changed(Object object, ParamSpec p) {
Geary.AccountInformation account = (Geary.AccountInformation) object;
Gtk.TreeIter? iter = list_contains(account.email);
if (iter == null)
return;
// Since nickname is the only column that can change, just set it.
list_model.set_value(iter, Column.ACCOUNT_NICKNAME, account.nickname);
}
// Returns TreeIter of the address in the account list, else null.
private Gtk.TreeIter? list_contains(string address) {
Gtk.TreeIter iter;
@ -135,7 +164,7 @@ public class AccountDialogAccountListPane : Gtk.Box {
do {
string list_address = "";
list_model.get(iter, 0, out list_address);
list_model.get(iter, Column.ACCOUNT_ADDRESS, out list_address);
if (list_address == address)
return iter;
} while (list_model.iter_next(ref iter));

View file

@ -17,6 +17,11 @@ public class AddEditPage : Gtk.Box {
set { entry_real_name.text = value; }
}
public string nickname {
get { return entry_nickname.text; }
set { entry_nickname.text = value; }
}
public string email_address {
get { return entry_email.text; }
set { entry_email.text = value; }
@ -120,6 +125,8 @@ public class AddEditPage : Gtk.Box {
private Gtk.Label label_password;
private Gtk.Entry entry_password;
private Gtk.Entry entry_real_name;
private Gtk.Label label_nickname;
private Gtk.Entry entry_nickname;
private Gtk.ComboBoxText combo_service;
private Gtk.CheckButton check_remember_password;
@ -161,6 +168,8 @@ public class AddEditPage : Gtk.Box {
_("Welcome to Geary."), _("Enter your account information to get started.")));
entry_real_name = (Gtk.Entry) builder.get_object("entry: real_name");
label_nickname = (Gtk.Label) builder.get_object("label: nickname");
entry_nickname = (Gtk.Entry) builder.get_object("entry: nickname");
combo_service = (Gtk.ComboBoxText) builder.get_object("combo: service");
entry_email = (Gtk.Entry) builder.get_object("entry: email");
label_password = (Gtk.Label) builder.get_object("label: password");
@ -213,6 +222,8 @@ public class AddEditPage : Gtk.Box {
entry_imap_port.insert_text.connect(on_port_insert_text);
entry_smtp_port.insert_text.connect(on_port_insert_text);
entry_nickname.insert_text.connect(on_nickname_insert_text);
// Shows/hides settings.
update_ui();
}
@ -220,6 +231,7 @@ public class AddEditPage : Gtk.Box {
// Sets the account information to display on this page.
public void set_account_information(Geary.AccountInformation info) {
set_all_info(info.real_name,
info.nickname,
info.email,
info.imap_credentials.user,
info.imap_credentials.pass,
@ -240,6 +252,7 @@ public class AddEditPage : Gtk.Box {
// Can use this instead of set_account_information(), both do the same thing.
public void set_all_info(
string? initial_real_name = null,
string? initial_nickname = null,
string? initial_email = null,
string? initial_imap_username = null,
string? initial_imap_password = null,
@ -258,6 +271,7 @@ public class AddEditPage : Gtk.Box {
// Set defaults
real_name = initial_real_name ?? "";
nickname = initial_nickname ?? "";
email_address = initial_email ?? "";
password = initial_imap_password != null ? initial_imap_password : "";
remember_password = initial_remember_password;
@ -321,6 +335,17 @@ public class AddEditPage : Gtk.Box {
info_changed();
}
// Prevent non-printable characters in nickname field.
private void on_nickname_insert_text(Gtk.Editable e, string text, int length, ref int position) {
for (long i = 0; i < text.char_count(); i++) {
if (!text.get_char(i).isprint()) {
Signal.stop_emission_by_name(e, "insert-text");
return;
}
}
}
private void on_port_insert_text(Gtk.Editable e, string text, int length, ref int position) {
// Prevent non-numerical characters and ensure port is <= uint16.MAX
if (!uint64.try_parse(text) || uint64.parse(((Gtk.Entry) e).text) > uint16.MAX) {
@ -378,7 +403,8 @@ public class AddEditPage : Gtk.Box {
public bool is_complete() {
switch (get_service_provider()) {
case Geary.ServiceProvider.OTHER:
if (Geary.String.is_empty_or_whitespace(email_address) ||
if (Geary.String.is_empty_or_whitespace(nickname) ||
Geary.String.is_empty_or_whitespace(email_address) ||
Geary.String.is_empty_or_whitespace(imap_host) ||
Geary.String.is_empty_or_whitespace(imap_port.to_string()) ||
Geary.String.is_empty_or_whitespace(imap_username) ||
@ -392,7 +418,8 @@ public class AddEditPage : Gtk.Box {
// GMAIL and YAHOO
default:
if (Geary.String.is_empty_or_whitespace(email_address) ||
if (Geary.String.is_empty_or_whitespace(nickname) ||
Geary.String.is_empty_or_whitespace(email_address) ||
Geary.String.is_empty_or_whitespace(password))
return false;
break;
@ -419,6 +446,7 @@ public class AddEditPage : Gtk.Box {
}
account_information.real_name = real_name.strip();
account_information.nickname = nickname.strip();
account_information.imap_credentials = imap_credentials;
account_information.smtp_credentials = smtp_credentials;
account_information.imap_remember_password = remember_password;
@ -452,6 +480,10 @@ public class AddEditPage : Gtk.Box {
private void update_ui() {
base.show_all();
welcome_box.visible = mode == PageMode.WELCOME;
entry_nickname.visible = label_nickname.visible = mode != PageMode.WELCOME;
if (mode == PageMode.WELCOME)
nickname = Geary.AccountInformation.DEFAULT_NICKNAME;
if (get_service_provider() == Geary.ServiceProvider.OTHER) {
// Display all options for custom providers.

View file

@ -7,6 +7,7 @@
public class Geary.AccountInformation : Object {
private const string GROUP = "AccountInformation";
private const string REAL_NAME_KEY = "real_name";
private const string NICKNAME_KEY = "nickname";
private const string SERVICE_PROVIDER_KEY = "service_provider";
private const string IMAP_USERNAME_KEY = "imap_username";
private const string IMAP_REMEMBER_PASSWORD_KEY = "imap_remember_password";
@ -23,11 +24,13 @@ public class Geary.AccountInformation : Object {
private const string SMTP_STARTTLS = "smtp_starttls";
public const string SETTINGS_FILENAME = "geary.ini";
public const string DEFAULT_NICKNAME = _("Default");
internal File settings_dir;
internal File file;
public string real_name { get; set; }
public string nickname { get; set; }
public string email { get; set; }
public Geary.ServiceProvider service_provider { get; set; }
public bool imap_server_pipeline { get; set; default = true; }
@ -61,6 +64,7 @@ public class Geary.AccountInformation : Object {
// It's no big deal if we couldn't load the key file -- just means we give you the defaults.
} finally {
real_name = get_string_value(key_file, GROUP, REAL_NAME_KEY);
nickname = get_string_value(key_file, GROUP, NICKNAME_KEY, DEFAULT_NICKNAME);
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);
@ -337,6 +341,7 @@ public class Geary.AccountInformation : Object {
KeyFile key_file = new KeyFile();
key_file.set_value(GROUP, REAL_NAME_KEY, real_name);
key_file.set_value(GROUP, NICKNAME_KEY, nickname);
key_file.set_value(GROUP, SERVICE_PROVIDER_KEY, service_provider.to_string());
key_file.set_value(GROUP, IMAP_USERNAME_KEY, imap_credentials.user);
key_file.set_boolean(GROUP, IMAP_REMEMBER_PASSWORD_KEY, imap_remember_password);

View file

@ -173,6 +173,12 @@ public class Geary.Engine {
Cancellable? cancellable = null) throws Error {
check_opened();
// Make sure the account nickname is not in use.
foreach (AccountInformation a in get_accounts().values) {
if (account != a && Geary.String.equals_ci(account.nickname, a.nickname))
return false;
}
// validate IMAP, which requires logging in and establishing an AUTHORIZED cx state
bool imap_valid = false;
Geary.Imap.ClientSession? imap_session = new Imap.ClientSession(account.get_imap_endpoint(), true);

View file

@ -60,6 +60,10 @@ public bool stri_equal(void *a, void *b) {
return str_equal(((string *) a)->down(), ((string *) b)->down());
}
public bool equals_ci(string a, string b) {
return a.down() == b.down();
}
public bool nullable_stri_equal(void *a, void *b) {
if (a == null)
return (b == null);

View file

@ -64,10 +64,11 @@
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="placeholder_text">email@example.com</property>
<property name="input_purpose">email</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
<property name="top_attach">3</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
@ -84,10 +85,11 @@
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="placeholder_text">Password</property>
<property name="input_purpose">password</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
<property name="top_attach">4</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
@ -103,7 +105,7 @@
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
<property name="top_attach">3</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
@ -119,7 +121,7 @@
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
<property name="top_attach">4</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
@ -180,6 +182,7 @@
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="placeholder_text">First Last</property>
<property name="input_purpose">name</property>
</object>
<packing>
<property name="left_attach">1</property>
@ -201,7 +204,44 @@
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">4</property>
<property name="top_attach">5</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label: nickname">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="xpad">1</property>
<property name="label" translatable="yes">N_ickname:</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">entry: nickname</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="entry: nickname">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="max_length">255</property>
<property name="invisible_char">•</property>
<property name="activates_default">True</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="placeholder_text">Work, Home, etc</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>