[evolution/wip/webkit-composer: 806/966] Add support for inline (CID) images



commit e628172b597bb2c4152a38ce73af27fce30b0085
Author: Tomas Popela <tpopela redhat com>
Date:   Thu Feb 6 10:07:04 2014 +0100

    Add support for inline (CID) images
    
    With this change all the inline images inserted into composer
    (emoticons as well) are upon their insertion encoded to base64 and the
    outcome is set to src attribute of the HTML image element. When sending
    the message for all these images the CamelMimeParts are created and
    attached to it. When saving message as draft no changes are made.  When
    opening the message as new (and the message containes CID images) the
    content from all the CamelMimeParts with images is encoded again to base64 and
    saved. When the message is loaded the CID sources are replaced with
    base64 data.The core functionality was moved from EMsgComposer to EEditorWidget
    because right now it is too tight with DOM of the composer's web view.

 composer/e-composer-private.c |    9 +-
 composer/e-msg-composer.c     |  137 +--------------
 composer/e-msg-composer.h     |    2 -
 e-util/e-editor-selection.c   |   86 ++++++---
 e-util/e-editor-widget.c      |  409 ++++++++++++++++++++++++++++++++++++++---
 e-util/e-editor-widget.h      |    7 +
 6 files changed, 454 insertions(+), 196 deletions(-)
---
diff --git a/composer/e-composer-private.c b/composer/e-composer-private.c
index e563a2a..81dc1d3 100644
--- a/composer/e-composer-private.c
+++ b/composer/e-composer-private.c
@@ -381,8 +381,10 @@ e_composer_private_dispose (EMsgComposer *composer)
                composer->priv->composer_actions = NULL;
        }
 
-       g_clear_object (&composer->priv->gallery_icon_view);
-       g_clear_object (&composer->priv->gallery_scrolled_window);
+       if (composer->priv->gallery_scrolled_window != NULL) {
+               g_object_unref (composer->priv->gallery_scrolled_window);
+               composer->priv->gallery_scrolled_window = NULL;
+       }
 
        if (composer->priv->redirect != NULL) {
                g_object_unref (composer->priv->redirect);
@@ -406,9 +408,6 @@ e_composer_private_finalize (EMsgComposer *composer)
        g_free (composer->priv->charset);
        g_free (composer->priv->mime_type);
        g_free (composer->priv->mime_body);
-
-       g_hash_table_destroy (composer->priv->inline_images);
-       g_hash_table_destroy (composer->priv->inline_images_by_url);
 }
 
 gchar *
diff --git a/composer/e-msg-composer.c b/composer/e-msg-composer.c
index 97844e3..c63a044 100644
--- a/composer/e-msg-composer.c
+++ b/composer/e-msg-composer.c
@@ -1339,11 +1339,12 @@ composer_build_message (EMsgComposer *composer,
                gboolean pre_encode;
                EEditor *editor;
                EEditorWidget *editor_widget;
-
-               clear_current_images (composer);
+               GList *inline_images;
 
                editor = e_msg_composer_get_editor (composer);
                editor_widget = e_editor_get_editor_widget (editor);
+               inline_images = e_editor_widget_get_parts_for_inline_images (editor_widget);
+
                data = g_byte_array_new ();
                text = e_editor_widget_get_text_html (editor_widget);
                length = strlen (text);
@@ -2348,39 +2349,6 @@ msg_composer_command_after (EMsgComposer *composer,
        gtkhtml_editor_run_command (editor, "text-default-color");
        gtkhtml_editor_run_command (editor, "italic-off");
 }
-
-static gchar *
-msg_composer_image_uri (EMsgComposer *composer,
-                        const gchar *uri)
-{
-       GHashTable *hash_table;
-       CamelMimePart *part;
-       const gchar *cid;
-
-       hash_table = composer->priv->inline_images_by_url;
-       part = g_hash_table_lookup (hash_table, uri);
-
-       if (part == NULL && g_str_has_prefix (uri, "file:"))
-               part = e_msg_composer_add_inline_image_from_file (
-                       composer, uri + 5);
-
-       if (part == NULL && g_str_has_prefix (uri, "cid:")) {
-               hash_table = composer->priv->inline_images;
-               part = g_hash_table_lookup (hash_table, uri);
-       }
-
-       if (part == NULL)
-               return NULL;
-
-       composer->priv->current_images =
-               g_list_prepend (composer->priv->current_images, part);
-
-       cid = camel_mime_part_get_content_id (part);
-       if (cid == NULL)
-               return NULL;
-
-       return g_strconcat ("cid:", cid, NULL);
-}
 #endif /* WEBKIT-COMPOSER */
 
 static gboolean
@@ -4514,105 +4482,6 @@ e_msg_composer_attach (EMsgComposer *composer,
        g_object_unref (attachment);
 }
 
-/**
- * e_msg_composer_add_inline_image_from_file:
- * @composer: a composer object
- * @filename: the name of the file containing the image
- *
- * This reads in the image in @filename and adds it to @composer
- * as an inline image, to be wrapped in a multipart/related.
- *
- * Returns: the newly-created CamelMimePart (which must be reffed
- * if the caller wants to keep its own reference), or %NULL on error.
- **/
-CamelMimePart *
-e_msg_composer_add_inline_image_from_file (EMsgComposer *composer,
-                                           const gchar *filename)
-{
-       gchar *mime_type, *cid, *url, *name, *dec_file_name;
-       CamelStream *stream;
-       CamelDataWrapper *wrapper;
-       CamelMimePart *part;
-       EMsgComposerPrivate *p = composer->priv;
-
-       dec_file_name = g_strdup (filename);
-       camel_url_decode (dec_file_name);
-
-       if (!g_file_test (dec_file_name, G_FILE_TEST_IS_REGULAR))
-               return NULL;
-
-       stream = camel_stream_fs_new_with_name (
-               dec_file_name, O_RDONLY, 0, NULL);
-       if (!stream)
-               return NULL;
-
-       wrapper = camel_data_wrapper_new ();
-       camel_data_wrapper_construct_from_stream_sync (
-               wrapper, stream, NULL, NULL);
-       g_object_unref (CAMEL_OBJECT (stream));
-
-       mime_type = e_util_guess_mime_type (dec_file_name, TRUE);
-       if (mime_type == NULL)
-               mime_type = g_strdup ("application/octet-stream");
-       camel_data_wrapper_set_mime_type (wrapper, mime_type);
-       g_free (mime_type);
-
-       part = camel_mime_part_new ();
-       camel_medium_set_content (CAMEL_MEDIUM (part), wrapper);
-       g_object_unref (wrapper);
-
-       cid = camel_header_msgid_generate ();
-       camel_mime_part_set_content_id (part, cid);
-       name = g_path_get_basename (dec_file_name);
-       camel_mime_part_set_filename (part, name);
-       g_free (name);
-       camel_mime_part_set_encoding (part, CAMEL_TRANSFER_ENCODING_BASE64);
-
-       url = g_strdup_printf ("file:%s", dec_file_name);
-       g_hash_table_insert (p->inline_images_by_url, url, part);
-
-       url = g_strdup_printf ("cid:%s", cid);
-       g_hash_table_insert (p->inline_images, url, part);
-       g_free (cid);
-
-       g_free (dec_file_name);
-
-       return part;
-}
-
-/**
- * e_msg_composer_add_inline_image_from_mime_part:
- * @composer: a composer object
- * @part: a CamelMimePart containing image data
- *
- * This adds the mime part @part to @composer as an inline image, to
- * be wrapped in a multipart/related.
- **/
-void
-e_msg_composer_add_inline_image_from_mime_part (EMsgComposer *composer,
-                                                CamelMimePart *part)
-{
-       gchar *url;
-       const gchar *location, *cid;
-       EMsgComposerPrivate *p = composer->priv;
-
-       cid = camel_mime_part_get_content_id (part);
-       if (!cid) {
-               camel_mime_part_set_content_id (part, NULL);
-               cid = camel_mime_part_get_content_id (part);
-       }
-
-       url = g_strdup_printf ("cid:%s", cid);
-       g_hash_table_insert (p->inline_images, url, part);
-       g_object_ref (part);
-
-       location = camel_mime_part_get_content_location (part);
-       if (location != NULL)
-               g_hash_table_insert (
-                       p->inline_images_by_url,
-                       g_strdup (location), part);
-}
-
 static void
 composer_get_message_ready (EMsgComposer *composer,
                             GAsyncResult *result,
diff --git a/composer/e-msg-composer.h b/composer/e-msg-composer.h
index e4cd4e1..35864d5 100644
--- a/composer/e-msg-composer.h
+++ b/composer/e-msg-composer.h
@@ -192,8 +192,6 @@ void                e_save_spell_languages          (const GList *spell_languages);
 void           e_msg_composer_is_from_new_message
                                                (EMsgComposer *composer,
                                                 gboolean is_from_new_message);
-
-
 G_END_DECLS
 
 #endif /* E_MSG_COMPOSER_H */
diff --git a/e-util/e-editor-selection.c b/e-util/e-editor-selection.c
index 123dc8f..8231133 100644
--- a/e-util/e-editor-selection.c
+++ b/e-util/e-editor-selection.c
@@ -3020,6 +3020,7 @@ struct _LoadContext {
        goffset total_num_bytes;
        gssize bytes_read;
        const gchar *content_type;
+       const gchar *filename;
        gchar buffer[4096];
 };
 
@@ -3059,44 +3060,32 @@ image_load_context_free (LoadContext *load_context)
 }
 
 static void
-image_load_finish (LoadContext *load_context)
+insert_base64_image (EEditorSelection *selection,
+                     const gchar *base64_content,
+                    const gchar *filename)
 {
-       EEditorSelection *selection;
        EEditorWidget *editor_widget;
        WebKitDOMDocument *document;
-       WebKitDOMElement *element;
-       WebKitDOMElement *caret_position;
-       GMemoryOutputStream *output_stream;
-       gchar *base64_encoded;
-       gchar *mime_type;
-       gchar *output;
-       gsize size;
-       gpointer data;
+       WebKitDOMElement *element, *caret_position;
 
-       output_stream = G_MEMORY_OUTPUT_STREAM (load_context->output_stream);
-
-       selection = load_context->selection;
+       e_editor_selection_save_caret_position (selection);
 
        editor_widget = e_editor_selection_ref_editor_widget (selection);
        g_return_if_fail (editor_widget != NULL);
 
        document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (editor_widget));
-       g_object_unref (editor_widget);
-
-       mime_type = g_content_type_get_mime_type (load_context->content_type);
 
-       data = g_memory_output_stream_get_data (output_stream);
-       size = g_memory_output_stream_get_data_size (output_stream);
-
-       base64_encoded = g_base64_encode ((const guchar *) data, size);
-       output = g_strconcat ("data:", mime_type, ";base64,", base64_encoded, NULL);
-
-       e_editor_selection_save_caret_position (selection);
+       g_object_unref (editor_widget);
 
        element = webkit_dom_document_create_element (document, "img", NULL);
        webkit_dom_html_image_element_set_src (
                WEBKIT_DOM_HTML_IMAGE_ELEMENT (element),
-               output);
+               base64_content);
+       webkit_dom_element_set_attribute (
+               WEBKIT_DOM_ELEMENT (element), "data-inline", "", NULL);
+       webkit_dom_element_set_attribute (
+               WEBKIT_DOM_ELEMENT (element), "data-name",
+               filename ? filename : "", NULL);
 
        caret_position = webkit_dom_document_get_element_by_id (
                document, "-x-evo-caret-position");
@@ -3109,6 +3098,30 @@ image_load_finish (LoadContext *load_context)
 
        e_editor_selection_restore_caret_position (selection);
 
+}
+
+static void
+image_load_finish (LoadContext *load_context)
+{
+       EEditorSelection *selection;
+       GMemoryOutputStream *output_stream;
+       gchar *base64_encoded, *mime_type, *output;
+       gsize size;
+       gpointer data;
+
+       output_stream = G_MEMORY_OUTPUT_STREAM (load_context->output_stream);
+
+       selection = load_context->selection;
+
+       mime_type = g_content_type_get_mime_type (load_context->content_type);
+
+       data = g_memory_output_stream_get_data (output_stream);
+       size = g_memory_output_stream_get_data_size (output_stream);
+
+       base64_encoded = g_base64_encode ((const guchar *) data, size);
+       output = g_strconcat ("data:", mime_type, ";base64,", base64_encoded, NULL);
+       insert_base64_image (selection, output, load_context->filename);
+
        g_free (base64_encoded);
        g_free (output);
        g_free (mime_type);
@@ -3119,7 +3132,7 @@ image_load_finish (LoadContext *load_context)
 static void
 image_load_write_cb (GOutputStream *output_stream,
                      GAsyncResult *result,
-                          LoadContext *load_context)
+                     LoadContext *load_context)
 {
        GInputStream *input_stream;
        gssize bytes_written;
@@ -3242,6 +3255,7 @@ image_load_query_info_cb (GFile *file,
 
        load_context->content_type = g_file_info_get_content_type (file_info);
        load_context->total_num_bytes = g_file_info_get_size (file_info);
+       load_context->filename = g_file_info_get_name (file_info);
 
        g_file_read_async (
                file, G_PRIORITY_DEFAULT,
@@ -3301,8 +3315,26 @@ e_editor_selection_insert_image (EEditorSelection *selection,
        g_return_if_fail (E_IS_EDITOR_SELECTION (selection));
        g_return_if_fail (image_uri != NULL);
 
-       if (is_in_html_mode (selection))
-               image_load_and_insert_async (selection, image_uri);
+       if (is_in_html_mode (selection)) {
+               if (strstr (image_uri, ";base64,")) {
+                       if (g_str_has_prefix (image_uri, "data:"))
+                               insert_base64_image (selection, image_uri, "");
+                       if (strstr (image_uri, ";data")) {
+                               const gchar *base64_data = strstr (image_uri, ";") + 1;
+                               gchar *filename;
+                               glong filename_length;
+
+                               filename_length =
+                                       g_utf8_strlen (image_uri, -1) -
+                                       g_utf8_strlen (base64_data, -1) - 1;
+                               filename = g_strndup (image_uri, filename_length);
+
+                               insert_base64_image (selection, base64_data, filename);
+                               g_free (filename);
+                       }
+               } else
+                       image_load_and_insert_async (selection, image_uri);
+       }
 }
 
 /**
diff --git a/e-util/e-editor-widget.c b/e-util/e-editor-widget.c
index 8f03957..712bd78 100644
--- a/e-util/e-editor-widget.c
+++ b/e-util/e-editor-widget.c
@@ -71,6 +71,8 @@ struct _EEditorWidgetPrivate {
 
        WebKitDOMElement *element_under_mouse;
 
+       GHashTable *inline_images;
+
        GSettings *font_settings;
        GSettings *aliasing_settings;
 
@@ -338,6 +340,47 @@ body_input_event_cb (WebKitDOMElement *element,
        }
 }
 
+static void
+change_cid_images_src_to_base64 (EEditorWidget *widget)
+{
+       gint ii, length;
+       WebKitDOMDocument *document;
+       WebKitDOMNodeList *list;
+
+       document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (widget));
+
+       list = webkit_dom_document_query_selector_all (document, "img[src^=\"cid:\"]", NULL);
+       length = webkit_dom_node_list_get_length (list);
+       for (ii = 0; ii < length; ii++) {
+               WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
+               gchar *cid_src;
+               const gchar *base64_src;
+
+               cid_src = webkit_dom_html_image_element_get_src (
+                       WEBKIT_DOM_HTML_IMAGE_ELEMENT (node));
+
+               if ((base64_src = g_hash_table_lookup (widget->priv->inline_images, cid_src)) != NULL) {
+                       const gchar *base64_data = strstr (base64_src, ";") + 1;
+                       gchar *name;
+                       glong name_length;
+
+                       name_length =
+                               g_utf8_strlen (base64_src, -1) -
+                               g_utf8_strlen (base64_data, -1) - 1;
+                       name = g_strndup (base64_src, name_length);
+
+                       webkit_dom_element_set_attribute (
+                               WEBKIT_DOM_ELEMENT (node), "data-inline", "", NULL);
+                       webkit_dom_element_set_attribute (
+                               WEBKIT_DOM_ELEMENT (node), "data-name", name, NULL);
+                       webkit_dom_html_image_element_set_src (
+                               WEBKIT_DOM_HTML_IMAGE_ELEMENT (node),
+                               base64_data);
+                       g_free (name);
+               }
+       }
+       g_hash_table_remove_all (widget->priv->inline_images);
+}
 
 static void
 editor_widget_load_status_changed (EEditorWidget *widget)
@@ -360,6 +403,10 @@ editor_widget_load_status_changed (EEditorWidget *widget)
                FALSE,
                widget);
 
+       if (widget->priv->html_mode) {
+               change_cid_images_src_to_base64 (widget);
+       }
+
        /* Dispatch queued operations */
        while (widget->priv->postreload_operations &&
               !g_queue_is_empty (widget->priv->postreload_operations)) {
@@ -671,59 +718,171 @@ editor_widget_check_magic_links (EEditorWidget *widget,
        g_free (node_text);
 }
 
-void
-e_editor_widget_insert_smiley (EEditorWidget *widget,
-                               EEmoticon *emoticon)
+typedef struct _LoadContext LoadContext;
+
+struct _LoadContext {
+       EEditorWidget *widget;
+       gchar *content_type;
+       gchar *name;
+       EEmoticon *emoticon;
+};
+
+static LoadContext *
+emoticon_load_context_new (EEditorWidget *widget,
+                           EEmoticon *emoticon)
 {
-       gchar *filename_uri, *html, *node_text;
+       LoadContext *load_context;
+
+       load_context = g_slice_new0 (LoadContext);
+       load_context->widget = widget;
+       load_context->emoticon = emoticon;
+
+       return load_context;
+}
+
+static void
+emoticon_load_context_free (LoadContext *load_context)
+{
+       g_free (load_context->content_type);
+       g_free (load_context->name);
+       g_slice_free (LoadContext, load_context);
+}
+
+static void
+emoticon_read_async_cb (GFile *file,
+                        GAsyncResult *result,
+                        LoadContext *load_context)
+{
+       EEditorWidget *widget = load_context->widget;
+       EEmoticon *emoticon = load_context->emoticon;
+       GError *error = NULL;
+       gchar *html, *node_text, *mime_type;
+       gchar *base64_encoded, *output, *data;
+       const gchar *emoticon_start;
+       GFileInputStream *input_stream;
+       GOutputStream *output_stream;
+       gssize size;
        WebKitDOMDocument *document;
-       WebKitDOMElement *span;
+       WebKitDOMElement *span, *caret_position;
        WebKitDOMNode *node;
        WebKitDOMNode *parent;
        WebKitDOMRange *range;
 
+       input_stream = g_file_read_finish (file, result, &error);
+       g_return_if_fail (!error && input_stream);
+
+       output_stream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
+
+       size = g_output_stream_splice (
+               output_stream, G_INPUT_STREAM (input_stream),
+               G_OUTPUT_STREAM_SPLICE_NONE, NULL, &error);
+
+       if (error || (size == -1))
+               goto out;
+
+       e_editor_selection_save_caret_position (
+               e_editor_widget_get_selection (widget));
+
+       mime_type = g_content_type_get_mime_type (load_context->content_type);
        document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (widget));
        range = editor_widget_get_dom_range (widget);
        node = webkit_dom_range_get_end_container (range, NULL);
        node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node));
-
-       filename_uri = e_emoticon_get_uri (emoticon);
        parent = webkit_dom_node_get_parent_node (node);
        span = webkit_dom_document_create_element (document, "SPAN", NULL);
+
+       data = g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (output_stream));
+
+       base64_encoded = g_base64_encode ((const guchar *) data, size);
+       output = g_strconcat ("data:", mime_type, ";base64,", base64_encoded, NULL);
+
        /* Insert span with image representation and another one with text
         * represetation and hide/show them dependant on active composer mode */
        /* &#8203 == UNICODE_ZERO_WIDTH_SPACE */
        html = g_strdup_printf (
-               "<span class\"-x-evo-smiley-wrapper\"><img src=\"%s\" alt=\"%s\" "
-               "x-evo-smiley=\"%s\" class=\"-x-evo-smiley-img\"/><span "
-               "class=\"-x-evo-smiley-text\" style=\"display: none;\">%s</span>"
-               "</span>&#8203;",
-               filename_uri, emoticon ? emoticon->text_face : "",
-               emoticon->icon_name, emoticon ? emoticon->text_face : "");
-
-       span = WEBKIT_DOM_ELEMENT (webkit_dom_node_append_child (
-               parent, WEBKIT_DOM_NODE (span), NULL));
+               "<span class=\"-x-evo-smiley-wrapper\"><img src=\"%s\" alt=\"%s\" "
+               "x-evo-smiley=\"%s\" class=\"-x-evo-smiley-img\" data-inline "
+               "data-name=\"%s\"/><span class=\"-x-evo-smiley-text\" "
+               "style=\"display: none;\">%s</span></span>&#8203;",
+               output, emoticon ? emoticon->text_face : "", emoticon->icon_name,
+               load_context->name, emoticon ? emoticon->text_face : "");
+
+       caret_position = webkit_dom_document_get_element_by_id (
+               document, "-x-evo-caret-position");
+       span = WEBKIT_DOM_ELEMENT (webkit_dom_node_insert_before (
+               parent,
+               WEBKIT_DOM_NODE (span),
+               WEBKIT_DOM_NODE (caret_position),
+               NULL));
 
        webkit_dom_html_element_set_outer_html (
                WEBKIT_DOM_HTML_ELEMENT (span), html, NULL);
 
-       webkit_dom_node_append_child (
-               parent,
-               e_editor_selection_get_caret_position_node (document),
-               NULL);
-
-       webkit_dom_character_data_delete_data (
-               WEBKIT_DOM_CHARACTER_DATA (node),
-               g_utf8_strlen (node_text, -1) - strlen (emoticon->text_face),
-               strlen (emoticon->text_face),
-               NULL);
+       emoticon_start = g_utf8_strrchr (
+               node_text, -1, g_utf8_get_char (emoticon->text_face));
+       if (emoticon_start) {
+               webkit_dom_character_data_delete_data (
+                       WEBKIT_DOM_CHARACTER_DATA (node),
+                       g_utf8_strlen (node_text, -1) - strlen (emoticon_start),
+                       strlen (emoticon->text_face),
+                       NULL);
+       }
 
        e_editor_selection_restore_caret_position (
                e_editor_widget_get_selection (widget));
 
        g_free (html);
-       g_free (filename_uri);
        g_free (node_text);
+       g_free (base64_encoded);
+       g_free (output);
+       g_free (mime_type);
+       g_object_unref (output_stream);
+ out:
+       emoticon_load_context_free (load_context);
+}
+
+static void
+emoticon_query_info_async_cb (GFile *file,
+                              GAsyncResult *result,
+                              LoadContext *load_context)
+{
+       GError *error = NULL;
+       GFileInfo *info;
+
+       info = g_file_query_info_finish (file, result, &error);
+       g_return_if_fail (!error && info);
+
+       load_context->content_type = g_strdup (g_file_info_get_content_type (info));
+       load_context->name = g_strdup (g_file_info_get_name (info));
+
+       g_file_read_async (
+               file, G_PRIORITY_DEFAULT, NULL,
+               (GAsyncReadyCallback) emoticon_read_async_cb, load_context);
+
+       g_object_unref (info);
+}
+
+void
+e_editor_widget_insert_smiley (EEditorWidget *widget,
+                               EEmoticon *emoticon)
+{
+       GFile *file;
+       gchar *filename_uri;
+       LoadContext *load_context;
+
+       filename_uri = e_emoticon_get_uri (emoticon);
+       g_return_if_fail (filename_uri != NULL);
+
+       load_context = emoticon_load_context_new (widget, emoticon);
+
+       file = g_file_new_for_uri (filename_uri);
+       g_file_query_info_async (
+               file,  "standard::*", G_FILE_QUERY_INFO_NONE,
+               G_PRIORITY_DEFAULT, NULL,
+               (GAsyncReadyCallback) emoticon_query_info_async_cb, load_context);
+
+       g_free (filename_uri);
+       g_object_unref (file);
 }
 
 static void
@@ -999,11 +1158,26 @@ editor_widget_dispose (GObject *object)
                priv->font_settings = NULL;
        }
 
+       g_hash_table_remove_all (priv->inline_images);
+
        /* Chain up to parent's dispose() method. */
        G_OBJECT_CLASS (e_editor_widget_parent_class)->dispose (object);
 }
 
 static void
+editor_widget_finalize (GObject *object)
+{
+       EEditorWidgetPrivate *priv;
+
+       priv = E_EDITOR_WIDGET_GET_PRIVATE (object);
+
+       g_hash_table_destroy (priv->inline_images);
+
+       /* Chain up to parent's finalize() method. */
+       G_OBJECT_CLASS (e_editor_widget_parent_class)->finalize (object);
+}
+
+static void
 editor_widget_constructed (GObject *object)
 {
        e_extensible_load_extensions (E_EXTENSIBLE (object));
@@ -1320,6 +1494,7 @@ e_editor_widget_class_init (EEditorWidgetClass *class)
        object_class->get_property = editor_widget_get_property;
        object_class->set_property = editor_widget_set_property;
        object_class->dispose = editor_widget_dispose;
+       object_class->finalize = editor_widget_finalize;
        object_class->constructed = editor_widget_constructed;
 
        widget_class = GTK_WIDGET_CLASS (class);
@@ -1555,6 +1730,7 @@ e_editor_widget_init (EEditorWidget *editor)
                "enable-plugins", FALSE,
                "enable-scripts", FALSE,
                "enable-spell-checking", TRUE,
+               "respect-image-orientation", TRUE,
                NULL);
 
        webkit_web_view_set_settings (WEBKIT_WEB_VIEW (editor), settings);
@@ -1608,6 +1784,11 @@ e_editor_widget_init (EEditorWidget *editor)
                editor->priv->aliasing_settings = g_settings;
        }
 
+       editor->priv->inline_images = g_hash_table_new_full (
+               g_str_hash, g_str_equal,
+               (GDestroyNotify) g_free,
+               (GDestroyNotify) g_free);
+
        e_editor_widget_update_fonts (editor);
 
        /* Make WebKit think we are displaying a local file, so that it
@@ -2242,7 +2423,7 @@ e_editor_widget_dequote_plain_text (EEditorWidget *widget)
        document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (widget));
 
        list = webkit_dom_document_query_selector_all (
-                       document, "blockquote.-x-evo-plaintext-quoted", NULL);
+               document, "blockquote.-x-evo-plaintext-quoted", NULL);
        length = webkit_dom_node_list_get_length (list);
        for (ii = 0; ii < length; ii++) {
                WebKitDOMNodeList *gt_list;
@@ -3421,3 +3602,175 @@ e_editor_widget_check_magic_links (EEditorWidget *widget,
        editor_widget_check_magic_links (widget, range, include_space, NULL);
 }
 
+static CamelMimePart *
+e_editor_widget_add_inline_image_from_element (EEditorWidget *widget,
+                                               WebKitDOMElement *element)
+{
+       CamelStream *stream;
+       CamelDataWrapper *wrapper;
+       CamelMimePart *part = NULL;
+       gsize decoded_size;
+       gssize size;
+       gchar *mime_type = NULL;
+       gchar *element_src, *cid, *name;
+       const gchar *base64_encoded_data;
+       guchar *base64_decoded_data;
+
+       if (!WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (element))
+               return NULL;
+
+       element_src = webkit_dom_html_image_element_get_src (
+               WEBKIT_DOM_HTML_IMAGE_ELEMENT (element));
+
+       base64_encoded_data = strstr (element_src, ";base64,");
+       if (!base64_encoded_data)
+               goto out;
+
+       mime_type = g_strndup (
+               element_src + 5,
+               base64_encoded_data - (strstr (element_src, "data:") + 5));
+
+       /* Move to actual data */
+       base64_encoded_data += 8;
+
+       base64_decoded_data = g_base64_decode (base64_encoded_data, &decoded_size);
+
+       stream = camel_stream_mem_new ();
+       size = camel_stream_write (
+               stream, (gchar *) base64_decoded_data, decoded_size, NULL, NULL);
+
+       if (size == -1)
+               goto out;
+
+       wrapper = camel_data_wrapper_new ();
+       camel_data_wrapper_construct_from_stream_sync (
+               wrapper, stream, NULL, NULL);
+       g_object_unref (CAMEL_OBJECT (stream));
+
+       camel_data_wrapper_set_mime_type (wrapper, mime_type);
+
+       part = camel_mime_part_new ();
+       camel_medium_set_content (CAMEL_MEDIUM (part), wrapper);
+       g_object_unref (wrapper);
+
+       cid = camel_header_msgid_generate ();
+       camel_mime_part_set_content_id (part, cid);
+       name = webkit_dom_element_get_attribute (element, "data-name");
+       camel_mime_part_set_filename (part, name);
+       g_free (name);
+       camel_mime_part_set_encoding (part, CAMEL_TRANSFER_ENCODING_BASE64);
+out:
+       g_free (mime_type);
+       g_free (element_src);
+       g_free (base64_decoded_data);
+
+       return part;
+}
+
+GList *
+e_editor_widget_get_parts_for_inline_images (EEditorWidget *widget)
+{
+       GHashTable *added;
+       GList *parts = NULL;
+       gint length, ii;
+       WebKitDOMDocument *document;
+       WebKitDOMNodeList *list;
+
+       document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW  (widget));
+       list = webkit_dom_document_query_selector_all (document, "img[data-inline]", NULL);
+
+       length = webkit_dom_node_list_get_length (list);
+       if (length == 0)
+               return parts;
+
+       added = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+       for (ii = 0; ii < length; ii++) {
+               CamelMimePart *part;
+               WebKitDOMNode *node;
+               gchar *src;
+
+               node = webkit_dom_node_list_item (list, ii);
+               src = webkit_dom_html_image_element_get_src (
+                       WEBKIT_DOM_HTML_IMAGE_ELEMENT (node));
+
+               if (!g_hash_table_lookup (added, src)) {
+                       part = e_editor_widget_add_inline_image_from_element (
+                               widget, WEBKIT_DOM_ELEMENT (node));
+                       parts = g_list_append (parts, part);
+                       g_hash_table_insert (
+                               added, src, (gpointer) camel_mime_part_get_content_id (part));
+               }
+               g_free (src);
+       }
+
+       for (ii = 0; ii < length; ii++) {
+               WebKitDOMNode *node;
+               gchar *src;
+               const gchar *id;
+
+               node = webkit_dom_node_list_item (list, ii);
+               src = webkit_dom_html_image_element_get_src (
+                       WEBKIT_DOM_HTML_IMAGE_ELEMENT (node));
+
+               if ((id = g_hash_table_lookup (added, src)) != NULL) {
+                       gchar *cid = g_strdup_printf ("cid:%s", id);
+                       webkit_dom_html_image_element_set_src (
+                               WEBKIT_DOM_HTML_IMAGE_ELEMENT (node), cid);
+                       g_free (cid);
+               }
+               g_free (src);
+       }
+       g_hash_table_destroy (added);
+
+       return parts;
+}
+
+/**
+ * e_editor_widget_add_inline_image_from_mime_part:
+ * @composer: a composer object
+ * @part: a CamelMimePart containing image data
+ *
+ * This adds the mime part @part to @composer as an inline image.
+ **/
+void
+e_editor_widget_add_inline_image_from_mime_part (EEditorWidget *widget,
+                                                 CamelMimePart *part)
+{
+       CamelDataWrapper *dw;
+       CamelStream *stream;
+       GByteArray *byte_array;
+       gchar *src, *base64_encoded, *mime_type, *cid_src;
+       const gchar *cid, *name;
+
+       stream = camel_stream_mem_new ();
+       dw = camel_medium_get_content (CAMEL_MEDIUM (part));
+       g_return_if_fail (dw);
+
+       mime_type = camel_data_wrapper_get_mime_type (dw);
+       camel_data_wrapper_decode_to_stream_sync (dw, stream, NULL, NULL);
+       camel_stream_close (stream, NULL, NULL);
+
+       byte_array = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (stream));
+
+       if (!byte_array->data)
+               return;
+
+       base64_encoded = g_base64_encode ((const guchar *) byte_array->data, byte_array->len);
+
+       name = camel_mime_part_get_filename (part);
+       /* Insert file name before new src */
+       src = g_strconcat (name, ";data:", mime_type, ";base64,", base64_encoded, NULL);
+
+       cid = camel_mime_part_get_content_id (part);
+       if (!cid) {
+               camel_mime_part_set_content_id (part, NULL);
+               cid = camel_mime_part_get_content_id (part);
+       }
+       cid_src = g_strdup_printf ("cid:%s", cid);
+
+       g_hash_table_insert (widget->priv->inline_images, cid_src, src);
+
+       g_free (base64_encoded);
+       g_free (mime_type);
+       g_object_unref (stream);
+}
diff --git a/e-util/e-editor-widget.h b/e-util/e-editor-widget.h
index 412f199..2b1a603 100644
--- a/e-util/e-editor-widget.h
+++ b/e-util/e-editor-widget.h
@@ -27,6 +27,8 @@
 
 #include <webkit/webkit.h>
 
+#include <camel/camel.h>
+
 #include <e-util/e-editor-selection.h>
 #include <e-util/e-emoticon.h>
 #include <e-util/e-spell-checker.h>
@@ -125,6 +127,11 @@ void               e_editor_widget_dequote_plain_text
                                                (EEditorWidget *widget);
 void           e_editor_widget_force_spellcheck
                                                (EEditorWidget *widget);
+void           e_editor_widget_add_inline_image_from_mime_part
+                                               (EEditorWidget *widget,
+                                                 CamelMimePart *part);
+GList *                e_editor_widget_get_parts_for_inline_images
+                                               (EEditorWidget *widget);
 G_END_DECLS
 
 #endif /* E_EDITOR_WIDGET_H */


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