[geary/bug/728002-webkit2: 127/140] Stop the insert link popover being dismissed when selecting text.



commit 5163b5198dce71f7dd7ac8e35a4f387311b743b7
Author: Michael James Gratton <mike vee net>
Date:   Mon Jan 30 00:23:49 2017 +1100

    Stop the insert link popover being dismissed when selecting text.
    
    * src/client/composer/composer-web-view.vala (ComposerWebView): Add
      ::save_selection and ::free_selection to allow the selection to be
      saved when inserting a link. Thunk calls to JS.
      (ComposerWebView::insert_link): Add selection id param, pass through to
      JS.
    
    * src/client/composer/composer-widget.vala
      (ComposerWidget::new_link_popover): Manage saving the editor's
      selection, passing its id when inserting a link, freeing it
      again. Convert into an async method so we can wait for the selection id
      to get back from the WebProcess, update call sites.
      (ComposerWidget::on_insert_link): Disconnect and reconnect selection
      changed signal so the popover isn't dismissed when the selection does
      change/
    
    * ui/composer-web-view.js (ComposerPageState): Implement new
      ::saveSelection, ::freeSelection and selectionId param on ::insertLink.

 src/client/composer/composer-web-view.vala |   26 ++++++++++-
 src/client/composer/composer-widget.vala   |   65 ++++++++++++++++++++-------
 ui/composer-web-view.js                    |   26 ++++++------
 3 files changed, 85 insertions(+), 32 deletions(-)
---
diff --git a/src/client/composer/composer-web-view.vala b/src/client/composer/composer-web-view.vala
index d76479c..f86b0e4 100644
--- a/src/client/composer/composer-web-view.vala
+++ b/src/client/composer/composer-web-view.vala
@@ -253,6 +253,25 @@ public class ComposerWebView : ClientWebView {
     }
 
     /**
+     * Saves the current text selection so it can be restored later.
+     *
+     * Returns an id to be used to refer to the selection in
+     * subsequent calls.
+     */
+    public async string save_selection() throws Error {
+        return WebKitUtil.to_string(
+            yield call(Geary.JS.callable("geary.saveSelection"), null)
+        );
+    }
+
+    /**
+     * Removes a saved selection.
+     */
+    public void free_selection(string id) {
+        this.call.begin(Geary.JS.callable("geary.freeSelection").string(id), null);
+    }
+
+    /**
      * Cuts selected content and sends it to the clipboard.
      */
     public void cut_clipboard() {
@@ -354,8 +373,11 @@ public class ComposerWebView : ClientWebView {
      * will be updated, else if some text is selected, an A element
      * will be inserted wrapping the selection.
      */
-    public void insert_link(string href) {
-        this.call.begin(Geary.JS.callable("geary.insertLink").string(href), null);
+    public void insert_link(string href, string selection_id) {
+        this.call.begin(
+            Geary.JS.callable("geary.insertLink").string(href).string(selection_id),
+            null
+        );
     }
 
     /**
diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala
index 215fc07..94c4c50 100644
--- a/src/client/composer/composer-widget.vala
+++ b/src/client/composer/composer-widget.vala
@@ -503,7 +503,7 @@ public class ComposerWidget : Gtk.EventBox {
         this.editor.key_press_event.connect(on_editor_key_press_event);
         this.editor.load_changed.connect(on_load_changed);
         this.editor.mouse_target_changed.connect(on_mouse_target_changed);
-        this.editor.selection_changed.connect((has_selection) => { update_cursor_actions(); });
+        this.editor.selection_changed.connect(on_selection_changed);
 
         // Place the message area before the compose toolbar in the focus chain, so that
         // the user can tab directly from the Subject: field to the message area.
@@ -2098,15 +2098,22 @@ public class ComposerWidget : Gtk.EventBox {
         return account_sig;
     }
 
-    private ComposerLinkPopover new_link_popover(ComposerLinkPopover.Type type,
-                                                 string url) {
+    private async ComposerLinkPopover new_link_popover(ComposerLinkPopover.Type type,
+                                                       string url) {
+        var selection_id = "";
+        try {
+            selection_id = yield this.editor.save_selection();
+        } catch (Error err) {
+            debug("Error saving selection: %s", err.message);
+        }
         ComposerLinkPopover popover = new ComposerLinkPopover(type);
         popover.set_link_url(url);
-        popover.hide.connect(() => {
+        popover.closed.connect(() => {
+                this.editor.free_selection(selection_id);
                 Idle.add(() => { popover.destroy(); return Source.REMOVE; });
             });
         popover.link_activate.connect((link_uri) => {
-                this.editor.insert_link(popover.link_uri);
+                this.editor.insert_link(popover.link_uri, selection_id);
             });
         popover.link_delete.connect(() => {
                 this.editor.delete_link();
@@ -2161,13 +2168,14 @@ public class ComposerWidget : Gtk.EventBox {
             location.x = (int) button.x;
             location.y = (int) button.y;
 
-            ComposerLinkPopover popover = new_link_popover(
-                ComposerLinkPopover.Type.EXISTING_LINK,
-                this.pointer_url
-            );
-            popover.set_relative_to(this.editor);
-            popover.set_pointing_to(location);
-            popover.show();
+            this.new_link_popover.begin(
+                ComposerLinkPopover.Type.EXISTING_LINK, this.pointer_url,
+                (obj, res) => {
+                    ComposerLinkPopover popover = this.new_link_popover.end(res);
+                    popover.set_relative_to(this.editor);
+                    popover.set_pointing_to(location);
+                    popover.show();
+                });
         }
         return Gdk.EVENT_PROPAGATE;
     }
@@ -2253,15 +2261,38 @@ public class ComposerWidget : Gtk.EventBox {
     }
 
     private void on_insert_link(SimpleAction action, Variant? param) {
-        ComposerLinkPopover popover = this.cursor_url == null
-            ? new_link_popover(ComposerLinkPopover.Type.NEW_LINK, "http://";)
-            : new_link_popover(ComposerLinkPopover.Type.EXISTING_LINK, this.cursor_url);
-        popover.set_relative_to(this.insert_link_button);
-        popover.show();
+        ComposerLinkPopover.Type type = ComposerLinkPopover.Type.NEW_LINK;
+        string url = "http://";;
+        if (this.cursor_url != null) {
+            type = ComposerLinkPopover.Type.EXISTING_LINK;
+            url = this.cursor_url;
+        }
+
+        this.new_link_popover.begin(type, url, (obj, res) => {
+                ComposerLinkPopover popover = this.new_link_popover.end(res);
+
+                // We have to disconnect then reconnect the selection
+                // changed signal for the duration of the popover
+                // being active since if the user selects the text in
+                // the URL entry, then the editor will lose its
+                // selection, the inset link action will become
+                // disabled, and the popover will disappear
+                this.editor.selection_changed.disconnect(on_selection_changed);
+                popover.closed.connect(() => {
+                        this.editor.selection_changed.connect(on_selection_changed);
+                    });
+
+                popover.set_relative_to(this.insert_link_button);
+                popover.show();
+            });
     }
 
     private void on_open_inspector(SimpleAction action, Variant? param) {
         this.editor.get_inspector().show();
     }
 
+    private void on_selection_changed(bool has_selection) {
+        update_cursor_actions();
+    }
+
 }
diff --git a/ui/composer-web-view.js b/ui/composer-web-view.js
index 7c7b612..db75ca1 100644
--- a/ui/composer-web-view.js
+++ b/ui/composer-web-view.js
@@ -26,12 +26,11 @@ ComposerPageState.prototype = {
     __proto__: PageState.prototype,
     init: function() {
         PageState.prototype.init.apply(this, []);
-
         this.messageBody = null;
-
         this.undoEnabled = false;
         this.redoEnabled = false;
-
+        this.selections = new Map();
+        this.nextSelectionId = 0;
         this.cursorContext = null;
 
         let state = this;
@@ -138,21 +137,22 @@ ComposerPageState.prototype = {
         document.execCommand("redo", false, null);
         this.checkCommandStack();
     },
-    insertLink: function(href) {
+    saveSelection: function() {
+        let id = ++this.nextSelectionId.toString();
+        this.selections.set(id, SelectionUtil.save());
+        return id;
+    },
+    freeSelection: function(id) {
+        this.selections.delete(id);
+    },
+    insertLink: function(href, selectionId) {
         if (!window.getSelection().isCollapsed) {
             // There is currently a selection, so assume the user
             // knows what they are doing and just linkify it.
             document.execCommand("createLink", false, href);
         } else {
-            let selected = SelectionUtil.getCursorElement();
-            if (selected != null && selected.tagName == "A") {
-                // The current cursor element is an A, so select it
-                // since createLink requires a range
-                let selection = SelectionUtil.save();
-                SelectionUtil.selectNode(selected);
-                document.execCommand("createLink", false, href);
-                SelectionUtil.restore(selection);
-            }
+            SelectionUtil.restore(this.selections.get(selectionId));
+            document.execCommand("createLink", false, href);
         }
     },
     deleteLink: function() {


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