[geary/wip/draft-management: 21/27] Rework how managing composers works when switching conversations



commit daa01c3d13e1769a8df347892288b958f2f7a9b5
Author: Michael Gratton <mike vee net>
Date:   Fri Aug 2 09:58:04 2019 +1000

    Rework how managing composers works when switching conversations
    
    Move responsibility for managing composers when switching conversations
    from the controller to the main window.
    
    Removes Application.Controller::should_create_new_composer and
    ::can_switch_conversation_view, moving half the code to
    ::create_compose_widget_async since it was only used there, and moving
    the rest to a new MainWindow::close_composer method, where it belongs.
    Add new MainWindow::has_composers property, and convert call sites of
    removed methods to use these instead.
    
    Update ConversationViewer to clean up composer management code a bit and
    add a ::current_composer property, and add a new ComposerWidget:close
    method to support the above changes.

 src/client/application/application-controller.vala | 110 +++++++--------------
 src/client/components/main-window.vala             |  68 ++++++++-----
 src/client/composer/composer-widget.vala           |  12 ++-
 .../conversation-list/conversation-list-view.vala  |  20 ++--
 .../conversation-viewer/conversation-viewer.vala   |  62 ++++++++----
 src/client/folder-list/folder-list-tree.vala       |   7 +-
 6 files changed, 156 insertions(+), 123 deletions(-)
---
diff --git a/src/client/application/application-controller.vala 
b/src/client/application/application-controller.vala
index ee619996..ced111ec 100644
--- a/src/client/application/application-controller.vala
+++ b/src/client/application/application-controller.vala
@@ -1186,7 +1186,7 @@ public class Application.Controller : Geary.BaseObject {
         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) {
+        if (this.current_folder != null && !this.main_window.has_composer) {
             switch(selected.size) {
             case 0:
                 enable_message_buttons(false);
@@ -1251,6 +1251,8 @@ public class Application.Controller : Geary.BaseObject {
             Geary.App.Conversation.Location.IN_FOLDER
         );
 
+        // Check all known composers since the draft may be open in a
+        // detached composer
         bool already_open = false;
         foreach (ComposerWidget composer in this.composer_widgets) {
             if (composer.draft_id != null &&
@@ -2031,8 +2033,41 @@ public class Application.Controller : Geary.BaseObject {
         if (current_account == null)
             return;
 
-        if (!should_create_new_composer(compose_type, referred, quote, is_draft))
-            return;
+        // There's a few situations where we can re-use an existing
+        // composer, check for these first.
+
+        if (compose_type == NEW_MESSAGE && !is_draft) {
+            // We're creating a new message that isn't a draft, if
+            // there's already a composer open, just use that
+            ComposerWidget? existing =
+                this.main_window.conversation_viewer.current_composer;
+            if (existing != null &&
+                existing.state == PANED &&
+                existing.is_blank) {
+                existing.present();
+                existing.set_focus();
+                return;
+            }
+        } else if (compose_type != NEW_MESSAGE) {
+            // We're replying, see whether we already have a reply for
+            // that message and if so, insert a quote into that.
+            foreach (ComposerWidget existing in this.composer_widgets) {
+                if (existing.state != DETACHED &&
+                    ((referred != null && existing.referred_ids.contains(referred.id)) ||
+                     quote != null)) {
+                    existing.change_compose_type(compose_type, referred, quote);
+                    return;
+                }
+            }
+
+            // Can't re-use an existing composer, so need to create a
+            // new one. Replies must open inline in the main window,
+            // so we need to ensure there are no composers open there
+            // first.
+            if (!this.main_window.close_composer()) {
+                return;
+            }
+        }
 
         ComposerWidget widget;
         if (mailto != null) {
@@ -2082,72 +2117,6 @@ public class Application.Controller : Geary.BaseObject {
         widget.set_focus();
     }
 
-    private bool should_create_new_composer(ComposerWidget.ComposeType? compose_type,
-                                            Geary.Email? referred,
-                                            string? quote,
-                                            bool is_draft) {
-        // In we're replying, see whether we already have a reply for that message.
-        if (compose_type != null && compose_type != ComposerWidget.ComposeType.NEW_MESSAGE) {
-            foreach (ComposerWidget cw in composer_widgets) {
-                if (cw.state != ComposerWidget.ComposerState.DETACHED &&
-                    ((referred != null && cw.referred_ids.contains(referred.id)) ||
-                     quote != null)) {
-                    cw.change_compose_type(compose_type, referred, quote);
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        // If there are no inline composers, go ahead!
-        if (!any_inline_composers())
-            return true;
-
-        // If we're resuming a draft with open composers, open in a new window.
-        if (is_draft) {
-            return true;
-        }
-
-        // If we're creating a new message, and there's already a new message open, focus on
-        // it if it hasn't been modified; otherwise open a new composer in a new window.
-        if (compose_type == ComposerWidget.ComposeType.NEW_MESSAGE) {
-            foreach (ComposerWidget cw in composer_widgets) {
-                if (cw.state == ComposerWidget.ComposerState.PANED) {
-                    if (!cw.is_blank) {
-                        return true;
-                    } else {
-                        cw.change_compose_type(compose_type);  // To refocus
-                        return false;
-                    }
-                }
-            }
-        }
-
-        // Find out what to do with the inline composers.
-        // TODO: Remove this in favor of automatically saving drafts
-        this.main_window.present();
-        bool create_okay = true;
-        foreach (ComposerWidget cw in composer_widgets) {
-            if (cw.state != ComposerWidget.ComposerState.DETACHED &&
-                cw.should_close() == ComposerWidget.CloseStatus.CANCEL_CLOSE) {
-                create_okay = false;
-                break;
-            }
-        }
-        return create_okay;
-    }
-
-    public bool can_switch_conversation_view() {
-        return should_create_new_composer(null, null, null, false);
-    }
-
-    public bool any_inline_composers() {
-        foreach (ComposerWidget cw in composer_widgets)
-            if (cw.state != ComposerWidget.ComposerState.DETACHED)
-                return true;
-        return false;
-    }
-
     private void on_composer_widget_destroy(Gtk.Widget sender) {
         composer_widgets.remove((ComposerWidget) sender);
         debug(@"Destroying composer of type $(((ComposerWidget) sender).compose_type); "
@@ -2339,9 +2308,6 @@ public class Application.Controller : Geary.BaseObject {
 
     private async void archive_or_delete_selection_async(bool archive, bool trash,
         Cancellable? cancellable) throws Error {
-        if (!can_switch_conversation_view())
-            return;
-
         ConversationListBox list_view =
             main_window.conversation_viewer.current_list;
         if (list_view != null &&
diff --git a/src/client/components/main-window.vala b/src/client/components/main-window.vala
index 2de8d35b..8caea3f4 100644
--- a/src/client/components/main-window.vala
+++ b/src/client/components/main-window.vala
@@ -28,11 +28,16 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
         get; private set; default = null;
     }
 
+    /** Determines if a composer is currently open in this window. */
+    public bool has_composer {
+        get {
+            return (this.conversation_viewer.current_composer != null);
+        }
+    }
+
     /** Specifies if the Shift key is currently being held. */
     public bool is_shift_down { get; private set; default = false; }
 
-    private Geary.AggregateProgressMonitor progress_monitor = new Geary.AggregateProgressMonitor();
-
     // Used to save/load the window state between sessions.
     public int window_width { get; set; }
     public int window_height { get; set; }
@@ -47,6 +52,7 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
     public StatusBar status_bar { get; private set; default = new StatusBar(); }
     private MonitoredSpinner spinner = new MonitoredSpinner();
 
+    private Geary.AggregateProgressMonitor progress_monitor = new Geary.AggregateProgressMonitor();
     private Geary.TimeoutManager update_ui_timeout;
     private int64 update_ui_last = 0;
 
@@ -125,17 +131,6 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
         base_unref();
     }
 
-    public void open_composer_for_mailbox(Geary.RFC822.MailboxAddress to) {
-        Application.Controller controller = this.application.controller;
-        ComposerWidget composer = new ComposerWidget(
-            this.application, this.current_folder.account, null, NEW_MESSAGE
-        );
-        composer.to = to.to_full_display();
-        controller.add_composer(composer);
-        show_composer(composer);
-        composer.load.begin(null, null, false);
-    }
-
     /** Updates the window's account status info bars. */
     public void update_account_status(Geary.Account.Status status,
                                       bool has_auth_error,
@@ -221,15 +216,21 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
         this.info_bar_frame.show();
     }
 
-    /** Displays a composer in the window if possible, else in a new window. */
-    public void show_composer(ComposerWidget composer) {
-        bool has_composer = (
-            this.conversation_viewer.is_composer_visible ||
-            (this.conversation_viewer.current_list != null &&
-             this.conversation_viewer.current_list.has_composer)
+    /** Displays a composer addressed to a specific email address. */
+    public void open_composer_for_mailbox(Geary.RFC822.MailboxAddress to) {
+        Application.Controller controller = this.application.controller;
+        ComposerWidget composer = new ComposerWidget(
+            this.application, this.current_folder.account, null, NEW_MESSAGE
         );
+        composer.to = to.to_full_display();
+        controller.add_composer(composer);
+        show_composer(composer);
+        composer.load.begin(null, null, false);
+    }
 
-        if (has_composer) {
+    /** Displays a composer in the window if possible, else in a new window. */
+    public void show_composer(ComposerWidget composer) {
+        if (this.has_composer) {
             composer.state = ComposerWidget.ComposerState.DETACHED;
             new ComposerWindow(composer, this.application);
         } else {
@@ -238,6 +239,29 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
         }
     }
 
+    /**
+     * Closes any open composers after prompting the user.
+     *
+     * Returns true if none were open or the user approved closing
+     * them.
+     */
+    public bool close_composer() {
+        bool closed = true;
+        ComposerWidget? composer = this.conversation_viewer.current_composer;
+        if (composer != null) {
+            switch (composer.should_close()) {
+            case DO_CLOSE:
+                composer.close();
+                break;
+
+            case CANCEL_CLOSE:
+                closed = false;
+                break;
+            }
+        }
+        return closed;
+    }
+
     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
@@ -622,8 +646,8 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
             // there isn't already a selection or a composer to avoid
             // interrupting those.
             if (!this.application.config.autoselect &&
-                this.conversation_list_view.get_selection().count_selected_rows() == 0 &&
-                !this.conversation_viewer.is_composer_visible) {
+                !this.has_composer &&
+                this.conversation_list_view.get_selection().count_selected_rows() == 0) {
                 this.conversation_viewer.show_none_selected();
                 this.application.controller.enable_message_buttons(false);
             }
diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala
index e3e331d7..de225573 100644
--- a/src/client/composer/composer-widget.vala
+++ b/src/client/composer/composer-widget.vala
@@ -623,6 +623,11 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
         }
     }
 
+    /** Closes the composer unconditionally. */
+    public void close() {
+        this.container.close_container();
+    }
+
     /**
      * Loads the message into the composer editor.
      */
@@ -779,7 +784,7 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
                 if (referred.references != null)
                     this.references = referred.references.to_rfc822_string();
                 if (referred.subject != null)
-                    this.subject = referred.subject.value;
+                    this.subject = referred.subject.value ?? "";
                 try {
                     Geary.RFC822.Message message = referred.get_message();
                     if (message.has_html_body()) {
@@ -1213,8 +1218,9 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
     }
 
     private void on_close(SimpleAction action, Variant? param) {
-        if (should_close() == CloseStatus.DO_CLOSE)
-            this.container.close_container();
+        if (should_close() == CloseStatus.DO_CLOSE) {
+            close();
+        }
     }
 
     private void on_close_and_save(SimpleAction action, Variant? param) {
diff --git a/src/client/conversation-list/conversation-list-view.vala 
b/src/client/conversation-list/conversation-list-view.vala
index 6ef39aaf..6dad0ff9 100644
--- a/src/client/conversation-list/conversation-list-view.vala
+++ b/src/client/conversation-list/conversation-list-view.vala
@@ -201,11 +201,14 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
         check_load_more();
 
         // Select the first conversation, if autoselect is enabled,
-        // nothing has been selected yet and we're not composing.
+        // nothing has been selected yet and we're not showing a
+        // composer.
         if (GearyApplication.instance.config.autoselect &&
-            get_selection().count_selected_rows() == 0 &&
-            !GearyApplication.instance.controller.any_inline_composers()) {
-            set_cursor(new Gtk.TreePath.from_indices(0, -1), null, false);
+            get_selection().count_selected_rows() == 0) {
+            MainWindow? parent = get_toplevel() as MainWindow;
+            if (parent != null && !parent.has_composer) {
+                set_cursor(new Gtk.TreePath.from_indices(0, -1), null, false);
+            }
         }
     }
 
@@ -311,9 +314,12 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
             }
         }
 
-        if (!get_selection().path_is_selected(path) &&
-            !GearyApplication.instance.controller.can_switch_conversation_view())
-            return true;
+        if (!get_selection().path_is_selected(path)) {
+            MainWindow? parent = get_toplevel() as MainWindow;
+            if (parent != null && !parent.close_composer()) {
+                return true;
+            }
+        }
 
         if (event.button == 3 && event.type == Gdk.EventType.BUTTON_PRESS) {
             Geary.App.Conversation conversation = get_model().get_conversation_at_path(path);
diff --git a/src/client/conversation-viewer/conversation-viewer.vala 
b/src/client/conversation-viewer/conversation-viewer.vala
index 1e4cf178..cce1b14d 100644
--- a/src/client/conversation-viewer/conversation-viewer.vala
+++ b/src/client/conversation-viewer/conversation-viewer.vala
@@ -19,15 +19,16 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
         get; private set; default = null;
     }
 
-    /**
-     * Specifies if a full-height composer is currently shown.
-     */
-    public bool is_composer_visible {
-        get { return (get_visible_child() == this.composer_page); }
+    /** Returns the currently displayed composer if any. */
+    public ComposerWidget? current_composer {
+        get; private set; default = null;
     }
 
     private Configuration config;
 
+    private Gee.Set<Geary.App.Conversation>? selection_while_composing = null;
+
+
     // Stack pages
     [GtkChild]
     private Gtk.Spinner loading_page;
@@ -141,21 +142,18 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
      */
     public void do_compose(ComposerWidget composer) {
         ComposerBox box = new ComposerBox(composer);
+        this.current_composer = composer;
 
         // XXX move the ConversationListView management code into
-        // GearyController or somewhere more appropriate
-        ConversationListView conversation_list_view =
-            ((MainWindow) GearyApplication.instance.controller.main_window).conversation_list_view;
-        Gee.Set<Geary.App.Conversation>? prev_selection = 
conversation_list_view.get_selected_conversations();
-        conversation_list_view.get_selection().unselect_all();
-        box.vanished.connect((box) => {
-                set_visible_child(this.conversation_page);
-                if (prev_selection.is_empty) {
-                    conversation_list_view.conversations_selected(prev_selection);
-                } else {
-                    conversation_list_view.select_conversations(prev_selection);
-                }
-            });
+        // MainWindow or somewhere more appropriate
+        MainWindow? main_window = get_toplevel() as MainWindow;
+        if (main_window != null) {
+            ConversationListView conversation_list = main_window.conversation_list_view;
+            this.selection_while_composing = conversation_list.get_selected_conversations();
+            conversation_list.get_selection().unselect_all();
+        }
+
+        box.vanished.connect(on_composer_closed);
         this.composer_page.add(box);
         set_visible_child(this.composer_page);
     }
@@ -166,11 +164,13 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
     public void do_compose_embedded(ComposerWidget composer,
                                     Geary.Email? referred,
                                     bool is_draft) {
+        this.current_composer = composer;
         ComposerEmbed embed = new ComposerEmbed(
             referred,
             composer,
             this.conversation_scroller
         );
+        embed.vanished.connect(on_composer_closed);
 
         // We need to disable kinetic scrolling so that if it still
         // has some momentum when the composer is inserted and
@@ -441,4 +441,30 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
         return Gdk.EVENT_PROPAGATE;
     }
 
+    private void on_composer_closed() {
+        this.current_composer = null;
+        if (get_visible_child() == this.composer_page) {
+            set_visible_child(this.conversation_page);
+
+            // Restore the old selection
+            MainWindow? main_window = get_toplevel() as MainWindow;
+            if (main_window != null &&
+                this.selection_while_composing != null) {
+                ConversationListView conversation_list =
+                    main_window.conversation_list_view;
+                if (this.selection_while_composing.is_empty) {
+                    conversation_list.conversations_selected(
+                        this.selection_while_composing
+                    );
+                } else {
+                    conversation_list.select_conversations(
+                        this.selection_while_composing
+                    );
+                }
+
+                this.selection_while_composing = null;
+            }
+        }
+    }
+
 }
diff --git a/src/client/folder-list/folder-list-tree.vala b/src/client/folder-list/folder-list-tree.vala
index 0fac3770..d7dc2419 100644
--- a/src/client/folder-list/folder-list-tree.vala
+++ b/src/client/folder-list/folder-list-tree.vala
@@ -54,7 +54,12 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
     }
 
     public override bool accept_cursor_changed() {
-        return GearyApplication.instance.controller.can_switch_conversation_view();
+        bool can_switch = true;
+        MainWindow? parent = get_toplevel() as MainWindow;
+        if (parent != null) {
+            can_switch = parent.close_composer();
+        }
+        return can_switch;
     }
 
     private void on_entry_selected(Sidebar.SelectableEntry selectable) {


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