[geary/wip/765516-gtk-widget-conversation-viewer: 167/169] Manage search term highlighting for messages appended to a conversation.



commit d713fa74576d36830fc11d0c6ba3f53687df27ff
Author: Michael James Gratton <mike vee net>
Date:   Mon Sep 12 20:51:37 2016 +1000

    Manage search term highlighting for messages appended to a conversation.
    
    * src/client/conversation-viewer/conversation-listbox.vala
      (ConversationListBox): Cache search terms and current location so new
      messages can have search applied to them as well. Add
      search_matches_found to communicate back to the ConversationViewer that
      the search maches have changed asynchronously.
    
    * src/client/conversation-viewer/conversation-viewer.vala
      (ConversationViewer): Hook up to search_matches_found when loading a
      new conversation. Manage find text/prev button state from there.

 .../conversation-viewer/conversation-listbox.vala  |   77 ++++++++++++++------
 .../conversation-viewer/conversation-viewer.vala   |   46 +++++++++---
 2 files changed, 88 insertions(+), 35 deletions(-)
---
diff --git a/src/client/conversation-viewer/conversation-listbox.vala 
b/src/client/conversation-viewer/conversation-listbox.vala
index 0f053e0..87c1aad 100644
--- a/src/client/conversation-viewer/conversation-listbox.vala
+++ b/src/client/conversation-viewer/conversation-listbox.vala
@@ -149,12 +149,16 @@ public class ConversationListBox : Gtk.ListBox {
     /** Conversation being displayed. */
     public Geary.App.Conversation conversation { get; private set; }
 
-    // Contacts for the account this conversation exists in
-    private Geary.ContactStore contact_store;
+    // Folder from which the conversation was loaded
+    internal Geary.Folder location { get; private set; }
 
+    // Used to load messages in conversation.
     private Geary.App.EmailStore email_store;
 
     // Contacts for the account this conversation exists in
+    private Geary.ContactStore contact_store;
+
+    // Contacts for the account this conversation exists in
     private Geary.AccountInformation account_info;
 
     // Was this conversation loaded from the drafts folder?
@@ -173,6 +177,9 @@ public class ConversationListBox : Gtk.ListBox {
     // Last visible row in the list, if any
     private EmailRow? last_email_row = null;
 
+    // Cached search terms to apply to new messages
+    private Gee.Set<string>? ordered_search_terms = null;
+
 
     /** Fired when an email view is added to the conversation list. */
     public signal void email_added(ConversationEmail email);
@@ -184,18 +191,24 @@ public class ConversationListBox : Gtk.ListBox {
     public signal void mark_emails(Gee.Collection<Geary.EmailIdentifier> emails,
         Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove);
 
+    /** Fired when an email that matches the current search terms is found. */
+    public signal void search_matches_found();
+
+
     /**
      * Constructs a new conversation list box instance.
      */
     public ConversationListBox(Geary.App.Conversation conversation,
-                               Geary.ContactStore contact_store,
+                               Geary.Folder location,
                                Geary.App.EmailStore? email_store,
+                               Geary.ContactStore contact_store,
                                Geary.AccountInformation account_info,
                                bool is_draft_folder,
                                Gtk.Adjustment adjustment) {
         this.conversation = conversation;
-        this.contact_store = contact_store;
+        this.location = location;
         this.email_store = email_store;
+        this.contact_store = contact_store;
         this.account_info = account_info;
         this.is_draft_folder = is_draft_folder;
 
@@ -434,7 +447,8 @@ public class ConversationListBox : Gtk.ListBox {
     /**
      * Loads search term matches for this list's emails.
      */
-    public async void load_search_terms(Geary.SearchFolder search) {
+    public async void load_search_terms() {
+        Geary.SearchFolder search = (Geary.SearchFolder) this.location;
         Geary.SearchQuery? query = search.search_query;
         if (query != null) {
 
@@ -487,38 +501,26 @@ public class ConversationListBox : Gtk.ListBox {
      *
      * Returns true if any were found, else returns false.
      */
-    public bool highlight_search_terms(Gee.Set<string> search_matches) {
+    public void highlight_search_terms(Gee.Set<string> search_matches) {
         // Webkit's highlighting is ... weird.  In order to actually
         // see all the highlighting you're applying, it seems
         // necessary to start with the shortest string and work up.
         // If you don't, it seems that shorter strings will overwrite
         // longer ones, and you're left with incomplete highlighting.
-        Gee.ArrayList<string> ordered_matches = new Gee.ArrayList<string>();
+        Gee.TreeSet<string> ordered_matches =
+            new Gee.TreeSet<string>((a, b) => a.length - b.length);
         ordered_matches.add_all(search_matches);
-        ordered_matches.sort((a, b) => a.length - b.length);
-
-        bool any_found = false;
+        this.ordered_search_terms = ordered_matches;
         this.foreach((child) => {
-                EmailRow row = (EmailRow) child;
-                bool email_found = false;
-                row.view.message_view_iterator().foreach((msg_view) => {
-                        if (msg_view.highlight_search_terms(search_matches) > 0) {
-                            email_found = true;
-                        }
-                        return true;
-                    });
-                row.is_search_match = email_found;
-                if (email_found) {
-                    any_found = true;
-                }
+                apply_search_terms((EmailRow) child);
             });
-        return any_found;
     }
 
     /**
      * Removes search term highlighting from all messages.
      */
     public void unmark_search_terms() {
+        this.ordered_search_terms = null;
         this.foreach((child) => {
                 EmailRow row = (EmailRow) child;
                 if (row.is_search_match) {
@@ -619,6 +621,12 @@ public class ConversationListBox : Gtk.ListBox {
             email.is_flagged().is_certain()) {
             row.expand();
         }
+
+        // Apply any existing search terms to the new row
+        if (this.ordered_search_terms != null) {
+            apply_search_terms(row);
+        }
+
         return row;
     }
 
@@ -688,7 +696,30 @@ public class ConversationListBox : Gtk.ListBox {
 
         return full_emails;
     }
+    
+    private void apply_search_terms(EmailRow row) {
+        if (row.view.message_bodies_loaded) {
+            apply_search_terms_impl(row);
+        } else {
+            row.view.notify["message-bodies-loaded"].connect(() => {
+                    apply_search_terms_impl(row);
+                });
+        }
+    }
 
+    private inline void apply_search_terms_impl(EmailRow row) {
+        bool found = false;
+        row.view.message_view_iterator().foreach((view) => {
+                if (view.highlight_search_terms(this.ordered_search_terms) > 0) {
+                    found = true;
+                    return false;
+                }
+                return true;
+            });
+        row.is_search_match = found;
+        if (found) {
+            search_matches_found();
+        }
     }
 
     /**
diff --git a/src/client/conversation-viewer/conversation-viewer.vala 
b/src/client/conversation-viewer/conversation-viewer.vala
index a1d99b7..0866206 100644
--- a/src/client/conversation-viewer/conversation-viewer.vala
+++ b/src/client/conversation-viewer/conversation-viewer.vala
@@ -189,8 +189,9 @@ public class ConversationViewer : Gtk.Stack {
         Geary.Account account = location.account;
         ConversationListBox new_list = new ConversationListBox(
             conversation,
-            account.get_contact_store(),
+            location,
             new Geary.App.EmailStore(account),
+            account.get_contact_store(),
             account.information,
             location.special_folder_type == Geary.SpecialFolderType.DRAFTS,
             conversation_scroller.get_vadjustment()
@@ -202,13 +203,27 @@ public class ConversationViewer : Gtk.Stack {
         this.conversation_added(new_list);
 
         yield new_list.load_conversation();
+        // Also set up find infrastructure early so matching emails
+        // are expanded and highlighted as they are added.
+        this.conversation_find_next.set_sensitive(false);
+        this.conversation_find_prev.set_sensitive(false);
+        new_list.search_matches_found.connect(() => {
+                this.conversation_find_next.set_sensitive(true);
+                this.conversation_find_prev.set_sensitive(true);
+            });
+        Gee.Set<string>? find_terms = get_find_search_terms();
+        if (find_terms != null) {
+            new_list.highlight_search_terms(find_terms);
+        }
 
         remove_current_list();
         add_new_list(new_list);
         set_visible_child(this.conversation_page);
         this.conversation_timeout_id = 0;
-        if (location is Geary.SearchFolder) {
-            yield new_list.load_search_terms((Geary.SearchFolder) location);
+        // Highlight matching terms from the search if it exists, but
+        // don't clobber any find terms.
+        if (find_terms == null && location is Geary.SearchFolder) {
+            yield new_list.load_search_terms();
         }
     }
 
@@ -255,6 +270,16 @@ public class ConversationViewer : Gtk.Stack {
         }
         base.set_visible_child(widget);
     }
+        
+    private Gee.Set<string>? get_find_search_terms() {
+        Gee.Set<string>? terms = null;
+        string search = this.conversation_find_entry.get_text().strip();
+        if (search.length > 0) {
+            terms = new Gee.HashSet<string>();
+            terms.add(search);
+        }
+        return terms;
+    }
 
     [GtkCallback]
     private void on_find_search_started(Object obj, ParamSpec param) {
@@ -275,15 +300,14 @@ public class ConversationViewer : Gtk.Stack {
 
     [GtkCallback]
     private void on_find_search_changed(Gtk.SearchEntry entry) {
-        string search = entry.get_text().strip();
-        bool have_matches = false;
+        this.conversation_find_next.set_sensitive(false);
+        this.conversation_find_prev.set_sensitive(false);
         if (this.current_list != null) {
-            if (search.length > 0) {
+            Gee.Set<string>? terms = get_find_search_terms();
+            if (terms != null) {
                 // Have a search string
-                Gee.Set<string> search_matches = new Gee.HashSet<string>();
-                search_matches.add(search);
-                have_matches =
-                    this.current_list.highlight_search_terms(search_matches);
+                this.current_list.highlight_search_terms(terms);
+                // XXX scroll to first match
             } else {
                 // Have no search string
                 // if (location is Geary.SearchFolder) {
@@ -296,8 +320,6 @@ public class ConversationViewer : Gtk.Stack {
                 // }
             }
         }
-        this.conversation_find_next.set_sensitive(have_matches);
-        this.conversation_find_prev.set_sensitive(have_matches);
     }
 
     [GtkCallback]


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