diff options
author | Philip Withnall <philip.withnall@collabora.co.uk> | 2012-07-25 13:10:02 -0600 |
---|---|---|
committer | Philip Withnall <philip.withnall@collabora.co.uk> | 2013-11-08 00:40:36 +0000 |
commit | fded410fd72c290596fb2b2fb2929fa92339b1c6 (patch) | |
tree | a01e632cc10609c48efa9000df76b55f71007f1b /folks | |
parent | c9c428ee6713fcc40e5db636c81f2598cec572ee (diff) |
Bug 651672 — Individual should have a display-name property
Based on work by Jeremy Whiting and Laurent Contzen.
New API:
• Individual.display_name
• StructuredName.to_string_with_format()
https://bugzilla.gnome.org/show_bug.cgi?id=651672
Diffstat (limited to 'folks')
-rw-r--r-- | folks/individual.vala | 233 | ||||
-rw-r--r-- | folks/name-details.vala | 193 |
2 files changed, 414 insertions, 12 deletions
diff --git a/folks/individual.vala b/folks/individual.vala index 06cae99d..7b3eccdd 100644 --- a/folks/individual.vala +++ b/folks/individual.vala @@ -1,6 +1,6 @@ /* * Copyright (C) 2010 Collabora Ltd. - * Copyright (C) 2011 Philip Withnall + * Copyright (C) 2011, 2013 Philip Withnall * * This library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -303,6 +303,29 @@ public class Folks.Individual : Object, */ public signal void removed (Individual? replacement_individual); + private string _display_name = ""; + + /** + * The name of this Individual to display in the UI. + * + * This value is set according to the following list of possibilities, each + * one being tried first on the primary persona, then on all other personas in + * the Individual, before falling back to the next item on the list: + * # Alias + * # Full name, structured name or nickname + * # E-mail address + * # Display ID (e.g. foo@example.org) + * # Postal address + * # _("Unnamed Person") + * + * @since UNRELEASED + */ + [CCode (notify = false)] + public string display_name + { + get { return this._display_name; } + } + private string _alias = ""; /** @@ -1340,6 +1363,9 @@ public class Folks.Individual : Object, this._update_postal_addresses (false); this._update_local_ids (false); this._update_location (); + + /* Entirely derived fields. */ + this._update_display_name (); } /* Delegate to update the value of a property on this individual from the @@ -1707,6 +1733,202 @@ public class Folks.Individual : Object, }); } + private string _look_up_alias_for_display_name (Persona? p) + { + var a = p as AliasDetails; + if (a != null && a.alias != null) + { + return a.alias; + } + + return ""; + } + + private string _look_up_name_details_for_display_name (Persona? p) + { + var n = p as NameDetails; + if (n != null) + { + if (n.full_name != "") + { + return n.full_name; + } + else if (n.structured_name != null) + { + return n.structured_name.to_string (); + } + else if (n.nickname != "") + { + return n.nickname; + } + } + + return ""; + } + + private string _look_up_email_address_for_display_name (Persona? p) + { + var e = p as EmailDetails; + if (e != null) + { + foreach (var email_fd in ((!) e).email_addresses) + { + if (email_fd.value != null) + { + return email_fd.value; + } + } + } + + return ""; + } + + private string _look_up_display_id_for_display_name (Persona? p) + { + if (p != null && p.display_id != null) + { + return p.display_id; + } + + return ""; + } + + private string _look_up_postal_address_for_display_name (Persona? p) + { + var address_details = p as PostalAddressDetails; + if (address_details != null) + { + foreach (var pa_fd in ((!) address_details).postal_addresses) + { + var pa = pa_fd.value; + if (pa != null) + { + return pa.to_string (); + } + } + } + + return ""; + } + + private void _update_display_name () + { + Persona? primary_persona = null; + var new_display_name = ""; + + /* Find the primary persona first. The primary persona's values will be + * preferred in every case where they're set. */ + foreach (var p in this._persona_set) + { + if (p.store.is_primary_store) + { + primary_persona = p; + break; + } + } + + /* See if any persona has an alias set. */ + new_display_name = this._look_up_alias_for_display_name (primary_persona); + + foreach (var p in this._persona_set) + { + if (new_display_name != "") + { + break; + } + + new_display_name = this._look_up_alias_for_display_name (p); + } + + /* Try NameDetails next. */ + if (new_display_name == "") + { + new_display_name = + this._look_up_name_details_for_display_name (primary_persona); + + foreach (var p in this._persona_set) + { + if (new_display_name != "") + { + break; + } + + new_display_name = + this._look_up_name_details_for_display_name (p); + } + } + + /* Now the e-mail addresses. */ + if (new_display_name == "") + { + new_display_name = + this._look_up_email_address_for_display_name (primary_persona); + + foreach (var p in this._persona_set) + { + if (new_display_name != "") + { + break; + } + + new_display_name = + this._look_up_email_address_for_display_name (p); + } + } + + /* Now the display-id. */ + if (new_display_name == "") + { + new_display_name = + this._look_up_display_id_for_display_name (primary_persona); + + foreach (var p in this._persona_set) + { + if (new_display_name != "") + { + break; + } + + new_display_name = + this._look_up_display_id_for_display_name (p); + } + } + + /* Finally fall back to the postal address. */ + if (new_display_name == "") + { + new_display_name = + this._look_up_postal_address_for_display_name (primary_persona); + + foreach (var p in this._persona_set) + { + if (new_display_name != "") + { + break; + } + + new_display_name = + this._look_up_postal_address_for_display_name (p); + } + } + + /* Ultimate fall back: a static string. */ + if (new_display_name == "") + { + /* Translators: This is the default name for an Individual + * when displayed in the UI if no personal details are available + * for them. */ + new_display_name = _("Unnamed Person"); + } + + if (new_display_name != this._display_name) + { + this._display_name = new_display_name; + debug ("Setting display name ‘%s’", new_display_name); + this.notify_property ("display-name"); + } + } + private void _update_alias () { this._update_single_valued_property (typeof (AliasDetails), (p) => @@ -1751,7 +1973,10 @@ public class Folks.Individual : Object, if (this._alias != alias) { this._alias = alias; + debug ("Setting alias ‘%s’", alias); this.notify_property ("alias"); + + this._update_display_name (); } }); } @@ -1932,6 +2157,8 @@ public class Folks.Individual : Object, { this._structured_name = name; this.notify_property ("structured-name"); + + this._update_display_name (); } }); } @@ -1961,6 +2188,8 @@ public class Folks.Individual : Object, { this._full_name = new_full_name; this.notify_property ("full-name"); + + this._update_display_name (); } }); } @@ -1990,6 +2219,8 @@ public class Folks.Individual : Object, { this._nickname = new_nickname; this.notify_property ("nickname"); + + this._update_display_name (); } }); } diff --git a/folks/name-details.vala b/folks/name-details.vala index 956346cd..5e87e83f 100644 --- a/folks/name-details.vala +++ b/folks/name-details.vala @@ -1,6 +1,6 @@ /* - * Copyright (C) 2011 Collabora Ltd. - * Copyright (C) 2011 Philip Withnall + * Copyright (C) 2011, 2013 Collabora Ltd. + * Copyright (C) 2011, 2013 Philip Withnall * * This library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -186,22 +186,193 @@ public class Folks.StructuredName : Object this._suffixes == other.suffixes; } + private string _extract_initials (string names) + { + /* Extract the first letter of each word (where a word is a group of + * characters following whitespace or a hyphen. + * I've made this up since the documentation on + * http://lh.2xlibre.net/values/name_fmt/ doesn't specify how to extract + * the initials from a set of names. It should work for Western names, + * but I'm not so sure about other names. */ + var output = new StringBuilder (); + var at_start_of_word = true; + int index = 0; + unichar c; + + while (names.get_next_char (ref index, out c) == true) + { + /* Grab a new initial from any word preceded by a space or a hyphen, + * so (e.g.) ‘Mary-Jane’ becomes ‘MJ’. */ + if (c.isspace () || c == '-') + { + at_start_of_word = true; + } + else if (at_start_of_word) + { + output.append_unichar (c); + at_start_of_word = false; + } + } + + return output.str; + } + /** * Formatted version of the structured name. * + * @return name formatted according to the current locale * @since 0.4.0 */ public string to_string () { - /* Translators: format for the formatted structured name. - * Parameters (in order) are: prefixes (for the name), given name, - * family name, additional names and (name) suffixes */ - var str = "%s, %s, %s, %s, %s"; - return str.printf (this.prefixes, - this.given_name, - this.family_name, - this.additional_names, - this.suffixes); + /* FIXME: Ideally we’d use a format string translated to the locale of the + * persona whose name is being formatted, but no backend provides + * information about personas’ locales, so we have to settle for the + * current user’s locale. + * + * We thought about using nl_langinfo(_NL_NAME_NAME_FMT) here, but + * decided against it because: + * 1. It’s not the best documented API in the world, and its stability + * is in question. + * 2. An attempt to improve the interface in glibc met with a wall of + * complaints: https://sourceware.org/bugzilla/show_bug.cgi?id=14641. + * + * However, we do re-use the string format placeholders from + * _NL_NAME_NAME_FMT (as documented here: + * http://lh.2xlibre.net/values/name_fmt/) because there’s a chance glibc + * might eventually grow a useful interface for this. + * + * It does mean we have to implement our own parser for the name_fmt + * format though, since glibc doesn’t provide a formatting function. */ + + /* Translators: This is a format string used to convert structured names + * to a single string. It should be translated to the predominant + * semi-formal name format for your locale, using the placeholders + * documented here: http://lh.2xlibre.net/values/name_fmt/. You may be + * able to re-use the existing glibc format string for your locale on that + * page if it’s suitable. + * + * More explicitly: the supported placeholders are %f, %F, %g, %G, %m, %M, + * %t. The romanisation modifier (e.g. %Rf) is recognized but ignored. + * %s, %S and %d are all replaced by the same thing (the ‘Honorific + * Prefixes’ from vCard) so please avoid using more than one. + * + * For example, the format string ‘%g%t%m%t%f’ expands to ‘John Andrew + * Lees’ when used for a persona with first name ‘John’, additional names + * ‘Andrew’ and family names ‘Lees’. + * + * If you need additional placeholders with other information or + * punctuation, please file a bug against libfolks: + * https://bugzilla.gnome.org/enter_bug.cgi?product=folks + */ + var name_fmt = _("%g%t%m%t%f"); + + return this.to_string_with_format (name_fmt); + } + + /** + * Formatted version of the structured name. + * + * This allows a custom format string to be specified, using the placeholders + * described on [[http://lh.2xlibre.net/values/name_fmt/]]. This ``name_fmt`` + * must almost always be translated to the current locale. (Ideally it would + * be translated to the locale of the persona whose name is being formatted, + * but such locale information isn’t available.) + * + * @param name_fmt format string for the name + * @return name formatted according to the given format + * @since UNRELEASED + */ + public string to_string_with_format (string name_fmt) + { + var output = new StringBuilder (); + var in_field_descriptor = false; + var field_descriptor_romanised = false; + var field_descriptor_empty = true; + int index = 0; + unichar c; + + while (name_fmt.get_next_char (ref index, out c) == true) + { + /* Start of a field descriptor. */ + if (c == '%') + { + in_field_descriptor = !in_field_descriptor; + + /* If entering a field descriptor, reset the state + * and continue to the next character. */ + if (in_field_descriptor) + { + field_descriptor_romanised = false; + continue; + } + } + + if (in_field_descriptor) + { + /* Romanisation, e.g. using a field descriptor ‘%Rg’. */ + if (c == 'R') + { + /* FIXME: Romanisation isn't supported yet. */ + field_descriptor_romanised = true; + continue; + } + + var val = ""; + + /* Handle the different types of field descriptor. */ + if (c == 'f') + { + val = this._family_name; + } + else if (c == 'F') + { + val = this._family_name.up (); + } + else if (c == 'g') + { + val = this._given_name; + } + else if (c == 'G') + { + val = this._extract_initials (this._given_name); + } + else if (c == 'm') + { + val = this._additional_names; + } + else if (c == 'M') + { + val = this._extract_initials (this._additional_names); + } + else if (c == 's' || c == 'S' || c == 'd') + { + /* FIXME: Not ideal, but prefixes will have to do. */ + val = this._prefixes; + } + else if (c == 't') + { + val = (field_descriptor_empty == false) ? " " : ""; + } + else if (c == 'l' || c == 'o' || c == 'p') + { + /* FIXME: Not supported. */ + val = ""; + } + + /* Append the value of the field descriptor. */ + output.append (val); + in_field_descriptor = false; + field_descriptor_empty = (val == ""); + } + else + { + /* Handle non-field descriptor characters. */ + output.append_unichar (c); + } + } + + return output.str; } } |