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