[geary/wip/730682-refine-convo-list] Remove support for traditional multiple selected conversations.



commit 7dff2f033d596243730ed39e48da12b69255df45
Author: Michael James Gratton <mike vee net>
Date:   Sat Dec 23 14:54:46 2017 +1030

    Remove support for traditional multiple selected conversations.
    
    In preparation for moving to using selection-mode for the conversation
    list, convert it to only supporting a single traditionally selected
    conversation. As an added benefit, it does/will lets us simplify a whole
    lot of complicated code paths.
    
    * src/client/conversation-list/conversation-list.vala (ConversationList):
      Replace get_selected_conversations() with `selected` property. Make arg
      for conversation_selection_changed be a single nullable Conversation,
      updated when the selection changes. Fix call sites.
    
    * src/client/components/main-toolbar.vala (MainToolbar): Remove
      `selected_conversations` property and support code for doing
      singular/plural translations of tooltips, just set them in
      ui/main-toolbar.ui. Manage visibility of trash vs delete explicitly,
      rather than a property + notify handler.
    
    * src/client/components/main-window.vala (MainWindow): Remove
      `selected_conversations` and related accessor since we don't need it
      anymore. When the selection changes, just load it in the conversation
      viewer or clear it.
    
    * src/client/conversation-viewer/conversation-viewer.vala
      (ConversationViewer): Remove support for displaying the multiple
      conversations placeholder.

 src/client/application/geary-controller.vala       |  145 +++++++-------------
 src/client/components/main-toolbar.vala            |   70 +---------
 src/client/components/main-window.vala             |   50 ++-----
 .../conversation-list/conversation-list-item.vala  |    5 +-
 .../conversation-list/conversation-list-view.vala  |   10 +-
 .../conversation-list/conversation-list.vala       |   66 ++++++----
 .../conversation-viewer/conversation-viewer.vala   |   26 +---
 src/engine/app/app-conversation-monitor.vala       |    4 +
 ui/conversation-viewer.ui                          |   14 --
 ui/main-toolbar.ui                                 |   37 +++++-
 10 files changed, 165 insertions(+), 262 deletions(-)
---
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 6d54410..6d407b6 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -1210,15 +1210,6 @@ public class GearyController : Geary.BaseObject {
         return num;
     }
 
-    // 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.main_window.get_selected_conversations().size;
-        main_window.main_toolbar.show_trash_button = current_folder_supports_trash() ||
-                                                    !(current_folder is Geary.FolderSupport.Remove);
-    }
-
     private void on_folder_selected(Geary.Folder? folder) {
         debug("Folder %s selected", folder != null ? folder.to_string() : "(null)");
         if (folder == null) {
@@ -1324,8 +1315,11 @@ public class GearyController : Geary.BaseObject {
             main_window.main_toolbar.copy_folder_menu.enable_disable_folder(current_folder, false);
             main_window.main_toolbar.move_folder_menu.enable_disable_folder(current_folder, false);
         }
-        
-        update_ui();
+
+        this.main_window.main_toolbar.update_trash_buttons(
+            current_folder_supports_trash() ||
+            !(current_folder is Geary.FolderSupport.Remove)
+        );
 
         current_conversations = new Geary.App.ConversationMonitor(
             current_folder,
@@ -1541,9 +1535,11 @@ public class GearyController : Geary.BaseObject {
     private void on_shift_key(bool pressed) {
         if (main_window != null && main_window.main_toolbar != null
             && current_account != null && current_folder != null) {
-            main_window.main_toolbar.show_trash_button =
-                (!pressed && current_folder_supports_trash()) ||
-                !(current_folder is Geary.FolderSupport.Remove);
+            main_window.main_toolbar.update_trash_buttons(
+                current_folder_supports_trash() &&
+                current_folder is Geary.FolderSupport.Remove &&
+                !pressed
+            );
         }
     }
 
@@ -1555,28 +1551,24 @@ public class GearyController : Geary.BaseObject {
 
     // latest_sent_only uses Email's Date: field, which corresponds to how they're sorted in the
     // ConversationViewer
-    private Gee.ArrayList<Geary.EmailIdentifier> get_conversation_email_ids(
-        Geary.App.Conversation conversation, bool latest_sent_only,
-        Gee.ArrayList<Geary.EmailIdentifier> add_to) {
-        if (latest_sent_only) {
-            Geary.Email? latest = conversation.get_latest_sent_email(
-                Geary.App.Conversation.Location.IN_FOLDER_OUT_OF_FOLDER);
-            if (latest != null)
-                add_to.add(latest.id);
-        } else {
-            add_to.add_all(conversation.get_email_ids());
-        }
-        
-        return add_to;
-    }
-
     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 this.main_window.get_selected_conversations())
-            get_conversation_email_ids(conversation, latest_sent_only, ids);
+        Geary.App.Conversation? conversation =
+            this.main_window.conversation_list.selected;
+        Gee.ArrayList<Geary.EmailIdentifier> ids =
+            new Gee.ArrayList<Geary.EmailIdentifier>();
+        if (conversation != null) {
+            if (latest_sent_only) {
+                Geary.Email? latest = conversation.get_latest_sent_email(
+                    Geary.App.Conversation.Location.IN_FOLDER_OUT_OF_FOLDER);
+                if (latest != null)
+                    ids.add(latest.id);
+            } else {
+                ids.add_all(conversation.get_email_ids());
+            }
+        }
         return ids;
     }
-    
+
     private void mark_email(Gee.Collection<Geary.EmailIdentifier> ids,
         Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove) {
         if (ids.size > 0) {
@@ -1584,32 +1576,32 @@ public class GearyController : Geary.BaseObject {
                 ids, flags_to_add, flags_to_remove, cancellable_folder);
         }
     }
-    
+
     private void on_show_mark_menu() {
-        bool unread_selected = false;
+        Geary.App.Conversation conversation =
+            this.main_window.conversation_list.selected;
+
+        bool unread_selected = conversation.is_unread();
         bool read_selected = false;
         bool starred_selected = false;
         bool unstarred_selected = false;
-        foreach (Geary.App.Conversation conversation in this.main_window.get_selected_conversations()) {
-            if (conversation.is_unread())
-                unread_selected = true;
-            
-            // Only check the messages that "Mark as Unread" would mark, so we
-            // don't add the menu option and have it not do anything.
-            //
-            // Sort by Date: field to correspond with ConversationViewer ordering
-            Geary.Email? latest = conversation.get_latest_sent_email(
-                Geary.App.Conversation.Location.IN_FOLDER_OUT_OF_FOLDER);
-            if (latest != null && latest.email_flags != null
-                && !latest.email_flags.contains(Geary.EmailFlags.UNREAD))
-                read_selected = true;
-
-            if (conversation.is_flagged()) {
-                starred_selected = true;
-            } else {
-                unstarred_selected = true;
-            }
+
+        // Only check the messages that "Mark as Unread" would mark, so we
+        // don't add the menu option and have it not do anything.
+        //
+        // Sort by Date: field to correspond with ConversationViewer ordering
+        Geary.Email? latest = conversation.get_latest_sent_email(
+            Geary.App.Conversation.Location.IN_FOLDER_OUT_OF_FOLDER);
+        if (latest != null && latest.email_flags != null
+            && !latest.email_flags.contains(Geary.EmailFlags.UNREAD))
+            read_selected = true;
+
+        if (conversation.is_flagged()) {
+            starred_selected = true;
+        } else {
+            unstarred_selected = true;
         }
+
         get_window_action(ACTION_MARK_AS_READ).set_enabled(unread_selected);
         get_window_action(ACTION_MARK_AS_UNREAD).set_enabled(read_selected);
         get_window_action(ACTION_MARK_AS_STARRED).set_enabled(unstarred_selected);
@@ -1752,16 +1744,11 @@ public class GearyController : Geary.BaseObject {
         if (ids.size == 0)
             return;
 
-        this.main_window.conversation_list.set_changing_selection(true);
-
         Geary.FolderSupport.Move? supports_move = current_folder as Geary.FolderSupport.Move;
         if (supports_move != null)
             move_conversation_async.begin(
-                supports_move, ids, destination.path, cancellable_folder,
-                (obj, ret) => {
-                    move_conversation_async.end(ret);
-                    this.main_window.conversation_list.set_changing_selection(false);
-                });
+                supports_move, ids, destination.path, cancellable_folder
+            );
     }
 
     private async void move_conversation_async(Geary.FolderSupport.Move source_folder,
@@ -2312,14 +2299,12 @@ public class GearyController : Geary.BaseObject {
             return;
         }
 
-        Gee.Set<Geary.App.Conversation> selected_conversations = 
this.main_window.get_selected_conversations();
-        if (selected_conversations.size == 0)
+        Geary.App.Conversation? selected =
+            this.main_window.conversation_list.selected;
+        if (selected == null)
             return;
 
-        last_deleted_conversation = selected_conversations.size > 0
-            ? Geary.traverse<Geary.App.Conversation>(selected_conversations).first() : null;
-
-        this.main_window.conversation_list.set_changing_selection(true);
+        this.last_deleted_conversation = selected;
 
         Gee.List<Geary.EmailIdentifier> ids = get_selected_email_ids(false);
         if (archive) {
@@ -2368,14 +2353,13 @@ public class GearyController : Geary.BaseObject {
                 last_deleted_conversation = null;
         }
     }
-    
+
     private void on_archive_or_delete_selection_finished(Object? source, AsyncResult result) {
         try {
             archive_or_delete_selection_async.end(result);
         } catch (Error e) {
             debug("Unable to archive/trash/delete messages: %s", e.message);
         }
-        this.main_window.conversation_list.set_changing_selection(false);
     }
 
     private void save_revokable(Geary.Revokable? new_revokable, string? description) {
@@ -2530,31 +2514,8 @@ public class GearyController : Geary.BaseObject {
         return (SimpleAction) this.main_window.lookup_action(action_name);
     }
 
-    // Disables all single-message buttons and enables all multi-message buttons.
-    public void enable_multiple_message_buttons() {
-        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);
-        get_window_action(ACTION_REPLY_ALL_MESSAGE).set_enabled(false);
-        get_window_action(ACTION_FORWARD_MESSAGE).set_enabled(false);
-
-        // Mutliple message buttons.
-        get_window_action(ACTION_MOVE_MENU).set_enabled(current_folder is Geary.FolderSupport.Move);
-        get_window_action(ACTION_ARCHIVE_CONVERSATION).set_enabled(current_folder is 
Geary.FolderSupport.Archive);
-        get_window_action(ACTION_TRASH_CONVERSATION).set_enabled(current_folder_supports_trash());
-        get_window_action(ACTION_DELETE_CONVERSATION).set_enabled(current_folder is 
Geary.FolderSupport.Remove);
-
-        cancel_context_dependent_buttons();
-        enable_context_dependent_buttons_async.begin(true, cancellable_context_dependent_buttons);
-    }
-
     // Enables or disables the message buttons on the toolbar.
     public void enable_message_buttons(bool sensitive) {
-        main_window.main_toolbar.selected_conversations =
-            this.main_window.get_selected_conversations().size;
-
         // No reply/forward in drafts folder.
         bool respond_sensitive = sensitive;
         if (current_folder != null && current_folder.special_folder_type == Geary.SpecialFolderType.DRAFTS)
diff --git a/src/client/components/main-toolbar.vala b/src/client/components/main-toolbar.vala
index 795a3a3..0ea4749 100644
--- a/src/client/components/main-toolbar.vala
+++ b/src/client/components/main-toolbar.vala
@@ -22,10 +22,6 @@ public class MainToolbar : Gtk.Box {
     // Copy and Move popovers
     public FolderPopover copy_folder_menu { get; private set; default = new FolderPopover(); }
     public FolderPopover move_folder_menu { get; private set; default = new FolderPopover(); }
-    // How many conversations are selected right now. Should automatically be updated.
-    public int selected_conversations { get; set; }
-    // Whether to show the trash or the delete button
-    public bool show_trash_button { get; set; default = true; }
     // The tooltip of the Undo-button
     public string undo_tooltip {
         owned get { return this.undo_button.tooltip_text; }
@@ -51,9 +47,9 @@ public class MainToolbar : Gtk.Box {
     [GtkChild]
     public Gtk.MenuButton move_conversation_button;
     [GtkChild]
-    private Gtk.Button archive_button;
+    private Gtk.Button trash_button;
     [GtkChild]
-    private Gtk.Button trash_delete_button;
+    private Gtk.Button delete_button;
     [GtkChild]
     private Gtk.ToggleButton find_button;
 
@@ -61,24 +57,6 @@ public class MainToolbar : Gtk.Box {
     [GtkChild]
     private Gtk.Button undo_button;
 
-    // Load these at construction time
-    private Gtk.Image trash_image = new Gtk.Image.from_icon_name("user-trash-symbolic", Gtk.IconSize.MENU);
-    private Gtk.Image delete_image = new Gtk.Image.from_icon_name("edit-delete-symbolic", Gtk.IconSize.MENU);
-
-    // Tooltips
-    private const string DELETE_CONVERSATION_TOOLTIP_SINGLE = _("Delete conversation (Shift+Delete)");
-    private const string DELETE_CONVERSATION_TOOLTIP_MULTIPLE = _("Delete conversations (Shift+Delete)");
-    private const string TRASH_CONVERSATION_TOOLTIP_SINGLE = _("Move conversation to Trash (Delete, 
Backspace)");
-    private const string TRASH_CONVERSATION_TOOLTIP_MULTIPLE = _("Move conversations to Trash (Delete, 
Backspace)");
-    private const string ARCHIVE_CONVERSATION_TOOLTIP_SINGLE = _("Archive conversation (A)");
-    private const string ARCHIVE_CONVERSATION_TOOLTIP_MULTIPLE = _("Archive conversations (A)");
-    private const string MARK_CONVERSATION_MENU_TOOLTIP_SINGLE = _("Mark conversation");
-    private const string MARK_CONVERSATION_MENU_TOOLTIP_MULTIPLE = _("Mark conversations");
-    private const string LABEL_CONVERSATION_TOOLTIP_SINGLE = _("Add label to conversation");
-    private const string LABEL_CONVERSATION_TOOLTIP_MULTIPLE = _("Add label to conversations");
-    private const string MOVE_CONVERSATION_TOOLTIP_SINGLE = _("Move conversation");
-    private const string MOVE_CONVERSATION_TOOLTIP_MULTIPLE = _("Move conversations");
-
     public MainToolbar(Configuration config) {
         // Instead of putting a separator between the two headerbars, as other applications do,
         // we put a separator at the right end of the left headerbar.  This greatly improves
@@ -113,8 +91,6 @@ public class MainToolbar : Gtk.Box {
             BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
 
         // Setup conversation header elements
-        this.notify["selected-conversations"].connect(() => update_conversation_buttons());
-        this.notify["show-trash-button"].connect(() => update_conversation_buttons());
         this.mark_conversation_button.popover = new Gtk.Popover.from_model(null, mark_menu);
         this.copy_conversation_button.popover = copy_folder_menu;
         this.move_conversation_button.popover = move_folder_menu;
@@ -146,6 +122,11 @@ public class MainToolbar : Gtk.Box {
         conversation_header.show();
     }
 
+    internal void update_trash_buttons(bool show_trash) {
+        this.trash_button.set_visible(show_trash);
+        this.delete_button.set_visible(!show_trash);
+    }
+
     private void set_window_buttons() {
         string[] buttons = Gtk.Settings.get_default().gtk_decoration_layout.split(":");
         if (buttons.length != 2) {
@@ -158,41 +139,4 @@ public class MainToolbar : Gtk.Box {
         conversation_header.decoration_layout = ":" + buttons[1];
     }
 
-    // Updates tooltip text depending on number of conversations selected.
-    private void update_conversation_buttons() {
-        this.mark_conversation_button.tooltip_text = ngettext(
-            MARK_CONVERSATION_MENU_TOOLTIP_SINGLE,
-            MARK_CONVERSATION_MENU_TOOLTIP_MULTIPLE,
-            this.selected_conversations
-        );
-        this.copy_conversation_button.tooltip_text = ngettext(
-            LABEL_CONVERSATION_TOOLTIP_SINGLE,
-            LABEL_CONVERSATION_TOOLTIP_MULTIPLE,
-            this.selected_conversations
-        );
-        this.move_conversation_button.tooltip_text = ngettext(
-            MOVE_CONVERSATION_TOOLTIP_SINGLE,
-            MOVE_CONVERSATION_TOOLTIP_MULTIPLE,
-            this.selected_conversations
-        );
-        this.archive_button.tooltip_text = ngettext(
-            ARCHIVE_CONVERSATION_TOOLTIP_SINGLE,
-            ARCHIVE_CONVERSATION_TOOLTIP_MULTIPLE,
-            this.selected_conversations
-        );
-
-        if (this.show_trash_button) {
-            this.trash_delete_button.action_name = "win."+GearyController.ACTION_TRASH_CONVERSATION;
-            this.trash_delete_button.image = trash_image;
-            this.trash_delete_button.tooltip_text = ngettext(TRASH_CONVERSATION_TOOLTIP_SINGLE,
-                                                             TRASH_CONVERSATION_TOOLTIP_MULTIPLE,
-                                                             this.selected_conversations);
-        } else {
-            this.trash_delete_button.action_name = "win."+GearyController.ACTION_DELETE_CONVERSATION;
-            this.trash_delete_button.image = delete_image;
-            this.trash_delete_button.tooltip_text = ngettext(DELETE_CONVERSATION_TOOLTIP_SINGLE,
-                                                             DELETE_CONVERSATION_TOOLTIP_MULTIPLE,
-                                                             this.selected_conversations);
-        }
-    }
 }
diff --git a/src/client/components/main-window.vala b/src/client/components/main-window.vala
index f5978fe..3e81caf 100644
--- a/src/client/components/main-window.vala
+++ b/src/client/components/main-window.vala
@@ -1,6 +1,6 @@
 /*
  * Copyright 2016 Software Freedom Conservancy Inc.
- * Copyright 2016 Michael Gratton <mike vee net>
+ * Copyright 2016-2017 Michael Gratton <mike vee net>
  *
  * This software is licensed under the GNU Lesser General Public License
  * (version 2.1 or later). See the COPYING file in this distribution.
@@ -32,7 +32,6 @@ public class MainWindow : Gtk.ApplicationWindow {
 
     private Geary.AggregateProgressMonitor progress_monitor = new Geary.AggregateProgressMonitor();
     private Geary.ProgressMonitor? folder_progress = null;
-    private Gee.Set<Geary.App.Conversation> selected_conversations = new 
Gee.HashSet<Geary.App.Conversation>();
 
     private MonitoredSpinner spinner = new MonitoredSpinner();
 
@@ -87,19 +86,6 @@ 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);
-        this.conversation_list.load_more.disconnect(on_load_more);
-    }
-
-    /**
-     * 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();
@@ -442,24 +428,20 @@ public class MainWindow : Gtk.ApplicationWindow {
         return (SimpleAction) lookup_action(name);
     }
 
-    private void on_conversation_selection_changed(Gee.Set<Geary.App.Conversation> selection) {
-        this.selected_conversations = selection;
+    private void on_conversation_selection_changed(Geary.App.Conversation? selection) {
+        Geary.App.Conversation? current = null;
+        ConversationListBox? listbox = this.conversation_viewer.current_list;
+        if (listbox != null) {
+            current = listbox.conversation;
+        }
         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
+        if (selection != null) {
+            if (selection != current &&
+                !this.conversation_viewer.is_composer_visible) {
                 this.conversation_viewer.load_conversation.begin(
-                    Geary.Collection.get_first(selection),
+                    selection,
                     this.current_folder,
                     this.application.config,
                     this.application.controller.avatar_session,
@@ -474,13 +456,11 @@ public class MainWindow : Gtk.ApplicationWindow {
                         }
                     }
                 );
-                break;
-
-            default:
-                this.application.controller.enable_multiple_message_buttons();
-                this.conversation_viewer.show_multiple_selected();
-                break;
             }
+        } else {
+            find_action.set_enabled(false);
+            this.application.controller.enable_message_buttons(false);
+            this.conversation_viewer.show_none_selected();
         }
     }
 
diff --git a/src/client/conversation-list/conversation-list-item.vala 
b/src/client/conversation-list/conversation-list-item.vala
index 6504aa5..29dcb05 100644
--- a/src/client/conversation-list/conversation-list-item.vala
+++ b/src/client/conversation-list/conversation-list-item.vala
@@ -73,6 +73,10 @@ public class ConversationListItem : Gtk.Grid {
     }
 
 
+    /** The conversation displayed by this item */
+    public Geary.App.Conversation conversation { get; private set; }
+
+
     [GtkChild]
     private Gtk.Button star_button;
 
@@ -94,7 +98,6 @@ public class ConversationListItem : Gtk.Grid {
     [GtkChild]
     private Gtk.Label count;
 
-    private Geary.App.Conversation conversation;
     private Gee.List<Geary.RFC822.MailboxAddress> account_addresses;
     private bool use_to;
     private PreviewLoader previews;
diff --git a/src/client/conversation-list/conversation-list-view.vala 
b/src/client/conversation-list/conversation-list-view.vala
index 14b1bce..c636be1 100644
--- a/src/client/conversation-list/conversation-list-view.vala
+++ b/src/client/conversation-list/conversation-list-view.vala
@@ -247,11 +247,11 @@ public class ConversationListView : Gtk.TreeView {
             // Get the current conversation.  If it's selected, we'll apply the mark operation to
             // 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.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();
+            Gee.Collection<Geary.App.Conversation> to_mark = new Gee.ArrayList<Geary.App.Conversation>();
+            // 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();
             
             if (read_clicked) {
                 // Read/unread.
diff --git a/src/client/conversation-list/conversation-list.vala 
b/src/client/conversation-list/conversation-list.vala
index e55af23..c52ed56 100644
--- a/src/client/conversation-list/conversation-list.vala
+++ b/src/client/conversation-list/conversation-list.vala
@@ -1,6 +1,6 @@
 /*
- * Copyright 2017 Michael Gratton <mike vee net>
  * Copyright 2016 Software Freedom Conservancy Inc.
+ * Copyright 2017 Michael Gratton <mike vee net>
  *
  * This software is licensed under the GNU Lesser General Public License
  * (version 2.1 or later).  See the COPYING file in this distribution.
@@ -18,8 +18,16 @@ public class ConversationList : Gtk.ListBox {
     /** Underlying model for this list */
     public ConversationListModel? model { get; private set; default=null; }
 
-    private Configuration config;
+    /**
+     * The conversation highlighted as selected, if any.
+     *
+     * This is distinct to the conversations chosen via selection
+     * mode, which are checked and might not be highlighted.
+     */
+    public Geary.App.Conversation? selected { get; private set; default = null; }
 
+
+    private Configuration config;
     private Gee.Set<Geary.App.Conversation>? visible_conversations = null;
     private Geary.Scheduler.Scheduled? update_visible_scheduled = null;
     private bool enable_load_more = true;
@@ -28,7 +36,7 @@ public class ConversationList : Gtk.ListBox {
 
 
     /** Fired when a user changes the list's selection. */
-    public signal void conversation_selection_changed(Gee.Set<Geary.App.Conversation> selection);
+    public signal void conversation_selection_changed(Geary.App.Conversation? selection);
 
     /** Fired when a user activates a row in the list. */
     public signal void conversation_activated(Geary.App.Conversation activated);
@@ -51,7 +59,9 @@ public class ConversationList : Gtk.ListBox {
                 uint activated = row.get_index();
                 this.conversation_activated(this.model.get_conversation(activated));
             });
-        this.selected_rows_changed.connect(on_selection_changed);
+        this.selected_rows_changed.connect(() => {
+                selection_changed();
+            });
         this.show.connect(on_show);
     }
 
@@ -69,6 +79,8 @@ public class ConversationList : Gtk.ListBox {
         this.model = new ConversationListModel(monitor, loader);
         this.model.items_changed.connect(on_model_items_changed);
 
+        // Clear these since they will belong to the old model
+        this.selected = null;
         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) => {
@@ -82,21 +94,17 @@ public class ConversationList : Gtk.ListBox {
     }
 
     public void select_conversation(Geary.App.Conversation target) {
-        // XXX Implement me
-    }
-
-    public void select_conversations(Gee.Set<Geary.App.Conversation> targets) {
-        // XXX Implement me
-    }
-
-    public Gee.Set<Geary.App.Conversation> get_selected_conversations() {
-        Gee.HashSet<Geary.App.Conversation> selection =
-            new Gee.HashSet<Geary.App.Conversation>();
-        foreach (Gtk.ListBoxRow row in get_selected_rows()) {
-            uint selected = row.get_index();
-            selection.add(this.model.get_conversation(selected));
+        for (int i = 0; i < this.model.get_n_items(); i++) {
+            Gtk.ListBoxRow? row = get_row_at_index(i);
+            if (row != null) {
+                ConversationListItem item =
+                    (ConversationListItem) row.get_child();
+                if (item.conversation == target) {
+                    select_row(row);
+                    break;
+                }
+            }
         }
-        return selection;
     }
 
     internal Gee.Set<Geary.App.Conversation> get_visible_conversations() {
@@ -105,11 +113,19 @@ public class ConversationList : Gtk.ListBox {
         return visible;
     }
 
-    internal void set_changing_selection(bool changing) {
-        if (changing) {
-            this.selected_rows_changed.disconnect(on_selection_changed);
-        } else {
-            this.selected_rows_changed.connect(on_selection_changed);
+    private void selection_changed() {
+        Geary.App.Conversation? selected = null;
+        Gtk.ListBoxRow? row = get_selected_row();
+        if (row != null) {
+            selected = ((ConversationListItem) row.get_child()).conversation;
+        }
+
+        debug("Selection changed to: %s",
+              selected != null ? selected.to_string() : null
+        );
+        if (this.selected != selected) {
+            this.selected = selected;
+            this.conversation_selection_changed(selected);
         }
     }
 
@@ -136,10 +152,6 @@ public class ConversationList : Gtk.ListBox {
         get_adjustment().value_changed.connect(on_adjustment_value_changed);
     }
 
-    private void on_selection_changed() {
-        this.conversation_selection_changed(get_selected_conversations());
-    }
-
     private void on_adjustment_value_changed() {
         Gtk.Adjustment? adjustment = get_adjustment();
         if (this.enable_load_more && adjustment != null) {
diff --git a/src/client/conversation-viewer/conversation-viewer.vala 
b/src/client/conversation-viewer/conversation-viewer.vala
index e0a4215..8964785 100644
--- a/src/client/conversation-viewer/conversation-viewer.vala
+++ b/src/client/conversation-viewer/conversation-viewer.vala
@@ -1,6 +1,6 @@
 /*
  * Copyright 2016 Software Freedom Conservancy Inc.
- * Copyright 2016 Michael Gratton <mike vee net>
+ * Copyright 2016-2017 Michael Gratton <mike vee net>
  *
  * This software is licensed under the GNU Lesser General Public License
  * (version 2.1 or later). See the COPYING file in this distribution.
@@ -34,8 +34,6 @@ public class ConversationViewer : Gtk.Stack {
     [GtkChild]
     private Gtk.Grid conversation_page;
     [GtkChild]
-    private Gtk.Grid multiple_conversations_page;
-    [GtkChild]
     private Gtk.Grid empty_folder_page;
     [GtkChild]
     private Gtk.Grid empty_search_page;
@@ -75,13 +73,6 @@ public class ConversationViewer : Gtk.Stack {
         );
         this.no_conversations_page.add(no_conversations);
 
-        EmptyPlaceholder multi_conversations = new EmptyPlaceholder();
-        multi_conversations.title = _("Multiple conversations selected");
-        multi_conversations.subtitle = _(
-            "Choosing an action will apply to all selected conversations"
-        );
-        this.multiple_conversations_page.add(multi_conversations);
-
         EmptyPlaceholder empty_folder = new EmptyPlaceholder();
         empty_folder.title = _("No conversations found");
         empty_folder.subtitle = _(
@@ -113,14 +104,14 @@ public class ConversationViewer : Gtk.Stack {
         // GearyController or somewhere more appropriate
         ConversationList conversation_list =
             ((MainWindow) GearyApplication.instance.controller.main_window).conversation_list;
-        Gee.Set<Geary.App.Conversation>? prev_selection = conversation_list.get_selected_conversations();
+        Geary.App.Conversation prev_selection = conversation_list.selected;
         conversation_list.unselect_all();
         box.vanished.connect((box) => {
                 set_visible_child(this.conversation_page);
-                if (prev_selection.is_empty) {
-                    conversation_list.conversation_selection_changed(prev_selection);
+                if (prev_selection == null) {
+                    conversation_list.conversation_selection_changed(null);
                 } else {
-                    conversation_list.select_conversations(prev_selection);
+                    conversation_list.select_conversation(prev_selection);
                 }
             });
         this.composer_page.add(box);
@@ -159,13 +150,6 @@ public class ConversationViewer : Gtk.Stack {
     }
 
     /**
-     * Shows the UI when multiple conversations have been selected
-     */
-    public void show_multiple_selected() {
-        set_visible_child(this.multiple_conversations_page);
-    }
-
-    /**
      * Shows the empty folder UI.
      */
     public void show_empty_folder() {
diff --git a/src/engine/app/app-conversation-monitor.vala b/src/engine/app/app-conversation-monitor.vala
index f607317..907fdc6 100644
--- a/src/engine/app/app-conversation-monitor.vala
+++ b/src/engine/app/app-conversation-monitor.vala
@@ -212,6 +212,10 @@ public class Geary.App.ConversationMonitor : BaseObject {
         return conversations.conversations;
     }
 
+    public bool has_conversation(Conversation target) {
+        return conversations.contains(target);
+    }
+
     public Geary.App.Conversation? get_conversation_for_email(Geary.EmailIdentifier email_id) {
         return conversations.get_by_email_identifier(email_id);
     }
diff --git a/ui/conversation-viewer.ui b/ui/conversation-viewer.ui
index b67be0e..d260514 100644
--- a/ui/conversation-viewer.ui
+++ b/ui/conversation-viewer.ui
@@ -129,20 +129,6 @@
       </packing>
     </child>
     <child>
-      <object class="GtkGrid" id="multiple_conversations_page">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="orientation">vertical</property>
-        <child>
-          <placeholder/>
-        </child>
-      </object>
-      <packing>
-        <property name="name">multiple_conversations_page</property>
-        <property name="position">3</property>
-      </packing>
-    </child>
-    <child>
       <object class="GtkGrid" id="empty_folder_page">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
diff --git a/ui/main-toolbar.ui b/ui/main-toolbar.ui
index 500b189..544c31c 100644
--- a/ui/main-toolbar.ui
+++ b/ui/main-toolbar.ui
@@ -182,6 +182,7 @@
                 <property name="can_focus">True</property>
                 <property name="focus_on_click">False</property>
                 <property name="receives_default">False</property>
+                <property name="tooltip_text" translatable="yes">Mark conversation</property>
                 <property name="action_name">win.mark-conversation-menu</property>
                 <property name="always_show_image">True</property>
                 <child>
@@ -203,6 +204,7 @@
                 <property name="can_focus">True</property>
                 <property name="focus_on_click">False</property>
                 <property name="receives_default">False</property>
+                <property name="tooltip_text" translatable="yes">Add label to conversation</property>
                 <property name="always_show_image">True</property>
                 <child>
                   <object class="GtkImage" id="copy_message_image">
@@ -223,6 +225,7 @@
                 <property name="can_focus">True</property>
                 <property name="focus_on_click">False</property>
                 <property name="receives_default">False</property>
+                <property name="tooltip_text" translatable="yes">Move conversation</property>
                 <property name="always_show_image">True</property>
                 <child>
                   <object class="GtkImage" id="move_message_image">
@@ -252,7 +255,7 @@
             <property name="can_focus">True</property>
             <property name="focus_on_click">False</property>
             <property name="receives_default">False</property>
-            <property name="tooltip_text" translatable="yes">Toggle find bar</property>
+            <property name="tooltip_text" translatable="yes">Find in conversation</property>
             <property name="always_show_image">True</property>
             <child>
               <object class="GtkImage" id="find_image">
@@ -297,6 +300,7 @@
                 <property name="can_focus">True</property>
                 <property name="focus_on_click">False</property>
                 <property name="receives_default">False</property>
+                <property name="tooltip_text" translatable="yes">Archive conversation (A)</property>
                 <property name="action_name">win.archive-conv</property>
                 <property name="image">archive_image</property>
                 <property name="use_underline">True</property>
@@ -309,15 +313,17 @@
               </packing>
             </child>
             <child>
-              <object class="GtkButton" id="trash_delete_button">
-                <property name="visible">True</property>
+              <object class="GtkButton" id="trash_button">
                 <property name="can_focus">True</property>
                 <property name="focus_on_click">False</property>
                 <property name="receives_default">False</property>
+                <property name="no_show_all">True</property>
+                <property name="tooltip_text" translatable="yes">Move conversation to Trash (Delete, 
Backspace)</property>
                 <property name="action_name">win.trash-conv</property>
                 <property name="always_show_image">True</property>
                 <child>
-                  <object class="GtkImage" id="trash_delete_image">
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
                     <property name="can_focus">False</property>
                     <property name="icon_name">user-trash-symbolic</property>
                   </object>
@@ -329,6 +335,29 @@
                 <property name="position">1</property>
               </packing>
             </child>
+            <child>
+              <object class="GtkButton" id="delete_button">
+                <property name="can_focus">True</property>
+                <property name="focus_on_click">False</property>
+                <property name="receives_default">False</property>
+                <property name="no_show_all">True</property>
+                <property name="tooltip_text" translatable="yes">Delete conversation 
(Shift+Delete)</property>
+                <property name="action_name">win.trash-conv</property>
+                <property name="always_show_image">True</property>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="icon_name">edit-delete-symbolic</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
             <style>
               <class name="raised"/>
               <class name="linked"/>



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