Update Romanian translation
[evolution.git] / src / modules / webkit-editor / web-extension / e-editor-dom-functions.c
blobbdef1ccf575281e885f3c0bd892da3d208cd3d99
1 /*
2 * Copyright (C) 2016 Red Hat, Inc. (www.redhat.com)
4 * This library is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
8 * This library is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
11 * for more details.
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this library. If not, see <http://www.gnu.org/licenses/>.
17 #include "evolution-config.h"
19 #include <string.h>
21 #include <webkitdom/webkitdom.h>
23 #include "web-extensions/e-dom-utils.h"
25 #include "e-editor-page.h"
26 #include "e-editor-undo-redo-manager.h"
28 #include "e-editor-dom-functions.h"
30 #define HTML_KEY_CODE_BACKSPACE 8
31 #define HTML_KEY_CODE_RETURN 13
32 #define HTML_KEY_CODE_CONTROL 17
33 #define HTML_KEY_CODE_SPACE 32
34 #define HTML_KEY_CODE_DELETE 46
35 #define HTML_KEY_CODE_TABULATOR 9
37 /* ******************** Tests ******************** */
39 static gchar *
40 workaround_spaces (const gchar *text)
42 GString *tmp;
43 gchar *str = NULL;
45 tmp = e_str_replace_string (text, "&nbsp;", " ");
46 if (tmp) {
47 str = g_string_free (tmp, FALSE);
48 text = str;
51 tmp = e_str_replace_string (text, " ", " ");
52 if (tmp) {
53 g_free (str);
54 str = g_string_free (tmp, FALSE);
55 } else if (!str) {
56 str = g_strdup (text);
59 return str;
62 gboolean
63 e_editor_dom_test_html_equal (WebKitDOMDocument *document,
64 const gchar *html1,
65 const gchar *html2)
67 WebKitDOMElement *elem1, *elem2;
68 gchar *str1, *str2;
69 gboolean res = FALSE;
70 GError *error = NULL;
72 g_return_val_if_fail (WEBKIT_DOM_IS_DOCUMENT (document), FALSE);
73 g_return_val_if_fail (html1 != NULL, FALSE);
74 g_return_val_if_fail (html2 != NULL, FALSE);
76 elem1 = webkit_dom_document_create_element (document, "TestHtmlEqual", &error);
77 if (error || !elem1) {
78 g_warning ("%s: Failed to create elem1: %s", G_STRFUNC, error ? error->message : "Unknown error");
79 g_clear_error (&error);
80 return FALSE;
83 elem2 = webkit_dom_document_create_element (document, "TestHtmlEqual", &error);
84 if (error || !elem2) {
85 g_warning ("%s: Failed to create elem2: %s", G_STRFUNC, error ? error->message : "Unknown error");
86 g_clear_error (&error);
87 return FALSE;
90 /* FIXME WK2: Workaround when &nbsp; is used instead of regular spaces. (Placed by WebKit?) */
91 str1 = workaround_spaces (html1);
92 str2 = workaround_spaces (html2);
94 webkit_dom_element_set_inner_html (elem1, str1, &error);
95 if (!error) {
96 webkit_dom_element_set_inner_html (elem2, str2, &error);
98 if (!error) {
99 webkit_dom_node_normalize (WEBKIT_DOM_NODE (elem1));
100 webkit_dom_node_normalize (WEBKIT_DOM_NODE (elem2));
102 res = webkit_dom_node_is_equal_node (WEBKIT_DOM_NODE (elem1), WEBKIT_DOM_NODE (elem2));
103 } else {
104 g_warning ("%s: Failed to set inner html2: %s", G_STRFUNC, error->message);
106 } else {
107 g_warning ("%s: Failed to set inner html1: %s", G_STRFUNC, error->message);
110 if (res && (g_strcmp0 (html1, str1) != 0 || g_strcmp0 (html2, str2) != 0))
111 g_warning ("%s: Applied the '&nbsp;' workaround", G_STRFUNC);
113 g_clear_error (&error);
114 g_free (str1);
115 g_free (str2);
117 return res;
120 /* ******************** Actions ******************** */
122 static WebKitDOMElement *
123 get_table_cell_element (EEditorPage *editor_page)
125 WebKitDOMDocument *document;
126 WebKitDOMElement *cell;
127 WebKitDOMNode *node_under_mouse_click;
129 document = e_editor_page_get_document (editor_page);
130 cell = webkit_dom_document_get_element_by_id (document, "-x-evo-current-cell");
132 if (cell)
133 return cell;
135 node_under_mouse_click = e_editor_page_get_node_under_mouse_click (editor_page);
137 if (node_under_mouse_click && WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (node_under_mouse_click)) {
138 cell = WEBKIT_DOM_ELEMENT (node_under_mouse_click);
139 } else {
140 WebKitDOMElement *selection_start;
142 e_editor_dom_selection_save (editor_page);
144 selection_start = webkit_dom_document_get_element_by_id (
145 document, "-x-evo-selection-start-marker");
147 cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (selection_start), "TD");
148 if (!cell)
149 cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (selection_start), "TH");
151 e_editor_dom_selection_restore (editor_page);
154 return cell;
157 static void
158 prepare_history_for_table (EEditorPage *editor_page,
159 WebKitDOMElement *table,
160 EEditorHistoryEvent *ev)
162 ev->type = HISTORY_TABLE_DIALOG;
164 e_editor_dom_selection_get_coordinates (editor_page, &ev->before.start.x, &ev->before.start.y, &ev->before.end.x, &ev->before.end.y);
166 ev->data.dom.from = g_object_ref (webkit_dom_node_clone_node_with_error (
167 WEBKIT_DOM_NODE (table), TRUE, NULL));
171 static void
172 save_history_for_table (EEditorPage *editor_page,
173 WebKitDOMElement *table,
174 EEditorHistoryEvent *ev)
176 EEditorUndoRedoManager *manager;
178 if (table)
179 ev->data.dom.to = g_object_ref (webkit_dom_node_clone_node_with_error (
180 WEBKIT_DOM_NODE (table), TRUE, NULL));
181 else
182 ev->data.dom.to = NULL;
184 e_editor_dom_selection_get_coordinates (editor_page,
185 &ev->after.start.x, &ev->after.start.y, &ev->after.end.x, &ev->after.end.y);
187 manager = e_editor_page_get_undo_redo_manager (editor_page);
188 e_editor_undo_redo_manager_insert_history_event (manager, ev);
191 void
192 e_editor_dom_delete_cell_contents (EEditorPage *editor_page)
194 WebKitDOMNode *node;
195 WebKitDOMElement *cell, *table_cell, *table;
196 EEditorHistoryEvent *ev = NULL;
198 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
200 table_cell = get_table_cell_element (editor_page);
201 g_return_if_fail (table_cell != NULL);
203 cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TD");
204 if (!cell)
205 cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TH");
206 g_return_if_fail (cell != NULL);
208 table = dom_node_find_parent_element (WEBKIT_DOM_NODE (cell), "TABLE");
209 g_return_if_fail (table != NULL);
211 ev = g_new0 (EEditorHistoryEvent, 1);
212 prepare_history_for_table (editor_page, table, ev);
214 while ((node = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (cell))))
215 remove_node (node);
217 save_history_for_table (editor_page, table, ev);
220 void
221 e_editor_dom_delete_column (EEditorPage *editor_page)
223 WebKitDOMElement *cell, *table, *table_cell;
224 WebKitDOMHTMLCollection *rows = NULL;
225 EEditorHistoryEvent *ev = NULL;
226 gulong index, length, ii;
228 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
230 table_cell = get_table_cell_element (editor_page);
231 g_return_if_fail (table_cell != NULL);
233 /* Find TD in which the selection starts */
234 cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TD");
235 if (!cell)
236 cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TH");
237 g_return_if_fail (cell != NULL);
239 table = dom_node_find_parent_element (WEBKIT_DOM_NODE (cell), "TABLE");
240 g_return_if_fail (table != NULL);
242 ev = g_new0 (EEditorHistoryEvent, 1);
243 prepare_history_for_table (editor_page, table, ev);
245 rows = webkit_dom_html_table_element_get_rows (
246 WEBKIT_DOM_HTML_TABLE_ELEMENT (table));
247 length = webkit_dom_html_collection_get_length (rows);
249 index = webkit_dom_html_table_cell_element_get_cell_index (
250 WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (cell));
252 for (ii = 0; ii < length; ii++) {
253 WebKitDOMNode *row;
255 row = webkit_dom_html_collection_item (rows, ii);
257 webkit_dom_html_table_row_element_delete_cell (
258 WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row), index, NULL);
261 g_clear_object (&rows);
263 save_history_for_table (editor_page, table, ev);
266 void
267 e_editor_dom_delete_row (EEditorPage *editor_page)
269 WebKitDOMElement *row, *table, *table_cell;
270 EEditorHistoryEvent *ev = NULL;
272 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
274 table_cell = get_table_cell_element (editor_page);
275 g_return_if_fail (table_cell != NULL);
277 row = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TR");
278 g_return_if_fail (row != NULL);
280 table = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TABLE");
281 g_return_if_fail (table != NULL);
283 ev = g_new0 (EEditorHistoryEvent, 1);
284 prepare_history_for_table (editor_page, table, ev);
286 remove_node (WEBKIT_DOM_NODE (row));
288 save_history_for_table (editor_page, table, ev);
291 void
292 e_editor_dom_delete_table (EEditorPage *editor_page)
294 WebKitDOMElement *table, *table_cell;
295 EEditorHistoryEvent *ev = NULL;
297 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
299 table_cell = get_table_cell_element (editor_page);
300 g_return_if_fail (table_cell != NULL);
302 table = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TABLE");
303 g_return_if_fail (table != NULL);
305 ev = g_new0 (EEditorHistoryEvent, 1);
306 prepare_history_for_table (editor_page, table, ev);
308 remove_node (WEBKIT_DOM_NODE (table));
310 save_history_for_table (editor_page, NULL, ev);
313 void
314 e_editor_dom_insert_column_after (EEditorPage *editor_page)
316 WebKitDOMElement *cell, *row, *table_cell, *table;
317 EEditorHistoryEvent *ev = NULL;
318 gulong index;
320 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
322 table_cell = get_table_cell_element (editor_page);
323 g_return_if_fail (table_cell != NULL);
325 cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TD");
326 if (!cell)
327 cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TH");
328 g_return_if_fail (cell != NULL);
330 row = dom_node_find_parent_element (WEBKIT_DOM_NODE (cell), "TR");
331 g_return_if_fail (row != NULL);
333 table = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TABLE");
334 g_return_if_fail (table != NULL);
336 ev = g_new0 (EEditorHistoryEvent, 1);
337 prepare_history_for_table (editor_page, table, ev);
339 /* Get the first row in the table */
340 row = WEBKIT_DOM_ELEMENT (
341 webkit_dom_node_get_first_child (
342 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (row))));
344 index = webkit_dom_html_table_cell_element_get_cell_index (
345 WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (cell));
347 while (row) {
348 webkit_dom_html_table_row_element_insert_cell (
349 WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row), index + 1, NULL);
351 row = WEBKIT_DOM_ELEMENT (
352 webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (row)));
355 save_history_for_table (editor_page, table, ev);
358 void
359 e_editor_dom_insert_column_before (EEditorPage *editor_page)
361 WebKitDOMElement *cell, *row, *table_cell, *table;
362 EEditorHistoryEvent *ev = NULL;
363 gulong index;
365 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
367 table_cell = get_table_cell_element (editor_page);
368 g_return_if_fail (table_cell != NULL);
370 cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TD");
371 if (!cell) {
372 cell = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TH");
374 g_return_if_fail (cell != NULL);
376 row = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TR");
377 g_return_if_fail (row != NULL);
379 table = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TABLE");
380 g_return_if_fail (table != NULL);
382 ev = g_new0 (EEditorHistoryEvent, 1);
383 prepare_history_for_table (editor_page, table, ev);
385 /* Get the first row in the table */
386 row = WEBKIT_DOM_ELEMENT (
387 webkit_dom_node_get_first_child (
388 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (row))));
390 index = webkit_dom_html_table_cell_element_get_cell_index (
391 WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (cell));
393 while (row) {
394 webkit_dom_html_table_row_element_insert_cell (
395 WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row), index, NULL);
397 row = WEBKIT_DOM_ELEMENT (
398 webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (row)));
401 save_history_for_table (editor_page, table, ev);
404 void
405 e_editor_dom_insert_row_above (EEditorPage *editor_page)
407 WebKitDOMElement *row, *table, *table_cell;
408 WebKitDOMHTMLCollection *cells = NULL;
409 WebKitDOMHTMLElement *new_row;
410 EEditorHistoryEvent *ev = NULL;
411 gulong index, cell_count, ii;
413 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
415 table_cell = get_table_cell_element (editor_page);
416 g_return_if_fail (table_cell != NULL);
418 row = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TR");
419 g_return_if_fail (row != NULL);
421 table = dom_node_find_parent_element (WEBKIT_DOM_NODE (row), "TABLE");
422 g_return_if_fail (table != NULL);
424 ev = g_new0 (EEditorHistoryEvent, 1);
425 prepare_history_for_table (editor_page, table, ev);
427 index = webkit_dom_html_table_row_element_get_row_index (
428 WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row));
430 new_row = webkit_dom_html_table_element_insert_row (
431 WEBKIT_DOM_HTML_TABLE_ELEMENT (table), index, NULL);
433 cells = webkit_dom_html_table_row_element_get_cells (
434 WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row));
435 cell_count = webkit_dom_html_collection_get_length (cells);
436 for (ii = 0; ii < cell_count; ii++) {
437 webkit_dom_html_table_row_element_insert_cell (
438 WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (new_row), -1, NULL);
441 g_clear_object (&cells);
443 save_history_for_table (editor_page, table, ev);
446 void
447 e_editor_dom_insert_row_below (EEditorPage *editor_page)
449 WebKitDOMElement *row, *table, *table_cell;
450 WebKitDOMHTMLCollection *cells = NULL;
451 WebKitDOMHTMLElement *new_row;
452 EEditorHistoryEvent *ev = NULL;
453 gulong index, cell_count, ii;
455 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
457 table_cell = get_table_cell_element (editor_page);
458 g_return_if_fail (table_cell != NULL);
460 row = dom_node_find_parent_element (WEBKIT_DOM_NODE (table_cell), "TR");
461 g_return_if_fail (row != NULL);
463 table = dom_node_find_parent_element (WEBKIT_DOM_NODE (row), "TABLE");
464 g_return_if_fail (table != NULL);
466 ev = g_new0 (EEditorHistoryEvent, 1);
467 prepare_history_for_table (editor_page, table, ev);
469 index = webkit_dom_html_table_row_element_get_row_index (
470 WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row));
472 new_row = webkit_dom_html_table_element_insert_row (
473 WEBKIT_DOM_HTML_TABLE_ELEMENT (table), index + 1, NULL);
475 cells = webkit_dom_html_table_row_element_get_cells (
476 WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row));
477 cell_count = webkit_dom_html_collection_get_length (cells);
478 for (ii = 0; ii < cell_count; ii++) {
479 webkit_dom_html_table_row_element_insert_cell (
480 WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (new_row), -1, NULL);
483 g_clear_object (&cells);
485 save_history_for_table (editor_page, table, ev);
488 void
489 e_editor_dom_save_history_for_cut (EEditorPage *editor_page)
491 WebKitDOMDocument *document;
492 WebKitDOMDocumentFragment *fragment;
493 WebKitDOMDOMWindow *dom_window = NULL;
494 WebKitDOMDOMSelection *dom_selection = NULL;
495 WebKitDOMRange *range = NULL;
496 EEditorHistoryEvent *ev;
497 EEditorUndoRedoManager *manager;
499 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
501 document = e_editor_page_get_document (editor_page);
502 dom_window = webkit_dom_document_get_default_view (document);
503 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
504 g_clear_object (&dom_window);
506 if (!webkit_dom_dom_selection_get_range_count (dom_selection) ||
507 webkit_dom_dom_selection_get_is_collapsed (dom_selection)) {
508 g_clear_object (&dom_selection);
509 return;
512 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
514 ev = g_new0 (EEditorHistoryEvent, 1);
515 ev->type = HISTORY_DELETE;
517 e_editor_dom_selection_get_coordinates (editor_page,
518 &ev->before.start.x,
519 &ev->before.start.y,
520 &ev->before.end.x,
521 &ev->before.end.y);
523 ev->after.start.x = ev->before.start.x;
524 ev->after.start.y = ev->before.start.y;
525 ev->after.end.x = ev->before.start.x;
526 ev->after.end.y = ev->before.start.y;
528 /* Save the fragment. */
529 fragment = webkit_dom_range_clone_contents (range, NULL);
530 g_clear_object (&dom_selection);
531 g_clear_object (&range);
532 ev->data.fragment = g_object_ref (fragment);
534 manager = e_editor_page_get_undo_redo_manager (editor_page);
535 e_editor_undo_redo_manager_insert_history_event (manager, ev);
536 e_editor_page_set_dont_save_history_in_body_input (editor_page, TRUE);
539 /* ******************** View ******************** */
542 * e_editor_dom_exec_command:
543 * @document: a #WebKitDOMDocument
544 * @command: an #EContentEditorCommand to execute
545 * @value: value of the command (or @NULL if the command does not require value)
547 * The function will fail when @value is @NULL or empty but the current @command
548 * requires a value to be passed. The @value is ignored when the @command does
549 * not expect any value.
551 * Returns: @TRUE when the command was succesfully executed, @FALSE otherwise.
553 gboolean
554 e_editor_dom_exec_command (EEditorPage *editor_page,
555 EContentEditorCommand command,
556 const gchar *value)
558 const gchar *cmd_str = 0;
559 gboolean has_value = FALSE;
561 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
563 #define CHECK_COMMAND(cmd,str,val) case cmd:\
564 if (val) {\
565 g_return_val_if_fail (value && *value, FALSE);\
567 has_value = val; \
568 cmd_str = str;\
569 break;
571 switch (command) {
572 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_BACKGROUND_COLOR, "BackColor", TRUE)
573 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_BOLD, "Bold", FALSE)
574 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_COPY, "Copy", FALSE)
575 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_CREATE_LINK, "CreateLink", TRUE)
576 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_CUT, "Cut", FALSE)
577 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_DEFAULT_PARAGRAPH_SEPARATOR, "DefaultParagraphSeparator", FALSE)
578 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_DELETE, "Delete", FALSE)
579 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_FIND_STRING, "FindString", TRUE)
580 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_FONT_NAME, "FontName", TRUE)
581 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_FONT_SIZE, "FontSize", TRUE)
582 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_FONT_SIZE_DELTA, "FontSizeDelta", TRUE)
583 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_FORE_COLOR, "ForeColor", TRUE)
584 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_FORMAT_BLOCK, "FormatBlock", TRUE)
585 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_FORWARD_DELETE, "ForwardDelete", FALSE)
586 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_HILITE_COLOR, "HiliteColor", TRUE)
587 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INDENT, "Indent", FALSE)
588 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INSERT_HORIZONTAL_RULE, "InsertHorizontalRule", FALSE)
589 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INSERT_HTML, "InsertHTML", TRUE)
590 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INSERT_IMAGE, "InsertImage", TRUE)
591 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INSERT_LINE_BREAK, "InsertLineBreak", FALSE)
592 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT, "InsertNewlineInQuotedContent", FALSE)
593 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INSERT_ORDERED_LIST, "InsertOrderedList", FALSE)
594 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INSERT_PARAGRAPH, "InsertParagraph", FALSE)
595 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INSERT_TEXT, "InsertText", TRUE)
596 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_INSERT_UNORDERED_LIST, "InsertUnorderedList", FALSE)
597 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_ITALIC, "Italic", FALSE)
598 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_JUSTIFY_CENTER, "JustifyCenter", FALSE)
599 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_JUSTIFY_FULL, "JustifyFull", FALSE)
600 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_JUSTIFY_LEFT, "JustifyLeft", FALSE)
601 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_JUSTIFY_NONE, "JustifyNone", FALSE)
602 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_JUSTIFY_RIGHT, "JustifyRight", FALSE)
603 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_OUTDENT, "Outdent", FALSE)
604 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_PASTE, "Paste", FALSE)
605 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_PASTE_AND_MATCH_STYLE, "PasteAndMatchStyle", FALSE)
606 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_PASTE_AS_PLAIN_TEXT, "PasteAsPlainText", FALSE)
607 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_PRINT, "Print", FALSE)
608 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_REDO, "Redo", FALSE)
609 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_REMOVE_FORMAT, "RemoveFormat", FALSE)
610 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_SELECT_ALL, "SelectAll", FALSE)
611 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_STRIKETHROUGH, "Strikethrough", FALSE)
612 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_STYLE_WITH_CSS, "StyleWithCSS", TRUE)
613 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_SUBSCRIPT, "Subscript", FALSE)
614 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_SUPERSCRIPT, "Superscript", FALSE)
615 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_TRANSPOSE, "Transpose", FALSE)
616 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_UNDERLINE, "Underline", FALSE)
617 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_UNDO, "Undo", FALSE)
618 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_UNLINK, "Unlink", FALSE)
619 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_UNSELECT, "Unselect", FALSE)
620 CHECK_COMMAND (E_CONTENT_EDITOR_COMMAND_USE_CSS, "UseCSS", TRUE)
623 e_editor_page_set_dont_save_history_in_body_input (editor_page, TRUE);
625 return webkit_dom_document_exec_command (
626 e_editor_page_get_document (editor_page), cmd_str, FALSE, has_value ? value : "" );
629 static void
630 perform_spell_check (WebKitDOMDOMSelection *dom_selection,
631 WebKitDOMRange *start_range,
632 WebKitDOMRange *end_range)
634 WebKitDOMRange *actual = start_range;
636 /* FIXME WK2: this doesn't work, the cursor is moved, but the spellcheck is not updated */
637 /* Go through all words to spellcheck them. To avoid this we have to wait for
638 * http://www.w3.org/html/wg/drafts/html/master/editing.html#dom-forcespellcheck */
639 /* We are moving forward word by word until we hit the text on the end. */
640 while (actual && webkit_dom_range_compare_boundary_points (actual, WEBKIT_DOM_RANGE_START_TO_START, end_range, NULL) < 0) {
641 if (actual != start_range)
642 g_object_unref (actual);
643 webkit_dom_dom_selection_modify (
644 dom_selection, "move", "forward", "word");
645 actual = webkit_dom_dom_selection_get_range_at (
646 dom_selection, 0, NULL);
648 g_clear_object (&actual);
651 void
652 e_editor_dom_force_spell_check_for_current_paragraph (EEditorPage *editor_page)
654 WebKitDOMDocument *document;
655 WebKitDOMDOMSelection *dom_selection = NULL;
656 WebKitDOMDOMWindow *dom_window = NULL;
657 WebKitDOMElement *selection_start_marker, *selection_end_marker;
658 WebKitDOMElement *parent;
659 WebKitDOMHTMLElement *body;
660 WebKitDOMRange *end_range = NULL, *actual = NULL;
661 WebKitDOMText *text;
663 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
665 document = e_editor_page_get_document (editor_page);
667 if (!e_editor_page_get_inline_spelling_enabled (editor_page))
668 return;
670 document = e_editor_page_get_document (editor_page);
671 body = webkit_dom_document_get_body (document);
673 if (!body)
674 return;
676 if (!webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)))
677 return;
679 e_editor_dom_selection_save (editor_page);
681 selection_start_marker = webkit_dom_document_get_element_by_id (
682 document, "-x-evo-selection-start-marker");
683 selection_end_marker = webkit_dom_document_get_element_by_id (
684 document, "-x-evo-selection-end-marker");
686 if (!selection_start_marker || !selection_end_marker)
687 return;
689 /* Block callbacks of selection-changed signal as we don't want to
690 * recount all the block format things in EEditorSelection and here as well
691 * when we are moving with caret */
692 e_editor_page_block_selection_changed (editor_page);
694 parent = get_parent_block_element (WEBKIT_DOM_NODE (selection_end_marker));
695 if (!parent)
696 parent = WEBKIT_DOM_ELEMENT (body);
698 /* Append some text on the end of the element */
699 text = webkit_dom_document_create_text_node (document, "-x-evo-end");
700 webkit_dom_node_append_child (
701 WEBKIT_DOM_NODE (parent),
702 WEBKIT_DOM_NODE (text),
703 NULL);
705 parent = get_parent_block_element (WEBKIT_DOM_NODE (selection_start_marker));
706 if (!parent)
707 parent = WEBKIT_DOM_ELEMENT (body);
709 /* Create range that's pointing on the end of this text */
710 end_range = webkit_dom_document_create_range (document);
711 webkit_dom_range_select_node_contents (
712 end_range, WEBKIT_DOM_NODE (text), NULL);
713 webkit_dom_range_collapse (end_range, FALSE, NULL);
715 /* Move on the beginning of the paragraph */
716 dom_window = webkit_dom_document_get_default_view (document);
717 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
719 actual = webkit_dom_document_create_range (document);
720 webkit_dom_range_select_node_contents (
721 actual, WEBKIT_DOM_NODE (parent), NULL);
722 webkit_dom_range_collapse (actual, TRUE, NULL);
723 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
724 webkit_dom_dom_selection_add_range (dom_selection, actual);
725 g_clear_object (&actual);
727 actual = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
728 perform_spell_check (dom_selection, actual, end_range);
730 g_clear_object (&dom_selection);
731 g_clear_object (&dom_window);
732 g_clear_object (&end_range);
733 g_clear_object (&actual);
735 /* Remove the text that we inserted on the end of the paragraph */
736 remove_node (WEBKIT_DOM_NODE (text));
738 e_editor_dom_selection_restore (editor_page);
739 /* Unblock the callbacks */
740 e_editor_page_unblock_selection_changed (editor_page);
743 static void
744 refresh_spell_check (EEditorPage *editor_page,
745 gboolean enable_spell_check)
747 WebKitDOMDocument *document;
748 WebKitDOMDOMSelection *dom_selection = NULL;
749 WebKitDOMDOMWindow *dom_window = NULL;
750 WebKitDOMElement *selection_start_marker, *selection_end_marker;
751 WebKitDOMHTMLElement *body;
752 WebKitDOMRange *end_range = NULL, *actual = NULL;
753 WebKitDOMText *text;
755 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
757 document = e_editor_page_get_document (editor_page);
758 body = webkit_dom_document_get_body (document);
760 if (!webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)))
761 return;
763 /* Enable/Disable spellcheck in composer */
764 webkit_dom_element_set_attribute (
765 WEBKIT_DOM_ELEMENT (body),
766 "spellcheck",
767 enable_spell_check ? "true" : "false",
768 NULL);
770 e_editor_dom_selection_save (editor_page);
772 selection_start_marker = webkit_dom_document_get_element_by_id (
773 document, "-x-evo-selection-start-marker");
774 selection_end_marker = webkit_dom_document_get_element_by_id (
775 document, "-x-evo-selection-end-marker");
777 /* Sometimes the web view is not focused, so we have to save the selection
778 * manually into the body */
779 if (!selection_start_marker || !selection_end_marker) {
780 WebKitDOMNode *child;
782 child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
783 if (!WEBKIT_DOM_IS_ELEMENT (child))
784 return;
786 dom_add_selection_markers_into_element_start (
787 document,
788 WEBKIT_DOM_ELEMENT (child),
789 &selection_start_marker,
790 &selection_end_marker);
793 /* Block callbacks of selection-changed signal as we don't want to
794 * recount all the block format things in EEditorSelection and here as well
795 * when we are moving with caret */
796 e_editor_page_block_selection_changed (editor_page);
798 /* Append some text on the end of the body */
799 text = webkit_dom_document_create_text_node (document, "-x-evo-end");
800 webkit_dom_node_append_child (
801 WEBKIT_DOM_NODE (body), WEBKIT_DOM_NODE (text), NULL);
803 /* Create range that's pointing on the end of this text */
804 end_range = webkit_dom_document_create_range (document);
805 webkit_dom_range_select_node_contents (
806 end_range, WEBKIT_DOM_NODE (text), NULL);
807 webkit_dom_range_collapse (end_range, FALSE, NULL);
809 dom_window = webkit_dom_document_get_default_view (document);
810 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
812 /* Move on the beginning of the document */
813 webkit_dom_dom_selection_modify (
814 dom_selection, "move", "backward", "documentboundary");
816 actual = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
817 perform_spell_check (dom_selection, actual, end_range);
819 g_clear_object (&dom_selection);
820 g_clear_object (&dom_window);
821 g_clear_object (&end_range);
822 g_clear_object (&actual);
824 /* Remove the text that we inserted on the end of the body */
825 remove_node (WEBKIT_DOM_NODE (text));
827 e_editor_dom_selection_restore (editor_page);
828 /* Unblock the callbacks */
829 e_editor_page_unblock_selection_changed (editor_page);
832 void
833 e_editor_dom_turn_spell_check_off (EEditorPage *editor_page)
835 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
837 refresh_spell_check (editor_page, FALSE);
840 void
841 e_editor_dom_force_spell_check_in_viewport (EEditorPage *editor_page)
843 WebKitDOMDocument *document;
844 WebKitDOMDOMSelection *dom_selection = NULL;
845 WebKitDOMDOMWindow *dom_window = NULL;
846 WebKitDOMElement *last_element;
847 WebKitDOMHTMLElement *body;
848 WebKitDOMRange *end_range = NULL, *actual = NULL;
849 WebKitDOMText *text;
850 glong viewport_height;
852 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
854 if (!e_editor_page_get_inline_spelling_enabled (editor_page))
855 return;
857 document = e_editor_page_get_document (editor_page);
858 body = webkit_dom_document_get_body (document);
860 if (!webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)))
861 return;
863 e_editor_dom_selection_save (editor_page);
865 /* Block callbacks of selection-changed signal as we don't want to
866 * recount all the block format things in EEditorSelection and here as well
867 * when we are moving with caret */
868 e_editor_page_block_selection_changed (editor_page);
870 /* We have to add 10 px offset as otherwise just the HTML element will be returned */
871 actual = webkit_dom_document_caret_range_from_point (document, 10, 10);
872 if (!actual)
873 goto out;
875 /* Append some text on the end of the body */
876 text = webkit_dom_document_create_text_node (document, "-x-evo-end");
878 dom_window = webkit_dom_document_get_default_view (document);
879 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
881 /* We have to add 10 px offset as otherwise just the HTML element will be returned */
882 viewport_height = webkit_dom_dom_window_get_inner_height (dom_window);
883 last_element = webkit_dom_document_element_from_point (document, 10, viewport_height - 10);
884 if (last_element && !WEBKIT_DOM_IS_HTML_HTML_ELEMENT (last_element) &&
885 !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (last_element)) {
886 WebKitDOMElement *parent;
888 parent = get_parent_block_element (WEBKIT_DOM_NODE (last_element));
889 webkit_dom_node_append_child (
890 WEBKIT_DOM_NODE (parent ? parent : last_element), WEBKIT_DOM_NODE (text), NULL);
891 } else
892 webkit_dom_node_append_child (
893 WEBKIT_DOM_NODE (body), WEBKIT_DOM_NODE (text), NULL);
895 /* Create range that's pointing on the end of viewport */
896 end_range = webkit_dom_document_create_range (document);
897 webkit_dom_range_select_node_contents (
898 end_range, WEBKIT_DOM_NODE (text), NULL);
899 webkit_dom_range_collapse (end_range, FALSE, NULL);
901 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
902 webkit_dom_dom_selection_add_range (dom_selection, actual);
903 perform_spell_check (dom_selection, actual, end_range);
905 g_clear_object (&dom_selection);
906 g_clear_object (&dom_window);
907 g_clear_object (&end_range);
908 g_clear_object (&actual);
910 /* Remove the text that we inserted on the end of the body */
911 remove_node (WEBKIT_DOM_NODE (text));
913 out:
914 e_editor_dom_selection_restore (editor_page);
915 /* Unblock the callbacks */
916 e_editor_page_unblock_selection_changed (editor_page);
919 void
920 e_editor_dom_force_spell_check (EEditorPage *editor_page)
922 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
924 if (e_editor_page_get_inline_spelling_enabled (editor_page))
925 refresh_spell_check (editor_page, TRUE);
928 gboolean
929 e_editor_dom_node_is_citation_node (WebKitDOMNode *node)
931 gboolean ret_val = FALSE;
932 gchar *value;
934 if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node))
935 return FALSE;
937 /* citation == <blockquote type='cite'> */
938 if ((value = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "type")))
939 ret_val = g_strcmp0 (value, "cite") == 0;
941 g_free (value);
943 return ret_val;
946 gint
947 e_editor_dom_get_citation_level (WebKitDOMNode *node)
949 WebKitDOMNode *parent = node;
950 gint level = 0;
952 while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
953 if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent) &&
954 webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (parent), "type"))
955 level++;
957 parent = webkit_dom_node_get_parent_node (parent);
960 return level;
963 static gchar *
964 get_quotation_for_level (gint quote_level)
966 const gchar *quote_element = "<span class=\"-x-evo-quote-character\">" QUOTE_SYMBOL " </span>";
967 gint ii;
968 GString *output = g_string_new ("");
970 for (ii = 0; ii < quote_level; ii++)
971 g_string_append (output, quote_element);
973 return g_string_free (output, FALSE);
976 void
977 e_editor_dom_quote_plain_text_element_after_wrapping (EEditorPage *editor_page,
978 WebKitDOMElement *element,
979 gint quote_level)
981 WebKitDOMDocument *document;
982 WebKitDOMNodeList *list = NULL;
983 WebKitDOMNode *quoted_node;
984 gint ii;
985 gchar *quotation;
987 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
988 g_return_if_fail (element != NULL);
990 document = e_editor_page_get_document (editor_page);
992 quoted_node = WEBKIT_DOM_NODE (
993 webkit_dom_document_create_element (document, "SPAN", NULL));
994 webkit_dom_element_set_class_name (
995 WEBKIT_DOM_ELEMENT (quoted_node), "-x-evo-quoted");
996 quotation = get_quotation_for_level (quote_level);
997 webkit_dom_element_set_inner_html (
998 WEBKIT_DOM_ELEMENT (quoted_node), quotation, NULL);
1000 list = webkit_dom_element_query_selector_all (
1001 element, "br.-x-evo-wrap-br, pre > br", NULL);
1002 webkit_dom_node_insert_before (
1003 WEBKIT_DOM_NODE (element),
1004 quoted_node,
1005 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)),
1006 NULL);
1008 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
1009 WebKitDOMNode *br = webkit_dom_node_list_item (list, ii);
1010 WebKitDOMNode *prev_sibling = webkit_dom_node_get_previous_sibling (br);
1012 if ((!WEBKIT_DOM_IS_ELEMENT (prev_sibling) ||
1013 !element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-quoted")) &&
1014 webkit_dom_node_get_next_sibling (br)) {
1016 webkit_dom_node_insert_before (
1017 webkit_dom_node_get_parent_node (br),
1018 webkit_dom_node_clone_node_with_error (quoted_node, TRUE, NULL),
1019 webkit_dom_node_get_next_sibling (br),
1020 NULL);
1024 g_clear_object (&list);
1025 g_free (quotation);
1028 static gboolean
1029 return_pressed_in_empty_line (EEditorPage *editor_page)
1031 WebKitDOMNode *node;
1032 WebKitDOMRange *range = NULL;
1034 range = e_editor_dom_get_current_range (editor_page);
1035 if (!range)
1036 return FALSE;
1038 node = webkit_dom_range_get_start_container (range, NULL);
1039 if (!WEBKIT_DOM_IS_TEXT (node)) {
1040 WebKitDOMNode *first_child;
1042 first_child = webkit_dom_node_get_first_child (node);
1043 if (first_child && WEBKIT_DOM_IS_ELEMENT (first_child) &&
1044 element_has_class (WEBKIT_DOM_ELEMENT (first_child), "-x-evo-quoted")) {
1045 WebKitDOMNode *prev_sibling;
1047 prev_sibling = webkit_dom_node_get_previous_sibling (node);
1048 if (!prev_sibling) {
1049 gboolean collapsed;
1051 collapsed = webkit_dom_range_get_collapsed (range, NULL);
1052 g_clear_object (&range);
1053 return collapsed;
1058 g_clear_object (&range);
1060 return FALSE;
1063 WebKitDOMNode *
1064 e_editor_dom_get_parent_block_node_from_child (WebKitDOMNode *node)
1066 WebKitDOMNode *parent = node;
1068 if (!WEBKIT_DOM_IS_ELEMENT (parent) ||
1069 e_editor_dom_is_selection_position_node (parent))
1070 parent = webkit_dom_node_get_parent_node (parent);
1072 if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-quoted") ||
1073 element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-quote-character") ||
1074 element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-signature") ||
1075 element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-resizable-wrapper") ||
1076 WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent) ||
1077 element_has_tag (WEBKIT_DOM_ELEMENT (parent), "b") ||
1078 element_has_tag (WEBKIT_DOM_ELEMENT (parent), "i") ||
1079 element_has_tag (WEBKIT_DOM_ELEMENT (parent), "u"))
1080 parent = webkit_dom_node_get_parent_node (parent);
1082 if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-quoted") ||
1083 element_has_class (WEBKIT_DOM_ELEMENT (parent), "Apple-tab-span") ||
1084 element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-resizable-wrapper"))
1085 parent = webkit_dom_node_get_parent_node (parent);
1087 return parent;
1090 gboolean
1091 e_editor_dom_node_is_paragraph (WebKitDOMNode *node)
1093 if (!WEBKIT_DOM_IS_HTML_DIV_ELEMENT (node))
1094 return FALSE;
1096 return webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (node), "data-evo-paragraph");
1099 WebKitDOMElement *
1100 e_editor_dom_wrap_and_quote_element (EEditorPage *editor_page,
1101 WebKitDOMElement *element)
1103 gint citation_level;
1104 WebKitDOMElement *tmp_element = element;
1106 g_return_val_if_fail (WEBKIT_DOM_IS_ELEMENT (element), element);
1108 if (e_editor_page_get_html_mode (editor_page))
1109 return element;
1111 citation_level = e_editor_dom_get_citation_level (WEBKIT_DOM_NODE (element));
1113 e_editor_dom_remove_quoting_from_element (element);
1114 e_editor_dom_remove_wrapping_from_element (element);
1116 if (e_editor_dom_node_is_paragraph (WEBKIT_DOM_NODE (element))) {
1117 gint word_wrap_length, length;
1119 word_wrap_length = e_editor_page_get_word_wrap_length (editor_page);
1120 length = word_wrap_length - 2 * citation_level;
1121 tmp_element = e_editor_dom_wrap_paragraph_length (
1122 editor_page, element, length);
1125 if (citation_level > 0) {
1126 webkit_dom_node_normalize (WEBKIT_DOM_NODE (tmp_element));
1127 e_editor_dom_quote_plain_text_element_after_wrapping (
1128 editor_page, tmp_element, citation_level);
1131 return tmp_element;
1134 WebKitDOMElement *
1135 e_editor_dom_insert_new_line_into_citation (EEditorPage *editor_page,
1136 const gchar *html_to_insert)
1138 WebKitDOMDocument *document;
1139 WebKitDOMElement *element, *paragraph = NULL;
1140 WebKitDOMNode *last_block;
1141 gboolean html_mode = FALSE, ret_val, avoid_editor_call;
1143 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
1145 document = e_editor_page_get_document (editor_page);
1146 html_mode = e_editor_page_get_html_mode (editor_page);
1148 avoid_editor_call = return_pressed_in_empty_line (editor_page);
1150 if (avoid_editor_call) {
1151 WebKitDOMElement *selection_start_marker;
1152 WebKitDOMNode *current_block, *parent, *parent_block, *block_clone;
1154 e_editor_dom_selection_save (editor_page);
1156 selection_start_marker = webkit_dom_document_get_element_by_id (
1157 document, "-x-evo-selection-start-marker");
1159 current_block = e_editor_dom_get_parent_block_node_from_child (
1160 WEBKIT_DOM_NODE (selection_start_marker));
1162 block_clone = webkit_dom_node_clone_node_with_error (current_block, TRUE, NULL);
1163 /* Find selection start marker and restore it after the new line
1164 * is inserted */
1165 selection_start_marker = webkit_dom_element_query_selector (
1166 WEBKIT_DOM_ELEMENT (block_clone), "#-x-evo-selection-start-marker", NULL);
1168 /* Find parent node that is immediate child of the BODY */
1169 /* Build the same structure of parent nodes of the current block */
1170 parent_block = current_block;
1171 parent = webkit_dom_node_get_parent_node (parent_block);
1172 while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
1173 WebKitDOMNode *node;
1175 parent_block = parent;
1176 node = webkit_dom_node_clone_node_with_error (parent_block, FALSE, NULL);
1177 webkit_dom_node_append_child (node, block_clone, NULL);
1178 block_clone = node;
1179 parent = webkit_dom_node_get_parent_node (parent_block);
1182 paragraph = e_editor_dom_get_paragraph_element (editor_page, -1, 0);
1184 webkit_dom_node_append_child (
1185 WEBKIT_DOM_NODE (paragraph),
1186 WEBKIT_DOM_NODE (
1187 webkit_dom_document_create_element (document, "BR", NULL)),
1188 NULL);
1190 /* Insert the selection markers to right place */
1191 webkit_dom_node_insert_before (
1192 WEBKIT_DOM_NODE (paragraph),
1193 webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_start_marker)),
1194 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (paragraph)),
1195 NULL);
1196 webkit_dom_node_insert_before (
1197 WEBKIT_DOM_NODE (paragraph),
1198 WEBKIT_DOM_NODE (selection_start_marker),
1199 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (paragraph)),
1200 NULL);
1202 /* Insert the cloned nodes before the BODY parent node */
1203 webkit_dom_node_insert_before (
1204 webkit_dom_node_get_parent_node (parent_block),
1205 block_clone,
1206 parent_block,
1207 NULL);
1209 /* Insert the new empty paragraph before the BODY parent node */
1210 webkit_dom_node_insert_before (
1211 webkit_dom_node_get_parent_node (parent_block),
1212 WEBKIT_DOM_NODE (paragraph),
1213 parent_block,
1214 NULL);
1216 /* Remove the old block (its copy was moved to the right place) */
1217 remove_node (current_block);
1219 e_editor_dom_selection_restore (editor_page);
1221 return NULL;
1222 } else {
1223 e_editor_dom_remove_input_event_listener_from_body (editor_page);
1224 e_editor_page_block_selection_changed (editor_page);
1226 ret_val = e_editor_dom_exec_command (
1227 editor_page, E_CONTENT_EDITOR_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT, NULL);
1229 e_editor_page_unblock_selection_changed (editor_page);
1230 e_editor_dom_register_input_event_listener_on_body (editor_page);
1232 if (!ret_val)
1233 return NULL;
1235 element = webkit_dom_document_query_selector (
1236 document, "body>br", NULL);
1238 if (!element)
1239 return NULL;
1242 last_block = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element));
1243 while (last_block && e_editor_dom_node_is_citation_node (last_block))
1244 last_block = webkit_dom_node_get_last_child (last_block);
1246 if (last_block) {
1247 WebKitDOMNode *last_child;
1249 if ((last_child = webkit_dom_node_get_last_child (last_block))) {
1250 if (WEBKIT_DOM_IS_ELEMENT (last_child) &&
1251 element_has_class (WEBKIT_DOM_ELEMENT (last_child), "-x-evo-quoted"))
1252 webkit_dom_node_append_child (
1253 last_block,
1254 WEBKIT_DOM_NODE (
1255 webkit_dom_document_create_element (
1256 document, "br", NULL)),
1257 NULL);
1261 if (!html_mode) {
1262 WebKitDOMNode *sibling;
1264 sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element));
1266 if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (sibling)) {
1267 WebKitDOMNode *node;
1269 node = webkit_dom_node_get_first_child (sibling);
1270 while (node && e_editor_dom_node_is_citation_node (node))
1271 node = webkit_dom_node_get_first_child (node);
1273 /* Rewrap and requote nodes that were created by split. */
1274 if (WEBKIT_DOM_IS_ELEMENT (node))
1275 e_editor_dom_wrap_and_quote_element (editor_page, WEBKIT_DOM_ELEMENT (node));
1277 if (WEBKIT_DOM_IS_ELEMENT (last_block))
1278 e_editor_dom_wrap_and_quote_element (editor_page, WEBKIT_DOM_ELEMENT (last_block));
1280 e_editor_dom_force_spell_check_in_viewport (editor_page);
1284 if (html_to_insert && *html_to_insert) {
1285 paragraph = e_editor_dom_prepare_paragraph (editor_page, FALSE);
1286 webkit_dom_element_set_inner_html (
1287 paragraph, html_to_insert, NULL);
1288 if (!webkit_dom_element_query_selector (paragraph, "#-x-evo-selection-start-marker", NULL))
1289 dom_add_selection_markers_into_element_end (
1290 document, paragraph, NULL, NULL);
1291 } else
1292 paragraph = e_editor_dom_prepare_paragraph (editor_page, TRUE);
1294 webkit_dom_node_insert_before (
1295 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
1296 WEBKIT_DOM_NODE (paragraph),
1297 WEBKIT_DOM_NODE (element),
1298 NULL);
1300 remove_node (WEBKIT_DOM_NODE (element));
1302 e_editor_dom_selection_restore (editor_page);
1304 return paragraph;
1307 /* For purpose of this function see e-mail-formatter-quote.c */
1308 static void
1309 put_body_in_citation (WebKitDOMDocument *document)
1311 WebKitDOMElement *cite_body = webkit_dom_document_query_selector (
1312 document, "span.-x-evo-cite-body", NULL);
1314 if (cite_body) {
1315 WebKitDOMHTMLElement *body = webkit_dom_document_get_body (document);
1316 WebKitDOMNode *citation;
1317 WebKitDOMNode *sibling;
1319 citation = WEBKIT_DOM_NODE (
1320 webkit_dom_document_create_element (document, "blockquote", NULL));
1321 webkit_dom_element_set_id (WEBKIT_DOM_ELEMENT (citation), "-x-evo-main-cite");
1322 webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (citation), "type", "cite", NULL);
1324 webkit_dom_node_insert_before (
1325 WEBKIT_DOM_NODE (body),
1326 citation,
1327 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)),
1328 NULL);
1330 while ((sibling = webkit_dom_node_get_next_sibling (citation)))
1331 webkit_dom_node_append_child (citation, sibling, NULL);
1333 remove_node (WEBKIT_DOM_NODE (cite_body));
1337 /* For purpose of this function see e-mail-formatter-quote.c */
1338 static void
1339 move_elements_to_body (EEditorPage *editor_page)
1341 WebKitDOMDocument *document;
1342 WebKitDOMHTMLElement *body;
1343 WebKitDOMNodeList *list = NULL;
1344 gint ii, jj;
1346 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
1348 document = e_editor_page_get_document (editor_page);
1349 body = webkit_dom_document_get_body (document);
1350 list = webkit_dom_document_query_selector_all (
1351 document, "div[data-headers]", NULL);
1352 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
1353 WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
1355 webkit_dom_element_remove_attribute (
1356 WEBKIT_DOM_ELEMENT (node), "data-headers");
1357 webkit_dom_node_insert_before (
1358 WEBKIT_DOM_NODE (body),
1359 node,
1360 webkit_dom_node_get_first_child (
1361 WEBKIT_DOM_NODE (body)),
1362 NULL);
1365 g_clear_object (&list);
1367 list = webkit_dom_document_query_selector_all (
1368 document, "span.-x-evo-to-body[data-credits]", NULL);
1369 e_editor_page_set_allow_top_signature (editor_page, webkit_dom_node_list_get_length (list) > 0);
1370 for (jj = 0, ii = webkit_dom_node_list_get_length (list); ii--; jj++) {
1371 char *credits;
1372 WebKitDOMElement *element;
1373 WebKitDOMNode *node = webkit_dom_node_list_item (list, jj);
1375 element = e_editor_dom_get_paragraph_element (editor_page, -1, 0);
1376 credits = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "data-credits");
1377 if (credits)
1378 webkit_dom_html_element_set_inner_text (WEBKIT_DOM_HTML_ELEMENT (element), credits, NULL);
1379 g_free (credits);
1381 webkit_dom_node_insert_before (
1382 WEBKIT_DOM_NODE (body),
1383 WEBKIT_DOM_NODE (element),
1384 webkit_dom_node_get_first_child (
1385 WEBKIT_DOM_NODE (body)),
1386 NULL);
1388 remove_node (node);
1390 g_clear_object (&list);
1393 static void
1394 repair_blockquotes (WebKitDOMDocument *document)
1396 WebKitDOMHTMLCollection *collection;
1397 gulong ii;
1399 collection = webkit_dom_document_get_elements_by_class_name_as_html_collection (
1400 document, "gmail_quote");
1401 for (ii = webkit_dom_html_collection_get_length (collection); ii--;) {
1402 WebKitDOMNode *node = webkit_dom_html_collection_item (collection, ii);
1404 if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node))
1405 continue;
1407 webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "class");
1408 webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "style");
1409 webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "type", "cite", NULL);
1411 if (!WEBKIT_DOM_IS_HTML_BR_ELEMENT (webkit_dom_node_get_last_child (node)) &&
1412 webkit_dom_node_get_next_sibling (node))
1413 webkit_dom_node_append_child (
1414 node,
1415 WEBKIT_DOM_NODE (
1416 webkit_dom_document_create_element (
1417 document, "br", NULL)),
1418 NULL);
1420 g_clear_object (&collection);
1422 collection = webkit_dom_document_get_elements_by_tag_name_as_html_collection (document, "blockquote");
1423 for (ii = webkit_dom_html_collection_get_length (collection); ii--;) {
1424 WebKitDOMNode *node = webkit_dom_html_collection_item (collection, ii);
1426 if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node))
1427 continue;
1429 webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "class");
1430 webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "style");
1431 webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "type", "cite", NULL);
1433 g_clear_object (&collection);
1436 static void
1437 style_blockquotes (WebKitDOMElement *element)
1439 WebKitDOMNodeList *list;
1440 gulong ii;
1442 g_return_if_fail (WEBKIT_DOM_IS_ELEMENT (element));
1444 list = webkit_dom_element_query_selector_all (element, "blockquote", NULL);
1445 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
1446 WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
1448 if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node))
1449 continue;
1451 webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "style", E_EVOLUTION_BLOCKQUOTE_STYLE, NULL);
1453 g_clear_object (&list);
1456 static void
1457 remove_thunderbird_signature (WebKitDOMDocument *document)
1459 WebKitDOMElement *signature;
1461 signature = webkit_dom_document_query_selector (
1462 document, "pre.moz-signature", NULL);
1463 if (signature)
1464 remove_node (WEBKIT_DOM_NODE (signature));
1467 void
1468 e_editor_dom_check_magic_links (EEditorPage *editor_page,
1469 gboolean include_space_by_user)
1471 WebKitDOMDocument *document;
1472 WebKitDOMNode *node;
1473 WebKitDOMRange *range = NULL;
1474 gchar *node_text;
1475 gchar **urls;
1476 gboolean include_space = FALSE;
1477 gboolean is_email_address = FALSE;
1478 gboolean return_key_pressed;
1479 GRegex *regex = NULL;
1480 GMatchInfo *match_info;
1481 gint start_pos_url, end_pos_url;
1482 gboolean has_selection;
1484 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
1486 if (!e_editor_page_get_magic_links_enabled (editor_page))
1487 return;
1489 return_key_pressed = e_editor_page_get_return_key_pressed (editor_page);
1490 document = e_editor_page_get_document (editor_page);
1492 if (include_space_by_user)
1493 include_space = TRUE;
1494 else
1495 include_space = e_editor_page_get_space_key_pressed (editor_page);
1497 range = e_editor_dom_get_current_range (editor_page);
1498 node = webkit_dom_range_get_end_container (range, NULL);
1499 has_selection = !webkit_dom_range_get_collapsed (range, NULL);
1500 g_clear_object (&range);
1502 if (return_key_pressed) {
1503 WebKitDOMNode* block;
1505 block = e_editor_dom_get_parent_block_node_from_child (node);
1506 /* Get previous block */
1507 if (!(block = webkit_dom_node_get_previous_sibling (block)))
1508 return;
1510 /* If block is quoted content, get the last block there */
1511 while (block && WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (block))
1512 block = webkit_dom_node_get_last_child (block);
1514 /* Get the last non-empty node */
1515 node = webkit_dom_node_get_last_child (block);
1516 if (WEBKIT_DOM_IS_CHARACTER_DATA (node) &&
1517 webkit_dom_character_data_get_length (WEBKIT_DOM_CHARACTER_DATA (node)) == 0)
1518 node = webkit_dom_node_get_previous_sibling (node);
1519 } else {
1520 e_editor_dom_selection_save (editor_page);
1521 if (has_selection) {
1522 WebKitDOMElement *selection_end_marker;
1524 selection_end_marker = webkit_dom_document_get_element_by_id (
1525 document, "-x-evo-selection-end-marker");
1527 node = webkit_dom_node_get_previous_sibling (
1528 WEBKIT_DOM_NODE (selection_end_marker));
1532 if (!node || WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node))
1533 goto out;
1535 if (!WEBKIT_DOM_IS_TEXT (node)) {
1536 if (webkit_dom_node_has_child_nodes (node))
1537 node = webkit_dom_node_get_first_child (node);
1538 if (!WEBKIT_DOM_IS_TEXT (node))
1539 goto out;
1542 node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node));
1543 if (!(node_text && *node_text) || !g_utf8_validate (node_text, -1, NULL)) {
1544 g_free (node_text);
1545 goto out;
1548 if (strstr (node_text, "@") && !strstr (node_text, "://")) {
1549 is_email_address = TRUE;
1550 regex = g_regex_new (include_space ? E_MAIL_PATTERN_SPACE : E_MAIL_PATTERN, 0, 0, NULL);
1551 } else
1552 regex = g_regex_new (include_space ? URL_PATTERN_SPACE : URL_PATTERN, 0, 0, NULL);
1554 if (!regex) {
1555 g_free (node_text);
1556 goto out;
1559 g_regex_match_all (regex, node_text, G_REGEX_MATCH_NOTEMPTY, &match_info);
1560 urls = g_match_info_fetch_all (match_info);
1562 if (urls) {
1563 const gchar *end_of_match = NULL;
1564 gchar *final_url = NULL, *url_end_raw, *url_text;
1565 glong url_start, url_end, url_length;
1566 WebKitDOMNode *url_text_node;
1567 WebKitDOMElement *anchor;
1569 g_match_info_fetch_pos (match_info, 0, &start_pos_url, &end_pos_url);
1571 /* Get start and end position of URL in node's text because positions
1572 * that we get from g_match_info_fetch_pos are not UTF-8 aware */
1573 url_end_raw = g_strndup (node_text, end_pos_url);
1574 url_end = g_utf8_strlen (url_end_raw, -1);
1575 url_length = g_utf8_strlen (urls[0], -1);
1577 end_of_match = url_end_raw + end_pos_url - (include_space ? 3 : 2);
1578 /* URLs are extremely unlikely to end with any punctuation, so
1579 * strip any trailing punctuation off from link and put it after
1580 * the link. Do the same for any closing double-quotes as well. */
1581 while (end_of_match && end_of_match != url_end_raw && strchr (URL_INVALID_TRAILING_CHARS, *end_of_match)) {
1582 url_length--;
1583 url_end--;
1584 end_of_match--;
1587 url_start = url_end - url_length;
1589 webkit_dom_text_split_text (
1590 WEBKIT_DOM_TEXT (node),
1591 include_space ? url_end - 1 : url_end,
1592 NULL);
1594 webkit_dom_text_split_text (
1595 WEBKIT_DOM_TEXT (node), url_start, NULL);
1596 url_text_node = webkit_dom_node_get_next_sibling (node);
1597 if (url_text_node)
1598 url_text = webkit_dom_character_data_get_data (WEBKIT_DOM_CHARACTER_DATA (url_text_node));
1599 else
1600 url_text = NULL;
1602 /* Sanity check */
1603 if (!url_text || !*url_text || strrchr (url_text, ' '))
1604 goto skip;
1606 if (g_str_has_prefix (url_text, "www."))
1607 final_url = g_strconcat ("http://" , url_text, NULL);
1608 else if (is_email_address)
1609 final_url = g_strconcat ("mailto:" , url_text, NULL);
1610 else
1611 final_url = g_strdup (url_text);
1613 /* Create and prepare new anchor element */
1614 anchor = webkit_dom_document_create_element (document, "A", NULL);
1616 webkit_dom_element_set_inner_html (anchor, url_text, NULL);
1618 webkit_dom_html_anchor_element_set_href (
1619 WEBKIT_DOM_HTML_ANCHOR_ELEMENT (anchor),
1620 final_url);
1622 /* Insert new anchor element into document */
1623 webkit_dom_node_replace_child (
1624 webkit_dom_node_get_parent_node (node),
1625 WEBKIT_DOM_NODE (anchor),
1626 WEBKIT_DOM_NODE (url_text_node),
1627 NULL);
1629 skip:
1630 g_free (url_end_raw);
1631 g_free (url_text);
1632 if (final_url)
1633 g_free (final_url);
1634 } else {
1635 gboolean appending_to_link = FALSE;
1636 gchar *href, *text, *url, *text_to_append = NULL;
1637 gint diff;
1638 WebKitDOMElement *parent;
1639 WebKitDOMNode *prev_sibling;
1641 parent = webkit_dom_node_get_parent_element (node);
1642 prev_sibling = webkit_dom_node_get_previous_sibling (node);
1644 /* If previous sibling is ANCHOR and actual text node is not beginning with
1645 * space => we're appending to link */
1646 if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling)) {
1647 text_to_append = webkit_dom_node_get_text_content (node);
1648 if (text_to_append && *text_to_append &&
1649 !strstr (text_to_append, " ") &&
1650 !(strchr (URL_INVALID_TRAILING_CHARS, *text_to_append) &&
1651 !(*text_to_append == '?' && strlen(text_to_append) > 1)) &&
1652 !g_str_has_prefix (text_to_append, UNICODE_NBSP)) {
1654 appending_to_link = TRUE;
1655 parent = WEBKIT_DOM_ELEMENT (prev_sibling);
1656 /* If the node(text) contains the some of unwanted characters
1657 * split it into two nodes and select the right one. */
1658 if (g_str_has_suffix (text_to_append, UNICODE_NBSP) ||
1659 g_str_has_suffix (text_to_append, UNICODE_ZERO_WIDTH_SPACE)) {
1660 webkit_dom_text_split_text (
1661 WEBKIT_DOM_TEXT (node),
1662 g_utf8_strlen (text_to_append, -1) - 1,
1663 NULL);
1664 g_free (text_to_append);
1665 text_to_append = webkit_dom_node_get_text_content (node);
1670 /* If parent is ANCHOR => we're editing the link */
1671 if ((!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent) && !appending_to_link) || !text_to_append) {
1672 g_match_info_free (match_info);
1673 g_regex_unref (regex);
1674 g_free (node_text);
1675 g_free (text_to_append);
1676 goto out;
1679 /* edit only if href and description are the same */
1680 href = webkit_dom_html_anchor_element_get_href (
1681 WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent));
1683 if (appending_to_link) {
1684 gchar *inner_text;
1686 inner_text =
1687 webkit_dom_html_element_get_inner_text (
1688 WEBKIT_DOM_HTML_ELEMENT (parent)),
1690 text = g_strconcat (inner_text, text_to_append, NULL);
1691 g_free (inner_text);
1692 } else
1693 text = webkit_dom_html_element_get_inner_text (
1694 WEBKIT_DOM_HTML_ELEMENT (parent));
1696 element_remove_class (parent, "-x-evo-visited-link");
1698 if (strstr (href, "://") && !strstr (text, "://")) {
1699 url = strstr (href, "://") + 3;
1700 diff = strlen (text) - strlen (url);
1702 if (text [strlen (text) - 1] != '/')
1703 diff++;
1705 if ((g_strcmp0 (url, text) != 0 && ABS (diff) == 1) || appending_to_link) {
1706 gchar *new_href;
1708 new_href = g_strconcat (href, appending_to_link ? "" : text_to_append, NULL);
1710 webkit_dom_html_anchor_element_set_href (
1711 WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent),
1712 new_href);
1714 if (appending_to_link) {
1715 webkit_dom_element_insert_adjacent_html (
1716 WEBKIT_DOM_ELEMENT (parent),
1717 "beforeend",
1718 text_to_append,
1719 NULL);
1721 remove_node (node);
1724 g_free (new_href);
1726 } else {
1727 diff = strlen (text) - strlen (href);
1728 if (text [strlen (text) - 1] != '/')
1729 diff++;
1731 if ((g_strcmp0 (href, text) != 0 && ABS (diff) == 1) || appending_to_link) {
1732 gchar *inner_html;
1733 gchar *new_href;
1735 inner_html = webkit_dom_element_get_inner_html (parent);
1736 new_href = g_strconcat (
1737 inner_html,
1738 appending_to_link ? text_to_append : "",
1739 NULL);
1741 webkit_dom_html_anchor_element_set_href (
1742 WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent),
1743 new_href);
1745 if (appending_to_link) {
1746 webkit_dom_element_insert_adjacent_html (
1747 WEBKIT_DOM_ELEMENT (parent),
1748 "beforeend",
1749 text_to_append,
1750 NULL);
1752 remove_node (node);
1755 g_free (new_href);
1756 g_free (inner_html);
1760 g_free (text_to_append);
1761 g_free (text);
1762 g_free (href);
1765 g_match_info_free (match_info);
1766 g_regex_unref (regex);
1767 g_free (node_text);
1769 out:
1770 if (!return_key_pressed)
1771 e_editor_dom_selection_restore (editor_page);
1774 void
1775 e_editor_dom_embed_style_sheet (EEditorPage *editor_page,
1776 const gchar *style_sheet_content)
1778 WebKitDOMDocument *document;
1779 WebKitDOMElement *sheet;
1781 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
1783 document = e_editor_page_get_document (editor_page);
1785 e_dom_utils_create_and_add_css_style_sheet (document, "-x-evo-composer-sheet");
1787 sheet = webkit_dom_document_get_element_by_id (document, "-x-evo-composer-sheet");
1788 webkit_dom_element_set_attribute (
1789 sheet,
1790 "type",
1791 "text/css",
1792 NULL);
1794 webkit_dom_element_set_inner_html (sheet, style_sheet_content, NULL);
1797 void
1798 e_editor_dom_remove_embedded_style_sheet (EEditorPage *editor_page)
1800 WebKitDOMDocument *document;
1801 WebKitDOMElement *sheet;
1803 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
1805 document = e_editor_page_get_document (editor_page);
1807 sheet = webkit_dom_document_get_element_by_id (
1808 document, "-x-evo-composer-sheet");
1810 if (sheet)
1811 remove_node (WEBKIT_DOM_NODE (sheet));
1814 static void
1815 insert_delete_event (EEditorPage *editor_page,
1816 WebKitDOMRange *range)
1818 EEditorHistoryEvent *ev;
1819 WebKitDOMDocumentFragment *fragment;
1820 EEditorUndoRedoManager *manager;
1822 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
1824 manager = e_editor_page_get_undo_redo_manager (editor_page);
1826 if (e_editor_undo_redo_manager_is_operation_in_progress (manager))
1827 return;
1829 ev = g_new0 (EEditorHistoryEvent, 1);
1830 ev->type = HISTORY_DELETE;
1832 fragment = webkit_dom_range_clone_contents (range, NULL);
1833 ev->data.fragment = g_object_ref (fragment);
1835 e_editor_dom_selection_get_coordinates (editor_page,
1836 &ev->before.start.x,
1837 &ev->before.start.y,
1838 &ev->before.end.x,
1839 &ev->before.end.y);
1841 ev->after.start.x = ev->before.start.x;
1842 ev->after.start.y = ev->before.start.y;
1843 ev->after.end.x = ev->before.start.x;
1844 ev->after.end.y = ev->before.start.y;
1846 e_editor_undo_redo_manager_insert_history_event (manager, ev);
1848 ev = g_new0 (EEditorHistoryEvent, 1);
1849 ev->type = HISTORY_AND;
1851 e_editor_undo_redo_manager_insert_history_event (manager, ev);
1854 /* Based on original use_pictograms() from GtkHTML */
1855 static const gchar *emoticons_chars =
1856 /* 0 */ "DO)(|/PQ*!"
1857 /* 10 */ "S\0:-\0:\0:-\0"
1858 /* 20 */ ":\0:;=-\"\0:;"
1859 /* 30 */ "B\"|\0:-'\0:X"
1860 /* 40 */ "\0:\0:-\0:\0:-"
1861 /* 50 */ "\0:\0:-\0:\0:-"
1862 /* 60 */ "\0:\0:\0:-\0:\0"
1863 /* 70 */ ":-\0:\0:-\0:\0";
1864 static gint emoticons_states[] = {
1865 /* 0 */ 12, 17, 22, 34, 43, 48, 53, 58, 65, 70,
1866 /* 10 */ 75, 0, -15, 15, 0, -15, 0, -17, 20, 0,
1867 /* 20 */ -17, 0, -14, -20, -14, 28, 63, 0, -14, -20,
1868 /* 30 */ -3, 63, -18, 0, -12, 38, 41, 0, -12, -2,
1869 /* 40 */ 0, -4, 0, -10, 46, 0, -10, 0, -19, 51,
1870 /* 50 */ 0, -19, 0, -11, 56, 0, -11, 0, -13, 61,
1871 /* 60 */ 0, -13, 0, -6, 0, 68, -7, 0, -7, 0,
1872 /* 70 */ -16, 73, 0, -16, 0, -21, 78, 0, -21, 0 };
1873 static const gchar *emoticons_icon_names[] = {
1874 "face-angel",
1875 "face-angry",
1876 "face-cool",
1877 "face-crying",
1878 "face-devilish",
1879 "face-embarrassed",
1880 "face-kiss",
1881 "face-laugh", /* not used */
1882 "face-monkey", /* not used */
1883 "face-plain",
1884 "face-raspberry",
1885 "face-sad",
1886 "face-sick",
1887 "face-smile",
1888 "face-smile-big",
1889 "face-smirk",
1890 "face-surprise",
1891 "face-tired",
1892 "face-uncertain",
1893 "face-wink",
1894 "face-worried"
1897 typedef struct _EmoticonLoadContext {
1898 EEmoticon *emoticon;
1899 EEditorPage *editor_page;
1900 gchar *content_type;
1901 gchar *name;
1902 } EmoticonLoadContext;
1904 static EmoticonLoadContext *
1905 emoticon_load_context_new (EEditorPage *editor_page,
1906 EEmoticon *emoticon)
1908 EmoticonLoadContext *load_context;
1910 load_context = g_slice_new0 (EmoticonLoadContext);
1911 load_context->emoticon = emoticon;
1912 load_context->editor_page = editor_page;
1914 return load_context;
1917 static void
1918 emoticon_load_context_free (EmoticonLoadContext *load_context)
1920 g_free (load_context->content_type);
1921 g_free (load_context->name);
1922 g_slice_free (EmoticonLoadContext, load_context);
1925 static void
1926 emoticon_insert_span (EEmoticon *emoticon,
1927 EmoticonLoadContext *load_context,
1928 WebKitDOMElement *span)
1930 EEditorHistoryEvent *ev = NULL;
1931 EEditorUndoRedoManager *manager;
1932 EEditorPage *editor_page = load_context->editor_page;
1933 gboolean misplaced_selection = FALSE, smiley_written;
1934 gchar *node_text = NULL;
1935 const gchar *emoticon_start;
1936 WebKitDOMDocument *document;
1937 WebKitDOMElement *selection_start_marker, *selection_end_marker;
1938 WebKitDOMNode *node, *insert_before, *prev_sibling, *next_sibling;
1939 WebKitDOMNode *selection_end_marker_parent, *inserted_node;
1940 WebKitDOMRange *range = NULL;
1942 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
1944 document = e_editor_page_get_document (editor_page);
1945 smiley_written = e_editor_page_get_is_smiley_written (editor_page);
1946 manager = e_editor_page_get_undo_redo_manager (editor_page);
1948 if (e_editor_dom_selection_is_collapsed (editor_page)) {
1949 e_editor_dom_selection_save (editor_page);
1951 selection_start_marker = webkit_dom_document_get_element_by_id (
1952 document, "-x-evo-selection-start-marker");
1953 selection_end_marker = webkit_dom_document_get_element_by_id (
1954 document, "-x-evo-selection-end-marker");
1956 if (!smiley_written) {
1957 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
1958 ev = g_new0 (EEditorHistoryEvent, 1);
1959 if (e_editor_page_get_unicode_smileys_enabled (editor_page))
1960 ev->type = HISTORY_INPUT;
1961 else {
1962 ev->type = HISTORY_SMILEY;
1964 e_editor_dom_selection_get_coordinates (editor_page,
1965 &ev->before.start.x,
1966 &ev->before.start.y,
1967 &ev->before.end.x,
1968 &ev->before.end.y);
1972 } else {
1973 WebKitDOMRange *tmp_range = NULL;
1975 tmp_range = e_editor_dom_get_current_range (editor_page);
1976 insert_delete_event (editor_page, tmp_range);
1977 g_clear_object (&tmp_range);
1979 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_DELETE, NULL);
1981 if (!smiley_written) {
1982 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
1983 ev = g_new0 (EEditorHistoryEvent, 1);
1985 if (e_editor_page_get_unicode_smileys_enabled (editor_page))
1986 ev->type = HISTORY_INPUT;
1987 else {
1988 ev->type = HISTORY_SMILEY;
1990 e_editor_dom_selection_get_coordinates (editor_page,
1991 &ev->before.start.x,
1992 &ev->before.start.y,
1993 &ev->before.end.x,
1994 &ev->before.end.y);
1999 e_editor_dom_selection_save (editor_page);
2001 selection_start_marker = webkit_dom_document_get_element_by_id (
2002 document, "-x-evo-selection-start-marker");
2003 selection_end_marker = webkit_dom_document_get_element_by_id (
2004 document, "-x-evo-selection-end-marker");
2007 /* If the selection was not saved, move it into the first child of body */
2008 if (!selection_start_marker || !selection_end_marker) {
2009 WebKitDOMHTMLElement *body;
2010 WebKitDOMNode *child;
2012 body = webkit_dom_document_get_body (document);
2013 child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
2015 dom_add_selection_markers_into_element_start (
2016 document,
2017 WEBKIT_DOM_ELEMENT (child),
2018 &selection_start_marker,
2019 &selection_end_marker);
2021 if (ev && !e_editor_page_get_unicode_smileys_enabled (editor_page))
2022 e_editor_dom_selection_get_coordinates (editor_page,
2023 &ev->before.start.x,
2024 &ev->before.start.y,
2025 &ev->before.end.x,
2026 &ev->before.end.y);
2029 /* Sometimes selection end marker is in body. Move it into next sibling */
2030 selection_end_marker_parent = e_editor_dom_get_parent_block_node_from_child (
2031 WEBKIT_DOM_NODE (selection_end_marker));
2032 if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (selection_end_marker_parent)) {
2033 webkit_dom_node_insert_before (
2034 webkit_dom_node_get_parent_node (
2035 WEBKIT_DOM_NODE (selection_start_marker)),
2036 WEBKIT_DOM_NODE (selection_end_marker),
2037 WEBKIT_DOM_NODE (selection_start_marker),
2038 NULL);
2039 if (ev && !e_editor_page_get_unicode_smileys_enabled (editor_page))
2040 e_editor_dom_selection_get_coordinates (editor_page,
2041 &ev->before.start.x,
2042 &ev->before.start.y,
2043 &ev->before.end.x,
2044 &ev->before.end.y);
2046 selection_end_marker_parent = webkit_dom_node_get_parent_node (
2047 WEBKIT_DOM_NODE (selection_end_marker));
2049 /* Determine before what node we have to insert the smiley */
2050 insert_before = WEBKIT_DOM_NODE (selection_start_marker);
2051 prev_sibling = webkit_dom_node_get_previous_sibling (
2052 WEBKIT_DOM_NODE (selection_start_marker));
2053 if (prev_sibling) {
2054 if (webkit_dom_node_is_same_node (
2055 prev_sibling, WEBKIT_DOM_NODE (selection_end_marker))) {
2056 insert_before = WEBKIT_DOM_NODE (selection_end_marker);
2057 } else {
2058 prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling);
2059 if (prev_sibling &&
2060 webkit_dom_node_is_same_node (
2061 prev_sibling, WEBKIT_DOM_NODE (selection_end_marker))) {
2062 insert_before = WEBKIT_DOM_NODE (selection_end_marker);
2065 } else
2066 insert_before = WEBKIT_DOM_NODE (selection_start_marker);
2068 /* Look if selection is misplaced - that means that the selection was
2069 * restored before the previously inserted smiley in situations when we
2070 * are writing more smileys in a row */
2071 next_sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_end_marker));
2072 if (next_sibling && WEBKIT_DOM_IS_ELEMENT (next_sibling))
2073 if (element_has_class (WEBKIT_DOM_ELEMENT (next_sibling), "-x-evo-smiley-wrapper"))
2074 misplaced_selection = TRUE;
2076 range = e_editor_dom_get_current_range (editor_page);
2077 node = webkit_dom_range_get_end_container (range, NULL);
2078 g_clear_object (&range);
2079 if (WEBKIT_DOM_IS_TEXT (node))
2080 node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node));
2082 if (misplaced_selection) {
2083 /* Insert smiley and selection markers after it */
2084 webkit_dom_node_insert_before (
2085 webkit_dom_node_get_parent_node (insert_before),
2086 WEBKIT_DOM_NODE (selection_start_marker),
2087 webkit_dom_node_get_next_sibling (next_sibling),
2088 NULL);
2089 webkit_dom_node_insert_before (
2090 webkit_dom_node_get_parent_node (insert_before),
2091 WEBKIT_DOM_NODE (selection_end_marker),
2092 webkit_dom_node_get_next_sibling (next_sibling),
2093 NULL);
2094 if (e_editor_page_get_unicode_smileys_enabled (editor_page))
2095 inserted_node = webkit_dom_node_insert_before (
2096 webkit_dom_node_get_parent_node (insert_before),
2097 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (span)),
2098 webkit_dom_node_get_next_sibling (next_sibling),
2099 NULL);
2100 else
2101 inserted_node = webkit_dom_node_insert_before (
2102 webkit_dom_node_get_parent_node (insert_before),
2103 WEBKIT_DOM_NODE (span),
2104 webkit_dom_node_get_next_sibling (next_sibling),
2105 NULL);
2106 } else {
2107 if (e_editor_page_get_unicode_smileys_enabled (editor_page))
2108 inserted_node = webkit_dom_node_insert_before (
2109 webkit_dom_node_get_parent_node (insert_before),
2110 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (span)),
2111 insert_before,
2112 NULL);
2113 else
2114 inserted_node = webkit_dom_node_insert_before (
2115 webkit_dom_node_get_parent_node (insert_before),
2116 WEBKIT_DOM_NODE (span),
2117 insert_before,
2118 NULL);
2121 if (!e_editor_page_get_unicode_smileys_enabled (editor_page)) {
2122 /* &#8203 == UNICODE_ZERO_WIDTH_SPACE */
2123 webkit_dom_element_insert_adjacent_html (
2124 WEBKIT_DOM_ELEMENT (span), "afterend", "&#8203;", NULL);
2127 if (ev) {
2128 WebKitDOMDocumentFragment *fragment;
2129 WebKitDOMNode *node;
2131 fragment = webkit_dom_document_create_document_fragment (document);
2132 node = webkit_dom_node_append_child (
2133 WEBKIT_DOM_NODE (fragment),
2134 webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (inserted_node), TRUE, NULL),
2135 NULL);
2136 if (e_editor_page_get_unicode_smileys_enabled (editor_page)) {
2137 webkit_dom_node_append_child (
2138 WEBKIT_DOM_NODE (fragment),
2139 WEBKIT_DOM_NODE (
2140 dom_create_selection_marker (document, TRUE)),
2141 NULL);
2142 webkit_dom_node_append_child (
2143 WEBKIT_DOM_NODE (fragment),
2144 WEBKIT_DOM_NODE (
2145 dom_create_selection_marker (document, FALSE)),
2146 NULL);
2147 } else
2148 webkit_dom_element_insert_adjacent_html (
2149 WEBKIT_DOM_ELEMENT (node), "afterend", "&#8203;", NULL);
2150 ev->data.fragment = g_object_ref (fragment);
2153 /* Remove the text that represents the text version of smiley that was
2154 * written into the composer. */
2155 if (node_text && smiley_written) {
2156 emoticon_start = g_utf8_strrchr (
2157 node_text, -1, g_utf8_get_char (emoticon->text_face));
2158 /* Check if the written smiley is really the one that we inserted. */
2159 if (emoticon_start) {
2160 /* The written smiley is the same as text version. */
2161 if (g_str_has_prefix (emoticon_start, emoticon->text_face)) {
2162 webkit_dom_character_data_delete_data (
2163 WEBKIT_DOM_CHARACTER_DATA (node),
2164 g_utf8_strlen (node_text, -1) - strlen (emoticon_start),
2165 strlen (emoticon->text_face),
2166 NULL);
2167 } else if (strstr (emoticon->text_face, "-")) {
2168 gboolean same = TRUE, compensate = FALSE;
2169 gint ii = 0, jj = 0;
2171 /* Try to recognize smileys without the dash e.g. :). */
2172 while (emoticon_start[ii] && emoticon->text_face[jj]) {
2173 if (emoticon_start[ii] == emoticon->text_face[jj]) {
2174 if (emoticon->text_face[jj+1] && emoticon->text_face[jj+1] == '-') {
2175 ii++;
2176 jj+=2;
2177 compensate = TRUE;
2178 } else {
2179 ii++;
2180 jj++;
2182 } else {
2183 same = FALSE;
2184 break;
2188 if (same) {
2189 webkit_dom_character_data_delete_data (
2190 WEBKIT_DOM_CHARACTER_DATA (node),
2191 g_utf8_strlen (node_text, -1) - strlen (emoticon_start),
2193 NULL);
2195 /* If we recognize smiley without dash, but we inserted
2196 * the text version with dash we need it insert new
2197 * history input event with that dash. */
2198 if (compensate)
2199 e_editor_undo_redo_manager_insert_dash_history_event (manager);
2203 e_editor_page_set_is_smiley_written (editor_page, FALSE);
2206 if (ev) {
2207 e_editor_dom_selection_get_coordinates (editor_page,
2208 &ev->after.start.x,
2209 &ev->after.start.y,
2210 &ev->after.end.x,
2211 &ev->after.end.y);
2212 e_editor_undo_redo_manager_insert_history_event (manager, ev);
2215 e_editor_dom_selection_restore (editor_page);
2217 e_editor_page_emit_content_changed (editor_page);
2219 g_free (node_text);
2222 static void
2223 emoticon_read_async_cb (GFile *file,
2224 GAsyncResult *result,
2225 EmoticonLoadContext *load_context)
2227 EEmoticon *emoticon = load_context->emoticon;
2228 EEditorPage *editor_page = load_context->editor_page;
2229 GError *error = NULL;
2230 gboolean html_mode;
2231 gchar *mime_type;
2232 gchar *base64_encoded, *output, *data;
2233 GFileInputStream *input_stream;
2234 GOutputStream *output_stream;
2235 gssize size;
2236 WebKitDOMElement *wrapper, *image, *smiley_text;
2237 WebKitDOMDocument *document;
2239 input_stream = g_file_read_finish (file, result, &error);
2240 g_return_if_fail (!error && input_stream);
2242 output_stream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
2244 size = g_output_stream_splice (
2245 output_stream, G_INPUT_STREAM (input_stream),
2246 G_OUTPUT_STREAM_SPLICE_NONE, NULL, &error);
2248 if (error || (size == -1))
2249 goto out;
2251 mime_type = g_content_type_get_mime_type (load_context->content_type);
2253 data = g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (output_stream));
2254 base64_encoded = g_base64_encode ((const guchar *) data, size);
2255 output = g_strconcat ("data:", mime_type, ";base64,", base64_encoded, NULL);
2257 html_mode = e_editor_page_get_html_mode (editor_page);
2258 document = e_editor_page_get_document (editor_page);
2260 /* Insert span with image representation and another one with text
2261 * representation and hide/show them dependant on active composer mode */
2262 wrapper = webkit_dom_document_create_element (document, "SPAN", NULL);
2263 if (html_mode)
2264 webkit_dom_element_set_attribute (
2265 wrapper, "class", "-x-evo-smiley-wrapper -x-evo-resizable-wrapper", NULL);
2266 else
2267 webkit_dom_element_set_attribute (
2268 wrapper, "class", "-x-evo-smiley-wrapper", NULL);
2270 image = webkit_dom_document_create_element (document, "IMG", NULL);
2271 webkit_dom_element_set_attribute (image, "src", output, NULL);
2272 webkit_dom_element_set_attribute (image, "data-inline", "", NULL);
2273 webkit_dom_element_set_attribute (image, "data-name", load_context->name, NULL);
2274 webkit_dom_element_set_attribute (image, "alt", emoticon->text_face, NULL);
2275 webkit_dom_element_set_attribute (image, "class", "-x-evo-smiley-img", NULL);
2276 webkit_dom_node_append_child (
2277 WEBKIT_DOM_NODE (wrapper), WEBKIT_DOM_NODE (image), NULL);
2279 smiley_text = webkit_dom_document_create_element (document, "SPAN", NULL);
2280 webkit_dom_element_set_attribute (smiley_text, "class", "-x-evo-smiley-text", NULL);
2281 webkit_dom_html_element_set_inner_text (
2282 WEBKIT_DOM_HTML_ELEMENT (smiley_text), emoticon->text_face, NULL);
2283 webkit_dom_node_append_child (
2284 WEBKIT_DOM_NODE (wrapper), WEBKIT_DOM_NODE (smiley_text), NULL);
2286 emoticon_insert_span (emoticon, load_context, wrapper);
2288 g_free (base64_encoded);
2289 g_free (output);
2290 g_free (mime_type);
2291 g_object_unref (output_stream);
2292 out:
2293 emoticon_load_context_free (load_context);
2296 static void
2297 emoticon_query_info_async_cb (GFile *file,
2298 GAsyncResult *result,
2299 EmoticonLoadContext *load_context)
2301 GError *error = NULL;
2302 GFileInfo *info;
2304 info = g_file_query_info_finish (file, result, &error);
2305 g_return_if_fail (!error && info);
2307 load_context->content_type = g_strdup (g_file_info_get_content_type (info));
2308 load_context->name = g_strdup (g_file_info_get_name (info));
2310 g_file_read_async (
2311 file, G_PRIORITY_DEFAULT, NULL,
2312 (GAsyncReadyCallback) emoticon_read_async_cb, load_context);
2314 g_object_unref (info);
2317 void
2318 e_editor_dom_insert_smiley (EEditorPage *editor_page,
2319 EEmoticon *emoticon)
2321 WebKitDOMDocument *document;
2322 GFile *file;
2323 gchar *filename_uri;
2324 EmoticonLoadContext *load_context;
2326 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
2328 document = e_editor_page_get_document (editor_page);
2330 if (e_editor_page_get_unicode_smileys_enabled (editor_page)) {
2331 WebKitDOMElement *wrapper;
2333 wrapper = webkit_dom_document_create_element (document, "SPAN", NULL);
2334 webkit_dom_html_element_set_inner_text (
2335 WEBKIT_DOM_HTML_ELEMENT (wrapper), emoticon->unicode_character, NULL);
2337 load_context = emoticon_load_context_new (editor_page, emoticon);
2338 emoticon_insert_span (emoticon, load_context, wrapper);
2339 emoticon_load_context_free (load_context);
2340 } else {
2341 filename_uri = e_emoticon_get_uri (emoticon);
2342 g_return_if_fail (filename_uri != NULL);
2344 load_context = emoticon_load_context_new (editor_page, emoticon);
2346 file = g_file_new_for_uri (filename_uri);
2347 g_file_query_info_async (
2348 file, "standard::*", G_FILE_QUERY_INFO_NONE,
2349 G_PRIORITY_DEFAULT, NULL,
2350 (GAsyncReadyCallback) emoticon_query_info_async_cb, load_context);
2352 g_free (filename_uri);
2353 g_object_unref (file);
2357 void
2358 e_editor_dom_insert_smiley_by_name (EEditorPage *editor_page,
2359 const gchar *name)
2361 const EEmoticon *emoticon;
2363 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
2365 emoticon = e_emoticon_chooser_lookup_emoticon (name);
2366 e_editor_page_set_is_smiley_written (editor_page, FALSE);
2367 e_editor_dom_insert_smiley (editor_page, (EEmoticon *) emoticon);
2370 void
2371 e_editor_dom_check_magic_smileys (EEditorPage *editor_page)
2373 WebKitDOMNode *node;
2374 WebKitDOMRange *range = NULL;
2375 gint pos, state, relative, start;
2376 gchar *node_text;
2377 gunichar uc;
2379 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
2381 if (!e_editor_page_get_magic_smileys_enabled (editor_page))
2382 return;
2384 range = e_editor_dom_get_current_range (editor_page);
2385 node = webkit_dom_range_get_end_container (range, NULL);
2386 if (!WEBKIT_DOM_IS_TEXT (node)) {
2387 g_clear_object (&range);
2388 return;
2391 node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node));
2392 if (node_text == NULL) {
2393 g_clear_object (&range);
2394 return;
2397 start = webkit_dom_range_get_end_offset (range, NULL) - 1;
2398 pos = start;
2399 state = 0;
2400 while (pos >= 0) {
2401 uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos));
2402 relative = 0;
2403 while (emoticons_chars[state + relative]) {
2404 if (emoticons_chars[state + relative] == uc)
2405 break;
2406 relative++;
2408 state = emoticons_states[state + relative];
2409 /* 0 .. not found, -n .. found n-th */
2410 if (state <= 0)
2411 break;
2412 pos--;
2415 /* Special case needed to recognize angel and devilish. */
2416 if (pos > 0 && state == -14) {
2417 uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos - 1));
2418 if (uc == 'O') {
2419 state = -1;
2420 pos--;
2421 } else if (uc == '>') {
2422 state = -5;
2423 pos--;
2427 if (state < 0) {
2428 const EEmoticon *emoticon;
2430 if (pos > 0) {
2431 uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos - 1));
2432 if (!g_unichar_isspace (uc)) {
2433 g_free (node_text);
2434 g_clear_object (&range);
2435 return;
2439 emoticon = e_emoticon_chooser_lookup_emoticon (
2440 emoticons_icon_names[-state - 1]);
2441 e_editor_page_set_is_smiley_written (editor_page, TRUE);
2442 e_editor_dom_insert_smiley (editor_page, (EEmoticon *) emoticon);
2445 g_clear_object (&range);
2446 g_free (node_text);
2449 static void
2450 dom_set_links_active (WebKitDOMDocument *document,
2451 gboolean active)
2453 WebKitDOMElement *style;
2455 style = webkit_dom_document_get_element_by_id (document, "-x-evo-style-a");
2456 if (style)
2457 remove_node (WEBKIT_DOM_NODE (style));
2459 if (!active) {
2460 WebKitDOMHTMLHeadElement *head;
2461 head = webkit_dom_document_get_head (document);
2463 style = webkit_dom_document_create_element (document, "STYLE", NULL);
2464 webkit_dom_element_set_id (style, "-x-evo-style-a");
2465 webkit_dom_element_set_attribute (style, "type", "text/css", NULL);
2466 webkit_dom_html_element_set_inner_text (
2467 WEBKIT_DOM_HTML_ELEMENT (style), "a { cursor: text; }", NULL);
2469 webkit_dom_node_append_child (
2470 WEBKIT_DOM_NODE (head), WEBKIT_DOM_NODE (style), NULL);
2474 static void
2475 fix_paragraph_structure_after_pressing_enter_after_smiley (WebKitDOMDocument *document)
2477 WebKitDOMElement *element;
2479 element = webkit_dom_document_query_selector (
2480 document, "span.-x-evo-smiley-wrapper > br", NULL);
2482 if (element) {
2483 WebKitDOMNode *parent;
2485 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
2486 webkit_dom_element_set_inner_html (
2487 webkit_dom_node_get_parent_element (parent),
2488 UNICODE_ZERO_WIDTH_SPACE,
2489 NULL);
2493 static gboolean
2494 fix_paragraph_structure_after_pressing_enter (EEditorPage *editor_page)
2496 WebKitDOMDocument *document;
2497 WebKitDOMNode *body, *prev_sibling, *node;
2498 WebKitDOMElement *br;
2499 gboolean prev_is_heading = FALSE;
2501 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
2503 document = e_editor_page_get_document (editor_page);
2504 body = WEBKIT_DOM_NODE (webkit_dom_document_get_body (document));
2506 e_editor_dom_selection_save (editor_page);
2508 /* When pressing Enter on empty line in the list (or after heading elements)
2509 * WebKit will end that list and inserts <div><br></div> so replace it
2510 * with the right paragraph element. */
2511 br = webkit_dom_document_query_selector (
2512 document, "body > div:not([data-evo-paragraph]) > #-x-evo-selection-end-marker + br", NULL);
2514 if (!br || webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (br)) ||
2515 webkit_dom_node_get_previous_sibling (webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (br))))
2516 goto out;
2518 node = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (br));
2520 prev_sibling = webkit_dom_node_get_previous_sibling (node);
2521 if (prev_sibling && WEBKIT_DOM_IS_HTML_HEADING_ELEMENT (prev_sibling))
2522 prev_is_heading = TRUE;
2524 webkit_dom_node_replace_child (
2525 body,
2526 WEBKIT_DOM_NODE (e_editor_dom_prepare_paragraph (editor_page, FALSE)),
2527 node,
2528 NULL);
2530 out:
2531 e_editor_dom_selection_restore (editor_page);
2533 return prev_is_heading;
2536 static gboolean
2537 surround_text_with_paragraph_if_needed (EEditorPage *editor_page,
2538 WebKitDOMNode *node)
2540 WebKitDOMNode *next_sibling = webkit_dom_node_get_next_sibling (node);
2541 WebKitDOMNode *prev_sibling = webkit_dom_node_get_previous_sibling (node);
2542 WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node);
2543 WebKitDOMElement *element;
2545 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
2547 /* All text in composer has to be written in div elements, so if
2548 * we are writing something straight to the body, surround it with
2549 * paragraph */
2550 if (WEBKIT_DOM_IS_TEXT (node) &&
2551 (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent) ||
2552 WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (parent))) {
2553 element = e_editor_dom_put_node_into_paragraph (editor_page, node, TRUE);
2554 if (WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (parent))
2555 webkit_dom_element_remove_attribute (element, "style");
2557 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (next_sibling))
2558 remove_node (next_sibling);
2560 /* Tab character */
2561 if (WEBKIT_DOM_IS_ELEMENT (prev_sibling) &&
2562 element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "Apple-tab-span")) {
2563 webkit_dom_node_insert_before (
2564 WEBKIT_DOM_NODE (element),
2565 prev_sibling,
2566 webkit_dom_node_get_first_child (
2567 WEBKIT_DOM_NODE (element)),
2568 NULL);
2571 return TRUE;
2574 return FALSE;
2577 static gboolean
2578 selection_is_in_table (WebKitDOMDocument *document,
2579 gboolean *first_cell,
2580 WebKitDOMNode **table_node)
2582 WebKitDOMDOMWindow *dom_window = NULL;
2583 WebKitDOMDOMSelection *dom_selection = NULL;
2584 WebKitDOMNode *node, *parent;
2585 WebKitDOMRange *range = NULL;
2587 dom_window = webkit_dom_document_get_default_view (document);
2588 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
2589 g_clear_object (&dom_window);
2591 if (first_cell != NULL)
2592 *first_cell = FALSE;
2594 if (table_node != NULL)
2595 *table_node = NULL;
2597 if (webkit_dom_dom_selection_get_range_count (dom_selection) < 1) {
2598 g_clear_object (&dom_selection);
2599 return FALSE;
2602 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
2603 node = webkit_dom_range_get_start_container (range, NULL);
2604 g_clear_object (&dom_selection);
2606 parent = node;
2607 while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
2608 if (WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (parent)) {
2609 if (first_cell != NULL) {
2610 if (!webkit_dom_node_get_previous_sibling (parent)) {
2611 gboolean on_start = TRUE;
2612 WebKitDOMNode *tmp;
2614 tmp = webkit_dom_node_get_previous_sibling (node);
2615 if (!tmp && WEBKIT_DOM_IS_TEXT (node))
2616 on_start = webkit_dom_range_get_start_offset (range, NULL) == 0;
2617 else if (tmp)
2618 on_start = FALSE;
2620 if (on_start) {
2621 node = webkit_dom_node_get_parent_node (parent);
2622 if (node && WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (node))
2623 if (!webkit_dom_node_get_previous_sibling (node))
2624 *first_cell = TRUE;
2627 } else {
2628 g_clear_object (&range);
2629 return TRUE;
2632 if (WEBKIT_DOM_IS_HTML_TABLE_ELEMENT (parent)) {
2633 if (table_node != NULL)
2634 *table_node = parent;
2635 else {
2636 g_clear_object (&range);
2637 return TRUE;
2640 parent = webkit_dom_node_get_parent_node (parent);
2643 g_clear_object (&range);
2645 if (table_node == NULL)
2646 return FALSE;
2648 return *table_node != NULL;
2651 static gboolean
2652 jump_to_next_table_cell (WebKitDOMDocument *document,
2653 gboolean jump_back)
2655 WebKitDOMDOMWindow *dom_window = NULL;
2656 WebKitDOMDOMSelection *dom_selection = NULL;
2657 WebKitDOMNode *node, *cell;
2658 WebKitDOMRange *range = NULL;
2660 if (!selection_is_in_table (document, NULL, NULL))
2661 return FALSE;
2663 dom_window = webkit_dom_document_get_default_view (document);
2664 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
2665 g_clear_object (&dom_window);
2666 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
2667 node = webkit_dom_range_get_start_container (range, NULL);
2669 cell = node;
2670 while (cell && !WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (cell)) {
2671 cell = webkit_dom_node_get_parent_node (cell);
2674 if (!WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (cell)) {
2675 g_clear_object (&range);
2676 g_clear_object (&dom_selection);
2677 return FALSE;
2680 if (jump_back) {
2681 /* Get previous cell */
2682 node = webkit_dom_node_get_previous_sibling (cell);
2683 if (!node || !WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (node)) {
2684 /* No cell, go one row up. */
2685 node = webkit_dom_node_get_parent_node (cell);
2686 node = webkit_dom_node_get_previous_sibling (node);
2687 if (node && WEBKIT_DOM_IS_HTML_TABLE_ROW_ELEMENT (node)) {
2688 node = webkit_dom_node_get_last_child (node);
2689 } else {
2690 /* No row above, move to the block before table. */
2691 node = webkit_dom_node_get_parent_node (cell);
2692 while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (webkit_dom_node_get_parent_node (node)))
2693 node = webkit_dom_node_get_parent_node (node);
2695 node = webkit_dom_node_get_previous_sibling (node);
2698 } else {
2699 /* Get next cell */
2700 node = webkit_dom_node_get_next_sibling (cell);
2701 if (!node || !WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (node)) {
2702 /* No cell, go one row below. */
2703 node = webkit_dom_node_get_parent_node (cell);
2704 node = webkit_dom_node_get_next_sibling (node);
2705 if (node && WEBKIT_DOM_IS_HTML_TABLE_ROW_ELEMENT (node)) {
2706 node = webkit_dom_node_get_first_child (node);
2707 } else {
2708 /* No row below, move to the block after table. */
2709 node = webkit_dom_node_get_parent_node (cell);
2710 while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (webkit_dom_node_get_parent_node (node)))
2711 node = webkit_dom_node_get_parent_node (node);
2713 node = webkit_dom_node_get_next_sibling (node);
2718 if (!node) {
2719 g_clear_object (&range);
2720 g_clear_object (&dom_selection);
2721 return FALSE;
2724 webkit_dom_range_select_node_contents (range, node, NULL);
2725 webkit_dom_range_collapse (range, TRUE, NULL);
2726 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
2727 webkit_dom_dom_selection_add_range (dom_selection, range);
2728 g_clear_object (&range);
2729 g_clear_object (&dom_selection);
2731 return TRUE;
2734 static gboolean
2735 save_history_before_event_in_table (EEditorPage *editor_page,
2736 WebKitDOMRange *range)
2738 WebKitDOMNode *node;
2739 WebKitDOMElement *block;
2741 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
2743 node = webkit_dom_range_get_start_container (range, NULL);
2744 if (WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (node))
2745 block = WEBKIT_DOM_ELEMENT (node);
2746 else
2747 block = get_parent_block_element (node);
2749 if (block && WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (block)) {
2750 EEditorUndoRedoManager *manager;
2751 EEditorHistoryEvent *ev;
2753 ev = g_new0 (EEditorHistoryEvent, 1);
2754 ev->type = HISTORY_TABLE_INPUT;
2756 e_editor_dom_selection_save (editor_page);
2757 ev->data.dom.from = g_object_ref (webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (block), TRUE, NULL));
2758 e_editor_dom_selection_restore (editor_page);
2760 e_editor_dom_selection_get_coordinates (editor_page,
2761 &ev->before.start.x,
2762 &ev->before.start.y,
2763 &ev->before.end.x,
2764 &ev->before.end.y);
2766 manager = e_editor_page_get_undo_redo_manager (editor_page);
2767 e_editor_undo_redo_manager_insert_history_event (manager, ev);
2769 return TRUE;
2772 return FALSE;
2775 static gboolean
2776 insert_tabulator (EEditorPage *editor_page)
2778 EEditorUndoRedoManager *manager;
2779 EEditorHistoryEvent *ev = NULL;
2780 gboolean success;
2782 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
2784 manager = e_editor_page_get_undo_redo_manager (editor_page);
2786 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
2787 ev = g_new0 (EEditorHistoryEvent, 1);
2788 ev->type = HISTORY_INPUT;
2790 if (!e_editor_dom_selection_is_collapsed (editor_page)) {
2791 WebKitDOMRange *tmp_range = NULL;
2793 tmp_range = e_editor_dom_get_current_range (editor_page);
2794 insert_delete_event (editor_page, tmp_range);
2795 g_clear_object (&tmp_range);
2798 e_editor_dom_selection_get_coordinates (editor_page,
2799 &ev->before.start.x,
2800 &ev->before.start.y,
2801 &ev->before.end.x,
2802 &ev->before.end.y);
2804 ev->before.end.x = ev->before.start.x;
2805 ev->before.end.y = ev->before.start.y;
2808 success = e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_INSERT_TEXT, "\t");
2810 if (ev) {
2811 if (success) {
2812 WebKitDOMDocument *document;
2813 WebKitDOMElement *element;
2814 WebKitDOMDocumentFragment *fragment;
2816 document = e_editor_page_get_document (editor_page);
2818 e_editor_dom_selection_get_coordinates (editor_page,
2819 &ev->after.start.x,
2820 &ev->after.start.y,
2821 &ev->after.end.x,
2822 &ev->after.end.y);
2824 fragment = webkit_dom_document_create_document_fragment (document);
2825 element = webkit_dom_document_create_element (document, "span", NULL);
2826 webkit_dom_html_element_set_inner_text (
2827 WEBKIT_DOM_HTML_ELEMENT (element), "\t", NULL);
2828 webkit_dom_element_set_attribute (
2829 element, "class", "Apple-tab-span", NULL);
2830 webkit_dom_element_set_attribute (
2831 element, "style", "white-space:pre", NULL);
2832 webkit_dom_node_append_child (
2833 WEBKIT_DOM_NODE (fragment), WEBKIT_DOM_NODE (element), NULL);
2834 webkit_dom_node_append_child (
2835 WEBKIT_DOM_NODE (fragment),
2836 WEBKIT_DOM_NODE (dom_create_selection_marker (document, TRUE)),
2837 NULL);
2838 webkit_dom_node_append_child (
2839 WEBKIT_DOM_NODE (fragment),
2840 WEBKIT_DOM_NODE (dom_create_selection_marker (document, FALSE)),
2841 NULL);
2842 ev->data.fragment = g_object_ref (fragment);
2844 e_editor_undo_redo_manager_insert_history_event (manager, ev);
2845 e_editor_page_emit_content_changed (editor_page);
2846 } else {
2847 e_editor_undo_redo_manager_remove_current_history_event (manager);
2848 e_editor_undo_redo_manager_remove_current_history_event (manager);
2849 g_free (ev);
2853 return success;
2856 static void
2857 body_keypress_event_cb (WebKitDOMElement *element,
2858 WebKitDOMUIEvent *event,
2859 EEditorPage *editor_page)
2861 WebKitDOMDocument *document;
2862 WebKitDOMDOMWindow *dom_window = NULL;
2863 WebKitDOMDOMSelection *dom_selection = NULL;
2864 WebKitDOMRange *range = NULL;
2866 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
2868 e_editor_page_set_is_processing_keypress_event (editor_page, TRUE);
2870 document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (element));
2871 dom_window = webkit_dom_document_get_default_view (document);
2872 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
2873 g_clear_object (&dom_window);
2874 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
2876 if (range && !webkit_dom_range_get_collapsed (range, NULL))
2877 insert_delete_event (editor_page, range);
2879 g_clear_object (&dom_selection);
2880 g_clear_object (&range);
2883 static void
2884 body_keydown_event_cb (WebKitDOMElement *element,
2885 WebKitDOMUIEvent *event,
2886 EEditorPage *editor_page)
2888 gboolean backspace_key, delete_key, space_key, return_key;
2889 gboolean shift_key, control_key, tabulator_key;
2890 glong key_code;
2891 WebKitDOMDocument *document;
2892 WebKitDOMDOMWindow *dom_window = NULL;
2893 WebKitDOMDOMSelection *dom_selection = NULL;
2894 WebKitDOMRange *range = NULL;
2896 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
2898 document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (element));
2900 key_code = webkit_dom_ui_event_get_key_code (event);
2901 delete_key = key_code == HTML_KEY_CODE_DELETE;
2902 return_key = key_code == HTML_KEY_CODE_RETURN;
2903 backspace_key = key_code == HTML_KEY_CODE_BACKSPACE;
2904 space_key = key_code == HTML_KEY_CODE_SPACE;
2905 tabulator_key = key_code == HTML_KEY_CODE_TABULATOR;
2907 if (key_code == HTML_KEY_CODE_CONTROL) {
2908 dom_set_links_active (document, TRUE);
2909 return;
2912 e_editor_page_set_dont_save_history_in_body_input (editor_page, delete_key || backspace_key);
2914 e_editor_page_set_return_key_pressed (editor_page, return_key);
2915 e_editor_page_set_space_key_pressed (editor_page, space_key);
2917 if (!(delete_key || return_key || backspace_key || space_key || tabulator_key))
2918 return;
2920 shift_key = webkit_dom_keyboard_event_get_shift_key (WEBKIT_DOM_KEYBOARD_EVENT (event));
2921 control_key = webkit_dom_keyboard_event_get_ctrl_key (WEBKIT_DOM_KEYBOARD_EVENT (event));
2923 if (tabulator_key) {
2924 if (jump_to_next_table_cell (document, shift_key)) {
2925 webkit_dom_event_prevent_default (WEBKIT_DOM_EVENT (event));
2926 goto out;
2929 if (!shift_key && insert_tabulator (editor_page))
2930 webkit_dom_event_prevent_default (WEBKIT_DOM_EVENT (event));
2932 goto out;
2935 if (return_key && e_editor_dom_key_press_event_process_return_key (editor_page)) {
2936 webkit_dom_event_prevent_default (WEBKIT_DOM_EVENT (event));
2937 goto out;
2940 if (backspace_key && e_editor_dom_key_press_event_process_backspace_key (editor_page)) {
2941 webkit_dom_event_prevent_default (WEBKIT_DOM_EVENT (event));
2942 goto out;
2945 if (delete_key || backspace_key) {
2946 if (e_editor_dom_key_press_event_process_delete_or_backspace_key (editor_page, key_code, control_key, delete_key))
2947 webkit_dom_event_prevent_default (WEBKIT_DOM_EVENT (event));
2948 goto out;
2951 dom_window = webkit_dom_document_get_default_view (document);
2952 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
2953 g_clear_object (&dom_window);
2954 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
2956 if (save_history_before_event_in_table (editor_page, range))
2957 goto out;
2959 if (return_key) {
2960 EEditorHistoryEvent *ev;
2961 EEditorUndoRedoManager *manager;
2963 /* Insert new history event for Return to have the right coordinates.
2964 * The fragment will be added later. */
2965 ev = g_new0 (EEditorHistoryEvent, 1);
2966 ev->type = HISTORY_INPUT;
2968 manager = e_editor_page_get_undo_redo_manager (editor_page);
2970 e_editor_dom_selection_get_coordinates (editor_page,
2971 &ev->before.start.x,
2972 &ev->before.start.y,
2973 &ev->before.end.x,
2974 &ev->before.end.y);
2975 e_editor_undo_redo_manager_insert_history_event (manager, ev);
2977 out:
2978 g_clear_object (&range);
2979 g_clear_object (&dom_selection);
2982 static gboolean
2983 save_history_after_event_in_table (EEditorPage *editor_page)
2985 WebKitDOMDocument *document;
2986 WebKitDOMDOMWindow *dom_window = NULL;
2987 WebKitDOMDOMSelection *dom_selection = NULL;
2988 WebKitDOMElement *element;
2989 WebKitDOMNode *node;
2990 WebKitDOMRange *range = NULL;
2991 EEditorHistoryEvent *ev;
2992 EEditorUndoRedoManager *manager;
2994 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
2996 document = e_editor_page_get_document (editor_page);
2997 dom_window = webkit_dom_document_get_default_view (document);
2998 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
2999 g_clear_object (&dom_window);
3001 if (!webkit_dom_dom_selection_get_range_count (dom_selection)) {
3002 g_clear_object (&dom_selection);
3003 return FALSE;
3005 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
3007 /* Find if writing into table. */
3008 node = webkit_dom_range_get_start_container (range, NULL);
3009 if (WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (node))
3010 element = WEBKIT_DOM_ELEMENT (node);
3011 else
3012 element = get_parent_block_element (node);
3014 g_clear_object (&dom_selection);
3015 g_clear_object (&range);
3017 manager = e_editor_page_get_undo_redo_manager (editor_page);
3018 /* If writing to table we have to create different history event. */
3019 if (WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (element)) {
3020 ev = e_editor_undo_redo_manager_get_current_history_event (manager);
3021 if (ev->type != HISTORY_TABLE_INPUT)
3022 return FALSE;
3023 } else
3024 return FALSE;
3026 e_editor_dom_selection_save (editor_page);
3028 e_editor_dom_selection_get_coordinates (editor_page,
3029 &ev->after.start.x,
3030 &ev->after.start.y,
3031 &ev->after.end.x,
3032 &ev->after.end.y);
3034 ev->data.dom.to = g_object_ref (webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (element), TRUE, NULL));
3036 e_editor_dom_selection_restore (editor_page);
3038 return TRUE;
3041 static void
3042 save_history_for_input (EEditorPage *editor_page)
3044 WebKitDOMDocument *document;
3045 WebKitDOMDocumentFragment *fragment;
3046 WebKitDOMDOMWindow *dom_window = NULL;
3047 WebKitDOMDOMSelection *dom_selection = NULL;
3048 WebKitDOMRange *range = NULL, *range_clone = NULL;
3049 WebKitDOMNode *start_container;
3050 EEditorHistoryEvent *ev;
3051 EEditorUndoRedoManager *manager;
3052 glong offset;
3054 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
3056 document = e_editor_page_get_document (editor_page);
3057 manager = e_editor_page_get_undo_redo_manager (editor_page);
3058 dom_window = webkit_dom_document_get_default_view (document);
3059 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
3060 g_clear_object (&dom_window);
3062 if (!webkit_dom_dom_selection_get_range_count (dom_selection)) {
3063 g_clear_object (&dom_selection);
3064 return;
3067 if (e_editor_page_get_return_key_pressed (editor_page)) {
3068 ev = e_editor_undo_redo_manager_get_current_history_event (manager);
3069 if (ev->type != HISTORY_INPUT) {
3070 g_clear_object (&dom_selection);
3071 return;
3073 } else {
3074 ev = g_new0 (EEditorHistoryEvent, 1);
3075 ev->type = HISTORY_INPUT;
3078 e_editor_page_block_selection_changed (editor_page);
3080 e_editor_dom_selection_get_coordinates (editor_page,
3081 &ev->after.start.x,
3082 &ev->after.start.y,
3083 &ev->after.end.x,
3084 &ev->after.end.y);
3086 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
3087 range_clone = webkit_dom_range_clone_range (range, NULL);
3088 offset = webkit_dom_range_get_start_offset (range_clone, NULL);
3089 start_container = webkit_dom_range_get_start_container (range_clone, NULL);
3090 if (offset > 0)
3091 webkit_dom_range_set_start (
3092 range_clone,
3093 start_container,
3094 offset - 1,
3095 NULL);
3096 fragment = webkit_dom_range_clone_contents (range_clone, NULL);
3097 /* We have to specially handle Return key press */
3098 if (e_editor_page_get_return_key_pressed (editor_page)) {
3099 WebKitDOMElement *element_start, *element_end;
3100 WebKitDOMNode *parent_start, *parent_end, *node;
3102 element_start = webkit_dom_document_create_element (document, "span", NULL);
3103 webkit_dom_range_surround_contents (range, WEBKIT_DOM_NODE (element_start), NULL);
3104 webkit_dom_dom_selection_modify (dom_selection, "move", "left", "character");
3105 g_clear_object (&range);
3106 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
3107 element_end = webkit_dom_document_create_element (document, "span", NULL);
3108 webkit_dom_range_surround_contents (range, WEBKIT_DOM_NODE (element_end), NULL);
3110 parent_start = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element_start));
3111 parent_end = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element_end));
3113 while (parent_start && parent_end && !webkit_dom_node_is_same_node (parent_start, parent_end) &&
3114 !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent_start) && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent_end)) {
3115 webkit_dom_node_insert_before (
3116 WEBKIT_DOM_NODE (fragment),
3117 webkit_dom_node_clone_node_with_error (parent_start, FALSE, NULL),
3118 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment)),
3119 NULL);
3120 parent_start = webkit_dom_node_get_parent_node (parent_start);
3121 parent_end = webkit_dom_node_get_parent_node (parent_end);
3124 node = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment));
3125 while (webkit_dom_node_get_next_sibling (node)) {
3126 WebKitDOMNode *last_child;
3128 last_child = webkit_dom_node_get_last_child (WEBKIT_DOM_NODE (fragment));
3129 webkit_dom_node_append_child (
3130 webkit_dom_node_get_previous_sibling (last_child),
3131 last_child,
3132 NULL);
3135 node = webkit_dom_node_get_last_child (WEBKIT_DOM_NODE (fragment));
3136 while (webkit_dom_node_get_last_child (node)) {
3137 node = webkit_dom_node_get_last_child (node);
3140 webkit_dom_node_append_child (
3141 node,
3142 WEBKIT_DOM_NODE (
3143 webkit_dom_document_create_element (document, "br", NULL)),
3144 NULL);
3145 webkit_dom_node_append_child (
3146 node,
3147 WEBKIT_DOM_NODE (
3148 dom_create_selection_marker (document, TRUE)),
3149 NULL);
3150 webkit_dom_node_append_child (
3151 node,
3152 WEBKIT_DOM_NODE (
3153 dom_create_selection_marker (document, FALSE)),
3154 NULL);
3156 remove_node (WEBKIT_DOM_NODE (element_start));
3157 remove_node (WEBKIT_DOM_NODE (element_end));
3159 g_object_set_data (
3160 G_OBJECT (fragment), "history-return-key", GINT_TO_POINTER (1));
3162 webkit_dom_dom_selection_modify (dom_selection, "move", "right", "character");
3163 } else {
3164 webkit_dom_node_append_child (
3165 WEBKIT_DOM_NODE (fragment),
3166 WEBKIT_DOM_NODE (
3167 dom_create_selection_marker (document, TRUE)),
3168 NULL);
3169 webkit_dom_node_append_child (
3170 WEBKIT_DOM_NODE (fragment),
3171 WEBKIT_DOM_NODE (
3172 dom_create_selection_marker (document, FALSE)),
3173 NULL);
3176 g_clear_object (&dom_selection);
3177 g_clear_object (&range);
3178 g_clear_object (&range_clone);
3180 e_editor_page_unblock_selection_changed (editor_page);
3182 ev->data.fragment = g_object_ref (fragment);
3184 if (!e_editor_page_get_return_key_pressed (editor_page))
3185 e_editor_undo_redo_manager_insert_history_event (manager, ev);
3188 typedef struct _TimeoutContext TimeoutContext;
3190 struct _TimeoutContext {
3191 EEditorPage *editor_page;
3194 static void
3195 timeout_context_free (TimeoutContext *context)
3197 g_slice_free (TimeoutContext, context);
3200 static gboolean
3201 force_spell_check_on_timeout (TimeoutContext *context)
3203 e_editor_dom_force_spell_check_in_viewport (context->editor_page);
3204 e_editor_page_set_spell_check_on_scroll_event_source_id (context->editor_page, 0);
3205 return FALSE;
3208 static void
3209 body_scroll_event_cb (WebKitDOMElement *element,
3210 WebKitDOMEvent *event,
3211 EEditorPage *editor_page)
3213 TimeoutContext *context;
3214 guint id;
3216 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
3218 if (!e_editor_page_get_inline_spelling_enabled (editor_page))
3219 return;
3221 context = g_slice_new0 (TimeoutContext);
3222 context->editor_page = editor_page;
3224 id = e_editor_page_get_spell_check_on_scroll_event_source_id (editor_page);
3225 if (id > 0)
3226 g_source_remove (id);
3228 id = g_timeout_add_seconds_full (
3230 G_PRIORITY_DEFAULT,
3231 (GSourceFunc)force_spell_check_on_timeout,
3232 context,
3233 (GDestroyNotify)timeout_context_free);
3235 e_editor_page_set_spell_check_on_scroll_event_source_id (editor_page, id);
3238 static void
3239 remove_zero_width_spaces_on_body_input (EEditorPage *editor_page,
3240 WebKitDOMNode *node)
3242 gboolean html_mode;
3244 html_mode = e_editor_page_get_html_mode (editor_page);
3245 /* After toggling monospaced format, we are using UNICODE_ZERO_WIDTH_SPACE
3246 * to move caret into right space. When this callback is called it is not
3247 * necessary anymore so remove it */
3248 if (html_mode) {
3249 WebKitDOMElement *parent = webkit_dom_node_get_parent_element (node);
3251 if (parent) {
3252 WebKitDOMNode *prev_sibling;
3254 prev_sibling = webkit_dom_node_get_previous_sibling (
3255 WEBKIT_DOM_NODE (parent));
3257 if (prev_sibling && WEBKIT_DOM_IS_TEXT (prev_sibling)) {
3258 gchar *text = webkit_dom_node_get_text_content (
3259 prev_sibling);
3261 if (g_strcmp0 (text, UNICODE_ZERO_WIDTH_SPACE) == 0)
3262 remove_node (prev_sibling);
3264 g_free (text);
3270 /* If text before caret includes UNICODE_ZERO_WIDTH_SPACE character, remove it */
3271 if (WEBKIT_DOM_IS_TEXT (node)) {
3272 gchar *text = webkit_dom_character_data_get_data (WEBKIT_DOM_CHARACTER_DATA (node));
3273 glong length = webkit_dom_character_data_get_length (WEBKIT_DOM_CHARACTER_DATA (node));
3274 WebKitDOMNode *parent;
3276 /* We have to preserve empty paragraphs with just UNICODE_ZERO_WIDTH_SPACE
3277 * character as when we will remove it it will collapse */
3278 if (length > 1) {
3279 if (g_str_has_prefix (text, UNICODE_ZERO_WIDTH_SPACE))
3280 webkit_dom_character_data_replace_data (
3281 WEBKIT_DOM_CHARACTER_DATA (node), 0, 1, "", NULL);
3282 else if (g_str_has_suffix (text, UNICODE_ZERO_WIDTH_SPACE))
3283 webkit_dom_character_data_replace_data (
3284 WEBKIT_DOM_CHARACTER_DATA (node), length - 1, 1, "", NULL);
3286 g_free (text);
3288 parent = webkit_dom_node_get_parent_node (node);
3289 if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent) &&
3290 !webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (parent), "data-evo-paragraph")) {
3291 if (html_mode)
3292 webkit_dom_element_set_attribute (
3293 WEBKIT_DOM_ELEMENT (parent),
3294 "data-evo-paragraph",
3296 NULL);
3297 else
3298 e_editor_dom_set_paragraph_style (
3299 editor_page, WEBKIT_DOM_ELEMENT (parent), -1, 0, NULL);
3302 /* When new smiley is added we have to use UNICODE_HIDDEN_SPACE to set the
3303 * caret position to right place. It is removed when user starts typing. But
3304 * when the user will press left arrow he will move the caret into
3305 * smiley wrapper. If he will start to write there we have to move the written
3306 * text out of the wrapper and move caret to right place */
3307 if (WEBKIT_DOM_IS_ELEMENT (parent) &&
3308 element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-smiley-text")) {
3309 gchar *text;
3310 WebKitDOMCharacterData *data;
3311 WebKitDOMText *text_node;
3312 WebKitDOMDocument *document;
3314 document = e_editor_page_get_document (editor_page);
3316 /* Split out the newly written character to its own text node, */
3317 data = WEBKIT_DOM_CHARACTER_DATA (node);
3318 parent = webkit_dom_node_get_parent_node (parent);
3319 text = webkit_dom_character_data_substring_data (
3320 data,
3321 webkit_dom_character_data_get_length (data) - 1,
3323 NULL);
3324 webkit_dom_character_data_delete_data (
3325 data,
3326 webkit_dom_character_data_get_length (data) - 1,
3328 NULL);
3329 text_node = webkit_dom_document_create_text_node (document, text);
3330 g_free (text);
3332 webkit_dom_node_insert_before (
3333 webkit_dom_node_get_parent_node (parent),
3334 WEBKIT_DOM_NODE (
3335 dom_create_selection_marker (document, FALSE)),
3336 webkit_dom_node_get_next_sibling (parent),
3337 NULL);
3338 webkit_dom_node_insert_before (
3339 webkit_dom_node_get_parent_node (parent),
3340 WEBKIT_DOM_NODE (
3341 dom_create_selection_marker (document, TRUE)),
3342 webkit_dom_node_get_next_sibling (parent),
3343 NULL);
3344 /* Move the text node outside of smiley. */
3345 webkit_dom_node_insert_before (
3346 webkit_dom_node_get_parent_node (parent),
3347 WEBKIT_DOM_NODE (text_node),
3348 webkit_dom_node_get_next_sibling (parent),
3349 NULL);
3350 e_editor_dom_selection_restore (editor_page);
3355 void
3356 e_editor_dom_body_input_event_process (EEditorPage *editor_page,
3357 WebKitDOMEvent *event)
3359 WebKitDOMDocument *document;
3360 WebKitDOMNode *node;
3361 WebKitDOMRange *range = NULL;
3362 EEditorUndoRedoManager *manager;
3363 gboolean do_spell_check = FALSE;
3364 gboolean html_mode;
3366 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
3368 document = e_editor_page_get_document (editor_page);
3369 range = e_editor_dom_get_current_range (editor_page);
3370 node = webkit_dom_range_get_end_container (range, NULL);
3372 manager = e_editor_page_get_undo_redo_manager (editor_page);
3374 html_mode = e_editor_page_get_html_mode (editor_page);
3375 e_editor_page_emit_content_changed (editor_page);
3377 if (e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
3378 e_editor_undo_redo_manager_set_operation_in_progress (manager, FALSE);
3379 e_editor_page_set_dont_save_history_in_body_input (editor_page, FALSE);
3380 remove_zero_width_spaces_on_body_input (editor_page, node);
3381 do_spell_check = TRUE;
3382 goto out;
3385 /* When the Backspace is pressed in a bulleted list item with just one
3386 * character left in it, WebKit will create another BR element in the
3387 * item. */
3388 if (!html_mode) {
3389 WebKitDOMElement *element;
3391 element = webkit_dom_document_query_selector (
3392 document, "ul > li > br + br", NULL);
3394 if (element)
3395 remove_node (WEBKIT_DOM_NODE (element));
3398 if (!save_history_after_event_in_table (editor_page)) {
3399 if (!e_editor_page_get_dont_save_history_in_body_input (editor_page))
3400 save_history_for_input (editor_page);
3401 else
3402 do_spell_check = TRUE;
3405 /* Don't try to look for smileys if we are deleting text. */
3406 if (!e_editor_page_get_dont_save_history_in_body_input (editor_page))
3407 e_editor_dom_check_magic_smileys (editor_page);
3409 e_editor_page_set_dont_save_history_in_body_input (editor_page, FALSE);
3411 if (e_editor_page_get_return_key_pressed (editor_page) ||
3412 e_editor_page_get_space_key_pressed (editor_page)) {
3413 e_editor_dom_check_magic_links (editor_page, FALSE);
3414 if (e_editor_page_get_return_key_pressed (editor_page)) {
3415 if (fix_paragraph_structure_after_pressing_enter (editor_page) &&
3416 html_mode) {
3417 /* When the return is pressed in a H1-6 element, WebKit doesn't
3418 * continue with the same element, but creates normal paragraph,
3419 * so we have to unset the bold font. */
3420 e_editor_undo_redo_manager_set_operation_in_progress (manager, TRUE);
3421 e_editor_dom_selection_set_bold (editor_page, FALSE);
3422 e_editor_undo_redo_manager_set_operation_in_progress (manager, FALSE);
3425 fix_paragraph_structure_after_pressing_enter_after_smiley (document);
3427 do_spell_check = TRUE;
3429 } else {
3430 WebKitDOMNode *node;
3432 node = webkit_dom_range_get_end_container (range, NULL);
3434 if (surround_text_with_paragraph_if_needed (editor_page, node)) {
3435 WebKitDOMElement *element;
3437 element = webkit_dom_document_get_element_by_id (
3438 document, "-x-evo-selection-start-marker");
3439 node = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element));
3440 e_editor_dom_selection_restore (editor_page);
3443 if (WEBKIT_DOM_IS_TEXT (node)) {
3444 WebKitDOMElement *parent;
3445 gchar *text;
3447 text = webkit_dom_node_get_text_content (node);
3449 if (text && *text && *text != ' ' && !g_str_has_prefix (text, UNICODE_NBSP)) {
3450 gboolean valid = FALSE;
3452 if (*text == '?' && strlen (text) > 1)
3453 valid = TRUE;
3454 else if (!strchr (URL_INVALID_TRAILING_CHARS, *text))
3455 valid = TRUE;
3457 if (valid) {
3458 WebKitDOMNode *prev_sibling;
3460 prev_sibling = webkit_dom_node_get_previous_sibling (node);
3462 if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling))
3463 e_editor_dom_check_magic_links (editor_page, FALSE);
3467 parent = webkit_dom_node_get_parent_element (node);
3468 if (element_has_class (parent, "-x-evo-resizable-wrapper") ||
3469 element_has_class (parent, "-x-evo-smiley-wrapper")) {
3470 WebKitDOMDOMWindow *dom_window = NULL;
3471 WebKitDOMDOMSelection *dom_selection = NULL;
3472 WebKitDOMNode *prev_sibling;
3473 gboolean writing_before = TRUE;
3475 dom_window = webkit_dom_document_get_default_view (document);
3476 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
3478 prev_sibling = webkit_dom_node_get_previous_sibling (node);
3479 if (prev_sibling && WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (prev_sibling))
3480 writing_before = FALSE;
3482 webkit_dom_node_insert_before (
3483 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (parent)),
3484 node,
3485 writing_before ?
3486 WEBKIT_DOM_NODE (parent) :
3487 webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (parent)),
3488 NULL);
3490 g_clear_object (&range);
3492 range = webkit_dom_document_create_range (document);
3493 webkit_dom_range_select_node_contents (range, node, NULL);
3494 webkit_dom_range_collapse (range, FALSE, NULL);
3496 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
3497 webkit_dom_dom_selection_add_range (dom_selection, range);
3499 g_clear_object (&dom_window);
3500 g_clear_object (&dom_selection);
3503 g_free (text);
3507 remove_zero_width_spaces_on_body_input (editor_page, node);
3509 /* Writing into quoted content */
3510 if (!html_mode) {
3511 gint citation_level;
3512 WebKitDOMElement *selection_start_marker, *selection_end_marker;
3513 WebKitDOMNode *node, *parent;
3515 node = webkit_dom_range_get_end_container (range, NULL);
3517 citation_level = e_editor_dom_get_citation_level (node);
3518 if (citation_level == 0)
3519 goto out;
3521 selection_start_marker = webkit_dom_document_get_element_by_id (
3522 document, "-x-evo-selection-start-marker");
3523 if (selection_start_marker)
3524 goto out;
3526 e_editor_dom_selection_save (editor_page);
3528 selection_start_marker = webkit_dom_document_get_element_by_id (
3529 document, "-x-evo-selection-start-marker");
3530 selection_end_marker = webkit_dom_document_get_element_by_id (
3531 document, "-x-evo-selection-end-marker");
3532 /* If the selection was not saved, move it into the first child of body */
3533 if (!selection_start_marker || !selection_end_marker) {
3534 WebKitDOMHTMLElement *body;
3535 WebKitDOMNode *child;
3537 body = webkit_dom_document_get_body (document);
3538 child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
3540 dom_add_selection_markers_into_element_start (
3541 document,
3542 WEBKIT_DOM_ELEMENT (child),
3543 &selection_start_marker,
3544 &selection_end_marker);
3547 /* We have to process elements only inside normal block */
3548 parent = WEBKIT_DOM_NODE (get_parent_block_element (
3549 WEBKIT_DOM_NODE (selection_start_marker)));
3550 if (WEBKIT_DOM_IS_HTML_PRE_ELEMENT (parent)) {
3551 e_editor_dom_selection_restore (editor_page);
3552 goto out;
3555 if (selection_start_marker) {
3556 gchar *content;
3557 gint text_length, word_wrap_length, length;
3558 WebKitDOMElement *block;
3559 gboolean remove_quoting = FALSE;
3561 word_wrap_length = e_editor_page_get_word_wrap_length (editor_page);
3562 length = word_wrap_length - 2 * citation_level;
3564 block = WEBKIT_DOM_ELEMENT (parent);
3565 if (webkit_dom_element_query_selector (
3566 WEBKIT_DOM_ELEMENT (block), ".-x-evo-quoted", NULL)) {
3567 WebKitDOMNode *prev_sibling;
3569 prev_sibling = webkit_dom_node_get_previous_sibling (
3570 WEBKIT_DOM_NODE (selection_end_marker));
3572 if (WEBKIT_DOM_IS_ELEMENT (prev_sibling))
3573 remove_quoting = element_has_class (
3574 WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-quoted");
3577 content = webkit_dom_node_get_text_content (WEBKIT_DOM_NODE (block));
3578 text_length = g_utf8_strlen (content, -1);
3579 g_free (content);
3581 /* Wrap and quote the line */
3582 if (!remove_quoting && text_length >= word_wrap_length) {
3583 e_editor_dom_remove_quoting_from_element (block);
3585 block = e_editor_dom_wrap_paragraph_length (editor_page, block, length);
3586 webkit_dom_node_normalize (WEBKIT_DOM_NODE (block));
3587 e_editor_dom_quote_plain_text_element_after_wrapping (
3588 editor_page, WEBKIT_DOM_ELEMENT (block), citation_level);
3589 selection_start_marker = webkit_dom_document_get_element_by_id (
3590 document, "-x-evo-selection-start-marker");
3591 if (!selection_start_marker)
3592 dom_add_selection_markers_into_element_end (
3593 document,
3594 WEBKIT_DOM_ELEMENT (block),
3595 NULL,
3596 NULL);
3598 e_editor_dom_selection_restore (editor_page);
3599 do_spell_check = TRUE;
3600 goto out;
3603 e_editor_dom_selection_restore (editor_page);
3605 out:
3606 if (do_spell_check)
3607 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
3609 g_clear_object (&range);
3612 static void
3613 body_input_event_cb (WebKitDOMElement *element,
3614 WebKitDOMEvent *event,
3615 EEditorPage *editor_page)
3617 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
3619 /* Only process the input event if it was triggered by the key press
3620 * and not i.e. by execCommand. This behavior changed when the support
3621 * for beforeinput event was introduced in WebKit. */
3622 if (e_editor_page_is_processing_keypress_event (editor_page))
3623 e_editor_dom_body_input_event_process (editor_page, event);
3625 e_editor_page_set_is_processing_keypress_event (editor_page, FALSE);
3628 void
3629 e_editor_dom_remove_input_event_listener_from_body (EEditorPage *editor_page)
3631 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
3633 if (!e_editor_page_get_body_input_event_removed (editor_page)) {
3634 WebKitDOMDocument *document;
3636 document = e_editor_page_get_document (editor_page);
3638 webkit_dom_event_target_remove_event_listener (
3639 WEBKIT_DOM_EVENT_TARGET (webkit_dom_document_get_body (document)),
3640 "input",
3641 G_CALLBACK (body_input_event_cb),
3642 FALSE);
3644 e_editor_page_set_body_input_event_removed (editor_page, TRUE);
3648 void
3649 e_editor_dom_register_input_event_listener_on_body (EEditorPage *editor_page)
3651 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
3653 if (e_editor_page_get_body_input_event_removed (editor_page)) {
3654 WebKitDOMDocument *document;
3656 document = e_editor_page_get_document (editor_page);
3658 webkit_dom_event_target_add_event_listener (
3659 WEBKIT_DOM_EVENT_TARGET (webkit_dom_document_get_body (document)),
3660 "input",
3661 G_CALLBACK (body_input_event_cb),
3662 FALSE,
3663 editor_page);
3665 e_editor_page_set_body_input_event_removed (editor_page, FALSE);
3669 static void
3670 remove_empty_blocks (WebKitDOMDocument *document)
3672 gint ii;
3673 WebKitDOMNodeList *list = NULL;
3675 list = webkit_dom_document_query_selector_all (
3676 document, "blockquote[type=cite] > :empty:not(br)", NULL);
3677 for (ii = webkit_dom_node_list_get_length (list); ii--;)
3678 remove_node (webkit_dom_node_list_item (list, ii));
3679 g_clear_object (&list);
3681 list = webkit_dom_document_query_selector_all (
3682 document, "blockquote[type=cite]:empty", NULL);
3683 for (ii = webkit_dom_node_list_get_length (list); ii--;)
3684 remove_node (webkit_dom_node_list_item (list, ii));
3685 g_clear_object (&list);
3688 /* Following two functions are used when deleting the selection inside
3689 * the quoted content. The thing is that normally the quote marks are not
3690 * selectable by user. But this caused a lot of problems for WebKit when removing
3691 * the selection. This will avoid it as when the delete or backspace key is pressed
3692 * we will make the quote marks user selectable so they will act as any other text.
3693 * On HTML keyup event callback we will make them again non-selectable. */
3694 void
3695 e_editor_dom_disable_quote_marks_select (EEditorPage *editor_page)
3697 WebKitDOMDocument *document;
3698 WebKitDOMHTMLHeadElement *head;
3699 WebKitDOMElement *style_element;
3701 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
3703 document = e_editor_page_get_document (editor_page);
3704 head = webkit_dom_document_get_head (document);
3706 if (!webkit_dom_document_get_element_by_id (document, "-x-evo-quote-style")) {
3707 style_element = webkit_dom_document_create_element (document, "style", NULL);
3708 webkit_dom_element_set_id (style_element, "-x-evo-quote-style");
3709 webkit_dom_element_set_attribute (style_element, "type", "text/css", NULL);
3710 webkit_dom_element_set_inner_html (
3711 style_element,
3712 ".-x-evo-quoted { -webkit-user-select: none; }",
3713 NULL);
3714 webkit_dom_node_append_child (
3715 WEBKIT_DOM_NODE (head), WEBKIT_DOM_NODE (style_element), NULL);
3719 static void
3720 enable_quote_marks_select (WebKitDOMDocument *document)
3722 WebKitDOMElement *style_element;
3724 if ((style_element = webkit_dom_document_get_element_by_id (document, "-x-evo-quote-style")))
3725 remove_node (WEBKIT_DOM_NODE (style_element));
3728 void
3729 e_editor_dom_remove_node_and_parents_if_empty (WebKitDOMNode *node)
3731 WebKitDOMNode *parent;
3733 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (node));
3735 remove_node (WEBKIT_DOM_NODE (node));
3737 while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
3738 WebKitDOMNode *tmp;
3740 tmp = webkit_dom_node_get_parent_node (parent);
3741 remove_node_if_empty (parent);
3742 parent = tmp;
3746 void
3747 e_editor_dom_merge_siblings_if_necessary (EEditorPage *editor_page,
3748 WebKitDOMDocumentFragment *deleted_content)
3750 WebKitDOMDocument *document;
3751 WebKitDOMElement *element, *prev_element;
3752 WebKitDOMNode *child;
3753 WebKitDOMNodeList *list = NULL;
3754 gboolean equal_nodes;
3755 gint ii;
3757 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
3759 document = e_editor_page_get_document (editor_page);
3761 if ((element = webkit_dom_document_get_element_by_id (document, "-x-evo-main-cite")))
3762 webkit_dom_element_remove_attribute (element, "id");
3764 element = webkit_dom_document_query_selector (document, "blockquote:not([data-evo-query-skip]) + blockquote:not([data-evo-query-skip])", NULL);
3765 if (!element)
3766 goto signature;
3767 repeat:
3768 child = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element));
3769 if (WEBKIT_DOM_IS_ELEMENT (child))
3770 prev_element = WEBKIT_DOM_ELEMENT (child);
3771 else
3772 goto signature;
3774 equal_nodes = webkit_dom_node_is_equal_node (
3775 webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (element), FALSE, NULL),
3776 webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (prev_element), FALSE, NULL));
3778 if (equal_nodes) {
3779 if (webkit_dom_element_get_child_element_count (element) >
3780 webkit_dom_element_get_child_element_count (prev_element)) {
3781 while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element))))
3782 webkit_dom_node_append_child (
3783 WEBKIT_DOM_NODE (prev_element), child, NULL);
3784 remove_node (WEBKIT_DOM_NODE (element));
3785 } else {
3786 while ((child = webkit_dom_node_get_last_child (WEBKIT_DOM_NODE (prev_element))))
3787 webkit_dom_node_insert_before (
3788 WEBKIT_DOM_NODE (element),
3789 child,
3790 webkit_dom_node_get_first_child (
3791 WEBKIT_DOM_NODE (element)),
3792 NULL);
3793 remove_node (WEBKIT_DOM_NODE (prev_element));
3795 } else
3796 webkit_dom_element_set_attribute (element, "data-evo-query-skip", "", NULL);
3798 element = webkit_dom_document_query_selector (document, "blockquote:not([data-evo-query-skip]) + blockquote:not([data-evo-query-skip])", NULL);
3799 if (element)
3800 goto repeat;
3802 signature:
3803 list = webkit_dom_document_query_selector_all (
3804 document, "blockquote[data-evo-query-skip]", NULL);
3805 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
3806 WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
3807 webkit_dom_element_remove_attribute (
3808 WEBKIT_DOM_ELEMENT (node), "data-evo-query-skip");
3810 g_clear_object (&list);
3812 if (!deleted_content)
3813 return;
3815 /* Replace the corrupted signatures with the right one. */
3816 element = webkit_dom_document_query_selector (
3817 document, ".-x-evo-signature-wrapper + .-x-evo-signature-wrapper", NULL);
3818 if (element) {
3819 WebKitDOMElement *right_signature;
3821 right_signature = webkit_dom_document_fragment_query_selector (
3822 deleted_content, ".-x-evo-signature-wrapper", NULL);
3823 remove_node (webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element)));
3824 webkit_dom_node_replace_child (
3825 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
3826 webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (right_signature), TRUE, NULL),
3827 WEBKIT_DOM_NODE (element),
3828 NULL);
3832 /* This will fix the structure after the situations where some text
3833 * inside the quoted content is selected and afterwards deleted with
3834 * BackSpace or Delete. */
3835 void
3836 e_editor_dom_body_key_up_event_process_backspace_or_delete (EEditorPage *editor_page,
3837 gboolean delete)
3839 WebKitDOMDocument *document;
3840 WebKitDOMElement *selection_start_marker, *selection_end_marker;
3841 WebKitDOMNode *parent, *node;
3842 gint level;
3844 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
3846 if (e_editor_page_get_html_mode (editor_page)) {
3847 if (!delete) {
3848 e_editor_dom_selection_save (editor_page);
3849 e_editor_dom_merge_siblings_if_necessary (editor_page, NULL);
3850 e_editor_dom_selection_restore (editor_page);
3852 return;
3855 document = e_editor_page_get_document (editor_page);
3856 e_editor_dom_disable_quote_marks_select (editor_page);
3857 /* Remove empty blocks if presented. */
3858 remove_empty_blocks (document);
3860 e_editor_dom_selection_save (editor_page);
3861 selection_start_marker = webkit_dom_document_get_element_by_id (
3862 document, "-x-evo-selection-start-marker");
3863 selection_end_marker = webkit_dom_document_get_element_by_id (
3864 document, "-x-evo-selection-end-marker");
3866 /* If we deleted a selection the caret will be inside the quote marks, fix it. */
3867 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start_marker));
3868 if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-quote-character")) {
3869 parent = webkit_dom_node_get_parent_node (parent);
3870 webkit_dom_node_insert_before (
3871 webkit_dom_node_get_parent_node (parent),
3872 WEBKIT_DOM_NODE (selection_end_marker),
3873 webkit_dom_node_get_next_sibling (parent),
3874 NULL);
3875 webkit_dom_node_insert_before (
3876 webkit_dom_node_get_parent_node (parent),
3877 WEBKIT_DOM_NODE (selection_start_marker),
3878 webkit_dom_node_get_next_sibling (parent),
3879 NULL);
3882 /* Under some circumstances we will end with block inside the citation
3883 * that has the quote marks removed and we have to reinsert them back. */
3884 level = e_editor_dom_get_citation_level (WEBKIT_DOM_NODE (selection_start_marker));
3885 node = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_end_marker));
3886 if (level > 0 && node && !WEBKIT_DOM_IS_HTML_BR_ELEMENT (node)) {
3887 WebKitDOMElement *block;
3889 block = WEBKIT_DOM_ELEMENT (e_editor_dom_get_parent_block_node_from_child (
3890 WEBKIT_DOM_NODE (selection_start_marker)));
3892 e_editor_dom_remove_quoting_from_element (block);
3893 if (webkit_dom_element_has_attribute (block, "data-evo-paragraph")) {
3894 gint length, word_wrap_length;
3896 word_wrap_length = e_editor_page_get_word_wrap_length (editor_page);
3897 length = word_wrap_length - 2 * level;
3898 block = e_editor_dom_wrap_paragraph_length (editor_page, block, length);
3899 webkit_dom_node_normalize (WEBKIT_DOM_NODE (block));
3901 e_editor_dom_quote_plain_text_element_after_wrapping (editor_page, block, level);
3902 } else if (level > 0 && !node) {
3903 WebKitDOMNode *prev_sibling;
3905 prev_sibling = webkit_dom_node_get_previous_sibling (
3906 WEBKIT_DOM_NODE (selection_start_marker));
3907 if (WEBKIT_DOM_IS_ELEMENT (prev_sibling) &&
3908 element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-quoted") &&
3909 !webkit_dom_node_get_previous_sibling (prev_sibling)) {
3910 webkit_dom_node_append_child (
3911 webkit_dom_node_get_parent_node (parent),
3912 WEBKIT_DOM_NODE (webkit_dom_document_create_element (document, "br", NULL)),
3913 NULL);
3917 e_editor_dom_merge_siblings_if_necessary (editor_page, NULL);
3919 e_editor_dom_selection_restore (editor_page);
3920 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
3923 void
3924 e_editor_dom_body_key_up_event_process_return_key (EEditorPage *editor_page)
3926 WebKitDOMDocument *document;
3927 WebKitDOMElement *selection_start_marker, *selection_end_marker;
3928 WebKitDOMNode *parent;
3930 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
3932 /* If the return is pressed in an unordered list in plain text mode
3933 * the caret is moved to the "*" character before the newly inserted
3934 * item. It looks like it is not enough that the item has BR element
3935 * inside, but we have to again use the zero width space character
3936 * to fix the situation. */
3937 if (e_editor_page_get_html_mode (editor_page))
3938 return;
3940 document = e_editor_page_get_document (editor_page);
3941 e_editor_dom_selection_save (editor_page);
3943 selection_start_marker = webkit_dom_document_get_element_by_id (
3944 document, "-x-evo-selection-start-marker");
3945 selection_end_marker = webkit_dom_document_get_element_by_id (
3946 document, "-x-evo-selection-end-marker");
3948 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start_marker));
3949 if (!WEBKIT_DOM_IS_HTML_LI_ELEMENT (parent) ||
3950 !WEBKIT_DOM_IS_HTML_U_LIST_ELEMENT (webkit_dom_node_get_parent_node (parent))) {
3951 e_editor_dom_selection_restore (editor_page);
3952 return;
3955 if (!webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_start_marker)) &&
3956 (!webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_end_marker)) ||
3957 WEBKIT_DOM_IS_HTML_BR_ELEMENT (webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_end_marker)))))
3958 webkit_dom_element_insert_adjacent_text (
3959 WEBKIT_DOM_ELEMENT (parent),
3960 "afterbegin",
3961 UNICODE_ZERO_WIDTH_SPACE,
3962 NULL);
3964 e_editor_dom_selection_restore (editor_page);
3967 static void
3968 body_keyup_event_cb (WebKitDOMElement *element,
3969 WebKitDOMUIEvent *event,
3970 EEditorPage *editor_page)
3972 WebKitDOMDocument *document;
3973 glong key_code;
3975 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
3977 document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (element));
3978 if (!e_editor_page_is_composition_in_progress (editor_page))
3979 e_editor_dom_register_input_event_listener_on_body (editor_page);
3981 if (!e_editor_dom_selection_is_collapsed (editor_page))
3982 return;
3984 key_code = webkit_dom_ui_event_get_key_code (event);
3985 if (key_code == HTML_KEY_CODE_BACKSPACE || key_code == HTML_KEY_CODE_DELETE) {
3986 e_editor_dom_body_key_up_event_process_backspace_or_delete (editor_page, key_code == HTML_KEY_CODE_DELETE);
3988 /* The content was wrapped and the coordinates
3989 * of caret could be changed, so renew them. But
3990 * only do that when we are not redoing a history
3991 * event, otherwise it would modify the history. */
3992 if (e_editor_page_get_renew_history_after_coordinates (editor_page)) {
3993 EEditorHistoryEvent *ev = NULL;
3994 EEditorUndoRedoManager *manager;
3996 manager = e_editor_page_get_undo_redo_manager (editor_page);
3997 ev = e_editor_undo_redo_manager_get_current_history_event (manager);
3998 e_editor_dom_selection_get_coordinates (editor_page,
3999 &ev->after.start.x,
4000 &ev->after.start.y,
4001 &ev->after.end.x,
4002 &ev->after.end.y);
4005 e_editor_page_emit_content_changed (editor_page);
4006 } else if (key_code == HTML_KEY_CODE_CONTROL)
4007 dom_set_links_active (document, FALSE);
4008 else if (key_code == HTML_KEY_CODE_RETURN)
4009 e_editor_dom_body_key_up_event_process_return_key (editor_page);
4012 static void
4013 fix_structure_after_pasting_multiline_content (WebKitDOMNode *node)
4015 WebKitDOMNode *first_child, *parent;
4017 /* When pasting content that does not contain just the
4018 * one line text WebKit inserts all the content after the
4019 * first line into one element. So we have to take it out
4020 * of this element and insert it after that element. */
4021 parent = webkit_dom_node_get_parent_node (node);
4022 if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent))
4023 return;
4024 first_child = webkit_dom_node_get_first_child (parent);
4025 while (first_child) {
4026 WebKitDOMNode *next_child =
4027 webkit_dom_node_get_next_sibling (first_child);
4028 if (webkit_dom_node_has_child_nodes (first_child))
4029 webkit_dom_node_insert_before (
4030 webkit_dom_node_get_parent_node (parent),
4031 first_child,
4032 parent,
4033 NULL);
4034 first_child = next_child;
4038 static gboolean
4039 delete_hidden_space (EEditorPage *editor_page)
4041 WebKitDOMDocument *document;
4042 WebKitDOMElement *selection_start_marker, *selection_end_marker, *block;
4043 gint citation_level;
4045 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
4047 document = e_editor_page_get_document (editor_page);
4049 selection_start_marker = webkit_dom_document_get_element_by_id (
4050 document, "-x-evo-selection-start-marker");
4051 selection_end_marker = webkit_dom_document_get_element_by_id (
4052 document, "-x-evo-selection-end-marker");
4054 if (!selection_start_marker || !selection_end_marker)
4055 return FALSE;
4057 block = WEBKIT_DOM_ELEMENT (e_editor_dom_get_parent_block_node_from_child (
4058 WEBKIT_DOM_NODE (selection_start_marker)));
4060 citation_level = e_editor_dom_get_citation_level (WEBKIT_DOM_NODE (selection_start_marker));
4062 if (selection_start_marker && citation_level > 0) {
4063 EEditorUndoRedoManager *manager;
4064 EEditorHistoryEvent *ev = NULL;
4065 WebKitDOMNode *node;
4066 WebKitDOMDocumentFragment *fragment;
4068 manager = e_editor_page_get_undo_redo_manager (editor_page);
4070 node = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_start_marker));
4071 if (!(WEBKIT_DOM_IS_ELEMENT (node) &&
4072 element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-quoted")))
4073 return FALSE;
4075 node = webkit_dom_node_get_previous_sibling (node);
4076 if (!(WEBKIT_DOM_IS_ELEMENT (node) &&
4077 element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-wrap-br")))
4078 return FALSE;
4080 node = webkit_dom_node_get_previous_sibling (node);
4081 if (!(WEBKIT_DOM_IS_ELEMENT (node) &&
4082 webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (node), "data-hidden-space")))
4083 return FALSE;
4085 ev = g_new0 (EEditorHistoryEvent, 1);
4086 ev->type = HISTORY_DELETE;
4088 e_editor_dom_selection_get_coordinates (editor_page, &ev->before.start.x, &ev->before.start.y, &ev->before.end.x, &ev->before.end.y);
4090 remove_node (node);
4092 e_editor_dom_wrap_and_quote_element (editor_page, block);
4094 fragment = webkit_dom_document_create_document_fragment (document);
4095 webkit_dom_node_append_child (
4096 WEBKIT_DOM_NODE (fragment),
4097 WEBKIT_DOM_NODE (
4098 webkit_dom_document_create_text_node (document, " ")),
4099 NULL);
4100 ev->data.fragment = g_object_ref (fragment);
4102 e_editor_dom_selection_get_coordinates (editor_page, &ev->after.start.x, &ev->after.start.y, &ev->after.end.x, &ev->after.end.y);
4104 e_editor_undo_redo_manager_insert_history_event (manager, ev);
4106 return TRUE;
4109 return FALSE;
4112 static gboolean
4113 caret_is_on_the_line_beginning_html (WebKitDOMDocument *document)
4115 gboolean ret_val = FALSE;
4116 WebKitDOMDOMWindow *dom_window = NULL;
4117 WebKitDOMDOMSelection *dom_selection = NULL;
4118 WebKitDOMRange *tmp_range = NULL, *actual_range = NULL;
4120 dom_window = webkit_dom_document_get_default_view (document);
4121 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
4123 actual_range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
4125 webkit_dom_dom_selection_modify (dom_selection, "move", "left", "lineBoundary");
4127 tmp_range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
4129 if (webkit_dom_range_compare_boundary_points (tmp_range, WEBKIT_DOM_RANGE_START_TO_START, actual_range, NULL) == 0)
4130 ret_val = TRUE;
4132 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
4133 webkit_dom_dom_selection_add_range (dom_selection, actual_range);
4135 g_clear_object (&tmp_range);
4136 g_clear_object (&actual_range);
4138 g_clear_object (&dom_window);
4139 g_clear_object (&dom_selection);
4141 return ret_val;
4144 static gboolean
4145 is_empty_quoted_element (WebKitDOMElement *element)
4147 WebKitDOMNode *node = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element));
4149 if (!WEBKIT_DOM_IS_ELEMENT (node) || !element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-quoted"))
4150 return FALSE;
4152 if (!(node = webkit_dom_node_get_next_sibling (node)))
4153 return TRUE;
4155 if (WEBKIT_DOM_IS_TEXT (node)) {
4156 gchar *content;
4158 content = webkit_dom_node_get_text_content (node);
4159 if (content && *content) {
4160 g_free (content);
4161 return FALSE;
4164 g_free (content);
4165 return webkit_dom_node_get_next_sibling (node) ? FALSE : TRUE;
4168 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (node))
4169 return webkit_dom_node_get_next_sibling (node) ? FALSE : TRUE;
4171 if (!WEBKIT_DOM_IS_ELEMENT (node) || !element_has_id (WEBKIT_DOM_ELEMENT (node), "-x-evo-selection-start-marker"))
4172 return FALSE;
4174 if (!(node = webkit_dom_node_get_next_sibling (node)))
4175 return FALSE;
4177 if (!WEBKIT_DOM_IS_ELEMENT (node) || !element_has_id (WEBKIT_DOM_ELEMENT (node), "-x-evo-selection-end-marker"))
4178 return FALSE;
4180 if (!(node = webkit_dom_node_get_next_sibling (node)))
4181 return TRUE;
4183 if (!WEBKIT_DOM_IS_HTML_BR_ELEMENT (node)) {
4184 if (WEBKIT_DOM_IS_TEXT (node)) {
4185 gchar *content;
4187 content = webkit_dom_node_get_text_content (node);
4188 if (content && *content) {
4189 g_free (content);
4190 return FALSE;
4193 g_free (content);
4194 return webkit_dom_node_get_next_sibling (node) ? FALSE : TRUE;
4196 return FALSE;
4199 if (!(node = webkit_dom_node_get_next_sibling (node)))
4200 return TRUE;
4202 return TRUE;
4205 gboolean
4206 e_editor_dom_move_quoted_block_level_up (EEditorPage *editor_page)
4208 WebKitDOMDocument *document;
4209 WebKitDOMElement *selection_start_marker, *selection_end_marker;
4210 WebKitDOMNode *block;
4211 EEditorHistoryEvent *ev = NULL;
4212 EEditorUndoRedoManager *manager;
4213 gboolean html_mode;
4214 gint citation_level, success = FALSE;
4216 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
4218 document = e_editor_page_get_document (editor_page);
4219 manager = e_editor_page_get_undo_redo_manager (editor_page);
4220 html_mode = e_editor_page_get_html_mode (editor_page);
4222 selection_start_marker = webkit_dom_document_get_element_by_id (
4223 document, "-x-evo-selection-start-marker");
4224 selection_end_marker = webkit_dom_document_get_element_by_id (
4225 document, "-x-evo-selection-end-marker");
4227 if (!selection_start_marker || !selection_end_marker)
4228 return FALSE;
4230 block = e_editor_dom_get_parent_block_node_from_child (WEBKIT_DOM_NODE (selection_start_marker));
4232 citation_level = e_editor_dom_get_citation_level (WEBKIT_DOM_NODE (selection_start_marker));
4234 if (selection_start_marker && citation_level > 0) {
4235 if (webkit_dom_element_query_selector (
4236 WEBKIT_DOM_ELEMENT (block), ".-x-evo-quoted", NULL)) {
4238 WebKitDOMNode *prev_sibling;
4240 webkit_dom_node_normalize (block);
4242 prev_sibling = webkit_dom_node_get_previous_sibling (
4243 WEBKIT_DOM_NODE (selection_start_marker));
4245 if (!prev_sibling) {
4246 WebKitDOMNode *parent;
4248 parent = webkit_dom_node_get_parent_node (
4249 WEBKIT_DOM_NODE (selection_start_marker));
4250 if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent))
4251 prev_sibling = webkit_dom_node_get_previous_sibling (parent);
4254 if (WEBKIT_DOM_IS_ELEMENT (prev_sibling))
4255 success = element_has_class (
4256 WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-quoted");
4258 /* We really have to be in the beginning of paragraph and
4259 * not on the beginning of some line in the paragraph */
4260 if (success && webkit_dom_node_get_previous_sibling (prev_sibling))
4261 success = FALSE;
4264 if (html_mode) {
4265 webkit_dom_node_normalize (block);
4267 success = caret_is_on_the_line_beginning_html (document);
4268 if (webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_start_marker)))
4269 block = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_start_marker));
4273 if (!success)
4274 return FALSE;
4276 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
4277 ev = g_new0 (EEditorHistoryEvent, 1);
4278 ev->type = HISTORY_UNQUOTE;
4280 e_editor_dom_selection_get_coordinates (editor_page, &ev->before.start.x, &ev->before.start.y, &ev->before.end.x, &ev->before.end.y);
4281 ev->data.dom.from = g_object_ref (webkit_dom_node_clone_node_with_error (block, TRUE, NULL));
4284 if (citation_level == 1) {
4285 gboolean is_empty_quoted_block = FALSE;
4286 gchar *inner_html = NULL;
4287 WebKitDOMElement *paragraph, *element;
4289 if (WEBKIT_DOM_IS_ELEMENT (block)) {
4290 is_empty_quoted_block = is_empty_quoted_element (WEBKIT_DOM_ELEMENT (block));
4291 inner_html = webkit_dom_element_get_inner_html (WEBKIT_DOM_ELEMENT (block));
4292 webkit_dom_element_set_id (WEBKIT_DOM_ELEMENT (block), "-x-evo-to-remove");
4295 paragraph = e_editor_dom_insert_new_line_into_citation (editor_page, inner_html);
4296 g_free (inner_html);
4298 if (paragraph) {
4299 if (!(webkit_dom_element_query_selector (paragraph, "#-x-evo-selection-start-marker", NULL)))
4300 webkit_dom_node_insert_before (
4301 WEBKIT_DOM_NODE (paragraph),
4302 WEBKIT_DOM_NODE (selection_start_marker),
4303 webkit_dom_node_get_first_child (
4304 WEBKIT_DOM_NODE (paragraph)),
4305 NULL);
4307 if (!(webkit_dom_element_query_selector (paragraph, "#-x-evo-selection-end-marker", NULL)))
4308 webkit_dom_node_insert_before (
4309 WEBKIT_DOM_NODE (paragraph),
4310 WEBKIT_DOM_NODE (selection_end_marker),
4311 webkit_dom_node_get_first_child (
4312 WEBKIT_DOM_NODE (paragraph)),
4313 NULL);
4315 e_editor_dom_remove_quoting_from_element (paragraph);
4316 e_editor_dom_remove_wrapping_from_element (paragraph);
4318 /* Moving PRE block from citation to body */
4319 if (WEBKIT_DOM_IS_HTML_PRE_ELEMENT (block) && !is_empty_quoted_block) {
4320 WebKitDOMElement *pre;
4321 WebKitDOMNode *child;
4323 pre = webkit_dom_document_create_element (document, "pre", NULL);
4324 webkit_dom_node_insert_before (
4325 webkit_dom_node_get_parent_node (
4326 WEBKIT_DOM_NODE (paragraph)),
4327 WEBKIT_DOM_NODE (pre),
4328 WEBKIT_DOM_NODE (paragraph),
4329 NULL);
4331 while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (paragraph))))
4332 webkit_dom_node_append_child (WEBKIT_DOM_NODE (pre), child, NULL);
4334 remove_node (WEBKIT_DOM_NODE (paragraph));
4335 paragraph = pre;
4339 if (block)
4340 remove_node (block);
4342 while ((element = webkit_dom_document_get_element_by_id (document, "-x-evo-to-remove")))
4343 remove_node (WEBKIT_DOM_NODE (element));
4345 if (paragraph)
4346 remove_node_if_empty (
4347 webkit_dom_node_get_next_sibling (
4348 WEBKIT_DOM_NODE (paragraph)));
4351 if (citation_level > 1) {
4352 WebKitDOMNode *parent;
4354 if (html_mode) {
4355 webkit_dom_node_insert_before (
4356 block,
4357 WEBKIT_DOM_NODE (selection_start_marker),
4358 webkit_dom_node_get_first_child (block),
4359 NULL);
4360 webkit_dom_node_insert_before (
4361 block,
4362 WEBKIT_DOM_NODE (selection_end_marker),
4363 webkit_dom_node_get_first_child (block),
4364 NULL);
4368 e_editor_dom_remove_quoting_from_element (WEBKIT_DOM_ELEMENT (block));
4369 e_editor_dom_remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (block));
4371 parent = webkit_dom_node_get_parent_node (block);
4373 if (!webkit_dom_node_get_previous_sibling (block)) {
4374 /* Currect block is in the beginning of citation, just move it
4375 * before the citation where already is */
4376 webkit_dom_node_insert_before (
4377 webkit_dom_node_get_parent_node (parent),
4378 block,
4379 parent,
4380 NULL);
4381 } else if (!webkit_dom_node_get_next_sibling (block)) {
4382 /* Currect block is at the end of the citation, just move it
4383 * after the citation where already is */
4384 webkit_dom_node_insert_before (
4385 webkit_dom_node_get_parent_node (parent),
4386 block,
4387 webkit_dom_node_get_next_sibling (parent),
4388 NULL);
4389 } else {
4390 /* Current block is somewhere in the middle of the citation
4391 * so we need to split the citation and insert the block into
4392 * the citation that is one level lower */
4393 WebKitDOMNode *clone, *child;
4395 clone = webkit_dom_node_clone_node_with_error (parent, FALSE, NULL);
4397 /* Move nodes that are after the currect block into the
4398 * new blockquote */
4399 child = webkit_dom_node_get_next_sibling (block);
4400 while (child) {
4401 WebKitDOMNode *next = webkit_dom_node_get_next_sibling (child);
4402 webkit_dom_node_append_child (clone, child, NULL);
4403 child = next;
4406 clone = webkit_dom_node_insert_before (
4407 webkit_dom_node_get_parent_node (parent),
4408 clone,
4409 webkit_dom_node_get_next_sibling (parent),
4410 NULL);
4412 webkit_dom_node_insert_before (
4413 webkit_dom_node_get_parent_node (parent),
4414 block,
4415 clone,
4416 NULL);
4419 e_editor_dom_wrap_and_quote_element (editor_page, WEBKIT_DOM_ELEMENT (block));
4422 remove_empty_blocks (document);
4424 if (ev) {
4425 e_editor_dom_selection_get_coordinates (editor_page,
4426 &ev->after.start.x,
4427 &ev->after.start.y,
4428 &ev->after.end.x,
4429 &ev->after.end.y);
4430 e_editor_undo_redo_manager_insert_history_event (manager, ev);
4433 return success;
4436 static gboolean
4437 prevent_from_deleting_last_element_in_body (WebKitDOMDocument *document)
4439 gboolean ret_val = FALSE;
4440 WebKitDOMHTMLElement *body;
4441 WebKitDOMNode *node;
4443 body = webkit_dom_document_get_body (document);
4445 node = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
4446 if (!node || !webkit_dom_node_get_next_sibling (node)) {
4447 gchar *content;
4449 content = webkit_dom_node_get_text_content (WEBKIT_DOM_NODE (body));
4451 if (!content || !*content)
4452 ret_val = TRUE;
4454 g_free (content);
4456 if (webkit_dom_element_query_selector (WEBKIT_DOM_ELEMENT (body), "img", NULL))
4457 ret_val = FALSE;
4460 return ret_val;
4463 static void
4464 insert_quote_symbols (WebKitDOMDocument *document,
4465 WebKitDOMHTMLElement *element,
4466 gint quote_level)
4468 gchar *quotation;
4469 WebKitDOMElement *quote_element;
4471 if (!WEBKIT_DOM_IS_ELEMENT (element))
4472 return;
4474 quotation = get_quotation_for_level (quote_level);
4476 quote_element = webkit_dom_document_create_element (document, "span", NULL);
4477 element_add_class (quote_element, "-x-evo-quoted");
4479 webkit_dom_element_set_inner_html (quote_element, quotation, NULL);
4480 webkit_dom_node_insert_before (
4481 WEBKIT_DOM_NODE (element),
4482 WEBKIT_DOM_NODE (quote_element),
4483 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)),
4484 NULL);
4486 g_free (quotation);
4489 static void
4490 quote_node (WebKitDOMDocument *document,
4491 WebKitDOMNode *node,
4492 gint quote_level)
4494 WebKitDOMNode *parent, *next_sibling;
4496 /* Don't quote when we are not in citation */
4497 if (quote_level == 0)
4498 return;
4500 if (WEBKIT_DOM_IS_COMMENT (node))
4501 return;
4503 if (WEBKIT_DOM_IS_ELEMENT (node)) {
4504 insert_quote_symbols (document, WEBKIT_DOM_HTML_ELEMENT (node), quote_level);
4505 return;
4508 next_sibling = webkit_dom_node_get_next_sibling (node);
4510 /* Skip the BR between first blockquote and pre */
4511 if (quote_level == 1 && next_sibling && WEBKIT_DOM_IS_HTML_PRE_ELEMENT (next_sibling))
4512 return;
4514 parent = webkit_dom_node_get_parent_node (node);
4516 insert_quote_symbols (
4517 document, WEBKIT_DOM_HTML_ELEMENT (parent), quote_level);
4520 static void
4521 insert_quote_symbols_before_node (WebKitDOMDocument *document,
4522 WebKitDOMNode *node,
4523 gint quote_level,
4524 gboolean is_html_node)
4526 gboolean skip, wrap_br;
4527 gchar *quotation;
4528 WebKitDOMElement *element;
4530 quotation = get_quotation_for_level (quote_level);
4531 element = webkit_dom_document_create_element (document, "SPAN", NULL);
4532 element_add_class (element, "-x-evo-quoted");
4533 webkit_dom_element_set_inner_html (element, quotation, NULL);
4535 /* Don't insert temporary BR before BR that is used for wrapping */
4536 skip = WEBKIT_DOM_IS_HTML_BR_ELEMENT (node);
4537 wrap_br = element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-wrap-br");
4538 skip = skip && wrap_br;
4540 if (is_html_node && !skip) {
4541 WebKitDOMElement *new_br;
4543 new_br = webkit_dom_document_create_element (document, "br", NULL);
4544 element_add_class (new_br, "-x-evo-temp-br");
4546 webkit_dom_node_insert_before (
4547 webkit_dom_node_get_parent_node (node),
4548 WEBKIT_DOM_NODE (new_br),
4549 node,
4550 NULL);
4553 webkit_dom_node_insert_before (
4554 webkit_dom_node_get_parent_node (node),
4555 WEBKIT_DOM_NODE (element),
4556 node,
4557 NULL);
4559 if (is_html_node && !wrap_br)
4560 remove_node (node);
4562 g_free (quotation);
4565 static gboolean
4566 check_if_suppress_next_node (WebKitDOMNode *node)
4568 if (!node)
4569 return FALSE;
4571 if (node && WEBKIT_DOM_IS_ELEMENT (node))
4572 if (e_editor_dom_is_selection_position_node (node))
4573 if (!webkit_dom_node_get_previous_sibling (node))
4574 return FALSE;
4576 return TRUE;
4579 static void
4580 quote_br_node (WebKitDOMNode *node,
4581 gint quote_level)
4583 gchar *quotation, *content;
4585 quotation = get_quotation_for_level (quote_level);
4587 content = g_strconcat (
4588 "<span class=\"-x-evo-quoted\">",
4589 quotation,
4590 "</span><br class=\"-x-evo-temp-br\">",
4591 NULL);
4593 webkit_dom_element_set_outer_html (
4594 WEBKIT_DOM_ELEMENT (node), content, NULL);
4596 g_free (content);
4597 g_free (quotation);
4600 static void
4601 quote_plain_text_recursive (WebKitDOMDocument *document,
4602 WebKitDOMNode *block,
4603 WebKitDOMNode *start_node,
4604 gint quote_level)
4606 gboolean skip_node = FALSE;
4607 gboolean move_next = FALSE;
4608 gboolean suppress_next = FALSE;
4609 gboolean is_html_node = FALSE;
4610 gboolean next = FALSE;
4611 WebKitDOMNode *node, *next_sibling, *prev_sibling;
4613 node = webkit_dom_node_get_first_child (block);
4615 while (node) {
4616 skip_node = FALSE;
4617 move_next = FALSE;
4618 is_html_node = FALSE;
4620 if (WEBKIT_DOM_IS_COMMENT (node) ||
4621 WEBKIT_DOM_IS_HTML_META_ELEMENT (node) ||
4622 WEBKIT_DOM_IS_HTML_STYLE_ELEMENT (node) ||
4623 WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (node)) {
4625 move_next = TRUE;
4626 goto next_node;
4629 prev_sibling = webkit_dom_node_get_previous_sibling (node);
4630 next_sibling = webkit_dom_node_get_next_sibling (node);
4632 if (WEBKIT_DOM_IS_TEXT (node)) {
4633 /* Start quoting after we are in blockquote */
4634 if (quote_level > 0 && !suppress_next) {
4635 /* When quoting text node, we are wrappering it and
4636 * afterwards replacing it with that wrapper, thus asking
4637 * for next_sibling after quoting will return NULL bacause
4638 * that node don't exist anymore */
4639 quote_node (document, node, quote_level);
4640 node = next_sibling;
4641 skip_node = TRUE;
4644 goto next_node;
4647 if (!(WEBKIT_DOM_IS_ELEMENT (node) || WEBKIT_DOM_IS_HTML_ELEMENT (node)))
4648 goto next_node;
4650 if (e_editor_dom_is_selection_position_node (node)) {
4651 /* If there is collapsed selection in the beginning of line
4652 * we cannot suppress first text that is after the end of
4653 * selection */
4654 suppress_next = check_if_suppress_next_node (prev_sibling);
4655 if (suppress_next)
4656 next = FALSE;
4657 move_next = TRUE;
4658 goto next_node;
4661 if (!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node) &&
4662 webkit_dom_element_get_child_element_count (WEBKIT_DOM_ELEMENT (node)) != 0)
4663 goto with_children;
4665 /* Even in plain text mode we can have some basic html element
4666 * like anchor and others. When Forwaring e-mail as Quoted EMFormat
4667 * generates header that contains <b> tags (bold font).
4668 * We have to treat these elements separately to avoid
4669 * modifications of theirs inner texts */
4670 is_html_node =
4671 WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node) ||
4672 element_has_tag (WEBKIT_DOM_ELEMENT (node), "b") ||
4673 element_has_tag (WEBKIT_DOM_ELEMENT (node), "i") ||
4674 element_has_tag (WEBKIT_DOM_ELEMENT (node), "u") ||
4675 element_has_class (WEBKIT_DOM_ELEMENT (node), "Apple-tab-span");
4677 if (is_html_node) {
4678 gboolean wrap_br;
4680 wrap_br =
4681 prev_sibling &&
4682 WEBKIT_DOM_IS_HTML_BR_ELEMENT (prev_sibling) &&
4683 element_has_class (
4684 WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-wrap-br");
4686 if (!prev_sibling || wrap_br) {
4687 insert_quote_symbols_before_node (
4688 document, node, quote_level, FALSE);
4689 if (!prev_sibling && next_sibling && WEBKIT_DOM_IS_TEXT (next_sibling))
4690 suppress_next = TRUE;
4693 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (prev_sibling) && !wrap_br)
4694 insert_quote_symbols_before_node (
4695 document, prev_sibling, quote_level, TRUE);
4697 move_next = TRUE;
4698 goto next_node;
4701 /* If element doesn't have children, we can quote it */
4702 if (e_editor_dom_node_is_citation_node (node)) {
4703 /* Citation with just text inside */
4704 quote_node (document, node, quote_level + 1);
4706 move_next = TRUE;
4707 goto next_node;
4710 if (!WEBKIT_DOM_IS_HTML_BR_ELEMENT (node)) {
4711 if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling)) {
4712 move_next = TRUE;
4713 goto next_node;
4715 goto not_br;
4716 } else if (element_has_id (WEBKIT_DOM_ELEMENT (node), "-x-evo-first-br") ||
4717 element_has_id (WEBKIT_DOM_ELEMENT (node), "-x-evo-last-br")) {
4718 quote_br_node (node, quote_level);
4719 node = next_sibling;
4720 skip_node = TRUE;
4721 goto next_node;
4724 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (prev_sibling)) {
4725 quote_br_node (prev_sibling, quote_level);
4726 node = next_sibling;
4727 skip_node = TRUE;
4728 goto next_node;
4731 if (!prev_sibling && !next_sibling) {
4732 WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node);
4734 if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent) ||
4735 WEBKIT_DOM_IS_HTML_PRE_ELEMENT (parent) ||
4736 (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent) &&
4737 !e_editor_dom_node_is_citation_node (parent))) {
4738 insert_quote_symbols_before_node (
4739 document, node, quote_level, FALSE);
4741 goto next_node;
4745 if (e_editor_dom_node_is_citation_node (prev_sibling)) {
4746 insert_quote_symbols_before_node (
4747 document, node, quote_level, FALSE);
4748 goto next_node;
4751 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (node) &&
4752 !next_sibling && WEBKIT_DOM_IS_ELEMENT (prev_sibling) &&
4753 e_editor_dom_is_selection_position_node (prev_sibling)) {
4754 insert_quote_symbols_before_node (
4755 document, node, quote_level, FALSE);
4756 goto next_node;
4759 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (node)) {
4760 if (!prev_sibling && !next_sibling) {
4761 insert_quote_symbols_before_node (
4762 document, node, quote_level, FALSE);
4763 } else
4764 move_next = TRUE;
4765 goto next_node;
4768 not_br:
4769 quote_node (document, node, quote_level);
4771 move_next = TRUE;
4772 goto next_node;
4774 with_children:
4775 if (e_editor_dom_node_is_citation_node (node)) {
4776 /* Go deeper and increase level */
4777 quote_plain_text_recursive (
4778 document, node, start_node, quote_level + 1);
4779 move_next = TRUE;
4780 } else {
4781 quote_plain_text_recursive (
4782 document, node, start_node, quote_level);
4783 move_next = TRUE;
4785 next_node:
4786 if (next) {
4787 suppress_next = FALSE;
4788 next = FALSE;
4791 if (suppress_next)
4792 next = TRUE;
4794 if (!skip_node) {
4795 /* Move to next node */
4796 if (!move_next && webkit_dom_node_has_child_nodes (node)) {
4797 node = webkit_dom_node_get_first_child (node);
4798 } else if (webkit_dom_node_get_next_sibling (node)) {
4799 node = webkit_dom_node_get_next_sibling (node);
4800 } else {
4801 return;
4807 WebKitDOMElement *
4808 e_editor_dom_quote_plain_text_element (EEditorPage *editor_page,
4809 WebKitDOMElement *element)
4811 WebKitDOMDocument *document;
4812 WebKitDOMNode *element_clone;
4813 WebKitDOMHTMLCollection *collection = NULL;
4814 gint ii, level;
4816 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
4818 document = e_editor_page_get_document (editor_page);
4819 element_clone = webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (element), TRUE, NULL);
4820 level = e_editor_dom_get_citation_level (WEBKIT_DOM_NODE (element));
4822 /* Remove old quote characters if the exists */
4823 collection = webkit_dom_element_get_elements_by_class_name_as_html_collection (
4824 WEBKIT_DOM_ELEMENT (element_clone), "-x-evo-quoted");
4825 for (ii = webkit_dom_html_collection_get_length (collection); ii--;)
4826 remove_node (webkit_dom_html_collection_item (collection, ii));
4827 g_clear_object (&collection);
4829 webkit_dom_node_normalize (element_clone);
4830 quote_plain_text_recursive (
4831 document, element_clone, element_clone, level);
4833 /* Replace old element with one, that is quoted */
4834 webkit_dom_node_replace_child (
4835 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
4836 element_clone,
4837 WEBKIT_DOM_NODE (element),
4838 NULL);
4840 return WEBKIT_DOM_ELEMENT (element_clone);
4844 * dom_quote_plain_text:
4846 * Quote text inside citation blockquotes in plain text mode.
4848 * As this function is cloning and replacing all citation blockquotes keep on
4849 * mind that any pointers to nodes inside these blockquotes will be invalidated.
4851 static WebKitDOMElement *
4852 dom_quote_plain_text (WebKitDOMDocument *document)
4854 WebKitDOMHTMLElement *body;
4855 WebKitDOMNode *body_clone;
4856 WebKitDOMNamedNodeMap *attributes = NULL;
4857 WebKitDOMNodeList *list = NULL;
4858 WebKitDOMElement *element;
4859 gint ii;
4860 gulong attributes_length;
4862 /* Check if the document is already quoted */
4863 element = webkit_dom_document_query_selector (
4864 document, ".-x-evo-quoted", NULL);
4865 if (element)
4866 return NULL;
4868 body = webkit_dom_document_get_body (document);
4869 body_clone = webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (body), TRUE, NULL);
4871 /* Clean unwanted spaces before and after blockquotes */
4872 list = webkit_dom_element_query_selector_all (
4873 WEBKIT_DOM_ELEMENT (body_clone), "blockquote[type|=cite]", NULL);
4874 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
4875 WebKitDOMNode *blockquote = webkit_dom_node_list_item (list, ii);
4876 WebKitDOMNode *prev_sibling = webkit_dom_node_get_previous_sibling (blockquote);
4877 WebKitDOMNode *next_sibling = webkit_dom_node_get_next_sibling (blockquote);
4879 if (prev_sibling && WEBKIT_DOM_IS_HTML_BR_ELEMENT (prev_sibling))
4880 remove_node (prev_sibling);
4882 if (next_sibling && WEBKIT_DOM_IS_HTML_BR_ELEMENT (next_sibling))
4883 remove_node (next_sibling);
4885 if (webkit_dom_node_has_child_nodes (blockquote)) {
4886 WebKitDOMNode *child = webkit_dom_node_get_first_child (blockquote);
4887 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (child))
4888 remove_node (child);
4891 g_clear_object (&list);
4893 webkit_dom_node_normalize (body_clone);
4894 quote_plain_text_recursive (document, body_clone, body_clone, 0);
4896 /* Copy attributes */
4897 attributes = webkit_dom_element_get_attributes (WEBKIT_DOM_ELEMENT (body));
4898 attributes_length = webkit_dom_named_node_map_get_length (attributes);
4899 for (ii = 0; ii < attributes_length; ii++) {
4900 gchar *name, *value;
4901 WebKitDOMNode *node = webkit_dom_named_node_map_item (attributes, ii);
4903 name = webkit_dom_attr_get_name (WEBKIT_DOM_ATTR (node));
4904 value = webkit_dom_node_get_node_value (node);
4906 webkit_dom_element_set_attribute (
4907 WEBKIT_DOM_ELEMENT (body_clone), name, value, NULL);
4909 g_free (name);
4910 g_free (value);
4912 g_clear_object (&attributes);
4914 /* Replace old BODY with one, that is quoted */
4915 webkit_dom_node_replace_child (
4916 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (body)),
4917 body_clone,
4918 WEBKIT_DOM_NODE (body),
4919 NULL);
4921 return WEBKIT_DOM_ELEMENT (body_clone);
4925 * dom_dequote_plain_text:
4927 * Dequote already quoted plain text in editor.
4928 * Editor have to be quoted with e_html_editor_view_quote_plain_text otherwise
4929 * it's not working.
4931 static void
4932 dom_dequote_plain_text (WebKitDOMDocument *document)
4934 WebKitDOMNodeList *paragraphs = NULL;
4935 gint ii;
4937 paragraphs = webkit_dom_document_query_selector_all (
4938 document, "blockquote[type=cite]", NULL);
4939 for (ii = webkit_dom_node_list_get_length (paragraphs); ii--;) {
4940 WebKitDOMElement *element;
4942 element = WEBKIT_DOM_ELEMENT (webkit_dom_node_list_item (paragraphs, ii));
4944 if (e_editor_dom_node_is_citation_node (WEBKIT_DOM_NODE (element)))
4945 e_editor_dom_remove_quoting_from_element (element);
4947 g_clear_object (&paragraphs);
4950 static gboolean
4951 create_anchor_for_link (const GMatchInfo *info,
4952 GString *res,
4953 gpointer data)
4955 gboolean link_surrounded, ending_with_nbsp = FALSE;
4956 gint offset = 0, truncate_from_end = 0;
4957 gint match_start, match_end;
4958 gchar *match;
4959 const gchar *end_of_match = NULL;
4960 const gchar *nbsp_match = NULL;
4962 match = g_match_info_fetch (info, 0);
4963 g_match_info_fetch_pos (info, 0, &match_start, &match_end);
4965 if (g_str_has_suffix (match, "&nbsp;")) {
4966 ending_with_nbsp = TRUE;
4967 truncate_from_end = 6;
4970 if (g_str_has_prefix (match, "&nbsp;"))
4971 offset += 6;
4973 end_of_match = match + match_end - match_start - 1;
4974 /* Taken from camel-url-scanner.c */
4975 /* URLs are extremely unlikely to end with any punctuation, so
4976 * strip any trailing punctuation off from link and put it after
4977 * the link. Do the same for any closing double-quotes as well. */
4978 while (end_of_match && end_of_match != match && strchr (URL_INVALID_TRAILING_CHARS, *end_of_match)) {
4979 truncate_from_end++;
4980 end_of_match--;
4982 end_of_match++;
4984 link_surrounded =
4985 g_str_has_suffix (res->str, "&lt;");
4987 if (link_surrounded) {
4988 if (end_of_match && *end_of_match && strlen (match) > strlen (end_of_match) + 3)
4989 link_surrounded = link_surrounded && g_str_has_prefix (end_of_match - 3, "&gt;");
4990 else
4991 link_surrounded = link_surrounded && g_str_has_suffix (match, "&gt;");
4993 if (link_surrounded) {
4994 truncate_from_end += 4;
4995 end_of_match -= 4;
4999 /* The ending ';' was counted when looking for the invalid trailing characters, substract it. */
5000 if (link_surrounded || ending_with_nbsp) {
5001 truncate_from_end -= 1;
5002 end_of_match += 1;
5005 /* If there is non-breaking space in the match, remove it and everything
5006 * after it from the match */
5007 if (!g_str_has_prefix (match, "&nbsp;") && !g_str_has_suffix (match, "&nbsp;") && (nbsp_match = strstr (match, "&nbsp;"))) {
5008 glong after_nbsp_length = g_utf8_strlen (nbsp_match, -1);
5009 truncate_from_end = after_nbsp_length;
5010 end_of_match -= after_nbsp_length;
5011 if (link_surrounded)
5012 end_of_match += 4;
5015 g_string_append (res, "<a href=\"");
5016 if (strstr (match, "@") && !strstr (match, "://"))
5017 g_string_append (res, "mailto:");
5018 g_string_append (res, match + offset);
5019 if (truncate_from_end > 0)
5020 g_string_truncate (res, res->len - truncate_from_end);
5022 g_string_append (res, "\">");
5023 g_string_append (res, match + offset);
5024 if (truncate_from_end > 0)
5025 g_string_truncate (res, res->len - truncate_from_end);
5027 g_string_append (res, "</a>");
5029 if (truncate_from_end > 0)
5030 g_string_append (res, end_of_match);
5032 if (ending_with_nbsp)
5033 g_string_append (res, "&nbsp;");
5035 g_free (match);
5037 return FALSE;
5040 static gboolean
5041 replace_to_nbsp (const GMatchInfo *info,
5042 GString *res)
5044 gchar *match;
5045 gint ii = 0;
5047 match = g_match_info_fetch (info, 0);
5049 while (match[ii] != '\0') {
5050 if (match[ii] == ' ') {
5051 /* Alone spaces or spaces before/after tabulator. */
5052 g_string_append (res, "&nbsp;");
5053 } else if (match[ii] == '\t') {
5054 /* Replace tabs with their WebKit HTML representation. */
5055 g_string_append (res, "<span class=\"Apple-tab-span\" style=\"white-space:pre\">\t</span>");
5058 ii++;
5061 g_free (match);
5063 return FALSE;
5066 static gboolean
5067 surround_links_with_anchor (const gchar *text)
5069 return (strstr (text, "http") || strstr (text, "ftp") ||
5070 strstr (text, "www") || strstr (text, "@"));
5073 static void
5074 append_new_block (WebKitDOMElement *parent,
5075 WebKitDOMElement **block)
5077 webkit_dom_node_append_child (
5078 WEBKIT_DOM_NODE (parent),
5079 WEBKIT_DOM_NODE (*block),
5080 NULL);
5082 *block = NULL;
5085 static WebKitDOMElement *
5086 create_and_append_new_block (EEditorPage *editor_page,
5087 WebKitDOMElement *parent,
5088 WebKitDOMElement *block_template,
5089 const gchar *content)
5091 WebKitDOMElement *block;
5093 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
5095 block = WEBKIT_DOM_ELEMENT (webkit_dom_node_clone_node_with_error (
5096 WEBKIT_DOM_NODE (block_template), FALSE, NULL));
5098 webkit_dom_element_set_inner_html (block, content, NULL);
5100 append_new_block (parent, &block);
5102 return block;
5105 static void
5106 append_citation_mark (WebKitDOMDocument *document,
5107 WebKitDOMElement *parent,
5108 const gchar *citation_mark_text)
5110 WebKitDOMText *text;
5112 text = webkit_dom_document_create_text_node (document, citation_mark_text);
5114 webkit_dom_node_append_child (
5115 WEBKIT_DOM_NODE (parent),
5116 WEBKIT_DOM_NODE (text),
5117 NULL);
5120 static void
5121 replace_selection_markers (gchar **text)
5123 if (!text)
5124 return;
5126 if (strstr (*text, "##SELECTION_START##")) {
5127 GString *tmp;
5129 tmp = e_str_replace_string (
5130 *text,
5131 "##SELECTION_START##",
5132 "<span id=\"-x-evo-selection-start-marker\"></span>");
5134 g_free (*text);
5135 *text = g_string_free (tmp, FALSE);
5138 if (strstr (*text, "##SELECTION_END##")) {
5139 GString *tmp;
5141 tmp = e_str_replace_string (
5142 *text,
5143 "##SELECTION_END##",
5144 "<span id=\"-x-evo-selection-end-marker\"></span>");
5146 g_free (*text);
5147 *text = g_string_free (tmp, FALSE);
5151 static GString *
5152 remove_new_lines_around_citations (const gchar *input)
5154 GString *str = NULL;
5155 const gchar *p, *next;
5157 str = g_string_new ("");
5159 /* Remove the new lines around citations:
5160 * Replace <br><br>##CITATION_START## with <br>##CITATION_START##
5161 * Replace ##CITATION_START##<br><br> with ##CITATION_START##<br>
5162 * Replace ##CITATION_END##<br><br> with ##CITATION_END##<br>
5163 * Replace <br>##CITATION_END## with ##CITATION_END##
5164 * Replace <br>##CITATION_START## with ##CITATION_START## */
5165 p = input;
5166 while (next = strstr (p, "##CITATION_"), next) {
5167 gchar citation_type = 0;
5169 if (p < next)
5170 g_string_append_len (str, p, next - p);
5172 if (next + 11)
5173 citation_type = next[11];
5174 /* ##CITATION_START## */
5175 if (citation_type == 'S') {
5176 if (g_str_has_suffix (str->str, "<br><br>") ||
5177 g_str_has_suffix (str->str, "<br>"))
5178 g_string_truncate (str, str->len - 4);
5180 if (g_str_has_prefix (next + 11, "START##<br><br>")) {
5181 g_string_append (str, "##CITATION_START##<br>");
5182 p = next + 26;
5183 continue;
5185 } else if (citation_type == 'E') {
5186 if (g_str_has_suffix (str->str, "<br>"))
5187 g_string_truncate (str, str->len - 4);
5189 if (g_str_has_prefix (next + 11, "END##<br><br>")) {
5190 g_string_append (str, "##CITATION_END##<br>");
5191 p = next + 24;
5192 continue;
5196 g_string_append (str, "##CITATION_");
5198 p = next + 11;
5201 g_string_append (str, p);
5203 if (camel_debug ("webkit:editor")) {
5204 printf ("EWebKitContentEditor - %s\n", G_STRFUNC);
5205 printf ("\toutput: '%s'\n", str->str);
5208 return str;
5211 static GString *
5212 replace_citation_marks_to_citations (const gchar *input)
5214 GString *str = NULL;
5215 const gchar *p, *next;
5217 str = g_string_new ("");
5219 /* Replaces text markers with actual HTML blockquotes */
5220 p = input;
5221 while (next = strstr (p, "##CITATION_"), next) {
5222 gchar citation_type = 0;
5224 if (p < next)
5225 g_string_append_len (str, p, next - p);
5227 if (next + 11)
5228 citation_type = next[11];
5229 /* ##CITATION_START## */
5230 if (citation_type == 'S') {
5231 g_string_append (str, "<blockquote type=\"cite\">");
5232 p = next + 18;
5233 } else if (citation_type == 'E') {
5234 g_string_append (str, "</blockquote>");
5235 p = next + 16;
5236 } else
5237 p = next + 11;
5240 g_string_append (str, p);
5242 return str;
5245 /* This parses the HTML code (that contains just text, &nbsp; and BR elements)
5246 * into blocks.
5247 * HTML code in that format we can get by taking innerText from some element,
5248 * setting it to another one and finally getting innerHTML from it */
5249 static void
5250 parse_html_into_blocks (EEditorPage *editor_page,
5251 WebKitDOMElement *parent,
5252 WebKitDOMElement *passed_block_template,
5253 const gchar *input)
5255 gboolean has_citation = FALSE, processing_last = FALSE;
5256 const gchar *prev_token, *next_token;
5257 const gchar *next_br_token = NULL, *next_citation_token = NULL;
5258 GString *html = NULL;
5259 GRegex *regex_nbsp = NULL, *regex_link = NULL, *regex_email = NULL;
5260 WebKitDOMDocument *document;
5261 WebKitDOMElement *block_template = passed_block_template;
5263 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
5265 if (!(input && *input))
5266 return;
5268 document = e_editor_page_get_document (editor_page);
5269 webkit_dom_element_set_inner_html (parent, "", NULL);
5271 if (!block_template) {
5272 gboolean use_paragraphs;
5273 GSettings *settings;
5275 settings = e_util_ref_settings ("org.gnome.evolution.mail");
5277 use_paragraphs = g_settings_get_boolean (
5278 settings, "composer-wrap-quoted-text-in-replies");
5280 if (use_paragraphs)
5281 block_template = e_editor_dom_get_paragraph_element (editor_page, -1, 0);
5282 else
5283 block_template = webkit_dom_document_create_element (document, "pre", NULL);
5285 g_object_unref (settings);
5288 /* Replace the tabulators with SPAN elements that corresponds to them.
5289 * If not inserting the content into the PRE element also replace single
5290 * spaces on the beginning of line, 2+ spaces and with non breaking
5291 * spaces. */
5292 if (WEBKIT_DOM_IS_HTML_PRE_ELEMENT (block_template))
5293 regex_nbsp = g_regex_new ("\x9", 0, 0, NULL);
5294 else
5295 regex_nbsp = g_regex_new ("^\\s{1}|\\s{2,}|\x9|\\s$", 0, 0, NULL);
5297 if (camel_debug ("webkit:editor")) {
5298 printf ("EWebKitContentEditor - %s\n", G_STRFUNC);
5299 printf ("\tinput: '%s'\n", input);
5301 html = remove_new_lines_around_citations (input);
5303 prev_token = html->str;
5304 next_br_token = (prev_token && *prev_token) ? strstr (prev_token + 1, "<br>") : NULL;
5305 next_citation_token = (prev_token && *prev_token) ? strstr (prev_token + 1, "##CITATION_") : NULL;
5306 if (next_br_token) {
5307 if (next_citation_token)
5308 next_token = next_br_token < next_citation_token ? next_br_token : next_citation_token;
5309 else
5310 next_token = next_br_token;
5311 } else {
5312 next_token = next_citation_token;
5314 processing_last = !next_token;
5316 while (next_token || processing_last) {
5317 const gchar *citation_start = NULL, *citation_end = NULL;
5318 const gchar *rest = NULL, *with_br = NULL;
5319 gchar *to_process = NULL, *to_insert = NULL;
5320 guint to_insert_start = 0, to_insert_end = 0;
5322 if (!next_token) {
5323 to_process = g_strdup (prev_token);
5324 processing_last = TRUE;
5325 } else if ((to_process = g_utf8_substring (prev_token, 0, g_utf8_pointer_to_offset (prev_token, next_token))) &&
5326 !*to_process && !processing_last) {
5327 g_free (to_process);
5328 to_process = g_strdup (next_token);
5329 next_token = NULL;
5330 processing_last = TRUE;
5333 if (camel_debug ("webkit:editor"))
5334 printf ("\tto_process: '%s'\n", to_process);
5336 if (to_process && !*to_process && processing_last) {
5337 g_free (to_process);
5338 to_process = g_strdup (next_token);
5339 next_token = NULL;
5342 to_insert_end = g_utf8_strlen (to_process, -1);
5344 if ((with_br = strstr (to_process, "<br>"))) {
5345 if (with_br == to_process)
5346 to_insert_start += 4;
5349 if ((citation_start = strstr (to_process, "##CITATION_START"))) {
5350 if (with_br && citation_start == with_br + 4)
5351 to_insert_start += 18; /* + ## */
5352 else if (!with_br && citation_start == to_process)
5353 to_insert_start += 18; /* + ## */
5354 else
5355 to_insert_end -= 18; /* + ## */
5356 has_citation = TRUE;
5359 if ((citation_end = strstr (to_process, "##CITATION_END"))) {
5360 if (citation_end == to_process)
5361 to_insert_start += 16;
5362 else
5363 to_insert_end -= 16; /* + ## */
5366 /* First BR */
5367 if (with_br && prev_token == html->str)
5368 create_and_append_new_block (
5369 editor_page, parent, block_template, "<br id=\"-x-evo-first-br\">");
5371 if (with_br && citation_start && citation_start == with_br + 4) {
5372 create_and_append_new_block (
5373 editor_page, parent, block_template, "<br>");
5375 append_citation_mark (document, parent, "##CITATION_START##");
5376 } else if (!with_br && citation_start == to_process) {
5377 append_citation_mark (document, parent, "##CITATION_START##");
5380 if (citation_end && citation_end == to_process) {
5381 append_citation_mark (document, parent, "##CITATION_END##");
5384 if ((to_insert = g_utf8_substring (to_process, to_insert_start, to_insert_end)) && *to_insert) {
5385 gboolean empty = FALSE;
5386 gchar *truncated = g_strdup (to_insert);
5387 gchar *rest_to_insert;
5389 if (camel_debug ("webkit:editor"))
5390 printf ("\tto_insert: '%s'\n", to_insert);
5392 empty = !*truncated && strlen (to_insert) > 0;
5394 rest_to_insert = g_regex_replace_eval (
5395 regex_nbsp,
5396 empty ? rest : truncated,
5400 (GRegexEvalCallback) replace_to_nbsp,
5401 NULL,
5402 NULL);
5403 g_free (truncated);
5405 replace_selection_markers (&rest_to_insert);
5407 if (surround_links_with_anchor (rest_to_insert)) {
5408 gboolean is_email_address =
5409 strstr (rest_to_insert, "@") &&
5410 !strstr (rest_to_insert, "://");
5412 if (is_email_address && !regex_email)
5413 regex_email = g_regex_new (E_MAIL_PATTERN, 0, 0, NULL);
5414 if (!is_email_address && !regex_link)
5415 regex_link = g_regex_new (URL_PATTERN, 0, 0, NULL);
5417 truncated = g_regex_replace_eval (
5418 is_email_address ? regex_email : regex_link,
5419 rest_to_insert,
5422 G_REGEX_MATCH_NOTEMPTY,
5423 create_anchor_for_link,
5424 NULL,
5425 NULL);
5427 g_free (rest_to_insert);
5428 rest_to_insert = truncated;
5431 create_and_append_new_block (
5432 editor_page, parent, block_template, rest_to_insert);
5434 g_free (rest_to_insert);
5435 } else if (to_insert) {
5436 if (!citation_start && (with_br || !citation_end))
5437 create_and_append_new_block (
5438 editor_page, parent, block_template, "<br>");
5439 else if (citation_end && citation_end == to_process &&
5440 next_token && g_str_has_prefix (next_token, "<br>")) {
5441 create_and_append_new_block (
5442 editor_page, parent, block_template, "<br>");
5446 g_free (to_insert);
5448 if (with_br && citation_start && citation_start != with_br + 4)
5449 append_citation_mark (document, parent, "##CITATION_START##");
5451 if (!with_br && citation_start && citation_start != to_process)
5452 append_citation_mark (document, parent, "##CITATION_START##");
5454 if (citation_end && citation_end != to_process)
5455 append_citation_mark (document, parent, "##CITATION_END##");
5457 g_free (to_process);
5459 prev_token = next_token;
5460 next_br_token = (prev_token && *prev_token) ? strstr (prev_token + 1, "<br>") : NULL;
5461 next_citation_token = (prev_token && *prev_token) ? strstr (prev_token + 1, "##CITATION_") : NULL;
5462 if (next_br_token) {
5463 if (next_citation_token)
5464 next_token = next_br_token < next_citation_token ? next_br_token : next_citation_token;
5465 else
5466 next_token = next_br_token;
5467 } else {
5468 next_token = next_citation_token;
5471 if (!next_token && !processing_last) {
5472 if (!prev_token)
5473 break;
5475 if (g_utf8_strlen (prev_token, -1) > 4) {
5476 next_token = prev_token;
5477 } else {
5478 WebKitDOMNode *child;
5480 if (g_strcmp0 (prev_token, "<br>") == 0)
5481 create_and_append_new_block (
5482 editor_page, parent, block_template, "<br>");
5484 child = webkit_dom_node_get_last_child (
5485 WEBKIT_DOM_NODE (parent));
5486 if (child) {
5487 child = webkit_dom_node_get_first_child (child);
5488 if (child && WEBKIT_DOM_IS_HTML_BR_ELEMENT (child)) {
5489 /* If the processed HTML contained just
5490 * the BR don't overwrite its id. */
5491 if (!element_has_id (WEBKIT_DOM_ELEMENT (child), "-x-evo-first-br"))
5492 webkit_dom_element_set_id (
5493 WEBKIT_DOM_ELEMENT (child),
5494 "-x-evo-last-br");
5496 } else {
5497 create_and_append_new_block (
5498 editor_page, parent, block_template, "<br>");
5500 break;
5502 processing_last = TRUE;
5503 } else if (processing_last && !prev_token && !next_token) {
5504 break;
5508 if (has_citation) {
5509 gchar *inner_html;
5510 GString *parsed;
5512 /* Replace text markers with actual HTML blockquotes */
5513 inner_html = webkit_dom_element_get_inner_html (parent);
5514 parsed = replace_citation_marks_to_citations (inner_html);
5515 webkit_dom_element_set_inner_html (parent, parsed->str, NULL);
5517 if (camel_debug ("webkit:editor"))
5518 printf ("\tparsed content: '%s'\n", inner_html);
5520 g_free (inner_html);
5521 g_string_free (parsed, TRUE);
5522 } else if (camel_debug ("webkit:editor")) {
5523 gchar *inner_html;
5525 inner_html = webkit_dom_element_get_inner_html (parent);
5526 printf ("\tparsed content: '%s'\n", inner_html);
5527 g_free (inner_html);
5530 g_string_free (html, TRUE);
5532 if (regex_email != NULL)
5533 g_regex_unref (regex_email);
5534 if (regex_link != NULL)
5535 g_regex_unref (regex_link);
5536 g_regex_unref (regex_nbsp);
5539 void
5540 e_editor_dom_quote_and_insert_text_into_selection (EEditorPage *editor_page,
5541 const gchar *text,
5542 gboolean is_html)
5544 WebKitDOMDocument *document;
5545 WebKitDOMElement *blockquote, *element, *selection_start;
5546 WebKitDOMNode *node;
5547 EEditorHistoryEvent *ev = NULL;
5548 EEditorUndoRedoManager *manager;
5549 gchar *inner_html;
5550 gboolean node_added = FALSE;
5552 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
5554 if (!text || !*text)
5555 return;
5557 document = e_editor_page_get_document (editor_page);
5559 if (is_html) {
5560 element = webkit_dom_document_create_element (document, "div", NULL);
5561 webkit_dom_element_set_inner_html (element, text, NULL);
5562 } else {
5563 /* This is a trick to escape any HTML characters (like <, > or &).
5564 * <textarea> automatically replaces all these unsafe characters
5565 * by &lt;, &gt; etc. */
5566 element = webkit_dom_document_create_element (document, "textarea", NULL);
5567 webkit_dom_html_element_set_inner_text (WEBKIT_DOM_HTML_ELEMENT (element), text, NULL);
5570 inner_html = webkit_dom_element_get_inner_html (element);
5572 e_editor_dom_selection_save (editor_page);
5574 manager = e_editor_page_get_undo_redo_manager (editor_page);
5575 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
5576 ev = g_new0 (EEditorHistoryEvent, 1);
5577 ev->type = HISTORY_PASTE_QUOTED;
5579 e_editor_dom_selection_get_coordinates (editor_page,
5580 &ev->before.start.x,
5581 &ev->before.start.y,
5582 &ev->before.end.x,
5583 &ev->before.end.y);
5585 ev->data.string.from = NULL;
5586 ev->data.string.to = g_strdup (text);
5589 blockquote = webkit_dom_document_create_element (document, "blockquote", NULL);
5590 webkit_dom_element_set_attribute (blockquote, "type", "cite", NULL);
5592 selection_start = webkit_dom_document_get_element_by_id (
5593 document, "-x-evo-selection-start-marker");
5594 node = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_start));
5596 /* Check if block is empty. If so, replace it otherwise insert the quoted
5597 * content after current block. */
5598 if (!node || WEBKIT_DOM_IS_HTML_BR_ELEMENT (node)) {
5599 node = webkit_dom_node_get_next_sibling (
5600 WEBKIT_DOM_NODE (selection_start));
5601 node = webkit_dom_node_get_next_sibling (node);
5602 if (!node || WEBKIT_DOM_IS_HTML_BR_ELEMENT (node)) {
5603 webkit_dom_node_replace_child (
5604 webkit_dom_node_get_parent_node (
5605 webkit_dom_node_get_parent_node (
5606 WEBKIT_DOM_NODE (selection_start))),
5607 WEBKIT_DOM_NODE (blockquote),
5608 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start)),
5609 NULL);
5610 node_added = TRUE;
5614 if (!node_added) {
5615 WebKitDOMNode *parent, *next_sibling = NULL;
5617 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start));
5618 next_sibling = webkit_dom_node_get_next_sibling (parent);
5620 if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent)) {
5621 WebKitDOMNode *up_parent;
5623 up_parent = webkit_dom_node_get_parent_node (parent);
5624 if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (up_parent)) {
5625 parent = up_parent;
5629 if (next_sibling) {
5630 webkit_dom_node_insert_before (
5631 webkit_dom_node_get_parent_node (next_sibling),
5632 WEBKIT_DOM_NODE (blockquote),
5633 next_sibling,
5634 NULL);
5635 } else {
5636 webkit_dom_node_append_child (
5637 parent,
5638 WEBKIT_DOM_NODE (blockquote),
5639 NULL);
5643 parse_html_into_blocks (editor_page, blockquote, NULL, inner_html);
5645 if (e_editor_page_get_html_mode (editor_page)) {
5646 node = webkit_dom_node_get_last_child (WEBKIT_DOM_NODE (blockquote));
5647 } else {
5648 gint word_wrap_length;
5650 word_wrap_length = e_editor_page_get_word_wrap_length (editor_page);
5651 node = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (blockquote));
5652 while (node) {
5653 WebKitDOMNode *next_sibling;
5655 if (!WEBKIT_DOM_IS_HTML_PRE_ELEMENT (node))
5656 node = WEBKIT_DOM_NODE (e_editor_dom_wrap_paragraph_length (editor_page, WEBKIT_DOM_ELEMENT (node), word_wrap_length - 2));
5658 webkit_dom_node_normalize (node);
5659 e_editor_dom_quote_plain_text_element_after_wrapping (editor_page, WEBKIT_DOM_ELEMENT (node),
5660 e_editor_dom_get_citation_level (node));
5662 next_sibling = webkit_dom_node_get_next_sibling (node);
5663 if (!next_sibling)
5664 break;
5666 node = next_sibling;
5670 dom_add_selection_markers_into_element_end (
5671 document, WEBKIT_DOM_ELEMENT (node), NULL, NULL);
5673 e_editor_dom_selection_restore (editor_page);
5675 if (ev) {
5676 e_editor_dom_selection_get_coordinates (editor_page,
5677 &ev->after.start.x,
5678 &ev->after.start.y,
5679 &ev->after.end.x,
5680 &ev->after.end.y);
5681 e_editor_undo_redo_manager_insert_history_event (manager, ev);
5684 if ((element = webkit_dom_document_get_element_by_id (document, "-x-evo-first-br")))
5685 webkit_dom_element_remove_attribute (element, "id");
5686 if ((element = webkit_dom_document_get_element_by_id (document, "-x-evo-last-br")))
5687 webkit_dom_element_remove_attribute (element, "id");
5689 e_editor_dom_force_spell_check_in_viewport (editor_page);
5690 e_editor_page_emit_content_changed (editor_page);
5692 g_free (inner_html);
5695 static void
5696 mark_citation (WebKitDOMElement *citation)
5698 webkit_dom_element_insert_adjacent_text (
5699 citation,
5700 "beforebegin",
5701 "##CITATION_START##",
5702 NULL);
5704 webkit_dom_element_insert_adjacent_text (
5705 citation,
5706 "afterend",
5707 "##CITATION_END##",
5708 NULL);
5710 element_add_class (citation, "marked");
5713 static gint
5714 create_text_markers_for_citations_in_element (WebKitDOMElement *element)
5716 gint count = 0;
5717 WebKitDOMElement *citation;
5719 citation = webkit_dom_element_query_selector (
5720 element, "blockquote[type=cite]:not(.marked)", NULL);
5722 while (citation) {
5723 mark_citation (citation);
5724 count ++;
5726 citation = webkit_dom_element_query_selector (
5727 element, "blockquote[type=cite]:not(.marked)", NULL);
5730 return count;
5733 static void
5734 create_text_markers_for_selection_in_element (WebKitDOMElement *element)
5736 WebKitDOMElement *selection_marker;
5738 selection_marker = webkit_dom_element_query_selector (
5739 element, "#-x-evo-selection-start-marker", NULL);
5740 if (selection_marker)
5741 webkit_dom_element_insert_adjacent_text (
5742 selection_marker,
5743 "afterend",
5744 "##SELECTION_START##",
5745 NULL);
5747 selection_marker = webkit_dom_element_query_selector (
5748 element, "#-x-evo-selection-end-marker", NULL);
5749 if (selection_marker)
5750 webkit_dom_element_insert_adjacent_text (
5751 selection_marker,
5752 "afterend",
5753 "##SELECTION_END##",
5754 NULL);
5757 static void
5758 quote_plain_text_elements_after_wrapping_in_element (EEditorPage *editor_page,
5759 WebKitDOMElement *element)
5761 WebKitDOMNodeList *list = NULL;
5762 gint ii;
5764 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
5766 /* Also quote the PRE elements as well. */
5767 list = webkit_dom_element_query_selector_all (
5768 element, "blockquote[type=cite] > [data-evo-paragraph], blockquote[type=cite] > pre", NULL);
5770 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
5771 gint citation_level;
5772 WebKitDOMNode *child;
5774 child = webkit_dom_node_list_item (list, ii);
5775 citation_level = e_editor_dom_get_citation_level (child);
5776 e_editor_dom_quote_plain_text_element_after_wrapping (editor_page, WEBKIT_DOM_ELEMENT (child), citation_level);
5778 g_clear_object (&list);
5781 static void
5782 quote_plain_text_elements_after_wrapping_in_document (EEditorPage *editor_page)
5784 WebKitDOMDocument *document;
5785 WebKitDOMHTMLElement *body;
5787 document = e_editor_page_get_document (editor_page);
5788 body = webkit_dom_document_get_body (document);
5790 quote_plain_text_elements_after_wrapping_in_element (editor_page, WEBKIT_DOM_ELEMENT (body));
5793 static void
5794 clear_attributes (EEditorPage *editor_page)
5796 WebKitDOMDocument *document;
5797 WebKitDOMNamedNodeMap *attributes = NULL;
5798 WebKitDOMHTMLElement *body;
5799 WebKitDOMHTMLHeadElement *head;
5800 WebKitDOMElement *document_element;
5801 gint length, ii;
5803 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
5805 document = e_editor_page_get_document (editor_page);
5806 body = webkit_dom_document_get_body (document);
5807 head = webkit_dom_document_get_head (document);
5808 document_element = webkit_dom_document_get_document_element (document);
5810 /* Remove all attributes from HTML element */
5811 attributes = webkit_dom_element_get_attributes (document_element);
5812 length = webkit_dom_named_node_map_get_length (attributes);
5813 for (ii = length - 1; ii >= 0; ii--)
5814 webkit_dom_element_remove_attribute_node (
5815 document_element,
5816 WEBKIT_DOM_ATTR (webkit_dom_named_node_map_item (attributes, ii)),
5817 NULL);
5818 g_clear_object (&attributes);
5820 /* Remove everything from HEAD element */
5821 while (webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (head)))
5822 remove_node (webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (head)));
5824 /* Make the quote marks non-selectable. */
5825 e_editor_dom_disable_quote_marks_select (editor_page);
5827 /* Remove non Evolution attributes from BODY element */
5828 attributes = webkit_dom_element_get_attributes (WEBKIT_DOM_ELEMENT (body));
5829 length = webkit_dom_named_node_map_get_length (attributes);
5830 for (ii = length - 1; ii >= 0; ii--) {
5831 gchar *name;
5832 WebKitDOMAttr *attribute = WEBKIT_DOM_ATTR (webkit_dom_named_node_map_item (attributes, ii));
5834 name = webkit_dom_attr_get_name (attribute);
5836 if (!g_str_has_prefix (name, "data-") && (g_strcmp0 (name, "spellcheck") != 0))
5837 webkit_dom_element_remove_attribute_node (
5838 WEBKIT_DOM_ELEMENT (body), attribute, NULL);
5840 g_free (name);
5842 g_clear_object (&attributes);
5845 static void
5846 body_compositionstart_event_cb (WebKitDOMElement *element,
5847 WebKitDOMUIEvent *event,
5848 EEditorPage *editor_page)
5850 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
5852 e_editor_page_set_composition_in_progress (editor_page, TRUE);
5853 e_editor_dom_remove_input_event_listener_from_body (editor_page);
5856 static void
5857 body_compositionend_event_cb (WebKitDOMElement *element,
5858 WebKitDOMUIEvent *event,
5859 EEditorPage *editor_page)
5861 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
5863 e_editor_page_set_composition_in_progress (editor_page, FALSE);
5864 e_editor_dom_remove_input_event_listener_from_body (editor_page);
5867 static void
5868 body_drop_event_cb (WebKitDOMElement *element,
5869 WebKitDOMUIEvent *dom_ui_event,
5870 EEditorPage *editor_page)
5872 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
5874 if (e_editor_page_is_pasting_content_from_itself (editor_page)) {
5875 EEditorUndoRedoManager *manager;
5876 EEditorHistoryEvent *and_event, *event = NULL;
5878 /* There is a weird thing going on and I still don't know if it's
5879 * caused by WebKit or Evolution. If dragging content around the
5880 * editor sometimes the current selection is changed. The problem
5881 * is that if moving the content, then WebKit is removing the
5882 * currently selected content and at that point it could be a
5883 * different one from the dragged one. So before the drop is
5884 * performed we restore the selection to the state when the
5885 * drag was initiated. */
5886 manager = e_editor_page_get_undo_redo_manager (editor_page);
5887 and_event = e_editor_undo_redo_manager_get_current_history_event (manager);
5888 while (and_event && and_event->type == HISTORY_AND) {
5889 event = e_editor_undo_redo_manager_get_next_history_event_for (manager, and_event);
5890 and_event = e_editor_undo_redo_manager_get_next_history_event_for (manager, event);
5893 if (event)
5894 e_editor_dom_selection_restore_to_history_event_state (editor_page, event->before);
5896 e_editor_dom_save_history_for_drop (editor_page);
5900 static void
5901 body_dragstart_event_cb (WebKitDOMElement *element,
5902 WebKitDOMUIEvent *event,
5903 EEditorPage *editor_page)
5905 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
5907 e_editor_dom_remove_input_event_listener_from_body (editor_page);
5908 e_editor_page_set_pasting_content_from_itself (editor_page, TRUE);
5909 e_editor_dom_save_history_for_drag (editor_page);
5912 static void
5913 body_dragend_event_cb (WebKitDOMElement *element,
5914 WebKitDOMUIEvent *event,
5915 EEditorPage *editor_page)
5917 EEditorHistoryEvent *ev;
5918 EEditorUndoRedoManager *manager;
5920 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
5922 manager = e_editor_page_get_undo_redo_manager (editor_page);
5923 if (e_editor_page_is_pasting_content_from_itself (editor_page) &&
5924 (ev = e_editor_undo_redo_manager_get_current_history_event (manager))) {
5925 if (ev->type == HISTORY_INSERT_HTML &&
5926 ev->after.start.x == 0 && ev->after.start.y == 0 &&
5927 ev->after.end.x == 0 && ev->after.end.y == 0) {
5928 e_editor_dom_selection_get_coordinates (editor_page,
5929 &ev->after.start.x,
5930 &ev->after.start.y,
5931 &ev->after.end.x,
5932 &ev->after.end.y);
5933 ev->before.start.x = ev->after.start.x;
5934 ev->before.start.y = ev->after.start.y;
5935 ev->before.end.x = ev->after.start.x;
5936 ev->before.end.y = ev->after.start.y;
5937 e_editor_dom_force_spell_check_in_viewport (editor_page);
5938 } else {
5939 /* Drag and Drop was cancelled */
5940 while (ev && ev->type == HISTORY_AND) {
5941 e_editor_undo_redo_manager_remove_current_history_event (manager);
5942 ev = e_editor_undo_redo_manager_get_current_history_event (manager);
5943 /* Basically the same as in body_drop_event_cb(). See the comment there. */
5944 e_editor_dom_selection_restore_to_history_event_state (editor_page, ev->before);
5945 e_editor_undo_redo_manager_remove_current_history_event (manager);
5946 ev = e_editor_undo_redo_manager_get_current_history_event (manager);
5951 e_editor_page_set_pasting_content_from_itself (editor_page, FALSE);
5952 e_editor_dom_register_input_event_listener_on_body (editor_page);
5955 static void
5956 register_html_events_handlers (EEditorPage *editor_page,
5957 WebKitDOMHTMLElement *body)
5959 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
5961 webkit_dom_event_target_add_event_listener (
5962 WEBKIT_DOM_EVENT_TARGET (body),
5963 "keydown",
5964 G_CALLBACK (body_keydown_event_cb),
5965 FALSE,
5966 editor_page);
5968 webkit_dom_event_target_add_event_listener (
5969 WEBKIT_DOM_EVENT_TARGET (body),
5970 "keypress",
5971 G_CALLBACK (body_keypress_event_cb),
5972 FALSE,
5973 editor_page);
5975 webkit_dom_event_target_add_event_listener (
5976 WEBKIT_DOM_EVENT_TARGET (body),
5977 "keyup",
5978 G_CALLBACK (body_keyup_event_cb),
5979 FALSE,
5980 editor_page);
5982 webkit_dom_event_target_add_event_listener (
5983 WEBKIT_DOM_EVENT_TARGET (body),
5984 "compositionstart",
5985 G_CALLBACK (body_compositionstart_event_cb),
5986 FALSE,
5987 editor_page);
5989 webkit_dom_event_target_add_event_listener (
5990 WEBKIT_DOM_EVENT_TARGET (body),
5991 "compositionend",
5992 G_CALLBACK (body_compositionend_event_cb),
5993 FALSE,
5994 editor_page);
5996 webkit_dom_event_target_add_event_listener (
5997 WEBKIT_DOM_EVENT_TARGET (body),
5998 "drop",
5999 G_CALLBACK (body_drop_event_cb),
6000 FALSE,
6001 editor_page);
6003 webkit_dom_event_target_add_event_listener (
6004 WEBKIT_DOM_EVENT_TARGET (body),
6005 "dragstart",
6006 G_CALLBACK (body_dragstart_event_cb),
6007 FALSE,
6008 editor_page);
6010 webkit_dom_event_target_add_event_listener (
6011 WEBKIT_DOM_EVENT_TARGET (body),
6012 "dragend",
6013 G_CALLBACK (body_dragend_event_cb),
6014 FALSE,
6015 editor_page);
6018 void
6019 e_editor_dom_convert_content (EEditorPage *editor_page,
6020 const gchar *preferred_text,
6021 gint16 in_start_at_bottom,
6022 gint16 in_top_signature)
6024 WebKitDOMDocument *document;
6025 WebKitDOMElement *paragraph, *content_wrapper, *top_signature;
6026 WebKitDOMElement *cite_body_element, *signature, *wrapper;
6027 WebKitDOMHTMLElement *body;
6028 WebKitDOMNodeList *list = NULL;
6029 WebKitDOMNode *node;
6030 WebKitDOMDOMWindow *dom_window = NULL;
6031 gboolean start_bottom, empty = FALSE, cite_body = FALSE;
6032 gchar *inner_html;
6033 gint ii, jj, length;
6034 GSettings *settings;
6036 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
6038 document = e_editor_page_get_document (editor_page);
6039 if (in_start_at_bottom == 0 || in_start_at_bottom == 1) {
6040 start_bottom = in_start_at_bottom == 1;
6041 } else {
6042 settings = e_util_ref_settings ("org.gnome.evolution.mail");
6043 start_bottom = g_settings_get_boolean (settings, "composer-reply-start-bottom");
6044 g_object_unref (settings);
6047 dom_window = webkit_dom_document_get_default_view (document);
6048 body = webkit_dom_document_get_body (document);
6049 /* Wrapper that will represent the new body. */
6050 wrapper = webkit_dom_document_create_element (document, "div", NULL);
6052 cite_body_element = webkit_dom_document_query_selector (
6053 document, "span.-x-evo-cite-body", NULL);
6055 /* content_wrapper when the processed text will be placed. */
6056 content_wrapper = webkit_dom_document_create_element (
6057 document, cite_body_element ? "blockquote" : "div", NULL);
6058 if (cite_body_element) {
6059 cite_body = TRUE;
6060 webkit_dom_element_set_attribute (content_wrapper, "type", "cite", NULL);
6061 webkit_dom_element_set_attribute (content_wrapper, "id", "-x-evo-main-cite", NULL);
6062 remove_node (WEBKIT_DOM_NODE (cite_body_element));
6065 webkit_dom_node_append_child (
6066 WEBKIT_DOM_NODE (wrapper), WEBKIT_DOM_NODE (content_wrapper), NULL);
6068 /* Remove all previously inserted paragraphs. */
6069 list = webkit_dom_document_query_selector_all (
6070 document, "[data-evo-paragraph]:not([data-headers])", NULL);
6071 for (ii = webkit_dom_node_list_get_length (list); ii--;)
6072 remove_node (webkit_dom_node_list_item (list, ii));
6073 g_clear_object (&list);
6075 /* Insert the paragraph where the caret will be. */
6076 paragraph = e_editor_dom_prepare_paragraph (editor_page, TRUE);
6077 webkit_dom_element_set_id (paragraph, "-x-evo-input-start");
6078 webkit_dom_node_insert_before (
6079 WEBKIT_DOM_NODE (wrapper),
6080 WEBKIT_DOM_NODE (paragraph),
6081 start_bottom ?
6082 webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (content_wrapper)) :
6083 WEBKIT_DOM_NODE (content_wrapper),
6084 NULL);
6086 /* Insert signature (if presented) to the right position. */
6087 top_signature = webkit_dom_document_query_selector (
6088 document, ".-x-evo-top-signature", NULL);
6089 signature = webkit_dom_document_query_selector (
6090 document, ".-x-evo-signature-wrapper", NULL);
6091 if (signature) {
6092 if (top_signature) {
6093 WebKitDOMElement *spacer;
6095 webkit_dom_node_insert_before (
6096 WEBKIT_DOM_NODE (wrapper),
6097 WEBKIT_DOM_NODE (signature),
6098 start_bottom ?
6099 WEBKIT_DOM_NODE (content_wrapper) :
6100 webkit_dom_node_get_next_sibling (
6101 WEBKIT_DOM_NODE (paragraph)),
6102 NULL);
6103 /* Insert NL after the signature */
6104 spacer = e_editor_dom_prepare_paragraph (editor_page, FALSE);
6105 element_add_class (spacer, "-x-evo-top-signature-spacer");
6106 webkit_dom_node_insert_before (
6107 WEBKIT_DOM_NODE (wrapper),
6108 WEBKIT_DOM_NODE (spacer),
6109 webkit_dom_node_get_next_sibling (
6110 WEBKIT_DOM_NODE (signature)),
6111 NULL);
6112 } else {
6113 webkit_dom_node_insert_before (
6114 WEBKIT_DOM_NODE (wrapper),
6115 WEBKIT_DOM_NODE (signature),
6116 webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (
6117 start_bottom ? paragraph : content_wrapper)),
6118 NULL);
6122 /* Move credits to the body */
6123 list = webkit_dom_document_query_selector_all (
6124 document, "span.-x-evo-to-body[data-credits]", NULL);
6125 e_editor_page_set_allow_top_signature (editor_page, webkit_dom_node_list_get_length (list) > 0);
6126 for (jj = 0, ii = webkit_dom_node_list_get_length (list); ii--; jj++) {
6127 char *credits;
6128 WebKitDOMElement *element;
6129 WebKitDOMNode *node = webkit_dom_node_list_item (list, jj);
6131 element = e_editor_dom_get_paragraph_element (editor_page, -1, 0);
6132 credits = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "data-credits");
6133 if (credits)
6134 webkit_dom_html_element_set_inner_text (WEBKIT_DOM_HTML_ELEMENT (element), credits, NULL);
6135 g_free (credits);
6137 webkit_dom_node_insert_before (
6138 WEBKIT_DOM_NODE (wrapper),
6139 WEBKIT_DOM_NODE (element),
6140 WEBKIT_DOM_NODE (content_wrapper),
6141 NULL);
6143 remove_node (node);
6145 g_clear_object (&list);
6147 /* Move headers to body */
6148 list = webkit_dom_document_query_selector_all (
6149 document, "div[data-headers]", NULL);
6150 for (jj = 0, ii = webkit_dom_node_list_get_length (list); ii--; jj++) {
6151 WebKitDOMNode *node;
6153 node = webkit_dom_node_list_item (list, jj);
6154 webkit_dom_element_remove_attribute (
6155 WEBKIT_DOM_ELEMENT (node), "data-headers");
6156 e_editor_dom_set_paragraph_style (editor_page, WEBKIT_DOM_ELEMENT (node), -1, 0, NULL);
6157 webkit_dom_node_insert_before (
6158 WEBKIT_DOM_NODE (wrapper),
6159 node,
6160 WEBKIT_DOM_NODE (content_wrapper),
6161 NULL);
6163 g_clear_object (&list);
6165 repair_blockquotes (document);
6166 remove_thunderbird_signature (document);
6167 create_text_markers_for_citations_in_element (WEBKIT_DOM_ELEMENT (body));
6169 if (preferred_text && *preferred_text)
6170 webkit_dom_html_element_set_inner_text (
6171 WEBKIT_DOM_HTML_ELEMENT (content_wrapper), preferred_text, NULL);
6172 else {
6173 gchar *inner_text;
6174 WebKitDOMNode *last_child;
6176 inner_text = webkit_dom_html_element_get_inner_text (body);
6177 webkit_dom_html_element_set_inner_text (
6178 WEBKIT_DOM_HTML_ELEMENT (content_wrapper), inner_text, NULL);
6180 last_child = webkit_dom_node_get_last_child (WEBKIT_DOM_NODE (content_wrapper));
6181 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (last_child))
6182 remove_node (last_child);
6184 g_free (inner_text);
6187 inner_html = webkit_dom_element_get_inner_html (content_wrapper);
6189 /* Replace the old body with the new one. */
6190 node = webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (body), FALSE, NULL);
6191 webkit_dom_node_replace_child (
6192 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (body)),
6193 node,
6194 WEBKIT_DOM_NODE (body),
6195 NULL);
6196 body = WEBKIT_DOM_HTML_ELEMENT (node);
6198 /* Copy all to nodes to the new body. */
6199 while ((node = webkit_dom_node_get_last_child (WEBKIT_DOM_NODE (wrapper)))) {
6200 webkit_dom_node_insert_before (
6201 WEBKIT_DOM_NODE (body),
6202 WEBKIT_DOM_NODE (node),
6203 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)),
6204 NULL);
6207 if (inner_html && !*inner_html)
6208 empty = TRUE;
6210 remove_node (WEBKIT_DOM_NODE (wrapper));
6212 length = webkit_dom_element_get_child_element_count (WEBKIT_DOM_ELEMENT (body));
6213 if (length <= 1) {
6214 empty = TRUE;
6215 if (length == 1) {
6216 WebKitDOMNode *child;
6218 child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
6219 empty = child && WEBKIT_DOM_IS_HTML_BR_ELEMENT (child);
6223 if (preferred_text && *preferred_text)
6224 empty = FALSE;
6226 if (!empty)
6227 parse_html_into_blocks (editor_page, content_wrapper, NULL, inner_html);
6228 else
6229 webkit_dom_node_append_child (
6230 WEBKIT_DOM_NODE (content_wrapper),
6231 WEBKIT_DOM_NODE (e_editor_dom_prepare_paragraph (editor_page, FALSE)),
6232 NULL);
6234 if (!cite_body) {
6235 if (!empty) {
6236 WebKitDOMNode *child;
6238 while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (content_wrapper)))) {
6239 webkit_dom_node_insert_before (
6240 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (content_wrapper)),
6241 child,
6242 WEBKIT_DOM_NODE (content_wrapper),
6243 NULL);
6247 remove_node (WEBKIT_DOM_NODE (content_wrapper));
6250 /* If not editing a message, don't add any new block and just place
6251 * the caret in the beginning of content. We want to have the same
6252 * behaviour when editing message as new or we start replying on top. */
6253 if (!signature && !start_bottom) {
6254 WebKitDOMNode *child;
6256 remove_node (WEBKIT_DOM_NODE (paragraph));
6257 child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
6258 if (child)
6259 dom_add_selection_markers_into_element_start (
6260 document, WEBKIT_DOM_ELEMENT (child), NULL, NULL);
6263 if ((paragraph = webkit_dom_document_get_element_by_id (document, "-x-evo-first-br")))
6264 webkit_dom_element_remove_attribute (paragraph, "id");
6265 if ((paragraph = webkit_dom_document_get_element_by_id (document, "-x-evo-last-br")))
6266 webkit_dom_element_remove_attribute (paragraph, "id");
6268 e_editor_dom_merge_siblings_if_necessary (editor_page, NULL);
6270 if (!e_editor_page_get_html_mode (editor_page)) {
6271 e_editor_dom_wrap_paragraphs_in_document (editor_page);
6273 quote_plain_text_elements_after_wrapping_in_document (editor_page);
6276 clear_attributes (editor_page);
6278 e_editor_dom_selection_restore (editor_page);
6279 e_editor_dom_force_spell_check_in_viewport (editor_page);
6281 /* Register on input event that is called when the content (body) is modified */
6282 webkit_dom_event_target_add_event_listener (
6283 WEBKIT_DOM_EVENT_TARGET (body),
6284 "input",
6285 G_CALLBACK (body_input_event_cb),
6286 FALSE,
6287 editor_page);
6289 webkit_dom_event_target_add_event_listener (
6290 WEBKIT_DOM_EVENT_TARGET (dom_window),
6291 "scroll",
6292 G_CALLBACK (body_scroll_event_cb),
6293 FALSE,
6294 editor_page);
6296 /* Intentionally leak the WebKitDOMDOMWindow object here as otherwise the
6297 * callback won't be set up. */
6299 register_html_events_handlers (editor_page, body);
6301 g_free (inner_html);
6304 static void
6305 preserve_line_breaks_in_element (WebKitDOMDocument *document,
6306 WebKitDOMElement *element,
6307 const gchar *selector)
6309 WebKitDOMNodeList *list = NULL;
6310 gint ii;
6312 if (!(list = webkit_dom_element_query_selector_all (element, selector, NULL)))
6313 return;
6315 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
6316 gboolean insert = TRUE;
6317 WebKitDOMNode *node, *next_sibling;
6319 node = webkit_dom_node_list_item (list, ii);
6320 next_sibling = webkit_dom_node_get_next_sibling (node);
6322 if (!next_sibling)
6323 insert = FALSE;
6325 while (insert && next_sibling) {
6326 if (!webkit_dom_node_has_child_nodes (next_sibling) &&
6327 !webkit_dom_node_get_next_sibling (next_sibling))
6328 insert = FALSE;
6329 next_sibling = webkit_dom_node_get_next_sibling (next_sibling);
6332 if (insert && !WEBKIT_DOM_IS_HTML_BR_ELEMENT (webkit_dom_node_get_last_child (node)))
6333 webkit_dom_node_append_child (
6334 node,
6335 WEBKIT_DOM_NODE (webkit_dom_document_create_element (document, "br", NULL)),
6336 NULL);
6338 g_clear_object (&list);
6341 static void
6342 preserve_pre_line_breaks_in_element (WebKitDOMDocument *document,
6343 WebKitDOMElement *element)
6345 WebKitDOMHTMLCollection *collection = NULL;
6346 gint ii;
6348 if (!(collection = webkit_dom_element_get_elements_by_tag_name_as_html_collection (element, "pre")))
6349 return;
6351 for (ii = webkit_dom_html_collection_get_length (collection); ii--;) {
6352 WebKitDOMNode *node;
6353 gchar *inner_html;
6354 GString *string;
6356 node = webkit_dom_html_collection_item (collection, ii);
6357 inner_html = webkit_dom_element_get_inner_html (WEBKIT_DOM_ELEMENT (node));
6358 string = e_str_replace_string (inner_html, "\n", "<br>");
6359 webkit_dom_element_set_inner_html (WEBKIT_DOM_ELEMENT (node), string->str, NULL);
6360 g_string_free (string, TRUE);
6361 g_free (inner_html);
6363 g_clear_object (&collection);
6366 void
6367 e_editor_dom_convert_and_insert_html_into_selection (EEditorPage *editor_page,
6368 const gchar *html,
6369 gboolean is_html)
6371 WebKitDOMDocument *document;
6372 WebKitDOMElement *selection_start_marker, *selection_end_marker, *element;
6373 WebKitDOMNode *node, *current_block;
6374 EEditorHistoryEvent *ev = NULL;
6375 EEditorUndoRedoManager *manager;
6376 gboolean has_selection;
6377 gchar *inner_html;
6378 gint citation_level;
6380 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
6382 document = e_editor_page_get_document (editor_page);
6384 e_editor_dom_remove_input_event_listener_from_body (editor_page);
6386 e_editor_dom_selection_save (editor_page);
6387 selection_start_marker = webkit_dom_document_get_element_by_id (
6388 document, "-x-evo-selection-start-marker");
6389 selection_end_marker = webkit_dom_document_get_element_by_id (
6390 document, "-x-evo-selection-end-marker");
6391 current_block = e_editor_dom_get_parent_block_node_from_child (
6392 WEBKIT_DOM_NODE (selection_start_marker));
6393 if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (current_block))
6394 current_block = NULL;
6396 manager = e_editor_page_get_undo_redo_manager (editor_page);
6397 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
6398 gboolean collapsed;
6400 ev = g_new0 (EEditorHistoryEvent, 1);
6401 ev->type = HISTORY_PASTE;
6402 /* FIXME WK2
6403 ev->type = HISTORY_PASTE_AS_TEXT;*/
6405 collapsed = e_editor_dom_selection_is_collapsed (editor_page);
6406 e_editor_dom_selection_get_coordinates (editor_page,
6407 &ev->before.start.x,
6408 &ev->before.start.y,
6409 &ev->before.end.x,
6410 &ev->before.end.y);
6412 if (!collapsed) {
6413 ev->before.end.x = ev->before.start.x;
6414 ev->before.end.y = ev->before.start.y;
6417 ev->data.string.from = NULL;
6418 ev->data.string.to = g_strdup (html);
6421 element = webkit_dom_document_create_element (document, "div", NULL);
6422 if (is_html) {
6423 gchar *inner_text;
6425 webkit_dom_element_set_inner_html (element, html, NULL);
6427 webkit_dom_element_set_attribute (
6428 WEBKIT_DOM_ELEMENT (element),
6429 "data-evo-html-to-plain-text-wrapper",
6431 NULL);
6433 /* Add the missing BR elements on the end of DIV and P elements to
6434 * preserve the line breaks. But we need to do that just in case that
6435 * there is another element that contains text. */
6436 preserve_line_breaks_in_element (document, WEBKIT_DOM_ELEMENT (element), "p, div, address");
6437 preserve_line_breaks_in_element (
6438 document,
6439 WEBKIT_DOM_ELEMENT (element),
6440 "[data-evo-html-to-plain-text-wrapper] > :matches(h1, h2, h3, h4, h5, h6)");
6441 preserve_pre_line_breaks_in_element (document, WEBKIT_DOM_ELEMENT (element));
6443 inner_text = webkit_dom_html_element_get_inner_text (
6444 WEBKIT_DOM_HTML_ELEMENT (element));
6445 webkit_dom_html_element_set_inner_text (
6446 WEBKIT_DOM_HTML_ELEMENT (element), inner_text, NULL);
6448 g_free (inner_text);
6449 } else
6450 webkit_dom_html_element_set_inner_text (
6451 WEBKIT_DOM_HTML_ELEMENT (element), html, NULL);
6453 inner_html = webkit_dom_element_get_inner_html (element);
6454 parse_html_into_blocks (editor_page, element, WEBKIT_DOM_ELEMENT (current_block), inner_html);
6456 g_free (inner_html);
6458 has_selection = !e_editor_dom_selection_is_collapsed (editor_page);
6459 if (has_selection && !e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
6460 WebKitDOMRange *range = NULL;
6462 range = e_editor_dom_get_current_range (editor_page);
6463 insert_delete_event (editor_page, range);
6464 g_clear_object (&range);
6466 /* Remove the text that was meant to be replaced by the pasted text */
6467 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_DELETE, NULL);
6469 e_editor_dom_selection_save (editor_page);
6471 selection_start_marker = webkit_dom_document_get_element_by_id (
6472 document, "-x-evo-selection-start-marker");
6473 selection_end_marker = webkit_dom_document_get_element_by_id (
6474 document, "-x-evo-selection-end-marker");
6475 current_block = e_editor_dom_get_parent_block_node_from_child (
6476 WEBKIT_DOM_NODE (selection_start_marker));
6477 if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (current_block))
6478 current_block = NULL;
6481 citation_level = e_editor_dom_get_citation_level (WEBKIT_DOM_NODE (selection_end_marker));
6482 /* Pasting into the citation */
6483 if (citation_level > 0) {
6484 gint length;
6485 gint word_wrap_length;
6486 WebKitDOMElement *br;
6487 WebKitDOMNode *first_paragraph, *last_paragraph;
6488 WebKitDOMNode *child, *parent, *current_block;
6490 first_paragraph = webkit_dom_node_get_first_child (
6491 WEBKIT_DOM_NODE (element));
6492 last_paragraph = webkit_dom_node_get_last_child (
6493 WEBKIT_DOM_NODE (element));
6495 word_wrap_length = e_editor_page_get_word_wrap_length (editor_page);
6496 length = word_wrap_length - 2 * citation_level;
6498 /* Pasting text that was parsed just into one paragraph */
6499 if (webkit_dom_node_is_same_node (first_paragraph, last_paragraph)) {
6500 WebKitDOMNode *child, *parent, *parent_block;
6502 parent_block = e_editor_dom_get_parent_block_node_from_child (
6503 WEBKIT_DOM_NODE (selection_start_marker));
6505 e_editor_dom_remove_quoting_from_element (WEBKIT_DOM_ELEMENT (parent_block));
6506 e_editor_dom_remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (parent_block));
6508 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start_marker));
6509 while ((child = webkit_dom_node_get_first_child (first_paragraph))) {
6510 if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent) &&
6511 WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (child)) {
6512 WebKitDOMNode *anchor_child;
6514 while ((anchor_child = webkit_dom_node_get_first_child (child)))
6515 webkit_dom_node_insert_before (
6516 webkit_dom_node_get_parent_node (
6517 WEBKIT_DOM_NODE (selection_start_marker)),
6518 anchor_child,
6519 WEBKIT_DOM_NODE (selection_start_marker),
6520 NULL);
6521 remove_node (child);
6522 } else
6523 webkit_dom_node_insert_before (
6524 webkit_dom_node_get_parent_node (
6525 WEBKIT_DOM_NODE (selection_start_marker)),
6526 child,
6527 WEBKIT_DOM_NODE (selection_start_marker),
6528 NULL);
6531 if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent)) {
6532 gchar *text_content;
6534 text_content = webkit_dom_node_get_text_content (parent);
6536 webkit_dom_element_set_attribute (
6537 WEBKIT_DOM_ELEMENT (parent),
6538 "href",
6539 text_content,
6540 NULL);
6541 g_free (text_content);
6544 parent_block = WEBKIT_DOM_NODE (
6545 e_editor_dom_wrap_paragraph_length (editor_page, WEBKIT_DOM_ELEMENT (parent_block), length));
6546 webkit_dom_node_normalize (parent_block);
6547 e_editor_dom_quote_plain_text_element_after_wrapping (editor_page, WEBKIT_DOM_ELEMENT (parent_block), citation_level);
6549 e_editor_dom_selection_restore (editor_page);
6551 goto out;
6554 /* Pasting content parsed into the multiple paragraphs */
6555 parent = e_editor_dom_get_parent_block_node_from_child (
6556 WEBKIT_DOM_NODE (selection_start_marker));
6558 e_editor_dom_remove_quoting_from_element (WEBKIT_DOM_ELEMENT (parent));
6559 e_editor_dom_remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (parent));
6561 /* Move the elements from the first paragraph before the selection start element */
6562 while ((child = webkit_dom_node_get_first_child (first_paragraph)))
6563 webkit_dom_node_insert_before (
6564 parent,
6565 child,
6566 WEBKIT_DOM_NODE (selection_start_marker),
6567 NULL);
6569 remove_node (first_paragraph);
6571 /* If the BR element is on the last position, remove it as we don't need it */
6572 child = webkit_dom_node_get_last_child (parent);
6573 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (child))
6574 remove_node (child);
6576 parent = e_editor_dom_get_parent_block_node_from_child (
6577 WEBKIT_DOM_NODE (selection_end_marker));
6579 child = webkit_dom_node_get_next_sibling (
6580 WEBKIT_DOM_NODE (selection_end_marker));
6581 /* Move the elements that are in the same paragraph as the selection end
6582 * on the end of pasted text, but avoid BR on the end of paragraph */
6583 while (child) {
6584 WebKitDOMNode *next_child =
6585 webkit_dom_node_get_next_sibling (child);
6586 if (!(!next_child && WEBKIT_DOM_IS_HTML_BR_ELEMENT (child)))
6587 webkit_dom_node_append_child (last_paragraph, child, NULL);
6588 child = next_child;
6591 current_block = e_editor_dom_get_parent_block_node_from_child (
6592 WEBKIT_DOM_NODE (selection_start_marker));
6594 dom_remove_selection_markers (document);
6596 /* Caret will be restored on the end of pasted text */
6597 webkit_dom_node_append_child (
6598 last_paragraph,
6599 WEBKIT_DOM_NODE (dom_create_selection_marker (document, TRUE)),
6600 NULL);
6602 webkit_dom_node_append_child (
6603 last_paragraph,
6604 WEBKIT_DOM_NODE (dom_create_selection_marker (document, FALSE)),
6605 NULL);
6607 /* Insert the paragraph with the end of the pasted text after
6608 * the paragraph that contains the selection end */
6609 webkit_dom_node_insert_before (
6610 webkit_dom_node_get_parent_node (parent),
6611 last_paragraph,
6612 webkit_dom_node_get_next_sibling (parent),
6613 NULL);
6615 /* Wrap, quote and move all paragraphs from pasted text into the body */
6616 while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)))) {
6617 child = WEBKIT_DOM_NODE (e_editor_dom_wrap_paragraph_length (
6618 editor_page, WEBKIT_DOM_ELEMENT (child), length));
6619 e_editor_dom_quote_plain_text_element_after_wrapping (editor_page, WEBKIT_DOM_ELEMENT (child), citation_level);
6620 webkit_dom_node_insert_before (
6621 webkit_dom_node_get_parent_node (last_paragraph),
6622 child,
6623 last_paragraph,
6624 NULL);
6627 webkit_dom_node_normalize (last_paragraph);
6629 last_paragraph = WEBKIT_DOM_NODE (
6630 e_editor_dom_wrap_paragraph_length (
6631 editor_page, WEBKIT_DOM_ELEMENT (last_paragraph), length));
6632 e_editor_dom_quote_plain_text_element_after_wrapping (editor_page, WEBKIT_DOM_ELEMENT (last_paragraph), citation_level);
6634 e_editor_dom_remove_quoting_from_element (WEBKIT_DOM_ELEMENT (parent));
6635 e_editor_dom_remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (parent));
6637 current_block = WEBKIT_DOM_NODE (e_editor_dom_wrap_paragraph_length (
6638 editor_page, WEBKIT_DOM_ELEMENT (current_block), length));
6639 e_editor_dom_quote_plain_text_element_after_wrapping (editor_page, WEBKIT_DOM_ELEMENT (current_block), citation_level);
6641 if ((br = webkit_dom_document_get_element_by_id (document, "-x-evo-first-br")))
6642 webkit_dom_element_remove_attribute (br, "class");
6644 if ((br = webkit_dom_document_get_element_by_id (document, "-x-evo-last-br")))
6645 webkit_dom_element_remove_attribute (br, "class");
6647 if (ev) {
6648 e_editor_dom_selection_get_coordinates (editor_page,
6649 &ev->after.start.x,
6650 &ev->after.start.y,
6651 &ev->after.end.x,
6652 &ev->after.end.y);
6653 e_editor_undo_redo_manager_insert_history_event (manager, ev);
6656 e_editor_dom_selection_restore (editor_page);
6658 goto out;
6661 remove_node (WEBKIT_DOM_NODE (selection_start_marker));
6662 remove_node (WEBKIT_DOM_NODE (selection_end_marker));
6664 /* If the text to insert was converted just to one block, pass just its
6665 * text to WebKit otherwise WebKit will insert unwanted block with
6666 * extra new line. */
6667 if (!webkit_dom_node_get_next_sibling (webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element))))
6668 inner_html = webkit_dom_element_get_inner_html (
6669 WEBKIT_DOM_ELEMENT (webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element))));
6670 else
6671 inner_html = webkit_dom_element_get_inner_html (WEBKIT_DOM_ELEMENT (element));
6673 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_INSERT_HTML, inner_html);
6675 if (g_str_has_suffix (inner_html, " "))
6676 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_INSERT_TEXT, " ");
6678 g_free (inner_html);
6680 e_editor_dom_selection_save (editor_page);
6682 element = webkit_dom_document_query_selector (
6683 document, "* > br#-x-evo-first-br", NULL);
6684 if (element) {
6685 WebKitDOMNode *sibling;
6686 WebKitDOMNode *parent;
6688 parent = webkit_dom_node_get_parent_node (
6689 WEBKIT_DOM_NODE (element));
6691 sibling = webkit_dom_node_get_previous_sibling (parent);
6692 if (sibling)
6693 remove_node (WEBKIT_DOM_NODE (parent));
6694 else
6695 webkit_dom_element_remove_attribute (element, "id");
6698 element = webkit_dom_document_query_selector (
6699 document, "* > br#-x-evo-last-br", NULL);
6700 if (element) {
6701 WebKitDOMNode *parent;
6702 WebKitDOMNode *child;
6704 parent = webkit_dom_node_get_parent_node (
6705 WEBKIT_DOM_NODE (element));
6707 node = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (parent));
6708 if (node) {
6709 node = webkit_dom_node_get_first_child (node);
6710 if (node) {
6711 inner_html = webkit_dom_node_get_text_content (node);
6712 if (g_str_has_prefix (inner_html, UNICODE_NBSP))
6713 webkit_dom_character_data_replace_data (
6714 WEBKIT_DOM_CHARACTER_DATA (node), 0, 1, "", NULL);
6715 g_free (inner_html);
6719 selection_end_marker = webkit_dom_document_get_element_by_id (
6720 document, "-x-evo-selection-end-marker");
6722 if (has_selection) {
6723 /* Everything after the selection end marker have to be in separate
6724 * paragraph */
6725 child = webkit_dom_node_get_next_sibling (
6726 WEBKIT_DOM_NODE (selection_end_marker));
6727 /* Move the elements that are in the same paragraph as the selection end
6728 * on the end of pasted text, but avoid BR on the end of paragraph */
6729 while (child) {
6730 WebKitDOMNode *next_child =
6731 webkit_dom_node_get_next_sibling (child);
6732 if (!(!next_child && WEBKIT_DOM_IS_HTML_BR_ELEMENT (child)))
6733 webkit_dom_node_append_child (parent, child, NULL);
6734 child = next_child;
6737 remove_node (WEBKIT_DOM_NODE (element));
6739 webkit_dom_node_insert_before (
6740 webkit_dom_node_get_parent_node (
6741 webkit_dom_node_get_parent_node (
6742 WEBKIT_DOM_NODE (selection_end_marker))),
6743 parent,
6744 webkit_dom_node_get_next_sibling (
6745 webkit_dom_node_get_parent_node (
6746 WEBKIT_DOM_NODE (selection_end_marker))),
6747 NULL);
6748 node = parent;
6749 } else {
6750 node = webkit_dom_node_get_next_sibling (parent);
6751 if (!node) {
6752 fix_structure_after_pasting_multiline_content (parent);
6753 if (!webkit_dom_node_get_first_child (parent))
6754 remove_node (parent);
6758 if (node) {
6759 /* Restore caret on the end of pasted text */
6760 webkit_dom_node_insert_before (
6761 node,
6762 WEBKIT_DOM_NODE (selection_end_marker),
6763 webkit_dom_node_get_first_child (node),
6764 NULL);
6766 selection_start_marker = webkit_dom_document_get_element_by_id (
6767 document, "-x-evo-selection-start-marker");
6768 webkit_dom_node_insert_before (
6769 node,
6770 WEBKIT_DOM_NODE (selection_start_marker),
6771 webkit_dom_node_get_first_child (node),
6772 NULL);
6775 if (element)
6776 webkit_dom_element_remove_attribute (element, "id");
6778 if (webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (parent)) && !has_selection)
6779 remove_node (parent);
6780 } else {
6781 /* When pasting the content that was copied from the composer, WebKit
6782 * restores the selection wrongly, thus is saved wrongly and we have
6783 * to fix it */
6784 WebKitDOMNode *block, *parent, *clone1, *clone2;
6786 selection_start_marker = webkit_dom_document_get_element_by_id (
6787 document, "-x-evo-selection-start-marker");
6788 selection_end_marker = webkit_dom_document_get_element_by_id (
6789 document, "-x-evo-selection-end-marker");
6791 block = e_editor_dom_get_parent_block_node_from_child (
6792 WEBKIT_DOM_NODE (selection_start_marker));
6793 parent = webkit_dom_node_get_parent_node (block);
6794 webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (parent), "id");
6796 /* Check if WebKit created wrong structure */
6797 clone1 = webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (block), FALSE, NULL);
6798 clone2 = webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (parent), FALSE, NULL);
6799 if (webkit_dom_node_is_equal_node (clone1, clone2) ||
6800 (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (clone1) && WEBKIT_DOM_IS_HTML_DIV_ELEMENT (clone2) &&
6801 !element_has_class (WEBKIT_DOM_ELEMENT (clone2), "-x-evo-indented"))) {
6802 fix_structure_after_pasting_multiline_content (block);
6803 if (g_strcmp0 (html, "\n") == 0) {
6804 WebKitDOMElement *br;
6806 br = webkit_dom_document_create_element (document, "br", NULL);
6807 webkit_dom_node_append_child (
6808 parent, WEBKIT_DOM_NODE (br), NULL);
6810 webkit_dom_node_insert_before (
6811 parent,
6812 WEBKIT_DOM_NODE (selection_start_marker),
6813 webkit_dom_node_get_last_child (parent),
6814 NULL);
6815 } else if (!webkit_dom_node_get_first_child (parent))
6816 remove_node (parent);
6819 webkit_dom_node_insert_before (
6820 webkit_dom_node_get_parent_node (
6821 WEBKIT_DOM_NODE (selection_start_marker)),
6822 WEBKIT_DOM_NODE (selection_end_marker),
6823 webkit_dom_node_get_next_sibling (
6824 WEBKIT_DOM_NODE (selection_start_marker)),
6825 NULL);
6828 if (ev) {
6829 e_editor_dom_selection_get_coordinates (editor_page,
6830 &ev->after.start.x,
6831 &ev->after.start.y,
6832 &ev->after.end.x,
6833 &ev->after.end.y);
6834 e_editor_undo_redo_manager_insert_history_event (manager, ev);
6837 e_editor_dom_selection_restore (editor_page);
6838 out:
6839 e_editor_dom_force_spell_check_in_viewport (editor_page);
6840 e_editor_dom_scroll_to_caret (editor_page);
6842 e_editor_dom_register_input_event_listener_on_body (editor_page);
6844 e_editor_page_emit_content_changed (editor_page);
6847 static gint
6848 get_indentation_level (WebKitDOMElement *element)
6850 WebKitDOMElement *parent;
6851 gint level = 0;
6853 if (!element)
6854 return 0;
6856 if (element_has_class (element, "-x-evo-indented"))
6857 level++;
6859 parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (element));
6860 /* Count level of indentation */
6861 while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
6862 if (element_has_class (parent, "-x-evo-indented"))
6863 level++;
6865 parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (parent));
6868 return level;
6871 static void
6872 process_indented_element (WebKitDOMElement *element)
6874 gchar *spaces;
6875 WebKitDOMNode *child;
6876 gboolean needs_indent = TRUE;
6878 if (!element)
6879 return;
6881 spaces = g_strnfill (SPACES_PER_INDENTATION * get_indentation_level (element), ' ');
6883 child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element));
6884 while (child) {
6885 /* If next sibling is indented blockqoute skip it,
6886 * it will be processed afterwards */
6887 if (WEBKIT_DOM_IS_ELEMENT (child) &&
6888 element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-indented")) {
6889 child = webkit_dom_node_get_next_sibling (child);
6890 if (!child)
6891 break;
6894 if (WEBKIT_DOM_IS_TEXT (child)) {
6895 gchar *text_content;
6896 gchar *indented_text = NULL;
6898 text_content = webkit_dom_character_data_get_data (WEBKIT_DOM_CHARACTER_DATA (child));
6899 if (needs_indent) {
6900 indented_text = g_strconcat (spaces, text_content, NULL);
6901 needs_indent = FALSE;
6904 webkit_dom_character_data_set_data (
6905 WEBKIT_DOM_CHARACTER_DATA (child),
6906 indented_text ? indented_text : text_content,
6907 NULL);
6909 g_free (text_content);
6910 g_free (indented_text);
6911 } else if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (child) ||
6912 WEBKIT_DOM_IS_HTML_DIV_ELEMENT (child) ||
6913 WEBKIT_DOM_IS_HTML_PRE_ELEMENT (child)) {
6914 needs_indent = TRUE;
6917 /* Move to next node */
6918 if (webkit_dom_node_has_child_nodes (child))
6919 child = webkit_dom_node_get_first_child (child);
6920 else if (webkit_dom_node_get_next_sibling (child))
6921 child = webkit_dom_node_get_next_sibling (child);
6922 else {
6923 if (webkit_dom_node_is_equal_node (WEBKIT_DOM_NODE (element), child))
6924 break;
6926 child = webkit_dom_node_get_parent_node (child);
6927 if (child)
6928 child = webkit_dom_node_get_next_sibling (child);
6931 g_free (spaces);
6933 webkit_dom_element_remove_attribute (element, "style");
6936 static void
6937 process_quote_nodes (WebKitDOMElement *blockquote)
6939 WebKitDOMHTMLCollection *collection = NULL;
6940 int ii;
6942 /* Replace quote nodes with symbols */
6943 collection = webkit_dom_element_get_elements_by_class_name_as_html_collection (
6944 blockquote, "-x-evo-quoted");
6945 for (ii = webkit_dom_html_collection_get_length (collection); ii--;) {
6946 WebKitDOMNode *quoted_node;
6947 gchar *text_content;
6949 quoted_node = webkit_dom_html_collection_item (collection, ii);
6950 text_content = webkit_dom_node_get_text_content (quoted_node);
6951 webkit_dom_element_set_outer_html (
6952 WEBKIT_DOM_ELEMENT (quoted_node), text_content, NULL);
6953 g_free (text_content);
6955 g_clear_object (&collection);
6958 /* Taken from GtkHTML */
6959 static gchar *
6960 get_alpha_value (gint value,
6961 gboolean lower)
6963 GString *str;
6964 gint add = lower ? 'a' : 'A';
6966 str = g_string_new (". ");
6968 do {
6969 g_string_prepend_c (str, ((value - 1) % 26) + add);
6970 value = (value - 1) / 26;
6971 } while (value);
6973 return g_string_free (str, FALSE);
6976 /* Taken from GtkHTML */
6977 static gchar *
6978 get_roman_value (gint value,
6979 gboolean lower)
6981 GString *str;
6982 const gchar *base = "IVXLCDM";
6983 gint b, r, add = lower ? 'a' - 'A' : 0;
6985 if (value > 3999)
6986 return g_strdup ("?. ");
6988 str = g_string_new (". ");
6990 for (b = 0; value > 0 && b < 7 - 1; b += 2, value /= 10) {
6991 r = value % 10;
6992 if (r != 0) {
6993 if (r < 4) {
6994 for (; r; r--)
6995 g_string_prepend_c (str, base[b] + add);
6996 } else if (r == 4) {
6997 g_string_prepend_c (str, base[b + 1] + add);
6998 g_string_prepend_c (str, base[b] + add);
6999 } else if (r == 5) {
7000 g_string_prepend_c (str, base[b + 1] + add);
7001 } else if (r < 9) {
7002 for (; r > 5; r--)
7003 g_string_prepend_c (str, base[b] + add);
7004 g_string_prepend_c (str, base[b + 1] + add);
7005 } else if (r == 9) {
7006 g_string_prepend_c (str, base[b + 2] + add);
7007 g_string_prepend_c (str, base[b] + add);
7012 return g_string_free (str, FALSE);
7015 static void
7016 process_list_to_plain_text (EEditorPage *editor_page,
7017 WebKitDOMElement *element,
7018 gint level,
7019 GString *output)
7021 EContentEditorBlockFormat format;
7022 EContentEditorAlignment alignment;
7023 gint counter = 1;
7024 gboolean empty = TRUE;
7025 gchar *indent_per_level;
7026 WebKitDOMNode *item;
7027 gint word_wrap_length;
7029 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
7031 indent_per_level = g_strnfill (SPACES_PER_LIST_LEVEL, ' ');
7032 word_wrap_length = e_editor_page_get_word_wrap_length (editor_page);
7033 format = dom_get_list_format_from_node (
7034 WEBKIT_DOM_NODE (element));
7036 /* Process list items to plain text */
7037 item = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element));
7038 while (item) {
7039 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (item))
7040 g_string_append (output, "\n");
7042 if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (item)) {
7043 gchar *space = NULL, *item_str = NULL;
7044 gint ii = 0;
7045 WebKitDOMElement *wrapped;
7046 GString *item_value = g_string_new ("");
7048 empty = FALSE;
7050 alignment = e_editor_dom_get_list_alignment_from_node (
7051 WEBKIT_DOM_NODE (item));
7053 wrapped = webkit_dom_element_query_selector (
7054 WEBKIT_DOM_ELEMENT (item), ".-x-evo-wrap-br", NULL);
7055 /* Wrapped text */
7056 if (wrapped) {
7057 WebKitDOMNode *node = webkit_dom_node_get_first_child (item);
7058 GString *line = g_string_new ("");
7060 while (node) {
7061 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (node) &&
7062 element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-wrap-br")) {
7063 g_string_append (line, "\n");
7064 /* put spaces before line characters -> wordwraplength - indentation */
7065 for (ii = 0; ii < level; ii++)
7066 g_string_append (line, indent_per_level);
7067 if (WEBKIT_DOM_IS_HTML_O_LIST_ELEMENT (element))
7068 g_string_append (line, indent_per_level);
7069 g_string_append (item_value, line->str);
7070 g_string_erase (line, 0, -1);
7071 } else {
7072 /* append text from node to line */
7073 gchar *text_content;
7074 text_content = webkit_dom_node_get_text_content (node);
7075 g_string_append (line, text_content);
7076 g_free (text_content);
7078 node = webkit_dom_node_get_next_sibling (node);
7081 if (alignment == E_CONTENT_EDITOR_ALIGNMENT_LEFT)
7082 g_string_append (item_value, line->str);
7084 if (alignment == E_CONTENT_EDITOR_ALIGNMENT_CENTER) {
7085 gchar *fill = NULL;
7086 gint fill_length;
7088 fill_length = word_wrap_length - g_utf8_strlen (line->str, -1);
7089 fill_length -= ii * SPACES_PER_LIST_LEVEL;
7090 if (WEBKIT_DOM_IS_HTML_O_LIST_ELEMENT (element))
7091 fill_length += SPACES_PER_LIST_LEVEL;
7092 fill_length /= 2;
7094 if (fill_length < 0)
7095 fill_length = 0;
7097 fill = g_strnfill (fill_length, ' ');
7099 g_string_append (item_value, fill);
7100 g_string_append (item_value, line->str);
7101 g_free (fill);
7104 if (alignment == E_CONTENT_EDITOR_ALIGNMENT_RIGHT) {
7105 gchar *fill = NULL;
7106 gint fill_length;
7108 fill_length = word_wrap_length - g_utf8_strlen (line->str, -1);
7109 fill_length -= ii * SPACES_PER_LIST_LEVEL;
7111 if (fill_length < 0)
7112 fill_length = 0;
7114 fill = g_strnfill (fill_length, ' ');
7116 g_string_append (item_value, fill);
7117 g_string_append (item_value, line->str);
7118 g_free (fill);
7120 g_string_free (line, TRUE);
7121 /* that same here */
7122 } else {
7123 gchar *text_content =
7124 webkit_dom_node_get_text_content (item);
7126 g_string_append (item_value, text_content);
7127 g_free (text_content);
7130 if (format == E_CONTENT_EDITOR_BLOCK_FORMAT_UNORDERED_LIST) {
7131 space = g_strnfill (SPACES_PER_LIST_LEVEL - 2, ' ');
7132 item_str = g_strdup_printf (
7133 "%s* %s", space, item_value->str);
7134 g_free (space);
7137 if (format == E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST) {
7138 gint length = 1, tmp = counter, spaces_count;
7140 while ((tmp = tmp / 10) > 1)
7141 length++;
7143 if (tmp == 1)
7144 length++;
7146 spaces_count = SPACES_ORDERED_LIST_FIRST_LEVEL - 2 - length;
7147 if (spaces_count > 0)
7148 space = g_strnfill (spaces_count, ' ');
7150 item_str = g_strdup_printf (
7151 "%s%d. %s", space && *space ? space : "", counter, item_value->str);
7152 g_free (space);
7155 if (format > E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST) {
7156 gchar *value, spaces_count;
7158 if (format == E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST_ALPHA)
7159 value = get_alpha_value (counter, FALSE);
7160 else
7161 value = get_roman_value (counter, FALSE);
7163 spaces_count = SPACES_ORDERED_LIST_FIRST_LEVEL - strlen (value);
7164 if (spaces_count > 0)
7165 space = g_strnfill (spaces_count, ' ');
7166 item_str = g_strdup_printf (
7167 "%s%s%s", space && *space ? space : "" , value, item_value->str);
7168 g_free (space);
7169 g_free (value);
7172 if (alignment == E_CONTENT_EDITOR_ALIGNMENT_LEFT) {
7173 for (ii = 0; ii < level - 1; ii++) {
7174 g_string_append (output, indent_per_level);
7176 if (WEBKIT_DOM_IS_HTML_U_LIST_ELEMENT (element))
7177 if (dom_node_find_parent_element (item, "OL"))
7178 g_string_append (output, indent_per_level);
7179 g_string_append (output, item_str);
7182 if (alignment == E_CONTENT_EDITOR_ALIGNMENT_RIGHT) {
7183 if (!wrapped) {
7184 gchar *fill = NULL;
7185 gint fill_length;
7187 fill_length = word_wrap_length - g_utf8_strlen (item_str, -1);
7188 fill_length -= ii * SPACES_PER_LIST_LEVEL;
7190 if (fill_length < 0)
7191 fill_length = 0;
7193 if (g_str_has_suffix (item_str, " "))
7194 fill_length++;
7196 fill = g_strnfill (fill_length, ' ');
7198 g_string_append (output, fill);
7199 g_free (fill);
7201 if (g_str_has_suffix (item_str, " "))
7202 g_string_append_len (output, item_str, g_utf8_strlen (item_str, -1) - 1);
7203 else
7204 g_string_append (output, item_str);
7207 if (alignment == E_CONTENT_EDITOR_ALIGNMENT_CENTER) {
7208 if (!wrapped) {
7209 gchar *fill = NULL;
7210 gint fill_length = 0;
7212 for (ii = 0; ii < level - 1; ii++)
7213 g_string_append (output, indent_per_level);
7215 fill_length = word_wrap_length - g_utf8_strlen (item_str, -1);
7216 fill_length -= ii * SPACES_PER_LIST_LEVEL;
7217 if (WEBKIT_DOM_IS_HTML_O_LIST_ELEMENT (element))
7218 fill_length += SPACES_PER_LIST_LEVEL;
7219 fill_length /= 2;
7221 if (fill_length < 0)
7222 fill_length = 0;
7224 if (g_str_has_suffix (item_str, " "))
7225 fill_length++;
7227 fill = g_strnfill (fill_length, ' ');
7229 g_string_append (output, fill);
7230 g_free (fill);
7232 if (g_str_has_suffix (item_str, " "))
7233 g_string_append_len (output, item_str, g_utf8_strlen (item_str, -1) - 1);
7234 else
7235 g_string_append (output, item_str);
7238 counter++;
7239 item = webkit_dom_node_get_next_sibling (item);
7240 if (item)
7241 g_string_append (output, "\n");
7243 g_free (item_str);
7244 g_string_free (item_value, TRUE);
7245 } else if (node_is_list (item)) {
7246 process_list_to_plain_text (
7247 editor_page, WEBKIT_DOM_ELEMENT (item), level + 1, output);
7248 item = webkit_dom_node_get_next_sibling (item);
7249 } else {
7250 item = webkit_dom_node_get_next_sibling (item);
7254 if (webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element)) && !empty)
7255 g_string_append (output, "\n");
7257 g_free (indent_per_level);
7260 static void
7261 remove_base_attributes (WebKitDOMElement *element)
7263 webkit_dom_element_remove_attribute (element, "class");
7264 webkit_dom_element_remove_attribute (element, "id");
7265 webkit_dom_element_remove_attribute (element, "name");
7268 static void
7269 remove_evolution_attributes (WebKitDOMElement *element)
7271 webkit_dom_element_remove_attribute (element, "data-evo-paragraph");
7272 webkit_dom_element_remove_attribute (element, "data-converted");
7273 webkit_dom_element_remove_attribute (element, "data-edit-as-new");
7274 webkit_dom_element_remove_attribute (element, "data-evo-draft");
7275 webkit_dom_element_remove_attribute (element, "data-inline");
7276 webkit_dom_element_remove_attribute (element, "data-uri");
7277 webkit_dom_element_remove_attribute (element, "data-message");
7278 webkit_dom_element_remove_attribute (element, "data-name");
7279 webkit_dom_element_remove_attribute (element, "data-new-message");
7280 webkit_dom_element_remove_attribute (element, "data-user-wrapped");
7281 webkit_dom_element_remove_attribute (element, "data-evo-plain-text");
7282 webkit_dom_element_remove_attribute (element, "data-plain-text-style");
7283 webkit_dom_element_remove_attribute (element, "data-style");
7284 webkit_dom_element_remove_attribute (element, "spellcheck");
7287 static void
7288 convert_element_from_html_to_plain_text (EEditorPage *editor_page,
7289 WebKitDOMElement *element,
7290 gboolean *wrap,
7291 gboolean *quote)
7293 WebKitDOMDocument *document;
7294 WebKitDOMElement *top_signature, *signature, *blockquote, *main_blockquote, *br_element;
7295 WebKitDOMNode *signature_clone, *from;
7296 gint blockquotes_count;
7297 gchar *inner_text, *inner_html;
7299 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
7301 document = e_editor_page_get_document (editor_page);
7302 top_signature = webkit_dom_element_query_selector (
7303 element, ".-x-evo-top-signature", NULL);
7304 signature = webkit_dom_element_query_selector (
7305 element, "span.-x-evo-signature", NULL);
7306 main_blockquote = webkit_dom_element_query_selector (
7307 element, "#-x-evo-main-cite", NULL);
7309 blockquote = webkit_dom_document_create_element (
7310 document, "blockquote", NULL);
7312 if (main_blockquote) {
7313 webkit_dom_element_set_attribute (
7314 blockquote, "type", "cite", NULL);
7315 from = WEBKIT_DOM_NODE (main_blockquote);
7316 } else {
7317 if (signature) {
7318 WebKitDOMNode *parent = webkit_dom_node_get_parent_node (
7319 WEBKIT_DOM_NODE (signature));
7320 signature_clone = webkit_dom_node_clone_node_with_error (parent, TRUE, NULL);
7321 remove_node (parent);
7323 from = WEBKIT_DOM_NODE (element);
7326 blockquotes_count = create_text_markers_for_citations_in_element (WEBKIT_DOM_ELEMENT (from));
7327 create_text_markers_for_selection_in_element (WEBKIT_DOM_ELEMENT (from));
7328 webkit_dom_element_set_attribute (
7329 WEBKIT_DOM_ELEMENT (from),
7330 "data-evo-html-to-plain-text-wrapper",
7332 NULL);
7334 /* Add the missing BR elements on the end of DIV and P elements to
7335 * preserve the line breaks. But we need to do that just in case that
7336 * there is another element that contains text. */
7337 preserve_line_breaks_in_element (document, WEBKIT_DOM_ELEMENT (from), "p, div, address");
7338 preserve_line_breaks_in_element (
7339 document,
7340 WEBKIT_DOM_ELEMENT (from),
7341 "[data-evo-html-to-plain-text-wrapper] > :matches(h1, h2, h3, h4, h5, h6)");
7342 preserve_pre_line_breaks_in_element (document, WEBKIT_DOM_ELEMENT (element));
7344 webkit_dom_element_remove_attribute (
7345 WEBKIT_DOM_ELEMENT (from), "data-evo-html-to-plain-text-wrapper");
7347 inner_text = webkit_dom_html_element_get_inner_text (
7348 WEBKIT_DOM_HTML_ELEMENT (from));
7350 webkit_dom_html_element_set_inner_text (
7351 WEBKIT_DOM_HTML_ELEMENT (blockquote), inner_text, NULL);
7353 inner_html = webkit_dom_element_get_inner_html (blockquote);
7355 parse_html_into_blocks (editor_page,
7356 main_blockquote ? blockquote : WEBKIT_DOM_ELEMENT (element),
7357 NULL,
7358 inner_html);
7360 if (main_blockquote) {
7361 webkit_dom_node_replace_child (
7362 webkit_dom_node_get_parent_node (
7363 WEBKIT_DOM_NODE (main_blockquote)),
7364 WEBKIT_DOM_NODE (blockquote),
7365 WEBKIT_DOM_NODE (main_blockquote),
7366 NULL);
7368 remove_evolution_attributes (WEBKIT_DOM_ELEMENT (element));
7369 } else {
7370 WebKitDOMNode *first_child;
7372 if (signature) {
7373 if (!top_signature) {
7374 signature_clone = webkit_dom_node_append_child (
7375 WEBKIT_DOM_NODE (element),
7376 signature_clone,
7377 NULL);
7378 } else {
7379 webkit_dom_node_insert_before (
7380 WEBKIT_DOM_NODE (element),
7381 signature_clone,
7382 webkit_dom_node_get_first_child (
7383 WEBKIT_DOM_NODE (element)),
7384 NULL);
7388 first_child = webkit_dom_node_get_first_child (
7389 WEBKIT_DOM_NODE (element));
7390 if (first_child) {
7391 if (!webkit_dom_node_has_child_nodes (first_child)) {
7392 webkit_dom_element_set_inner_html (
7393 WEBKIT_DOM_ELEMENT (first_child),
7394 "<br>",
7395 NULL);
7397 dom_add_selection_markers_into_element_start (
7398 document, WEBKIT_DOM_ELEMENT (first_child), NULL, NULL);
7402 if (wrap)
7403 *wrap = TRUE;
7404 if (quote)
7405 *quote = main_blockquote || blockquotes_count > 0;
7407 webkit_dom_element_set_attribute (
7408 WEBKIT_DOM_ELEMENT (element), "data-converted", "", NULL);
7410 if ((br_element = webkit_dom_document_get_element_by_id (document, "-x-evo-first-br")))
7411 webkit_dom_element_remove_attribute (br_element, "id");
7413 if ((br_element = webkit_dom_document_get_element_by_id (document, "-x-evo-last-br")))
7414 webkit_dom_element_remove_attribute (br_element, "id");
7416 g_free (inner_text);
7417 g_free (inner_html);
7420 void
7421 e_editor_dom_convert_element_from_html_to_plain_text (EEditorPage *editor_page,
7422 WebKitDOMElement *element)
7424 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
7426 convert_element_from_html_to_plain_text (editor_page, element, NULL, NULL);
7429 static void
7430 process_node_to_plain_text_changing_composer_mode (EEditorPage *editor_page,
7431 WebKitDOMNode *source)
7433 WebKitDOMElement *element;
7434 WebKitDOMNamedNodeMap *attributes = NULL;
7435 gint ii;
7437 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
7439 attributes = webkit_dom_element_get_attributes (WEBKIT_DOM_ELEMENT (source));
7440 for (ii = webkit_dom_named_node_map_get_length (attributes); ii--;) {
7441 gchar *name = NULL;
7442 WebKitDOMAttr *attribute;
7444 attribute = WEBKIT_DOM_ATTR (webkit_dom_named_node_map_item (attributes, ii));
7446 name = webkit_dom_attr_get_name (attribute);
7448 if (g_strcmp0 (name, "bgcolor") == 0 ||
7449 g_strcmp0 (name, "text") == 0 ||
7450 g_strcmp0 (name, "vlink") == 0 ||
7451 g_strcmp0 (name, "link") == 0) {
7453 webkit_dom_element_remove_attribute_node (
7454 WEBKIT_DOM_ELEMENT (source), attribute, NULL);
7456 g_free (name);
7458 g_clear_object (&attributes);
7460 /* Signature */
7461 element = webkit_dom_element_query_selector (
7462 WEBKIT_DOM_ELEMENT (source), "div.-x-evo-signature-wrapper", NULL);
7463 if (element) {
7464 WebKitDOMNode *first_child;
7465 gchar *id = NULL;
7467 first_child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element));
7468 id = webkit_dom_element_get_id (WEBKIT_DOM_ELEMENT (first_child));
7470 if (g_strcmp0 (id, "none") != 0)
7471 convert_element_from_html_to_plain_text (
7472 editor_page, WEBKIT_DOM_ELEMENT (first_child), NULL, NULL);
7473 g_free (id);
7477 /* This function is different than the others there as this needs to go through
7478 * the DOM node by node and generate the plain text of their content. For some
7479 * it will just take the text content, but for example the lists are not that
7480 * easy. */
7481 static void
7482 process_node_to_plain_text_for_exporting (EEditorPage *editor_page,
7483 WebKitDOMNode *source,
7484 GString *buffer)
7486 WebKitDOMNodeList *nodes = NULL;
7487 gboolean html_mode;
7488 gchar *content = NULL;
7489 gint ii, length;
7491 html_mode = e_editor_page_get_html_mode (editor_page);
7493 nodes = webkit_dom_node_get_child_nodes (source);
7494 length = webkit_dom_node_list_get_length (nodes);
7495 for (ii = 0; ii < length; ii++) {
7496 WebKitDOMNode *child;
7497 gboolean skip_node = FALSE;
7499 child = webkit_dom_node_list_item (nodes, ii);
7501 if (WEBKIT_DOM_IS_TEXT (child)) {
7502 gchar *class;
7503 const gchar *css_align = NULL;
7504 GRegex *regex;
7506 content = webkit_dom_node_get_text_content (child);
7507 if (!content)
7508 goto next;
7510 /* The text nodes with only '\n' are reflected only in
7511 * PRE elements, otherwise skip them. */
7512 /* FIXME wrong for "white-space: pre", but we don't use
7513 * that in editor in our expected DOM structure */
7514 if (content[0] == '\n' && content[1] == '\0' &&
7515 !WEBKIT_DOM_IS_HTML_PRE_ELEMENT (source)) {
7516 g_free (content);
7517 skip_node = TRUE;
7518 goto next;
7521 if (strstr (content, UNICODE_ZERO_WIDTH_SPACE)) {
7522 gchar *tmp;
7524 regex = g_regex_new (UNICODE_ZERO_WIDTH_SPACE, 0, 0, NULL);
7525 tmp = g_regex_replace (
7526 regex, content, -1, 0, "", 0, NULL);
7527 g_free (content);
7528 content = tmp;
7529 g_regex_unref (regex);
7532 if (strstr (content, UNICODE_NBSP)) {
7533 gchar *tmp;
7535 regex = g_regex_new (UNICODE_NBSP, 0, 0, NULL);
7536 tmp = g_regex_replace (regex, content, -1, 0, " ", 0, NULL);
7537 g_free (content);
7538 content = tmp;
7539 g_regex_unref (regex);
7542 class = webkit_dom_element_get_class_name (WEBKIT_DOM_ELEMENT (source));
7543 if (class && (css_align = strstr (class, "-x-evo-align-"))) {
7544 gchar *content_with_align;
7545 gint word_wrap_length = e_editor_page_get_word_wrap_length (editor_page);
7547 if (!g_str_has_prefix (css_align + 13, "left")) {
7548 gchar *align;
7549 gint len;
7551 if (g_str_has_prefix (css_align + 13, "center"))
7552 len = (word_wrap_length - g_utf8_strlen (content, -1)) / 2;
7553 else
7554 len = word_wrap_length - g_utf8_strlen (content, -1);
7556 if (len < 0)
7557 len = 0;
7559 if (g_str_has_suffix (content, " ")) {
7560 gchar *tmp;
7562 len++;
7563 align = g_strnfill (len, ' ');
7565 tmp = g_strndup (content, g_utf8_strlen (content, -1) -1);
7567 content_with_align = g_strconcat (
7568 align, tmp, NULL);
7569 g_free (tmp);
7570 } else {
7571 align = g_strnfill (len, ' ');
7573 content_with_align = g_strconcat (
7574 align, content, NULL);
7577 g_free (content);
7578 g_free (align);
7579 content = content_with_align;
7583 g_free (class);
7585 g_string_append (buffer, content);
7587 g_free (content);
7588 content = NULL;
7590 goto next;
7593 if (!WEBKIT_DOM_IS_ELEMENT (child))
7594 goto next;
7596 if (element_has_class (WEBKIT_DOM_ELEMENT (child), "Apple-tab-span")) {
7597 content = webkit_dom_node_get_text_content (child);
7598 g_string_append (buffer, content);
7599 g_free (content);
7600 skip_node = TRUE;
7601 goto next;
7604 if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (child))
7605 process_quote_nodes (WEBKIT_DOM_ELEMENT (child));
7607 if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (child) &&
7608 element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-indented"))
7609 process_indented_element (WEBKIT_DOM_ELEMENT (child));
7611 if (node_is_list (child)) {
7612 process_list_to_plain_text (editor_page, WEBKIT_DOM_ELEMENT (child), 1, buffer);
7613 skip_node = TRUE;
7614 goto next;
7617 if (element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-resizable-wrapper") &&
7618 !element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-smiley-wrapper")) {
7619 skip_node = TRUE;
7620 goto next;
7623 /* Signature */
7624 if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (child) &&
7625 element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-signature-wrapper")) {
7626 WebKitDOMNode *first_child;
7627 gchar *id;
7629 first_child = webkit_dom_node_get_first_child (child);
7631 /* Don't generate any text if the signature is set to None. */
7632 id = webkit_dom_element_get_id (WEBKIT_DOM_ELEMENT (first_child));
7633 if (g_strcmp0 (id, "none") == 0) {
7634 g_free (id);
7636 remove_node (child);
7637 skip_node = TRUE;
7638 length--;
7639 ii--;
7640 goto next;
7642 g_free (id);
7644 if (html_mode)
7645 convert_element_from_html_to_plain_text (
7646 editor_page, WEBKIT_DOM_ELEMENT (first_child), NULL, NULL);
7648 goto next;
7651 /* Replace smileys with their text representation */
7652 if (element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-smiley-wrapper")) {
7653 WebKitDOMNode *text_version;
7655 text_version = webkit_dom_node_get_last_child (child);
7656 content = webkit_dom_html_element_get_inner_text (
7657 WEBKIT_DOM_HTML_ELEMENT (text_version));
7658 g_string_append (buffer, content);
7659 g_free (content);
7660 skip_node = TRUE;
7661 goto next;
7664 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (child)) {
7665 g_string_append (buffer, "\n");
7666 goto next;
7669 if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (child)) {
7670 content = webkit_dom_html_element_get_inner_text (
7671 WEBKIT_DOM_HTML_ELEMENT (child));
7672 g_string_append (buffer, content);
7673 g_free (content);
7674 skip_node = TRUE;
7676 next:
7677 if (!skip_node && webkit_dom_node_has_child_nodes (child))
7678 process_node_to_plain_text_for_exporting (editor_page, child, buffer);
7680 g_clear_object (&nodes);
7682 if (!g_str_has_suffix (buffer->str, "\n") &&
7683 (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (source) ||
7684 WEBKIT_DOM_IS_HTML_PARAGRAPH_ELEMENT (source) ||
7685 WEBKIT_DOM_IS_HTML_PRE_ELEMENT (source) ||
7686 WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (source)))
7687 g_string_append (buffer, "\n");
7689 if (g_str_has_suffix (buffer->str, "\n") && buffer->len > 1 &&
7690 WEBKIT_DOM_IS_HTML_BODY_ELEMENT (source))
7691 g_string_truncate (buffer, buffer->len - 1);
7694 static void
7695 process_node_to_html_changing_composer_mode (EEditorPage *editor_page,
7696 WebKitDOMNode *source)
7700 static void
7701 process_node_to_html_for_exporting (EEditorPage *editor_page,
7702 WebKitDOMNode *source)
7704 WebKitDOMNodeList *list = NULL;
7705 WebKitDOMHTMLCollection *collection = NULL;
7706 WebKitDOMElement *element;
7707 WebKitDOMDocument *document;
7708 gint ii;
7710 document = webkit_dom_node_get_owner_document (source);
7712 remove_evolution_attributes (WEBKIT_DOM_ELEMENT (source));
7714 /* Aligned elements */
7715 list = webkit_dom_element_query_selector_all (
7716 WEBKIT_DOM_ELEMENT (source), "[class*=\"-x-evo-align\"]", NULL);
7717 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
7718 gchar *class = NULL;
7719 WebKitDOMNode *node;
7720 gboolean center = FALSE;
7722 node = webkit_dom_node_list_item (list, ii);
7723 class = webkit_dom_element_get_class_name (WEBKIT_DOM_ELEMENT (node));
7724 center = g_strrstr (class, "center") != NULL;
7725 if (center || g_strrstr (class, "right")) {
7726 if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (node))
7727 webkit_dom_element_set_attribute (
7728 WEBKIT_DOM_ELEMENT (node),
7729 "style",
7730 center ?
7731 "list-style-position: inside; text-align: center" :
7732 "list-style-position: inside; text-align: right",
7733 NULL);
7734 else
7735 webkit_dom_element_set_attribute (
7736 WEBKIT_DOM_ELEMENT (node),
7737 "style",
7738 center ?
7739 "text-align: center" :
7740 "text-align: right",
7741 NULL);
7743 element_remove_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-align-left");
7744 element_remove_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-align-center");
7745 element_remove_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-align-right");
7746 g_free (class);
7748 g_clear_object (&list);
7750 /* Indented elements */
7751 collection = webkit_dom_element_get_elements_by_class_name_as_html_collection (
7752 WEBKIT_DOM_ELEMENT (source), "-x-evo-indented");
7753 for (ii = webkit_dom_html_collection_get_length (collection); ii--;) {
7754 WebKitDOMNode *node;
7756 node = webkit_dom_html_collection_item (collection, ii);
7757 element_remove_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-indented");
7758 remove_evolution_attributes (WEBKIT_DOM_ELEMENT (node));
7760 g_clear_object (&collection);
7762 /* Tab characters */
7763 collection = webkit_dom_element_get_elements_by_class_name_as_html_collection (
7764 WEBKIT_DOM_ELEMENT (source), "Apple-tab-span");
7765 for (ii = webkit_dom_html_collection_get_length (collection); ii--;) {
7766 gchar *text_content;
7767 WebKitDOMNode *node;
7769 node = webkit_dom_html_collection_item (collection, ii);
7770 text_content = webkit_dom_node_get_text_content (node);
7771 webkit_dom_node_insert_before (
7772 webkit_dom_node_get_parent_node (node),
7773 WEBKIT_DOM_NODE (webkit_dom_document_create_text_node (document, text_content)),
7774 node,
7775 NULL);
7777 remove_node (node);
7778 g_free (text_content);
7780 g_clear_object (&collection);
7782 collection = webkit_dom_element_get_elements_by_class_name_as_html_collection (
7783 WEBKIT_DOM_ELEMENT (source), "-x-evo-quoted");
7784 for (ii = webkit_dom_html_collection_get_length (collection); ii--;) {
7785 WebKitDOMNode *quoted_node;
7786 gchar *text_content;
7788 quoted_node = webkit_dom_html_collection_item (collection, ii);
7789 text_content = webkit_dom_node_get_text_content (quoted_node);
7790 webkit_dom_element_set_outer_html (
7791 WEBKIT_DOM_ELEMENT (quoted_node), text_content, NULL);
7793 g_free (text_content);
7795 g_clear_object (&collection);
7797 /* Images */
7798 list = webkit_dom_element_query_selector_all (
7799 WEBKIT_DOM_ELEMENT (source), ".-x-evo-resizable-wrapper:not(.-x-evo-smiley-wrapper)", NULL);
7800 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
7801 WebKitDOMNode *node, *image;
7803 node = webkit_dom_node_list_item (list, ii);
7804 image = webkit_dom_node_get_first_child (node);
7806 if (WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (image)) {
7807 remove_evolution_attributes (
7808 WEBKIT_DOM_ELEMENT (image));
7810 webkit_dom_node_replace_child (
7811 webkit_dom_node_get_parent_node (node), image, node, NULL);
7814 g_clear_object (&list);
7816 /* Signature */
7817 element = webkit_dom_element_query_selector (
7818 WEBKIT_DOM_ELEMENT (source), "div.-x-evo-signature-wrapper", NULL);
7819 if (element) {
7820 WebKitDOMNode *first_child;
7821 gchar *id;
7823 /* Don't generate any text if the signature is set to None. */
7824 first_child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element));
7825 id = webkit_dom_element_get_id (WEBKIT_DOM_ELEMENT (first_child));
7826 if (g_strcmp0 (id, "none") == 0) {
7827 remove_node (WEBKIT_DOM_NODE (element));
7828 } else {
7829 remove_base_attributes (element);
7830 remove_base_attributes (WEBKIT_DOM_ELEMENT (first_child));
7831 remove_evolution_attributes (WEBKIT_DOM_ELEMENT (first_child));
7833 g_free (id);
7836 /* Smileys */
7837 collection = webkit_dom_element_get_elements_by_class_name_as_html_collection (
7838 WEBKIT_DOM_ELEMENT (source), "-x-evo-smiley-wrapper");
7839 for (ii = webkit_dom_html_collection_get_length (collection); ii--;) {
7840 WebKitDOMNode *node;
7841 WebKitDOMElement *img;
7843 node = webkit_dom_html_collection_item (collection, ii);
7844 img = WEBKIT_DOM_ELEMENT (webkit_dom_node_get_first_child (node));
7846 remove_evolution_attributes (img);
7847 remove_base_attributes (img);
7849 webkit_dom_node_replace_child (
7850 webkit_dom_node_get_parent_node (node),
7851 WEBKIT_DOM_NODE (img),
7852 node,
7853 NULL);
7855 g_clear_object (&collection);
7857 collection = webkit_dom_element_get_elements_by_tag_name_as_html_collection (
7858 WEBKIT_DOM_ELEMENT (source), "pre");
7859 for (ii = webkit_dom_html_collection_get_length (collection); ii--;) {
7860 WebKitDOMNode *node;
7862 node = webkit_dom_html_collection_item (collection, ii);
7863 remove_evolution_attributes (WEBKIT_DOM_ELEMENT (node));
7865 g_clear_object (&collection);
7867 list = webkit_dom_element_query_selector_all (
7868 WEBKIT_DOM_ELEMENT (source), "[data-evo-paragraph]", NULL);
7869 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
7870 WebKitDOMNode *node;
7872 node = webkit_dom_node_list_item (list, ii);
7873 remove_evolution_attributes (WEBKIT_DOM_ELEMENT (node));
7874 remove_base_attributes (WEBKIT_DOM_ELEMENT (node));
7876 g_clear_object (&list);
7878 collection = webkit_dom_element_get_elements_by_class_name_as_html_collection (
7879 WEBKIT_DOM_ELEMENT (source), "-x-evo-wrap-br");
7880 for (ii = webkit_dom_html_collection_get_length (collection); ii--;) {
7881 WebKitDOMNode *node;
7883 node = webkit_dom_html_collection_item (collection, ii);
7884 webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "class");
7886 g_clear_object (&collection);
7888 list = webkit_dom_element_query_selector_all (
7889 WEBKIT_DOM_ELEMENT (source), "#-x-evo-main-cite", NULL);
7890 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
7891 WebKitDOMNode *node;
7893 node = webkit_dom_node_list_item (list, ii);
7894 webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "id");
7896 g_clear_object (&list);
7899 static void
7900 remove_image_attributes_from_element (WebKitDOMElement *element)
7902 webkit_dom_element_remove_attribute (element, "background");
7903 webkit_dom_element_remove_attribute (element, "data-uri");
7904 webkit_dom_element_remove_attribute (element, "data-inline");
7905 webkit_dom_element_remove_attribute (element, "data-name");
7908 static void
7909 remove_background_images_in_element (WebKitDOMElement *element)
7911 gint ii;
7912 WebKitDOMNodeList *images = NULL;
7914 images = webkit_dom_element_query_selector_all (
7915 element, "[background][data-inline]", NULL);
7916 for (ii = webkit_dom_node_list_get_length (images); ii--;) {
7917 WebKitDOMElement *image = WEBKIT_DOM_ELEMENT (
7918 webkit_dom_node_list_item (images, ii));
7920 remove_image_attributes_from_element (image);
7922 g_clear_object (&images);
7924 remove_image_attributes_from_element (element);
7927 static void
7928 remove_images_in_element (WebKitDOMElement *element)
7930 gint ii;
7931 WebKitDOMNodeList *images = NULL;
7933 images = webkit_dom_element_query_selector_all (
7934 element, "img:not(.-x-evo-smiley-img)", NULL);
7935 for (ii = webkit_dom_node_list_get_length (images); ii--;)
7936 remove_node (webkit_dom_node_list_item (images, ii));
7937 g_clear_object (&images);
7940 static void
7941 remove_images (WebKitDOMDocument *document)
7943 remove_images_in_element (
7944 WEBKIT_DOM_ELEMENT (webkit_dom_document_get_body (document)));
7947 static void
7948 toggle_smileys (EEditorPage *editor_page)
7950 WebKitDOMDocument *document;
7951 WebKitDOMHTMLCollection *collection = NULL;
7952 gboolean html_mode;
7953 gint ii;
7955 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
7957 document = e_editor_page_get_document (editor_page);
7958 html_mode = e_editor_page_get_html_mode (editor_page);
7960 collection = webkit_dom_document_get_elements_by_class_name_as_html_collection (
7961 document, "-x-evo-smiley-img");
7962 for (ii = webkit_dom_html_collection_get_length (collection); ii--;) {
7963 WebKitDOMNode *img = webkit_dom_html_collection_item (collection, ii);
7964 WebKitDOMElement *parent = webkit_dom_node_get_parent_element (img);
7966 if (html_mode)
7967 element_add_class (parent, "-x-evo-resizable-wrapper");
7968 else
7969 element_remove_class (parent, "-x-evo-resizable-wrapper");
7971 g_clear_object (&collection);
7974 static void
7975 toggle_paragraphs_style_in_element (EEditorPage *editor_page,
7976 WebKitDOMElement *element,
7977 gboolean html_mode)
7979 gint ii;
7980 WebKitDOMNodeList *paragraphs = NULL;
7982 paragraphs = webkit_dom_element_query_selector_all (
7983 element, ":not(td) > [data-evo-paragraph]", NULL);
7985 for (ii = webkit_dom_node_list_get_length (paragraphs); ii--;) {
7986 gchar *style;
7987 const gchar *css_align;
7988 WebKitDOMNode *node = webkit_dom_node_list_item (paragraphs, ii);
7990 if (html_mode) {
7991 style = webkit_dom_element_get_attribute (
7992 WEBKIT_DOM_ELEMENT (node), "style");
7994 if (style && (css_align = strstr (style, "text-align: "))) {
7995 webkit_dom_element_set_attribute (
7996 WEBKIT_DOM_ELEMENT (node),
7997 "style",
7998 g_str_has_prefix (css_align + 12, "center") ?
7999 "text-align: center" :
8000 "text-align: right",
8001 NULL);
8002 } else {
8003 /* In HTML mode the paragraphs don't have width limit */
8004 webkit_dom_element_remove_attribute (
8005 WEBKIT_DOM_ELEMENT (node), "style");
8007 g_free (style);
8008 } else {
8009 WebKitDOMNode *parent;
8011 parent = webkit_dom_node_get_parent_node (node);
8012 /* If the paragraph is inside indented paragraph don't set
8013 * the style as it will be inherited */
8014 if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent) && node_is_list (node)) {
8015 gint offset;
8017 offset = WEBKIT_DOM_IS_HTML_U_LIST_ELEMENT (node) ?
8018 SPACES_PER_LIST_LEVEL : SPACES_ORDERED_LIST_FIRST_LEVEL;
8019 /* In plain text mode the paragraphs have width limit */
8020 e_editor_dom_set_paragraph_style (
8021 editor_page, WEBKIT_DOM_ELEMENT (node), -1, -offset, NULL);
8022 } else if (!element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-indented")) {
8023 const gchar *style_to_add = "";
8024 style = webkit_dom_element_get_attribute (
8025 WEBKIT_DOM_ELEMENT (node), "style");
8027 if (style && (css_align = strstr (style, "text-align: "))) {
8028 style_to_add = g_str_has_prefix (
8029 css_align + 12, "center") ?
8030 "text-align: center;" :
8031 "text-align: right;";
8034 /* In plain text mode the paragraphs have width limit */
8035 e_editor_dom_set_paragraph_style (
8036 editor_page, WEBKIT_DOM_ELEMENT (node), -1, 0, style_to_add);
8038 g_free (style);
8042 g_clear_object (&paragraphs);
8045 static void
8046 toggle_paragraphs_style (EEditorPage *editor_page)
8048 WebKitDOMDocument *document;
8050 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
8052 document = e_editor_page_get_document (editor_page);
8054 toggle_paragraphs_style_in_element (
8055 editor_page,
8056 WEBKIT_DOM_ELEMENT (webkit_dom_document_get_body (document)),
8057 e_editor_page_get_html_mode (editor_page));
8060 gchar *
8061 e_editor_dom_process_content_for_draft (EEditorPage *editor_page,
8062 gboolean only_inner_body)
8064 WebKitDOMDocument *document;
8065 WebKitDOMHTMLElement *body;
8066 WebKitDOMElement *document_element;
8067 WebKitDOMNodeList *list = NULL;
8068 WebKitDOMNode *document_element_clone;
8069 gboolean selection_saved = FALSE;
8070 gchar *content;
8071 gint ii;
8073 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
8075 document = e_editor_page_get_document (editor_page);
8076 body = webkit_dom_document_get_body (document);
8078 webkit_dom_element_set_attribute (
8079 WEBKIT_DOM_ELEMENT (body), "data-evo-draft", "", NULL);
8081 if (webkit_dom_document_get_element_by_id (document, "-x-evo-selection-start-marker"))
8082 selection_saved = TRUE;
8084 if (!selection_saved)
8085 e_editor_dom_selection_save (editor_page);
8087 document_element = webkit_dom_document_get_document_element (document);
8089 document_element_clone = webkit_dom_node_clone_node_with_error (
8090 WEBKIT_DOM_NODE (document_element), TRUE, NULL);
8092 list = webkit_dom_element_query_selector_all (
8093 WEBKIT_DOM_ELEMENT (document_element_clone), "a.-x-evo-visited-link", NULL);
8094 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
8095 WebKitDOMNode *anchor;
8097 anchor = webkit_dom_node_list_item (list, ii);
8098 webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (anchor), "class");
8100 g_clear_object (&list);
8102 list = webkit_dom_element_query_selector_all (
8103 WEBKIT_DOM_ELEMENT (document_element_clone), "#-x-evo-input-start", NULL);
8104 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
8105 WebKitDOMNode *node;
8107 node = webkit_dom_node_list_item (list, ii);
8108 webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "id");
8110 g_clear_object (&list);
8112 if (e_editor_page_get_html_mode (editor_page))
8113 style_blockquotes (WEBKIT_DOM_ELEMENT (document_element_clone));
8115 if (only_inner_body) {
8116 WebKitDOMElement *body;
8117 WebKitDOMNode *first_child;
8119 body = webkit_dom_element_query_selector (
8120 WEBKIT_DOM_ELEMENT (document_element_clone), "body", NULL);
8122 first_child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
8124 if (!e_editor_page_get_html_mode (editor_page))
8125 webkit_dom_element_set_attribute (
8126 WEBKIT_DOM_ELEMENT (first_child),
8127 "data-evo-signature-plain-text-mode",
8129 NULL);
8131 content = webkit_dom_element_get_inner_html (body);
8133 if (!e_editor_page_get_html_mode (editor_page))
8134 webkit_dom_element_remove_attribute (
8135 WEBKIT_DOM_ELEMENT (first_child),
8136 "data-evo-signature-plain-text-mode");
8137 } else
8138 content = webkit_dom_element_get_outer_html (
8139 WEBKIT_DOM_ELEMENT (document_element_clone));
8141 webkit_dom_element_remove_attribute (
8142 WEBKIT_DOM_ELEMENT (body), "data-evo-draft");
8144 e_editor_dom_selection_restore (editor_page);
8145 e_editor_dom_force_spell_check_in_viewport (editor_page);
8147 if (selection_saved)
8148 e_editor_dom_selection_save (editor_page);
8150 return content;
8153 static void
8154 toggle_indented_elements (EEditorPage *editor_page)
8156 gboolean html_mode;
8157 gint ii;
8158 WebKitDOMDocument *document;
8159 WebKitDOMHTMLCollection *collection = NULL;
8161 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
8163 document = e_editor_page_get_document (editor_page);
8164 html_mode = e_editor_page_get_html_mode (editor_page);
8165 collection = webkit_dom_document_get_elements_by_class_name_as_html_collection (
8166 document, "-x-evo-indented");
8167 for (ii = webkit_dom_html_collection_get_length (collection); ii--;) {
8168 WebKitDOMNode *node = webkit_dom_html_collection_item (collection, ii);
8170 if (html_mode)
8171 dom_element_swap_attributes (WEBKIT_DOM_ELEMENT (node), "style", "data-plain-text-style");
8172 else
8173 dom_element_swap_attributes (WEBKIT_DOM_ELEMENT (node), "data-plain-text-style", "style");
8175 g_clear_object (&collection);
8178 static void
8179 process_content_to_html_changing_composer_mode (EEditorPage *editor_page)
8181 WebKitDOMDocument *document;
8182 WebKitDOMNode *body;
8183 WebKitDOMElement *blockquote;
8185 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
8187 document = e_editor_page_get_document (editor_page);
8188 body = WEBKIT_DOM_NODE (webkit_dom_document_get_body (document));
8190 webkit_dom_element_remove_attribute (
8191 WEBKIT_DOM_ELEMENT (body), "data-evo-plain-text");
8192 blockquote = webkit_dom_document_query_selector (
8193 document, "blockquote[type|=cite]", NULL);
8195 if (blockquote)
8196 dom_dequote_plain_text (document);
8198 toggle_paragraphs_style (editor_page);
8199 toggle_smileys (editor_page);
8200 remove_images (document);
8201 e_editor_dom_remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (body));
8203 process_node_to_html_changing_composer_mode (editor_page, body);
8206 static void
8207 wrap_paragraphs_in_quoted_content (EEditorPage *editor_page)
8209 WebKitDOMDocument *document;
8210 WebKitDOMNodeList *paragraphs = NULL;
8211 gint ii;
8213 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
8215 document = e_editor_page_get_document (editor_page);
8217 paragraphs = webkit_dom_document_query_selector_all (
8218 document, "blockquote[type=cite] > [data-evo-paragraph]", NULL);
8219 for (ii = webkit_dom_node_list_get_length (paragraphs); ii--;) {
8220 WebKitDOMNode *paragraph;
8222 paragraph = webkit_dom_node_list_item (paragraphs, ii);
8224 e_editor_dom_wrap_paragraph (editor_page, WEBKIT_DOM_ELEMENT (paragraph));
8226 g_clear_object (&paragraphs);
8229 static void
8230 process_content_to_plain_text_changing_composer_mode (EEditorPage *editor_page)
8232 WebKitDOMDocument *document;
8233 WebKitDOMNode *body, *head, *node;
8234 WebKitDOMElement *blockquote;
8236 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
8238 document = e_editor_page_get_document (editor_page);
8239 body = WEBKIT_DOM_NODE (webkit_dom_document_get_body (document));
8240 head = WEBKIT_DOM_NODE (webkit_dom_document_get_head (document));
8242 while ((node = webkit_dom_node_get_last_child (head)))
8243 remove_node (node);
8245 e_editor_dom_selection_save (editor_page);
8247 webkit_dom_element_remove_attribute (
8248 WEBKIT_DOM_ELEMENT (body), "data-user-colors");
8250 e_editor_page_emit_user_changed_default_colors (editor_page, FALSE);
8252 webkit_dom_element_set_attribute (
8253 WEBKIT_DOM_ELEMENT (body), "data-evo-plain-text", "", NULL);
8255 blockquote = webkit_dom_document_query_selector (
8256 document, "blockquote[type|=cite]", NULL);
8258 if (blockquote) {
8259 wrap_paragraphs_in_quoted_content (editor_page);
8260 preserve_pre_line_breaks_in_element (document, WEBKIT_DOM_ELEMENT (body));
8261 quote_plain_text_elements_after_wrapping_in_document (editor_page);
8264 toggle_paragraphs_style (editor_page);
8265 toggle_smileys (editor_page);
8266 toggle_indented_elements (editor_page);
8267 remove_images (document);
8268 remove_background_images_in_element (WEBKIT_DOM_ELEMENT (body));
8270 process_node_to_plain_text_changing_composer_mode (editor_page, body);
8272 e_editor_dom_selection_restore (editor_page);
8273 e_editor_dom_force_spell_check_in_viewport (editor_page);
8276 gchar *
8277 e_editor_dom_process_content_to_plain_text_for_exporting (EEditorPage *editor_page)
8279 WebKitDOMDocument *document;
8280 WebKitDOMElement *element;
8281 WebKitDOMNode *body, *source;
8282 WebKitDOMNodeList *list = NULL;
8283 WebKitDOMDOMWindow *dom_window = NULL;
8284 WebKitDOMDOMSelection *dom_selection = NULL;
8285 gboolean wrap = TRUE, quote = FALSE, remove_last_new_line = FALSE;
8286 gint ii;
8287 GString *plain_text;
8289 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
8291 document = e_editor_page_get_document (editor_page);
8292 plain_text = g_string_sized_new (1024);
8294 body = WEBKIT_DOM_NODE (webkit_dom_document_get_body (document));
8295 source = webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (body), TRUE, NULL);
8297 e_editor_dom_selection_save (editor_page);
8299 /* If composer is in HTML mode we have to move the content to plain version */
8300 if (e_editor_page_get_html_mode (editor_page)) {
8301 if (e_editor_dom_check_if_conversion_needed (editor_page)) {
8302 WebKitDOMElement *wrapper;
8303 WebKitDOMNode *child, *last_child;
8305 wrapper = webkit_dom_document_create_element (document, "div", NULL);
8306 webkit_dom_element_set_attribute (
8307 WEBKIT_DOM_ELEMENT (wrapper),
8308 "data-evo-html-to-plain-text-wrapper",
8310 NULL);
8311 while ((child = webkit_dom_node_get_first_child (source))) {
8312 webkit_dom_node_append_child (
8313 WEBKIT_DOM_NODE (wrapper),
8314 child,
8315 NULL);
8318 list = webkit_dom_element_query_selector_all (
8319 wrapper, "#-x-evo-input-start", NULL);
8320 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
8321 WebKitDOMNode *paragraph;
8323 paragraph = webkit_dom_node_list_item (list, ii);
8325 webkit_dom_element_remove_attribute (
8326 WEBKIT_DOM_ELEMENT (paragraph), "id");
8328 g_clear_object (&list);
8330 remove_images_in_element (wrapper);
8332 list = webkit_dom_element_query_selector_all (
8333 wrapper, "[data-evo-html-to-plain-text-wrapper] > :matches(ul, ol)", NULL);
8334 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
8335 WebKitDOMElement *list_pre;
8336 WebKitDOMNode *item;
8337 GString *list_plain_text;
8339 item = webkit_dom_node_list_item (list, ii);
8341 list_plain_text = g_string_new ("");
8343 process_list_to_plain_text (
8344 editor_page, WEBKIT_DOM_ELEMENT (item), 1, list_plain_text);
8346 list_pre = webkit_dom_document_create_element (document, "pre", NULL);
8347 webkit_dom_html_element_set_inner_text (
8348 WEBKIT_DOM_HTML_ELEMENT (list_pre),
8349 list_plain_text->str,
8350 NULL);
8351 webkit_dom_node_replace_child (
8352 WEBKIT_DOM_NODE (wrapper),
8353 WEBKIT_DOM_NODE (list_pre),
8354 item,
8355 NULL);
8357 g_string_free (list_plain_text, TRUE);
8359 g_clear_object (&list);
8361 /* BR on the end of the last element would cause an extra
8362 * new line, remove it if there are some nodes before it. */
8363 last_child = webkit_dom_node_get_last_child (WEBKIT_DOM_NODE (wrapper));
8364 while (webkit_dom_node_get_last_child (last_child))
8365 last_child = webkit_dom_node_get_last_child (last_child);
8367 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (last_child) &&
8368 webkit_dom_node_get_previous_sibling (last_child))
8369 remove_node (last_child);
8371 convert_element_from_html_to_plain_text (
8372 editor_page, wrapper, &wrap, &quote);
8374 source = WEBKIT_DOM_NODE (wrapper);
8376 remove_last_new_line = TRUE;
8377 } else {
8378 toggle_paragraphs_style_in_element (
8379 editor_page, WEBKIT_DOM_ELEMENT (source), FALSE);
8380 remove_images_in_element (
8381 WEBKIT_DOM_ELEMENT (source));
8382 remove_background_images_in_element (
8383 WEBKIT_DOM_ELEMENT (source));
8387 list = webkit_dom_element_query_selector_all (
8388 WEBKIT_DOM_ELEMENT (source), "[data-evo-paragraph]", NULL);
8390 dom_window = webkit_dom_document_get_default_view (document);
8391 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
8392 webkit_dom_dom_selection_collapse_to_end (dom_selection, NULL);
8393 g_clear_object (&dom_window);
8394 g_clear_object (&dom_selection);
8396 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
8397 WebKitDOMNode *paragraph;
8399 paragraph = webkit_dom_node_list_item (list, ii);
8401 if (node_is_list (paragraph)) {
8402 WebKitDOMNode *item = webkit_dom_node_get_first_child (paragraph);
8404 while (item) {
8405 WebKitDOMNode *next_item =
8406 webkit_dom_node_get_next_sibling (item);
8408 if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (item))
8409 e_editor_dom_wrap_paragraph (editor_page, WEBKIT_DOM_ELEMENT (item));
8411 item = next_item;
8413 } else if (!webkit_dom_element_query_selector (WEBKIT_DOM_ELEMENT (paragraph), ".-x-evo-wrap-br,.-x-evo-quoted", NULL)) {
8414 /* Don't try to wrap the already wrapped content. */
8415 e_editor_dom_wrap_paragraph (editor_page, WEBKIT_DOM_ELEMENT (paragraph));
8418 g_clear_object (&list);
8420 if ((element = webkit_dom_document_get_element_by_id (document, "-x-evo-selection-start-marker")))
8421 remove_node (WEBKIT_DOM_NODE (element));
8422 if ((element = webkit_dom_document_get_element_by_id (document, "-x-evo-selection-end-marker")))
8423 remove_node (WEBKIT_DOM_NODE (element));
8425 webkit_dom_node_normalize (source);
8427 if (quote) {
8428 quote_plain_text_elements_after_wrapping_in_element (editor_page, WEBKIT_DOM_ELEMENT (source));
8429 } else if (e_editor_page_get_html_mode (editor_page)) {
8430 WebKitDOMElement *citation;
8432 citation = webkit_dom_element_query_selector (
8433 WEBKIT_DOM_ELEMENT (source), "blockquote[type=cite]", NULL);
8434 if (citation) {
8435 preserve_pre_line_breaks_in_element (document, WEBKIT_DOM_ELEMENT (source));
8436 quote_plain_text_elements_after_wrapping_in_element (editor_page, WEBKIT_DOM_ELEMENT (source));
8440 process_node_to_plain_text_for_exporting (editor_page, source, plain_text);
8441 /* Truncate the extra new line on the end of generated text as the
8442 * check inside the previous function is based on whether the processed
8443 * node is BODY or not, but in this case the content is wrapped in DIV. */
8444 if (remove_last_new_line)
8445 g_string_truncate (plain_text, plain_text->len - 1);
8447 e_editor_dom_selection_restore (editor_page);
8449 /* Return text content between <body> and </body> */
8450 return g_string_free (plain_text, FALSE);
8453 static void
8454 restore_image (WebKitDOMDocument *document,
8455 const gchar *id,
8456 const gchar *element_src)
8458 gchar *selector;
8459 gint ii;
8460 WebKitDOMNodeList *list = NULL;
8462 selector = g_strconcat ("[data-inline][background=\"cid:", id, "\"]", NULL);
8463 list = webkit_dom_document_query_selector_all (document, selector, NULL);
8464 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
8465 WebKitDOMElement *element = WEBKIT_DOM_ELEMENT (
8466 webkit_dom_node_list_item (list, ii));
8468 webkit_dom_element_set_attribute (element, "background", element_src, NULL);
8470 g_free (selector);
8471 g_clear_object (&list);
8473 selector = g_strconcat ("[data-inline][src=\"cid:", id, "\"]", NULL);
8474 list = webkit_dom_document_query_selector_all (document, selector, NULL);
8475 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
8476 WebKitDOMElement *element = WEBKIT_DOM_ELEMENT (
8477 webkit_dom_node_list_item (list, ii));
8479 webkit_dom_element_set_attribute (element, "src", element_src, NULL);
8481 g_free (selector);
8482 g_clear_object (&list);
8485 void
8486 e_editor_dom_restore_images (EEditorPage *editor_page,
8487 GVariant *inline_images_to_restore)
8489 WebKitDOMDocument *document;
8490 const gchar *element_src, *name, *id;
8491 GVariantIter *iter;
8493 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
8495 document = e_editor_page_get_document (editor_page);
8497 g_variant_get (inline_images_to_restore, "a(sss)", &iter);
8498 while (g_variant_iter_loop (iter, "(&s&s&s)", &element_src, &name, &id))
8499 restore_image (document, id, element_src);
8501 g_variant_iter_free (iter);
8504 gchar *
8505 e_editor_dom_process_content_to_html_for_exporting (EEditorPage *editor_page)
8507 WebKitDOMDocument *document;
8508 WebKitDOMElement *element;
8509 WebKitDOMNode *node, *document_clone;
8510 WebKitDOMNodeList *list = NULL;
8511 GSettings *settings;
8512 gint ii;
8513 gchar *html_content;
8514 gboolean send_editor_colors = FALSE;
8516 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
8518 document = e_editor_page_get_document (editor_page);
8520 document_clone = webkit_dom_node_clone_node_with_error (
8521 WEBKIT_DOM_NODE (webkit_dom_document_get_document_element (document)), TRUE, NULL);
8522 element = webkit_dom_element_query_selector (
8523 WEBKIT_DOM_ELEMENT (document_clone), "style#-x-evo-quote-style", NULL);
8524 if (element)
8525 remove_node (WEBKIT_DOM_NODE (element));
8526 element = webkit_dom_element_query_selector (
8527 WEBKIT_DOM_ELEMENT (document_clone), "style#-x-evo-a-color-style", NULL);
8528 if (element)
8529 remove_node (WEBKIT_DOM_NODE (element));
8530 element = webkit_dom_element_query_selector (
8531 WEBKIT_DOM_ELEMENT (document_clone), "style#-x-evo-a-color-style-visited", NULL);
8532 if (element)
8533 remove_node (WEBKIT_DOM_NODE (element));
8534 /* When the Ctrl + Enter is pressed for sending, the links are activated. */
8535 element = webkit_dom_element_query_selector (
8536 WEBKIT_DOM_ELEMENT (document_clone), "style#-x-evo-style-a", NULL);
8537 if (element)
8538 remove_node (WEBKIT_DOM_NODE (element));
8539 node = WEBKIT_DOM_NODE (webkit_dom_element_query_selector (
8540 WEBKIT_DOM_ELEMENT (document_clone), "body", NULL));
8541 element = webkit_dom_element_query_selector (
8542 WEBKIT_DOM_ELEMENT (node), "#-x-evo-selection-start-marker", NULL);
8543 if (element)
8544 remove_node (WEBKIT_DOM_NODE (element));
8545 element = webkit_dom_element_query_selector (
8546 WEBKIT_DOM_ELEMENT (node), "#-x-evo-selection-end-marker", NULL);
8547 if (element)
8548 remove_node (WEBKIT_DOM_NODE (element));
8550 settings = e_util_ref_settings ("org.gnome.evolution.mail");
8551 send_editor_colors = g_settings_get_boolean (settings, "composer-inherit-theme-colors");
8552 g_object_unref (settings);
8554 if (webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (node), "data-user-colors")) {
8555 webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "data-user-colors");
8556 } else if (!send_editor_colors) {
8557 webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "bgcolor");
8558 webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "text");
8559 webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "link");
8560 webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "vlink");
8563 list = webkit_dom_element_query_selector_all (
8564 WEBKIT_DOM_ELEMENT (node), "span[data-hidden-space]", NULL);
8565 for (ii = webkit_dom_node_list_get_length (list); ii--;)
8566 remove_node (webkit_dom_node_list_item (list, ii));
8567 g_clear_object (&list);
8569 list = webkit_dom_element_query_selector_all (
8570 WEBKIT_DOM_ELEMENT (node), "[data-style]", NULL);
8571 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
8572 WebKitDOMNode *data_style_node;
8574 data_style_node = webkit_dom_node_list_item (list, ii);
8576 element_rename_attribute (WEBKIT_DOM_ELEMENT (data_style_node), "data-style", "style");
8578 g_clear_object (&list);
8580 style_blockquotes (WEBKIT_DOM_ELEMENT (node));
8581 process_node_to_html_for_exporting (editor_page, node);
8583 html_content = webkit_dom_element_get_outer_html (
8584 WEBKIT_DOM_ELEMENT (document_clone));
8586 if (strstr (html_content, UNICODE_ZERO_WIDTH_SPACE)) {
8587 GString *processed;
8589 processed = e_str_replace_string (html_content, UNICODE_ZERO_WIDTH_SPACE, "");
8590 g_free (html_content);
8591 html_content = g_string_free (processed, FALSE);
8594 return html_content;
8597 void
8598 e_editor_dom_convert_when_changing_composer_mode (EEditorPage *editor_page)
8600 WebKitDOMDocument *document;
8601 WebKitDOMHTMLElement *body;
8602 gboolean quote = FALSE, wrap = FALSE;
8604 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
8606 document = e_editor_page_get_document (editor_page);
8607 body = webkit_dom_document_get_body (document);
8609 convert_element_from_html_to_plain_text (
8610 editor_page, WEBKIT_DOM_ELEMENT (body), &wrap, &quote);
8612 if (wrap)
8613 e_editor_dom_wrap_paragraphs_in_document (editor_page);
8615 if (quote) {
8616 e_editor_dom_selection_save (editor_page);
8617 if (wrap)
8618 quote_plain_text_elements_after_wrapping_in_document (editor_page);
8619 else
8620 body = WEBKIT_DOM_HTML_ELEMENT (dom_quote_plain_text (document));
8621 e_editor_dom_selection_restore (editor_page);
8624 toggle_paragraphs_style (editor_page);
8625 toggle_smileys (editor_page);
8626 remove_images (document);
8627 remove_background_images_in_element (WEBKIT_DOM_ELEMENT (body));
8629 clear_attributes (editor_page);
8631 if (!e_editor_page_get_html_mode (editor_page))
8632 webkit_dom_element_set_attribute (
8633 WEBKIT_DOM_ELEMENT (body), "data-evo-plain-text", "", NULL);
8634 else
8635 webkit_dom_element_remove_attribute (
8636 WEBKIT_DOM_ELEMENT (body), "data-evo-plain-text");
8638 e_editor_dom_force_spell_check_in_viewport (editor_page);
8639 e_editor_dom_scroll_to_caret (editor_page);
8642 static void
8643 set_base64_to_element_attribute (GHashTable *inline_images,
8644 WebKitDOMElement *element,
8645 const gchar *attribute)
8647 gchar *attribute_value;
8648 const gchar *base64_src;
8650 attribute_value = webkit_dom_element_get_attribute (element, attribute);
8652 if (attribute_value && (base64_src = g_hash_table_lookup (inline_images, attribute_value)) != NULL) {
8653 const gchar *base64_data = strstr (base64_src, ";") + 1;
8654 gchar *name;
8655 glong name_length;
8657 name_length =
8658 g_utf8_strlen (base64_src, -1) -
8659 g_utf8_strlen (base64_data, -1) - 1;
8660 name = g_strndup (base64_src, name_length);
8662 webkit_dom_element_set_attribute (element, "data-inline", "", NULL);
8663 webkit_dom_element_set_attribute (element, "data-name", name, NULL);
8664 webkit_dom_element_set_attribute (element, attribute, base64_data, NULL);
8666 g_free (name);
8668 g_free (attribute_value);
8671 static void
8672 change_cid_images_src_to_base64 (EEditorPage *editor_page)
8674 WebKitDOMDocument *document;
8675 WebKitDOMElement *document_element;
8676 WebKitDOMNamedNodeMap *attributes = NULL;
8677 WebKitDOMNodeList *list = NULL;
8678 GHashTable *inline_images;
8679 gint ii, length;
8681 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
8683 document = e_editor_page_get_document (editor_page);
8684 inline_images = e_editor_page_get_inline_images (editor_page);
8686 document_element = webkit_dom_document_get_document_element (document);
8688 list = webkit_dom_document_query_selector_all (document, "img[src^=\"cid:\"]", NULL);
8689 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
8690 WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
8692 set_base64_to_element_attribute (inline_images, WEBKIT_DOM_ELEMENT (node), "src");
8694 g_clear_object (&list);
8696 /* Namespaces */
8697 attributes = webkit_dom_element_get_attributes (document_element);
8698 length = webkit_dom_named_node_map_get_length (attributes);
8699 for (ii = 0; ii < length; ii++) {
8700 gchar *name;
8701 WebKitDOMAttr *attribute = WEBKIT_DOM_ATTR( webkit_dom_named_node_map_item (attributes, ii));
8703 name = webkit_dom_attr_get_name (attribute);
8705 if (g_str_has_prefix (name, "xmlns:")) {
8706 const gchar *ns = name + 6;
8707 gchar *attribute_ns = g_strconcat (ns, ":src", NULL);
8708 gchar *selector = g_strconcat ("img[", ns, "\\:src^=\"cid:\"]", NULL);
8709 gint jj;
8711 list = webkit_dom_document_query_selector_all (
8712 document, selector, NULL);
8713 for (jj = webkit_dom_node_list_get_length (list); jj--;) {
8714 WebKitDOMNode *node = webkit_dom_node_list_item (list, jj);
8716 set_base64_to_element_attribute (
8717 inline_images, WEBKIT_DOM_ELEMENT (node), attribute_ns);
8720 g_clear_object (&list);
8721 g_free (attribute_ns);
8722 g_free (selector);
8724 g_free (name);
8726 g_clear_object (&attributes);
8728 list = webkit_dom_document_query_selector_all (
8729 document, "[background^=\"cid:\"]", NULL);
8730 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
8731 WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
8733 set_base64_to_element_attribute (
8734 inline_images, WEBKIT_DOM_ELEMENT (node), "background");
8736 g_clear_object (&list);
8739 static void
8740 split_div_into_paragraphs (EEditorPage *editor_page,
8741 WebKitDOMDocument *document,
8742 WebKitDOMNode *element,
8743 WebKitDOMNode *parent)
8745 WebKitDOMNode *node, *new_div = NULL;
8747 if (!element || !parent || !WEBKIT_DOM_IS_HTML_DIV_ELEMENT (element))
8748 return;
8750 node = webkit_dom_node_get_first_child (element);
8751 while (node) {
8752 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (node)) {
8753 if (!new_div) {
8754 new_div = WEBKIT_DOM_NODE (e_editor_dom_get_paragraph_element (editor_page, -1, 0));
8755 webkit_dom_node_insert_before (parent, new_div, element, NULL);
8756 webkit_dom_node_append_child (new_div, webkit_dom_node_clone_node_with_error (node, TRUE, NULL), NULL);
8759 new_div = NULL;
8760 } else if (WEBKIT_DOM_IS_HTML_PRE_ELEMENT (node) ||
8761 WEBKIT_DOM_IS_HTML_O_LIST_ELEMENT (node) ||
8762 WEBKIT_DOM_IS_HTML_U_LIST_ELEMENT (node)) {
8763 new_div = NULL;
8764 webkit_dom_node_insert_before (parent, webkit_dom_node_clone_node_with_error (node, TRUE, NULL), element, NULL);
8765 } else {
8766 if (!new_div) {
8767 new_div = WEBKIT_DOM_NODE (e_editor_dom_get_paragraph_element (editor_page, -1, 0));
8768 webkit_dom_node_insert_before (parent, new_div, element, NULL);
8771 webkit_dom_node_append_child (new_div, webkit_dom_node_clone_node_with_error (node, TRUE, NULL), NULL);
8774 node = webkit_dom_node_get_next_sibling (node);
8777 webkit_dom_node_remove_child (parent, element, NULL);
8780 void
8781 e_editor_dom_adapt_to_editor_dom_changes (EEditorPage *editor_page)
8783 WebKitDOMDocument *document;
8784 WebKitDOMHTMLCollection *collection = NULL;
8785 gint ii;
8787 document = e_editor_page_get_document (editor_page);
8789 /* Normal block code div.-x-evo-paragraph replaced by div[data-evo-paragraph] */
8790 collection = webkit_dom_document_get_elements_by_class_name_as_html_collection (document, "-x-evo-paragraph");
8791 for (ii = webkit_dom_html_collection_get_length (collection); ii--;) {
8792 WebKitDOMNode *node;
8793 WebKitDOMElement *parent_element;
8795 node = webkit_dom_html_collection_item (collection, ii);
8796 element_remove_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-paragraph");
8797 webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "data-evo-paragraph", "", NULL);
8799 parent_element = webkit_dom_node_get_parent_element (node);
8801 if (parent_element)
8802 split_div_into_paragraphs (editor_page, document, node, WEBKIT_DOM_NODE (parent_element));
8804 g_clear_object (&collection);
8807 void
8808 e_editor_dom_process_content_after_load (EEditorPage *editor_page)
8810 gboolean html_mode;
8811 gint16 start_at_bottom = -1, top_signature = -1;
8812 WebKitDOMDocument *document;
8813 WebKitDOMHTMLElement *body;
8814 WebKitDOMDOMWindow *dom_window = NULL;
8816 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
8818 document = e_editor_page_get_document (editor_page);
8820 /* Don't use CSS when possible to preserve compatibility with older
8821 * versions of Evolution or other MUAs */
8822 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_STYLE_WITH_CSS, "false");
8823 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_DEFAULT_PARAGRAPH_SEPARATOR, "div");
8825 body = webkit_dom_document_get_body (document);
8827 webkit_dom_element_remove_attribute (webkit_dom_document_get_document_element (document), "dir");
8828 webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (body), "style");
8829 html_mode = e_editor_page_get_html_mode (editor_page);
8830 if (!html_mode)
8831 webkit_dom_element_set_attribute (
8832 WEBKIT_DOM_ELEMENT (body), "data-evo-plain-text", "", NULL);
8834 if (e_editor_page_get_convert_in_situ (editor_page, &start_at_bottom, &top_signature)) {
8835 e_editor_dom_convert_content (editor_page, NULL, start_at_bottom, top_signature);
8836 /* The BODY could be replaced during the conversion */
8837 body = webkit_dom_document_get_body (document);
8838 /* Make the quote marks non-selectable. */
8839 e_editor_dom_disable_quote_marks_select (editor_page);
8840 dom_set_links_active (document, FALSE);
8841 e_editor_page_set_convert_in_situ (editor_page, FALSE, -1, -1);
8843 /* The composer body could be empty in some case (loading an empty string
8844 * or empty HTML). In that case create the initial paragraph. */
8845 if (!webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body))) {
8846 WebKitDOMElement *paragraph;
8848 paragraph = e_editor_dom_prepare_paragraph (editor_page, TRUE);
8849 webkit_dom_element_set_id (paragraph, "-x-evo-input-start");
8850 webkit_dom_node_append_child (
8851 WEBKIT_DOM_NODE (body), WEBKIT_DOM_NODE (paragraph), NULL);
8852 e_editor_dom_selection_restore (editor_page);
8855 goto out;
8856 } else {
8857 WebKitDOMNodeList *list;
8858 gulong ii;
8860 list = webkit_dom_document_query_selector_all (document, "pre", NULL);
8861 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
8862 WebKitDOMNode *node = webkit_dom_node_list_item (list, ii), *parent;
8863 WebKitDOMElement *element;
8864 gchar *inner_html;
8866 element = WEBKIT_DOM_ELEMENT (node);
8867 parent = webkit_dom_node_get_parent_node (node);
8868 inner_html = webkit_dom_element_get_inner_html (element);
8870 if (inner_html && *inner_html) {
8871 gchar **strv;
8873 strv = g_strsplit (inner_html, "\n", -1);
8874 if (strv && strv[0] && strv[1]) {
8875 WebKitDOMElement *pre;
8876 gint jj;
8878 for (jj = 0; strv[jj]; jj++) {
8879 pre = webkit_dom_document_create_element (document, "pre", NULL);
8880 if (*(strv[jj])) {
8881 gint len = strlen (strv[jj]);
8883 if (strv[jj][len - 1] == '\r') {
8884 strv[jj][len - 1] = '\0';
8888 if (*(strv[jj])) {
8889 webkit_dom_html_element_set_inner_html (WEBKIT_DOM_HTML_ELEMENT (pre), strv[jj], NULL);
8890 } else {
8891 WebKitDOMElement *br;
8893 br = webkit_dom_document_create_element (document, "br", NULL);
8894 webkit_dom_node_append_child (WEBKIT_DOM_NODE (pre), WEBKIT_DOM_NODE (br), NULL);
8897 webkit_dom_node_insert_before (parent, WEBKIT_DOM_NODE (pre), node, NULL);
8900 remove_node (node);
8903 g_strfreev (strv);
8906 g_free (inner_html);
8909 g_clear_object (&list);
8912 e_editor_dom_adapt_to_editor_dom_changes (editor_page);
8914 /* Make the quote marks non-selectable. */
8915 e_editor_dom_disable_quote_marks_select (editor_page);
8916 dom_set_links_active (document, FALSE);
8917 put_body_in_citation (document);
8918 move_elements_to_body (editor_page);
8919 repair_blockquotes (document);
8920 remove_thunderbird_signature (document);
8922 if (webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (body), "data-evo-draft")) {
8923 /* Restore the selection how it was when the draft was saved */
8924 e_editor_dom_move_caret_into_element (editor_page, WEBKIT_DOM_ELEMENT (body), FALSE);
8925 e_editor_dom_selection_restore (editor_page);
8926 e_editor_dom_remove_embedded_style_sheet (editor_page);
8929 /* The composer body could be empty in some case (loading an empty string
8930 * or empty HTML. In that case create the initial paragraph. */
8931 if (!webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body))) {
8932 WebKitDOMElement *paragraph;
8934 paragraph = e_editor_dom_prepare_paragraph (editor_page, TRUE);
8935 webkit_dom_element_set_id (paragraph, "-x-evo-input-start");
8936 webkit_dom_node_append_child (
8937 WEBKIT_DOM_NODE (body), WEBKIT_DOM_NODE (paragraph), NULL);
8938 e_editor_dom_selection_restore (editor_page);
8941 e_editor_dom_fix_file_uri_images (editor_page);
8942 change_cid_images_src_to_base64 (editor_page);
8944 out:
8945 webkit_dom_element_set_attribute (webkit_dom_document_get_document_element (document), "dir", "ltr", NULL);
8946 webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (body), "style", "text-align:left; direction:ltr;", NULL);
8948 /* Register on input event that is called when the content (body) is modified */
8949 e_editor_dom_register_input_event_listener_on_body (editor_page);
8950 register_html_events_handlers (editor_page, body);
8952 if (e_editor_page_get_inline_spelling_enabled (editor_page))
8953 e_editor_dom_force_spell_check_in_viewport (editor_page);
8954 else
8955 e_editor_dom_turn_spell_check_off (editor_page);
8957 e_editor_dom_scroll_to_caret (editor_page);
8959 dom_window = webkit_dom_document_get_default_view (document);
8961 webkit_dom_event_target_add_event_listener (
8962 WEBKIT_DOM_EVENT_TARGET (dom_window),
8963 "scroll",
8964 G_CALLBACK (body_scroll_event_cb),
8965 FALSE,
8966 editor_page);
8968 /* Intentionally leak the WebKitDOMDOMWindow object here as otherwise the
8969 * callback won't be set up. */
8972 static gchar *
8973 encode_to_base64_data (const gchar *src_uri,
8974 gchar **data_name)
8976 GFile *file;
8977 GFileInfo *info;
8978 gchar *filename, *data = NULL;
8980 g_return_val_if_fail (src_uri != NULL, NULL);
8982 file = g_file_new_for_uri (src_uri);
8983 if (!file)
8984 return NULL;
8986 filename = g_file_get_path (file);
8987 if (!filename) {
8988 g_object_unref (file);
8989 return NULL;
8992 info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
8993 G_FILE_QUERY_INFO_NONE, NULL, NULL);
8995 if (info) {
8996 gchar *mime_type, *content = NULL;
8997 gsize length = 0;
8999 mime_type = g_content_type_get_mime_type (g_file_info_get_content_type (info));
9001 if (mime_type && g_file_get_contents (filename, &content, &length, NULL)) {
9002 gchar *base64_encoded;
9004 if (data_name)
9005 *data_name = g_strdup (g_file_info_get_display_name (info));
9007 base64_encoded = g_base64_encode ((const guchar *) content, length);
9008 data = g_strconcat ("data:", mime_type, ";base64,", base64_encoded, NULL);
9009 g_free (base64_encoded);
9012 g_clear_object (&info);
9013 g_free (mime_type);
9014 g_free (content);
9017 g_clear_object (&file);
9018 g_free (filename);
9020 return data;
9023 GVariant *
9024 e_editor_dom_get_inline_images_data (EEditorPage *editor_page,
9025 const gchar *uid_domain)
9027 WebKitDOMDocument *document;
9028 WebKitDOMNodeList *list = NULL;
9029 GVariant *result = NULL;
9030 GVariantBuilder *builder = NULL;
9031 GHashTable *added = NULL;
9032 gint length, ii;
9034 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
9036 document = e_editor_page_get_document (editor_page);
9037 list = webkit_dom_document_query_selector_all (document, "img[src]", NULL);
9039 length = webkit_dom_node_list_get_length (list);
9040 if (length == 0) {
9041 g_clear_object (&list);
9042 goto background;
9045 builder = g_variant_builder_new (G_VARIANT_TYPE ("a(sss)"));
9047 added = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
9048 for (ii = length; ii--;) {
9049 const gchar *id;
9050 gchar *cid = NULL;
9051 WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
9052 gchar *src = webkit_dom_element_get_attribute (
9053 WEBKIT_DOM_ELEMENT (node), "src");
9055 if (!src)
9056 continue;
9058 if ((id = g_hash_table_lookup (added, src)) != NULL) {
9059 cid = g_strdup_printf ("cid:%s", id);
9060 } else if (g_ascii_strncasecmp (src, "data:", 5) == 0) {
9061 gchar *data_name = webkit_dom_element_get_attribute (
9062 WEBKIT_DOM_ELEMENT (node), "data-name");
9064 if (data_name) {
9065 gchar *new_id;
9067 new_id = camel_header_msgid_generate (uid_domain);
9068 g_variant_builder_add (
9069 builder, "(sss)", src, data_name, new_id);
9070 cid = g_strdup_printf ("cid:%s", new_id);
9072 g_hash_table_insert (added, g_strdup (src), new_id);
9074 webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "data-inline", "", NULL);
9076 g_free (data_name);
9077 } else if (g_ascii_strncasecmp (src, "file://", 7) == 0) {
9078 gchar *data, *data_name = NULL;
9080 data = encode_to_base64_data (src, &data_name);
9082 if (data && data_name) {
9083 gchar *new_id;
9085 new_id = camel_header_msgid_generate (uid_domain);
9086 g_variant_builder_add (builder, "(sss)", data, data_name, new_id);
9087 cid = g_strdup_printf ("cid:%s", new_id);
9089 g_hash_table_insert (added, data, new_id);
9091 webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "data-name", data_name, NULL);
9092 webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "data-inline", "", NULL);
9093 } else {
9094 g_free (data);
9097 g_free (data_name);
9100 if (cid) {
9101 webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "src", cid, NULL);
9102 g_free (cid);
9105 g_free (src);
9107 g_clear_object (&list);
9109 background:
9110 list = webkit_dom_document_query_selector_all (
9111 document, "[data-inline][background]", NULL);
9112 length = webkit_dom_node_list_get_length (list);
9113 if (length == 0)
9114 goto out;
9115 if (!builder)
9116 builder = g_variant_builder_new (G_VARIANT_TYPE ("a(sss)"));
9117 if (!added)
9118 added = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
9120 for (ii = length; ii--;) {
9121 const gchar *id;
9122 gchar *cid = NULL;
9123 WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
9124 gchar *src = webkit_dom_element_get_attribute (
9125 WEBKIT_DOM_ELEMENT (node), "background");
9127 if (!src)
9128 continue;
9130 if ((id = g_hash_table_lookup (added, src)) != NULL) {
9131 cid = g_strdup_printf ("cid:%s", id);
9132 webkit_dom_element_set_attribute (
9133 WEBKIT_DOM_ELEMENT (node), "background", cid, NULL);
9134 g_free (src);
9135 } else {
9136 gchar *data_name = webkit_dom_element_get_attribute (
9137 WEBKIT_DOM_ELEMENT (node), "data-name");
9139 if (data_name) {
9140 gchar *new_id;
9142 new_id = camel_header_msgid_generate (uid_domain);
9143 g_variant_builder_add (
9144 builder, "(sss)", src, data_name, new_id);
9145 cid = g_strdup_printf ("cid:%s", new_id);
9147 g_hash_table_insert (added, src, new_id);
9149 webkit_dom_element_set_attribute (
9150 WEBKIT_DOM_ELEMENT (node), "background", cid, NULL);
9152 g_free (data_name);
9154 g_free (cid);
9156 out:
9157 g_clear_object (&list);
9158 if (added)
9159 g_hash_table_destroy (added);
9161 if (builder) {
9162 result = g_variant_new ("a(sss)", builder);
9163 g_variant_builder_unref (builder);
9166 return result;
9169 static gboolean
9170 pasting_quoted_content (const gchar *content)
9172 /* Check if the content we are pasting is a quoted content from composer.
9173 * If it is, we can't use WebKit to paste it as it would leave the formatting
9174 * on the content. */
9175 return g_str_has_prefix (
9176 content,
9177 "<meta http-equiv=\"content-type\" content=\"text/html; "
9178 "charset=utf-8\"><blockquote type=\"cite\"") &&
9179 strstr (content, "\"-x-evo-");
9182 static void
9183 remove_apple_interchange_newline_elements (WebKitDOMDocument *document)
9185 gint ii;
9186 WebKitDOMHTMLCollection *collection = NULL;
9188 collection = webkit_dom_document_get_elements_by_class_name_as_html_collection (
9189 document, "Apple-interchange-newline");
9190 for (ii = webkit_dom_html_collection_get_length (collection); ii--;) {
9191 WebKitDOMNode *node = webkit_dom_html_collection_item (collection, ii);
9193 remove_node (node);
9195 g_clear_object (&collection);
9199 * e_editor_dom_insert_html:
9200 * @selection: an #EEditorSelection
9201 * @html_text: an HTML code to insert
9203 * Insert @html_text into document at current cursor position. When a text range
9204 * is selected, it will be replaced by @html_text.
9206 void
9207 e_editor_dom_insert_html (EEditorPage *editor_page,
9208 const gchar *html_text)
9210 EEditorHistoryEvent *ev = NULL;
9211 EEditorUndoRedoManager *manager;
9212 gboolean html_mode, undo_redo_in_progress;
9213 WebKitDOMDocument *document;
9214 WebKitDOMNode *block = NULL;
9216 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
9217 g_return_if_fail (html_text != NULL);
9219 document = e_editor_page_get_document (editor_page);
9221 manager = e_editor_page_get_undo_redo_manager (editor_page);
9222 undo_redo_in_progress = e_editor_undo_redo_manager_is_operation_in_progress (manager);
9223 if (!undo_redo_in_progress) {
9224 gboolean collapsed;
9226 ev = g_new0 (EEditorHistoryEvent, 1);
9227 ev->type = HISTORY_INSERT_HTML;
9229 collapsed = e_editor_dom_selection_is_collapsed (editor_page);
9230 e_editor_dom_selection_get_coordinates (editor_page,
9231 &ev->before.start.x,
9232 &ev->before.start.y,
9233 &ev->before.end.x,
9234 &ev->before.end.y);
9236 if (!collapsed) {
9237 ev->before.end.x = ev->before.start.x;
9238 ev->before.end.y = ev->before.start.y;
9241 ev->data.string.from = NULL;
9242 ev->data.string.to = g_strdup (html_text);
9245 html_mode = e_editor_page_get_html_mode (editor_page);
9246 if (html_mode ||
9247 (e_editor_page_is_pasting_content_from_itself (editor_page) &&
9248 !pasting_quoted_content (html_text))) {
9249 if (!e_editor_dom_selection_is_collapsed (editor_page)) {
9250 EEditorHistoryEvent *event;
9251 WebKitDOMDocumentFragment *fragment;
9252 WebKitDOMRange *range = NULL;
9254 event = g_new0 (EEditorHistoryEvent, 1);
9255 event->type = HISTORY_DELETE;
9257 range = e_editor_dom_get_current_range (editor_page);
9258 fragment = webkit_dom_range_clone_contents (range, NULL);
9259 g_clear_object (&range);
9260 event->data.fragment = g_object_ref (fragment);
9262 e_editor_dom_selection_get_coordinates (editor_page,
9263 &event->before.start.x,
9264 &event->before.start.y,
9265 &event->before.end.x,
9266 &event->before.end.y);
9268 event->after.start.x = event->before.start.x;
9269 event->after.start.y = event->before.start.y;
9270 event->after.end.x = event->before.start.x;
9271 event->after.end.y = event->before.start.y;
9273 e_editor_undo_redo_manager_insert_history_event (manager, event);
9275 event = g_new0 (EEditorHistoryEvent, 1);
9276 event->type = HISTORY_AND;
9278 e_editor_undo_redo_manager_insert_history_event (manager, event);
9279 } else {
9280 WebKitDOMElement *selection_marker;
9282 e_editor_dom_selection_save (editor_page);
9284 /* If current block contains just the BR element, remove
9285 * it otherwise WebKit will create a new block (with
9286 * text node that will contain '\n') on the end of inserted
9287 * content. Also remember the block and remove it if it's
9288 * empty after we insert the content. */
9289 selection_marker = webkit_dom_document_get_element_by_id (
9290 document, "-x-evo-selection-start-marker");
9292 if (!e_editor_page_is_pasting_content_from_itself (editor_page)) {
9293 if (!webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_marker))) {
9294 WebKitDOMNode *sibling;
9296 sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_marker));
9297 sibling = webkit_dom_node_get_next_sibling (sibling);
9298 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (sibling))
9299 remove_node (sibling);
9302 block = e_editor_dom_get_parent_block_node_from_child (WEBKIT_DOM_NODE (selection_marker));
9304 e_editor_dom_selection_restore (editor_page);
9307 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_INSERT_HTML, html_text);
9309 if (block)
9310 remove_node_if_empty (block);
9312 e_editor_dom_fix_file_uri_images (editor_page);
9314 if (strstr (html_text, "id=\"-x-evo-selection-start-marker\""))
9315 e_editor_dom_selection_restore (editor_page);
9317 e_editor_dom_check_magic_links (editor_page, FALSE);
9318 e_editor_dom_scroll_to_caret (editor_page);
9319 e_editor_dom_force_spell_check_in_viewport (editor_page);
9320 } else {
9321 /* Don't save history in the underlying function. */
9322 if (!undo_redo_in_progress)
9323 e_editor_undo_redo_manager_set_operation_in_progress (manager, TRUE);
9324 e_editor_dom_convert_and_insert_html_into_selection (editor_page, html_text, TRUE);
9325 if (!undo_redo_in_progress)
9326 e_editor_undo_redo_manager_set_operation_in_progress (manager, FALSE);
9329 remove_apple_interchange_newline_elements (document);
9331 if (ev) {
9332 e_editor_dom_selection_get_coordinates (editor_page,
9333 &ev->after.start.x,
9334 &ev->after.start.y,
9335 &ev->after.end.x,
9336 &ev->after.end.y);
9338 e_editor_undo_redo_manager_insert_history_event (manager, ev);
9342 static void
9343 save_history_for_delete_or_backspace (EEditorPage *editor_page,
9344 gboolean delete_key,
9345 gboolean control_key)
9347 WebKitDOMDocument *document;
9348 WebKitDOMDocumentFragment *fragment = NULL;
9349 WebKitDOMDOMWindow *dom_window = NULL;
9350 WebKitDOMDOMSelection *dom_selection = NULL;
9351 WebKitDOMRange *range = NULL;
9352 EEditorHistoryEvent *ev = NULL;
9353 EEditorUndoRedoManager *manager;
9355 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
9357 document = e_editor_page_get_document (editor_page);
9358 dom_window = webkit_dom_document_get_default_view (document);
9359 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
9360 g_clear_object (&dom_window);
9362 if (!webkit_dom_dom_selection_get_range_count (dom_selection)) {
9363 g_clear_object (&dom_selection);
9364 return;
9367 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
9369 /* Check if we can delete something */
9370 if (webkit_dom_range_get_collapsed (range, NULL)) {
9371 WebKitDOMRange *tmp_range = NULL;
9373 webkit_dom_dom_selection_modify (
9374 dom_selection, "move", delete_key ? "right" : "left", "character");
9376 tmp_range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
9377 if (webkit_dom_range_compare_boundary_points (tmp_range, WEBKIT_DOM_RANGE_END_TO_END, range, NULL) == 0) {
9378 g_clear_object (&dom_selection);
9379 g_clear_object (&range);
9380 g_clear_object (&tmp_range);
9382 return;
9385 webkit_dom_dom_selection_modify (
9386 dom_selection, "move", delete_key ? "left" : "right", "character");
9388 g_clear_object (&tmp_range);
9391 if (save_history_before_event_in_table (editor_page, range)) {
9392 g_clear_object (&range);
9393 g_clear_object (&dom_selection);
9394 return;
9397 ev = g_new0 (EEditorHistoryEvent, 1);
9398 ev->type = HISTORY_DELETE;
9400 e_editor_dom_selection_save (editor_page);
9402 e_editor_dom_selection_get_coordinates (editor_page, &ev->before.start.x, &ev->before.start.y, &ev->before.end.x, &ev->before.end.y);
9403 g_clear_object (&range);
9404 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
9406 if (webkit_dom_range_get_collapsed (range, NULL)) {
9407 gboolean removing_from_anchor = FALSE;
9408 WebKitDOMRange *range_clone = NULL;
9409 WebKitDOMNode *node;
9411 e_editor_page_block_selection_changed (editor_page);
9413 range_clone = webkit_dom_range_clone_range (range, NULL);
9414 if (control_key) {
9415 WebKitDOMRange *tmp_range = NULL;
9417 /* Control + Delete/Backspace deletes previous/next word. */
9418 webkit_dom_dom_selection_modify (
9419 dom_selection, "move", delete_key ? "right" : "left", "word");
9420 tmp_range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
9421 if (delete_key)
9422 webkit_dom_range_set_end (
9423 range_clone,
9424 webkit_dom_range_get_end_container (tmp_range, NULL),
9425 webkit_dom_range_get_end_offset (tmp_range, NULL),
9426 NULL);
9427 else
9428 webkit_dom_range_set_start (
9429 range_clone,
9430 webkit_dom_range_get_start_container (tmp_range, NULL),
9431 webkit_dom_range_get_start_offset (tmp_range, NULL),
9432 NULL);
9433 g_clear_object (&tmp_range);
9434 } else {
9435 typedef WebKitDOMNode * (*GetSibling)(WebKitDOMNode *node);
9436 WebKitDOMNode *container, *sibling;
9437 WebKitDOMElement *selection_marker;
9439 GetSibling get_sibling = delete_key ?
9440 webkit_dom_node_get_next_sibling :
9441 webkit_dom_node_get_previous_sibling;
9443 container = webkit_dom_range_get_end_container (range_clone, NULL);
9444 sibling = get_sibling (container);
9446 selection_marker = webkit_dom_document_get_element_by_id (
9447 document,
9448 delete_key ?
9449 "-x-evo-selection-end-marker" :
9450 "-x-evo-selection-start-marker");
9452 if (selection_marker) {
9453 WebKitDOMNode *tmp_sibling;
9455 tmp_sibling = get_sibling (WEBKIT_DOM_NODE (selection_marker));
9456 if (!tmp_sibling || (WEBKIT_DOM_IS_HTML_BR_ELEMENT (tmp_sibling) &&
9457 !element_has_class (WEBKIT_DOM_ELEMENT (tmp_sibling), "-x-evo-wrap-br")))
9458 sibling = WEBKIT_DOM_NODE (selection_marker);
9461 if (e_editor_dom_is_selection_position_node (sibling)) {
9462 if ((node = get_sibling (sibling)))
9463 node = get_sibling (node);
9464 if (node) {
9465 if (WEBKIT_DOM_IS_ELEMENT (node) &&
9466 webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (node), "data-hidden-space")) {
9467 fragment = webkit_dom_document_create_document_fragment (document);
9468 webkit_dom_node_append_child (
9469 WEBKIT_DOM_NODE (fragment),
9470 WEBKIT_DOM_NODE (
9471 webkit_dom_document_create_text_node (document, " ")),
9472 NULL);
9473 } else if (delete_key) {
9474 webkit_dom_range_set_start (
9475 range_clone, node, 0, NULL);
9476 webkit_dom_range_set_end (
9477 range_clone, node, 1, NULL);
9479 } else {
9480 WebKitDOMRange *tmp_range = NULL, *actual_range = NULL;
9482 actual_range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
9484 webkit_dom_dom_selection_modify (
9485 dom_selection, "move", delete_key ? "right" : "left", "character");
9487 tmp_range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
9488 if (webkit_dom_range_compare_boundary_points (tmp_range, WEBKIT_DOM_RANGE_END_TO_END, actual_range, NULL) != 0) {
9489 WebKitDOMNode *actual_block;
9490 WebKitDOMNode *tmp_block;
9492 actual_block = e_editor_dom_get_parent_block_node_from_child (container);
9494 tmp_block = delete_key ?
9495 webkit_dom_range_get_end_container (tmp_range, NULL) :
9496 webkit_dom_range_get_start_container (tmp_range, NULL);
9497 tmp_block = e_editor_dom_get_parent_block_node_from_child (tmp_block);
9499 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
9500 webkit_dom_dom_selection_add_range (dom_selection, actual_range);
9502 if (tmp_block) {
9503 if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (actual_block))
9504 actual_block = webkit_dom_node_get_parent_node (actual_block);
9506 fragment = webkit_dom_document_create_document_fragment (document);
9507 if (delete_key) {
9508 WebKitDOMNode *clone;
9510 clone = webkit_dom_node_clone_node_with_error (actual_block, TRUE, NULL);
9511 if (e_editor_dom_get_citation_level (actual_block) > 0)
9512 webkit_dom_element_set_attribute (
9513 WEBKIT_DOM_ELEMENT (clone),
9514 "data-evo-quoted",
9516 NULL);
9517 webkit_dom_node_append_child (
9518 WEBKIT_DOM_NODE (fragment), clone, NULL);
9520 clone = webkit_dom_node_clone_node_with_error (tmp_block, TRUE, NULL);
9521 if (e_editor_dom_get_citation_level (tmp_block) > 0)
9522 webkit_dom_element_set_attribute (
9523 WEBKIT_DOM_ELEMENT (clone),
9524 "data-evo-quoted",
9526 NULL);
9527 webkit_dom_node_append_child (
9528 WEBKIT_DOM_NODE (fragment), clone, NULL);
9529 } else {
9530 WebKitDOMNode *clone;
9532 clone = webkit_dom_node_clone_node_with_error (tmp_block, TRUE, NULL);
9533 if (e_editor_dom_get_citation_level (tmp_block) > 0)
9534 webkit_dom_element_set_attribute (
9535 WEBKIT_DOM_ELEMENT (clone),
9536 "data-evo-quoted",
9538 NULL);
9539 webkit_dom_node_append_child (
9540 WEBKIT_DOM_NODE (fragment), clone, NULL);
9542 clone = webkit_dom_node_clone_node_with_error (actual_block, TRUE, NULL);
9543 if (e_editor_dom_get_citation_level (tmp_block) > 0)
9544 webkit_dom_element_set_attribute (
9545 WEBKIT_DOM_ELEMENT (clone),
9546 "data-evo-quoted",
9548 NULL);
9549 webkit_dom_node_append_child (
9550 WEBKIT_DOM_NODE (fragment), clone, NULL);
9552 g_object_set_data (
9553 G_OBJECT (fragment),
9554 "history-concatenating-blocks",
9555 GINT_TO_POINTER (1));
9557 } else {
9558 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
9559 webkit_dom_dom_selection_add_range (dom_selection, actual_range);
9561 g_clear_object (&tmp_range);
9562 g_clear_object (&actual_range);
9564 } else {
9565 glong offset;
9567 /* FIXME This code is wrong for unicode smileys. */
9568 offset = webkit_dom_range_get_start_offset (range_clone, NULL);
9570 if (delete_key)
9571 webkit_dom_range_set_end (
9572 range_clone, container, offset + 1, NULL);
9573 else
9574 webkit_dom_range_set_start (
9575 range_clone, container, offset - 1, NULL);
9577 removing_from_anchor = WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (
9578 webkit_dom_node_get_parent_node (container));
9582 if (!fragment)
9583 fragment = webkit_dom_range_clone_contents (range_clone, NULL);
9584 if (removing_from_anchor)
9585 g_object_set_data (
9586 G_OBJECT (fragment),
9587 "history-removing-from-anchor",
9588 GINT_TO_POINTER (1));
9589 node = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment));
9590 if (!node) {
9591 g_free (ev);
9592 e_editor_page_unblock_selection_changed (editor_page);
9593 g_clear_object (&range);
9594 g_clear_object (&range_clone);
9595 g_clear_object (&dom_selection);
9596 g_warning ("History event was not saved for %s key", delete_key ? "Delete" : "Backspace");
9597 e_editor_dom_selection_restore (editor_page);
9598 return;
9601 if (control_key) {
9602 if (delete_key) {
9603 ev->after.start.x = ev->before.start.x;
9604 ev->after.start.y = ev->before.start.y;
9605 ev->after.end.x = ev->before.end.x;
9606 ev->after.end.y = ev->before.end.y;
9608 webkit_dom_range_collapse (range_clone, TRUE, NULL);
9609 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
9610 webkit_dom_dom_selection_add_range (dom_selection, range_clone);
9611 } else {
9612 gboolean selection_saved = FALSE;
9613 WebKitDOMRange *tmp_range = NULL;
9615 if (webkit_dom_document_get_element_by_id (document, "-x-evo-selection-start-marker"))
9616 selection_saved = TRUE;
9618 if (selection_saved)
9619 e_editor_dom_selection_restore (editor_page);
9621 tmp_range = webkit_dom_range_clone_range (range_clone, NULL);
9622 /* Prepare the selection to the right position after
9623 * delete and save it. */
9624 webkit_dom_range_collapse (range_clone, TRUE, NULL);
9625 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
9626 webkit_dom_dom_selection_add_range (dom_selection, range_clone);
9627 e_editor_dom_selection_get_coordinates (editor_page, &ev->after.start.x, &ev->after.start.y, &ev->after.end.x, &ev->after.end.y);
9628 /* Restore the selection where it was before the
9629 * history event was saved. */
9630 webkit_dom_range_collapse (tmp_range, FALSE, NULL);
9631 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
9632 webkit_dom_dom_selection_add_range (dom_selection, tmp_range);
9633 g_clear_object (&tmp_range);
9635 if (selection_saved)
9636 e_editor_dom_selection_save (editor_page);
9638 } else {
9639 gboolean selection_saved = FALSE;
9641 if (webkit_dom_document_get_element_by_id (document, "-x-evo-selection-start-marker"))
9642 selection_saved = TRUE;
9644 if (selection_saved)
9645 e_editor_dom_selection_restore (editor_page);
9647 if (delete_key) {
9648 e_editor_dom_selection_get_coordinates (editor_page, &ev->after.start.x, &ev->after.start.y, &ev->after.end.x, &ev->after.end.y);
9649 } else {
9650 webkit_dom_dom_selection_modify (dom_selection, "move", "left", "character");
9651 e_editor_dom_selection_get_coordinates (editor_page, &ev->after.start.x, &ev->after.start.y, &ev->after.end.x, &ev->after.end.y);
9652 webkit_dom_dom_selection_modify (dom_selection, "move", "right", "character");
9654 ev->after.end.x = ev->after.start.x;
9655 ev->after.end.y = ev->after.start.y;
9658 if (selection_saved)
9659 e_editor_dom_selection_save (editor_page);
9662 g_clear_object (&range_clone);
9664 if (delete_key) {
9665 if (!WEBKIT_DOM_IS_ELEMENT (node)) {
9666 webkit_dom_node_insert_before (
9667 WEBKIT_DOM_NODE (fragment),
9668 WEBKIT_DOM_NODE (
9669 dom_create_selection_marker (document, FALSE)),
9670 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment)),
9671 NULL);
9672 webkit_dom_node_insert_before (
9673 WEBKIT_DOM_NODE (fragment),
9674 WEBKIT_DOM_NODE (
9675 dom_create_selection_marker (document, TRUE)),
9676 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment)),
9677 NULL);
9679 } else {
9680 if (!WEBKIT_DOM_IS_ELEMENT (node)) {
9681 webkit_dom_node_append_child (
9682 WEBKIT_DOM_NODE (fragment),
9683 WEBKIT_DOM_NODE (
9684 dom_create_selection_marker (document, TRUE)),
9685 NULL);
9686 webkit_dom_node_append_child (
9687 WEBKIT_DOM_NODE (fragment),
9688 WEBKIT_DOM_NODE (
9689 dom_create_selection_marker (document, FALSE)),
9690 NULL);
9694 e_editor_page_unblock_selection_changed (editor_page);
9695 } else {
9696 WebKitDOMElement *tmp_element;
9697 WebKitDOMNode *sibling;
9699 ev->after.start.x = ev->before.start.x;
9700 ev->after.start.y = ev->before.start.y;
9701 ev->after.end.x = ev->before.start.x;
9702 ev->after.end.y = ev->before.start.y;
9704 fragment = webkit_dom_range_clone_contents (range, NULL);
9706 tmp_element = webkit_dom_document_fragment_query_selector (
9707 fragment, "#-x-evo-selection-start-marker", NULL);
9708 if (tmp_element)
9709 remove_node (WEBKIT_DOM_NODE (tmp_element));
9711 tmp_element = webkit_dom_document_fragment_query_selector (
9712 fragment, "#-x-evo-selection-end-marker", NULL);
9713 if (tmp_element)
9714 remove_node (WEBKIT_DOM_NODE (tmp_element));
9716 remove_empty_blocks (document);
9718 /* Selection starts in the beginning of blockquote. */
9719 tmp_element = webkit_dom_document_get_element_by_id (
9720 document, "-x-evo-selection-start-marker");
9721 sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (tmp_element));
9722 if (sibling && WEBKIT_DOM_IS_ELEMENT (sibling) &&
9723 element_has_class (WEBKIT_DOM_ELEMENT (sibling), "-x-evo-quoted")) {
9724 WebKitDOMNode *child;
9726 tmp_element = webkit_dom_document_get_element_by_id (
9727 document, "-x-evo-selection-end-marker");
9729 /* If there is no text after the selection end it means that
9730 * the block will be replaced with block that is body's descendant
9731 * and not the blockquote's one. Also if the selection started
9732 * in the beginning of blockquote we have to insert the quote
9733 * characters into the deleted content to correctly restore
9734 * them during undo/redo operations. */
9735 if (!(tmp_element && webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (tmp_element)))) {
9736 child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment));
9737 while (child && WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (child))
9738 child = webkit_dom_node_get_first_child (child);
9740 child = webkit_dom_node_get_first_child (child);
9741 if (child && (WEBKIT_DOM_IS_TEXT (child) ||
9742 (WEBKIT_DOM_IS_ELEMENT (child) &&
9743 !element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-quoted")))) {
9744 webkit_dom_node_insert_before (
9745 webkit_dom_node_get_parent_node (child),
9746 webkit_dom_node_clone_node_with_error (sibling, TRUE, NULL),
9747 child,
9748 NULL);
9753 /* When we were cloning the range above and the range contained
9754 * quoted content there will still be blockquote missing in the
9755 * final range. Let's modify the fragment and add it there. */
9756 tmp_element = webkit_dom_document_get_element_by_id (
9757 document, "-x-evo-selection-end-marker");
9758 if (tmp_element) {
9759 WebKitDOMNode *node;
9761 node = WEBKIT_DOM_NODE (tmp_element);
9762 while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (webkit_dom_node_get_parent_node (node)))
9763 node = webkit_dom_node_get_parent_node (node);
9765 if (node && WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node)) {
9766 WebKitDOMNode *last_child;
9768 last_child = webkit_dom_node_get_last_child (WEBKIT_DOM_NODE (fragment));
9770 if (last_child && !WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (last_child)) {
9771 WebKitDOMDocumentFragment *tmp_fragment;
9772 WebKitDOMNode *clone;
9774 tmp_fragment = webkit_dom_document_create_document_fragment (document);
9775 clone = webkit_dom_node_clone_node_with_error (node, FALSE, NULL);
9776 clone = webkit_dom_node_append_child (
9777 WEBKIT_DOM_NODE (tmp_fragment), clone, NULL);
9778 webkit_dom_node_append_child (clone, WEBKIT_DOM_NODE (fragment), NULL);
9779 fragment = tmp_fragment;
9784 /* FIXME Ugly hack */
9785 /* If the deleted selection contained the signature (or at least its
9786 * part) replace it with the unchanged signature to correctly perform
9787 * undo operation. */
9788 tmp_element = webkit_dom_document_fragment_query_selector (fragment, ".-x-evo-signature-wrapper", NULL);
9789 if (tmp_element) {
9790 WebKitDOMElement *signature;
9792 signature = webkit_dom_document_query_selector (document, ".-x-evo-signature-wrapper", NULL);
9793 if (signature) {
9794 webkit_dom_node_replace_child (
9795 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (tmp_element)),
9796 webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (signature), TRUE, NULL),
9797 WEBKIT_DOM_NODE (tmp_element),
9798 NULL);
9803 g_clear_object (&range);
9804 g_clear_object (&dom_selection);
9806 g_object_set_data (G_OBJECT (fragment), "history-delete-key", GINT_TO_POINTER (delete_key));
9807 g_object_set_data (G_OBJECT (fragment), "history-control-key", GINT_TO_POINTER (control_key));
9809 ev->data.fragment = g_object_ref (fragment);
9811 manager = e_editor_page_get_undo_redo_manager (editor_page);
9812 e_editor_undo_redo_manager_insert_history_event (manager, ev);
9814 e_editor_dom_selection_restore (editor_page);
9817 gboolean
9818 e_editor_dom_fix_structure_after_delete_before_quoted_content (EEditorPage *editor_page,
9819 glong key_code,
9820 gboolean control_key,
9821 gboolean delete_key)
9823 WebKitDOMDocument *document;
9824 WebKitDOMElement *selection_start_marker, *selection_end_marker;
9825 WebKitDOMNode *block, *node;
9826 gboolean collapsed = FALSE;
9828 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
9830 document = e_editor_page_get_document (editor_page);
9831 collapsed = e_editor_dom_selection_is_collapsed (editor_page);
9833 e_editor_dom_selection_save (editor_page);
9835 selection_start_marker = webkit_dom_document_get_element_by_id (
9836 document, "-x-evo-selection-start-marker");
9837 selection_end_marker = webkit_dom_document_get_element_by_id (
9838 document, "-x-evo-selection-end-marker");
9840 if (!selection_start_marker || !selection_end_marker)
9841 return FALSE;
9843 if (collapsed) {
9844 WebKitDOMNode *next_block;
9846 block = e_editor_dom_get_parent_block_node_from_child (
9847 WEBKIT_DOM_NODE (selection_start_marker));
9849 next_block = webkit_dom_node_get_next_sibling (block);
9851 /* Next block is quoted content */
9852 if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (next_block)) {
9853 e_editor_dom_selection_restore (editor_page);
9854 return FALSE;
9857 /* Delete was pressed in block without any content */
9858 if (webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_start_marker))) {
9859 e_editor_dom_selection_restore (editor_page);
9860 return FALSE;
9863 /* If there is just BR element go ahead */
9864 node = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_end_marker));
9865 if (node && !WEBKIT_DOM_IS_HTML_BR_ELEMENT (node)) {
9866 e_editor_dom_selection_restore (editor_page);
9867 return FALSE;
9868 } else {
9869 if (key_code != ~0) {
9870 e_editor_dom_selection_restore (editor_page);
9871 save_history_for_delete_or_backspace (
9872 editor_page, key_code == HTML_KEY_CODE_DELETE, control_key);
9873 } else
9874 e_editor_dom_selection_restore (editor_page);
9876 /* Remove the empty block and move caret to the right place. */
9877 remove_node (block);
9879 if (delete_key) {
9880 /* To the beginning of the next block. */
9881 while (next_block && WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (next_block))
9882 next_block = webkit_dom_node_get_first_child (next_block);
9884 if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-quoted"))
9885 next_block = webkit_dom_node_get_next_sibling (next_block);
9887 e_editor_dom_move_caret_into_element (editor_page, WEBKIT_DOM_ELEMENT (next_block), TRUE);
9888 } else {
9889 WebKitDOMNode *prev_block;
9891 /* On the end of previous block. */
9892 prev_block = webkit_dom_node_get_previous_sibling (next_block);
9893 while (prev_block && WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (prev_block))
9894 prev_block = webkit_dom_node_get_last_child (prev_block);
9896 if (prev_block)
9897 e_editor_dom_move_caret_into_element (editor_page, WEBKIT_DOM_ELEMENT (prev_block), FALSE);
9900 return TRUE;
9904 e_editor_dom_selection_restore (editor_page);
9906 return FALSE;
9909 static gboolean
9910 split_citation (EEditorPage *editor_page)
9912 WebKitDOMDocument *document;
9913 WebKitDOMElement *element;
9914 EEditorHistoryEvent *ev = NULL;
9915 EEditorUndoRedoManager *manager;
9917 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
9919 document = e_editor_page_get_document (editor_page);
9920 manager = e_editor_page_get_undo_redo_manager (editor_page);
9922 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
9923 WebKitDOMElement *selection_end;
9924 WebKitDOMNode *sibling;
9926 ev = g_new0 (EEditorHistoryEvent, 1);
9927 ev->type = HISTORY_CITATION_SPLIT;
9929 e_editor_dom_selection_save (editor_page);
9931 e_editor_dom_selection_get_coordinates (editor_page, &ev->before.start.x, &ev->before.start.y, &ev->before.end.x, &ev->before.end.y);
9933 if (!e_editor_dom_selection_is_collapsed (editor_page)) {
9934 WebKitDOMRange *range = NULL;
9936 range = e_editor_dom_get_current_range (editor_page);
9937 insert_delete_event (editor_page, range);
9939 g_clear_object (&range);
9941 ev->before.end.x = ev->before.start.x;
9942 ev->before.end.y = ev->before.start.y;
9945 selection_end = webkit_dom_document_get_element_by_id (
9946 document, "-x-evo-selection-end-marker");
9948 sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_end));
9949 if (!sibling || (WEBKIT_DOM_IS_HTML_BR_ELEMENT (sibling) &&
9950 !element_has_class (WEBKIT_DOM_ELEMENT (sibling), "-x-evo-wrap-br"))) {
9951 WebKitDOMDocumentFragment *fragment;
9953 fragment = webkit_dom_document_create_document_fragment (document);
9954 ev->data.fragment = g_object_ref (fragment);
9955 } else
9956 ev->data.fragment = NULL;
9958 e_editor_dom_selection_restore (editor_page);
9961 element = e_editor_dom_insert_new_line_into_citation (editor_page, "");
9963 if (ev) {
9964 e_editor_dom_selection_get_coordinates (editor_page, &ev->after.start.x, &ev->after.start.y, &ev->after.end.x, &ev->after.end.y);
9966 e_editor_undo_redo_manager_insert_history_event (manager, ev);
9969 return element != NULL;
9972 static gboolean
9973 delete_last_character_from_previous_line_in_quoted_block (EEditorPage *editor_page,
9974 glong key_code,
9975 guint state)
9977 WebKitDOMDocument *document;
9978 WebKitDOMDocumentFragment *fragment = NULL;
9979 WebKitDOMElement *element;
9980 WebKitDOMNode *node, *beginning, *prev_sibling;
9981 EEditorHistoryEvent *ev = NULL;
9982 gboolean hidden_space = FALSE;
9984 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
9986 /* We have to be in quoted content. */
9987 if (!e_editor_dom_selection_is_citation (editor_page))
9988 return FALSE;
9990 /* Selection is just caret. */
9991 if (!e_editor_dom_selection_is_collapsed (editor_page))
9992 return FALSE;
9994 document = e_editor_page_get_document (editor_page);
9996 e_editor_dom_selection_save (editor_page);
9998 element = webkit_dom_document_get_element_by_id (
9999 document, "-x-evo-selection-start-marker");
10001 /* Before the caret are just quote characters */
10002 beginning = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element));
10003 if (!(beginning && WEBKIT_DOM_IS_ELEMENT (beginning))) {
10004 WebKitDOMNode *parent;
10006 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
10007 if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent))
10008 beginning = webkit_dom_node_get_previous_sibling (parent);
10009 else
10010 goto out;
10013 /* Before the text is the beginning of line. */
10014 if (!(element_has_class (WEBKIT_DOM_ELEMENT (beginning), "-x-evo-quoted")))
10015 goto out;
10017 /* If we are just on the beginning of the line and not on the beginning of
10018 * the block we need to remove the last character ourselves as well, otherwise
10019 * WebKit will put the caret to wrong position. */
10020 if (!(prev_sibling = webkit_dom_node_get_previous_sibling (beginning)))
10021 goto out;
10023 if (key_code != ~0) {
10024 ev = g_new0 (EEditorHistoryEvent, 1);
10025 ev->type = HISTORY_DELETE;
10027 e_editor_dom_selection_get_coordinates (editor_page,
10028 &ev->before.start.x,
10029 &ev->before.start.y,
10030 &ev->before.end.x,
10031 &ev->before.end.y);
10033 fragment = webkit_dom_document_create_document_fragment (document);
10036 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (prev_sibling)) {
10037 if (key_code != ~0)
10038 webkit_dom_node_append_child (WEBKIT_DOM_NODE (fragment), prev_sibling, NULL);
10039 else
10040 remove_node (prev_sibling);
10043 prev_sibling = webkit_dom_node_get_previous_sibling (beginning);
10044 if (WEBKIT_DOM_IS_ELEMENT (prev_sibling) &&
10045 webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (prev_sibling), "data-hidden-space")) {
10046 hidden_space = TRUE;
10047 if (key_code != ~0)
10048 webkit_dom_node_insert_before (
10049 WEBKIT_DOM_NODE (fragment),
10050 prev_sibling,
10051 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment)),
10052 NULL);
10053 else
10054 remove_node (prev_sibling);
10057 node = webkit_dom_node_get_previous_sibling (beginning);
10059 if (key_code != ~0)
10060 webkit_dom_node_append_child (WEBKIT_DOM_NODE (fragment), beginning, NULL);
10061 else
10062 remove_node (beginning);
10064 if (!hidden_space) {
10065 if (key_code != ~0) {
10066 gchar *data;
10068 data = webkit_dom_character_data_substring_data (
10069 WEBKIT_DOM_CHARACTER_DATA (node),
10070 webkit_dom_character_data_get_length (
10071 WEBKIT_DOM_CHARACTER_DATA (node)) -1,
10073 NULL);
10075 webkit_dom_node_append_child (
10076 WEBKIT_DOM_NODE (fragment),
10077 WEBKIT_DOM_NODE (
10078 webkit_dom_document_create_text_node (document, data)),
10079 NULL);
10081 g_free (data);
10084 webkit_dom_character_data_delete_data (
10085 WEBKIT_DOM_CHARACTER_DATA (node),
10086 webkit_dom_character_data_get_length (
10087 WEBKIT_DOM_CHARACTER_DATA (node)) -1,
10089 NULL);
10092 if (key_code != ~0) {
10093 EEditorUndoRedoManager *manager;
10095 e_editor_dom_selection_get_coordinates (editor_page,
10096 &ev->after.start.x,
10097 &ev->after.start.y,
10098 &ev->after.end.x,
10099 &ev->after.end.y);
10101 ev->data.fragment = g_object_ref (fragment);
10103 manager = e_editor_page_get_undo_redo_manager (editor_page);
10104 e_editor_undo_redo_manager_insert_history_event (manager, ev);
10107 e_editor_dom_selection_restore (editor_page);
10109 return TRUE;
10110 out:
10111 e_editor_dom_selection_restore (editor_page);
10113 return FALSE;
10116 gboolean
10117 e_editor_dom_delete_last_character_on_line_in_quoted_block (EEditorPage *editor_page,
10118 glong key_code,
10119 gboolean control_key)
10121 WebKitDOMDocument *document;
10122 WebKitDOMElement *element;
10123 WebKitDOMNode *node, *beginning, *next_sibling;
10124 gboolean success = FALSE;
10126 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
10128 document = e_editor_page_get_document (editor_page);
10130 /* We have to be in quoted content. */
10131 if (!e_editor_dom_selection_is_citation (editor_page))
10132 return FALSE;
10134 /* Selection is just caret. */
10135 if (!e_editor_dom_selection_is_collapsed (editor_page))
10136 return FALSE;
10138 e_editor_dom_selection_save (editor_page);
10140 element = webkit_dom_document_get_element_by_id (
10141 document, "-x-evo-selection-start-marker");
10143 /* selection end marker */
10144 node = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element));
10146 /* We have to be on the end of line. */
10147 next_sibling = webkit_dom_node_get_next_sibling (node);
10148 if (next_sibling &&
10149 (!WEBKIT_DOM_IS_HTML_BR_ELEMENT (next_sibling) ||
10150 webkit_dom_node_get_next_sibling (next_sibling)))
10151 goto out;
10153 /* Before the caret is just text. */
10154 node = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element));
10155 if (!(node && WEBKIT_DOM_IS_TEXT (node)))
10156 goto out;
10158 /* There is just one character. */
10159 if (webkit_dom_character_data_get_length (WEBKIT_DOM_CHARACTER_DATA (node)) != 1)
10160 goto out;
10162 beginning = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (node));
10163 if (!(beginning && WEBKIT_DOM_IS_ELEMENT (beginning)))
10164 goto out;
10166 /* Before the text is the beginning of line. */
10167 if (!(element_has_class (WEBKIT_DOM_ELEMENT (beginning), "-x-evo-quoted")))
10168 goto out;
10170 if (!webkit_dom_node_get_previous_sibling (beginning))
10171 goto out;
10173 if (key_code != ~0) {
10174 e_editor_dom_selection_restore (editor_page);
10175 save_history_for_delete_or_backspace (
10176 editor_page, key_code == HTML_KEY_CODE_DELETE, control_key);
10177 e_editor_dom_selection_save (editor_page);
10180 element = webkit_dom_node_get_parent_element (beginning);
10181 remove_node (WEBKIT_DOM_NODE (element));
10183 success = TRUE;
10184 out:
10185 e_editor_dom_selection_restore (editor_page);
10187 if (success)
10188 e_editor_dom_insert_new_line_into_citation (editor_page, NULL);
10190 return success;
10193 static gboolean
10194 selection_is_in_empty_list_item (WebKitDOMNode *selection_start_marker)
10196 gchar *text;
10197 WebKitDOMNode *sibling;
10199 /* Selection needs to be collapsed. */
10200 sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_start_marker));
10201 if (!e_editor_dom_is_selection_position_node (sibling))
10202 return FALSE;
10204 /* After the selection end there could be just the BR element. */
10205 sibling = webkit_dom_node_get_next_sibling (sibling);
10206 if (sibling && !WEBKIT_DOM_IS_HTML_BR_ELEMENT (sibling))
10207 return FALSE;
10209 if (sibling && webkit_dom_node_get_next_sibling (sibling))
10210 return FALSE;
10212 sibling = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_start_marker));
10214 if (!sibling)
10215 return TRUE;
10217 /* Only text node with the zero width space character is allowed. */
10218 if (!WEBKIT_DOM_IS_TEXT (sibling))
10219 return FALSE;
10221 if (webkit_dom_node_get_previous_sibling (sibling))
10222 return FALSE;
10224 if (webkit_dom_character_data_get_length (WEBKIT_DOM_CHARACTER_DATA (sibling)) != 1)
10225 return FALSE;
10227 text = webkit_dom_character_data_get_data (WEBKIT_DOM_CHARACTER_DATA (sibling));
10228 if (!(text && g_strcmp0 (text, UNICODE_ZERO_WIDTH_SPACE) == 0)) {
10229 g_free (text);
10230 return FALSE;
10233 g_free (text);
10235 return TRUE;
10238 static gboolean
10239 return_pressed_in_image_wrapper (EEditorPage *editor_page)
10241 WebKitDOMDocument *document;
10242 WebKitDOMDocumentFragment *fragment;
10243 WebKitDOMElement *selection_start_marker;
10244 WebKitDOMNode *parent, *block, *clone;
10245 EEditorHistoryEvent *ev = NULL;
10246 EEditorUndoRedoManager *manager;
10248 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
10250 document = e_editor_page_get_document (editor_page);
10252 if (!e_editor_dom_selection_is_collapsed (editor_page))
10253 return FALSE;
10255 e_editor_dom_selection_save (editor_page);
10257 selection_start_marker = webkit_dom_document_get_element_by_id (
10258 document, "-x-evo-selection-start-marker");
10260 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start_marker));
10261 if (!element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-resizable-wrapper")) {
10262 e_editor_dom_selection_restore (editor_page);
10263 return FALSE;
10266 manager = e_editor_page_get_undo_redo_manager (editor_page);
10268 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
10269 ev = g_new0 (EEditorHistoryEvent, 1);
10270 ev->type = HISTORY_INPUT;
10272 e_editor_dom_selection_get_coordinates (editor_page,
10273 &ev->before.start.x,
10274 &ev->before.start.y,
10275 &ev->before.end.x,
10276 &ev->before.end.y);
10278 fragment = webkit_dom_document_create_document_fragment (document);
10280 g_object_set_data (
10281 G_OBJECT (fragment), "history-return-key", GINT_TO_POINTER (1));
10284 block = e_editor_dom_get_parent_block_node_from_child (
10285 WEBKIT_DOM_NODE (selection_start_marker));
10287 clone = webkit_dom_node_clone_node_with_error (block, FALSE, NULL);
10288 webkit_dom_node_append_child (
10289 clone, WEBKIT_DOM_NODE (webkit_dom_document_create_element (document, "br", NULL)), NULL);
10291 webkit_dom_node_insert_before (
10292 webkit_dom_node_get_parent_node (block),
10293 clone,
10294 block,
10295 NULL);
10297 if (ev) {
10298 webkit_dom_node_append_child (
10299 WEBKIT_DOM_NODE (fragment),
10300 webkit_dom_node_clone_node_with_error (clone, TRUE, NULL),
10301 NULL);
10303 e_editor_dom_selection_get_coordinates (editor_page,
10304 &ev->after.start.x,
10305 &ev->after.start.y,
10306 &ev->after.end.x,
10307 &ev->after.end.y);
10309 ev->data.fragment = g_object_ref (fragment);
10311 e_editor_undo_redo_manager_insert_history_event (manager, ev);
10314 e_editor_page_emit_content_changed (editor_page);
10316 e_editor_dom_selection_restore (editor_page);
10318 return TRUE;
10321 static gboolean
10322 return_pressed_after_h_rule (EEditorPage *editor_page)
10324 WebKitDOMDocument *document;
10325 WebKitDOMDocumentFragment *fragment;
10326 WebKitDOMElement *selection_marker;
10327 WebKitDOMNode *node, *block, *clone, *hr, *insert_before = NULL;
10328 EEditorHistoryEvent *ev = NULL;
10329 EEditorUndoRedoManager *manager;
10331 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
10333 document = e_editor_page_get_document (editor_page);
10335 if (!e_editor_dom_selection_is_collapsed (editor_page))
10336 return FALSE;
10338 e_editor_dom_selection_save (editor_page);
10340 manager = e_editor_page_get_undo_redo_manager (editor_page);
10342 if (e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
10343 selection_marker = webkit_dom_document_get_element_by_id (
10344 document, "-x-evo-selection-end-marker");
10346 hr = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_marker));
10347 hr = webkit_dom_node_get_next_sibling (hr);
10348 node = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_marker));
10349 if (!node || !WEBKIT_DOM_IS_HTML_BR_ELEMENT (node) || !hr ||
10350 !WEBKIT_DOM_IS_HTML_HR_ELEMENT (hr)) {
10351 e_editor_dom_selection_restore (editor_page);
10352 return FALSE;
10355 insert_before = webkit_dom_node_get_next_sibling (hr);
10356 } else {
10357 selection_marker = webkit_dom_document_get_element_by_id (
10358 document, "-x-evo-selection-start-marker");
10360 node = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_marker));
10361 hr = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_marker));
10362 if (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node) ||
10363 !WEBKIT_DOM_IS_HTML_HR_ELEMENT (hr)) {
10364 e_editor_dom_selection_restore (editor_page);
10365 return FALSE;
10368 insert_before = WEBKIT_DOM_NODE (selection_marker);
10370 ev = g_new0 (EEditorHistoryEvent, 1);
10371 ev->type = HISTORY_INPUT;
10373 e_editor_dom_selection_get_coordinates (editor_page,
10374 &ev->before.start.x,
10375 &ev->before.start.y,
10376 &ev->before.end.x,
10377 &ev->before.end.y);
10379 fragment = webkit_dom_document_create_document_fragment (document);
10381 g_object_set_data (
10382 G_OBJECT (fragment), "history-return-key", GINT_TO_POINTER (1));
10385 block = webkit_dom_node_get_previous_sibling (hr);
10387 clone = webkit_dom_node_clone_node_with_error (block, FALSE, NULL);
10389 webkit_dom_node_append_child (
10390 clone, WEBKIT_DOM_NODE (webkit_dom_document_create_element (document, "br", NULL)), NULL);
10392 webkit_dom_node_insert_before (
10393 webkit_dom_node_get_parent_node (hr), clone, insert_before, NULL);
10395 dom_remove_selection_markers (document);
10397 webkit_dom_node_append_child (
10398 WEBKIT_DOM_NODE (clone),
10399 WEBKIT_DOM_NODE (
10400 dom_create_selection_marker (document, TRUE)),
10401 NULL);
10402 webkit_dom_node_append_child (
10403 WEBKIT_DOM_NODE (clone),
10404 WEBKIT_DOM_NODE (
10405 dom_create_selection_marker (document, FALSE)),
10406 NULL);
10408 if (ev) {
10409 webkit_dom_node_append_child (
10410 WEBKIT_DOM_NODE (fragment),
10411 webkit_dom_node_clone_node_with_error (clone, TRUE, NULL),
10412 NULL);
10414 e_editor_dom_selection_get_coordinates (editor_page,
10415 &ev->after.start.x,
10416 &ev->after.start.y,
10417 &ev->after.end.x,
10418 &ev->after.end.y);
10420 ev->data.fragment = g_object_ref (fragment);
10422 e_editor_undo_redo_manager_insert_history_event (manager, ev);
10425 e_editor_page_emit_content_changed (editor_page);
10427 e_editor_dom_selection_restore (editor_page);
10429 return TRUE;
10432 gboolean
10433 e_editor_dom_return_pressed_in_empty_list_item (EEditorPage *editor_page)
10435 WebKitDOMDocument *document;
10436 WebKitDOMElement *selection_start_marker;
10437 WebKitDOMNode *parent;
10439 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
10441 document = e_editor_page_get_document (editor_page);
10443 if (!e_editor_dom_selection_is_collapsed (editor_page))
10444 return FALSE;
10446 e_editor_dom_selection_save (editor_page);
10448 selection_start_marker = webkit_dom_document_get_element_by_id (
10449 document, "-x-evo-selection-start-marker");
10451 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start_marker));
10452 if (!WEBKIT_DOM_IS_HTML_LI_ELEMENT (parent)) {
10453 e_editor_dom_selection_restore (editor_page);
10454 return FALSE;
10457 if (selection_is_in_empty_list_item (WEBKIT_DOM_NODE (selection_start_marker))) {
10458 EEditorHistoryEvent *ev = NULL;
10459 EEditorUndoRedoManager *manager;
10460 WebKitDOMDocumentFragment *fragment;
10461 WebKitDOMElement *paragraph;
10462 WebKitDOMNode *list;
10464 manager = e_editor_page_get_undo_redo_manager (editor_page);
10466 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
10467 ev = g_new0 (EEditorHistoryEvent, 1);
10468 ev->type = HISTORY_INPUT;
10470 e_editor_dom_selection_get_coordinates (editor_page,
10471 &ev->before.start.x,
10472 &ev->before.start.y,
10473 &ev->before.end.x,
10474 &ev->before.end.y);
10476 fragment = webkit_dom_document_create_document_fragment (document);
10478 g_object_set_data (
10479 G_OBJECT (fragment), "history-return-key", GINT_TO_POINTER (1));
10482 list = split_list_into_two (parent, -1);
10484 if (ev) {
10485 webkit_dom_node_append_child (
10486 WEBKIT_DOM_NODE (fragment),
10487 parent,
10488 NULL);
10489 } else {
10490 remove_node (parent);
10493 paragraph = e_editor_dom_prepare_paragraph (editor_page, TRUE);
10495 webkit_dom_node_insert_before (
10496 webkit_dom_node_get_parent_node (list),
10497 WEBKIT_DOM_NODE (paragraph),
10498 list,
10499 NULL);
10501 remove_node_if_empty (list);
10503 if (ev) {
10504 e_editor_dom_selection_get_coordinates (editor_page,
10505 &ev->after.start.x,
10506 &ev->after.start.y,
10507 &ev->after.end.x,
10508 &ev->after.end.y);
10510 ev->data.fragment = g_object_ref (fragment);
10512 e_editor_undo_redo_manager_insert_history_event (manager, ev);
10515 e_editor_dom_selection_restore (editor_page);
10517 e_editor_page_emit_content_changed (editor_page);
10519 return TRUE;
10522 e_editor_dom_selection_restore (editor_page);
10524 return FALSE;
10527 static void
10528 process_smiley_on_delete_or_backspace (EEditorPage *editor_page)
10530 WebKitDOMDocument *document;
10531 WebKitDOMElement *element;
10532 WebKitDOMNode *parent;
10533 gboolean in_smiley = FALSE;
10535 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
10537 document = e_editor_page_get_document (editor_page);
10538 e_editor_dom_selection_save (editor_page);
10539 element = webkit_dom_document_get_element_by_id (
10540 document, "-x-evo-selection-start-marker");
10542 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
10543 if (WEBKIT_DOM_IS_ELEMENT (parent) &&
10544 element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-smiley-text"))
10545 in_smiley = TRUE;
10546 else {
10547 if (e_editor_dom_selection_is_collapsed (editor_page)) {
10548 WebKitDOMNode *prev_sibling;
10550 prev_sibling = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element));
10551 if (prev_sibling && WEBKIT_DOM_IS_TEXT (prev_sibling)) {
10552 gchar *text = webkit_dom_character_data_get_data (
10553 WEBKIT_DOM_CHARACTER_DATA (prev_sibling));
10555 if (g_strcmp0 (text, UNICODE_ZERO_WIDTH_SPACE) == 0) {
10556 WebKitDOMNode *prev_prev_sibling;
10558 prev_prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling);
10559 if (WEBKIT_DOM_IS_ELEMENT (prev_prev_sibling) &&
10560 element_has_class (WEBKIT_DOM_ELEMENT (prev_prev_sibling), "-x-evo-smiley-wrapper")) {
10561 remove_node (prev_sibling);
10562 in_smiley = TRUE;
10563 parent = webkit_dom_node_get_last_child (prev_prev_sibling);
10567 g_free (text);
10569 } else {
10570 element = webkit_dom_document_get_element_by_id (
10571 document, "-x-evo-selection-end-marker");
10573 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
10574 if (WEBKIT_DOM_IS_ELEMENT (parent) &&
10575 element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-smiley-text"))
10576 in_smiley = TRUE;
10580 if (in_smiley) {
10581 WebKitDOMNode *wrapper;
10583 wrapper = webkit_dom_node_get_parent_node (parent);
10584 if (!e_editor_page_get_html_mode (editor_page)) {
10585 WebKitDOMNode *child;
10587 while ((child = webkit_dom_node_get_first_child (parent)))
10588 webkit_dom_node_insert_before (
10589 webkit_dom_node_get_parent_node (wrapper),
10590 child,
10591 wrapper,
10592 NULL);
10594 /* In the HTML mode the whole smiley will be removed. */
10595 remove_node (wrapper);
10596 /* FIXME history will be probably broken here */
10599 e_editor_dom_selection_restore (editor_page);
10602 gboolean
10603 e_editor_dom_key_press_event_process_return_key (EEditorPage *editor_page)
10605 WebKitDOMDocument *document;
10606 WebKitDOMNode *table = NULL;
10607 gboolean first_cell = FALSE;
10609 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
10611 document = e_editor_page_get_document (editor_page);
10612 /* Return pressed in the beginning of the first cell will insert
10613 * new block before the table (and move the caret there) if none
10614 * is already there, otherwise it will act as normal return. */
10615 if (selection_is_in_table (document, &first_cell, &table) && first_cell) {
10616 WebKitDOMNode *node;
10618 node = webkit_dom_node_get_previous_sibling (table);
10619 if (!node) {
10620 node = webkit_dom_node_get_next_sibling (table);
10621 node = webkit_dom_node_clone_node_with_error (node, FALSE, NULL);
10622 webkit_dom_node_append_child (
10623 node,
10624 WEBKIT_DOM_NODE (webkit_dom_document_create_element (
10625 document, "br", NULL)),
10626 NULL);
10627 dom_add_selection_markers_into_element_start (
10628 document, WEBKIT_DOM_ELEMENT (node), NULL, NULL);
10629 webkit_dom_node_insert_before (
10630 webkit_dom_node_get_parent_node (table),
10631 node,
10632 table,
10633 NULL);
10634 e_editor_dom_selection_restore (editor_page);
10635 e_editor_page_emit_content_changed (editor_page);
10636 return TRUE;
10640 /* When user presses ENTER in a citation block, WebKit does
10641 * not break the citation automatically, so we need to use
10642 * the special command to do it. */
10643 if (e_editor_dom_selection_is_citation (editor_page)) {
10644 e_editor_dom_remove_input_event_listener_from_body (editor_page);
10645 if (split_citation (editor_page)) {
10646 e_editor_page_set_return_key_pressed (editor_page, TRUE);
10647 e_editor_dom_check_magic_links (editor_page, FALSE);
10648 e_editor_page_set_return_key_pressed (editor_page, FALSE);
10649 e_editor_page_emit_content_changed (editor_page);
10651 return TRUE;
10653 return FALSE;
10656 /* If the ENTER key is pressed inside an empty list item then the list
10657 * is broken into two and empty paragraph is inserted between lists. */
10658 if (e_editor_dom_return_pressed_in_empty_list_item (editor_page))
10659 return TRUE;
10661 if (return_pressed_in_image_wrapper (editor_page))
10662 return TRUE;
10664 if (return_pressed_after_h_rule (editor_page))
10665 return TRUE;
10667 return FALSE;
10670 static gboolean
10671 remove_empty_bulleted_list_item (EEditorPage *editor_page)
10673 WebKitDOMDocument *document;
10674 WebKitDOMElement *selection_start;
10675 WebKitDOMNode *parent;
10676 EEditorUndoRedoManager *manager;
10678 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
10680 document = e_editor_page_get_document (editor_page);
10681 manager = e_editor_page_get_undo_redo_manager (editor_page);
10682 e_editor_dom_selection_save (editor_page);
10684 selection_start = webkit_dom_document_get_element_by_id (
10685 document, "-x-evo-selection-start-marker");
10687 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start));
10688 while (parent && !node_is_list_or_item (parent))
10689 parent = webkit_dom_node_get_parent_node (parent);
10691 if (!parent)
10692 goto out;
10694 if (selection_is_in_empty_list_item (WEBKIT_DOM_NODE (selection_start))) {
10695 EEditorHistoryEvent *ev = NULL;
10696 WebKitDOMDocumentFragment *fragment;
10697 WebKitDOMNode *prev_item;
10699 prev_item = webkit_dom_node_get_previous_sibling (parent);
10701 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
10702 /* Insert new history event for Return to have the right coordinates.
10703 * The fragment will be added later. */
10704 ev = g_new0 (EEditorHistoryEvent, 1);
10705 ev->type = HISTORY_DELETE;
10707 e_editor_dom_selection_get_coordinates (editor_page,
10708 &ev->before.start.x,
10709 &ev->before.start.y,
10710 &ev->before.end.x,
10711 &ev->before.end.y);
10713 fragment = webkit_dom_document_create_document_fragment (document);
10716 if (ev) {
10717 if (prev_item)
10718 webkit_dom_node_append_child (
10719 WEBKIT_DOM_NODE (fragment),
10720 webkit_dom_node_clone_node_with_error (prev_item, TRUE, NULL),
10721 NULL);
10723 webkit_dom_node_append_child (
10724 WEBKIT_DOM_NODE (fragment),
10725 parent,
10726 NULL);
10727 } else
10728 remove_node (parent);
10730 if (prev_item)
10731 dom_add_selection_markers_into_element_end (
10732 document, WEBKIT_DOM_ELEMENT (prev_item), NULL, NULL);
10734 if (ev) {
10735 e_editor_dom_selection_get_coordinates (editor_page,
10736 &ev->after.start.x,
10737 &ev->after.start.y,
10738 &ev->after.end.x,
10739 &ev->after.end.y);
10741 ev->data.fragment = g_object_ref (fragment);
10743 e_editor_undo_redo_manager_insert_history_event (manager, ev);
10746 e_editor_page_emit_content_changed (editor_page);
10747 e_editor_dom_selection_restore (editor_page);
10749 return TRUE;
10751 out:
10752 e_editor_dom_selection_restore (editor_page);
10754 return FALSE;
10757 gboolean
10758 e_editor_dom_key_press_event_process_backspace_key (EEditorPage *editor_page)
10760 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
10762 /* BackSpace pressed in the beginning of quoted content changes
10763 * format to normal and inserts text into body */
10764 if (e_editor_dom_selection_is_collapsed (editor_page)) {
10765 e_editor_dom_selection_save (editor_page);
10766 if (e_editor_dom_move_quoted_block_level_up (editor_page) || delete_hidden_space (editor_page)) {
10767 e_editor_dom_selection_restore (editor_page);
10768 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
10769 e_editor_page_emit_content_changed (editor_page);
10770 return TRUE;
10772 e_editor_dom_selection_restore (editor_page);
10775 /* BackSpace in indented block decrease indent level by one */
10776 if (e_editor_dom_selection_is_indented (editor_page) &&
10777 e_editor_dom_selection_is_collapsed (editor_page)) {
10778 WebKitDOMDocument *document;
10779 WebKitDOMElement *selection_start;
10780 WebKitDOMNode *prev_sibling;
10782 document = e_editor_page_get_document (editor_page);
10784 e_editor_dom_selection_save (editor_page);
10785 selection_start = webkit_dom_document_get_element_by_id (
10786 document, "-x-evo-selection-start-marker");
10788 /* Empty text node before caret */
10789 prev_sibling = webkit_dom_node_get_previous_sibling (
10790 WEBKIT_DOM_NODE (selection_start));
10791 if (prev_sibling && WEBKIT_DOM_IS_TEXT (prev_sibling))
10792 if (webkit_dom_character_data_get_length (WEBKIT_DOM_CHARACTER_DATA (prev_sibling)) == 0)
10793 prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling);
10795 e_editor_dom_selection_restore (editor_page);
10796 if (!prev_sibling) {
10797 e_editor_dom_selection_unindent (editor_page);
10798 e_editor_page_emit_content_changed (editor_page);
10799 return TRUE;
10803 /* BackSpace pressed in an empty item in the bulleted list removes it. */
10804 if (!e_editor_page_get_html_mode (editor_page) && e_editor_dom_selection_is_collapsed (editor_page) &&
10805 remove_empty_bulleted_list_item (editor_page))
10806 return TRUE;
10809 if (prevent_from_deleting_last_element_in_body (e_editor_page_get_document (editor_page)))
10810 return TRUE;
10812 return FALSE;
10815 static gboolean
10816 deleting_block_starting_in_quoted_content (EEditorPage *editor_page,
10817 glong key_code,
10818 gboolean control_key)
10820 gint citation_level;
10821 WebKitDOMDocument *document;
10822 WebKitDOMElement *element;
10823 WebKitDOMHTMLElement *body;
10824 WebKitDOMNode *node, *parent, *block, *sibling;
10825 WebKitDOMRange *range = NULL;
10827 e_editor_dom_selection_save (editor_page);
10829 document = e_editor_page_get_document (editor_page);
10831 element = webkit_dom_document_get_element_by_id (
10832 document, "-x-evo-selection-start-marker");
10834 /* Before the caret are just quote characters */
10835 sibling = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element));
10836 if (!(sibling && WEBKIT_DOM_IS_ELEMENT (sibling) &&
10837 element_has_class (WEBKIT_DOM_ELEMENT (sibling), "-x-evo-quoted"))) {
10838 goto out;
10841 if (webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (sibling))) {
10842 goto out;
10845 element = webkit_dom_document_get_element_by_id (
10846 document, "-x-evo-selection-end-marker");
10848 citation_level = e_editor_dom_get_citation_level (WEBKIT_DOM_NODE (element));
10850 enable_quote_marks_select (document);
10851 e_editor_dom_selection_restore (editor_page);
10853 if (key_code != ~0)
10854 save_history_for_delete_or_backspace (
10855 editor_page, key_code == HTML_KEY_CODE_DELETE, control_key);
10857 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_DELETE, NULL);
10859 range = e_editor_dom_get_current_range (editor_page);
10860 node = webkit_dom_range_get_end_container (range, NULL);
10862 block = e_editor_dom_get_parent_block_node_from_child (node);
10863 parent = webkit_dom_node_get_parent_node (block);
10864 body = webkit_dom_document_get_body (document);
10865 while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (webkit_dom_node_get_parent_node (parent)))
10866 parent = webkit_dom_node_get_parent_node (parent);
10868 if (!citation_level) {
10869 e_editor_dom_remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (block));
10870 e_editor_dom_remove_quoting_from_element (WEBKIT_DOM_ELEMENT (block));
10872 webkit_dom_node_insert_before (
10873 WEBKIT_DOM_NODE (body),
10874 block,
10875 webkit_dom_node_get_next_sibling (parent),
10876 NULL);
10877 } else {
10878 WebKitDOMNode *last_child = webkit_dom_node_get_last_child (block);
10880 if (WEBKIT_DOM_IS_ELEMENT (last_child) &&
10881 element_has_class (WEBKIT_DOM_ELEMENT (last_child), "-x-evo-quoted")) {
10882 webkit_dom_node_append_child (
10883 block,
10884 WEBKIT_DOM_NODE (webkit_dom_document_create_element (document, "br", NULL)),
10885 NULL);
10890 e_editor_dom_disable_quote_marks_select (editor_page);
10891 e_editor_dom_move_caret_into_element (editor_page, WEBKIT_DOM_ELEMENT (block), TRUE);
10893 g_clear_object (&range);
10895 return TRUE;
10896 out:
10897 e_editor_dom_selection_restore (editor_page);
10899 return FALSE;
10902 gboolean
10903 e_editor_dom_key_press_event_process_delete_or_backspace_key (EEditorPage *editor_page,
10904 glong key_code,
10905 gboolean control_key,
10906 gboolean delete)
10908 WebKitDOMDocument *document;
10909 gboolean html_mode, local_delete, collapsed;
10911 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
10913 document = e_editor_page_get_document (editor_page);
10914 html_mode = e_editor_page_get_html_mode (editor_page);
10915 local_delete = (key_code == HTML_KEY_CODE_DELETE) || delete;
10917 if (e_editor_page_get_magic_smileys_enabled (editor_page)) {
10918 /* If deleting something in a smiley it won't be a smiley
10919 * anymore (at least from Evolution' POV), so remove all
10920 * the elements that are hidden in the wrapper and leave
10921 * just the text. Also this ensures that when a smiley is
10922 * recognized and we press the BackSpace key we won't delete
10923 * the UNICODE_HIDDEN_SPACE, but we will correctly delete
10924 * the last character of smiley. */
10925 process_smiley_on_delete_or_backspace (editor_page);
10928 if (!local_delete && !html_mode &&
10929 e_editor_dom_delete_last_character_on_line_in_quoted_block (editor_page, key_code, control_key))
10930 goto out;
10932 if (!local_delete && !html_mode &&
10933 delete_last_character_from_previous_line_in_quoted_block (editor_page, key_code, control_key))
10934 goto out;
10936 if (!html_mode && e_editor_dom_fix_structure_after_delete_before_quoted_content (editor_page, key_code, control_key, delete))
10937 goto out;
10939 collapsed = e_editor_dom_selection_is_collapsed (editor_page);
10941 if (!html_mode && !collapsed && deleting_block_starting_in_quoted_content (editor_page, key_code, control_key))
10942 goto out;
10944 if (!collapsed) {
10945 /* Let the quote marks be selectable to nearly correctly remove the
10946 * selection. Corrections after are done in body_keyup_event_cb. */
10947 enable_quote_marks_select (document);
10950 if (key_code != ~0)
10951 save_history_for_delete_or_backspace (
10952 editor_page, key_code == HTML_KEY_CODE_DELETE, control_key);
10954 if (local_delete) {
10955 WebKitDOMElement *selection_start_marker;
10956 WebKitDOMNode *sibling, *block, *next_block;
10958 /* This needs to be performed just in plain text mode
10959 * and when the selection is collapsed. */
10960 if (html_mode)
10961 return FALSE;
10963 e_editor_dom_selection_save (editor_page);
10965 selection_start_marker = webkit_dom_document_get_element_by_id (
10966 document, "-x-evo-selection-start-marker");
10967 sibling = webkit_dom_node_get_previous_sibling (
10968 WEBKIT_DOM_NODE (selection_start_marker));
10969 /* Check if the key was pressed in the beginning of block. */
10970 if (!(sibling && WEBKIT_DOM_IS_ELEMENT (sibling) &&
10971 element_has_class (WEBKIT_DOM_ELEMENT (sibling), "-x-evo-quoted"))) {
10972 e_editor_dom_selection_restore (editor_page);
10973 return FALSE;
10976 sibling = webkit_dom_node_get_next_sibling (
10977 WEBKIT_DOM_NODE (selection_start_marker));
10978 sibling = webkit_dom_node_get_next_sibling (sibling);
10980 /* And also the current block was empty. */
10981 if (!(!sibling || (sibling && WEBKIT_DOM_IS_HTML_BR_ELEMENT (sibling) &&
10982 !element_has_class (WEBKIT_DOM_ELEMENT (sibling), "-x-evo-wrap-br")))) {
10983 e_editor_dom_selection_restore (editor_page);
10984 return FALSE;
10987 block = e_editor_dom_get_parent_block_node_from_child (
10988 WEBKIT_DOM_NODE (selection_start_marker));
10989 next_block = webkit_dom_node_get_next_sibling (block);
10991 remove_node (block);
10993 e_editor_dom_move_caret_into_element (editor_page, WEBKIT_DOM_ELEMENT (next_block), TRUE);
10995 goto out;
10996 } else {
10997 /* Concatenating a non-quoted block with Backspace key to the
10998 * previous block that is inside a quoted content. */
10999 WebKitDOMElement *selection_start_marker;
11000 WebKitDOMNode *node, *block, *prev_block, *last_child, *child;
11002 if (html_mode || e_editor_dom_selection_is_citation (editor_page))
11003 return FALSE;
11005 e_editor_dom_selection_save (editor_page);
11007 selection_start_marker = webkit_dom_document_get_element_by_id (
11008 document, "-x-evo-selection-start-marker");
11010 node = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_start_marker));
11011 if (node) {
11012 e_editor_dom_selection_restore (editor_page);
11013 return FALSE;
11016 remove_empty_blocks (document);
11018 block = e_editor_dom_get_parent_block_node_from_child (
11019 WEBKIT_DOM_NODE (selection_start_marker));
11021 prev_block = webkit_dom_node_get_previous_sibling (block);
11022 if (!prev_block || !WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (prev_block)) {
11023 e_editor_dom_selection_restore (editor_page);
11024 return FALSE;
11027 last_child = webkit_dom_node_get_last_child (prev_block);
11028 while (last_child && WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (last_child))
11029 last_child = webkit_dom_node_get_last_child (last_child);
11031 if (!last_child) {
11032 e_editor_dom_selection_restore (editor_page);
11033 return FALSE;
11036 e_editor_dom_remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (last_child));
11037 e_editor_dom_remove_quoting_from_element (WEBKIT_DOM_ELEMENT (last_child));
11039 node = webkit_dom_node_get_last_child (last_child);
11040 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (node))
11041 remove_node (node);
11043 while ((child = webkit_dom_node_get_first_child (block)))
11044 webkit_dom_node_append_child (last_child, child, NULL);
11046 remove_node (block);
11048 if (WEBKIT_DOM_IS_ELEMENT (last_child))
11049 e_editor_dom_wrap_and_quote_element (editor_page, WEBKIT_DOM_ELEMENT (last_child));
11051 e_editor_dom_selection_restore (editor_page);
11053 goto out;
11056 return FALSE;
11057 out:
11058 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
11059 e_editor_page_emit_content_changed (editor_page);
11061 return TRUE;
11064 static gboolean
11065 contains_forbidden_elements (WebKitDOMDocument *document)
11067 WebKitDOMElement *body, *element;
11069 body = WEBKIT_DOM_ELEMENT (webkit_dom_document_get_body (document));
11071 /* Try to find disallowed elements in the plain text mode */
11072 element = webkit_dom_element_query_selector (
11073 body,
11074 ":not("
11075 /* Basic elements used as blocks allowed in the plain text mode */
11076 "[data-evo-paragraph], pre, ul, ol, li, blockquote[type=cite], "
11077 /* Other elements */
11078 "br, a, "
11079 /* Indented elements */
11080 ".-x-evo-indented, "
11081 /* Signature */
11082 ".-x-evo-signature-wrapper, .-x-evo-signature, "
11083 /* Smileys */
11084 ".-x-evo-smiley-wrapper, .-x-evo-smiley-img, .-x-evo-smiley-text, "
11085 /* Selection markers */
11086 "#-x-evo-selection-start-marker, #-x-evo-selection-end-marker"
11087 ")",
11088 NULL);
11090 if (element)
11091 return TRUE;
11093 /* Try to find disallowed elements relationship in the plain text */
11094 element = webkit_dom_element_query_selector (
11095 body,
11096 ":not("
11097 /* Body descendants */
11098 "body > :matches(blockquote[type=cite], .-x-evo-signature-wrapper), "
11099 /* Main blocks and indented blocks */
11100 ":matches(body, .-x-evo-indented) > :matches(pre, ul, ol, .-x-evo-indented, [data-evo-paragraph]), "
11101 /* Blockquote descendants */
11102 "blockquote[type=cite] > :matches(pre, [data-evo-paragraph], blockquote[type=cite]), "
11103 /* Block descendants */
11104 ":matches(pre, [data-evo-paragraph], li) > :matches(br, span, a), "
11105 /* Lists */
11106 ":matches(ul, ol) > :matches(ul, ol, li), "
11107 /* Smileys */
11108 ".-x-evo-smiley-wrapper > :matches(.-x-evo-smiley-img, .-x-evo-smiley-text), "
11109 /* Signature */
11110 ".-x-evo-signature-wrapper > .-x-evo-signature"
11111 ")",
11112 NULL);
11114 return element ? TRUE : FALSE;
11117 gboolean
11118 e_editor_dom_check_if_conversion_needed (EEditorPage *editor_page)
11120 WebKitDOMDocument *document;
11121 gboolean html_mode, convert = FALSE;
11123 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
11125 document = e_editor_page_get_document (editor_page);
11126 html_mode = e_editor_page_get_html_mode (editor_page);
11128 if (html_mode)
11129 convert = contains_forbidden_elements (document);
11131 return convert;
11134 void
11135 e_editor_dom_process_content_after_mode_change (EEditorPage *editor_page)
11137 EEditorUndoRedoManager *manager;
11138 gboolean html_mode;
11140 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11142 html_mode = e_editor_page_get_html_mode (editor_page);
11144 if (html_mode)
11145 process_content_to_html_changing_composer_mode (editor_page);
11146 else
11147 process_content_to_plain_text_changing_composer_mode (editor_page);
11149 manager = e_editor_page_get_undo_redo_manager (editor_page);
11150 e_editor_undo_redo_manager_clean_history (manager);
11153 guint
11154 e_editor_dom_get_caret_offset (EEditorPage *editor_page)
11156 WebKitDOMDocument *document;
11157 WebKitDOMDOMWindow *dom_window = NULL;
11158 WebKitDOMDOMSelection *dom_selection = NULL;
11159 WebKitDOMNode *anchor;
11160 WebKitDOMRange *range = NULL;
11161 guint ret_val;
11162 gchar *text;
11164 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), 0);
11166 document = e_editor_page_get_document (editor_page);
11167 dom_window = webkit_dom_document_get_default_view (document);
11168 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
11169 g_clear_object (&dom_window);
11171 if (webkit_dom_dom_selection_get_range_count (dom_selection) < 1) {
11172 g_clear_object (&dom_selection);
11173 return 0;
11176 webkit_dom_dom_selection_collapse_to_start (dom_selection, NULL);
11177 /* Select the text from the current caret position to the beginning of the line. */
11178 webkit_dom_dom_selection_modify (dom_selection, "extend", "left", "lineBoundary");
11180 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
11181 anchor = webkit_dom_dom_selection_get_anchor_node (dom_selection);
11182 text = webkit_dom_range_to_string (range, NULL);
11183 ret_val = strlen (text);
11184 g_free (text);
11186 webkit_dom_dom_selection_collapse_to_end (dom_selection, NULL);
11188 /* In the plain text mode we need to increase the return value by 2 per
11189 * citation level because of "> ". */
11190 if (!e_editor_page_get_html_mode (editor_page)) {
11191 WebKitDOMNode *parent = anchor;
11193 while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
11194 if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent))
11195 ret_val += 2;
11197 parent = webkit_dom_node_get_parent_node (parent);
11201 g_clear_object (&range);
11202 g_clear_object (&dom_selection);
11204 return ret_val;
11207 guint
11208 e_editor_dom_get_caret_position (EEditorPage *editor_page)
11210 WebKitDOMDocument *document;
11211 WebKitDOMHTMLElement *body;
11212 WebKitDOMDOMWindow *dom_window = NULL;
11213 WebKitDOMDOMSelection *dom_selection = NULL;
11214 WebKitDOMRange *range = NULL, *range_clone = NULL;
11215 guint ret_val;
11216 gchar *text;
11218 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), 0);
11220 document = e_editor_page_get_document (editor_page);
11221 dom_window = webkit_dom_document_get_default_view (document);
11222 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
11223 g_clear_object (&dom_window);
11225 if (webkit_dom_dom_selection_get_range_count (dom_selection) < 1) {
11226 g_clear_object (&dom_selection);
11227 return 0;
11230 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
11231 range_clone = webkit_dom_range_clone_range (range, NULL);
11233 body = webkit_dom_document_get_body (document);
11234 /* Select the text from the beginning of the body to the current caret. */
11235 webkit_dom_range_set_start_before (
11236 range_clone, webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)), NULL);
11238 /* This is returning a text without new lines! */
11239 text = webkit_dom_range_to_string (range_clone, NULL);
11240 ret_val = strlen (text);
11241 g_free (text);
11243 g_clear_object (&range_clone);
11244 g_clear_object (&range);
11245 g_clear_object (&dom_selection);
11247 return ret_val;
11250 static void
11251 insert_nbsp_history_event (WebKitDOMDocument *document,
11252 EEditorUndoRedoManager *manager,
11253 gboolean delete,
11254 guint x,
11255 guint y)
11257 EEditorHistoryEvent *event;
11258 WebKitDOMDocumentFragment *fragment;
11260 event = g_new0 (EEditorHistoryEvent, 1);
11261 event->type = HISTORY_AND;
11262 e_editor_undo_redo_manager_insert_history_event (manager, event);
11264 fragment = webkit_dom_document_create_document_fragment (document);
11265 webkit_dom_node_append_child (
11266 WEBKIT_DOM_NODE (fragment),
11267 WEBKIT_DOM_NODE (
11268 webkit_dom_document_create_text_node (document, UNICODE_NBSP)),
11269 NULL);
11271 event = g_new0 (EEditorHistoryEvent, 1);
11272 event->type = HISTORY_DELETE;
11274 if (delete)
11275 g_object_set_data (G_OBJECT (fragment), "history-delete-key", GINT_TO_POINTER (1));
11277 event->data.fragment = fragment;
11279 event->before.start.x = x;
11280 event->before.start.y = y;
11281 event->before.end.x = x;
11282 event->before.end.y = y;
11284 event->after.start.x = x;
11285 event->after.start.y = y;
11286 event->after.end.x = x;
11287 event->after.end.y = y;
11289 e_editor_undo_redo_manager_insert_history_event (manager, event);
11291 void
11292 e_editor_dom_save_history_for_drag (EEditorPage *editor_page)
11294 WebKitDOMDocument *document;
11295 WebKitDOMDocumentFragment *fragment;
11296 WebKitDOMDOMSelection *dom_selection = NULL;
11297 WebKitDOMDOMWindow *dom_window = NULL;
11298 WebKitDOMRange *beginning_of_line = NULL;
11299 WebKitDOMRange *range = NULL, *range_clone = NULL;
11300 EEditorHistoryEvent *event;
11301 EEditorUndoRedoManager *manager;
11302 gboolean start_to_start = FALSE, end_to_end = FALSE;
11303 gchar *range_text;
11304 guint x, y;
11306 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11308 document = e_editor_page_get_document (editor_page);
11309 manager = e_editor_page_get_undo_redo_manager (editor_page);
11311 if (!(dom_window = webkit_dom_document_get_default_view (document)))
11312 return;
11314 if (!(dom_selection = webkit_dom_dom_window_get_selection (dom_window))) {
11315 g_clear_object (&dom_window);
11316 return;
11319 g_clear_object (&dom_window);
11321 if (webkit_dom_dom_selection_get_range_count (dom_selection) < 1) {
11322 g_clear_object (&dom_selection);
11323 return;
11326 /* Obtain the dragged content. */
11327 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
11328 range_clone = webkit_dom_range_clone_range (range, NULL);
11330 /* Create the history event for the content that will
11331 * be removed by DnD. */
11332 event = g_new0 (EEditorHistoryEvent, 1);
11333 event->type = HISTORY_DELETE;
11335 e_editor_dom_selection_get_coordinates (editor_page,
11336 &event->before.start.x,
11337 &event->before.start.y,
11338 &event->before.end.x,
11339 &event->before.end.y);
11341 x = event->before.start.x;
11342 y = event->before.start.y;
11344 event->after.start.x = x;
11345 event->after.start.y = y;
11346 event->after.end.x = x;
11347 event->after.end.y = y;
11349 /* Save the content that will be removed. */
11350 fragment = webkit_dom_range_clone_contents (range_clone, NULL);
11352 /* Extend the cloned range to point one character after
11353 * the selection ends to later check if there is a whitespace
11354 * after it. */
11355 webkit_dom_range_set_end (
11356 range_clone,
11357 webkit_dom_range_get_end_container (range_clone, NULL),
11358 webkit_dom_range_get_end_offset (range_clone, NULL) + 1,
11359 NULL);
11360 range_text = webkit_dom_range_get_text (range_clone);
11362 /* Check if the current selection starts on the beginning of line. */
11363 webkit_dom_dom_selection_modify (
11364 dom_selection, "extend", "left", "lineboundary");
11365 beginning_of_line = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
11366 start_to_start = webkit_dom_range_compare_boundary_points (
11367 beginning_of_line, WEBKIT_DOM_RANGE_START_TO_START, range, NULL) == 0;
11369 /* Restore the selection to state before the check. */
11370 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
11371 webkit_dom_dom_selection_add_range (dom_selection, range);
11372 g_clear_object (&beginning_of_line);
11374 /* Check if the current selection end on the end of the line. */
11375 webkit_dom_dom_selection_modify (
11376 dom_selection, "extend", "right", "lineboundary");
11377 beginning_of_line = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
11378 end_to_end = webkit_dom_range_compare_boundary_points (
11379 beginning_of_line, WEBKIT_DOM_RANGE_END_TO_END, range, NULL) == 0;
11381 /* Dragging the whole line. */
11382 if (start_to_start && end_to_end) {
11383 WebKitDOMNode *container, *actual_block, *tmp_block;
11385 /* Select the whole line (to the beginning of the next
11386 * one so we can reuse the undo code while undoing this.
11387 * Because of this we need to special mark the event
11388 * with history-drag-and-drop to correct the selection
11389 * after undoing it (otherwise the beginning of the next
11390 * line will be selected as well. */
11391 webkit_dom_dom_selection_modify (
11392 dom_selection, "extend", "right", "character");
11393 g_clear_object (&beginning_of_line);
11394 beginning_of_line = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
11396 container = webkit_dom_range_get_end_container (range, NULL);
11397 actual_block = e_editor_dom_get_parent_block_node_from_child (container);
11399 tmp_block = webkit_dom_range_get_end_container (beginning_of_line, NULL);
11400 if ((tmp_block = e_editor_dom_get_parent_block_node_from_child (tmp_block))) {
11401 e_editor_dom_selection_get_coordinates (editor_page,
11402 &event->before.start.x,
11403 &event->before.start.y,
11404 &event->before.end.x,
11405 &event->before.end.y);
11407 /* Create the right content for the history event. */
11408 fragment = webkit_dom_document_create_document_fragment (document);
11409 /* The removed line. */
11410 webkit_dom_node_append_child (
11411 WEBKIT_DOM_NODE (fragment),
11412 webkit_dom_node_clone_node_with_error (actual_block, TRUE, NULL),
11413 NULL);
11414 /* The following block, but empty. */
11415 webkit_dom_node_append_child (
11416 WEBKIT_DOM_NODE (fragment),
11417 webkit_dom_node_clone_node_with_error (tmp_block, FALSE, NULL),
11418 NULL);
11419 g_object_set_data (
11420 G_OBJECT (fragment),
11421 "history-drag-and-drop",
11422 GINT_TO_POINTER (1));
11425 /* It should act as a Delete key press. */
11426 g_object_set_data (G_OBJECT (fragment), "history-delete-key", GINT_TO_POINTER (1));
11428 event->data.fragment = fragment;
11429 e_editor_undo_redo_manager_insert_history_event (manager, event);
11431 /* WebKit removes the space (if presented) after selection and
11432 * we need to create a new history event for it. */
11433 if (g_str_has_suffix (range_text, " ") ||
11434 g_str_has_suffix (range_text, UNICODE_NBSP))
11435 insert_nbsp_history_event (document, manager, TRUE, x, y);
11436 else {
11437 /* If there is a space before the selection WebKit will remove
11438 * it as well unless there is a space after the selection. */
11439 gchar *range_text_start;
11440 glong start_offset;
11442 start_offset = webkit_dom_range_get_start_offset (range_clone, NULL);
11443 webkit_dom_range_set_start (
11444 range_clone,
11445 webkit_dom_range_get_start_container (range_clone, NULL),
11446 start_offset > 0 ? start_offset - 1 : 0,
11447 NULL);
11449 range_text_start = webkit_dom_range_get_text (range_clone);
11450 if (g_str_has_prefix (range_text_start, " ") ||
11451 g_str_has_prefix (range_text_start, UNICODE_NBSP)) {
11452 if (!end_to_end) {
11453 webkit_dom_dom_selection_collapse_to_start (dom_selection, NULL);
11454 webkit_dom_dom_selection_modify (
11455 dom_selection, "move", "backward", "character");
11456 e_editor_dom_selection_get_coordinates (editor_page, &x, &y, &x, &y);
11458 insert_nbsp_history_event (document, manager, TRUE, x, y);
11461 g_free (range_text_start);
11464 g_free (range_text);
11466 /* Restore the selection to original state. */
11467 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
11468 webkit_dom_dom_selection_add_range (dom_selection, range);
11469 g_clear_object (&beginning_of_line);
11471 /* All the things above were about removing the content,
11472 * create an AND event to continue later with inserting
11473 * the dropped content. */
11474 event = g_new0 (EEditorHistoryEvent, 1);
11475 event->type = HISTORY_AND;
11476 e_editor_undo_redo_manager_insert_history_event (manager, event);
11478 g_clear_object (&dom_selection);
11480 g_clear_object (&range);
11481 g_clear_object (&range_clone);
11484 void
11485 e_editor_dom_save_history_for_drop (EEditorPage *editor_page)
11487 WebKitDOMDocument *document;
11488 WebKitDOMDocumentFragment *fragment;
11489 WebKitDOMDOMSelection *dom_selection = NULL;
11490 WebKitDOMDOMWindow *dom_window = NULL;
11491 WebKitDOMNodeList *list = NULL;
11492 WebKitDOMRange *range = NULL;
11493 EEditorUndoRedoManager *manager;
11494 EEditorHistoryEvent *event;
11495 gint ii, length;
11497 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11499 document = e_editor_page_get_document (editor_page);
11500 manager = e_editor_page_get_undo_redo_manager (editor_page);
11502 /* When the image is DnD inside the view WebKit removes the wrapper that
11503 * is used for resizing the image, so we have to recreate it again. */
11504 list = webkit_dom_document_query_selector_all (document, ":not(span) > img[data-inline]", NULL);
11505 length = webkit_dom_node_list_get_length (list);
11506 for (ii = 0; ii < length; ii++) {
11507 WebKitDOMElement *element;
11508 WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
11510 element = webkit_dom_document_create_element (document, "span", NULL);
11511 webkit_dom_element_set_class_name (element, "-x-evo-resizable-wrapper");
11513 webkit_dom_node_insert_before (
11514 webkit_dom_node_get_parent_node (node),
11515 WEBKIT_DOM_NODE (element),
11516 node,
11517 NULL);
11519 webkit_dom_node_append_child (WEBKIT_DOM_NODE (element), node, NULL);
11521 g_clear_object (&list);
11523 /* When the image is moved the new selection is created after after it, so
11524 * lets collapse the selection to have the caret right after the image. */
11525 dom_window = webkit_dom_document_get_default_view (document);
11526 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
11527 g_clear_object (&dom_window);
11529 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
11531 event = g_new0 (EEditorHistoryEvent, 1);
11532 event->type = HISTORY_INSERT_HTML;
11534 /* Get the dropped content. It's easy as it is selected by WebKit. */
11535 fragment = webkit_dom_range_clone_contents (range, NULL);
11536 event->data.string.from = NULL;
11537 /* Get the HTML content of the dropped content. */
11538 event->data.string.to = dom_get_node_inner_html (WEBKIT_DOM_NODE (fragment));
11540 e_editor_undo_redo_manager_insert_history_event (manager, event);
11542 g_clear_object (&range);
11543 g_clear_object (&dom_selection);
11546 static void
11547 dom_set_link_color_in_document (EEditorPage *editor_page,
11548 const gchar *color,
11549 gboolean visited)
11551 WebKitDOMDocument *document;
11552 WebKitDOMHTMLHeadElement *head;
11553 WebKitDOMElement *style_element;
11554 WebKitDOMHTMLElement *body;
11555 gchar *color_str = NULL;
11556 const gchar *style_id;
11558 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11559 g_return_if_fail (color != NULL);
11561 style_id = visited ? "-x-evo-a-color-style-visited" : "-x-evo-a-color-style";
11563 document = e_editor_page_get_document (editor_page);
11564 head = webkit_dom_document_get_head (document);
11565 body = webkit_dom_document_get_body (document);
11567 style_element = webkit_dom_document_get_element_by_id (document, style_id);
11568 if (!style_element) {
11569 style_element = webkit_dom_document_create_element (document, "style", NULL);
11570 webkit_dom_element_set_id (style_element, style_id);
11571 webkit_dom_element_set_attribute (style_element, "type", "text/css", NULL);
11572 webkit_dom_node_append_child (
11573 WEBKIT_DOM_NODE (head), WEBKIT_DOM_NODE (style_element), NULL);
11576 color_str = g_strdup_printf (
11577 visited ? "a.-x-evo-visited-link { color: %s; }" : "a { color: %s; }", color);
11578 webkit_dom_element_set_inner_html (style_element, color_str, NULL);
11579 g_free (color_str);
11581 if (visited)
11582 webkit_dom_html_body_element_set_v_link (
11583 WEBKIT_DOM_HTML_BODY_ELEMENT (body), color);
11584 else
11585 webkit_dom_html_body_element_set_link (
11586 WEBKIT_DOM_HTML_BODY_ELEMENT (body), color);
11589 void
11590 e_editor_dom_set_link_color (EEditorPage *editor_page,
11591 const gchar *color)
11593 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11595 dom_set_link_color_in_document (editor_page, color, FALSE);
11598 void
11599 e_editor_dom_set_visited_link_color (EEditorPage *editor_page,
11600 const gchar *color)
11602 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11604 dom_set_link_color_in_document (editor_page, color, TRUE);
11607 void
11608 e_editor_dom_fix_file_uri_images (EEditorPage *editor_page)
11610 WebKitDOMDocument *document;
11611 WebKitDOMNodeList *list = NULL;
11612 gint ii;
11614 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11616 document = e_editor_page_get_document (editor_page);
11618 list = webkit_dom_document_query_selector_all (
11619 document, "img[src^=\"file://\"]", NULL);
11620 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
11621 WebKitDOMNode *node;
11622 gchar *uri;
11624 node = webkit_dom_node_list_item (list, ii);
11625 uri = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "src");
11626 g_free (uri);
11629 g_clear_object (&list);
11632 /* ******************** Selection ******************** */
11634 void
11635 e_editor_dom_replace_base64_image_src (EEditorPage *editor_page,
11636 const gchar *selector,
11637 const gchar *base64_content,
11638 const gchar *filename,
11639 const gchar *uri)
11641 WebKitDOMDocument *document;
11642 WebKitDOMElement *element;
11644 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11646 document = e_editor_page_get_document (editor_page);
11647 element = webkit_dom_document_query_selector (document, selector, NULL);
11649 if (WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (element))
11650 webkit_dom_html_image_element_set_src (
11651 WEBKIT_DOM_HTML_IMAGE_ELEMENT (element),
11652 base64_content);
11653 else
11654 webkit_dom_element_set_attribute (
11655 element, "background", base64_content, NULL);
11657 webkit_dom_element_set_attribute (element, "data-uri", uri, NULL);
11658 webkit_dom_element_set_attribute (element, "data-inline", "", NULL);
11659 webkit_dom_element_set_attribute (
11660 element, "data-name", filename ? filename : "", NULL);
11663 WebKitDOMRange *
11664 e_editor_dom_get_current_range (EEditorPage *editor_page)
11666 WebKitDOMDocument *document;
11667 WebKitDOMDOMWindow *dom_window = NULL;
11668 WebKitDOMDOMSelection *dom_selection = NULL;
11669 WebKitDOMRange *range = NULL;
11671 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
11673 document = e_editor_page_get_document (editor_page);
11674 dom_window = webkit_dom_document_get_default_view (document);
11675 if (!dom_window)
11676 return NULL;
11678 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
11679 if (!WEBKIT_DOM_IS_DOM_SELECTION (dom_selection)) {
11680 g_clear_object (&dom_window);
11681 return NULL;
11684 if (webkit_dom_dom_selection_get_range_count (dom_selection) < 1)
11685 goto exit;
11687 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
11688 exit:
11689 g_clear_object (&dom_selection);
11690 g_clear_object (&dom_window);
11692 return range;
11695 void
11696 e_editor_dom_move_caret_into_element (EEditorPage *editor_page,
11697 WebKitDOMElement *element,
11698 gboolean to_start)
11700 WebKitDOMDocument *document;
11701 WebKitDOMDOMWindow *dom_window = NULL;
11702 WebKitDOMDOMSelection *dom_selection = NULL;
11703 WebKitDOMRange *range = NULL;
11705 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11707 if (!element)
11708 return;
11710 document = e_editor_page_get_document (editor_page);
11711 dom_window = webkit_dom_document_get_default_view (document);
11712 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
11713 range = webkit_dom_document_create_range (document);
11715 webkit_dom_range_select_node_contents (
11716 range, WEBKIT_DOM_NODE (element), NULL);
11717 webkit_dom_range_collapse (range, to_start, NULL);
11718 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
11719 webkit_dom_dom_selection_add_range (dom_selection, range);
11721 g_clear_object (&range);
11722 g_clear_object (&dom_selection);
11723 g_clear_object (&dom_window);
11726 void
11727 e_editor_dom_insert_base64_image (EEditorPage *editor_page,
11728 const gchar *base64_content,
11729 const gchar *filename,
11730 const gchar *uri)
11732 WebKitDOMDocument *document;
11733 WebKitDOMElement *element, *selection_start_marker, *resizable_wrapper;
11734 WebKitDOMText *text;
11735 EEditorHistoryEvent *ev = NULL;
11736 EEditorUndoRedoManager *manager;
11738 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11740 document = e_editor_page_get_document (editor_page);
11741 manager = e_editor_page_get_undo_redo_manager (editor_page);
11743 if (!e_editor_dom_selection_is_collapsed (editor_page)) {
11744 EEditorHistoryEvent *ev;
11745 WebKitDOMDocumentFragment *fragment;
11746 WebKitDOMRange *range = NULL;
11748 ev = g_new0 (EEditorHistoryEvent, 1);
11749 ev->type = HISTORY_DELETE;
11751 range = e_editor_dom_get_current_range (editor_page);
11752 fragment = webkit_dom_range_clone_contents (range, NULL);
11753 g_clear_object (&range);
11754 ev->data.fragment = g_object_ref (fragment);
11756 e_editor_dom_selection_get_coordinates (editor_page,
11757 &ev->before.start.x,
11758 &ev->before.start.y,
11759 &ev->before.end.x,
11760 &ev->before.end.y);
11762 ev->after.start.x = ev->before.start.x;
11763 ev->after.start.y = ev->before.start.y;
11764 ev->after.end.x = ev->before.start.x;
11765 ev->after.end.y = ev->before.start.y;
11767 e_editor_undo_redo_manager_insert_history_event (manager, ev);
11769 ev = g_new0 (EEditorHistoryEvent, 1);
11770 ev->type = HISTORY_AND;
11772 e_editor_undo_redo_manager_insert_history_event (manager, ev);
11773 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_DELETE, NULL);
11776 e_editor_dom_selection_save (editor_page);
11777 selection_start_marker = webkit_dom_document_get_element_by_id (
11778 document, "-x-evo-selection-start-marker");
11780 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
11781 ev = g_new0 (EEditorHistoryEvent, 1);
11782 ev->type = HISTORY_IMAGE;
11784 e_editor_dom_selection_get_coordinates (editor_page,
11785 &ev->before.start.x,
11786 &ev->before.start.y,
11787 &ev->before.end.x,
11788 &ev->before.end.y);
11791 resizable_wrapper =
11792 webkit_dom_document_create_element (document, "span", NULL);
11793 webkit_dom_element_set_attribute (
11794 resizable_wrapper, "class", "-x-evo-resizable-wrapper", NULL);
11796 element = webkit_dom_document_create_element (document, "img", NULL);
11797 webkit_dom_html_image_element_set_src (
11798 WEBKIT_DOM_HTML_IMAGE_ELEMENT (element),
11799 base64_content);
11800 webkit_dom_element_set_attribute (
11801 WEBKIT_DOM_ELEMENT (element), "data-uri", uri, NULL);
11802 webkit_dom_element_set_attribute (
11803 WEBKIT_DOM_ELEMENT (element), "data-inline", "", NULL);
11804 webkit_dom_element_set_attribute (
11805 WEBKIT_DOM_ELEMENT (element), "data-name",
11806 filename ? filename : "", NULL);
11807 webkit_dom_node_append_child (
11808 WEBKIT_DOM_NODE (resizable_wrapper),
11809 WEBKIT_DOM_NODE (element),
11810 NULL);
11812 webkit_dom_node_insert_before (
11813 webkit_dom_node_get_parent_node (
11814 WEBKIT_DOM_NODE (selection_start_marker)),
11815 WEBKIT_DOM_NODE (resizable_wrapper),
11816 WEBKIT_DOM_NODE (selection_start_marker),
11817 NULL);
11819 /* We have to again use UNICODE_ZERO_WIDTH_SPACE character to restore
11820 * caret on right position */
11821 text = webkit_dom_document_create_text_node (
11822 document, UNICODE_ZERO_WIDTH_SPACE);
11824 webkit_dom_node_insert_before (
11825 webkit_dom_node_get_parent_node (
11826 WEBKIT_DOM_NODE (selection_start_marker)),
11827 WEBKIT_DOM_NODE (text),
11828 WEBKIT_DOM_NODE (selection_start_marker),
11829 NULL);
11831 if (ev) {
11832 WebKitDOMDocumentFragment *fragment;
11833 WebKitDOMNode *node;
11835 fragment = webkit_dom_document_create_document_fragment (document);
11836 node = webkit_dom_node_append_child (
11837 WEBKIT_DOM_NODE (fragment),
11838 webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (resizable_wrapper), TRUE, NULL),
11839 NULL);
11841 webkit_dom_element_insert_adjacent_html (
11842 WEBKIT_DOM_ELEMENT (node), "afterend", "&#8203;", NULL);
11843 ev->data.fragment = g_object_ref (fragment);
11845 e_editor_dom_selection_get_coordinates (editor_page,
11846 &ev->after.start.x,
11847 &ev->after.start.y,
11848 &ev->after.end.x,
11849 &ev->after.end.y);
11851 e_editor_undo_redo_manager_insert_history_event (manager, ev);
11854 e_editor_dom_selection_restore (editor_page);
11855 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
11856 e_editor_dom_scroll_to_caret (editor_page);
11859 /* ************************ image_load_and_insert_async() ************************ */
11861 typedef struct _ImageLoadContext {
11862 EEditorPage *editor_page;
11863 GInputStream *input_stream;
11864 GOutputStream *output_stream;
11865 GFile *file;
11866 GFileInfo *file_info;
11867 goffset total_num_bytes;
11868 gssize bytes_read;
11869 const gchar *content_type;
11870 const gchar *filename;
11871 const gchar *selector;
11872 gchar buffer[4096];
11873 } ImageLoadContext;
11875 /* Forward Declaration */
11876 static void
11877 image_load_stream_read_cb (GInputStream *input_stream,
11878 GAsyncResult *result,
11879 ImageLoadContext *load_context);
11881 static ImageLoadContext *
11882 image_load_context_new (EEditorPage *editor_page)
11884 ImageLoadContext *load_context;
11886 load_context = g_slice_new0 (ImageLoadContext);
11887 load_context->editor_page = editor_page;
11889 return load_context;
11892 static void
11893 image_load_context_free (ImageLoadContext *load_context)
11895 if (load_context->input_stream != NULL)
11896 g_object_unref (load_context->input_stream);
11898 if (load_context->output_stream != NULL)
11899 g_object_unref (load_context->output_stream);
11901 if (load_context->file_info != NULL)
11902 g_object_unref (load_context->file_info);
11904 if (load_context->file != NULL)
11905 g_object_unref (load_context->file);
11907 g_slice_free (ImageLoadContext, load_context);
11910 static void
11911 image_load_finish (ImageLoadContext *load_context)
11913 EEditorPage *editor_page;
11914 GMemoryOutputStream *output_stream;
11915 const gchar *selector;
11916 gchar *base64_encoded, *mime_type, *output, *uri;
11917 gsize size;
11918 gpointer data;
11920 output_stream = G_MEMORY_OUTPUT_STREAM (load_context->output_stream);
11921 editor_page = load_context->editor_page;
11922 mime_type = g_content_type_get_mime_type (load_context->content_type);
11924 data = g_memory_output_stream_get_data (output_stream);
11925 size = g_memory_output_stream_get_data_size (output_stream);
11926 uri = g_file_get_uri (load_context->file);
11928 base64_encoded = g_base64_encode ((const guchar *) data, size);
11929 output = g_strconcat ("data:", mime_type, ";base64,", base64_encoded, NULL);
11930 selector = load_context->selector;
11931 if (selector && *selector)
11932 e_editor_dom_replace_base64_image_src (editor_page, selector, output, load_context->filename, uri);
11933 else
11934 e_editor_dom_insert_base64_image (editor_page, output, load_context->filename, uri);
11936 g_free (base64_encoded);
11937 g_free (output);
11938 g_free (mime_type);
11939 g_free (uri);
11941 image_load_context_free (load_context);
11944 static void
11945 image_load_write_cb (GOutputStream *output_stream,
11946 GAsyncResult *result,
11947 ImageLoadContext *load_context)
11949 GInputStream *input_stream;
11950 gssize bytes_written;
11951 GError *error = NULL;
11953 bytes_written = g_output_stream_write_finish (
11954 output_stream, result, &error);
11956 if (error) {
11957 image_load_context_free (load_context);
11958 return;
11961 input_stream = load_context->input_stream;
11963 if (bytes_written < load_context->bytes_read) {
11964 g_memmove (
11965 load_context->buffer,
11966 load_context->buffer + bytes_written,
11967 load_context->bytes_read - bytes_written);
11968 load_context->bytes_read -= bytes_written;
11970 g_output_stream_write_async (
11971 output_stream,
11972 load_context->buffer,
11973 load_context->bytes_read,
11974 G_PRIORITY_DEFAULT, NULL,
11975 (GAsyncReadyCallback) image_load_write_cb,
11976 load_context);
11977 } else
11978 g_input_stream_read_async (
11979 input_stream,
11980 load_context->buffer,
11981 sizeof (load_context->buffer),
11982 G_PRIORITY_DEFAULT, NULL,
11983 (GAsyncReadyCallback) image_load_stream_read_cb,
11984 load_context);
11987 static void
11988 image_load_stream_read_cb (GInputStream *input_stream,
11989 GAsyncResult *result,
11990 ImageLoadContext *load_context)
11992 GOutputStream *output_stream;
11993 gssize bytes_read;
11994 GError *error = NULL;
11996 bytes_read = g_input_stream_read_finish (
11997 input_stream, result, &error);
11999 if (error) {
12000 image_load_context_free (load_context);
12001 return;
12004 if (bytes_read == 0) {
12005 image_load_finish (load_context);
12006 return;
12009 output_stream = load_context->output_stream;
12010 load_context->bytes_read = bytes_read;
12012 g_output_stream_write_async (
12013 output_stream,
12014 load_context->buffer,
12015 load_context->bytes_read,
12016 G_PRIORITY_DEFAULT, NULL,
12017 (GAsyncReadyCallback) image_load_write_cb,
12018 load_context);
12021 static void
12022 image_load_file_read_cb (GFile *file,
12023 GAsyncResult *result,
12024 ImageLoadContext *load_context)
12026 GFileInputStream *input_stream;
12027 GOutputStream *output_stream;
12028 GError *error = NULL;
12030 /* Input stream might be NULL, so don't use cast macro. */
12031 input_stream = g_file_read_finish (file, result, &error);
12032 load_context->input_stream = (GInputStream *) input_stream;
12034 if (error) {
12035 image_load_context_free (load_context);
12036 return;
12039 /* Load the contents into a GMemoryOutputStream. */
12040 output_stream = g_memory_output_stream_new (
12041 NULL, 0, g_realloc, g_free);
12043 load_context->output_stream = output_stream;
12045 g_input_stream_read_async (
12046 load_context->input_stream,
12047 load_context->buffer,
12048 sizeof (load_context->buffer),
12049 G_PRIORITY_DEFAULT, NULL,
12050 (GAsyncReadyCallback) image_load_stream_read_cb,
12051 load_context);
12054 static void
12055 image_load_query_info_cb (GFile *file,
12056 GAsyncResult *result,
12057 ImageLoadContext *load_context)
12059 GFileInfo *file_info;
12060 GError *error = NULL;
12062 file_info = g_file_query_info_finish (file, result, &error);
12063 if (error) {
12064 image_load_context_free (load_context);
12065 return;
12068 load_context->content_type = g_file_info_get_content_type (file_info);
12069 load_context->total_num_bytes = g_file_info_get_size (file_info);
12070 load_context->filename = g_file_info_get_name (file_info);
12072 g_file_read_async (
12073 file, G_PRIORITY_DEFAULT,
12074 NULL, (GAsyncReadyCallback)
12075 image_load_file_read_cb, load_context);
12078 static void
12079 image_load_and_insert_async (EEditorPage *editor_page,
12080 const gchar *selector,
12081 const gchar *uri)
12083 ImageLoadContext *load_context;
12084 GFile *file;
12086 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
12087 g_return_if_fail (uri && *uri);
12089 file = g_file_new_for_uri (uri);
12090 g_return_if_fail (file != NULL);
12092 load_context = image_load_context_new (editor_page);
12093 load_context->file = file;
12094 if (selector && *selector)
12095 load_context->selector = g_strdup (selector);
12097 g_file_query_info_async (
12098 file, "standard::*",
12099 G_FILE_QUERY_INFO_NONE,G_PRIORITY_DEFAULT,
12100 NULL, (GAsyncReadyCallback)
12101 image_load_query_info_cb, load_context);
12104 void
12105 e_editor_dom_insert_image (EEditorPage *editor_page,
12106 const gchar *uri)
12108 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
12110 if (!e_editor_page_get_html_mode (editor_page))
12111 return;
12113 if (strstr (uri, ";base64,")) {
12114 if (g_str_has_prefix (uri, "data:"))
12115 e_editor_dom_insert_base64_image (editor_page, uri, "", "");
12116 if (strstr (uri, ";data")) {
12117 const gchar *base64_data = strstr (uri, ";") + 1;
12118 gchar *filename;
12119 glong filename_length;
12121 filename_length =
12122 g_utf8_strlen (uri, -1) -
12123 g_utf8_strlen (base64_data, -1) - 1;
12124 filename = g_strndup (uri, filename_length);
12126 e_editor_dom_insert_base64_image (editor_page, base64_data, filename, "");
12127 g_free (filename);
12129 } else
12130 image_load_and_insert_async (editor_page, NULL, uri);
12133 void
12134 e_editor_dom_replace_image_src (EEditorPage *editor_page,
12135 const gchar *selector,
12136 const gchar *uri)
12138 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
12140 if (strstr (uri, ";base64,")) {
12141 if (g_str_has_prefix (uri, "data:"))
12142 e_editor_dom_replace_base64_image_src (
12143 editor_page, selector, uri, "", "");
12144 if (strstr (uri, ";data")) {
12145 const gchar *base64_data = strstr (uri, ";") + 1;
12146 gchar *filename;
12147 glong filename_length;
12149 filename_length =
12150 g_utf8_strlen (uri, -1) -
12151 g_utf8_strlen (base64_data, -1) - 1;
12152 filename = g_strndup (uri, filename_length);
12154 e_editor_dom_replace_base64_image_src (
12155 editor_page, selector, base64_data, filename, "");
12156 g_free (filename);
12158 } else
12159 image_load_and_insert_async (editor_page, selector, uri);
12163 * e_html_editor_selection_unlink:
12164 * @selection: an #EEditorSelection
12166 * Removes any links (&lt;A&gt; elements) from current selection or at current
12167 * cursor position.
12169 void
12170 e_editor_dom_selection_unlink (EEditorPage *editor_page)
12172 WebKitDOMDocument *document;
12173 WebKitDOMDOMWindow *dom_window = NULL;
12174 WebKitDOMDOMSelection *dom_selection = NULL;
12175 WebKitDOMRange *range = NULL;
12176 WebKitDOMElement *link;
12177 EEditorUndoRedoManager *manager;
12178 gchar *text;
12180 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
12182 document = e_editor_page_get_document (editor_page);
12183 dom_window = webkit_dom_document_get_default_view (document);
12184 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
12186 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
12187 link = dom_node_find_parent_element (
12188 webkit_dom_range_get_start_container (range, NULL), "A");
12190 g_clear_object (&dom_selection);
12191 g_clear_object (&dom_window);
12193 if (!link) {
12194 WebKitDOMNode *node;
12196 /* get element that was clicked on */
12197 node = webkit_dom_range_get_common_ancestor_container (range, NULL);
12198 if (node && !WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node)) {
12199 link = dom_node_find_parent_element (node, "A");
12200 if (link && !WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (link)) {
12201 g_clear_object (&range);
12202 return;
12203 } else
12204 link = WEBKIT_DOM_ELEMENT (node);
12208 g_clear_object (&range);
12210 if (!link)
12211 return;
12213 manager = e_editor_page_get_undo_redo_manager (editor_page);
12214 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
12215 EEditorHistoryEvent *ev;
12216 WebKitDOMDocumentFragment *fragment;
12218 ev = g_new0 (EEditorHistoryEvent, 1);
12219 ev->type = HISTORY_REMOVE_LINK;
12221 e_editor_dom_selection_get_coordinates (editor_page,
12222 &ev->before.start.x,
12223 &ev->before.start.y,
12224 &ev->before.end.x,
12225 &ev->before.end.y);
12227 fragment = webkit_dom_document_create_document_fragment (document);
12228 webkit_dom_node_append_child (
12229 WEBKIT_DOM_NODE (fragment),
12230 webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (link), TRUE, NULL),
12231 NULL);
12232 ev->data.fragment = g_object_ref (fragment);
12234 e_editor_undo_redo_manager_insert_history_event (manager, ev);
12237 text = webkit_dom_html_element_get_inner_text (
12238 WEBKIT_DOM_HTML_ELEMENT (link));
12239 webkit_dom_element_set_outer_html (link, text, NULL);
12240 g_free (text);
12244 * e_html_editor_selection_create_link:
12245 * @document: a @WebKitDOMDocument
12246 * @uri: destination of the new link
12248 * Converts current selection into a link pointing to @url.
12250 void
12251 e_editor_dom_create_link (EEditorPage *editor_page,
12252 const gchar *uri)
12254 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
12255 g_return_if_fail (uri != NULL && *uri != '\0');
12257 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_CREATE_LINK, uri);
12260 static gint
12261 get_list_level (WebKitDOMNode *node)
12263 gint level = 0;
12265 while (node && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node)) {
12266 if (node_is_list (node))
12267 level++;
12268 node = webkit_dom_node_get_parent_node (node);
12271 return level;
12274 static void
12275 set_ordered_list_type_to_element (WebKitDOMElement *list,
12276 EContentEditorBlockFormat format)
12278 if (format == E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST)
12279 webkit_dom_element_remove_attribute (list, "type");
12280 else if (format == E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST_ALPHA)
12281 webkit_dom_element_set_attribute (list, "type", "A", NULL);
12282 else if (format == E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST_ROMAN)
12283 webkit_dom_element_set_attribute (list, "type", "I", NULL);
12286 static const gchar *
12287 get_css_alignment_value_class (EContentEditorAlignment alignment)
12289 if (alignment == E_CONTENT_EDITOR_ALIGNMENT_LEFT)
12290 return ""; /* Left is by default on ltr */
12292 if (alignment == E_CONTENT_EDITOR_ALIGNMENT_CENTER)
12293 return "-x-evo-align-center";
12295 if (alignment == E_CONTENT_EDITOR_ALIGNMENT_RIGHT)
12296 return "-x-evo-align-right";
12298 return "";
12302 * e_html_editor_selection_get_alignment:
12303 * @selection: #an EEditorSelection
12305 * Returns alignment of current paragraph
12307 * Returns: #EContentEditorAlignment
12309 static EContentEditorAlignment
12310 dom_get_alignment (EEditorPage *editor_page)
12312 WebKitDOMDocument *document;
12313 WebKitDOMCSSStyleDeclaration *style = NULL;
12314 WebKitDOMDOMWindow *dom_window = NULL;
12315 WebKitDOMElement *element;
12316 WebKitDOMNode *node;
12317 WebKitDOMRange *range = NULL;
12318 EContentEditorAlignment alignment;
12319 gchar *value;
12321 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), E_CONTENT_EDITOR_ALIGNMENT_LEFT);
12323 document = e_editor_page_get_document (editor_page);
12324 range = e_editor_dom_get_current_range (editor_page);
12325 if (!range)
12326 return E_CONTENT_EDITOR_ALIGNMENT_LEFT;
12328 node = webkit_dom_range_get_start_container (range, NULL);
12329 g_clear_object (&range);
12330 if (!node)
12331 return E_CONTENT_EDITOR_ALIGNMENT_LEFT;
12333 if (WEBKIT_DOM_IS_ELEMENT (node))
12334 element = WEBKIT_DOM_ELEMENT (node);
12335 else
12336 element = WEBKIT_DOM_ELEMENT (e_editor_dom_get_parent_block_node_from_child (node));
12338 if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (element)) {
12339 if (element_has_class (element, "-x-evo-align-right"))
12340 alignment = E_CONTENT_EDITOR_ALIGNMENT_RIGHT;
12341 else if (element_has_class (element, "-x-evo-align-center"))
12342 alignment = E_CONTENT_EDITOR_ALIGNMENT_CENTER;
12343 else
12344 alignment = E_CONTENT_EDITOR_ALIGNMENT_LEFT;
12346 return alignment;
12349 dom_window = webkit_dom_document_get_default_view (document);
12350 style = webkit_dom_dom_window_get_computed_style (dom_window, element, NULL);
12351 value = webkit_dom_css_style_declaration_get_property_value (style, "text-align");
12353 if (!value || !*value ||
12354 (g_ascii_strncasecmp (value, "left", 4) == 0)) {
12355 alignment = E_CONTENT_EDITOR_ALIGNMENT_LEFT;
12356 } else if (g_ascii_strncasecmp (value, "center", 6) == 0) {
12357 alignment = E_CONTENT_EDITOR_ALIGNMENT_CENTER;
12358 } else if (g_ascii_strncasecmp (value, "right", 5) == 0) {
12359 alignment = E_CONTENT_EDITOR_ALIGNMENT_RIGHT;
12360 } else {
12361 alignment = E_CONTENT_EDITOR_ALIGNMENT_LEFT;
12364 g_clear_object (&dom_window);
12365 g_clear_object (&style);
12366 g_free (value);
12368 return alignment;
12371 static gint
12372 set_word_wrap_length (EEditorPage *editor_page,
12373 gint user_word_wrap_length)
12375 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), 0);
12377 /* user_word_wrap_length < 0, set block width to word_wrap_length
12378 * user_word_wrap_length == 0, no width limit set,
12379 * user_word_wrap_length > 0, set width limit to given value */
12380 return (user_word_wrap_length < 0) ?
12381 e_editor_page_get_word_wrap_length (editor_page) : user_word_wrap_length;
12384 void
12385 e_editor_dom_set_paragraph_style (EEditorPage *editor_page,
12386 WebKitDOMElement *element,
12387 gint width,
12388 gint offset,
12389 const gchar *style_to_add)
12391 WebKitDOMNode *parent;
12392 gchar *style = NULL;
12393 gint word_wrap_length;
12395 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
12397 word_wrap_length = set_word_wrap_length (editor_page, width);
12398 webkit_dom_element_set_attribute (element, "data-evo-paragraph", "", NULL);
12400 /* Don't set the alignment for nodes as they are handled separately. */
12401 if (!node_is_list (WEBKIT_DOM_NODE (element))) {
12402 EContentEditorAlignment alignment;
12404 alignment = dom_get_alignment (editor_page);
12405 element_add_class (element, get_css_alignment_value_class (alignment));
12408 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
12409 /* Don't set the width limit to sub-blocks as the width limit is inhered
12410 * from its parents. */
12411 if (!e_editor_page_get_html_mode (editor_page) &&
12412 (!parent || WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent))) {
12413 style = g_strdup_printf (
12414 "width: %dch;%s%s",
12415 (word_wrap_length + offset),
12416 style_to_add && *style_to_add ? " " : "",
12417 style_to_add && *style_to_add ? style_to_add : "");
12418 } else {
12419 if (style_to_add && *style_to_add)
12420 style = g_strdup_printf ("%s", style_to_add);
12422 if (style) {
12423 webkit_dom_element_set_attribute (element, "style", style, NULL);
12424 g_free (style);
12428 static WebKitDOMElement *
12429 create_list_element (EEditorPage *editor_page,
12430 EContentEditorBlockFormat format,
12431 gint level,
12432 gboolean html_mode)
12434 WebKitDOMDocument *document;
12435 WebKitDOMElement *list;
12436 gboolean inserting_unordered_list;
12438 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
12440 document = e_editor_page_get_document (editor_page);
12441 inserting_unordered_list = format == E_CONTENT_EDITOR_BLOCK_FORMAT_UNORDERED_LIST;
12443 list = webkit_dom_document_create_element (
12444 document, inserting_unordered_list ? "UL" : "OL", NULL);
12446 if (!inserting_unordered_list)
12447 set_ordered_list_type_to_element (list, format);
12449 if (level >= 0 && !html_mode) {
12450 gint offset;
12452 offset = (level + 1) * SPACES_PER_LIST_LEVEL;
12454 offset += !inserting_unordered_list ?
12455 SPACES_ORDERED_LIST_FIRST_LEVEL - SPACES_PER_LIST_LEVEL: 0;
12457 e_editor_dom_set_paragraph_style (editor_page, list, -1, -offset, NULL);
12460 return list;
12463 static gboolean
12464 indent_list (EEditorPage *editor_page)
12466 WebKitDOMDocument *document;
12467 WebKitDOMElement *selection_start_marker, *selection_end_marker;
12468 WebKitDOMNode *item, *next_item;
12469 gboolean after_selection_end = FALSE;
12471 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
12473 document = e_editor_page_get_document (editor_page);
12474 selection_start_marker = webkit_dom_document_get_element_by_id (
12475 document, "-x-evo-selection-start-marker");
12476 selection_end_marker = webkit_dom_document_get_element_by_id (
12477 document, "-x-evo-selection-end-marker");
12479 item = e_editor_dom_get_parent_block_node_from_child (
12480 WEBKIT_DOM_NODE (selection_start_marker));
12482 if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (item)) {
12483 gboolean html_mode = e_editor_page_get_html_mode (editor_page);
12484 WebKitDOMElement *list;
12485 WebKitDOMNode *source_list = webkit_dom_node_get_parent_node (item);
12486 EContentEditorBlockFormat format;
12488 format = dom_get_list_format_from_node (source_list);
12490 list = create_list_element (
12491 editor_page, format, get_list_level (item), html_mode);
12493 element_add_class (list, "-x-evo-indented");
12495 webkit_dom_node_insert_before (
12496 source_list, WEBKIT_DOM_NODE (list), item, NULL);
12498 while (item && !after_selection_end) {
12499 after_selection_end = webkit_dom_node_contains (
12500 item, WEBKIT_DOM_NODE (selection_end_marker));
12502 next_item = webkit_dom_node_get_next_sibling (item);
12504 webkit_dom_node_append_child (
12505 WEBKIT_DOM_NODE (list), item, NULL);
12507 item = next_item;
12510 merge_lists_if_possible (WEBKIT_DOM_NODE (list));
12513 return after_selection_end;
12516 static void
12517 dom_set_indented_style (EEditorPage *editor_page,
12518 WebKitDOMElement *element,
12519 gint width)
12521 gchar *style;
12522 gint word_wrap_length;
12524 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
12526 word_wrap_length = set_word_wrap_length (editor_page, width);
12527 webkit_dom_element_set_class_name (element, "-x-evo-indented");
12529 if (e_editor_page_get_html_mode (editor_page) || word_wrap_length == 0) {
12530 style = g_strdup_printf ("margin-left: %dch;", SPACES_PER_INDENTATION);
12532 if (word_wrap_length != 0) {
12533 gchar *plain_text_style;
12535 plain_text_style = g_strdup_printf (
12536 "margin-left: %dch; word-wrap: normal; width: %dch;",
12537 SPACES_PER_INDENTATION, word_wrap_length);
12539 webkit_dom_element_set_attribute (
12540 element, "data-plain-text-style", plain_text_style, NULL);
12541 g_free (plain_text_style);
12543 } else {
12544 style = g_strdup_printf (
12545 "margin-left: %dch; word-wrap: normal; width: %dch;",
12546 SPACES_PER_INDENTATION, word_wrap_length);
12549 webkit_dom_element_set_attribute (element, "style", style, NULL);
12550 g_free (style);
12553 static WebKitDOMElement *
12554 dom_get_indented_element (EEditorPage *editor_page,
12555 gint width)
12557 WebKitDOMDocument *document;
12558 WebKitDOMElement *element;
12560 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
12562 document = e_editor_page_get_document (editor_page);
12563 element = webkit_dom_document_create_element (document, "DIV", NULL);
12564 dom_set_indented_style (editor_page, element, width);
12566 return element;
12569 static WebKitDOMNode *
12570 indent_block (EEditorPage *editor_page,
12571 WebKitDOMNode *block,
12572 gint width)
12574 WebKitDOMElement *element;
12575 WebKitDOMNode *sibling, *tmp;
12577 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
12579 sibling = webkit_dom_node_get_previous_sibling (block);
12580 if (WEBKIT_DOM_IS_ELEMENT (sibling) &&
12581 element_has_class (WEBKIT_DOM_ELEMENT (sibling), "-x-evo-indented")) {
12582 element = WEBKIT_DOM_ELEMENT (sibling);
12583 } else {
12584 element = dom_get_indented_element (editor_page, width);
12586 webkit_dom_node_insert_before (
12587 webkit_dom_node_get_parent_node (block),
12588 WEBKIT_DOM_NODE (element),
12589 block,
12590 NULL);
12593 /* Remove style and let the paragraph inherit it from parent */
12594 if (webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (block), "data-evo-paragraph"))
12595 webkit_dom_element_remove_attribute (
12596 WEBKIT_DOM_ELEMENT (block), "style");
12598 tmp = webkit_dom_node_append_child (
12599 WEBKIT_DOM_NODE (element),
12600 block,
12601 NULL);
12603 sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element));
12605 while (WEBKIT_DOM_IS_ELEMENT (sibling) &&
12606 element_has_class (WEBKIT_DOM_ELEMENT (sibling), "-x-evo-indented")) {
12607 WebKitDOMNode *next_sibling;
12608 WebKitDOMNode *child;
12610 next_sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (sibling));
12612 while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (sibling)))) {
12613 webkit_dom_node_append_child (
12614 WEBKIT_DOM_NODE (element),
12615 child,
12616 NULL);
12618 remove_node (sibling);
12619 sibling = next_sibling;
12622 return tmp;
12625 static WebKitDOMNode *
12626 get_list_item_node_from_child (WebKitDOMNode *child)
12628 WebKitDOMNode *parent = webkit_dom_node_get_parent_node (child);
12630 while (parent && !WEBKIT_DOM_IS_HTML_LI_ELEMENT (parent))
12631 parent = webkit_dom_node_get_parent_node (parent);
12633 return parent;
12636 static WebKitDOMNode *
12637 get_list_node_from_child (WebKitDOMNode *child)
12639 WebKitDOMNode *parent = get_list_item_node_from_child (child);
12641 return webkit_dom_node_get_parent_node (parent);
12644 static gboolean
12645 do_format_change_list_to_block (EEditorPage *editor_page,
12646 EContentEditorBlockFormat format,
12647 WebKitDOMNode *item,
12648 const gchar *value)
12650 WebKitDOMDocument *document;
12651 WebKitDOMElement *element, *selection_end;
12652 WebKitDOMNode *node, *source_list;
12653 gboolean after_end = FALSE;
12654 gint level;
12656 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
12658 document = e_editor_page_get_document (editor_page);
12659 selection_end = webkit_dom_document_get_element_by_id (
12660 document, "-x-evo-selection-end-marker");
12662 source_list = webkit_dom_node_get_parent_node (item);
12663 while (source_list) {
12664 WebKitDOMNode *parent;
12666 parent = webkit_dom_node_get_parent_node (source_list);
12667 if (node_is_list (parent))
12668 source_list = parent;
12669 else
12670 break;
12673 if (webkit_dom_node_contains (source_list, WEBKIT_DOM_NODE (selection_end)))
12674 source_list = split_list_into_two (item, -1);
12675 else {
12676 source_list = webkit_dom_node_get_next_sibling (source_list);
12679 /* Process all nodes that are in selection one by one */
12680 while (item && WEBKIT_DOM_IS_HTML_LI_ELEMENT (item)) {
12681 WebKitDOMNode *next_item;
12683 next_item = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (item));
12684 if (!next_item) {
12685 WebKitDOMNode *parent;
12686 WebKitDOMNode *tmp = item;
12688 while (tmp) {
12689 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (tmp));
12690 if (!node_is_list (parent))
12691 break;
12693 next_item = webkit_dom_node_get_next_sibling (parent);
12694 if (node_is_list (next_item)) {
12695 next_item = webkit_dom_node_get_first_child (next_item);
12696 break;
12697 } else if (next_item && !WEBKIT_DOM_IS_HTML_LI_ELEMENT (next_item)) {
12698 next_item = webkit_dom_node_get_next_sibling (next_item);
12699 break;
12700 } else if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (next_item)) {
12701 break;
12703 tmp = parent;
12705 } else if (node_is_list (next_item)) {
12706 next_item = webkit_dom_node_get_first_child (next_item);
12707 } else if (!WEBKIT_DOM_IS_HTML_LI_ELEMENT (next_item)) {
12708 next_item = webkit_dom_node_get_next_sibling (item);
12709 continue;
12712 if (!after_end) {
12713 after_end = webkit_dom_node_contains (item, WEBKIT_DOM_NODE (selection_end));
12715 level = get_indentation_level (WEBKIT_DOM_ELEMENT (item));
12717 if (format == E_CONTENT_EDITOR_BLOCK_FORMAT_PARAGRAPH) {
12718 element = e_editor_dom_get_paragraph_element (editor_page, -1, 0);
12719 } else
12720 element = webkit_dom_document_create_element (
12721 document, value, NULL);
12723 while ((node = webkit_dom_node_get_first_child (item)))
12724 webkit_dom_node_append_child (
12725 WEBKIT_DOM_NODE (element), node, NULL);
12727 webkit_dom_node_insert_before (
12728 webkit_dom_node_get_parent_node (source_list),
12729 WEBKIT_DOM_NODE (element),
12730 source_list,
12731 NULL);
12733 if (level > 0) {
12734 gint final_width = 0;
12736 node = WEBKIT_DOM_NODE (element);
12738 if (webkit_dom_element_has_attribute (element, "data-evo-paragraph"))
12739 final_width = e_editor_page_get_word_wrap_length (editor_page) -
12740 SPACES_PER_INDENTATION * level;
12742 while (level--)
12743 node = indent_block (editor_page, node, final_width);
12746 e_editor_dom_remove_node_and_parents_if_empty (item);
12747 } else
12748 break;
12750 item = next_item;
12753 remove_node_if_empty (source_list);
12755 return after_end;
12758 static void
12759 format_change_list_to_block (EEditorPage *editor_page,
12760 EContentEditorBlockFormat format,
12761 const gchar *value)
12763 WebKitDOMDocument *document;
12764 WebKitDOMElement *selection_start;
12765 WebKitDOMNode *item;
12767 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
12769 document = e_editor_page_get_document (editor_page);
12771 selection_start = webkit_dom_document_get_element_by_id (
12772 document, "-x-evo-selection-start-marker");
12774 item = get_list_item_node_from_child (WEBKIT_DOM_NODE (selection_start));
12776 do_format_change_list_to_block (editor_page, format, item, value);
12779 static WebKitDOMNode *
12780 get_parent_indented_block (WebKitDOMNode *node)
12782 WebKitDOMNode *parent, *block = NULL;
12784 parent = webkit_dom_node_get_parent_node (node);
12785 if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-indented"))
12786 block = parent;
12788 while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
12789 if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-indented"))
12790 block = parent;
12791 parent = webkit_dom_node_get_parent_node (parent);
12794 return block;
12797 static WebKitDOMElement*
12798 get_element_for_inspection (WebKitDOMRange *range)
12800 WebKitDOMNode *node;
12802 node = webkit_dom_range_get_end_container (range, NULL);
12803 /* No selection or whole body selected */
12804 if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node))
12805 return NULL;
12807 return WEBKIT_DOM_ELEMENT (get_parent_indented_block (node));
12810 static EContentEditorAlignment
12811 dom_get_alignment_from_node (WebKitDOMNode *node)
12813 EContentEditorAlignment alignment;
12814 gchar *value;
12815 WebKitDOMCSSStyleDeclaration *style = NULL;
12817 style = webkit_dom_element_get_style (WEBKIT_DOM_ELEMENT (node));
12818 value = webkit_dom_css_style_declaration_get_property_value (style, "text-align");
12820 if (!value || !*value ||
12821 (g_ascii_strncasecmp (value, "left", 4) == 0)) {
12822 alignment = E_CONTENT_EDITOR_ALIGNMENT_LEFT;
12823 } else if (g_ascii_strncasecmp (value, "center", 6) == 0) {
12824 alignment = E_CONTENT_EDITOR_ALIGNMENT_CENTER;
12825 } else if (g_ascii_strncasecmp (value, "right", 5) == 0) {
12826 alignment = E_CONTENT_EDITOR_ALIGNMENT_RIGHT;
12827 } else {
12828 alignment = E_CONTENT_EDITOR_ALIGNMENT_LEFT;
12831 g_clear_object (&style);
12832 g_free (value);
12834 return alignment;
12838 * e_html_editor_selection_indent:
12839 * @selection: an #EEditorSelection
12841 * Indents current paragraph by one level.
12843 void
12844 e_editor_dom_selection_indent (EEditorPage *editor_page)
12846 WebKitDOMDocument *document;
12847 WebKitDOMElement *selection_start_marker, *selection_end_marker;
12848 WebKitDOMNode *block;
12849 EEditorHistoryEvent *ev = NULL;
12850 EEditorUndoRedoManager *manager;
12851 gboolean after_selection_start = FALSE, after_selection_end = FALSE;
12853 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
12855 document = e_editor_page_get_document (editor_page);
12856 e_editor_dom_selection_save (editor_page);
12858 manager = e_editor_page_get_undo_redo_manager (editor_page);
12860 selection_start_marker = webkit_dom_document_get_element_by_id (
12861 document, "-x-evo-selection-start-marker");
12862 selection_end_marker = webkit_dom_document_get_element_by_id (
12863 document, "-x-evo-selection-end-marker");
12865 /* If the selection was not saved, move it into the first child of body */
12866 if (!selection_start_marker || !selection_end_marker) {
12867 WebKitDOMHTMLElement *body;
12868 WebKitDOMNode *child;
12870 body = webkit_dom_document_get_body (document);
12871 child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
12873 dom_add_selection_markers_into_element_start (
12874 document,
12875 WEBKIT_DOM_ELEMENT (child),
12876 &selection_start_marker,
12877 &selection_end_marker);
12880 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
12881 ev = g_new0 (EEditorHistoryEvent, 1);
12882 ev->type = HISTORY_INDENT;
12884 e_editor_dom_selection_get_coordinates (editor_page,
12885 &ev->before.start.x,
12886 &ev->before.start.y,
12887 &ev->before.end.x,
12888 &ev->before.end.y);
12890 ev->data.style.from = 1;
12891 ev->data.style.to = 1;
12894 block = get_parent_indented_block (
12895 WEBKIT_DOM_NODE (selection_start_marker));
12896 if (!block)
12897 block = e_editor_dom_get_parent_block_node_from_child (
12898 WEBKIT_DOM_NODE (selection_start_marker));
12900 while (block && !after_selection_end) {
12901 gint ii, length, level, word_wrap_length, final_width = 0;
12902 WebKitDOMNode *next_block;
12903 WebKitDOMNodeList *list = NULL;
12905 word_wrap_length = e_editor_page_get_word_wrap_length (editor_page);
12907 next_block = webkit_dom_node_get_next_sibling (block);
12909 list = webkit_dom_element_query_selector_all (
12910 WEBKIT_DOM_ELEMENT (block),
12911 ".-x-evo-indented > *:not(.-x-evo-indented):not(li)",
12912 NULL);
12914 after_selection_end = webkit_dom_node_contains (
12915 block, WEBKIT_DOM_NODE (selection_end_marker));
12917 length = webkit_dom_node_list_get_length (list);
12918 if (length == 0 && node_is_list_or_item (block)) {
12919 after_selection_end = indent_list (editor_page);
12920 goto next;
12923 if (length == 0) {
12924 if (!after_selection_start) {
12925 after_selection_start = webkit_dom_node_contains (
12926 block, WEBKIT_DOM_NODE (selection_start_marker));
12927 if (!after_selection_start)
12928 goto next;
12931 if (webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (block), "data-evo-paragraph")) {
12932 level = get_indentation_level (WEBKIT_DOM_ELEMENT (block));
12934 final_width = word_wrap_length - SPACES_PER_INDENTATION * (level + 1);
12935 if (final_width < MINIMAL_PARAGRAPH_WIDTH &&
12936 !e_editor_page_get_html_mode (editor_page))
12937 goto next;
12940 indent_block (editor_page, block, final_width);
12942 if (after_selection_end)
12943 goto next;
12946 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
12947 WebKitDOMNode *block_to_process;
12949 block_to_process = webkit_dom_node_list_item (list, ii);
12951 after_selection_end = webkit_dom_node_contains (
12952 block_to_process, WEBKIT_DOM_NODE (selection_end_marker));
12954 if (!after_selection_start) {
12955 after_selection_start = webkit_dom_node_contains (
12956 block_to_process,
12957 WEBKIT_DOM_NODE (selection_start_marker));
12958 if (!after_selection_start)
12959 continue;
12962 if (webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (block_to_process), "data-evo-paragraph")) {
12963 level = get_indentation_level (
12964 WEBKIT_DOM_ELEMENT (block_to_process));
12966 final_width = word_wrap_length - SPACES_PER_INDENTATION * (level + 1);
12967 if (final_width < MINIMAL_PARAGRAPH_WIDTH &&
12968 !e_editor_page_get_html_mode (editor_page))
12969 continue;
12972 indent_block (editor_page, block_to_process, final_width);
12974 if (after_selection_end)
12975 break;
12978 next:
12979 g_clear_object (&list);
12981 if (!after_selection_end)
12982 block = next_block;
12985 if (ev) {
12986 e_editor_dom_selection_get_coordinates (editor_page,
12987 &ev->after.start.x,
12988 &ev->after.start.y,
12989 &ev->after.end.x,
12990 &ev->after.end.y);
12991 e_editor_undo_redo_manager_insert_history_event (manager, ev);
12994 e_editor_dom_selection_restore (editor_page);
12995 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
12996 e_editor_page_emit_content_changed (editor_page);
12999 static void
13000 unindent_list (WebKitDOMDocument *document)
13002 gboolean after = FALSE;
13003 WebKitDOMElement *new_list;
13004 WebKitDOMElement *selection_start_marker, *selection_end_marker;
13005 WebKitDOMNode *source_list, *source_list_clone, *current_list, *item;
13006 WebKitDOMNode *prev_item;
13008 selection_start_marker = webkit_dom_document_get_element_by_id (
13009 document, "-x-evo-selection-start-marker");
13010 selection_end_marker = webkit_dom_document_get_element_by_id (
13011 document, "-x-evo-selection-end-marker");
13013 if (!selection_start_marker || !selection_end_marker)
13014 return;
13016 /* Copy elements from previous block to list */
13017 item = e_editor_dom_get_parent_block_node_from_child (
13018 WEBKIT_DOM_NODE (selection_start_marker));
13019 source_list = webkit_dom_node_get_parent_node (item);
13020 new_list = WEBKIT_DOM_ELEMENT (
13021 webkit_dom_node_clone_node_with_error (source_list, FALSE, NULL));
13022 current_list = source_list;
13023 source_list_clone = webkit_dom_node_clone_node_with_error (source_list, FALSE, NULL);
13025 webkit_dom_node_insert_before (
13026 webkit_dom_node_get_parent_node (source_list),
13027 WEBKIT_DOM_NODE (source_list_clone),
13028 webkit_dom_node_get_next_sibling (source_list),
13029 NULL);
13031 if (element_has_class (WEBKIT_DOM_ELEMENT (source_list), "-x-evo-indented"))
13032 element_add_class (WEBKIT_DOM_ELEMENT (new_list), "-x-evo-indented");
13034 prev_item = source_list;
13036 while (item) {
13037 WebKitDOMNode *next_item = webkit_dom_node_get_next_sibling (item);
13039 if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (item)) {
13040 if (after)
13041 prev_item = webkit_dom_node_append_child (
13042 source_list_clone, WEBKIT_DOM_NODE (item), NULL);
13043 else
13044 prev_item = webkit_dom_node_insert_before (
13045 webkit_dom_node_get_parent_node (prev_item),
13046 item,
13047 webkit_dom_node_get_next_sibling (prev_item),
13048 NULL);
13051 if (webkit_dom_node_contains (item, WEBKIT_DOM_NODE (selection_end_marker)))
13052 after = TRUE;
13054 if (!next_item) {
13055 if (after)
13056 break;
13058 current_list = webkit_dom_node_get_next_sibling (current_list);
13059 next_item = webkit_dom_node_get_first_child (current_list);
13061 item = next_item;
13064 remove_node_if_empty (source_list_clone);
13065 remove_node_if_empty (source_list);
13068 static void
13069 unindent_block (EEditorPage *editor_page,
13070 WebKitDOMNode *block)
13072 WebKitDOMElement *element;
13073 WebKitDOMElement *prev_blockquote = NULL, *next_blockquote = NULL;
13074 WebKitDOMNode *block_to_process, *node_clone = NULL, *child;
13075 EContentEditorAlignment alignment;
13076 gboolean before_node = TRUE;
13077 gint word_wrap_length, level, width;
13079 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
13081 block_to_process = block;
13083 alignment = dom_get_alignment_from_node (block_to_process);
13084 element = webkit_dom_node_get_parent_element (block_to_process);
13086 if (!WEBKIT_DOM_IS_HTML_DIV_ELEMENT (element) &&
13087 !element_has_class (element, "-x-evo-indented"))
13088 return;
13090 element_add_class (WEBKIT_DOM_ELEMENT (block_to_process), "-x-evo-to-unindent");
13092 level = get_indentation_level (element);
13093 word_wrap_length = e_editor_page_get_word_wrap_length (editor_page);
13094 width = word_wrap_length - SPACES_PER_INDENTATION * level;
13096 /* Look if we have previous siblings, if so, we have to
13097 * create new blockquote that will include them */
13098 if (webkit_dom_node_get_previous_sibling (block_to_process))
13099 prev_blockquote = dom_get_indented_element (editor_page, width);
13101 /* Look if we have next siblings, if so, we have to
13102 * create new blockquote that will include them */
13103 if (webkit_dom_node_get_next_sibling (block_to_process))
13104 next_blockquote = dom_get_indented_element (editor_page, width);
13106 /* Copy nodes that are before / after the element that we want to unindent */
13107 while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)))) {
13108 if (webkit_dom_node_is_equal_node (child, block_to_process)) {
13109 before_node = FALSE;
13110 node_clone = webkit_dom_node_clone_node_with_error (child, TRUE, NULL);
13111 remove_node (child);
13112 continue;
13115 webkit_dom_node_append_child (
13116 before_node ?
13117 WEBKIT_DOM_NODE (prev_blockquote) :
13118 WEBKIT_DOM_NODE (next_blockquote),
13119 child,
13120 NULL);
13123 if (node_clone) {
13124 element_remove_class (WEBKIT_DOM_ELEMENT (node_clone), "-x-evo-to-unindent");
13126 /* Insert blockqoute with nodes that were before the element that we want to unindent */
13127 if (prev_blockquote) {
13128 if (webkit_dom_node_has_child_nodes (WEBKIT_DOM_NODE (prev_blockquote))) {
13129 webkit_dom_node_insert_before (
13130 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
13131 WEBKIT_DOM_NODE (prev_blockquote),
13132 WEBKIT_DOM_NODE (element),
13133 NULL);
13137 if (level == 1 && webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (node_clone), "data-evo-paragraph")) {
13138 e_editor_dom_set_paragraph_style (
13139 editor_page, WEBKIT_DOM_ELEMENT (node_clone), word_wrap_length, 0, NULL);
13140 element_add_class (
13141 WEBKIT_DOM_ELEMENT (node_clone),
13142 get_css_alignment_value_class (alignment));
13145 /* Insert the unindented element */
13146 webkit_dom_node_insert_before (
13147 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
13148 node_clone,
13149 WEBKIT_DOM_NODE (element),
13150 NULL);
13151 } else {
13152 g_warn_if_reached ();
13155 /* Insert blockqoute with nodes that were after the element that we want to unindent */
13156 if (next_blockquote) {
13157 if (webkit_dom_node_has_child_nodes (WEBKIT_DOM_NODE (next_blockquote))) {
13158 webkit_dom_node_insert_before (
13159 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
13160 WEBKIT_DOM_NODE (next_blockquote),
13161 WEBKIT_DOM_NODE (element),
13162 NULL);
13166 /* Remove old blockquote */
13167 remove_node (WEBKIT_DOM_NODE (element));
13171 * dom_unindent:
13172 * @selection: an #EEditorSelection
13174 * Unindents current paragraph by one level.
13176 void
13177 e_editor_dom_selection_unindent (EEditorPage *editor_page)
13179 WebKitDOMDocument *document;
13180 WebKitDOMElement *selection_start_marker, *selection_end_marker;
13181 WebKitDOMNode *block;
13182 EEditorHistoryEvent *ev = NULL;
13183 EEditorUndoRedoManager *manager;
13184 gboolean after_selection_start = FALSE, after_selection_end = FALSE;
13186 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
13188 document = e_editor_page_get_document (editor_page);
13189 e_editor_dom_selection_save (editor_page);
13191 selection_start_marker = webkit_dom_document_get_element_by_id (
13192 document, "-x-evo-selection-start-marker");
13193 selection_end_marker = webkit_dom_document_get_element_by_id (
13194 document, "-x-evo-selection-end-marker");
13196 /* If the selection was not saved, move it into the first child of body */
13197 if (!selection_start_marker || !selection_end_marker) {
13198 WebKitDOMHTMLElement *body;
13199 WebKitDOMNode *child;
13201 body = webkit_dom_document_get_body (document);
13202 child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
13204 dom_add_selection_markers_into_element_start (
13205 document,
13206 WEBKIT_DOM_ELEMENT (child),
13207 &selection_start_marker,
13208 &selection_end_marker);
13211 manager = e_editor_page_get_undo_redo_manager (editor_page);
13212 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
13213 ev = g_new0 (EEditorHistoryEvent, 1);
13214 ev->type = HISTORY_INDENT;
13216 e_editor_dom_selection_get_coordinates (editor_page,
13217 &ev->before.start.x,
13218 &ev->before.start.y,
13219 &ev->before.end.x,
13220 &ev->before.end.y);
13223 block = get_parent_indented_block (
13224 WEBKIT_DOM_NODE (selection_start_marker));
13225 if (!block)
13226 block = e_editor_dom_get_parent_block_node_from_child (
13227 WEBKIT_DOM_NODE (selection_start_marker));
13229 while (block && !after_selection_end) {
13230 gint ii, length;
13231 WebKitDOMNode *next_block;
13232 WebKitDOMNodeList *list = NULL;
13234 next_block = webkit_dom_node_get_next_sibling (block);
13236 list = webkit_dom_element_query_selector_all (
13237 WEBKIT_DOM_ELEMENT (block),
13238 ".-x-evo-indented > *:not(.-x-evo-indented):not(li)",
13239 NULL);
13241 after_selection_end = webkit_dom_node_contains (
13242 block, WEBKIT_DOM_NODE (selection_end_marker));
13244 length = webkit_dom_node_list_get_length (list);
13245 if (length == 0 && node_is_list_or_item (block)) {
13246 unindent_list (document);
13247 goto next;
13250 if (length == 0) {
13251 if (!after_selection_start) {
13252 after_selection_start = webkit_dom_node_contains (
13253 block, WEBKIT_DOM_NODE (selection_start_marker));
13254 if (!after_selection_start)
13255 goto next;
13258 unindent_block (editor_page, block);
13260 if (after_selection_end)
13261 goto next;
13264 for (ii = 0; ii < length; ii++) {
13265 WebKitDOMNode *block_to_process;
13267 block_to_process = webkit_dom_node_list_item (list, ii);
13269 after_selection_end = webkit_dom_node_contains (
13270 block_to_process,
13271 WEBKIT_DOM_NODE (selection_end_marker));
13273 if (!after_selection_start) {
13274 after_selection_start = webkit_dom_node_contains (
13275 block_to_process,
13276 WEBKIT_DOM_NODE (selection_start_marker));
13277 if (!after_selection_start)
13278 continue;
13281 unindent_block (editor_page, block_to_process);
13283 if (after_selection_end)
13284 break;
13286 next:
13287 g_clear_object (&list);
13288 block = next_block;
13291 if (ev) {
13292 e_editor_dom_selection_get_coordinates (editor_page,
13293 &ev->after.start.x,
13294 &ev->after.start.y,
13295 &ev->after.end.x,
13296 &ev->after.end.y);
13297 e_editor_undo_redo_manager_insert_history_event (manager, ev);
13300 e_editor_dom_selection_restore (editor_page);
13302 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
13303 e_editor_page_emit_content_changed (editor_page);
13306 static void
13307 dom_insert_selection_point (WebKitDOMNode *container,
13308 glong offset,
13309 WebKitDOMElement *selection_point)
13311 WebKitDOMNode *parent;
13313 parent = webkit_dom_node_get_parent_node (container);
13315 if (WEBKIT_DOM_IS_TEXT (container) ||
13316 WEBKIT_DOM_IS_COMMENT (container) ||
13317 WEBKIT_DOM_IS_CHARACTER_DATA (container)) {
13318 if (offset != 0) {
13319 WebKitDOMText *split_text;
13321 split_text = webkit_dom_text_split_text (
13322 WEBKIT_DOM_TEXT (container), offset, NULL);
13323 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (split_text));
13325 webkit_dom_node_insert_before (
13326 parent,
13327 WEBKIT_DOM_NODE (selection_point),
13328 WEBKIT_DOM_NODE (split_text),
13329 NULL);
13330 } else {
13331 webkit_dom_node_insert_before (
13332 parent,
13333 WEBKIT_DOM_NODE (selection_point),
13334 container,
13335 NULL);
13337 } else {
13338 gulong child_element_count = 0;
13340 child_element_count =
13341 webkit_dom_element_get_child_element_count (
13342 WEBKIT_DOM_ELEMENT (container));
13344 if (offset == 0) {
13345 /* Selection point is on the beginning of container */
13346 webkit_dom_node_insert_before (
13347 container,
13348 WEBKIT_DOM_NODE (selection_point),
13349 webkit_dom_node_get_first_child (container),
13350 NULL);
13351 } else if (offset != 0 && (offset == child_element_count)) {
13352 /* Selection point is on the end of container */
13353 webkit_dom_node_append_child (
13354 container, WEBKIT_DOM_NODE (selection_point), NULL);
13355 } else {
13356 WebKitDOMElement *child;
13357 gint ii = 0;
13359 child = webkit_dom_element_get_first_element_child (WEBKIT_DOM_ELEMENT (container));
13360 for (ii = 1; ii < child_element_count; ii++)
13361 child = webkit_dom_element_get_next_element_sibling (child);
13363 webkit_dom_node_insert_before (
13364 container,
13365 WEBKIT_DOM_NODE (selection_point),
13366 WEBKIT_DOM_NODE (child),
13367 NULL);
13371 webkit_dom_node_normalize (parent);
13375 * e_html_editor_selection_save:
13376 * @selection: an #EEditorSelection
13378 * Saves current cursor position or current selection range. The selection can
13379 * be later restored by calling e_html_editor_selection_restore().
13381 * Note that calling e_html_editor_selection_save() overwrites previously saved
13382 * position.
13384 * Note that this method inserts special markings into the HTML code that are
13385 * used to later restore the selection. It can happen that by deleting some
13386 * segments of the document some of the markings are deleted too. In that case
13387 * restoring the selection by e_html_editor_selection_restore() can fail. Also by
13388 * moving text segments (Cut & Paste) can result in moving the markings
13389 * elsewhere, thus e_html_editor_selection_restore() will restore the selection
13390 * incorrectly.
13392 * It is recommended to use this method only when you are not planning to make
13393 * bigger changes to content or structure of the document (formatting changes
13394 * are usually OK).
13396 void
13397 e_editor_dom_selection_save (EEditorPage *editor_page)
13399 WebKitDOMDocument *document;
13400 WebKitDOMDOMWindow *dom_window = NULL;
13401 WebKitDOMDOMSelection *dom_selection = NULL;
13402 WebKitDOMRange *range = NULL;
13403 WebKitDOMNode *container;
13404 WebKitDOMNode *anchor;
13405 WebKitDOMElement *start_marker = NULL, *end_marker = NULL;
13406 gboolean collapsed = FALSE;
13407 glong offset, anchor_offset;
13409 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
13411 document = e_editor_page_get_document (editor_page);
13413 /* First remove all markers (if present) */
13414 dom_remove_selection_markers (document);
13416 dom_window = webkit_dom_document_get_default_view (document);
13417 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
13418 g_clear_object (&dom_window);
13420 if (webkit_dom_dom_selection_get_range_count (dom_selection) < 1) {
13421 g_clear_object (&dom_selection);
13422 return;
13425 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
13426 if (!range) {
13427 g_clear_object (&dom_selection);
13428 return;
13431 anchor = webkit_dom_dom_selection_get_anchor_node (dom_selection);
13432 anchor_offset = webkit_dom_dom_selection_get_anchor_offset (dom_selection);
13434 collapsed = webkit_dom_range_get_collapsed (range, NULL);
13435 start_marker = dom_create_selection_marker (document, TRUE);
13437 container = webkit_dom_range_get_start_container (range, NULL);
13438 offset = webkit_dom_range_get_start_offset (range, NULL);
13440 if (webkit_dom_node_is_same_node (anchor, container) && offset == anchor_offset)
13441 webkit_dom_element_set_attribute (start_marker, "data-anchor", "", NULL);
13443 dom_insert_selection_point (container, offset, start_marker);
13445 end_marker = dom_create_selection_marker (document, FALSE);
13447 if (collapsed) {
13448 webkit_dom_node_insert_before (
13449 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (start_marker)),
13450 WEBKIT_DOM_NODE (end_marker),
13451 webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (start_marker)),
13452 NULL);
13453 goto out;
13456 container = webkit_dom_range_get_end_container (range, NULL);
13457 offset = webkit_dom_range_get_end_offset (range, NULL);
13459 if (webkit_dom_node_is_same_node (anchor, container) && offset == anchor_offset)
13460 webkit_dom_element_set_attribute (end_marker, "data-anchor", "", NULL);
13462 dom_insert_selection_point (container, offset, end_marker);
13464 if (!collapsed) {
13465 if (start_marker && end_marker) {
13466 webkit_dom_range_set_start_after (range, WEBKIT_DOM_NODE (start_marker), NULL);
13467 webkit_dom_range_set_end_before (range, WEBKIT_DOM_NODE (end_marker), NULL);
13468 } else {
13469 g_warn_if_reached ();
13472 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
13473 webkit_dom_dom_selection_add_range (dom_selection, range);
13475 out:
13476 g_clear_object (&range);
13477 g_clear_object (&dom_selection);
13480 gboolean
13481 e_editor_dom_is_selection_position_node (WebKitDOMNode *node)
13483 WebKitDOMElement *element;
13485 if (!node || !WEBKIT_DOM_IS_ELEMENT (node))
13486 return FALSE;
13488 element = WEBKIT_DOM_ELEMENT (node);
13490 return element_has_id (element, "-x-evo-selection-start-marker") ||
13491 element_has_id (element, "-x-evo-selection-end-marker");
13495 * e_html_editor_selection_restore:
13496 * @selection: an #EEditorSelection
13498 * Restores cursor position or selection range that was saved by
13499 * e_html_editor_selection_save().
13501 * Note that calling this function without calling e_html_editor_selection_save()
13502 * before is a programming error and the behavior is undefined.
13504 void
13505 e_editor_dom_selection_restore (EEditorPage *editor_page)
13507 WebKitDOMDocument *document;
13508 WebKitDOMElement *marker;
13509 WebKitDOMNode *selection_start_marker, *selection_end_marker;
13510 WebKitDOMNode *parent_start, *parent_end, *anchor;
13511 WebKitDOMRange *range = NULL;
13512 WebKitDOMDOMSelection *dom_selection = NULL;
13513 WebKitDOMDOMWindow *dom_window = NULL;
13514 gboolean start_is_anchor = FALSE;
13515 glong offset;
13517 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
13519 document = e_editor_page_get_document (editor_page);
13520 dom_window = webkit_dom_document_get_default_view (document);
13521 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
13522 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
13523 g_clear_object (&dom_window);
13524 if (!range) {
13525 WebKitDOMHTMLElement *body;
13527 range = webkit_dom_document_create_range (document);
13528 body = webkit_dom_document_get_body (document);
13530 webkit_dom_range_select_node_contents (range, WEBKIT_DOM_NODE (body), NULL);
13531 webkit_dom_range_collapse (range, TRUE, NULL);
13532 webkit_dom_dom_selection_add_range (dom_selection, range);
13535 selection_start_marker = webkit_dom_range_get_start_container (range, NULL);
13536 if (selection_start_marker) {
13537 gboolean ok = FALSE;
13538 selection_start_marker =
13539 webkit_dom_node_get_next_sibling (selection_start_marker);
13541 ok = e_editor_dom_is_selection_position_node (selection_start_marker);
13543 if (ok) {
13544 ok = FALSE;
13545 if (webkit_dom_range_get_collapsed (range, NULL)) {
13546 selection_end_marker = webkit_dom_node_get_next_sibling (
13547 selection_start_marker);
13549 ok = e_editor_dom_is_selection_position_node (selection_end_marker);
13550 if (ok) {
13551 WebKitDOMNode *next_sibling;
13553 next_sibling = webkit_dom_node_get_next_sibling (selection_end_marker);
13555 if (next_sibling && !WEBKIT_DOM_IS_HTML_BR_ELEMENT (next_sibling)) {
13556 parent_start = webkit_dom_node_get_parent_node (selection_end_marker);
13558 remove_node (selection_start_marker);
13559 remove_node (selection_end_marker);
13561 webkit_dom_node_normalize (parent_start);
13562 g_clear_object (&range);
13563 g_clear_object (&dom_selection);
13564 return;
13571 g_clear_object (&range);
13572 range = webkit_dom_document_create_range (document);
13573 if (!range) {
13574 g_clear_object (&dom_selection);
13575 return;
13578 marker = webkit_dom_document_get_element_by_id (
13579 document, "-x-evo-selection-start-marker");
13580 if (!marker) {
13581 marker = webkit_dom_document_get_element_by_id (
13582 document, "-x-evo-selection-end-marker");
13583 if (marker)
13584 remove_node (WEBKIT_DOM_NODE (marker));
13585 g_clear_object (&dom_selection);
13586 g_clear_object (&range);
13587 return;
13590 start_is_anchor = webkit_dom_element_has_attribute (marker, "data-anchor");
13591 parent_start = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (marker));
13593 webkit_dom_range_set_start_after (range, WEBKIT_DOM_NODE (marker), NULL);
13594 remove_node (WEBKIT_DOM_NODE (marker));
13596 marker = webkit_dom_document_get_element_by_id (
13597 document, "-x-evo-selection-end-marker");
13598 if (!marker) {
13599 marker = webkit_dom_document_get_element_by_id (
13600 document, "-x-evo-selection-start-marker");
13601 if (marker)
13602 remove_node (WEBKIT_DOM_NODE (marker));
13603 g_clear_object (&dom_selection);
13604 g_clear_object (&range);
13605 return;
13608 parent_end = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (marker));
13610 webkit_dom_range_set_end_before (range, WEBKIT_DOM_NODE (marker), NULL);
13611 remove_node (WEBKIT_DOM_NODE (marker));
13613 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
13614 if (webkit_dom_node_is_same_node (parent_start, parent_end))
13615 webkit_dom_node_normalize (parent_start);
13616 else {
13617 webkit_dom_node_normalize (parent_start);
13618 webkit_dom_node_normalize (parent_end);
13621 if (start_is_anchor) {
13622 anchor = webkit_dom_range_get_end_container (range, NULL);
13623 offset = webkit_dom_range_get_end_offset (range, NULL);
13625 webkit_dom_range_collapse (range, TRUE, NULL);
13626 } else {
13627 anchor = webkit_dom_range_get_start_container (range, NULL);
13628 offset = webkit_dom_range_get_start_offset (range, NULL);
13630 webkit_dom_range_collapse (range, FALSE, NULL);
13632 webkit_dom_dom_selection_add_range (dom_selection, range);
13633 webkit_dom_dom_selection_extend (dom_selection, anchor, offset, NULL);
13635 g_clear_object (&dom_selection);
13636 g_clear_object (&range);
13639 static gint
13640 find_where_to_break_line (WebKitDOMCharacterData *node,
13641 gint max_length)
13643 gboolean last_break_position_is_dash = FALSE;
13644 gchar *str, *text_start;
13645 gunichar uc;
13646 gint pos = 1, last_break_position = 0, ret_val = 0;
13648 text_start = webkit_dom_character_data_get_data (node);
13650 str = text_start;
13651 do {
13652 uc = g_utf8_get_char (str);
13653 if (!uc) {
13654 ret_val = pos <= max_length ? pos : last_break_position > 0 ? last_break_position - 1 : 0;
13655 goto out;
13658 if ((g_unichar_isspace (uc) && !(g_unichar_break_type (uc) == G_UNICODE_BREAK_NON_BREAKING_GLUE)) ||
13659 *str == '-') {
13660 if ((last_break_position_is_dash = *str == '-')) {
13661 /* There was no space before the dash */
13662 if (pos - 1 != last_break_position) {
13663 gchar *rest;
13665 rest = g_utf8_next_char (str);
13666 if (rest && *rest) {
13667 gunichar next_char;
13669 /* There is no space after the dash */
13670 next_char = g_utf8_get_char (rest);
13671 if (g_unichar_isspace (next_char))
13672 last_break_position_is_dash = FALSE;
13673 else
13674 last_break_position = pos;
13675 } else
13676 last_break_position_is_dash = FALSE;
13677 } else
13678 last_break_position_is_dash = FALSE;
13679 } else
13680 last_break_position = pos;
13683 if ((pos == max_length)) {
13684 /* Look one character after the limit to check if there
13685 * is a space (skip dash) that we are allowed to break at, if so
13686 * break it there. */
13687 if (*str) {
13688 str = g_utf8_next_char (str);
13689 uc = g_utf8_get_char (str);
13691 if ((g_unichar_isspace (uc) &&
13692 !(g_unichar_break_type (uc) == G_UNICODE_BREAK_NON_BREAKING_GLUE)))
13693 last_break_position = ++pos;
13695 break;
13698 pos++;
13699 str = g_utf8_next_char (str);
13700 } while (*str);
13702 if (last_break_position != 0)
13703 ret_val = last_break_position - 1;
13704 out:
13705 g_free (text_start);
13707 /* Always break after the dash character. */
13708 if (last_break_position_is_dash)
13709 ret_val++;
13711 /* No character to break at is found. We should split at max_length, but
13712 * we will leave the decision on caller as it depends on context. */
13713 if (ret_val == 0 && last_break_position == 0)
13714 ret_val = -1;
13716 return ret_val;
13720 * e_html_editor_selection_is_collapsed:
13721 * @selection: an #EEditorSelection
13723 * Returns if selection is collapsed.
13725 * Returns: Whether the selection is collapsed (just caret) or not (someting is selected).
13727 gboolean
13728 e_editor_dom_selection_is_collapsed (EEditorPage *editor_page)
13730 WebKitDOMDocument *document;
13731 WebKitDOMDOMWindow *dom_window = NULL;
13732 WebKitDOMDOMSelection *dom_selection = NULL;
13733 gboolean collapsed;
13735 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
13737 document = e_editor_page_get_document (editor_page);
13738 if (!(dom_window = webkit_dom_document_get_default_view (document)))
13739 return FALSE;
13741 if (!(dom_selection = webkit_dom_dom_window_get_selection (dom_window))) {
13742 g_clear_object (&dom_window);
13743 return FALSE;
13746 collapsed = webkit_dom_dom_selection_get_is_collapsed (dom_selection);
13748 g_clear_object (&dom_selection);
13750 return collapsed;
13753 void
13754 e_editor_dom_scroll_to_caret (EEditorPage *editor_page)
13756 WebKitDOMDocument *document;
13757 WebKitDOMDOMWindow *dom_window = NULL;
13758 WebKitDOMElement *selection_start_marker;
13759 glong element_top, element_left;
13760 glong window_top, window_left, window_right, window_bottom;
13762 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
13764 document = e_editor_page_get_document (editor_page);
13765 e_editor_dom_selection_save (editor_page);
13767 selection_start_marker = webkit_dom_document_get_element_by_id (
13768 document, "-x-evo-selection-start-marker");
13769 if (!selection_start_marker)
13770 return;
13772 dom_window = webkit_dom_document_get_default_view (document);
13774 window_top = webkit_dom_dom_window_get_scroll_y (dom_window);
13775 window_left = webkit_dom_dom_window_get_scroll_x (dom_window);
13776 window_bottom = window_top + webkit_dom_dom_window_get_inner_height (dom_window);
13777 window_right = window_left + webkit_dom_dom_window_get_inner_width (dom_window);
13779 element_left = webkit_dom_element_get_offset_left (selection_start_marker);
13780 element_top = webkit_dom_element_get_offset_top (selection_start_marker);
13782 /* Check if caret is inside viewport, if not move to it */
13783 if (!(element_top >= window_top && element_top <= window_bottom &&
13784 element_left >= window_left && element_left <= window_right)) {
13785 webkit_dom_element_scroll_into_view (selection_start_marker, TRUE);
13788 e_editor_dom_selection_restore (editor_page);
13790 g_clear_object (&dom_window);
13793 static void
13794 mark_and_remove_trailing_space (WebKitDOMDocument *document,
13795 WebKitDOMNode *node)
13797 WebKitDOMElement *element;
13799 element = webkit_dom_document_create_element (document, "SPAN", NULL);
13800 webkit_dom_element_set_attribute (element, "data-hidden-space", "", NULL);
13801 webkit_dom_node_insert_before (
13802 webkit_dom_node_get_parent_node (node),
13803 WEBKIT_DOM_NODE (element),
13804 webkit_dom_node_get_next_sibling (node),
13805 NULL);
13806 webkit_dom_character_data_replace_data (
13807 WEBKIT_DOM_CHARACTER_DATA (node),
13808 webkit_dom_character_data_get_length (WEBKIT_DOM_CHARACTER_DATA (node)),
13811 NULL);
13814 static void
13815 mark_and_remove_leading_space (WebKitDOMDocument *document,
13816 WebKitDOMNode *node)
13818 WebKitDOMElement *element;
13820 element = webkit_dom_document_create_element (document, "SPAN", NULL);
13821 webkit_dom_element_set_attribute (element, "data-hidden-space", "", NULL);
13822 webkit_dom_node_insert_before (
13823 webkit_dom_node_get_parent_node (node),
13824 WEBKIT_DOM_NODE (element),
13825 node,
13826 NULL);
13827 webkit_dom_character_data_replace_data (
13828 WEBKIT_DOM_CHARACTER_DATA (node), 0, 1, "", NULL);
13831 static WebKitDOMElement *
13832 wrap_lines (EEditorPage *editor_page,
13833 WebKitDOMNode *block,
13834 gboolean remove_all_br,
13835 gint length_to_wrap,
13836 gint word_wrap_length)
13838 WebKitDOMDocument *document;
13839 WebKitDOMElement *selection_start_marker, *selection_end_marker;
13840 WebKitDOMNode *node, *start_node, *block_clone = NULL;
13841 WebKitDOMNode *start_point = NULL, *first_child, *last_child;
13842 guint line_length;
13843 gulong length_left;
13844 gchar *text_content;
13845 gboolean compensated = FALSE;
13846 gboolean check_next_node = FALSE;
13848 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
13850 document = e_editor_page_get_document (editor_page);
13852 if (!webkit_dom_node_has_child_nodes (block))
13853 return WEBKIT_DOM_ELEMENT (block);
13855 /* Avoid wrapping when the block contains just the BR element alone
13856 * or with selection markers. */
13857 if ((first_child = webkit_dom_node_get_first_child (block)) &&
13858 WEBKIT_DOM_IS_HTML_BR_ELEMENT (first_child)) {
13859 WebKitDOMNode *next_sibling;
13861 if ((next_sibling = webkit_dom_node_get_next_sibling (first_child))) {
13862 if (e_editor_dom_is_selection_position_node (next_sibling) &&
13863 (next_sibling = webkit_dom_node_get_next_sibling (next_sibling)) &&
13864 e_editor_dom_is_selection_position_node (next_sibling) &&
13865 !webkit_dom_node_get_next_sibling (next_sibling))
13866 return WEBKIT_DOM_ELEMENT (block);
13867 } else
13868 return WEBKIT_DOM_ELEMENT (block);
13871 block_clone = webkit_dom_node_clone_node_with_error (block, TRUE, NULL);
13873 /* When we wrap, we are wrapping just the text after caret, text
13874 * before the caret is already wrapped, so unwrap the text after
13875 * the caret position */
13876 selection_end_marker = webkit_dom_element_query_selector (
13877 WEBKIT_DOM_ELEMENT (block_clone),
13878 "span#-x-evo-selection-end-marker",
13879 NULL);
13881 if (selection_end_marker) {
13882 WebKitDOMNode *nd = WEBKIT_DOM_NODE (selection_end_marker);
13884 while (nd) {
13885 WebKitDOMNode *parent_node;
13886 WebKitDOMNode *next_nd = webkit_dom_node_get_next_sibling (nd);
13888 parent_node = webkit_dom_node_get_parent_node (nd);
13889 if (!next_nd && parent_node && !webkit_dom_node_is_same_node (parent_node, block_clone))
13890 next_nd = webkit_dom_node_get_next_sibling (parent_node);
13892 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (nd)) {
13893 if (remove_all_br)
13894 remove_node (nd);
13895 else if (element_has_class (WEBKIT_DOM_ELEMENT (nd), "-x-evo-wrap-br"))
13896 remove_node (nd);
13897 } else if (WEBKIT_DOM_IS_ELEMENT (nd) &&
13898 webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (nd), "data-hidden-space"))
13899 webkit_dom_html_element_set_outer_text (
13900 WEBKIT_DOM_HTML_ELEMENT (nd), " ", NULL);
13902 nd = next_nd;
13904 } else {
13905 gint ii;
13906 WebKitDOMNodeList *list = NULL;
13908 list = webkit_dom_element_query_selector_all (
13909 WEBKIT_DOM_ELEMENT (block_clone), "span[data-hidden-space]", NULL);
13910 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
13911 WebKitDOMNode *hidden_space_node;
13913 hidden_space_node = webkit_dom_node_list_item (list, ii);
13914 webkit_dom_html_element_set_outer_text (
13915 WEBKIT_DOM_HTML_ELEMENT (hidden_space_node), " ", NULL);
13917 g_clear_object (&list);
13920 /* We have to start from the end of the last wrapped line */
13921 selection_start_marker = webkit_dom_element_query_selector (
13922 WEBKIT_DOM_ELEMENT (block_clone),
13923 "span#-x-evo-selection-start-marker",
13924 NULL);
13926 if (selection_start_marker) {
13927 gboolean first_removed = FALSE;
13928 WebKitDOMNode *nd;
13930 nd = webkit_dom_node_get_previous_sibling (
13931 WEBKIT_DOM_NODE (selection_start_marker));
13932 while (nd) {
13933 WebKitDOMNode *prev_nd = webkit_dom_node_get_previous_sibling (nd);
13935 if (!prev_nd && !webkit_dom_node_is_same_node (webkit_dom_node_get_parent_node (nd), block_clone))
13936 prev_nd = webkit_dom_node_get_previous_sibling (webkit_dom_node_get_parent_node (nd));
13938 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (nd)) {
13939 if (first_removed) {
13940 start_point = nd;
13941 break;
13942 } else {
13943 remove_node (nd);
13944 first_removed = TRUE;
13946 } else if (WEBKIT_DOM_IS_ELEMENT (nd) &&
13947 webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (nd), "data-hidden-space")) {
13948 webkit_dom_html_element_set_outer_text (
13949 WEBKIT_DOM_HTML_ELEMENT (nd), " ", NULL);
13950 } else if (!prev_nd) {
13951 WebKitDOMNode *parent;
13953 parent = webkit_dom_node_get_parent_node (nd);
13954 if (!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent))
13955 start_point = nd;
13958 nd = prev_nd;
13962 webkit_dom_node_normalize (block_clone);
13963 node = webkit_dom_node_get_first_child (block_clone);
13964 if (node) {
13965 text_content = webkit_dom_node_get_text_content (node);
13966 if (g_strcmp0 ("\n", text_content) == 0)
13967 node = webkit_dom_node_get_next_sibling (node);
13968 g_free (text_content);
13971 if (start_point) {
13972 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (start_point))
13973 node = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (start_point));
13974 else
13975 node = start_point;
13976 start_node = block_clone;
13977 } else
13978 start_node = node;
13980 line_length = 0;
13981 while (node) {
13982 gint offset = 0;
13983 WebKitDOMElement *element;
13985 if (WEBKIT_DOM_IS_TEXT (node)) {
13986 const gchar *newline;
13987 WebKitDOMNode *next_sibling;
13989 /* If there is temporary hidden space we remove it */
13990 text_content = webkit_dom_node_get_text_content (node);
13991 if (strstr (text_content, UNICODE_ZERO_WIDTH_SPACE)) {
13992 if (g_str_has_prefix (text_content, UNICODE_ZERO_WIDTH_SPACE))
13993 webkit_dom_character_data_delete_data (
13994 WEBKIT_DOM_CHARACTER_DATA (node),
13997 NULL);
13998 if (g_str_has_suffix (text_content, UNICODE_ZERO_WIDTH_SPACE))
13999 webkit_dom_character_data_delete_data (
14000 WEBKIT_DOM_CHARACTER_DATA (node),
14001 g_utf8_strlen (text_content, -1) - 1,
14003 NULL);
14004 g_free (text_content);
14005 text_content = webkit_dom_node_get_text_content (node);
14007 newline = strstr (text_content, "\n");
14009 next_sibling = node;
14010 while (newline) {
14011 next_sibling = WEBKIT_DOM_NODE (webkit_dom_text_split_text (
14012 WEBKIT_DOM_TEXT (next_sibling),
14013 g_utf8_pointer_to_offset (text_content, newline),
14014 NULL));
14016 if (!next_sibling)
14017 break;
14019 element = webkit_dom_document_create_element (
14020 document, "BR", NULL);
14021 element_add_class (element, "-x-evo-wrap-br");
14023 webkit_dom_node_insert_before (
14024 webkit_dom_node_get_parent_node (next_sibling),
14025 WEBKIT_DOM_NODE (element),
14026 next_sibling,
14027 NULL);
14029 g_free (text_content);
14031 text_content = webkit_dom_node_get_text_content (next_sibling);
14032 if (g_str_has_prefix (text_content, "\n")) {
14033 webkit_dom_character_data_delete_data (
14034 WEBKIT_DOM_CHARACTER_DATA (next_sibling), 0, 1, NULL);
14035 g_free (text_content);
14036 text_content =
14037 webkit_dom_node_get_text_content (next_sibling);
14039 newline = strstr (text_content, "\n");
14041 g_free (text_content);
14042 } else if (WEBKIT_DOM_IS_ELEMENT (node)) {
14043 if (e_editor_dom_is_selection_position_node (node)) {
14044 if (line_length == 0) {
14045 WebKitDOMNode *tmp_node;
14047 tmp_node = webkit_dom_node_get_previous_sibling (node);
14048 /* Only check if there is some node before the selection marker. */
14049 if (tmp_node && !e_editor_dom_is_selection_position_node (tmp_node))
14050 check_next_node = TRUE;
14052 node = webkit_dom_node_get_next_sibling (node);
14053 continue;
14056 check_next_node = FALSE;
14057 /* If element is ANCHOR we wrap it separately */
14058 if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node)) {
14059 glong anchor_length;
14060 WebKitDOMNode *next_sibling;
14062 text_content = webkit_dom_node_get_text_content (node);
14063 anchor_length = g_utf8_strlen (text_content, -1);
14064 g_free (text_content);
14066 next_sibling = webkit_dom_node_get_next_sibling (node);
14067 /* If the anchor doesn't fit on the line, add it to a separate line. */
14068 if ((line_length + anchor_length) > length_to_wrap) {
14069 /* Put <BR> before the anchor, thus it starts on a new line */
14070 element = webkit_dom_document_create_element (document, "BR", NULL);
14071 element_add_class (element, "-x-evo-wrap-br");
14072 webkit_dom_node_insert_before (
14073 webkit_dom_node_get_parent_node (node),
14074 WEBKIT_DOM_NODE (element),
14075 node,
14076 NULL);
14078 /* When the anchor itself is too long */
14079 if (anchor_length >= length_to_wrap) {
14080 /* Put <BR> after the anchor, thus it doesn't contain text after it */
14081 element = webkit_dom_document_create_element (document, "BR", NULL);
14082 element_add_class (element, "-x-evo-wrap-br");
14083 webkit_dom_node_insert_before (
14084 webkit_dom_node_get_parent_node (node),
14085 WEBKIT_DOM_NODE (element),
14086 next_sibling,
14087 NULL);
14089 line_length = 0;
14090 } else {
14091 line_length = anchor_length;
14093 } else {
14094 line_length += anchor_length;
14097 node = next_sibling;
14098 continue;
14101 if (element_has_class (WEBKIT_DOM_ELEMENT (node), "Apple-tab-span")) {
14102 WebKitDOMNode *sibling;
14103 gint tab_length;
14105 sibling = webkit_dom_node_get_previous_sibling (node);
14106 if (sibling && WEBKIT_DOM_IS_ELEMENT (sibling) &&
14107 element_has_class (WEBKIT_DOM_ELEMENT (sibling), "Apple-tab-span"))
14108 tab_length = TAB_LENGTH;
14109 else {
14110 tab_length = TAB_LENGTH - (line_length + (compensated ? 0 : (word_wrap_length - length_to_wrap))) % TAB_LENGTH;
14111 compensated = TRUE;
14114 if (line_length + tab_length > length_to_wrap) {
14115 if (webkit_dom_node_get_next_sibling (node)) {
14116 element = webkit_dom_document_create_element (
14117 document, "BR", NULL);
14118 element_add_class (element, "-x-evo-wrap-br");
14119 node = webkit_dom_node_insert_before (
14120 webkit_dom_node_get_parent_node (node),
14121 WEBKIT_DOM_NODE (element),
14122 webkit_dom_node_get_next_sibling (node),
14123 NULL);
14125 line_length = 0;
14126 compensated = FALSE;
14127 } else
14128 line_length += tab_length;
14130 sibling = webkit_dom_node_get_next_sibling (node);
14131 node = sibling;
14132 continue;
14134 /* When we are not removing user-entered BR elements (lines wrapped by user),
14135 * we need to skip those elements */
14136 if (!remove_all_br && WEBKIT_DOM_IS_HTML_BR_ELEMENT (node)) {
14137 if (!element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-wrap-br")) {
14138 line_length = 0;
14139 compensated = FALSE;
14140 node = webkit_dom_node_get_next_sibling (node);
14141 continue;
14144 goto next_node;
14145 } else {
14146 WebKitDOMNode *sibling;
14148 sibling = webkit_dom_node_get_next_sibling (node);
14149 node = sibling;
14150 continue;
14153 /* If length of this node + what we already have is still less
14154 * then length_to_wrap characters, then just concatenate it and
14155 * continue to next node */
14156 length_left = webkit_dom_character_data_get_length (
14157 WEBKIT_DOM_CHARACTER_DATA (node));
14159 if ((length_left + line_length) <= length_to_wrap) {
14160 if (check_next_node)
14161 goto check_node;
14162 line_length += length_left;
14163 if (line_length == length_to_wrap) {
14164 line_length = 0;
14166 element = webkit_dom_document_create_element (document, "BR", NULL);
14167 element_add_class (element, "-x-evo-wrap-br");
14169 webkit_dom_node_insert_before (
14170 webkit_dom_node_get_parent_node (node),
14171 WEBKIT_DOM_NODE (element),
14172 webkit_dom_node_get_next_sibling (node),
14173 NULL);
14175 goto next_node;
14178 /* wrap until we have something */
14179 while (node && (length_left + line_length) > length_to_wrap) {
14180 gboolean insert_and_continue;
14181 gint max_length;
14183 check_node:
14184 insert_and_continue = FALSE;
14186 if (!WEBKIT_DOM_IS_CHARACTER_DATA (node))
14187 goto next_node;
14189 element = webkit_dom_document_create_element (document, "BR", NULL);
14190 element_add_class (element, "-x-evo-wrap-br");
14192 max_length = length_to_wrap - line_length;
14193 if (max_length < 0)
14194 max_length = length_to_wrap;
14195 else if (max_length == 0) {
14196 if (check_next_node) {
14197 insert_and_continue = TRUE;
14198 goto check;
14201 /* Break before the current node and continue. */
14202 webkit_dom_node_insert_before (
14203 webkit_dom_node_get_parent_node (node),
14204 WEBKIT_DOM_NODE (element),
14205 node,
14206 NULL);
14207 line_length = 0;
14208 continue;
14211 /* Allow anchors to break on any character. */
14212 if (g_object_steal_data (G_OBJECT (node), "-x-evo-anchor-text"))
14213 offset = max_length;
14214 else {
14215 /* Find where we can line-break the node so that it
14216 * effectively fills the rest of current row. */
14217 offset = find_where_to_break_line (
14218 WEBKIT_DOM_CHARACTER_DATA (node), max_length);
14220 /* When pressing delete on the end of line to concatenate
14221 * the last word from the line and first word from the
14222 * next line we will end with the second word split
14223 * somewhere in the middle (to be precise it will be
14224 * split after the last character that will fit on the
14225 * previous line. To avoid that we need to put the
14226 * concatenated word on the next line. */
14227 if (offset == -1 || check_next_node) {
14228 WebKitDOMNode *prev_sibling;
14230 check:
14231 check_next_node = FALSE;
14232 prev_sibling = webkit_dom_node_get_previous_sibling (node);
14233 if (prev_sibling && e_editor_dom_is_selection_position_node (prev_sibling)) {
14234 WebKitDOMNode *prev_br = NULL;
14236 prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling);
14238 /* Collapsed selection */
14239 if (prev_sibling && e_editor_dom_is_selection_position_node (prev_sibling))
14240 prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling);
14242 if (prev_sibling && WEBKIT_DOM_IS_HTML_BR_ELEMENT (prev_sibling) &&
14243 element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-wrap-br")) {
14244 prev_br = prev_sibling;
14245 prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling);
14248 if (prev_sibling && WEBKIT_DOM_IS_CHARACTER_DATA (prev_sibling)) {
14249 gchar *data;
14250 glong text_length, length = 0;
14252 data = webkit_dom_character_data_get_data (
14253 WEBKIT_DOM_CHARACTER_DATA (prev_sibling));
14254 text_length = webkit_dom_character_data_get_length (
14255 WEBKIT_DOM_CHARACTER_DATA (prev_sibling));
14257 /* Find the last character where we can break. */
14258 while (text_length - length > 0) {
14259 if (strchr (" ", data[text_length - length - 1])) {
14260 length++;
14261 break;
14262 } else if (data[text_length - length - 1] == '-' &&
14263 text_length - length > 1 &&
14264 !strchr (" ", data[text_length - length - 2]))
14265 break;
14266 length++;
14269 if (text_length != length) {
14270 WebKitDOMNode *nd;
14272 webkit_dom_text_split_text (
14273 WEBKIT_DOM_TEXT (prev_sibling),
14274 text_length - length,
14275 NULL);
14277 if ((nd = webkit_dom_node_get_next_sibling (prev_sibling))) {
14278 gchar *nd_content;
14280 while (nd_content = webkit_dom_node_get_text_content (nd), nd_content) {
14281 gboolean changed = FALSE;
14283 if (*nd_content) {
14284 if (*nd_content == ' ') {
14285 mark_and_remove_leading_space (document, nd);
14286 changed = TRUE;
14289 if (!webkit_dom_node_get_next_sibling (nd) &&
14290 g_str_has_suffix (nd_content, " ")) {
14291 mark_and_remove_trailing_space (document, nd);
14292 changed = TRUE;
14296 g_free (nd_content);
14298 if (!changed)
14299 break;
14302 if (nd) {
14303 if (prev_br)
14304 remove_node (prev_br);
14305 webkit_dom_node_insert_before (
14306 webkit_dom_node_get_parent_node (nd),
14307 WEBKIT_DOM_NODE (element),
14309 NULL);
14311 offset = 0;
14312 line_length = length;
14313 continue;
14319 if (insert_and_continue) {
14320 webkit_dom_node_insert_before (
14321 webkit_dom_node_get_parent_node (node),
14322 WEBKIT_DOM_NODE (element),
14323 node,
14324 NULL);
14326 offset = 0;
14327 line_length = 0;
14328 insert_and_continue = FALSE;
14329 continue;
14332 offset = offset != -1 ? offset : max_length;
14336 if (offset >= 0) {
14337 WebKitDOMNode *nd;
14339 if (offset != length_left && offset != 0) {
14340 webkit_dom_text_split_text (
14341 WEBKIT_DOM_TEXT (node), offset, NULL);
14343 nd = webkit_dom_node_get_next_sibling (node);
14344 } else
14345 nd = node;
14347 if (nd) {
14348 gboolean no_sibling = FALSE;
14349 gchar *nd_content;
14351 while (nd_content = webkit_dom_node_get_text_content (nd), nd_content) {
14352 gboolean changed = FALSE;
14354 if (*nd_content) {
14355 if (*nd_content == ' ') {
14356 mark_and_remove_leading_space (document, nd);
14357 changed = TRUE;
14360 if (!webkit_dom_node_get_next_sibling (nd) &&
14361 length_left <= length_to_wrap &&
14362 g_str_has_suffix (nd_content, " ")) {
14363 mark_and_remove_trailing_space (document, nd);
14364 no_sibling = TRUE;
14365 changed = TRUE;
14369 g_free (nd_content);
14371 if (!changed)
14372 break;
14375 if (!no_sibling)
14376 webkit_dom_node_insert_before (
14377 webkit_dom_node_get_parent_node (node),
14378 WEBKIT_DOM_NODE (element),
14380 NULL);
14382 offset = 0;
14384 nd_content = webkit_dom_node_get_text_content (nd);
14385 if (!*nd_content)
14386 remove_node (nd);
14387 g_free (nd_content);
14389 if (no_sibling) {
14390 node = NULL;
14391 } else {
14392 nd = node;
14394 node = webkit_dom_node_get_next_sibling (
14395 WEBKIT_DOM_NODE (element));
14397 if (nd == node)
14398 node = webkit_dom_node_get_next_sibling (node);
14399 if (nd == node)
14400 node = NULL;
14402 } else {
14403 webkit_dom_node_append_child (
14404 webkit_dom_node_get_parent_node (node),
14405 WEBKIT_DOM_NODE (element),
14406 NULL);
14409 if (node && WEBKIT_DOM_IS_CHARACTER_DATA (node))
14410 length_left = webkit_dom_character_data_get_length (
14411 WEBKIT_DOM_CHARACTER_DATA (node));
14413 line_length = 0;
14414 compensated = FALSE;
14416 line_length += length_left - offset;
14417 next_node:
14418 if (!node)
14419 break;
14421 if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (node)) {
14422 line_length = 0;
14423 compensated = FALSE;
14426 /* Move to next node */
14427 if (webkit_dom_node_has_child_nodes (node)) {
14428 node = webkit_dom_node_get_first_child (node);
14429 } else if (webkit_dom_node_get_next_sibling (node)) {
14430 node = webkit_dom_node_get_next_sibling (node);
14431 } else {
14432 WebKitDOMNode *tmp_parent;
14434 if (webkit_dom_node_is_equal_node (node, start_node))
14435 break;
14437 /* Find a next node that we can process. */
14438 tmp_parent = webkit_dom_node_get_parent_node (node);
14439 if (tmp_parent && webkit_dom_node_get_next_sibling (tmp_parent))
14440 node = webkit_dom_node_get_next_sibling (tmp_parent);
14441 else {
14442 WebKitDOMNode *tmp;
14444 tmp = tmp_parent;
14445 /* Find a node that is not a start node (that would mean
14446 * that we already processed the whole block) and it has
14447 * a sibling that we can process. */
14448 while (tmp && !webkit_dom_node_is_equal_node (tmp, start_node) &&
14449 !webkit_dom_node_get_next_sibling (tmp)) {
14450 tmp = webkit_dom_node_get_parent_node (tmp);
14453 /* If we found a node to process, let's process its
14454 * sibling, otherwise give up. */
14455 if (tmp)
14456 node = webkit_dom_node_get_next_sibling (tmp);
14457 else
14458 break;
14463 last_child = webkit_dom_node_get_last_child (block_clone);
14464 if (last_child && WEBKIT_DOM_IS_HTML_BR_ELEMENT (last_child) &&
14465 element_has_class (WEBKIT_DOM_ELEMENT (last_child), "-x-evo-wrap-br"))
14466 remove_node (last_child);
14468 webkit_dom_node_normalize (block_clone);
14470 node = webkit_dom_node_get_parent_node (block);
14471 if (node) {
14472 /* Replace block with wrapped one */
14473 webkit_dom_node_replace_child (
14474 node, block_clone, block, NULL);
14477 return WEBKIT_DOM_ELEMENT (block_clone);
14480 void
14481 e_editor_dom_remove_wrapping_from_element (WebKitDOMElement *element)
14483 WebKitDOMNodeList *list = NULL;
14484 gint ii;
14486 g_return_if_fail (element != NULL);
14488 list = webkit_dom_element_query_selector_all (
14489 element, "br.-x-evo-wrap-br", NULL);
14490 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
14491 WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
14492 WebKitDOMNode *parent;
14494 parent = e_editor_dom_get_parent_block_node_from_child (node);
14495 if (!webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (parent), "data-user-wrapped"))
14496 remove_node (node);
14499 g_clear_object (&list);
14501 list = webkit_dom_element_query_selector_all (
14502 element, "span[data-hidden-space]", NULL);
14503 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
14504 WebKitDOMNode *hidden_space_node;
14505 WebKitDOMNode *parent;
14507 hidden_space_node = webkit_dom_node_list_item (list, ii);
14508 parent = e_editor_dom_get_parent_block_node_from_child (hidden_space_node);
14509 if (!webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (parent), "data-user-wrapped")) {
14510 webkit_dom_html_element_set_outer_text (
14511 WEBKIT_DOM_HTML_ELEMENT (hidden_space_node), " ", NULL);
14514 g_clear_object (&list);
14516 webkit_dom_node_normalize (WEBKIT_DOM_NODE (element));
14519 void
14520 e_editor_dom_remove_quoting_from_element (WebKitDOMElement *element)
14522 gint ii;
14523 WebKitDOMHTMLCollection *collection = NULL;
14525 g_return_if_fail (element != NULL);
14527 collection = webkit_dom_element_get_elements_by_class_name_as_html_collection (
14528 element, "-x-evo-quoted");
14529 for (ii = webkit_dom_html_collection_get_length (collection); ii--;)
14530 remove_node (webkit_dom_html_collection_item (collection, ii));
14531 g_clear_object (&collection);
14533 collection = webkit_dom_element_get_elements_by_class_name_as_html_collection (
14534 element, "-x-evo-temp-br");
14535 for (ii = webkit_dom_html_collection_get_length (collection); ii--;)
14536 remove_node (webkit_dom_html_collection_item (collection, ii));
14537 g_clear_object (&collection);
14539 webkit_dom_node_normalize (WEBKIT_DOM_NODE (element));
14542 WebKitDOMElement *
14543 e_editor_dom_get_paragraph_element (EEditorPage *editor_page,
14544 gint width,
14545 gint offset)
14547 WebKitDOMDocument *document;
14548 WebKitDOMElement *element;
14550 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
14552 document = e_editor_page_get_document (editor_page);
14553 element = webkit_dom_document_create_element (document, "DIV", NULL);
14554 e_editor_dom_set_paragraph_style (editor_page, element, width, offset, NULL);
14556 return element;
14559 WebKitDOMElement *
14560 e_editor_dom_put_node_into_paragraph (EEditorPage *editor_page,
14561 WebKitDOMNode *node,
14562 gboolean with_input)
14564 WebKitDOMDocument *document;
14565 WebKitDOMRange *range = NULL;
14566 WebKitDOMElement *container;
14568 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
14570 document = e_editor_page_get_document (editor_page);
14571 range = webkit_dom_document_create_range (document);
14572 container = e_editor_dom_get_paragraph_element (editor_page, -1, 0);
14573 webkit_dom_range_select_node (range, node, NULL);
14574 webkit_dom_range_surround_contents (range, WEBKIT_DOM_NODE (container), NULL);
14575 /* We have to move caret position inside this container */
14576 if (with_input)
14577 dom_add_selection_markers_into_element_end (document, container, NULL, NULL);
14579 g_clear_object (&range);
14581 return container;
14584 WebKitDOMElement *
14585 e_editor_dom_wrap_paragraph_length (EEditorPage *editor_page,
14586 WebKitDOMElement *paragraph,
14587 gint length)
14589 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
14590 g_return_val_if_fail (WEBKIT_DOM_IS_ELEMENT (paragraph), NULL);
14591 g_return_val_if_fail (length >= MINIMAL_PARAGRAPH_WIDTH, NULL);
14593 return wrap_lines (editor_page, WEBKIT_DOM_NODE (paragraph), FALSE, length,
14594 e_editor_page_get_word_wrap_length (editor_page));
14598 * e_html_editor_selection_wrap_lines:
14599 * @selection: an #EEditorSelection
14601 * Wraps all lines in current selection to be 71 characters long.
14604 void
14605 e_editor_dom_selection_wrap (EEditorPage *editor_page)
14607 WebKitDOMDocument *document;
14608 WebKitDOMElement *selection_start_marker, *selection_end_marker;
14609 WebKitDOMNode *block, *next_block;
14610 EEditorHistoryEvent *ev = NULL;
14611 EEditorUndoRedoManager *manager;
14612 gboolean after_selection_end = FALSE, html_mode;
14613 gint word_wrap_length;
14615 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
14617 document = e_editor_page_get_document (editor_page);
14618 word_wrap_length = e_editor_page_get_word_wrap_length (editor_page);
14620 e_editor_dom_selection_save (editor_page);
14621 selection_start_marker = webkit_dom_document_get_element_by_id (
14622 document, "-x-evo-selection-start-marker");
14623 selection_end_marker = webkit_dom_document_get_element_by_id (
14624 document, "-x-evo-selection-end-marker");
14626 /* If the selection was not saved, move it into the first child of body */
14627 if (!selection_start_marker || !selection_end_marker) {
14628 WebKitDOMHTMLElement *body;
14629 WebKitDOMNode *child;
14631 body = webkit_dom_document_get_body (document);
14632 child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
14634 dom_add_selection_markers_into_element_start (
14635 document,
14636 WEBKIT_DOM_ELEMENT (child),
14637 &selection_start_marker,
14638 &selection_end_marker);
14641 manager = e_editor_page_get_undo_redo_manager (editor_page);
14642 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
14643 ev = g_new0 (EEditorHistoryEvent, 1);
14644 ev->type = HISTORY_WRAP;
14646 e_editor_dom_selection_get_coordinates (editor_page,
14647 &ev->before.start.x,
14648 &ev->before.start.y,
14649 &ev->before.end.x,
14650 &ev->before.end.y);
14652 ev->data.style.from = 1;
14653 ev->data.style.to = 1;
14656 block = e_editor_dom_get_parent_block_node_from_child (
14657 WEBKIT_DOM_NODE (selection_start_marker));
14659 html_mode = e_editor_page_get_html_mode (editor_page);
14661 /* Process all blocks that are in the selection one by one */
14662 while (block && !after_selection_end) {
14663 gboolean quoted = FALSE;
14664 gint citation_level, quote;
14665 WebKitDOMElement *wrapped_paragraph;
14667 next_block = webkit_dom_node_get_next_sibling (block);
14669 /* Don't try to wrap the 'Normal' blocks as they are already wrapped and*/
14670 /* also skip blocks that we already wrapped with this function. */
14671 if ((!html_mode && webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (block), "data-evo-paragraph")) ||
14672 webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (block), "data-user-wrapped")) {
14673 block = next_block;
14674 continue;
14677 if (webkit_dom_element_query_selector (
14678 WEBKIT_DOM_ELEMENT (block), "span.-x-evo-quoted", NULL)) {
14679 quoted = TRUE;
14680 e_editor_dom_remove_quoting_from_element (WEBKIT_DOM_ELEMENT (block));
14683 if (!html_mode)
14684 e_editor_dom_remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (block));
14686 after_selection_end = webkit_dom_node_contains (
14687 block, WEBKIT_DOM_NODE (selection_end_marker));
14689 citation_level = e_editor_dom_get_citation_level (block);
14690 quote = citation_level ? citation_level * 2 : 0;
14692 wrapped_paragraph = e_editor_dom_wrap_paragraph_length (
14693 editor_page, WEBKIT_DOM_ELEMENT (block), word_wrap_length - quote);
14695 webkit_dom_element_set_attribute (
14696 wrapped_paragraph, "data-user-wrapped", "", NULL);
14698 if (quoted && !html_mode)
14699 e_editor_dom_quote_plain_text_element_after_wrapping (
14700 editor_page, wrapped_paragraph, citation_level);
14702 block = next_block;
14705 if (ev) {
14706 e_editor_dom_selection_get_coordinates (editor_page,
14707 &ev->after.start.x,
14708 &ev->after.start.y,
14709 &ev->after.end.x,
14710 &ev->after.end.y);
14711 e_editor_undo_redo_manager_insert_history_event (manager, ev);
14714 e_editor_dom_selection_restore (editor_page);
14716 e_editor_dom_force_spell_check_in_viewport (editor_page);
14718 e_editor_page_emit_content_changed (editor_page);
14721 void
14722 e_editor_dom_wrap_paragraphs_in_document (EEditorPage *editor_page)
14724 WebKitDOMDocument *document;
14725 WebKitDOMNodeList *list = NULL;
14726 gint ii;
14728 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
14730 document = e_editor_page_get_document (editor_page);
14731 list = webkit_dom_document_query_selector_all (
14732 document, "[data-evo-paragraph]:not(#-x-evo-input-start)", NULL);
14734 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
14735 gint word_wrap_length, quote, citation_level;
14736 WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
14738 citation_level = e_editor_dom_get_citation_level (node);
14739 quote = citation_level ? citation_level * 2 : 0;
14740 word_wrap_length = e_editor_page_get_word_wrap_length (editor_page);
14742 if (node_is_list (node)) {
14743 WebKitDOMNode *item = webkit_dom_node_get_first_child (node);
14745 while (item && WEBKIT_DOM_IS_HTML_LI_ELEMENT (item)) {
14746 e_editor_dom_wrap_paragraph_length (
14747 editor_page, WEBKIT_DOM_ELEMENT (item), word_wrap_length - quote);
14748 item = webkit_dom_node_get_next_sibling (item);
14750 } else {
14751 e_editor_dom_wrap_paragraph_length (
14752 editor_page, WEBKIT_DOM_ELEMENT (node), word_wrap_length - quote);
14755 g_clear_object (&list);
14758 WebKitDOMElement *
14759 e_editor_dom_wrap_paragraph (EEditorPage *editor_page,
14760 WebKitDOMElement *paragraph)
14762 gint indentation_level, citation_level, quote;
14763 gint word_wrap_length, final_width, offset = 0;
14765 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
14766 g_return_val_if_fail (WEBKIT_DOM_IS_ELEMENT (paragraph), NULL);
14768 indentation_level = get_indentation_level (paragraph);
14769 citation_level = e_editor_dom_get_citation_level (WEBKIT_DOM_NODE (paragraph));
14771 if (node_is_list_or_item (WEBKIT_DOM_NODE (paragraph))) {
14772 gint list_level = get_list_level (WEBKIT_DOM_NODE (paragraph));
14773 indentation_level = 0;
14775 if (list_level > 0)
14776 offset = list_level * -SPACES_PER_LIST_LEVEL;
14777 else
14778 offset = -SPACES_PER_LIST_LEVEL;
14781 quote = citation_level ? citation_level * 2 : 0;
14783 word_wrap_length = e_editor_page_get_word_wrap_length (editor_page);
14784 final_width = word_wrap_length - quote + offset;
14785 final_width -= SPACES_PER_INDENTATION * indentation_level;
14787 return e_editor_dom_wrap_paragraph_length (
14788 editor_page, WEBKIT_DOM_ELEMENT (paragraph), final_width);
14791 static gboolean
14792 get_has_style (EEditorPage *editor_page,
14793 const gchar *style_tag)
14795 WebKitDOMNode *node;
14796 WebKitDOMElement *element;
14797 WebKitDOMRange *range = NULL;
14798 gboolean result;
14799 gint tag_len;
14801 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
14803 range = e_editor_dom_get_current_range (editor_page);
14804 if (!range)
14805 return FALSE;
14807 node = webkit_dom_range_get_start_container (range, NULL);
14808 if (WEBKIT_DOM_IS_ELEMENT (node))
14809 element = WEBKIT_DOM_ELEMENT (node);
14810 else
14811 element = webkit_dom_node_get_parent_element (node);
14812 g_clear_object (&range);
14814 tag_len = strlen (style_tag);
14815 result = FALSE;
14816 while (!result && element) {
14817 gchar *element_tag;
14818 gboolean accept_citation = FALSE;
14820 element_tag = webkit_dom_element_get_tag_name (element);
14822 if (g_ascii_strncasecmp (style_tag, "citation", 8) == 0) {
14823 accept_citation = TRUE;
14824 result = WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (element);
14825 if (element_has_class (element, "-x-evo-indented"))
14826 result = FALSE;
14827 } else {
14828 result = ((tag_len == strlen (element_tag)) &&
14829 (g_ascii_strncasecmp (element_tag, style_tag, tag_len) == 0));
14832 /* Special case: <blockquote type=cite> marks quotation, while
14833 * just <blockquote> is used for indentation. If the <blockquote>
14834 * has type=cite, then ignore it unless style_tag is "citation" */
14835 if (result && WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (element)) {
14836 if (webkit_dom_element_has_attribute (element, "type")) {
14837 gchar *type = webkit_dom_element_get_attribute (element, "type");
14838 if (!accept_citation && (type && g_ascii_strncasecmp (type, "cite", 4) == 0)) {
14839 result = FALSE;
14841 g_free (type);
14842 } else {
14843 if (accept_citation)
14844 result = FALSE;
14848 g_free (element_tag);
14850 if (result)
14851 break;
14853 element = webkit_dom_node_get_parent_element (
14854 WEBKIT_DOM_NODE (element));
14857 return result;
14860 typedef gboolean (*IsRightFormatNodeFunc) (WebKitDOMElement *element);
14862 static gboolean
14863 dom_selection_is_font_format (EEditorPage *editor_page,
14864 IsRightFormatNodeFunc func,
14865 gboolean *previous_value)
14867 WebKitDOMDocument *document;
14868 WebKitDOMDOMWindow *dom_window = NULL;
14869 WebKitDOMDOMSelection *dom_selection = NULL;
14870 WebKitDOMNode *start, *end, *sibling;
14871 WebKitDOMRange *range = NULL;
14872 gboolean ret_val = FALSE;
14874 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
14876 if (!e_editor_page_get_html_mode (editor_page))
14877 goto out;
14879 document = e_editor_page_get_document (editor_page);
14880 dom_window = webkit_dom_document_get_default_view (document);
14881 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
14882 g_clear_object (&dom_window);
14884 if (!webkit_dom_dom_selection_get_range_count (dom_selection))
14885 goto out;
14887 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
14888 if (!range)
14889 goto out;
14891 if (webkit_dom_range_get_collapsed (range, NULL) && previous_value) {
14892 WebKitDOMNode *node;
14893 gchar* text_content;
14895 node = webkit_dom_range_get_common_ancestor_container (range, NULL);
14896 /* If we are changing the format of block we have to re-set the
14897 * format property, otherwise it will be turned off because of no
14898 * text in block. */
14899 text_content = webkit_dom_node_get_text_content (node);
14900 if (g_strcmp0 (text_content, "") == 0) {
14901 g_free (text_content);
14902 ret_val = *previous_value;
14903 goto out;
14905 g_free (text_content);
14908 /* Range without start or end point is a wrong range. */
14909 start = webkit_dom_range_get_start_container (range, NULL);
14910 end = webkit_dom_range_get_end_container (range, NULL);
14911 if (!start || !end)
14912 goto out;
14914 if (WEBKIT_DOM_IS_TEXT (start))
14915 start = webkit_dom_node_get_parent_node (start);
14916 while (start && WEBKIT_DOM_IS_ELEMENT (start) && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (start)) {
14917 /* Find the start point's parent node with given formatting. */
14918 if (func (WEBKIT_DOM_ELEMENT (start))) {
14919 ret_val = TRUE;
14920 break;
14922 start = webkit_dom_node_get_parent_node (start);
14925 /* Start point doesn't have the given formatting. */
14926 if (!ret_val)
14927 goto out;
14929 /* If the selection is collapsed, we can return early. */
14930 if (webkit_dom_range_get_collapsed (range, NULL))
14931 goto out;
14933 /* The selection is in the same node and that node is supposed to have
14934 * the same formatting (otherwise it is split up with formatting element. */
14935 if (webkit_dom_node_is_same_node (
14936 webkit_dom_range_get_start_container (range, NULL),
14937 webkit_dom_range_get_end_container (range, NULL)))
14938 goto out;
14940 ret_val = FALSE;
14942 if (WEBKIT_DOM_IS_TEXT (end))
14943 end = webkit_dom_node_get_parent_node (end);
14944 while (end && WEBKIT_DOM_IS_ELEMENT (end) && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (end)) {
14945 /* Find the end point's parent node with given formatting. */
14946 if (func (WEBKIT_DOM_ELEMENT (end))) {
14947 ret_val = TRUE;
14948 break;
14950 end = webkit_dom_node_get_parent_node (end);
14953 if (!ret_val)
14954 goto out;
14956 ret_val = FALSE;
14958 /* Now go between the end points and check the inner nodes for format validity. */
14959 sibling = start;
14960 while ((sibling = webkit_dom_node_get_next_sibling (sibling))) {
14961 if (webkit_dom_node_is_same_node (sibling, end)) {
14962 ret_val = TRUE;
14963 goto out;
14966 if (WEBKIT_DOM_IS_TEXT (sibling))
14967 goto out;
14968 else if (func (WEBKIT_DOM_ELEMENT (sibling)))
14969 continue;
14970 else if (webkit_dom_node_get_first_child (sibling)) {
14971 WebKitDOMNode *first_child;
14973 first_child = webkit_dom_node_get_first_child (sibling);
14974 if (!webkit_dom_node_get_next_sibling (first_child))
14975 if (WEBKIT_DOM_IS_ELEMENT (first_child) && func (WEBKIT_DOM_ELEMENT (first_child)))
14976 continue;
14977 else
14978 goto out;
14979 else
14980 goto out;
14981 } else
14982 goto out;
14985 sibling = end;
14986 while ((sibling = webkit_dom_node_get_previous_sibling (sibling))) {
14987 if (webkit_dom_node_is_same_node (sibling, start))
14988 break;
14990 if (WEBKIT_DOM_IS_TEXT (sibling))
14991 goto out;
14992 else if (func (WEBKIT_DOM_ELEMENT (sibling)))
14993 continue;
14994 else if (webkit_dom_node_get_first_child (sibling)) {
14995 WebKitDOMNode *first_child;
14997 first_child = webkit_dom_node_get_first_child (sibling);
14998 if (!webkit_dom_node_get_next_sibling (first_child))
14999 if (WEBKIT_DOM_IS_ELEMENT (first_child) && func (WEBKIT_DOM_ELEMENT (first_child)))
15000 continue;
15001 else
15002 goto out;
15003 else
15004 goto out;
15005 } else
15006 goto out;
15009 ret_val = TRUE;
15010 out:
15011 g_clear_object (&range);
15012 g_clear_object (&dom_selection);
15014 return ret_val;
15017 static gboolean
15018 is_underline_element (WebKitDOMElement *element)
15020 if (!element || !WEBKIT_DOM_IS_ELEMENT (element))
15021 return FALSE;
15023 return element_has_tag (element, "u");
15027 * e_html_editor_selection_is_underline:
15028 * @selection: an #EEditorSelection
15030 * Returns whether current selection or letter at current cursor position
15031 * is underlined.
15033 * Returns @TRUE when selection is underlined, @FALSE otherwise.
15035 gboolean
15036 e_editor_dom_selection_is_underline (EEditorPage *editor_page)
15038 gboolean is_underline;
15040 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
15042 is_underline = e_editor_page_get_underline (editor_page);
15043 is_underline = dom_selection_is_font_format (
15044 editor_page, (IsRightFormatNodeFunc) is_underline_element, &is_underline);
15046 return is_underline;
15049 static WebKitDOMElement *
15050 set_font_style (WebKitDOMDocument *document,
15051 const gchar *element_name,
15052 gboolean value)
15054 WebKitDOMElement *element;
15055 WebKitDOMNode *parent, *clone = NULL;
15057 element = webkit_dom_document_get_element_by_id (document, "-x-evo-selection-end-marker");
15058 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
15059 if (value) {
15060 WebKitDOMNode *node;
15061 WebKitDOMElement *el;
15062 gchar *name;
15064 el = webkit_dom_document_create_element (document, element_name, NULL);
15065 webkit_dom_html_element_set_inner_text (
15066 WEBKIT_DOM_HTML_ELEMENT (el), UNICODE_ZERO_WIDTH_SPACE, NULL);
15068 node = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element));
15069 webkit_dom_node_append_child (
15070 WEBKIT_DOM_NODE (el), node, NULL);
15071 name = webkit_dom_element_get_tag_name (WEBKIT_DOM_ELEMENT (parent));
15072 if (g_ascii_strcasecmp (name, element_name) == 0 && g_ascii_strcasecmp (name, "font") != 0)
15073 webkit_dom_node_insert_before (
15074 webkit_dom_node_get_parent_node (parent),
15075 WEBKIT_DOM_NODE (el),
15076 webkit_dom_node_get_next_sibling (parent),
15077 NULL);
15078 else
15079 webkit_dom_node_insert_before (
15080 parent,
15081 WEBKIT_DOM_NODE (el),
15082 WEBKIT_DOM_NODE (element),
15083 NULL);
15084 g_free (name);
15086 webkit_dom_node_append_child (
15087 WEBKIT_DOM_NODE (el), WEBKIT_DOM_NODE (element), NULL);
15089 return el;
15090 } else {
15091 gboolean no_sibling;
15092 WebKitDOMNode *node, *sibling;
15094 node = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element));
15096 /* Turning the formatting in the middle of element. */
15097 sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element));
15098 no_sibling = sibling &&
15099 !WEBKIT_DOM_IS_HTML_BR_ELEMENT (sibling) &&
15100 !webkit_dom_node_get_next_sibling (sibling);
15102 if (no_sibling) {
15103 gboolean do_clone = TRUE;
15104 gchar *text_content = NULL;
15105 WebKitDOMNode *child;
15107 if ((text_content = webkit_dom_node_get_text_content (parent)) &&
15108 (g_strcmp0 (text_content, UNICODE_ZERO_WIDTH_SPACE) == 0))
15109 do_clone = FALSE;
15111 g_free (text_content);
15113 if (do_clone) {
15114 clone = webkit_dom_node_clone_node_with_error (
15115 WEBKIT_DOM_NODE (parent), FALSE, NULL);
15117 while ((child = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element))))
15118 webkit_dom_node_insert_before (
15119 clone,
15120 child,
15121 webkit_dom_node_get_first_child (clone),
15122 NULL);
15124 webkit_dom_node_insert_before (
15125 webkit_dom_node_get_parent_node (parent),
15126 clone,
15127 webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (parent)),
15128 NULL);
15132 webkit_dom_node_insert_before (
15133 webkit_dom_node_get_parent_node (parent),
15134 WEBKIT_DOM_NODE (element),
15135 webkit_dom_node_get_next_sibling (parent),
15136 NULL);
15137 webkit_dom_node_insert_before (
15138 webkit_dom_node_get_parent_node (parent),
15139 node,
15140 webkit_dom_node_get_next_sibling (parent),
15141 NULL);
15143 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (sibling) && !no_sibling) {
15144 webkit_dom_node_insert_before (
15145 webkit_dom_node_get_parent_node (parent),
15146 node,
15147 webkit_dom_node_get_next_sibling (parent),
15148 NULL);
15151 if (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (webkit_dom_node_get_parent_node (parent))) {
15152 WebKitDOMNode *first_child;
15154 if ((first_child = webkit_dom_node_get_first_child (parent))) {
15155 gchar *text_content = NULL;
15157 text_content = webkit_dom_node_get_text_content (first_child);
15159 if (g_strcmp0 (text_content, UNICODE_ZERO_WIDTH_SPACE) != 0)
15160 webkit_dom_element_insert_adjacent_text (
15161 WEBKIT_DOM_ELEMENT (parent),
15162 "afterend",
15163 UNICODE_ZERO_WIDTH_SPACE,
15164 NULL);
15166 g_free (text_content);
15169 remove_node_if_empty (parent);
15170 remove_node_if_empty (clone);
15174 return NULL;
15177 static void
15178 selection_set_font_style (EEditorPage *editor_page,
15179 EContentEditorCommand command,
15180 gboolean value)
15182 EEditorHistoryEvent *ev = NULL;
15183 EEditorUndoRedoManager *manager;
15185 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
15187 e_editor_dom_selection_save (editor_page);
15189 manager = e_editor_page_get_undo_redo_manager (editor_page);
15190 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
15191 ev = g_new0 (EEditorHistoryEvent, 1);
15192 if (command == E_CONTENT_EDITOR_COMMAND_BOLD)
15193 ev->type = HISTORY_BOLD;
15194 else if (command == E_CONTENT_EDITOR_COMMAND_ITALIC)
15195 ev->type = HISTORY_ITALIC;
15196 else if (command == E_CONTENT_EDITOR_COMMAND_UNDERLINE)
15197 ev->type = HISTORY_UNDERLINE;
15198 else if (command == E_CONTENT_EDITOR_COMMAND_STRIKETHROUGH)
15199 ev->type = HISTORY_STRIKETHROUGH;
15201 e_editor_dom_selection_get_coordinates (editor_page,
15202 &ev->before.start.x,
15203 &ev->before.start.y,
15204 &ev->before.end.x,
15205 &ev->before.end.y);
15207 ev->data.style.from = !value;
15208 ev->data.style.to = value;
15211 if (e_editor_dom_selection_is_collapsed (editor_page)) {
15212 const gchar *element_name = NULL;
15214 if (command == E_CONTENT_EDITOR_COMMAND_BOLD)
15215 element_name = "b";
15216 else if (command == E_CONTENT_EDITOR_COMMAND_ITALIC)
15217 element_name = "i";
15218 else if (command == E_CONTENT_EDITOR_COMMAND_UNDERLINE)
15219 element_name = "u";
15220 else if (command == E_CONTENT_EDITOR_COMMAND_STRIKETHROUGH)
15221 element_name = "strike";
15223 if (element_name)
15224 set_font_style (e_editor_page_get_document (editor_page), element_name, value);
15225 e_editor_dom_selection_restore (editor_page);
15227 goto exit;
15229 e_editor_dom_selection_restore (editor_page);
15231 e_editor_dom_exec_command (editor_page, command, NULL);
15232 exit:
15233 if (ev) {
15234 e_editor_dom_selection_get_coordinates (editor_page,
15235 &ev->after.start.x,
15236 &ev->after.start.y,
15237 &ev->after.end.x,
15238 &ev->after.end.y);
15239 e_editor_undo_redo_manager_insert_history_event (manager, ev);
15242 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
15246 * e_html_editor_selection_set_underline:
15247 * @selection: an #EEditorSelection
15248 * @underline: @TRUE to enable underline, @FALSE to disable
15250 * Toggles underline formatting of current selection or letter at current
15251 * cursor position, depending on whether @underline is @TRUE or @FALSE.
15253 void
15254 e_editor_dom_selection_set_underline (EEditorPage *editor_page,
15255 gboolean underline)
15257 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
15259 if (e_editor_dom_selection_is_underline (editor_page) == underline)
15260 return;
15262 selection_set_font_style (
15263 editor_page, E_CONTENT_EDITOR_COMMAND_UNDERLINE, underline);
15266 static gboolean
15267 is_subscript_element (WebKitDOMElement *element)
15269 if (!element || !WEBKIT_DOM_IS_ELEMENT (element))
15270 return FALSE;
15272 return element_has_tag (element, "sub");
15276 * e_html_editor_selection_is_subscript:
15277 * @selection: an #EEditorSelection
15279 * Returns whether current selection or letter at current cursor position
15280 * is in subscript.
15282 * Returns @TRUE when selection is in subscript, @FALSE otherwise.
15284 gboolean
15285 e_editor_dom_selection_is_subscript (EEditorPage *editor_page)
15287 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
15289 return dom_selection_is_font_format (
15290 editor_page, (IsRightFormatNodeFunc) is_subscript_element, NULL);
15294 * e_html_editor_selection_set_subscript:
15295 * @selection: an #EEditorSelection
15296 * @subscript: @TRUE to enable subscript, @FALSE to disable
15298 * Toggles subscript of current selection or letter at current cursor position,
15299 * depending on whether @subscript is @TRUE or @FALSE.
15301 void
15302 e_editor_dom_selection_set_subscript (EEditorPage *editor_page,
15303 gboolean subscript)
15305 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
15307 if (e_editor_dom_selection_is_subscript (editor_page) == subscript)
15308 return;
15310 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_SUBSCRIPT, NULL);
15313 static gboolean
15314 is_superscript_element (WebKitDOMElement *element)
15316 if (!element || !WEBKIT_DOM_IS_ELEMENT (element))
15317 return FALSE;
15319 return element_has_tag (element, "sup");
15323 * e_html_editor_selection_is_superscript:
15324 * @selection: an #EEditorSelection
15326 * Returns whether current selection or letter at current cursor position
15327 * is in superscript.
15329 * Returns @TRUE when selection is in superscript, @FALSE otherwise.
15331 gboolean
15332 e_editor_dom_selection_is_superscript (EEditorPage *editor_page)
15334 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
15336 return dom_selection_is_font_format (
15337 editor_page, (IsRightFormatNodeFunc) is_superscript_element, NULL);
15341 * e_html_editor_selection_set_superscript:
15342 * @selection: an #EEditorSelection
15343 * @superscript: @TRUE to enable superscript, @FALSE to disable
15345 * Toggles superscript of current selection or letter at current cursor position,
15346 * depending on whether @superscript is @TRUE or @FALSE.
15348 void
15349 e_editor_dom_selection_set_superscript (EEditorPage *editor_page,
15350 gboolean superscript)
15352 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
15354 if (e_editor_dom_selection_is_superscript (editor_page) == superscript)
15355 return;
15357 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_SUPERSCRIPT, NULL);
15360 static gboolean
15361 is_strikethrough_element (WebKitDOMElement *element)
15363 if (!element || !WEBKIT_DOM_IS_ELEMENT (element))
15364 return FALSE;
15366 return element_has_tag (element, "strike");
15370 * e_html_editor_selection_is_strikethrough:
15371 * @selection: an #EEditorSelection
15373 * Returns whether current selection or letter at current cursor position
15374 * is striked through.
15376 * Returns @TRUE when selection is striked through, @FALSE otherwise.
15378 gboolean
15379 e_editor_dom_selection_is_strikethrough (EEditorPage *editor_page)
15381 gboolean is_strikethrough;
15383 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
15385 is_strikethrough = e_editor_page_get_strikethrough (editor_page);
15386 is_strikethrough = dom_selection_is_font_format (
15387 editor_page, (IsRightFormatNodeFunc) is_strikethrough_element, &is_strikethrough);
15389 return is_strikethrough;
15393 * e_html_editor_selection_set_strikethrough:
15394 * @selection: an #EEditorSelection
15395 * @strikethrough: @TRUE to enable strikethrough, @FALSE to disable
15397 * Toggles strike through formatting of current selection or letter at current
15398 * cursor position, depending on whether @strikethrough is @TRUE or @FALSE.
15400 void
15401 e_editor_dom_selection_set_strikethrough (EEditorPage *editor_page,
15402 gboolean strikethrough)
15404 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
15406 if (e_editor_dom_selection_is_strikethrough (editor_page) == strikethrough)
15407 return;
15409 selection_set_font_style (
15410 editor_page, E_CONTENT_EDITOR_COMMAND_STRIKETHROUGH, strikethrough);
15413 static gboolean
15414 is_monospace_element (WebKitDOMElement *element)
15416 gchar *value;
15417 gboolean ret_val = FALSE;
15419 if (!element)
15420 return FALSE;
15422 if (!WEBKIT_DOM_IS_HTML_FONT_ELEMENT (element))
15423 return FALSE;
15425 value = webkit_dom_element_get_attribute (element, "face");
15426 if (value && g_strcmp0 (value, "monospace") == 0)
15427 ret_val = TRUE;
15429 g_free (value);
15431 return ret_val;
15435 * e_html_editor_selection_is_monospaced:
15436 * @selection: an #EEditorSelection
15438 * Returns whether current selection or letter at current cursor position
15439 * is monospaced.
15441 * Returns @TRUE when selection is monospaced, @FALSE otherwise.
15443 gboolean
15444 e_editor_dom_selection_is_monospace (EEditorPage *editor_page)
15446 gboolean is_monospace;
15448 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
15450 is_monospace = e_editor_page_get_monospace (editor_page);
15451 is_monospace = dom_selection_is_font_format (
15452 editor_page, (IsRightFormatNodeFunc) is_monospace_element, &is_monospace);
15454 return is_monospace;
15457 static void
15458 monospace_selection (EEditorPage *editor_page,
15459 WebKitDOMElement *monospace_element)
15461 WebKitDOMDocument *document;
15462 WebKitDOMElement *selection_start_marker, *selection_end_marker;
15463 WebKitDOMNode *sibling, *node, *monospace, *block;
15464 WebKitDOMNodeList *list = NULL;
15465 gboolean selection_end = FALSE;
15466 gboolean first = TRUE;
15467 gint ii;
15469 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
15471 document = e_editor_page_get_document (editor_page);
15472 e_editor_dom_selection_save (editor_page);
15474 selection_start_marker = webkit_dom_document_get_element_by_id (
15475 document, "-x-evo-selection-start-marker");
15476 selection_end_marker = webkit_dom_document_get_element_by_id (
15477 document, "-x-evo-selection-end-marker");
15479 block = WEBKIT_DOM_NODE (get_parent_block_element (WEBKIT_DOM_NODE (selection_start_marker)));
15481 monospace = WEBKIT_DOM_NODE (monospace_element);
15482 node = WEBKIT_DOM_NODE (selection_start_marker);
15483 /* Go through first block in selection. */
15484 while (block && node && !webkit_dom_node_is_same_node (block, node)) {
15485 if (webkit_dom_node_get_next_sibling (node)) {
15486 /* Prepare the monospaced element. */
15487 monospace = webkit_dom_node_insert_before (
15488 webkit_dom_node_get_parent_node (node),
15489 first ? monospace : webkit_dom_node_clone_node_with_error (monospace, FALSE, NULL),
15490 first ? node : webkit_dom_node_get_next_sibling (node),
15491 NULL);
15492 } else
15493 break;
15495 /* Move the nodes into monospaced element. */
15496 while (((sibling = webkit_dom_node_get_next_sibling (monospace)))) {
15497 webkit_dom_node_append_child (monospace, sibling, NULL);
15498 if (webkit_dom_node_is_same_node (WEBKIT_DOM_NODE (selection_end_marker), sibling)) {
15499 selection_end = TRUE;
15500 break;
15504 node = webkit_dom_node_get_parent_node (monospace);
15505 first = FALSE;
15508 /* Just one block was selected. */
15509 if (selection_end)
15510 goto out;
15512 /* Middle blocks (blocks not containing the end of the selection. */
15513 block = webkit_dom_node_get_next_sibling (block);
15514 while (block && !selection_end) {
15515 WebKitDOMNode *next_block;
15517 selection_end = webkit_dom_node_contains (
15518 block, WEBKIT_DOM_NODE (selection_end_marker));
15520 if (selection_end)
15521 break;
15523 next_block = webkit_dom_node_get_next_sibling (block);
15525 monospace = webkit_dom_node_insert_before (
15526 block,
15527 webkit_dom_node_clone_node_with_error (monospace, FALSE, NULL),
15528 webkit_dom_node_get_first_child (block),
15529 NULL);
15531 while (((sibling = webkit_dom_node_get_next_sibling (monospace))))
15532 webkit_dom_node_append_child (monospace, sibling, NULL);
15534 block = next_block;
15537 /* Block containing the end of selection. */
15538 node = WEBKIT_DOM_NODE (selection_end_marker);
15539 while (block && node && !webkit_dom_node_is_same_node (block, node)) {
15540 monospace = webkit_dom_node_insert_before (
15541 webkit_dom_node_get_parent_node (node),
15542 webkit_dom_node_clone_node_with_error (monospace, FALSE, NULL),
15543 webkit_dom_node_get_next_sibling (node),
15544 NULL);
15546 while (((sibling = webkit_dom_node_get_previous_sibling (monospace)))) {
15547 webkit_dom_node_insert_before (
15548 monospace,
15549 sibling,
15550 webkit_dom_node_get_first_child (monospace),
15551 NULL);
15554 node = webkit_dom_node_get_parent_node (monospace);
15556 out:
15557 /* Merge all the monospace elements inside other monospace elements. */
15558 list = webkit_dom_document_query_selector_all (
15559 document, "font[face=monospace] > font[face=monospace]", NULL);
15560 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
15561 WebKitDOMNode *item;
15562 WebKitDOMNode *child;
15564 item = webkit_dom_node_list_item (list, ii);
15565 while ((child = webkit_dom_node_get_first_child (item))) {
15566 webkit_dom_node_insert_before (
15567 webkit_dom_node_get_parent_node (item),
15568 child,
15569 item,
15570 NULL);
15572 remove_node (item);
15574 g_clear_object (&list);
15576 /* Merge all the adjacent monospace elements. */
15577 list = webkit_dom_document_query_selector_all (
15578 document, "font[face=monospace] + font[face=monospace]", NULL);
15579 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
15580 WebKitDOMNode *item;
15581 WebKitDOMNode *child;
15583 item = webkit_dom_node_list_item (list, ii);
15584 /* The + CSS selector will return some false positives as it doesn't
15585 * take text between elements into account so it will return this:
15586 * <font face="monospace">xx</font>yy<font face="monospace">zz</font>
15587 * as valid, but it isn't so we have to check if previous node
15588 * is indeed element or not. */
15589 if (WEBKIT_DOM_IS_ELEMENT (webkit_dom_node_get_previous_sibling (item))) {
15590 while ((child = webkit_dom_node_get_first_child (item))) {
15591 webkit_dom_node_append_child (
15592 webkit_dom_node_get_previous_sibling (item), child, NULL);
15594 remove_node (item);
15597 g_clear_object (&list);
15599 e_editor_dom_selection_restore (editor_page);
15602 static void
15603 unmonospace_selection (EEditorPage *editor_page)
15605 WebKitDOMDocument *document;
15606 WebKitDOMElement *selection_start_marker;
15607 WebKitDOMElement *selection_end_marker;
15608 WebKitDOMElement *selection_start_clone;
15609 WebKitDOMElement *selection_end_clone;
15610 WebKitDOMNode *sibling, *node;
15611 WebKitDOMNode *block, *clone, *monospace;
15612 gboolean selection_end = FALSE;
15614 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
15616 document = e_editor_page_get_document (editor_page);
15617 e_editor_dom_selection_save (editor_page);
15619 selection_start_marker = webkit_dom_document_get_element_by_id (
15620 document, "-x-evo-selection-start-marker");
15621 selection_end_marker = webkit_dom_document_get_element_by_id (
15622 document, "-x-evo-selection-end-marker");
15624 block = WEBKIT_DOM_NODE (get_parent_block_element (WEBKIT_DOM_NODE (selection_start_marker)));
15626 node = WEBKIT_DOM_NODE (selection_start_marker);
15627 monospace = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start_marker));
15628 while (monospace && !is_monospace_element (WEBKIT_DOM_ELEMENT (monospace)))
15629 monospace = webkit_dom_node_get_parent_node (monospace);
15631 /* No monospaced element was found as a parent of selection start node. */
15632 if (!monospace)
15633 goto out;
15635 /* Make a clone of current monospaced element. */
15636 clone = webkit_dom_node_clone_node_with_error (monospace, TRUE, NULL);
15638 /* First block */
15639 /* Remove all the nodes that are after the selection start point as they
15640 * will be in the cloned node. */
15641 while (monospace && node && !webkit_dom_node_is_same_node (monospace, node)) {
15642 WebKitDOMNode *tmp;
15643 while (((sibling = webkit_dom_node_get_next_sibling (node))))
15644 remove_node (sibling);
15646 tmp = webkit_dom_node_get_parent_node (node);
15647 if (webkit_dom_node_get_next_sibling (node))
15648 remove_node (node);
15649 node = tmp;
15652 selection_start_clone = webkit_dom_element_query_selector (
15653 WEBKIT_DOM_ELEMENT (clone), "#-x-evo-selection-start-marker", NULL);
15654 selection_end_clone = webkit_dom_element_query_selector (
15655 WEBKIT_DOM_ELEMENT (clone), "#-x-evo-selection-end-marker", NULL);
15657 /* No selection start node in the block where it is supposed to be, return. */
15658 if (!selection_start_clone)
15659 goto out;
15661 /* Remove all the nodes until we hit the selection start point as these
15662 * nodes will stay monospaced and they are already in original element. */
15663 node = webkit_dom_node_get_first_child (clone);
15664 while (node) {
15665 WebKitDOMNode *next_sibling;
15667 next_sibling = webkit_dom_node_get_next_sibling (node);
15668 if (webkit_dom_node_get_first_child (node)) {
15669 if (webkit_dom_node_contains (node, WEBKIT_DOM_NODE (selection_start_clone))) {
15670 node = webkit_dom_node_get_first_child (node);
15671 continue;
15672 } else
15673 remove_node (node);
15674 } else if (webkit_dom_node_is_same_node (node, WEBKIT_DOM_NODE (selection_start_clone)))
15675 break;
15676 else
15677 remove_node (node);
15679 node = next_sibling;
15682 /* Insert the clone into the tree. Do it after the previous clean up. If
15683 * we would do it the other way the line would contain duplicated text nodes
15684 * and the block would be expading and shrinking while we would modify it. */
15685 webkit_dom_node_insert_before (
15686 webkit_dom_node_get_parent_node (monospace),
15687 clone,
15688 webkit_dom_node_get_next_sibling (monospace),
15689 NULL);
15691 /* Move selection start point the right place. */
15692 remove_node (WEBKIT_DOM_NODE (selection_start_marker));
15693 webkit_dom_node_insert_before (
15694 webkit_dom_node_get_parent_node (clone),
15695 WEBKIT_DOM_NODE (selection_start_clone),
15696 clone,
15697 NULL);
15699 /* Move all the nodes the are supposed to lose the monospace formatting
15700 * out of monospaced element. */
15701 node = webkit_dom_node_get_first_child (clone);
15702 while (node) {
15703 WebKitDOMNode *next_sibling;
15705 next_sibling = webkit_dom_node_get_next_sibling (node);
15706 if (webkit_dom_node_get_first_child (node)) {
15707 if (selection_end_clone &&
15708 webkit_dom_node_contains (node, WEBKIT_DOM_NODE (selection_end_clone))) {
15709 node = webkit_dom_node_get_first_child (node);
15710 continue;
15711 } else
15712 webkit_dom_node_insert_before (
15713 webkit_dom_node_get_parent_node (clone),
15714 node,
15715 clone,
15716 NULL);
15717 } else if (selection_end_clone &&
15718 webkit_dom_node_is_same_node (node, WEBKIT_DOM_NODE (selection_end_clone))) {
15719 selection_end = TRUE;
15720 webkit_dom_node_insert_before (
15721 webkit_dom_node_get_parent_node (clone),
15722 node,
15723 clone,
15724 NULL);
15725 break;
15726 } else
15727 webkit_dom_node_insert_before (
15728 webkit_dom_node_get_parent_node (clone),
15729 node,
15730 clone,
15731 NULL);
15733 node = next_sibling;
15736 remove_node_if_empty (clone);
15737 remove_node_if_empty (monospace);
15739 /* Just one block was selected and we hit the selection end point. */
15740 if (selection_end)
15741 goto out;
15743 /* Middle blocks */
15744 block = webkit_dom_node_get_next_sibling (block);
15745 while (block && !selection_end) {
15746 WebKitDOMNode *next_block, *child, *parent;
15747 WebKitDOMElement *monospace_element;
15749 selection_end = webkit_dom_node_contains (
15750 block, WEBKIT_DOM_NODE (selection_end_marker));
15752 if (selection_end)
15753 break;
15755 next_block = webkit_dom_node_get_next_sibling (block);
15757 /* Find the monospaced element and move all the nodes from it and
15758 * finally remove it. */
15759 monospace_element = webkit_dom_element_query_selector (
15760 WEBKIT_DOM_ELEMENT (block), "font[face=monospace]", NULL);
15761 if (!monospace_element)
15762 break;
15764 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (monospace_element));
15765 while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (monospace_element)))) {
15766 webkit_dom_node_insert_before (
15767 parent, child, WEBKIT_DOM_NODE (monospace_element), NULL);
15770 remove_node (WEBKIT_DOM_NODE (monospace_element));
15772 block = next_block;
15775 /* End block */
15776 node = WEBKIT_DOM_NODE (selection_end_marker);
15777 monospace = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_end_marker));
15778 while (monospace && !is_monospace_element (WEBKIT_DOM_ELEMENT (monospace)))
15779 monospace = webkit_dom_node_get_parent_node (monospace);
15781 /* No monospaced element was found as a parent of selection end node. */
15782 if (!monospace)
15783 return;
15785 clone = WEBKIT_DOM_NODE (monospace);
15786 node = webkit_dom_node_get_first_child (clone);
15787 /* Move all the nodes that are supposed to lose the monospaced formatting
15788 * out of the monospaced element. */
15789 while (node) {
15790 WebKitDOMNode *next_sibling;
15792 next_sibling = webkit_dom_node_get_next_sibling (node);
15793 if (webkit_dom_node_get_first_child (node)) {
15794 if (webkit_dom_node_contains (node, WEBKIT_DOM_NODE (selection_end_marker))) {
15795 node = webkit_dom_node_get_first_child (node);
15796 continue;
15797 } else
15798 webkit_dom_node_insert_before (
15799 webkit_dom_node_get_parent_node (clone),
15800 node,
15801 clone,
15802 NULL);
15803 } else if (webkit_dom_node_is_same_node (node, WEBKIT_DOM_NODE (selection_end_marker))) {
15804 selection_end = TRUE;
15805 webkit_dom_node_insert_before (
15806 webkit_dom_node_get_parent_node (clone),
15807 node,
15808 clone,
15809 NULL);
15810 break;
15811 } else {
15812 webkit_dom_node_insert_before (
15813 webkit_dom_node_get_parent_node (clone),
15814 node,
15815 clone,
15816 NULL);
15819 node = next_sibling;
15822 remove_node_if_empty (clone);
15823 out:
15824 e_editor_dom_selection_restore (editor_page);
15828 * e_html_editor_selection_set_monospaced:
15829 * @selection: an #EEditorSelection
15830 * @monospaced: @TRUE to enable monospaced, @FALSE to disable
15832 * Toggles monospaced formatting of current selection or letter at current cursor
15833 * position, depending on whether @monospaced is @TRUE or @FALSE.
15835 void
15836 e_editor_dom_selection_set_monospace (EEditorPage *editor_page,
15837 gboolean value)
15839 WebKitDOMDocument *document;
15840 WebKitDOMRange *range = NULL;
15841 EEditorHistoryEvent *ev = NULL;
15842 EEditorUndoRedoManager *manager;
15843 guint font_size = 0;
15845 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
15847 if ((e_editor_dom_selection_is_monospace (editor_page) ? 1 : 0) == (value ? 1 : 0))
15848 return;
15850 document = e_editor_page_get_document (editor_page);
15851 range = e_editor_dom_get_current_range (editor_page);
15852 if (!range)
15853 return;
15855 manager = e_editor_page_get_undo_redo_manager (editor_page);
15856 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
15857 ev = g_new0 (EEditorHistoryEvent, 1);
15858 ev->type = HISTORY_MONOSPACE;
15860 e_editor_dom_selection_get_coordinates (editor_page,
15861 &ev->before.start.x,
15862 &ev->before.start.y,
15863 &ev->before.end.x,
15864 &ev->before.end.y);
15866 ev->data.style.from = !value;
15867 ev->data.style.to = value;
15870 font_size = e_editor_page_get_font_size (editor_page);
15871 if (font_size == 0)
15872 font_size = E_CONTENT_EDITOR_FONT_SIZE_NORMAL;
15874 if (value) {
15875 WebKitDOMElement *monospace;
15877 monospace = webkit_dom_document_create_element (
15878 document, "font", NULL);
15879 webkit_dom_element_set_attribute (
15880 monospace, "face", "monospace", NULL);
15881 if (font_size != 0) {
15882 gchar *font_size_str;
15884 font_size_str = g_strdup_printf ("%d", font_size);
15885 webkit_dom_element_set_attribute (
15886 monospace, "size", font_size_str, NULL);
15887 g_free (font_size_str);
15890 if (!webkit_dom_range_get_collapsed (range, NULL))
15891 monospace_selection (editor_page, monospace);
15892 else {
15893 /* https://bugs.webkit.org/show_bug.cgi?id=15256 */
15894 webkit_dom_element_set_inner_html (
15895 monospace,
15896 UNICODE_ZERO_WIDTH_SPACE,
15897 NULL);
15898 webkit_dom_range_insert_node (
15899 range, WEBKIT_DOM_NODE (monospace), NULL);
15901 e_editor_dom_move_caret_into_element (editor_page, monospace, FALSE);
15903 } else {
15904 gboolean is_bold = FALSE, is_italic = FALSE;
15905 gboolean is_underline = FALSE, is_strikethrough = FALSE;
15906 WebKitDOMElement *tt_element;
15907 WebKitDOMNode *node;
15909 node = webkit_dom_range_get_end_container (range, NULL);
15910 if (WEBKIT_DOM_IS_ELEMENT (node) &&
15911 is_monospace_element (WEBKIT_DOM_ELEMENT (node))) {
15912 tt_element = WEBKIT_DOM_ELEMENT (node);
15913 } else {
15914 tt_element = dom_node_find_parent_element (node, "FONT");
15916 if (!is_monospace_element (tt_element)) {
15917 g_clear_object (&range);
15918 g_free (ev);
15919 return;
15923 /* Save current formatting */
15924 is_bold = e_editor_page_get_bold (editor_page);
15925 is_italic = e_editor_page_get_italic (editor_page);
15926 is_underline = e_editor_page_get_underline (editor_page);
15927 is_strikethrough = e_editor_page_get_strikethrough (editor_page);
15929 if (!e_editor_dom_selection_is_collapsed (editor_page))
15930 unmonospace_selection (editor_page);
15931 else {
15932 e_editor_dom_selection_save (editor_page);
15933 set_font_style (document, "", FALSE);
15934 e_editor_dom_selection_restore (editor_page);
15937 /* Re-set formatting */
15938 if (is_bold)
15939 e_editor_dom_selection_set_bold (editor_page, TRUE);
15940 if (is_italic)
15941 e_editor_dom_selection_set_italic (editor_page, TRUE);
15942 if (is_underline)
15943 e_editor_dom_selection_set_underline (editor_page, TRUE);
15944 if (is_strikethrough)
15945 e_editor_dom_selection_set_strikethrough (editor_page, TRUE);
15947 if (font_size)
15948 e_editor_dom_selection_set_font_size (editor_page, font_size);
15951 if (ev) {
15952 e_editor_dom_selection_get_coordinates (editor_page,
15953 &ev->after.start.x,
15954 &ev->after.start.y,
15955 &ev->after.end.x,
15956 &ev->after.end.y);
15957 e_editor_undo_redo_manager_insert_history_event (manager, ev);
15960 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
15962 g_clear_object (&range);
15965 static gboolean
15966 is_bold_element (WebKitDOMElement *element)
15968 if (!element || !WEBKIT_DOM_IS_ELEMENT (element))
15969 return FALSE;
15971 if (element_has_tag (element, "b"))
15972 return TRUE;
15974 /* Headings are bold by default */
15975 return WEBKIT_DOM_IS_HTML_HEADING_ELEMENT (element);
15979 * e_html_editor_selection_is_bold:
15980 * @selection: an #EEditorSelection
15982 * Returns whether current selection or letter at current cursor position
15983 * is bold.
15985 * Returns @TRUE when selection is bold, @FALSE otherwise.
15987 gboolean
15988 e_editor_dom_selection_is_bold (EEditorPage *editor_page)
15990 gboolean is_bold;
15992 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
15994 is_bold = e_editor_page_get_bold (editor_page);
15995 is_bold = dom_selection_is_font_format (
15996 editor_page, (IsRightFormatNodeFunc) is_bold_element, &is_bold);
15998 return is_bold;
16002 * e_html_editor_selection_set_bold:
16003 * @selection: an #EEditorSelection
16004 * @bold: @TRUE to enable bold, @FALSE to disable
16006 * Toggles bold formatting of current selection or letter at current cursor
16007 * position, depending on whether @bold is @TRUE or @FALSE.
16009 void
16010 e_editor_dom_selection_set_bold (EEditorPage *editor_page,
16011 gboolean bold)
16013 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
16015 if (e_editor_dom_selection_is_bold (editor_page) == bold)
16016 return;
16018 selection_set_font_style (
16019 editor_page, E_CONTENT_EDITOR_COMMAND_BOLD, bold);
16021 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
16024 static gboolean
16025 is_italic_element (WebKitDOMElement *element)
16027 if (!element || !WEBKIT_DOM_IS_ELEMENT (element))
16028 return FALSE;
16030 return element_has_tag (element, "i") || element_has_tag (element, "address");
16034 * e_html_editor_selection_is_italic:
16035 * @selection: an #EEditorSelection
16037 * Returns whether current selection or letter at current cursor position
16038 * is italic.
16040 * Returns @TRUE when selection is italic, @FALSE otherwise.
16042 gboolean
16043 e_editor_dom_selection_is_italic (EEditorPage *editor_page)
16045 gboolean is_italic;
16047 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
16049 is_italic = e_editor_page_get_italic (editor_page);
16050 is_italic = dom_selection_is_font_format (
16051 editor_page, (IsRightFormatNodeFunc) is_italic_element, &is_italic);
16053 return is_italic;
16057 * e_html_editor_selection_set_italic:
16058 * @selection: an #EEditorSelection
16059 * @italic: @TRUE to enable italic, @FALSE to disable
16061 * Toggles italic formatting of current selection or letter at current cursor
16062 * position, depending on whether @italic is @TRUE or @FALSE.
16064 void
16065 e_editor_dom_selection_set_italic (EEditorPage *editor_page,
16066 gboolean italic)
16068 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
16070 if (e_editor_dom_selection_is_italic (editor_page) == italic)
16071 return;
16073 selection_set_font_style (
16074 editor_page, E_CONTENT_EDITOR_COMMAND_ITALIC, italic);
16078 * e_html_editor_selection_is_indented:
16079 * @selection: an #EEditorSelection
16081 * Returns whether current paragraph is indented. This does not include
16082 * citations. To check, whether paragraph is a citation, use
16083 * e_html_editor_selection_is_citation().
16085 * Returns: @TRUE when current paragraph is indented, @FALSE otherwise.
16087 gboolean
16088 e_editor_dom_selection_is_indented (EEditorPage *editor_page)
16090 WebKitDOMElement *element;
16091 WebKitDOMRange *range = NULL;
16093 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
16095 range = e_editor_dom_get_current_range (editor_page);
16096 if (!range)
16097 return FALSE;
16099 if (webkit_dom_range_get_collapsed (range, NULL)) {
16100 element = get_element_for_inspection (range);
16101 g_clear_object (&range);
16102 return element_has_class (element, "-x-evo-indented");
16103 } else {
16104 WebKitDOMNode *node;
16105 gboolean ret_val;
16107 node = webkit_dom_range_get_end_container (range, NULL);
16108 /* No selection or whole body selected */
16109 if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node))
16110 goto out;
16112 element = WEBKIT_DOM_ELEMENT (get_parent_indented_block (node));
16113 ret_val = element_has_class (element, "-x-evo-indented");
16114 if (!ret_val)
16115 goto out;
16117 node = webkit_dom_range_get_start_container (range, NULL);
16118 /* No selection or whole body selected */
16119 if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node))
16120 goto out;
16122 element = WEBKIT_DOM_ELEMENT (get_parent_indented_block (node));
16123 ret_val = element_has_class (element, "-x-evo-indented");
16125 g_clear_object (&range);
16127 return ret_val;
16130 out:
16131 g_clear_object (&range);
16133 return FALSE;
16137 * e_html_editor_selection_is_citation:
16138 * @selection: an #EEditorSelection
16140 * Returns whether current paragraph is a citation.
16142 * Returns: @TRUE when current paragraph is a citation, @FALSE otherwise.
16144 gboolean
16145 e_editor_dom_selection_is_citation (EEditorPage *editor_page)
16147 WebKitDOMNode *node;
16148 WebKitDOMRange *range = NULL;
16149 gboolean ret_val;
16150 gchar *value, *text_content;
16152 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
16154 range = e_editor_dom_get_current_range (editor_page);
16155 if (!range)
16156 return FALSE;
16158 node = webkit_dom_range_get_common_ancestor_container (range, NULL);
16159 g_clear_object (&range);
16161 if (WEBKIT_DOM_IS_TEXT (node))
16162 return get_has_style (editor_page, "citation");
16164 text_content = webkit_dom_node_get_text_content (node);
16165 if (g_strcmp0 (text_content, "") == 0) {
16166 g_free (text_content);
16167 return FALSE;
16169 g_free (text_content);
16171 value = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "type");
16172 /* citation == <blockquote type='cite'> */
16173 if (value && strstr (value, "cite"))
16174 ret_val = TRUE;
16175 else
16176 ret_val = get_has_style (editor_page, "citation");
16178 g_free (value);
16179 return ret_val;
16182 static gchar *
16183 get_font_property (EEditorPage *editor_page,
16184 const gchar *font_property)
16186 WebKitDOMRange *range = NULL;
16187 WebKitDOMNode *node;
16188 WebKitDOMElement *element;
16189 gchar *value;
16191 range = e_editor_dom_get_current_range (editor_page);
16192 if (!range)
16193 return NULL;
16195 node = webkit_dom_range_get_common_ancestor_container (range, NULL);
16196 g_clear_object (&range);
16197 element = dom_node_find_parent_element (node, "FONT");
16198 while (element && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (element) &&
16199 !webkit_dom_element_has_attribute (element, font_property)) {
16200 element = dom_node_find_parent_element (
16201 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)), "FONT");
16204 if (!element)
16205 return NULL;
16207 g_object_get (G_OBJECT (element), font_property, &value, NULL);
16209 return value;
16213 * e_editor_dom_selection_get_font_size:
16214 * @selection: an #EEditorSelection
16216 * Returns point size of current selection or of letter at current cursor position.
16218 guint
16219 e_editor_dom_selection_get_font_size (EEditorPage *editor_page)
16221 gchar *size;
16222 guint size_int;
16223 gboolean increment;
16225 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), 0);
16227 size = get_font_property (editor_page, "size");
16228 if (!(size && *size)) {
16229 g_free (size);
16230 return E_CONTENT_EDITOR_FONT_SIZE_NORMAL;
16233 /* We don't support increments, but when going through a content that
16234 * was not written in Evolution we can find it. In this case just report
16235 * the normal size. */
16236 /* FIXME: go through all parent and get the right value. */
16237 increment = size[0] == '+' || size[0] == '-';
16238 size_int = atoi (size);
16239 g_free (size);
16241 if (increment || size_int == 0)
16242 return E_CONTENT_EDITOR_FONT_SIZE_NORMAL;
16244 return size_int;
16248 * e_html_editor_selection_set_font_size:
16249 * @selection: an #EEditorSelection
16250 * @font_size: point size to apply
16252 * Sets font size of current selection or of letter at current cursor position
16253 * to @font_size.
16255 void
16256 e_editor_dom_selection_set_font_size (EEditorPage *editor_page,
16257 EContentEditorFontSize font_size)
16259 WebKitDOMDocument *document;
16260 EEditorUndoRedoManager *manager;
16261 EEditorHistoryEvent *ev = NULL;
16262 gchar *size_str;
16263 guint current_font_size;
16265 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
16267 document = e_editor_page_get_document (editor_page);
16268 current_font_size = e_editor_dom_selection_get_font_size (editor_page);
16269 if (current_font_size == font_size)
16270 return;
16272 e_editor_dom_selection_save (editor_page);
16274 manager = e_editor_page_get_undo_redo_manager (editor_page);
16275 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
16276 ev = g_new0 (EEditorHistoryEvent, 1);
16277 ev->type = HISTORY_FONT_SIZE;
16279 e_editor_dom_selection_get_coordinates (editor_page,
16280 &ev->before.start.x,
16281 &ev->before.start.y,
16282 &ev->before.end.x,
16283 &ev->before.end.y);
16285 ev->data.style.from = current_font_size;
16286 ev->data.style.to = font_size;
16289 size_str = g_strdup_printf ("%d", font_size);
16291 if (e_editor_dom_selection_is_collapsed (editor_page)) {
16292 WebKitDOMElement *font;
16294 font = set_font_style (document, "font", font_size != 3);
16295 if (font)
16296 webkit_dom_element_set_attribute (font, "size", size_str, NULL);
16297 e_editor_dom_selection_restore (editor_page);
16298 goto exit;
16301 e_editor_dom_selection_restore (editor_page);
16303 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_FONT_SIZE, size_str);
16305 /* Text in <font size="3"></font> (size 3 is our default size) is a little
16306 * bit smaller than font outsize it. So move it outside of it. */
16307 if (font_size == E_CONTENT_EDITOR_FONT_SIZE_NORMAL) {
16308 WebKitDOMElement *element;
16310 element = webkit_dom_document_query_selector (document, "font[size=\"3\"]", NULL);
16311 if (element) {
16312 WebKitDOMNode *child;
16314 while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element))))
16315 webkit_dom_node_insert_before (
16316 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
16317 child,
16318 WEBKIT_DOM_NODE (element),
16319 NULL);
16321 remove_node (WEBKIT_DOM_NODE (element));
16325 exit:
16326 g_free (size_str);
16328 if (ev) {
16329 e_editor_dom_selection_get_coordinates (editor_page,
16330 &ev->after.start.x,
16331 &ev->after.start.y,
16332 &ev->after.end.x,
16333 &ev->after.end.y);
16335 e_editor_undo_redo_manager_insert_history_event (manager, ev);
16340 * e_html_editor_selection_set_font_name:
16341 * @selection: an #EEditorSelection
16342 * @font_name: a font name to apply
16344 * Sets font name of current selection or of letter at current cursor position
16345 * to @font_name.
16347 void
16348 e_editor_dom_selection_set_font_name (EEditorPage *editor_page,
16349 const gchar *font_name)
16351 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
16353 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_FONT_NAME, font_name);
16357 * e_html_editor_selection_get_font_name:
16358 * @selection: an #EEditorSelection
16360 * Returns name of font used in current selection or at letter at current cursor
16361 * position.
16363 * Returns: A string with font name. [transfer-none]
16365 gchar *
16366 e_editor_dom_selection_get_font_name (EEditorPage *editor_page)
16368 WebKitDOMNode *node;
16369 WebKitDOMRange *range = NULL;
16370 WebKitDOMCSSStyleDeclaration *css = NULL;
16371 gchar *value;
16373 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
16375 range = e_editor_dom_get_current_range (editor_page);
16376 node = webkit_dom_range_get_common_ancestor_container (range, NULL);
16377 g_clear_object (&range);
16379 css = webkit_dom_element_get_style (WEBKIT_DOM_ELEMENT (node));
16380 value = webkit_dom_css_style_declaration_get_property_value (css, "fontFamily");
16381 g_clear_object (&css);
16383 return value;
16387 * e_html_editor_selection_set_font_color:
16388 * @selection: an #EEditorSelection
16389 * @rgba: a #GdkRGBA
16391 * Sets font color of current selection or letter at current cursor position to
16392 * color defined in @rgba.
16394 void
16395 e_editor_dom_selection_set_font_color (EEditorPage *editor_page,
16396 const gchar *color)
16398 EEditorUndoRedoManager *manager;
16399 EEditorHistoryEvent *ev = NULL;
16401 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
16403 manager = e_editor_page_get_undo_redo_manager (editor_page);
16404 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
16405 ev = g_new0 (EEditorHistoryEvent, 1);
16406 ev->type = HISTORY_FONT_COLOR;
16408 e_editor_dom_selection_get_coordinates (editor_page,
16409 &ev->before.start.x,
16410 &ev->before.start.y,
16411 &ev->before.end.x,
16412 &ev->before.end.y);
16414 ev->data.string.from = g_strdup (e_editor_page_get_font_color (editor_page));
16415 ev->data.string.to = g_strdup (color);
16418 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_FORE_COLOR, color);
16420 if (ev) {
16421 ev->after.start.x = ev->before.start.x;
16422 ev->after.start.y = ev->before.start.y;
16423 ev->after.end.x = ev->before.end.x;
16424 ev->after.end.y = ev->before.end.y;
16426 e_editor_undo_redo_manager_insert_history_event (manager, ev);
16431 * e_html_editor_selection_get_font_color:
16432 * @selection: an #EEditorSelection
16433 * @rgba: a #GdkRGBA object to be set to current font color
16435 * Sets @rgba to contain color of current text selection or letter at current
16436 * cursor position.
16438 gchar *
16439 e_editor_dom_selection_get_font_color (EEditorPage *editor_page)
16441 WebKitDOMDocument *document;
16442 gchar *color;
16444 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
16446 document = e_editor_page_get_document (editor_page);
16447 color = get_font_property (editor_page, "color");
16448 if (!(color && *color)) {
16449 WebKitDOMHTMLElement *body;
16451 body = webkit_dom_document_get_body (document);
16452 g_free (color);
16453 color = webkit_dom_html_body_element_get_text (WEBKIT_DOM_HTML_BODY_ELEMENT (body));
16454 if (!(color && *color)) {
16455 g_free (color);
16456 return g_strdup ("#000000");
16460 return color;
16464 * e_html_editor_selection_get_block_format:
16465 * @selection: an #EEditorSelection
16467 * Returns block format of current paragraph.
16469 * Returns: #EContentEditorBlockFormat
16471 EContentEditorBlockFormat
16472 e_editor_dom_selection_get_block_format (EEditorPage *editor_page)
16474 WebKitDOMNode *node;
16475 WebKitDOMRange *range = NULL;
16476 WebKitDOMElement *element;
16477 EContentEditorBlockFormat result;
16479 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), E_CONTENT_EDITOR_BLOCK_FORMAT_NONE);
16481 range = e_editor_dom_get_current_range (editor_page);
16482 if (!range)
16483 return E_CONTENT_EDITOR_BLOCK_FORMAT_PARAGRAPH;
16485 node = webkit_dom_range_get_start_container (range, NULL);
16487 if ((element = dom_node_find_parent_element (node, "UL"))) {
16488 WebKitDOMElement *tmp_element;
16490 tmp_element = dom_node_find_parent_element (node, "OL");
16491 if (tmp_element) {
16492 if (webkit_dom_node_contains (WEBKIT_DOM_NODE (tmp_element), WEBKIT_DOM_NODE (element)))
16493 result = dom_get_list_format_from_node (WEBKIT_DOM_NODE (element));
16494 else
16495 result = dom_get_list_format_from_node (WEBKIT_DOM_NODE (tmp_element));
16496 } else
16497 result = E_CONTENT_EDITOR_BLOCK_FORMAT_UNORDERED_LIST;
16498 } else if ((element = dom_node_find_parent_element (node, "OL")) != NULL) {
16499 WebKitDOMElement *tmp_element;
16501 tmp_element = dom_node_find_parent_element (node, "UL");
16502 if (tmp_element) {
16503 if (webkit_dom_node_contains (WEBKIT_DOM_NODE (element), WEBKIT_DOM_NODE (tmp_element)))
16504 result = dom_get_list_format_from_node (WEBKIT_DOM_NODE (element));
16505 else
16506 result = dom_get_list_format_from_node (WEBKIT_DOM_NODE (tmp_element));
16507 } else
16508 result = dom_get_list_format_from_node (WEBKIT_DOM_NODE (element));
16509 } else if (dom_node_find_parent_element (node, "PRE")) {
16510 result = E_CONTENT_EDITOR_BLOCK_FORMAT_PRE;
16511 } else if (dom_node_find_parent_element (node, "ADDRESS")) {
16512 result = E_CONTENT_EDITOR_BLOCK_FORMAT_ADDRESS;
16513 } else if (dom_node_find_parent_element (node, "H1")) {
16514 result = E_CONTENT_EDITOR_BLOCK_FORMAT_H1;
16515 } else if (dom_node_find_parent_element (node, "H2")) {
16516 result = E_CONTENT_EDITOR_BLOCK_FORMAT_H2;
16517 } else if (dom_node_find_parent_element (node, "H3")) {
16518 result = E_CONTENT_EDITOR_BLOCK_FORMAT_H3;
16519 } else if (dom_node_find_parent_element (node, "H4")) {
16520 result = E_CONTENT_EDITOR_BLOCK_FORMAT_H4;
16521 } else if (dom_node_find_parent_element (node, "H5")) {
16522 result = E_CONTENT_EDITOR_BLOCK_FORMAT_H5;
16523 } else if (dom_node_find_parent_element (node, "H6")) {
16524 result = E_CONTENT_EDITOR_BLOCK_FORMAT_H6;
16525 } else {
16526 /* Everything else is a paragraph (normal block) for us */
16527 result = E_CONTENT_EDITOR_BLOCK_FORMAT_PARAGRAPH;
16530 g_clear_object (&range);
16532 return result;
16535 static void
16536 change_leading_space_to_nbsp (WebKitDOMNode *block)
16538 WebKitDOMNode *child;
16540 if (!WEBKIT_DOM_IS_HTML_PRE_ELEMENT (block))
16541 return;
16543 if ((child = webkit_dom_node_get_first_child (block)) &&
16544 WEBKIT_DOM_IS_CHARACTER_DATA (child)) {
16545 gchar *data;
16547 data = webkit_dom_character_data_substring_data (
16548 WEBKIT_DOM_CHARACTER_DATA (child), 0, 1, NULL);
16550 if (data && *data == ' ')
16551 webkit_dom_character_data_replace_data (
16552 WEBKIT_DOM_CHARACTER_DATA (child), 0, 1, UNICODE_NBSP, NULL);
16553 g_free (data);
16557 static void
16558 change_trailing_space_in_block_to_nbsp (WebKitDOMNode *block)
16560 WebKitDOMNode *child;
16562 if ((child = webkit_dom_node_get_last_child (block)) &&
16563 WEBKIT_DOM_IS_CHARACTER_DATA (child)) {
16564 gchar *tmp;
16565 gulong length;
16567 length = webkit_dom_character_data_get_length (
16568 WEBKIT_DOM_CHARACTER_DATA (child));
16570 tmp = webkit_dom_character_data_substring_data (
16571 WEBKIT_DOM_CHARACTER_DATA (child), length - 1, 1, NULL);
16572 if (tmp && *tmp == ' ') {
16573 webkit_dom_character_data_replace_data (
16574 WEBKIT_DOM_CHARACTER_DATA (child),
16575 length - 1,
16577 UNICODE_NBSP,
16578 NULL);
16580 g_free (tmp);
16584 static void
16585 change_space_before_selection_to_nbsp (WebKitDOMNode *node)
16587 WebKitDOMNode *prev_sibling;
16589 if ((prev_sibling = webkit_dom_node_get_previous_sibling (node))) {
16590 if (WEBKIT_DOM_IS_CHARACTER_DATA (prev_sibling)) {
16591 gchar *tmp;
16592 gulong length;
16594 length = webkit_dom_character_data_get_length (
16595 WEBKIT_DOM_CHARACTER_DATA (prev_sibling));
16597 tmp = webkit_dom_character_data_substring_data (
16598 WEBKIT_DOM_CHARACTER_DATA (prev_sibling), length - 1, 1, NULL);
16599 if (tmp && *tmp == ' ') {
16600 webkit_dom_character_data_replace_data (
16601 WEBKIT_DOM_CHARACTER_DATA (prev_sibling),
16602 length - 1,
16604 UNICODE_NBSP,
16605 NULL);
16607 g_free (tmp);
16612 static gboolean
16613 process_block_to_block (EEditorPage *editor_page,
16614 EContentEditorBlockFormat format,
16615 const gchar *value,
16616 WebKitDOMNode *block,
16617 WebKitDOMNode *end_block,
16618 WebKitDOMNode *blockquote,
16619 gboolean html_mode)
16621 WebKitDOMDocument *document;
16622 WebKitDOMNode *next_block;
16623 gboolean after_selection_end = FALSE;
16625 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
16627 document = e_editor_page_get_document (editor_page);
16629 while (!after_selection_end && block) {
16630 gboolean quoted = FALSE;
16631 gboolean empty = FALSE;
16632 gchar *content;
16633 gint citation_level = 0;
16634 WebKitDOMNode *child;
16635 WebKitDOMElement *element;
16637 if (e_editor_dom_node_is_citation_node (block)) {
16638 gboolean finished;
16640 next_block = webkit_dom_node_get_next_sibling (block);
16641 finished = process_block_to_block (
16642 editor_page,
16643 format,
16644 value,
16645 webkit_dom_node_get_first_child (block),
16646 end_block,
16647 blockquote,
16648 html_mode);
16650 if (finished)
16651 return TRUE;
16653 block = next_block;
16655 continue;
16658 if (webkit_dom_element_query_selector (
16659 WEBKIT_DOM_ELEMENT (block), "span.-x-evo-quoted", NULL)) {
16660 quoted = TRUE;
16661 e_editor_dom_remove_quoting_from_element (WEBKIT_DOM_ELEMENT (block));
16664 if (!html_mode)
16665 e_editor_dom_remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (block));
16667 after_selection_end = webkit_dom_node_is_same_node (block, end_block);
16669 next_block = webkit_dom_node_get_next_sibling (block);
16671 if (node_is_list (block)) {
16672 WebKitDOMNode *item;
16674 item = webkit_dom_node_get_first_child (block);
16675 while (item && !WEBKIT_DOM_IS_HTML_LI_ELEMENT (item))
16676 item = webkit_dom_node_get_first_child (item);
16678 if (item && do_format_change_list_to_block (editor_page, format, item, value))
16679 return TRUE;
16681 block = next_block;
16683 continue;
16686 if (format == E_CONTENT_EDITOR_BLOCK_FORMAT_PARAGRAPH)
16687 element = e_editor_dom_get_paragraph_element (editor_page, -1, 0);
16688 else
16689 element = webkit_dom_document_create_element (
16690 document, value, NULL);
16692 content = webkit_dom_node_get_text_content (block);
16694 empty = !*content || (g_strcmp0 (content, UNICODE_ZERO_WIDTH_SPACE) == 0);
16695 g_free (content);
16697 change_leading_space_to_nbsp (block);
16698 change_trailing_space_in_block_to_nbsp (block);
16700 while ((child = webkit_dom_node_get_first_child (block))) {
16701 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (child))
16702 empty = FALSE;
16704 webkit_dom_node_append_child (
16705 WEBKIT_DOM_NODE (element), child, NULL);
16708 if (empty) {
16709 WebKitDOMElement *br;
16711 br = webkit_dom_document_create_element (
16712 document, "BR", NULL);
16713 webkit_dom_node_append_child (
16714 WEBKIT_DOM_NODE (element), WEBKIT_DOM_NODE (br), NULL);
16717 webkit_dom_node_insert_before (
16718 webkit_dom_node_get_parent_node (block),
16719 WEBKIT_DOM_NODE (element),
16720 block,
16721 NULL);
16723 remove_node (block);
16725 citation_level = e_editor_dom_get_citation_level (WEBKIT_DOM_NODE (element));
16727 if (!next_block && !after_selection_end && citation_level > 0) {
16728 next_block = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
16729 next_block = webkit_dom_node_get_next_sibling (next_block);
16732 block = next_block;
16734 if (!html_mode && format == E_CONTENT_EDITOR_BLOCK_FORMAT_PARAGRAPH) {
16735 citation_level = e_editor_dom_get_citation_level (WEBKIT_DOM_NODE (element));
16737 if (citation_level > 0) {
16738 gint quote, word_wrap_length;
16740 word_wrap_length =
16741 e_editor_page_get_word_wrap_length (editor_page);
16742 quote = citation_level * 2;
16744 element = e_editor_dom_wrap_paragraph_length (
16745 editor_page, element, word_wrap_length - quote);
16750 if (!html_mode && quoted && citation_level > 0)
16751 e_editor_dom_quote_plain_text_element_after_wrapping (
16752 editor_page, element, citation_level);
16755 return after_selection_end;
16758 static void
16759 format_change_block_to_block (EEditorPage *editor_page,
16760 EContentEditorBlockFormat format,
16761 const gchar *value)
16763 WebKitDOMDocument *document;
16764 WebKitDOMElement *selection_start_marker, *selection_end_marker;
16765 WebKitDOMNode *block, *end_block, *blockquote = NULL;
16766 gboolean html_mode = FALSE;
16768 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
16770 document = e_editor_page_get_document (editor_page);
16771 selection_start_marker = webkit_dom_document_get_element_by_id (
16772 document, "-x-evo-selection-start-marker");
16773 selection_end_marker = webkit_dom_document_get_element_by_id (
16774 document, "-x-evo-selection-end-marker");
16776 /* If the selection was not saved, move it into the first child of body */
16777 if (!selection_start_marker || !selection_end_marker) {
16778 WebKitDOMHTMLElement *body;
16779 WebKitDOMNode *child;
16781 body = webkit_dom_document_get_body (document);
16782 child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
16784 dom_add_selection_markers_into_element_start (
16785 document,
16786 WEBKIT_DOM_ELEMENT (child),
16787 &selection_start_marker,
16788 &selection_end_marker);
16791 block = e_editor_dom_get_parent_block_node_from_child (
16792 WEBKIT_DOM_NODE (selection_start_marker));
16794 html_mode = e_editor_page_get_html_mode (editor_page);
16796 end_block = e_editor_dom_get_parent_block_node_from_child (
16797 WEBKIT_DOM_NODE (selection_end_marker));
16799 /* Process all blocks that are in the selection one by one */
16800 process_block_to_block (
16801 editor_page, format, value, block, end_block, blockquote, html_mode);
16804 static void
16805 format_change_block_to_list (EEditorPage *editor_page,
16806 EContentEditorBlockFormat format)
16808 WebKitDOMDocument *document;
16809 WebKitDOMElement *selection_start_marker, *selection_end_marker, *item, *list;
16810 WebKitDOMNode *block, *next_block;
16811 gboolean after_selection_end = FALSE, in_quote = FALSE;
16812 gboolean html_mode = e_editor_page_get_html_mode (editor_page);
16814 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
16816 document = e_editor_page_get_document (editor_page);
16817 selection_start_marker = webkit_dom_document_get_element_by_id (
16818 document, "-x-evo-selection-start-marker");
16819 selection_end_marker = webkit_dom_document_get_element_by_id (
16820 document, "-x-evo-selection-end-marker");
16822 /* If the selection was not saved, move it into the first child of body */
16823 if (!selection_start_marker || !selection_end_marker) {
16824 WebKitDOMHTMLElement *body;
16825 WebKitDOMNode *child;
16827 body = webkit_dom_document_get_body (document);
16828 child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
16830 dom_add_selection_markers_into_element_start (
16831 document,
16832 WEBKIT_DOM_ELEMENT (child),
16833 &selection_start_marker,
16834 &selection_end_marker);
16837 block = e_editor_dom_get_parent_block_node_from_child (
16838 WEBKIT_DOM_NODE (selection_start_marker));
16840 list = create_list_element (editor_page, format, 0, html_mode);
16842 if (webkit_dom_element_query_selector (
16843 WEBKIT_DOM_ELEMENT (block), "span.-x-evo-quoted", NULL)) {
16844 WebKitDOMElement *element;
16845 WebKitDOMDOMWindow *dom_window = NULL;
16846 WebKitDOMDOMSelection *dom_selection = NULL;
16847 WebKitDOMRange *range = NULL;
16849 in_quote = TRUE;
16851 dom_window = webkit_dom_document_get_default_view (document);
16852 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
16853 range = webkit_dom_document_create_range (document);
16855 webkit_dom_range_select_node (range, block, NULL);
16856 webkit_dom_range_collapse (range, TRUE, NULL);
16857 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
16858 webkit_dom_dom_selection_add_range (dom_selection, range);
16860 g_clear_object (&range);
16861 g_clear_object (&dom_selection);
16862 g_clear_object (&dom_window);
16864 e_editor_dom_remove_input_event_listener_from_body (editor_page);
16865 e_editor_page_block_selection_changed (editor_page);
16867 e_editor_dom_exec_command (
16868 editor_page, E_CONTENT_EDITOR_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT, NULL);
16870 e_editor_dom_register_input_event_listener_on_body (editor_page);
16871 e_editor_page_unblock_selection_changed (editor_page);
16873 element = webkit_dom_document_query_selector (
16874 document, "body>br", NULL);
16876 webkit_dom_node_replace_child (
16877 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
16878 WEBKIT_DOM_NODE (list),
16879 WEBKIT_DOM_NODE (element),
16880 NULL);
16882 block = e_editor_dom_get_parent_block_node_from_child (
16883 WEBKIT_DOM_NODE (selection_start_marker));
16884 } else
16885 webkit_dom_node_insert_before (
16886 webkit_dom_node_get_parent_node (block),
16887 WEBKIT_DOM_NODE (list),
16888 block,
16889 NULL);
16891 /* Process all blocks that are in the selection one by one */
16892 while (block && !after_selection_end) {
16893 gboolean empty = FALSE, block_is_list;
16894 gchar *content;
16895 WebKitDOMNode *child, *parent;
16897 after_selection_end = webkit_dom_node_contains (
16898 block, WEBKIT_DOM_NODE (selection_end_marker));
16900 next_block = webkit_dom_node_get_next_sibling (
16901 WEBKIT_DOM_NODE (block));
16903 e_editor_dom_remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (block));
16904 e_editor_dom_remove_quoting_from_element (WEBKIT_DOM_ELEMENT (block));
16906 item = webkit_dom_document_create_element (document, "LI", NULL);
16907 content = webkit_dom_node_get_text_content (block);
16909 empty = !*content || (g_strcmp0 (content, UNICODE_ZERO_WIDTH_SPACE) == 0);
16910 g_free (content);
16912 change_leading_space_to_nbsp (block);
16913 change_trailing_space_in_block_to_nbsp (block);
16915 block_is_list = node_is_list_or_item (block);
16917 while ((child = webkit_dom_node_get_first_child (block))) {
16918 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (child))
16919 empty = FALSE;
16921 webkit_dom_node_append_child (
16922 WEBKIT_DOM_NODE (block_is_list ? list : item), child, NULL);
16925 if (!block_is_list) {
16926 /* We have to use again the hidden space to move caret into newly inserted list */
16927 if (empty) {
16928 WebKitDOMElement *br;
16930 br = webkit_dom_document_create_element (
16931 document, "BR", NULL);
16932 webkit_dom_node_append_child (
16933 WEBKIT_DOM_NODE (item), WEBKIT_DOM_NODE (br), NULL);
16936 webkit_dom_node_append_child (
16937 WEBKIT_DOM_NODE (list), WEBKIT_DOM_NODE (item), NULL);
16940 parent = webkit_dom_node_get_parent_node (block);
16941 remove_node (block);
16943 if (in_quote) {
16944 /* Remove all parents if previously removed node was the
16945 * only one with text content */
16946 content = webkit_dom_node_get_text_content (parent);
16947 while (parent && content && !*content) {
16948 WebKitDOMNode *tmp = webkit_dom_node_get_parent_node (parent);
16950 remove_node (parent);
16951 parent = tmp;
16953 g_free (content);
16954 content = webkit_dom_node_get_text_content (parent);
16956 g_free (content);
16959 block = next_block;
16962 merge_lists_if_possible (WEBKIT_DOM_NODE (list));
16965 static WebKitDOMElement *
16966 do_format_change_list_to_list (WebKitDOMElement *list_to_process,
16967 WebKitDOMElement *new_list_template,
16968 EContentEditorBlockFormat to)
16970 EContentEditorBlockFormat current_format;
16972 current_format = dom_get_list_format_from_node (
16973 WEBKIT_DOM_NODE (list_to_process));
16974 if (to == current_format) {
16975 /* Same format, skip it. */
16976 return list_to_process;
16977 } else if (current_format >= E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST &&
16978 to >= E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST) {
16979 /* Changing from ordered list type to another ordered list type. */
16980 set_ordered_list_type_to_element (list_to_process, to);
16981 return list_to_process;
16982 } else {
16983 WebKitDOMNode *clone, *child;
16985 /* Create new list from template. */
16986 clone = webkit_dom_node_clone_node_with_error (
16987 WEBKIT_DOM_NODE (new_list_template), FALSE, NULL);
16989 /* Insert it before the list that we are processing. */
16990 webkit_dom_node_insert_before (
16991 webkit_dom_node_get_parent_node (
16992 WEBKIT_DOM_NODE (list_to_process)),
16993 clone,
16994 WEBKIT_DOM_NODE (list_to_process),
16995 NULL);
16997 /* Move all it children to the new one. */
16998 while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (list_to_process))))
16999 webkit_dom_node_append_child (clone, child, NULL);
17001 remove_node (WEBKIT_DOM_NODE (list_to_process));
17003 return WEBKIT_DOM_ELEMENT (clone);
17006 return NULL;
17009 static void
17010 format_change_list_from_list (EEditorPage *editor_page,
17011 EContentEditorBlockFormat to,
17012 gboolean html_mode)
17014 WebKitDOMDocument *document;
17015 WebKitDOMElement *selection_start_marker, *selection_end_marker, *new_list;
17016 WebKitDOMNode *source_list, *source_list_clone, *current_list, *item;
17017 gboolean after_selection_end = FALSE;
17019 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17021 document = e_editor_page_get_document (editor_page);
17022 selection_start_marker = webkit_dom_document_get_element_by_id (
17023 document, "-x-evo-selection-start-marker");
17024 selection_end_marker = webkit_dom_document_get_element_by_id (
17025 document, "-x-evo-selection-end-marker");
17027 if (!selection_start_marker || !selection_end_marker)
17028 return;
17030 /* Copy elements from previous block to list */
17031 item = get_list_item_node_from_child (WEBKIT_DOM_NODE (selection_start_marker));
17032 source_list = webkit_dom_node_get_parent_node (item);
17033 current_list = source_list;
17034 source_list_clone = webkit_dom_node_clone_node_with_error (source_list, FALSE, NULL);
17036 new_list = create_list_element (editor_page, to, 0, html_mode);
17038 if (element_has_class (WEBKIT_DOM_ELEMENT (source_list), "-x-evo-indented"))
17039 element_add_class (WEBKIT_DOM_ELEMENT (new_list), "-x-evo-indented");
17041 while (item) {
17042 gboolean selection_end;
17043 WebKitDOMNode *next_item = webkit_dom_node_get_next_sibling (item);
17045 selection_end = webkit_dom_node_contains (
17046 item, WEBKIT_DOM_NODE (selection_end_marker));
17048 if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (item)) {
17049 /* Actual node is an item, just copy it. */
17050 webkit_dom_node_append_child (
17051 after_selection_end ?
17052 source_list_clone : WEBKIT_DOM_NODE (new_list),
17053 item,
17054 NULL);
17055 } else if (node_is_list (item) && !selection_end && !after_selection_end) {
17056 /* Node is a list and it doesn't contain the selection end
17057 * marker, we can process the whole list. */
17058 gint ii;
17059 WebKitDOMNodeList *list = NULL;
17060 WebKitDOMElement *processed_list;
17062 list = webkit_dom_element_query_selector_all (
17063 WEBKIT_DOM_ELEMENT (item), "ol,ul", NULL);
17064 ii = webkit_dom_node_list_get_length (list);
17065 g_clear_object (&list);
17067 /* Process every sublist separately. */
17068 while (ii) {
17069 WebKitDOMElement *list_to_process;
17071 list_to_process = webkit_dom_element_query_selector (
17072 WEBKIT_DOM_ELEMENT (item), "ol,ul", NULL);
17073 if (list_to_process)
17074 do_format_change_list_to_list (list_to_process, new_list, to);
17075 ii--;
17078 /* Process the current list. */
17079 processed_list = do_format_change_list_to_list (
17080 WEBKIT_DOM_ELEMENT (item), new_list, to);
17082 webkit_dom_node_append_child (
17083 WEBKIT_DOM_NODE (new_list),
17084 WEBKIT_DOM_NODE (processed_list),
17085 NULL);
17086 } else if (node_is_list (item) && !after_selection_end) {
17087 /* Node is a list and it contains the selection end marker,
17088 * thus we have to process it until we find the marker. */
17089 gint ii;
17090 WebKitDOMNodeList *list = NULL;
17092 list = webkit_dom_element_query_selector_all (
17093 WEBKIT_DOM_ELEMENT (item), "ol,ul", NULL);
17094 ii = webkit_dom_node_list_get_length (list);
17095 g_clear_object (&list);
17097 /* No nested lists - process the items. */
17098 if (ii == 0) {
17099 WebKitDOMNode *clone, *child;
17101 clone = webkit_dom_node_clone_node_with_error (
17102 WEBKIT_DOM_NODE (new_list), FALSE, NULL);
17104 webkit_dom_node_append_child (
17105 WEBKIT_DOM_NODE (new_list), clone, NULL);
17107 while ((child = webkit_dom_node_get_first_child (item))) {
17108 webkit_dom_node_append_child (clone, child, NULL);
17109 if (webkit_dom_node_contains (child, WEBKIT_DOM_NODE (selection_end_marker)))
17110 break;
17113 if (webkit_dom_node_get_first_child (item))
17114 webkit_dom_node_append_child (
17115 WEBKIT_DOM_NODE (new_list), item, NULL);
17116 else
17117 remove_node (item);
17118 } else {
17119 gboolean done = FALSE;
17120 WebKitDOMNode *tmp_parent = WEBKIT_DOM_NODE (new_list);
17121 WebKitDOMNode *tmp_item = WEBKIT_DOM_NODE (item);
17123 while (!done) {
17124 WebKitDOMNode *clone, *child;
17126 clone = webkit_dom_node_clone_node_with_error (
17127 WEBKIT_DOM_NODE (new_list), FALSE, NULL);
17129 webkit_dom_node_append_child (
17130 tmp_parent, clone, NULL);
17132 while ((child = webkit_dom_node_get_first_child (tmp_item))) {
17133 if (!webkit_dom_node_contains (child, WEBKIT_DOM_NODE (selection_end_marker))) {
17134 webkit_dom_node_append_child (clone, child, NULL);
17135 } else if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (child)) {
17136 webkit_dom_node_append_child (clone, child, NULL);
17137 done = TRUE;
17138 break;
17139 } else {
17140 tmp_parent = clone;
17141 tmp_item = child;
17142 break;
17147 } else {
17148 webkit_dom_node_append_child (
17149 after_selection_end ?
17150 source_list_clone : WEBKIT_DOM_NODE (new_list),
17151 item,
17152 NULL);
17155 if (selection_end) {
17156 source_list_clone = webkit_dom_node_clone_node_with_error (current_list, FALSE, NULL);
17157 after_selection_end = TRUE;
17160 if (!next_item) {
17161 if (after_selection_end)
17162 break;
17164 current_list = webkit_dom_node_get_next_sibling (current_list);
17165 if (!node_is_list_or_item (current_list))
17166 break;
17167 if (node_is_list (current_list)) {
17168 next_item = webkit_dom_node_get_first_child (current_list);
17169 if (!node_is_list_or_item (next_item))
17170 break;
17171 } else if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (current_list)) {
17172 next_item = current_list;
17173 current_list = webkit_dom_node_get_parent_node (next_item);
17177 item = next_item;
17180 webkit_dom_node_insert_before (
17181 webkit_dom_node_get_parent_node (source_list),
17182 WEBKIT_DOM_NODE (source_list_clone),
17183 webkit_dom_node_get_next_sibling (source_list),
17184 NULL);
17186 if (webkit_dom_node_has_child_nodes (WEBKIT_DOM_NODE (new_list)))
17187 webkit_dom_node_insert_before (
17188 webkit_dom_node_get_parent_node (source_list_clone),
17189 WEBKIT_DOM_NODE (new_list),
17190 source_list_clone,
17191 NULL);
17193 remove_node_if_empty (source_list);
17194 remove_node_if_empty (source_list_clone);
17195 remove_node_if_empty (current_list);
17197 merge_lists_if_possible (WEBKIT_DOM_NODE (new_list));
17200 static void
17201 format_change_list_to_list (EEditorPage *editor_page,
17202 EContentEditorBlockFormat format,
17203 gboolean html_mode)
17205 WebKitDOMDocument *document;
17206 WebKitDOMElement *selection_start_marker, *selection_end_marker;
17207 WebKitDOMNode *prev_list, *current_list, *next_list;
17208 EContentEditorBlockFormat prev = 0, next = 0;
17209 gboolean done = FALSE, indented = FALSE;
17210 gboolean selection_starts_in_first_child, selection_ends_in_last_child;
17212 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17214 document = e_editor_page_get_document (editor_page);
17215 selection_start_marker = webkit_dom_document_get_element_by_id (
17216 document, "-x-evo-selection-start-marker");
17217 selection_end_marker = webkit_dom_document_get_element_by_id (
17218 document, "-x-evo-selection-end-marker");
17220 current_list = get_list_node_from_child (
17221 WEBKIT_DOM_NODE (selection_start_marker));
17223 prev_list = get_list_node_from_child (
17224 WEBKIT_DOM_NODE (selection_start_marker));
17226 next_list = get_list_node_from_child (
17227 WEBKIT_DOM_NODE (selection_end_marker));
17229 selection_starts_in_first_child =
17230 webkit_dom_node_contains (
17231 webkit_dom_node_get_first_child (current_list),
17232 WEBKIT_DOM_NODE (selection_start_marker));
17234 selection_ends_in_last_child =
17235 webkit_dom_node_contains (
17236 webkit_dom_node_get_last_child (current_list),
17237 WEBKIT_DOM_NODE (selection_end_marker));
17239 indented = element_has_class (WEBKIT_DOM_ELEMENT (current_list), "-x-evo-indented");
17241 if (!prev_list || !next_list || indented) {
17242 format_change_list_from_list (editor_page, format, html_mode);
17243 return;
17246 if (webkit_dom_node_is_same_node (prev_list, next_list)) {
17247 prev_list = webkit_dom_node_get_previous_sibling (
17248 webkit_dom_node_get_parent_node (
17249 webkit_dom_node_get_parent_node (
17250 WEBKIT_DOM_NODE (selection_start_marker))));
17251 next_list = webkit_dom_node_get_next_sibling (
17252 webkit_dom_node_get_parent_node (
17253 webkit_dom_node_get_parent_node (
17254 WEBKIT_DOM_NODE (selection_end_marker))));
17255 if (!prev_list || !next_list) {
17256 format_change_list_from_list (editor_page, format, html_mode);
17257 return;
17261 prev = dom_get_list_format_from_node (prev_list);
17262 next = dom_get_list_format_from_node (next_list);
17264 if (format != E_CONTENT_EDITOR_BLOCK_FORMAT_NONE) {
17265 if (format == prev && prev != E_CONTENT_EDITOR_BLOCK_FORMAT_NONE) {
17266 if (selection_starts_in_first_child && selection_ends_in_last_child) {
17267 done = TRUE;
17268 merge_list_into_list (current_list, prev_list, FALSE);
17271 if (format == next && next != E_CONTENT_EDITOR_BLOCK_FORMAT_NONE) {
17272 if (selection_starts_in_first_child && selection_ends_in_last_child) {
17273 done = TRUE;
17274 merge_list_into_list (next_list, prev_list, FALSE);
17279 if (done)
17280 return;
17282 format_change_list_from_list (editor_page, format, html_mode);
17286 * e_html_editor_selection_set_block_format:
17287 * @selection: an #EEditorSelection
17288 * @format: an #EContentEditorBlockFormat value
17290 * Changes block format of current paragraph to @format.
17292 void
17293 e_editor_dom_selection_set_block_format (EEditorPage *editor_page,
17294 EContentEditorBlockFormat format)
17296 WebKitDOMDocument *document;
17297 WebKitDOMRange *range = NULL;
17298 EContentEditorBlockFormat current_format;
17299 EContentEditorAlignment current_alignment;
17300 EEditorUndoRedoManager *manager;
17301 EEditorHistoryEvent *ev = NULL;
17302 const gchar *value;
17303 gboolean from_list = FALSE, to_list = FALSE, html_mode = FALSE;
17305 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17307 document = e_editor_page_get_document (editor_page);
17308 current_format = e_editor_dom_selection_get_block_format (editor_page);
17309 if (current_format == format)
17310 return;
17312 switch (format) {
17313 case E_CONTENT_EDITOR_BLOCK_FORMAT_H1:
17314 value = "H1";
17315 break;
17316 case E_CONTENT_EDITOR_BLOCK_FORMAT_H2:
17317 value = "H2";
17318 break;
17319 case E_CONTENT_EDITOR_BLOCK_FORMAT_H3:
17320 value = "H3";
17321 break;
17322 case E_CONTENT_EDITOR_BLOCK_FORMAT_H4:
17323 value = "H4";
17324 break;
17325 case E_CONTENT_EDITOR_BLOCK_FORMAT_H5:
17326 value = "H5";
17327 break;
17328 case E_CONTENT_EDITOR_BLOCK_FORMAT_H6:
17329 value = "H6";
17330 break;
17331 case E_CONTENT_EDITOR_BLOCK_FORMAT_PARAGRAPH:
17332 value = "DIV";
17333 break;
17334 case E_CONTENT_EDITOR_BLOCK_FORMAT_PRE:
17335 value = "PRE";
17336 break;
17337 case E_CONTENT_EDITOR_BLOCK_FORMAT_ADDRESS:
17338 value = "ADDRESS";
17339 break;
17340 case E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST:
17341 case E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST_ALPHA:
17342 case E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST_ROMAN:
17343 to_list = TRUE;
17344 value = NULL;
17345 break;
17346 case E_CONTENT_EDITOR_BLOCK_FORMAT_UNORDERED_LIST:
17347 to_list = TRUE;
17348 value = NULL;
17349 break;
17350 case E_CONTENT_EDITOR_BLOCK_FORMAT_NONE:
17351 default:
17352 value = NULL;
17353 break;
17356 html_mode = e_editor_page_get_html_mode (editor_page);
17358 from_list =
17359 current_format >= E_CONTENT_EDITOR_BLOCK_FORMAT_UNORDERED_LIST;
17361 range = e_editor_dom_get_current_range (editor_page);
17362 if (!range)
17363 return;
17365 current_alignment = e_editor_page_get_alignment (editor_page);
17367 e_editor_dom_selection_save (editor_page);
17369 manager = e_editor_page_get_undo_redo_manager (editor_page);
17370 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
17371 ev = g_new0 (EEditorHistoryEvent, 1);
17372 ev->type = HISTORY_BLOCK_FORMAT;
17374 e_editor_dom_selection_get_coordinates (editor_page,
17375 &ev->before.start.x,
17376 &ev->before.start.y,
17377 &ev->before.end.x,
17378 &ev->before.end.y);
17380 ev->data.style.from = current_format;
17381 ev->data.style.to = format;
17384 g_clear_object (&range);
17386 if (current_format == E_CONTENT_EDITOR_BLOCK_FORMAT_PRE) {
17387 WebKitDOMElement *selection_marker;
17389 selection_marker = webkit_dom_document_get_element_by_id (
17390 document, "-x-evo-selection-start-marker");
17391 if (selection_marker)
17392 change_space_before_selection_to_nbsp (WEBKIT_DOM_NODE (selection_marker));
17393 selection_marker = webkit_dom_document_get_element_by_id (
17394 document, "-x-evo-selection-end-marker");
17395 if (selection_marker)
17396 change_space_before_selection_to_nbsp (WEBKIT_DOM_NODE (selection_marker));
17399 if (from_list && to_list)
17400 format_change_list_to_list (editor_page, format, html_mode);
17402 if (!from_list && !to_list)
17403 format_change_block_to_block (editor_page, format, value);
17405 if (from_list && !to_list)
17406 format_change_list_to_block (editor_page, format, value);
17408 if (!from_list && to_list)
17409 format_change_block_to_list (editor_page, format);
17411 e_editor_dom_selection_restore (editor_page);
17413 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
17415 /* When changing the format we need to re-set the alignment */
17416 e_editor_dom_selection_set_alignment (editor_page, current_alignment);
17418 e_editor_page_emit_content_changed (editor_page);
17420 if (ev) {
17421 e_editor_dom_selection_get_coordinates (editor_page,
17422 &ev->after.start.x,
17423 &ev->after.start.y,
17424 &ev->after.end.x,
17425 &ev->after.end.y);
17426 e_editor_undo_redo_manager_insert_history_event (manager, ev);
17431 * e_html_editor_selection_get_background_color:
17432 * @selection: an #EEditorSelection
17434 * Returns background color of currently selected text or letter at current
17435 * cursor position.
17437 * Returns: A string with code of current background color.
17439 gchar *
17440 e_editor_dom_selection_get_background_color (EEditorPage *editor_page)
17442 WebKitDOMNode *ancestor;
17443 WebKitDOMRange *range = NULL;
17444 WebKitDOMCSSStyleDeclaration *css = NULL;
17445 gchar *value;
17447 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
17449 range = e_editor_dom_get_current_range (editor_page);
17450 ancestor = webkit_dom_range_get_common_ancestor_container (range, NULL);
17451 css = webkit_dom_element_get_style (WEBKIT_DOM_ELEMENT (ancestor));
17452 /* FIXME WK2
17453 g_free (selection->priv->background_color);
17454 selection->priv->background_color =
17455 webkit_dom_css_style_declaration_get_property_value (
17456 css, "background-color");*/
17458 value = webkit_dom_css_style_declaration_get_property_value (css, "background-color");
17460 g_clear_object (&css);
17461 g_clear_object (&range);
17463 return value;
17467 * e_html_editor_selection_set_background_color:
17468 * @selection: an #EEditorSelection
17469 * @color: code of new background color to set
17471 * Changes background color of current selection or letter at current cursor
17472 * position to @color.
17474 void
17475 e_editor_dom_selection_set_background_color (EEditorPage *editor_page,
17476 const gchar *color)
17478 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17480 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_BACKGROUND_COLOR, color);
17484 * e_html_editor_selection_get_alignment:
17485 * @selection: #an EEditorSelection
17487 * Returns alignment of current paragraph
17489 * Returns: #EContentEditorAlignment
17491 EContentEditorAlignment
17492 e_editor_dom_selection_get_alignment (EEditorPage *editor_page)
17494 WebKitDOMCSSStyleDeclaration *style = NULL;
17495 WebKitDOMElement *element;
17496 WebKitDOMNode *node;
17497 WebKitDOMRange *range = NULL;
17498 EContentEditorAlignment alignment;
17499 gchar *value;
17501 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), E_CONTENT_EDITOR_ALIGNMENT_LEFT);
17503 range = e_editor_dom_get_current_range (editor_page);
17504 if (!range) {
17505 alignment = E_CONTENT_EDITOR_ALIGNMENT_LEFT;
17506 goto out;
17509 node = webkit_dom_range_get_start_container (range, NULL);
17510 g_clear_object (&range);
17511 if (!node) {
17512 alignment = E_CONTENT_EDITOR_ALIGNMENT_LEFT;
17513 goto out;
17516 if (WEBKIT_DOM_IS_ELEMENT (node))
17517 element = WEBKIT_DOM_ELEMENT (node);
17518 else
17519 element = webkit_dom_node_get_parent_element (node);
17521 if (element_has_class (element, "-x-evo-align-right")) {
17522 alignment = E_CONTENT_EDITOR_ALIGNMENT_RIGHT;
17523 goto out;
17524 } else if (element_has_class (element, "-x-evo-align-center")) {
17525 alignment = E_CONTENT_EDITOR_ALIGNMENT_CENTER;
17526 goto out;
17529 style = webkit_dom_element_get_style (element);
17530 value = webkit_dom_css_style_declaration_get_property_value (style, "text-align");
17532 if (!value || !*value ||
17533 (g_ascii_strncasecmp (value, "left", 4) == 0)) {
17534 alignment = E_CONTENT_EDITOR_ALIGNMENT_LEFT;
17535 } else if (g_ascii_strncasecmp (value, "center", 6) == 0) {
17536 alignment = E_CONTENT_EDITOR_ALIGNMENT_CENTER;
17537 } else if (g_ascii_strncasecmp (value, "right", 5) == 0) {
17538 alignment = E_CONTENT_EDITOR_ALIGNMENT_RIGHT;
17539 } else {
17540 alignment = E_CONTENT_EDITOR_ALIGNMENT_LEFT;
17543 g_clear_object (&style);
17544 g_free (value);
17546 out:
17547 return alignment;
17550 static void
17551 set_block_alignment (WebKitDOMElement *element,
17552 const gchar *class)
17554 WebKitDOMElement *parent;
17556 element_remove_class (element, "-x-evo-align-center");
17557 element_remove_class (element, "-x-evo-align-right");
17558 element_add_class (element, class);
17559 parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (element));
17560 while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
17561 element_remove_class (parent, "-x-evo-align-center");
17562 element_remove_class (parent, "-x-evo-align-right");
17563 parent = webkit_dom_node_get_parent_element (
17564 WEBKIT_DOM_NODE (parent));
17569 * e_html_editor_selection_set_alignment:
17570 * @selection: an #EEditorSelection
17571 * @alignment: an #EContentEditorAlignment value to apply
17573 * Sets alignment of current paragraph to give @alignment.
17575 void
17576 e_editor_dom_selection_set_alignment (EEditorPage *editor_page,
17577 EContentEditorAlignment alignment)
17579 WebKitDOMDocument *document;
17580 WebKitDOMElement *selection_start_marker, *selection_end_marker;
17581 WebKitDOMNode *block;
17582 EContentEditorAlignment current_alignment;
17583 EEditorUndoRedoManager *manager;
17584 EEditorHistoryEvent *ev = NULL;
17585 gboolean after_selection_end = FALSE;
17586 const gchar *class = "";
17588 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17590 document = e_editor_page_get_document (editor_page);
17591 current_alignment = e_editor_page_get_alignment (editor_page);
17593 if (current_alignment == alignment)
17594 return;
17596 switch (alignment) {
17597 case E_CONTENT_EDITOR_ALIGNMENT_CENTER:
17598 class = "-x-evo-align-center";
17599 break;
17601 case E_CONTENT_EDITOR_ALIGNMENT_LEFT:
17602 break;
17604 case E_CONTENT_EDITOR_ALIGNMENT_RIGHT:
17605 class = "-x-evo-align-right";
17606 break;
17609 e_editor_dom_selection_save (editor_page);
17611 selection_start_marker = webkit_dom_document_get_element_by_id (
17612 document, "-x-evo-selection-start-marker");
17613 selection_end_marker = webkit_dom_document_get_element_by_id (
17614 document, "-x-evo-selection-end-marker");
17616 if (!selection_start_marker)
17617 return;
17619 manager = e_editor_page_get_undo_redo_manager (editor_page);
17620 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
17621 ev = g_new0 (EEditorHistoryEvent, 1);
17622 ev->type = HISTORY_ALIGNMENT;
17624 e_editor_dom_selection_get_coordinates (editor_page,
17625 &ev->before.start.x,
17626 &ev->before.start.y,
17627 &ev->before.end.x,
17628 &ev->before.end.y);
17629 ev->data.style.from = current_alignment;
17630 ev->data.style.to = alignment;
17633 block = e_editor_dom_get_parent_block_node_from_child (
17634 WEBKIT_DOM_NODE (selection_start_marker));
17636 while (block && !after_selection_end) {
17637 WebKitDOMNode *next_block;
17639 next_block = webkit_dom_node_get_next_sibling (block);
17641 after_selection_end = webkit_dom_node_contains (
17642 block, WEBKIT_DOM_NODE (selection_end_marker));
17644 if (element_has_class (WEBKIT_DOM_ELEMENT (block), "-x-evo-indented")) {
17645 gint ii;
17646 WebKitDOMNodeList *list = NULL;
17648 list = webkit_dom_element_query_selector_all (
17649 WEBKIT_DOM_ELEMENT (block),
17650 ".-x-evo-indented > *:not(.-x-evo-indented):not(li)",
17651 NULL);
17652 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
17653 WebKitDOMNode *item = webkit_dom_node_list_item (list, ii);
17655 set_block_alignment (WEBKIT_DOM_ELEMENT (item), class);
17657 after_selection_end = webkit_dom_node_contains (
17658 item, WEBKIT_DOM_NODE (selection_end_marker));
17659 if (after_selection_end)
17660 break;
17663 g_clear_object (&list);
17664 } else {
17665 set_block_alignment (WEBKIT_DOM_ELEMENT (block), class);
17668 block = next_block;
17671 if (ev) {
17672 e_editor_dom_selection_get_coordinates (editor_page,
17673 &ev->after.start.x,
17674 &ev->after.start.y,
17675 &ev->after.end.x,
17676 &ev->after.end.y);
17677 e_editor_undo_redo_manager_insert_history_event (manager, ev);
17680 e_editor_dom_selection_restore (editor_page);
17682 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
17683 e_editor_page_emit_content_changed (editor_page);
17686 void
17687 e_editor_dom_insert_replace_all_history_event (EEditorPage *editor_page,
17688 const gchar *search_text,
17689 const gchar *replacement)
17691 EEditorUndoRedoManager *manager;
17693 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17695 manager = e_editor_page_get_undo_redo_manager (editor_page);
17697 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
17698 EEditorHistoryEvent *ev = g_new0 (EEditorHistoryEvent, 1);
17699 ev->type = HISTORY_REPLACE_ALL;
17701 ev->data.string.from = g_strdup (search_text);
17702 ev->data.string.to = g_strdup (replacement);
17704 e_editor_undo_redo_manager_insert_history_event (manager, ev);
17709 * e_html_editor_selection_replace:
17710 * @selection: an #EEditorSelection
17711 * @replacement: a string to replace current selection with
17713 * Replaces currently selected text with @replacement.
17715 void
17716 e_editor_dom_selection_replace (EEditorPage *editor_page,
17717 const gchar *replacement)
17719 EEditorHistoryEvent *ev = NULL;
17720 EEditorUndoRedoManager *manager;
17721 WebKitDOMRange *range = NULL;
17723 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17725 manager = e_editor_page_get_undo_redo_manager (editor_page);
17727 if (!(range = e_editor_dom_get_current_range (editor_page)) ||
17728 e_editor_dom_selection_is_collapsed (editor_page))
17729 return;
17731 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
17732 ev = g_new0 (EEditorHistoryEvent, 1);
17733 ev->type = HISTORY_REPLACE;
17735 e_editor_dom_selection_get_coordinates (editor_page,
17736 &ev->before.start.x,
17737 &ev->before.start.y,
17738 &ev->before.end.x,
17739 &ev->before.end.y);
17741 ev->data.string.from = webkit_dom_range_get_text (range);
17742 ev->data.string.to = g_strdup (replacement);
17745 g_clear_object (&range);
17747 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_INSERT_TEXT, replacement);
17749 if (ev) {
17750 e_editor_dom_selection_get_coordinates (editor_page,
17751 &ev->after.start.x,
17752 &ev->after.start.y,
17753 &ev->after.end.x,
17754 &ev->after.end.y);
17756 e_editor_undo_redo_manager_insert_history_event (manager, ev);
17759 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
17761 e_editor_page_emit_content_changed (editor_page);
17765 * e_html_editor_selection_replace_caret_word:
17766 * @selection: an #EEditorSelection
17767 * @replacement: a string to replace current caret word with
17769 * Replaces current word under cursor with @replacement.
17771 void
17772 e_editor_dom_replace_caret_word (EEditorPage *editor_page,
17773 const gchar *replacement)
17775 WebKitDOMDocument *document;
17776 WebKitDOMDOMWindow *dom_window = NULL;
17777 WebKitDOMDOMSelection *dom_selection = NULL;
17778 WebKitDOMDocumentFragment *fragment;
17779 WebKitDOMNode *node;
17780 WebKitDOMRange *range = NULL;
17782 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17784 document = e_editor_page_get_document (editor_page);
17785 dom_window = webkit_dom_document_get_default_view (document);
17786 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
17787 g_clear_object (&dom_window);
17789 e_editor_page_emit_content_changed (editor_page);
17790 range = e_editor_dom_get_current_range (editor_page);
17791 webkit_dom_range_expand (range, "word", NULL);
17792 webkit_dom_dom_selection_add_range (dom_selection, range);
17794 fragment = webkit_dom_range_extract_contents (range, NULL);
17796 /* Get the text node to replace and leave other formatting nodes
17797 * untouched (font color, boldness, ...). */
17798 webkit_dom_node_normalize (WEBKIT_DOM_NODE (fragment));
17799 node = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment));
17800 if (!WEBKIT_DOM_IS_TEXT (node)) {
17801 while (node && WEBKIT_DOM_IS_ELEMENT (node))
17802 node = webkit_dom_node_get_first_child (node);
17805 if (node && WEBKIT_DOM_IS_TEXT (node)) {
17806 WebKitDOMText *text;
17808 /* Replace the word */
17809 text = webkit_dom_document_create_text_node (document, replacement);
17810 webkit_dom_node_replace_child (
17811 webkit_dom_node_get_parent_node (node),
17812 WEBKIT_DOM_NODE (text),
17813 node,
17814 NULL);
17816 /* Insert the word on current location. */
17817 webkit_dom_range_insert_node (range, WEBKIT_DOM_NODE (fragment), NULL);
17819 webkit_dom_dom_selection_collapse_to_end (dom_selection, NULL);
17822 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
17824 g_clear_object (&range);
17825 g_clear_object (&dom_selection);
17829 * e_html_editor_selection_get_caret_word:
17830 * @selection: an #EEditorSelection
17832 * Returns word under cursor.
17834 * Returns: A newly allocated string with current caret word or @NULL when there
17835 * is no text under cursor or when selection is active. [transfer-full].
17837 gchar *
17838 e_editor_dom_get_caret_word (EEditorPage *editor_page)
17840 gchar *word;
17841 WebKitDOMRange *range = NULL, *range_clone = NULL;
17843 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
17845 range = e_editor_dom_get_current_range (editor_page);
17847 /* Don't operate on the visible selection */
17848 range_clone = webkit_dom_range_clone_range (range, NULL);
17849 webkit_dom_range_expand (range_clone, "word", NULL);
17850 word = webkit_dom_range_to_string (range_clone, NULL);
17852 g_clear_object (&range);
17853 g_clear_object (&range_clone);
17855 return word;
17859 * e_html_editor_selection_get_list_alignment_from_node:
17860 * @node: #an WebKitDOMNode
17862 * Returns alignment of given list.
17864 * Returns: #EContentEditorAlignment
17866 EContentEditorAlignment
17867 e_editor_dom_get_list_alignment_from_node (WebKitDOMNode *node)
17869 if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-align-center"))
17870 return E_CONTENT_EDITOR_ALIGNMENT_CENTER;
17871 if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-align-right"))
17872 return E_CONTENT_EDITOR_ALIGNMENT_RIGHT;
17873 else
17874 return E_CONTENT_EDITOR_ALIGNMENT_LEFT;
17877 WebKitDOMElement *
17878 e_editor_dom_prepare_paragraph (EEditorPage *editor_page,
17879 gboolean with_selection)
17881 WebKitDOMDocument *document;
17882 WebKitDOMElement *element, *paragraph;
17884 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
17886 document = e_editor_page_get_document (editor_page);
17887 paragraph = e_editor_dom_get_paragraph_element (editor_page, -1, 0);
17889 if (with_selection)
17890 dom_add_selection_markers_into_element_start (
17891 document, paragraph, NULL, NULL);
17893 element = webkit_dom_document_create_element (document, "BR", NULL);
17895 webkit_dom_node_append_child (
17896 WEBKIT_DOM_NODE (paragraph), WEBKIT_DOM_NODE (element), NULL);
17898 return paragraph;
17901 void
17902 e_editor_dom_selection_set_on_point (EEditorPage *editor_page,
17903 guint x,
17904 guint y)
17906 WebKitDOMDocument *document;
17907 WebKitDOMRange *range = NULL;
17908 WebKitDOMDOMWindow *dom_window = NULL;
17909 WebKitDOMDOMSelection *dom_selection = NULL;
17911 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17913 document = e_editor_page_get_document (editor_page);
17914 dom_window = webkit_dom_document_get_default_view (document);
17915 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
17917 range = webkit_dom_document_caret_range_from_point (document, x, y);
17918 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
17919 webkit_dom_dom_selection_add_range (dom_selection, range);
17921 g_clear_object (&range);
17922 g_clear_object (&dom_selection);
17923 g_clear_object (&dom_window);
17926 void
17927 e_editor_dom_selection_get_coordinates (EEditorPage *editor_page,
17928 guint *start_x,
17929 guint *start_y,
17930 guint *end_x,
17931 guint *end_y)
17933 WebKitDOMDocument *document;
17934 WebKitDOMElement *element, *parent;
17935 gboolean created_selection_markers = FALSE;
17936 guint local_x = 0, local_y = 0;
17938 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17939 g_return_if_fail (start_x != NULL);
17940 g_return_if_fail (start_y != NULL);
17941 g_return_if_fail (end_x != NULL);
17942 g_return_if_fail (end_y != NULL);
17944 document = e_editor_page_get_document (editor_page);
17945 element = webkit_dom_document_get_element_by_id (
17946 document, "-x-evo-selection-start-marker");
17947 if (!element) {
17948 created_selection_markers = TRUE;
17949 e_editor_dom_selection_save (editor_page);
17950 element = webkit_dom_document_get_element_by_id (
17951 document, "-x-evo-selection-start-marker");
17952 if (!element)
17953 return;
17956 parent = element;
17957 while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
17958 local_x += (guint) webkit_dom_element_get_offset_left (parent);
17959 local_y += (guint) webkit_dom_element_get_offset_top (parent);
17960 parent = webkit_dom_element_get_offset_parent (parent);
17963 *start_x = local_x;
17964 *start_y = local_y;
17966 if (e_editor_dom_selection_is_collapsed (editor_page)) {
17967 *end_x = local_x;
17968 *end_y = local_y;
17970 if (created_selection_markers)
17971 e_editor_dom_selection_restore (editor_page);
17973 goto workaroud;
17976 element = webkit_dom_document_get_element_by_id (
17977 document, "-x-evo-selection-end-marker");
17979 local_x = 0;
17980 local_y = 0;
17982 parent = element;
17983 while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
17984 local_x += (guint) webkit_dom_element_get_offset_left (parent);
17985 local_y += (guint) webkit_dom_element_get_offset_top (parent);
17986 parent = webkit_dom_element_get_offset_parent (parent);
17989 *end_x = local_x;
17990 *end_y = local_y;
17992 if (created_selection_markers)
17993 e_editor_dom_selection_restore (editor_page);
17995 workaroud:
17996 /* Workaround for bug 749712 on the Evolution side. The cause of the bug
17997 * is that WebKit is having problems determining the right line height
17998 * for some fonts and font sizes (the right and wrong value differ by 1).
17999 * To fix this we will add an extra one to the final top offset. This is
18000 * safe to do even for fonts and font sizes that don't behave badly as we
18001 * will still get the right element as we use fonts bigger than 1 pixel. */
18002 *start_y += 1;
18003 *end_y += 1;
18006 WebKitDOMRange *
18007 e_editor_dom_get_range_for_point (WebKitDOMDocument *document,
18008 EEditorSelectionPoint point)
18010 glong scroll_left, scroll_top;
18011 WebKitDOMHTMLElement *body;
18012 WebKitDOMRange *range = NULL;
18014 body = webkit_dom_document_get_body (document);
18015 scroll_left = webkit_dom_element_get_scroll_left (WEBKIT_DOM_ELEMENT (body));
18016 scroll_top = webkit_dom_element_get_scroll_top (WEBKIT_DOM_ELEMENT (body));
18018 range = webkit_dom_document_caret_range_from_point (
18019 document, point.x - scroll_left, point.y - scroll_top);
18021 /* The point is outside the viewport, scroll to it. */
18022 if (!range) {
18023 WebKitDOMDOMWindow *dom_window = NULL;
18025 dom_window = webkit_dom_document_get_default_view (document);
18026 webkit_dom_dom_window_scroll_to (dom_window, point.x, point.y);
18028 scroll_left = webkit_dom_element_get_scroll_left (WEBKIT_DOM_ELEMENT (body));
18029 scroll_top = webkit_dom_element_get_scroll_top (WEBKIT_DOM_ELEMENT (body));
18030 range = webkit_dom_document_caret_range_from_point (
18031 document, point.x - scroll_left, point.y - scroll_top);
18032 g_clear_object (&dom_window);
18035 return range;
18038 void
18039 e_editor_dom_selection_restore_to_history_event_state (EEditorPage *editor_page,
18040 EEditorSelection selection_state)
18042 WebKitDOMDocument *document;
18043 WebKitDOMDOMWindow *dom_window = NULL;
18044 WebKitDOMDOMSelection *dom_selection = NULL;
18045 WebKitDOMElement *element, *tmp;
18046 WebKitDOMRange *range = NULL;
18047 gboolean was_collapsed = FALSE;
18049 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
18051 document = e_editor_page_get_document (editor_page);
18052 dom_window = webkit_dom_document_get_default_view (document);
18053 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
18054 g_clear_object (&dom_window);
18056 /* Restore the selection how it was before the event occured. */
18057 range = e_editor_dom_get_range_for_point (document, selection_state.start);
18058 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
18059 webkit_dom_dom_selection_add_range (dom_selection, range);
18060 g_clear_object (&range);
18062 was_collapsed = selection_state.start.x == selection_state.end.x;
18063 was_collapsed = was_collapsed && selection_state.start.y == selection_state.end.y;
18064 if (was_collapsed) {
18065 g_clear_object (&dom_selection);
18066 return;
18069 e_editor_dom_selection_save (editor_page);
18071 element = webkit_dom_document_get_element_by_id (
18072 document, "-x-evo-selection-end-marker");
18074 remove_node (WEBKIT_DOM_NODE (element));
18076 element = webkit_dom_document_get_element_by_id (
18077 document, "-x-evo-selection-start-marker");
18079 webkit_dom_element_remove_attribute (element, "id");
18081 range = e_editor_dom_get_range_for_point (document, selection_state.end);
18082 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
18083 webkit_dom_dom_selection_add_range (dom_selection, range);
18084 g_clear_object (&range);
18086 e_editor_dom_selection_save (editor_page);
18088 tmp = webkit_dom_document_get_element_by_id (
18089 document, "-x-evo-selection-start-marker");
18091 remove_node (WEBKIT_DOM_NODE (tmp));
18093 webkit_dom_element_set_id (
18094 element, "-x-evo-selection-start-marker");
18096 e_editor_dom_selection_restore (editor_page);
18098 g_clear_object (&dom_selection);