[geary/wip/730682-refine-convo-list] Refine conversation selection process.



commit 164486901613ffb8a2e38f4277978da0d90faea1
Author: Michael James Gratton <mike vee net>
Date:   Sat Dec 23 16:54:56 2017 +1030

    Refine conversation selection process.
    
    * src/client/conversation-list/conversation-list.vala
      (ConversationList): Add freeze_selection() and thaw_selection() methods
      to prevent the user selecting any conversations and restoring the
      previous selection, or nearest conversation. Use these when displaying
      an inline composer or changing folders.
    
    * src/client/application/geary-controller.vala (GearyController): Don't
      clear the selected conversation when changing folders, only when the
      user deselects it, so it doesn't flash loading needlessly.
    
    * src/client/components/main-window.vala (MainWindow): Freeze the
      conversation list when changing conversation list's model, so it
      doesn't flash empty folder needlessly.
    
    * src/client/conversation-viewer/conversation-viewer.vala
      (ConversationViewer): Freeze the conversation list when displaying an
      inline composer so the user can't select another conversation and we
      needlessly need to prompt to discard the draft.

 src/client/application/geary-controller.vala       |    1 -
 src/client/components/main-window.vala             |   28 +++++---
 .../conversation-list/conversation-list.vala       |   76 ++++++++++++++++---
 .../conversation-viewer/conversation-viewer.vala   |   13 +---
 4 files changed, 86 insertions(+), 32 deletions(-)
---
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 6d407b6..0b327dc 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -1216,7 +1216,6 @@ public class GearyController : Geary.BaseObject {
             this.current_folder = null;
             folder_selected(null);
         } else if (folder != this.current_folder) {
-            this.main_window.conversation_viewer.show_loading();
             get_window_action(ACTION_FIND_IN_CONVERSATION).set_enabled(false);
             enable_message_buttons(false);
 
diff --git a/src/client/components/main-window.vala b/src/client/components/main-window.vala
index 28a3e65..e6993a5 100644
--- a/src/client/components/main-window.vala
+++ b/src/client/components/main-window.vala
@@ -275,6 +275,7 @@ public class MainWindow : Gtk.ApplicationWindow {
     }
 
     private void on_conversation_monitor_changed() {
+        this.conversation_list.freeze_selection();
         ConversationListModel? old_model = this.conversation_list.model;
         if (old_model != null) {
             this.progress_monitor.remove(old_model.monitor.progress_monitor);
@@ -284,6 +285,7 @@ public class MainWindow : Gtk.ApplicationWindow {
             old_monitor.scan_error.disconnect(on_scan_error);
             old_monitor.seed_completed.disconnect(on_seed_completed);
             old_monitor.seed_completed.disconnect(on_conversation_count_changed);
+            old_monitor.seed_completed.disconnect(on_initial_conversation_load);
             old_monitor.scan_completed.disconnect(on_conversation_count_changed);
             old_monitor.conversations_added.disconnect(on_conversation_count_changed);
             old_monitor.conversations_removed.disconnect(on_conversation_count_changed);
@@ -301,10 +303,12 @@ public class MainWindow : Gtk.ApplicationWindow {
             new_monitor.scan_error.connect(on_scan_error);
             new_monitor.seed_completed.connect(on_seed_completed);
             new_monitor.seed_completed.connect(on_conversation_count_changed);
+            new_monitor.seed_completed.connect(on_initial_conversation_load);
             new_monitor.scan_completed.connect(on_conversation_count_changed);
             new_monitor.conversations_added.connect(on_conversation_count_changed);
             new_monitor.conversations_removed.connect(on_conversation_count_changed);
         }
+        this.conversation_list.thaw_selection();
     }
 
     private void on_folder_selected(Geary.Folder? folder) {
@@ -477,6 +481,18 @@ public class MainWindow : Gtk.ApplicationWindow {
         }
     }
 
+    private void on_initial_conversation_load() {
+        // When not doing autoselect, we never get
+        // conversations_selected firing from the convo list, so we
+        // need to clear the convo viewer here
+        if (!this.application.config.autoselect) {
+            this.conversation_viewer.show_none_selected();
+            this.application.controller.enable_message_buttons(false);
+        }
+
+        this.conversation_list.model.monitor.seed_completed.disconnect(on_initial_conversation_load);
+    }
+
     private void on_load_more() {
         debug("on_load_more");
         this.application.controller.current_conversations.min_window_count += 
GearyController.CONVERSATION_PAGE_SIZE;
@@ -496,8 +512,8 @@ public class MainWindow : Gtk.ApplicationWindow {
     }
 
     private void on_conversation_count_changed() {
-        if (this.application.controller.current_conversations != null) {
-            int count = this.application.controller.current_conversations.get_conversation_count();
+        if (this.conversation_list.model.monitor != null) {
+            int count = this.conversation_list.model.monitor.get_conversation_count();
             if (count == 0) {
                 // Let the user know if there's no available conversations
                 if (this.current_folder is Geary.SearchFolder) {
@@ -506,14 +522,6 @@ public class MainWindow : Gtk.ApplicationWindow {
                     this.conversation_viewer.show_empty_folder();
                 }
                 this.application.controller.enable_message_buttons(false);
-            } else {
-                // When not doing autoselect, we never get
-                // conversations_selected firing from the convo list,
-                // so we need to stop the loading spinner here
-                if (!this.application.config.autoselect) {
-                    this.conversation_viewer.show_none_selected();
-                    this.application.controller.enable_message_buttons(false);
-                }
             }
         }
     }
diff --git a/src/client/conversation-list/conversation-list.vala 
b/src/client/conversation-list/conversation-list.vala
index c52ed56..6dc3ddc 100644
--- a/src/client/conversation-list/conversation-list.vala
+++ b/src/client/conversation-list/conversation-list.vala
@@ -28,6 +28,8 @@ public class ConversationList : Gtk.ListBox {
 
 
     private Configuration config;
+    private int selected_index = -1;
+    private bool selection_frozen = false;
     private Gee.Set<Geary.App.Conversation>? visible_conversations = null;
     private Geary.Scheduler.Scheduled? update_visible_scheduled = null;
     private bool enable_load_more = true;
@@ -81,6 +83,8 @@ public class ConversationList : Gtk.ListBox {
 
         // Clear these since they will belong to the old model
         this.selected = null;
+        this.selected_index = -1;
+
         Gee.List<Geary.RFC822.MailboxAddress> account_addresses = 
displayed.account.information.get_all_mailboxes();
         bool use_to = displayed.special_folder_type.is_outgoing();
         base.bind_model(this.model, (convo) => {
@@ -93,6 +97,18 @@ public class ConversationList : Gtk.ListBox {
         );
     }
 
+    public void freeze_selection() {
+        this.selection_frozen = true;
+        this.selected = null;
+        set_selection_mode(Gtk.SelectionMode.NONE);
+    }
+
+    public void thaw_selection() {
+        set_selection_mode(Gtk.SelectionMode.SINGLE);
+        this.selection_frozen = false;
+        restore_selection();
+    }
+
     public void select_conversation(Geary.App.Conversation target) {
         for (int i = 0; i < this.model.get_n_items(); i++) {
             Gtk.ListBoxRow? row = get_row_at_index(i);
@@ -113,20 +129,25 @@ public class ConversationList : Gtk.ListBox {
         return visible;
     }
 
-    private void selection_changed() {
-        Geary.App.Conversation? selected = null;
-        Gtk.ListBoxRow? row = get_selected_row();
-        if (row != null) {
-            selected = ((ConversationListItem) row.get_child()).conversation;
-        }
+    private Gtk.ListBoxRow? restore_selection() {
+        Gtk.ListBoxRow? row = null;
+        if (this.selected_index >= 0) {
+            int new_index = this.selected_index;
+            if (new_index >= this.model.get_n_items()) {
+                new_index = ((int) this.model.get_n_items()) - 1;
+            }
 
-        debug("Selection changed to: %s",
-              selected != null ? selected.to_string() : null
-        );
-        if (this.selected != selected) {
-            this.selected = selected;
-            this.conversation_selection_changed(selected);
+            // XXX we should be only calling select_row() if
+            // autoselect is enabled, otherwise we should simply be
+            // updating the cursor, but Gtk.ListBox doesn't allow us
+            // to do that --- move_cursor() doesn't seem to work and
+            // is O(n) anyway.
+            row = get_row_at_index(new_index);
+            if (row != null) {
+                select_row(row);
+            }
         }
+        return row;
     }
 
     private void schedule_visible_conversations_changed() {
@@ -137,6 +158,37 @@ public class ConversationList : Gtk.ListBox {
             });
     }
 
+    private void selection_changed() {
+        if (!this.selection_frozen) {
+            Geary.App.Conversation? selected = null;
+            Gtk.ListBoxRow? row = get_selected_row();
+
+            // If a row was de-selected then we need to work out if
+            // that was because of a conversation being removed from
+            // the list, and if so select a new one
+            if (row == null &&
+                this.selected != null &&
+                !this.model.monitor.has_conversation(this.selected)) {
+                row = restore_selection();
+            }
+
+            if (row != null) {
+                selected = ((ConversationListItem) row.get_child()).conversation;
+                this.selected_index = row.get_index();
+            } else {
+                this.selected_index = -1;
+            }
+
+            debug("Selection changed to: %s",
+                  selected != null ? selected.to_string() : null
+            );
+            if (this.selected != selected) {
+                this.selected = selected;
+                this.conversation_selection_changed(selected);
+            }
+        }
+    }
+
     private void update_visible_conversations() {
         Gee.Set<Geary.App.Conversation> visible_now = get_visible_conversations();
         if (this.visible_conversations == null ||
diff --git a/src/client/conversation-viewer/conversation-viewer.vala 
b/src/client/conversation-viewer/conversation-viewer.vala
index 65e4af5..c7517b8 100644
--- a/src/client/conversation-viewer/conversation-viewer.vala
+++ b/src/client/conversation-viewer/conversation-viewer.vala
@@ -103,16 +103,11 @@ public class ConversationViewer : Gtk.Stack {
         // XXX move the ConversationListView management code into
         // GearyController or somewhere more appropriate
         ConversationList conversation_list =
-            ((MainWindow) GearyApplication.instance.controller.main_window).conversation_list;
-        Geary.App.Conversation prev_selection = conversation_list.selected;
-        conversation_list.unselect_all();
+            GearyApplication.instance.controller.main_window.conversation_list;
+        conversation_list.freeze_selection();
         box.vanished.connect((box) => {
-                set_visible_child(this.conversation_page);
-                if (prev_selection == null) {
-                    conversation_list.conversation_selection_changed(null);
-                } else {
-                    conversation_list.select_conversation(prev_selection);
-                }
+                show_none_selected();
+                conversation_list.thaw_selection();
             });
         this.composer_page.add(box);
         set_visible_child(this.composer_page);


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