[gnome-contacts/wip/nielsdg/cleanup-types] Types: decouple TypeSet and TypeDescriptor



commit e999846811107493fa3525d2484c15704a8dbd45
Author: Niels De Graef <nielsdegraef gmail com>
Date:   Sat Dec 15 22:22:42 2018 +0100

    Types: decouple TypeSet and TypeDescriptor
    
    We completely decouple TypeSet and TypeDescriptor and expose the former
    as a public class, so we can use it from outside. This means we don't
    necessarily need to fiddle with TreeIters if we know what we need from
    the TypeDescriptor. We also slit up VCardTypeMapping in a separate file,
    and put all the logic whether something matches or not as methods of
    that struct.
    
    This commit cleans up a lot of the mess in TypeSet. I'm afraid that
    means it's quite a large diff, but from now on, the code should be more
    decoupled than before, making changes at the very least bearable.
    
    There's also some more debug statements, which should help in case
    something goes wrong when running at one of our users.

 po/POTFILES.in                       |   1 +
 po/POTFILES.skip                     |   1 +
 src/contacts-type-combo.vala         |  13 +-
 src/contacts-type-descriptor.vala    | 151 ++++++++++++++
 src/contacts-typeset.vala            | 390 +++++++++++++++--------------------
 src/contacts-vcard-type-mapping.vala |  66 ++++++
 src/meson.build                      |   2 +
 7 files changed, 401 insertions(+), 223 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 2650ca2..f40b93e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -32,6 +32,7 @@ src/contacts-link-suggestion-grid.vala
 src/contacts-settings.vala
 src/contacts-setup-window.vala
 src/contacts-typeset.vala
+src/contacts-type-descriptor.vala
 src/contacts-window.vala
 src/main.vala
 src/org.gnome.Contacts.gschema.xml
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 56a88a3..5adf6c0 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -20,5 +20,6 @@ src/contacts-link-suggestion-grid.c
 src/contacts-settings.c
 src/contacts-setup-window.c
 src/contacts-typeset.c
+src/contacts-type-descriptor.c
 src/contacts-window.c
 src/main.c
diff --git a/src/contacts-type-combo.vala b/src/contacts-type-combo.vala
index 6cdeede..aea3c6e 100644
--- a/src/contacts-type-combo.vala
+++ b/src/contacts-type-combo.vala
@@ -77,7 +77,7 @@ public class Contacts.TypeCombo : Grid  {
 
     if (text != "") {
       TreeIter iter;
-      type_set.add_custom_label (text, out iter);
+      type_set.get_iter_for_custom_label (text, out iter);
 
       last_active = iter;
       combo.set_active_iter (iter);
@@ -128,19 +128,24 @@ public class Contacts.TypeCombo : Grid  {
 
   public void set_active (AbstractFieldDetails details) {
     TreeIter iter;
-    type_set.lookup_type (details, out iter);
+    type_set.get_iter_for_field_details (details, out iter);
     set_from_iter (iter);
   }
 
   public void set_to (string type) {
     TreeIter iter;
-    type_set.lookup_type_by_string (type, out iter);
+    type_set.get_iter_for_vcard_type (type, out iter);
     set_from_iter (iter);
   }
 
   public void update_details (AbstractFieldDetails details) {
     TreeIter iter;
     combo.get_active_iter (out iter);
-    type_set.update_details (details, iter);
+
+    TypeDescriptor descriptor;
+    string display_name;
+    combo.model.get (iter, 0, out display_name, 1, out descriptor);
+    assert (display_name != null); // Not separator
+    descriptor.save_to_field_details (details);
   }
 }
diff --git a/src/contacts-type-descriptor.vala b/src/contacts-type-descriptor.vala
new file mode 100644
index 0000000..efc96ce
--- /dev/null
+++ b/src/contacts-type-descriptor.vala
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2018 Niels De Graef <nielsdegraef gmail com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Gtk;
+using Gee;
+using Folks;
+
+/**
+ * The TypeDescriptor is the internal representation of a property's type.
+ */
+public class Contacts.TypeDescriptor : Object {
+
+  public const string X_GOOGLE_LABEL = "x-google-label";
+
+  private enum Source {
+    VCARD,
+    OTHER,
+    CUSTOM;
+
+    public string to_string () {
+      switch (this) {
+        case VCARD:
+          return "vcard";
+        case OTHER:
+          return "other";
+        case CUSTOM:
+          return "custom";
+      }
+      return "INVALID";
+    }
+  }
+
+  private Source source;
+  public string? name = null;
+  public string[]? vcard_types = null;
+  public TreeIter iter;
+
+  /**
+   * Returns the translated name for this property.
+   */
+  public string display_name {
+    get {
+      if (is_custom ())
+        return this.name;
+      return dgettext (Config.GETTEXT_PACKAGE, this.name);
+    }
+  }
+
+  /**
+   * Creates a TypeDescriptor which is mappable to the given VCard Type strings.
+   */
+  public TypeDescriptor.vcard (string untranslated_name, string[] types) {
+    this.source = Source.VCARD;
+    this.name = untranslated_name;
+    this.vcard_types = types;
+  }
+
+  /**
+   * Creates a TypeDescriptor with a custom label
+   */
+  public TypeDescriptor.custom (string name) {
+    this.source = Source.CUSTOM;
+    this.name = name;
+  }
+
+  /**
+   * Creates a TypeDescriptor which represents all non-representable types.
+   */
+  public TypeDescriptor.other () {
+    this.source = Source.OTHER;
+    this.name = N_("Other");
+  }
+
+  public bool is_custom () {
+    return this.source == Source.CUSTOM;
+  }
+
+  public void save_to_field_details (AbstractFieldDetails details) {
+    debug ("Saving type %s", to_string ());
+
+    var old_parameters = details.parameters;
+    var new_parameters = new HashMultiMap<string, string> ();
+
+    // Check whether PREF VCard "flag" is set
+    bool has_pref = false;
+    foreach (var val in old_parameters["type"]) {
+      if (val.ascii_casecmp ("PREF") == 0) {
+        has_pref = true;
+        break;
+      }
+    }
+
+    // Copy over all parameters, execept the ones we're going to create ourselves
+    foreach (var param in old_parameters.get_keys ()) {
+      if (param != "type" && param != X_GOOGLE_LABEL)
+        foreach (var val in old_parameters[param])
+          new_parameters[param] = val;
+    }
+
+    // Set the type based on our Source
+    switch (this.source) {
+      case Source.VCARD:
+        foreach (var type in this.vcard_types)
+          if (type != null)
+            new_parameters["type"] = type;
+        break;
+      case Source.OTHER:
+        new_parameters["type"] = "OTHER";
+        break;
+      case Source.CUSTOM:
+        new_parameters["type"] = "OTHER";
+        new_parameters[X_GOOGLE_LABEL] = this.name;
+        break;
+    }
+
+    if (has_pref)
+      new_parameters["type"] = "PREF";
+
+    // We didn't crash 'n burn, so lets
+    details.parameters = new_parameters;
+  }
+
+  /**
+   * Converts the TypeDescriptor to a string. Should only be used for debugging.
+   */
+  public string to_string () {
+    StringBuilder str = new StringBuilder ("{ ");
+    str.append_printf (".source = %s, ", this.source.to_string ());
+    str.append_printf (".name = \"%s\", ", this.name);
+    str.append_printf (".display_name = \"%s\", ", this.display_name);
+    if (this.vcard_types == null)
+      str.append_printf (".vcard_types = NULL }");
+    else
+      str.append_printf (".vcard_types = [ %s }", string.joinv (", ", this.vcard_types));
+    return str.str;
+  }
+}
diff --git a/src/contacts-typeset.vala b/src/contacts-typeset.vala
index fb329f8..4b5a5e4 100644
--- a/src/contacts-typeset.vala
+++ b/src/contacts-typeset.vala
@@ -19,224 +19,189 @@ using Gtk;
 using Gee;
 using Folks;
 
+/**
+ * A TypeSet contains all the possible types of a property. For example, a
+ * phone number can be both for a personal phone, a work phone or even a fax
+ * machine.
+ */
 public class Contacts.TypeSet : Object  {
-  const string X_GOOGLE_LABEL = "x-google-label";
-  const int MAX_TYPES = 3;
-
-  private struct VcardTypeMapping {
-    unowned string display_name_u;
-    unowned string types[3]; //MAX_TYPES
-  }
 
-  private class TypeDescriptor : Object {
-    public string display_name; // Translated
-    public VcardTypeMapping? vcard_mapping;
-    public TreeIter iter; // Set if in_store
-    public bool in_store;
-  }
+  /** Returns the category of typeset (mostly used for debugging). */
+  public string category { get; construct set; }
 
   // Dummy TypeDescriptor to mark the "Other..." store entry
-  private static TypeDescriptor other_dummy = new TypeDescriptor ();
-
-  // Map from translated display name to TypeDescriptor for all "standard" types
-  private HashTable<unowned string, TypeDescriptor> display_name_hash;
-  // List of VcardTypeMapping
-  private Gee.List<VcardTypeMapping?> vcard_type_mappings;
-  // Map from display name to TreeIter for all custom types
-  private HashTable<string, TreeIter?> custom_hash;
-
-  public Gtk.ListStore store;
-  private TreeIter other_iter;
-
-  private TypeSet () {
-    display_name_hash = new HashTable<unowned string, TypeDescriptor> (str_hash, str_equal);
-    this.vcard_type_mappings = new Gee.ArrayList<VcardTypeMapping?> ();
-    custom_hash = new HashTable<string, TreeIter? > (str_hash, str_equal);
-
-    store = new Gtk.ListStore (2,
-                               // Display name or null for separator
-                               typeof(string?),
-                               // TypeDescriptor for standard types, null for custom
-                               typeof (TypeDescriptor));
-  }
-
-  private void add_descriptor_to_store (TypeDescriptor descriptor, bool is_custom) {
-    if (descriptor.in_store)
-      return;
-
-    descriptor.in_store = true;
-    if (is_custom)
-      this.store.insert_before (out descriptor.iter, null);
-    else
-      this.store.append (out descriptor.iter);
+  private TypeDescriptor other_dummy = new TypeDescriptor.other ();
 
-    store.set (descriptor.iter, 0, descriptor.display_name, 1, descriptor);
-  }
+  // List of VcardTypeMapping. This makes sure of keeping the correct order
+  private Gee.List<VcardTypeMapping?> vcard_type_mappings
+      = new Gee.ArrayList<VcardTypeMapping?> ();
 
-  private void add_vcard_mapping (VcardTypeMapping vcard_mapping) {
-    unowned string dn = dgettext (Config.GETTEXT_PACKAGE, vcard_mapping.display_name_u);
-    TypeDescriptor descriptor = display_name_hash.lookup (dn);
-    if (descriptor == null) {
-      descriptor = new TypeDescriptor ();
-      descriptor.display_name = dn;
-      display_name_hash.insert (dn, descriptor);
-    }
+  // Contains 2 columns:
+  // 1. The type's display name (or null for a separator)
+  // 2. The TypeDescriptor
+  public Gtk.ListStore store { get; private set; }
 
-    if (descriptor.vcard_mapping == null)
-      descriptor.vcard_mapping = vcard_mapping;
+  /**
+   * Creates a TypeSet for the given category, e.g. "phones" (used for debugging)
+   */
+  private TypeSet (string? category) {
+    Object (category: category);
 
-    this.vcard_type_mappings.add (vcard_mapping);
+    this.store = new Gtk.ListStore (2, typeof (unowned string?), typeof (TypeDescriptor));
   }
 
-  private void add_vcard_mapping_done (string[] standard_untranslated) {
-    foreach (var untranslated in standard_untranslated) {
-      var descriptor = display_name_hash.lookup (dgettext (Config.GETTEXT_PACKAGE, untranslated));
-      if (descriptor != null)
-        add_descriptor_to_store (descriptor, false);
-      else
-        error ("Internal error: Can't find display name %s in TypeSet data", untranslated);
-    }
-
-    store.append (out other_iter);
-    /* Refers to the type of the detail, could be Home, Work or Other for email, and the same
-     * for phone numbers, addresses, etc. */
-    store.set (other_iter, 0, _("Other"), 1, other_dummy);
+  /**
+   * Returns the TreeIter which corresponds to the type of the given
+   * AbstractFieldDetails.
+   */
+  public void get_iter_for_field_details (AbstractFieldDetails detail, out TreeIter iter) {
+    // Note that we shouldn't have null here, but it's there just to be sure.
+    var d = lookup_descriptor_for_field_details (detail);
+    iter = (d != null)? d.iter : other_dummy.iter;
   }
 
-  public void add_custom_label (string label, out TreeIter iter) {
-    // If we add a custom name equal to one of the standard ones, reuse that one
-    var descriptor = display_name_hash.lookup (label);
-    if (descriptor != null) {
-      add_descriptor_to_store (descriptor, true);
-      iter = descriptor.iter;
-      return;
-    }
-
-    if (label == _("Other")) {
-      iter = other_iter;
-      return;
-    }
+  /**
+   * Returns the TreeIter which corresponds the best to the given vcard type.
+   * @param type: A VCard-like type, such as "HOME" or "CELL".
+   */
+  public void get_iter_for_vcard_type (string type, out TreeIter iter) {
+    unowned TypeDescriptor? d = lookup_descriptor_by_vcard_type (type);
+    iter = (d != null)? d.iter : this.other_dummy.iter;
+  }
 
-    unowned TreeIter? iterp = custom_hash.lookup (label);
-    if (iterp != null) {
-      iter = iterp;
-      return;
-    }
+  /**
+   * Returns the TreeIter which corresponds the best to the given custom label.
+   */
+  public void get_iter_for_custom_label (string label, out TreeIter iter) {
+    var descr = get_descriptor_for_custom_label (label);
+    if (descr == null)
+      descr = create_descriptor_for_custom_label (label);
+    iter = descr.iter;
+  }
 
-    store.insert_before (out iter, null);
-    store.set (iter, 0, label, 1, null);
-    custom_hash.insert (label, iter);
+  /**
+   * Returns the display name for the type of the given AbstractFieldDetails.
+   */
+  public string format_type (AbstractFieldDetails detail) {
+    var d = lookup_descriptor_for_field_details (detail);
+    return (d != null)? d.display_name : _("Other");
   }
 
-  private unowned TypeDescriptor? lookup_descriptor_by_string (string str) {
-    foreach (VcardTypeMapping? d in this.vcard_type_mappings) {
-      if (d.types[1] == null) {
-        unowned string dn = dgettext (Config.GETTEXT_PACKAGE, d.display_name_u);
-        return display_name_hash.lookup (dn);
-      }
-    }
+  /**
+   * Adds the TypeDescriptor to the {@link Typeset}'s store.
+   * @param descriptor: The TypeDescription to be added
+   */
+  private void add_descriptor_to_store (TypeDescriptor descriptor) {
+    debug ("%s: Adding type %s to store", this.category, descriptor.to_string ());
 
-    return null;
+    if (descriptor.is_custom ())
+      this.store.insert_before (out descriptor.iter, null);
+    else
+      this.store.append (out descriptor.iter);
+
+    store.set (descriptor.iter, 0, descriptor.display_name, 1, descriptor);
   }
 
-  private unowned TypeDescriptor? lookup_descriptor (AbstractFieldDetails detail) {
-    var i = detail.get_parameter_values ("type");
-    if (i == null || i.is_empty)
+  /**
+   * Returns the TypeDescriptor for the given display name in the
+   * {@link Typeset}'s store, if any.
+   *
+   * @param display_name: The translated display name
+   * @return: The appropriate TypeDescriptor or null if no match was found.
+   */
+  public unowned TypeDescriptor? lookup_descriptor_in_store (string display_name) {
+    TreeIter iter;
+
+    // Make sure we handle an empty store
+    if (!this.store.get_iter_first (out iter))
       return null;
 
-    var list = new Gee.ArrayList<string> ();
-    foreach (var s in detail.get_parameter_values ("type"))
-      list.add (s.up ());
+    do {
+      unowned TypeDescriptor? type_descr;
+      this.store.get (iter, 1, out type_descr);
 
-    // Make sure all items in the VcardTypeMapping is in the specified type, there might
-    // be more, but we ignore them (so a HOME,FOO,PREF,BLAH contact still matches
-    // the standard HOME one, but not HOME,FAX
-    foreach (VcardTypeMapping? d in this.vcard_type_mappings) {
-      bool all_found = true;
-      for (int j = 0; j < MAX_TYPES && d.types[j] != null; j++) {
-        if (!list.contains (d.types[j])) {
-          all_found = false;
-          break;
-        }
-      }
-      if (all_found) {
-        unowned string dn = dgettext (Config.GETTEXT_PACKAGE, d.display_name_u);
-        return display_name_hash.lookup (dn);
-      }
-    }
+      if (display_name.ascii_casecmp (type_descr.display_name) == 0)
+        return type_descr;
+      if (display_name.ascii_casecmp (type_descr.name) == 0)
+        return type_descr;
+    } while (this.store.iter_next (ref iter));
 
+    // Nothing was found
     return null;
   }
 
-  // Looks up (and creates if necessary) the type in the store
-  public void lookup_type (AbstractFieldDetails detail, out TreeIter iter) {
-    if (detail.parameters.contains (X_GOOGLE_LABEL)) {
-      var label = Utils.get_first<string> (detail.parameters.get (X_GOOGLE_LABEL));
-      add_custom_label (label, out iter);
-      return;
+  private void add_vcard_mapping (VcardTypeMapping vcard_mapping) {
+    TypeDescriptor? descriptor = lookup_descriptor_in_store (vcard_mapping.name);
+    if (descriptor == null) {
+      descriptor = new TypeDescriptor.vcard (vcard_mapping.name, vcard_mapping.types);
+      add_descriptor_to_store (descriptor);
     }
 
-    unowned TypeDescriptor? d = lookup_descriptor (detail);
-    if (d != null) {
-      add_descriptor_to_store (d, true);
-      iter = d.iter;
-    } else {
-      iter = other_iter;
-    }
+    this.vcard_type_mappings.add (vcard_mapping);
   }
 
-  public void lookup_type_by_string (string type, out TreeIter iter) {
-    unowned TypeDescriptor? d = lookup_descriptor_by_string (type);
-    iter = (d != null)? d.iter : this.other_iter;
+  // Refers to the type of the detail, i.e. "Other" instead of "Personal" or "Work"
+  private void add_type_other () {
+    store.append (out other_dummy.iter);
+    store.set (other_dummy.iter, 0, other_dummy.display_name, 1, other_dummy);
   }
 
-  public string format_type (AbstractFieldDetails detail) {
-    if (detail.parameters.contains (X_GOOGLE_LABEL))
-      return Utils.get_first<string> (detail.parameters[X_GOOGLE_LABEL]);
+  /**
+   * Tries to find the TypeDescriptor matching the given custom label, or null if none.
+   */
+  public unowned TypeDescriptor? get_descriptor_for_custom_label (string label) {
+    // Check in the current display names
+    unowned TypeDescriptor? descriptor = lookup_descriptor_in_store (label);
+    if (descriptor != null)
+      return descriptor;
+
+    // Try again, but use the vcard types too
+    descriptor = lookup_descriptor_by_vcard_type (label);
+    return descriptor;
+  }
 
-    unowned TypeDescriptor? d = lookup_descriptor (detail);
-    return (d != null)? d.display_name : _("Other");
+  private TypeDescriptor create_descriptor_for_custom_label (string label) {
+    var new_descriptor = new TypeDescriptor.custom (label);
+    add_descriptor_to_store (new_descriptor);
+    return new_descriptor;
   }
 
-  public void update_details (AbstractFieldDetails details, TreeIter iter) {
-    var old_parameters = details.parameters;
-    details.parameters = new HashMultiMap<string, string> ();
-    bool has_pref = false;
-    foreach (var val in old_parameters["type"]) {
-      if (val.ascii_casecmp ("PREF") == 0) {
-        has_pref = true;
-        break;
-      }
+  /**
+   * Returns the TypeDescriptor which corresponds the best to the given vcard type.
+   * @param str: A VCard-like type, such as "HOME" or "CELL".
+   */
+  private unowned TypeDescriptor? lookup_descriptor_by_vcard_type (string str) {
+    foreach (VcardTypeMapping? mapping in this.vcard_type_mappings) {
+      if (mapping.contains (str))
+        return lookup_descriptor_in_store (mapping.name);
     }
-    foreach (var param in old_parameters.get_keys()) {
-      if (param != "type" && param != X_GOOGLE_LABEL)
-        foreach (var val in old_parameters[param])
-          details.parameters[param] = val;
+
+    return null;
+  }
+
+  private TypeDescriptor? lookup_descriptor_for_field_details (AbstractFieldDetails detail) {
+    if (detail.parameters.contains (TypeDescriptor.X_GOOGLE_LABEL)) {
+      var label = Utils.get_first<string> (detail.parameters[TypeDescriptor.X_GOOGLE_LABEL]);
+      var descriptor = get_descriptor_for_custom_label (label);
+      // Still didn't find it => create it
+      if (descriptor == null)
+        descriptor = create_descriptor_for_custom_label (label);
+      return descriptor;
     }
 
-    TypeDescriptor descriptor;
-    string display_name;
-    store.get (iter, 0, out display_name, 1, out descriptor);
-
-    assert (display_name != null); // Not separator
-
-    if (descriptor == null) { // A custom label
-      details.parameters["type"] = "OTHER";
-      details.parameters[X_GOOGLE_LABEL] = display_name;
-    } else {
-      if (descriptor == other_dummy) {
-        details.parameters["type"] = "OTHER";
-      } else {
-        VcardTypeMapping? vcard_mapping = descriptor.vcard_mapping;
-        for (int j = 0; j < MAX_TYPES && vcard_mapping.types[j] != null; j++)
-          details.parameters["type"] = vcard_mapping.types[j];
-      }
+    var types = detail.get_parameter_values ("type");
+    if (types == null || types.is_empty)
+      return null;
+
+    foreach (VcardTypeMapping? d in this.vcard_type_mappings) {
+      if (d.matches (types))
+        return lookup_descriptor_in_store (d.name);
     }
 
-    if (has_pref)
-      details.parameters["type"] = "PREF";
+    return null;
   }
 
+
   private static TypeSet _general;
   private const VcardTypeMapping[] general_data = {
     // List most specific first, always in upper case
@@ -245,15 +210,11 @@ public class Contacts.TypeSet : Object  {
   };
   public static TypeSet general {
     get {
-      string[] standard = {
-        "Work", "Home"
-      };
-
       if (_general == null) {
-        _general = new TypeSet ();
+        _general = new TypeSet ("General");
         for (int i = 0; i < general_data.length; i++)
           _general.add_vcard_mapping (general_data[i]);
-        _general.add_vcard_mapping_done (standard);
+        _general.add_type_other ();
       }
 
       return _general;
@@ -269,15 +230,11 @@ public class Contacts.TypeSet : Object  {
   };
   public static TypeSet email {
     get {
-      string[] standard = {
-        "Personal", "Home", "Work"
-      };
-
       if (_email == null) {
-        _email = new TypeSet ();
+        _email = new TypeSet ("Emails");
         for (int i = 0; i < email_data.length; i++)
           _email.add_vcard_mapping (email_data[i]);
-        _email.add_vcard_mapping_done (standard);
+        _email.add_type_other ();
       }
 
       return _email;
@@ -285,41 +242,36 @@ public class Contacts.TypeSet : Object  {
   }
 
   private static TypeSet _phone;
+  private const VcardTypeMapping[] phone_data = {
+    // List most specific first, always in upper case
+    { N_("Assistant"),  { "X-EVOLUTION-ASSISTANT" } },
+    { N_("Work"),       { "WORK", "VOICE" } },
+    { N_("Work Fax"),   { "WORK", "FAX" } },
+    { N_("Work"),       { "WORK" } },
+    { N_("Callback"),   { "X-EVOLUTION-CALLBACK" } },
+    { N_("Car"),        { "CAR" } },
+    { N_("Company"),    { "X-EVOLUTION-COMPANY" } },
+    { N_("Home"),       { "HOME", "VOICE" } },
+    { N_("Home Fax"),   { "HOME", "FAX" } },
+    { N_("Home"),       { "HOME" } },
+    { N_("ISDN"),       { "ISDN" } },
+    { N_("Mobile"),     { "CELL" } },
+    { N_("Other"),      { "VOICE" } },
+    { N_("Fax"),        { "FAX" } },
+    { N_("Pager"),      { "PAGER" } },
+    { N_("Radio"),      { "X-EVOLUTION-RADIO" } },
+    { N_("Telex"),      { "X-EVOLUTION-TELEX" } },
+    /* To translators: TTY is Teletypewriter */
+    { N_("TTY"),        { "X-EVOLUTION-TTYTDD" } }
+  };
   public static TypeSet phone {
     get {
-      const VcardTypeMapping[] data = {
-        // List most specific first, always in upper case
-        { N_("Assistant"),  { "X-EVOLUTION-ASSISTANT" } },
-        { N_("Work"),       { "WORK", "VOICE" } },
-        { N_("Work Fax"),   { "WORK", "FAX" } },
-        { N_("Callback"),   { "X-EVOLUTION-CALLBACK" } },
-        { N_("Car"),        { "CAR" } },
-        { N_("Company"),    { "X-EVOLUTION-COMPANY" } },
-        { N_("Home"),       { "HOME", "VOICE" } },
-        { N_("Home Fax"),   { "HOME", "FAX" } },
-        { N_("ISDN"),       { "ISDN" } },
-        { N_("Mobile"),     { "CELL" } },
-        { N_("Other"),      { "VOICE" } },
-        { N_("Fax"),        { "FAX" } },
-        { N_("Pager"),      { "PAGER" } },
-        { N_("Radio"),      { "X-EVOLUTION-RADIO" } },
-        { N_("Telex"),      { "X-EVOLUTION-TELEX" } },
-        /* To translators: TTY is Teletypewriter */
-        { N_("TTY"),        { "X-EVOLUTION-TTYTDD" } }
-      };
-
-      // Make sure these strings are the same as the above
-      string[] standard = {
-        "Mobile", "Work", "Home"
-      };
 
       if (_phone == null) {
-        _phone = new TypeSet ();
-        for (int i = 0; i < data.length; i++)
-          _phone.add_vcard_mapping (data[i]);
-        for (int i = 0; i < general_data.length; i++)
-          _phone.add_vcard_mapping (general_data[i]);
-        _phone.add_vcard_mapping_done (standard);
+        _phone = new TypeSet ("Phones");
+        for (int i = 0; i < phone_data.length; i++)
+          _phone.add_vcard_mapping (phone_data[i]);
+        _phone.add_type_other ();
       }
 
       return _phone;
diff --git a/src/contacts-vcard-type-mapping.vala b/src/contacts-vcard-type-mapping.vala
new file mode 100644
index 0000000..35ce3e0
--- /dev/null
+++ b/src/contacts-vcard-type-mapping.vala
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2018 Niels De Graef <nielsdegraef gmail com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Gtk;
+using Gee;
+using Folks;
+
+/**
+ * The VcardTypeMapping struct is used to map known vcard types to a display
+ * name. It also contains the logic when a vard-like type string matches with
+ * another.
+ */
+internal struct Contacts.VcardTypeMapping {
+  unowned string name; // untranslated
+  unowned string types[3]; //MAX_TYPES
+  private const int MAX_TYPES = 3;
+
+  /** Returns whether the mapping contains the given vcard type. */
+  public bool contains (string type) {
+    for (int i = 0; i < MAX_TYPES && this.types[i] != null; i++)
+      if (types_are_equal (this.types[i], type))
+        return true;
+    return false;
+  }
+
+  /**
+   * Checks whether all items in the VcardTypeMapping are in the specified @types.
+   * Even though there might be other values in @types, we ignore them.
+   *
+   * For example: [ HOME, FOO, PREF, BLAH ] should match the [ HOME ] VCard
+   * type, but not [ HOME, FAX ]
+   */
+  public bool matches (Collection<string> types) {
+    for (int i = 0; i < MAX_TYPES && this.types[i] != null; i++) {
+      bool occurs_in_list = true;
+      foreach (var type in types) {
+        if (!types_are_equal (type, this.types[i])) {
+          occurs_in_list = false;
+          break;
+        }
+      }
+
+      if (!occurs_in_list)
+        return false;
+    }
+    return true;
+  }
+
+  private static bool types_are_equal (string a, string b) {
+    return a.ascii_casecmp (b) == 0;
+  }
+}
diff --git a/src/meson.build b/src/meson.build
index f3e69c6..773c3dc 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -12,7 +12,9 @@ libcontacts_sources = files(
   'contacts-im-service.vala',
   'contacts-store.vala',
   'contacts-typeset.vala',
+  'contacts-type-descriptor.vala',
   'contacts-utils.vala',
+  'contacts-vcard-type-mapping.vala',
 )
 
 contacts_vala_args = [


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]