/*
* 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) &&
!WEBKIT_DOM_IS_HTML_HEADING_ELEMENT (parent) &&
!element_has_tag (parent, "address")) {
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_object_unref (list);
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 *prev_sibling;
prev_sibling = webkit_dom_node_get_previous_sibling (node);
if (!prev_sibling)
return webkit_dom_range_get_collapsed (range, NULL);
}
}
return FALSE;
}
static WebKitDOMNode *
get_parent_block_node_from_child (WebKitDOMNode *node)
{
WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node);
if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-temp-text-wrapper") ||
WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent) ||
element_has_tag (WEBKIT_DOM_ELEMENT (parent), "b") ||
element_has_tag (WEBKIT_DOM_ELEMENT (parent), "i") ||
element_has_tag (WEBKIT_DOM_ELEMENT (parent), "u"))
parent = webkit_dom_node_get_parent_node (parent);
return parent;
}
static WebKitDOMElement *
insert_new_line_into_citation (EHTMLEditorView *view,
const gchar *html_to_insert)
{
gboolean html_mode, ret_val, avoid_editor_call;
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));
avoid_editor_call =
return_pressed_in_empty_line (selection, document);
if (avoid_editor_call) {
WebKitDOMElement *selection_start_marker;
WebKitDOMNode *current_block, *parent, *parent_block, *block_clone;
e_html_editor_selection_save (selection);
selection_start_marker = webkit_dom_document_get_element_by_id (
document, "-x-evo-selection-start-marker");
current_block = get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
block_clone = webkit_dom_node_clone_node (current_block, TRUE);
/* Find selection start marker and restore it after the new line
* is inserted */
selection_start_marker = webkit_dom_element_query_selector (
WEBKIT_DOM_ELEMENT (block_clone), "#-x-evo-selection-start-marker", NULL);
/* Find parent node that is immediate child of the BODY */
/* Build the same structure of parent nodes of the current block */
parent_block = current_block;
parent = webkit_dom_node_get_parent_node (parent_block);
while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
WebKitDOMNode *node;
parent_block = parent;
node = webkit_dom_node_clone_node (parent_block, FALSE);
webkit_dom_node_append_child (node, block_clone, NULL);
block_clone = node;
parent = webkit_dom_node_get_parent_node (parent_block);
}
paragraph = e_html_editor_selection_get_paragraph_element (
selection, document, -1, 0);
webkit_dom_html_element_set_inner_text (
WEBKIT_DOM_HTML_ELEMENT (paragraph),
UNICODE_ZERO_WIDTH_SPACE,
NULL);
/* Insert the selection markers to right place */
webkit_dom_node_insert_before (
WEBKIT_DOM_NODE (paragraph),
webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_start_marker)),
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (paragraph)),
NULL);
webkit_dom_node_insert_before (
WEBKIT_DOM_NODE (paragraph),
WEBKIT_DOM_NODE (selection_start_marker),
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (paragraph)),
NULL);
/* Insert the cloned nodes before the BODY parent node */
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (parent_block),
block_clone,
parent_block,
NULL);
/* Insert the new empty paragraph before the BODY parent node */
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (parent_block),
WEBKIT_DOM_NODE (paragraph),
parent_block,
NULL);
/* Remove the old block (its copy was moved to the right place) */
remove_node (current_block);
e_html_editor_selection_restore (selection);
return NULL;
} else {
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 (!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_ELEMENT (parent);
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");
}
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");
}
g_object_unref (list);
/* 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_object_unref (list);
g_free (attribute_ns);
g_free (selector);
}
g_free (name);
}
g_object_unref (attributes);
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_object_unref (list);
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);
}
g_object_unref (list);
}
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);
}
g_object_unref (list);
}
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);
if (webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (body), "data-evo-draft")) {
/* Restore the selection how it was when the draft was saved */
e_html_editor_selection_move_caret_into_element (
document, WEBKIT_DOM_ELEMENT (body));
e_html_editor_selection_restore (
e_html_editor_view_get_selection (view));
}
/* 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_force_spell_check (view);
else
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 = get_parent_block_node_from_child (
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",
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 &).
*