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