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



commit 6615dc3d7b69fc4254019b5d7d04454cc8869179
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       |   96 +++-----------------
 src/client/components/main-window.vala             |   70 ++++++++++++++
 .../conversation-list/conversation-list-view.vala  |    4 +-
 .../conversation-list/conversation-list.vala       |   30 ++++++-
 4 files changed, 112 insertions(+), 88 deletions(-)
---
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index b263530..735e2bf 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -109,7 +109,6 @@ public class GearyController : Geary.BaseObject {
     private Cancellable cancellable_context_dependent_buttons = new Cancellable();
     private Gee.HashMap<Geary.Account, Cancellable> inbox_cancellables
         = new Gee.HashMap<Geary.Account, Cancellable>();
-    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;
@@ -232,8 +231,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);
@@ -306,8 +303,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);
@@ -1552,63 +1547,6 @@ public class GearyController : Geary.BaseObject {
         }
     }
 
-    private void on_conversations_selected(Gee.Set<Geary.App.Conversation> selected) {
-        this.selected_conversations = selected;
-        this.application.get_action(
-            ACTION_FIND_IN_CONVERSATION
-            ).set_sensitive(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);
-                            this.application.get_action(
-                                ACTION_FIND_IN_CONVERSATION
-                            ).set_sensitive(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);
@@ -1794,7 +1732,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;
     }
@@ -1812,7 +1750,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;
             
@@ -1984,9 +1922,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;
@@ -2241,12 +2176,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.
@@ -2256,12 +2185,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;
@@ -2554,6 +2489,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;
 
@@ -2854,7 +2793,7 @@ public class GearyController : Geary.BaseObject {
 
     // Updates tooltip text depending on number of conversations selected.
     private void update_tooltips() {
-        bool single = selected_conversations.size == 1;
+        bool single = this.main_window.get_selected_conversations().size == 1;
 
         this.application.actions.get_action(ACTION_MARK_AS_MENU).tooltip = single ?
             MARK_MESSAGE_MENU_TOOLTIP_SINGLE : MARK_MESSAGE_MENU_TOOLTIP_MULTIPLE;
@@ -2921,13 +2860,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 ad5b52e..0d5deb0 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;
@@ -60,6 +61,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();
@@ -77,6 +80,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;
+    }
+
     private void load_config(Configuration config) {
         // This code both loads AND saves the pane positions with live updating. This is more
         // resilient against crashes because the value in dconf changes *immediately*, and
@@ -404,6 +419,61 @@ public class MainWindow : Gtk.ApplicationWindow {
         }
     }
 
+    private void on_conversation_selection_changed(Gee.Set<Geary.App.Conversation> selection) {
+        this.selected_conversations = selection;
+        this.application.get_action(
+            GearyController.ACTION_FIND_IN_CONVERSATION
+        ).set_sensitive(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);
+                            this.application.get_action(
+                                GearyController.ACTION_FIND_IN_CONVERSATION
+                            ).set_sensitive(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 bad2744..bbd4b26 100644
--- a/src/client/conversation-list/conversation-list-view.vala
+++ b/src/client/conversation-list/conversation-list-view.vala
@@ -245,8 +245,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]