Files
evolution/e-util/e-html-editor-selection.c
Tomas Popela a60e6be8f1 EHTMLEditorView - Improve how the content is processed
Split the process_elements function to:
 * process_node_to_plain_text_for_exporting
 * process_node_to_plain_text_changing_composer_mode
 * process_node_to_html_changing_composer_mode
 * process_node_to_html_for_exporting

that process the given nodes (usually just BODY) for purposes described by the
function names - i.e. the first function will process the BODY and will transform
the lists to their plain text version, fill the indentation or alignment with
spaces and so on; the process_node_to_html_for_exporting will remove all the
classes, ids and other attributes from the DOM.

Also in the process_node_to_plain_text_for_exporting correctly process the block
elements to avoid situations when an extra new line could be added on the end of
the block or not added at all. Always look if the new line is indeed there, if
not add it there explicitly. This fixes the bug 767681. Also avoid working with
the innerHTML of the BODY (that was previously leaked), but instead copy the
nodes. Also remove images from the BODY if they are presented to avoid unneeded
line break.

After this change the indented elements will be preserved when switching between
composer modes.

When removing an image also remove its wrappers and the block where the image
was, if the block is empty after the image was removed, otherwise an extra line
will be added to the output.
2016-06-29 15:19:13 +02:00

8121 lines
233 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)
{
EHTMLEditorView *view;
gboolean has_text = FALSE;
gchar *text = NULL;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *dom_window = NULL;
WebKitDOMDOMSelection *dom_selection = NULL;
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);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
g_object_unref (view);
if (!(dom_window = webkit_dom_document_get_default_view (document)))
goto out;
if (!(dom_selection = webkit_dom_dom_window_get_selection (dom_window)))
goto out;
if (webkit_dom_dom_selection_get_is_collapsed (dom_selection))
goto out;
if (!(range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL)))
goto out;
text = webkit_dom_range_get_text (range);
has_text = text && *text;
out:
g_free (text);
g_clear_object (&dom_window);
g_clear_object (&dom_selection);
g_clear_object (&range);
return has_text;
}
/**
* 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)
{
EHTMLEditorView *view;
gboolean collapsed;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *dom_window;
WebKitDOMDOMSelection *dom_selection;
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), TRUE);
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_val_if_fail (view != NULL, TRUE);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
g_object_unref (view);
if (!(dom_window = webkit_dom_document_get_default_view (document)))
return FALSE;
if (!(dom_selection = webkit_dom_dom_window_get_selection (dom_window))) {
g_object_unref (dom_window);
return FALSE;
}
collapsed = webkit_dom_dom_selection_get_is_collapsed (dom_selection);
g_object_unref (dom_selection);
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_ELEMENT (e_html_editor_get_parent_block_node_from_child (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);
goto workaroud;
}
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);
workaroud:
/* Workaround for bug 749712 on the Evolution side. The cause of the bug
* is that WebKit is having problems determining the right line height
* for some fonts and font sizes (the right and wrong value differ by 1).
* To fix this we will add an extra one to the final top offset. This is
* safe to do even for fonts and font sizes that don't behave badly as we
* will still get the right element as we use fonts bigger than 1 pixel. */
*start_y += 1;
*end_y += 1;
}
/**
* 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, "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;
gint citation_level = 0;
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 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) {
if (citation_level > 0)
e_html_editor_view_quote_plain_text_element_after_wrapping (document, element, citation_level);
else
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_remove_input_event_listener_from_body (view);
e_html_editor_selection_block_selection_changed (selection);
e_html_editor_view_exec_command (
view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT, NULL);
e_html_editor_view_register_input_event_listener_on_body (view);
e_html_editor_selection_unblock_selection_changed (selection);
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 != E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_NONE) {
if (format == prev && prev != E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_NONE) {
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 && next != E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_NONE) {
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 {
gboolean no_sibling;
WebKitDOMNode *node, *sibling;
node = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element));
/* Turning the formatting in the middle of element. */
sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element));
no_sibling = sibling &&
!WEBKIT_DOM_IS_HTMLBR_ELEMENT (sibling) &&
!webkit_dom_node_get_next_sibling (sibling);
if (no_sibling) {
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);
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (sibling) && !no_sibling) {
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);
remove_node_if_empty (parent);
}
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_to_process), "-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);
}
e_html_editor_view_set_changed (view, TRUE);
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);
}
e_html_editor_view_set_changed (view, TRUE);
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);
e_html_editor_selection_scroll_to_caret (selection);
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_trailing_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),
webkit_dom_node_get_next_sibling (node),
NULL);
webkit_dom_character_data_replace_data (
WEBKIT_DOM_CHARACTER_DATA (node),
webkit_dom_character_data_get_length (WEBKIT_DOM_CHARACTER_DATA (node)),
1,
"",
NULL);
}
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, *first_child;
if (!webkit_dom_node_has_child_nodes (block))
return WEBKIT_DOM_ELEMENT (block);
/* Avoid wrapping when the block contains just the BR element alone
* or with selection markers. */
if ((first_child = webkit_dom_node_get_first_child (block)) &&
WEBKIT_DOM_IS_HTMLBR_ELEMENT (first_child)) {
WebKitDOMNode *next_sibling;
if ((next_sibling = webkit_dom_node_get_next_sibling (first_child))) {
if (e_html_editor_node_is_selection_position_node (next_sibling) &&
(next_sibling = webkit_dom_node_get_next_sibling (next_sibling)) &&
e_html_editor_node_is_selection_position_node (next_sibling) &&
!webkit_dom_node_get_next_sibling (next_sibling))
return WEBKIT_DOM_ELEMENT (block);
} else
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) {
WebKitDOMNode *parent;
parent = webkit_dom_node_get_parent_node (nd);
if (!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent))
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);
if (!webkit_dom_node_get_next_sibling (nd) &&
g_str_has_suffix (nd_content, " "))
mark_and_remove_trailing_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) {
gboolean no_sibling = FALSE;
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);
if (!webkit_dom_node_get_next_sibling (nd) &&
length_left <= length_to_wrap &&
g_str_has_suffix (nd_content, " ")) {
mark_and_remove_trailing_space (document, nd);
no_sibling = TRUE;
}
g_free (nd_content);
}
if (!no_sibling)
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);
if (no_sibling)
node = NULL;
else
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) {
gchar *plain_text_style;
style = g_strdup_printf ("margin-left: %dch;", SPACES_PER_INDENTATION);
plain_text_style = g_strdup_printf (
"margin-left: %dch; word-wrap: normal; width: %dch;",
SPACES_PER_INDENTATION, word_wrap_length);
webkit_dom_element_set_attribute (
element, "data-plain-text-style", plain_text_style, NULL);
g_free (plain_text_style);
} 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 {
/* 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) &&
!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent_node)) {
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);
}