/* * e-html-editor-view.c * * Copyright (C) 2012 Dan Vrátil * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * */ #ifdef HAVE_CONFIG_H #include #endif #include "e-html-editor-view.h" #include "e-html-editor.h" #include "e-emoticon-chooser.h" #include #include #include #include #define E_HTML_EDITOR_VIEW_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_HTML_EDITOR_VIEW, EHTMLEditorViewPrivate)) #define UNICODE_ZERO_WIDTH_SPACE "\xe2\x80\x8b" #define UNICODE_NBSP "\xc2\xa0" #define URL_PATTERN \ "((([A-Za-z]{3,9}:(?:\\/\\/)?)(?:[\\-;:&=\\+\\$,\\w]+@)?" \ "[A-Za-z0-9\\.\\-]+|(?:www\\.|[\\-;:&=\\+\\$,\\w]+@)" \ "[A-Za-z0-9\\.\\-]+)((?:\\/[\\+~%\\/\\.\\w\\-]*)?\\?" \ "?(?:[\\-\\+=&;%@\\.\\w]*)#?(?:[\\.\\!\\/\\\\w]*))?)" #define URL_PATTERN_SPACE URL_PATTERN "\\s" #define QUOTE_SYMBOL ">" /* Keep synchronized with the same value in EHTMLEditorSelection */ #define SPACES_PER_LIST_LEVEL 8 #define TAB_LENGTH 8 /** * EHTMLEditorView: * * The #EHTMLEditorView is a WebKit-based rich text editor. The view itself * only provides means to configure global behavior of the editor. To work * with the actual content, current cursor position or current selection, * use #EHTMLEditorSelection object. */ struct _EHTMLEditorViewPrivate { gint changed : 1; gint inline_spelling : 1; gint magic_links : 1; gint magic_smileys : 1; gint can_copy : 1; gint can_cut : 1; gint can_paste : 1; gint can_redo : 1; gint can_undo : 1; gint reload_in_progress : 1; gint html_mode : 1; EHTMLEditorSelection *selection; WebKitDOMElement *element_under_mouse; GHashTable *inline_images; GSettings *mail_settings; GSettings *font_settings; GSettings *aliasing_settings; gboolean convertor_insert; gboolean body_input_event_removed; WebKitWebView *convertor_web_view; GHashTable *old_settings; }; enum { PROP_0, PROP_CAN_COPY, PROP_CAN_CUT, PROP_CAN_PASTE, PROP_CAN_REDO, PROP_CAN_UNDO, PROP_CHANGED, PROP_HTML_MODE, PROP_INLINE_SPELLING, PROP_MAGIC_LINKS, PROP_MAGIC_SMILEYS, PROP_SPELL_CHECKER }; enum { POPUP_EVENT, PASTE_PRIMARY_CLIPBOARD, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; static CamelDataCache *emd_global_http_cache = NULL; G_DEFINE_TYPE_WITH_CODE ( EHTMLEditorView, e_html_editor_view, WEBKIT_TYPE_WEB_VIEW, G_IMPLEMENT_INTERFACE ( E_TYPE_EXTENSIBLE, NULL)) static WebKitDOMRange * html_editor_view_get_dom_range (EHTMLEditorView *view) { WebKitDOMDocument *document; WebKitDOMDOMWindow *window; WebKitDOMDOMSelection *selection; document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); window = webkit_dom_document_get_default_view (document); selection = webkit_dom_dom_window_get_selection (window); if (webkit_dom_dom_selection_get_range_count (selection) < 1) { return NULL; } return webkit_dom_dom_selection_get_range_at (selection, 0, NULL); } static void html_editor_view_user_changed_contents_cb (EHTMLEditorView *view, gpointer user_data) { WebKitWebView *web_view; gboolean can_redo, can_undo; web_view = WEBKIT_WEB_VIEW (view); e_html_editor_view_set_changed (view, TRUE); can_redo = webkit_web_view_can_redo (web_view); if (view->priv->can_redo != can_redo) { view->priv->can_redo = can_redo; g_object_notify (G_OBJECT (view), "can-redo"); } can_undo = webkit_web_view_can_undo (web_view); if (view->priv->can_undo != can_undo) { view->priv->can_undo = can_undo; g_object_notify (G_OBJECT (view), "can-undo"); } } static void html_editor_view_selection_changed_cb (EHTMLEditorView *view, gpointer user_data) { WebKitWebView *web_view; gboolean can_copy, can_cut, can_paste; web_view = WEBKIT_WEB_VIEW (view); /* When the webview is being (re)loaded, the document is in an * inconsistant state and there is no selection, so don't propagate * the signal further to EHTMLEditorSelection and others and wait until * the load is finished. */ if (view->priv->reload_in_progress) { g_signal_stop_emission_by_name (view, "selection-changed"); return; } can_copy = webkit_web_view_can_copy_clipboard (web_view); if (view->priv->can_copy != can_copy) { view->priv->can_copy = can_copy; g_object_notify (G_OBJECT (view), "can-copy"); } can_cut = webkit_web_view_can_cut_clipboard (web_view); if (view->priv->can_cut != can_cut) { view->priv->can_cut = can_cut; g_object_notify (G_OBJECT (view), "can-cut"); } can_paste = webkit_web_view_can_paste_clipboard (web_view); if (view->priv->can_paste != can_paste) { view->priv->can_paste = can_paste; g_object_notify (G_OBJECT (view), "can-paste"); } } static gboolean html_editor_view_should_show_delete_interface_for_element (EHTMLEditorView *view, WebKitDOMHTMLElement *element) { return FALSE; } static WebKitDOMElement * get_parent_block_element (WebKitDOMNode *node) { WebKitDOMElement *parent = webkit_dom_node_get_parent_element (node); while (parent && !WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent) && !WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent) && !WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (parent) && !WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (parent) && !WEBKIT_DOM_IS_HTML_PRE_ELEMENT (parent)) { parent = webkit_dom_node_get_parent_element ( WEBKIT_DOM_NODE (parent)); } return parent; } void e_html_editor_view_force_spell_check_for_current_paragraph (EHTMLEditorView *view) { EHTMLEditorSelection *selection; WebKitDOMDocument *document; WebKitDOMDOMSelection *dom_selection; WebKitDOMDOMWindow *window; WebKitDOMElement *selection_start_marker, *selection_end_marker; WebKitDOMElement *parent, *element; WebKitDOMRange *end_range, *actual; WebKitDOMText *text; if (!view->priv->inline_spelling) return; document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); window = webkit_dom_document_get_default_view (document); dom_selection = webkit_dom_dom_window_get_selection (window); element = webkit_dom_document_query_selector ( document, "body[spellcheck=true]", NULL); if (!element) return; selection = e_html_editor_view_get_selection (view); e_html_editor_selection_save (selection); selection_start_marker = webkit_dom_document_query_selector ( document, "span#-x-evo-selection-start-marker", NULL); selection_end_marker = webkit_dom_document_query_selector ( document, "span#-x-evo-selection-end-marker", NULL); if (!selection_start_marker || !selection_end_marker) return; /* Block callbacks of selection-changed signal as we don't want to * recount all the block format things in EHTMLEditorSelection and here as well * when we are moving with caret */ g_signal_handlers_block_by_func ( view, html_editor_view_selection_changed_cb, NULL); e_html_editor_selection_block_selection_changed (selection); parent = get_parent_block_element (WEBKIT_DOM_NODE (selection_start_marker)); /* Append some text on the end of the element */ text = webkit_dom_document_create_text_node (document, "-x-evo-end"); webkit_dom_node_append_child ( WEBKIT_DOM_NODE (get_parent_block_element ( WEBKIT_DOM_NODE (selection_end_marker))), WEBKIT_DOM_NODE (text), NULL); /* Create range that's pointing on the end of this text */ end_range = webkit_dom_document_create_range (document); webkit_dom_range_select_node_contents ( end_range, WEBKIT_DOM_NODE (text), NULL); webkit_dom_range_collapse (end_range, FALSE, NULL); /* Move on the beginning of the paragraph */ actual = webkit_dom_document_create_range (document); webkit_dom_range_select_node_contents ( actual, WEBKIT_DOM_NODE (parent), NULL); webkit_dom_range_collapse (actual, TRUE, NULL); webkit_dom_dom_selection_remove_all_ranges (dom_selection); webkit_dom_dom_selection_add_range (dom_selection, actual); /* Go through all words to spellcheck them. To avoid this we have to wait for * http://www.w3.org/html/wg/drafts/html/master/editing.html#dom-forcespellcheck */ actual = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); /* We are moving forward word by word until we hit the text on the end of * the paragraph that we previously inserted there */ while (actual && webkit_dom_range_compare_boundary_points (end_range, 2, actual, NULL) != 0) { webkit_dom_dom_selection_modify ( dom_selection, "move", "forward", "word"); actual = webkit_dom_dom_selection_get_range_at ( dom_selection, 0, NULL); } /* Remove the text that we inserted on the end of the paragraph */ remove_node (WEBKIT_DOM_NODE (text)); /* Unblock the callbacks */ g_signal_handlers_unblock_by_func ( view, html_editor_view_selection_changed_cb, NULL); e_html_editor_selection_unblock_selection_changed (selection); e_html_editor_selection_restore (selection); } static void refresh_spell_check (EHTMLEditorView *view, gboolean enable_spell_check) { EHTMLEditorSelection *selection; WebKitDOMDocument *document; WebKitDOMDOMSelection *dom_selection; WebKitDOMDOMWindow *window; WebKitDOMElement *selection_start_marker, *selection_end_marker; WebKitDOMHTMLElement *body; WebKitDOMRange *end_range, *actual; WebKitDOMText *text; document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); window = webkit_dom_document_get_default_view (document); dom_selection = webkit_dom_dom_window_get_selection (window); /* Enable/Disable spellcheck in composer */ body = webkit_dom_document_get_body (document); webkit_dom_element_set_attribute ( WEBKIT_DOM_ELEMENT (body), "spellcheck", enable_spell_check ? "true" : "false", NULL); selection = e_html_editor_view_get_selection (view); e_html_editor_selection_save (selection); selection_start_marker = webkit_dom_document_query_selector ( document, "span#-x-evo-selection-start-marker", NULL); selection_end_marker = webkit_dom_document_query_selector ( document, "span#-x-evo-selection-end-marker", NULL); /* Sometimes the web view is not focused, so we have to save the selection * manually into the body */ if (!selection_start_marker || !selection_end_marker) { if (!webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body))) return; selection_start_marker = webkit_dom_document_create_element ( document, "SPAN", NULL); webkit_dom_element_set_id ( selection_start_marker, "-x-evo-selection-start-marker"); webkit_dom_node_insert_before ( webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)), WEBKIT_DOM_NODE (selection_start_marker), webkit_dom_node_get_first_child ( webkit_dom_node_get_first_child ( WEBKIT_DOM_NODE (body))), NULL); selection_end_marker = webkit_dom_document_create_element ( document, "SPAN", NULL); webkit_dom_element_set_id ( selection_end_marker, "-x-evo-selection-end-marker"); webkit_dom_node_insert_before ( webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)), WEBKIT_DOM_NODE (selection_end_marker), webkit_dom_node_get_first_child ( webkit_dom_node_get_first_child ( WEBKIT_DOM_NODE (body))), NULL); } /* Block callbacks of selection-changed signal as we don't want to * recount all the block format things in EHTMLEditorSelection and here as well * when we are moving with caret */ g_signal_handlers_block_by_func ( view, html_editor_view_selection_changed_cb, NULL); e_html_editor_selection_block_selection_changed (selection); /* Append some text on the end of the body */ text = webkit_dom_document_create_text_node (document, "-x-evo-end"); webkit_dom_node_append_child ( WEBKIT_DOM_NODE (body), WEBKIT_DOM_NODE (text), NULL); /* Create range that's pointing on the end of this text */ end_range = webkit_dom_document_create_range (document); webkit_dom_range_select_node_contents ( end_range, WEBKIT_DOM_NODE (text), NULL); webkit_dom_range_collapse (end_range, FALSE, NULL); /* Move on the beginning of the document */ webkit_dom_dom_selection_modify ( dom_selection, "move", "backward", "documentboundary"); /* Go through all words to spellcheck them. To avoid this we have to wait for * http://www.w3.org/html/wg/drafts/html/master/editing.html#dom-forcespellcheck */ actual = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); /* We are moving forward word by word until we hit the text on the end of * the body that we previously inserted there */ while (actual && webkit_dom_range_compare_boundary_points (end_range, 2, actual, NULL) != 0) { webkit_dom_dom_selection_modify ( dom_selection, "move", "forward", "word"); actual = webkit_dom_dom_selection_get_range_at ( dom_selection, 0, NULL); } /* Remove the text that we inserted on the end of the body */ remove_node (WEBKIT_DOM_NODE (text)); /* Unblock the callbacks */ g_signal_handlers_unblock_by_func ( view, html_editor_view_selection_changed_cb, NULL); e_html_editor_selection_unblock_selection_changed (selection); e_html_editor_selection_restore (selection); } void e_html_editor_view_turn_spell_check_off (EHTMLEditorView *view) { refresh_spell_check (view, FALSE); } void e_html_editor_view_force_spell_check (EHTMLEditorView *view) { if (view->priv->inline_spelling) refresh_spell_check (view, TRUE); } static gint get_citation_level (WebKitDOMNode *node, gboolean set_plaintext_quoted) { WebKitDOMNode *parent = node; gint level = 0; while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) { if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent) && webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (parent), "type")) { level++; if (set_plaintext_quoted) { element_add_class ( WEBKIT_DOM_ELEMENT (parent), "-x-evo-plaintext-quoted"); } } parent = webkit_dom_node_get_parent_node (parent); } return level; } static gchar * get_quotation_for_level (gint quote_level) { gint ii; GString *output = g_string_new (""); for (ii = 0; ii < quote_level; ii++) { g_string_append (output, ""); g_string_append (output, QUOTE_SYMBOL); g_string_append (output, " "); g_string_append (output, ""); } return g_string_free (output, FALSE); } static void quote_plain_text_element_after_wrapping (WebKitDOMDocument *document, WebKitDOMElement *element, gint quote_level) { WebKitDOMNodeList *list; WebKitDOMNode *quoted_node; gint length, ii; gchar *quotation; quoted_node = WEBKIT_DOM_NODE ( webkit_dom_document_create_element (document, "SPAN", NULL)); webkit_dom_element_set_class_name ( WEBKIT_DOM_ELEMENT (quoted_node), "-x-evo-quoted"); quotation = get_quotation_for_level (quote_level); webkit_dom_html_element_set_inner_html ( WEBKIT_DOM_HTML_ELEMENT (quoted_node), quotation, NULL); list = webkit_dom_element_query_selector_all ( element, "br.-x-evo-wrap-br", NULL); webkit_dom_node_insert_before ( WEBKIT_DOM_NODE (element), quoted_node, webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)), NULL); length = webkit_dom_node_list_get_length (list); for (ii = 0; ii < length; ii++) { WebKitDOMNode *br = webkit_dom_node_list_item (list, ii); webkit_dom_node_insert_before ( webkit_dom_node_get_parent_node (br), webkit_dom_node_clone_node (quoted_node, TRUE), webkit_dom_node_get_next_sibling (br), NULL); } g_free (quotation); } static gboolean is_citation_node (WebKitDOMNode *node) { char *value; if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node)) return FALSE; value = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "type"); /* citation ==
*/ if (g_strcmp0 (value, "cite") == 0) { g_free (value); return TRUE; } else { g_free (value); return FALSE; } } static gboolean return_pressed_in_empty_line (EHTMLEditorSelection *selection, WebKitDOMDocument *document) { WebKitDOMDOMSelection *dom_selection; WebKitDOMDOMWindow *dom_window; WebKitDOMNode *node; WebKitDOMRange *range; dom_window = webkit_dom_document_get_default_view (document); dom_selection = webkit_dom_dom_window_get_selection (dom_window); range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); if (!range) return FALSE; node = webkit_dom_range_get_start_container (range, NULL); if (!WEBKIT_DOM_IS_TEXT (node)) { WebKitDOMNode *first_child; first_child = webkit_dom_node_get_first_child (node); if (first_child && WEBKIT_DOM_IS_ELEMENT (first_child) && element_has_class (WEBKIT_DOM_ELEMENT (first_child), "-x-evo-quoted")) { WebKitDOMNode *next_sibling; next_sibling = webkit_dom_node_get_next_sibling (first_child); if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (next_sibling)) { next_sibling = webkit_dom_node_get_next_sibling (node); if (webkit_dom_node_get_first_child (next_sibling)) { WebKitDOMElement *element; element = webkit_dom_document_create_element ( document, "SPAN", NULL); webkit_dom_element_set_id ( element, "-x-evo-selection-end-marker"); webkit_dom_node_insert_before ( next_sibling, WEBKIT_DOM_NODE (element), webkit_dom_node_get_first_child (next_sibling), NULL); element = webkit_dom_document_create_element ( document, "SPAN", NULL); webkit_dom_element_set_id ( element, "-x-evo-selection-start-marker"); webkit_dom_node_insert_before ( next_sibling, WEBKIT_DOM_NODE (element), webkit_dom_node_get_first_child (next_sibling), NULL); e_html_editor_selection_restore (selection); return TRUE; } } } } return FALSE; } static WebKitDOMElement * insert_new_line_into_citation (EHTMLEditorView *view, const gchar *html_to_insert) { gboolean html_mode, ret_val, fix_after_return_pressed_in_empty_line; EHTMLEditorSelection *selection; WebKitDOMDocument *document; WebKitDOMElement *element, *paragraph = NULL; WebKitDOMNode *caret; html_mode = e_html_editor_view_get_html_mode (view); selection = e_html_editor_view_get_selection (view); document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); fix_after_return_pressed_in_empty_line = return_pressed_in_empty_line (selection, document); ret_val = e_html_editor_view_exec_command ( view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT, NULL); if (!ret_val) return NULL; element = webkit_dom_document_query_selector ( document, "body>br", NULL); if (!element) return NULL; if (fix_after_return_pressed_in_empty_line) { WebKitDOMNode *node; node = webkit_dom_node_get_previous_sibling ( WEBKIT_DOM_NODE (element)); node = webkit_dom_node_get_last_child (node); while (node && is_citation_node (node)) node = webkit_dom_node_get_last_child (node); if (node) remove_node (webkit_dom_node_get_last_child (node)); } if (!html_mode) { WebKitDOMNode *next_sibling; next_sibling = webkit_dom_node_get_next_sibling ( WEBKIT_DOM_NODE (element)); if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (next_sibling)) { gint citation_level, length; gint word_wrap_length = e_html_editor_selection_get_word_wrap_length (selection); WebKitDOMNode *node; node = webkit_dom_node_get_first_child (next_sibling); while (node && is_citation_node (node)) node = webkit_dom_node_get_first_child (node); citation_level = get_citation_level (node, FALSE); length = word_wrap_length - 2 * citation_level; /* Rewrap and requote first block after the newly inserted line */ if (node && WEBKIT_DOM_IS_ELEMENT (node)) { remove_quoting_from_element (WEBKIT_DOM_ELEMENT (node)); remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (node)); node = WEBKIT_DOM_NODE (e_html_editor_selection_wrap_paragraph_length ( selection, WEBKIT_DOM_ELEMENT (node), length)); quote_plain_text_element_after_wrapping ( document, WEBKIT_DOM_ELEMENT (node), citation_level); } e_html_editor_view_force_spell_check (view); } } caret = e_html_editor_selection_get_caret_position_node (document); paragraph = e_html_editor_selection_get_paragraph_element ( selection, document, -1, 0); if (html_to_insert && *html_to_insert) webkit_dom_html_element_set_inner_html ( WEBKIT_DOM_HTML_ELEMENT (paragraph), html_to_insert, NULL); else webkit_dom_html_element_set_inner_text ( WEBKIT_DOM_HTML_ELEMENT (paragraph), UNICODE_ZERO_WIDTH_SPACE, NULL); webkit_dom_node_append_child (WEBKIT_DOM_NODE (paragraph), caret, NULL); webkit_dom_node_insert_before ( webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)), WEBKIT_DOM_NODE (paragraph), WEBKIT_DOM_NODE (element), NULL); remove_node (WEBKIT_DOM_NODE (element)); e_html_editor_selection_restore_caret_position (selection); return paragraph; } static void body_input_event_cb (WebKitDOMElement *element, WebKitDOMEvent *event, EHTMLEditorView *view) { WebKitDOMNode *node; WebKitDOMRange *range = html_editor_view_get_dom_range (view); e_html_editor_view_set_changed (view, TRUE); node = webkit_dom_range_get_end_container (range, NULL); /* After toggling monospaced format, we are using UNICODE_ZERO_WIDTH_SPACE * to move caret into right space. When this callback is called it is not * necassary anymore so remove it */ if (e_html_editor_view_get_html_mode (view)) { WebKitDOMElement *parent = webkit_dom_node_get_parent_element (node); if (parent) { WebKitDOMNode *prev_sibling; prev_sibling = webkit_dom_node_get_previous_sibling ( WEBKIT_DOM_NODE (parent)); if (prev_sibling && WEBKIT_DOM_IS_TEXT (prev_sibling)) { gchar *text = webkit_dom_node_get_text_content ( prev_sibling); if (g_strcmp0 (text, UNICODE_ZERO_WIDTH_SPACE) == 0) remove_node (prev_sibling); g_free (text); } } } /* If text before caret includes UNICODE_ZERO_WIDTH_SPACE character, remove it */ if (WEBKIT_DOM_IS_TEXT (node)) { gchar *text = webkit_dom_character_data_get_data (WEBKIT_DOM_CHARACTER_DATA (node)); glong length = g_utf8_strlen (text, -1); WebKitDOMNode *parent; /* We have to preserve empty paragraphs with just UNICODE_ZERO_WIDTH_SPACE * character as when we will remove it it will collapse */ if (length > 1) { if (g_str_has_prefix (text, UNICODE_ZERO_WIDTH_SPACE)) webkit_dom_character_data_replace_data ( WEBKIT_DOM_CHARACTER_DATA (node), 0, 1, "", NULL); else if (g_str_has_suffix (text, UNICODE_ZERO_WIDTH_SPACE)) webkit_dom_character_data_replace_data ( WEBKIT_DOM_CHARACTER_DATA (node), length - 1, 1, "", NULL); } g_free (text); parent = webkit_dom_node_get_parent_node (node); if ((WEBKIT_DOM_IS_HTML_PARAGRAPH_ELEMENT (parent) || WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent)) && !element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-paragraph")) { if (e_html_editor_view_get_html_mode (view)) { element_add_class ( WEBKIT_DOM_ELEMENT (parent), "-x-evo-paragraph"); } else { e_html_editor_selection_set_paragraph_style ( e_html_editor_view_get_selection (view), WEBKIT_DOM_ELEMENT (parent), -1, 0, ""); } } /* When new smiley is added we have to use UNICODE_HIDDEN_SPACE to set the * caret position to right place. It is removed when user starts typing. But * when the user will press left arrow he will move the caret into * smiley wrapper. If he will start to write there we have to move the written * text out of the wrapper and move caret to right place */ if (WEBKIT_DOM_IS_ELEMENT (parent) && element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-smiley-wrapper")) { WebKitDOMDocument *document; document = webkit_web_view_get_dom_document ( WEBKIT_WEB_VIEW (view)); webkit_dom_node_insert_before ( webkit_dom_node_get_parent_node (parent), e_html_editor_selection_get_caret_position_node ( document), webkit_dom_node_get_next_sibling (parent), NULL); webkit_dom_node_insert_before ( webkit_dom_node_get_parent_node (parent), node, webkit_dom_node_get_next_sibling (parent), NULL); e_html_editor_selection_restore_caret_position ( e_html_editor_view_get_selection (view)); } } /* Writing into quoted content */ if (!view->priv->html_mode) { gint citation_level, length, word_wrap_length; EHTMLEditorSelection *selection; WebKitDOMElement *element; WebKitDOMDocument *document; WebKitDOMNode *parent; document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); selection = e_html_editor_view_get_selection (view); word_wrap_length = e_html_editor_selection_get_word_wrap_length (selection); element = webkit_dom_document_query_selector ( document, "span#-x-evo-selection-start-marker", NULL); if (element) return; e_html_editor_selection_save (selection); element = webkit_dom_document_query_selector ( document, "span#-x-evo-selection-start-marker", NULL); /* If the selection was not saved, move it into the first child of body */ if (!element) { WebKitDOMHTMLElement *body; body = webkit_dom_document_get_body (document); element = webkit_dom_document_create_element ( document, "SPAN", NULL); webkit_dom_element_set_id ( element, "-x-evo-selection-end-marker"); webkit_dom_node_insert_before ( webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)), WEBKIT_DOM_NODE (element), webkit_dom_node_get_first_child ( webkit_dom_node_get_first_child ( WEBKIT_DOM_NODE (body))), NULL); element = webkit_dom_document_create_element ( document, "SPAN", NULL); webkit_dom_element_set_id ( element, "-x-evo-selection-start-marker"); webkit_dom_node_insert_before ( webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)), WEBKIT_DOM_NODE (element), webkit_dom_node_get_first_child ( webkit_dom_node_get_first_child ( WEBKIT_DOM_NODE (body))), NULL); } /* We have to process elements only inside normal block */ parent = WEBKIT_DOM_NODE (get_parent_block_element (WEBKIT_DOM_NODE (element))); if (WEBKIT_DOM_IS_HTML_PRE_ELEMENT (parent)) { e_html_editor_selection_restore (selection); return; } citation_level = get_citation_level (WEBKIT_DOM_NODE (element), FALSE); length = word_wrap_length - 2 * citation_level; if (element && citation_level > 0) { gchar *content; gint text_length; WebKitDOMElement *block; gboolean remove_quoting = FALSE; block = webkit_dom_node_get_parent_element ( WEBKIT_DOM_NODE (element)); if (webkit_dom_element_query_selector ( WEBKIT_DOM_ELEMENT (block), ".-x-evo-quoted", NULL)) { WebKitDOMNode *prev_sibling; WebKitDOMElement *selection_end_marker; selection_end_marker = webkit_dom_document_query_selector ( document, "span#-x-evo-selection-end-marker", NULL); prev_sibling = webkit_dom_node_get_previous_sibling ( WEBKIT_DOM_NODE (selection_end_marker)); if (WEBKIT_DOM_IS_ELEMENT (prev_sibling)) remove_quoting = element_has_class ( WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-quoted"); } if (element_has_class (block, "-x-evo-temp-text-wrapper")) block = webkit_dom_node_get_parent_element ( WEBKIT_DOM_NODE (block)); content = webkit_dom_node_get_text_content (WEBKIT_DOM_NODE (block)); text_length = g_utf8_strlen (content, -1); g_free (content); /* Wrap and quote the line */ if (!remove_quoting && text_length >= word_wrap_length) { remove_quoting_from_element (block); block = e_html_editor_selection_wrap_paragraph_length ( selection, block, length); webkit_dom_node_normalize (WEBKIT_DOM_NODE (block)); quote_plain_text_element_after_wrapping ( document, WEBKIT_DOM_ELEMENT (block), citation_level); element = webkit_dom_document_query_selector ( document, "span#-x-evo-selection-start-marker", NULL); if (!element) { WebKitDOMElement *marker; marker = webkit_dom_document_create_element ( document, "SPAN", NULL); webkit_dom_element_set_id ( marker, "-x-evo-selection-start-marker"); webkit_dom_node_append_child ( WEBKIT_DOM_NODE (block), WEBKIT_DOM_NODE (marker), NULL); marker = webkit_dom_document_create_element ( document, "SPAN", NULL); webkit_dom_element_set_id ( marker, "-x-evo-selection-end-marker"); webkit_dom_node_append_child ( WEBKIT_DOM_NODE (block), WEBKIT_DOM_NODE (marker), NULL); } e_html_editor_selection_restore (selection); e_html_editor_view_force_spell_check_for_current_paragraph (view); return; } } e_html_editor_selection_restore (selection); } } static void set_base64_to_element_attribute (EHTMLEditorView *view, WebKitDOMElement *element, const gchar *attribute) { gchar *attribute_value; const gchar *base64_src; attribute_value = webkit_dom_element_get_attribute (element, attribute); if ((base64_src = g_hash_table_lookup (view->priv->inline_images, attribute_value)) != 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 (element, "data-inline", "", NULL); webkit_dom_element_set_attribute (element, "data-name", name, NULL); webkit_dom_element_set_attribute (element, attribute, base64_data, NULL); g_free (name); } } static void change_cid_images_src_to_base64 (EHTMLEditorView *view) { gint ii, length; WebKitDOMDocument *document; WebKitDOMElement *document_element; WebKitDOMNamedNodeMap *attributes; WebKitDOMNodeList *list; document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); document_element = webkit_dom_document_get_document_element (document); 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); set_base64_to_element_attribute (view, WEBKIT_DOM_ELEMENT (node), "src"); } /* Namespaces */ attributes = webkit_dom_element_get_attributes (document_element); length = webkit_dom_named_node_map_get_length (attributes); for (ii = 0; ii < length; ii++) { gchar *name; WebKitDOMNode *node = webkit_dom_named_node_map_item (attributes, ii); name = webkit_dom_node_get_local_name (node); if (g_str_has_prefix (name, "xmlns:")) { const gchar *ns = name + 6; gchar *attribute_ns = g_strconcat (ns, ":src", NULL); gchar *selector = g_strconcat ("img[", ns, "\\:src^=\"cid:\"]", NULL); gint ns_length, jj; list = webkit_dom_document_query_selector_all ( document, selector, NULL); ns_length = webkit_dom_node_list_get_length (list); for (jj = 0; jj < ns_length; jj++) { WebKitDOMNode *node = webkit_dom_node_list_item (list, jj); set_base64_to_element_attribute ( view, WEBKIT_DOM_ELEMENT (node), attribute_ns); } g_free (attribute_ns); g_free (selector); } g_free (name); } list = webkit_dom_document_query_selector_all ( document, "[background^=\"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); set_base64_to_element_attribute ( view, WEBKIT_DOM_ELEMENT (node), "background"); } g_hash_table_remove_all (view->priv->inline_images); } /* For purpose of this function see e-mail-formatter-quote.c */ static void put_body_in_citation (WebKitDOMDocument *document) { WebKitDOMElement *cite_body = webkit_dom_document_query_selector ( document, "span.-x-evo-cite-body", NULL); if (cite_body) { WebKitDOMHTMLElement *body = webkit_dom_document_get_body (document); gchar *inner_html, *with_citation; remove_node (WEBKIT_DOM_NODE (cite_body)); inner_html = webkit_dom_html_element_get_inner_html (body); with_citation = g_strconcat ( "
", inner_html, "", NULL); webkit_dom_html_element_set_inner_html (body, with_citation, NULL); g_free (inner_html); g_free (with_citation); } } /* For purpose of this function see e-mail-formatter-quote.c */ static void move_elements_to_body (WebKitDOMDocument *document) { WebKitDOMHTMLElement *body = webkit_dom_document_get_body (document); WebKitDOMNodeList *list; gint ii; list = webkit_dom_document_query_selector_all ( document, "span.-x-evo-to-body", NULL); for (ii = webkit_dom_node_list_get_length (list) - 1; ii >= 0; ii--) { WebKitDOMNode *node = webkit_dom_node_list_item (list, ii); while (webkit_dom_node_has_child_nodes (node)) { webkit_dom_node_insert_before ( WEBKIT_DOM_NODE (body), webkit_dom_node_get_first_child (node), webkit_dom_node_get_first_child ( WEBKIT_DOM_NODE (body)), NULL); } remove_node (node); } } static void repair_gmail_blockquotes (WebKitDOMDocument *document) { WebKitDOMNodeList *list; gint ii, length; list = webkit_dom_document_query_selector_all ( document, "blockquote.gmail_quote", NULL); length = webkit_dom_node_list_get_length (list); for (ii = 0; ii < length; ii++) { WebKitDOMNode *node = webkit_dom_node_list_item (list, ii); webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "class"); webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "style"); webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "type", "cite", NULL); } } static void remove_input_event_listener_from_body (EHTMLEditorView *view) { if (!view->priv->body_input_event_removed) { WebKitDOMDocument *document; document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); webkit_dom_event_target_remove_event_listener ( WEBKIT_DOM_EVENT_TARGET ( webkit_dom_document_get_body (document)), "input", G_CALLBACK (body_input_event_cb), FALSE); view->priv->body_input_event_removed = TRUE; } } static void register_input_event_listener_on_body (EHTMLEditorView *view) { if (view->priv->body_input_event_removed) { WebKitDOMDocument *document; document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); webkit_dom_event_target_add_event_listener ( WEBKIT_DOM_EVENT_TARGET ( webkit_dom_document_get_body (document)), "input", G_CALLBACK (body_input_event_cb), FALSE, view); view->priv->body_input_event_removed = FALSE; } } static void html_editor_view_load_status_changed (EHTMLEditorView *view) { WebKitDOMDocument *document; WebKitDOMHTMLElement *body; WebKitLoadStatus status; status = webkit_web_view_get_load_status (WEBKIT_WEB_VIEW (view)); if (status != WEBKIT_LOAD_FINISHED) return; view->priv->reload_in_progress = FALSE; document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); body = webkit_dom_document_get_body (document); webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (body), "style"); webkit_dom_element_set_attribute ( WEBKIT_DOM_ELEMENT (body), "data-message", "", NULL); put_body_in_citation (document); move_elements_to_body (document); repair_gmail_blockquotes (document); /* Register on input event that is called when the content (body) is modified */ register_input_event_listener_on_body (view); if (view->priv->html_mode) change_cid_images_src_to_base64 (view); if (!view->priv->inline_spelling) e_html_editor_view_turn_spell_check_off (view); } /* Based on original use_pictograms() from GtkHTML */ static const gchar *emoticons_chars = /* 0 */ "DO)(|/PQ*!" /* 10 */ "S\0:-\0:\0:-\0" /* 20 */ ":\0:;=-\"\0:;" /* 30 */ "B\"|\0:-'\0:X" /* 40 */ "\0:\0:-\0:\0:-" /* 50 */ "\0:\0:-\0:\0:-" /* 60 */ "\0:\0:\0:-\0:\0" /* 70 */ ":-\0:\0:-\0:\0"; static gint emoticons_states[] = { /* 0 */ 12, 17, 22, 34, 43, 48, 53, 58, 65, 70, /* 10 */ 75, 0, -15, 15, 0, -15, 0, -17, 20, 0, /* 20 */ -17, 0, -14, -20, -14, 28, 63, 0, -14, -20, /* 30 */ -3, 63, -18, 0, -12, 38, 41, 0, -12, -2, /* 40 */ 0, -4, 0, -10, 46, 0, -10, 0, -19, 51, /* 50 */ 0, -19, 0, -11, 56, 0, -11, 0, -13, 61, /* 60 */ 0, -13, 0, -6, 0, 68, -7, 0, -7, 0, /* 70 */ -16, 73, 0, -16, 0, -21, 78, 0, -21, 0 }; static const gchar *emoticons_icon_names[] = { "face-angel", "face-angry", "face-cool", "face-crying", "face-devilish", "face-embarrassed", "face-kiss", "face-laugh", /* not used */ "face-monkey", /* not used */ "face-plain", "face-raspberry", "face-sad", "face-sick", "face-smile", "face-smile-big", "face-smirk", "face-surprise", "face-tired", "face-uncertain", "face-wink", "face-worried" }; static gboolean is_return_key (GdkEventKey *event) { return ( (event->keyval == GDK_KEY_Return) || (event->keyval == GDK_KEY_Linefeed) || (event->keyval == GDK_KEY_KP_Enter)); } static void html_editor_view_check_magic_links (EHTMLEditorView *view, WebKitDOMRange *range, gboolean include_space_by_user, GdkEventKey *event) { gchar *node_text; gchar **urls; GRegex *regex = NULL; GMatchInfo *match_info; gint start_pos_url, end_pos_url; WebKitDOMNode *node; gboolean include_space = FALSE; gboolean return_pressed = FALSE; if (event != NULL) { return_pressed = is_return_key (event); include_space = (event->keyval == GDK_KEY_space); } else { include_space = include_space_by_user; } node = webkit_dom_range_get_end_container (range, NULL); if (return_pressed) node = webkit_dom_node_get_previous_sibling (node); if (!node) return; if (!WEBKIT_DOM_IS_TEXT (node)) { if (webkit_dom_node_has_child_nodes (node)) node = webkit_dom_node_get_first_child (node); if (!WEBKIT_DOM_IS_TEXT (node)) return; } node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node)); if (!node_text || !(*node_text) || !g_utf8_validate (node_text, -1, NULL)) return; regex = g_regex_new (include_space ? URL_PATTERN_SPACE : URL_PATTERN, 0, 0, NULL); if (!regex) { g_free (node_text); return; } g_regex_match_all (regex, node_text, G_REGEX_MATCH_NOTEMPTY, &match_info); urls = g_match_info_fetch_all (match_info); if (urls) { gchar *final_url, *url_end_raw; glong url_start, url_end, url_length; WebKitDOMDocument *document; WebKitDOMNode *url_text_node_clone; WebKitDOMText *url_text_node; WebKitDOMElement *anchor; const gchar* url_text; document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); if (!return_pressed) e_html_editor_selection_save_caret_position ( e_html_editor_view_get_selection (view)); g_match_info_fetch_pos (match_info, 0, &start_pos_url, &end_pos_url); /* Get start and end position of url in node's text because positions * that we get from g_match_info_fetch_pos are not UTF-8 aware */ url_end_raw = g_strndup(node_text, end_pos_url); url_end = g_utf8_strlen (url_end_raw, -1); url_length = g_utf8_strlen (urls[0], -1); url_start = url_end - url_length; webkit_dom_text_split_text ( WEBKIT_DOM_TEXT (node), include_space ? url_end - 1 : url_end, NULL); url_text_node = webkit_dom_text_split_text ( WEBKIT_DOM_TEXT (node), url_start, NULL); url_text_node_clone = webkit_dom_node_clone_node ( WEBKIT_DOM_NODE (url_text_node), TRUE); url_text = webkit_dom_text_get_whole_text ( WEBKIT_DOM_TEXT (url_text_node_clone)); final_url = g_strconcat ( g_str_has_prefix (url_text, "www") ? "http://" : "", url_text, NULL); /* Create and prepare new anchor element */ anchor = webkit_dom_document_create_element (document, "A", NULL); webkit_dom_html_element_set_inner_html ( WEBKIT_DOM_HTML_ELEMENT (anchor), url_text, NULL); webkit_dom_html_anchor_element_set_href ( WEBKIT_DOM_HTML_ANCHOR_ELEMENT (anchor), final_url); /* Insert new anchor element into document */ webkit_dom_node_replace_child ( webkit_dom_node_get_parent_node (node), WEBKIT_DOM_NODE (anchor), WEBKIT_DOM_NODE (url_text_node), NULL); if (!return_pressed) e_html_editor_selection_restore_caret_position ( e_html_editor_view_get_selection (view)); g_free (url_end_raw); g_free (final_url); } else { WebKitDOMElement *parent; WebKitDOMNode *prev_sibling; gchar *href, *text, *url; gint diff; const char* text_to_append; gboolean appending_to_link = FALSE; parent = webkit_dom_node_get_parent_element (node); prev_sibling = webkit_dom_node_get_previous_sibling (node); /* If previous sibling is ANCHOR and actual text node is not beginning with * space => we're appending to link */ if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling)) { text_to_append = webkit_dom_node_get_text_content (node); if (g_strcmp0 (text_to_append, "") != 0 && !g_unichar_isspace (g_utf8_get_char (text_to_append))) { appending_to_link = TRUE; parent = WEBKIT_DOM_ELEMENT (prev_sibling); } } /* If parent is ANCHOR => we're editing the link */ if (!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent) && !appending_to_link) { g_match_info_free (match_info); g_regex_unref (regex); g_free (node_text); return; } /* edit only if href and description are the same */ href = webkit_dom_html_anchor_element_get_href ( WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent)); if (appending_to_link) { gchar *inner_text; inner_text = webkit_dom_html_element_get_inner_text ( WEBKIT_DOM_HTML_ELEMENT (parent)), text = g_strconcat (inner_text, text_to_append, NULL); g_free (inner_text); } else text = webkit_dom_html_element_get_inner_text ( WEBKIT_DOM_HTML_ELEMENT (parent)); if (strstr (href, "://") && !strstr (text, "://")) { url = strstr (href, "://") + 3; diff = strlen (text) - strlen (url); if (text [strlen (text) - 1] != '/') diff++; if ((g_strcmp0 (url, text) != 0 && ABS (diff) == 1) || appending_to_link) { gchar *inner_html, *protocol, *new_href; protocol = g_strndup (href, strstr (href, "://") - href + 3); inner_html = webkit_dom_html_element_get_inner_html ( WEBKIT_DOM_HTML_ELEMENT (parent)); new_href = g_strconcat ( protocol, inner_html, appending_to_link ? text_to_append : "", NULL); webkit_dom_html_anchor_element_set_href ( WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent), new_href); if (appending_to_link) { gchar *tmp; tmp = g_strconcat (inner_html, text_to_append, NULL); webkit_dom_html_element_set_inner_html ( WEBKIT_DOM_HTML_ELEMENT (parent), tmp, NULL); remove_node (node); g_free (tmp); } g_free (new_href); g_free (protocol); g_free (inner_html); } } else { diff = strlen (text) - strlen (href); if (text [strlen (text) - 1] != '/') diff++; if ((g_strcmp0 (href, text) != 0 && ABS (diff) == 1) || appending_to_link) { gchar *inner_html; gchar *new_href; inner_html = webkit_dom_html_element_get_inner_html ( WEBKIT_DOM_HTML_ELEMENT (parent)); new_href = g_strconcat ( inner_html, appending_to_link ? text_to_append : "", NULL); webkit_dom_html_anchor_element_set_href ( WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent), new_href); if (appending_to_link) { gchar *tmp; tmp = g_strconcat (inner_html, text_to_append, NULL); webkit_dom_html_element_set_inner_html ( WEBKIT_DOM_HTML_ELEMENT (parent), tmp, NULL); remove_node (node); g_free (tmp); } g_free (new_href); g_free (inner_html); } } g_free (text); g_free (href); } g_match_info_free (match_info); g_regex_unref (regex); g_free (node_text); } typedef struct _LoadContext LoadContext; struct _LoadContext { EHTMLEditorView *view; gchar *content_type; gchar *name; EEmoticon *emoticon; }; static LoadContext * emoticon_load_context_new (EHTMLEditorView *view, EEmoticon *emoticon) { LoadContext *load_context; load_context = g_slice_new0 (LoadContext); load_context->view = view; 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) { EHTMLEditorView *view = load_context->view; EEmoticon *emoticon = load_context->emoticon; GError *error = NULL; gboolean misplaced_selection = FALSE, empty = FALSE; gchar *html, *node_text = NULL, *mime_type, *content; gchar *base64_encoded, *output, *data; const gchar *emoticon_start; GFileInputStream *input_stream; GOutputStream *output_stream; gssize size; WebKitDOMDocument *document; WebKitDOMElement *span, *selection_start_marker, *selection_end_marker; WebKitDOMNode *node, *insert_before, *prev_sibling, *next_sibling; WebKitDOMNode *selection_end_marker_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_html_editor_selection_save (e_html_editor_view_get_selection (view)); document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); selection_start_marker = webkit_dom_document_get_element_by_id ( document, "-x-evo-selection-start-marker"); selection_end_marker = webkit_dom_document_get_element_by_id ( document, "-x-evo-selection-end-marker"); /* If the selection was not saved, move it into the first child of body */ if (!selection_start_marker || !selection_end_marker) { WebKitDOMHTMLElement *body; body = webkit_dom_document_get_body (document); selection_start_marker = webkit_dom_document_create_element ( document, "SPAN", NULL); webkit_dom_element_set_id ( selection_start_marker, "-x-evo-selection-start-marker"); webkit_dom_node_insert_before ( webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)), WEBKIT_DOM_NODE (selection_start_marker), webkit_dom_node_get_first_child ( webkit_dom_node_get_first_child ( WEBKIT_DOM_NODE (body))), NULL); selection_end_marker = webkit_dom_document_create_element ( document, "SPAN", NULL); webkit_dom_element_set_id ( selection_end_marker, "-x-evo-selection-end-marker"); webkit_dom_node_insert_before ( webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)), WEBKIT_DOM_NODE (selection_end_marker), webkit_dom_node_get_first_child ( webkit_dom_node_get_first_child ( WEBKIT_DOM_NODE (body))), NULL); } /* Sometimes selection end marker is in body. Move it into next sibling */ selection_end_marker_parent = webkit_dom_node_get_parent_node ( WEBKIT_DOM_NODE (selection_end_marker)); if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (selection_end_marker_parent)) { webkit_dom_node_insert_before ( webkit_dom_node_get_parent_node ( WEBKIT_DOM_NODE (selection_start_marker)), WEBKIT_DOM_NODE (selection_end_marker), WEBKIT_DOM_NODE (selection_start_marker), NULL); } selection_end_marker_parent = webkit_dom_node_get_parent_node ( WEBKIT_DOM_NODE (selection_end_marker)); /* Determine before what node we have to insert the smiley */ insert_before = WEBKIT_DOM_NODE (selection_start_marker); prev_sibling = webkit_dom_node_get_previous_sibling ( WEBKIT_DOM_NODE (selection_start_marker)); if (prev_sibling) { if (webkit_dom_node_is_same_node ( prev_sibling, WEBKIT_DOM_NODE (selection_end_marker))) { insert_before = WEBKIT_DOM_NODE (selection_end_marker); } else { prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling); if (prev_sibling && webkit_dom_node_is_same_node ( prev_sibling, WEBKIT_DOM_NODE (selection_end_marker))) { insert_before = WEBKIT_DOM_NODE (selection_end_marker); } } } else insert_before = WEBKIT_DOM_NODE (selection_start_marker); /* Look if selection is misplaced - that means that the selection was * restored before the previously inserted smiley in situations when we * are writing more smileys in a row */ next_sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_end_marker)); if (next_sibling && WEBKIT_DOM_IS_ELEMENT (next_sibling)) if (element_has_class (WEBKIT_DOM_ELEMENT (next_sibling), "-x-evo-smiley-wrapper")) misplaced_selection = TRUE; mime_type = g_content_type_get_mime_type (load_context->content_type); range = html_editor_view_get_dom_range (view); node = webkit_dom_range_get_end_container (range, NULL); if (WEBKIT_DOM_IS_TEXT (node)) node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node)); 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); content = webkit_dom_node_get_text_content (selection_end_marker_parent); empty = !*content || (g_strcmp0 (content, UNICODE_ZERO_WIDTH_SPACE) == 0); g_free (content); /* Insert span with image representation and another one with text * represetation and hide/show them dependant on active composer mode */ /* ​ == UNICODE_ZERO_WIDTH_SPACE */ html = g_strdup_printf ( "" "\"%s\"" "%s" "%s", output, emoticon ? emoticon->text_face : "", emoticon->icon_name, load_context->name, emoticon ? emoticon->text_face : "", empty ? "​" : ""); span = webkit_dom_document_create_element (document, "SPAN", NULL); if (misplaced_selection) { /* Insert smiley and selection markers after it */ webkit_dom_node_insert_before ( webkit_dom_node_get_parent_node (insert_before), WEBKIT_DOM_NODE (selection_start_marker), webkit_dom_node_get_next_sibling (next_sibling), NULL); webkit_dom_node_insert_before ( webkit_dom_node_get_parent_node (insert_before), WEBKIT_DOM_NODE (selection_end_marker), webkit_dom_node_get_next_sibling (next_sibling), NULL); span = WEBKIT_DOM_ELEMENT ( webkit_dom_node_insert_before ( webkit_dom_node_get_parent_node (insert_before), WEBKIT_DOM_NODE (span), webkit_dom_node_get_next_sibling (next_sibling), NULL)); } else { span = WEBKIT_DOM_ELEMENT ( webkit_dom_node_insert_before ( webkit_dom_node_get_parent_node (insert_before), WEBKIT_DOM_NODE (span), insert_before, NULL)); } webkit_dom_html_element_set_outer_html ( WEBKIT_DOM_HTML_ELEMENT (span), html, NULL); if (node_text) { 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_html_editor_selection_restore ( e_html_editor_view_get_selection (view)); e_html_editor_view_set_changed (view, TRUE); g_free (html); 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_html_editor_view_insert_smiley (EHTMLEditorView *view, 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 (view, 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 html_editor_view_check_magic_smileys (EHTMLEditorView *view, WebKitDOMRange *range) { gint pos; gint state; gint relative; gint start; gchar *node_text; gunichar uc; WebKitDOMNode *node; node = webkit_dom_range_get_end_container (range, NULL); if (!WEBKIT_DOM_IS_TEXT (node)) return; node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node)); if (node_text == NULL) return; start = webkit_dom_range_get_end_offset (range, NULL) - 1; pos = start; state = 0; while (pos >= 0) { uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos)); relative = 0; while (emoticons_chars[state + relative]) { if (emoticons_chars[state + relative] == uc) break; relative++; } state = emoticons_states[state + relative]; /* 0 .. not found, -n .. found n-th */ if (state <= 0) break; pos--; } /* Special case needed to recognize angel and devilish. */ if (pos > 0 && state == -14) { uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos - 1)); if (uc == 'O') { state = -1; pos--; } else if (uc == '>') { state = -5; pos--; } } if (state < 0) { const EEmoticon *emoticon; if (pos > 0) { uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos - 1)); if (!g_unichar_isspace (uc)) { g_free (node_text); return; } } emoticon = (e_emoticon_chooser_lookup_emoticon ( emoticons_icon_names[-state - 1])); e_html_editor_view_insert_smiley (view, (EEmoticon *) emoticon); } g_free (node_text); } static void html_editor_view_set_links_active (EHTMLEditorView *view, gboolean active) { WebKitDOMDocument *document; WebKitDOMElement *style; document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); if (active) { style = webkit_dom_document_get_element_by_id ( document, "--evolution-editor-style-a"); if (style) remove_node (WEBKIT_DOM_NODE (style)); } else { WebKitDOMHTMLHeadElement *head; head = webkit_dom_document_get_head (document); style = webkit_dom_document_create_element (document, "STYLE", NULL); webkit_dom_element_set_id (style, "--evolution-editor-style-a"); webkit_dom_html_element_set_inner_text ( WEBKIT_DOM_HTML_ELEMENT (style), "a { cursor: text; }", NULL); webkit_dom_node_append_child ( WEBKIT_DOM_NODE (head), WEBKIT_DOM_NODE (style), NULL); } } static void clipboard_text_received_for_paste_as_text (GtkClipboard *clipboard, const gchar *text, EHTMLEditorView *view) { EHTMLEditorSelection *selection; if (!text || !*text) return; selection = e_html_editor_view_get_selection (view); e_html_editor_selection_insert_as_text (selection, text); } static void clipboard_text_received (GtkClipboard *clipboard, const gchar *text, EHTMLEditorView *view) { EHTMLEditorSelection *selection; gchar *escaped_text; WebKitDOMDocument *document; WebKitDOMDOMWindow *window; WebKitDOMDOMSelection *dom_selection; WebKitDOMElement *blockquote, *element; WebKitDOMNode *node; WebKitDOMRange *range; if (!text || !*text) return; selection = e_html_editor_view_get_selection (view); document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); window = webkit_dom_document_get_default_view (document); dom_selection = webkit_dom_dom_window_get_selection (window); /* This is a trick to escape any HTML characters (like <, > or &). *