[geary/wip/730682-refine-convo-list: 169/175] Hook up selection/activation handling for new conversation list widget.



commit 1db34cf1e8c33cba191bfe6f37d51bfdb128c463
Author: Michael James Gratton <mike vee net>
Date:   Sat Oct 14 17:18:37 2017 +1100

    Hook up selection/activation handling for new conversation list widget.
    
    This also moves selection management code from GearyController to
    MainWindow in prep for that glorious future where multiple main windows
    are supported.
    
    * src/client/conversation-list/conversation-list.vala (ListBox): Add
      conversation_selection_changed and conversation_activated signals,
      ensure they are fired as needed.
    
    * src/client/components/main-window.vala (MainWindow): Move selection
      management code over from GearyController and adapt for new
      ConversationList widget. Update call sites.

 src/client/application/geary-controller.vala       |  108 +++++---------------
 src/client/components/main-window.vala             |   73 +++++++++++++
 .../conversation-list/conversation-list-view.vala  |    4 +-
 .../conversation-list/conversation-list.vala       |   30 +++++-
 4 files changed, 126 insertions(+), 89 deletions(-)
---
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 08fd326..abbcaab 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -49,7 +49,6 @@ public class GearyController : Geary.BaseObject {
 
     // Properties
     public const string PROP_CURRENT_CONVERSATION ="current-conversations";
-    public const string PROP_SELECTED_CONVERSATIONS ="selected-conversations";
 
     public const int MIN_CONVERSATION_COUNT = 50;
 
@@ -83,7 +82,6 @@ public class GearyController : Geary.BaseObject {
     private Gee.HashMap<Geary.Account, Cancellable> inbox_cancellables
         = new Gee.HashMap<Geary.Account, Cancellable>();
     private ContactListStoreCache contact_list_store_cache = new ContactListStoreCache();
-    private Gee.Set<Geary.App.Conversation> selected_conversations = new 
Gee.HashSet<Geary.App.Conversation>();
     private Geary.App.Conversation? last_deleted_conversation = null;
     private Gee.LinkedList<ComposerWidget> composer_widgets = new Gee.LinkedList<ComposerWidget>();
     private NewMessagesMonitor? new_messages_monitor = null;
@@ -230,8 +228,6 @@ public class GearyController : Geary.BaseObject {
         Geary.Engine.instance.untrusted_host.connect(on_untrusted_host);
         
         // Connect to various UI signals.
-        main_window.conversation_list_view.conversations_selected.connect(on_conversations_selected);
-        main_window.conversation_list_view.conversation_activated.connect(on_conversation_activated);
         main_window.conversation_list_view.load_more.connect(on_load_more);
         main_window.conversation_list_view.mark_conversations.connect(on_mark_conversations);
         
main_window.conversation_list_view.visible_conversations_changed.connect(on_visible_conversations_changed);
@@ -304,8 +300,6 @@ public class GearyController : Geary.BaseObject {
         Geary.Engine.instance.untrusted_host.disconnect(on_untrusted_host);
 
         // Disconnect from various UI signals.
-        main_window.conversation_list_view.conversations_selected.disconnect(on_conversations_selected);
-        main_window.conversation_list_view.conversation_activated.disconnect(on_conversation_activated);
         main_window.conversation_list_view.load_more.disconnect(on_load_more);
         main_window.conversation_list_view.mark_conversations.disconnect(on_mark_conversations);
         
main_window.conversation_list_view.visible_conversations_changed.disconnect(on_visible_conversations_changed);
@@ -1230,7 +1224,8 @@ public class GearyController : Geary.BaseObject {
     // Update widgets and such to match capabilities of the current folder ... sensitivity is handled
     // by other utility methods
     private void update_ui() {
-        main_window.main_toolbar.selected_conversations = this.selected_conversations.size;
+        main_window.main_toolbar.selected_conversations =
+            this.main_window.get_selected_conversations().size;
         main_window.main_toolbar.show_trash_button = current_folder_supports_trash() ||
                                                     !(current_folder is Geary.FolderSupport.Remove);
     }
@@ -1448,59 +1443,6 @@ public class GearyController : Geary.BaseObject {
         }
     }
 
-    private void on_conversations_selected(Gee.Set<Geary.App.Conversation> selected) {
-        this.selected_conversations = selected;
-        get_window_action(ACTION_FIND_IN_CONVERSATION).set_enabled(false);
-        ConversationViewer viewer = this.main_window.conversation_viewer;
-        if (this.current_folder != null && !viewer.is_composer_visible) {
-            switch(selected.size) {
-            case 0:
-                enable_message_buttons(false);
-                viewer.show_none_selected();
-                break;
-
-            case 1:
-                // Cancel existing avatar loads before loading new
-                // convo since that will start loading more avatars
-                viewer.load_conversation.begin(
-                    Geary.Collection.get_first(selected),
-                    this.current_folder,
-                    (obj, ret) => {
-                        try {
-                            viewer.load_conversation.end(ret);
-                            enable_message_buttons(true);
-                            get_window_action(ACTION_FIND_IN_CONVERSATION).set_enabled(true);
-                        } catch (Error err) {
-                            debug("Unable to load conversation: %s",
-                                  err.message);
-                        }
-                    }
-                );
-                break;
-
-            default:
-                enable_multiple_message_buttons();
-                viewer.show_multiple_selected();
-                break;
-            }
-        }
-    }
-
-    private void on_conversation_activated(Geary.App.Conversation activated) {
-        // Currently activating a conversation is only available for drafts folders.
-        if (current_folder == null || current_folder.special_folder_type !=
-            Geary.SpecialFolderType.DRAFTS)
-            return;
-
-        // TODO: Determine how to map between conversations and drafts correctly.
-        Geary.Email draft = activated.get_latest_recv_email(
-            Geary.App.Conversation.Location.IN_FOLDER
-        );
-        create_compose_widget(
-            ComposerWidget.ComposeType.NEW_MESSAGE, draft, null, null, true
-        );
-    }
-
     private void on_special_folder_type_changed(Geary.Folder folder, Geary.SpecialFolderType old_type,
         Geary.SpecialFolderType new_type) {
         main_window.folder_list.remove_folder(folder);
@@ -1699,7 +1641,7 @@ public class GearyController : Geary.BaseObject {
     
     private Gee.ArrayList<Geary.EmailIdentifier> get_selected_email_ids(bool latest_sent_only) {
         Gee.ArrayList<Geary.EmailIdentifier> ids = new Gee.ArrayList<Geary.EmailIdentifier>();
-        foreach (Geary.App.Conversation conversation in selected_conversations)
+        foreach (Geary.App.Conversation conversation in this.main_window.get_selected_conversations())
             get_conversation_email_ids(conversation, latest_sent_only, ids);
         return ids;
     }
@@ -1717,7 +1659,7 @@ public class GearyController : Geary.BaseObject {
         bool read_selected = false;
         bool starred_selected = false;
         bool unstarred_selected = false;
-        foreach (Geary.App.Conversation conversation in selected_conversations) {
+        foreach (Geary.App.Conversation conversation in this.main_window.get_selected_conversations()) {
             if (conversation.is_unread())
                 unread_selected = true;
             
@@ -1886,9 +1828,6 @@ public class GearyController : Geary.BaseObject {
     
     private void on_move_conversation(Geary.Folder destination) {
         // Nothing to do if nothing selected.
-        if (selected_conversations == null || selected_conversations.size == 0)
-            return;
-        
         Gee.List<Geary.EmailIdentifier> ids = get_selected_email_ids(false);
         if (ids.size == 0)
             return;
@@ -2137,12 +2076,6 @@ public class GearyController : Geary.BaseObject {
         }
     }
 
-    private void create_compose_widget(ComposerWidget.ComposeType compose_type,
-        Geary.Email? referred = null, string? quote = null, string? mailto = null,
-        bool is_draft = false) {
-        create_compose_widget_async.begin(compose_type, referred, quote, mailto, is_draft);
-    }
-
     /**
      * Creates a composer widget. Depending on the arguments, this can be inline in the
      * conversation or as a new window.
@@ -2152,12 +2085,18 @@ public class GearyController : Geary.BaseObject {
      * @param mailto - A "mailto:"-link
      * @param is_draft - Whether we're starting from a draft (true) or a new mail (false)
      */
+    internal void create_compose_widget(ComposerWidget.ComposeType compose_type,
+        Geary.Email? referred = null, string? quote = null, string? mailto = null,
+        bool is_draft = false) {
+        create_compose_widget_async.begin(compose_type, referred, quote, mailto, is_draft);
+    }
+
     private async void create_compose_widget_async(ComposerWidget.ComposeType compose_type,
         Geary.Email? referred = null, string? quote = null, string? mailto = null,
         bool is_draft = false) {
         if (current_account == null)
             return;
-        
+
         bool inline;
         if (!should_create_new_composer(compose_type, referred, quote, is_draft, out inline))
             return;
@@ -2453,6 +2392,10 @@ public class GearyController : Geary.BaseObject {
             return;
         }
 
+        Gee.Set<Geary.App.Conversation> selected_conversations = 
this.main_window.get_selected_conversations();
+        if (selected_conversations.size == 0)
+            return;
+
         last_deleted_conversation = selected_conversations.size > 0
             ? Geary.traverse<Geary.App.Conversation>(selected_conversations).first() : null;
 
@@ -2669,7 +2612,8 @@ public class GearyController : Geary.BaseObject {
 
     // Disables all single-message buttons and enables all multi-message buttons.
     public void enable_multiple_message_buttons() {
-        main_window.main_toolbar.selected_conversations = this.selected_conversations.size;
+        main_window.main_toolbar.selected_conversations =
+            this.main_window.get_selected_conversations().size;
 
         // Single message only buttons.
         get_window_action(ACTION_REPLY_TO_MESSAGE).set_enabled(false);
@@ -2688,7 +2632,8 @@ public class GearyController : Geary.BaseObject {
 
     // Enables or disables the message buttons on the toolbar.
     public void enable_message_buttons(bool sensitive) {
-        main_window.main_toolbar.selected_conversations = this.selected_conversations.size;
+        main_window.main_toolbar.selected_conversations =
+            this.main_window.get_selected_conversations().size;
 
         // No reply/forward in drafts folder.
         bool respond_sensitive = sensitive;
@@ -2730,8 +2675,12 @@ public class GearyController : Geary.BaseObject {
         if (selected_operations != null)
             supported_operations.add_all(selected_operations.get_values());
 
-        get_window_action(ACTION_SHOW_MARK_MENU).set_enabled(sensitive && (typeof(Geary.FolderSupport.Mark) 
in supported_operations));
-        get_window_action(ACTION_COPY_MENU).set_enabled(sensitive && 
(supported_operations.contains(typeof(Geary.FolderSupport.Copy))));
+        get_window_action(ACTION_SHOW_MARK_MENU).set_enabled(
+            sensitive && (typeof(Geary.FolderSupport.Mark) in supported_operations)
+        );
+        get_window_action(ACTION_COPY_MENU).set_enabled(
+            sensitive && (supported_operations.contains(typeof(Geary.FolderSupport.Copy)))
+        );
     }
 
     // Returns a list of composer windows for an account, or null if none.
@@ -2784,13 +2733,6 @@ public class GearyController : Geary.BaseObject {
         search_text_changed(main_window.search_bar.search_text);
     }
 
-    /**
-     * Returns a read-only set of currently selected conversations.
-     */
-    public Gee.Set<Geary.App.Conversation> get_selected_conversations() {
-        return selected_conversations.read_only_view;
-    }
-    
     // Find the first inbox we know about and switch to it.
     private void switch_to_first_inbox() {
         try {
diff --git a/src/client/components/main-window.vala b/src/client/components/main-window.vala
index ef12a33..14c3326 100644
--- a/src/client/components/main-window.vala
+++ b/src/client/components/main-window.vala
@@ -33,6 +33,7 @@ public class MainWindow : Gtk.ApplicationWindow {
     public ConversationList conversation_list  { get; private set; }
     public ConversationViewer conversation_viewer { get; private set; default = new ConversationViewer(); }
     public StatusBar status_bar { get; private set; default = new StatusBar(); }
+    private Gee.Set<Geary.App.Conversation> selected_conversations = new 
Gee.HashSet<Geary.App.Conversation>();
     private MonitoredSpinner spinner = new MonitoredSpinner();
     [GtkChild]
     private Gtk.Box main_layout;
@@ -66,6 +67,8 @@ public class MainWindow : Gtk.ApplicationWindow {
         Object(application: application);
 
         this.conversation_list = new ConversationList(application.config);
+        this.conversation_list.conversation_selection_changed.connect(on_conversation_selection_changed);
+        this.conversation_list.conversation_activated.connect(on_conversation_activated);
 
         load_config(application.config);
         restore_saved_window_state();
@@ -81,6 +84,18 @@ public class MainWindow : Gtk.ApplicationWindow {
         on_change_orientation();
     }
 
+    ~MainWindow() {
+        this.conversation_list.conversation_selection_changed.disconnect(on_conversation_selection_changed);
+        this.conversation_list.conversation_activated.disconnect(on_conversation_activated);
+    }
+
+    /**
+     * Returns a read-only set of currently selected conversations.
+     */
+    public Gee.Set<Geary.App.Conversation> get_selected_conversations() {
+        return this.selected_conversations.read_only_view;
+    }
+
     public void show_infobar(MainWindowInfoBar info_bar) {
         this.info_bar_container.add(info_bar);
         this.info_bar_frame.show();
@@ -415,6 +430,64 @@ public class MainWindow : Gtk.ApplicationWindow {
         }
     }
 
+    private inline SimpleAction get_action(string name) {
+        return (SimpleAction) lookup_action(name);
+    }
+
+    private void on_conversation_selection_changed(Gee.Set<Geary.App.Conversation> selection) {
+        this.selected_conversations = selection;
+        SimpleAction find_action = get_action(
+            GearyController.ACTION_FIND_IN_CONVERSATION
+        );
+        find_action.set_enabled(false);
+        if (this.current_folder != null && !this.conversation_viewer.is_composer_visible) {
+            switch(selection.size) {
+            case 0:
+                this.application.controller.enable_message_buttons(false);
+                this.conversation_viewer.show_none_selected();
+                break;
+
+            case 1:
+                // Cancel existing avatar loads before loading new
+                // convo since that will start loading more avatars
+                this.conversation_viewer.load_conversation.begin(
+                    Geary.Collection.get_first(selection),
+                    this.current_folder,
+                    (obj, ret) => {
+                        try {
+                            this.conversation_viewer.load_conversation.end(ret);
+                            this.application.controller.enable_message_buttons(true);
+                            find_action.set_enabled(true);
+                        } catch (Error err) {
+                            debug("Unable to load conversation: %s",
+                                  err.message);
+                        }
+                    }
+                );
+                break;
+
+            default:
+                this.application.controller.enable_multiple_message_buttons();
+                this.conversation_viewer.show_multiple_selected();
+                break;
+            }
+        }
+    }
+
+    private void on_conversation_activated(Geary.App.Conversation activated) {
+        // Currently activating a conversation is only available for drafts folders.
+        if (this.current_folder != null &&
+            this.current_folder.special_folder_type == Geary.SpecialFolderType.DRAFTS) {
+            // TODO: Determine how to map between conversations and drafts correctly.
+            Geary.Email draft = activated.get_latest_recv_email(
+                Geary.App.Conversation.Location.IN_FOLDER
+                );
+            this.application.controller.create_compose_widget(
+                ComposerWidget.ComposeType.NEW_MESSAGE, draft, null, null, true
+            );
+        }
+    }
+
     [GtkCallback]
     private bool on_key_release_event(Gdk.EventKey event) {
         check_shift_event(event);
diff --git a/src/client/conversation-list/conversation-list-view.vala 
b/src/client/conversation-list/conversation-list-view.vala
index abacb34..db34478 100644
--- a/src/client/conversation-list/conversation-list-view.vala
+++ b/src/client/conversation-list/conversation-list-view.vala
@@ -248,8 +248,8 @@ public class ConversationListView : Gtk.TreeView {
             // all selected conversations; otherwise, it just applies to this one.
             Geary.App.Conversation conversation = get_model().get_conversation_at_path(path);
             Gee.Collection<Geary.App.Conversation> to_mark;
-            if (GearyApplication.instance.controller.get_selected_conversations().contains(conversation))
-                to_mark = GearyApplication.instance.controller.get_selected_conversations();
+            if 
(GearyApplication.instance.controller.main_window.get_selected_conversations().contains(conversation))
+                to_mark = GearyApplication.instance.controller.main_window.get_selected_conversations();
             else
                 to_mark = Geary.iterate<Geary.App.Conversation>(conversation).to_array_list();
             
diff --git a/src/client/conversation-list/conversation-list.vala 
b/src/client/conversation-list/conversation-list.vala
index 20edc8d..6ef40f5 100644
--- a/src/client/conversation-list/conversation-list.vala
+++ b/src/client/conversation-list/conversation-list.vala
@@ -16,21 +16,43 @@ public class ConversationList : Gtk.ListBox {
 
     private Configuration config;
 
+    private ConversationListModel? model = null;
+
+
+    /** Fired when a user changes the list's selection. */
+    public signal void conversation_selection_changed(Gee.Set<Geary.App.Conversation> selection);
+
+    /** Fired when a user activates a row in the list. */
+    public signal void conversation_activated(Geary.App.Conversation activated);
+
 
     public ConversationList(Configuration config) {
         this.config = config;
         get_style_context().add_class(CLASS);
         set_activate_on_single_click(true);
-        set_selection_mode(Gtk.SelectionMode.MULTIPLE);
+        set_selection_mode(Gtk.SelectionMode.SINGLE);
+
+        this.row_activated.connect((row) => {
+                uint activated = row.get_index();
+                this.conversation_activated(this.model.get_conversation(activated));
+            });
+        this.selected_rows_changed.connect(() => {
+                Gee.HashSet<Geary.App.Conversation> new_selection =
+                    new Gee.HashSet<Geary.App.Conversation>();
+                foreach (Gtk.ListBoxRow row in get_selected_rows()) {
+                    uint selected = row.get_index();
+                    new_selection.add(this.model.get_conversation(selected));
+                }
+                this.conversation_selection_changed(new_selection);
+            });
     }
 
     public void set_model(Geary.App.ConversationMonitor monitor) {
+        this.model = new ConversationListModel(monitor);
         Geary.Folder displayed = monitor.folder;
         Gee.List<Geary.RFC822.MailboxAddress> account_addresses = 
displayed.account.information.get_all_mailboxes();
         bool use_to = (displayed != null) && displayed.special_folder_type.is_outgoing();
-        bind_model(
-            new ConversationListModel(monitor),
-            (convo) => {
+        bind_model(this.model, (convo) => {
                 return new ConversationListItem(convo as Geary.App.Conversation,
                                                 account_addresses,
                                                 use_to,


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