Files
evolution/e-util/e-html-editor-view.c
Tomas Popela dfd1a6cb50 EHTMLEditor - Avoid asking for loosing the formatting if replying from a selection
Add a special header to the newly created message from the selection
text and later mark the flag in EHTMLEditorView to avoid showing the
dialog for loosing the formatting. Do something similar for messages
from drafts to avoid searching for string inside the message's source.
2014-10-16 08:28:58 +02:00

7996 lines
226 KiB
C

/*
* e-html-editor-view.c
*
* Copyright (C) 2012 Dan Vrátil <dvratil@redhat.com>
*
* 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 <http://www.gnu.org/licenses/>
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "e-html-editor-view.h"
#include "e-html-editor.h"
#include "e-emoticon-chooser.h"
#include <e-util/e-util.h>
#include <e-util/e-marshal.h>
#include <glib/gi18n-lib.h>
#include <gdk/gdkkeysyms.h>
#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;
gboolean is_message_from_draft;
gboolean is_message_from_selection;
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, "<span class=\"-x-evo-quote-character\">");
g_string_append (output, QUOTE_SYMBOL);
g_string_append (output, " ");
g_string_append (output, "</span>");
}
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 == <blockquote type='cite'> */
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 (view->priv->html_mode) {
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;
EHTMLEditorSelection *selection;
WebKitDOMDocument *document;
WebKitDOMElement *element;
WebKitDOMNode *node, *parent;
WebKitDOMRange *range;
range = html_editor_view_get_dom_range (view);
node = webkit_dom_range_get_end_container (range, NULL);
citation_level = get_citation_level (node, FALSE);
if (citation_level == 0)
return;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
element = webkit_dom_document_query_selector (
document, "span#-x-evo-selection-start-marker", NULL);
if (element)
return;
selection = e_html_editor_view_get_selection (view);
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;
}
if (element) {
gchar *content;
gint text_length, word_wrap_length, length;
WebKitDOMElement *block;
gboolean remove_quoting = FALSE;
word_wrap_length =
e_html_editor_selection_get_word_wrap_length (selection);
length = word_wrap_length - 2 * citation_level;
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 (
"<blockquote type=\"cite\" id=\"-x-evo-main-cite\">",
inner_html, "</span>", 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 */
/* &#8203 == UNICODE_ZERO_WIDTH_SPACE */
html = g_strdup_printf (
"<span class=\"-x-evo-smiley-wrapper -x-evo-resizable-wrapper\">"
"<img src=\"%s\" alt=\"%s\" x-evo-smiley=\"%s\" "
"class=\"-x-evo-smiley-img\" data-inline data-name=\"%s\"/>"
"<span class=\"-x-evo-smiley-text\" style=\"display: none;\">%s"
"</span></span>%s",
output, emoticon ? emoticon->text_face : "", emoticon->icon_name,
load_context->name, emoticon ? emoticon->text_face : "",
empty ? "&#8203;" : "");
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 &).
* <textarea> automatically replaces all these unsafe characters
* by &lt;, &gt; etc. */
element = webkit_dom_document_create_element (document, "textarea", NULL);
webkit_dom_html_element_set_inner_html (
WEBKIT_DOM_HTML_ELEMENT (element), text, NULL);
escaped_text = webkit_dom_html_element_get_inner_html (
WEBKIT_DOM_HTML_ELEMENT (element));
element = webkit_dom_document_create_element (document, "pre", NULL);
webkit_dom_html_element_set_inner_text (
WEBKIT_DOM_HTML_ELEMENT (element), escaped_text, NULL);
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (element),
e_html_editor_selection_get_caret_position_node (document),
NULL);
blockquote = webkit_dom_document_create_element (document, "blockquote", NULL);
webkit_dom_element_set_attribute (blockquote, "type", "cite", NULL);
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (blockquote), WEBKIT_DOM_NODE (element), NULL);
if (!e_html_editor_view_get_html_mode (view))
e_html_editor_view_quote_plain_text_element (view, element);
range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
node = webkit_dom_range_get_end_container (range, NULL);
webkit_dom_node_append_child (
webkit_dom_node_get_parent_node (node),
WEBKIT_DOM_NODE (blockquote),
NULL);
e_html_editor_selection_restore_caret_position (selection);
e_html_editor_view_force_spell_check_for_current_paragraph (view);
g_free (escaped_text);
}
static void
html_editor_view_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_CHANGED:
e_html_editor_view_set_changed (
E_HTML_EDITOR_VIEW (object),
g_value_get_boolean (value));
return;
case PROP_HTML_MODE:
e_html_editor_view_set_html_mode (
E_HTML_EDITOR_VIEW (object),
g_value_get_boolean (value));
return;
case PROP_INLINE_SPELLING:
e_html_editor_view_set_inline_spelling (
E_HTML_EDITOR_VIEW (object),
g_value_get_boolean (value));
return;
case PROP_MAGIC_LINKS:
e_html_editor_view_set_magic_links (
E_HTML_EDITOR_VIEW (object),
g_value_get_boolean (value));
return;
case PROP_MAGIC_SMILEYS:
e_html_editor_view_set_magic_smileys (
E_HTML_EDITOR_VIEW (object),
g_value_get_boolean (value));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
html_editor_view_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_CAN_COPY:
g_value_set_boolean (
value, webkit_web_view_can_copy_clipboard (
WEBKIT_WEB_VIEW (object)));
return;
case PROP_CAN_CUT:
g_value_set_boolean (
value, webkit_web_view_can_cut_clipboard (
WEBKIT_WEB_VIEW (object)));
return;
case PROP_CAN_PASTE:
g_value_set_boolean (
value, webkit_web_view_can_paste_clipboard (
WEBKIT_WEB_VIEW (object)));
return;
case PROP_CAN_REDO:
g_value_set_boolean (
value, webkit_web_view_can_redo (
WEBKIT_WEB_VIEW (object)));
return;
case PROP_CAN_UNDO:
g_value_set_boolean (
value, webkit_web_view_can_undo (
WEBKIT_WEB_VIEW (object)));
return;
case PROP_CHANGED:
g_value_set_boolean (
value, e_html_editor_view_get_changed (
E_HTML_EDITOR_VIEW (object)));
return;
case PROP_HTML_MODE:
g_value_set_boolean (
value, e_html_editor_view_get_html_mode (
E_HTML_EDITOR_VIEW (object)));
return;
case PROP_INLINE_SPELLING:
g_value_set_boolean (
value, e_html_editor_view_get_inline_spelling (
E_HTML_EDITOR_VIEW (object)));
return;
case PROP_MAGIC_LINKS:
g_value_set_boolean (
value, e_html_editor_view_get_magic_links (
E_HTML_EDITOR_VIEW (object)));
return;
case PROP_MAGIC_SMILEYS:
g_value_set_boolean (
value, e_html_editor_view_get_magic_smileys (
E_HTML_EDITOR_VIEW (object)));
return;
case PROP_SPELL_CHECKER:
g_value_set_object (
value, e_html_editor_view_get_spell_checker (
E_HTML_EDITOR_VIEW (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
html_editor_view_dispose (GObject *object)
{
EHTMLEditorViewPrivate *priv;
priv = E_HTML_EDITOR_VIEW_GET_PRIVATE (object);
g_clear_object (&priv->selection);
if (priv->convertor_web_view != NULL) {
g_object_unref (priv->convertor_web_view);
priv->convertor_web_view = NULL;
}
if (priv->aliasing_settings != NULL) {
g_signal_handlers_disconnect_by_data (priv->aliasing_settings, object);
g_object_unref (priv->aliasing_settings);
priv->aliasing_settings = NULL;
}
if (priv->font_settings != NULL) {
g_signal_handlers_disconnect_by_data (priv->font_settings, object);
g_object_unref (priv->font_settings);
priv->font_settings = NULL;
}
if (priv->mail_settings != NULL) {
g_signal_handlers_disconnect_by_data (priv->mail_settings, object);
g_object_unref (priv->mail_settings);
priv->mail_settings = NULL;
}
g_hash_table_remove_all (priv->inline_images);
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (e_html_editor_view_parent_class)->dispose (object);
}
static void
html_editor_view_finalize (GObject *object)
{
EHTMLEditorViewPrivate *priv;
priv = E_HTML_EDITOR_VIEW_GET_PRIVATE (object);
g_hash_table_destroy (priv->inline_images);
if (priv->old_settings) {
g_hash_table_destroy (priv->old_settings);
priv->old_settings = NULL;
}
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (e_html_editor_view_parent_class)->finalize (object);
}
static void
html_editor_view_constructed (GObject *object)
{
e_extensible_load_extensions (E_EXTENSIBLE (object));
/* Chain up to parent's constructed() method. */
G_OBJECT_CLASS (e_html_editor_view_parent_class)->constructed (object);
}
static void
html_editor_view_save_element_under_mouse_click (GtkWidget *widget)
{
gint x, y;
GdkDeviceManager *device_manager;
GdkDevice *pointer;
EHTMLEditorView *view;
WebKitDOMDocument *document;
WebKitDOMElement *element;
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (widget));
device_manager = gdk_display_get_device_manager (
gtk_widget_get_display (GTK_WIDGET (widget)));
pointer = gdk_device_manager_get_client_pointer (device_manager);
gdk_window_get_device_position (
gtk_widget_get_window (GTK_WIDGET (widget)), pointer, &x, &y, NULL);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (widget));
element = webkit_dom_document_element_from_point (document, x, y);
view = E_HTML_EDITOR_VIEW (widget);
view->priv->element_under_mouse = element;
}
static gboolean
html_editor_view_button_press_event (GtkWidget *widget,
GdkEventButton *event)
{
gboolean event_handled;
if (event->button == 2) {
/* Middle click paste */
g_signal_emit (widget, signals[PASTE_PRIMARY_CLIPBOARD], 0);
event_handled = TRUE;
} else if (event->button == 3) {
html_editor_view_save_element_under_mouse_click (widget);
g_signal_emit (
widget, signals[POPUP_EVENT],
0, event, &event_handled);
} else {
event_handled = FALSE;
}
if (event_handled)
return TRUE;
/* Chain up to parent's button_press_event() method. */
return GTK_WIDGET_CLASS (e_html_editor_view_parent_class)->
button_press_event (widget, event);
}
static gboolean
html_editor_view_button_release_event (GtkWidget *widget,
GdkEventButton *event)
{
WebKitWebView *webview;
WebKitHitTestResult *hit_test;
WebKitHitTestResultContext context;
gchar *uri;
webview = WEBKIT_WEB_VIEW (widget);
hit_test = webkit_web_view_get_hit_test_result (webview, event);
g_object_get (
hit_test,
"context", &context,
"link-uri", &uri,
NULL);
g_object_unref (hit_test);
/* Left click on a link */
if ((context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) &&
(event->button == 1)) {
/* Ctrl + Left Click on link opens it, otherwise ignore the
* click completely */
if (event->state & GDK_CONTROL_MASK) {
GtkWidget *toplevel;
GdkScreen *screen;
toplevel = gtk_widget_get_toplevel (widget);
screen = gtk_window_get_screen (GTK_WINDOW (toplevel));
gtk_show_uri (screen, uri, event->time, NULL);
g_free (uri);
}
return TRUE;
}
g_free (uri);
/* Chain up to parent's button_release_event() method. */
return GTK_WIDGET_CLASS (e_html_editor_view_parent_class)->
button_release_event (widget, event);
}
static gboolean
prevent_from_deleting_last_element_in_body (EHTMLEditorView *view)
{
gboolean ret_val = FALSE;
WebKitDOMDocument *document;
WebKitDOMHTMLElement *body;
WebKitDOMNodeList *list;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
body = webkit_dom_document_get_body (document);
list = webkit_dom_node_get_child_nodes (WEBKIT_DOM_NODE (body));
if (webkit_dom_node_list_get_length (list) <= 1) {
gchar *content;
content = webkit_dom_node_get_text_content (WEBKIT_DOM_NODE (body));
if (!*content)
ret_val = TRUE;
g_free (content);
if (webkit_dom_element_query_selector (WEBKIT_DOM_ELEMENT (body), "img", NULL))
ret_val = FALSE;
}
g_object_unref (list);
return ret_val;
}
static gboolean
change_quoted_block_to_normal (EHTMLEditorView *view)
{
gint citation_level, success = FALSE;
WebKitDOMDocument *document;
WebKitDOMElement *selection_start_marker, *selection_end_marker, *block;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
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 FALSE;
block = WEBKIT_DOM_ELEMENT (get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker)));
citation_level = get_citation_level (
WEBKIT_DOM_NODE (selection_start_marker), FALSE);
if (selection_start_marker && citation_level > 0) {
if (webkit_dom_element_query_selector (
WEBKIT_DOM_ELEMENT (block), ".-x-evo-quoted", NULL)) {
WebKitDOMNode *prev_sibling;
webkit_dom_node_normalize (WEBKIT_DOM_NODE (block));
prev_sibling = webkit_dom_node_get_previous_sibling (
WEBKIT_DOM_NODE (selection_start_marker));
if (WEBKIT_DOM_IS_ELEMENT (prev_sibling))
success = element_has_class (
WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-quoted");
/* We really have to be in the beginning of paragraph and
* not on the beginning of some line in the paragraph */
if (success && webkit_dom_node_get_previous_sibling (prev_sibling))
success = FALSE;
}
if (view->priv->html_mode)
success = WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (
webkit_dom_node_get_parent_element (
WEBKIT_DOM_NODE (block)));
}
if (success && citation_level == 1) {
gchar *inner_html;
WebKitDOMElement *paragraph;
inner_html = webkit_dom_html_element_get_inner_html (
WEBKIT_DOM_HTML_ELEMENT (block));
webkit_dom_element_set_id (
WEBKIT_DOM_ELEMENT (block), "-x-evo-to-remove");
paragraph = insert_new_line_into_citation (view, inner_html);
g_free (inner_html);
if (paragraph) {
if (view->priv->html_mode) {
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);
webkit_dom_node_insert_before (
WEBKIT_DOM_NODE (paragraph),
WEBKIT_DOM_NODE (selection_end_marker),
webkit_dom_node_get_first_child (
WEBKIT_DOM_NODE (paragraph)),
NULL);
}
remove_quoting_from_element (paragraph);
remove_wrapping_from_element (paragraph);
}
if (block)
remove_node (WEBKIT_DOM_NODE (block));
block = webkit_dom_document_get_element_by_id (
document, "-x-evo-to-remove");
if (block)
remove_node (WEBKIT_DOM_NODE (block));
if (paragraph)
remove_node_if_empty (
webkit_dom_node_get_next_sibling (
WEBKIT_DOM_NODE (paragraph)));
}
if (success && citation_level > 1) {
gint length, word_wrap_length;
EHTMLEditorSelection *selection;
WebKitDOMNode *parent;
selection = e_html_editor_view_get_selection (view);
word_wrap_length = e_html_editor_selection_get_word_wrap_length (selection);
length = word_wrap_length - 2 * (citation_level - 1);
if (view->priv->html_mode) {
webkit_dom_node_insert_before (
WEBKIT_DOM_NODE (block),
WEBKIT_DOM_NODE (selection_start_marker),
webkit_dom_node_get_first_child (
WEBKIT_DOM_NODE (block)),
NULL);
webkit_dom_node_insert_before (
WEBKIT_DOM_NODE (block),
WEBKIT_DOM_NODE (selection_end_marker),
webkit_dom_node_get_first_child (
WEBKIT_DOM_NODE (block)),
NULL);
}
remove_quoting_from_element (block);
remove_wrapping_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, block, citation_level - 1);
parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (block));
if (!webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (block))) {
/* Currect block is in the beginning of citation, just move it
* before the citation where already is */
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (parent),
WEBKIT_DOM_NODE (block),
parent,
NULL);
} else if (!webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (block))) {
/* Currect block is at the end of the citation, just move it
* after the citation where already is */
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (parent),
WEBKIT_DOM_NODE (block),
webkit_dom_node_get_next_sibling (parent),
NULL);
} else {
/* Current block is somewhere in the middle of the citation
* so we need to split the citation and insert the block into
* the citation that is one level lower */
WebKitDOMNode *clone, *child;
clone = webkit_dom_node_clone_node (parent, FALSE);
/* Move nodes that are after the currect block into the
* new blockquote */
child = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (block));
while (child) {
WebKitDOMNode *next = webkit_dom_node_get_next_sibling (child);
webkit_dom_node_append_child (clone, child, NULL);
child = next;
}
clone = webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (parent),
clone,
webkit_dom_node_get_next_sibling (parent),
NULL);
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (parent),
WEBKIT_DOM_NODE (block),
clone,
NULL);
}
}
return success;
}
static gboolean
html_editor_view_key_press_event (GtkWidget *widget,
GdkEventKey *event)
{
EHTMLEditorView *view = E_HTML_EDITOR_VIEW (widget);
if (event->keyval == GDK_KEY_Tab)
return e_html_editor_view_exec_command (
view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_TEXT, "\t");
if ((event->keyval == GDK_KEY_Control_L) ||
(event->keyval == GDK_KEY_Control_R)) {
html_editor_view_set_links_active (view, TRUE);
}
if (is_return_key (event)) {
EHTMLEditorSelection *selection;
EHTMLEditorSelectionBlockFormat format;
selection = e_html_editor_view_get_selection (view);
/* When user presses ENTER in a citation block, WebKit does
* not break the citation automatically, so we need to use
* the special command to do it. */
if (e_html_editor_selection_is_citation (selection)) {
remove_input_event_listener_from_body (view);
return (insert_new_line_into_citation (view, "")) ? TRUE : FALSE;
}
/* When the return is pressed in a H1-6 element, WebKit doesn't
* continue with the same element, but creates normal paragraph,
* so we have to unset the bold font. */
format = e_html_editor_selection_get_block_format (selection);
if (format >= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H1 &&
format <= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H6)
e_html_editor_selection_set_bold (selection, FALSE);
}
if (event->keyval == GDK_KEY_BackSpace) {
EHTMLEditorSelection *selection;
selection = e_html_editor_view_get_selection (view);
/* BackSpace pressed in the beginning of quoted content changes
* format to normal and inserts text into body */
if (e_html_editor_selection_is_collapsed (selection)) {
e_html_editor_selection_save (selection);
if (change_quoted_block_to_normal (view)) {
e_html_editor_selection_restore (selection);
e_html_editor_view_force_spell_check_for_current_paragraph (view);
return TRUE;
}
e_html_editor_selection_restore (selection);
} else
remove_input_event_listener_from_body (view);
/* BackSpace in indented block decrease indent level by one */
if (e_html_editor_selection_is_indented (selection)) {
WebKitDOMElement *caret;
WebKitDOMNode *prev_sibling;
caret = e_html_editor_selection_save_caret_position (selection);
/* Empty text node before caret */
prev_sibling = webkit_dom_node_get_previous_sibling (
WEBKIT_DOM_NODE (caret));
if (prev_sibling && WEBKIT_DOM_IS_TEXT (prev_sibling)) {
gchar *content;
content = webkit_dom_node_get_text_content (prev_sibling);
if (g_strcmp0 (content, "") == 0)
prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling);
g_free (content);
}
if (!prev_sibling) {
e_html_editor_selection_clear_caret_position_marker (selection);
e_html_editor_selection_unindent (selection);
return TRUE;
} else
e_html_editor_selection_clear_caret_position_marker (selection);
}
if (prevent_from_deleting_last_element_in_body (view))
return TRUE;
}
/* Chain up to parent's key_press_event() method. */
return GTK_WIDGET_CLASS (e_html_editor_view_parent_class)->
key_press_event (widget, event);
}
static void
fix_paragraph_structure_after_pressing_enter_after_smiley (EHTMLEditorSelection *selection,
WebKitDOMDocument *document)
{
WebKitDOMElement *element;
element = webkit_dom_document_query_selector (
document, "span.-x-evo-smiley-wrapper > br", NULL);
if (element) {
WebKitDOMNode *parent;
parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
webkit_dom_html_element_set_inner_html (
WEBKIT_DOM_HTML_ELEMENT (
webkit_dom_node_get_parent_node (parent)),
UNICODE_ZERO_WIDTH_SPACE,
NULL);
}
}
static void
mark_node_as_paragraph_after_ending_list (EHTMLEditorSelection *selection,
WebKitDOMDocument *document)
{
gint ii, length;
WebKitDOMNodeList *list;
/* When pressing Enter on empty line in the list WebKit will end that
* list and inserts <div><br></div> so mark it for wrapping */
list = webkit_dom_document_query_selector_all (
document, "body > div:not(.-x-evo-paragraph) > br", NULL);
length = webkit_dom_node_list_get_length (list);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *node = webkit_dom_node_get_parent_node (
webkit_dom_node_list_item (list, ii));
e_html_editor_selection_set_paragraph_style (
selection, WEBKIT_DOM_ELEMENT (node), -1, 0, "");
}
g_object_unref (list);
}
static gboolean
surround_text_with_paragraph_if_needed (EHTMLEditorSelection *selection,
WebKitDOMDocument *document,
WebKitDOMNode *node)
{
WebKitDOMNode *next_sibling = webkit_dom_node_get_next_sibling (node);
WebKitDOMNode *prev_sibling = webkit_dom_node_get_previous_sibling (node);
WebKitDOMElement *element;
/* All text in composer has to be written in div elements, so if
* we are writing something straight to the body, surround it with
* paragraph */
if (WEBKIT_DOM_IS_TEXT (node) &&
WEBKIT_DOM_IS_HTML_BODY_ELEMENT (webkit_dom_node_get_parent_node (node))) {
element = e_html_editor_selection_put_node_into_paragraph (
selection,
document,
node,
e_html_editor_selection_get_caret_position_node (document));
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (next_sibling))
remove_node (next_sibling);
/* Tab character */
if (WEBKIT_DOM_IS_ELEMENT (prev_sibling) &&
element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "Apple-tab-span")) {
webkit_dom_node_insert_before (
WEBKIT_DOM_NODE (element),
prev_sibling,
webkit_dom_node_get_first_child (
WEBKIT_DOM_NODE (element)),
NULL);
}
return TRUE;
}
return FALSE;
}
static gboolean
html_editor_view_key_release_event (GtkWidget *widget,
GdkEventKey *event)
{
WebKitDOMDocument *document;
WebKitDOMRange *range;
EHTMLEditorView *view;
EHTMLEditorSelection *selection;
view = E_HTML_EDITOR_VIEW (widget);
range = html_editor_view_get_dom_range (view);
selection = e_html_editor_view_get_selection (view);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (widget));
register_input_event_listener_on_body (view);
if (view->priv->magic_smileys && view->priv->html_mode)
html_editor_view_check_magic_smileys (view, range);
if (is_return_key (event) || (event->keyval == GDK_KEY_space)) {
html_editor_view_check_magic_links (view, range, FALSE, event);
mark_node_as_paragraph_after_ending_list (selection, document);
if (view->priv->html_mode)
fix_paragraph_structure_after_pressing_enter_after_smiley (selection, document);
} else {
WebKitDOMNode *node;
node = webkit_dom_range_get_end_container (range, NULL);
if (surround_text_with_paragraph_if_needed (selection, document, node)) {
e_html_editor_selection_restore_caret_position (selection);
node = webkit_dom_range_get_end_container (range, NULL);
range = html_editor_view_get_dom_range (view);
}
if (WEBKIT_DOM_IS_TEXT (node)) {
gchar *text;
text = webkit_dom_node_get_text_content (node);
if (g_strcmp0 (text, "") != 0 && !g_unichar_isspace (g_utf8_get_char (text))) {
WebKitDOMNode *prev_sibling;
prev_sibling = webkit_dom_node_get_previous_sibling (node);
if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling))
html_editor_view_check_magic_links (view, range, FALSE, event);
}
g_free (text);
}
}
/* This will fix the structure after the situations where some text
* inside the quoted content is selected and afterwards deleted with
* BackSpace or Delete. */
if ((event->keyval == GDK_KEY_BackSpace) ||
(event->keyval == GDK_KEY_Delete)) {
gint level;
WebKitDOMNode *node, *parent;
WebKitDOMElement *selection_start_marker, *selection_end_marker;
WebKitDOMElement *element;
e_html_editor_selection_save (selection);
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");
level = get_citation_level (
WEBKIT_DOM_NODE (selection_start_marker), FALSE);
if (level == 0)
goto restore;
node = webkit_dom_node_get_previous_sibling (
WEBKIT_DOM_NODE (selection_start_marker));
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (node))
node = webkit_dom_node_get_previous_sibling (node);
if (node)
goto restore;
parent = get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
node = webkit_dom_node_get_previous_sibling (parent);
if (!node) {
/* Situation where the start of the selection was in the
* multiple quoted content and that start on the beginning
* of the citation.
*
* >
* >> |
* >> xx|x
* */
node = webkit_dom_node_get_parent_node (parent);
if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node))
goto restore;
node = webkit_dom_node_get_previous_sibling (node);
if (!node)
goto restore;
if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (webkit_dom_node_get_parent_node (node)))
goto restore;
}
element = webkit_dom_element_query_selector (
WEBKIT_DOM_ELEMENT (node), "span.-x-evo-quote-character > br", NULL);
if (element) {
WebKitDOMNode *tmp;
if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node)) {
/* We have to select the right block when the selection
* started on the end of the citation that is
* inside another citation.
*
* >>|
* > xx|x
*/
/* <span class="-x-evo-quote-character"> */
node = webkit_dom_node_get_parent_node (
WEBKIT_DOM_NODE (element));
/* <span class="-x-evo-quoted"> */
node = webkit_dom_node_get_parent_node (node);
/* right block */
node = webkit_dom_node_get_parent_node (node);
}
webkit_dom_node_append_child (
node, WEBKIT_DOM_NODE (selection_start_marker), NULL);
while ((tmp = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_end_marker))))
webkit_dom_node_append_child (node, tmp, NULL);
webkit_dom_node_insert_before (
node,
WEBKIT_DOM_NODE (selection_end_marker),
webkit_dom_node_get_next_sibling (
WEBKIT_DOM_NODE (selection_start_marker)),
NULL);
webkit_dom_node_append_child (
node, WEBKIT_DOM_NODE (element), NULL);
remove_node (parent);
}
restore:
e_html_editor_selection_restore (selection);
}
if ((event->keyval == GDK_KEY_Control_L) ||
(event->keyval == GDK_KEY_Control_R)) {
html_editor_view_set_links_active (view, FALSE);
}
/* Chain up to parent's key_release_event() method. */
return GTK_WIDGET_CLASS (e_html_editor_view_parent_class)->
key_release_event (widget, event);
}
static void
html_editor_view_paste_as_text (EHTMLEditorView *view)
{
GtkClipboard *clipboard;
clipboard = gtk_clipboard_get_for_display (
gdk_display_get_default (),
GDK_SELECTION_CLIPBOARD);
gtk_clipboard_request_text (
clipboard,
(GtkClipboardTextReceivedFunc) clipboard_text_received_for_paste_as_text,
view);
}
static void
html_editor_view_paste_clipboard_quoted (EHTMLEditorView *view)
{
GtkClipboard *clipboard;
clipboard = gtk_clipboard_get_for_display (
gdk_display_get_default (),
GDK_SELECTION_CLIPBOARD);
gtk_clipboard_request_text (
clipboard,
(GtkClipboardTextReceivedFunc) clipboard_text_received,
view);
}
static gboolean
html_editor_view_image_exists_in_cache (const gchar *image_uri)
{
gchar *filename;
gchar *hash;
gboolean exists = FALSE;
g_return_val_if_fail (emd_global_http_cache != NULL, FALSE);
hash = g_compute_checksum_for_string (G_CHECKSUM_MD5, image_uri, -1);
filename = camel_data_cache_get_filename (
emd_global_http_cache, "http", hash);
if (filename != NULL) {
exists = g_file_test (filename, G_FILE_TEST_EXISTS);
g_free (filename);
}
g_free (hash);
return exists;
}
static gchar *
html_editor_view_redirect_uri (EHTMLEditorView *view,
const gchar *uri)
{
EImageLoadingPolicy image_policy;
GSettings *settings;
gboolean uri_is_http;
uri_is_http =
g_str_has_prefix (uri, "http:") ||
g_str_has_prefix (uri, "https:") ||
g_str_has_prefix (uri, "evo-http:") ||
g_str_has_prefix (uri, "evo-https:");
/* Redirect http(s) request to evo-http(s) protocol.
* See EMailRequest for further details about this. */
if (uri_is_http) {
gchar *new_uri;
SoupURI *soup_uri;
gboolean image_exists;
/* Check Evolution's cache */
image_exists = html_editor_view_image_exists_in_cache (uri);
settings = g_settings_new ("org.gnome.evolution.mail");
image_policy = g_settings_get_enum (settings, "image-loading-policy");
g_object_unref (settings);
/* If the URI is not cached and we are not allowed to load it
* then redirect to invalid URI, so that webkit would display
* a native placeholder for it. */
if (!image_exists && (image_policy == E_IMAGE_LOADING_POLICY_NEVER)) {
return g_strdup ("about:blank");
}
new_uri = g_strconcat ("evo-", uri, NULL);
soup_uri = soup_uri_new (new_uri);
g_free (new_uri);
new_uri = soup_uri_to_string (soup_uri, FALSE);
soup_uri_free (soup_uri);
return new_uri;
}
return g_strdup (uri);
}
static void
html_editor_view_resource_requested (WebKitWebView *web_view,
WebKitWebFrame *frame,
WebKitWebResource *resource,
WebKitNetworkRequest *request,
WebKitNetworkResponse *response,
gpointer user_data)
{
const gchar *original_uri;
original_uri = webkit_network_request_get_uri (request);
if (original_uri != NULL) {
gchar *redirected_uri;
redirected_uri = html_editor_view_redirect_uri (
E_HTML_EDITOR_VIEW (web_view), original_uri);
webkit_network_request_set_uri (request, redirected_uri);
g_free (redirected_uri);
}
}
static void
e_html_editor_view_class_init (EHTMLEditorViewClass *class)
{
GObjectClass *object_class;
GtkWidgetClass *widget_class;
g_type_class_add_private (class, sizeof (EHTMLEditorViewPrivate));
object_class = G_OBJECT_CLASS (class);
object_class->get_property = html_editor_view_get_property;
object_class->set_property = html_editor_view_set_property;
object_class->dispose = html_editor_view_dispose;
object_class->finalize = html_editor_view_finalize;
object_class->constructed = html_editor_view_constructed;
widget_class = GTK_WIDGET_CLASS (class);
widget_class->button_press_event = html_editor_view_button_press_event;
widget_class->button_release_event = html_editor_view_button_release_event;
widget_class->key_press_event = html_editor_view_key_press_event;
widget_class->key_release_event = html_editor_view_key_release_event;
class->paste_clipboard_quoted = html_editor_view_paste_clipboard_quoted;
/**
* EHTMLEditorView:can-copy
*
* Determines whether it's possible to copy to clipboard. The action
* is usually disabled when there is no selection to copy.
*/
g_object_class_install_property (
object_class,
PROP_CAN_COPY,
g_param_spec_boolean (
"can-copy",
"Can Copy",
NULL,
FALSE,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorView:can-cut
*
* Determines whether it's possible to cut to clipboard. The action
* is usually disabled when there is no selection to cut.
*/
g_object_class_install_property (
object_class,
PROP_CAN_CUT,
g_param_spec_boolean (
"can-cut",
"Can Cut",
NULL,
FALSE,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorView:can-paste
*
* Determines whether it's possible to paste from clipboard. The action
* is usually disabled when there is no valid content in clipboard to
* paste.
*/
g_object_class_install_property (
object_class,
PROP_CAN_PASTE,
g_param_spec_boolean (
"can-paste",
"Can Paste",
NULL,
FALSE,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorView:can-redo
*
* Determines whether it's possible to redo previous action. The action
* is usually disabled when there is no action to redo.
*/
g_object_class_install_property (
object_class,
PROP_CAN_REDO,
g_param_spec_boolean (
"can-redo",
"Can Redo",
NULL,
FALSE,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorView:can-undo
*
* Determines whether it's possible to undo last action. The action
* is usually disabled when there is no previous action to undo.
*/
g_object_class_install_property (
object_class,
PROP_CAN_UNDO,
g_param_spec_boolean (
"can-undo",
"Can Undo",
NULL,
FALSE,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorView:changed
*
* Determines whether document has been modified
*/
g_object_class_install_property (
object_class,
PROP_CHANGED,
g_param_spec_boolean (
"changed",
_("Changed property"),
_("Whether editor changed"),
FALSE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorView:html-mode
*
* Determines whether HTML or plain text mode is enabled.
**/
g_object_class_install_property (
object_class,
PROP_HTML_MODE,
g_param_spec_boolean (
"html-mode",
"HTML Mode",
"Edit HTML or plain text",
TRUE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorView::inline-spelling
*
* Determines whether automatic spellchecking is enabled.
*/
g_object_class_install_property (
object_class,
PROP_INLINE_SPELLING,
g_param_spec_boolean (
"inline-spelling",
"Inline Spelling",
"Check your spelling as you type",
TRUE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorView:magic-links
*
* Determines whether automatic conversion of text links into
* HTML links is enabled.
*/
g_object_class_install_property (
object_class,
PROP_MAGIC_LINKS,
g_param_spec_boolean (
"magic-links",
"Magic Links",
"Make URIs clickable as you type",
TRUE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorView:magic-smileys
*
* Determines whether automatic conversion of text smileys into
* images is enabled.
*/
g_object_class_install_property (
object_class,
PROP_MAGIC_SMILEYS,
g_param_spec_boolean (
"magic-smileys",
"Magic Smileys",
"Convert emoticons to images as you type",
TRUE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorView:spell-checker:
*
* The #ESpellChecker used for spell checking.
**/
g_object_class_install_property (
object_class,
PROP_SPELL_CHECKER,
g_param_spec_object (
"spell-checker",
"Spell Checker",
"The spell checker",
E_TYPE_SPELL_CHECKER,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorView:popup-event
*
* Emitted whenever a context menu is requested.
*/
signals[POPUP_EVENT] = g_signal_new (
"popup-event",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (EHTMLEditorViewClass, popup_event),
g_signal_accumulator_true_handled, NULL,
e_marshal_BOOLEAN__BOXED,
G_TYPE_BOOLEAN, 1,
GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
/**
* EHTMLEditorView:paste-primary-clipboad
*
* Emitted when user presses middle button on EHTMLEditorView
*/
signals[PASTE_PRIMARY_CLIPBOARD] = g_signal_new (
"paste-primary-clipboard",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (EHTMLEditorViewClass, paste_primary_clipboard),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
static void
insert_quote_symbols (WebKitDOMHTMLElement *element,
gint quote_level,
gboolean skip_first,
gboolean insert_newline)
{
gchar *text;
gint ii;
GString *output;
gchar *quotation;
if (!WEBKIT_DOM_IS_HTML_ELEMENT (element))
return;
text = webkit_dom_html_element_get_inner_html (element);
output = g_string_new ("");
quotation = get_quotation_for_level (quote_level);
if (g_strcmp0 (text, "\n") == 0) {
g_string_append (output, "<span class=\"-x-evo-quoted\">");
g_string_append (output, quotation);
g_string_append (output, "</span>");
g_string_append (output, "\n");
} else {
gchar **lines;
lines = g_strsplit (text, "\n", 0);
for (ii = 0; lines[ii]; ii++) {
if (ii == 0 && skip_first) {
if (g_strv_length (lines) == 1) {
g_strfreev (lines);
goto exit;
}
g_string_append (output, lines[ii]);
g_string_append (output, "\n");
}
g_string_append (output, "<span class=\"-x-evo-quoted\">");
g_string_append (output, quotation);
g_string_append (output, "</span>");
/* Insert line of text */
g_string_append (output, lines[ii]);
if ((ii == g_strv_length (lines) - 1) &&
!g_str_has_suffix (text, "\n") && !insert_newline) {
/* If we are on last line and node's text doesn't
* end with \n, don't insert it */
break;
}
g_string_append (output, "\n");
}
g_strfreev (lines);
}
webkit_dom_html_element_set_inner_html (element, output->str, NULL);
exit:
g_free (quotation);
g_free (text);
g_string_free (output, TRUE);
}
static void
quote_node (WebKitDOMDocument *document,
WebKitDOMNode *node,
gint quote_level)
{
gboolean skip_first = FALSE;
gboolean insert_newline = FALSE;
gboolean is_html_node = FALSE;
WebKitDOMElement *wrapper;
WebKitDOMNode *node_clone, *prev_sibling, *next_sibling;
/* Don't quote when we are not in citation */
if (quote_level == 0)
return;
if (WEBKIT_DOM_IS_COMMENT (node))
return;
if (WEBKIT_DOM_IS_HTML_ELEMENT (node)) {
insert_quote_symbols (
WEBKIT_DOM_HTML_ELEMENT (node), quote_level, FALSE, FALSE);
return;
}
prev_sibling = webkit_dom_node_get_previous_sibling (node);
next_sibling = webkit_dom_node_get_next_sibling (node);
is_html_node =
!WEBKIT_DOM_IS_TEXT (prev_sibling) &&
!WEBKIT_DOM_IS_COMMENT (prev_sibling) && (
WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling) ||
element_has_tag (WEBKIT_DOM_ELEMENT (prev_sibling), "b") ||
element_has_tag (WEBKIT_DOM_ELEMENT (prev_sibling), "i") ||
element_has_tag (WEBKIT_DOM_ELEMENT (prev_sibling), "u"));
if (prev_sibling && is_html_node)
skip_first = TRUE;
/* Skip the BR between first blockquote and pre */
if (quote_level == 1 && next_sibling && WEBKIT_DOM_IS_HTML_PRE_ELEMENT (next_sibling))
return;
/* Do temporary wrapper */
wrapper = webkit_dom_document_create_element (document, "SPAN", NULL);
webkit_dom_element_set_class_name (wrapper, "-x-evo-temp-text-wrapper");
node_clone = webkit_dom_node_clone_node (node, TRUE);
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (wrapper),
node_clone,
NULL);
insert_quote_symbols (
WEBKIT_DOM_HTML_ELEMENT (wrapper),
quote_level,
skip_first,
insert_newline);
webkit_dom_node_replace_child (
webkit_dom_node_get_parent_node (node),
WEBKIT_DOM_NODE (wrapper),
node,
NULL);
}
static void
insert_quote_symbols_before_node (WebKitDOMDocument *document,
WebKitDOMNode *node,
gint quote_level,
gboolean is_html_node)
{
gboolean skip, wrap_br;
gchar *quotation;
WebKitDOMElement *element;
quotation = get_quotation_for_level (quote_level);
element = webkit_dom_document_create_element (document, "SPAN", NULL);
element_add_class (element, "-x-evo-quoted");
webkit_dom_html_element_set_inner_html (
WEBKIT_DOM_HTML_ELEMENT (element), quotation, NULL);
/* Don't insert temporary BR before BR that is used for wrapping */
skip = WEBKIT_DOM_IS_HTMLBR_ELEMENT (node);
wrap_br = element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-wrap-br");
skip = skip && wrap_br;
if (is_html_node && !skip) {
WebKitDOMElement *new_br;
new_br = webkit_dom_document_create_element (document, "br", NULL);
element_add_class (new_br, "-x-evo-temp-br");
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (node),
WEBKIT_DOM_NODE (new_br),
node,
NULL);
}
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (node),
WEBKIT_DOM_NODE (element),
node,
NULL);
if (is_html_node && !wrap_br)
remove_node (node);
g_free (quotation);
}
static gboolean
element_is_selection_marker (WebKitDOMElement *element)
{
gboolean is_marker = FALSE;
is_marker =
element_has_id (element, "-x-evo-selection-start-marker") ||
element_has_id (element, "-x-evo-selection-end-marker");
return is_marker;
}
static gboolean
check_if_suppress_next_node (WebKitDOMNode *node)
{
if (node && WEBKIT_DOM_IS_ELEMENT (node))
if (element_is_selection_marker (WEBKIT_DOM_ELEMENT (node)))
if (!webkit_dom_node_get_previous_sibling (node))
return FALSE;
return TRUE;
}
static void
quote_br_node (WebKitDOMNode *node,
gint quote_level)
{
gchar *quotation, *content;
quotation = get_quotation_for_level (quote_level);
content = g_strconcat (
"<span class=\"-x-evo-quoted\">",
quotation,
"</span><br class=\"-x-evo-temp-br\">",
NULL);
webkit_dom_html_element_set_outer_html (
WEBKIT_DOM_HTML_ELEMENT (node),
content,
NULL);
g_free (content);
g_free (quotation);
}
static void
quote_plain_text_recursive (WebKitDOMDocument *document,
WebKitDOMNode *node,
WebKitDOMNode *start_node,
gint quote_level)
{
gboolean skip_node = FALSE;
gboolean move_next = FALSE;
gboolean suppress_next = FALSE;
gboolean is_html_node = FALSE;
WebKitDOMNode *next_sibling, *prev_sibling;
node = webkit_dom_node_get_first_child (node);
while (node) {
skip_node = FALSE;
move_next = FALSE;
is_html_node = FALSE;
if (WEBKIT_DOM_IS_COMMENT (node))
goto next_node;
prev_sibling = webkit_dom_node_get_previous_sibling (node);
next_sibling = webkit_dom_node_get_next_sibling (node);
if (WEBKIT_DOM_IS_TEXT (node)) {
/* Start quoting after we are in blockquote */
if (quote_level > 0 && !suppress_next) {
/* When quoting text node, we are wrappering it and
* afterwards replacing it with that wrapper, thus asking
* for next_sibling after quoting will return NULL bacause
* that node don't exist anymore */
quote_node (document, node, quote_level);
node = next_sibling;
skip_node = TRUE;
} else
suppress_next = FALSE;
goto next_node;
}
if (!(WEBKIT_DOM_IS_ELEMENT (node) || WEBKIT_DOM_IS_HTML_ELEMENT (node)))
goto next_node;
if (element_has_id (WEBKIT_DOM_ELEMENT (node), "-x-evo-caret-position")) {
if (quote_level > 0)
element_add_class (
WEBKIT_DOM_ELEMENT (node), "-x-evo-caret-quoting");
move_next = TRUE;
suppress_next = TRUE;
goto next_node;
}
if (element_is_selection_marker (WEBKIT_DOM_ELEMENT (node))) {
move_next = TRUE;
/* If there is collapsed selection in the beginning of line
* we cannot suppress first text that is after the end of
* selection */
suppress_next = check_if_suppress_next_node (prev_sibling);
goto next_node;
}
if (WEBKIT_DOM_IS_HTML_META_ELEMENT (node)) {
goto next_node;
}
if (WEBKIT_DOM_IS_HTML_STYLE_ELEMENT (node)) {
move_next = TRUE;
goto next_node;
}
if (WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (node)) {
move_next = TRUE;
goto next_node;
}
if (!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node))
if (webkit_dom_element_get_child_element_count (
WEBKIT_DOM_ELEMENT (node)) != 0)
goto with_children;
/* Even in plain text mode we can have some basic html element
* like anchor and others. When Forwaring e-mail as Quoted EMFormat
* generates header that contatains <b> tags (bold font).
* We have to treat these elements separately to avoid
* modifications of theirs inner texts */
is_html_node =
WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node) ||
element_has_tag (WEBKIT_DOM_ELEMENT (node), "b") ||
element_has_tag (WEBKIT_DOM_ELEMENT (node), "i") ||
element_has_tag (WEBKIT_DOM_ELEMENT (node), "u");
if (is_html_node) {
gboolean wrap_br;
wrap_br =
prev_sibling &&
WEBKIT_DOM_IS_HTMLBR_ELEMENT (prev_sibling) &&
element_has_class (
WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-wrap-br");
if (!prev_sibling || wrap_br)
insert_quote_symbols_before_node (
document, node, quote_level, FALSE);
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (prev_sibling) && !wrap_br)
insert_quote_symbols_before_node (
document, prev_sibling, quote_level, TRUE);
move_next = TRUE;
goto next_node;
}
/* If element doesn't have children, we can quote it */
if (is_citation_node (node)) {
/* Citation with just text inside */
quote_node (document, node, quote_level + 1);
/* Set citation as quoted */
element_add_class (
WEBKIT_DOM_ELEMENT (node),
"-x-evo-plaintext-quoted");
move_next = TRUE;
goto next_node;
}
if (!WEBKIT_DOM_IS_HTMLBR_ELEMENT (node)) {
if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling)) {
move_next = TRUE;
goto next_node;
}
goto not_br;
} else if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-first-br") ||
element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-last-br")) {
quote_br_node (node, quote_level);
node = next_sibling;
skip_node = TRUE;
goto next_node;
}
if (!prev_sibling) {
WebKitDOMNode *parent;
parent = webkit_dom_node_get_parent_node (node);
/* BR in the beginning of the citation */
if (WEBKIT_DOM_IS_HTML_PRE_ELEMENT (parent))
insert_quote_symbols_before_node (
document, node, quote_level, FALSE);
}
if (WEBKIT_DOM_IS_ELEMENT (prev_sibling) &&
WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (next_sibling) &&
element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-temp-text-wrapper")) {
/* Situation when anchors are alone on line */
gchar *text_content;
text_content = webkit_dom_node_get_text_content (prev_sibling);
if (g_str_has_suffix (text_content, "\n")) {
insert_quote_symbols_before_node (
document, node, quote_level, FALSE);
remove_node (node);
g_free (text_content);
node = next_sibling;
skip_node = TRUE;
goto next_node;
}
g_free (text_content);
}
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (prev_sibling)) {
quote_br_node (prev_sibling, quote_level);
node = next_sibling;
skip_node = TRUE;
goto next_node;
}
if (!prev_sibling && !next_sibling) {
WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node);
if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent)) {
insert_quote_symbols_before_node (
document, node, quote_level, FALSE);
}
}
if (WEBKIT_DOM_IS_ELEMENT (prev_sibling) &&
element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-temp-text-wrapper")) {
gchar *text_content;
text_content = webkit_dom_node_get_text_content (prev_sibling);
if (g_strcmp0 (text_content, "") == 0)
insert_quote_symbols_before_node (
document, node, quote_level, FALSE);
g_free (text_content);
}
if (is_citation_node (prev_sibling)) {
insert_quote_symbols_before_node (
document, node, quote_level, FALSE);
}
not_br:
if (g_strcmp0 (webkit_dom_node_get_text_content (node), "") == 0) {
move_next = TRUE;
goto next_node;
}
quote_node (document, node, quote_level);
move_next = TRUE;
goto next_node;
with_children:
if (is_citation_node (node)) {
/* Go deeper and increase level */
quote_plain_text_recursive (
document, node, start_node, quote_level + 1);
/* set citation as quoted */
element_add_class (
WEBKIT_DOM_ELEMENT (node),
"-x-evo-plaintext-quoted");
move_next = TRUE;
} else {
quote_plain_text_recursive (
document, node, start_node, quote_level);
move_next = TRUE;
}
next_node:
if (!skip_node) {
/* Move to next node */
if (!move_next && webkit_dom_node_has_child_nodes (node)) {
node = webkit_dom_node_get_first_child (node);
} else if (webkit_dom_node_get_next_sibling (node)) {
node = webkit_dom_node_get_next_sibling (node);
} else {
return;
}
}
}
}
WebKitDOMElement *
e_html_editor_view_quote_plain_text_element (EHTMLEditorView *view,
WebKitDOMElement *element)
{
WebKitDOMDocument *document;
WebKitDOMNode *element_clone;
WebKitDOMNodeList *list;
gint ii, length, level;
document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (element));
element_clone = webkit_dom_node_clone_node (WEBKIT_DOM_NODE (element), TRUE);
level = get_citation_level (WEBKIT_DOM_NODE (element), TRUE);
/* Remove old quote characters if the exists */
list = webkit_dom_element_query_selector_all (
WEBKIT_DOM_ELEMENT (element_clone), "span.-x-evo-quoted", NULL);
length = webkit_dom_node_list_get_length (list);
for (ii = 0; ii < length; ii++)
remove_node (webkit_dom_node_list_item (list, ii));
webkit_dom_node_normalize (element_clone);
quote_plain_text_recursive (
document, element_clone, element_clone, level);
/* Replace old element with one, that is quoted */
webkit_dom_node_replace_child (
webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
element_clone,
WEBKIT_DOM_NODE (element),
NULL);
g_object_unref (list);
return WEBKIT_DOM_ELEMENT (element_clone);
}
/**
* e_html_editor_view_quote_plain_text:
* @view: an #EHTMLEditorView
*
* Quote text inside citation blockquotes in plain text mode.
*
* As this function is cloning and replacing all citation blockquotes keep on
* mind that any pointers to nodes inside these blockquotes will be invalidated.
*/
WebKitDOMElement *
e_html_editor_view_quote_plain_text (EHTMLEditorView *view)
{
WebKitDOMDocument *document;
WebKitDOMHTMLElement *body;
WebKitDOMNode *body_clone;
WebKitDOMNamedNodeMap *attributes;
WebKitDOMNodeList *list;
WebKitDOMElement *element;
gint ii, length;
gulong attributes_length;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
/* Check if the document is already quoted */
element = webkit_dom_document_query_selector (
document, ".-x-evo-plaintext-quoted", NULL);
if (element)
return NULL;
body = webkit_dom_document_get_body (document);
body_clone = webkit_dom_node_clone_node (WEBKIT_DOM_NODE (body), TRUE);
/* Clean unwanted spaces before and after blockquotes */
list = webkit_dom_element_query_selector_all (
WEBKIT_DOM_ELEMENT (body_clone), "blockquote[type|=cite]", NULL);
length = webkit_dom_node_list_get_length (list);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *blockquote = webkit_dom_node_list_item (list, ii);
WebKitDOMNode *prev_sibling = webkit_dom_node_get_previous_sibling (blockquote);
WebKitDOMNode *next_sibling = webkit_dom_node_get_next_sibling (blockquote);
if (prev_sibling && WEBKIT_DOM_IS_HTMLBR_ELEMENT (prev_sibling))
remove_node (prev_sibling);
if (next_sibling && WEBKIT_DOM_IS_HTMLBR_ELEMENT (next_sibling))
remove_node (next_sibling);
if (webkit_dom_node_has_child_nodes (blockquote)) {
WebKitDOMNode *child = webkit_dom_node_get_first_child (blockquote);
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (child))
remove_node (child);
}
}
g_object_unref (list);
webkit_dom_node_normalize (body_clone);
quote_plain_text_recursive (document, body_clone, body_clone, 0);
/* Copy attributes */
attributes = webkit_dom_element_get_attributes (WEBKIT_DOM_ELEMENT (body));
attributes_length = webkit_dom_named_node_map_get_length (attributes);
for (ii = 0; ii < attributes_length; ii++) {
gchar *name, *value;
WebKitDOMNode *node = webkit_dom_named_node_map_item (attributes, ii);
name = webkit_dom_node_get_local_name (node);
value = webkit_dom_node_get_node_value (node);
webkit_dom_element_set_attribute (
WEBKIT_DOM_ELEMENT (body_clone), name, value, NULL);
g_free (name);
g_free (value);
}
g_object_unref (attributes);
/* Replace old BODY with one, that is quoted */
webkit_dom_node_replace_child (
webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (body)),
body_clone,
WEBKIT_DOM_NODE (body),
NULL);
return WEBKIT_DOM_ELEMENT (body_clone);
}
/**
* e_html_editor_view_dequote_plain_text:
* @view: an #EHTMLEditorView
*
* Dequote already quoted plain text in editor.
* Editor have to be quoted with e_html_editor_view_quote_plain_text otherwise
* it's not working.
*/
void
e_html_editor_view_dequote_plain_text (EHTMLEditorView *view)
{
WebKitDOMDocument *document;
WebKitDOMNodeList *paragraphs;
gint length, ii;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
paragraphs = webkit_dom_document_query_selector_all (
document, "blockquote.-x-evo-plaintext-quoted", NULL);
length = webkit_dom_node_list_get_length (paragraphs);
for (ii = 0; ii < length; ii++) {
WebKitDOMElement *element;
element = WEBKIT_DOM_ELEMENT (webkit_dom_node_list_item (paragraphs, ii));
if (is_citation_node (WEBKIT_DOM_NODE (element))) {
element_remove_class (element, "-x-evo-plaintext-quoted");
remove_quoting_from_element (element);
}
}
g_object_unref (paragraphs);
}
static gboolean
create_anchor_for_link (const GMatchInfo *info,
GString *res,
gpointer data)
{
gint offset = 0;
gchar *match;
gboolean address_surrounded;
match = g_match_info_fetch (info, 0);
address_surrounded =
strstr (match, "@") &&
g_str_has_prefix (match, "&lt;") &&
g_str_has_suffix (match, "&gt;");
if (address_surrounded)
offset += 4;
if (g_str_has_prefix (match, "&nbsp;"))
offset += 6;
if (address_surrounded)
g_string_append (res, "&lt;");
g_string_append (res, "<a href=\"");
if (strstr (match, "@")) {
g_string_append (res, "mailto:");
g_string_append (res, match + offset);
if (address_surrounded)
g_string_truncate (res, res->len - 4);
g_string_append (res, "\">");
g_string_append (res, match + offset);
if (address_surrounded)
g_string_truncate (res, res->len - 4);
} else {
g_string_append (res, match + offset);
g_string_append (res, "\">");
g_string_append (res, match + offset);
}
g_string_append (res, "</a>");
if (address_surrounded)
g_string_append (res, "&gt;");
g_free (match);
return FALSE;
}
static gboolean
replace_to_nbsp (const GMatchInfo *info,
GString *res,
gboolean use_nbsp)
{
gchar *match, *previous_tab;
const gchar *string;
gint ii, length = 0, start = 0;
match = g_match_info_fetch (info, 0);
g_match_info_fetch_pos (info, 0, &start, NULL);
string = g_match_info_get_string (info);
if (start > 0) {
previous_tab = g_strrstr_len (string, start, "\x9");
if (previous_tab && *previous_tab) {
char *act_tab = NULL;
act_tab = strstr (previous_tab + 1, "\x9");
if (act_tab && *act_tab) {
length = act_tab - previous_tab - 1;
length = TAB_LENGTH - length;
}
}
}
if (length == 0) {
if (strstr (match, "\x9"))
length = TAB_LENGTH - (start % TAB_LENGTH);
else
length = strlen (match);
}
for (ii = 0; ii < length; ii++)
g_string_append (res, "&nbsp;");
g_free (match);
return FALSE;
}
static gboolean
surround_links_with_anchor (const gchar *text)
{
return (strstr (text, "http") || strstr (text, "ftp") ||
strstr (text, "www") || strstr (text, "@"));
}
/* This parses the HTML code (that contains just text, &nbsp; and BR elements)
* into paragraphs.
* HTML code in that format we can get by taking innerText from some element,
* setting it to another one and finally getting innerHTML from it */
static void
parse_html_into_paragraphs (EHTMLEditorView *view,
WebKitDOMDocument *document,
WebKitDOMElement *blockquote,
const gchar *html,
gboolean use_pre)
{
const gchar *prev_br, *next_br;
gchar *inner_html;
gint citation_level = 0;
GString *start, *end;
gboolean ignore_next_br = FALSE;
GRegex *regex_nbsp = NULL, *regex_links = NULL;
gboolean first_element = TRUE;
webkit_dom_html_element_set_inner_html (
WEBKIT_DOM_HTML_ELEMENT (blockquote), "", NULL);
prev_br = html;
next_br = strstr (prev_br, "<br>");
/* Replace single spaces on the beginning of line, 2+ spaces and
* tabulators with non breaking spaces */
regex_nbsp = g_regex_new ("^\\s{1}|\\s{2,}|\x9", 0, 0, NULL);
regex_links = g_regex_new (URL_PATTERN, 0, 0, NULL);
while (next_br) {
gboolean local_ignore_next_br = ignore_next_br;
const gchar *citation = NULL, *citation_end = NULL;
const gchar *rest = NULL, *with_br = NULL;
gchar *to_insert = NULL;
WebKitDOMElement *paragraph;
to_insert = g_utf8_substring (
prev_br, 0, g_utf8_pointer_to_offset (prev_br, next_br));
with_br = strstr (to_insert, "<br>");
ignore_next_br = FALSE;
citation = strstr (to_insert, "##CITATION_");
if (citation) {
if (strstr (to_insert, "##CITATION_START##"))
citation_level++;
else
citation_level--;
citation_end = strstr (citation + 2, "##");
if (citation_end)
rest = citation_end + 2;
} else {
rest = with_br ?
to_insert + 4 + (with_br - to_insert) : to_insert;
}
if (use_pre)
paragraph = webkit_dom_document_create_element (document, "pre", NULL);
else
paragraph = e_html_editor_selection_get_paragraph_element (
e_html_editor_view_get_selection (view), document, -1, 0);
if (with_br && !*rest && !citation &&!local_ignore_next_br) {
WebKitDOMNode *paragraph_clone;
paragraph_clone = webkit_dom_node_clone_node (
WEBKIT_DOM_NODE (paragraph), TRUE);
webkit_dom_html_element_set_inner_html (
WEBKIT_DOM_HTML_ELEMENT (paragraph_clone),
"<br>",
NULL);
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (blockquote),
paragraph_clone,
NULL);
}
if (citation) {
WebKitDOMText *text;
gchar *citation_mark;
citation_mark = g_utf8_substring (
citation, 0,
g_utf8_pointer_to_offset (
citation, citation_end + 2));
text = webkit_dom_document_create_text_node (
document, citation_mark);
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (blockquote),
WEBKIT_DOM_NODE (text),
NULL);
g_free (citation_mark);
}
if (rest && *rest){
gchar *truncated = g_strdup (rest);
gchar *rest_to_insert;
g_strchomp (truncated);
rest_to_insert = g_regex_replace_eval (
regex_nbsp,
truncated,
-1,
0,
0,
(GRegexEvalCallback) replace_to_nbsp,
NULL,
NULL);
g_free (truncated);
if (surround_links_with_anchor (rest_to_insert)) {
truncated = g_regex_replace_eval (
regex_links,
rest_to_insert,
-1,
0,
0,
create_anchor_for_link,
NULL,
NULL);
g_free (rest_to_insert);
rest_to_insert = truncated;
}
webkit_dom_html_element_set_inner_html (
WEBKIT_DOM_HTML_ELEMENT (paragraph),
*rest_to_insert ? rest_to_insert : "<br>",
NULL);
if (g_strcmp0 (rest_to_insert, UNICODE_ZERO_WIDTH_SPACE) == 0)
webkit_dom_html_element_set_inner_html (
WEBKIT_DOM_HTML_ELEMENT (paragraph), "<br>", NULL);
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (blockquote),
WEBKIT_DOM_NODE (paragraph),
NULL);
g_free (rest_to_insert);
} else if (rest && !*rest && first_element) {
webkit_dom_html_element_set_inner_html (
WEBKIT_DOM_HTML_ELEMENT (paragraph),
"<br class=\"-x-evo-first-br\">",
NULL);
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (blockquote),
WEBKIT_DOM_NODE (paragraph),
NULL);
}
if (citation_end)
ignore_next_br = TRUE;
first_element = FALSE;
prev_br = next_br;
next_br = strstr (prev_br + 4, "<br>");
g_free (to_insert);
}
if (g_utf8_strlen (prev_br, -1) > 0) {
gchar *truncated = g_strdup (
g_str_has_prefix (prev_br, "<br>") ? prev_br + 4 : prev_br);
gchar *rest_to_insert;
WebKitDOMElement *paragraph;
if (use_pre)
paragraph = webkit_dom_document_create_element (document, "pre", NULL);
else
paragraph = e_html_editor_selection_get_paragraph_element (
e_html_editor_view_get_selection (view), document, -1, 0);
g_strchomp (truncated);
rest_to_insert = g_regex_replace_eval (
regex_nbsp,
truncated,
-1,
0,
0,
(GRegexEvalCallback) replace_to_nbsp,
NULL,
NULL);
g_free (truncated);
if (surround_links_with_anchor (rest_to_insert)) {
truncated = g_regex_replace_eval (
regex_links,
rest_to_insert,
-1,
0,
0,
create_anchor_for_link,
NULL,
NULL);
g_free (rest_to_insert);
rest_to_insert = truncated;
}
webkit_dom_html_element_set_inner_html (
WEBKIT_DOM_HTML_ELEMENT (paragraph),
*rest_to_insert ?
rest_to_insert : "<br class=\"-x-evo-last-br\">",
NULL);
if (g_strcmp0 (rest_to_insert, UNICODE_ZERO_WIDTH_SPACE) == 0)
webkit_dom_html_element_set_inner_html (
WEBKIT_DOM_HTML_ELEMENT (paragraph),
"<br class=\"-x-evo-last-br\">",
NULL);
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (blockquote),
WEBKIT_DOM_NODE (paragraph),
NULL);
g_free (rest_to_insert);
}
/* Replace text markers with actual HTML blockquotes */
inner_html = webkit_dom_html_element_get_inner_html (
WEBKIT_DOM_HTML_ELEMENT (blockquote));
start = e_str_replace_string (
inner_html, "##CITATION_START##","<blockquote type=\"cite\">");
end = e_str_replace_string (
start->str, "##CITATION_END##", "</blockquote>");
webkit_dom_html_element_set_inner_html (
WEBKIT_DOM_HTML_ELEMENT (blockquote), end->str, NULL);
g_regex_unref (regex_nbsp);
g_regex_unref (regex_links);
g_free (inner_html);
g_string_free (start, TRUE);
g_string_free (end, TRUE);
}
static void
mark_citation (WebKitDOMElement *citation)
{
gchar *inner_html, *surrounded;
inner_html = webkit_dom_html_element_get_inner_html (
WEBKIT_DOM_HTML_ELEMENT (citation));
surrounded = g_strconcat (
"<span>##CITATION_START##</span>", inner_html,
"<span>##CITATION_END##</span>", NULL);
webkit_dom_html_element_set_inner_html (
WEBKIT_DOM_HTML_ELEMENT (citation), surrounded, NULL);
element_add_class (citation, "marked");
g_free (inner_html);
g_free (surrounded);
}
static gint
create_text_markers_for_citations_in_document (WebKitDOMDocument *document)
{
gint count = 0;
WebKitDOMElement *citation;
citation = webkit_dom_document_query_selector (
document, "blockquote[type=cite]:not(.marked)", NULL);
while (citation) {
mark_citation (citation);
count ++;
citation = webkit_dom_document_query_selector (
document, "blockquote[type=cite]:not(.marked)", NULL);
}
return count;
}
static gint
create_text_markers_for_citations_in_element (WebKitDOMElement *element)
{
gint count = 0;
WebKitDOMElement *citation;
citation = webkit_dom_element_query_selector (
element, "blockquote[type=cite]:not(.marked)", NULL);
while (citation) {
mark_citation (citation);
count ++;
citation = webkit_dom_element_query_selector (
element, "blockquote[type=cite]:not(.marked)", NULL);
}
return count;
}
static void
html_editor_view_process_document_from_convertor (EHTMLEditorView *view,
WebKitDOMDocument *document_convertor)
{
EHTMLEditorSelection *selection = e_html_editor_view_get_selection (view);
gboolean start_bottom;
gchar *inner_text, *inner_html;
gint ii;
GSettings *settings;
WebKitDOMDocument *document;
WebKitDOMElement *paragraph, *new_blockquote, *top_signature;
WebKitDOMElement *cite_body, *signature;
WebKitDOMHTMLElement *body, *body_convertor;
WebKitDOMNodeList *list;
settings = g_settings_new ("org.gnome.evolution.mail");
start_bottom = g_settings_get_boolean (settings, "composer-reply-start-bottom");
g_object_unref (settings);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
body = webkit_dom_document_get_body (document);
body_convertor = webkit_dom_document_get_body (document_convertor);
webkit_dom_element_set_attribute (
WEBKIT_DOM_ELEMENT (body), "data-converted", "", NULL);
paragraph = webkit_dom_document_get_element_by_id (document, "-x-evo-input-start");
if (!paragraph) {
paragraph = e_html_editor_selection_get_paragraph_element (
selection, document, -1, 0);
webkit_dom_element_set_id (paragraph, "-x-evo-input-start");
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 (webkit_dom_document_get_body (document)),
WEBKIT_DOM_NODE (paragraph),
NULL);
}
list = webkit_dom_document_query_selector_all (
document_convertor, "span.-x-evo-to-body", NULL);
for (ii = webkit_dom_node_list_get_length (list) - 1; ii >= 0; ii--) {
WebKitDOMNode *node;
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_clone_node (
webkit_dom_node_get_first_child (node), TRUE),
webkit_dom_node_get_next_sibling (
WEBKIT_DOM_NODE (paragraph)),
NULL);
remove_node (webkit_dom_node_get_first_child (node));
}
remove_node (node);
}
g_object_unref (list);
repair_gmail_blockquotes (document_convertor);
create_text_markers_for_citations_in_document (document_convertor);
/* Get innertText from convertor */
inner_text = webkit_dom_html_element_get_inner_text (body_convertor);
cite_body = webkit_dom_document_query_selector (
document_convertor, "span.-x-evo-cite-body", NULL);
top_signature = webkit_dom_document_query_selector (
document, ".-x-evo-top-signature", NULL);
signature = webkit_dom_document_query_selector (
document, "span.-x-evo-signature", NULL);
if (cite_body) {
if (!(top_signature && start_bottom))
e_html_editor_selection_save_caret_position (selection);
} else {
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (paragraph),
WEBKIT_DOM_NODE (
e_html_editor_selection_get_caret_position_node (
document)),
NULL);
}
new_blockquote = webkit_dom_document_create_element (
document, "blockquote", NULL);
webkit_dom_element_set_attribute (
new_blockquote, "type", "cite", NULL);
webkit_dom_html_element_set_inner_text (
WEBKIT_DOM_HTML_ELEMENT (new_blockquote), inner_text, NULL);
inner_html = webkit_dom_html_element_get_inner_html (
WEBKIT_DOM_HTML_ELEMENT (new_blockquote));
if (cite_body) {
webkit_dom_element_set_attribute (
new_blockquote, "id", "-x-evo-main-cite", NULL);
parse_html_into_paragraphs (
view, document, new_blockquote, inner_html, FALSE);
if (start_bottom) {
if (signature) {
WebKitDOMNode *parent =
webkit_dom_node_get_parent_node (
WEBKIT_DOM_NODE (signature));
if (top_signature) {
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (body),
WEBKIT_DOM_NODE (new_blockquote),
NULL);
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (body),
WEBKIT_DOM_NODE (paragraph),
NULL);
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (paragraph),
e_html_editor_selection_get_caret_position_node (
document),
NULL);
} else {
webkit_dom_node_insert_before (
WEBKIT_DOM_NODE (body),
WEBKIT_DOM_NODE (new_blockquote),
WEBKIT_DOM_NODE (parent),
NULL);
webkit_dom_node_insert_before (
WEBKIT_DOM_NODE (body),
WEBKIT_DOM_NODE (paragraph),
webkit_dom_node_get_next_sibling (
WEBKIT_DOM_NODE (new_blockquote)),
NULL);
}
} else {
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (body),
WEBKIT_DOM_NODE (new_blockquote),
NULL);
webkit_dom_node_insert_before (
WEBKIT_DOM_NODE (body),
WEBKIT_DOM_NODE (paragraph),
webkit_dom_node_get_next_sibling (
WEBKIT_DOM_NODE (new_blockquote)),
NULL);
}
} else {
if (signature) {
WebKitDOMNode *parent =
webkit_dom_node_get_parent_node (
WEBKIT_DOM_NODE (signature));
if (top_signature) {
WebKitDOMElement *br;
br = webkit_dom_document_create_element (
document, "BR", NULL);
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (body),
WEBKIT_DOM_NODE (new_blockquote),
NULL);
/* Insert NL after signature */
webkit_dom_node_insert_before (
WEBKIT_DOM_NODE (body),
WEBKIT_DOM_NODE (br),
webkit_dom_node_get_next_sibling (
WEBKIT_DOM_NODE (paragraph)),
NULL);
webkit_dom_node_insert_before (
WEBKIT_DOM_NODE (body),
WEBKIT_DOM_NODE (parent),
WEBKIT_DOM_NODE (br),
NULL);
} else
webkit_dom_node_insert_before (
WEBKIT_DOM_NODE (body),
WEBKIT_DOM_NODE (new_blockquote),
WEBKIT_DOM_NODE (parent),
NULL);
} else {
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (body),
WEBKIT_DOM_NODE (new_blockquote),
NULL);
}
}
} else {
WebKitDOMNode *signature_clone, *first_child;
if (signature) {
signature_clone = webkit_dom_node_clone_node (
webkit_dom_node_get_parent_node (
WEBKIT_DOM_NODE (signature)),
TRUE);
}
parse_html_into_paragraphs (
view, document, WEBKIT_DOM_ELEMENT (body),
inner_html, FALSE);
if (signature) {
if (!top_signature) {
signature_clone = webkit_dom_node_append_child (
WEBKIT_DOM_NODE (body),
signature_clone,
NULL);
} else {
webkit_dom_node_insert_before (
WEBKIT_DOM_NODE (body),
signature_clone,
webkit_dom_node_get_first_child (
WEBKIT_DOM_NODE (body)),
NULL);
}
}
first_child = webkit_dom_node_get_first_child (
WEBKIT_DOM_NODE (body));
if (!first_child) {
paragraph = e_html_editor_selection_get_paragraph_element (
selection, document, -1, 0);
first_child = webkit_dom_node_append_child (
WEBKIT_DOM_NODE (body),
WEBKIT_DOM_NODE (paragraph),
NULL);
}
webkit_dom_node_insert_before (
first_child,
e_html_editor_selection_get_caret_position_node (
document),
webkit_dom_node_get_first_child (first_child),
NULL);
}
paragraph = webkit_dom_document_query_selector (document, "br.-x-evo-last-br", NULL);
if (paragraph)
webkit_dom_element_remove_attribute (paragraph, "class");
paragraph = webkit_dom_document_query_selector (document, "br.-x-evo-first-br", NULL);
if (paragraph)
webkit_dom_element_remove_attribute (paragraph, "class");
if (!e_html_editor_view_get_html_mode (view))
e_html_editor_selection_wrap_paragraphs_in_document (
selection, document);
if (webkit_dom_document_query_selector (document, "blockquote[type=cite]", NULL))
body = WEBKIT_DOM_HTML_ELEMENT (
e_html_editor_view_quote_plain_text (view));
e_html_editor_selection_restore_caret_position (selection);
e_html_editor_view_force_spell_check (view);
/* Register on input event that is called when the content (body) is modified */
webkit_dom_event_target_add_event_listener (
WEBKIT_DOM_EVENT_TARGET (body),
"input",
G_CALLBACK (body_input_event_cb),
FALSE,
view);
g_free (inner_html);
g_free (inner_text);
}
static void
fix_structure_after_pasting_multiline_content (WebKitDOMNode *node)
{
WebKitDOMNode *first_child, *parent;
/* When pasting content that does not contain just the
* one line text WebKit inserts all the content after the
* first line into one element. So we have to take it out
* of this element and insert it after that element. */
parent = webkit_dom_node_get_parent_node (node);
if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent))
return;
first_child = webkit_dom_node_get_first_child (parent);
while (first_child) {
WebKitDOMNode *next_child =
webkit_dom_node_get_next_sibling (first_child);
if (webkit_dom_node_has_child_nodes (first_child))
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (parent),
first_child,
parent,
NULL);
first_child = next_child;
}
remove_node (parent);
}
static void
html_editor_view_insert_converted_html_into_selection (EHTMLEditorView *view,
WebKitDOMDocument *document_convertor)
{
EHTMLEditorSelection *selection = e_html_editor_view_get_selection (view);
gboolean has_selection;
gchar *inner_text, *inner_html;
gint citation_level;
WebKitDOMDocument *document;
WebKitDOMElement *element;
WebKitDOMHTMLElement *convertor_body;
WebKitDOMNode *node;
WebKitDOMRange *range;
remove_input_event_listener_from_body (view);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
convertor_body = webkit_dom_document_get_body (document_convertor);
inner_text = webkit_dom_html_element_get_inner_text (convertor_body);
element = webkit_dom_document_create_element (document, "div", NULL);
webkit_dom_html_element_set_inner_text (
WEBKIT_DOM_HTML_ELEMENT (element), inner_text, NULL);
inner_html = webkit_dom_html_element_get_inner_html (
WEBKIT_DOM_HTML_ELEMENT (element));
parse_html_into_paragraphs (view, document, element, inner_html, FALSE);
g_free (inner_html);
has_selection = !e_html_editor_selection_is_collapsed (selection);
range = html_editor_view_get_dom_range (view);
node = webkit_dom_range_get_end_container (range, NULL);
citation_level = get_citation_level (node, FALSE);
/* Pasting into the citation */
if (citation_level > 0) {
gint length;
gint word_wrap_length = e_html_editor_selection_get_word_wrap_length (selection);
WebKitDOMElement *selection_start_marker, *selection_end_marker;
WebKitDOMElement *br;
WebKitDOMNode *first_paragraph, *last_paragraph;
WebKitDOMNode *child, *parent;
e_html_editor_selection_save (selection);
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");
first_paragraph = webkit_dom_node_get_first_child (
WEBKIT_DOM_NODE (element));
last_paragraph = webkit_dom_node_get_last_child (
WEBKIT_DOM_NODE (element));
length = word_wrap_length - 2 * citation_level;
/* Pasting text that was parsed just into one paragraph */
if (webkit_dom_node_is_same_node (first_paragraph, last_paragraph)) {
WebKitDOMNode *child, *parent;
parent = get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
remove_quoting_from_element (WEBKIT_DOM_ELEMENT (parent));
remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (parent));
while ((child = webkit_dom_node_get_first_child (first_paragraph)))
webkit_dom_node_insert_before (
parent,
child,
WEBKIT_DOM_NODE (selection_start_marker),
NULL);
parent = WEBKIT_DOM_NODE (
e_html_editor_selection_wrap_paragraph_length (
selection, WEBKIT_DOM_ELEMENT (parent), length));
webkit_dom_node_normalize (parent);
quote_plain_text_element_after_wrapping (
document, WEBKIT_DOM_ELEMENT (parent), citation_level);
goto delete;
}
/* Pasting content parsed into the multiple paragraphs */
parent = get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
remove_quoting_from_element (WEBKIT_DOM_ELEMENT (parent));
remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (parent));
/* Move the elements from the first paragraph before the selection start element */
while ((child = webkit_dom_node_get_first_child (first_paragraph)))
webkit_dom_node_insert_before (
parent,
child,
WEBKIT_DOM_NODE (selection_start_marker),
NULL);
remove_node (first_paragraph);
/* If the BR element is on the last position, remove it as we don't need it */
child = webkit_dom_node_get_last_child (parent);
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (child))
remove_node (child);
parent = get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_end_marker)),
child = webkit_dom_node_get_next_sibling (
WEBKIT_DOM_NODE (selection_end_marker));
/* Move the elements that are in the same paragraph as the selection end
* on the end of pasted text, but avoid BR on the end of paragraph */
while (child) {
WebKitDOMNode *next_child =
webkit_dom_node_get_next_sibling (child);
if (!(!next_child && WEBKIT_DOM_IS_HTMLBR_ELEMENT (child)))
webkit_dom_node_append_child (last_paragraph, child, NULL);
child = next_child;
}
/* Caret will be restored on the end of pasted text */
webkit_dom_node_append_child (
last_paragraph,
e_html_editor_selection_get_caret_position_node (document),
NULL);
/* Insert the paragraph with the end of the pasted text after
* the paragraph that contains the selection end */
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (parent),
last_paragraph,
webkit_dom_node_get_next_sibling (parent),
NULL);
/* Wrap, quote and move all paragraphs from pasted text into the body */
while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)))) {
child = WEBKIT_DOM_NODE (e_html_editor_selection_wrap_paragraph_length (
selection, WEBKIT_DOM_ELEMENT (child), length));
quote_plain_text_element_after_wrapping (
document, WEBKIT_DOM_ELEMENT (child), citation_level);
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (last_paragraph),
child,
last_paragraph,
NULL);
}
webkit_dom_node_normalize (last_paragraph);
last_paragraph = WEBKIT_DOM_NODE (
e_html_editor_selection_wrap_paragraph_length (
selection, WEBKIT_DOM_ELEMENT (last_paragraph), length));
quote_plain_text_element_after_wrapping (
document, WEBKIT_DOM_ELEMENT (last_paragraph), citation_level);
remove_quoting_from_element (WEBKIT_DOM_ELEMENT (parent));
remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (parent));
parent = get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
parent = WEBKIT_DOM_NODE (e_html_editor_selection_wrap_paragraph_length (
selection, WEBKIT_DOM_ELEMENT (parent), length));
quote_plain_text_element_after_wrapping (
document, WEBKIT_DOM_ELEMENT (parent), citation_level);
/* If the pasted text begun or ended with a new line we have to
* quote these paragraphs as well */
br = webkit_dom_element_query_selector (
WEBKIT_DOM_ELEMENT (last_paragraph), "br.-x-evo-last-br", NULL);
if (br) {
WebKitDOMNode *parent;
parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (br));
quote_plain_text_recursive (document, parent, parent, citation_level);
webkit_dom_element_remove_attribute (br, "class");
}
br = webkit_dom_document_query_selector (
document, "* > br.-x-evo-first-br", NULL);
if (br) {
WebKitDOMNode *parent;
parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (br));
quote_plain_text_recursive (document, parent, parent, citation_level);
webkit_dom_element_remove_attribute (br, "class");
}
delete:
e_html_editor_selection_restore (selection);
/* Remove the text that was meant to be replaced by the pasted text */
if (has_selection)
e_html_editor_view_exec_command (
view, E_HTML_EDITOR_VIEW_COMMAND_DELETE, NULL);
e_html_editor_selection_restore_caret_position (selection);
goto out;
}
inner_html = webkit_dom_html_element_get_inner_html (
WEBKIT_DOM_HTML_ELEMENT (element));
e_html_editor_view_exec_command (
view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_HTML, inner_html);
g_free (inner_html);
e_html_editor_selection_save (selection);
element = webkit_dom_document_query_selector (
document, "* > br.-x-evo-first-br", NULL);
if (element) {
WebKitDOMNode *next_sibling;
WebKitDOMNode *parent;
parent = webkit_dom_node_get_parent_node (
WEBKIT_DOM_NODE (element));
next_sibling = webkit_dom_node_get_next_sibling (parent);
if (next_sibling)
remove_node (WEBKIT_DOM_NODE (parent));
else
webkit_dom_element_remove_attribute (element, "class");
}
element = webkit_dom_document_query_selector (
document, "* > br.-x-evo-last-br", NULL);
if (element) {
WebKitDOMNode *parent;
WebKitDOMNode *child;
WebKitDOMElement *selection_marker;
parent = webkit_dom_node_get_parent_node (
WEBKIT_DOM_NODE (element));
node = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (parent));
if (node) {
node = webkit_dom_node_get_first_child (node);
if (node) {
inner_html = webkit_dom_node_get_text_content (node);
if (g_str_has_prefix (inner_html, UNICODE_NBSP))
webkit_dom_character_data_replace_data (
WEBKIT_DOM_CHARACTER_DATA (node), 0, 1, "", NULL);
g_free (inner_html);
}
}
selection_marker = webkit_dom_document_get_element_by_id (
document, "-x-evo-selection-end-marker");
if (has_selection) {
/* Everything after the selection end marker have to be in separate
* paragraph */
child = webkit_dom_node_get_next_sibling (
WEBKIT_DOM_NODE (selection_marker));
/* Move the elements that are in the same paragraph as the selection end
* on the end of pasted text, but avoid BR on the end of paragraph */
while (child) {
WebKitDOMNode *next_child =
webkit_dom_node_get_next_sibling (child);
if (!(!next_child && WEBKIT_DOM_IS_HTMLBR_ELEMENT (child)))
webkit_dom_node_append_child (parent, child, NULL);
child = next_child;
}
remove_node (WEBKIT_DOM_NODE (element));
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (
webkit_dom_node_get_parent_node (
WEBKIT_DOM_NODE (selection_marker))),
parent,
webkit_dom_node_get_next_sibling (
webkit_dom_node_get_parent_node (
WEBKIT_DOM_NODE (selection_marker))),
NULL);
node = parent;
} else {
node = webkit_dom_node_get_next_sibling (parent);
if (!node)
fix_structure_after_pasting_multiline_content (parent);
}
if (node) {
/* Restore caret on the end of pasted text */
webkit_dom_node_insert_before (
node,
WEBKIT_DOM_NODE (selection_marker),
webkit_dom_node_get_first_child (node),
NULL);
selection_marker = webkit_dom_document_get_element_by_id (
document, "-x-evo-selection-start-marker");
webkit_dom_node_insert_before (
node,
WEBKIT_DOM_NODE (selection_marker),
webkit_dom_node_get_first_child (node),
NULL);
}
if (element)
webkit_dom_element_remove_attribute (element, "class");
if (webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (parent)) && !has_selection)
remove_node (parent);
} else {
/* When pasting the content that was copied from the composer, WebKit
* restores the selection wrongly, thus is saved wrongly and we have
* to fix it */
WebKitDOMElement *selection_start_marker, *selection_end_marker;
WebKitDOMNode *paragraph, *parent;
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");
paragraph = get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
parent = webkit_dom_node_get_parent_node (paragraph);
if (element_has_class (WEBKIT_DOM_ELEMENT (paragraph), "-x-evo-paragraph") &&
element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-paragraph"))
fix_structure_after_pasting_multiline_content (paragraph);
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_get_next_sibling (
WEBKIT_DOM_NODE (selection_start_marker)),
NULL);
}
e_html_editor_selection_restore (selection);
out:
e_html_editor_view_force_spell_check (view);
e_html_editor_selection_scroll_to_caret (selection);
register_input_event_listener_on_body (view);
g_free (inner_text);
}
static void
html_plain_text_convertor_load_status_changed (WebKitWebView *web_view,
GParamSpec *pspec,
EHTMLEditorView *view)
{
WebKitDOMDocument *document_convertor;
if (webkit_web_view_get_load_status (web_view) != WEBKIT_LOAD_FINISHED)
return;
document_convertor = webkit_web_view_get_dom_document (web_view);
if (view->priv->convertor_insert)
html_editor_view_insert_converted_html_into_selection (
view, document_convertor);
else
html_editor_view_process_document_from_convertor (
view, document_convertor);
}
static void
e_html_editor_settings_changed_cb (GSettings *settings,
const gchar *key,
EHTMLEditorView *view)
{
GVariant *new_value, *old_value;
new_value = g_settings_get_value (settings, key);
old_value = g_hash_table_lookup (view->priv->old_settings, key);
if (!new_value || !old_value || !g_variant_equal (new_value, old_value)) {
if (new_value)
g_hash_table_insert (view->priv->old_settings, g_strdup (key), new_value);
else
g_hash_table_remove (view->priv->old_settings, key);
e_html_editor_view_update_fonts (view);
} else if (new_value) {
g_variant_unref (new_value);
}
}
static void
e_html_editor_view_init (EHTMLEditorView *view)
{
WebKitWebSettings *settings;
GSettings *g_settings;
GSettingsSchema *settings_schema;
ESpellChecker *checker;
gchar **languages;
gchar *comma_separated;
const gchar *user_cache_dir;
view->priv = E_HTML_EDITOR_VIEW_GET_PRIVATE (view);
webkit_web_view_set_editable (WEBKIT_WEB_VIEW (view), TRUE);
settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view));
g_object_set (
G_OBJECT (settings),
"enable-developer-extras", TRUE,
"enable-dom-paste", TRUE,
"enable-file-access-from-file-uris", TRUE,
"enable-plugins", FALSE,
"enable-scripts", FALSE,
"enable-spell-checking", TRUE,
"respect-image-orientation", TRUE,
NULL);
webkit_web_view_set_settings (WEBKIT_WEB_VIEW (view), settings);
view->priv->old_settings = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
/* Override the spell-checker, use our own */
checker = e_spell_checker_new ();
webkit_set_text_checker (G_OBJECT (checker));
g_object_unref (checker);
/* Don't use CSS when possible to preserve compatibility with older
* versions of Evolution or other MUAs */
e_html_editor_view_exec_command (
view, E_HTML_EDITOR_VIEW_COMMAND_STYLE_WITH_CSS, "false");
g_signal_connect (
view, "user-changed-contents",
G_CALLBACK (html_editor_view_user_changed_contents_cb), NULL);
g_signal_connect (
view, "selection-changed",
G_CALLBACK (html_editor_view_selection_changed_cb), NULL);
g_signal_connect (
view, "should-show-delete-interface-for-element",
G_CALLBACK (html_editor_view_should_show_delete_interface_for_element), NULL);
g_signal_connect (
view, "resource-request-starting",
G_CALLBACK (html_editor_view_resource_requested), NULL);
g_signal_connect (
view, "notify::load-status",
G_CALLBACK (html_editor_view_load_status_changed), NULL);
view->priv->selection = g_object_new (
E_TYPE_HTML_EDITOR_SELECTION,
"html-editor-view", view,
NULL);
g_settings = g_settings_new ("org.gnome.desktop.interface");
g_signal_connect (
g_settings, "changed::font-name",
G_CALLBACK (e_html_editor_settings_changed_cb), view);
g_signal_connect (
g_settings, "changed::monospace-font-name",
G_CALLBACK (e_html_editor_settings_changed_cb), view);
view->priv->font_settings = g_settings;
g_settings = g_settings_new ("org.gnome.evolution.mail");
view->priv->mail_settings = g_settings;
/* This schema is optional. Use if available. */
settings_schema = g_settings_schema_source_lookup (
g_settings_schema_source_get_default (),
"org.gnome.settings-daemon.plugins.xsettings", FALSE);
if (settings_schema != NULL) {
g_settings = g_settings_new ("org.gnome.settings-daemon.plugins.xsettings");
g_signal_connect (
settings, "changed::antialiasing",
G_CALLBACK (e_html_editor_settings_changed_cb), view);
view->priv->aliasing_settings = g_settings;
}
view->priv->inline_images = g_hash_table_new_full (
g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_free);
e_html_editor_view_update_fonts (view);
/* Give spell check languages to WebKit */
languages = e_spell_checker_list_active_languages (checker, NULL);
comma_separated = g_strjoinv (",", languages);
g_strfreev (languages);
g_object_set (
G_OBJECT (settings),
"spell-checking-languages", comma_separated,
NULL);
g_free (comma_separated);
view->priv->body_input_event_removed = TRUE;
view->priv->is_message_from_draft = FALSE;
view->priv->is_message_from_selection = FALSE;
view->priv->convertor_insert = FALSE;
view->priv->convertor_web_view =
g_object_ref_sink (WEBKIT_WEB_VIEW (webkit_web_view_new ()));
settings = webkit_web_view_get_settings (view->priv->convertor_web_view);
g_object_set (
G_OBJECT (settings),
"enable-scripts", FALSE,
"enable-plugins", FALSE,
NULL);
g_signal_connect (
view->priv->convertor_web_view, "notify::load-status",
G_CALLBACK (html_plain_text_convertor_load_status_changed), view);
/* Make WebKit think we are displaying a local file, so that it
* does not block loading resources from file:// protocol */
webkit_web_view_load_string (
WEBKIT_WEB_VIEW (view), "", "text/html", "UTF-8", "file://");
html_editor_view_set_links_active (view, FALSE);
if (emd_global_http_cache == NULL) {
user_cache_dir = e_get_user_cache_dir ();
emd_global_http_cache = camel_data_cache_new (user_cache_dir, NULL);
/* cache expiry - 2 hour access, 1 day max */
camel_data_cache_set_expire_age (
emd_global_http_cache, 24 * 60 * 60);
camel_data_cache_set_expire_access (
emd_global_http_cache, 2 * 60 * 60);
}
}
/**
* e_html_editor_view_new:
*
* Returns a new instance of the editor.
*
* Returns: A newly created #EHTMLEditorView. [transfer-full]
*/
EHTMLEditorView *
e_html_editor_view_new (void)
{
return g_object_new (E_TYPE_HTML_EDITOR_VIEW, NULL);
}
/**
* e_html_editor_view_get_selection:
* @view: an #EHTMLEditorView
*
* Returns an #EHTMLEditorSelection object which represents current selection or
* cursor position within the editor document. The #EHTMLEditorSelection allows
* programmer to manipulate with formatting, selection, styles etc.
*
* Returns: An always valid #EHTMLEditorSelection object. The object is owned by
* the @view and should never be free'd.
*/
EHTMLEditorSelection *
e_html_editor_view_get_selection (EHTMLEditorView *view)
{
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), NULL);
return view->priv->selection;
}
/**
* e_html_editor_view_exec_command:
* @view: an #EHTMLEditorView
* @command: an #EHTMLEditorViewCommand to execute
* @value: value of the command (or @NULL if the command does not require value)
*
* The function will fail when @value is @NULL or empty but the current @command
* requires a value to be passed. The @value is ignored when the @command does
* not expect any value.
*
* Returns: @TRUE when the command was succesfully executed, @FALSE otherwise.
*/
gboolean
e_html_editor_view_exec_command (EHTMLEditorView *view,
EHTMLEditorViewCommand command,
const gchar *value)
{
WebKitDOMDocument *document;
const gchar *cmd_str = 0;
gboolean has_value;
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
#define CHECK_COMMAND(cmd,str,val) case cmd:\
if (val) {\
g_return_val_if_fail (value && *value, FALSE);\
}\
has_value = val; \
cmd_str = str;\
break;
switch (command) {
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_BACKGROUND_COLOR, "BackColor", TRUE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_BOLD, "Bold", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_COPY, "Copy", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_CREATE_LINK, "CreateLink", TRUE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_CUT, "Cut", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_DEFAULT_PARAGRAPH_SEPARATOR, "DefaultParagraphSeparator", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_DELETE, "Delete", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FIND_STRING, "FindString", TRUE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FONT_NAME, "FontName", TRUE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FONT_SIZE, "FontSize", TRUE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FONT_SIZE_DELTA, "FontSizeDelta", TRUE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FORE_COLOR, "ForeColor", TRUE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FORMAT_BLOCK, "FormatBlock", TRUE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FORWARD_DELETE, "ForwardDelete", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_HILITE_COLOR, "HiliteColor", TRUE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INDENT, "Indent", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_HORIZONTAL_RULE, "InsertHorizontalRule", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_HTML, "InsertHTML", TRUE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_IMAGE, "InsertImage", TRUE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_LINE_BREAK, "InsertLineBreak", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT, "InsertNewlineInQuotedContent", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_ORDERED_LIST, "InsertOrderedList", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_PARAGRAPH, "InsertParagraph", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_TEXT, "InsertText", TRUE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_UNORDERED_LIST, "InsertUnorderedList", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_ITALIC, "Italic", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_CENTER, "JustifyCenter", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_FULL, "JustifyFull", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_LEFT, "JustifyLeft", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_NONE, "JustifyNone", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_RIGHT, "JustifyRight", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_OUTDENT, "Outdent", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_PASTE, "Paste", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_PASTE_AND_MATCH_STYLE, "PasteAndMatchStyle", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_PASTE_AS_PLAIN_TEXT, "PasteAsPlainText", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_PRINT, "Print", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_REDO, "Redo", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_REMOVE_FORMAT, "RemoveFormat", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_SELECT_ALL, "SelectAll", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_STRIKETHROUGH, "Strikethrough", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_STYLE_WITH_CSS, "StyleWithCSS", TRUE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_SUBSCRIPT, "Subscript", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_SUPERSCRIPT, "Superscript", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_TRANSPOSE, "Transpose", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_UNDERLINE, "Underline", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_UNDO, "Undo", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_UNLINK, "Unlink", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_UNSELECT, "Unselect", FALSE)
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_USE_CSS, "UseCSS", TRUE)
}
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
return webkit_dom_document_exec_command (
document, cmd_str, FALSE, has_value ? value : "" );
}
/**
* e_html_editor_view_get_changed:
* @view: an #EHTMLEditorView
*
* Whether content of the editor has been changed.
*
* Returns: @TRUE when document was changed, @FALSE otherwise.
*/
gboolean
e_html_editor_view_get_changed (EHTMLEditorView *view)
{
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
return view->priv->changed;
}
/**
* e_html_editor_view_set_changed:
* @view: an #EHTMLEditorView
* @changed: whether document has been changed or not
*
* Sets whether document has been changed or not. The editor is tracking changes
* automatically, but sometimes it's necessary to change the dirty flag to force
* "Save changes" dialog for example.
*/
void
e_html_editor_view_set_changed (EHTMLEditorView *view,
gboolean changed)
{
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
if (view->priv->changed == changed)
return;
view->priv->changed = changed;
g_object_notify (G_OBJECT (view), "changed");
}
/**
* e_html_editor_view_get_html_mode:
* @view: an #EHTMLEditorView
*
* Whether the editor is in HTML mode or plain text mode. In HTML mode,
* more formatting options are avilable an the email is sent as
* multipart/alternative.
*
* Returns: @TRUE when HTML mode is enabled, @FALSE otherwise.
*/
gboolean
e_html_editor_view_get_html_mode (EHTMLEditorView *view)
{
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
return view->priv->html_mode;
}
static gint
get_indentation_level (WebKitDOMElement *element)
{
WebKitDOMElement *parent;
gint level = 1;
parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (element));
/* Count level of indentation */
while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
if (element_has_class (parent, "-x-evo-indented"))
level++;
parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (parent));
}
return level;
}
static void
process_blockquote (WebKitDOMElement *blockquote)
{
WebKitDOMNodeList *list;
int jj, length;
/* First replace wrappers */
list = webkit_dom_element_query_selector_all (
blockquote, "span.-x-evo-temp-text-wrapper", NULL);
length = webkit_dom_node_list_get_length (list);
for (jj = 0; jj < length; jj++) {
WebKitDOMNode *quoted_node;
gchar *text_content;
quoted_node = webkit_dom_node_list_item (list, jj);
text_content = webkit_dom_node_get_text_content (quoted_node);
webkit_dom_html_element_set_outer_html (
WEBKIT_DOM_HTML_ELEMENT (quoted_node), text_content, NULL);
g_free (text_content);
}
g_object_unref (list);
/* Afterwards replace quote nodes with symbols */
list = webkit_dom_element_query_selector_all (
blockquote, "span.-x-evo-quoted", NULL);
length = webkit_dom_node_list_get_length (list);
for (jj = 0; jj < length; jj++) {
WebKitDOMNode *quoted_node;
gchar *text_content;
quoted_node = webkit_dom_node_list_item (list, jj);
text_content = webkit_dom_node_get_text_content (quoted_node);
webkit_dom_html_element_set_outer_html (
WEBKIT_DOM_HTML_ELEMENT (quoted_node), text_content, NULL);
g_free (text_content);
}
g_object_unref (list);
if (element_has_class (blockquote, "-x-evo-indented")) {
WebKitDOMNode *child;
gchar *spaces;
spaces = g_strnfill (4 * get_indentation_level (blockquote), ' ');
child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (blockquote));
while (child) {
/* If next sibling is indented blockqoute skip it,
* it will be processed afterwards */
if (WEBKIT_DOM_IS_ELEMENT (child) &&
element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-indented"))
child = webkit_dom_node_get_next_sibling (child);
if (WEBKIT_DOM_IS_TEXT (child)) {
gchar *text_content;
gchar *indented_text;
text_content = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (child));
indented_text = g_strconcat (spaces, text_content, NULL);
webkit_dom_text_replace_whole_text (
WEBKIT_DOM_TEXT (child),
indented_text,
NULL);
g_free (text_content);
g_free (indented_text);
}
if (!child)
break;
/* Move to next node */
if (webkit_dom_node_has_child_nodes (child))
child = webkit_dom_node_get_first_child (child);
else if (webkit_dom_node_get_next_sibling (child))
child = webkit_dom_node_get_next_sibling (child);
else {
if (webkit_dom_node_is_equal_node (WEBKIT_DOM_NODE (blockquote), child))
break;
child = webkit_dom_node_get_parent_node (child);
if (child)
child = webkit_dom_node_get_next_sibling (child);
}
}
g_free (spaces);
webkit_dom_element_remove_attribute (blockquote, "style");
}
}
/* Taken from GtkHTML */
static gchar *
get_alpha_value (gint value,
gboolean lower)
{
GString *str;
gchar *rv;
gint add = lower ? 'a' : 'A';
str = g_string_new (". ");
do {
g_string_prepend_c (str, ((value - 1) % 26) + add);
value = (value - 1) / 26;
} while (value);
rv = str->str;
g_string_free (str, FALSE);
return rv;
}
/* Taken from GtkHTML */
static gchar *
get_roman_value (gint value,
gboolean lower)
{
GString *str;
const gchar *base = "IVXLCDM";
gchar *rv;
gint b, r, add = lower ? 'a' - 'A' : 0;
if (value > 3999)
return g_strdup ("?. ");
str = g_string_new (". ");
for (b = 0; value > 0 && b < 7 - 1; b += 2, value /= 10) {
r = value % 10;
if (r != 0) {
if (r < 4) {
for (; r; r--)
g_string_prepend_c (str, base[b] + add);
} else if (r == 4) {
g_string_prepend_c (str, base[b + 1] + add);
g_string_prepend_c (str, base[b] + add);
} else if (r == 5) {
g_string_prepend_c (str, base[b + 1] + add);
} else if (r < 9) {
for (; r > 5; r--)
g_string_prepend_c (str, base[b] + add);
g_string_prepend_c (str, base[b + 1] + add);
} else if (r == 9) {
g_string_prepend_c (str, base[b + 2] + add);
g_string_prepend_c (str, base[b] + add);
}
}
}
rv = str->str;
g_string_free (str, FALSE);
return rv;
}
static void
process_list_to_plain_text (EHTMLEditorView *view,
WebKitDOMElement *element,
gint level,
GString *output)
{
EHTMLEditorSelectionBlockFormat format;
EHTMLEditorSelectionAlignment alignment;
gint counter = 1;
gchar *indent_per_level = g_strnfill (SPACES_PER_LIST_LEVEL, ' ');
WebKitDOMNode *item;
gint word_wrap_length = e_html_editor_selection_get_word_wrap_length (
e_html_editor_view_get_selection (view));
format = e_html_editor_selection_get_list_format_from_node (
WEBKIT_DOM_NODE (element));
/* Process list items to plain text */
item = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element));
while (item) {
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (item))
g_string_append (output, "\n");
if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) {
gchar *space, *item_str = NULL;
gint ii = 0;
WebKitDOMElement *wrapped;
GString *item_value = g_string_new ("");
alignment = e_html_editor_selection_get_list_alignment_from_node (
WEBKIT_DOM_NODE (item));
wrapped = webkit_dom_element_query_selector (
WEBKIT_DOM_ELEMENT (item), ".-x-evo-wrap-br", NULL);
/* Wrapped text */
if (wrapped) {
WebKitDOMNode *node = webkit_dom_node_get_first_child (item);
GString *line = g_string_new ("");
while (node) {
if (WEBKIT_DOM_IS_TEXT (node)) {
/* append text from line */
gchar *text_content;
text_content = webkit_dom_node_get_text_content (node);
g_string_append (line, text_content);
g_free (text_content);
}
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (node) &&
element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-wrap-br")) {
g_string_append (line, "\n");
/* put spaces before line characters -> wordwraplength - indentation */
for (ii = 0; ii < level; ii++)
g_string_append (line, indent_per_level);
g_string_append (item_value, line->str);
g_string_erase (line, 0, -1);
}
node = webkit_dom_node_get_next_sibling (node);
}
if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT)
g_string_append (item_value, line->str);
if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER) {
gchar *fill = NULL;
gint fill_length;
fill_length = word_wrap_length - g_utf8_strlen (line->str, -1);
fill_length -= ii * SPACES_PER_LIST_LEVEL;
fill_length /= 2;
if (fill_length < 0)
fill_length = 0;
fill = g_strnfill (fill_length, ' ');
g_string_append (item_value, fill);
g_string_append (item_value, line->str);
g_free (fill);
}
if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT) {
gchar *fill = NULL;
gint fill_length;
fill_length = word_wrap_length - g_utf8_strlen (line->str, -1);
fill_length -= ii * SPACES_PER_LIST_LEVEL;
if (fill_length < 0)
fill_length = 0;
fill = g_strnfill (fill_length, ' ');
g_string_append (item_value, fill);
g_string_append (item_value, line->str);
g_free (fill);
}
g_string_free (line, TRUE);
/* that same here */
} else {
gchar *text_content =
webkit_dom_node_get_text_content (item);
g_string_append (item_value, text_content);
g_free (text_content);
}
if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST) {
space = g_strnfill (SPACES_PER_LIST_LEVEL - 2, ' ');
item_str = g_strdup_printf (
"%s* %s", space, item_value->str);
g_free (space);
}
if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST) {
gint length = 1, tmp = counter;
while ((tmp = tmp / 10) > 1)
length++;
if (tmp == 1)
length++;
space = g_strnfill (SPACES_PER_LIST_LEVEL - 2 - length, ' ');
item_str = g_strdup_printf (
"%s%d. %s", space, counter, item_value->str);
g_free (space);
}
if (format > E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST) {
gchar *value;
if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA)
value = get_alpha_value (counter, FALSE);
else
value = get_roman_value (counter, FALSE);
/* Value already containes dot and space */
space = g_strnfill (SPACES_PER_LIST_LEVEL - strlen (value), ' ');
item_str = g_strdup_printf (
"%s%s%s", space, value, item_value->str);
g_free (space);
g_free (value);
}
if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT) {
for (ii = 0; ii < level - 1; ii++) {
g_string_append (output, indent_per_level);
}
g_string_append (output, item_str);
}
if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT) {
if (!wrapped) {
gchar *fill = NULL;
gint fill_length;
fill_length = word_wrap_length - g_utf8_strlen (item_str, -1);
fill_length -= ii * SPACES_PER_LIST_LEVEL;
if (fill_length < 0)
fill_length = 0;
if (g_str_has_suffix (item_str, " "))
fill_length++;
fill = g_strnfill (fill_length, ' ');
g_string_append (output, fill);
g_free (fill);
}
if (g_str_has_suffix (item_str, " "))
g_string_append_len (output, item_str, g_utf8_strlen (item_str, -1) - 1);
else
g_string_append (output, item_str);
}
if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER) {
if (!wrapped) {
gchar *fill = NULL;
gint fill_length = 0;
for (ii = 0; ii < level - 1; ii++)
g_string_append (output, indent_per_level);
fill_length = word_wrap_length - g_utf8_strlen (item_str, -1);
fill_length -= ii * SPACES_PER_LIST_LEVEL;
fill_length /= 2;
if (fill_length < 0)
fill_length = 0;
if (g_str_has_suffix (item_str, " "))
fill_length++;
fill = g_strnfill (fill_length, ' ');
g_string_append (output, fill);
g_free (fill);
}
if (g_str_has_suffix (item_str, " "))
g_string_append_len (output, item_str, g_utf8_strlen (item_str, -1) - 1);
else
g_string_append (output, item_str);
}
counter++;
item = webkit_dom_node_get_next_sibling (item);
if (item)
g_string_append (output, "\n");
g_free (item_str);
g_string_free (item_value, TRUE);
} else if (WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (item) ||
WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (item)) {
process_list_to_plain_text (
view, WEBKIT_DOM_ELEMENT (item), level + 1, output);
item = webkit_dom_node_get_next_sibling (item);
} else {
item = webkit_dom_node_get_next_sibling (item);
}
}
if (webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element)))
g_string_append (output, "\n");
g_free (indent_per_level);
}
static void
remove_base_attributes (WebKitDOMElement *element)
{
webkit_dom_element_remove_attribute (element, "class");
webkit_dom_element_remove_attribute (element, "id");
webkit_dom_element_remove_attribute (element, "name");
}
static void
remove_evolution_attributes (WebKitDOMElement *element)
{
webkit_dom_element_remove_attribute (element, "x-evo-smiley");
webkit_dom_element_remove_attribute (element, "data-converted");
webkit_dom_element_remove_attribute (element, "data-edit-as-new");
webkit_dom_element_remove_attribute (element, "data-evo-draft");
webkit_dom_element_remove_attribute (element, "data-inline");
webkit_dom_element_remove_attribute (element, "data-uri");
webkit_dom_element_remove_attribute (element, "data-message");
webkit_dom_element_remove_attribute (element, "data-name");
webkit_dom_element_remove_attribute (element, "data-new-message");
webkit_dom_element_remove_attribute (element, "spellcheck");
}
/*
static void
remove_style_attributes (WebKitDOMElement *element)
{
webkit_dom_element_remove_attribute (element, "bgcolor");
webkit_dom_element_remove_attribute (element, "background");
webkit_dom_element_remove_attribute (element, "style");
}
*/
static gboolean
replace_to_whitespaces (const GMatchInfo *info,
GString *res,
gpointer data)
{
gint ii, length = 0;
gint chars_count = GPOINTER_TO_INT (data);
length = TAB_LENGTH - (chars_count % TAB_LENGTH);
for (ii = 0; ii < length; ii++)
g_string_append (res, " ");
return FALSE;
}
static void
process_elements (EHTMLEditorView *view,
WebKitDOMNode *node,
gboolean to_html,
gboolean changing_mode,
gboolean to_plain_text,
GString *buffer)
{
WebKitDOMNodeList *nodes;
gulong ii, length;
gchar *content;
gboolean skip_nl = FALSE;
if (to_plain_text && !buffer)
return;
if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node)) {
if (changing_mode && to_plain_text) {
WebKitDOMNamedNodeMap *attributes;
gulong attributes_length;
/* Copy attributes */
g_string_append (buffer, "<html><head></head><body ");
attributes = webkit_dom_element_get_attributes (
WEBKIT_DOM_ELEMENT (node));
attributes_length =
webkit_dom_named_node_map_get_length (attributes);
for (ii = 0; ii < attributes_length; ii++) {
gchar *name, *value;
WebKitDOMNode *node =
webkit_dom_named_node_map_item (
attributes, ii);
name = webkit_dom_node_get_local_name (node);
value = webkit_dom_node_get_node_value (node);
g_string_append (buffer, name);
g_string_append (buffer, "=\"");
g_string_append (buffer, value);
g_string_append (buffer, "\" ");
g_free (name);
g_free (value);
}
g_string_append (buffer, ">");
g_object_unref (attributes);
}
if (to_html)
remove_evolution_attributes (WEBKIT_DOM_ELEMENT (node));
}
nodes = webkit_dom_node_get_child_nodes (node);
length = webkit_dom_node_list_get_length (nodes);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *child;
gboolean skip_node = FALSE;
child = webkit_dom_node_list_item (nodes, ii);
if (WEBKIT_DOM_IS_TEXT (child)) {
gchar *content, *tmp;
GRegex *regex;
gint char_count = 0;
content = webkit_dom_node_get_text_content (child);
if (!changing_mode && to_plain_text) {
/* Replace tabs with 8 whitespaces, otherwise they got
* replaced by single whitespace */
if (strstr (content, "\x9")) {
if (buffer->str && *buffer->str) {
gchar *start_of_line = g_strrstr_len (
buffer->str, -1, "\n") + 1;
if (start_of_line && *start_of_line)
char_count = strlen (start_of_line);
} else
char_count = 0;
regex = g_regex_new ("\x9", 0, 0, NULL);
tmp = g_regex_replace_eval (
regex,
content,
-1,
0,
0,
(GRegexEvalCallback) replace_to_whitespaces,
GINT_TO_POINTER (char_count),
NULL);
g_string_append (buffer, tmp);
g_free (tmp);
g_free (content);
content = webkit_dom_node_get_text_content (child);
g_regex_unref (regex);
}
}
if (strstr (content, UNICODE_ZERO_WIDTH_SPACE)) {
regex = g_regex_new (UNICODE_ZERO_WIDTH_SPACE, 0, 0, NULL);
tmp = g_regex_replace (
regex, content, -1, 0, "", 0, NULL);
webkit_dom_node_set_text_content (child, tmp, NULL);
g_free (tmp);
g_free (content);
content = webkit_dom_node_get_text_content (child);
g_regex_unref (regex);
}
if (to_plain_text && !changing_mode) {
gchar *class;
const gchar *css_align;
if (strstr (content, UNICODE_NBSP)) {
GString *nbsp_free;
nbsp_free = e_str_replace_string (
content, UNICODE_NBSP, " ");
g_free (content);
content = g_string_free (nbsp_free, FALSE);
}
class = webkit_dom_element_get_class_name (WEBKIT_DOM_ELEMENT (node));
if ((css_align = strstr (class, "-x-evo-align-"))) {
gchar *align;
gchar *content_with_align;
gint length;
gint word_wrap_length =
e_html_editor_selection_get_word_wrap_length (
e_html_editor_view_get_selection (view));
if (!g_str_has_prefix (css_align + 13, "left")) {
if (g_str_has_prefix (css_align + 13, "center"))
length = (word_wrap_length - g_utf8_strlen (content, -1)) / 2;
else
length = word_wrap_length - g_utf8_strlen (content, -1);
if (length < 0)
length = 0;
if (g_str_has_suffix (content, " ")) {
char *tmp;
length++;
align = g_strnfill (length, ' ');
tmp = g_strndup (content, g_utf8_strlen (content, -1) -1);
content_with_align = g_strconcat (
align, tmp, NULL);
g_free (tmp);
} else {
align = g_strnfill (length, ' ');
content_with_align = g_strconcat (
align, content, NULL);
}
g_free (content);
g_free (align);
content = content_with_align;
}
}
g_free (class);
}
if (to_plain_text || changing_mode)
g_string_append (buffer, content);
g_free (content);
goto next;
}
if (WEBKIT_DOM_IS_COMMENT (child) || !WEBKIT_DOM_IS_ELEMENT (child))
goto next;
/* Leave caret position untouched */
if (element_has_id (WEBKIT_DOM_ELEMENT (child), "-x-evo-caret-position")) {
if (changing_mode && to_plain_text) {
content = webkit_dom_html_element_get_outer_html (
WEBKIT_DOM_HTML_ELEMENT (child));
g_string_append (buffer, content);
g_free (content);
}
if (to_html)
remove_node (child);
skip_node = TRUE;
goto next;
}
if (element_has_class (WEBKIT_DOM_ELEMENT (child), "Apple-tab-span")) {
if (!changing_mode && to_plain_text) {
gchar *content, *tmp;
GRegex *regex;
gint char_count = 0;
content = webkit_dom_node_get_text_content (child);
/* Replace tabs with 8 whitespaces, otherwise they got
* replaced by single whitespace */
if (strstr (content, "\x9")) {
if (buffer->str && *buffer->str) {
gchar *start_of_line = g_strrstr_len (
buffer->str, -1, "\n") + 1;
if (start_of_line && *start_of_line)
char_count = strlen (start_of_line);
} else
char_count = 0;
regex = g_regex_new ("\x9", 0, 0, NULL);
tmp = g_regex_replace_eval (
regex,
content,
-1,
0,
0,
(GRegexEvalCallback) replace_to_whitespaces,
GINT_TO_POINTER (char_count),
NULL);
g_string_append (buffer, tmp);
g_free (tmp);
g_regex_unref (regex);
} else if (content && *content) {
/* Some it happens that some text is written inside
* the tab span element, so save it. */
g_string_append (buffer, content);
}
g_free (content);
}
if (to_html) {
element_remove_class (
WEBKIT_DOM_ELEMENT (child),
"Applet-tab-span");
}
skip_node = TRUE;
goto next;
}
/* Leave blockquotes as they are */
if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (child)) {
if (changing_mode && to_plain_text) {
content = webkit_dom_html_element_get_outer_html (
WEBKIT_DOM_HTML_ELEMENT (child));
g_string_append (buffer, content);
g_free (content);
skip_node = TRUE;
goto next;
} else {
if (!changing_mode && to_plain_text) {
if (get_citation_level (child, FALSE) == 0) {
gchar *value = webkit_dom_element_get_attribute (
WEBKIT_DOM_ELEMENT (child), "type");
if (value && g_strcmp0 (value, "cite") == 0)
g_string_append (buffer, "\n");
g_free (value);
}
}
process_blockquote (WEBKIT_DOM_ELEMENT (child));
if (to_html)
remove_base_attributes (WEBKIT_DOM_ELEMENT (child));
}
}
if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (child) &&
element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-indented"))
process_blockquote (WEBKIT_DOM_ELEMENT (child));
if (WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (child) ||
WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (child)) {
if (to_plain_text) {
if (changing_mode) {
content = webkit_dom_html_element_get_outer_html (
WEBKIT_DOM_HTML_ELEMENT (child));
g_string_append (buffer, content);
g_free (content);
} else {
process_list_to_plain_text (
view, WEBKIT_DOM_ELEMENT (child), 1, buffer);
}
skip_node = TRUE;
goto next;
}
}
if (element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-resizable-wrapper") &&
!element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-smiley-wrapper")) {
WebKitDOMNode *image =
webkit_dom_node_get_first_child (child);
if (to_html && WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (image)) {
remove_evolution_attributes (
WEBKIT_DOM_ELEMENT (image));
webkit_dom_node_replace_child (
node, image, child, NULL);
}
skip_node = TRUE;
goto next;
}
/* Leave paragraphs as they are */
if (element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-paragraph")) {
if (changing_mode && to_plain_text) {
content = webkit_dom_html_element_get_outer_html (
WEBKIT_DOM_HTML_ELEMENT (child));
g_string_append (buffer, content);
g_free (content);
skip_node = TRUE;
goto next;
}
if (to_html) {
remove_base_attributes (WEBKIT_DOM_ELEMENT (child));
remove_evolution_attributes (WEBKIT_DOM_ELEMENT (child));
}
if (!changing_mode && to_plain_text)
if (!webkit_dom_node_has_child_nodes (child))
g_string_append (buffer, "\n");
}
/* Signature */
if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (child) &&
element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-signature-wrapper")) {
WebKitDOMNode *first_child;
first_child = webkit_dom_node_get_first_child (child);
if (to_html) {
remove_base_attributes (
WEBKIT_DOM_ELEMENT (first_child));
remove_evolution_attributes (
WEBKIT_DOM_ELEMENT (first_child));
}
if (to_plain_text && !changing_mode) {
g_string_append (buffer, "\n");
content = webkit_dom_html_element_get_inner_text (
WEBKIT_DOM_HTML_ELEMENT (first_child));
g_string_append (buffer, content);
g_free (content);
skip_nl = TRUE;
}
if (to_plain_text && changing_mode) {
content = webkit_dom_html_element_get_outer_html (
WEBKIT_DOM_HTML_ELEMENT (child));
g_string_append (buffer, content);
g_free (content);
skip_node = TRUE;
}
skip_node = TRUE;
goto next;
}
/* Replace smileys with their text representation */
if (element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-smiley-wrapper")) {
if (to_plain_text && !changing_mode) {
WebKitDOMNode *text_version;
text_version = webkit_dom_node_get_last_child (child);
content = webkit_dom_html_element_get_inner_text (
WEBKIT_DOM_HTML_ELEMENT (text_version));
g_string_append (buffer, content);
g_free (content);
skip_node = TRUE;
goto next;
}
if (to_html) {
WebKitDOMElement *img;
img = WEBKIT_DOM_ELEMENT (
webkit_dom_node_get_first_child (child)),
remove_evolution_attributes (img);
remove_base_attributes (img);
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (child),
WEBKIT_DOM_NODE (img),
child,
NULL);
remove_node (child);
skip_node = TRUE;
goto next;
}
}
/* Leave PRE elements untouched */
if (WEBKIT_DOM_IS_HTML_PRE_ELEMENT (child)) {
if (changing_mode && to_plain_text) {
content = webkit_dom_html_element_get_outer_html (
WEBKIT_DOM_HTML_ELEMENT (child));
g_string_append (buffer, content);
g_free (content);
skip_node = TRUE;
}
if (to_html)
remove_evolution_attributes (WEBKIT_DOM_ELEMENT (child));
}
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (child)) {
if (to_plain_text) {
if (element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-wrap-br")) {
g_string_append (buffer, changing_mode ? "<br>" : "\n");
goto next;
}
/* Insert new line when we hit the BR element that is
* not the last element in the block */
if (!webkit_dom_node_is_same_node (
child, webkit_dom_node_get_last_child (node))) {
g_string_append (buffer, changing_mode ? "<br>" : "\n");
} else {
/* In citations in the empty lines the BR element
* is on the end and we have to put NL there */
WebKitDOMNode *parent;
parent = webkit_dom_node_get_parent_node (child);
if (webkit_dom_node_get_next_sibling (parent)) {
parent = webkit_dom_node_get_parent_node (parent);
if (is_citation_node (parent))
g_string_append (buffer, changing_mode ? "<br>" : "\n");
}
}
}
}
if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (child)) {
if (changing_mode && to_plain_text) {
content = webkit_dom_html_element_get_outer_html (
WEBKIT_DOM_HTML_ELEMENT (child));
g_string_append (buffer, content);
g_free (content);
skip_node = TRUE;
}
if (!changing_mode && to_plain_text) {
content = webkit_dom_html_element_get_inner_text (
WEBKIT_DOM_HTML_ELEMENT (child));
g_string_append (buffer, content);
g_free (content);
skip_node = TRUE;
}
}
next:
if (webkit_dom_node_has_child_nodes (child) && !skip_node)
process_elements (
view, child, to_html, changing_mode, to_plain_text, buffer);
}
if (to_plain_text && (
WEBKIT_DOM_IS_HTML_DIV_ELEMENT (node) ||
WEBKIT_DOM_IS_HTML_PARAGRAPH_ELEMENT (node) ||
WEBKIT_DOM_IS_HTML_PRE_ELEMENT (node) ||
WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node))) {
gboolean add_br = TRUE;
WebKitDOMNode *next_sibling = webkit_dom_node_get_next_sibling (node);
WebKitDOMNode *last_child = webkit_dom_node_get_last_child (node);
if (last_child && WEBKIT_DOM_IS_HTMLBR_ELEMENT (last_child))
if (webkit_dom_node_get_previous_sibling (last_child))
add_br = FALSE;
/* If we don't have next sibling (last element in body) or next element is
* signature we are not adding the BR element */
if (!next_sibling)
add_br = FALSE;
else if (next_sibling && WEBKIT_DOM_IS_HTML_DIV_ELEMENT (next_sibling)) {
if (webkit_dom_element_query_selector (
WEBKIT_DOM_ELEMENT (next_sibling),
"span.-x-evo-signature", NULL)) {
add_br = FALSE;
}
}
if (add_br && !skip_nl)
g_string_append (buffer, changing_mode ? "<br>" : "\n");
}
g_object_unref (nodes);
}
static void
remove_wrapping_from_view (EHTMLEditorView *view)
{
gint length;
gint ii;
WebKitDOMDocument *document;
WebKitDOMNodeList *list;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
list = webkit_dom_document_query_selector_all (document, "br.-x-evo-wrap-br", NULL);
length = webkit_dom_node_list_get_length (list);
for (ii = 0; ii < length; ii++)
remove_node (webkit_dom_node_list_item (list, ii));
g_object_unref (list);
}
void
remove_image_attributes_from_element (WebKitDOMElement *element)
{
webkit_dom_element_remove_attribute (element, "background");
webkit_dom_element_remove_attribute (element, "data-uri");
webkit_dom_element_remove_attribute (element, "data-inline");
webkit_dom_element_remove_attribute (element, "data-name");
}
static void
remove_background_images_in_document (WebKitDOMDocument *document)
{
gint length, ii;
WebKitDOMNodeList *elements;
elements = webkit_dom_document_query_selector_all (
document, "[background][data-inline]", NULL);
length = webkit_dom_node_list_get_length (elements);
for (ii = 0; ii < length; ii++) {
WebKitDOMElement *element = WEBKIT_DOM_ELEMENT (
webkit_dom_node_list_item (elements, ii));
remove_image_attributes_from_element (element);
}
g_object_unref (elements);
}
static void
remove_images_in_element (EHTMLEditorView *view,
WebKitDOMElement *element)
{
gint length, ii;
WebKitDOMNodeList *images;
images = webkit_dom_element_query_selector_all (
element, "img:not(.-x-evo-smiley-img)", NULL);
length = webkit_dom_node_list_get_length (images);
for (ii = 0; ii < length; ii++)
remove_node (webkit_dom_node_list_item (images, ii));
g_object_unref (images);
}
static void
remove_images (EHTMLEditorView *view)
{
WebKitDOMDocument *document;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
remove_images_in_element (
view, WEBKIT_DOM_ELEMENT (webkit_dom_document_get_body (document)));
}
static void
toggle_smileys (EHTMLEditorView *view)
{
gboolean html_mode;
gint length;
gint ii;
WebKitDOMDocument *document;
WebKitDOMNodeList *smileys;
html_mode = e_html_editor_view_get_html_mode (view);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
smileys = webkit_dom_document_query_selector_all (
document, "img.-x-evo-smiley-img", NULL);
length = webkit_dom_node_list_get_length (smileys);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *img = webkit_dom_node_list_item (smileys, ii);
WebKitDOMNode *text = webkit_dom_node_get_next_sibling (img);
WebKitDOMElement *parent = webkit_dom_node_get_parent_element (img);
webkit_dom_element_set_attribute (
WEBKIT_DOM_ELEMENT (html_mode ? text : img),
"style",
"display: none",
NULL);
webkit_dom_element_remove_attribute (
WEBKIT_DOM_ELEMENT (html_mode ? img : text), "style");
if (html_mode)
element_add_class (parent, "-x-evo-resizable-wrapper");
else
element_remove_class (parent, "-x-evo-resizable-wrapper");
}
g_object_unref (smileys);
}
static void
toggle_paragraphs_style_in_element (EHTMLEditorView *view,
WebKitDOMElement *element,
gboolean html_mode)
{
EHTMLEditorSelection *selection;
gint ii, length;
WebKitDOMNodeList *paragraphs;
selection = e_html_editor_view_get_selection (view);
paragraphs = webkit_dom_element_query_selector_all (
element, ".-x-evo-paragraph", NULL);
length = webkit_dom_node_list_get_length (paragraphs);
for (ii = 0; ii < length; ii++) {
gchar *style;
const gchar *css_align;
WebKitDOMNode *node = webkit_dom_node_list_item (paragraphs, ii);
if (html_mode) {
style = webkit_dom_element_get_attribute (
WEBKIT_DOM_ELEMENT (node), "style");
if ((css_align = strstr (style, "text-align: "))) {
webkit_dom_element_set_attribute (
WEBKIT_DOM_ELEMENT (node),
"style",
g_str_has_prefix (css_align + 12, "center") ?
"text-align: center" :
"text-align: right",
NULL);
} else {
/* In HTML mode the paragraphs don't have width limit */
webkit_dom_element_remove_attribute (
WEBKIT_DOM_ELEMENT (node), "style");
}
g_free (style);
} else {
WebKitDOMNode *parent;
parent = webkit_dom_node_get_parent_node (node);
/* If the paragraph is inside indented paragraph don't set
* the style as it will be inherited */
if (!element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-indented")) {
const gchar *style_to_add = "";
style = webkit_dom_element_get_attribute (
WEBKIT_DOM_ELEMENT (node), "style");
if ((css_align = strstr (style, "text-align: "))) {
style_to_add = g_str_has_prefix (
css_align + 12, "center") ?
"text-align: center;" :
"text-align: right;";
}
/* In plain text mode the paragraphs have width limit */
e_html_editor_selection_set_paragraph_style (
selection, WEBKIT_DOM_ELEMENT (node),
-1, 0, style_to_add);
g_free (style);
}
}
}
g_object_unref (paragraphs);
}
static void
toggle_paragraphs_style (EHTMLEditorView *view)
{
WebKitDOMDocument *document;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
toggle_paragraphs_style_in_element (
view,
WEBKIT_DOM_ELEMENT (webkit_dom_document_get_body (document)),
view->priv->html_mode);
}
static gchar *
process_content_for_saving_as_draft (EHTMLEditorView *view)
{
WebKitDOMDocument *document;
WebKitDOMHTMLElement *body;
WebKitDOMElement *document_element;
gchar *content;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
body = webkit_dom_document_get_body (document);
webkit_dom_element_set_attribute (
WEBKIT_DOM_ELEMENT (body), "data-evo-draft", "", NULL);
document_element = webkit_dom_document_get_document_element (document);
content = webkit_dom_html_element_get_outer_html (
WEBKIT_DOM_HTML_ELEMENT (document_element));
webkit_dom_element_remove_attribute (
WEBKIT_DOM_ELEMENT (body), "data-evo-draft");
return content;
}
static gchar *
process_content_for_mode_change (EHTMLEditorView *view)
{
WebKitDOMDocument *document;
WebKitDOMNode *body;
GString *plain_text;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
body = WEBKIT_DOM_NODE (webkit_dom_document_get_body (document));
plain_text = g_string_sized_new (1024);
process_elements (view, body, FALSE, TRUE, TRUE, plain_text);
g_string_append (plain_text, "</body></html>");
return g_string_free (plain_text, FALSE);
}
static void
convert_element_from_html_to_plain_text (EHTMLEditorView *view,
WebKitDOMElement *element,
gboolean *wrap,
gboolean *quote)
{
EHTMLEditorSelection *selection;
gint blockquotes_count;
gchar *inner_text, *inner_html;
gboolean restore = TRUE;
WebKitDOMDocument *document;
WebKitDOMElement *top_signature, *signature, *blockquote, *main_blockquote;
WebKitDOMNode *signature_clone, *from;
selection = e_html_editor_view_get_selection (view);
document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (element));
top_signature = webkit_dom_element_query_selector (
element, ".-x-evo-top-signature", NULL);
signature = webkit_dom_element_query_selector (
element, "span.-x-evo-signature", NULL);
main_blockquote = webkit_dom_element_query_selector (
element, "#-x-evo-main-cite", NULL);
blockquote = webkit_dom_document_create_element (
document, "blockquote", NULL);
if (main_blockquote) {
WebKitDOMElement *input_start;
webkit_dom_element_set_attribute (
blockquote, "type", "cite", NULL);
input_start = webkit_dom_element_query_selector (
element, "#-x-evo-input-start", NULL);
restore = input_start ? TRUE : FALSE;
if (input_start) {
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (input_start),
e_html_editor_selection_get_caret_position_node (
document),
NULL);
}
from = WEBKIT_DOM_NODE (main_blockquote);
} else {
if (signature) {
WebKitDOMNode *parent = webkit_dom_node_get_parent_node (
WEBKIT_DOM_NODE (signature));
signature_clone = webkit_dom_node_clone_node (parent, TRUE);
remove_node (parent);
}
from = WEBKIT_DOM_NODE (element);
}
blockquotes_count = create_text_markers_for_citations_in_element (
WEBKIT_DOM_ELEMENT (from));
inner_text = webkit_dom_html_element_get_inner_text (
WEBKIT_DOM_HTML_ELEMENT (from));
webkit_dom_html_element_set_inner_text (
WEBKIT_DOM_HTML_ELEMENT (blockquote), inner_text, NULL);
inner_html = webkit_dom_html_element_get_inner_html (
WEBKIT_DOM_HTML_ELEMENT (blockquote));
parse_html_into_paragraphs (
view, document,
main_blockquote ? blockquote : WEBKIT_DOM_ELEMENT (element),
inner_html,
FALSE);
if (main_blockquote) {
webkit_dom_node_replace_child (
webkit_dom_node_get_parent_node (
WEBKIT_DOM_NODE (main_blockquote)),
WEBKIT_DOM_NODE (blockquote),
WEBKIT_DOM_NODE (main_blockquote),
NULL);
remove_evolution_attributes (WEBKIT_DOM_ELEMENT (element));
*wrap = TRUE;
} else {
WebKitDOMNode *first_child;
if (signature) {
if (!top_signature) {
signature_clone = webkit_dom_node_append_child (
WEBKIT_DOM_NODE (element),
signature_clone,
NULL);
} else {
webkit_dom_node_insert_before (
WEBKIT_DOM_NODE (element),
signature_clone,
webkit_dom_node_get_first_child (
WEBKIT_DOM_NODE (element)),
NULL);
}
}
first_child = webkit_dom_node_get_first_child (
WEBKIT_DOM_NODE (element));
if (first_child) {
if (!webkit_dom_node_has_child_nodes (first_child)) {
webkit_dom_html_element_set_inner_html (
WEBKIT_DOM_HTML_ELEMENT (first_child),
UNICODE_ZERO_WIDTH_SPACE,
NULL);
}
webkit_dom_node_insert_before (
first_child,
e_html_editor_selection_get_caret_position_node (
document),
webkit_dom_node_get_first_child (first_child),
NULL);
}
*wrap = TRUE;
}
*quote = main_blockquote || blockquotes_count > 0;
webkit_dom_element_set_attribute (
WEBKIT_DOM_ELEMENT (element), "data-converted", "", NULL);
g_free (inner_text);
g_free (inner_html);
if (restore)
e_html_editor_selection_restore_caret_position (selection);
}
static gchar *
process_content_for_plain_text (EHTMLEditorView *view)
{
EHTMLEditorSelection *selection;
gboolean wrap = FALSE, quote = FALSE, clean = FALSE;
gboolean converted, is_from_new_message;
gint length, ii;
GString *plain_text;
WebKitDOMDocument *document;
WebKitDOMNode *body, *source;
WebKitDOMNodeList *paragraphs;
plain_text = g_string_sized_new (1024);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
body = WEBKIT_DOM_NODE (webkit_dom_document_get_body (document));
converted = webkit_dom_element_has_attribute (
WEBKIT_DOM_ELEMENT (body), "data-converted");
is_from_new_message = webkit_dom_element_has_attribute (
WEBKIT_DOM_ELEMENT (body), "data-new-message");
source = webkit_dom_node_clone_node (WEBKIT_DOM_NODE (body), TRUE);
selection = e_html_editor_view_get_selection (view);
/* If composer is in HTML mode we have to move the content to plain version */
if (view->priv->html_mode) {
if (converted || is_from_new_message) {
toggle_paragraphs_style_in_element (
view, WEBKIT_DOM_ELEMENT (source), FALSE);
remove_images_in_element (
view, WEBKIT_DOM_ELEMENT (source));
remove_background_images_in_document (
document);
} else {
gchar *inner_html;
WebKitDOMElement *div;
inner_html = webkit_dom_html_element_get_inner_html (
WEBKIT_DOM_HTML_ELEMENT (body));
div = webkit_dom_document_create_element (
document, "div", NULL);
webkit_dom_html_element_set_inner_html (
WEBKIT_DOM_HTML_ELEMENT (div), inner_html, NULL);
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (body),
WEBKIT_DOM_NODE (div),
NULL);
paragraphs = webkit_dom_element_query_selector_all (
div, "#-x-evo-input-start", NULL);
length = webkit_dom_node_list_get_length (paragraphs);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *paragraph;
paragraph = webkit_dom_node_list_item (paragraphs, ii);
webkit_dom_element_remove_attribute (
WEBKIT_DOM_ELEMENT (paragraph), "id");
}
g_object_unref (paragraphs);
convert_element_from_html_to_plain_text (
view, div, &wrap, &quote);
g_object_unref (source);
source = WEBKIT_DOM_NODE (div);
clean = TRUE;
}
}
paragraphs = webkit_dom_element_query_selector_all (
WEBKIT_DOM_ELEMENT (source), ".-x-evo-paragraph", NULL);
length = webkit_dom_node_list_get_length (paragraphs);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *paragraph;
paragraph = webkit_dom_node_list_item (paragraphs, ii);
if (WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (paragraph) ||
WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (paragraph)) {
WebKitDOMNode *item = webkit_dom_node_get_first_child (paragraph);
while (item) {
WebKitDOMNode *next_item =
webkit_dom_node_get_next_sibling (item);
if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) {
e_html_editor_selection_wrap_paragraph (
selection, WEBKIT_DOM_ELEMENT (item));
}
item = next_item;
}
} else {
e_html_editor_selection_wrap_paragraph (
selection, WEBKIT_DOM_ELEMENT (paragraph));
}
}
g_object_unref (paragraphs);
paragraphs = webkit_dom_element_query_selector_all (
WEBKIT_DOM_ELEMENT (source),
"span[id^=\"-x-evo-selection-\"], span#-x-evo-caret-position",
NULL);
length = webkit_dom_node_list_get_length (paragraphs);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *node = webkit_dom_node_list_item (paragraphs, ii);
WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node);
remove_node (node);
webkit_dom_node_normalize (parent);
}
g_object_unref (paragraphs);
if (view->priv->html_mode || quote)
quote_plain_text_recursive (document, source, source, 0);
process_elements (view, source, FALSE, FALSE, TRUE, plain_text);
if (clean)
remove_node (source);
else
g_object_unref (source);
/* Return text content between <body> and </body> */
return g_string_free (plain_text, FALSE);
}
static gchar *
process_content_for_html (EHTMLEditorView *view)
{
WebKitDOMDocument *document;
WebKitDOMNode *body, *document_clone;
gchar *html_content;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
document_clone = webkit_dom_node_clone_node (
WEBKIT_DOM_NODE (webkit_dom_document_get_document_element (document)), TRUE);
body = WEBKIT_DOM_NODE (webkit_dom_element_query_selector (
WEBKIT_DOM_ELEMENT (document_clone), "body", NULL));
process_elements (view, body, TRUE, FALSE, FALSE, NULL);
html_content = webkit_dom_html_element_get_outer_html (
WEBKIT_DOM_HTML_ELEMENT (document_clone));
g_object_unref (document_clone);
return html_content;
}
static gboolean
show_lose_formatting_dialog (EHTMLEditorView *view)
{
gint result;
GtkWidget *toplevel, *dialog;
GtkWindow *parent = NULL;
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
if (GTK_IS_WINDOW (toplevel))
parent = GTK_WINDOW (toplevel);
dialog = gtk_message_dialog_new (
parent,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_WARNING,
GTK_BUTTONS_NONE,
_("Turning HTML mode off will cause the text "
"to lose all formatting. Do you want to continue?"));
gtk_dialog_add_buttons (
GTK_DIALOG (dialog),
_("_Don't lose formatting"), GTK_RESPONSE_CANCEL,
_("_Lose formatting"), GTK_RESPONSE_OK,
NULL);
result = gtk_dialog_run (GTK_DIALOG (dialog));
if (result != GTK_RESPONSE_OK) {
gtk_widget_destroy (dialog);
/* Nothing has changed, but notify anyway */
g_object_notify (G_OBJECT (view), "html-mode");
return FALSE;
}
gtk_widget_destroy (dialog);
return TRUE;
}
static void
clear_attributes (WebKitDOMDocument *document)
{
gint length, ii;
WebKitDOMNamedNodeMap *attributes;
WebKitDOMHTMLElement *body = webkit_dom_document_get_body (document);
WebKitDOMHTMLHeadElement *head = webkit_dom_document_get_head (document);
WebKitDOMElement *document_element =
webkit_dom_document_get_document_element (document);
/* Remove all attributes from HTML element */
attributes = webkit_dom_element_get_attributes (document_element);
length = webkit_dom_named_node_map_get_length (attributes);
for (ii = length - 1; ii >= 0; ii--) {
WebKitDOMNode *node = webkit_dom_named_node_map_item (attributes, ii);
webkit_dom_element_remove_attribute_node (
document_element, WEBKIT_DOM_ATTR (node), NULL);
}
g_object_unref (attributes);
/* Remove everything from HEAD element */
while (webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (head)))
remove_node (webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (head)));
/* Remove non Evolution attributes from BODY element */
attributes = webkit_dom_element_get_attributes (WEBKIT_DOM_ELEMENT (body));
length = webkit_dom_named_node_map_get_length (attributes);
for (ii = length - 1; ii >= 0; 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, "data-") ||
g_str_has_prefix (name, "data-inline") ||
g_str_has_prefix (name, "data-name")) {
webkit_dom_element_remove_attribute_node (
WEBKIT_DOM_ELEMENT (body),
WEBKIT_DOM_ATTR (node),
NULL);
}
g_free (name);
}
g_object_unref (attributes);
}
static void
convert_when_changing_composer_mode (EHTMLEditorView *view)
{
EHTMLEditorSelection *selection;
gboolean quote = FALSE, wrap = FALSE;
WebKitDOMDocument *document;
WebKitDOMHTMLElement *body;
selection = e_html_editor_view_get_selection (view);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
body = webkit_dom_document_get_body (document);
convert_element_from_html_to_plain_text (
view, WEBKIT_DOM_ELEMENT (body), &wrap, &quote);
if (wrap)
e_html_editor_selection_wrap_paragraphs_in_document (selection, document);
if (quote) {
e_html_editor_selection_save_caret_position (selection);
body = WEBKIT_DOM_HTML_ELEMENT (e_html_editor_view_quote_plain_text (view));
e_html_editor_selection_restore_caret_position (selection);
}
toggle_paragraphs_style (view);
toggle_smileys (view);
remove_images (view);
remove_background_images_in_document (document);
clear_attributes (document);
webkit_dom_element_set_attribute (
WEBKIT_DOM_ELEMENT (body), "data-converted", "", NULL);
/* Update fonts - in plain text we only want monospace */
e_html_editor_view_update_fonts (view);
e_html_editor_view_force_spell_check (view);
}
static void
wrap_paragraphs_in_quoted_content (EHTMLEditorSelection *selection,
WebKitDOMDocument *document)
{
gint ii, length;
WebKitDOMNodeList *paragraphs;
paragraphs = webkit_dom_document_query_selector_all (
document, "blockquote[type=cite] > .-x-evo-paragraph", NULL);
length = webkit_dom_node_list_get_length (paragraphs);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *paragraph;
paragraph = webkit_dom_node_list_item (paragraphs, ii);
e_html_editor_selection_wrap_paragraph (
selection, WEBKIT_DOM_ELEMENT (paragraph));
}
g_object_unref (paragraphs);
}
/**
* e_html_editor_view_set_html_mode:
* @view: an #EHTMLEditorView
* @html_mode: @TRUE to enable HTML mode, @FALSE to enable plain text mode
*
* When switching from HTML to plain text mode, user will be prompted whether
* he/she really wants to switch the mode and lose all formatting. When user
* declines, the property is not changed. When they accept, the all formatting
* is lost.
*/
void
e_html_editor_view_set_html_mode (EHTMLEditorView *view,
gboolean html_mode)
{
EHTMLEditorSelection *selection;
gboolean is_from_new_message, converted, edit_as_new, message, convert;
gboolean reply, hide;
WebKitDOMElement *blockquote;
WebKitDOMHTMLElement *body;
WebKitDOMDocument *document;
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
selection = e_html_editor_view_get_selection (view);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
body = webkit_dom_document_get_body (document);
is_from_new_message = webkit_dom_element_has_attribute (
WEBKIT_DOM_ELEMENT (body), "data-new-message");
converted = webkit_dom_element_has_attribute (
WEBKIT_DOM_ELEMENT (body), "data-converted");
edit_as_new = webkit_dom_element_has_attribute (
WEBKIT_DOM_ELEMENT (body), "data-edit-as-new");
message = webkit_dom_element_has_attribute (
WEBKIT_DOM_ELEMENT (body), "data-message");
reply = !is_from_new_message && !edit_as_new && message;
hide = !reply && !converted;
convert = message && ((!hide && reply && !converted) || (edit_as_new && !converted));
/* If toggling from HTML to plain text mode, ask user first */
if (convert && view->priv->html_mode && !html_mode) {
if (!show_lose_formatting_dialog (view))
return;
view->priv->html_mode = html_mode;
convert_when_changing_composer_mode (view);
e_html_editor_selection_scroll_to_caret (selection);
goto out;
}
if (html_mode == view->priv->html_mode)
return;
view->priv->html_mode = html_mode;
/* Update fonts - in plain text we only want monospace */
e_html_editor_view_update_fonts (view);
blockquote = webkit_dom_document_query_selector (
document, "blockquote[type|=cite]", NULL);
if (view->priv->html_mode) {
if (blockquote)
e_html_editor_view_dequote_plain_text (view);
toggle_paragraphs_style (view);
toggle_smileys (view);
remove_wrapping_from_view (view);
} else {
gchar *plain;
e_html_editor_selection_save (selection);
if (blockquote) {
wrap_paragraphs_in_quoted_content (selection, document);
e_html_editor_view_quote_plain_text (view);
}
toggle_paragraphs_style (view);
toggle_smileys (view);
remove_images (view);
remove_background_images_in_document (document);
plain = process_content_for_mode_change (view);
if (*plain) {
webkit_dom_html_element_set_outer_html (
WEBKIT_DOM_HTML_ELEMENT (
webkit_dom_document_get_document_element (document)),
plain,
NULL);
e_html_editor_selection_restore (selection);
e_html_editor_view_force_spell_check (view);
}
g_free (plain);
}
out:
g_object_notify (G_OBJECT (view), "html-mode");
}
/**
* e_html_editor_view_get_inline_spelling:
* @view: an #EHTMLEditorView
*
* Returns whether automatic spellchecking is enabled or not. When enabled,
* editor will perform spellchecking as user is typing. Otherwise spellcheck
* has to be run manually from menu.
*
* Returns: @TRUE when automatic spellchecking is enabled, @FALSE otherwise.
*/
gboolean
e_html_editor_view_get_inline_spelling (EHTMLEditorView *view)
{
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
return view->priv->inline_spelling;
}
/**
* e_html_editor_view_set_inline_spelling:
* @view: an #EHTMLEditorView
* @inline_spelling: @TRUE to enable automatic spellchecking, @FALSE otherwise
*
* Enables or disables automatic spellchecking.
*/
void
e_html_editor_view_set_inline_spelling (EHTMLEditorView *view,
gboolean inline_spelling)
{
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
if (view->priv->inline_spelling == inline_spelling)
return;
view->priv->inline_spelling = inline_spelling;
if (inline_spelling)
e_html_editor_view_force_spell_check (view);
else
e_html_editor_view_turn_spell_check_off (view);
g_object_notify (G_OBJECT (view), "inline-spelling");
}
/**
* e_html_editor_view_get_magic_links:
* @view: an #EHTMLEditorView
*
* Returns whether automatic links conversion is enabled. When enabled, the editor
* will automatically convert any HTTP links into clickable HTML links.
*
* Returns: @TRUE when magic links are enabled, @FALSE otherwise.
*/
gboolean
e_html_editor_view_get_magic_links (EHTMLEditorView *view)
{
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
return view->priv->magic_links;
}
/**
* e_html_editor_view_set_magic_links:
* @view: an #EHTMLEditorView
* @magic_links: @TRUE to enable magic links, @FALSE to disable them
*
* Enables or disables automatic links conversion.
*/
void
e_html_editor_view_set_magic_links (EHTMLEditorView *view,
gboolean magic_links)
{
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
if (view->priv->magic_links == magic_links)
return;
view->priv->magic_links = magic_links;
g_object_notify (G_OBJECT (view), "magic-links");
}
/**
* e_html_editor_view_get_magic_smileys:
* @view: an #EHTMLEditorView
*
* Returns whether automatic conversion of smileys is enabled or disabled. When
* enabled, the editor will automatically convert text smileys ( :-), ;-),...)
* into images.
*
* Returns: @TRUE when magic smileys are enabled, @FALSE otherwise.
*/
gboolean
e_html_editor_view_get_magic_smileys (EHTMLEditorView *view)
{
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
return view->priv->magic_smileys;
}
/**
* e_html_editor_view_set_magic_smileys:
* @view: an #EHTMLEditorView
* @magic_smileys: @TRUE to enable magic smileys, @FALSE to disable them
*
* Enables or disables magic smileys.
*/
void
e_html_editor_view_set_magic_smileys (EHTMLEditorView *view,
gboolean magic_smileys)
{
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
if (view->priv->magic_smileys == magic_smileys)
return;
view->priv->magic_smileys = magic_smileys;
g_object_notify (G_OBJECT (view), "magic-smileys");
}
/**
* e_html_editor_view_get_spell_checker:
* @view: an #EHTMLEditorView
*
* Returns an #ESpellChecker object that is used to perform spellchecking.
*
* Returns: An always-valid #ESpellChecker object
*/
ESpellChecker *
e_html_editor_view_get_spell_checker (EHTMLEditorView *view)
{
return E_SPELL_CHECKER (webkit_get_text_checker ());
}
/**
* e_html_editor_view_get_text_html:
* @view: an #EHTMLEditorView:
*
* Returns processed HTML content of the editor document (with elements attributes
* used in Evolution composer)
*
* Returns: A newly allocated string
*/
gchar *
e_html_editor_view_get_text_html (EHTMLEditorView *view)
{
return process_content_for_html (view);
}
/**
* e_html_editor_view_get_text_html_for_drafts:
* @view: an #EHTMLEditorView:
*
* Returns HTML content of the editor document (without elements attributes
* used in Evolution composer)
*
* Returns: A newly allocated string
*/
gchar *
e_html_editor_view_get_text_html_for_drafts (EHTMLEditorView *view)
{
return process_content_for_saving_as_draft (view);
}
/**
* e_html_editor_view_get_text_plain:
* @view: an #EHTMLEditorView
*
* Returns plain text content of the @view. The algorithm removes any
* formatting or styles from the document and keeps only the text and line
* breaks.
*
* Returns: A newly allocated string with plain text content of the document.
*/
gchar *
e_html_editor_view_get_text_plain (EHTMLEditorView *view)
{
return process_content_for_plain_text (view);
}
static void
convert_and_load_html_to_plain_text (EHTMLEditorView *view,
const gchar *html)
{
view->priv->convertor_insert = FALSE;
webkit_web_view_load_string (
view->priv->convertor_web_view, html, NULL, NULL, "file://");
}
static void
convert_and_load_plain_text (EHTMLEditorView *view,
const gchar *text)
{
view->priv->convertor_insert = FALSE;
webkit_web_view_load_string (
view->priv->convertor_web_view, text, "text/plain", NULL, "file://");
}
void
e_html_editor_view_convert_and_insert_plain_text (EHTMLEditorView *view,
const gchar *text)
{
view->priv->convertor_insert = TRUE;
webkit_web_view_load_string (
view->priv->convertor_web_view, text, "text/plain", NULL, "file://");
}
void
e_html_editor_view_convert_and_insert_html_to_plain_text (EHTMLEditorView *view,
const gchar *html)
{
view->priv->convertor_insert = TRUE;
webkit_web_view_load_string (
view->priv->convertor_web_view, html, NULL, NULL, "file://");
}
/**
* e_html_editor_view_set_text_html:
* @view: an #EHTMLEditorView
* @text: HTML code to load into the editor
*
* Loads given @text into the editor, destroying any content already present.
*/
void
e_html_editor_view_set_text_html (EHTMLEditorView *view,
const gchar *text)
{
view->priv->reload_in_progress = TRUE;
if (view->priv->is_message_from_draft) {
webkit_web_view_load_string (
WEBKIT_WEB_VIEW (view), text, NULL, NULL, "file://");
return;
}
if (view->priv->is_message_from_selection && !view->priv->html_mode) {
convert_and_load_html_to_plain_text (view, text);
return;
}
/* Only convert messages that are in HTML */
if (!view->priv->html_mode) {
if (strstr (text, "<!-- text/html -->")) {
if (!show_lose_formatting_dialog (view)) {
e_html_editor_view_set_html_mode (view, TRUE);
webkit_web_view_load_string (
WEBKIT_WEB_VIEW (view), text, NULL, NULL, "file://");
return;
}
}
convert_and_load_html_to_plain_text (view, text);
} else
webkit_web_view_load_string (
WEBKIT_WEB_VIEW (view), text, NULL, NULL, "file://");
}
/**
* e_html_editor_view_set_text_plain:
* @view: an #EHTMLEditorView
* @text: A plain text to load into the editor
*
* Loads given @text into the editor, destryoing any content already present.
*/
void
e_html_editor_view_set_text_plain (EHTMLEditorView *view,
const gchar *text)
{
view->priv->reload_in_progress = TRUE;
convert_and_load_plain_text (view, text);
}
/**
* e_html_editor_view_paste_as_text:
* @view: an #EHTMLEditorView
*
* Pastes current content of clipboard into the editor without formatting
*/
void
e_html_editor_view_paste_as_text (EHTMLEditorView *view)
{
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
html_editor_view_paste_as_text (view);
}
/**
* e_html_editor_view_paste_clipboard_quoted:
* @view: an #EHTMLEditorView
*
* Pastes current content of clipboard into the editor as quoted text
*/
void
e_html_editor_view_paste_clipboard_quoted (EHTMLEditorView *view)
{
EHTMLEditorViewClass *class;
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
class = E_HTML_EDITOR_VIEW_GET_CLASS (view);
g_return_if_fail (class->paste_clipboard_quoted != NULL);
class->paste_clipboard_quoted (view);
}
void
e_html_editor_view_embed_styles (EHTMLEditorView *view)
{
WebKitWebSettings *settings;
WebKitDOMDocument *document;
WebKitDOMElement *sheet;
gchar *stylesheet_uri;
gchar *stylesheet_content;
const gchar *stylesheet;
gsize length;
settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view));
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
g_object_get (
G_OBJECT (settings),
"user-stylesheet-uri", &stylesheet_uri,
NULL);
stylesheet = strstr (stylesheet_uri, ",");
stylesheet_content = (gchar *) g_base64_decode (stylesheet, &length);
g_free (stylesheet_uri);
if (length == 0) {
g_free (stylesheet_content);
return;
}
e_web_view_create_and_add_css_style_sheet (document, "-x-evo-composer-sheet");
sheet = webkit_dom_document_get_element_by_id (document, "-x-evo-composer-sheet");
webkit_dom_element_set_attribute (
sheet,
"type",
"text/css",
NULL);
webkit_dom_html_element_set_inner_html (WEBKIT_DOM_HTML_ELEMENT (sheet), stylesheet_content, NULL);
g_free (stylesheet_content);
}
void
e_html_editor_view_remove_embed_styles (EHTMLEditorView *view)
{
WebKitDOMDocument *document;
WebKitDOMElement *sheet;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
sheet = webkit_dom_document_get_element_by_id (
document, "-x-evo-composer-sheet");
remove_node (WEBKIT_DOM_NODE (sheet));
}
static const gchar *
citation_color_level_1 (void)
{
return "rgb(114,159,207)"; /* Sky Blue 1 */
}
static const gchar *
citation_color_level_2 (void)
{
return "rgb(173,127,168)"; /* Plum 1 */
}
static const gchar *
citation_color_level_3 (void)
{
return "rgb(138,226,52)"; /* Chameleon 1 */
}
static const gchar *
citation_color_level_4 (void)
{
return "rgb(252,175,62)"; /* Orange 1 */
}
static const gchar *
citation_color_level_5 (void)
{
return "rgb(233,185,110)"; /* Chocolate 1 */
}
/**
* e_html_editor_view_update_fonts:
* @view: an #EHTMLEditorView
*
* Forces the editor to reload font settings from WebKitWebSettings and apply
* it on the content of the editor document.
*/
void
e_html_editor_view_update_fonts (EHTMLEditorView *view)
{
gboolean mark_citations, use_custom_font;
GdkColor *link = NULL;
GdkColor *visited = NULL;
gchar *base64, *font, *aa = NULL, *citation_color;
const gchar *styles[] = { "normal", "oblique", "italic" };
const gchar *smoothing = NULL;
GString *stylesheet;
GtkStyleContext *context;
PangoFontDescription *ms, *vw;
WebKitWebSettings *settings;
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
use_custom_font = g_settings_get_boolean (
view->priv->mail_settings, "use-custom-font");
if (use_custom_font) {
font = g_settings_get_string (
view->priv->mail_settings, "monospace-font");
ms = pango_font_description_from_string (font ? font : "monospace 10");
g_free (font);
} else {
font = g_settings_get_string (
view->priv->font_settings, "monospace-font-name");
ms = pango_font_description_from_string (font ? font : "monospace 10");
g_free (font);
}
if (view->priv->html_mode) {
if (use_custom_font) {
font = g_settings_get_string (
view->priv->mail_settings, "variable-width-font");
vw = pango_font_description_from_string (font ? font : "serif 10");
g_free (font);
} else {
font = g_settings_get_string (
view->priv->font_settings, "font-name");
vw = pango_font_description_from_string (font ? font : "serif 10");
g_free (font);
}
} else {
/* When in plain text mode, force monospace font */
vw = pango_font_description_copy (ms);
}
stylesheet = g_string_new ("");
g_string_append_printf (
stylesheet,
"body {\n"
" font-family: '%s';\n"
" font-size: %dpt;\n"
" font-weight: %d;\n"
" font-style: %s;\n",
pango_font_description_get_family (vw),
pango_font_description_get_size (vw) / PANGO_SCALE,
pango_font_description_get_weight (vw),
styles[pango_font_description_get_style (vw)]);
if (view->priv->aliasing_settings != NULL)
aa = g_settings_get_string (
view->priv->aliasing_settings, "antialiasing");
if (g_strcmp0 (aa, "none") == 0)
smoothing = "none";
else if (g_strcmp0 (aa, "grayscale") == 0)
smoothing = "antialiased";
else if (g_strcmp0 (aa, "rgba") == 0)
smoothing = "subpixel-antialiased";
if (smoothing != NULL)
g_string_append_printf (
stylesheet,
" -webkit-font-smoothing: %s;\n",
smoothing);
g_free (aa);
g_string_append (stylesheet, "}\n");
g_string_append_printf (
stylesheet,
"pre,code,.pre {\n"
" font-family: '%s';\n"
" font-size: %dpt;\n"
" font-weight: %d;\n"
" font-style: %s;\n"
"}",
pango_font_description_get_family (ms),
pango_font_description_get_size (ms) / PANGO_SCALE,
pango_font_description_get_weight (ms),
styles[pango_font_description_get_style (ms)]);
context = gtk_widget_get_style_context (GTK_WIDGET (view));
gtk_style_context_get_style (
context,
"link-color", &link,
"visited-link-color", &visited,
NULL);
if (link == NULL) {
link = g_slice_new0 (GdkColor);
link->blue = G_MAXINT16;
}
if (visited == NULL) {
visited = g_slice_new0 (GdkColor);
visited->red = G_MAXINT16;
}
g_string_append_printf (
stylesheet,
"a {\n"
" color: #%06x;\n"
"}\n"
"a:visited {\n"
" color: #%06x;\n"
"}\n",
e_color_to_value (link),
e_color_to_value (visited));
/* See bug #689777 for details */
g_string_append (
stylesheet,
"p,pre,code,address {\n"
" margin: 0;\n"
"}\n"
"h1,h2,h3,h4,h5,h6 {\n"
" margin-top: 0.2em;\n"
" margin-bottom: 0.2em;\n"
"}\n");
g_string_append (
stylesheet,
"img "
"{\n"
" height: inherit; \n"
" width: inherit; \n"
"}\n");
g_string_append (
stylesheet,
"span.-x-evo-resizable-wrapper:hover "
"{\n"
" outline: 1px dashed red; \n"
" resize: both; \n"
" overflow: hidden; \n"
" display: inline-block; \n"
"}\n");
g_string_append (
stylesheet,
"ul,ol "
"{\n"
" -webkit-padding-start: 7ch; \n"
"}\n");
g_string_append (
stylesheet,
".-x-evo-align-left "
"{\n"
" text-align: left; \n"
"}\n");
g_string_append (
stylesheet,
".-x-evo-align-center "
"{\n"
" text-align: center; \n"
"}\n");
g_string_append (
stylesheet,
".-x-evo-align-right "
"{\n"
" text-align: right; \n"
"}\n");
g_string_append (
stylesheet,
".-x-evo-list-item-align-left "
"{\n"
" text-align: left; \n"
"}\n");
g_string_append (
stylesheet,
".-x-evo-list-item-align-center "
"{\n"
" text-align: center; \n"
" -webkit-padding-start: 0ch; \n"
" margin-left: -3ch; \n"
" margin-right: 1ch; \n"
" list-style-position: inside; \n"
"}\n");
g_string_append (
stylesheet,
".-x-evo-list-item-align-right "
"{\n"
" text-align: right; \n"
" -webkit-padding-start: 0ch; \n"
" margin-left: -3ch; \n"
" margin-right: 1ch; \n"
" list-style-position: inside; \n"
"}\n");
g_string_append (
stylesheet,
"ol,ul "
"{\n"
" -webkit-margin-before: 0em; \n"
" -webkit-margin-after: 0em; \n"
"}\n");
g_string_append (
stylesheet,
"blockquote "
"{\n"
" -webkit-margin-before: 0em; \n"
" -webkit-margin-after: 0em; \n"
"}\n");
citation_color = g_settings_get_string (
view->priv->mail_settings, "citation-color");
mark_citations = g_settings_get_boolean (
view->priv->mail_settings, "mark-citations");
g_string_append (
stylesheet,
"blockquote[type=cite] "
"{\n"
" padding: 0.0ex 0ex;\n"
" margin: 0ex;\n"
" -webkit-margin-start: 0em; \n"
" -webkit-margin-end : 0em; \n");
if (mark_citations && citation_color)
g_string_append_printf (
stylesheet,
" color: %s !important; \n",
citation_color);
g_string_append (stylesheet, "}\n");
g_string_append (
stylesheet,
".-x-evo-quoted "
"{\n"
" -webkit-user-select: none;\n"
"}\n");
g_string_append_printf (
stylesheet,
".-x-evo-quote-character "
"{\n"
" color: %s;\n"
"}\n",
citation_color_level_1 ());
g_string_append_printf (
stylesheet,
".-x-evo-quote-character+"
".-x-evo-quote-character"
"{\n"
" color: %s;\n"
"}\n",
citation_color_level_2 ());
g_string_append_printf (
stylesheet,
".-x-evo-quote-character+"
".-x-evo-quote-character+"
".-x-evo-quote-character"
"{\n"
" color: %s;\n"
"}\n",
citation_color_level_3 ());
g_string_append_printf (
stylesheet,
".-x-evo-quote-character+"
".-x-evo-quote-character+"
".-x-evo-quote-character+"
".-x-evo-quote-character"
"{\n"
" color: %s;\n"
"}\n",
citation_color_level_4 ());
g_string_append_printf (
stylesheet,
".-x-evo-quote-character+"
".-x-evo-quote-character+"
".-x-evo-quote-character+"
".-x-evo-quote-character+"
".-x-evo-quote-character"
"{\n"
" color: %s;\n"
"}\n",
citation_color_level_5 ());
g_string_append (
stylesheet,
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
"{\n"
" padding: 0.4ex 1ex;\n"
" margin: 1ex;\n"
" border-width: 0px 2px 0px 2px;\n"
" border-style: none solid none solid;\n"
" border-radius: 2px;\n"
"}\n");
/* Block quote border colors are borrowed from Thunderbird. */
g_string_append_printf (
stylesheet,
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
"{\n"
" border-color: %s;\n"
"}\n",
citation_color_level_1 ());
g_string_append_printf (
stylesheet,
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
"{\n"
" border-color: %s;\n"
"}\n",
citation_color_level_2 ());
g_string_append_printf (
stylesheet,
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
"{\n"
" border-color: %s;\n"
"}\n",
citation_color_level_3 ());
g_string_append_printf (
stylesheet,
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
"{\n"
" border-color: %s;\n"
"}\n",
citation_color_level_4 ());
g_string_append_printf (
stylesheet,
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
"{\n"
" border-color: %s;\n"
"}\n",
citation_color_level_5 ());
gdk_color_free (link);
gdk_color_free (visited);
base64 = g_base64_encode ((guchar *) stylesheet->str, stylesheet->len);
g_string_free (stylesheet, TRUE);
stylesheet = g_string_new ("data:text/css;charset=utf-8;base64,");
g_string_append (stylesheet, base64);
g_free (base64);
settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view));
g_object_set (
G_OBJECT (settings),
"default-font-size", pango_font_description_get_size (vw) / PANGO_SCALE,
"default-font-family", pango_font_description_get_family (vw),
"monospace-font-family", pango_font_description_get_family (ms),
"default-monospace-font-size", (pango_font_description_get_size (ms) / PANGO_SCALE),
"user-stylesheet-uri", stylesheet->str,
NULL);
g_string_free (stylesheet, TRUE);
pango_font_description_free (ms);
pango_font_description_free (vw);
}
/**
* e_html_editor_view_get_element_under_mouse_click:
* @view: an #EHTMLEditorView
*
* Returns DOM element, that was clicked on.
*
* Returns: DOM element on that was clicked.
*/
WebKitDOMElement *
e_html_editor_view_get_element_under_mouse_click (EHTMLEditorView *view)
{
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), NULL);
return view->priv->element_under_mouse;
}
/**
* e_html_editor_view_check_magic_links
* @view: an #EHTMLEditorView
* @include_space: If TRUE the pattern for link expects space on end
*
* Check if actual selection in given editor is link. If so, it is surrounded
* with ANCHOR element.
*/
void
e_html_editor_view_check_magic_links (EHTMLEditorView *view,
gboolean include_space)
{
WebKitDOMRange *range;
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
range = html_editor_view_get_dom_range (view);
html_editor_view_check_magic_links (view, range, include_space, NULL);
}
static CamelMimePart *
e_html_editor_view_add_inline_image_from_element (EHTMLEditorView *view,
WebKitDOMElement *element,
const gchar *attribute,
const gchar *uid_domain)
{
CamelStream *stream;
CamelDataWrapper *wrapper;
CamelMimePart *part = NULL;
gsize decoded_size;
gssize size;
gchar *mime_type = NULL;
gchar *element_src, *cid, *name;
const gchar *base64_encoded_data;
guchar *base64_decoded_data;
if (!WEBKIT_DOM_IS_ELEMENT (element)) {
return NULL;
}
element_src = webkit_dom_element_get_attribute (
WEBKIT_DOM_ELEMENT (element), attribute);
base64_encoded_data = strstr (element_src, ";base64,");
if (!base64_encoded_data)
goto out;
mime_type = g_strndup (
element_src + 5,
base64_encoded_data - (strstr (element_src, "data:") + 5));
/* Move to actual data */
base64_encoded_data += 8;
base64_decoded_data = g_base64_decode (base64_encoded_data, &decoded_size);
stream = camel_stream_mem_new ();
size = camel_stream_write (
stream, (gchar *) base64_decoded_data, decoded_size, NULL, NULL);
if (size == -1)
goto out;
wrapper = camel_data_wrapper_new ();
camel_data_wrapper_construct_from_stream_sync (
wrapper, stream, NULL, NULL);
g_object_unref (stream);
camel_data_wrapper_set_mime_type (wrapper, mime_type);
part = camel_mime_part_new ();
camel_medium_set_content (CAMEL_MEDIUM (part), wrapper);
g_object_unref (wrapper);
cid = camel_header_msgid_generate (uid_domain);
camel_mime_part_set_content_id (part, cid);
name = webkit_dom_element_get_attribute (element, "data-name");
camel_mime_part_set_filename (part, name);
g_free (name);
camel_mime_part_set_encoding (part, CAMEL_TRANSFER_ENCODING_BASE64);
out:
g_free (mime_type);
g_free (element_src);
g_free (base64_decoded_data);
return part;
}
GList *
e_html_editor_view_get_parts_for_inline_images (EHTMLEditorView *view,
const gchar *uid_domain)
{
GHashTable *added;
GList *parts = NULL;
gint length, ii;
WebKitDOMDocument *document;
WebKitDOMNodeList *list;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
list = webkit_dom_document_query_selector_all (document, "img[data-inline]", NULL);
length = webkit_dom_node_list_get_length (list);
if (length == 0)
return parts;
added = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
for (ii = 0; ii < length; ii++) {
const gchar *id;
gchar *cid;
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
gchar *src = webkit_dom_element_get_attribute (
WEBKIT_DOM_ELEMENT (node), "src");
if ((id = g_hash_table_lookup (added, src)) != NULL) {
cid = g_strdup_printf ("cid:%s", id);
} else {
CamelMimePart *part;
part = e_html_editor_view_add_inline_image_from_element (
view, WEBKIT_DOM_ELEMENT (node), "src", uid_domain);
parts = g_list_append (parts, part);
id = camel_mime_part_get_content_id (part);
cid = g_strdup_printf ("cid:%s", id);
g_hash_table_insert (added, src, (gpointer) id);
}
webkit_dom_element_set_attribute (
WEBKIT_DOM_ELEMENT (node), "src", cid, NULL);
g_free (src);
g_free (cid);
}
g_object_unref (list);
list = webkit_dom_document_query_selector_all (
document, "[data-inline][background]", NULL);
length = webkit_dom_node_list_get_length (list);
for (ii = 0; ii < length; ii++) {
CamelMimePart *part;
const gchar *id;
gchar *cid = NULL;
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
gchar *src = webkit_dom_element_get_attribute (
WEBKIT_DOM_ELEMENT (node), "background");
if ((id = g_hash_table_lookup (added, src)) != NULL) {
cid = g_strdup_printf ("cid:%s", id);
webkit_dom_element_set_attribute (
WEBKIT_DOM_ELEMENT (node), "background", cid, NULL);
} else {
part = e_html_editor_view_add_inline_image_from_element (
view, WEBKIT_DOM_ELEMENT (node), "background", uid_domain);
if (part) {
parts = g_list_append (parts, part);
id = camel_mime_part_get_content_id (part);
g_hash_table_insert (added, src, (gpointer) id);
cid = g_strdup_printf ("cid:%s", id);
webkit_dom_element_set_attribute (
WEBKIT_DOM_ELEMENT (node), "background", cid, NULL);
}
}
g_free (src);
g_free (cid);
}
g_object_unref (list);
g_hash_table_destroy (added);
return parts;
}
/**
* e_html_editor_view_add_inline_image_from_mime_part:
* @composer: a composer object
* @part: a CamelMimePart containing image data
*
* This adds the mime part @part to @composer as an inline image.
**/
void
e_html_editor_view_add_inline_image_from_mime_part (EHTMLEditorView *view,
CamelMimePart *part)
{
CamelDataWrapper *dw;
CamelStream *stream;
GByteArray *byte_array;
gchar *src, *base64_encoded, *mime_type, *cid_src;
const gchar *cid, *name;
stream = camel_stream_mem_new ();
dw = camel_medium_get_content (CAMEL_MEDIUM (part));
g_return_if_fail (dw);
mime_type = camel_data_wrapper_get_mime_type (dw);
camel_data_wrapper_decode_to_stream_sync (dw, stream, NULL, NULL);
camel_stream_close (stream, NULL, NULL);
byte_array = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (stream));
if (!byte_array->data)
return;
base64_encoded = g_base64_encode ((const guchar *) byte_array->data, byte_array->len);
name = camel_mime_part_get_filename (part);
/* Insert file name before new src */
src = g_strconcat (name, ";data:", mime_type, ";base64,", base64_encoded, NULL);
cid = camel_mime_part_get_content_id (part);
if (!cid) {
camel_mime_part_set_content_id (part, NULL);
cid = camel_mime_part_get_content_id (part);
}
cid_src = g_strdup_printf ("cid:%s", cid);
g_hash_table_insert (view->priv->inline_images, cid_src, src);
g_free (base64_encoded);
g_free (mime_type);
g_object_unref (stream);
}
void
e_html_editor_view_set_is_message_from_draft (EHTMLEditorView *view,
gboolean value)
{
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
view->priv->is_message_from_draft = value;
}
gboolean
e_html_editor_view_is_message_from_draft (EHTMLEditorView *view)
{
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
return view->priv->is_message_from_draft;
}
void
e_html_editor_view_set_is_message_from_selection (EHTMLEditorView *view,
gboolean value)
{
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
view->priv->is_message_from_selection = value;
}