[geary/wip/765516-gtk-widget-conversation-viewer: 86/101] Re-enable replying to and quoting the selected message, if any.



commit 1f6388ee72337a6b0627ef880fc7542944c280fc
Author: Michael James Gratton <mike vee net>
Date:   Mon Jul 4 22:31:47 2016 +1000

    Re-enable replying to and quoting the selected message, if any.

 src/client/application/geary-controller.vala       |   16 ++++----
 .../conversation-viewer/conversation-email.vala    |   38 ++++++++++++++++-
 .../conversation-viewer/conversation-message.vala  |   42 ++++++++++++++++++++
 .../conversation-viewer/conversation-viewer.vala   |   34 +++++++++-------
 4 files changed, 105 insertions(+), 25 deletions(-)
---
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index c1306a5..c5545b8 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -2228,15 +2228,15 @@ public class GearyController : Geary.BaseObject {
     // was triggered.  If null, this was triggered from the headerbar
     // or shortcut.
     private void create_reply_forward_widget(ComposerWidget.ComposeType compose_type,
-        string? quote;
-        Geary.Email? quote_message = main_window.conversation_viewer.get_selected_email(out quote);
-        if (message == null)
-            message = quote_message;
-        if (quote_message != message)
-            quote = null;
-        create_compose_widget(compose_type, message, quote);
                                              owned ConversationEmail? view) {
-        Geary.Email message = (view != null) ? view.email : null;
+        if (view == null) {
+            view = main_window.conversation_viewer.get_reply_email_view();
+        }
+        string? quote = null;
+        if (view != null) {
+            quote = view.get_body_selection();
+        }
+        create_compose_widget(compose_type, view.email, quote);
     }
 
     private void create_compose_widget(ComposerWidget.ComposeType compose_type,
diff --git a/src/client/conversation-viewer/conversation-email.vala 
b/src/client/conversation-viewer/conversation-email.vala
index 4a3446b..5115a2c 100644
--- a/src/client/conversation-viewer/conversation-email.vala
+++ b/src/client/conversation-viewer/conversation-email.vala
@@ -75,6 +75,9 @@ public class ConversationEmail : Gtk.Box {
     // Contacts for the email's account
     private Geary.ContactStore contact_store;
 
+    // Message view with selected text, if any
+    private ConversationMessage? body_selection_message = null;
+
     // Attachment ids that have been displayed inline
     private Gee.HashSet<string> inlined_content_ids = new Gee.HashSet<string>();
 
@@ -162,6 +165,8 @@ public class ConversationEmail : Gtk.Box {
     /** Fired when the view source action is activated. */
     public signal void view_source();
 
+    /** Fired when the user selects text in a message. */
+    internal signal void body_selection_changed(bool has_selection);
 
     /**
      * Constructs a new view to display an email.
@@ -238,6 +243,9 @@ public class ConversationEmail : Gtk.Box {
         primary_message.web_view.link_selected.connect((link) => {
                 link_activated(link);
             });
+        primary_message.web_view.selection_changed.connect(() => {
+                on_message_selection_changed(primary_message);
+            });
         primary_message.save_image.connect((filename, buffer) => {
                 save_image(filename, buffer);
             });
@@ -275,10 +283,13 @@ public class ConversationEmail : Gtk.Box {
             primary_message.body_box.pack_start(sub_messages_box, false, false, 0);
         }
         foreach (Geary.RFC822.Message sub_message in sub_messages) {
-            ConversationMessage conversation_message =
+            ConversationMessage attached_message =
                 new ConversationMessage(sub_message, contact_store, false);
-            sub_messages_box.pack_start(conversation_message, false, false, 0);
-            this._attached_messages.add(conversation_message);
+                attached_message.web_view.selection_changed.connect(() => {
+                        on_message_selection_changed(attached_message);
+                    });
+            sub_messages_box.pack_start(attached_message, false, false, 0);
+            this._attached_messages.add(attached_message);
         }
 
         pack_start(primary_message, true, true, 0);
@@ -356,6 +367,15 @@ public class ConversationEmail : Gtk.Box {
         get_style_context().add_class("geary_manual_read");
     }
 
+    /**
+     * Returns user-selected body HTML from a message, if any.
+     */
+    public string? get_body_selection() {
+        return (this.body_selection_message != null)
+            ? this.body_selection_message.get_selection_for_quoting()
+            : null;
+    }
+
     private SimpleAction add_action(string name) {
         SimpleAction action = new SimpleAction(name, null);
         message_actions.add_action(action);
@@ -433,6 +453,18 @@ public class ConversationEmail : Gtk.Box {
         contact_store.mark_contacts_async.begin(contact_list, flags, null);
     }
 
+    private void on_message_selection_changed(ConversationMessage view) {
+        bool has_selection = false;
+        if (view.web_view.has_selection()) {
+            WebKit.DOM.Document document = view.web_view.get_dom_document();
+            has_selection = !document.default_view.get_selection().is_collapsed;
+            this.body_selection_message = view;
+        } else {
+            this.body_selection_message = null;
+        }
+        body_selection_changed(has_selection);
+    }
+
     [GtkCallback]
     private void on_attachments_view_activated(Gtk.IconView view, Gtk.TreePath path) {
         AttachmentInfo attachment_info = attachment_info_for_view_path(path);
diff --git a/src/client/conversation-viewer/conversation-message.vala 
b/src/client/conversation-viewer/conversation-message.vala
index 4f923dd..efae476 100644
--- a/src/client/conversation-viewer/conversation-message.vala
+++ b/src/client/conversation-viewer/conversation-message.vala
@@ -413,6 +413,48 @@ public class ConversationMessage : Gtk.Box {
         web_view.unmark_text_matches();
     }
 
+    internal string? get_selection_for_quoting() {
+        string? quote = null;
+        WebKit.DOM.Document document = this.web_view.get_dom_document();
+        WebKit.DOM.DOMSelection selection = document.default_view.get_selection();
+        if (!selection.is_collapsed) {
+            try {
+                WebKit.DOM.Range range = selection.get_range_at(0);
+                WebKit.DOM.HTMLElement dummy =
+                    (WebKit.DOM.HTMLElement) document.create_element("div");
+                bool include_dummy = false;
+                WebKit.DOM.Node ancestor_node = range.get_common_ancestor_container();
+                WebKit.DOM.Element? ancestor = ancestor_node as WebKit.DOM.Element;
+                if (ancestor == null)
+                    ancestor = ancestor_node.get_parent_element();
+                // If the selection is part of a plain text message,
+                // we have to stick it in an appropriately styled div,
+                // so that new lines are preserved.
+                if (Util.DOM.is_descendant_of(ancestor, ".plaintext")) {
+                    dummy.get_class_list().add("plaintext");
+                    dummy.set_attribute("style", "white-space: pre-wrap;");
+                    include_dummy = true;
+                }
+                dummy.append_child(range.clone_contents());
+
+                // Remove the chrome we put around quotes, leaving
+                // only the blockquote element.
+                WebKit.DOM.NodeList quotes =
+                    dummy.query_selector_all(".quote_container");
+                for (int i = 0; i < quotes.length; i++) {
+                    WebKit.DOM.Element div = (WebKit.DOM.Element) quotes.item(i);
+                    WebKit.DOM.Element blockquote = div.query_selector("blockquote");
+                    div.get_parent_element().replace_child(blockquote, div);
+                }
+
+                quote = include_dummy ? dummy.get_outer_html() : dummy.get_inner_html();
+            } catch (Error error) {
+                debug("Problem getting selected text: %s", error.message);
+            }
+        }
+        return quote;
+    }
+
     private SimpleAction add_action(string name, bool enabled) {
         SimpleAction action = new SimpleAction(name, null);
         action.set_enabled(enabled);
diff --git a/src/client/conversation-viewer/conversation-viewer.vala 
b/src/client/conversation-viewer/conversation-viewer.vala
index aa23752..7ace7e0 100644
--- a/src/client/conversation-viewer/conversation-viewer.vala
+++ b/src/client/conversation-viewer/conversation-viewer.vala
@@ -94,7 +94,10 @@ public class ConversationViewer : Gtk.Stack {
     // Conversation emails list
     [GtkChild]
     private Gtk.ListBox conversation_listbox;
-    private Gtk.Widget? last_list_row;
+    private Gtk.Widget? last_list_row = null;
+
+    // Email view with selected text, if any
+    private ConversationEmail? body_selected_view = null;
 
     // Label for displaying messages in the main pane.
     [GtkChild]
@@ -210,20 +213,19 @@ public class ConversationViewer : Gtk.Stack {
     }
 
     /**
-     * Returns the last email in the list by sort order, if any.
+     * Returns the email view to be replied to, if any.
+     *
+     * If an email view has selected body text that view will be
+     * returned. Else the last message by sort order will be returned,
+     * if any.
      */
-    public Geary.Email? get_last_email() {
-        return emails.is_empty ? null : emails.last();
-    }
-
-    /**
-     * Returns the email with text currently selected, if any.
-     */
-    public Geary.Email? get_selected_email(out string? quote) {
-        // XXX check to see if there is a email with selected text,
-        // if so return that
-        quote = null;
-        return emails.is_empty ? null : emails.last();
+    public ConversationEmail get_reply_email_view() {
+        ConversationEmail view = this.body_selected_view;
+        if (view == null) {
+            Geary.Email last_email = emails.is_empty ? null : emails.last();
+            view = conversation_email_for_id(last_email.id);
+        }
+        return view;
     }
 
     /**
@@ -405,6 +407,7 @@ public class ConversationViewer : Gtk.Stack {
         email_to_row.clear();
         emails.clear();
         current_conversation = null;
+        body_selected_view = null;
         cleared();
     }
 
@@ -691,6 +694,9 @@ public class ConversationViewer : Gtk.Stack {
         );
         conversation_email.mark_email.connect(on_mark_email);
         conversation_email.mark_email_from_here.connect(on_mark_email_from_here);
+        conversation_email.body_selection_changed.connect((email, has_selection) => {
+                this.body_selected_view = has_selection ? email : null;
+            });
 
         ConversationMessage conversation_message = conversation_email.primary_message;
         conversation_message.body_box.button_release_event.connect_after((event) => {


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