[geary/wip/composer-folks: 20/22] Convert ContactEntryCompletion to use app contacts



commit 6db466e866d2e279fcf8afe3d27b1ac0bf2a0a61
Author: Michael Gratton <mike vee net>
Date:   Sat Jun 15 16:47:34 2019 +1000

    Convert ContactEntryCompletion to use app contacts
    
    Remove ContactListStore, replace with Gtk.ListStore updated directly
    from within the completion class. Use Application.Contact and store
    rather than Geary.Contact objects and store for searching for contacts
    for the completion model.

 po/POTFILES.in                                     |   1 -
 src/client/application/application-controller.vala |  12 ++-
 src/client/composer/composer-widget.vala           |   5 +-
 src/client/composer/contact-entry-completion.vala  | 116 +++++++++++++++------
 src/client/composer/contact-list-store.vala        |  72 -------------
 src/client/meson.build                             |   1 -
 6 files changed, 94 insertions(+), 113 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index f117fa0d..cc42fb14 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -50,7 +50,6 @@ src/client/composer/composer-web-view.vala
 src/client/composer/composer-widget.vala
 src/client/composer/composer-window.vala
 src/client/composer/contact-entry-completion.vala
-src/client/composer/contact-list-store.vala
 src/client/composer/email-entry.vala
 src/client/composer/spell-check-popover.vala
 src/client/conversation-list/conversation-list-cell-renderer.vala
diff --git a/src/client/application/application-controller.vala 
b/src/client/application/application-controller.vala
index 31e2803e..90fcd0ea 100644
--- a/src/client/application/application-controller.vala
+++ b/src/client/application/application-controller.vala
@@ -563,6 +563,13 @@ public class Application.Controller : Geary.BaseObject {
         }
     }
 
+    /** Returns the contact store for an account, if any. */
+    public Application.ContactStore?
+        get_contact_store_for_account(Geary.Account target) {
+        AccountContext? context = this.accounts.get(target.information);
+        return (context != null) ? context.contacts : null;
+    }
+
     /** Expunges removed accounts while the controller remains open. */
     internal async void expunge_accounts() {
         try {
@@ -2691,11 +2698,6 @@ public class Application.Controller : Geary.BaseObject {
         return (context != null) ? context.emails : null;
     }
 
-    private Application.ContactStore? get_contact_store_for_account(Geary.Account target) {
-        AccountContext? context = this.accounts.get(target.information);
-        return (context != null) ? context.contacts : null;
-    }
-
     private bool should_add_folder(Gee.Collection<Geary.Folder>? all,
                                    Geary.Folder folder) {
         // if folder is openable, add it
diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala
index d29a110d..92d9bb50 100644
--- a/src/client/composer/composer-widget.vala
+++ b/src/client/composer/composer-widget.vala
@@ -653,7 +653,10 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
      * Loads and sets contact auto-complete data for the current account.
      */
     private void load_entry_completions() {
-        Geary.ContactStore contacts = this.account.contact_store;
+        Application.ContactStore contacts =
+            this.application.controller.get_contact_store_for_account(
+                this.account
+            );
         this.to_entry.completion = new ContactEntryCompletion(contacts);
         this.cc_entry.completion = new ContactEntryCompletion(contacts);
         this.bcc_entry.completion = new ContactEntryCompletion(contacts);
diff --git a/src/client/composer/contact-entry-completion.vala 
b/src/client/composer/contact-entry-completion.vala
index 96ab1333..e4419d09 100644
--- a/src/client/composer/contact-entry-completion.vala
+++ b/src/client/composer/contact-entry-completion.vala
@@ -9,11 +9,26 @@
 public class ContactEntryCompletion : Gtk.EntryCompletion, Geary.BaseInterface {
 
 
-    public new ContactListStore model {
-        get { return base.get_model() as ContactListStore; }
-        set { base.set_model(value); }
+    // Minimum visibility for the contact to appear in autocompletion.
+    private const Geary.Contact.Importance VISIBILITY_THRESHOLD =
+        Geary.Contact.Importance.RECEIVED_FROM;
+
+
+    public enum Column {
+        CONTACT,
+        MAILBOX;
+
+        public static Type[] get_types() {
+            return {
+                typeof(Application.Contact), // CONTACT
+                typeof(Geary.RFC822.MailboxAddress) // MAILBOX
+            };
+        }
     }
 
+
+    private Application.ContactStore contacts;
+
     // Text between the start of the entry or of the previous email
     // address and the current position of the cursor, if any.
     private string current_key = "";
@@ -28,9 +43,10 @@ public class ContactEntryCompletion : Gtk.EntryCompletion, Geary.BaseInterface {
     private Gtk.TreeIter? last_iter = null;
 
 
-    public ContactEntryCompletion(Geary.ContactStore contacts) {
+    public ContactEntryCompletion(Application.ContactStore contacts) {
         base_ref();
-        this.model = new ContactListStore(contacts);
+        this.contacts = contacts;
+        this.model = new Gtk.ListStore.newv(Column.get_types());
 
         // Always match all rows, since the model will only contain
         // matching addresses from the search query
@@ -62,9 +78,9 @@ public class ContactEntryCompletion : Gtk.EntryCompletion, Geary.BaseInterface {
         string completion_key = this.current_key;
         if (!Geary.String.is_empty_or_whitespace(completion_key)) {
             this.search_cancellable = new GLib.Cancellable();
-            this.model.search.begin(completion_key, this.search_cancellable);
+            this.search_contacts.begin(completion_key, this.search_cancellable);
         } else {
-            this.model.clear();
+            ((Gtk.ListStore) this.model).clear();
         }
     }
 
@@ -121,23 +137,57 @@ public class ContactEntryCompletion : Gtk.EntryCompletion, Geary.BaseInterface {
         }
     }
 
-    private string match_prefix_contact(Geary.Contact contact) {
-        string email = match_prefix_string(contact.email);
-        string real_name = match_prefix_string(contact.real_name);
+    public async void search_contacts(string query,
+                                      GLib.Cancellable? cancellable) {
+        try {
+            Gee.Collection<Application.Contact> results =
+                yield this.contacts.search(
+                    query,
+                    VISIBILITY_THRESHOLD,
+                    20,
+                    cancellable
+                );
+
+            Gtk.ListStore model = (Gtk.ListStore) this.model;
+            model.clear();
+            foreach (Application.Contact contact in results) {
+                foreach (Geary.RFC822.MailboxAddress addr
+                         in contact.email_addresses) {
+                    Gtk.TreeIter iter;
+                    model.append(out iter);
+                    model.set(iter, Column.CONTACT, contact);
+                    model.set(iter, Column.MAILBOX, addr);
+                }
+            }
 
-        // email and real_name were already escaped, then <b></b> tags
-        // were added to highlight matches. We don't want to escape
-        // them again.
-        return (contact.real_name == null)
-            ? email
-            : real_name + Markup.escape_text(" <") + email + Markup.escape_text(">");
+            // Ensure completion is visible if loading finishes after
+            // the base class's handler has triggered a completion
+            complete();
+        } catch (GLib.IOError.CANCELLED err) {
+            // All good
+        } catch (GLib.Error err) {
+            debug("Error searching contacts for completion: %s", err.message);
+        }
     }
 
-    private string? match_prefix_string(string? haystack) {
-        string? value = haystack;
-        if (!Geary.String.is_empty(this.current_key) &&
-            !Geary.String.is_empty(haystack)) {
+    private string match_prefix_contact(Geary.RFC822.MailboxAddress mailbox) {
+        string email = match_prefix_string(mailbox.address);
+        if (mailbox.name != null && !mailbox.is_spoofed()) {
+            string real_name = match_prefix_string(mailbox.name);
+            // email and real_name were already escaped, then <b></b> tags
+            // were added to highlight matches. We don't want to escape
+            // them again.
+            email = (
+                real_name +
+                Markup.escape_text(" <") + email + Markup.escape_text(">")
+            );
+        }
+        return email;
+    }
 
+    private string? match_prefix_string(string haystack) {
+        string value = haystack;
+        if (!Geary.String.is_empty(this.current_key)) {
             bool matched = false;
             try {
                 string escaped_needle = Regex.escape_string(
@@ -182,27 +232,27 @@ public class ContactEntryCompletion : Gtk.EntryCompletion, Geary.BaseInterface {
                                        Gtk.CellRenderer cell,
                                        Gtk.TreeModel tree_model,
                                        Gtk.TreeIter iter) {
-        GLib.Value contact_value;
-        tree_model.get_value(
-            iter, ContactListStore.Column.CONTACT_OBJECT, out contact_value
-        );
-
-        string render = "";
-        Geary.Contact? contact = (Geary.Contact) contact_value.get_object();
-        if (contact != null) {
-            render = this.match_prefix_contact(contact);
-        }
+        GLib.Value value;
+        tree_model.get_value(iter, Column.MAILBOX, out value);
+        Geary.RFC822.MailboxAddress mailbox =
+            (Geary.RFC822.MailboxAddress) value.get_object();
+
+        string render = this.match_prefix_contact(mailbox);
 
-        Gtk.CellRendererText text_renderer = (Gtk.CellRendererText) cell;
-        text_renderer.markup = render;
+        Gtk.CellRendererText renderer = (Gtk.CellRendererText) cell;
+        renderer.markup = render;
     }
 
     private bool on_match_selected(Gtk.TreeModel model, Gtk.TreeIter iter) {
         Gtk.Entry? entry = get_entry() as Gtk.Entry;
         if (entry != null) {
             // Update the address
+            GLib.Value value;
+            model.get_value(iter, Column.MAILBOX, out value);
+            Geary.RFC822.MailboxAddress mailbox =
+                (Geary.RFC822.MailboxAddress) value.get_object();
             this.email_addresses[this.cursor_at_address] =
-                this.model.to_full_address(iter);
+                mailbox.to_full_display();
 
             // Update the entry text
             bool current_is_last = (
diff --git a/src/client/meson.build b/src/client/meson.build
index 20802a23..df1cacdc 100644
--- a/src/client/meson.build
+++ b/src/client/meson.build
@@ -47,7 +47,6 @@ geary_client_vala_sources = files(
   'composer/composer-widget.vala',
   'composer/composer-window.vala',
   'composer/contact-entry-completion.vala',
-  'composer/contact-list-store.vala',
   'composer/email-entry.vala',
   'composer/spell-check-popover.vala',
 


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