summaryrefslogtreecommitdiff
path: root/folks
diff options
context:
space:
mode:
authorPhilip Withnall <philip.withnall@collabora.co.uk>2012-07-25 13:10:02 -0600
committerPhilip Withnall <philip.withnall@collabora.co.uk>2013-11-08 00:40:36 +0000
commitfded410fd72c290596fb2b2fb2929fa92339b1c6 (patch)
treea01e632cc10609c48efa9000df76b55f71007f1b /folks
parentc9c428ee6713fcc40e5db636c81f2598cec572ee (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.vala233
-rw-r--r--folks/name-details.vala193
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;
}
}