[evolution] Composer - Make drag and drop usable



commit 1740543a23cc792042eabb09a25bd8878a332eb0
Author: Tomas Popela <tpopela redhat com>
Date:   Tue Feb 10 11:58:36 2015 +0100

    Composer - Make drag and drop usable
    
    Fix the DnD inside the view itself and DnD from outside to composer.
    Comments about some workarounds that had to be used are inside the
    commit.

 composer/e-composer-private.c |    4 +-
 composer/e-composer-private.h |    2 +
 composer/e-msg-composer.c     |  255 +++++++++++++++++++++++++++++------------
 e-util/e-html-editor-view.c   |   47 ++++++++
 4 files changed, 233 insertions(+), 75 deletions(-)
---
diff --git a/composer/e-composer-private.c b/composer/e-composer-private.c
index 4ab817a..771dc4e 100644
--- a/composer/e-composer-private.c
+++ b/composer/e-composer-private.c
@@ -135,7 +135,9 @@ e_composer_private_constructed (EMsgComposer *composer)
        priv->set_signature_from_message = FALSE;
        priv->disable_signature = FALSE;
        priv->busy = FALSE;
-       priv->saved_editable= FALSE;
+       priv->saved_editable = FALSE;
+       priv->drop_occured = FALSE;
+       priv->remove_inserted_uri_on_drop = FALSE;
 
        priv->focused_entry = NULL;
 
diff --git a/composer/e-composer-private.h b/composer/e-composer-private.h
index 5cc09e3..69ebd90 100644
--- a/composer/e-composer-private.h
+++ b/composer/e-composer-private.h
@@ -100,6 +100,8 @@ struct _EMsgComposerPrivate {
         * This is used to restore the previous editable state. */
        gboolean saved_editable;
        gboolean set_signature_from_message;
+       gboolean drop_occured;
+       gboolean remove_inserted_uri_on_drop;
 
        gint focused_entry_selection_start;
        gint focused_entry_selection_end;
diff --git a/composer/e-msg-composer.c b/composer/e-msg-composer.c
index 72bc687..99e12ad 100644
--- a/composer/e-msg-composer.c
+++ b/composer/e-msg-composer.c
@@ -1743,14 +1743,24 @@ msg_composer_drag_motion_cb (GtkWidget *widget,
                              guint time,
                              EMsgComposer *composer)
 {
-       EAttachmentView *view;
+       GtkWidget *source_widget;
+       EHTMLEditor *editor = e_msg_composer_get_editor (composer);
+       EHTMLEditorView *editor_view = e_html_editor_get_view (editor);
 
-       view = e_msg_composer_get_attachment_view (composer);
+       source_widget = gtk_drag_get_source_widget (context);
+       /* When we are doind DnD just inside the web view, the DnD is supposed
+        * to move things around. */
+       if (E_IS_HTML_EDITOR_VIEW (source_widget)) {
+               if ((gpointer) editor_view == (gpointer) source_widget) {
+                       gdk_drag_status (context, GDK_ACTION_MOVE, time);
+
+                       return FALSE;
+               }
+       }
 
-       /* Stop the signal from propagating to GtkHtml. */
-       g_signal_stop_emission_by_name (widget, "drag-motion");
+       gdk_drag_status (context, GDK_ACTION_COPY, time);
 
-       return e_attachment_view_drag_motion (view, context, x, y, time);
+       return FALSE;
 }
 
 static gchar *
@@ -1778,6 +1788,80 @@ next_uri (guchar **uri_list,
        return (gchar *) uri;
 }
 
+static gboolean
+msg_composer_drag_drop_cb (GtkWidget *widget,
+                           GdkDragContext *context,
+                           gint x,
+                           gint y,
+                           guint time,
+                           EMsgComposer *composer)
+{
+       GdkAtom target;
+       GtkWidget *source_widget;
+
+       /* When we are doind DnD just inside the web view, the DnD is supposed
+        * to move things around. */
+       source_widget = gtk_drag_get_source_widget (context);
+       if (E_IS_HTML_EDITOR_VIEW (source_widget)) {
+               EHTMLEditor *editor = e_msg_composer_get_editor (composer);
+               EHTMLEditorView *editor_view = e_html_editor_get_view (editor);
+
+               if ((gpointer) editor_view == (gpointer) source_widget)
+                       return FALSE;
+       }
+
+       target = gtk_drag_dest_find_target (widget, context, NULL);
+       if (target == GDK_NONE)
+               gdk_drag_status (context, 0, time);
+       else {
+               gdk_drag_status (context, GDK_ACTION_COPY, time);
+               composer->priv->drop_occured = TRUE;
+               gtk_drag_get_data (widget, context, target, time);
+       }
+
+       return FALSE;
+}
+
+static void
+msg_composer_drag_data_received_after_cb (GtkWidget *widget,
+                                          GdkDragContext *context,
+                                          gint x,
+                                          gint y,
+                                          GtkSelectionData *selection,
+                                          guint info,
+                                          guint time,
+                                          EMsgComposer *composer)
+{
+       EHTMLEditor *editor;
+       EHTMLEditorView *view;
+       WebKitDOMDocument *document;
+       WebKitDOMDOMWindow *dom_window;
+       WebKitDOMDOMSelection *dom_selection;
+
+       editor = e_msg_composer_get_editor (composer);
+       view = e_html_editor_get_view (editor);
+
+       document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+       dom_window = webkit_dom_document_get_default_view (document);
+       dom_selection = webkit_dom_dom_window_get_selection (dom_window);
+
+       /* FIXME When the user drops something inside the view and it is
+        * added as an EAttachment, WebKit still inserts the URI of the
+        * resource into the view. Let's delete it as it is selected. */
+       if (composer->priv->remove_inserted_uri_on_drop)
+               webkit_dom_dom_selection_delete_from_document (dom_selection);
+       else {
+               /* When text is DnD'ed into the view, WebKit will select it. So let's
+                * collapse it to its end to have the caret after the DnD'ed text. */
+               webkit_dom_dom_selection_collapse_to_end (dom_selection, NULL);
+       }
+
+       webkit_dom_dom_selection_collapse_to_end (dom_selection, NULL);
+       e_html_editor_view_check_magic_links (view, FALSE);
+       /* Also force spell check on view. */
+       e_html_editor_view_force_spell_check (view);
+}
+
 static void
 msg_composer_drag_data_received_cb (GtkWidget *widget,
                                     GdkDragContext *context,
@@ -1788,44 +1872,60 @@ msg_composer_drag_data_received_cb (GtkWidget *widget,
                                     guint time,
                                     EMsgComposer *composer)
 {
-       GdkAtom atom;
-       gchar *name;
-       EAttachmentView *view;
        EHTMLEditor *editor;
        EHTMLEditorView *html_editor_view;
        EHTMLEditorSelection *editor_selection;
+       gboolean html_mode;
+       GtkWidget *source_widget;
 
        editor = e_msg_composer_get_editor (composer);
        html_editor_view = e_html_editor_get_view (editor);
+       html_mode = e_html_editor_view_get_html_mode (html_editor_view);
        editor_selection = e_html_editor_view_get_selection (html_editor_view);
 
-       atom = gtk_selection_data_get_target (selection);
-       name = gdk_atom_name (atom);
+       if (!composer->priv->drop_occured)
+               return;
+
+       composer->priv->remove_inserted_uri_on_drop = FALSE;
+       composer->priv->drop_occured = FALSE;
+
+       /* When we are doind DnD just inside the web view, the DnD is supposed
+        * to move things around. */
+       source_widget = gtk_drag_get_source_widget (context);
+       if (E_IS_HTML_EDITOR_VIEW (source_widget)) {
+               EHTMLEditor *editor = e_msg_composer_get_editor (composer);
+               EHTMLEditorView *editor_view = e_html_editor_get_view (editor);
 
-       if (g_strcmp0 (name, "UTF8_STRING") == 0 || g_strcmp0 (name, "text/html") == 0) {
-               gboolean is_text;
+               if ((gpointer) editor_view == (gpointer) source_widget)
+                       return;
+       }
+
+       /* Leave the text on WebKit to handle it. */
+       if (info == DND_TARGET_TYPE_UTF8_STRING ||
+           info == DND_TARGET_TYPE_STRING ||
+           info == DND_TARGET_TYPE_TEXT_PLAIN) {
+               gdk_drag_status (context, 0, time);
+               return;
+       }
+
+       if (info == DND_TARGET_TYPE_TEXT_HTML) {
                const guchar *data;
                gint length;
                gint list_len, len;
                gchar *text;
 
-               is_text = g_strcmp0 (name, "UTF8_STRING") == 0;
-
                data = gtk_selection_data_get_data (selection);
                length = gtk_selection_data_get_length (selection);
 
                if (!data || length < 0) {
-                       g_free (name);
+                       gtk_drag_finish (context, FALSE, FALSE, time);
                        return;
                }
 
                list_len = length;
                do {
                        text = next_uri ((guchar **) &data, &len, &list_len);
-                       if (is_text)
-                               e_html_editor_selection_insert_text (editor_selection, text);
-                       else
-                               e_html_editor_selection_insert_html (editor_selection, text);
+                       e_html_editor_selection_insert_html (editor_selection, text);
                } while (list_len);
 
                e_html_editor_view_check_magic_links (html_editor_view, FALSE);
@@ -1833,66 +1933,66 @@ msg_composer_drag_data_received_cb (GtkWidget *widget,
 
                e_html_editor_selection_scroll_to_caret (editor_selection);
 
-               /* Stop the signal from propagating */
-               g_signal_stop_emission_by_name (widget, "drag-data-received");
-               g_free (name);
+               gtk_drag_finish (context, TRUE, FALSE, time);
                return;
        }
 
-       g_free (name);
-
        /* HTML mode has a few special cases for drops... */
-       if (e_html_editor_view_get_html_mode (html_editor_view)) {
-               /* If we're receiving an image, we want the image to be
-                * inserted in the message body.  Let GtkHtml handle it. */
-               /* FIXME WebKit - how to reproduce this?
-               if (gtk_selection_data_targets_include_image (selection, TRUE))
+       /* If we're receiving URIs and -all- the URIs point to
+        * image files, we want the image(s) to be inserted in
+        * the message body. */
+       if (html_mode && e_composer_selection_is_image_uris (composer, selection)) {
+               const guchar *data;
+               gint length;
+               gint list_len, len;
+               gchar *uri;
+
+               data = gtk_selection_data_get_data (selection);
+               length = gtk_selection_data_get_length (selection);
+
+               if (!data || length < 0) {
+                       gtk_drag_finish (context, FALSE, FALSE, time);
                        return;
-                */
-               /* If we're receiving URIs and -all- the URIs point to
-                * image files, we want the image(s) to be inserted in
-                * the message body. */
-               if (e_composer_selection_is_image_uris (composer, selection)) {
-                       const guchar *data;
-                       gint length;
-                       gint list_len, len;
-                       gchar *uri;
-
-                       data = gtk_selection_data_get_data (selection);
-                       length = gtk_selection_data_get_length (selection);
-
-                       if (!data || length < 0)
-                               return;
-
-                       list_len = length;
-                       do {
-                               uri = next_uri ((guchar **) &data, &len, &list_len);
-                               e_html_editor_selection_insert_image (editor_selection, uri);
-                       } while (list_len);
                }
 
-               if (e_composer_selection_is_base64_uris (composer, selection)) {
-                       const guchar *data;
-                       gint length;
-                       gint list_len, len;
-                       gchar *uri;
-
-                       data = gtk_selection_data_get_data (selection);
-                       length = gtk_selection_data_get_length (selection);
+               list_len = length;
+               do {
+                       uri = next_uri ((guchar **) &data, &len, &list_len);
+                       e_html_editor_selection_insert_image (editor_selection, uri);
+               } while (list_len);
 
-                       if (!data || length < 0)
-                               return;
+               gtk_drag_finish (context, TRUE, FALSE, time);
+       } else if (html_mode && e_composer_selection_is_base64_uris (composer, selection)) {
+               const guchar *data;
+               gint length;
+               gint list_len, len;
+               gchar *uri;
 
-                       list_len = length;
-                       do {
-                               uri = next_uri ((guchar **) &data, &len, &list_len);
+               data = gtk_selection_data_get_data (selection);
+               length = gtk_selection_data_get_length (selection);
 
-                               e_html_editor_selection_insert_image (editor_selection, uri);
-                       } while (list_len);
+               if (!data || length < 0) {
+                       gtk_drag_finish (context, FALSE, FALSE, time);
+                       return;
                }
-       } else {
-               view = e_msg_composer_get_attachment_view (composer);
 
+               list_len = length;
+               do {
+                       uri = next_uri ((guchar **) &data, &len, &list_len);
+
+                       e_html_editor_selection_insert_image (editor_selection, uri);
+               } while (list_len);
+
+               gtk_drag_finish (context, TRUE, FALSE, time);
+       } else {
+               EAttachmentView *view = e_msg_composer_get_attachment_view (composer);
+
+               /* FIXME When the user drops something inside the view and it is
+                * added as an EAttachment, WebKit still inserts the URI of the
+                * resource into the view. Now we are deleting it in
+                * msg_composer_drag_data_received_after_cb, but there has to be
+                * a way how to tell the WebKit to not process this drop. */
+               composer->priv->remove_inserted_uri_on_drop = TRUE;
                /* Forward the data to the attachment view.  Note that calling
                 * e_attachment_view_drag_data_received() will not work because
                 * that function only handles the case where all the other drag
@@ -1901,8 +2001,6 @@ msg_composer_drag_data_received_cb (GtkWidget *widget,
                        E_ATTACHMENT_PANED (view),
                        context, x, y, selection, info, time);
        }
-       /* Stop the signal from propagating */
-       g_signal_stop_emission_by_name (widget, "drag-data-received");
 }
 
 static void
@@ -2205,9 +2303,18 @@ msg_composer_constructed (GObject *object)
                G_CALLBACK (msg_composer_drag_motion_cb), composer);
 
        g_signal_connect (
+               html_editor_view, "drag-drop",
+               G_CALLBACK (msg_composer_drag_drop_cb), composer);
+
+       g_signal_connect (
                html_editor_view, "drag-data-received",
                G_CALLBACK (msg_composer_drag_data_received_cb), composer);
 
+       /* Used for fixing various stuff after WebKit processed the DnD data. */
+       g_signal_connect_after (
+               html_editor_view, "drag-data-received",
+               G_CALLBACK (msg_composer_drag_data_received_after_cb), composer);
+
        g_signal_connect (
                composer->priv->gallery_icon_view, "drag-data-get",
                G_CALLBACK (msg_composer_gallery_drag_data_get), NULL);
@@ -2256,11 +2363,11 @@ msg_composer_constructed (GObject *object)
        /* Initialization may have tripped the "changed" state. */
        e_html_editor_view_set_changed (html_editor_view, FALSE);
 
-       gtk_drag_dest_set (
-               GTK_WIDGET (html_editor_view),
-               GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
-               drag_dest_targets, G_N_ELEMENTS (drag_dest_targets),
-               GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
+       gtk_target_list_add_table (
+               gtk_drag_dest_get_target_list (
+                       GTK_WIDGET (html_editor_view)),
+               drag_dest_targets,
+               G_N_ELEMENTS (drag_dest_targets));
 
        id = "org.gnome.evolution.composer";
        e_plugin_ui_register_manager (ui_manager, id, composer);
diff --git a/e-util/e-html-editor-view.c b/e-util/e-html-editor-view.c
index 5f30156..9a7c984 100644
--- a/e-util/e-html-editor-view.c
+++ b/e-util/e-html-editor-view.c
@@ -7500,6 +7500,50 @@ e_html_editor_view_set_html_mode (EHTMLEditorView *view,
 }
 
 static void
+html_editor_view_drag_end_cb (EHTMLEditorView *view,
+                              GdkDragContext *context)
+{
+       gint ii, length;
+       WebKitDOMDocument *document;
+       WebKitDOMDOMWindow *window;
+       WebKitDOMDOMSelection *selection;
+       WebKitDOMNodeList *list;
+
+       document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+
+       /* When the image is DnD inside the view WebKit removes the wrapper that
+        * is used for resizing the image, so we have to recreate it again. */
+       list = webkit_dom_document_query_selector_all (document, ":not(span) > img[data-inline]", NULL);
+       length = webkit_dom_node_list_get_length (list);
+       for (ii = 0; ii < length; ii++) {
+               WebKitDOMElement *element;
+               WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
+
+               element = webkit_dom_document_create_element (document, "span", NULL);
+               webkit_dom_element_set_class_name (element, "-x-evo-resizable-wrapper");
+
+               webkit_dom_node_insert_before (
+                       webkit_dom_node_get_parent_node (node),
+                       WEBKIT_DOM_NODE (element),
+                       node,
+                       NULL);
+
+               webkit_dom_node_append_child (WEBKIT_DOM_NODE (element), node, NULL);
+       }
+
+       /* When the image is moved the new selection is created after after it, so
+        * lets collapse the selection to have the caret right after the image. */
+       window = webkit_dom_document_get_default_view (document);
+       selection = webkit_dom_dom_window_get_selection (window);
+       if (length > 0)
+               webkit_dom_dom_selection_collapse_to_start (selection, NULL);
+       else
+               webkit_dom_dom_selection_collapse_to_end (selection, NULL);
+
+       e_html_editor_view_force_spell_check (view);
+}
+
+static void
 e_html_editor_view_init (EHTMLEditorView *view)
 {
        WebKitWebSettings *settings;
@@ -7541,6 +7585,9 @@ e_html_editor_view_init (EHTMLEditorView *view)
                view, E_HTML_EDITOR_VIEW_COMMAND_STYLE_WITH_CSS, "false");
 
        g_signal_connect (
+               view, "drag-end",
+               G_CALLBACK (html_editor_view_drag_end_cb), NULL);
+       g_signal_connect (
                view, "user-changed-contents",
                G_CALLBACK (html_editor_view_user_changed_contents_cb), NULL);
        g_signal_connect (


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