[geary/wip/713739-inline: 2/37] Separate composer widget from composer window



commit 8da7a73c027824d56ddf65a11158f9268fe3d6c5
Author: Robert Schroll <rschroll gmail com>
Date:   Tue Feb 11 01:31:45 2014 -0500

    Separate composer widget from composer window
    
    In anticipation of inline composition, we need the composer widget to be
    separate from the window in which it lives.  We introduce a new
    interface, ComposerContainer, that the thing that holds to
    ComposerWidget must implement.

 src/CMakeLists.txt                           |    2 +
 src/client/accounts/account-dialog.vala      |   16 +++---
 src/client/application/geary-controller.vala |   51 +++++++++-----------
 src/client/composer/composer-container.vala  |   13 +++++
 src/client/composer/composer-toolbar.vala    |   18 ++++----
 src/client/composer/composer-widget.vala     |   66 +++++++++++++-------------
 src/client/composer/composer-window.vala     |   44 +++++++++++++++++
 7 files changed, 133 insertions(+), 77 deletions(-)
---
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index b992514..4cfa0a9 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -322,7 +322,9 @@ client/components/pill-toolbar.vala
 client/components/status-bar.vala
 client/components/stock.vala
 
+client/composer/composer-container.vala
 client/composer/composer-toolbar.vala
+client/composer/composer-widget.vala
 client/composer/composer-window.vala
 client/composer/contact-entry-completion.vala
 client/composer/contact-list-store.vala
diff --git a/src/client/accounts/account-dialog.vala b/src/client/accounts/account-dialog.vala
index daa8e84..6070dea 100644
--- a/src/client/accounts/account-dialog.vala
+++ b/src/client/accounts/account-dialog.vala
@@ -126,22 +126,22 @@ public class AccountDialog : Gtk.Dialog {
             return;
         
         // Check for open composer windows.
-        bool composer_window_found = false;
-        Gee.List<ComposerWindow>? windows = 
-            GearyApplication.instance.controller.get_composer_windows_for_account(account);
+        bool composer_widget_found = false;
+        Gee.List<ComposerWidget>? widgets = 
+            GearyApplication.instance.controller.get_composer_widgets_for_account(account);
         
-        if (windows != null) {
-            foreach (ComposerWindow cw in windows) {
+        if (widgets != null) {
+            foreach (ComposerWidget cw in widgets) {
                 if (cw.account.information == account &&
-                    cw.compose_type != ComposerWindow.ComposeType.NEW_MESSAGE) {
-                    composer_window_found = true;
+                    cw.compose_type != ComposerWidget.ComposeType.NEW_MESSAGE) {
+                    composer_widget_found = true;
                     
                     break;
                 }
             }
         }
         
-        if (composer_window_found) {
+        if (composer_widget_found) {
             // Warn user that account cannot be deleted until composer is closed.
             remove_fail_pane.present();
         } else {
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 01b2d47..416d102 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -91,7 +91,7 @@ public class GearyController : Geary.BaseObject {
         = 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<ComposerWindow> composer_windows = new Gee.LinkedList<ComposerWindow>();
+    private Gee.LinkedList<ComposerWidget> composer_widgets = new Gee.LinkedList<ComposerWidget>();
     private File? last_save_directory = null;
     private NewMessagesMonitor? new_messages_monitor = null;
     private NewMessagesIndicator? new_messages_indicator = null;
@@ -109,7 +109,7 @@ public class GearyController : Geary.BaseObject {
     private Gee.List<string> pending_mailtos = new Gee.ArrayList<string>();
     
     // List of windows we're waiting to close before Geary closes.
-    private Gee.List<ComposerWindow> waiting_to_close = new Gee.ArrayList<ComposerWindow>();
+    private Gee.List<ComposerWidget> waiting_to_close = new Gee.ArrayList<ComposerWidget>();
     
     /**
      * Fired when the currently selected account has changed.
@@ -1153,7 +1153,7 @@ public class GearyController : Geary.BaseObject {
     }
     
     private void on_edit_draft(Geary.Email draft) {
-        create_compose_window(ComposerWindow.ComposeType.NEW_MESSAGE, draft, null, true);
+        create_compose_widget(ComposerWidget.ComposeType.NEW_MESSAGE, draft, null, true);
     }
     
     private void on_special_folder_type_changed(Geary.Folder folder, Geary.SpecialFolderType old_type,
@@ -1761,11 +1761,11 @@ public class GearyController : Geary.BaseObject {
     }
     
     private bool close_composition_windows() {
-        Gee.List<ComposerWindow> composers_to_destroy = new Gee.ArrayList<ComposerWindow>();
+        Gee.List<ComposerWidget> composers_to_destroy = new Gee.ArrayList<ComposerWidget>();
         bool quit_cancelled = false;
         
         // If there's composer windows open, give the user a chance to save or cancel.
-        foreach(ComposerWindow cw in composer_windows) {
+        foreach(ComposerWidget cw in composer_widgets) {
             // Check if we should close the window immediately, or if we need to wait.
             if (!cw.should_close()) {
                 if (cw.delayed_close) {
@@ -1788,7 +1788,7 @@ public class GearyController : Geary.BaseObject {
         }
         
         // Safely destroy windows.
-        foreach(ComposerWindow cw in composers_to_destroy)
+        foreach(ComposerWidget cw in composers_to_destroy)
             cw.destroy();
         
         // If we cancelled the quit we can bail here.
@@ -1809,19 +1809,19 @@ public class GearyController : Geary.BaseObject {
         return true;
     }
     
-    private void create_compose_window(ComposerWindow.ComposeType compose_type,
+    private void create_compose_widget(ComposerWidget.ComposeType compose_type,
         Geary.Email? referred = null, string? mailto = null, bool is_draft = false) {
-        create_compose_window_async.begin(compose_type, referred, mailto, is_draft);
+        create_compose_widget_async.begin(compose_type, referred, mailto, is_draft);
     }
     
-    private async void create_compose_window_async(ComposerWindow.ComposeType compose_type,
+    private async void create_compose_widget_async(ComposerWidget.ComposeType compose_type,
         Geary.Email? referred = null, string? mailto = null, bool is_draft = false) {
         if (current_account == null)
             return;
         
-        ComposerWindow window;
+        ComposerWidget widget;
         if (mailto != null) {
-            window = new ComposerWindow.from_mailto(current_account, mailto);
+            widget = new ComposerWidget.from_mailto(current_account, mailto);
         } else {
             Geary.Email? full = null;
             if (referred != null) {
@@ -1834,22 +1834,19 @@ public class GearyController : Geary.BaseObject {
                 }
             }
             
-            window = new ComposerWindow(current_account, compose_type, full, is_draft);
+            widget = new ComposerWidget(current_account, compose_type, full, is_draft);
         }
-        window.set_position(Gtk.WindowPosition.CENTER);
         
         // We want to keep track of the open composer windows, so we can allow the user to cancel
         // an exit without losing their data.
-        composer_windows.add(window);
-        window.destroy.connect(on_composer_window_destroy);
-        
-        window.show_all();
+        composer_widgets.add(widget);
+        widget.destroy.connect(on_composer_widget_destroy);
     }
     
-    private void on_composer_window_destroy(Gtk.Widget sender) {
-        composer_windows.remove((ComposerWindow) sender);
+    private void on_composer_widget_destroy(Gtk.Widget sender) {
+        composer_widgets.remove((ComposerWidget) sender);
         
-        if (waiting_to_close.remove((ComposerWindow) sender)) {
+        if (waiting_to_close.remove((ComposerWidget) sender)) {
             // If we just removed the last window in the waiting to close list, it's time to exit!
             if (waiting_to_close.size == 0)
                 GearyApplication.instance.exit();
@@ -1857,11 +1854,11 @@ public class GearyController : Geary.BaseObject {
     }
     
     private void on_new_message() {
-        create_compose_window(ComposerWindow.ComposeType.NEW_MESSAGE);
+        create_compose_widget(ComposerWidget.ComposeType.NEW_MESSAGE);
     }
     
     private void on_reply_to_message(Geary.Email message) {
-        create_compose_window(ComposerWindow.ComposeType.REPLY, message);
+        create_compose_widget(ComposerWidget.ComposeType.REPLY, message);
     }
     
     private void on_reply_to_message_action() {
@@ -1871,7 +1868,7 @@ public class GearyController : Geary.BaseObject {
     }
     
     private void on_reply_all_message(Geary.Email message) {
-        create_compose_window(ComposerWindow.ComposeType.REPLY_ALL, message);
+        create_compose_widget(ComposerWidget.ComposeType.REPLY_ALL, message);
     }
     
     private void on_reply_all_message_action() {
@@ -1881,7 +1878,7 @@ public class GearyController : Geary.BaseObject {
     }
     
     private void on_forward_message(Geary.Email message) {
-        create_compose_window(ComposerWindow.ComposeType.FORWARD, message);
+        create_compose_widget(ComposerWidget.ComposeType.FORWARD, message);
     }
     
     private void on_forward_message_action() {
@@ -2130,12 +2127,12 @@ public class GearyController : Geary.BaseObject {
             return;
         }
         
-        create_compose_window(ComposerWindow.ComposeType.NEW_MESSAGE, null, mailto);
+        create_compose_widget(ComposerWidget.ComposeType.NEW_MESSAGE, null, mailto);
     }
     
     // Returns a list of composer windows for an account, or null if none.
-    public Gee.List<ComposerWindow>? get_composer_windows_for_account(Geary.AccountInformation account) {
-        Gee.LinkedList<ComposerWindow> ret = Geary.traverse<ComposerWindow>(composer_windows)
+    public Gee.List<ComposerWidget>? get_composer_widgets_for_account(Geary.AccountInformation account) {
+        Gee.LinkedList<ComposerWidget> ret = Geary.traverse<ComposerWidget>(composer_widgets)
             .filter(w => w.account.information == account)
             .to_linked_list();
         
diff --git a/src/client/composer/composer-container.vala b/src/client/composer/composer-container.vala
new file mode 100644
index 0000000..966bd25
--- /dev/null
+++ b/src/client/composer/composer-container.vala
@@ -0,0 +1,13 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+public interface ComposerContainer {
+    public abstract Gtk.Window top_window { get; }
+    
+    public abstract void present();
+    public abstract unowned Gtk.Widget get_focus();
+    public abstract void close();
+}
diff --git a/src/client/composer/composer-toolbar.vala b/src/client/composer/composer-toolbar.vala
index c389972..8896e08 100644
--- a/src/client/composer/composer-toolbar.vala
+++ b/src/client/composer/composer-toolbar.vala
@@ -11,27 +11,27 @@ public class ComposerToolbar : PillToolbar {
         Gee.List<Gtk.Button> insert = new Gee.ArrayList<Gtk.Button>();
         
         // Font formatting.
-        insert.add(create_toggle_button(null, ComposerWindow.ACTION_BOLD));
-        insert.add(create_toggle_button(null, ComposerWindow.ACTION_ITALIC));
-        insert.add(create_toggle_button(null, ComposerWindow.ACTION_UNDERLINE));
-        insert.add(create_toggle_button(null, ComposerWindow.ACTION_STRIKETHROUGH));
+        insert.add(create_toggle_button(null, ComposerWidget.ACTION_BOLD));
+        insert.add(create_toggle_button(null, ComposerWidget.ACTION_ITALIC));
+        insert.add(create_toggle_button(null, ComposerWidget.ACTION_UNDERLINE));
+        insert.add(create_toggle_button(null, ComposerWidget.ACTION_STRIKETHROUGH));
         Gtk.ToolItem font_format_item = create_pill_buttons(insert, false, true);
         add(font_format_item);
         
         // Indent level.
         insert.clear();
-        insert.add(create_toolbar_button(null, ComposerWindow.ACTION_INDENT));
-        insert.add(create_toolbar_button(null, ComposerWindow.ACTION_OUTDENT));
+        insert.add(create_toolbar_button(null, ComposerWidget.ACTION_INDENT));
+        insert.add(create_toolbar_button(null, ComposerWidget.ACTION_OUTDENT));
         add(create_pill_buttons(insert, false));
         
         // Link.
         insert.clear();
-        insert.add(create_toolbar_button(null, ComposerWindow.ACTION_INSERT_LINK));
+        insert.add(create_toolbar_button(null, ComposerWidget.ACTION_INSERT_LINK));
         add(create_pill_buttons(insert));
         
         // Remove formatting.
         insert.clear();
-        insert.add(create_toolbar_button(null, ComposerWindow.ACTION_REMOVE_FORMAT));
+        insert.add(create_toolbar_button(null, ComposerWidget.ACTION_REMOVE_FORMAT));
         add(create_pill_buttons(insert));
         
         // Spacer.
@@ -39,7 +39,7 @@ public class ComposerToolbar : PillToolbar {
         
         // Menu.
         insert.clear();
-        insert.add(create_menu_button(null, menu, ComposerWindow.ACTION_MENU));
+        insert.add(create_menu_button(null, menu, ComposerWidget.ACTION_MENU));
         add(create_pill_buttons(insert));
     }
 }
diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala
index 85efee8..580ec06 100644
--- a/src/client/composer/composer-widget.vala
+++ b/src/client/composer/composer-widget.vala
@@ -4,8 +4,8 @@
  * (version 2.1 or later).  See the COPYING file in this distribution.
  */
 
-// Window for sending messages.
-public class ComposerWindow : Gtk.Window {
+// Widget for sending messages.
+public class ComposerWidget : Gtk.EventBox {
     public enum ComposeType {
         NEW_MESSAGE,
         REPLY,
@@ -37,7 +37,6 @@ public class ComposerWindow : Gtk.Window {
     public const string ACTION_COMPOSE_AS_HTML = "compose as html";
     public const string ACTION_CLOSE = "close";
     
-    private const string DEFAULT_TITLE = _("New Message");
     private const string DRAFT_SAVED_TEXT = _("Saved");
     private const string DRAFT_SAVING_TEXT = _("Saving");
     private const string DRAFT_ERROR_TEXT = _("Error saving");
@@ -147,7 +146,7 @@ public class ComposerWindow : Gtk.Window {
     private EmailEntry to_entry;
     private EmailEntry cc_entry;
     private EmailEntry bcc_entry;
-    private Gtk.Entry subject_entry;
+    public Gtk.Entry subject_entry;
     private Gtk.Button close_button;
     private Gtk.Button send_button;
     private Gtk.Label message_overlay_label;
@@ -189,10 +188,13 @@ public class ComposerWindow : Gtk.Window {
     // We need to keep a reference to the edit-fixer in composer-window, so it doesn't get
     // garbage-collected.
     private WebViewEditFixer edit_fixer;
-    private Gtk.UIManager ui;
+    public Gtk.UIManager ui;
+    private ComposerContainer container {
+        get { return (ComposerContainer) parent; }
+    }
     
-    public ComposerWindow(Geary.Account account, ComposeType compose_type,
-        Geary.Email? referred = null, bool is_referred_draft = false) {
+    public ComposerWidget(Geary.Account account, ComposeType compose_type,
+        Geary.Email? referred = null, bool is_referred_draft = false, bool in_window = true) {
         this.account = account;
         this.compose_type = compose_type;
         
@@ -266,7 +268,6 @@ public class ComposerWindow : Gtk.Window {
         message_overlay_label.valign = Gtk.Align.END;
         message_overlay.add_overlay(message_overlay_label);
         
-        title = DEFAULT_TITLE;
         subject_entry.changed.connect(on_subject_changed);
         to_entry.changed.connect(validate_send_button);
         cc_entry.changed.connect(validate_send_button);
@@ -316,7 +317,6 @@ public class ComposerWindow : Gtk.Window {
         
         ui = new Gtk.UIManager();
         ui.insert_action_group(actions, 0);
-        add_accel_group(ui.get_accel_group());
         GearyApplication.instance.load_ui_file_for_manager(ui, "composer_accelerators.ui");
         
         add_extra_accelerators();
@@ -460,9 +460,12 @@ public class ComposerWindow : Gtk.Window {
         // the drafts folder will be opened by on_from_changed().
         if (!from_multiple.visible)
             open_drafts_folder_async.begin(cancellable_drafts);
+        
+        if (in_window)
+            new ComposerWindow(this);
     }
     
-    public ComposerWindow.from_mailto(Geary.Account account, string mailto) {
+    public ComposerWidget.from_mailto(Geary.Account account, string mailto) {
         this(account, ComposeType.NEW_MESSAGE);
         
         Gee.HashMultiMap<string, string> headers = new Gee.HashMultiMap<string, string>();
@@ -662,7 +665,6 @@ public class ComposerWindow : Gtk.Window {
     }
     
     public override void show_all() {
-        set_default_size(680, 600);
         base.show_all();
         update_from_field();
     }
@@ -675,18 +677,18 @@ public class ComposerWindow : Gtk.Window {
     public bool should_close() {
         bool try_to_save = can_save();
         
-        present();
+        container.present();
         AlertDialog dialog;
         
         if (drafts_folder == null && try_to_save) {
-            dialog = new ConfirmationDialog(this,
+            dialog = new ConfirmationDialog(container.top_window,
                 _("Do you want to discard the unsaved message?"), null, Stock._DISCARD);
         } else if (try_to_save) {
-            dialog = new TernaryConfirmationDialog(this,
+            dialog = new TernaryConfirmationDialog(container.top_window,
                 _("Do you want to discard this message?"), null, Stock._KEEP, Stock._DISCARD,
                 Gtk.ResponseType.CLOSE);
         } else {
-            dialog = new ConfirmationDialog(this,
+            dialog = new ConfirmationDialog(container.top_window,
                 _("Do you want to discard this message?"), null, Stock._DISCARD);
         }
         
@@ -776,7 +778,7 @@ public class ComposerWindow : Gtk.Window {
             confirmation = _("Send message without an attachment?");
         }
         if (confirmation != null) {
-            ConfirmationDialog dialog = new ConfirmationDialog(this,
+            ConfirmationDialog dialog = new ConfirmationDialog(container.top_window,
                 confirmation, null, Stock._OK);
             if (dialog.run() != Gtk.ResponseType.OK)
                 return false;
@@ -943,7 +945,7 @@ public class ComposerWindow : Gtk.Window {
             // ComposerWindow, but as a GtkBin subclass a ComposerWindow can
             // only contain one widget at a time;
             // it already contains a widget of type GtkBox
-            dialog = new AttachmentDialog(this);
+            dialog = new AttachmentDialog(container.top_window);
         } while (!dialog.is_finished(add_attachment));
     }
     
@@ -964,7 +966,7 @@ public class ComposerWindow : Gtk.Window {
     }
     
     private void attachment_failed(string msg) {
-        ErrorDialog dialog = new ErrorDialog(this, _("Cannot add attachment"), msg);
+        ErrorDialog dialog = new ErrorDialog(container.top_window, _("Cannot add attachment"), msg);
         dialog.run();
     }
     
@@ -1057,9 +1059,6 @@ public class ComposerWindow : Gtk.Window {
     }
     
     private void on_subject_changed() {
-        title = Geary.String.is_empty(subject_entry.text.strip()) ? DEFAULT_TITLE :
-            subject_entry.text.strip();
-        
         reset_draft_timer();
     }
     
@@ -1086,17 +1085,17 @@ public class ComposerWindow : Gtk.Window {
     }
     
     private void on_cut() {
-        if (get_focus() == editor)
+        if (container.get_focus() == editor)
             editor.cut_clipboard();
-        else if (get_focus() is Gtk.Editable)
-            ((Gtk.Editable) get_focus()).cut_clipboard();
+        else if (container.get_focus() is Gtk.Editable)
+            ((Gtk.Editable) container.get_focus()).cut_clipboard();
     }
     
     private void on_copy() {
-        if (get_focus() == editor)
+        if (container.get_focus() == editor)
             editor.copy_clipboard();
-        else if (get_focus() is Gtk.Editable)
-            ((Gtk.Editable) get_focus()).copy_clipboard();
+        else if (container.get_focus() is Gtk.Editable)
+            ((Gtk.Editable) container.get_focus()).copy_clipboard();
     }
     
     private void on_copy_link() {
@@ -1168,14 +1167,14 @@ public class ComposerWindow : Gtk.Window {
     }
     
     private void on_paste() {
-        if (get_focus() == editor)
+        if (container.get_focus() == editor)
             get_clipboard(Gdk.SELECTION_CLIPBOARD).request_text(on_clipboard_text_received);
-        else if (get_focus() is Gtk.Editable)
-            ((Gtk.Editable) get_focus()).paste_clipboard();
+        else if (container.get_focus() is Gtk.Editable)
+            ((Gtk.Editable) container.get_focus()).paste_clipboard();
     }
     
     private void on_paste_with_formatting() {
-        if (get_focus() == editor)
+        if (container.get_focus() == editor)
             editor.paste_clipboard();
     }
     
@@ -1281,7 +1280,8 @@ public class ComposerWindow : Gtk.Window {
     
     private void on_select_color() {
         if (compose_as_html) {
-            Gtk.ColorChooserDialog dialog = new Gtk.ColorChooserDialog(_("Select Color"), this);
+            Gtk.ColorChooserDialog dialog = new Gtk.ColorChooserDialog(_("Select Color"),
+                container.top_window);
             if (dialog.run() == Gtk.ResponseType.OK)
                 editor.get_dom_document().exec_command("forecolor", false, dialog.get_rgba().to_string());
             
@@ -1327,7 +1327,7 @@ public class ComposerWindow : Gtk.Window {
     }
     
     private static void on_link_clicked(WebKit.DOM.Element element, WebKit.DOM.Event event,
-        ComposerWindow composer) {
+        ComposerWidget composer) {
         try {
             composer.editor.get_dom_document().get_default_view().get_selection().
                 select_all_children(element);
diff --git a/src/client/composer/composer-window.vala b/src/client/composer/composer-window.vala
new file mode 100644
index 0000000..55df8f7
--- /dev/null
+++ b/src/client/composer/composer-window.vala
@@ -0,0 +1,44 @@
+/* Copyright 2011-2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+// Window for sending messages.
+public class ComposerWindow : Gtk.Window, ComposerContainer {
+
+    private const string DEFAULT_TITLE = _("New Message");
+    
+    public ComposerWindow(ComposerWidget composer) {
+        Object(type: Gtk.WindowType.TOPLEVEL);
+        
+        add(composer);
+        composer.subject_entry.changed.connect(() => {
+            title = Geary.String.is_empty(composer.subject_entry.text.strip()) ? DEFAULT_TITLE :
+                composer.subject_entry.text.strip();
+        });
+        composer.subject_entry.changed();
+        
+        add_accel_group(composer.ui.get_accel_group());
+        show_all();
+        set_position(Gtk.WindowPosition.CENTER);
+    }
+    
+    public Gtk.Window top_window {
+        get { return this; }
+    }
+    
+    public override void show_all() {
+        set_default_size(680, 600);
+        base.show_all();
+    }
+    
+    public override bool delete_event(Gdk.EventAny event) {
+        return !((ComposerWidget) get_child()).should_close();
+    }
+    
+    public new void close() {
+        destroy();
+    }
+}
+


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