[geary/mjog/mail-merge-plugin: 19/28] Plugins: Update to new composer lifecycle



commit c00c7c38a152be346c2d86f1e5acbc381e91a341
Author: Michael Gratton <mike vee net>
Date:   Fri Jul 3 15:35:58 2020 +1000

    Plugins: Update to new composer lifecycle
    
    Update to be compatible with Application.Controller composer lifecycle
    changes, add matching signals so plugins can decorate composers as
    they are created.
    
    Keep track of Plugin.Composer implementation instances so that the same
    object can always be returned for the same Composer.Widget instance.
    
    Update plugin call sites.

 .../application/application-plugin-manager.vala    | 224 +++++++++++++++------
 src/client/composer/composer-widget.vala           |  11 +-
 .../plugin/email-templates/email-templates.vala    |  16 +-
 src/client/plugin/mail-merge/mail-merge.vala       |   9 +-
 src/client/plugin/plugin-application.vala          |  57 +++++-
 src/client/plugin/plugin-composer.vala             |  65 ++++--
 .../plugin/special-folders/special-folders.vala    |   9 +-
 7 files changed, 300 insertions(+), 91 deletions(-)
---
diff --git a/src/client/application/application-plugin-manager.vala 
b/src/client/application/application-plugin-manager.vala
index 9e8678667..82df12e70 100644
--- a/src/client/application/application-plugin-manager.vala
+++ b/src/client/application/application-plugin-manager.vala
@@ -124,6 +124,8 @@ public class Application.PluginManager : GLib.Object {
         internal weak PluginGlobals globals;
 
         private GLib.SimpleActionGroup? action_group = null;
+        private Gee.Map<Composer.Widget,ComposerImpl> composer_impls =
+        new Gee.HashMap<Composer.Widget,ComposerImpl>();
 
 
         public ApplicationImpl(Client backing,
@@ -134,14 +136,82 @@ public class Application.PluginManager : GLib.Object {
             this.globals = globals;
         }
 
-        public Plugin.Composer new_composer(Plugin.Account source)
+        public async Plugin.Composer compose_blank(Plugin.Account source)
             throws Plugin.Error {
             var impl = source as AccountImpl;
             if (impl == null) {
                 throw new Plugin.Error.NOT_SUPPORTED("Not a valid account");
             }
-            return new ComposerImpl(
-                this.backing, impl.backing, this.folders, this.email
+            return to_plugin_composer(
+                yield this.backing.controller.compose_blank(impl.backing)
+            );
+        }
+
+        public async Plugin.Composer? compose_with_context(
+            Plugin.Account send_from,
+            Plugin.Composer.ContextType plugin_type,
+            Plugin.EmailIdentifier to_load,
+            string? quote = null
+        ) throws Plugin.Error {
+            var source_impl = send_from as AccountImpl;
+            if (source_impl == null) {
+                throw new Plugin.Error.NOT_SUPPORTED("Not a valid account");
+            }
+            var id = this.globals.email.to_engine_id(to_load);
+            if (id == null) {
+                throw new Plugin.Error.NOT_FOUND("Email id not found");
+            }
+            Gee.Collection<Geary.Email>? email = null;
+            try {
+                email = yield source_impl.backing.emails.list_email_by_sparse_id_async(
+                    Geary.Collection.single(id),
+                    Composer.Widget.REQUIRED_FIELDS,
+                    NONE,
+                    source_impl.backing.cancellable
+                );
+            } catch (GLib.Error err) {
+                throw new Plugin.Error.NOT_FOUND(
+                    "Error looking up email: %s", err.message
+                );
+            }
+            if (email == null || email.is_empty) {
+                throw new Plugin.Error.NOT_FOUND("Email not found for id");
+            }
+            var context = Geary.Collection.first(email);
+
+            var type = Composer.Widget.ContextType.NONE;
+            switch (plugin_type) {
+            case NONE:
+                type = Composer.Widget.ContextType.NONE;
+                break;
+
+            case EDIT:
+                type = Composer.Widget.ContextType.EDIT;
+                // Use the same folder that the email exists since it
+                // could be getting edited somewhere outside of drafts
+                // (e.g. templates)
+                break;
+
+            case REPLY_SENDER:
+                type = Composer.Widget.ContextType.REPLY_SENDER;
+                break;
+
+            case REPLY_ALL:
+                type = Composer.Widget.ContextType.REPLY_ALL;
+                break;
+
+            case FORWARD:
+                type = Composer.Widget.ContextType.FORWARD;
+                break;
+            }
+
+            return to_plugin_composer(
+                yield this.backing.controller.compose_with_context(
+                    source_impl.backing,
+                    type,
+                    context,
+                    quote
+                )
             );
         }
 
@@ -208,6 +278,33 @@ public class Application.PluginManager : GLib.Object {
            }
         }
 
+        internal void engine_composer_registered(Composer.Widget registered) {
+            var impl = to_plugin_composer(registered);
+            if (impl != null) {
+                composer_registered(impl);
+            }
+        }
+
+        internal void engine_composer_deregistered(Composer.Widget deregistered) {
+            var impl = this.composer_impls.get(deregistered);
+            if (impl != null) {
+                composer_deregistered(impl);
+                this.composer_impls.unset(deregistered);
+            }
+        }
+
+        private ComposerImpl? to_plugin_composer(Composer.Widget? widget) {
+            ComposerImpl impl = null;
+            if (widget != null) {
+                impl = this.composer_impls.get(widget);
+                if (impl == null) {
+                    impl = new ComposerImpl(widget, this);
+                    this.composer_impls.set(widget, impl);
+                }
+            }
+            return impl;
+        }
+
         private void on_window_added(Gtk.Window window) {
             if (this.action_group != null) {
                 var main = window as MainWindow;
@@ -242,75 +339,72 @@ public class Application.PluginManager : GLib.Object {
     }
 
 
-    private class ComposerImpl : Geary.BaseObject, Plugin.Composer {
-
+    /** An implementation of the plugin Composer interface. */
+    internal class ComposerImpl : Geary.BaseObject, Plugin.Composer {
 
-        public override bool can_send { get; set; default = true; }
 
-        private Client application;
-        private AccountContext account;
-        private FolderStoreFactory folders;
-        private EmailStoreFactory email;
-        private Geary.Email? to_load = null;
-        private Geary.Folder? save_location = null;
-
-
-        public ComposerImpl(Client application,
-                            AccountContext account,
-                            FolderStoreFactory folders,
-                            EmailStoreFactory email) {
-            this.application = application;
-            this.account = account;
-            this.folders = folders;
-            this.email = email;
-        }
-
-        public void show() {
-            this.show_impl.begin();
+        public bool can_send {
+            get { return this.backing.can_send; }
+            set { this.backing.can_send = value; }
         }
 
-        public async void edit_email(Plugin.EmailIdentifier to_load)
-            throws Error {
-            Geary.EmailIdentifier? id = this.email.to_engine_id(to_load);
-            if (id == null) {
-                throw new Plugin.Error.NOT_FOUND("Email id not found");
+        public Plugin.Account? sender_context {
+            get {
+                // ugh
+                this._sender_context = this.application.globals.accounts.get(
+                    this.backing.sender_context
+                );
+                return this._sender_context;
             }
-            Gee.Collection<Geary.Email>? email =
-                yield this.account.emails.list_email_by_sparse_id_async(
-                    Geary.Collection.single(id),
-                    Composer.Widget.REQUIRED_FIELDS,
-                    NONE,
-                    this.account.cancellable
+        }
+        private Plugin.Account? _sender_context = null;
+
+        public Plugin.Folder? save_to  {
+            get {
+                // Ugh
+                this._save_to = (
+                    (backing.save_to != null)
+                    ? this.application.globals.folders.get_plugin_folder(
+                        this.backing.save_to
+                    )
+                    : null
                 );
-            if (email != null && !email.is_empty) {
-                this.to_load = Geary.Collection.first(email);
+                return this._save_to;
             }
         }
+        private Plugin.Folder? _save_to = null;
 
-        public void save_to_folder(Plugin.Folder? location) {
-            var folder = this.folders.get_engine_folder(location);
-            if (folder != null && folder.account == this.account.account) {
-                this.save_location = folder;
-            }
+        private Composer.Widget backing;
+        private weak ApplicationImpl application;
+
+
+        public ComposerImpl(Composer.Widget backing,
+                            ApplicationImpl application) {
+            this.backing = backing;
+            this.application = application;
         }
 
-        private async void show_impl() {
-            var controller = this.application.controller;
-            Composer.Widget? composer = null;
-            if (this.to_load == null) {
-                composer = yield controller.compose_new_email(
-                    null,
-                    this.save_location
-                );
-            } else {
-                composer = yield controller.compose_with_context_email(
-                    EDIT,
-                    this.to_load,
-                    null,
-                    this.save_location
+        public void save_to_folder(Plugin.Folder? location) {
+            var engine = this.application.globals.folders.get_engine_folder(location);
+            if (engine != null && engine.account == this.backing.sender_context.account) {
+                this.backing.set_save_to_override.begin(
+                    engine,
+                    (obj, res) => {
+                        try {
+                            this.backing.set_save_to_override.end(res);
+                        } catch (GLib.Error err) {
+                            debug(
+                                "Error setting folder for saving: %s",
+                                err.message
+                            );
+                        }
+                    }
                 );
             }
-            composer.can_send = this.can_send;
+        }
+
+        public void present() {
+            this.application.backing.controller.present_composer(this.backing);
         }
 
     }
@@ -622,4 +716,16 @@ public class Application.PluginManager : GLib.Object {
         this.plugin_set.unset(context.info);
     }
 
+    private void on_composer_registered(Composer.Widget registered) {
+        foreach (var context in this.plugin_set.values) {
+            context.application.engine_composer_registered(registered);
+        }
+    }
+
+    private void on_composer_deregistered(Composer.Widget deregistered) {
+        foreach (var context in this.plugin_set.values) {
+            context.application.engine_composer_deregistered(deregistered);
+        }
+    }
+
 }
diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala
index 19b853cbd..77330ca39 100644
--- a/src/client/composer/composer-widget.vala
+++ b/src/client/composer/composer-widget.vala
@@ -317,6 +317,9 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
         private set { this.subject_entry.set_text(value); }
     }
 
+    /** Overrides for the draft folder as save destination, if any. */
+    internal Geary.Folder? save_to { get; private set; default = null; }
+
     private Geary.RFC822.MailboxAddresses from { get; private set; }
 
     private string to {
@@ -482,7 +485,6 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
     private Gee.Map<string,Geary.Memory.Buffer> inline_files = new Gee.HashMap<string,Geary.Memory.Buffer>();
     private Gee.Map<string,Geary.Memory.Buffer> cid_files = new Gee.HashMap<string,Geary.Memory.Buffer>();
 
-    private Geary.Folder? save_to;
     private Geary.App.DraftManager? draft_manager = null;
     private GLib.Cancellable? draft_manager_opening = null;
     private Geary.TimeoutManager draft_timer;
@@ -1032,6 +1034,13 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
         }
     }
 
+    /** Overrides the draft folder as a destination for saving. */
+    internal async void set_save_to_override(Geary.Folder? save_to)
+        throws GLib.Error {
+        this.save_to = save_to;
+        yield reopen_draft_manager();
+    }
+
     /**
      * Loads and sets contact auto-complete data for the current account.
      */
diff --git a/src/client/plugin/email-templates/email-templates.vala 
b/src/client/plugin/email-templates/email-templates.vala
index 9278b3490..39006bea9 100644
--- a/src/client/plugin/email-templates/email-templates.vala
+++ b/src/client/plugin/email-templates/email-templates.vala
@@ -138,7 +138,16 @@ public class Plugin.EmailTemplates :
     private async void edit_email(Folder? target, EmailIdentifier? id, bool send) {
         var account = (target != null) ? target.account : id.account;
         try {
-            var composer = this.plugin_application.new_composer(account);
+            Plugin.Composer? composer = null;
+            if (id != null) {
+                composer = yield this.plugin_application.compose_with_context(
+                    id.account,
+                    Composer.ContextType.EDIT,
+                    id
+                );
+            } else {
+                composer = yield this.plugin_application.compose_blank(account);
+            }
             if (!send) {
                 var folder = target;
                 if (folder == null && id != null) {
@@ -153,10 +162,7 @@ public class Plugin.EmailTemplates :
                 composer.can_send = false;
             }
 
-            if (id != null) {
-                yield composer.edit_email(id);
-            }
-            composer.show();
+            composer.present();
         } catch (GLib.Error err) {
             warning("Unable to construct composer: %s", err.message);
         }
diff --git a/src/client/plugin/mail-merge/mail-merge.vala b/src/client/plugin/mail-merge/mail-merge.vala
index f190f8b28..5d0eee083 100644
--- a/src/client/plugin/mail-merge/mail-merge.vala
+++ b/src/client/plugin/mail-merge/mail-merge.vala
@@ -138,7 +138,11 @@ public class Plugin.MailMerge :
 
     private async void edit_email(EmailIdentifier id) {
         try {
-            var composer = this.plugin_application.new_composer(id.account);
+            var composer = yield this.plugin_application.compose_with_context(
+                id.account,
+                Composer.ContextType.EDIT,
+                id
+            );
             var containing = yield this.folder_store.list_containing_folders(
                 id, this.cancellable
             );
@@ -148,8 +152,7 @@ public class Plugin.MailMerge :
 
             composer.save_to_folder(folder);
             composer.can_send = false;
-            yield composer.edit_email(id);
-            composer.show();
+            composer.present();
         } catch (GLib.Error err) {
             warning("Unable to construct composer: %s", err.message);
         }
diff --git a/src/client/plugin/plugin-application.vala b/src/client/plugin/plugin-application.vala
index 19bb837c5..4e532362b 100644
--- a/src/client/plugin/plugin-application.vala
+++ b/src/client/plugin/plugin-application.vala
@@ -15,13 +15,62 @@ public interface Plugin.Application : Geary.BaseObject {
 
 
     /**
-     * Constructs a new, blank composer for the given account.
+     * Emitted when a new composer is registered with the application.
+     *
+     * A composer is registered when it is first constructed.
+     *
+     * @see Composer.present
+     */
+    public signal void composer_registered(Composer composer);
+
+    /**
+     * Emitted when an existing composer is de-registered.
+     *
+     * A composer is deregistered when it is destroyed, either after
+     * being sent, closed, or discarded.
+     */
+    public signal void composer_deregistered(Composer composer);
+
+
+    /**
+     * Obtains a new, blank composer for the given account.
      *
      * The composer will be initialised to send an email from the
-     * given account. This may be changed by people before they send
-     * the email, however.
+     * given account. This may be changed via the UI before the email
+     * is sent, however.
+     *
+     * Existing composer instances are re-used where possible, thus if
+     * a blank composer is already open, the same instance may be
+     * returned if this method is called multiple times.
+     */
+    public abstract async Composer compose_blank(Account send_from)
+        throws Error;
+
+    /**
+     * Obtains a new composer with the given message as a context
+     *
+     * The composer will be initialised to send an email from the
+     * given account, with the given email loaded as either an email
+     * to edit, a reply, or a forwarded message, depending on the
+     * given context.
+     *
+     * If a quote is given, this added as a quote in the composer's
+     * body.
+     *
+     * Existing composer instances are re-used where possible, thus if
+     * a composer with a given context and email is already open, the
+     * same instance may be returned if this method is called multiple
+     * times with the same arguments.
+     *
+     * Returns null if there is an existing composer open and the
+     * prompt to close it was declined.
      */
-    public abstract Composer new_composer(Account source) throws Error;
+    public abstract async Composer? compose_with_context(
+        Account send_from,
+        Composer.ContextType type,
+        EmailIdentifier context,
+        string? quote = null
+    ) throws Error;
 
     /**
      * Registers a plugin action with the application.
diff --git a/src/client/plugin/plugin-composer.vala b/src/client/plugin/plugin-composer.vala
index 146721ddc..9765ef13d 100644
--- a/src/client/plugin/plugin-composer.vala
+++ b/src/client/plugin/plugin-composer.vala
@@ -7,43 +7,76 @@
 
 /**
  * An object representing a composer for use by plugins.
+ *
+ * Instances of this interface can be obtained by calling {@link
+ * Application.compose_blank} or {@link
+ * Application.compose_with_context}. A composer instance may not be
+ * visible until {@link present} is called, allowing it to be
+ * configured via calls to this interface first, if required.
  */
 public interface Plugin.Composer : Geary.BaseObject {
 
 
+    /**
+     * Determines the type of the context email passed to the composer
+     *
+     * @see Application.compose_with_context
+     */
+    public enum ContextType {
+        /** No context mail was provided. */
+        NONE,
+
+        /** Context is an email to edited, for example a draft or template. */
+        EDIT,
+
+        /** Context is an email being replied to the sender only. */
+        REPLY_SENDER,
+
+        /** Context is an email being replied to all recipients. */
+        REPLY_ALL,
+
+        /** Context is an email being forwarded. */
+        FORWARD
+    }
+
+    /**
+     * Denotes the account the composed email will be sent from.
+     */
+    public abstract Plugin.Account? sender_context { get; }
+
     /**
      * Determines if the email in the composer can be sent.
      */
     public abstract bool can_send { get; set; }
 
     /**
-     * Causes the composer to be made visible.
+     * Denotes the folder that the email will be saved to.
+     *
+     * If non-null, fixes the folder used by the composer for saving
+     * the email. If null, the current account's Draft folder will be
+     * used.
      *
-     * The composer will be shown as either full-pane and in-window if
-     * not a reply to a currently displayed conversation, inline and
-     * in-window if a reply to an existing conversation being
-     * displayed, or detached if there is already an in-window
-     * composer being displayed.
+     * @see save_to_folder
      */
-    public abstract void show();
+    public abstract Plugin.Folder? save_to { get; }
+
 
     /**
-     * Loads an email into the composer to be edited.
+     * Presents the composer on screen.
      *
-     * Loads the given email, and sets it as the email to be edited in
-     * this composer. This must be called before calling {@link show},
-     * and has no effect if called afterwards.
+     * The composer is made visible if this has not yet been done so,
+     * and the application attempts to ensure that it is presented on
+     * the active display.
      */
-    public async abstract void edit_email(EmailIdentifier to_load)
-        throws GLib.Error;
+    public abstract void present();
 
     /**
      * Sets the folder used to save the message being composed.
      *
      * Ensures email for both automatic and manual saving of the email
-     * in the composer is saved to the given folder. This must be
-     * called before calling {@link show}, and has no effect if called
-     * afterwards.
+     * in the composer is saved to the given folder. This disables
+     * changing accounts in the composer's UI since email cannot be
+     * saved across accounts.
      */
     public abstract void save_to_folder(Plugin.Folder? location);
 
diff --git a/src/client/plugin/special-folders/special-folders.vala 
b/src/client/plugin/special-folders/special-folders.vala
index aa9e120d6..15affb3b3 100644
--- a/src/client/plugin/special-folders/special-folders.vala
+++ b/src/client/plugin/special-folders/special-folders.vala
@@ -142,9 +142,12 @@ public class Plugin.SpecialFolders :
 
     private async void edit_draft(EmailIdentifier id) {
         try {
-            var composer = this.plugin_application.new_composer(id.account);
-            yield composer.edit_email(id);
-            composer.show();
+            var composer = yield this.plugin_application.compose_with_context(
+                id.account,
+                Composer.ContextType.EDIT,
+                id
+            );
+            composer.present();
         } catch (GLib.Error err) {
             warning("Unable to construct composer: %s", err.message);
         }


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