/*
* 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))
#define UNICODE_ZERO_WIDTH_SPACE "\xe2\x80\x8b"
#define UNICODE_NBSP "\xc2\xa0"
#define SPACES_PER_INDENTATION 4
#define SPACES_PER_LIST_LEVEL 8
/**
* 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;
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 };
G_DEFINE_TYPE (
EHTMLEditorSelection,
e_html_editor_selection,
G_TYPE_OBJECT
);
static WebKitDOMRange *
html_editor_selection_get_current_range (EHTMLEditorSelection *selection)
{
EHTMLEditorView *view;
WebKitWebView *web_view;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *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);
web_view = WEBKIT_WEB_VIEW (view);
document = webkit_web_view_get_dom_document (web_view);
window = webkit_dom_document_get_default_view (document);
if (!window)
goto exit;
dom_selection = webkit_dom_dom_window_get_selection (window);
if (!WEBKIT_DOM_IS_DOM_SELECTION (dom_selection))
goto exit;
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 (view);
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);
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 = ((strlen (element_tag) == 10 /* strlen ("blockquote") */) &&
(g_ascii_strncasecmp (element_tag, "blockquote", 10) == 0));
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 && g_ascii_strncasecmp (element_tag, "blockquote", 10) == 0) {
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);
element = e_html_editor_dom_node_find_parent_element (node, "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 *webview,
EHTMLEditorSelection *selection)
{
g_object_freeze_notify (G_OBJECT (selection));
g_object_notify (G_OBJECT (selection), "alignment");
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), "block-format");
g_object_notify (G_OBJECT (selection), "indented");
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), "text");
g_object_notify (G_OBJECT (selection), "underline");
g_object_thaw_notify (G_OBJECT (selection));
}
void
e_html_editor_selection_block_selection_changed (EHTMLEditorSelection *selection)
{
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);
}
void
e_html_editor_selection_unblock_selection_changed (EHTMLEditorSelection *selection)
{
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);
}
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;
/**
* EHTMLEditorSelectionalignment
*
* 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));
/**
* EHTMLEditorSelectionbackground-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));
/**
* EHTMLEditorSelectionblock-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));
/**
* EHTMLEditorSelectionbold
*
* 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));
/**
* EHTMLEditorSelectionfont-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));
/**
* EHTMLEditorSelectionfont-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));
/**
* EHTMLEditorSelectionfont-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));
/**
* EHTMLEditorSelectionindented
*
* 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));
/**
* EHTMLEditorSelectionitalic
*
* 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));
/**
* EHTMLEditorSelectionmonospaced
*
* 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));
/**
* EHTMLEditorSelectionstrikethrough
*
* 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));
/**
* EHTMLEditorSelectionsuperscript
*
* 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));
/**
* EHTMLEditorSelectionsubscript
*
* 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));
/**
* EHTMLEditorSelectiontext
*
* 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));
/**
* EHTMLEditorSelectionunderline
*
* 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 = g_settings_new ("org.gnome.evolution.mail");
selection->priv->word_wrap_length =
g_settings_get_int (g_settings, "composer-word-wrap-length");
g_object_unref (g_settings);
}
gint
e_html_editor_selection_get_word_wrap_length (EHTMLEditorSelection *selection)
{
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), 72);
return selection->priv->word_wrap_length;
}
/**
* e_html_editor_selection_ref_html_editor_view:
* @selection: an #EHTMLEditorSelection
*
* Returns a new reference to @selection's #EHTMLEditorView. Unreference
* the #EHTMLEditorView with g_object_unref() when finished with it.
*
* Returns: an #EHTMLEditorView
**/
EHTMLEditorView *
e_html_editor_selection_ref_html_editor_view (EHTMLEditorSelection *selection)
{
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL);
return g_weak_ref_get (&selection->priv->html_editor_view);
}
/**
* e_html_editor_selection_has_text:
* @selection: an #EHTMLEditorSelection
*
* Returns whether current selection contains any text.
*
* Returns: @TRUE when current selection contains text, @FALSE otherwise.
*/
gboolean
e_html_editor_selection_has_text (EHTMLEditorSelection *selection)
{
WebKitDOMRange *range;
WebKitDOMNode *node;
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
range = html_editor_selection_get_current_range (selection);
node = webkit_dom_range_get_start_container (range, NULL);
if (webkit_dom_node_get_node_type (node) == 3)
return TRUE;
node = webkit_dom_range_get_end_container (range, NULL);
if (webkit_dom_node_get_node_type (node) == 3)
return TRUE;
node = WEBKIT_DOM_NODE (webkit_dom_range_clone_contents (range, NULL));
while (node) {
if (webkit_dom_node_get_node_type (node) == 3)
return TRUE;
if (webkit_dom_node_has_child_nodes (node)) {
node = webkit_dom_node_get_first_child (node);
} else if (webkit_dom_node_get_next_sibling (node)) {
node = webkit_dom_node_get_next_sibling (node);
} else {
node = webkit_dom_node_get_parent_node (node);
if (node) {
node = webkit_dom_node_get_next_sibling (node);
}
}
}
return FALSE;
}
/**
* e_html_editor_selection_get_caret_word:
* @selection: an #EHTMLEditorSelection
*
* Returns word under cursor.
*
* Returns: A newly allocated string with current caret word or @NULL when there
* is no text under cursor or when selection is active. [transfer-full].
*/
gchar *
e_html_editor_selection_get_caret_word (EHTMLEditorSelection *selection)
{
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);
return webkit_dom_range_to_string (range, NULL);
}
/**
* 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;
WebKitDOMDOMWindow *window;
WebKitDOMDOMSelection *dom_selection;
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);
window = webkit_dom_document_get_default_view (document);
dom_selection = webkit_dom_dom_window_get_selection (window);
webkit_dom_range_expand (range, "word", NULL);
webkit_dom_dom_selection_add_range (dom_selection, range);
e_html_editor_selection_insert_html (selection, replacement);
g_object_unref (view);
}
/**
* 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);
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;
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);
e_html_editor_view_exec_command (
view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_TEXT, new_string);
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-list-item-align-left"))
return E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT;
if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-list-item-align-center"))
return E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER;
if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-list-item-align-right"))
return E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT;
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;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *window;
document = webkit_dom_node_get_owner_document (node);
window = webkit_dom_document_get_default_view (document);
style = webkit_dom_dom_window_get_computed_style (
window, WEBKIT_DOM_ELEMENT (node), NULL);
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_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;
EHTMLEditorView *view;
gchar *value;
WebKitDOMCSSStyleDeclaration *style;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *window;
WebKitDOMElement *element;
WebKitDOMNode *node;
WebKitDOMRange *range;
g_return_val_if_fail (
E_IS_HTML_EDITOR_SELECTION (selection),
E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT);
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);
window = webkit_dom_document_get_default_view (document);
range = html_editor_selection_get_current_range (selection);
if (!range)
return E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT;
node = webkit_dom_range_get_start_container (range, NULL);
if (!node)
return E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT;
if (WEBKIT_DOM_IS_ELEMENT (node))
element = WEBKIT_DOM_ELEMENT (node);
else
element = webkit_dom_node_get_parent_element (node);
style = webkit_dom_dom_window_get_computed_style (window, element, NULL);
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_free (value);
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)
{
WebKitDOMElement *list;
gint offset = -SPACES_PER_LIST_LEVEL;
gboolean 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);
set_ordered_list_type_to_element (list, format);
if (level >= 0)
offset = (level + 1) * -SPACES_PER_LIST_LEVEL;
if (!html_mode)
e_html_editor_selection_set_paragraph_style (
selection, list, -1, offset, "");
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 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;
new_list = create_list_element (selection, document, to, 0, html_mode);
/* 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);
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) {
WebKitDOMNode *next_item = webkit_dom_node_get_next_sibling (item);
if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) {
webkit_dom_node_append_child (
after_selection_end ?
source_list_clone : WEBKIT_DOM_NODE (new_list),
WEBKIT_DOM_NODE (item),
NULL);
}
if (webkit_dom_node_contains (item, WEBKIT_DOM_NODE (selection_end_marker))) {
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);
next_item = webkit_dom_node_get_first_child (current_list);
}
item = next_item;
}
if (webkit_dom_node_has_child_nodes (source_list_clone))
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),
WEBKIT_DOM_NODE (new_list),
webkit_dom_node_get_next_sibling (source_list), NULL);
if (!webkit_dom_node_has_child_nodes (source_list))
remove_node (source_list);
}
/**
* 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)
{
EHTMLEditorView *view;
EHTMLEditorViewCommand command;
const gchar *class = "";
WebKitDOMDocument *document;
WebKitDOMElement *selection_start_marker;
WebKitDOMNode *parent;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
if (e_html_editor_selection_get_alignment (selection) == alignment)
return;
switch (alignment) {
case E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER:
command = E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_CENTER;
class = "-x-evo-list-item-align-center";
break;
case E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT:
command = E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_LEFT;
break;
case E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT:
command = E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_RIGHT;
class = "-x-evo-list-item-align-right";
break;
}
selection->priv->alignment = alignment;
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);
if (!selection_start_marker) {
g_object_unref (view);
return;
}
parent = webkit_dom_node_get_parent_node (
WEBKIT_DOM_NODE (selection_start_marker));
if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (parent)) {
element_remove_class (
WEBKIT_DOM_ELEMENT (parent),
"-x-evo-list-item-align-center");
element_remove_class (
WEBKIT_DOM_ELEMENT (parent),
"-x-evo-list-item-align-right");
element_add_class (WEBKIT_DOM_ELEMENT (parent), class);
} else {
e_html_editor_view_exec_command (view, command, NULL);
}
e_html_editor_selection_restore (selection);
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)
{
WebKitDOMNode *ancestor;
WebKitDOMRange *range;
WebKitDOMCSSStyleDeclaration *css;
g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL);
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));
selection->priv->background_color =
webkit_dom_css_style_declaration_get_property_value (
css, "background-color");
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);
if (!WEBKIT_DOM_IS_ELEMENT (node))
node = WEBKIT_DOM_NODE (webkit_dom_node_get_parent_element (node));
if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-temp-text-wrapper"))
node = WEBKIT_DOM_NODE (webkit_dom_node_get_parent_element (node));
return node;
}
/**
* e_html_editor_selection_get_list_format_from_node:
* @node: an #WebKitDOMNode
*
* Returns block format of given list.
*
* Returns: #EHTMLEditorSelectionBlockFormat
*/
EHTMLEditorSelectionBlockFormat
e_html_editor_selection_get_list_format_from_node (WebKitDOMNode *node)
{
EHTMLEditorSelectionBlockFormat format =
E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST;
if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (node))
return -1;
if (WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (node))
return format;
if (WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (node)) {
gchar *type_value = webkit_dom_element_get_attribute (
WEBKIT_DOM_ELEMENT (node), "type");
if (!type_value)
return E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST;
if (!*type_value)
format = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST;
else if (g_ascii_strcasecmp (type_value, "A") == 0)
format = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA;
else if (g_ascii_strcasecmp (type_value, "I") == 0)
format = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ROMAN;
g_free (type_value);
return format;
}
return -1;
}
/**
* 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 (e_html_editor_dom_node_find_parent_element (node, "UL")) {
result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST;
} else if ((element = e_html_editor_dom_node_find_parent_element (node, "OL")) != NULL) {
result = e_html_editor_selection_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 (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;
}
return result;
}
static gboolean
is_caret_position_node (WebKitDOMNode *node)
{
return element_has_id (WEBKIT_DOM_ELEMENT (node), "-x-evo-caret-position");
}
static void
merge_list_into_list (WebKitDOMNode *from,
WebKitDOMNode *to,
gboolean insert_before)
{
WebKitDOMNode *item;
if (!(to && from))
return;
while ((item = webkit_dom_node_get_first_child (from)) != NULL) {
if (insert_before)
webkit_dom_node_insert_before (
to, item, webkit_dom_node_get_last_child (to), NULL);
else
webkit_dom_node_append_child (to, item, NULL);
}
if (!webkit_dom_node_has_child_nodes (from))
remove_node (from);
}
static void
merge_lists_if_possible (WebKitDOMNode *list)
{
EHTMLEditorSelectionBlockFormat format, prev, next;
WebKitDOMNode *prev_sibling, *next_sibling;
prev_sibling = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (list));
next_sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (list));
format = e_html_editor_selection_get_list_format_from_node (list),
prev = e_html_editor_selection_get_list_format_from_node (prev_sibling);
next = e_html_editor_selection_get_list_format_from_node (next_sibling);
if (format == prev && format != -1 && prev != -1)
merge_list_into_list (prev_sibling, list, TRUE);
if (format == next && format != -1 && next != -1)
merge_list_into_list (next_sibling, list, FALSE);
}
void
remove_wrapping_from_element (WebKitDOMElement *element)
{
WebKitDOMNodeList *list;
gint ii, length;
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++)
remove_node (webkit_dom_node_list_item (list, ii));
webkit_dom_node_normalize (WEBKIT_DOM_NODE (element));
}
void
remove_quoting_from_element (WebKitDOMElement *element)
{
gint ii, length;
WebKitDOMNodeList *list;
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++)
remove_node (webkit_dom_node_list_item (list, ii));
list = webkit_dom_element_query_selector_all (
element, "span.-x-evo-temp-text-wrapper", NULL);
length = webkit_dom_node_list_get_length (list);
for (ii = 0; ii < length; ii++) {
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node);
while (webkit_dom_node_get_first_child (node))
webkit_dom_node_insert_before (
parent,
webkit_dom_node_get_first_child (node),
node,
NULL);
remove_node (node);
}
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++)
remove_node (webkit_dom_node_list_item (list, ii));
webkit_dom_node_normalize (WEBKIT_DOM_NODE (element));
}
static gboolean
node_is_list (WebKitDOMNode *node)
{
return node && (
WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (node) ||
WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (node));
}
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 WebKitDOMNode *
get_parent_block_node_from_child (WebKitDOMNode *node)
{
WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node);
if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-temp-text-wrapper") ||
WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent) ||
element_has_tag (WEBKIT_DOM_ELEMENT (parent), "b") ||
element_has_tag (WEBKIT_DOM_ELEMENT (parent), "i") ||
element_has_tag (WEBKIT_DOM_ELEMENT (parent), "u"))
parent = webkit_dom_node_get_parent_node (parent);
return parent;
}
static void
format_change_block_to_block (EHTMLEditorSelection *selection,
EHTMLEditorSelectionBlockFormat format,
EHTMLEditorView *view,
const gchar *value,
WebKitDOMDocument *document)
{
gboolean after_selection_end, html_mode;
WebKitDOMElement *selection_start_marker, *selection_end_marker, *element;
WebKitDOMNode *block, *next_block;
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;
body = webkit_dom_document_get_body (document);
selection_start_marker = webkit_dom_document_create_element (
document, "SPAN", NULL);
webkit_dom_element_set_id (
selection_start_marker, "-x-evo-selection-start-marker");
webkit_dom_node_insert_before (
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)),
WEBKIT_DOM_NODE (selection_start_marker),
webkit_dom_node_get_first_child (
webkit_dom_node_get_first_child (
WEBKIT_DOM_NODE (body))),
NULL);
selection_end_marker = webkit_dom_document_create_element (
document, "SPAN", NULL);
webkit_dom_element_set_id (
selection_end_marker, "-x-evo-selection-end-marker");
webkit_dom_node_insert_before (
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)),
WEBKIT_DOM_NODE (selection_end_marker),
webkit_dom_node_get_first_child (
webkit_dom_node_get_first_child (
WEBKIT_DOM_NODE (body))),
NULL);
}
block = 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) {
gboolean quoted = FALSE;
gboolean empty = FALSE;
gchar *content;
WebKitDOMNode *child;
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));
next_block = webkit_dom_node_get_next_sibling (block);
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);
content = webkit_dom_node_get_text_content (block);
empty = !*content || (g_strcmp0 (content, UNICODE_ZERO_WIDTH_SPACE) == 0);
g_free (content);
if (empty) {
webkit_dom_html_element_set_inner_html (
WEBKIT_DOM_HTML_ELEMENT (element),
UNICODE_ZERO_WIDTH_SPACE,
NULL);
}
while ((child = webkit_dom_node_get_first_child (block)))
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (element), child, NULL);
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (block),
WEBKIT_DOM_NODE (element),
block,
NULL);
remove_node (block);
block = next_block;
if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH && !html_mode) {
gint citation_level, quote;
citation_level = get_citation_level (WEBKIT_DOM_NODE (element));
quote = citation_level ? citation_level * 2 : 0;
element = e_html_editor_selection_wrap_paragraph_length (
selection, element, selection->priv->word_wrap_length - quote);
}
if (quoted)
e_html_editor_view_quote_plain_text_element (view, element);
if (after_selection_end)
break;
}
e_html_editor_selection_restore (selection);
}
static void
format_change_block_to_list (EHTMLEditorSelection *selection,
EHTMLEditorSelectionBlockFormat format,
EHTMLEditorView *view,
WebKitDOMDocument *document)
{
gboolean after_selection_end, 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;
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;
body = webkit_dom_document_get_body (document);
selection_start_marker = webkit_dom_document_create_element (
document, "SPAN", NULL);
webkit_dom_element_set_id (
selection_start_marker, "-x-evo-selection-start-marker");
webkit_dom_node_insert_before (
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)),
WEBKIT_DOM_NODE (selection_start_marker),
webkit_dom_node_get_first_child (
webkit_dom_node_get_first_child (
WEBKIT_DOM_NODE (body))),
NULL);
selection_end_marker = webkit_dom_document_create_element (
document, "SPAN", NULL);
webkit_dom_element_set_id (
selection_end_marker, "-x-evo-selection-end-marker");
webkit_dom_node_insert_before (
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)),
WEBKIT_DOM_NODE (selection_end_marker),
webkit_dom_node_get_first_child (
webkit_dom_node_get_first_child (
WEBKIT_DOM_NODE (body))),
NULL);
}
block = 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;
in_quote = TRUE;
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (block),
e_html_editor_selection_get_caret_position_node (document),
block,
NULL);
e_html_editor_selection_restore_caret_position (selection);
e_html_editor_view_exec_command (
view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT, NULL);
element = webkit_dom_document_query_selector (
document, "body>br", NULL);
webkit_dom_node_replace_child (
webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
WEBKIT_DOM_NODE (list),
WEBKIT_DOM_NODE (element),
NULL);
block = 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) {
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);
/* We have to use again the hidden space to move caret into newly inserted list */
if (empty) {
webkit_dom_html_element_set_inner_html (
WEBKIT_DOM_HTML_ELEMENT (item),
UNICODE_ZERO_WIDTH_SPACE,
NULL);
}
while ((child = webkit_dom_node_get_first_child (block)))
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (item), child, 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;
if (after_selection_end)
break;
}
merge_lists_if_possible (WEBKIT_DOM_NODE (list));
e_html_editor_selection_restore (selection);
e_html_editor_view_force_spell_check (view);
}
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;
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);
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);
goto out;
}
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);
goto out;
}
}
prev = e_html_editor_selection_get_list_format_from_node (prev_list);
next = e_html_editor_selection_get_list_format_from_node (next_list);
if (format == prev && format != -1 && prev != -1) {
if (selection_starts_in_first_child && selection_ends_in_last_child) {
done = TRUE;
merge_list_into_list (current_list, prev_list, FALSE);
}
}
if (format == next && format != -1 && next != -1) {
if (selection_starts_in_first_child && selection_ends_in_last_child) {
done = TRUE;
merge_list_into_list (next_list, prev_list, FALSE);
}
}
if (done)
goto out;
format_change_list_from_list (selection, document, format, html_mode);
out:
e_html_editor_selection_restore (selection);
}
static void
format_change_list_to_block (EHTMLEditorSelection *selection,
EHTMLEditorSelectionBlockFormat format,
WebKitDOMDocument *document)
{
gboolean after_end = FALSE;
WebKitDOMElement *selection_start, *element, *selection_end;
WebKitDOMNode *source_list, *next_item, *item, *source_list_clone;
e_html_editor_selection_save (selection);
selection_start = webkit_dom_document_query_selector (
document, "span#-x-evo-selection-start-marker", NULL);
selection_end = webkit_dom_document_query_selector (
document, "span#-x-evo-selection-end-marker", NULL);
item = get_list_item_node_from_child (
WEBKIT_DOM_NODE (selection_start));
source_list = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (item));
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);
next_item = item;
/* Process all nodes that are in selection one by one */
while (next_item) {
WebKitDOMNode *tmp;
tmp = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (next_item));
if (!after_end) {
if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH)
element = e_html_editor_selection_get_paragraph_element (selection, document, -1, 0);
else if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PRE)
element = webkit_dom_document_create_element (document, "PRE", NULL);
else if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE)
element = webkit_dom_document_create_element (document, "BLOCKQUOTE", NULL);
else
element = e_html_editor_selection_get_paragraph_element (selection, document, -1, 0);
after_end = webkit_dom_node_contains (next_item, WEBKIT_DOM_NODE (selection_end));
while (webkit_dom_node_get_first_child (next_item)) {
WebKitDOMNode *node = webkit_dom_node_get_first_child (next_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_clone,
NULL);
remove_node (next_item);
next_item = tmp;
} else {
webkit_dom_node_append_child (
source_list_clone, next_item, NULL);
next_item = tmp;
}
}
remove_node_if_empty (source_list_clone);
remove_node_if_empty (source_list);
e_html_editor_selection_restore (selection);
}
/**
* 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;
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;
}
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, document);
if (!from_list && to_list)
format_change_block_to_list (selection, format, view, document);
e_html_editor_view_force_spell_check (view);
g_object_unref (view);
/* When changing the format we need to re-set the alignment */
e_html_editor_selection_set_alignment (selection, selection->priv->alignment);
e_html_editor_view_set_changed (view, TRUE);
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)
{
gchar *color;
g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
if (g_strcmp0 (e_html_editor_selection_get_string (selection), "") == 0) {
color = g_strdup (selection->priv->font_color);
} else {
color = get_font_property (selection, "color");
if (!color) {
*rgba = black;
return;
}
}
gdk_rgba_parse (rgba, color);
g_free (color);
}
/**
* 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;
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);
selection->priv->font_color = g_strdup (color);
e_html_editor_view_exec_command (view, command, color);
g_free (color);
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_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");
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;
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)
return E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL;
size_int = atoi (size);
g_free (size);
if (size_int == 0)
return E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL;
return size_int;
}
/**
* 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;
gchar *size_str;
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);
selection->priv->font_size = font_size;
command = E_HTML_EDITOR_VIEW_COMMAND_FONT_SIZE;
size_str = g_strdup_printf ("%d", font_size);
e_html_editor_view_exec_command (view, command, size_str);
g_free (size_str);
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);
if (WEBKIT_DOM_IS_TEXT (node))
return get_has_style (selection, "citation");
/* If we are changing the format of block we have to re-set bold property,
* otherwise it will be turned off because of no text in composer */
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 (g_strstr_len (value, -1, "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)) {
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);
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 (g_strcmp0 (e_html_editor_selection_get_string (selection), "") == 0) {
element = get_element_for_inspection (range);
return element_has_class (element, "-x-evo-indented");
} else {
/* If there is a selection search in it and don't look just in
* the end container */
WebKitDOMDocumentFragment *fragment;
fragment = webkit_dom_range_clone_contents (range, NULL);
if (fragment) {
element = webkit_dom_document_fragment_query_selector (
fragment, ".-x-evo-indented", NULL);
if (element)
return TRUE;
element = get_element_for_inspection (range);
return element_has_class (element, "-x-evo-indented");
}
}
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 void
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 = 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 = e_html_editor_selection_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 = 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;
if (after_selection_end)
break;
}
merge_lists_if_possible (WEBKIT_DOM_NODE (list));
}
}
static void
indent_block (EHTMLEditorSelection *selection,
WebKitDOMDocument *document,
WebKitDOMNode *block,
gint width)
{
WebKitDOMElement *element;
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");
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (element),
block,
NULL);
}
/**
* e_html_editor_selection_indent:
* @selection: an #EHTMLEditorSelection
*
* Indents current paragraph by one level.
*/
void
e_html_editor_selection_indent (EHTMLEditorSelection *selection)
{
EHTMLEditorView *view;
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;
body = webkit_dom_document_get_body (document);
selection_start_marker = webkit_dom_document_create_element (
document, "SPAN", NULL);
webkit_dom_element_set_id (
selection_start_marker, "-x-evo-selection-start-marker");
webkit_dom_node_insert_before (
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)),
WEBKIT_DOM_NODE (selection_start_marker),
webkit_dom_node_get_first_child (
webkit_dom_node_get_first_child (
WEBKIT_DOM_NODE (body))),
NULL);
selection_end_marker = webkit_dom_document_create_element (
document, "SPAN", NULL);
webkit_dom_element_set_id (
selection_end_marker, "-x-evo-selection-end-marker");
webkit_dom_node_insert_before (
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)),
WEBKIT_DOM_NODE (selection_end_marker),
webkit_dom_node_get_first_child (
webkit_dom_node_get_first_child (
WEBKIT_DOM_NODE (body))),
NULL);
}
block = get_parent_indented_block (
WEBKIT_DOM_NODE (selection_start_marker));
if (!block)
block = get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
while (block) {
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 (block)) {
indent_list (selection, document);
if (!after_selection_end) {
block = next_block;
continue;
} else
goto out;
}
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) {
block = next_block;
continue;
}
}
level = get_indentation_level (WEBKIT_DOM_ELEMENT (block));
final_width = word_wrap_length - SPACES_PER_INDENTATION * (level + 1);
if (final_width < 10 && !is_in_html_mode (selection)) {
if (!after_selection_end) {
block = next_block;
continue;
} else
goto out;
}
indent_block (selection, document, block, final_width);
if (after_selection_end)
goto out;
}
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)
continue;
}
level = get_indentation_level (
WEBKIT_DOM_ELEMENT (block_to_process));
final_width = word_wrap_length - SPACES_PER_INDENTATION * (level + 1);
if (final_width < 10 && !is_in_html_mode (selection))
continue;
indent_block (selection, document, block_to_process, final_width);
if (after_selection_end)
goto out;
}
block = next_block;
}
out:
e_html_editor_selection_restore (selection);
e_html_editor_view_force_spell_check (view);
g_object_unref (view);
g_object_notify (G_OBJECT (selection), "indented");
}
static const gchar *
get_css_alignment_value (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 "text-align: center;";
if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT)
return "text-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 = 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;
const gchar *align_value;
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, *child;
block_to_process = block;
alignment = e_html_editor_selection_get_alignment_from_node (block_to_process);
align_value = get_css_alignment_value (alignment);
element = webkit_dom_node_get_parent_element (block_to_process);
if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (element))
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);
}
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, align_value);
/* 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);
/* 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;
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;
body = webkit_dom_document_get_body (document);
selection_start_marker = webkit_dom_document_create_element (
document, "SPAN", NULL);
webkit_dom_element_set_id (
selection_start_marker, "-x-evo-selection-start-marker");
webkit_dom_node_insert_before (
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)),
WEBKIT_DOM_NODE (selection_start_marker),
webkit_dom_node_get_first_child (
webkit_dom_node_get_first_child (
WEBKIT_DOM_NODE (body))),
NULL);
selection_end_marker = webkit_dom_document_create_element (
document, "SPAN", NULL);
webkit_dom_element_set_id (
selection_end_marker, "-x-evo-selection-end-marker");
webkit_dom_node_insert_before (
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)),
WEBKIT_DOM_NODE (selection_end_marker),
webkit_dom_node_get_first_child (
webkit_dom_node_get_first_child (
WEBKIT_DOM_NODE (body))),
NULL);
}
block = get_parent_indented_block (
WEBKIT_DOM_NODE (selection_start_marker));
if (!block)
block = get_parent_block_node_from_child (
WEBKIT_DOM_NODE (selection_start_marker));
while (block) {
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 (block)) {
unindent_list (selection, document);
if (!after_selection_end) {
block = next_block;
continue;
} else
goto out;
}
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) {
block = next_block;
continue;
}
}
unindent_block (selection, document, block);
if (after_selection_end)
goto out;
}
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)
continue;
}
unindent_block (selection, document, block_to_process);
if (after_selection_end)
goto out;
}
block = next_block;
}
out:
e_html_editor_selection_restore (selection);
e_html_editor_view_force_spell_check (view);
g_object_unref (view);
g_object_notify (G_OBJECT (selection), "indented");
}
/**
* 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)
{
gboolean ret_val;
gchar *value, *text_content;
EHTMLEditorView *view;
WebKitDOMCSSStyleDeclaration *style;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *window;
WebKitDOMNode *node;
WebKitDOMElement *element;
WebKitDOMRange *range;
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)) {
g_object_unref (view);
return FALSE;
}
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
g_object_unref (view);
window = webkit_dom_document_get_default_view (document);
range = html_editor_selection_get_current_range (selection);
if (!range)
return FALSE;
node = webkit_dom_range_get_common_ancestor_container (range, NULL);
/* If we are changing the format of block we have to re-set bold property,
* otherwise it will be turned off because of no text in composer */
text_content = webkit_dom_node_get_text_content (node);
if (g_strcmp0 (text_content, "") == 0) {
g_free (text_content);
return selection->priv->is_bold;
}
g_free (text_content);
if (WEBKIT_DOM_IS_ELEMENT (node))
element = WEBKIT_DOM_ELEMENT (node);
else
element = webkit_dom_node_get_parent_element (node);
style = webkit_dom_dom_window_get_computed_style (window, element, NULL);
value = webkit_dom_css_style_declaration_get_property_value (style, "font-weight");
if (g_strstr_len (value, -1, "normal"))
ret_val = FALSE;
else
ret_val = TRUE;
g_free (value);
return ret_val;
}
/**
* 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)
{
EHTMLEditorView *view;
EHTMLEditorViewCommand command;
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;
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
command = E_HTML_EDITOR_VIEW_COMMAND_BOLD;
e_html_editor_view_exec_command (view, command, NULL);
g_object_unref (view);
g_object_notify (G_OBJECT (selection), "bold");
}
/**
* 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)
{
gboolean ret_val;
gchar *value, *text_content;
EHTMLEditorView *view;
WebKitDOMCSSStyleDeclaration *style;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *window;
WebKitDOMNode *node;
WebKitDOMElement *element;
WebKitDOMRange *range;
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)) {
g_object_unref (view);
return FALSE;
}
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
g_object_unref (view);
window = webkit_dom_document_get_default_view (document);
range = html_editor_selection_get_current_range (selection);
if (!range)
return FALSE;
node = webkit_dom_range_get_common_ancestor_container (range, NULL);
/* If we are changing the format of block we have to re-set italic property,
* otherwise it will be turned off because of no text in composer */
text_content = webkit_dom_node_get_text_content (node);
if (g_strcmp0 (text_content, "") == 0) {
g_free (text_content);
return selection->priv->is_italic;
}
g_free (text_content);
if (WEBKIT_DOM_IS_ELEMENT (node))
element = WEBKIT_DOM_ELEMENT (node);
else
element = webkit_dom_node_get_parent_element (node);
style = webkit_dom_dom_window_get_computed_style (window, element, NULL);
value = webkit_dom_css_style_declaration_get_property_value (style, "font-style");
if (g_strstr_len (value, -1, "italic"))
ret_val = TRUE;
else
ret_val = FALSE;
g_free (value);
return ret_val;
}
/**
* 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)
{
EHTMLEditorView *view;
EHTMLEditorViewCommand command;
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;
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
command = E_HTML_EDITOR_VIEW_COMMAND_ITALIC;
e_html_editor_view_exec_command (view, command, NULL);
g_object_unref (view);
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)
{
gboolean ret_val;
gchar *value, *text_content;
EHTMLEditorView *view;
WebKitDOMCSSStyleDeclaration *style;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *window;
WebKitDOMNode *node;
WebKitDOMElement *element;
WebKitDOMRange *range;
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)) {
g_object_unref (view);
return FALSE;
}
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
g_object_unref (view);
window = webkit_dom_document_get_default_view (document);
range = html_editor_selection_get_current_range (selection);
if (!range)
return FALSE;
node = webkit_dom_range_get_common_ancestor_container (range, NULL);
/* If we are changing the format of block we have to re-set italic property,
* otherwise it will be turned off because of no text in composer */
text_content = webkit_dom_node_get_text_content (node);
if (g_strcmp0 (text_content, "") == 0) {
g_free (text_content);
return selection->priv->is_monospaced;
}
g_free (text_content);
if (WEBKIT_DOM_IS_ELEMENT (node))
element = WEBKIT_DOM_ELEMENT (node);
else
element = webkit_dom_node_get_parent_element (node);
style = webkit_dom_dom_window_get_computed_style (window, element, NULL);
value = webkit_dom_css_style_declaration_get_property_value (style, "font-family");
if (g_strstr_len (value, -1, "monospace"))
ret_val = TRUE;
else
ret_val = FALSE;
g_free (value);
return ret_val;
}
static void
move_caret_into_element (WebKitDOMDocument *document,
WebKitDOMElement *element)
{
WebKitDOMDOMWindow *window;
WebKitDOMDOMSelection *window_selection;
WebKitDOMRange *new_range;
if (!element)
return;
window = webkit_dom_document_get_default_view (document);
window_selection = webkit_dom_dom_window_get_selection (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, FALSE, NULL);
webkit_dom_dom_selection_remove_all_ranges (window_selection);
webkit_dom_dom_selection_add_range (window_selection, new_range);
}
/**
* 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;
WebKitWebView *web_view;
WebKitDOMDocument *document;
WebKitDOMRange *range;
WebKitDOMDOMWindow *window;
WebKitDOMDOMSelection *window_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);
web_view = WEBKIT_WEB_VIEW (view);
document = webkit_web_view_get_dom_document (web_view);
window = webkit_dom_document_get_default_view (document);
window_selection = webkit_dom_dom_window_get_selection (window);
if (monospaced) {
gchar *font_size_str;
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)
font_size = E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL;
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 (g_strcmp0 (e_html_editor_selection_get_string (selection), "") != 0) {
gchar *html, *outer_html;
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (monospace),
WEBKIT_DOM_NODE (
webkit_dom_range_clone_contents (range, NULL)),
NULL);
outer_html = webkit_dom_html_element_get_outer_html (
WEBKIT_DOM_HTML_ELEMENT (monospace));
html = g_strconcat (
/* Mark selection for restoration */
"<span id=\"-x-evo-selection-start-marker\"></span>",
outer_html,
"<span id=\"-x-evo-selection-end-marker\"></span>",
NULL),
e_html_editor_selection_insert_html (selection, html);
e_html_editor_selection_restore (selection);
g_free (html);
g_free (outer_html);
} 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);
move_caret_into_element (document, monospace);
}
} 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);
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 (font_size == 0)
font_size = E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL;
if (g_strcmp0 (e_html_editor_selection_get_string (selection), "") != 0) {
gchar *html, *outer_html, *inner_html, *beginning, *end;
gchar *start_position, *end_position, *font_size_str;
WebKitDOMElement *wrapper;
wrapper = webkit_dom_document_create_element (
document, "SPAN", NULL);
webkit_dom_element_set_id (wrapper, "-x-evo-remove-tt");
webkit_dom_range_surround_contents (
range, WEBKIT_DOM_NODE (wrapper), NULL);
html = webkit_dom_html_element_get_outer_html (
WEBKIT_DOM_HTML_ELEMENT (tt_element));
start_position = g_strstr_len (
html, -1, "<span id=\"-x-evo-remove-tt\"");
end_position = g_strstr_len (start_position, -1, "</span>");
beginning = g_utf8_substring (
html, 0, g_utf8_pointer_to_offset (html, start_position));
inner_html = webkit_dom_html_element_get_inner_html (
WEBKIT_DOM_HTML_ELEMENT (wrapper));
end = g_utf8_substring (
html,
g_utf8_pointer_to_offset (html, end_position) + 7,
g_utf8_strlen (html, -1)),
font_size_str = g_strdup_printf ("%d", font_size);
outer_html =
g_strconcat (
/* Beginning */
beginning,
/* End the previous FONT tag */
"</font>",
/* Mark selection for restoration */
"<span id=\"-x-evo-selection-start-marker\"></span>",
/* Inside will be the same */
inner_html,
"<span id=\"-x-evo-selection-end-marker\"></span>",
/* Start the new FONT element */
"<font face=\"monospace\" size=\"",
font_size_str,
"\">",
/* End - we have to start after </span> */
end,
NULL),
g_free (font_size_str);
webkit_dom_html_element_set_outer_html (
WEBKIT_DOM_HTML_ELEMENT (tt_element),
outer_html,
NULL);
e_html_editor_selection_restore (selection);
g_free (html);
g_free (outer_html);
g_free (inner_html);
g_free (beginning);
g_free (end);
} else {
WebKitDOMRange *new_range;
gchar *outer_html;
gchar *tmp;
webkit_dom_element_set_id (tt_element, "ev-tt");
outer_html = webkit_dom_html_element_get_outer_html (
WEBKIT_DOM_HTML_ELEMENT (tt_element));
tmp = g_strconcat (outer_html, UNICODE_ZERO_WIDTH_SPACE, NULL);
webkit_dom_html_element_set_outer_html (
WEBKIT_DOM_HTML_ELEMENT (tt_element),
tmp, NULL);
/* We need to get that element again */
tt_element = webkit_dom_document_get_element_by_id (
document, "ev-tt");
webkit_dom_element_remove_attribute (
WEBKIT_DOM_ELEMENT (tt_element), "id");
new_range = webkit_dom_document_create_range (document);
webkit_dom_range_set_start_after (
new_range, WEBKIT_DOM_NODE (tt_element), NULL);
webkit_dom_range_set_end_after (
new_range, WEBKIT_DOM_NODE (tt_element), NULL);
webkit_dom_dom_selection_remove_all_ranges (
window_selection);
webkit_dom_dom_selection_add_range (
window_selection, new_range);
webkit_dom_dom_selection_modify (
window_selection, "move", "right", "character");
g_free (outer_html);
g_free (tmp);
e_html_editor_view_force_spell_check_for_current_paragraph (
view);
}
/* 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);
e_html_editor_selection_set_font_size (selection, font_size);
}
g_object_unref (view);
g_object_notify (G_OBJECT (selection), "monospaced");
}
/**
* 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)
{
gboolean ret_val;
gchar *value, *text_content;
EHTMLEditorView *view;
WebKitDOMCSSStyleDeclaration *style;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *window;
WebKitDOMNode *node;
WebKitDOMElement *element;
WebKitDOMRange *range;
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)) {
g_object_unref (view);
return FALSE;
}
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
g_object_unref (view);
window = webkit_dom_document_get_default_view (document);
range = html_editor_selection_get_current_range (selection);
if (!range)
return FALSE;
node = webkit_dom_range_get_common_ancestor_container (range, NULL);
/* If we are changing the format of block we have to re-set strikethrough property,
* otherwise it will be turned off because of no text in composer */
text_content = webkit_dom_node_get_text_content (node);
if (g_strcmp0 (text_content, "") == 0) {
g_free (text_content);
return selection->priv->is_strikethrough;
}
g_free (text_content);
if (WEBKIT_DOM_IS_ELEMENT (node))
element = WEBKIT_DOM_ELEMENT (node);
else
element = webkit_dom_node_get_parent_element (node);
style = webkit_dom_dom_window_get_computed_style (window, element, NULL);
value = webkit_dom_css_style_declaration_get_property_value (style, "text-decoration");
if (g_strstr_len (value, -1, "line-through"))
ret_val = TRUE;
else
ret_val = get_has_style (selection, "strike");
g_free (value);
return ret_val;
}
/**
* 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)
{
EHTMLEditorView *view;
EHTMLEditorViewCommand command;
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;
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
command = E_HTML_EDITOR_VIEW_COMMAND_STRIKETHROUGH;
e_html_editor_view_exec_command (view, command, NULL);
g_object_unref (view);
g_object_notify (G_OBJECT (selection), "strikethrough");
}
/**
* 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)
{
EHTMLEditorView *view;
WebKitDOMNode *node;
WebKitDOMRange *range;
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)) {
g_object_unref (view);
return FALSE;
}
g_object_unref (view);
range = html_editor_selection_get_current_range (selection);
node = webkit_dom_range_get_common_ancestor_container (range, NULL);
while (node) {
gchar *tag_name;
tag_name = webkit_dom_element_get_tag_name (WEBKIT_DOM_ELEMENT (node));
if (g_ascii_strncasecmp (tag_name, "sub", 3) == 0) {
g_free (tag_name);
break;
}
g_free (tag_name);
node = webkit_dom_node_get_parent_node (node);
}
return (node != 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");
}
/**
* 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)
{
EHTMLEditorView *view;
WebKitDOMNode *node;
WebKitDOMRange *range;
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)) {
g_object_unref (view);
return FALSE;
}
g_object_unref (view);
range = html_editor_selection_get_current_range (selection);
node = webkit_dom_range_get_common_ancestor_container (range, NULL);
while (node) {
gchar *tag_name;
tag_name = webkit_dom_element_get_tag_name (WEBKIT_DOM_ELEMENT (node));
if (g_ascii_strncasecmp (tag_name, "sup", 3) == 0) {
g_free (tag_name);
break;
}
g_free (tag_name);
node = webkit_dom_node_get_parent_node (node);
}
return (node != 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");
}
/**
* 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)
{
gboolean ret_val;
gchar *value, *text_content;
EHTMLEditorView *view;
WebKitDOMCSSStyleDeclaration *style;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *window;
WebKitDOMNode *node;
WebKitDOMElement *element;
WebKitDOMRange *range;
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)) {
g_object_unref (view);
return FALSE;
}
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
g_object_unref (view);
window = webkit_dom_document_get_default_view (document);
range = html_editor_selection_get_current_range (selection);
if (!range)
return FALSE;
node = webkit_dom_range_get_common_ancestor_container (range, NULL);
/* If we are changing the format of block we have to re-set underline property,
* otherwise it will be turned off because of no text in composer */
text_content = webkit_dom_node_get_text_content (node);
if (g_strcmp0 (text_content, "") == 0) {
g_free (text_content);
return selection->priv->is_underline;
}
g_free (text_content);
if (WEBKIT_DOM_IS_ELEMENT (node))
element = WEBKIT_DOM_ELEMENT (node);
else
element = webkit_dom_node_get_parent_element (node);
style = webkit_dom_dom_window_get_computed_style (window, element, NULL);
value = webkit_dom_css_style_declaration_get_property_value (style, "text-decoration");
if (g_strstr_len (value, -1, "underline"))
ret_val = TRUE;
else
ret_val = get_has_style (selection, "u");
g_free (value);
return ret_val;
}
/**
* 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)
{
EHTMLEditorView *view;
EHTMLEditorViewCommand command;
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;
view = e_html_editor_selection_ref_html_editor_view (selection);
g_return_if_fail (view != NULL);
command = E_HTML_EDITOR_VIEW_COMMAND_UNDERLINE;
e_html_editor_view_exec_command (view, command, NULL);
g_object_unref (view);
g_object_notify (G_OBJECT (selection), "underline");
}
/**
* e_html_editor_selection_unlink:
* @selection: an #EHTMLEditorSelection
*
* Removes any links (<A> elements) from current selection or at current
* cursor position.
*/
void
e_html_editor_selection_unlink (EHTMLEditorSelection *selection)
{
EHTMLEditorView *view;
EHTMLEditorViewCommand command;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *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));
window = webkit_dom_document_get_default_view (document);
dom_selection = webkit_dom_dom_window_get_selection (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");
if (!link) {
gchar *text;
/* get element that was clicked on */
link = e_html_editor_view_get_element_under_mouse_click (view);
if (!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (link))
link = NULL;
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);
} else {
command = E_HTML_EDITOR_VIEW_COMMAND_UNLINK;
e_html_editor_view_exec_command (view, command, NULL);
}
g_object_unref (view);
}
/**
* 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;
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);
e_html_editor_view_convert_and_insert_plain_text (view, plain_text);
g_object_unref (view);
}
/**
* 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;
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);
command = E_HTML_EDITOR_VIEW_COMMAND_INSERT_HTML;
if (e_html_editor_view_get_html_mode (view)) {
e_html_editor_view_exec_command (view, command, html_text);
e_html_editor_view_check_magic_links (view, FALSE);
e_html_editor_view_force_spell_check (view);
e_html_editor_selection_scroll_to_caret (selection);
} else
e_html_editor_view_convert_and_insert_html_to_plain_text (
view, html_text);
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;
WebKitDOMDocument *document;
WebKitDOMElement *element, *caret_position, *resizable_wrapper;
WebKitDOMText *text;
caret_position = e_html_editor_selection_save_caret_position (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_view_set_changed (view, TRUE);
g_object_unref (view);
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 (caret_position)),
WEBKIT_DOM_NODE (resizable_wrapper),
WEBKIT_DOM_NODE (caret_position),
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 (caret_position)),
WEBKIT_DOM_NODE (text),
WEBKIT_DOM_NODE (caret_position),
NULL);
e_html_editor_selection_restore_caret_position (selection);
}
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);
}
/**
* e_html_editor_selection_clear_caret_position_marker:
* @selection: an #EHTMLEditorSelection
*
* Removes previously set caret position marker from composer.
*/
void
e_html_editor_selection_clear_caret_position_marker (EHTMLEditorSelection *selection)
{
EHTMLEditorView *view;
WebKitDOMDocument *document;
WebKitDOMElement *element;
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));
element = webkit_dom_document_get_element_by_id (document, "-x-evo-caret-position");
if (element)
remove_node (WEBKIT_DOM_NODE (element));
g_object_unref (view);
}
WebKitDOMNode *
e_html_editor_selection_get_caret_position_node (WebKitDOMDocument *document)
{
WebKitDOMElement *element;
element = webkit_dom_document_create_element (document, "SPAN", NULL);
webkit_dom_element_set_id (element, "-x-evo-caret-position");
webkit_dom_element_set_attribute (
element, "style", "color: red", NULL);
webkit_dom_html_element_set_inner_html (
WEBKIT_DOM_HTML_ELEMENT (element), "*", NULL);
return WEBKIT_DOM_NODE (element);
}
/**
* e_html_editor_selection_save_caret_position:
* @selection: an #EHTMLEditorSelection
*
* Saves current caret position in composer.
*
* Returns: #WebKitDOMElement that was created on caret position
*/
WebKitDOMElement *
e_html_editor_selection_save_caret_position (EHTMLEditorSelection *selection)
{
EHTMLEditorView *view;
WebKitDOMDocument *document;
WebKitDOMNode *split_node;
WebKitDOMNode *start_offset_node;
WebKitDOMNode *caret_node;
WebKitDOMRange *range;
gulong start_offset;
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, NULL);
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
g_object_unref (view);
e_html_editor_selection_clear_caret_position_marker (selection);
range = html_editor_selection_get_current_range (selection);
if (!range)
return NULL;
start_offset = webkit_dom_range_get_start_offset (range, NULL);
start_offset_node = webkit_dom_range_get_end_container (range, NULL);
caret_node = e_html_editor_selection_get_caret_position_node (document);
if (WEBKIT_DOM_IS_TEXT (start_offset_node) && start_offset != 0) {
WebKitDOMText *split_text;
split_text = webkit_dom_text_split_text (
WEBKIT_DOM_TEXT (start_offset_node),
start_offset, NULL);
split_node = WEBKIT_DOM_NODE (split_text);
} else {
split_node = start_offset_node;
}
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (start_offset_node),
caret_node,
split_node,
NULL);
return WEBKIT_DOM_ELEMENT (caret_node);
}
static void
fix_quoting_nodes_after_caret_restoration (WebKitDOMDOMSelection *window_selection,
WebKitDOMNode *prev_sibling,
WebKitDOMNode *next_sibling)
{
WebKitDOMNode *tmp_node;
if (!element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-temp-text-wrapper"))
return;
webkit_dom_dom_selection_modify (
window_selection, "move", "forward", "character");
tmp_node = webkit_dom_node_get_next_sibling (
webkit_dom_node_get_first_child (prev_sibling));
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (prev_sibling),
tmp_node,
next_sibling,
NULL);
tmp_node = webkit_dom_node_get_first_child (prev_sibling);
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (prev_sibling),
tmp_node,
webkit_dom_node_get_previous_sibling (next_sibling),
NULL);
remove_node (prev_sibling);
webkit_dom_dom_selection_modify (
window_selection, "move", "backward", "character");
}
/**
* e_html_editor_selection_restore_caret_position:
* @selection: an #EHTMLEditorSelection
*
* Restores previously saved caret position in composer.
*/
void
e_html_editor_selection_restore_caret_position (EHTMLEditorSelection *selection)
{
EHTMLEditorView *view;
WebKitDOMDocument *document;
WebKitDOMElement *element;
gboolean fix_after_quoting;
gboolean swap_direction = FALSE;
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);
element = webkit_dom_document_get_element_by_id (
document, "-x-evo-caret-position");
fix_after_quoting = element_has_class (element, "-x-evo-caret-quoting");
if (element) {
WebKitDOMDOMWindow *window;
WebKitDOMNode *parent_node;
WebKitDOMDOMSelection *window_selection;
WebKitDOMNode *prev_sibling;
WebKitDOMNode *next_sibling;
if (!webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element)))
swap_direction = TRUE;
window = webkit_dom_document_get_default_view (document);
window_selection = webkit_dom_dom_window_get_selection (window);
parent_node = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
/* If parent is BODY element, we try to restore the position on the
* element that is next to us */
if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent_node)) {
/* Look if we have DIV on right */
next_sibling = webkit_dom_node_get_next_sibling (
WEBKIT_DOM_NODE (element));
if (!WEBKIT_DOM_IS_ELEMENT (next_sibling)) {
e_html_editor_selection_clear_caret_position_marker (selection);
return;
}
if (element_has_class (WEBKIT_DOM_ELEMENT (next_sibling), "-x-evo-paragraph")) {
remove_node (WEBKIT_DOM_NODE (element));
move_caret_into_element (
document, WEBKIT_DOM_ELEMENT (next_sibling));
goto out;
}
}
move_caret_into_element (document, element);
if (fix_after_quoting) {
prev_sibling = webkit_dom_node_get_previous_sibling (
WEBKIT_DOM_NODE (element));
next_sibling = webkit_dom_node_get_next_sibling (
WEBKIT_DOM_NODE (element));
if (!next_sibling)
fix_after_quoting = FALSE;
}
remove_node (WEBKIT_DOM_NODE (element));
if (fix_after_quoting)
fix_quoting_nodes_after_caret_restoration (
window_selection, prev_sibling, next_sibling);
out:
/* FIXME If caret position is restored and afterwards the
* position is saved it is not on the place where it supposed
* to be (it is in the beginning of parent's element. It can
* be avoided by moving with the caret. */
if (swap_direction) {
webkit_dom_dom_selection_modify (
window_selection, "move", "forward", "character");
webkit_dom_dom_selection_modify (
window_selection, "move", "backward", "character");
} else {
webkit_dom_dom_selection_modify (
window_selection, "move", "backward", "character");
webkit_dom_dom_selection_modify (
window_selection, "move", "forward", "character");
}
}
}
static gint
find_where_to_break_line (WebKitDOMNode *node,
gint max_len,
gint word_wrap_length)
{
gchar *str, *text_start;
gunichar uc;
gint pos;
gint last_space = 0;
gint length;
gint ret_val = 0;
gchar* position;
text_start = webkit_dom_character_data_get_data (WEBKIT_DOM_CHARACTER_DATA (node));
length = g_utf8_strlen (text_start, -1);
pos = 1;
last_space = 0;
str = text_start;
do {
uc = g_utf8_get_char (str);
if (!uc) {
g_free (text_start);
if (pos <= max_len)
return pos;
else
return last_space;
}
/* If last_space is zero then the word is longer than
* word_wrap_length characters, so continue until we find
* a space */
if ((pos > max_len) && (last_space > 0)) {
if (last_space > word_wrap_length) {
g_free (text_start);
return last_space - 1;
}
if (last_space > max_len) {
if (g_unichar_isspace (g_utf8_get_char (text_start)))
ret_val = 1;
g_free (text_start);
return ret_val;
}
if (last_space == max_len - 1) {
uc = g_utf8_get_char (str);
if (g_unichar_isspace (uc))
last_space++;
}
g_free (text_start);
return last_space;
}
if (g_unichar_isspace (uc))
last_space = pos;
pos += 1;
str = g_utf8_next_char (str);
} while (*str);
position = g_utf8_offset_to_pointer (text_start, max_len);
if (g_unichar_isspace (g_utf8_get_char (position))) {
ret_val = max_len + 1;
} else {
if (last_space == 0) {
/* If word is longer than word_wrap_length, we cannot wrap it */
ret_val = length;
} else if (last_space < max_len) {
ret_val = last_space;
} else {
if (length > word_wrap_length)
ret_val = last_space;
else
ret_val = 0;
}
}
g_free (text_start);
return ret_val;
}
static WebKitDOMElement *
wrap_lines (EHTMLEditorSelection *selection,
WebKitDOMNode *paragraph,
WebKitDOMDocument *document,
gboolean remove_all_br,
gint word_wrap_length)
{
WebKitDOMNode *node, *start_node;
WebKitDOMNode *paragraph_clone;
WebKitDOMDocumentFragment *fragment;
WebKitDOMElement *element;
WebKitDOMNodeList *wrap_br;
gint len, ii, br_count;
gulong length_left;
glong paragraph_char_count;
gchar *text_content;
if (selection) {
paragraph_char_count = g_utf8_strlen (
e_html_editor_selection_get_string (selection), -1);
fragment = webkit_dom_range_clone_contents (
html_editor_selection_get_current_range (selection), NULL);
/* 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 */
wrap_br = webkit_dom_document_fragment_query_selector_all (
fragment,
remove_all_br ? "br" : "br.-x-evo-wrap-br",
NULL);
} else {
WebKitDOMElement *caret_node;
if (!webkit_dom_node_has_child_nodes (paragraph))
return WEBKIT_DOM_ELEMENT (paragraph);
paragraph_clone = webkit_dom_node_clone_node (paragraph, TRUE);
caret_node = webkit_dom_element_query_selector (
WEBKIT_DOM_ELEMENT (paragraph_clone),
"span#-x-evo-caret-position", NULL);
text_content = webkit_dom_node_get_text_content (paragraph_clone);
paragraph_char_count = g_utf8_strlen (text_content, -1);
if (caret_node)
paragraph_char_count--;
g_free (text_content);
wrap_br = webkit_dom_element_query_selector_all (
WEBKIT_DOM_ELEMENT (paragraph_clone),
remove_all_br ? "br" : "br.-x-evo-wrap-br",
NULL);
}
/* And remove them */
br_count = webkit_dom_node_list_get_length (wrap_br);
for (ii = 0; ii < br_count; ii++)
remove_node (webkit_dom_node_list_item (wrap_br, ii));
if (selection)
node = WEBKIT_DOM_NODE (fragment);
else {
webkit_dom_node_normalize (paragraph_clone);
node = webkit_dom_node_get_first_child (paragraph_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);
}
}
start_node = node;
len = 0;
while (node) {
gint offset = 0;
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 = g_strstr_len (text_content, -1, "\n");
next_sibling = node;
while (newline) {
WebKitDOMElement *element;
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 = g_strstr_len (text_content, -1, "\n");
}
g_free (text_content);
} else {
/* If element is ANCHOR we wrap it separately */
if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node)) {
glong anchor_length;
text_content = webkit_dom_node_get_text_content (node);
anchor_length = g_utf8_strlen (text_content, -1);
if (len + anchor_length > word_wrap_length) {
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),
node,
NULL);
len = anchor_length;
} else
len += anchor_length;
g_free (text_content);
/* If there is space after the anchor don't try to
* wrap before it */
node = webkit_dom_node_get_next_sibling (node);
if (WEBKIT_DOM_IS_TEXT (node)) {
text_content = webkit_dom_node_get_text_content (node);
if (g_strcmp0 (text_content, " ") == 0) {
node = webkit_dom_node_get_next_sibling (node);
len++;
}
g_free (text_content);
}
continue;
}
if (is_caret_position_node (node)) {
node = webkit_dom_node_get_next_sibling (node);
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")) {
len = 0;
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 word_wrap_length characters, then just join it and continue to next
* node */
length_left = webkit_dom_character_data_get_length (
WEBKIT_DOM_CHARACTER_DATA (node));
if ((length_left + len) < word_wrap_length) {
len += length_left;
goto next_node;
}
/* wrap until we have something */
while ((length_left + len) > word_wrap_length) {
gint max_length;
max_length = word_wrap_length - len;
if (max_length < 0)
max_length = word_wrap_length;
/* Find where we can line-break the node so that it
* effectively fills the rest of current row */
offset = find_where_to_break_line (
node, max_length, word_wrap_length);
element = webkit_dom_document_create_element (document, "BR", NULL);
element_add_class (element, "-x-evo-wrap-br");
if (offset > 0 && offset <= word_wrap_length) {
if (offset != length_left)
webkit_dom_text_split_text (
WEBKIT_DOM_TEXT (node), offset, NULL);
if (webkit_dom_node_get_next_sibling (node)) {
gchar *nd_content;
WebKitDOMNode *nd = webkit_dom_node_get_next_sibling (node);
nd = webkit_dom_node_get_next_sibling (node);
nd_content = webkit_dom_node_get_text_content (nd);
if (nd_content && *nd_content) {
if (g_str_has_prefix (nd_content, " "))
webkit_dom_character_data_replace_data (
WEBKIT_DOM_CHARACTER_DATA (nd), 0, 1, "", NULL);
g_free (nd_content);
nd_content = webkit_dom_node_get_text_content (nd);
if (g_strcmp0 (nd_content, UNICODE_NBSP) == 0)
remove_node (nd);
g_free (nd_content);
}
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (node),
WEBKIT_DOM_NODE (element),
nd,
NULL);
} else {
webkit_dom_node_append_child (
webkit_dom_node_get_parent_node (node),
WEBKIT_DOM_NODE (element),
NULL);
}
} else if (offset > word_wrap_length) {
if (offset != length_left)
webkit_dom_text_split_text (
WEBKIT_DOM_TEXT (node), offset + 1, NULL);
if (webkit_dom_node_get_next_sibling (node)) {
gchar *nd_content;
WebKitDOMNode *nd = webkit_dom_node_get_next_sibling (node);
nd = webkit_dom_node_get_next_sibling (node);
nd_content = webkit_dom_node_get_text_content (nd);
if (nd_content && *nd_content) {
if (g_str_has_prefix (nd_content, " "))
webkit_dom_character_data_replace_data (
WEBKIT_DOM_CHARACTER_DATA (nd), 0, 1, "", NULL);
g_free (nd_content);
nd_content = webkit_dom_node_get_text_content (nd);
if (g_strcmp0 (nd_content, UNICODE_NBSP) == 0)
remove_node (nd);
g_free (nd_content);
}
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (node),
WEBKIT_DOM_NODE (element),
nd,
NULL);
} else {
webkit_dom_node_append_child (
webkit_dom_node_get_parent_node (node),
WEBKIT_DOM_NODE (element),
NULL);
}
len = 0;
break;
} else {
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (node),
WEBKIT_DOM_NODE (element),
node,
NULL);
}
length_left = webkit_dom_character_data_get_length (
WEBKIT_DOM_CHARACTER_DATA (node));
len = 0;
}
len += length_left - offset;
next_node:
if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (node))
len = 0;
/* 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 {
if (webkit_dom_node_is_equal_node (node, start_node))
break;
node = webkit_dom_node_get_parent_node (node);
if (node)
node = webkit_dom_node_get_next_sibling (node);
}
}
if (selection) {
gchar *html;
/* 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 be the processed content */
e_html_editor_selection_insert_html (selection, html);
g_free (html);
return NULL;
} else {
webkit_dom_node_normalize (paragraph_clone);
node = webkit_dom_node_get_parent_node (paragraph);
if (node) {
/* Replace paragraph with wrapped one */
webkit_dom_node_replace_child (
node, paragraph_clone, paragraph, NULL);
}
return WEBKIT_DOM_ELEMENT (paragraph_clone);
}
}
void
e_html_editor_selection_set_indented_style (EHTMLEditorSelection *selection,
WebKitDOMElement *element,
gint width)
{
EHTMLEditorSelectionAlignment alignment;
gchar *style;
const gchar *align_value;
gint word_wrap_length = (width == -1) ? selection->priv->word_wrap_length : width;
gint start = 0, end = 0;
alignment = e_html_editor_selection_get_alignment (selection);
align_value = get_css_alignment_value (alignment);
if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT)
start = SPACES_PER_INDENTATION;
if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER) {
start = SPACES_PER_INDENTATION;
}
if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT) {
start = 0;
end = SPACES_PER_INDENTATION;
}
webkit_dom_element_set_class_name (element, "-x-evo-indented");
/* We don't want vertical space bellow and above blockquote inserted by
* WebKit's User Agent Stylesheet. We have to override it through style attribute. */
if (is_in_html_mode (selection))
style = g_strdup_printf (
"-webkit-margin-start: %dch; -webkit-margin-end : %dch; %s",
start, end, align_value);
else
style = g_strdup_printf (
"-webkit-margin-start: %dch; -webkit-margin-end : %dch; "
"word-wrap: normal; width: %dch; %s",
start, end, word_wrap_length, align_value);
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, "BLOCKQUOTE", 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)
{
EHTMLEditorSelectionAlignment alignment;
const gchar *align_value = NULL;
char *style = NULL;
gint word_wrap_length = (width == -1) ? selection->priv->word_wrap_length : width;
alignment = e_html_editor_selection_get_alignment (selection);
align_value = get_css_alignment_value (alignment);
element_add_class (element, "-x-evo-paragraph");
if (!is_in_html_mode (selection)) {
style = g_strdup_printf (
"width: %dch; word-wrap: normal; %s %s",
(word_wrap_length + offset), align_value, style_to_add);
} else {
if (*align_value || *style_to_add)
style = g_strdup_printf (
"%s %s", align_value, 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,
WebKitDOMNode *caret_position)
{
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 */
webkit_dom_node_append_child (WEBKIT_DOM_NODE (container), caret_position, NULL);
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;
WebKitDOMRange *range;
WebKitDOMDocument *document;
WebKitDOMElement *active_paragraph, *caret;
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);
caret = e_html_editor_selection_save_caret_position (selection);
if (g_strcmp0 (e_html_editor_selection_get_string (selection), "") == 0) {
WebKitDOMNode *end_container;
WebKitDOMNode *parent;
WebKitDOMNode *paragraph;
gchar *text_content;
/* We need to save caret position and restore it after
* wrapping the selection, but we need to save it before we
* start to modify selection */
range = html_editor_selection_get_current_range (selection);
if (!range)
return;
end_container = webkit_dom_range_get_common_ancestor_container (range, NULL);
/* Wrap only text surrounded in DIV and P tags */
parent = webkit_dom_node_get_parent_node(end_container);
if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent) ||
WEBKIT_DOM_IS_HTML_PARAGRAPH_ELEMENT (parent)) {
element_add_class (
WEBKIT_DOM_ELEMENT (parent), "-x-evo-paragraph");
paragraph = parent;
} else {
WebKitDOMElement *parent_div =
e_html_editor_dom_node_find_parent_element (parent, "DIV");
if (element_has_class (parent_div, "-x-evo-paragraph")) {
paragraph = WEBKIT_DOM_NODE (parent_div);
} else {
if (!caret)
return;
/* We try to select previous sibling */
paragraph = webkit_dom_node_get_previous_sibling (
WEBKIT_DOM_NODE (caret));
if (paragraph) {
/* When there is just text without container
* we have to surround it with paragraph div */
if (WEBKIT_DOM_IS_TEXT (paragraph))
paragraph = WEBKIT_DOM_NODE (
e_html_editor_selection_put_node_into_paragraph (
selection, document, paragraph,
WEBKIT_DOM_NODE (caret)));
} else {
/* When some weird element is selected, return */
e_html_editor_selection_clear_caret_position_marker (selection);
return;
}
}
}
if (!paragraph)
return;
webkit_dom_element_remove_attribute (
WEBKIT_DOM_ELEMENT (paragraph), "style");
webkit_dom_element_set_id (
WEBKIT_DOM_ELEMENT (paragraph), "-x-evo-active-paragraph");
text_content = webkit_dom_node_get_text_content (paragraph);
/* If there is hidden space character in the beginning we remove it */
if (strstr (text_content, UNICODE_ZERO_WIDTH_SPACE)) {
if (g_str_has_prefix (text_content, UNICODE_ZERO_WIDTH_SPACE)) {
WebKitDOMNode *node;
node = webkit_dom_node_get_first_child (paragraph);
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)) {
WebKitDOMNode *node;
node = webkit_dom_node_get_last_child (paragraph);
webkit_dom_character_data_delete_data (
WEBKIT_DOM_CHARACTER_DATA (node),
g_utf8_strlen (text_content, -1) -1,
1,
NULL);
}
}
g_free (text_content);
wrap_lines (
NULL, paragraph, document, FALSE,
selection->priv->word_wrap_length);
} else {
e_html_editor_selection_save_caret_position (selection);
/* If we have selection -> wrap it */
wrap_lines (
selection, NULL, document, FALSE,
selection->priv->word_wrap_length);
}
active_paragraph = webkit_dom_document_get_element_by_id (
document, "-x-evo-active-paragraph");
/* We have to move caret on position where it was before modifying the text */
e_html_editor_selection_restore_caret_position (selection);
/* Set paragraph as non-active */
if (active_paragraph)
webkit_dom_element_remove_attribute (
WEBKIT_DOM_ELEMENT (active_paragraph), "id");
}
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 > 10, NULL);
document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (paragraph));
return wrap_lines (
NULL, WEBKIT_DOM_NODE (paragraph), document, FALSE, 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));
list = webkit_dom_document_query_selector_all (
document, "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);
}
}
}
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 (WEBKIT_DOM_NODE (paragraph)) ||
WEBKIT_DOM_IS_HTMLLI_ELEMENT (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;
if (!WEBKIT_DOM_IS_HTML_DIV_ELEMENT (element))
return NULL;
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)
{
EHTMLEditorView *view;
WebKitWebView *web_view;
WebKitDOMDocument *document;
WebKitDOMRange *range;
WebKitDOMNode *container, *next_sibling;
WebKitDOMElement *marker;
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);
/* First remove all markers (if present) */
marker = webkit_dom_document_get_element_by_id (
document, "-x-evo-selection-start-marker");
if (marker != NULL)
remove_node (WEBKIT_DOM_NODE (marker));
marker = webkit_dom_document_get_element_by_id (
document, "-x-evo-selection-end-marker");
if (marker != NULL)
remove_node (WEBKIT_DOM_NODE (marker));
range = html_editor_selection_get_current_range (selection);
if (range != NULL) {
WebKitDOMNode *marker_node;
WebKitDOMNode *parent_node;
WebKitDOMNode *split_node;
glong offset;
marker = webkit_dom_document_create_element (
document, "SPAN", NULL);
webkit_dom_element_set_id (
marker, "-x-evo-selection-start-marker");
container = webkit_dom_range_get_start_container (range, NULL);
offset = webkit_dom_range_get_start_offset (range, NULL);
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 (
webkit_dom_node_get_parent_node (container),
WEBKIT_DOM_NODE (marker),
container,
NULL);
goto end_marker;
}
} else if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (container)) {
marker_node = webkit_dom_node_insert_before (
container,
WEBKIT_DOM_NODE (marker),
webkit_dom_node_get_first_child (container),
NULL);
goto 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 (marker),
next_sibling,
NULL);
goto end_marker;
}
if (!webkit_dom_node_get_previous_sibling (container)) {
marker_node = webkit_dom_node_insert_before (
container,
WEBKIT_DOM_NODE (marker),
webkit_dom_node_get_first_child (container),
NULL);
goto end_marker;
} else if (!webkit_dom_node_get_next_sibling (container)) {
marker_node = webkit_dom_node_append_child (
container,
WEBKIT_DOM_NODE (marker),
NULL);
goto end_marker;
} else {
if (webkit_dom_node_get_first_child (container)) {
marker_node = webkit_dom_node_insert_before (
container,
WEBKIT_DOM_NODE (marker),
webkit_dom_node_get_first_child (container),
NULL);
goto end_marker;
}
split_node = container;
}
}
/* Don't save selection straight into body */
if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (split_node))
return;
if (!split_node) {
marker_node = webkit_dom_node_insert_before (
container,
WEBKIT_DOM_NODE (marker),
webkit_dom_node_get_first_child (
WEBKIT_DOM_NODE (container)),
NULL);
} else {
marker_node = WEBKIT_DOM_NODE (marker);
parent_node = webkit_dom_node_get_parent_node (split_node);
webkit_dom_node_insert_before (
parent_node, marker_node, split_node, NULL);
}
end_marker:
marker = webkit_dom_document_create_element (
document, "SPAN", NULL);
webkit_dom_element_set_id (
marker, "-x-evo-selection-end-marker");
if (webkit_dom_range_get_collapsed (range, NULL)) {
webkit_dom_node_insert_before (
/* Selection start marker */
webkit_dom_node_get_parent_node (marker_node),
WEBKIT_DOM_NODE (marker),
webkit_dom_node_get_next_sibling (marker_node),
NULL);
return;
}
container = webkit_dom_range_get_end_container (range, NULL);
offset = webkit_dom_range_get_end_offset (range, NULL);
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 (
webkit_dom_node_get_parent_node (container),
WEBKIT_DOM_NODE (marker),
container,
NULL);
goto check;
}
} else if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (container)) {
webkit_dom_node_append_child (
container, WEBKIT_DOM_NODE (marker), NULL);
return;
} 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 (marker),
next_sibling,
NULL);
return;
}
if (!webkit_dom_node_get_previous_sibling (container)) {
split_node = webkit_dom_node_get_parent_node (
container);
} else if (!webkit_dom_node_get_next_sibling (container)) {
split_node = webkit_dom_node_get_parent_node (
container);
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)) {
marker = webkit_dom_document_get_element_by_id (
document, "-x-evo-selection-start-marker");
remove_node (WEBKIT_DOM_NODE (marker));
return;
}
marker_node = WEBKIT_DOM_NODE (marker);
if (split_node) {
parent_node = webkit_dom_node_get_parent_node (split_node);
webkit_dom_node_insert_before (
parent_node, marker_node, split_node, NULL);
} else {
webkit_dom_node_append_child (
WEBKIT_DOM_NODE (container),
marker_node,
NULL);
}
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_IS_ELEMENT (next_sibling) &&
element_has_id (WEBKIT_DOM_ELEMENT (next_sibling), "-x-evo-selection-start-marker")) {
webkit_dom_node_insert_before (
webkit_dom_node_get_parent_node (marker_node),
next_sibling,
marker_node,
NULL);
}
}
}
}
/**
* 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;
WebKitWebView *web_view;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *window;
WebKitDOMDOMSelection *dom_selection;
WebKitDOMRange *range;
WebKitDOMElement *marker;
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);
window = webkit_dom_document_get_default_view (document);
dom_selection = webkit_dom_dom_window_get_selection (window);
range = webkit_dom_document_create_range (document);
if (range != NULL) {
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));
return;
}
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));
return;
}
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);
webkit_dom_dom_selection_add_range (dom_selection, range);
}
}
static void
html_editor_selection_modify (EHTMLEditorSelection *selection,
const gchar *alter,
gboolean forward,
EHTMLEditorSelectionGranularity granularity)
{
EHTMLEditorView *view;
WebKitWebView *web_view;
WebKitDOMDocument *document;
WebKitDOMDOMWindow *window;
WebKitDOMDOMSelection *dom_selection;
const gchar *granularity_str;
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);
window = webkit_dom_document_get_default_view (document);
dom_selection = webkit_dom_dom_window_get_selection (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;
}
webkit_dom_dom_selection_modify (
dom_selection, alter,
forward ? "forward" : "backward",
granularity_str);
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 *window;
WebKitDOMElement *caret;
caret = e_html_editor_selection_save_caret_position (selection);
if (!caret)
return;
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);
window = webkit_dom_document_get_default_view (document);
window_top = webkit_dom_dom_window_get_scroll_y (window);
window_left = webkit_dom_dom_window_get_scroll_x (window);
window_bottom = window_top + webkit_dom_dom_window_get_inner_height (window);
window_right = window_left + webkit_dom_dom_window_get_inner_width (window);
element_left = webkit_dom_element_get_offset_left (caret);
element_top = webkit_dom_element_get_offset_top (caret);
/* 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 (caret, TRUE);
}
e_html_editor_selection_clear_caret_position_marker (selection);
}