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



commit f6bf37486ed0aba845cac2388ce0c7ad6d169abd
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 |   16 --
 composer/e-composer-private.h |    4 -
 composer/e-msg-composer.c     |  206 +++------------------
 composer/e-msg-composer.h     |   10 -
 e-util/e-editor-selection.c   |   86 ++++++---
 e-util/e-editor-widget.c      |  409 ++++++++++++++++++++++++++++++++++++++---
 e-util/e-editor-widget.h      |    7 +
 7 files changed, 472 insertions(+), 266 deletions(-)
---
diff --git a/composer/e-composer-private.c b/composer/e-composer-private.c
index 3e79c16..5cf56f4 100644
--- a/composer/e-composer-private.c
+++ b/composer/e-composer-private.c
@@ -129,16 +129,6 @@ e_composer_private_constructed (EMsgComposer *composer)
        priv->extra_hdr_names = g_ptr_array_new ();
        priv->extra_hdr_values = g_ptr_array_new ();
 
-       priv->inline_images = g_hash_table_new_full (
-               g_str_hash, g_str_equal,
-               (GDestroyNotify) g_free,
-               (GDestroyNotify) NULL);
-
-       priv->inline_images_by_url = g_hash_table_new_full (
-               g_str_hash, g_str_equal,
-               (GDestroyNotify) g_free,
-               (GDestroyNotify) g_object_unref);
-
        priv->charset = e_composer_get_default_charset ();
 
        priv->is_from_message = FALSE;
@@ -394,9 +384,6 @@ e_composer_private_dispose (EMsgComposer *composer)
        g_clear_object (&composer->priv->gallery_icon_view);
        g_clear_object (&composer->priv->gallery_scrolled_window);
 
-       g_hash_table_remove_all (composer->priv->inline_images);
-       g_hash_table_remove_all (composer->priv->inline_images_by_url);
-
        if (composer->priv->redirect != NULL) {
                g_object_unref (composer->priv->redirect);
                composer->priv->redirect = NULL;
@@ -419,9 +406,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-composer-private.h b/composer/e-composer-private.h
index e9b40c2..d4dadbc 100644
--- a/composer/e-composer-private.h
+++ b/composer/e-composer-private.h
@@ -81,10 +81,6 @@ struct _EMsgComposerPrivate {
 
        GtkWidget *address_dialog;
 
-       GHashTable *inline_images;
-       GHashTable *inline_images_by_url;
-       GList *current_images;
-
        gchar *mime_type;
        gchar *mime_body;
        gchar *charset;
diff --git a/composer/e-msg-composer.c b/composer/e-msg-composer.c
index 63a02e3..c63a044 100644
--- a/composer/e-msg-composer.c
+++ b/composer/e-msg-composer.c
@@ -425,45 +425,6 @@ best_charset (GByteArray *buf,
        return g_strdup (charset);
 }
 
-static void
-clear_current_images (EMsgComposer *composer)
-{
-       EMsgComposerPrivate *p = composer->priv;
-       g_list_free (p->current_images);
-       p->current_images = NULL;
-}
-
-void
-e_msg_composer_clear_inlined_table (EMsgComposer *composer)
-{
-       EMsgComposerPrivate *p = composer->priv;
-
-       g_hash_table_remove_all (p->inline_images);
-       g_hash_table_remove_all (p->inline_images_by_url);
-}
-
-static void
-add_inlined_images (EMsgComposer *composer,
-                    CamelMultipart *multipart)
-{
-       EMsgComposerPrivate *p = composer->priv;
-
-       GList *d = p->current_images;
-       GHashTable *added;
-
-       added = g_hash_table_new (g_direct_hash, g_direct_equal);
-       while (d) {
-               CamelMimePart *part = d->data;
-
-               if (!g_hash_table_lookup (added, part)) {
-                       camel_multipart_add_part (multipart, part);
-                       g_hash_table_insert (added, part, part);
-               }
-               d = d->next;
-       }
-       g_hash_table_destroy (added);
-}
-
 /* These functions builds a CamelMimeMessage for the message that the user has
  * composed in 'composer'.
  */
@@ -1373,15 +1334,17 @@ composer_build_message (EMsgComposer *composer,
        if ((flags & COMPOSER_FLAG_HTML_CONTENT) != 0 &&
            !(flags & COMPOSER_FLAG_SAVE_DRAFT)) {
                gchar *text;
+               guint count;
                gsize length;
                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);
@@ -1438,7 +1401,9 @@ composer_build_message (EMsgComposer *composer,
 
                /* If there are inlined images, construct a multipart/related
                 * containing the multipart/alternative and the images. */
-               if (priv->current_images) {
+               count = g_list_length (inline_images);
+               if (count > 0) {
+                       guint ii;
                        CamelMultipart *html_with_images;
 
                        html_with_images = camel_multipart_new ();
@@ -1457,8 +1422,12 @@ composer_build_message (EMsgComposer *composer,
 
                        g_object_unref (body);
 
-                       add_inlined_images (composer, html_with_images);
-                       clear_current_images (composer);
+                       for (ii = 0; ii < count; ii++) {
+                               CamelMimePart *part = g_list_nth_data (inline_images, ii);
+                               camel_multipart_add_part (
+                                       html_with_images, part);
+                               g_object_unref (part);
+                       }
 
                        context->top_level_part =
                                CAMEL_DATA_WRAPPER (html_with_images);
@@ -2380,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
@@ -2616,12 +2552,16 @@ add_attachments_handle_mime_part (EMsgComposer *composer,
 {
        CamelContentType *content_type;
        CamelDataWrapper *wrapper;
+       EEditor *editor;
+       EEditorWidget *editor_widget;
 
        if (!mime_part)
                return;
 
        content_type = camel_mime_part_get_content_type (mime_part);
        wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
+       editor = e_msg_composer_get_editor (composer);
+       editor_widget = e_editor_get_editor_widget (editor);
 
        if (CAMEL_IS_MULTIPART (wrapper)) {
                /* another layer of multipartness... */
@@ -2631,10 +2571,10 @@ add_attachments_handle_mime_part (EMsgComposer *composer,
        } else if (just_inlines) {
                if (camel_mime_part_get_content_id (mime_part) ||
                    camel_mime_part_get_content_location (mime_part))
-                       e_msg_composer_add_inline_image_from_mime_part (
-                               composer, mime_part);
+                       e_editor_widget_add_inline_image_from_mime_part (
+                               editor_widget, mime_part);
        } else if (related && camel_content_type_is (content_type, "image", "*")) {
-               e_msg_composer_add_inline_image_from_mime_part (composer, mime_part);
+               e_editor_widget_add_inline_image_from_mime_part (editor_widget, mime_part);
        } else if (camel_content_type_is (content_type, "text", "*") &&
                camel_mime_part_get_filename (mime_part) == NULL) {
                /* Do nothing if this is a text/anything without a
@@ -3053,8 +2993,11 @@ handle_multipart (EMsgComposer *composer,
                } else if (camel_mime_part_get_content_id (mime_part) ||
                           camel_mime_part_get_content_location (mime_part)) {
                        /* special in-line attachment */
-                       e_msg_composer_add_inline_image_from_mime_part (
-                               composer, mime_part);
+                       EEditor *editor;
+
+                       editor = e_msg_composer_get_editor (composer);
+                       e_editor_widget_add_inline_image_from_mime_part (
+                               e_editor_get_editor_widget (editor), mime_part);
 
                } else {
                        /* normal attachment */
@@ -4539,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 2444df9..35864d5 100644
--- a/composer/e-msg-composer.h
+++ b/composer/e-msg-composer.h
@@ -129,12 +129,6 @@ void               e_msg_composer_set_source_headers
                                                 CamelMessageFlags flags);
 void           e_msg_composer_attach           (EMsgComposer *composer,
                                                 CamelMimePart *mime_part);
-CamelMimePart *        e_msg_composer_add_inline_image_from_file
-                                               (EMsgComposer *composer,
-                                                const gchar *filename);
-void           e_msg_composer_add_inline_image_from_mime_part
-                                               (EMsgComposer *composer,
-                                                CamelMimePart *part);
 void           e_msg_composer_get_message      (EMsgComposer *composer,
                                                 gint io_priority,
                                                 GCancellable *cancellable,
@@ -173,8 +167,6 @@ CamelInternetAddress *
 CamelInternetAddress *
                e_msg_composer_get_reply_to     (EMsgComposer *composer);
 
-void           e_msg_composer_clear_inlined_table
-                                               (EMsgComposer *composer);
 void           e_msg_composer_add_message_attachments
                                                (EMsgComposer *composer,
                                                 CamelMimeMessage *message,
@@ -200,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 242a9dc..363af63 100644
--- a/e-util/e-editor-selection.c
+++ b/e-util/e-editor-selection.c
@@ -3024,6 +3024,7 @@ struct _LoadContext {
        goffset total_num_bytes;
        gssize bytes_read;
        const gchar *content_type;
+       const gchar *filename;
        gchar buffer[4096];
 };
 
@@ -3063,44 +3064,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");
@@ -3113,6 +3102,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);
@@ -3123,7 +3136,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;
@@ -3246,6 +3259,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,
@@ -3305,8 +3319,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]