Files
evolution/e-util/e-html-editor-selection.c

8027 lines
229 KiB
C

/*
* e-html-editor-selection.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-selection.h"
#include "e-html-editor-view.h"
#include "e-html-editor.h"
#include "e-html-editor-utils.h"
#include <e-util/e-util.h>
#include <webkit/webkit.h>
#include <webkit/webkitdom.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#define E_HTML_EDITOR_SELECTION_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), E_TYPE_HTML_EDITOR_SELECTION, EHTMLEditorSelectionPrivate))
/**
* EHTMLEditorSelection
*
* The #EHTMLEditorSelection object represents current position of the cursor
* with the editor or current text selection within the editor. To obtain
* valid #EHTMLEditorSelection, call e_html_editor_view_get_selection().
*/
struct _EHTMLEditorSelectionPrivate {
GWeakRef html_editor_view;
gulong selection_changed_handler_id;
gboolean selection_changed_callbacks_blocked;
gchar *text;
gboolean is_bold;
gboolean is_italic;
gboolean is_underline;
gboolean is_monospaced;
gboolean is_strikethrough;
gchar *background_color;
gchar *font_color;
gchar *font_family;
gulong selection_offset;
gint word_wrap_length;
guint font_size;
EHTMLEditorSelectionAlignment alignment;
};
enum {
PROP_0,
PROP_ALIGNMENT,
PROP_BACKGROUND_COLOR,
PROP_BLOCK_FORMAT,
PROP_BOLD,
PROP_HTML_EDITOR_VIEW,
PROP_FONT_COLOR,
PROP_FONT_NAME,
PROP_FONT_SIZE,
PROP_INDENTED,
PROP_ITALIC,
PROP_MONOSPACED,
PROP_STRIKETHROUGH,
PROP_SUBSCRIPT,
PROP_SUPERSCRIPT,
PROP_TEXT,
PROP_UNDERLINE
};
static const GdkRGBA black = { 0, 0, 0, 1 };
G_DEFINE_TYPE (
EHTMLEditorSelection,
e_html_editor_selection,
G_TYPE_OBJECT
);
static WebKitDOMRange *
html_editor_selection_get_current_range (EHTMLEditorSelection *selection)
{
EHTMLEditorView *view;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *dom_window;
WebKitDOMDOMSelection *dom_selection;
WebKitDOMRange *range = NULL;
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_val_if_fail (view != NULL, NULL);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
g_object_unref (view);
dom_window = webkit_dom_document_get_default_view (document);
if (!dom_window)
return NULL;
dom_selection = webkit_dom_dom_window_get_selection (dom_window);
if (!dom_selection) {
g_object_unref (dom_window);
return NULL;
}
if (webkit_dom_dom_selection_get_range_count (dom_selection) < 1)
goto exit;
range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
exit:
g_object_unref (dom_selection);
g_object_unref (dom_window);
return range;
}
static gboolean
get_has_style (EHTMLEditorSelection *selection,
const gchar *style_tag)
{
WebKitDOMNode *node;
WebKitDOMElement *element;
WebKitDOMRange *range;
gboolean result;
gint tag_len;
range = html_editor_selection_get_current_range (selection);
if (!range)
return FALSE;
node = webkit_dom_range_get_start_container (range, NULL);
if (WEBKIT_DOM_IS_ELEMENT (node))
element = WEBKIT_DOM_ELEMENT (node);
else
element = webkit_dom_node_get_parent_element (node);
g_object_unref (range);
tag_len = strlen (style_tag);
result = FALSE;
while (!result && element) {
gchar *element_tag;
gboolean accept_citation = FALSE;
element_tag = webkit_dom_element_get_tag_name (element);
if (g_ascii_strncasecmp (style_tag, "citation", 8) == 0) {
accept_citation = TRUE;
result = WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (element);
if (element_has_class (element, "-x-evo-indented"))
result = FALSE;
} else {
result = ((tag_len == strlen (element_tag)) &&
(g_ascii_strncasecmp (element_tag, style_tag, tag_len) == 0));
}
/* Special case: <blockquote type=cite> marks quotation, while
* just <blockquote> is used for indentation. If the <blockquote>
* has type=cite, then ignore it unless style_tag is "citation" */
if (result && WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (element)) {
if (webkit_dom_element_has_attribute (element, "type")) {
gchar *type;
type = webkit_dom_element_get_attribute (element, "type");
if (!accept_citation && (g_ascii_strncasecmp (type, "cite", 4) == 0)) {
result = FALSE;
}
g_free (type);
} else {
if (accept_citation)
result = FALSE;
}
}
g_free (element_tag);
if (result)
break;
element = webkit_dom_node_get_parent_element (
WEBKIT_DOM_NODE (element));
}
return result;
}
static gchar *
get_font_property (EHTMLEditorSelection *selection,
const gchar *font_property)
{
WebKitDOMRange *range;
WebKitDOMNode *node;
WebKitDOMElement *element;
gchar *value;
range = html_editor_selection_get_current_range (selection);
if (!range)
return NULL;
node = webkit_dom_range_get_common_ancestor_container (range, NULL);
g_object_unref (range);
element = e_html_editor_dom_node_find_parent_element (node, "FONT");
while (element && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (element) &&
!webkit_dom_element_has_attribute (element, font_property)) {
element = e_html_editor_dom_node_find_parent_element (
webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)), "FONT");
}
if (!element)
return NULL;
g_object_get (G_OBJECT (element), font_property, &value, NULL);
return value;
}
static void
html_editor_selection_selection_changed_cb (WebKitWebView *web_view,
EHTMLEditorSelection *selection)
{
WebKitDOMRange *range = NULL;
range = html_editor_selection_get_current_range (selection);
if (!range)
return;
g_object_unref (range);
g_object_freeze_notify (G_OBJECT (selection));
g_object_notify (G_OBJECT (selection), "alignment");
g_object_notify (G_OBJECT (selection), "block-format");
g_object_notify (G_OBJECT (selection), "indented");
g_object_notify (G_OBJECT (selection), "text");
if (!e_html_editor_view_get_html_mode (E_HTML_EDITOR_VIEW (web_view)))
goto out;
g_object_notify (G_OBJECT (selection), "background-color");
g_object_notify (G_OBJECT (selection), "bold");
g_object_notify (G_OBJECT (selection), "font-name");
g_object_notify (G_OBJECT (selection), "font-size");
g_object_notify (G_OBJECT (selection), "font-color");
g_object_notify (G_OBJECT (selection), "italic");
g_object_notify (G_OBJECT (selection), "monospaced");
g_object_notify (G_OBJECT (selection), "strikethrough");
g_object_notify (G_OBJECT (selection), "subscript");
g_object_notify (G_OBJECT (selection), "superscript");
g_object_notify (G_OBJECT (selection), "underline");
out:
g_object_thaw_notify (G_OBJECT (selection));
}
void
e_html_editor_selection_block_selection_changed (EHTMLEditorSelection *selection)
{
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
if (!selection->priv->selection_changed_callbacks_blocked) {
EHTMLEditorView *view;
view = e_html_editor_selection_ref_html_editor_view (selection);
g_signal_handlers_block_by_func (
view, html_editor_selection_selection_changed_cb, selection);
g_object_unref (view);
selection->priv->selection_changed_callbacks_blocked = TRUE;
}
}
void
e_html_editor_selection_unblock_selection_changed (EHTMLEditorSelection *selection)
{
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
if (selection->priv->selection_changed_callbacks_blocked) {
EHTMLEditorView *view;
view = e_html_editor_selection_ref_html_editor_view (selection);
g_signal_handlers_unblock_by_func (
view, html_editor_selection_selection_changed_cb, selection);
g_object_unref (view);
selection->priv->selection_changed_callbacks_blocked = FALSE;
html_editor_selection_selection_changed_cb (WEBKIT_WEB_VIEW (view), selection);
}
}
static void
html_editor_selection_set_html_editor_view (EHTMLEditorSelection *selection,
EHTMLEditorView *view)
{
gulong handler_id;
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
g_weak_ref_set (&selection->priv->html_editor_view, view);
handler_id = g_signal_connect (
view, "selection-changed",
G_CALLBACK (html_editor_selection_selection_changed_cb),
selection);
selection->priv->selection_changed_handler_id = handler_id;
}
static void
html_editor_selection_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GdkRGBA rgba = { 0 };
switch (property_id) {
case PROP_ALIGNMENT:
g_value_set_int (
value,
e_html_editor_selection_get_alignment (
E_HTML_EDITOR_SELECTION (object)));
return;
case PROP_BACKGROUND_COLOR:
g_value_set_string (
value,
e_html_editor_selection_get_background_color (
E_HTML_EDITOR_SELECTION (object)));
return;
case PROP_BLOCK_FORMAT:
g_value_set_int (
value,
e_html_editor_selection_get_block_format (
E_HTML_EDITOR_SELECTION (object)));
return;
case PROP_BOLD:
g_value_set_boolean (
value,
e_html_editor_selection_is_bold (
E_HTML_EDITOR_SELECTION (object)));
return;
case PROP_HTML_EDITOR_VIEW:
g_value_take_object (
value,
e_html_editor_selection_ref_html_editor_view (
E_HTML_EDITOR_SELECTION (object)));
return;
case PROP_FONT_COLOR:
e_html_editor_selection_get_font_color (
E_HTML_EDITOR_SELECTION (object), &rgba);
g_value_set_boxed (value, &rgba);
return;
case PROP_FONT_NAME:
g_value_set_string (
value,
e_html_editor_selection_get_font_name (
E_HTML_EDITOR_SELECTION (object)));
return;
case PROP_FONT_SIZE:
g_value_set_int (
value,
e_html_editor_selection_get_font_size (
E_HTML_EDITOR_SELECTION (object)));
return;
case PROP_INDENTED:
g_value_set_boolean (
value,
e_html_editor_selection_is_indented (
E_HTML_EDITOR_SELECTION (object)));
return;
case PROP_ITALIC:
g_value_set_boolean (
value,
e_html_editor_selection_is_italic (
E_HTML_EDITOR_SELECTION (object)));
return;
case PROP_MONOSPACED:
g_value_set_boolean (
value,
e_html_editor_selection_is_monospaced (
E_HTML_EDITOR_SELECTION (object)));
return;
case PROP_STRIKETHROUGH:
g_value_set_boolean (
value,
e_html_editor_selection_is_strikethrough (
E_HTML_EDITOR_SELECTION (object)));
return;
case PROP_SUBSCRIPT:
g_value_set_boolean (
value,
e_html_editor_selection_is_subscript (
E_HTML_EDITOR_SELECTION (object)));
return;
case PROP_SUPERSCRIPT:
g_value_set_boolean (
value,
e_html_editor_selection_is_superscript (
E_HTML_EDITOR_SELECTION (object)));
return;
case PROP_TEXT:
g_value_set_string (
value,
e_html_editor_selection_get_string (
E_HTML_EDITOR_SELECTION (object)));
break;
case PROP_UNDERLINE:
g_value_set_boolean (
value,
e_html_editor_selection_is_underline (
E_HTML_EDITOR_SELECTION (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
html_editor_selection_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_ALIGNMENT:
e_html_editor_selection_set_alignment (
E_HTML_EDITOR_SELECTION (object),
g_value_get_int (value));
return;
case PROP_BACKGROUND_COLOR:
e_html_editor_selection_set_background_color (
E_HTML_EDITOR_SELECTION (object),
g_value_get_string (value));
return;
case PROP_BOLD:
e_html_editor_selection_set_bold (
E_HTML_EDITOR_SELECTION (object),
g_value_get_boolean (value));
return;
case PROP_HTML_EDITOR_VIEW:
html_editor_selection_set_html_editor_view (
E_HTML_EDITOR_SELECTION (object),
g_value_get_object (value));
return;
case PROP_FONT_COLOR:
e_html_editor_selection_set_font_color (
E_HTML_EDITOR_SELECTION (object),
g_value_get_boxed (value));
return;
case PROP_BLOCK_FORMAT:
e_html_editor_selection_set_block_format (
E_HTML_EDITOR_SELECTION (object),
g_value_get_int (value));
return;
case PROP_FONT_NAME:
e_html_editor_selection_set_font_name (
E_HTML_EDITOR_SELECTION (object),
g_value_get_string (value));
return;
case PROP_FONT_SIZE:
e_html_editor_selection_set_font_size (
E_HTML_EDITOR_SELECTION (object),
g_value_get_int (value));
return;
case PROP_ITALIC:
e_html_editor_selection_set_italic (
E_HTML_EDITOR_SELECTION (object),
g_value_get_boolean (value));
return;
case PROP_MONOSPACED:
e_html_editor_selection_set_monospaced (
E_HTML_EDITOR_SELECTION (object),
g_value_get_boolean (value));
return;
case PROP_STRIKETHROUGH:
e_html_editor_selection_set_strikethrough (
E_HTML_EDITOR_SELECTION (object),
g_value_get_boolean (value));
return;
case PROP_SUBSCRIPT:
e_html_editor_selection_set_subscript (
E_HTML_EDITOR_SELECTION (object),
g_value_get_boolean (value));
return;
case PROP_SUPERSCRIPT:
e_html_editor_selection_set_superscript (
E_HTML_EDITOR_SELECTION (object),
g_value_get_boolean (value));
return;
case PROP_UNDERLINE:
e_html_editor_selection_set_underline (
E_HTML_EDITOR_SELECTION (object),
g_value_get_boolean (value));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
html_editor_selection_dispose (GObject *object)
{
EHTMLEditorSelectionPrivate *priv;
EHTMLEditorView *view;
priv = E_HTML_EDITOR_SELECTION_GET_PRIVATE (object);
view = g_weak_ref_get (&priv->html_editor_view);
if (view != NULL) {
g_signal_handler_disconnect (
view, priv->selection_changed_handler_id);
priv->selection_changed_handler_id = 0;
g_object_unref (view);
}
g_weak_ref_set (&priv->html_editor_view, NULL);
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (e_html_editor_selection_parent_class)->dispose (object);
}
static void
html_editor_selection_finalize (GObject *object)
{
EHTMLEditorSelection *selection = E_HTML_EDITOR_SELECTION (object);
g_free (selection->priv->text);
g_free (selection->priv->background_color);
g_free (selection->priv->font_color);
g_free (selection->priv->font_family);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (e_html_editor_selection_parent_class)->finalize (object);
}
static void
e_html_editor_selection_class_init (EHTMLEditorSelectionClass *class)
{
GObjectClass *object_class;
g_type_class_add_private (class, sizeof (EHTMLEditorSelectionPrivate));
object_class = G_OBJECT_CLASS (class);
object_class->get_property = html_editor_selection_get_property;
object_class->set_property = html_editor_selection_set_property;
object_class->dispose = html_editor_selection_dispose;
object_class->finalize = html_editor_selection_finalize;
/**
* EHTMLEditorSelection:alignment
*
* Holds alignment of current paragraph.
*/
/* FIXME: Convert the enum to a proper type */
g_object_class_install_property (
object_class,
PROP_ALIGNMENT,
g_param_spec_int (
"alignment",
NULL,
NULL,
E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT,
E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT,
E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT,
G_PARAM_READWRITE));
/**
* EHTMLEditorSelection:background-color
*
* Holds background color of current selection or at current cursor
* position.
*/
g_object_class_install_property (
object_class,
PROP_BACKGROUND_COLOR,
g_param_spec_string (
"background-color",
NULL,
NULL,
NULL,
G_PARAM_READWRITE));
/**
* EHTMLEditorSelection:block-format
*
* Holds block format of current paragraph. See
* #EHTMLEditorSelectionBlockFormat for valid values.
*/
/* FIXME Convert the EHTMLEditorSelectionBlockFormat
* enum to a proper type. */
g_object_class_install_property (
object_class,
PROP_BLOCK_FORMAT,
g_param_spec_int (
"block-format",
NULL,
NULL,
0,
G_MAXINT,
0,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorSelection:bold
*
* Holds whether current selection or text at current cursor position
* is bold.
*/
g_object_class_install_property (
object_class,
PROP_BOLD,
g_param_spec_boolean (
"bold",
NULL,
NULL,
FALSE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
PROP_HTML_EDITOR_VIEW,
g_param_spec_object (
"html-editor-view",
NULL,
NULL,
E_TYPE_HTML_EDITOR_VIEW,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorSelection:font-color
*
* Holds font color of current selection or at current cursor position.
*/
g_object_class_install_property (
object_class,
PROP_FONT_COLOR,
g_param_spec_boxed (
"font-color",
NULL,
NULL,
GDK_TYPE_RGBA,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorSelection:font-name
*
* Holds name of font in current selection or at current cursor
* position.
*/
g_object_class_install_property (
object_class,
PROP_FONT_NAME,
g_param_spec_string (
"font-name",
NULL,
NULL,
NULL,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorSelection:font-size
*
* Holds point size of current selection or at current cursor position.
*/
g_object_class_install_property (
object_class,
PROP_FONT_SIZE,
g_param_spec_int (
"font-size",
NULL,
NULL,
1,
7,
3,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorSelection:indented
*
* Holds whether current paragraph is indented. This does not include
* citations.
*/
g_object_class_install_property (
object_class,
PROP_INDENTED,
g_param_spec_boolean (
"indented",
NULL,
NULL,
FALSE,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorSelection:italic
*
* Holds whether current selection or letter at current cursor position
* is italic.
*/
g_object_class_install_property (
object_class,
PROP_ITALIC,
g_param_spec_boolean (
"italic",
NULL,
NULL,
FALSE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorSelection:monospaced
*
* Holds whether current selection or letter at current cursor position
* is monospaced.
*/
g_object_class_install_property (
object_class,
PROP_MONOSPACED,
g_param_spec_boolean (
"monospaced",
NULL,
NULL,
FALSE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorSelection:strikethrough
*
* Holds whether current selection or letter at current cursor position
* is strikethrough.
*/
g_object_class_install_property (
object_class,
PROP_STRIKETHROUGH,
g_param_spec_boolean (
"strikethrough",
NULL,
NULL,
FALSE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorSelection:superscript
*
* Holds whether current selection or letter at current cursor position
* is in superscript.
*/
g_object_class_install_property (
object_class,
PROP_SUPERSCRIPT,
g_param_spec_boolean (
"superscript",
NULL,
NULL,
FALSE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorSelection:subscript
*
* Holds whether current selection or letter at current cursor position
* is in subscript.
*/
g_object_class_install_property (
object_class,
PROP_SUBSCRIPT,
g_param_spec_boolean (
"subscript",
NULL,
NULL,
FALSE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorSelection:text
*
* Holds always up-to-date text of current selection.
*/
g_object_class_install_property (
object_class,
PROP_TEXT,
g_param_spec_string (
"text",
NULL,
NULL,
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* EHTMLEditorSelection:underline
*
* Holds whether current selection or letter at current cursor position
* is underlined.
*/
g_object_class_install_property (
object_class,
PROP_UNDERLINE,
g_param_spec_boolean (
"underline",
NULL,
NULL,
FALSE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
}
static void
e_html_editor_selection_init (EHTMLEditorSelection *selection)
{
GSettings *g_settings;
selection->priv = E_HTML_EDITOR_SELECTION_GET_PRIVATE (selection);
g_settings = e_util_ref_settings ("org.gnome.evolution.mail");
selection->priv->word_wrap_length =
g_settings_get_int (g_settings, "composer-word-wrap-length");
g_object_unref (g_settings);
selection->priv->selection_changed_callbacks_blocked = FALSE;
}
gint
e_html_editor_selection_get_word_wrap_length (EHTMLEditorSelection *selection)
{
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), 72);
return selection->priv->word_wrap_length;
}
/**
* e_html_editor_selection_ref_html_editor_view:
* @selection: an #EHTMLEditorSelection
*
* Returns a new reference to @selection's #EHTMLEditorView. Unreference
* the #EHTMLEditorView with g_object_unref() when finished with it.
*
* Returns: an #EHTMLEditorView
**/
EHTMLEditorView *
e_html_editor_selection_ref_html_editor_view (EHTMLEditorSelection *selection)
{
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL);
return g_weak_ref_get (&selection->priv->html_editor_view);
}
/**
* e_html_editor_selection_has_text:
* @selection: an #EHTMLEditorSelection
*
* Returns whether current selection contains any text.
*
* Returns: @TRUE when current selection contains text, @FALSE otherwise.
*/
gboolean
e_html_editor_selection_has_text (EHTMLEditorSelection *selection)
{
WebKitDOMRange *range;
WebKitDOMNode *node;
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
range = html_editor_selection_get_current_range (selection);
node = webkit_dom_range_get_start_container (range, NULL);
if (WEBKIT_DOM_IS_TEXT (node)) {
g_object_unref (range);
return TRUE;
}
node = webkit_dom_range_get_end_container (range, NULL);
if (WEBKIT_DOM_IS_TEXT (node)) {
g_object_unref (range);
return TRUE;
}
node = WEBKIT_DOM_NODE (webkit_dom_range_clone_contents (range, NULL));
while (node) {
if (WEBKIT_DOM_IS_TEXT (node)) {
g_object_unref (range);
return TRUE;
}
if (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 {
node = webkit_dom_node_get_parent_node (node);
if (node) {
node = webkit_dom_node_get_next_sibling (node);
}
}
}
if (node)
g_object_unref (node);
g_object_unref (range);
return FALSE;
}
/**
* e_html_editor_selection_get_caret_word:
* @selection: an #EHTMLEditorSelection
*
* Returns word under cursor.
*
* Returns: A newly allocated string with current caret word or @NULL when there
* is no text under cursor or when selection is active. [transfer-full].
*/
gchar *
e_html_editor_selection_get_caret_word (EHTMLEditorSelection *selection)
{
gchar *word;
WebKitDOMRange *range;
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL);
range = html_editor_selection_get_current_range (selection);
/* Don't operate on the visible selection */
range = webkit_dom_range_clone_range (range, NULL);
webkit_dom_range_expand (range, "word", NULL);
word = webkit_dom_range_to_string (range, NULL);
g_object_unref (range);
return word;
}
/**
* e_html_editor_selection_replace_caret_word:
* @selection: an #EHTMLEditorSelection
* @replacement: a string to replace current caret word with
*
* Replaces current word under cursor with @replacement.
*/
void
e_html_editor_selection_replace_caret_word (EHTMLEditorSelection *selection,
const gchar *replacement)
{
EHTMLEditorView *view;
WebKitWebView *web_view;
WebKitDOMDocument *document;
WebKitDOMDocumentFragment *fragment;
WebKitDOMDOMWindow *dom_window;
WebKitDOMDOMSelection *dom_selection;
WebKitDOMNode *node;
WebKitDOMRange *range;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
g_return_if_fail (replacement != NULL);
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
web_view = WEBKIT_WEB_VIEW (view);
range = html_editor_selection_get_current_range (selection);
document = webkit_web_view_get_dom_document (web_view);
dom_window = webkit_dom_document_get_default_view (document);
dom_selection = webkit_dom_dom_window_get_selection (dom_window);
webkit_dom_range_expand (range, "word", NULL);
webkit_dom_dom_selection_add_range (dom_selection, range);
fragment = webkit_dom_range_extract_contents (range, NULL);
/* Get the text node to replace and leave other formatting nodes
* untouched (font color, boldness, ...). */
webkit_dom_node_normalize (WEBKIT_DOM_NODE (fragment));
node = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment));
if (!WEBKIT_DOM_IS_TEXT (node)) {
while (node && WEBKIT_DOM_IS_ELEMENT (node))
node = webkit_dom_node_get_first_child (node);
}
if (node && WEBKIT_DOM_IS_TEXT (node)) {
WebKitDOMText *text;
/* Replace the word */
text = webkit_dom_document_create_text_node (document, replacement);
webkit_dom_node_replace_child (
webkit_dom_node_get_parent_node (node),
WEBKIT_DOM_NODE (text),
node,
NULL);
/* Insert the word on current location. */
webkit_dom_range_insert_node (range, WEBKIT_DOM_NODE (fragment), NULL);
webkit_dom_dom_selection_collapse_to_end (dom_selection, NULL);
}
e_html_editor_view_force_spell_check_for_current_paragraph (view);
g_object_unref (range);
g_object_unref (dom_selection);
g_object_unref (dom_window);
g_object_unref (view);
}
/**
* e_html_editor_selection_is_collapsed:
* @selection: an #EHTMLEditorSelection
*
* Returns if selection is collapsed.
*
* Returns: Whether the selection is collapsed (just caret) or not (someting is selected).
*/
gboolean
e_html_editor_selection_is_collapsed (EHTMLEditorSelection *selection)
{
gboolean collapsed;
WebKitDOMRange *range;
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), TRUE);
range = html_editor_selection_get_current_range (selection);
if (!range)
return TRUE;
collapsed = webkit_dom_range_get_collapsed (range, NULL);
g_object_unref (range);
return collapsed;
}
/**
* e_html_editor_selection_get_string:
* @selection: an #EHTMLEditorSelection
*
* Returns currently selected string.
*
* Returns: A pointer to content of current selection. The string is owned by
* #EHTMLEditorSelection and should not be free'd.
*/
const gchar *
e_html_editor_selection_get_string (EHTMLEditorSelection *selection)
{
WebKitDOMRange *range;
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL);
range = html_editor_selection_get_current_range (selection);
if (!range)
return NULL;
g_free (selection->priv->text);
selection->priv->text = webkit_dom_range_get_text (range);
g_object_unref (range);
return selection->priv->text;
}
/**
* e_html_editor_selection_replace:
* @selection: an #EHTMLEditorSelection
* @new_string: a string to replace current selection with
*
* Replaces currently selected text with @new_string.
*/
void
e_html_editor_selection_replace (EHTMLEditorSelection *selection,
const gchar *new_string)
{
EHTMLEditorView *view;
EHTMLEditorViewHistoryEvent *ev = NULL;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
if (!e_html_editor_view_is_undo_redo_in_progress (view)) {
WebKitDOMDocument *document;
WebKitDOMDOMWindow *dom_window;
WebKitDOMDOMSelection *dom_selection;
WebKitDOMRange *range;
ev = g_new0 (EHTMLEditorViewHistoryEvent, 1);
ev->type = HISTORY_REPLACE;
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->before.start.x,
&ev->before.start.y,
&ev->before.end.x,
&ev->before.end.y);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
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);
ev->data.string.from = webkit_dom_range_get_text (range);
ev->data.string.to = g_strdup (new_string);
g_object_unref (dom_selection);
g_object_unref (dom_window);
g_object_unref (range);
}
e_html_editor_view_exec_command (
view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_TEXT, new_string);
if (ev) {
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->after.start.x,
&ev->after.start.y,
&ev->after.end.x,
&ev->after.end.y);
e_html_editor_view_insert_new_history_event (view, ev);
}
e_html_editor_view_force_spell_check_for_current_paragraph (view);
g_object_unref (view);
}
/**
* e_html_editor_selection_get_list_alignment_from_node:
* @node: #an WebKitDOMNode
*
* Returns alignment of given list.
*
* Returns: #EHTMLEditorSelectionAlignment
*/
EHTMLEditorSelectionAlignment
e_html_editor_selection_get_list_alignment_from_node (WebKitDOMNode *node)
{
if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-align-center"))
return E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER;
if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-align-right"))
return E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT;
else
return E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT;
}
static EHTMLEditorSelectionAlignment
e_html_editor_selection_get_alignment_from_node (WebKitDOMNode *node)
{
EHTMLEditorSelectionAlignment alignment;
gchar *value;
WebKitDOMCSSStyleDeclaration *style;
style = webkit_dom_element_get_style (WEBKIT_DOM_ELEMENT (node));
value = webkit_dom_css_style_declaration_get_property_value (style, "text-align");
if (!value || !*value ||
(g_ascii_strncasecmp (value, "left", 4) == 0)) {
alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT;
} else if (g_ascii_strncasecmp (value, "center", 6) == 0) {
alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER;
} else if (g_ascii_strncasecmp (value, "right", 5) == 0) {
alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT;
} else {
alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT;
}
g_object_unref (style);
g_free (value);
return alignment;
}
/**
* e_html_editor_selection_get_alignment:
* @selection: #an EHTMLEditorSelection
*
* Returns alignment of current paragraph
*
* Returns: #EHTMLEditorSelectionAlignment
*/
EHTMLEditorSelectionAlignment
e_html_editor_selection_get_alignment (EHTMLEditorSelection *selection)
{
EHTMLEditorSelectionAlignment alignment;
gchar *value;
WebKitDOMCSSStyleDeclaration *style;
WebKitDOMElement *element;
WebKitDOMNode *node;
WebKitDOMRange *range;
g_return_val_if_fail (
E_IS_HTML_EDITOR_SELECTION (selection),
E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT);
range = html_editor_selection_get_current_range (selection);
if (!range) {
alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT;
goto out;
}
node = webkit_dom_range_get_start_container (range, NULL);
g_object_unref (range);
if (!node) {
alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT;
goto out;
}
if (WEBKIT_DOM_IS_ELEMENT (node))
element = WEBKIT_DOM_ELEMENT (node);
else
element = webkit_dom_node_get_parent_element (node);
if (element_has_class (element, "-x-evo-align-right")) {
alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT;
goto out;
} else if (element_has_class (element, "-x-evo-align-center")) {
alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER;
goto out;
}
style = webkit_dom_element_get_style (element);
value = webkit_dom_css_style_declaration_get_property_value (style, "text-align");
if (!value || !*value ||
(g_ascii_strncasecmp (value, "left", 4) == 0)) {
alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT;
} else if (g_ascii_strncasecmp (value, "center", 6) == 0) {
alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER;
} else if (g_ascii_strncasecmp (value, "right", 5) == 0) {
alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT;
} else {
alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT;
}
g_object_unref (style);
g_free (value);
out:
selection->priv->alignment = alignment;
return alignment;
}
static void
set_ordered_list_type_to_element (WebKitDOMElement *list,
EHTMLEditorSelectionBlockFormat format)
{
if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST)
webkit_dom_element_remove_attribute (list, "type");
else if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA)
webkit_dom_element_set_attribute (list, "type", "A", NULL);
else if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ROMAN)
webkit_dom_element_set_attribute (list, "type", "I", NULL);
}
static WebKitDOMElement *
create_list_element (EHTMLEditorSelection *selection,
WebKitDOMDocument *document,
EHTMLEditorSelectionBlockFormat format,
gint level,
gboolean html_mode)
{
gboolean inserting_unordered_list;
WebKitDOMElement *list;
inserting_unordered_list = format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST;
list = webkit_dom_document_create_element (
document, inserting_unordered_list ? "UL" : "OL", NULL);
if (!inserting_unordered_list)
set_ordered_list_type_to_element (list, format);
if (level >= 0 && !html_mode) {
gint offset;
offset = (level + 1) * SPACES_PER_LIST_LEVEL;
offset += !inserting_unordered_list ?
SPACES_ORDERED_LIST_FIRST_LEVEL - SPACES_PER_LIST_LEVEL: 0;
e_html_editor_selection_set_paragraph_style (
selection, list, -1, -offset, "");
if (inserting_unordered_list)
webkit_dom_element_set_attribute (list, "data-evo-plain-text", "", NULL);
}
return list;
}
static WebKitDOMNode *
get_list_item_node_from_child (WebKitDOMNode *child)
{
WebKitDOMNode *parent = webkit_dom_node_get_parent_node (child);
while (parent && !WEBKIT_DOM_IS_HTMLLI_ELEMENT (parent))
parent = webkit_dom_node_get_parent_node (parent);
return parent;
}
static WebKitDOMNode *
get_list_node_from_child (WebKitDOMNode *child)
{
WebKitDOMNode *parent = get_list_item_node_from_child (child);
return webkit_dom_node_get_parent_node (parent);
}
static WebKitDOMElement *
do_format_change_list_to_list (WebKitDOMElement *list_to_process,
WebKitDOMElement *new_list_template,
EHTMLEditorSelectionBlockFormat to)
{
EHTMLEditorSelectionBlockFormat current_format;
current_format = get_list_format_from_node (
WEBKIT_DOM_NODE (list_to_process));
if (to == current_format) {
/* Same format, skip it. */
return list_to_process;
} else if (current_format >= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST &&
to >= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST) {
/* Changing from ordered list type to another ordered list type. */
set_ordered_list_type_to_element (list_to_process, to);
return list_to_process;
} else {
WebKitDOMNode *clone, *child;
/* Create new list from template. */
clone = webkit_dom_node_clone_node (
WEBKIT_DOM_NODE (new_list_template), FALSE);
/* Insert it before the list that we are processing. */
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (
WEBKIT_DOM_NODE (list_to_process)),
clone,
WEBKIT_DOM_NODE (list_to_process),
NULL);
/* Move all it children to the new one. */
while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (list_to_process))))
webkit_dom_node_append_child (clone, child, NULL);
remove_node (WEBKIT_DOM_NODE (list_to_process));
return WEBKIT_DOM_ELEMENT (clone);
}
return NULL;
}
static void
format_change_list_from_list (EHTMLEditorSelection *selection,
WebKitDOMDocument *document,
EHTMLEditorSelectionBlockFormat to,
gboolean html_mode)
{
gboolean after_selection_end = FALSE;
WebKitDOMElement *selection_start_marker, *selection_end_marker, *new_list;
WebKitDOMNode *source_list, *source_list_clone, *current_list, *item;
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;
/* Copy elements from previous block to list */
item = get_list_item_node_from_child (WEBKIT_DOM_NODE (selection_start_marker));
source_list = webkit_dom_node_get_parent_node (item);
current_list = source_list;
source_list_clone = webkit_dom_node_clone_node (source_list, FALSE);
new_list = create_list_element (selection, document, to, 0, html_mode);
if (element_has_class (WEBKIT_DOM_ELEMENT (source_list), "-x-evo-indented"))
element_add_class (WEBKIT_DOM_ELEMENT (new_list), "-x-evo-indented");
while (item) {
gboolean selection_end;
WebKitDOMNode *next_item = webkit_dom_node_get_next_sibling (item);
selection_end = webkit_dom_node_contains (
item, WEBKIT_DOM_NODE (selection_end_marker));
if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) {
/* Actual node is an item, just copy it. */
webkit_dom_node_append_child (
after_selection_end ?
source_list_clone : WEBKIT_DOM_NODE (new_list),
item,
NULL);
} else if (node_is_list (item) && !selection_end && !after_selection_end) {
/* Node is a list and it doesn't contain the selection end
* marker, we can process the whole list. */
gint ii;
WebKitDOMNodeList *list;
WebKitDOMElement *processed_list;
list = webkit_dom_element_query_selector_all (
WEBKIT_DOM_ELEMENT (item), "ol,ul", NULL);
ii = webkit_dom_node_list_get_length (list);
g_object_unref (list);
/* Process every sublist separately. */
while (ii) {
WebKitDOMElement *list_to_process;
list_to_process = webkit_dom_element_query_selector (
WEBKIT_DOM_ELEMENT (item), "ol,ul", NULL);
if (list_to_process)
do_format_change_list_to_list (list_to_process, new_list, to);
ii--;
}
/* Process the current list. */
processed_list = do_format_change_list_to_list (
WEBKIT_DOM_ELEMENT (item), new_list, to);
webkit_dom_node_append_child (
after_selection_end ?
source_list_clone : WEBKIT_DOM_NODE (new_list),
WEBKIT_DOM_NODE (processed_list),
NULL);
} else if (node_is_list (item) && !after_selection_end) {
/* Node is a list and it contains the selection end marker,
* thus we have to process it until we find the marker. */
gint ii;
WebKitDOMNodeList *list;
list = webkit_dom_element_query_selector_all (
WEBKIT_DOM_ELEMENT (item), "ol,ul", NULL);
ii = webkit_dom_node_list_get_length (list);
g_object_unref (list);
/* No nested lists - process the items. */
if (ii == 0) {
WebKitDOMNode *clone, *child;
clone = webkit_dom_node_clone_node (
WEBKIT_DOM_NODE (new_list), FALSE);
webkit_dom_node_append_child (
after_selection_end ?
source_list_clone : WEBKIT_DOM_NODE (new_list),
clone,
NULL);
while ((child = webkit_dom_node_get_first_child (item))) {
webkit_dom_node_append_child (clone, child, NULL);
if (webkit_dom_node_contains (child, WEBKIT_DOM_NODE (selection_end_marker)))
break;
}
if (webkit_dom_node_get_first_child (item))
webkit_dom_node_append_child (
after_selection_end ?
source_list_clone : WEBKIT_DOM_NODE (new_list),
item,
NULL);
else
remove_node (item);
} else {
gboolean done = FALSE;
WebKitDOMNode *tmp_parent = WEBKIT_DOM_NODE (new_list);
WebKitDOMNode *tmp_item = WEBKIT_DOM_NODE (item);
while (!done) {
WebKitDOMNode *clone, *child;
clone = webkit_dom_node_clone_node (
WEBKIT_DOM_NODE (new_list), FALSE);
webkit_dom_node_append_child (
tmp_parent, clone, NULL);
while ((child = webkit_dom_node_get_first_child (tmp_item))) {
if (!webkit_dom_node_contains (child, WEBKIT_DOM_NODE (selection_end_marker))) {
webkit_dom_node_append_child (clone, child, NULL);
} else if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (child)) {
webkit_dom_node_append_child (clone, child, NULL);
done = TRUE;
break;
} else {
tmp_parent = clone;
tmp_item = child;
break;
}
}
}
}
} else {
webkit_dom_node_append_child (
after_selection_end ?
source_list_clone : WEBKIT_DOM_NODE (new_list),
item,
NULL);
}
if (selection_end) {
source_list_clone = webkit_dom_node_clone_node (current_list, FALSE);
after_selection_end = TRUE;
}
if (!next_item) {
if (after_selection_end)
break;
current_list = webkit_dom_node_get_next_sibling (current_list);
if (!node_is_list_or_item (current_list))
break;
if (node_is_list (current_list)) {
next_item = webkit_dom_node_get_first_child (current_list);
if (!node_is_list_or_item (next_item))
break;
} else if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (current_list)) {
next_item = current_list;
current_list = webkit_dom_node_get_parent_node (next_item);
}
}
item = next_item;
}
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (source_list),
WEBKIT_DOM_NODE (source_list_clone),
webkit_dom_node_get_next_sibling (source_list),
NULL);
if (webkit_dom_node_has_child_nodes (WEBKIT_DOM_NODE (new_list)))
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (source_list_clone),
WEBKIT_DOM_NODE (new_list),
source_list_clone,
NULL);
if (!webkit_dom_node_has_child_nodes (source_list))
remove_node (source_list);
if (!webkit_dom_node_has_child_nodes (source_list_clone))
remove_node (source_list_clone);
merge_lists_if_possible (WEBKIT_DOM_NODE (new_list));
}
static void
set_block_alignment (WebKitDOMElement *element,
const gchar *class)
{
WebKitDOMElement *parent;
element_remove_class (element, "-x-evo-align-center");
element_remove_class (element, "-x-evo-align-right");
element_add_class (element, class);
parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (element));
while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
element_remove_class (parent, "-x-evo-align-center");
element_remove_class (parent, "-x-evo-align-right");
parent = webkit_dom_node_get_parent_element (
WEBKIT_DOM_NODE (parent));
}
}
void
e_html_editor_selection_get_selection_coordinates (EHTMLEditorSelection *selection,
guint *start_x,
guint *start_y,
guint *end_x,
guint *end_y)
{
EHTMLEditorView *view;
gboolean created_selection_markers = FALSE;
guint local_x = 0, local_y = 0;
WebKitDOMElement *element, *parent;
WebKitDOMDocument *document;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
g_return_if_fail (start_x != NULL);
g_return_if_fail (start_y != NULL);
g_return_if_fail (end_x != NULL);
g_return_if_fail (end_y != NULL);
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
g_object_unref (view);
element = webkit_dom_document_get_element_by_id (
document, "-x-evo-selection-start-marker");
if (!element) {
created_selection_markers = TRUE;
e_html_editor_selection_save (selection);
element = webkit_dom_document_get_element_by_id (
document, "-x-evo-selection-start-marker");
if (!element)
return;
}
parent = element;
while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
local_x += (guint) webkit_dom_element_get_offset_left (parent);
local_y += (guint) webkit_dom_element_get_offset_top (parent);
parent = webkit_dom_element_get_offset_parent (parent);
}
if (start_x)
*start_x = local_x;
if (start_y)
*start_y = local_y;
if (e_html_editor_selection_is_collapsed (selection)) {
*end_x = local_x;
*end_y = local_y;
if (created_selection_markers)
e_html_editor_selection_restore (selection);
return;
}
element = webkit_dom_document_get_element_by_id (
document, "-x-evo-selection-end-marker");
local_x = 0;
local_y = 0;
parent = element;
while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
local_x += (guint) webkit_dom_element_get_offset_left (parent);
local_y += (guint) webkit_dom_element_get_offset_top (parent);
parent = webkit_dom_element_get_offset_parent (parent);
}
if (end_x)
*end_x = local_x;
if (end_y)
*end_y = local_y;
if (created_selection_markers)
e_html_editor_selection_restore (selection);
}
/**
* e_html_editor_selection_set_alignment:
* @selection: an #EHTMLEditorSelection
* @alignment: an #EHTMLEditorSelectionAlignment value to apply
*
* Sets alignment of current paragraph to give @alignment.
*/
void
e_html_editor_selection_set_alignment (EHTMLEditorSelection *selection,
EHTMLEditorSelectionAlignment alignment)
{
EHTMLEditorSelectionAlignment current_alignment;
EHTMLEditorView *view;
EHTMLEditorViewHistoryEvent *ev = NULL;
gboolean after_selection_end = FALSE;
const gchar *class = "";
WebKitDOMDocument *document;
WebKitDOMElement *selection_start_marker, *selection_end_marker;
WebKitDOMNode *block;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
current_alignment = e_html_editor_selection_get_alignment (selection);
if (current_alignment == alignment)
return;
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
switch (alignment) {
case E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER:
class = "-x-evo-align-center";
break;
case E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT:
break;
case E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT:
class = "-x-evo-align-right";
break;
}
selection->priv->alignment = alignment;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (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) {
g_object_unref (view);
return;
}
if (!e_html_editor_view_is_undo_redo_in_progress (view)) {
ev = g_new0 (EHTMLEditorViewHistoryEvent, 1);
ev->type = HISTORY_ALIGNMENT;
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->before.start.x,
&ev->before.start.y,
&ev->before.end.x,
&ev->before.end.y);
ev->data.style.from = current_alignment;
ev->data.style.to = alignment;
}
block = e_html_editor_get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
while (block && !after_selection_end) {
WebKitDOMNode *next_block;
next_block = webkit_dom_node_get_next_sibling (block);
after_selection_end = webkit_dom_node_contains (
block, WEBKIT_DOM_NODE (selection_end_marker));
if (element_has_class (WEBKIT_DOM_ELEMENT (block), "-x-evo-indented")) {
gint ii, length;
WebKitDOMNodeList *list;
list = webkit_dom_element_query_selector_all (
WEBKIT_DOM_ELEMENT (block),
".-x-evo-indented > *:not(.-x-evo-indented):not(li)",
NULL);
length = webkit_dom_node_list_get_length (list);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *item = webkit_dom_node_list_item (list, ii);
set_block_alignment (WEBKIT_DOM_ELEMENT (item), class);
after_selection_end = webkit_dom_node_contains (
item, WEBKIT_DOM_NODE (selection_end_marker));
g_object_unref (item);
if (after_selection_end)
break;
}
g_object_unref (list);
} else {
set_block_alignment (WEBKIT_DOM_ELEMENT (block), class);
}
block = next_block;
}
if (ev) {
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->after.start.x,
&ev->after.start.y,
&ev->after.end.x,
&ev->after.end.y);
e_html_editor_view_insert_new_history_event (view, ev);
}
e_html_editor_selection_restore (selection);
e_html_editor_view_force_spell_check_for_current_paragraph (view);
g_object_unref (view);
g_object_notify (G_OBJECT (selection), "alignment");
}
/**
* e_html_editor_selection_get_background_color:
* @selection: an #EHTMLEditorSelection
*
* Returns background color of currently selected text or letter at current
* cursor position.
*
* Returns: A string with code of current background color.
*/
const gchar *
e_html_editor_selection_get_background_color (EHTMLEditorSelection *selection)
{
EHTMLEditorView *view;
WebKitDOMNode *ancestor;
WebKitDOMRange *range;
WebKitDOMCSSStyleDeclaration *css;
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL);
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_val_if_fail (view != NULL, FALSE);
if (!e_html_editor_view_get_html_mode (view)) {
g_object_unref (view);
return "#ffffff";
}
g_object_unref (view);
range = html_editor_selection_get_current_range (selection);
ancestor = webkit_dom_range_get_common_ancestor_container (range, NULL);
css = webkit_dom_element_get_style (WEBKIT_DOM_ELEMENT (ancestor));
g_free (selection->priv->background_color);
selection->priv->background_color =
webkit_dom_css_style_declaration_get_property_value (
css, "background-color");
g_object_unref (css);
g_object_unref (range);
return selection->priv->background_color;
}
/**
* e_html_editor_selection_set_background_color:
* @selection: an #EHTMLEditorSelection
* @color: code of new background color to set
*
* Changes background color of current selection or letter at current cursor
* position to @color.
*/
void
e_html_editor_selection_set_background_color (EHTMLEditorSelection *selection,
const gchar *color)
{
EHTMLEditorView *view;
EHTMLEditorViewCommand command;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
g_return_if_fail (color != NULL && *color != '\0');
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
command = E_HTML_EDITOR_VIEW_COMMAND_BACKGROUND_COLOR;
e_html_editor_view_exec_command (view, command, color);
g_object_unref (view);
g_object_notify (G_OBJECT (selection), "background-color");
}
static gint
get_indentation_level (WebKitDOMElement *element)
{
WebKitDOMElement *parent;
gint level = 0;
if (element_has_class (element, "-x-evo-indented"))
level++;
parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (element));
/* Count level of indentation */
while (parent && !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 WebKitDOMNode *
get_block_node (WebKitDOMRange *range)
{
WebKitDOMNode *node;
node = webkit_dom_range_get_common_ancestor_container (range, NULL);
node = e_html_editor_get_parent_block_node_from_child (node);
return node;
}
/**
* e_html_editor_selection_get_block_format:
* @selection: an #EHTMLEditorSelection
*
* Returns block format of current paragraph.
*
* Returns: #EHTMLEditorSelectionBlockFormat
*/
EHTMLEditorSelectionBlockFormat
e_html_editor_selection_get_block_format (EHTMLEditorSelection *selection)
{
WebKitDOMNode *node;
WebKitDOMRange *range;
WebKitDOMElement *element;
EHTMLEditorSelectionBlockFormat result;
g_return_val_if_fail (
E_IS_HTML_EDITOR_SELECTION (selection),
E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH);
range = html_editor_selection_get_current_range (selection);
if (!range)
return E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH;
node = webkit_dom_range_get_start_container (range, NULL);
if ((element = e_html_editor_dom_node_find_parent_element (node, "UL"))) {
WebKitDOMElement *tmp_element;
tmp_element = e_html_editor_dom_node_find_parent_element (node, "OL");
if (tmp_element) {
if (webkit_dom_node_contains (WEBKIT_DOM_NODE (tmp_element), WEBKIT_DOM_NODE (element)))
result = get_list_format_from_node (WEBKIT_DOM_NODE (element));
else
result = get_list_format_from_node (WEBKIT_DOM_NODE (tmp_element));
} else
result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST;
} else if ((element = e_html_editor_dom_node_find_parent_element (node, "OL")) != NULL) {
WebKitDOMElement *tmp_element;
tmp_element = e_html_editor_dom_node_find_parent_element (node, "UL");
if (tmp_element) {
if (webkit_dom_node_contains (WEBKIT_DOM_NODE (element), WEBKIT_DOM_NODE (tmp_element)))
result = get_list_format_from_node (WEBKIT_DOM_NODE (element));
else
result = get_list_format_from_node (WEBKIT_DOM_NODE (tmp_element));
} else
result = get_list_format_from_node (WEBKIT_DOM_NODE (element));
} else if (e_html_editor_dom_node_find_parent_element (node, "PRE")) {
result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PRE;
} else if (e_html_editor_dom_node_find_parent_element (node, "ADDRESS")) {
result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ADDRESS;
} else if (e_html_editor_dom_node_find_parent_element (node, "H1")) {
result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H1;
} else if (e_html_editor_dom_node_find_parent_element (node, "H2")) {
result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H2;
} else if (e_html_editor_dom_node_find_parent_element (node, "H3")) {
result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H3;
} else if (e_html_editor_dom_node_find_parent_element (node, "H4")) {
result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H4;
} else if (e_html_editor_dom_node_find_parent_element (node, "H5")) {
result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H5;
} else if (e_html_editor_dom_node_find_parent_element (node, "H6")) {
result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H6;
} else if ((element = e_html_editor_dom_node_find_parent_element (node, "BLOCKQUOTE")) != NULL) {
if (element_has_class (element, "-x-evo-indented"))
result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH;
else {
WebKitDOMNode *block = get_block_node (range);
if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (block) ||
element_has_class (WEBKIT_DOM_ELEMENT (block), "-x-evo-paragraph"))
result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH;
else {
/* Paragraphs inside quote */
if ((element = e_html_editor_dom_node_find_parent_element (node, "DIV")) != NULL)
if (element_has_class (element, "-x-evo-paragraph"))
result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH;
else
result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE;
else
result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE;
}
}
} else if (e_html_editor_dom_node_find_parent_element (node, "P")) {
result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH;
} else {
result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH;
}
g_object_unref (range);
return result;
}
void
remove_wrapping_from_element (WebKitDOMElement *element)
{
WebKitDOMNodeList *list;
gint ii, length;
g_return_if_fail (element != NULL);
list = webkit_dom_element_query_selector_all (
element, "br.-x-evo-wrap-br", NULL);
length = webkit_dom_node_list_get_length (list);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
WebKitDOMNode *parent;
parent = e_html_editor_get_parent_block_node_from_child (node);
if (!webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (parent), "data-user-wrapped"))
remove_node (node);
g_object_unref (node);
}
g_object_unref (list);
list = webkit_dom_element_query_selector_all (
element, "span[data-hidden-space]", NULL);
length = webkit_dom_node_list_get_length (list);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *hidden_space_node;
WebKitDOMNode *parent;
hidden_space_node = webkit_dom_node_list_item (list, ii);
parent = e_html_editor_get_parent_block_node_from_child (hidden_space_node);
if (!webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (parent), "data-user-wrapped")) {
webkit_dom_html_element_set_outer_text (
WEBKIT_DOM_HTML_ELEMENT (hidden_space_node), " ", NULL);
}
g_object_unref (hidden_space_node);
}
g_object_unref (list);
webkit_dom_node_normalize (WEBKIT_DOM_NODE (element));
}
void
remove_quoting_from_element (WebKitDOMElement *element)
{
gint ii, length;
WebKitDOMNodeList *list;
g_return_if_fail (element != NULL);
list = webkit_dom_element_query_selector_all (
element, "span.-x-evo-quoted", NULL);
length = webkit_dom_node_list_get_length (list);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
remove_node (node);
g_object_unref (node);
}
g_object_unref (list);
list = webkit_dom_element_query_selector_all (
element, "span.-x-evo-temp-text-wrapper", NULL);
length = webkit_dom_node_list_get_length (list);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node);
while (webkit_dom_node_get_first_child (node))
webkit_dom_node_insert_before (
parent,
webkit_dom_node_get_first_child (node),
node,
NULL);
remove_node (node);
g_object_unref (node);
}
g_object_unref (list);
list = webkit_dom_element_query_selector_all (
element, "br.-x-evo-temp-br", NULL);
length = webkit_dom_node_list_get_length (list);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
remove_node (node);
g_object_unref (node);
}
g_object_unref (list);
webkit_dom_node_normalize (WEBKIT_DOM_NODE (element));
}
static gint
get_citation_level (WebKitDOMNode *node)
{
WebKitDOMNode *parent = webkit_dom_node_get_parent_node (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++;
parent = webkit_dom_node_get_parent_node (parent);
}
return level;
}
static gboolean
is_citation_node (WebKitDOMNode *node)
{
gchar *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 WebKitDOMNode *
indent_block (EHTMLEditorSelection *selection,
WebKitDOMDocument *document,
WebKitDOMNode *block,
gint width)
{
WebKitDOMElement *element;
WebKitDOMNode *sibling, *tmp;
sibling = webkit_dom_node_get_previous_sibling (block);
if (WEBKIT_DOM_IS_ELEMENT (sibling) &&
element_has_class (WEBKIT_DOM_ELEMENT (sibling), "-x-evo-indented")) {
element = WEBKIT_DOM_ELEMENT (sibling);
} else {
element = e_html_editor_selection_get_indented_element (
selection, document, width);
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (block),
WEBKIT_DOM_NODE (element),
block,
NULL);
}
/* Remove style and let the paragraph inherit it from parent */
if (element_has_class (WEBKIT_DOM_ELEMENT (block), "-x-evo-paragraph"))
webkit_dom_element_remove_attribute (
WEBKIT_DOM_ELEMENT (block), "style");
tmp = webkit_dom_node_append_child (
WEBKIT_DOM_NODE (element),
block,
NULL);
sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element));
while (WEBKIT_DOM_IS_ELEMENT (sibling) &&
element_has_class (WEBKIT_DOM_ELEMENT (sibling), "-x-evo-indented")) {
WebKitDOMNode *next_sibling;
WebKitDOMNode *child;
next_sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (sibling));
while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (sibling)))) {
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (element),
child,
NULL);
}
remove_node (sibling);
sibling = next_sibling;
}
return tmp;
}
static void
remove_node_and_parents_if_empty (WebKitDOMNode *node)
{
WebKitDOMNode *parent;
parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (node));
remove_node (WEBKIT_DOM_NODE (node));
while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
WebKitDOMNode *prev_sibling, *next_sibling;
prev_sibling = webkit_dom_node_get_previous_sibling (parent);
next_sibling = webkit_dom_node_get_next_sibling (parent);
/* Empty or BR as sibling, but no sibling after it. */
if ((!prev_sibling ||
(WEBKIT_DOM_IS_HTMLBR_ELEMENT (prev_sibling) &&
!webkit_dom_node_get_previous_sibling (prev_sibling))) &&
(!next_sibling ||
(WEBKIT_DOM_IS_HTMLBR_ELEMENT (next_sibling) &&
!webkit_dom_node_get_next_sibling (next_sibling)))) {
WebKitDOMNode *tmp;
tmp = webkit_dom_node_get_parent_node (parent);
remove_node (parent);
parent = tmp;
} else {
if (!webkit_dom_node_get_first_child (parent))
remove_node (parent);
return;
}
}
}
static gboolean
do_format_change_list_to_block (EHTMLEditorSelection *selection,
EHTMLEditorSelectionBlockFormat format,
WebKitDOMNode *item,
const gchar *value,
WebKitDOMDocument *document)
{
gboolean after_end = FALSE;
gint level;
WebKitDOMElement *element, *selection_end;
WebKitDOMNode *node, *source_list;
selection_end = webkit_dom_document_query_selector (
document, "span#-x-evo-selection-end-marker", NULL);
source_list = webkit_dom_node_get_parent_node (item);
while (source_list) {
WebKitDOMNode *parent;
parent = webkit_dom_node_get_parent_node (source_list);
if (node_is_list (parent))
source_list = parent;
else
break;
}
if (webkit_dom_node_contains (source_list, WEBKIT_DOM_NODE (selection_end)))
source_list = split_node_into_two (item, -1);
else {
source_list = webkit_dom_node_get_next_sibling (source_list);
}
/* Process all nodes that are in selection one by one */
while (item && WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) {
WebKitDOMNode *next_item;
next_item = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (item));
if (!next_item) {
WebKitDOMNode *parent;
WebKitDOMNode *tmp = item;
while (tmp) {
parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (tmp));
if (!node_is_list (parent))
break;
next_item = webkit_dom_node_get_next_sibling (parent);
if (node_is_list (next_item)) {
next_item = webkit_dom_node_get_first_child (next_item);
break;
} else if (next_item && !WEBKIT_DOM_IS_HTMLLI_ELEMENT (next_item)) {
next_item = webkit_dom_node_get_next_sibling (next_item);
break;
} else if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (next_item)) {
break;
}
tmp = parent;
}
} else if (node_is_list (next_item)) {
next_item = webkit_dom_node_get_first_child (next_item);
} else if (!WEBKIT_DOM_IS_HTMLLI_ELEMENT (next_item)) {
next_item = webkit_dom_node_get_next_sibling (item);
continue;
}
if (!after_end) {
after_end = webkit_dom_node_contains (item, WEBKIT_DOM_NODE (selection_end));
level = get_indentation_level (WEBKIT_DOM_ELEMENT (item));
if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH) {
element = e_html_editor_selection_get_paragraph_element (
selection, document, -1, 0);
} else
element = webkit_dom_document_create_element (
document, value, NULL);
while ((node = webkit_dom_node_get_first_child (item)))
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (element), node, NULL);
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (source_list),
WEBKIT_DOM_NODE (element),
source_list,
NULL);
if (level > 0) {
gint final_width = 0;
node = WEBKIT_DOM_NODE (element);
if (element_has_class (element, "-x-evo-paragraph"))
final_width = selection->priv->word_wrap_length -
SPACES_PER_INDENTATION * level;
while (level--)
node = indent_block (selection, document, node, final_width);
}
remove_node_and_parents_if_empty (item);
} else
break;
item = next_item;
}
remove_node_if_empty (source_list);
return after_end;
}
static void
format_change_list_to_block (EHTMLEditorSelection *selection,
EHTMLEditorSelectionBlockFormat format,
const gchar *value,
WebKitDOMDocument *document)
{
WebKitDOMElement *selection_start;
WebKitDOMNode *item;
selection_start = webkit_dom_document_query_selector (
document, "span#-x-evo-selection-start-marker", NULL);
item = get_list_item_node_from_child (WEBKIT_DOM_NODE (selection_start));
do_format_change_list_to_block (selection, format, item, value, document);
}
static void
change_leading_space_to_nbsp (WebKitDOMNode *block)
{
WebKitDOMNode *child;
if (!WEBKIT_DOM_IS_HTML_PRE_ELEMENT (block))
return;
if ((child = webkit_dom_node_get_first_child (block)) &&
WEBKIT_DOM_IS_CHARACTER_DATA (child)) {
gchar *data;
data = webkit_dom_character_data_substring_data (
WEBKIT_DOM_CHARACTER_DATA (child), 0, 1, NULL);
if (data && *data == ' ')
webkit_dom_character_data_replace_data (
WEBKIT_DOM_CHARACTER_DATA (child), 0, 1, UNICODE_NBSP, NULL);
g_free (data);
}
}
static void
change_trailing_space_in_block_to_nbsp (WebKitDOMNode *block)
{
WebKitDOMNode *child;
if ((child = webkit_dom_node_get_last_child (block)) &&
WEBKIT_DOM_IS_CHARACTER_DATA (child)) {
gchar *tmp;
gulong length;
length = webkit_dom_character_data_get_length (
WEBKIT_DOM_CHARACTER_DATA (child));
tmp = webkit_dom_character_data_substring_data (
WEBKIT_DOM_CHARACTER_DATA (child), length - 1, 1, NULL);
if (tmp && *tmp == ' ') {
webkit_dom_character_data_replace_data (
WEBKIT_DOM_CHARACTER_DATA (child),
length - 1,
1,
UNICODE_NBSP,
NULL);
}
g_free (tmp);
}
}
static void
change_space_before_selection_to_nbsp (WebKitDOMNode *node)
{
WebKitDOMNode *prev_sibling;
if ((prev_sibling = webkit_dom_node_get_previous_sibling (node))) {
if (WEBKIT_DOM_IS_CHARACTER_DATA (prev_sibling)) {
gchar *tmp;
gulong length;
length = webkit_dom_character_data_get_length (
WEBKIT_DOM_CHARACTER_DATA (prev_sibling));
tmp = webkit_dom_character_data_substring_data (
WEBKIT_DOM_CHARACTER_DATA (prev_sibling), length - 1, 1, NULL);
if (tmp && *tmp == ' ') {
webkit_dom_character_data_replace_data (
WEBKIT_DOM_CHARACTER_DATA (prev_sibling),
length - 1,
1,
UNICODE_NBSP,
NULL);
}
g_free (tmp);
}
}
}
static gboolean
process_block_to_block (EHTMLEditorSelection *selection,
EHTMLEditorView *view,
WebKitDOMDocument *document,
EHTMLEditorSelectionBlockFormat format,
const gchar *value,
WebKitDOMNode *block,
WebKitDOMNode *end_block,
WebKitDOMNode *blockquote,
gboolean html_mode)
{
gboolean after_selection_end = FALSE;
WebKitDOMNode *next_block;
while (!after_selection_end && block) {
gboolean quoted = FALSE;
gboolean empty = FALSE;
gchar *content;
WebKitDOMNode *child;
WebKitDOMElement *element;
if (is_citation_node (block)) {
gboolean finished;
next_block = webkit_dom_node_get_next_sibling (block);
finished = process_block_to_block (
selection,
view,
document,
format,
value,
webkit_dom_node_get_first_child (block),
end_block,
blockquote,
html_mode);
if (finished)
return TRUE;
block = next_block;
continue;
}
if (webkit_dom_element_query_selector (
WEBKIT_DOM_ELEMENT (block), "span.-x-evo-quoted", NULL)) {
quoted = TRUE;
remove_quoting_from_element (WEBKIT_DOM_ELEMENT (block));
}
if (!html_mode)
remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (block));
after_selection_end = webkit_dom_node_is_same_node (block, end_block);
next_block = webkit_dom_node_get_next_sibling (block);
if (node_is_list (block)) {
WebKitDOMNode *item;
item = webkit_dom_node_get_first_child (block);
while (item && !WEBKIT_DOM_IS_HTMLLI_ELEMENT (item))
item = webkit_dom_node_get_first_child (item);
if (item && do_format_change_list_to_block (selection, format, item, value, document))
return TRUE;
block = next_block;
continue;
}
if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH ||
format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE)
element = e_html_editor_selection_get_paragraph_element (
selection, document, -1, 0);
else
element = webkit_dom_document_create_element (
document, value, NULL);
content = webkit_dom_node_get_text_content (block);
empty = !*content || (g_strcmp0 (content, UNICODE_ZERO_WIDTH_SPACE) == 0);
g_free (content);
change_leading_space_to_nbsp (block);
change_trailing_space_in_block_to_nbsp (block);
while ((child = webkit_dom_node_get_first_child (block))) {
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (child))
empty = FALSE;
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (element), child, NULL);
}
if (empty) {
WebKitDOMElement *br;
br = webkit_dom_document_create_element (
document, "BR", NULL);
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (element), WEBKIT_DOM_NODE (br), NULL);
}
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (block),
WEBKIT_DOM_NODE (element),
block,
NULL);
remove_node (block);
if (!next_block && !after_selection_end) {
gint citation_level;
citation_level = get_citation_level (WEBKIT_DOM_NODE (element));
if (citation_level > 0) {
next_block = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
next_block = webkit_dom_node_get_next_sibling (next_block);
}
}
block = next_block;
if (!html_mode &&
(format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH ||
format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE)) {
gint citation_level, quote;
if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE)
citation_level = 1;
else
citation_level = get_citation_level (WEBKIT_DOM_NODE (element));
quote = citation_level ? citation_level * 2 : 0;
if (citation_level > 0)
element = e_html_editor_selection_wrap_paragraph_length (
selection, element, selection->priv->word_wrap_length - quote);
}
if (blockquote && format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE) {
webkit_dom_node_append_child (
blockquote, WEBKIT_DOM_NODE (element), NULL);
if (!html_mode)
e_html_editor_view_quote_plain_text_element_after_wrapping (document, element, 1);
} else
if (!html_mode && quoted)
e_html_editor_view_quote_plain_text_element (view, element);
}
return after_selection_end;
}
static void
format_change_block_to_block (EHTMLEditorSelection *selection,
EHTMLEditorSelectionBlockFormat format,
EHTMLEditorView *view,
const gchar *value,
WebKitDOMDocument *document)
{
gboolean html_mode;
WebKitDOMElement *selection_start_marker, *selection_end_marker;
WebKitDOMNode *block, *end_block, *blockquote = NULL;
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 the selection was not saved, move it into the first child of body */
if (!selection_start_marker || !selection_end_marker) {
WebKitDOMHTMLElement *body;
WebKitDOMNode *child;
body = webkit_dom_document_get_body (document);
child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
add_selection_markers_into_element_start (
document,
WEBKIT_DOM_ELEMENT (child),
&selection_start_marker,
&selection_end_marker);
}
block = e_html_editor_get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
html_mode = e_html_editor_view_get_html_mode (view);
if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE) {
blockquote = WEBKIT_DOM_NODE (
webkit_dom_document_create_element (document, "BLOCKQUOTE", NULL));
webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (blockquote), "type", "cite", NULL);
if (!html_mode)
webkit_dom_element_set_attribute (
WEBKIT_DOM_ELEMENT (blockquote), "class", "-x-evo-plaintext-quoted", NULL);
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (block),
blockquote,
block,
NULL);
}
end_block = e_html_editor_get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_end_marker));
/* Process all blocks that are in the selection one by one */
process_block_to_block (
selection, view, document, format, value, block, end_block, blockquote, html_mode);
}
static void
format_change_block_to_list (EHTMLEditorSelection *selection,
EHTMLEditorSelectionBlockFormat format,
EHTMLEditorView *view,
WebKitDOMDocument *document)
{
gboolean after_selection_end = FALSE, in_quote = FALSE;
gboolean html_mode = e_html_editor_view_get_html_mode (view);
WebKitDOMElement *selection_start_marker, *selection_end_marker, *item, *list;
WebKitDOMNode *block, *next_block;
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 the selection was not saved, move it into the first child of body */
if (!selection_start_marker || !selection_end_marker) {
WebKitDOMHTMLElement *body;
WebKitDOMNode *child;
body = webkit_dom_document_get_body (document);
child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
add_selection_markers_into_element_start (
document,
WEBKIT_DOM_ELEMENT (child),
&selection_start_marker,
&selection_end_marker);
}
block = e_html_editor_get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
list = create_list_element (selection, document, format, 0, html_mode);
if (webkit_dom_element_query_selector (
WEBKIT_DOM_ELEMENT (block), "span.-x-evo-quoted", NULL)) {
WebKitDOMElement *element;
WebKitDOMDOMWindow *dom_window;
WebKitDOMDOMSelection *dom_selection;
WebKitDOMRange *range;
in_quote = TRUE;
dom_window = webkit_dom_document_get_default_view (document);
dom_selection = webkit_dom_dom_window_get_selection (dom_window);
range = webkit_dom_document_create_range (document);
webkit_dom_range_select_node (range, block, NULL);
webkit_dom_range_collapse (range, TRUE, NULL);
webkit_dom_dom_selection_remove_all_ranges (dom_selection);
webkit_dom_dom_selection_add_range (dom_selection, range);
g_object_unref (range);
g_object_unref (dom_selection);
g_object_unref (dom_window);
e_html_editor_view_exec_command (
view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT, NULL);
element = webkit_dom_document_query_selector (
document, "body>br", NULL);
webkit_dom_node_replace_child (
webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
WEBKIT_DOM_NODE (list),
WEBKIT_DOM_NODE (element),
NULL);
block = e_html_editor_get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
} else
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (block),
WEBKIT_DOM_NODE (list),
block,
NULL);
/* Process all blocks that are in the selection one by one */
while (block && !after_selection_end) {
gboolean empty = FALSE;
gchar *content;
WebKitDOMNode *child, *parent;
after_selection_end = webkit_dom_node_contains (
block, WEBKIT_DOM_NODE (selection_end_marker));
next_block = webkit_dom_node_get_next_sibling (
WEBKIT_DOM_NODE (block));
remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (block));
remove_quoting_from_element (WEBKIT_DOM_ELEMENT (block));
item = webkit_dom_document_create_element (document, "LI", NULL);
content = webkit_dom_node_get_text_content (block);
empty = !*content || (g_strcmp0 (content, UNICODE_ZERO_WIDTH_SPACE) == 0);
g_free (content);
change_leading_space_to_nbsp (block);
change_trailing_space_in_block_to_nbsp (block);
while ((child = webkit_dom_node_get_first_child (block))) {
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (child))
empty = FALSE;
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (item), child, NULL);
}
/* We have to use again the hidden space to move caret into newly inserted list */
if (empty) {
WebKitDOMElement *br;
br = webkit_dom_document_create_element (
document, "BR", NULL);
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (item), WEBKIT_DOM_NODE (br), NULL);
}
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (list), WEBKIT_DOM_NODE (item), NULL);
parent = webkit_dom_node_get_parent_node (block);
remove_node (block);
if (in_quote) {
/* Remove all parents if previously removed node was the
* only one with text content */
content = webkit_dom_node_get_text_content (parent);
while (parent && content && !*content) {
WebKitDOMNode *tmp = webkit_dom_node_get_parent_node (parent);
remove_node (parent);
parent = tmp;
g_free (content);
content = webkit_dom_node_get_text_content (parent);
}
g_free (content);
}
block = next_block;
}
merge_lists_if_possible (WEBKIT_DOM_NODE (list));
}
static void
format_change_list_to_list (EHTMLEditorSelection *selection,
EHTMLEditorSelectionBlockFormat format,
WebKitDOMDocument *document,
gboolean html_mode)
{
EHTMLEditorSelectionBlockFormat prev = 0, next = 0;
gboolean done = FALSE, indented = FALSE;
gboolean selection_starts_in_first_child, selection_ends_in_last_child;
WebKitDOMElement *selection_start_marker, *selection_end_marker;
WebKitDOMNode *prev_list, *current_list, *next_list;
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);
current_list = get_list_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
prev_list = get_list_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
next_list = get_list_node_from_child (
WEBKIT_DOM_NODE (selection_end_marker));
selection_starts_in_first_child =
webkit_dom_node_contains (
webkit_dom_node_get_first_child (current_list),
WEBKIT_DOM_NODE (selection_start_marker));
selection_ends_in_last_child =
webkit_dom_node_contains (
webkit_dom_node_get_last_child (current_list),
WEBKIT_DOM_NODE (selection_end_marker));
indented = element_has_class (WEBKIT_DOM_ELEMENT (current_list), "-x-evo-indented");
if (!prev_list || !next_list || indented) {
format_change_list_from_list (selection, document, format, html_mode);
return;
}
if (webkit_dom_node_is_same_node (prev_list, next_list)) {
prev_list = webkit_dom_node_get_previous_sibling (
webkit_dom_node_get_parent_node (
webkit_dom_node_get_parent_node (
WEBKIT_DOM_NODE (selection_start_marker))));
next_list = webkit_dom_node_get_next_sibling (
webkit_dom_node_get_parent_node (
webkit_dom_node_get_parent_node (
WEBKIT_DOM_NODE (selection_end_marker))));
if (!prev_list || !next_list) {
format_change_list_from_list (selection, document, format, html_mode);
return;
}
}
prev = get_list_format_from_node (prev_list);
next = get_list_format_from_node (next_list);
if (format == prev && format != -1 && prev != -1) {
if (selection_starts_in_first_child && selection_ends_in_last_child) {
done = TRUE;
merge_list_into_list (current_list, prev_list, FALSE);
}
}
if (format == next && format != -1 && next != -1) {
if (selection_starts_in_first_child && selection_ends_in_last_child) {
done = TRUE;
merge_list_into_list (next_list, prev_list, FALSE);
}
}
if (done)
return;
format_change_list_from_list (selection, document, format, html_mode);
}
/**
* e_html_editor_selection_set_block_format:
* @selection: an #EHTMLEditorSelection
* @format: an #EHTMLEditorSelectionBlockFormat value
*
* Changes block format of current paragraph to @format.
*/
void
e_html_editor_selection_set_block_format (EHTMLEditorSelection *selection,
EHTMLEditorSelectionBlockFormat format)
{
EHTMLEditorView *view;
EHTMLEditorSelectionBlockFormat current_format;
EHTMLEditorSelectionAlignment current_alignment;
EHTMLEditorViewHistoryEvent *ev = NULL;
const gchar *value;
gboolean from_list = FALSE, to_list = FALSE, html_mode;
WebKitDOMDocument *document;
WebKitDOMRange *range;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
current_format = e_html_editor_selection_get_block_format (selection);
if (current_format == format) {
return;
}
switch (format) {
case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE:
value = "BLOCKQUOTE";
break;
case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H1:
value = "H1";
break;
case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H2:
value = "H2";
break;
case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H3:
value = "H3";
break;
case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H4:
value = "H4";
break;
case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H5:
value = "H5";
break;
case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H6:
value = "H6";
break;
case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH:
value = "P";
break;
case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PRE:
value = "PRE";
break;
case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ADDRESS:
value = "ADDRESS";
break;
case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST:
case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA:
case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ROMAN:
to_list = TRUE;
value = NULL;
break;
case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST:
to_list = TRUE;
value = NULL;
break;
case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_NONE:
default:
value = NULL;
break;
}
/* H1 - H6 have bold font by default */
if (format >= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H1 &&
format <= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H6)
selection->priv->is_bold = TRUE;
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
html_mode = e_html_editor_view_get_html_mode (view);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
from_list =
current_format >= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST;
range = html_editor_selection_get_current_range (selection);
if (!range) {
g_object_unref (view);
return;
}
current_alignment = selection->priv->alignment;
e_html_editor_selection_save (selection);
if (!e_html_editor_view_is_undo_redo_in_progress (view)) {
ev = g_new0 (EHTMLEditorViewHistoryEvent, 1);
if (format != E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE)
ev->type = HISTORY_BLOCK_FORMAT;
else
ev->type = HISTORY_BLOCKQUOTE;
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->before.start.x,
&ev->before.start.y,
&ev->before.end.x,
&ev->before.end.y);
if (format != E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE) {
ev->data.style.from = current_format;
ev->data.style.to = format;
} else {
WebKitDOMDocumentFragment *fragment;
WebKitDOMElement *selection_start_marker, *selection_end_marker;
WebKitDOMNode *block, *end_block;
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");
block = e_html_editor_get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
end_block = e_html_editor_get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_end_marker));
if (webkit_dom_range_get_collapsed (range, NULL) ||
webkit_dom_node_is_same_node (block, end_block)) {
fragment = webkit_dom_document_create_document_fragment (document);
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (fragment),
webkit_dom_node_clone_node (block, TRUE),
NULL);
} else {
fragment = webkit_dom_range_clone_contents (range, NULL);
webkit_dom_node_replace_child (
WEBKIT_DOM_NODE (fragment),
webkit_dom_node_clone_node (block, TRUE),
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment)),
NULL);
webkit_dom_node_replace_child (
WEBKIT_DOM_NODE (fragment),
webkit_dom_node_clone_node (end_block, TRUE),
webkit_dom_node_get_last_child (WEBKIT_DOM_NODE (fragment)),
NULL);
}
ev->data.fragment = fragment;
}
}
g_object_unref (range);
if (current_format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PRE) {
WebKitDOMElement *selection_marker;
selection_marker = webkit_dom_document_get_element_by_id (
document, "-x-evo-selection-start-marker");
if (selection_marker)
change_space_before_selection_to_nbsp (WEBKIT_DOM_NODE (selection_marker));
selection_marker = webkit_dom_document_get_element_by_id (
document, "-x-evo-selection-end-marker");
if (selection_marker)
change_space_before_selection_to_nbsp (WEBKIT_DOM_NODE (selection_marker));
}
if (from_list && to_list)
format_change_list_to_list (selection, format, document, html_mode);
if (!from_list && !to_list)
format_change_block_to_block (selection, format, view, value, document);
if (from_list && !to_list) {
format_change_list_to_block (selection, format, value, document);
if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE) {
e_html_editor_selection_restore (selection);
format_change_block_to_block (selection, format, view, value, document);
}
}
if (!from_list && to_list)
format_change_block_to_list (selection, format, view, document);
e_html_editor_selection_restore (selection);
e_html_editor_view_force_spell_check_for_current_paragraph (view);
/* When changing the format we need to re-set the alignment */
e_html_editor_selection_set_alignment (selection, current_alignment);
e_html_editor_view_set_changed (view, TRUE);
if (ev) {
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->after.start.x,
&ev->after.start.y,
&ev->after.end.x,
&ev->after.end.y);
e_html_editor_view_insert_new_history_event (view, ev);
}
g_object_unref (view);
g_object_notify (G_OBJECT (selection), "block-format");
}
/**
* e_html_editor_selection_get_font_color:
* @selection: an #EHTMLEditorSelection
* @rgba: a #GdkRGBA object to be set to current font color
*
* Sets @rgba to contain color of current text selection or letter at current
* cursor position.
*/
void
e_html_editor_selection_get_font_color (EHTMLEditorSelection *selection,
GdkRGBA *rgba)
{
EHTMLEditorView *view;
gchar *color;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
if (!e_html_editor_view_get_html_mode (view)) {
g_object_unref (view);
*rgba = black;
return;
}
color = get_font_property (selection, "color");
if (!(color && *color)) {
WebKitDOMDocument *document;
WebKitDOMHTMLElement *body;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
body = webkit_dom_document_get_body (document);
g_free (color);
color = webkit_dom_html_body_element_get_text (WEBKIT_DOM_HTML_BODY_ELEMENT (body));
if (!(color && *color)) {
*rgba = black;
g_object_unref (view);
g_free (color);
return;
}
}
gdk_rgba_parse (rgba, color);
g_free (color);
g_object_unref (view);
}
/**
* e_html_editor_selection_set_font_color:
* @selection: an #EHTMLEditorSelection
* @rgba: a #GdkRGBA
*
* Sets font color of current selection or letter at current cursor position to
* color defined in @rgba.
*/
void
e_html_editor_selection_set_font_color (EHTMLEditorSelection *selection,
const GdkRGBA *rgba)
{
EHTMLEditorView *view;
EHTMLEditorViewCommand command;
EHTMLEditorViewHistoryEvent *ev = NULL;
guint32 rgba_value;
gchar *color;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
if (!rgba)
rgba = &black;
rgba_value = e_rgba_to_value ((GdkRGBA *) rgba);
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
command = E_HTML_EDITOR_VIEW_COMMAND_FORE_COLOR;
color = g_strdup_printf ("#%06x", rgba_value);
if (!e_html_editor_view_is_undo_redo_in_progress (view)) {
ev = g_new0 (EHTMLEditorViewHistoryEvent, 1);
ev->type = HISTORY_FONT_COLOR;
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->before.start.x,
&ev->before.start.y,
&ev->before.end.x,
&ev->before.end.y);
ev->data.string.from = g_strdup (selection->priv->font_color);
ev->data.string.to = g_strdup (color);
}
g_free (selection->priv->font_color);
selection->priv->font_color = g_strdup (color);
e_html_editor_view_exec_command (view, command, color);
g_free (color);
if (ev) {
ev->after.start.x = ev->before.start.x;
ev->after.start.y = ev->before.start.y;
ev->after.end.x = ev->before.end.x;
ev->after.end.y = ev->before.end.y;
e_html_editor_view_insert_new_history_event (view, ev);
}
g_object_unref (view);
g_object_notify (G_OBJECT (selection), "font-color");
}
/**
* e_html_editor_selection_get_font_name:
* @selection: an #EHTMLEditorSelection
*
* Returns name of font used in current selection or at letter at current cursor
* position.
*
* Returns: A string with font name. [transfer-none]
*/
const gchar *
e_html_editor_selection_get_font_name (EHTMLEditorSelection *selection)
{
WebKitDOMNode *node;
WebKitDOMRange *range;
WebKitDOMCSSStyleDeclaration *css;
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL);
range = html_editor_selection_get_current_range (selection);
node = webkit_dom_range_get_common_ancestor_container (range, NULL);
g_object_unref (range);
g_free (selection->priv->font_family);
css = webkit_dom_element_get_style (WEBKIT_DOM_ELEMENT (node));
selection->priv->font_family =
webkit_dom_css_style_declaration_get_property_value (css, "fontFamily");
g_object_unref (css);
return selection->priv->font_family;
}
/**
* e_html_editor_selection_set_font_name:
* @selection: an #EHTMLEditorSelection
* @font_name: a font name to apply
*
* Sets font name of current selection or of letter at current cursor position
* to @font_name.
*/
void
e_html_editor_selection_set_font_name (EHTMLEditorSelection *selection,
const gchar *font_name)
{
EHTMLEditorView *view;
EHTMLEditorViewCommand command;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
command = E_HTML_EDITOR_VIEW_COMMAND_FONT_NAME;
e_html_editor_view_exec_command (view, command, font_name);
g_object_unref (view);
g_object_notify (G_OBJECT (selection), "font-name");
}
/**
* e_editor_Selection_get_font_size:
* @selection: an #EHTMLEditorSelection
*
* Returns point size of current selection or of letter at current cursor position.
*/
guint
e_html_editor_selection_get_font_size (EHTMLEditorSelection *selection)
{
gchar *size;
guint size_int;
gboolean increment;
g_return_val_if_fail (
E_IS_HTML_EDITOR_SELECTION (selection),
E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL);
size = get_font_property (selection, "size");
if (!(size && *size)) {
g_free (size);
return E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL;
}
/* We don't support increments, but when going through a content that
* was not written in Evolution we can find it. In this case just report
* the normal size. */
/* FIXME: go through all parent and get the right value. */
increment = size[0] == '+' || size[0] == '-';
size_int = atoi (size);
g_free (size);
if (increment || size_int == 0)
return E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL;
return size_int;
}
static WebKitDOMElement *
set_font_style (WebKitDOMDocument *document,
const gchar *element_name,
gboolean value)
{
WebKitDOMElement *element;
WebKitDOMNode *parent;
element = webkit_dom_document_get_element_by_id (document, "-x-evo-selection-end-marker");
parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
if (value) {
WebKitDOMNode *node;
WebKitDOMElement *el;
gchar *name;
el = webkit_dom_document_create_element (document, element_name, NULL);
webkit_dom_html_element_set_inner_text (
WEBKIT_DOM_HTML_ELEMENT (el), UNICODE_ZERO_WIDTH_SPACE, NULL);
node = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element));
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (el), node, NULL);
name = webkit_dom_node_get_local_name (parent);
if (g_strcmp0 (name, element_name) == 0 && g_strcmp0 (name, "font") != 0)
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (parent),
WEBKIT_DOM_NODE (el),
webkit_dom_node_get_next_sibling (parent),
NULL);
else
webkit_dom_node_insert_before (
parent,
WEBKIT_DOM_NODE (el),
WEBKIT_DOM_NODE (element),
NULL);
g_free (name);
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (el), WEBKIT_DOM_NODE (element), NULL);
return el;
} else {
WebKitDOMNode *node;
node = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element));
/* Turning the formatting in the middle of element. */
if (webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element))) {
WebKitDOMNode *clone;
WebKitDOMNode *sibling;
clone = webkit_dom_node_clone_node (
WEBKIT_DOM_NODE (parent), FALSE);
while ((sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element))))
webkit_dom_node_insert_before (
clone,
sibling,
webkit_dom_node_get_first_child (clone),
NULL);
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (parent),
clone,
webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (parent)),
NULL);
}
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (parent),
WEBKIT_DOM_NODE (element),
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);
webkit_dom_html_element_insert_adjacent_text (
WEBKIT_DOM_HTML_ELEMENT (parent),
"afterend",
UNICODE_ZERO_WIDTH_SPACE,
NULL);
}
return NULL;
}
/**
* e_html_editor_selection_set_font_size:
* @selection: an #EHTMLEditorSelection
* @font_size: point size to apply
*
* Sets font size of current selection or of letter at current cursor position
* to @font_size.
*/
void
e_html_editor_selection_set_font_size (EHTMLEditorSelection *selection,
guint font_size)
{
EHTMLEditorView *view;
EHTMLEditorViewCommand command;
EHTMLEditorViewHistoryEvent *ev = NULL;
gchar *size_str;
guint current_font_size;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
current_font_size = e_html_editor_selection_get_font_size (selection);
if (current_font_size == font_size) {
g_object_unref (view);
return;
}
e_html_editor_selection_save (selection);
if (!e_html_editor_view_is_undo_redo_in_progress (view)) {
ev = g_new0 (EHTMLEditorViewHistoryEvent, 1);
ev->type = HISTORY_FONT_SIZE;
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->before.start.x,
&ev->before.start.y,
&ev->before.end.x,
&ev->before.end.y);
ev->data.style.from = current_font_size;
ev->data.style.to = font_size;
}
selection->priv->font_size = font_size;
size_str = g_strdup_printf ("%d", font_size);
if (e_html_editor_selection_is_collapsed (selection)) {
WebKitDOMElement *font;
WebKitDOMDocument *document;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
font = set_font_style (document, "font", font_size != 3);
if (font)
webkit_dom_element_set_attribute (font, "size", size_str, NULL);
e_html_editor_selection_restore (selection);
goto exit;
}
e_html_editor_selection_restore (selection);
command = E_HTML_EDITOR_VIEW_COMMAND_FONT_SIZE;
e_html_editor_view_exec_command (view, command, size_str);
/* Text in <font size="3"></font> (size 3 is our default size) is a little
* bit smaller than font outsize it. So move it outside of it. */
if (font_size == E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL) {
WebKitDOMDocument *document;
WebKitDOMElement *element;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
element = webkit_dom_document_query_selector (document, "font[size=\"3\"]", NULL);
if (element) {
WebKitDOMNode *child;
while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element))))
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
child,
WEBKIT_DOM_NODE (element),
NULL);
remove_node (WEBKIT_DOM_NODE (element));
}
}
exit:
g_free (size_str);
if (ev) {
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->after.start.x,
&ev->after.start.y,
&ev->after.end.x,
&ev->after.end.y);
e_html_editor_view_insert_new_history_event (view, ev);
}
g_object_unref (view);
g_object_notify (G_OBJECT (selection), "font-size");
}
/**
* e_html_editor_selection_is_citation:
* @selection: an #EHTMLEditorSelection
*
* Returns whether current paragraph is a citation.
*
* Returns: @TRUE when current paragraph is a citation, @FALSE otherwise.
*/
gboolean
e_html_editor_selection_is_citation (EHTMLEditorSelection *selection)
{
gboolean ret_val;
gchar *value, *text_content;
WebKitDOMNode *node;
WebKitDOMRange *range;
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
range = html_editor_selection_get_current_range (selection);
if (!range)
return FALSE;
node = webkit_dom_range_get_common_ancestor_container (range, NULL);
g_object_unref (range);
if (WEBKIT_DOM_IS_TEXT (node))
return get_has_style (selection, "citation");
text_content = webkit_dom_node_get_text_content (node);
if (g_strcmp0 (text_content, "") == 0) {
g_free (text_content);
return FALSE;
}
g_free (text_content);
value = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "type");
/* citation == <blockquote type='cite'> */
if (strstr (value, "cite"))
ret_val = TRUE;
else
ret_val = get_has_style (selection, "citation");
g_free (value);
return ret_val;
}
static WebKitDOMNode *
get_parent_indented_block (WebKitDOMNode *node)
{
WebKitDOMNode *parent, *block = NULL;
parent = webkit_dom_node_get_parent_node (node);
if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-indented"))
block = parent;
while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent) &&
!WEBKIT_DOM_IS_HTML_HTML_ELEMENT (parent)) {
if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-indented"))
block = parent;
parent = webkit_dom_node_get_parent_node (parent);
}
return block;
}
static WebKitDOMElement*
get_element_for_inspection (WebKitDOMRange *range)
{
WebKitDOMNode *node;
node = webkit_dom_range_get_end_container (range, NULL);
/* No selection or whole body selected */
if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node))
return NULL;
return WEBKIT_DOM_ELEMENT (get_parent_indented_block (node));
}
/**
* e_html_editor_selection_is_indented:
* @selection: an #EHTMLEditorSelection
*
* Returns whether current paragraph is indented. This does not include
* citations. To check, whether paragraph is a citation, use
* e_html_editor_selection_is_citation().
*
* Returns: @TRUE when current paragraph is indented, @FALSE otherwise.
*/
gboolean
e_html_editor_selection_is_indented (EHTMLEditorSelection *selection)
{
WebKitDOMElement *element;
WebKitDOMRange *range;
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
range = html_editor_selection_get_current_range (selection);
if (!range)
return FALSE;
if (webkit_dom_range_get_collapsed (range, NULL)) {
element = get_element_for_inspection (range);
g_object_unref (range);
return element_has_class (element, "-x-evo-indented");
} else {
WebKitDOMNode *node;
gboolean ret_val;
node = webkit_dom_range_get_end_container (range, NULL);
/* No selection or whole body selected */
if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node))
goto out;
element = WEBKIT_DOM_ELEMENT (get_parent_indented_block (node));
ret_val = element_has_class (element, "-x-evo-indented");
if (!ret_val)
goto out;
node = webkit_dom_range_get_start_container (range, NULL);
/* No selection or whole body selected */
if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node))
goto out;
element = WEBKIT_DOM_ELEMENT (get_parent_indented_block (node));
ret_val = element_has_class (element, "-x-evo-indented");
g_object_unref (range);
return ret_val;
}
out:
g_object_unref (range);
return FALSE;
}
static gboolean
is_in_html_mode (EHTMLEditorSelection *selection)
{
EHTMLEditorView *view = e_html_editor_selection_ref_html_editor_view (selection);
gboolean ret_val;
g_return_val_if_fail (view != NULL, FALSE);
ret_val = e_html_editor_view_get_html_mode (view);
g_object_unref (view);
return ret_val;
}
static gint
get_list_level (WebKitDOMNode *node)
{
gint level = 0;
while (node && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node)) {
if (node_is_list (node))
level++;
node = webkit_dom_node_get_parent_node (node);
}
return level;
}
static gboolean
indent_list (EHTMLEditorSelection *selection,
WebKitDOMDocument *document)
{
WebKitDOMElement *selection_start_marker, *selection_end_marker;
WebKitDOMNode *item, *next_item;
gboolean after_selection_end = FALSE;
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);
item = e_html_editor_get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) {
gboolean html_mode = is_in_html_mode (selection);
WebKitDOMElement *list;
WebKitDOMNode *source_list = webkit_dom_node_get_parent_node (item);
EHTMLEditorSelectionBlockFormat format;
format = get_list_format_from_node (source_list);
list = create_list_element (
selection, document, format, get_list_level (item), html_mode);
element_add_class (list, "-x-evo-indented");
webkit_dom_node_insert_before (
source_list, WEBKIT_DOM_NODE (list), item, NULL);
while (item && !after_selection_end) {
after_selection_end = webkit_dom_node_contains (
item, WEBKIT_DOM_NODE (selection_end_marker));
next_item = webkit_dom_node_get_next_sibling (item);
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (list), item, NULL);
item = next_item;
}
merge_lists_if_possible (WEBKIT_DOM_NODE (list));
}
return after_selection_end;
}
/**
* e_html_editor_selection_indent:
* @selection: an #EHTMLEditorSelection
*
* Indents current paragraph by one level.
*/
void
e_html_editor_selection_indent (EHTMLEditorSelection *selection)
{
EHTMLEditorView *view;
EHTMLEditorViewHistoryEvent *ev = NULL;
gboolean after_selection_start = FALSE, after_selection_end = FALSE;
WebKitDOMDocument *document;
WebKitDOMElement *selection_start_marker, *selection_end_marker;
WebKitDOMNode *block;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (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 the selection was not saved, move it into the first child of body */
if (!selection_start_marker || !selection_end_marker) {
WebKitDOMHTMLElement *body;
WebKitDOMNode *child;
body = webkit_dom_document_get_body (document);
child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
add_selection_markers_into_element_start (
document,
WEBKIT_DOM_ELEMENT (child),
&selection_start_marker,
&selection_end_marker);
}
if (!e_html_editor_view_is_undo_redo_in_progress (view)) {
ev = g_new0 (EHTMLEditorViewHistoryEvent, 1);
ev->type = HISTORY_INDENT;
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->before.start.x,
&ev->before.start.y,
&ev->before.end.x,
&ev->before.end.y);
ev->data.style.from = 1;
ev->data.style.to = 1;
}
block = get_parent_indented_block (
WEBKIT_DOM_NODE (selection_start_marker));
if (!block)
block = e_html_editor_get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
while (block && !after_selection_end) {
gint ii, length, level, final_width = 0;
gint word_wrap_length = selection->priv->word_wrap_length;
WebKitDOMNode *next_block;
WebKitDOMNodeList *list;
next_block = webkit_dom_node_get_next_sibling (block);
list = webkit_dom_element_query_selector_all (
WEBKIT_DOM_ELEMENT (block),
".-x-evo-indented > *:not(.-x-evo-indented):not(li)",
NULL);
after_selection_end = webkit_dom_node_contains (
block, WEBKIT_DOM_NODE (selection_end_marker));
length = webkit_dom_node_list_get_length (list);
if (length == 0 && node_is_list_or_item (block)) {
after_selection_end = indent_list (selection, document);
goto next;
}
if (length == 0) {
if (!after_selection_start) {
after_selection_start = webkit_dom_node_contains (
block, WEBKIT_DOM_NODE (selection_start_marker));
if (!after_selection_start)
goto next;
}
if (element_has_class (WEBKIT_DOM_ELEMENT (block), "-x-evo-paragraph")) {
level = get_indentation_level (WEBKIT_DOM_ELEMENT (block));
final_width = word_wrap_length - SPACES_PER_INDENTATION * (level + 1);
if (final_width < MINIMAL_PARAGRAPH_WIDTH &&
!is_in_html_mode (selection))
goto next;
}
indent_block (selection, document, block, final_width);
if (after_selection_end)
goto next;
}
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *block_to_process;
block_to_process = webkit_dom_node_list_item (list, ii);
after_selection_end = webkit_dom_node_contains (
block_to_process, WEBKIT_DOM_NODE (selection_end_marker));
if (!after_selection_start) {
after_selection_start = webkit_dom_node_contains (
block_to_process,
WEBKIT_DOM_NODE (selection_start_marker));
if (!after_selection_start) {
g_object_unref (block_to_process);
continue;
}
}
if (element_has_class (WEBKIT_DOM_ELEMENT (block), "-x-evo-paragraph")) {
level = get_indentation_level (
WEBKIT_DOM_ELEMENT (block_to_process));
final_width = word_wrap_length - SPACES_PER_INDENTATION * (level + 1);
if (final_width < MINIMAL_PARAGRAPH_WIDTH &&
!is_in_html_mode (selection)) {
g_object_unref (block_to_process);
continue;
}
}
indent_block (selection, document, block_to_process, final_width);
if (after_selection_end) {
g_object_unref (block_to_process);
break;
}
g_object_unref (block_to_process);
}
next:
g_object_unref (list);
if (!after_selection_end)
block = next_block;
}
if (ev) {
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->after.start.x,
&ev->after.start.y,
&ev->after.end.x,
&ev->after.end.y);
e_html_editor_view_insert_new_history_event (view, ev);
}
e_html_editor_selection_restore (selection);
e_html_editor_view_force_spell_check_for_current_paragraph (view);
g_object_unref (view);
g_object_notify (G_OBJECT (selection), "indented");
}
static const gchar *
get_css_alignment_value_class (EHTMLEditorSelectionAlignment alignment)
{
if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT)
return ""; /* Left is by default on ltr */
if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER)
return "-x-evo-align-center";
if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT)
return "-x-evo-align-right";
return "";
}
static void
unindent_list (EHTMLEditorSelection *selection,
WebKitDOMDocument *document)
{
gboolean after = FALSE;
WebKitDOMElement *new_list;
WebKitDOMElement *selection_start_marker, *selection_end_marker;
WebKitDOMNode *source_list, *source_list_clone, *current_list, *item;
WebKitDOMNode *prev_item;
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;
/* Copy elements from previous block to list */
item = e_html_editor_get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
source_list = webkit_dom_node_get_parent_node (item);
new_list = WEBKIT_DOM_ELEMENT (
webkit_dom_node_clone_node (source_list, FALSE));
current_list = source_list;
source_list_clone = webkit_dom_node_clone_node (source_list, FALSE);
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (source_list),
WEBKIT_DOM_NODE (source_list_clone),
webkit_dom_node_get_next_sibling (source_list),
NULL);
if (element_has_class (WEBKIT_DOM_ELEMENT (source_list), "-x-evo-indented"))
element_add_class (WEBKIT_DOM_ELEMENT (new_list), "-x-evo-indented");
prev_item = source_list;
while (item) {
WebKitDOMNode *next_item = webkit_dom_node_get_next_sibling (item);
if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) {
if (after)
prev_item = webkit_dom_node_append_child (
source_list_clone, WEBKIT_DOM_NODE (item), NULL);
else
prev_item = webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (prev_item),
item,
webkit_dom_node_get_next_sibling (prev_item),
NULL);
}
if (webkit_dom_node_contains (item, WEBKIT_DOM_NODE (selection_end_marker)))
after = TRUE;
if (!next_item) {
if (after)
break;
current_list = webkit_dom_node_get_next_sibling (current_list);
next_item = webkit_dom_node_get_first_child (current_list);
}
item = next_item;
}
remove_node_if_empty (source_list_clone);
remove_node_if_empty (source_list);
}
static void
unindent_block (EHTMLEditorSelection *selection,
WebKitDOMDocument *document,
WebKitDOMNode *block)
{
gboolean before_node = TRUE;
gint word_wrap_length = selection->priv->word_wrap_length;
gint level, width;
EHTMLEditorSelectionAlignment alignment;
WebKitDOMElement *element;
WebKitDOMElement *prev_blockquote = NULL, *next_blockquote = NULL;
WebKitDOMNode *block_to_process, *node_clone = NULL, *child;
block_to_process = block;
alignment = e_html_editor_selection_get_alignment_from_node (block_to_process);
element = webkit_dom_node_get_parent_element (block_to_process);
if (!WEBKIT_DOM_IS_HTML_DIV_ELEMENT (element) &&
!element_has_class (element, "-x-evo-indented"))
return;
element_add_class (WEBKIT_DOM_ELEMENT (block_to_process), "-x-evo-to-unindent");
level = get_indentation_level (element);
width = word_wrap_length - SPACES_PER_INDENTATION * level;
/* Look if we have previous siblings, if so, we have to
* create new blockquote that will include them */
if (webkit_dom_node_get_previous_sibling (block_to_process))
prev_blockquote = e_html_editor_selection_get_indented_element (
selection, document, width);
/* Look if we have next siblings, if so, we have to
* create new blockquote that will include them */
if (webkit_dom_node_get_next_sibling (block_to_process))
next_blockquote = e_html_editor_selection_get_indented_element (
selection, document, width);
/* Copy nodes that are before / after the element that we want to unindent */
while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)))) {
if (webkit_dom_node_is_equal_node (child, block_to_process)) {
before_node = FALSE;
node_clone = webkit_dom_node_clone_node (child, TRUE);
remove_node (child);
continue;
}
webkit_dom_node_append_child (
before_node ?
WEBKIT_DOM_NODE (prev_blockquote) :
WEBKIT_DOM_NODE (next_blockquote),
child,
NULL);
}
if (node_clone) {
element_remove_class (WEBKIT_DOM_ELEMENT (node_clone), "-x-evo-to-unindent");
/* Insert blockqoute with nodes that were before the element that we want to unindent */
if (prev_blockquote) {
if (webkit_dom_node_has_child_nodes (WEBKIT_DOM_NODE (prev_blockquote))) {
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
WEBKIT_DOM_NODE (prev_blockquote),
WEBKIT_DOM_NODE (element),
NULL);
}
}
if (level == 1 && element_has_class (WEBKIT_DOM_ELEMENT (node_clone), "-x-evo-paragraph")) {
e_html_editor_selection_set_paragraph_style (
selection, WEBKIT_DOM_ELEMENT (node_clone), word_wrap_length, 0, "");
element_add_class (
WEBKIT_DOM_ELEMENT (node_clone),
get_css_alignment_value_class (alignment));
}
/* Insert the unindented element */
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
node_clone,
WEBKIT_DOM_NODE (element),
NULL);
} else {
g_warn_if_reached ();
}
/* Insert blockqoute with nodes that were after the element that we want to unindent */
if (next_blockquote) {
if (webkit_dom_node_has_child_nodes (WEBKIT_DOM_NODE (next_blockquote))) {
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
WEBKIT_DOM_NODE (next_blockquote),
WEBKIT_DOM_NODE (element),
NULL);
}
}
/* Remove old blockquote */
remove_node (WEBKIT_DOM_NODE (element));
}
/**
* e_html_editor_selection_unindent:
* @selection: an #EHTMLEditorSelection
*
* Unindents current paragraph by one level.
*/
void
e_html_editor_selection_unindent (EHTMLEditorSelection *selection)
{
EHTMLEditorView *view;
EHTMLEditorViewHistoryEvent *ev = NULL;
gboolean after_selection_start = FALSE, after_selection_end = FALSE;
WebKitDOMDocument *document;
WebKitDOMElement *selection_start_marker, *selection_end_marker;
WebKitDOMNode *block;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (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 the selection was not saved, move it into the first child of body */
if (!selection_start_marker || !selection_end_marker) {
WebKitDOMHTMLElement *body;
WebKitDOMNode *child;
body = webkit_dom_document_get_body (document);
child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
add_selection_markers_into_element_start (
document,
WEBKIT_DOM_ELEMENT (child),
&selection_start_marker,
&selection_end_marker);
}
if (!e_html_editor_view_is_undo_redo_in_progress (view)) {
ev = g_new0 (EHTMLEditorViewHistoryEvent, 1);
ev->type = HISTORY_INDENT;
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->before.start.x,
&ev->before.start.y,
&ev->before.end.x,
&ev->before.end.y);
}
block = get_parent_indented_block (
WEBKIT_DOM_NODE (selection_start_marker));
if (!block)
block = e_html_editor_get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
while (block && !after_selection_end) {
gint ii, length;
WebKitDOMNode *next_block;
WebKitDOMNodeList *list;
next_block = webkit_dom_node_get_next_sibling (block);
list = webkit_dom_element_query_selector_all (
WEBKIT_DOM_ELEMENT (block),
".-x-evo-indented > *:not(.-x-evo-indented):not(li)",
NULL);
after_selection_end = webkit_dom_node_contains (
block, WEBKIT_DOM_NODE (selection_end_marker));
length = webkit_dom_node_list_get_length (list);
if (length == 0 && node_is_list_or_item (block)) {
unindent_list (selection, document);
goto next;
}
if (length == 0) {
if (!after_selection_start) {
after_selection_start = webkit_dom_node_contains (
block, WEBKIT_DOM_NODE (selection_start_marker));
if (!after_selection_start)
goto next;
}
unindent_block (selection, document, block);
if (after_selection_end)
goto next;
}
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *block_to_process;
block_to_process = webkit_dom_node_list_item (list, ii);
after_selection_end = webkit_dom_node_contains (
block_to_process,
WEBKIT_DOM_NODE (selection_end_marker));
if (!after_selection_start) {
after_selection_start = webkit_dom_node_contains (
block_to_process,
WEBKIT_DOM_NODE (selection_start_marker));
if (!after_selection_start) {
g_object_unref (block_to_process);
continue;
}
}
unindent_block (selection, document, block_to_process);
if (after_selection_end) {
g_object_unref (block_to_process);
break;
}
g_object_unref (block_to_process);
}
next:
g_object_unref (list);
block = next_block;
}
if (ev) {
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->after.start.x,
&ev->after.start.y,
&ev->after.end.x,
&ev->after.end.y);
e_html_editor_view_insert_new_history_event (view, ev);
}
e_html_editor_selection_restore (selection);
e_html_editor_view_force_spell_check_for_current_paragraph (view);
g_object_unref (view);
g_object_notify (G_OBJECT (selection), "indented");
}
typedef gboolean (*IsRightFormatNodeFunc) (WebKitDOMElement *element);
static gboolean
is_bold_element (WebKitDOMElement *element)
{
if (!element || !WEBKIT_DOM_IS_ELEMENT (element))
return FALSE;
if (element_has_tag (element, "b"))
return TRUE;
/* Headings are bold by default */
return WEBKIT_DOM_IS_HTML_HEADING_ELEMENT (element);
}
static gboolean
html_editor_selection_is_font_format (EHTMLEditorSelection *selection,
IsRightFormatNodeFunc func,
gboolean *previous_value)
{
EHTMLEditorView *view;
gboolean ret_val = FALSE;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *dom_window = NULL;
WebKitDOMDOMSelection *dom_selection = NULL;
WebKitDOMNode *start, *end, *sibling;
WebKitDOMRange *range = NULL;
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_val_if_fail (view != NULL, FALSE);
if (!e_html_editor_view_get_html_mode (view))
goto out;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
dom_window = webkit_dom_document_get_default_view (document);
dom_selection = webkit_dom_dom_window_get_selection (dom_window);
if (!webkit_dom_dom_selection_get_range_count (dom_selection))
goto out;
range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
if (!range)
goto out;
if (webkit_dom_range_get_collapsed (range, NULL) && previous_value) {
WebKitDOMNode *node;
gchar* text_content;
node = webkit_dom_range_get_common_ancestor_container (range, NULL);
/* If we are changing the format of block we have to re-set the
* format property, otherwise it will be turned off because of no
* text in block. */
text_content = webkit_dom_node_get_text_content (node);
if (g_strcmp0 (text_content, "") == 0) {
g_free (text_content);
ret_val = *previous_value;
goto out;
}
g_free (text_content);
}
/* Range without start or end point is a wrong range. */
start = webkit_dom_range_get_start_container (range, NULL);
end = webkit_dom_range_get_end_container (range, NULL);
if (!start || !end)
goto out;
if (WEBKIT_DOM_IS_TEXT (start))
start = webkit_dom_node_get_parent_node (start);
while (start && WEBKIT_DOM_IS_ELEMENT (start) && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (start)) {
/* Find the start point's parent node with given formatting. */
if (func (WEBKIT_DOM_ELEMENT (start))) {
ret_val = TRUE;
break;
}
start = webkit_dom_node_get_parent_node (start);
}
/* Start point doesn't have the given formatting. */
if (!ret_val)
goto out;
/* If the selection is collapsed, we can return early. */
if (webkit_dom_range_get_collapsed (range, NULL))
goto out;
/* The selection is in the same node and that node is supposed to have
* the same formatting (otherwise it is split up with formatting element. */
if (webkit_dom_node_is_same_node (
webkit_dom_range_get_start_container (range, NULL),
webkit_dom_range_get_end_container (range, NULL)))
goto out;
ret_val = FALSE;
if (WEBKIT_DOM_IS_TEXT (end))
end = webkit_dom_node_get_parent_node (end);
while (end && WEBKIT_DOM_IS_ELEMENT (end) && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (end)) {
/* Find the end point's parent node with given formatting. */
if (func (WEBKIT_DOM_ELEMENT (end))) {
ret_val = TRUE;
break;
}
end = webkit_dom_node_get_parent_node (end);
}
if (!ret_val)
goto out;
ret_val = FALSE;
/* Now go between the end points and check the inner nodes for format validity. */
sibling = start;
while ((sibling = webkit_dom_node_get_next_sibling (sibling))) {
if (webkit_dom_node_is_same_node (sibling, end)) {
ret_val = TRUE;
goto out;
}
if (WEBKIT_DOM_IS_TEXT (sibling))
goto out;
else if (func (WEBKIT_DOM_ELEMENT (sibling)))
continue;
else if (webkit_dom_node_get_first_child (sibling)) {
WebKitDOMNode *first_child;
first_child = webkit_dom_node_get_first_child (sibling);
if (!webkit_dom_node_get_next_sibling (first_child))
if (WEBKIT_DOM_IS_ELEMENT (first_child) && func (WEBKIT_DOM_ELEMENT (first_child)))
continue;
else
goto out;
else
goto out;
} else
goto out;
}
sibling = end;
while ((sibling = webkit_dom_node_get_previous_sibling (sibling))) {
if (webkit_dom_node_is_same_node (sibling, start))
break;
if (WEBKIT_DOM_IS_TEXT (sibling))
goto out;
else if (func (WEBKIT_DOM_ELEMENT (sibling)))
continue;
else if (webkit_dom_node_get_first_child (sibling)) {
WebKitDOMNode *first_child;
first_child = webkit_dom_node_get_first_child (sibling);
if (!webkit_dom_node_get_next_sibling (first_child))
if (WEBKIT_DOM_IS_ELEMENT (first_child) && func (WEBKIT_DOM_ELEMENT (first_child)))
continue;
else
goto out;
else
goto out;
} else
goto out;
}
ret_val = TRUE;
out:
g_object_unref (view);
g_clear_object (&range);
g_clear_object (&dom_window);
g_clear_object (&dom_selection);
return ret_val;
}
/**
* e_html_editor_selection_is_bold:
* @selection: an #EHTMLEditorSelection
*
* Returns whether current selection or letter at current cursor position
* is bold.
*
* Returns @TRUE when selection is bold, @FALSE otherwise.
*/
gboolean
e_html_editor_selection_is_bold (EHTMLEditorSelection *selection)
{
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
selection->priv->is_bold = html_editor_selection_is_font_format (
selection, (IsRightFormatNodeFunc) is_bold_element, &selection->priv->is_bold);
return selection->priv->is_bold;
}
static void
html_editor_selection_set_font_style (EHTMLEditorSelection *selection,
EHTMLEditorViewCommand command,
gboolean value)
{
EHTMLEditorView *view;
EHTMLEditorViewHistoryEvent *ev = NULL;
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
e_html_editor_selection_save (selection);
if (!e_html_editor_view_is_undo_redo_in_progress (view)) {
ev = g_new0 (EHTMLEditorViewHistoryEvent, 1);
if (command == E_HTML_EDITOR_VIEW_COMMAND_BOLD)
ev->type = HISTORY_BOLD;
else if (command == E_HTML_EDITOR_VIEW_COMMAND_ITALIC)
ev->type = HISTORY_ITALIC;
else if (command == E_HTML_EDITOR_VIEW_COMMAND_UNDERLINE)
ev->type = HISTORY_UNDERLINE;
else if (command == E_HTML_EDITOR_VIEW_COMMAND_STRIKETHROUGH)
ev->type = HISTORY_STRIKETHROUGH;
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->before.start.x,
&ev->before.start.y,
&ev->before.end.x,
&ev->before.end.y);
ev->data.style.from = !value;
ev->data.style.to = value;
}
if (e_html_editor_selection_is_collapsed (selection)) {
WebKitDOMDocument *document;
const gchar *element_name = NULL;
if (command == E_HTML_EDITOR_VIEW_COMMAND_BOLD)
element_name = "b";
else if (command == E_HTML_EDITOR_VIEW_COMMAND_ITALIC)
element_name = "i";
else if (command == E_HTML_EDITOR_VIEW_COMMAND_UNDERLINE)
element_name = "u";
else if (command == E_HTML_EDITOR_VIEW_COMMAND_STRIKETHROUGH)
element_name = "strike";
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
if (element_name)
set_font_style (document, element_name, value);
e_html_editor_selection_restore (selection);
goto exit;
}
e_html_editor_selection_restore (selection);
e_html_editor_view_exec_command (view, command, NULL);
exit:
if (ev) {
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->after.start.x,
&ev->after.start.y,
&ev->after.end.x,
&ev->after.end.y);
e_html_editor_view_insert_new_history_event (view, ev);
}
e_html_editor_view_force_spell_check_for_current_paragraph (view);
g_object_unref (view);
}
/**
* e_html_editor_selection_set_bold:
* @selection: an #EHTMLEditorSelection
* @bold: @TRUE to enable bold, @FALSE to disable
*
* Toggles bold formatting of current selection or letter at current cursor
* position, depending on whether @bold is @TRUE or @FALSE.
*/
void
e_html_editor_selection_set_bold (EHTMLEditorSelection *selection,
gboolean bold)
{
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
if (e_html_editor_selection_is_bold (selection) == bold)
return;
selection->priv->is_bold = bold;
html_editor_selection_set_font_style (
selection, E_HTML_EDITOR_VIEW_COMMAND_BOLD, bold);
g_object_notify (G_OBJECT (selection), "bold");
}
static gboolean
is_italic_element (WebKitDOMElement *element)
{
if (!element || !WEBKIT_DOM_IS_ELEMENT (element))
return FALSE;
return element_has_tag (element, "i") || element_has_tag (element, "address");
}
/**
* e_html_editor_selection_is_italic:
* @selection: an #EHTMLEditorSelection
*
* Returns whether current selection or letter at current cursor position
* is italic.
*
* Returns @TRUE when selection is italic, @FALSE otherwise.
*/
gboolean
e_html_editor_selection_is_italic (EHTMLEditorSelection *selection)
{
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
selection->priv->is_italic = html_editor_selection_is_font_format (
selection, (IsRightFormatNodeFunc) is_italic_element, &selection->priv->is_italic);
return selection->priv->is_italic;
}
/**
* e_html_editor_selection_set_italic:
* @selection: an #EHTMLEditorSelection
* @italic: @TRUE to enable italic, @FALSE to disable
*
* Toggles italic formatting of current selection or letter at current cursor
* position, depending on whether @italic is @TRUE or @FALSE.
*/
void
e_html_editor_selection_set_italic (EHTMLEditorSelection *selection,
gboolean italic)
{
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
if (e_html_editor_selection_is_italic (selection) == italic)
return;
selection->priv->is_italic = italic;
html_editor_selection_set_font_style (
selection, E_HTML_EDITOR_VIEW_COMMAND_ITALIC, italic);
g_object_notify (G_OBJECT (selection), "italic");
}
static gboolean
is_monospaced_element (WebKitDOMElement *element)
{
gchar *value;
gboolean ret_val = FALSE;
if (!element)
return FALSE;
if (!WEBKIT_DOM_IS_HTML_FONT_ELEMENT (element))
return FALSE;
value = webkit_dom_element_get_attribute (element, "face");
if (g_strcmp0 (value, "monospace") == 0)
ret_val = TRUE;
g_free (value);
return ret_val;
}
/**
* e_html_editor_selection_is_monospaced:
* @selection: an #EHTMLEditorSelection
*
* Returns whether current selection or letter at current cursor position
* is monospaced.
*
* Returns @TRUE when selection is monospaced, @FALSE otherwise.
*/
gboolean
e_html_editor_selection_is_monospaced (EHTMLEditorSelection *selection)
{
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
selection->priv->is_monospaced = html_editor_selection_is_font_format (
selection, (IsRightFormatNodeFunc) is_monospaced_element, &selection->priv->is_monospaced);
return selection->priv->is_monospaced;
}
static void
monospace_selection (EHTMLEditorSelection *selection,
WebKitDOMDocument *document,
WebKitDOMElement *monospaced_element)
{
gboolean selection_end = FALSE;
gboolean first = TRUE;
gint length, ii;
WebKitDOMElement *selection_start_marker, *selection_end_marker;
WebKitDOMNode *sibling, *node, *monospace, *block;
WebKitDOMNodeList *list;
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");
block = WEBKIT_DOM_NODE (get_parent_block_element (WEBKIT_DOM_NODE (selection_start_marker)));
monospace = WEBKIT_DOM_NODE (monospaced_element);
node = WEBKIT_DOM_NODE (selection_start_marker);
/* Go through first block in selection. */
while (block && node && !webkit_dom_node_is_same_node (block, node)) {
if (webkit_dom_node_get_next_sibling (node)) {
/* Prepare the monospaced element. */
monospace = webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (node),
first ? monospace : webkit_dom_node_clone_node (monospace, FALSE),
first ? node : webkit_dom_node_get_next_sibling (node),
NULL);
} else
break;
/* Move the nodes into monospaced element. */
while (((sibling = webkit_dom_node_get_next_sibling (monospace)))) {
webkit_dom_node_append_child (monospace, sibling, NULL);
if (webkit_dom_node_is_same_node (WEBKIT_DOM_NODE (selection_end_marker), sibling)) {
selection_end = TRUE;
break;
}
}
node = webkit_dom_node_get_parent_node (monospace);
first = FALSE;
}
/* Just one block was selected. */
if (selection_end)
goto out;
/* Middle blocks (blocks not containing the end of the selection. */
block = webkit_dom_node_get_next_sibling (block);
while (block && !selection_end) {
WebKitDOMNode *next_block;
selection_end = webkit_dom_node_contains (
block, WEBKIT_DOM_NODE (selection_end_marker));
if (selection_end)
break;
next_block = webkit_dom_node_get_next_sibling (block);
monospace = webkit_dom_node_insert_before (
block,
webkit_dom_node_clone_node (monospace, FALSE),
webkit_dom_node_get_first_child (block),
NULL);
while (((sibling = webkit_dom_node_get_next_sibling (monospace))))
webkit_dom_node_append_child (monospace, sibling, NULL);
block = next_block;
}
/* Block containing the end of selection. */
node = WEBKIT_DOM_NODE (selection_end_marker);
while (block && node && !webkit_dom_node_is_same_node (block, node)) {
monospace = webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (node),
webkit_dom_node_clone_node (monospace, FALSE),
webkit_dom_node_get_next_sibling (node),
NULL);
while (((sibling = webkit_dom_node_get_previous_sibling (monospace)))) {
webkit_dom_node_insert_before (
monospace,
sibling,
webkit_dom_node_get_first_child (monospace),
NULL);
}
node = webkit_dom_node_get_parent_node (monospace);
}
out:
/* Merge all the monospace elements inside other monospace elements. */
list = webkit_dom_document_query_selector_all (
document, "font[face=monospace] > font[face=monospace]", NULL);
length = webkit_dom_node_list_get_length (list);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *item;
WebKitDOMNode *child;
item = webkit_dom_node_list_item (list, ii);
while ((child = webkit_dom_node_get_first_child (item))) {
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (item),
child,
item,
NULL);
}
remove_node (item);
g_object_unref (item);
}
g_object_unref (list);
/* Merge all the adjacent monospace elements. */
list = webkit_dom_document_query_selector_all (
document, "font[face=monospace] + font[face=monospace]", NULL);
length = webkit_dom_node_list_get_length (list);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *item;
WebKitDOMNode *child;
item = webkit_dom_node_list_item (list, ii);
/* The + CSS selector will return some false positives as it doesn't
* take text between elements into account so it will return this:
* <font face="monospace">xx</font>yy<font face="monospace">zz</font>
* as valid, but it isn't so we have to check if previous node
* is indeed element or not. */
if (WEBKIT_DOM_IS_ELEMENT (webkit_dom_node_get_previous_sibling (item))) {
while ((child = webkit_dom_node_get_first_child (item))) {
webkit_dom_node_append_child (
webkit_dom_node_get_previous_sibling (item), child, NULL);
}
remove_node (item);
}
g_object_unref (item);
}
g_object_unref (list);
e_html_editor_selection_restore (selection);
}
static void
unmonospace_selection (EHTMLEditorSelection *selection,
WebKitDOMDocument *document)
{
WebKitDOMElement *selection_start_marker;
WebKitDOMElement *selection_end_marker;
WebKitDOMElement *selection_start_clone;
WebKitDOMElement *selection_end_clone;
WebKitDOMNode *sibling, *node;
gboolean selection_end = FALSE;
WebKitDOMNode *block, *clone, *monospace;
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");
block = WEBKIT_DOM_NODE (get_parent_block_element (WEBKIT_DOM_NODE (selection_start_marker)));
node = WEBKIT_DOM_NODE (selection_start_marker);
monospace = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start_marker));
while (monospace && !is_monospaced_element (WEBKIT_DOM_ELEMENT (monospace)))
monospace = webkit_dom_node_get_parent_node (monospace);
/* No monospaced element was found as a parent of selection start node. */
if (!monospace)
goto out;
/* Make a clone of current monospaced element. */
clone = webkit_dom_node_clone_node (monospace, TRUE);
/* First block */
/* Remove all the nodes that are after the selection start point as they
* will be in the cloned node. */
while (monospace && node && !webkit_dom_node_is_same_node (monospace, node)) {
WebKitDOMNode *tmp;
while (((sibling = webkit_dom_node_get_next_sibling (node))))
remove_node (sibling);
tmp = webkit_dom_node_get_parent_node (node);
if (webkit_dom_node_get_next_sibling (node))
remove_node (node);
node = tmp;
}
selection_start_clone = webkit_dom_element_query_selector (
WEBKIT_DOM_ELEMENT (clone), "#-x-evo-selection-start-marker", NULL);
selection_end_clone = webkit_dom_element_query_selector (
WEBKIT_DOM_ELEMENT (clone), "#-x-evo-selection-end-marker", NULL);
/* No selection start node in the block where it is supposed to be, return. */
if (!selection_start_clone)
goto out;
/* Remove all the nodes until we hit the selection start point as these
* nodes will stay monospaced and they are already in original element. */
node = webkit_dom_node_get_first_child (clone);
while (node) {
WebKitDOMNode *next_sibling;
next_sibling = webkit_dom_node_get_next_sibling (node);
if (webkit_dom_node_get_first_child (node)) {
if (webkit_dom_node_contains (node, WEBKIT_DOM_NODE (selection_start_clone))) {
node = webkit_dom_node_get_first_child (node);
continue;
} else
remove_node (node);
} else if (webkit_dom_node_is_same_node (node, WEBKIT_DOM_NODE (selection_start_clone)))
break;
else
remove_node (node);
node = next_sibling;
}
/* Insert the clone into the tree. Do it after the previous clean up. If
* we would do it the other way the line would contain duplicated text nodes
* and the block would be expading and shrinking while we would modify it. */
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (monospace),
clone,
webkit_dom_node_get_next_sibling (monospace),
NULL);
/* Move selection start point the right place. */
remove_node (WEBKIT_DOM_NODE (selection_start_marker));
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (clone),
WEBKIT_DOM_NODE (selection_start_clone),
clone,
NULL);
/* Move all the nodes the are supposed to lose the monospace formatting
* out of monospaced element. */
node = webkit_dom_node_get_first_child (clone);
while (node) {
WebKitDOMNode *next_sibling;
next_sibling = webkit_dom_node_get_next_sibling (node);
if (webkit_dom_node_get_first_child (node)) {
if (selection_end_clone &&
webkit_dom_node_contains (node, WEBKIT_DOM_NODE (selection_end_clone))) {
node = webkit_dom_node_get_first_child (node);
continue;
} else
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (clone),
node,
clone,
NULL);
} else if (selection_end_clone &&
webkit_dom_node_is_same_node (node, WEBKIT_DOM_NODE (selection_end_clone))) {
selection_end = TRUE;
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (clone),
node,
clone,
NULL);
break;
} else
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (clone),
node,
clone,
NULL);
node = next_sibling;
}
if (!webkit_dom_node_get_first_child (clone))
remove_node (clone);
/* Just one block was selected and we hit the selection end point. */
if (selection_end)
goto out;
/* Middle blocks */
block = webkit_dom_node_get_next_sibling (block);
while (block && !selection_end) {
WebKitDOMNode *next_block, *child, *parent;
WebKitDOMElement *monospaced_element;
selection_end = webkit_dom_node_contains (
block, WEBKIT_DOM_NODE (selection_end_marker));
if (selection_end)
break;
next_block = webkit_dom_node_get_next_sibling (block);
/* Find the monospaced element and move all the nodes from it and
* finally remove it. */
monospaced_element = webkit_dom_element_query_selector (
WEBKIT_DOM_ELEMENT (block), "font[face=monospace]", NULL);
if (!monospaced_element)
break;
parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (monospaced_element));
while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (monospaced_element)))) {
webkit_dom_node_insert_before (
parent, child, WEBKIT_DOM_NODE (monospaced_element), NULL);
}
remove_node (WEBKIT_DOM_NODE (monospaced_element));
block = next_block;
}
/* End block */
node = WEBKIT_DOM_NODE (selection_end_marker);
monospace = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_end_marker));
while (monospace && !is_monospaced_element (WEBKIT_DOM_ELEMENT (monospace)))
monospace = webkit_dom_node_get_parent_node (monospace);
/* No monospaced element was found as a parent of selection end node. */
if (!monospace)
return;
clone = WEBKIT_DOM_NODE (monospace);
node = webkit_dom_node_get_first_child (clone);
/* Move all the nodes that are supposed to lose the monospaced formatting
* out of the monospaced element. */
while (node) {
WebKitDOMNode *next_sibling;
next_sibling = webkit_dom_node_get_next_sibling (node);
if (webkit_dom_node_get_first_child (node)) {
if (webkit_dom_node_contains (node, WEBKIT_DOM_NODE (selection_end_marker))) {
node = webkit_dom_node_get_first_child (node);
continue;
} else
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (clone),
node,
clone,
NULL);
} else if (webkit_dom_node_is_same_node (node, WEBKIT_DOM_NODE (selection_end_marker))) {
selection_end = TRUE;
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (clone),
node,
clone,
NULL);
break;
} else {
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (clone),
node,
clone,
NULL);
}
node = next_sibling;
}
if (!webkit_dom_node_get_first_child (clone))
remove_node (clone);
out:
e_html_editor_selection_restore (selection);
}
/**
* e_html_editor_selection_set_monospaced:
* @selection: an #EHTMLEditorSelection
* @monospaced: @TRUE to enable monospaced, @FALSE to disable
*
* Toggles monospaced formatting of current selection or letter at current cursor
* position, depending on whether @monospaced is @TRUE or @FALSE.
*/
void
e_html_editor_selection_set_monospaced (EHTMLEditorSelection *selection,
gboolean monospaced)
{
EHTMLEditorView *view;
EHTMLEditorViewHistoryEvent *ev = NULL;
WebKitDOMDocument *document;
WebKitDOMRange *range;
WebKitDOMDOMWindow *dom_window;
WebKitDOMDOMSelection *dom_selection;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
if (e_html_editor_selection_is_monospaced (selection) == monospaced)
return;
selection->priv->is_monospaced = monospaced;
range = html_editor_selection_get_current_range (selection);
if (!range)
return;
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
if (!e_html_editor_view_is_undo_redo_in_progress (view)) {
ev = g_new0 (EHTMLEditorViewHistoryEvent, 1);
ev->type = HISTORY_MONOSPACE;
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->before.start.x,
&ev->before.start.y,
&ev->before.end.x,
&ev->before.end.y);
ev->data.style.from = !monospaced;
ev->data.style.to = monospaced;
}
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
dom_window = webkit_dom_document_get_default_view (document);
dom_selection = webkit_dom_dom_window_get_selection (dom_window);
if (monospaced) {
guint font_size;
WebKitDOMElement *monospace;
monospace = webkit_dom_document_create_element (
document, "font", NULL);
webkit_dom_element_set_attribute (
monospace, "face", "monospace", NULL);
font_size = selection->priv->font_size;
if (font_size != 0) {
gchar *font_size_str;
font_size_str = g_strdup_printf ("%d", font_size);
webkit_dom_element_set_attribute (
monospace, "size", font_size_str, NULL);
g_free (font_size_str);
}
if (!webkit_dom_range_get_collapsed (range, NULL))
monospace_selection (selection, document, monospace);
else {
/* https://bugs.webkit.org/show_bug.cgi?id=15256 */
webkit_dom_html_element_set_inner_html (
WEBKIT_DOM_HTML_ELEMENT (monospace),
UNICODE_ZERO_WIDTH_SPACE,
NULL);
webkit_dom_range_insert_node (
range, WEBKIT_DOM_NODE (monospace), NULL);
e_html_editor_selection_move_caret_into_element (
document, monospace, FALSE);
}
} else {
gboolean is_bold, is_italic, is_underline, is_strikethrough;
guint font_size;
WebKitDOMElement *tt_element;
WebKitDOMNode *node;
node = webkit_dom_range_get_end_container (range, NULL);
if (WEBKIT_DOM_IS_ELEMENT (node) &&
is_monospaced_element (WEBKIT_DOM_ELEMENT (node))) {
tt_element = WEBKIT_DOM_ELEMENT (node);
} else {
tt_element = e_html_editor_dom_node_find_parent_element (node, "FONT");
if (!is_monospaced_element (tt_element)) {
g_object_unref (view);
g_object_unref (range);
g_object_unref (dom_selection);
g_object_unref (dom_window);
g_free (ev);
return;
}
}
/* Save current formatting */
is_bold = selection->priv->is_bold;
is_italic = selection->priv->is_italic;
is_underline = selection->priv->is_underline;
is_strikethrough = selection->priv->is_strikethrough;
font_size = selection->priv->font_size;
if (!webkit_dom_range_get_collapsed (range, NULL))
unmonospace_selection (selection, document);
else {
e_html_editor_selection_save (selection);
set_font_style (document, "", FALSE);
e_html_editor_selection_restore (selection);
}
/* Re-set formatting */
if (is_bold)
e_html_editor_selection_set_bold (selection, TRUE);
if (is_italic)
e_html_editor_selection_set_italic (selection, TRUE);
if (is_underline)
e_html_editor_selection_set_underline (selection, TRUE);
if (is_strikethrough)
e_html_editor_selection_set_strikethrough (selection, TRUE);
if (font_size)
e_html_editor_selection_set_font_size (selection, font_size);
}
if (ev) {
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->after.start.x,
&ev->after.start.y,
&ev->after.end.x,
&ev->after.end.y);
e_html_editor_view_insert_new_history_event (view, ev);
}
e_html_editor_view_force_spell_check_for_current_paragraph (view);
g_object_unref (range);
g_object_unref (dom_selection);
g_object_unref (dom_window);
g_object_unref (view);
g_object_notify (G_OBJECT (selection), "monospaced");
}
static gboolean
is_strikethrough_element (WebKitDOMElement *element)
{
if (!element || !WEBKIT_DOM_IS_ELEMENT (element))
return FALSE;
return element_has_tag (element, "strike");
}
/**
* e_html_editor_selection_is_strikethrough:
* @selection: an #EHTMLEditorSelection
*
* Returns whether current selection or letter at current cursor position
* is striked through.
*
* Returns @TRUE when selection is striked through, @FALSE otherwise.
*/
gboolean
e_html_editor_selection_is_strikethrough (EHTMLEditorSelection *selection)
{
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
selection->priv->is_strikethrough = html_editor_selection_is_font_format (
selection, (IsRightFormatNodeFunc) is_strikethrough_element, &selection->priv->is_strikethrough);
return selection->priv->is_strikethrough;
}
/**
* e_html_editor_selection_set_strikethrough:
* @selection: an #EHTMLEditorSelection
* @strikethrough: @TRUE to enable strikethrough, @FALSE to disable
*
* Toggles strike through formatting of current selection or letter at current
* cursor position, depending on whether @strikethrough is @TRUE or @FALSE.
*/
void
e_html_editor_selection_set_strikethrough (EHTMLEditorSelection *selection,
gboolean strikethrough)
{
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
if (e_html_editor_selection_is_strikethrough (selection) == strikethrough)
return;
selection->priv->is_strikethrough = strikethrough;
html_editor_selection_set_font_style (
selection, E_HTML_EDITOR_VIEW_COMMAND_STRIKETHROUGH, strikethrough);
g_object_notify (G_OBJECT (selection), "strikethrough");
}
static gboolean
is_subscript_element (WebKitDOMElement *element)
{
if (!element || !WEBKIT_DOM_IS_ELEMENT (element))
return FALSE;
return element_has_tag (element, "sub");
}
/**
* e_html_editor_selection_is_subscript:
* @selection: an #EHTMLEditorSelection
*
* Returns whether current selection or letter at current cursor position
* is in subscript.
*
* Returns @TRUE when selection is in subscript, @FALSE otherwise.
*/
gboolean
e_html_editor_selection_is_subscript (EHTMLEditorSelection *selection)
{
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
return html_editor_selection_is_font_format (
selection, (IsRightFormatNodeFunc) is_subscript_element, NULL);
}
/**
* e_html_editor_selection_set_subscript:
* @selection: an #EHTMLEditorSelection
* @subscript: @TRUE to enable subscript, @FALSE to disable
*
* Toggles subscript of current selection or letter at current cursor position,
* depending on whether @subscript is @TRUE or @FALSE.
*/
void
e_html_editor_selection_set_subscript (EHTMLEditorSelection *selection,
gboolean subscript)
{
EHTMLEditorView *view;
EHTMLEditorViewCommand command;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
if (e_html_editor_selection_is_subscript (selection) == subscript)
return;
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
command = E_HTML_EDITOR_VIEW_COMMAND_SUBSCRIPT;
e_html_editor_view_exec_command (view, command, NULL);
g_object_unref (view);
g_object_notify (G_OBJECT (selection), "subscript");
}
static gboolean
is_superscript_element (WebKitDOMElement *element)
{
if (!element || !WEBKIT_DOM_IS_ELEMENT (element))
return FALSE;
return element_has_tag (element, "sup");
}
/**
* e_html_editor_selection_is_superscript:
* @selection: an #EHTMLEditorSelection
*
* Returns whether current selection or letter at current cursor position
* is in superscript.
*
* Returns @TRUE when selection is in superscript, @FALSE otherwise.
*/
gboolean
e_html_editor_selection_is_superscript (EHTMLEditorSelection *selection)
{
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
return html_editor_selection_is_font_format (
selection, (IsRightFormatNodeFunc) is_superscript_element, NULL);
}
/**
* e_html_editor_selection_set_superscript:
* @selection: an #EHTMLEditorSelection
* @superscript: @TRUE to enable superscript, @FALSE to disable
*
* Toggles superscript of current selection or letter at current cursor position,
* depending on whether @superscript is @TRUE or @FALSE.
*/
void
e_html_editor_selection_set_superscript (EHTMLEditorSelection *selection,
gboolean superscript)
{
EHTMLEditorView *view;
EHTMLEditorViewCommand command;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
if (e_html_editor_selection_is_superscript (selection) == superscript)
return;
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
command = E_HTML_EDITOR_VIEW_COMMAND_SUPERSCRIPT;
e_html_editor_view_exec_command (view, command, NULL);
g_object_unref (view);
g_object_notify (G_OBJECT (selection), "superscript");
}
static gboolean
is_underline_element (WebKitDOMElement *element)
{
if (!element || !WEBKIT_DOM_IS_ELEMENT (element))
return FALSE;
return element_has_tag (element, "u");
}
/**
* e_html_editor_selection_is_underline:
* @selection: an #EHTMLEditorSelection
*
* Returns whether current selection or letter at current cursor position
* is underlined.
*
* Returns @TRUE when selection is underlined, @FALSE otherwise.
*/
gboolean
e_html_editor_selection_is_underline (EHTMLEditorSelection *selection)
{
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
selection->priv->is_underline = html_editor_selection_is_font_format (
selection, (IsRightFormatNodeFunc) is_underline_element, &selection->priv->is_underline);
return selection->priv->is_underline;
}
/**
* e_html_editor_selection_set_underline:
* @selection: an #EHTMLEditorSelection
* @underline: @TRUE to enable underline, @FALSE to disable
*
* Toggles underline formatting of current selection or letter at current
* cursor position, depending on whether @underline is @TRUE or @FALSE.
*/
void
e_html_editor_selection_set_underline (EHTMLEditorSelection *selection,
gboolean underline)
{
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
if (e_html_editor_selection_is_underline (selection) == underline)
return;
selection->priv->is_underline = underline;
html_editor_selection_set_font_style (
selection, E_HTML_EDITOR_VIEW_COMMAND_UNDERLINE, underline);
g_object_notify (G_OBJECT (selection), "underline");
}
/**
* e_html_editor_selection_unlink:
* @selection: an #EHTMLEditorSelection
*
* Removes any links (&lt;A&gt; elements) from current selection or at current
* cursor position.
*/
void
e_html_editor_selection_unlink (EHTMLEditorSelection *selection)
{
EHTMLEditorView *view;
gchar *text;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *dom_window;
WebKitDOMDOMSelection *dom_selection;
WebKitDOMRange *range;
WebKitDOMElement *link;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
g_object_unref (view);
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);
link = e_html_editor_dom_node_find_parent_element (
webkit_dom_range_get_start_container (range, NULL), "A");
g_object_unref (dom_selection);
g_object_unref (dom_window);
if (!link) {
WebKitDOMNode *node;
/* get element that was clicked on */
node = webkit_dom_range_get_common_ancestor_container (range, NULL);
if (node && !WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node)) {
link = e_html_editor_dom_node_find_parent_element (node, "A");
if (link && !WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (link)) {
g_object_unref (range);
return;
}
} else
link = WEBKIT_DOM_ELEMENT (node);
}
g_object_unref (range);
if (!link)
return;
if (!e_html_editor_view_is_undo_redo_in_progress (view)) {
EHTMLEditorViewHistoryEvent *ev;
WebKitDOMDocumentFragment *fragment;
ev = g_new0 (EHTMLEditorViewHistoryEvent, 1);
ev->type = HISTORY_REMOVE_LINK;
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->before.start.x,
&ev->before.start.y,
&ev->before.end.x,
&ev->before.end.y);
fragment = webkit_dom_document_create_document_fragment (document);
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (fragment),
webkit_dom_node_clone_node (WEBKIT_DOM_NODE (link), TRUE),
NULL);
ev->data.fragment = fragment;
e_html_editor_view_insert_new_history_event (view, ev);
}
text = webkit_dom_html_element_get_inner_text (
WEBKIT_DOM_HTML_ELEMENT (link));
webkit_dom_html_element_set_outer_html (
WEBKIT_DOM_HTML_ELEMENT (link), text, NULL);
g_free (text);
}
/**
* e_html_editor_selection_create_link:
* @selection: an #EHTMLEditorSelection
* @uri: destination of the new link
*
* Converts current selection into a link pointing to @url.
*/
void
e_html_editor_selection_create_link (EHTMLEditorSelection *selection,
const gchar *uri)
{
EHTMLEditorView *view;
EHTMLEditorViewCommand command;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
g_return_if_fail (uri != NULL && *uri != '\0');
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
command = E_HTML_EDITOR_VIEW_COMMAND_CREATE_LINK;
e_html_editor_view_exec_command (view, command, uri);
g_object_unref (view);
}
/**
* e_html_editor_selection_insert_text:
* @selection: an #EHTMLEditorSelection
* @plain_text: text to insert
*
* Inserts @plain_text at current cursor position. When a text range is selected,
* it will be replaced by @plain_text.
*/
void
e_html_editor_selection_insert_text (EHTMLEditorSelection *selection,
const gchar *plain_text)
{
EHTMLEditorView *view;
EHTMLEditorViewHistoryEvent *ev = NULL;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
g_return_if_fail (plain_text != NULL);
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
if (!e_html_editor_view_is_undo_redo_in_progress (view)) {
gboolean collapsed;
ev = g_new0 (EHTMLEditorViewHistoryEvent, 1);
ev->type = HISTORY_PASTE;
collapsed = e_html_editor_selection_is_collapsed (selection);
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->before.start.x,
&ev->before.start.y,
&ev->before.end.x,
&ev->before.end.y);
if (!collapsed) {
ev->before.end.x = ev->before.start.x;
ev->before.end.y = ev->before.start.y;
}
ev->data.string.from = NULL;
ev->data.string.to = g_strdup (plain_text);
}
e_html_editor_view_convert_and_insert_plain_text (view, plain_text);
if (ev) {
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->after.start.x,
&ev->after.start.y,
&ev->after.end.x,
&ev->after.end.y);
e_html_editor_view_insert_new_history_event (view, ev);
}
g_object_unref (view);
}
static gboolean
pasting_quoted_content (const gchar *content)
{
/* Check if the content we are pasting is a quoted content from composer.
* If it is, we can't use WebKit to paste it as it would leave the formatting
* on the content. */
return g_str_has_prefix (
content,
"<meta http-equiv=\"content-type\" content=\"text/html; "
"charset=utf-8\"><blockquote type=\"cite\"") &&
strstr (content, "\"-x-evo-");
}
/**
* e_html_editor_selection_insert_html:
* @selection: an #EHTMLEditorSelection
* @html_text: an HTML code to insert
*
* Insert @html_text into document at current cursor position. When a text range
* is selected, it will be replaced by @html_text.
*/
void
e_html_editor_selection_insert_html (EHTMLEditorSelection *selection,
const gchar *html_text)
{
EHTMLEditorView *view;
EHTMLEditorViewCommand command;
EHTMLEditorViewHistoryEvent *ev = NULL;
gboolean html_mode;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
g_return_if_fail (html_text != NULL);
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
if (!e_html_editor_view_is_undo_redo_in_progress (view)) {
gboolean collapsed;
ev = g_new0 (EHTMLEditorViewHistoryEvent, 1);
ev->type = HISTORY_INSERT_HTML;
collapsed = e_html_editor_selection_is_collapsed (selection);
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->before.start.x,
&ev->before.start.y,
&ev->before.end.x,
&ev->before.end.y);
if (!collapsed) {
ev->before.end.x = ev->before.start.x;
ev->before.end.y = ev->before.start.y;
}
ev->data.string.from = NULL;
ev->data.string.to = g_strdup (html_text);
}
html_mode = e_html_editor_view_get_html_mode (view);
command = E_HTML_EDITOR_VIEW_COMMAND_INSERT_HTML;
if (html_mode ||
(e_html_editor_view_is_pasting_content_from_itself (view) &&
!pasting_quoted_content (html_text))) {
if (!e_html_editor_selection_is_collapsed (selection)) {
EHTMLEditorViewHistoryEvent *event;
WebKitDOMDocumentFragment *fragment;
WebKitDOMRange *range;
event = g_new0 (EHTMLEditorViewHistoryEvent, 1);
event->type = HISTORY_DELETE;
range = html_editor_selection_get_current_range (selection);
fragment = webkit_dom_range_clone_contents (range, NULL);
g_object_unref (range);
event->data.fragment = fragment;
e_html_editor_selection_get_selection_coordinates (
selection,
&event->before.start.x,
&event->before.start.y,
&event->before.end.x,
&event->before.end.y);
event->after.start.x = event->before.start.x;
event->after.start.y = event->before.start.y;
event->after.end.x = event->before.start.x;
event->after.end.y = event->before.start.y;
e_html_editor_view_insert_new_history_event (view, event);
event = g_new0 (EHTMLEditorViewHistoryEvent, 1);
event->type = HISTORY_AND;
e_html_editor_view_insert_new_history_event (view, event);
}
e_html_editor_view_exec_command (view, command, html_text);
e_html_editor_view_fix_file_uri_images (view);
if (strstr (html_text, "id=\"-x-evo-selection-start-marker\""))
e_html_editor_selection_restore (selection);
if (!html_mode) {
WebKitDOMDocument *document;
WebKitDOMNodeList *list;
gint ii, length;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
list = webkit_dom_document_query_selector_all (
document, "span[style^=font-family]", NULL);
length = webkit_dom_node_list_get_length (list);
if (length > 0)
e_html_editor_selection_save (selection);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *span, *child;
span = webkit_dom_node_list_item (list, ii);
while ((child = webkit_dom_node_get_first_child (span)))
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (span),
child,
span,
NULL);
remove_node (span);
g_object_unref (span);
}
g_object_unref (list);
if (length > 0)
e_html_editor_selection_restore (selection);
}
e_html_editor_view_check_magic_links (view, FALSE);
e_html_editor_view_force_spell_check_in_viewport (view);
e_html_editor_selection_scroll_to_caret (selection);
} else
e_html_editor_view_convert_and_insert_html_to_plain_text (
view, html_text);
if (ev) {
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->after.start.x,
&ev->after.start.y,
&ev->after.end.x,
&ev->after.end.y);
e_html_editor_view_insert_new_history_event (view, ev);
}
g_object_unref (view);
}
void
e_html_editor_selection_insert_as_text (EHTMLEditorSelection *selection,
const gchar *html_text)
{
EHTMLEditorView *view;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
g_return_if_fail (html_text != NULL);
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
e_html_editor_view_convert_and_insert_html_to_plain_text (view, html_text);
g_object_unref (view);
}
/************************* image_load_and_insert_async() *************************/
typedef struct _LoadContext LoadContext;
struct _LoadContext {
EHTMLEditorSelection *selection;
WebKitDOMElement *element;
GInputStream *input_stream;
GOutputStream *output_stream;
GFile *file;
GFileInfo *file_info;
goffset total_num_bytes;
gssize bytes_read;
const gchar *content_type;
const gchar *filename;
gchar buffer[4096];
};
/* Forward Declaration */
static void
image_load_stream_read_cb (GInputStream *input_stream,
GAsyncResult *result,
LoadContext *load_context);
static LoadContext *
image_load_context_new (EHTMLEditorSelection *selection)
{
LoadContext *load_context;
load_context = g_slice_new0 (LoadContext);
load_context->selection = selection;
return load_context;
}
static void
image_load_context_free (LoadContext *load_context)
{
if (load_context->input_stream != NULL)
g_object_unref (load_context->input_stream);
if (load_context->output_stream != NULL)
g_object_unref (load_context->output_stream);
if (load_context->file_info != NULL)
g_object_unref (load_context->file_info);
if (load_context->file != NULL)
g_object_unref (load_context->file);
g_slice_free (LoadContext, load_context);
}
static void
replace_base64_image_src (EHTMLEditorSelection *selection,
WebKitDOMElement *element,
const gchar *base64_content,
const gchar *filename,
const gchar *uri)
{
EHTMLEditorView *view;
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
e_html_editor_view_set_changed (view, TRUE);
g_object_unref (view);
if (WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (element))
webkit_dom_html_image_element_set_src (
WEBKIT_DOM_HTML_IMAGE_ELEMENT (element),
base64_content);
else
webkit_dom_element_set_attribute (
WEBKIT_DOM_ELEMENT (element),
"background",
base64_content,
NULL);
webkit_dom_element_set_attribute (
WEBKIT_DOM_ELEMENT (element), "data-uri", uri, NULL);
webkit_dom_element_set_attribute (
WEBKIT_DOM_ELEMENT (element), "data-inline", "", NULL);
webkit_dom_element_set_attribute (
WEBKIT_DOM_ELEMENT (element), "data-name",
filename ? filename : "", NULL);
}
static void
insert_base64_image (EHTMLEditorSelection *selection,
const gchar *base64_content,
const gchar *filename,
const gchar *uri)
{
EHTMLEditorView *view;
EHTMLEditorViewHistoryEvent *ev = NULL;
WebKitDOMDocument *document;
WebKitDOMElement *element, *selection_start_marker, *resizable_wrapper;
WebKitDOMText *text;
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
e_html_editor_view_set_changed (view, TRUE);
if (!e_html_editor_selection_is_collapsed (selection)) {
EHTMLEditorViewHistoryEvent *ev;
WebKitDOMDocumentFragment *fragment;
WebKitDOMRange *range;
ev = g_new0 (EHTMLEditorViewHistoryEvent, 1);
ev->type = HISTORY_DELETE;
range = html_editor_selection_get_current_range (selection);
fragment = webkit_dom_range_clone_contents (range, NULL);
g_object_unref (range);
ev->data.fragment = fragment;
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->before.start.x,
&ev->before.start.y,
&ev->before.end.x,
&ev->before.end.y);
ev->after.start.x = ev->before.start.x;
ev->after.start.y = ev->before.start.y;
ev->after.end.x = ev->before.start.x;
ev->after.end.y = ev->before.start.y;
e_html_editor_view_insert_new_history_event (view, ev);
ev = g_new0 (EHTMLEditorViewHistoryEvent, 1);
ev->type = HISTORY_AND;
e_html_editor_view_insert_new_history_event (view, ev);
e_html_editor_view_exec_command (
view, E_HTML_EDITOR_VIEW_COMMAND_DELETE, NULL);
}
e_html_editor_selection_save (selection);
selection_start_marker = webkit_dom_document_query_selector (
document, "span#-x-evo-selection-start-marker", NULL);
if (!e_html_editor_view_is_undo_redo_in_progress (view)) {
ev = g_new0 (EHTMLEditorViewHistoryEvent, 1);
ev->type = HISTORY_IMAGE;
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->before.start.x,
&ev->before.start.y,
&ev->before.end.x,
&ev->before.end.y);
}
resizable_wrapper =
webkit_dom_document_create_element (document, "span", NULL);
webkit_dom_element_set_attribute (
resizable_wrapper, "class", "-x-evo-resizable-wrapper", NULL);
element = webkit_dom_document_create_element (document, "img", NULL);
webkit_dom_html_image_element_set_src (
WEBKIT_DOM_HTML_IMAGE_ELEMENT (element),
base64_content);
webkit_dom_element_set_attribute (
WEBKIT_DOM_ELEMENT (element), "data-uri", uri, NULL);
webkit_dom_element_set_attribute (
WEBKIT_DOM_ELEMENT (element), "data-inline", "", NULL);
webkit_dom_element_set_attribute (
WEBKIT_DOM_ELEMENT (element), "data-name",
filename ? filename : "", NULL);
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (resizable_wrapper),
WEBKIT_DOM_NODE (element),
NULL);
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (
WEBKIT_DOM_NODE (selection_start_marker)),
WEBKIT_DOM_NODE (resizable_wrapper),
WEBKIT_DOM_NODE (selection_start_marker),
NULL);
/* We have to again use UNICODE_ZERO_WIDTH_SPACE character to restore
* caret on right position */
text = webkit_dom_document_create_text_node (
document, UNICODE_ZERO_WIDTH_SPACE);
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (
WEBKIT_DOM_NODE (selection_start_marker)),
WEBKIT_DOM_NODE (text),
WEBKIT_DOM_NODE (selection_start_marker),
NULL);
if (ev) {
WebKitDOMDocumentFragment *fragment;
WebKitDOMNode *node;
fragment = webkit_dom_document_create_document_fragment (document);
node = webkit_dom_node_append_child (
WEBKIT_DOM_NODE (fragment),
webkit_dom_node_clone_node (WEBKIT_DOM_NODE (resizable_wrapper), TRUE),
NULL);
webkit_dom_html_element_insert_adjacent_html (
WEBKIT_DOM_HTML_ELEMENT (node), "afterend", "&#8203;", NULL);
ev->data.fragment = fragment;
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->after.start.x,
&ev->after.start.y,
&ev->after.end.x,
&ev->after.end.y);
e_html_editor_view_insert_new_history_event (view, ev);
}
e_html_editor_selection_restore (selection);
e_html_editor_view_force_spell_check_for_current_paragraph (view);
g_object_unref (view);
}
static void
image_load_finish (LoadContext *load_context)
{
EHTMLEditorSelection *selection;
GMemoryOutputStream *output_stream;
gchar *base64_encoded, *mime_type, *output, *uri;
gsize size;
gpointer data;
output_stream = G_MEMORY_OUTPUT_STREAM (load_context->output_stream);
selection = load_context->selection;
mime_type = g_content_type_get_mime_type (load_context->content_type);
data = g_memory_output_stream_get_data (output_stream);
size = g_memory_output_stream_get_data_size (output_stream);
uri = g_file_get_uri (load_context->file);
base64_encoded = g_base64_encode ((const guchar *) data, size);
output = g_strconcat ("data:", mime_type, ";base64,", base64_encoded, NULL);
if (load_context->element)
replace_base64_image_src (
selection, load_context->element, output, load_context->filename, uri);
else
insert_base64_image (selection, output, load_context->filename, uri);
g_free (base64_encoded);
g_free (output);
g_free (mime_type);
g_free (uri);
image_load_context_free (load_context);
}
static void
image_load_write_cb (GOutputStream *output_stream,
GAsyncResult *result,
LoadContext *load_context)
{
GInputStream *input_stream;
gssize bytes_written;
GError *error = NULL;
bytes_written = g_output_stream_write_finish (
output_stream, result, &error);
if (error) {
image_load_context_free (load_context);
return;
}
input_stream = load_context->input_stream;
if (bytes_written < load_context->bytes_read) {
g_memmove (
load_context->buffer,
load_context->buffer + bytes_written,
load_context->bytes_read - bytes_written);
load_context->bytes_read -= bytes_written;
g_output_stream_write_async (
output_stream,
load_context->buffer,
load_context->bytes_read,
G_PRIORITY_DEFAULT, NULL,
(GAsyncReadyCallback) image_load_write_cb,
load_context);
} else
g_input_stream_read_async (
input_stream,
load_context->buffer,
sizeof (load_context->buffer),
G_PRIORITY_DEFAULT, NULL,
(GAsyncReadyCallback) image_load_stream_read_cb,
load_context);
}
static void
image_load_stream_read_cb (GInputStream *input_stream,
GAsyncResult *result,
LoadContext *load_context)
{
GOutputStream *output_stream;
gssize bytes_read;
GError *error = NULL;
bytes_read = g_input_stream_read_finish (
input_stream, result, &error);
if (error) {
image_load_context_free (load_context);
return;
}
if (bytes_read == 0) {
image_load_finish (load_context);
return;
}
output_stream = load_context->output_stream;
load_context->bytes_read = bytes_read;
g_output_stream_write_async (
output_stream,
load_context->buffer,
load_context->bytes_read,
G_PRIORITY_DEFAULT, NULL,
(GAsyncReadyCallback) image_load_write_cb,
load_context);
}
static void
image_load_file_read_cb (GFile *file,
GAsyncResult *result,
LoadContext *load_context)
{
GFileInputStream *input_stream;
GOutputStream *output_stream;
GError *error = NULL;
/* Input stream might be NULL, so don't use cast macro. */
input_stream = g_file_read_finish (file, result, &error);
load_context->input_stream = (GInputStream *) input_stream;
if (error) {
image_load_context_free (load_context);
return;
}
/* Load the contents into a GMemoryOutputStream. */
output_stream = g_memory_output_stream_new (
NULL, 0, g_realloc, g_free);
load_context->output_stream = output_stream;
g_input_stream_read_async (
load_context->input_stream,
load_context->buffer,
sizeof (load_context->buffer),
G_PRIORITY_DEFAULT, NULL,
(GAsyncReadyCallback) image_load_stream_read_cb,
load_context);
}
static void
image_load_query_info_cb (GFile *file,
GAsyncResult *result,
LoadContext *load_context)
{
GFileInfo *file_info;
GError *error = NULL;
file_info = g_file_query_info_finish (file, result, &error);
if (error) {
image_load_context_free (load_context);
return;
}
load_context->content_type = g_file_info_get_content_type (file_info);
load_context->total_num_bytes = g_file_info_get_size (file_info);
load_context->filename = g_file_info_get_name (file_info);
g_file_read_async (
file, G_PRIORITY_DEFAULT,
NULL, (GAsyncReadyCallback)
image_load_file_read_cb, load_context);
}
static void
image_load_and_insert_async (EHTMLEditorSelection *selection,
WebKitDOMElement *element,
const gchar *uri)
{
LoadContext *load_context;
GFile *file;
g_return_if_fail (uri && *uri);
file = g_file_new_for_uri (uri);
g_return_if_fail (file != NULL);
load_context = image_load_context_new (selection);
load_context->file = file;
load_context->element = element;
g_file_query_info_async (
file, "standard::*",
G_FILE_QUERY_INFO_NONE,G_PRIORITY_DEFAULT,
NULL, (GAsyncReadyCallback)
image_load_query_info_cb, load_context);
}
/**
* e_html_editor_selection_insert_image:
* @selection: an #EHTMLEditorSelection
* @image_uri: an URI of the source image
*
* Inserts image at current cursor position using @image_uri as source. When a
* text range is selected, it will be replaced by the image.
*/
void
e_html_editor_selection_insert_image (EHTMLEditorSelection *selection,
const gchar *image_uri)
{
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
g_return_if_fail (image_uri != NULL);
if (is_in_html_mode (selection)) {
if (strstr (image_uri, ";base64,")) {
if (g_str_has_prefix (image_uri, "data:"))
insert_base64_image (selection, image_uri, "", "");
if (strstr (image_uri, ";data")) {
const gchar *base64_data = strstr (image_uri, ";") + 1;
gchar *filename;
glong filename_length;
filename_length =
g_utf8_strlen (image_uri, -1) -
g_utf8_strlen (base64_data, -1) - 1;
filename = g_strndup (image_uri, filename_length);
insert_base64_image (selection, base64_data, filename, "");
g_free (filename);
}
} else
image_load_and_insert_async (selection, NULL, image_uri);
}
}
/**
* e_html_editor_selection_replace_image_src:
* @selection: an #EHTMLEditorSelection
* @element: #WebKitDOMElement element
* @image_uri: an URI of the source image
*
* If given @element is image we will replace the src attribute of it with base64
* data from given @image_uri. Otherwise we will set the base64 data to
* the background attribute of given @element.
*/
void
e_html_editor_selection_replace_image_src (EHTMLEditorSelection *selection,
WebKitDOMElement *element,
const gchar *image_uri)
{
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
g_return_if_fail (image_uri != NULL);
g_return_if_fail (element && WEBKIT_DOM_IS_ELEMENT (element));
if (strstr (image_uri, ";base64,")) {
if (g_str_has_prefix (image_uri, "data:"))
replace_base64_image_src (
selection, element, image_uri, "", "");
if (strstr (image_uri, ";data")) {
const gchar *base64_data = strstr (image_uri, ";") + 1;
gchar *filename;
glong filename_length;
filename_length =
g_utf8_strlen (image_uri, -1) -
g_utf8_strlen (base64_data, -1) - 1;
filename = g_strndup (image_uri, filename_length);
replace_base64_image_src (
selection, element, base64_data, filename, "");
g_free (filename);
}
} else
image_load_and_insert_async (selection, element, image_uri);
}
void
e_html_editor_selection_move_caret_into_element (WebKitDOMDocument *document,
WebKitDOMElement *element,
gboolean to_start)
{
WebKitDOMDOMWindow *dom_window;
WebKitDOMDOMSelection *dom_selection;
WebKitDOMRange *new_range;
if (!element)
return;
dom_window = webkit_dom_document_get_default_view (document);
dom_selection = webkit_dom_dom_window_get_selection (dom_window);
new_range = webkit_dom_document_create_range (document);
webkit_dom_range_select_node_contents (
new_range, WEBKIT_DOM_NODE (element), NULL);
webkit_dom_range_collapse (new_range, to_start, NULL);
webkit_dom_dom_selection_remove_all_ranges (dom_selection);
webkit_dom_dom_selection_add_range (dom_selection, new_range);
g_object_unref (new_range);
g_object_unref (dom_selection);
g_object_unref (dom_window);
}
static gint
find_where_to_break_line (WebKitDOMCharacterData *node,
gint max_length)
{
gboolean last_break_position_is_dash = FALSE;
gchar *str, *text_start;
gunichar uc;
gint pos = 1, last_break_position = 0, ret_val = 0;
text_start = webkit_dom_character_data_get_data (node);
str = text_start;
do {
uc = g_utf8_get_char (str);
if (!uc) {
ret_val = pos <= max_length ? pos : last_break_position > 0 ? last_break_position - 1 : 0;
goto out;
}
if ((g_unichar_isspace (uc) && !(g_unichar_break_type (uc) == G_UNICODE_BREAK_NON_BREAKING_GLUE)) ||
*str == '-') {
if ((last_break_position_is_dash = *str == '-')) {
/* There was no space before the dash */
if (pos - 1 != last_break_position) {
gchar *rest;
rest = g_utf8_next_char (str);
if (rest && *rest) {
gunichar next_char;
/* There is no space after the dash */
next_char = g_utf8_get_char (rest);
if (g_unichar_isspace (next_char))
last_break_position_is_dash = FALSE;
else
last_break_position = pos;
} else
last_break_position_is_dash = FALSE;
} else
last_break_position_is_dash = FALSE;
} else
last_break_position = pos;
}
if ((pos == max_length)) {
/* Look one character after the limit to check if there
* is a space (skip dash) that we are allowed to break at, if so
* break it there. */
if (*str) {
str = g_utf8_next_char (str);
uc = g_utf8_get_char (str);
if ((g_unichar_isspace (uc) &&
!(g_unichar_break_type (uc) == G_UNICODE_BREAK_NON_BREAKING_GLUE)))
last_break_position = ++pos;
}
break;
}
pos++;
str = g_utf8_next_char (str);
} while (*str);
if (last_break_position != 0)
ret_val = last_break_position - 1;
out:
g_free (text_start);
/* Always break after the dash character. */
if (last_break_position_is_dash)
ret_val++;
/* No character to break at is found. We should split at max_length, but
* we will leave the decision on caller as it depends on context. */
if (ret_val == 0 && last_break_position == 0)
ret_val = -1;
return ret_val;
}
static void
mark_and_remove_leading_space (WebKitDOMDocument *document,
WebKitDOMNode *node)
{
WebKitDOMElement *element;
element = webkit_dom_document_create_element (document, "SPAN", NULL);
webkit_dom_element_set_attribute (element, "data-hidden-space", "", NULL);
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (node),
WEBKIT_DOM_NODE (element),
node,
NULL);
webkit_dom_character_data_replace_data (
WEBKIT_DOM_CHARACTER_DATA (node), 0, 1, "", NULL);
}
static WebKitDOMElement *
wrap_lines (EHTMLEditorSelection *selection,
WebKitDOMNode *block,
WebKitDOMDocument *document,
gboolean remove_all_br,
gint length_to_wrap,
gint word_wrap_length)
{
WebKitDOMNode *node, *start_node, *block_clone;
guint line_length;
gulong length_left;
gchar *text_content;
gboolean compensated = FALSE;
gboolean check_next_node = FALSE;
if (selection) {
gint ii, length;
WebKitDOMDocumentFragment *fragment;
WebKitDOMNodeList *list;
WebKitDOMRange *range;
range = html_editor_selection_get_current_range (selection);
fragment = webkit_dom_range_clone_contents (range, NULL);
g_object_unref (range);
/* Select all BR elements or just ours that are used for wrapping.
* We are not removing user BR elements when this function is activated
* from Format->Wrap Lines action */
list = webkit_dom_document_fragment_query_selector_all (
fragment,
remove_all_br ? "br" : "br.-x-evo-wrap-br",
NULL);
length = webkit_dom_node_list_get_length (list);
/* And remove them */
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *node = webkit_dom_node_list_item (list, length);
remove_node (node);
g_object_unref (node);
}
g_object_unref (list);
list = webkit_dom_document_fragment_query_selector_all (
fragment, "span[data-hidden-space]", NULL);
length = webkit_dom_node_list_get_length (list);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *hidden_space_node;
hidden_space_node = webkit_dom_node_list_item (list, ii);
webkit_dom_html_element_set_outer_text (
WEBKIT_DOM_HTML_ELEMENT (hidden_space_node), " ", NULL);
g_object_unref (hidden_space_node);
}
g_object_unref (list);
node = WEBKIT_DOM_NODE (fragment);
start_node = node;
} else {
WebKitDOMElement *selection_start_marker, *selection_end_marker;
WebKitDOMNode *start_point = NULL;
if (!webkit_dom_node_has_child_nodes (block))
return WEBKIT_DOM_ELEMENT (block);
block_clone = webkit_dom_node_clone_node (block, TRUE);
/* When we wrap, we are wrapping just the text after caret, text
* before the caret is already wrapped, so unwrap the text after
* the caret position */
selection_end_marker = webkit_dom_element_query_selector (
WEBKIT_DOM_ELEMENT (block_clone),
"span#-x-evo-selection-end-marker",
NULL);
if (selection_end_marker) {
WebKitDOMNode *nd = WEBKIT_DOM_NODE (selection_end_marker);
while (nd) {
WebKitDOMNode *parent_node;
WebKitDOMNode *next_nd = webkit_dom_node_get_next_sibling (nd);
parent_node = webkit_dom_node_get_parent_node (nd);
if (!next_nd && parent_node && !webkit_dom_node_is_same_node (parent_node, block_clone))
next_nd = webkit_dom_node_get_next_sibling (parent_node);
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (nd)) {
if (remove_all_br)
remove_node (nd);
else if (element_has_class (WEBKIT_DOM_ELEMENT (nd), "-x-evo-wrap-br"))
remove_node (nd);
} else if (WEBKIT_DOM_IS_ELEMENT (nd) &&
webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (nd), "data-hidden-space"))
webkit_dom_html_element_set_outer_text (
WEBKIT_DOM_HTML_ELEMENT (nd), " ", NULL);
nd = next_nd;
}
} else {
gint ii, length;
WebKitDOMNodeList *list;
list = webkit_dom_element_query_selector_all (
WEBKIT_DOM_ELEMENT (block_clone), "span[data-hidden-space]", NULL);
length = webkit_dom_node_list_get_length (list);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *hidden_space_node;
hidden_space_node = webkit_dom_node_list_item (list, ii);
webkit_dom_html_element_set_outer_text (
WEBKIT_DOM_HTML_ELEMENT (hidden_space_node), " ", NULL);
g_object_unref (hidden_space_node);
}
g_object_unref (list);
}
/* We have to start from the end of the last wrapped line */
selection_start_marker = webkit_dom_element_query_selector (
WEBKIT_DOM_ELEMENT (block_clone),
"span#-x-evo-selection-start-marker",
NULL);
if (selection_start_marker) {
gboolean first_removed = FALSE;
WebKitDOMNode *nd;
nd = webkit_dom_node_get_previous_sibling (
WEBKIT_DOM_NODE (selection_start_marker));
while (nd) {
WebKitDOMNode *prev_nd = webkit_dom_node_get_previous_sibling (nd);
if (!prev_nd && !webkit_dom_node_is_same_node (webkit_dom_node_get_parent_node (nd), block_clone))
prev_nd = webkit_dom_node_get_previous_sibling (webkit_dom_node_get_parent_node (nd));
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (nd)) {
if (first_removed) {
start_point = nd;
break;
} else {
remove_node (nd);
first_removed = TRUE;
}
} else if (WEBKIT_DOM_IS_ELEMENT (nd) &&
webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (nd), "data-hidden-space")) {
webkit_dom_html_element_set_outer_text (
WEBKIT_DOM_HTML_ELEMENT (nd), " ", NULL);
} else if (!prev_nd) {
start_point = nd;
}
nd = prev_nd;
}
}
webkit_dom_node_normalize (block_clone);
node = webkit_dom_node_get_first_child (block_clone);
if (node) {
text_content = webkit_dom_node_get_text_content (node);
if (g_strcmp0 ("\n", text_content) == 0)
node = webkit_dom_node_get_next_sibling (node);
g_free (text_content);
}
if (start_point) {
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (start_point))
node = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (start_point));
else
node = start_point;
start_node = block_clone;
} else
start_node = node;
}
line_length = 0;
while (node) {
gint offset = 0;
WebKitDOMElement *element;
if (WEBKIT_DOM_IS_TEXT (node)) {
const gchar *newline;
WebKitDOMNode *next_sibling;
/* If there is temporary hidden space we remove it */
text_content = webkit_dom_node_get_text_content (node);
if (strstr (text_content, UNICODE_ZERO_WIDTH_SPACE)) {
if (g_str_has_prefix (text_content, UNICODE_ZERO_WIDTH_SPACE))
webkit_dom_character_data_delete_data (
WEBKIT_DOM_CHARACTER_DATA (node),
0,
1,
NULL);
if (g_str_has_suffix (text_content, UNICODE_ZERO_WIDTH_SPACE))
webkit_dom_character_data_delete_data (
WEBKIT_DOM_CHARACTER_DATA (node),
g_utf8_strlen (text_content, -1) - 1,
1,
NULL);
g_free (text_content);
text_content = webkit_dom_node_get_text_content (node);
}
newline = strstr (text_content, "\n");
next_sibling = node;
while (newline) {
next_sibling = WEBKIT_DOM_NODE (webkit_dom_text_split_text (
WEBKIT_DOM_TEXT (next_sibling),
g_utf8_pointer_to_offset (text_content, newline),
NULL));
if (!next_sibling)
break;
element = webkit_dom_document_create_element (
document, "BR", NULL);
element_add_class (element, "-x-evo-temp-wrap-text-br");
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (next_sibling),
WEBKIT_DOM_NODE (element),
next_sibling,
NULL);
g_free (text_content);
text_content = webkit_dom_node_get_text_content (next_sibling);
if (g_str_has_prefix (text_content, "\n")) {
webkit_dom_character_data_delete_data (
WEBKIT_DOM_CHARACTER_DATA (next_sibling), 0, 1, NULL);
g_free (text_content);
text_content =
webkit_dom_node_get_text_content (next_sibling);
}
newline = strstr (text_content, "\n");
}
g_free (text_content);
} else {
if (e_html_editor_node_is_selection_position_node (node)) {
if (line_length == 0) {
WebKitDOMNode *tmp_node;
tmp_node = webkit_dom_node_get_previous_sibling (node);
/* Only check if there is some node before the selection marker. */
if (tmp_node && !e_html_editor_node_is_selection_position_node (tmp_node))
check_next_node = TRUE;
}
node = webkit_dom_node_get_next_sibling (node);
continue;
}
check_next_node = FALSE;
/* If element is ANCHOR we wrap it separately */
if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node)) {
glong anchor_length;
WebKitDOMNode *next_sibling;
text_content = webkit_dom_node_get_text_content (node);
anchor_length = g_utf8_strlen (text_content, -1);
g_free (text_content);
next_sibling = webkit_dom_node_get_next_sibling (node);
/* If the anchor doesn't fit on the line move the inner
* nodes out of it and start to wrap them. */
if ((line_length + anchor_length) > length_to_wrap) {
WebKitDOMNode *inner_node;
while ((inner_node = webkit_dom_node_get_first_child (node))) {
g_object_set_data (
G_OBJECT (inner_node),
"-x-evo-anchor-text",
GINT_TO_POINTER (1));
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (node),
inner_node,
next_sibling,
NULL);
}
next_sibling = webkit_dom_node_get_next_sibling (node);
remove_node (node);
node = next_sibling;
continue;
}
line_length += anchor_length;
node = next_sibling;
continue;
}
if (element_has_class (WEBKIT_DOM_ELEMENT (node), "Apple-tab-span")) {
WebKitDOMNode *sibling;
gint tab_length;
sibling = webkit_dom_node_get_previous_sibling (node);
if (sibling && WEBKIT_DOM_IS_ELEMENT (sibling) &&
element_has_class (WEBKIT_DOM_ELEMENT (sibling), "Apple-tab-span"))
tab_length = TAB_LENGTH;
else {
tab_length = TAB_LENGTH - (line_length + compensated ? 0 : (word_wrap_length - length_to_wrap)) % TAB_LENGTH;
compensated = TRUE;
}
if (line_length + tab_length > length_to_wrap) {
if (webkit_dom_node_get_next_sibling (node)) {
element = webkit_dom_document_create_element (
document, "BR", NULL);
element_add_class (element, "-x-evo-wrap-br");
node = webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (node),
WEBKIT_DOM_NODE (element),
webkit_dom_node_get_next_sibling (node),
NULL);
}
line_length = 0;
compensated = FALSE;
} else
line_length += tab_length;
sibling = webkit_dom_node_get_next_sibling (node);
node = sibling;
continue;
}
/* When we are not removing user-entered BR elements (lines wrapped by user),
* we need to skip those elements */
if (!remove_all_br && WEBKIT_DOM_IS_HTMLBR_ELEMENT (node)) {
if (!element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-wrap-br")) {
line_length = 0;
compensated = FALSE;
node = webkit_dom_node_get_next_sibling (node);
continue;
}
}
goto next_node;
}
/* If length of this node + what we already have is still less
* then length_to_wrap characters, then just concatenate it and
* continue to next node */
length_left = webkit_dom_character_data_get_length (
WEBKIT_DOM_CHARACTER_DATA (node));
if ((length_left + line_length) <= length_to_wrap) {
if (check_next_node)
goto check_node;
line_length += length_left;
if (line_length == length_to_wrap) {
line_length = 0;
element = webkit_dom_document_create_element (document, "BR", NULL);
element_add_class (element, "-x-evo-wrap-br");
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (node),
WEBKIT_DOM_NODE (element),
webkit_dom_node_get_next_sibling (node),
NULL);
}
goto next_node;
}
/* wrap until we have something */
while (node && (length_left + line_length) > length_to_wrap) {
gboolean insert_and_continue;
gint max_length;
check_node:
insert_and_continue = FALSE;
if (!WEBKIT_DOM_IS_CHARACTER_DATA (node))
goto next_node;
element = webkit_dom_document_create_element (document, "BR", NULL);
element_add_class (element, "-x-evo-wrap-br");
max_length = length_to_wrap - line_length;
if (max_length < 0)
max_length = length_to_wrap;
else if (max_length == 0) {
if (check_next_node) {
insert_and_continue = TRUE;
goto check;
}
/* Break before the current node and continue. */
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (node),
WEBKIT_DOM_NODE (element),
node,
NULL);
line_length = 0;
continue;
}
/* Allow anchors to break on any character. */
if (g_object_steal_data (G_OBJECT (node), "-x-evo-anchor-text"))
offset = max_length;
else {
/* Find where we can line-break the node so that it
* effectively fills the rest of current row. */
offset = find_where_to_break_line (
WEBKIT_DOM_CHARACTER_DATA (node), max_length);
/* When pressing delete on the end of line to concatenate
* the last word from the line and first word from the
* next line we will end with the second word split
* somewhere in the middle (to be precise it will be
* split after the last character that will fit on the
* previous line. To avoid that we need to put the
* concatenated word on the next line. */
if (offset == -1 || check_next_node) {
WebKitDOMNode *prev_sibling;
check:
check_next_node = FALSE;
prev_sibling = webkit_dom_node_get_previous_sibling (node);
if (prev_sibling && e_html_editor_node_is_selection_position_node (prev_sibling)) {
WebKitDOMNode *prev_br = NULL;
prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling);
/* Collapsed selection */
if (prev_sibling && e_html_editor_node_is_selection_position_node (prev_sibling))
prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling);
if (prev_sibling && WEBKIT_DOM_IS_HTMLBR_ELEMENT (prev_sibling) &&
element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-wrap-br")) {
prev_br = prev_sibling;
prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling);
}
if (prev_sibling && WEBKIT_DOM_IS_CHARACTER_DATA (prev_sibling)) {
gchar *data;
glong text_length, length = 0;
data = webkit_dom_character_data_get_data (
WEBKIT_DOM_CHARACTER_DATA (prev_sibling));
text_length = webkit_dom_character_data_get_length (
WEBKIT_DOM_CHARACTER_DATA (prev_sibling));
/* Find the last character where we can break. */
while (text_length - length > 0) {
if (strchr (" ", data[text_length - length - 1])) {
length++;
break;
} else if (data[text_length - length - 1] == '-' &&
text_length - length > 1 &&
!strchr (" ", data[text_length - length - 2]))
break;
length++;
}
if (text_length != length) {
WebKitDOMNode *nd;
webkit_dom_text_split_text (
WEBKIT_DOM_TEXT (prev_sibling),
text_length - length,
NULL);
if ((nd = webkit_dom_node_get_next_sibling (prev_sibling))) {
gchar *nd_content;
nd_content = webkit_dom_node_get_text_content (nd);
if (nd_content && *nd_content) {
if (*nd_content == ' ')
mark_and_remove_leading_space (document, nd);
g_free (nd_content);
}
if (nd) {
if (prev_br)
remove_node (prev_br);
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (nd),
WEBKIT_DOM_NODE (element),
nd,
NULL);
offset = 0;
line_length = length;
continue;
}
}
}
}
}
if (insert_and_continue) {
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (node),
WEBKIT_DOM_NODE (element),
node,
NULL);
offset = 0;
line_length = 0;
insert_and_continue = FALSE;
continue;
}
offset = offset != -1 ? offset : max_length;
}
}
if (offset >= 0) {
WebKitDOMNode *nd;
if (offset != length_left && offset != 0) {
webkit_dom_text_split_text (
WEBKIT_DOM_TEXT (node), offset, NULL);
nd = webkit_dom_node_get_next_sibling (node);
} else
nd = node;
if (nd) {
gchar *nd_content;
nd_content = webkit_dom_node_get_text_content (nd);
if (nd_content && *nd_content) {
if (*nd_content == ' ')
mark_and_remove_leading_space (document, nd);
g_free (nd_content);
}
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (node),
WEBKIT_DOM_NODE (element),
nd,
NULL);
offset = 0;
nd_content = webkit_dom_node_get_text_content (nd);
if (!*nd_content)
remove_node (nd);
g_free (nd_content);
node = webkit_dom_node_get_next_sibling (
WEBKIT_DOM_NODE (element));
} else {
webkit_dom_node_append_child (
webkit_dom_node_get_parent_node (node),
WEBKIT_DOM_NODE (element),
NULL);
}
}
if (node && WEBKIT_DOM_IS_CHARACTER_DATA (node))
length_left = webkit_dom_character_data_get_length (
WEBKIT_DOM_CHARACTER_DATA (node));
line_length = 0;
compensated = FALSE;
}
line_length += length_left - offset;
next_node:
if (!node)
break;
if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (node)) {
line_length = 0;
compensated = FALSE;
}
/* Move to next node */
if (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 {
WebKitDOMNode *tmp_parent;
if (webkit_dom_node_is_equal_node (node, start_node))
break;
/* Find a next node that we can process. */
tmp_parent = webkit_dom_node_get_parent_node (node);
if (tmp_parent && webkit_dom_node_get_next_sibling (tmp_parent))
node = webkit_dom_node_get_next_sibling (tmp_parent);
else {
WebKitDOMNode *tmp;
tmp = tmp_parent;
/* Find a node that is not a start node (that would mean
* that we already processed the whole block) and it has
* a sibling that we can process. */
while (tmp && !webkit_dom_node_is_equal_node (tmp, start_node) &&
!webkit_dom_node_get_next_sibling (tmp)) {
tmp = webkit_dom_node_get_parent_node (tmp);
}
/* If we found a node to process, let's process its
* sibling, otherwise give up. */
if (tmp)
node = webkit_dom_node_get_next_sibling (tmp);
else
break;
}
}
}
if (selection) {
gchar *html;
WebKitDOMElement *element;
/* Create a wrapper DIV and put the processed content into it */
element = webkit_dom_document_create_element (document, "DIV", NULL);
element_add_class (element, "-x-evo-paragraph");
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (element),
WEBKIT_DOM_NODE (start_node),
NULL);
webkit_dom_node_normalize (WEBKIT_DOM_NODE (element));
/* Get HTML code of the processed content */
html = webkit_dom_html_element_get_inner_html (WEBKIT_DOM_HTML_ELEMENT (element));
/* Overwrite the current selection by the processed content */
e_html_editor_selection_insert_html (selection, html);
g_free (html);
return NULL;
} else {
WebKitDOMNode *last_child;
last_child = webkit_dom_node_get_last_child (block_clone);
if (last_child && WEBKIT_DOM_IS_HTMLBR_ELEMENT (last_child) &&
element_has_class (WEBKIT_DOM_ELEMENT (last_child), "-x-evo-wrap-br"))
remove_node (last_child);
webkit_dom_node_normalize (block_clone);
node = webkit_dom_node_get_parent_node (block);
if (node) {
/* Replace block with wrapped one */
webkit_dom_node_replace_child (
node, block_clone, block, NULL);
}
return WEBKIT_DOM_ELEMENT (block_clone);
}
}
void
e_html_editor_selection_set_indented_style (EHTMLEditorSelection *selection,
WebKitDOMElement *element,
gint width)
{
gchar *style;
gint word_wrap_length;
/* width < 0, set block width to word_wrap_length
* width == 0, no width limit set,
* width > 0, set width limit to given value */
word_wrap_length = (width < 0) ? selection->priv->word_wrap_length : width;
webkit_dom_element_set_class_name (element, "-x-evo-indented");
if (is_in_html_mode (selection) || word_wrap_length == 0)
style = g_strdup_printf ("margin-left: %dch;", SPACES_PER_INDENTATION);
else
style = g_strdup_printf (
"margin-left: %dch; word-wrap: normal; width: %dch;",
SPACES_PER_INDENTATION, word_wrap_length);
webkit_dom_element_set_attribute (element, "style", style, NULL);
g_free (style);
}
WebKitDOMElement *
e_html_editor_selection_get_indented_element (EHTMLEditorSelection *selection,
WebKitDOMDocument *document,
gint width)
{
WebKitDOMElement *element;
element = webkit_dom_document_create_element (document, "DIV", NULL);
e_html_editor_selection_set_indented_style (selection, element, width);
return element;
}
void
e_html_editor_selection_set_paragraph_style (EHTMLEditorSelection *selection,
WebKitDOMElement *element,
gint width,
gint offset,
const gchar *style_to_add)
{
char *style = NULL;
gint word_wrap_length = (width == -1) ? selection->priv->word_wrap_length : width;
WebKitDOMNode *parent;
element_add_class (element, "-x-evo-paragraph");
/* Don't set the alignment for nodes as they are handled separately. */
if (!node_is_list (WEBKIT_DOM_NODE (element))) {
EHTMLEditorSelectionAlignment alignment;
alignment = e_html_editor_selection_get_alignment (selection);
element_add_class (element, get_css_alignment_value_class (alignment));
}
parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
/* Don't set the width limit to sub-blocks as the width limit is inhered
* from its parents. */
if (!is_in_html_mode (selection) &&
(!parent || WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent))) {
style = g_strdup_printf (
"width: %dch; "
"word-wrap: break-word; "
"word-break: break-word; %s",
(word_wrap_length + offset), style_to_add);
} else {
if (*style_to_add)
style = g_strdup_printf ("%s", style_to_add);
}
if (style) {
webkit_dom_element_set_attribute (element, "style", style, NULL);
g_free (style);
}
}
WebKitDOMElement *
e_html_editor_selection_get_paragraph_element (EHTMLEditorSelection *selection,
WebKitDOMDocument *document,
gint width,
gint offset)
{
WebKitDOMElement *element;
element = webkit_dom_document_create_element (document, "DIV", NULL);
e_html_editor_selection_set_paragraph_style (selection, element, width, offset, "");
return element;
}
WebKitDOMElement *
e_html_editor_selection_put_node_into_paragraph (EHTMLEditorSelection *selection,
WebKitDOMDocument *document,
WebKitDOMNode *node,
gboolean with_input)
{
WebKitDOMRange *range;
WebKitDOMElement *container;
range = webkit_dom_document_create_range (document);
container = e_html_editor_selection_get_paragraph_element (selection, document, -1, 0);
webkit_dom_range_select_node (range, node, NULL);
webkit_dom_range_surround_contents (range, WEBKIT_DOM_NODE (container), NULL);
/* We have to move caret position inside this container */
if (with_input)
add_selection_markers_into_element_end (document, container, NULL, NULL);
g_object_unref (range);
return container;
}
/**
* e_html_editor_selection_wrap_lines:
* @selection: an #EHTMLEditorSelection
*
* Wraps all lines in current selection to be 71 characters long.
*/
void
e_html_editor_selection_wrap_lines (EHTMLEditorSelection *selection)
{
EHTMLEditorView *view;
EHTMLEditorViewHistoryEvent *ev = NULL;
gboolean after_selection_end = FALSE, html_mode;
WebKitDOMDocument *document;
WebKitDOMElement *selection_start_marker, *selection_end_marker;
WebKitDOMNode *block, *next_block;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (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 the selection was not saved, move it into the first child of body */
if (!selection_start_marker || !selection_end_marker) {
WebKitDOMHTMLElement *body;
WebKitDOMNode *child;
body = webkit_dom_document_get_body (document);
child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
add_selection_markers_into_element_start (
document,
WEBKIT_DOM_ELEMENT (child),
&selection_start_marker,
&selection_end_marker);
}
if (!e_html_editor_view_is_undo_redo_in_progress (view)) {
ev = g_new0 (EHTMLEditorViewHistoryEvent, 1);
ev->type = HISTORY_WRAP;
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->before.start.x,
&ev->before.start.y,
&ev->before.end.x,
&ev->before.end.y);
ev->data.style.from = 1;
ev->data.style.to = 1;
}
block = e_html_editor_get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
html_mode = e_html_editor_view_get_html_mode (view);
/* Process all blocks that are in the selection one by one */
while (block && !after_selection_end) {
gboolean quoted = FALSE;
gint citation_level, quote;
WebKitDOMElement *wrapped_paragraph;
next_block = webkit_dom_node_get_next_sibling (block);
/* Don't try to wrap the 'Normal' blocks as they are already wrapped and*/
/* also skip blocks that we already wrapped with this function. */
if ((!html_mode && element_has_class (WEBKIT_DOM_ELEMENT (block), "-x-evo-paragraph")) ||
webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (block), "data-user-wrapped")) {
block = next_block;
continue;
}
if (webkit_dom_element_query_selector (
WEBKIT_DOM_ELEMENT (block), "span.-x-evo-quoted", NULL)) {
quoted = TRUE;
remove_quoting_from_element (WEBKIT_DOM_ELEMENT (block));
}
if (!html_mode)
remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (block));
after_selection_end = webkit_dom_node_contains (
block, WEBKIT_DOM_NODE (selection_end_marker));
citation_level = get_citation_level (block);
quote = citation_level ? citation_level * 2 : 0;
wrapped_paragraph = e_html_editor_selection_wrap_paragraph_length (
selection, WEBKIT_DOM_ELEMENT (block), selection->priv->word_wrap_length - quote);
webkit_dom_element_set_attribute (
wrapped_paragraph, "data-user-wrapped", "", NULL);
if (quoted && !html_mode)
e_html_editor_view_quote_plain_text_element (view, wrapped_paragraph);
block = next_block;
}
if (ev) {
e_html_editor_selection_get_selection_coordinates (
selection,
&ev->after.start.x,
&ev->after.start.y,
&ev->after.end.x,
&ev->after.end.y);
e_html_editor_view_insert_new_history_event (view, ev);
}
e_html_editor_selection_restore (selection);
e_html_editor_view_force_spell_check_in_viewport (view);
g_object_unref (view);
}
WebKitDOMElement *
e_html_editor_selection_wrap_paragraph_length (EHTMLEditorSelection *selection,
WebKitDOMElement *paragraph,
gint length)
{
WebKitDOMDocument *document;
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL);
g_return_val_if_fail (WEBKIT_DOM_IS_ELEMENT (paragraph), NULL);
g_return_val_if_fail (length >= MINIMAL_PARAGRAPH_WIDTH, NULL);
document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (paragraph));
return wrap_lines (
NULL, WEBKIT_DOM_NODE (paragraph), document, FALSE, length, selection->priv->word_wrap_length);
}
void
e_html_editor_selection_wrap_paragraphs_in_document (EHTMLEditorSelection *selection,
WebKitDOMDocument *document)
{
WebKitDOMNodeList *list;
gint ii, length;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
/* Only wrap paragraphs that are inside the quoted content, others are
* wrapped by CSS. */
list = webkit_dom_document_query_selector_all (
document,
"blockquote[type=cite] > div.-x-evo-paragraph:not(#-x-evo-input-start)",
NULL);
length = webkit_dom_node_list_get_length (list);
for (ii = 0; ii < length; ii++) {
gint quote, citation_level;
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
citation_level = get_citation_level (node);
quote = citation_level ? citation_level * 2 : 0;
if (node_is_list (node)) {
WebKitDOMNode *item = webkit_dom_node_get_first_child (node);
while (item && WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) {
e_html_editor_selection_wrap_paragraph_length (
selection,
WEBKIT_DOM_ELEMENT (item),
selection->priv->word_wrap_length - quote);
item = webkit_dom_node_get_next_sibling (item);
}
} else {
e_html_editor_selection_wrap_paragraph_length (
selection,
WEBKIT_DOM_ELEMENT (node),
selection->priv->word_wrap_length - quote);
}
g_object_unref (node);
}
g_object_unref (list);
}
WebKitDOMElement *
e_html_editor_selection_wrap_paragraph (EHTMLEditorSelection *selection,
WebKitDOMElement *paragraph)
{
gint indentation_level, citation_level, quote;
gint final_width, word_wrap_length, offset = 0;
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL);
g_return_val_if_fail (WEBKIT_DOM_IS_ELEMENT (paragraph), NULL);
word_wrap_length = selection->priv->word_wrap_length;
indentation_level = get_indentation_level (paragraph);
citation_level = get_citation_level (WEBKIT_DOM_NODE (paragraph));
if (node_is_list_or_item (WEBKIT_DOM_NODE (paragraph))) {
gint list_level = get_list_level (WEBKIT_DOM_NODE (paragraph));
indentation_level = 0;
if (list_level > 0)
offset = list_level * -SPACES_PER_LIST_LEVEL;
else
offset = -SPACES_PER_LIST_LEVEL;
}
quote = citation_level ? citation_level * 2 : 0;
final_width = word_wrap_length - quote + offset;
final_width -= SPACES_PER_INDENTATION * indentation_level;
return e_html_editor_selection_wrap_paragraph_length (
selection, WEBKIT_DOM_ELEMENT (paragraph), final_width);
}
static WebKitDOMNode *
in_empty_block_in_quoted_content (WebKitDOMNode *element)
{
WebKitDOMNode *first_child, *next_sibling;
first_child = webkit_dom_node_get_first_child (element);
if (!WEBKIT_DOM_IS_ELEMENT (first_child))
return NULL;
if (!element_has_class (WEBKIT_DOM_ELEMENT (first_child), "-x-evo-quoted"))
return NULL;
next_sibling = webkit_dom_node_get_next_sibling (first_child);
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (next_sibling))
return next_sibling;
if (!WEBKIT_DOM_IS_ELEMENT (next_sibling))
return NULL;
if (!element_has_id (WEBKIT_DOM_ELEMENT (next_sibling), "-x-evo-selection-start-marker"))
return NULL;
next_sibling = webkit_dom_node_get_next_sibling (next_sibling);
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (next_sibling))
return next_sibling;
return NULL;
}
/**
* e_html_editor_selection_save:
* @selection: an #EHTMLEditorSelection
*
* Saves current cursor position or current selection range. The selection can
* be later restored by calling e_html_editor_selection_restore().
*
* Note that calling e_html_editor_selection_save() overwrites previously saved
* position.
*
* Note that this method inserts special markings into the HTML code that are
* used to later restore the selection. It can happen that by deleting some
* segments of the document some of the markings are deleted too. In that case
* restoring the selection by e_html_editor_selection_restore() can fail. Also by
* moving text segments (Cut & Paste) can result in moving the markings
* elsewhere, thus e_html_editor_selection_restore() will restore the selection
* incorrectly.
*
* It is recommended to use this method only when you are not planning to make
* bigger changes to content or structure of the document (formatting changes
* are usually OK).
*/
void
e_html_editor_selection_save (EHTMLEditorSelection *selection)
{
gboolean collapsed = FALSE;
glong offset, anchor_offset;
EHTMLEditorView *view;
WebKitWebView *web_view;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *dom_window;
WebKitDOMDOMSelection *dom_selection;
WebKitDOMRange *range;
WebKitDOMNode *container, *next_sibling, *marker_node;
WebKitDOMNode *split_node, *parent_node, *anchor;
WebKitDOMElement *start_marker = NULL, *end_marker = NULL;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
web_view = WEBKIT_WEB_VIEW (view);
document = webkit_web_view_get_dom_document (web_view);
dom_window = webkit_dom_document_get_default_view (document);
dom_selection = webkit_dom_dom_window_get_selection (dom_window);
g_object_unref (view);
/* First remove all markers (if present) */
remove_selection_markers (document);
if (webkit_dom_dom_selection_get_range_count (dom_selection) < 1) {
g_object_unref (dom_selection);
g_object_unref (dom_window);
return;
}
range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
if (!range) {
g_object_unref (dom_selection);
g_object_unref (dom_window);
return;
}
anchor = webkit_dom_dom_selection_get_anchor_node (dom_selection);
anchor_offset = webkit_dom_dom_selection_get_anchor_offset (dom_selection);
collapsed = webkit_dom_range_get_collapsed (range, NULL);
start_marker = create_selection_marker (document, TRUE);
container = webkit_dom_range_get_start_container (range, NULL);
offset = webkit_dom_range_get_start_offset (range, NULL);
parent_node = webkit_dom_node_get_parent_node (container);
if (webkit_dom_node_is_same_node (anchor, container) && offset == anchor_offset)
webkit_dom_element_set_attribute (start_marker, "data-anchor", "", NULL);
if (element_has_class (WEBKIT_DOM_ELEMENT (parent_node), "-x-evo-quote-character")) {
WebKitDOMNode *node;
node = webkit_dom_node_get_parent_node (
webkit_dom_node_get_parent_node (parent_node));
if ((next_sibling = in_empty_block_in_quoted_content (node))) {
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (next_sibling),
WEBKIT_DOM_NODE (start_marker),
next_sibling,
NULL);
} else {
webkit_dom_node_insert_before (
node,
WEBKIT_DOM_NODE (start_marker),
webkit_dom_node_get_next_sibling (
webkit_dom_node_get_parent_node (parent_node)),
NULL);
}
goto insert_end_marker;
} else if (element_has_class (WEBKIT_DOM_ELEMENT (parent_node), "-x-evo-smiley-text")) {
WebKitDOMNode *node;
node = webkit_dom_node_get_parent_node (parent_node);
if (offset == 0) {
marker_node = webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (node),
WEBKIT_DOM_NODE (start_marker),
node,
NULL);
goto insert_end_marker;
}
} else if (element_has_class (WEBKIT_DOM_ELEMENT (parent_node), "Apple-tab-span") && offset == 1) {
marker_node = webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (parent_node),
WEBKIT_DOM_NODE (start_marker),
webkit_dom_node_get_next_sibling (parent_node),
NULL);
goto insert_end_marker;
}
if (WEBKIT_DOM_IS_TEXT (container)) {
if (offset != 0) {
WebKitDOMText *split_text;
split_text = webkit_dom_text_split_text (
WEBKIT_DOM_TEXT (container), offset, NULL);
split_node = WEBKIT_DOM_NODE (split_text);
} else {
marker_node = webkit_dom_node_insert_before (
parent_node,
WEBKIT_DOM_NODE (start_marker),
container,
NULL);
goto insert_end_marker;
}
} else if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (container)) {
marker_node = webkit_dom_node_insert_before (
container,
WEBKIT_DOM_NODE (start_marker),
webkit_dom_node_get_first_child (container),
NULL);
goto insert_end_marker;
} else if (WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (container)) {
marker_node = webkit_dom_node_insert_before (
container,
WEBKIT_DOM_NODE (start_marker),
webkit_dom_node_get_first_child (container),
NULL);
goto insert_end_marker;
} else if (element_has_class (WEBKIT_DOM_ELEMENT (container), "-x-evo-resizable-wrapper")) {
marker_node = webkit_dom_node_insert_before (
parent_node,
WEBKIT_DOM_NODE (start_marker),
webkit_dom_node_get_next_sibling (container),
NULL);
goto insert_end_marker;
} else {
/* Insert the selection marker on the right position in
* an empty paragraph in the quoted content */
if ((next_sibling = in_empty_block_in_quoted_content (container))) {
marker_node = webkit_dom_node_insert_before (
container,
WEBKIT_DOM_NODE (start_marker),
next_sibling,
NULL);
goto insert_end_marker;
}
if (!webkit_dom_node_get_previous_sibling (container)) {
marker_node = webkit_dom_node_insert_before (
container,
WEBKIT_DOM_NODE (start_marker),
webkit_dom_node_get_first_child (container),
NULL);
goto insert_end_marker;
} else if (!webkit_dom_node_get_next_sibling (container)) {
WebKitDOMNode *tmp;
tmp = webkit_dom_node_get_last_child (container);
if (tmp && WEBKIT_DOM_IS_HTMLBR_ELEMENT (tmp))
marker_node = webkit_dom_node_insert_before (
container,
WEBKIT_DOM_NODE (start_marker),
tmp,
NULL);
else
marker_node = webkit_dom_node_append_child (
container,
WEBKIT_DOM_NODE (start_marker),
NULL);
goto insert_end_marker;
} else {
if (webkit_dom_node_get_first_child (container)) {
marker_node = webkit_dom_node_insert_before (
container,
WEBKIT_DOM_NODE (start_marker),
webkit_dom_node_get_first_child (container),
NULL);
goto insert_end_marker;
}
split_node = container;
}
}
/* Don't save selection straight into body */
if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (split_node))
goto out;
if (!split_node) {
marker_node = webkit_dom_node_insert_before (
container,
WEBKIT_DOM_NODE (start_marker),
webkit_dom_node_get_first_child (
WEBKIT_DOM_NODE (container)),
NULL);
} else {
marker_node = WEBKIT_DOM_NODE (start_marker);
parent_node = webkit_dom_node_get_parent_node (split_node);
webkit_dom_node_insert_before (
parent_node, marker_node, split_node, NULL);
}
webkit_dom_node_normalize (parent_node);
insert_end_marker:
end_marker = create_selection_marker (document, FALSE);
if (collapsed) {
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (start_marker)),
WEBKIT_DOM_NODE (end_marker),
webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (start_marker)),
NULL);
goto out;
}
container = webkit_dom_range_get_end_container (range, NULL);
offset = webkit_dom_range_get_end_offset (range, NULL);
parent_node = webkit_dom_node_get_parent_node (container);
if (webkit_dom_node_is_same_node (anchor, container) && offset == anchor_offset)
webkit_dom_element_set_attribute (end_marker, "data-anchor", "", NULL);
if (element_has_class (WEBKIT_DOM_ELEMENT (parent_node), "-x-evo-quote-character")) {
WebKitDOMNode *node;
node = webkit_dom_node_get_parent_node (
webkit_dom_node_get_parent_node (parent_node));
if ((next_sibling = in_empty_block_in_quoted_content (node))) {
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (next_sibling),
WEBKIT_DOM_NODE (end_marker),
next_sibling,
NULL);
} else {
webkit_dom_node_insert_before (
node,
WEBKIT_DOM_NODE (end_marker),
webkit_dom_node_get_next_sibling (
webkit_dom_node_get_parent_node (parent_node)),
NULL);
}
goto out;
}
if (WEBKIT_DOM_IS_TEXT (container)) {
if (offset != 0) {
WebKitDOMText *split_text;
split_text = webkit_dom_text_split_text (
WEBKIT_DOM_TEXT (container), offset, NULL);
split_node = WEBKIT_DOM_NODE (split_text);
} else {
marker_node = webkit_dom_node_insert_before (
parent_node, WEBKIT_DOM_NODE (end_marker), container, NULL);
goto check;
}
} else if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (container)) {
webkit_dom_node_append_child (
container, WEBKIT_DOM_NODE (end_marker), NULL);
goto out;
} else {
/* Insert the selection marker on the right position in
* an empty paragraph in the quoted content */
if ((next_sibling = in_empty_block_in_quoted_content (container))) {
webkit_dom_node_insert_before (
container,
WEBKIT_DOM_NODE (end_marker),
next_sibling,
NULL);
goto out;
}
if (!webkit_dom_node_get_previous_sibling (container)) {
split_node = parent_node;
} else if (!webkit_dom_node_get_next_sibling (container)) {
split_node = parent_node;
split_node = webkit_dom_node_get_next_sibling (split_node);
} else
split_node = container;
}
/* Don't save selection straight into body */
if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (split_node)) {
remove_node (WEBKIT_DOM_NODE (start_marker));
g_object_unref (range);
g_object_unref (dom_selection);
g_object_unref (dom_window);
return;
}
marker_node = WEBKIT_DOM_NODE (end_marker);
if (split_node) {
parent_node = webkit_dom_node_get_parent_node (split_node);
if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent_node)) {
if (offset == 0)
webkit_dom_node_insert_before (
split_node,
marker_node,
webkit_dom_node_get_first_child (split_node),
NULL);
else
webkit_dom_node_append_child (
webkit_dom_node_get_previous_sibling (split_node),
marker_node,
NULL);
} else
webkit_dom_node_insert_before (
parent_node, marker_node, split_node, NULL);
} else {
WebKitDOMNode *first_child;
first_child = webkit_dom_node_get_first_child (container);
if (offset == 0 && WEBKIT_DOM_IS_TEXT (first_child))
webkit_dom_node_insert_before (
WEBKIT_DOM_NODE (container), marker_node, webkit_dom_node_get_first_child (container), NULL);
else
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (container), marker_node, NULL);
}
webkit_dom_node_normalize (parent_node);
check:
if ((next_sibling = webkit_dom_node_get_next_sibling (marker_node))) {
if (!WEBKIT_DOM_IS_ELEMENT (next_sibling))
next_sibling = webkit_dom_node_get_next_sibling (next_sibling);
/* If the selection is collapsed ensure that the selection start marker
* is before the end marker */
if (next_sibling && webkit_dom_node_is_same_node (next_sibling, WEBKIT_DOM_NODE (start_marker))) {
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (marker_node),
next_sibling,
marker_node,
NULL);
}
}
out:
if (!collapsed) {
if (start_marker && end_marker) {
webkit_dom_range_set_start_after (range, WEBKIT_DOM_NODE (start_marker), NULL);
webkit_dom_range_set_end_before (range, WEBKIT_DOM_NODE (end_marker), NULL);
} else {
g_warn_if_reached ();
}
webkit_dom_dom_selection_remove_all_ranges (dom_selection);
webkit_dom_dom_selection_add_range (dom_selection, range);
}
g_object_unref (range);
g_object_unref (dom_selection);
g_object_unref (dom_window);
}
/**
* e_html_editor_selection_restore:
* @selection: an #EHTMLEditorSelection
*
* Restores cursor position or selection range that was saved by
* e_html_editor_selection_save().
*
* Note that calling this function without calling e_html_editor_selection_save()
* before is a programming error and the behavior is undefined.
*/
void
e_html_editor_selection_restore (EHTMLEditorSelection *selection)
{
EHTMLEditorView *view;
gboolean start_is_anchor = FALSE;
glong offset;
WebKitWebView *web_view;
WebKitDOMDocument *document;
WebKitDOMElement *marker;
WebKitDOMNode *selection_start_marker, *selection_end_marker;
WebKitDOMNode *parent_start, *parent_end, *anchor;
WebKitDOMRange *range;
WebKitDOMDOMSelection *dom_selection;
WebKitDOMDOMWindow *dom_window;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
web_view = WEBKIT_WEB_VIEW (view);
document = webkit_web_view_get_dom_document (web_view);
g_object_unref (view);
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);
g_object_unref (dom_window);
if (!range) {
WebKitDOMHTMLElement *body;
range = webkit_dom_document_create_range (document);
body = webkit_dom_document_get_body (document);
webkit_dom_range_select_node_contents (range, WEBKIT_DOM_NODE (body), NULL);
webkit_dom_range_collapse (range, TRUE, NULL);
webkit_dom_dom_selection_add_range (dom_selection, range);
}
selection_start_marker = webkit_dom_range_get_start_container (range, NULL);
if (selection_start_marker) {
gboolean ok = FALSE;
selection_start_marker =
webkit_dom_node_get_next_sibling (selection_start_marker);
ok = e_html_editor_node_is_selection_position_node (selection_start_marker);
if (ok) {
ok = FALSE;
if (webkit_dom_range_get_collapsed (range, NULL)) {
selection_end_marker = webkit_dom_node_get_next_sibling (
selection_start_marker);
ok = e_html_editor_node_is_selection_position_node (selection_end_marker);
if (ok) {
WebKitDOMNode *next_sibling;
next_sibling = webkit_dom_node_get_next_sibling (selection_end_marker);
if (next_sibling && !WEBKIT_DOM_IS_HTMLBR_ELEMENT (next_sibling)) {
parent_start = webkit_dom_node_get_parent_node (selection_end_marker);
remove_node (selection_start_marker);
remove_node (selection_end_marker);
webkit_dom_node_normalize (parent_start);
g_object_unref (range);
g_object_unref (dom_selection);
return;
}
}
}
}
}
g_object_unref (range);
range = webkit_dom_document_create_range (document);
if (!range) {
g_object_unref (dom_selection);
return;
}
marker = webkit_dom_document_get_element_by_id (
document, "-x-evo-selection-start-marker");
if (!marker) {
marker = webkit_dom_document_get_element_by_id (
document, "-x-evo-selection-end-marker");
if (marker)
remove_node (WEBKIT_DOM_NODE (marker));
g_object_unref (dom_selection);
g_object_unref (range);
return;
}
start_is_anchor = webkit_dom_element_has_attribute (marker, "data-anchor");
parent_start = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (marker));
webkit_dom_range_set_start_after (range, WEBKIT_DOM_NODE (marker), NULL);
remove_node (WEBKIT_DOM_NODE (marker));
marker = webkit_dom_document_get_element_by_id (
document, "-x-evo-selection-end-marker");
if (!marker) {
marker = webkit_dom_document_get_element_by_id (
document, "-x-evo-selection-start-marker");
if (marker)
remove_node (WEBKIT_DOM_NODE (marker));
g_object_unref (dom_selection);
g_object_unref (range);
return;
}
parent_end = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (marker));
webkit_dom_range_set_end_before (range, WEBKIT_DOM_NODE (marker), NULL);
remove_node (WEBKIT_DOM_NODE (marker));
webkit_dom_dom_selection_remove_all_ranges (dom_selection);
if (webkit_dom_node_is_same_node (parent_start, parent_end))
webkit_dom_node_normalize (parent_start);
else {
webkit_dom_node_normalize (parent_start);
webkit_dom_node_normalize (parent_end);
}
if (start_is_anchor) {
anchor = webkit_dom_range_get_end_container (range, NULL);
offset = webkit_dom_range_get_end_offset (range, NULL);
webkit_dom_range_collapse (range, TRUE, NULL);
} else {
anchor = webkit_dom_range_get_start_container (range, NULL);
offset = webkit_dom_range_get_start_offset (range, NULL);
webkit_dom_range_collapse (range, FALSE, NULL);
}
webkit_dom_dom_selection_add_range (dom_selection, range);
webkit_dom_dom_selection_extend (dom_selection, anchor, offset, NULL);
g_object_unref (range);
g_object_unref (dom_selection);
}
void
e_html_editor_selection_set_on_point (EHTMLEditorSelection *selection,
guint x,
guint y)
{
EHTMLEditorView *view;
WebKitDOMRange *range;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *dom_window;
WebKitDOMDOMSelection *dom_selection;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
g_object_unref (view);
dom_window = webkit_dom_document_get_default_view (document);
dom_selection = webkit_dom_dom_window_get_selection (dom_window);
range = webkit_dom_document_caret_range_from_point (document, x, y);
webkit_dom_dom_selection_remove_all_ranges (dom_selection);
webkit_dom_dom_selection_add_range (dom_selection, range);
g_object_unref (range);
g_object_unref (dom_selection);
g_object_unref (dom_window);
}
static void
html_editor_selection_modify (EHTMLEditorSelection *selection,
const gchar *alter,
gboolean forward,
EHTMLEditorSelectionGranularity granularity)
{
EHTMLEditorView *view;
WebKitWebView *web_view;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *dom_window;
WebKitDOMDOMSelection *dom_selection;
const gchar *granularity_str = NULL;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
web_view = WEBKIT_WEB_VIEW (view);
document = webkit_web_view_get_dom_document (web_view);
dom_window = webkit_dom_document_get_default_view (document);
dom_selection = webkit_dom_dom_window_get_selection (dom_window);
switch (granularity) {
case E_HTML_EDITOR_SELECTION_GRANULARITY_CHARACTER:
granularity_str = "character";
break;
case E_HTML_EDITOR_SELECTION_GRANULARITY_WORD:
granularity_str = "word";
break;
}
if (granularity_str) {
webkit_dom_dom_selection_modify (
dom_selection, alter,
forward ? "forward" : "backward",
granularity_str);
}
g_object_unref (dom_selection);
g_object_unref (dom_window);
g_object_unref (view);
}
/**
* e_html_editor_selection_extend:
* @selection: an #EHTMLEditorSelection
* @forward: whether to extend selection forward or backward
* @granularity: granularity of the extension
*
* Extends current selection in given direction by given granularity.
*/
void
e_html_editor_selection_extend (EHTMLEditorSelection *selection,
gboolean forward,
EHTMLEditorSelectionGranularity granularity)
{
html_editor_selection_modify (selection, "extend", forward, granularity);
}
/**
* e_html_editor_selection_move:
* @selection: an #EHTMLEditorSelection
* @forward: whether to move the selection forward or backward
* @granularity: granularity of the movement
*
* Moves current selection in given direction by given granularity
*/
void
e_html_editor_selection_move (EHTMLEditorSelection *selection,
gboolean forward,
EHTMLEditorSelectionGranularity granularity)
{
html_editor_selection_modify (selection, "move", forward, granularity);
}
void
e_html_editor_selection_scroll_to_caret (EHTMLEditorSelection *selection)
{
glong element_top, element_left;
glong window_top, window_left, window_right, window_bottom;
EHTMLEditorView *view;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *dom_window;
WebKitDOMElement *selection_start_marker;
e_html_editor_selection_save (selection);
view = e_html_editor_selection_ref_html_editor_view (selection);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
g_object_unref (view);
selection_start_marker = webkit_dom_document_get_element_by_id (
document, "-x-evo-selection-start-marker");
if (!selection_start_marker)
return;
dom_window = webkit_dom_document_get_default_view (document);
window_top = webkit_dom_dom_window_get_scroll_y (dom_window);
window_left = webkit_dom_dom_window_get_scroll_x (dom_window);
window_bottom = window_top + webkit_dom_dom_window_get_inner_height (dom_window);
window_right = window_left + webkit_dom_dom_window_get_inner_width (dom_window);
element_left = webkit_dom_element_get_offset_left (selection_start_marker);
element_top = webkit_dom_element_get_offset_top (selection_start_marker);
/* Check if caret is inside viewport, if not move to it */
if (!(element_top >= window_top && element_top <= window_bottom &&
element_left >= window_left && element_left <= window_right)) {
webkit_dom_element_scroll_into_view (selection_start_marker, TRUE);
}
e_html_editor_selection_restore (selection);
g_object_unref (dom_window);
}