Crash when making links clickable in composer
[evolution.git] / src / modules / webkit-editor / web-extension / e-editor-dom-functions.c
blob343b1c1b5dbfb0567a880a12434032b7076b15c2
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 adapt_to_editor_dom_changes (WebKitDOMDocument *document)
8742 WebKitDOMHTMLCollection *collection = NULL;
8743 gint ii;
8745 /* Normal block code div.-x-evo-paragraph replaced by div[data-evo-paragraph] */
8746 collection = webkit_dom_document_get_elements_by_class_name_as_html_collection (document, "-x-evo-paragraph");
8747 for (ii = webkit_dom_html_collection_get_length (collection); ii--;) {
8748 WebKitDOMNode *node;
8750 node = webkit_dom_html_collection_item (collection, ii);
8751 element_remove_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-paragraph");
8752 webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "data-evo-paragraph", "", NULL);
8754 g_clear_object (&collection);
8757 void
8758 e_editor_dom_process_content_after_load (EEditorPage *editor_page)
8760 gboolean html_mode;
8761 gint16 start_at_bottom = -1, top_signature = -1;
8762 WebKitDOMDocument *document;
8763 WebKitDOMHTMLElement *body;
8764 WebKitDOMDOMWindow *dom_window = NULL;
8766 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
8768 document = e_editor_page_get_document (editor_page);
8770 /* Don't use CSS when possible to preserve compatibility with older
8771 * versions of Evolution or other MUAs */
8772 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_STYLE_WITH_CSS, "false");
8773 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_DEFAULT_PARAGRAPH_SEPARATOR, "div");
8775 body = webkit_dom_document_get_body (document);
8777 webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (body), "style");
8778 html_mode = e_editor_page_get_html_mode (editor_page);
8779 if (!html_mode)
8780 webkit_dom_element_set_attribute (
8781 WEBKIT_DOM_ELEMENT (body), "data-evo-plain-text", "", NULL);
8783 if (e_editor_page_get_convert_in_situ (editor_page, &start_at_bottom, &top_signature)) {
8784 e_editor_dom_convert_content (editor_page, NULL, start_at_bottom, top_signature);
8785 /* The BODY could be replaced during the conversion */
8786 body = webkit_dom_document_get_body (document);
8787 /* Make the quote marks non-selectable. */
8788 e_editor_dom_disable_quote_marks_select (editor_page);
8789 dom_set_links_active (document, FALSE);
8790 e_editor_page_set_convert_in_situ (editor_page, FALSE, -1, -1);
8792 /* The composer body could be empty in some case (loading an empty string
8793 * or empty HTML). In that case create the initial paragraph. */
8794 if (!webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body))) {
8795 WebKitDOMElement *paragraph;
8797 paragraph = e_editor_dom_prepare_paragraph (editor_page, TRUE);
8798 webkit_dom_element_set_id (paragraph, "-x-evo-input-start");
8799 webkit_dom_node_append_child (
8800 WEBKIT_DOM_NODE (body), WEBKIT_DOM_NODE (paragraph), NULL);
8801 e_editor_dom_selection_restore (editor_page);
8804 goto out;
8805 } else {
8806 WebKitDOMNodeList *list;
8807 gulong ii;
8809 list = webkit_dom_document_query_selector_all (document, "pre", NULL);
8810 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
8811 WebKitDOMNode *node = webkit_dom_node_list_item (list, ii), *parent;
8812 WebKitDOMElement *element;
8813 gchar *inner_html;
8815 element = WEBKIT_DOM_ELEMENT (node);
8816 parent = webkit_dom_node_get_parent_node (node);
8817 inner_html = webkit_dom_element_get_inner_html (element);
8819 if (inner_html && *inner_html) {
8820 gchar **strv;
8822 strv = g_strsplit (inner_html, "\n", -1);
8823 if (strv && strv[0] && strv[1]) {
8824 WebKitDOMElement *pre;
8825 gint jj;
8827 for (jj = 0; strv[jj]; jj++) {
8828 pre = webkit_dom_document_create_element (document, "pre", NULL);
8829 if (*(strv[jj])) {
8830 gint len = strlen (strv[jj]);
8832 if (strv[jj][len - 1] == '\r') {
8833 strv[jj][len - 1] = '\0';
8837 if (*(strv[jj])) {
8838 webkit_dom_html_element_set_inner_html (WEBKIT_DOM_HTML_ELEMENT (pre), strv[jj], NULL);
8839 } else {
8840 WebKitDOMElement *br;
8842 br = webkit_dom_document_create_element (document, "br", NULL);
8843 webkit_dom_node_append_child (WEBKIT_DOM_NODE (pre), WEBKIT_DOM_NODE (br), NULL);
8846 webkit_dom_node_insert_before (parent, WEBKIT_DOM_NODE (pre), node, NULL);
8849 remove_node (node);
8852 g_strfreev (strv);
8855 g_free (inner_html);
8858 g_clear_object (&list);
8861 adapt_to_editor_dom_changes (document);
8863 /* Make the quote marks non-selectable. */
8864 e_editor_dom_disable_quote_marks_select (editor_page);
8865 dom_set_links_active (document, FALSE);
8866 put_body_in_citation (document);
8867 move_elements_to_body (editor_page);
8868 repair_blockquotes (document);
8869 remove_thunderbird_signature (document);
8871 if (webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (body), "data-evo-draft")) {
8872 /* Restore the selection how it was when the draft was saved */
8873 e_editor_dom_move_caret_into_element (editor_page, WEBKIT_DOM_ELEMENT (body), FALSE);
8874 e_editor_dom_selection_restore (editor_page);
8875 e_editor_dom_remove_embedded_style_sheet (editor_page);
8878 /* The composer body could be empty in some case (loading an empty string
8879 * or empty HTML. In that case create the initial paragraph. */
8880 if (!webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body))) {
8881 WebKitDOMElement *paragraph;
8883 paragraph = e_editor_dom_prepare_paragraph (editor_page, TRUE);
8884 webkit_dom_element_set_id (paragraph, "-x-evo-input-start");
8885 webkit_dom_node_append_child (
8886 WEBKIT_DOM_NODE (body), WEBKIT_DOM_NODE (paragraph), NULL);
8887 e_editor_dom_selection_restore (editor_page);
8890 e_editor_dom_fix_file_uri_images (editor_page);
8891 change_cid_images_src_to_base64 (editor_page);
8893 out:
8894 /* Register on input event that is called when the content (body) is modified */
8895 e_editor_dom_register_input_event_listener_on_body (editor_page);
8896 register_html_events_handlers (editor_page, body);
8898 if (e_editor_page_get_inline_spelling_enabled (editor_page))
8899 e_editor_dom_force_spell_check_in_viewport (editor_page);
8900 else
8901 e_editor_dom_turn_spell_check_off (editor_page);
8903 e_editor_dom_scroll_to_caret (editor_page);
8905 dom_window = webkit_dom_document_get_default_view (document);
8907 webkit_dom_event_target_add_event_listener (
8908 WEBKIT_DOM_EVENT_TARGET (dom_window),
8909 "scroll",
8910 G_CALLBACK (body_scroll_event_cb),
8911 FALSE,
8912 editor_page);
8914 /* Intentionally leak the WebKitDOMDOMWindow object here as otherwise the
8915 * callback won't be set up. */
8918 static gchar *
8919 encode_to_base64_data (const gchar *src_uri,
8920 gchar **data_name)
8922 GFile *file;
8923 GFileInfo *info;
8924 gchar *filename, *data = NULL;
8926 g_return_val_if_fail (src_uri != NULL, NULL);
8928 file = g_file_new_for_uri (src_uri);
8929 if (!file)
8930 return NULL;
8932 filename = g_file_get_path (file);
8933 if (!filename) {
8934 g_object_unref (file);
8935 return NULL;
8938 info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
8939 G_FILE_QUERY_INFO_NONE, NULL, NULL);
8941 if (info) {
8942 gchar *mime_type, *content = NULL;
8943 gsize length = 0;
8945 mime_type = g_content_type_get_mime_type (g_file_info_get_content_type (info));
8947 if (mime_type && g_file_get_contents (filename, &content, &length, NULL)) {
8948 gchar *base64_encoded;
8950 if (data_name)
8951 *data_name = g_strdup (g_file_info_get_display_name (info));
8953 base64_encoded = g_base64_encode ((const guchar *) content, length);
8954 data = g_strconcat ("data:", mime_type, ";base64,", base64_encoded, NULL);
8955 g_free (base64_encoded);
8958 g_clear_object (&info);
8959 g_free (mime_type);
8960 g_free (content);
8963 g_clear_object (&file);
8964 g_free (filename);
8966 return data;
8969 GVariant *
8970 e_editor_dom_get_inline_images_data (EEditorPage *editor_page,
8971 const gchar *uid_domain)
8973 WebKitDOMDocument *document;
8974 WebKitDOMNodeList *list = NULL;
8975 GVariant *result = NULL;
8976 GVariantBuilder *builder = NULL;
8977 GHashTable *added = NULL;
8978 gint length, ii;
8980 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
8982 document = e_editor_page_get_document (editor_page);
8983 list = webkit_dom_document_query_selector_all (document, "img[src]", NULL);
8985 length = webkit_dom_node_list_get_length (list);
8986 if (length == 0) {
8987 g_clear_object (&list);
8988 goto background;
8991 builder = g_variant_builder_new (G_VARIANT_TYPE ("a(sss)"));
8993 added = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
8994 for (ii = length; ii--;) {
8995 const gchar *id;
8996 gchar *cid = NULL;
8997 WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
8998 gchar *src = webkit_dom_element_get_attribute (
8999 WEBKIT_DOM_ELEMENT (node), "src");
9001 if (!src)
9002 continue;
9004 if ((id = g_hash_table_lookup (added, src)) != NULL) {
9005 cid = g_strdup_printf ("cid:%s", id);
9006 } else if (g_ascii_strncasecmp (src, "data:", 5) == 0) {
9007 gchar *data_name = webkit_dom_element_get_attribute (
9008 WEBKIT_DOM_ELEMENT (node), "data-name");
9010 if (data_name) {
9011 gchar *new_id;
9013 new_id = camel_header_msgid_generate (uid_domain);
9014 g_variant_builder_add (
9015 builder, "(sss)", src, data_name, new_id);
9016 cid = g_strdup_printf ("cid:%s", new_id);
9018 g_hash_table_insert (added, g_strdup (src), new_id);
9020 webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "data-inline", "", NULL);
9022 g_free (data_name);
9023 } else if (g_ascii_strncasecmp (src, "file://", 7) == 0) {
9024 gchar *data, *data_name = NULL;
9026 data = encode_to_base64_data (src, &data_name);
9028 if (data && data_name) {
9029 gchar *new_id;
9031 new_id = camel_header_msgid_generate (uid_domain);
9032 g_variant_builder_add (builder, "(sss)", data, data_name, new_id);
9033 cid = g_strdup_printf ("cid:%s", new_id);
9035 g_hash_table_insert (added, data, new_id);
9037 webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "data-name", data_name, NULL);
9038 webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "data-inline", "", NULL);
9039 } else {
9040 g_free (data);
9043 g_free (data_name);
9046 if (cid) {
9047 webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "src", cid, NULL);
9048 g_free (cid);
9051 g_free (src);
9053 g_clear_object (&list);
9055 background:
9056 list = webkit_dom_document_query_selector_all (
9057 document, "[data-inline][background]", NULL);
9058 length = webkit_dom_node_list_get_length (list);
9059 if (length == 0)
9060 goto out;
9061 if (!builder)
9062 builder = g_variant_builder_new (G_VARIANT_TYPE ("a(sss)"));
9063 if (!added)
9064 added = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
9066 for (ii = length; ii--;) {
9067 const gchar *id;
9068 gchar *cid = NULL;
9069 WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
9070 gchar *src = webkit_dom_element_get_attribute (
9071 WEBKIT_DOM_ELEMENT (node), "background");
9073 if (!src)
9074 continue;
9076 if ((id = g_hash_table_lookup (added, src)) != NULL) {
9077 cid = g_strdup_printf ("cid:%s", id);
9078 webkit_dom_element_set_attribute (
9079 WEBKIT_DOM_ELEMENT (node), "background", cid, NULL);
9080 g_free (src);
9081 } else {
9082 gchar *data_name = webkit_dom_element_get_attribute (
9083 WEBKIT_DOM_ELEMENT (node), "data-name");
9085 if (data_name) {
9086 gchar *new_id;
9088 new_id = camel_header_msgid_generate (uid_domain);
9089 g_variant_builder_add (
9090 builder, "(sss)", src, data_name, new_id);
9091 cid = g_strdup_printf ("cid:%s", new_id);
9093 g_hash_table_insert (added, src, new_id);
9095 webkit_dom_element_set_attribute (
9096 WEBKIT_DOM_ELEMENT (node), "background", cid, NULL);
9098 g_free (data_name);
9100 g_free (cid);
9102 out:
9103 g_clear_object (&list);
9104 if (added)
9105 g_hash_table_destroy (added);
9107 if (builder) {
9108 result = g_variant_new ("a(sss)", builder);
9109 g_variant_builder_unref (builder);
9112 return result;
9115 static gboolean
9116 pasting_quoted_content (const gchar *content)
9118 /* Check if the content we are pasting is a quoted content from composer.
9119 * If it is, we can't use WebKit to paste it as it would leave the formatting
9120 * on the content. */
9121 return g_str_has_prefix (
9122 content,
9123 "<meta http-equiv=\"content-type\" content=\"text/html; "
9124 "charset=utf-8\"><blockquote type=\"cite\"") &&
9125 strstr (content, "\"-x-evo-");
9128 static void
9129 remove_apple_interchange_newline_elements (WebKitDOMDocument *document)
9131 gint ii;
9132 WebKitDOMHTMLCollection *collection = NULL;
9134 collection = webkit_dom_document_get_elements_by_class_name_as_html_collection (
9135 document, "Apple-interchange-newline");
9136 for (ii = webkit_dom_html_collection_get_length (collection); ii--;) {
9137 WebKitDOMNode *node = webkit_dom_html_collection_item (collection, ii);
9139 remove_node (node);
9141 g_clear_object (&collection);
9145 * e_editor_dom_insert_html:
9146 * @selection: an #EEditorSelection
9147 * @html_text: an HTML code to insert
9149 * Insert @html_text into document at current cursor position. When a text range
9150 * is selected, it will be replaced by @html_text.
9152 void
9153 e_editor_dom_insert_html (EEditorPage *editor_page,
9154 const gchar *html_text)
9156 EEditorHistoryEvent *ev = NULL;
9157 EEditorUndoRedoManager *manager;
9158 gboolean html_mode, undo_redo_in_progress;
9159 WebKitDOMDocument *document;
9160 WebKitDOMNode *block = NULL;
9162 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
9163 g_return_if_fail (html_text != NULL);
9165 document = e_editor_page_get_document (editor_page);
9167 manager = e_editor_page_get_undo_redo_manager (editor_page);
9168 undo_redo_in_progress = e_editor_undo_redo_manager_is_operation_in_progress (manager);
9169 if (!undo_redo_in_progress) {
9170 gboolean collapsed;
9172 ev = g_new0 (EEditorHistoryEvent, 1);
9173 ev->type = HISTORY_INSERT_HTML;
9175 collapsed = e_editor_dom_selection_is_collapsed (editor_page);
9176 e_editor_dom_selection_get_coordinates (editor_page,
9177 &ev->before.start.x,
9178 &ev->before.start.y,
9179 &ev->before.end.x,
9180 &ev->before.end.y);
9182 if (!collapsed) {
9183 ev->before.end.x = ev->before.start.x;
9184 ev->before.end.y = ev->before.start.y;
9187 ev->data.string.from = NULL;
9188 ev->data.string.to = g_strdup (html_text);
9191 html_mode = e_editor_page_get_html_mode (editor_page);
9192 if (html_mode ||
9193 (e_editor_page_is_pasting_content_from_itself (editor_page) &&
9194 !pasting_quoted_content (html_text))) {
9195 if (!e_editor_dom_selection_is_collapsed (editor_page)) {
9196 EEditorHistoryEvent *event;
9197 WebKitDOMDocumentFragment *fragment;
9198 WebKitDOMRange *range = NULL;
9200 event = g_new0 (EEditorHistoryEvent, 1);
9201 event->type = HISTORY_DELETE;
9203 range = e_editor_dom_get_current_range (editor_page);
9204 fragment = webkit_dom_range_clone_contents (range, NULL);
9205 g_clear_object (&range);
9206 event->data.fragment = g_object_ref (fragment);
9208 e_editor_dom_selection_get_coordinates (editor_page,
9209 &event->before.start.x,
9210 &event->before.start.y,
9211 &event->before.end.x,
9212 &event->before.end.y);
9214 event->after.start.x = event->before.start.x;
9215 event->after.start.y = event->before.start.y;
9216 event->after.end.x = event->before.start.x;
9217 event->after.end.y = event->before.start.y;
9219 e_editor_undo_redo_manager_insert_history_event (manager, event);
9221 event = g_new0 (EEditorHistoryEvent, 1);
9222 event->type = HISTORY_AND;
9224 e_editor_undo_redo_manager_insert_history_event (manager, event);
9225 } else {
9226 WebKitDOMElement *selection_marker;
9228 e_editor_dom_selection_save (editor_page);
9230 /* If current block contains just the BR element, remove
9231 * it otherwise WebKit will create a new block (with
9232 * text node that will contain '\n') on the end of inserted
9233 * content. Also remember the block and remove it if it's
9234 * empty after we insert the content. */
9235 selection_marker = webkit_dom_document_get_element_by_id (
9236 document, "-x-evo-selection-start-marker");
9238 if (!e_editor_page_is_pasting_content_from_itself (editor_page)) {
9239 if (!webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_marker))) {
9240 WebKitDOMNode *sibling;
9242 sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_marker));
9243 sibling = webkit_dom_node_get_next_sibling (sibling);
9244 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (sibling))
9245 remove_node (sibling);
9248 block = e_editor_dom_get_parent_block_node_from_child (WEBKIT_DOM_NODE (selection_marker));
9250 e_editor_dom_selection_restore (editor_page);
9253 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_INSERT_HTML, html_text);
9255 if (block)
9256 remove_node_if_empty (block);
9258 e_editor_dom_fix_file_uri_images (editor_page);
9260 if (strstr (html_text, "id=\"-x-evo-selection-start-marker\""))
9261 e_editor_dom_selection_restore (editor_page);
9263 e_editor_dom_check_magic_links (editor_page, FALSE);
9264 e_editor_dom_scroll_to_caret (editor_page);
9265 e_editor_dom_force_spell_check_in_viewport (editor_page);
9266 } else {
9267 /* Don't save history in the underlying function. */
9268 if (!undo_redo_in_progress)
9269 e_editor_undo_redo_manager_set_operation_in_progress (manager, TRUE);
9270 e_editor_dom_convert_and_insert_html_into_selection (editor_page, html_text, TRUE);
9271 if (!undo_redo_in_progress)
9272 e_editor_undo_redo_manager_set_operation_in_progress (manager, FALSE);
9275 remove_apple_interchange_newline_elements (document);
9277 if (ev) {
9278 e_editor_dom_selection_get_coordinates (editor_page,
9279 &ev->after.start.x,
9280 &ev->after.start.y,
9281 &ev->after.end.x,
9282 &ev->after.end.y);
9284 e_editor_undo_redo_manager_insert_history_event (manager, ev);
9288 static void
9289 save_history_for_delete_or_backspace (EEditorPage *editor_page,
9290 gboolean delete_key,
9291 gboolean control_key)
9293 WebKitDOMDocument *document;
9294 WebKitDOMDocumentFragment *fragment = NULL;
9295 WebKitDOMDOMWindow *dom_window = NULL;
9296 WebKitDOMDOMSelection *dom_selection = NULL;
9297 WebKitDOMRange *range = NULL;
9298 EEditorHistoryEvent *ev = NULL;
9299 EEditorUndoRedoManager *manager;
9301 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
9303 document = e_editor_page_get_document (editor_page);
9304 dom_window = webkit_dom_document_get_default_view (document);
9305 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
9306 g_clear_object (&dom_window);
9308 if (!webkit_dom_dom_selection_get_range_count (dom_selection)) {
9309 g_clear_object (&dom_selection);
9310 return;
9313 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
9315 /* Check if we can delete something */
9316 if (webkit_dom_range_get_collapsed (range, NULL)) {
9317 WebKitDOMRange *tmp_range = NULL;
9319 webkit_dom_dom_selection_modify (
9320 dom_selection, "move", delete_key ? "right" : "left", "character");
9322 tmp_range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
9323 if (webkit_dom_range_compare_boundary_points (tmp_range, WEBKIT_DOM_RANGE_END_TO_END, range, NULL) == 0) {
9324 g_clear_object (&dom_selection);
9325 g_clear_object (&range);
9326 g_clear_object (&tmp_range);
9328 return;
9331 webkit_dom_dom_selection_modify (
9332 dom_selection, "move", delete_key ? "left" : "right", "character");
9334 g_clear_object (&tmp_range);
9337 if (save_history_before_event_in_table (editor_page, range)) {
9338 g_clear_object (&range);
9339 g_clear_object (&dom_selection);
9340 return;
9343 ev = g_new0 (EEditorHistoryEvent, 1);
9344 ev->type = HISTORY_DELETE;
9346 e_editor_dom_selection_save (editor_page);
9348 e_editor_dom_selection_get_coordinates (editor_page, &ev->before.start.x, &ev->before.start.y, &ev->before.end.x, &ev->before.end.y);
9349 g_clear_object (&range);
9350 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
9352 if (webkit_dom_range_get_collapsed (range, NULL)) {
9353 gboolean removing_from_anchor = FALSE;
9354 WebKitDOMRange *range_clone = NULL;
9355 WebKitDOMNode *node;
9357 e_editor_page_block_selection_changed (editor_page);
9359 range_clone = webkit_dom_range_clone_range (range, NULL);
9360 if (control_key) {
9361 WebKitDOMRange *tmp_range = NULL;
9363 /* Control + Delete/Backspace deletes previous/next word. */
9364 webkit_dom_dom_selection_modify (
9365 dom_selection, "move", delete_key ? "right" : "left", "word");
9366 tmp_range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
9367 if (delete_key)
9368 webkit_dom_range_set_end (
9369 range_clone,
9370 webkit_dom_range_get_end_container (tmp_range, NULL),
9371 webkit_dom_range_get_end_offset (tmp_range, NULL),
9372 NULL);
9373 else
9374 webkit_dom_range_set_start (
9375 range_clone,
9376 webkit_dom_range_get_start_container (tmp_range, NULL),
9377 webkit_dom_range_get_start_offset (tmp_range, NULL),
9378 NULL);
9379 g_clear_object (&tmp_range);
9380 } else {
9381 typedef WebKitDOMNode * (*GetSibling)(WebKitDOMNode *node);
9382 WebKitDOMNode *container, *sibling;
9383 WebKitDOMElement *selection_marker;
9385 GetSibling get_sibling = delete_key ?
9386 webkit_dom_node_get_next_sibling :
9387 webkit_dom_node_get_previous_sibling;
9389 container = webkit_dom_range_get_end_container (range_clone, NULL);
9390 sibling = get_sibling (container);
9392 selection_marker = webkit_dom_document_get_element_by_id (
9393 document,
9394 delete_key ?
9395 "-x-evo-selection-end-marker" :
9396 "-x-evo-selection-start-marker");
9398 if (selection_marker) {
9399 WebKitDOMNode *tmp_sibling;
9401 tmp_sibling = get_sibling (WEBKIT_DOM_NODE (selection_marker));
9402 if (!tmp_sibling || (WEBKIT_DOM_IS_HTML_BR_ELEMENT (tmp_sibling) &&
9403 !element_has_class (WEBKIT_DOM_ELEMENT (tmp_sibling), "-x-evo-wrap-br")))
9404 sibling = WEBKIT_DOM_NODE (selection_marker);
9407 if (e_editor_dom_is_selection_position_node (sibling)) {
9408 if ((node = get_sibling (sibling)))
9409 node = get_sibling (node);
9410 if (node) {
9411 if (WEBKIT_DOM_IS_ELEMENT (node) &&
9412 webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (node), "data-hidden-space")) {
9413 fragment = webkit_dom_document_create_document_fragment (document);
9414 webkit_dom_node_append_child (
9415 WEBKIT_DOM_NODE (fragment),
9416 WEBKIT_DOM_NODE (
9417 webkit_dom_document_create_text_node (document, " ")),
9418 NULL);
9419 } else if (delete_key) {
9420 webkit_dom_range_set_start (
9421 range_clone, node, 0, NULL);
9422 webkit_dom_range_set_end (
9423 range_clone, node, 1, NULL);
9425 } else {
9426 WebKitDOMRange *tmp_range = NULL, *actual_range = NULL;
9428 actual_range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
9430 webkit_dom_dom_selection_modify (
9431 dom_selection, "move", delete_key ? "right" : "left", "character");
9433 tmp_range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
9434 if (webkit_dom_range_compare_boundary_points (tmp_range, WEBKIT_DOM_RANGE_END_TO_END, actual_range, NULL) != 0) {
9435 WebKitDOMNode *actual_block;
9436 WebKitDOMNode *tmp_block;
9438 actual_block = e_editor_dom_get_parent_block_node_from_child (container);
9440 tmp_block = delete_key ?
9441 webkit_dom_range_get_end_container (tmp_range, NULL) :
9442 webkit_dom_range_get_start_container (tmp_range, NULL);
9443 tmp_block = e_editor_dom_get_parent_block_node_from_child (tmp_block);
9445 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
9446 webkit_dom_dom_selection_add_range (dom_selection, actual_range);
9448 if (tmp_block) {
9449 if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (actual_block))
9450 actual_block = webkit_dom_node_get_parent_node (actual_block);
9452 fragment = webkit_dom_document_create_document_fragment (document);
9453 if (delete_key) {
9454 WebKitDOMNode *clone;
9456 clone = webkit_dom_node_clone_node_with_error (actual_block, TRUE, NULL);
9457 if (e_editor_dom_get_citation_level (actual_block) > 0)
9458 webkit_dom_element_set_attribute (
9459 WEBKIT_DOM_ELEMENT (clone),
9460 "data-evo-quoted",
9462 NULL);
9463 webkit_dom_node_append_child (
9464 WEBKIT_DOM_NODE (fragment), clone, NULL);
9466 clone = webkit_dom_node_clone_node_with_error (tmp_block, TRUE, NULL);
9467 if (e_editor_dom_get_citation_level (tmp_block) > 0)
9468 webkit_dom_element_set_attribute (
9469 WEBKIT_DOM_ELEMENT (clone),
9470 "data-evo-quoted",
9472 NULL);
9473 webkit_dom_node_append_child (
9474 WEBKIT_DOM_NODE (fragment), clone, NULL);
9475 } else {
9476 WebKitDOMNode *clone;
9478 clone = webkit_dom_node_clone_node_with_error (tmp_block, TRUE, NULL);
9479 if (e_editor_dom_get_citation_level (tmp_block) > 0)
9480 webkit_dom_element_set_attribute (
9481 WEBKIT_DOM_ELEMENT (clone),
9482 "data-evo-quoted",
9484 NULL);
9485 webkit_dom_node_append_child (
9486 WEBKIT_DOM_NODE (fragment), clone, NULL);
9488 clone = webkit_dom_node_clone_node_with_error (actual_block, TRUE, NULL);
9489 if (e_editor_dom_get_citation_level (tmp_block) > 0)
9490 webkit_dom_element_set_attribute (
9491 WEBKIT_DOM_ELEMENT (clone),
9492 "data-evo-quoted",
9494 NULL);
9495 webkit_dom_node_append_child (
9496 WEBKIT_DOM_NODE (fragment), clone, NULL);
9498 g_object_set_data (
9499 G_OBJECT (fragment),
9500 "history-concatenating-blocks",
9501 GINT_TO_POINTER (1));
9503 } else {
9504 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
9505 webkit_dom_dom_selection_add_range (dom_selection, actual_range);
9507 g_clear_object (&tmp_range);
9508 g_clear_object (&actual_range);
9510 } else {
9511 glong offset;
9513 /* FIXME This code is wrong for unicode smileys. */
9514 offset = webkit_dom_range_get_start_offset (range_clone, NULL);
9516 if (delete_key)
9517 webkit_dom_range_set_end (
9518 range_clone, container, offset + 1, NULL);
9519 else
9520 webkit_dom_range_set_start (
9521 range_clone, container, offset - 1, NULL);
9523 removing_from_anchor = WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (
9524 webkit_dom_node_get_parent_node (container));
9528 if (!fragment)
9529 fragment = webkit_dom_range_clone_contents (range_clone, NULL);
9530 if (removing_from_anchor)
9531 g_object_set_data (
9532 G_OBJECT (fragment),
9533 "history-removing-from-anchor",
9534 GINT_TO_POINTER (1));
9535 node = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment));
9536 if (!node) {
9537 g_free (ev);
9538 e_editor_page_unblock_selection_changed (editor_page);
9539 g_clear_object (&range);
9540 g_clear_object (&range_clone);
9541 g_clear_object (&dom_selection);
9542 g_warning ("History event was not saved for %s key", delete_key ? "Delete" : "Backspace");
9543 e_editor_dom_selection_restore (editor_page);
9544 return;
9547 if (control_key) {
9548 if (delete_key) {
9549 ev->after.start.x = ev->before.start.x;
9550 ev->after.start.y = ev->before.start.y;
9551 ev->after.end.x = ev->before.end.x;
9552 ev->after.end.y = ev->before.end.y;
9554 webkit_dom_range_collapse (range_clone, TRUE, NULL);
9555 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
9556 webkit_dom_dom_selection_add_range (dom_selection, range_clone);
9557 } else {
9558 gboolean selection_saved = FALSE;
9559 WebKitDOMRange *tmp_range = NULL;
9561 if (webkit_dom_document_get_element_by_id (document, "-x-evo-selection-start-marker"))
9562 selection_saved = TRUE;
9564 if (selection_saved)
9565 e_editor_dom_selection_restore (editor_page);
9567 tmp_range = webkit_dom_range_clone_range (range_clone, NULL);
9568 /* Prepare the selection to the right position after
9569 * delete and save it. */
9570 webkit_dom_range_collapse (range_clone, TRUE, NULL);
9571 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
9572 webkit_dom_dom_selection_add_range (dom_selection, range_clone);
9573 e_editor_dom_selection_get_coordinates (editor_page, &ev->after.start.x, &ev->after.start.y, &ev->after.end.x, &ev->after.end.y);
9574 /* Restore the selection where it was before the
9575 * history event was saved. */
9576 webkit_dom_range_collapse (tmp_range, FALSE, NULL);
9577 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
9578 webkit_dom_dom_selection_add_range (dom_selection, tmp_range);
9579 g_clear_object (&tmp_range);
9581 if (selection_saved)
9582 e_editor_dom_selection_save (editor_page);
9584 } else {
9585 gboolean selection_saved = FALSE;
9587 if (webkit_dom_document_get_element_by_id (document, "-x-evo-selection-start-marker"))
9588 selection_saved = TRUE;
9590 if (selection_saved)
9591 e_editor_dom_selection_restore (editor_page);
9593 if (delete_key) {
9594 e_editor_dom_selection_get_coordinates (editor_page, &ev->after.start.x, &ev->after.start.y, &ev->after.end.x, &ev->after.end.y);
9595 } else {
9596 webkit_dom_dom_selection_modify (dom_selection, "move", "left", "character");
9597 e_editor_dom_selection_get_coordinates (editor_page, &ev->after.start.x, &ev->after.start.y, &ev->after.end.x, &ev->after.end.y);
9598 webkit_dom_dom_selection_modify (dom_selection, "move", "right", "character");
9600 ev->after.end.x = ev->after.start.x;
9601 ev->after.end.y = ev->after.start.y;
9604 if (selection_saved)
9605 e_editor_dom_selection_save (editor_page);
9608 g_clear_object (&range_clone);
9610 if (delete_key) {
9611 if (!WEBKIT_DOM_IS_ELEMENT (node)) {
9612 webkit_dom_node_insert_before (
9613 WEBKIT_DOM_NODE (fragment),
9614 WEBKIT_DOM_NODE (
9615 dom_create_selection_marker (document, FALSE)),
9616 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment)),
9617 NULL);
9618 webkit_dom_node_insert_before (
9619 WEBKIT_DOM_NODE (fragment),
9620 WEBKIT_DOM_NODE (
9621 dom_create_selection_marker (document, TRUE)),
9622 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment)),
9623 NULL);
9625 } else {
9626 if (!WEBKIT_DOM_IS_ELEMENT (node)) {
9627 webkit_dom_node_append_child (
9628 WEBKIT_DOM_NODE (fragment),
9629 WEBKIT_DOM_NODE (
9630 dom_create_selection_marker (document, TRUE)),
9631 NULL);
9632 webkit_dom_node_append_child (
9633 WEBKIT_DOM_NODE (fragment),
9634 WEBKIT_DOM_NODE (
9635 dom_create_selection_marker (document, FALSE)),
9636 NULL);
9640 e_editor_page_unblock_selection_changed (editor_page);
9641 } else {
9642 WebKitDOMElement *tmp_element;
9643 WebKitDOMNode *sibling;
9645 ev->after.start.x = ev->before.start.x;
9646 ev->after.start.y = ev->before.start.y;
9647 ev->after.end.x = ev->before.start.x;
9648 ev->after.end.y = ev->before.start.y;
9650 fragment = webkit_dom_range_clone_contents (range, NULL);
9652 tmp_element = webkit_dom_document_fragment_query_selector (
9653 fragment, "#-x-evo-selection-start-marker", NULL);
9654 if (tmp_element)
9655 remove_node (WEBKIT_DOM_NODE (tmp_element));
9657 tmp_element = webkit_dom_document_fragment_query_selector (
9658 fragment, "#-x-evo-selection-end-marker", NULL);
9659 if (tmp_element)
9660 remove_node (WEBKIT_DOM_NODE (tmp_element));
9662 remove_empty_blocks (document);
9664 /* Selection starts in the beginning of blockquote. */
9665 tmp_element = webkit_dom_document_get_element_by_id (
9666 document, "-x-evo-selection-start-marker");
9667 sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (tmp_element));
9668 if (sibling && WEBKIT_DOM_IS_ELEMENT (sibling) &&
9669 element_has_class (WEBKIT_DOM_ELEMENT (sibling), "-x-evo-quoted")) {
9670 WebKitDOMNode *child;
9672 tmp_element = webkit_dom_document_get_element_by_id (
9673 document, "-x-evo-selection-end-marker");
9675 /* If there is no text after the selection end it means that
9676 * the block will be replaced with block that is body's descendant
9677 * and not the blockquote's one. Also if the selection started
9678 * in the beginning of blockquote we have to insert the quote
9679 * characters into the deleted content to correctly restore
9680 * them during undo/redo operations. */
9681 if (!(tmp_element && webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (tmp_element)))) {
9682 child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment));
9683 while (child && WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (child))
9684 child = webkit_dom_node_get_first_child (child);
9686 child = webkit_dom_node_get_first_child (child);
9687 if (child && (WEBKIT_DOM_IS_TEXT (child) ||
9688 (WEBKIT_DOM_IS_ELEMENT (child) &&
9689 !element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-quoted")))) {
9690 webkit_dom_node_insert_before (
9691 webkit_dom_node_get_parent_node (child),
9692 webkit_dom_node_clone_node_with_error (sibling, TRUE, NULL),
9693 child,
9694 NULL);
9699 /* When we were cloning the range above and the range contained
9700 * quoted content there will still be blockquote missing in the
9701 * final range. Let's modify the fragment and add it there. */
9702 tmp_element = webkit_dom_document_get_element_by_id (
9703 document, "-x-evo-selection-end-marker");
9704 if (tmp_element) {
9705 WebKitDOMNode *node;
9707 node = WEBKIT_DOM_NODE (tmp_element);
9708 while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (webkit_dom_node_get_parent_node (node)))
9709 node = webkit_dom_node_get_parent_node (node);
9711 if (node && WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node)) {
9712 WebKitDOMNode *last_child;
9714 last_child = webkit_dom_node_get_last_child (WEBKIT_DOM_NODE (fragment));
9716 if (last_child && !WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (last_child)) {
9717 WebKitDOMDocumentFragment *tmp_fragment;
9718 WebKitDOMNode *clone;
9720 tmp_fragment = webkit_dom_document_create_document_fragment (document);
9721 clone = webkit_dom_node_clone_node_with_error (node, FALSE, NULL);
9722 clone = webkit_dom_node_append_child (
9723 WEBKIT_DOM_NODE (tmp_fragment), clone, NULL);
9724 webkit_dom_node_append_child (clone, WEBKIT_DOM_NODE (fragment), NULL);
9725 fragment = tmp_fragment;
9730 /* FIXME Ugly hack */
9731 /* If the deleted selection contained the signature (or at least its
9732 * part) replace it with the unchanged signature to correctly perform
9733 * undo operation. */
9734 tmp_element = webkit_dom_document_fragment_query_selector (fragment, ".-x-evo-signature-wrapper", NULL);
9735 if (tmp_element) {
9736 WebKitDOMElement *signature;
9738 signature = webkit_dom_document_query_selector (document, ".-x-evo-signature-wrapper", NULL);
9739 if (signature) {
9740 webkit_dom_node_replace_child (
9741 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (tmp_element)),
9742 webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (signature), TRUE, NULL),
9743 WEBKIT_DOM_NODE (tmp_element),
9744 NULL);
9749 g_clear_object (&range);
9750 g_clear_object (&dom_selection);
9752 g_object_set_data (G_OBJECT (fragment), "history-delete-key", GINT_TO_POINTER (delete_key));
9753 g_object_set_data (G_OBJECT (fragment), "history-control-key", GINT_TO_POINTER (control_key));
9755 ev->data.fragment = g_object_ref (fragment);
9757 manager = e_editor_page_get_undo_redo_manager (editor_page);
9758 e_editor_undo_redo_manager_insert_history_event (manager, ev);
9760 e_editor_dom_selection_restore (editor_page);
9763 gboolean
9764 e_editor_dom_fix_structure_after_delete_before_quoted_content (EEditorPage *editor_page,
9765 glong key_code,
9766 gboolean control_key,
9767 gboolean delete_key)
9769 WebKitDOMDocument *document;
9770 WebKitDOMElement *selection_start_marker, *selection_end_marker;
9771 WebKitDOMNode *block, *node;
9772 gboolean collapsed = FALSE;
9774 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
9776 document = e_editor_page_get_document (editor_page);
9777 collapsed = e_editor_dom_selection_is_collapsed (editor_page);
9779 e_editor_dom_selection_save (editor_page);
9781 selection_start_marker = webkit_dom_document_get_element_by_id (
9782 document, "-x-evo-selection-start-marker");
9783 selection_end_marker = webkit_dom_document_get_element_by_id (
9784 document, "-x-evo-selection-end-marker");
9786 if (!selection_start_marker || !selection_end_marker)
9787 return FALSE;
9789 if (collapsed) {
9790 WebKitDOMNode *next_block;
9792 block = e_editor_dom_get_parent_block_node_from_child (
9793 WEBKIT_DOM_NODE (selection_start_marker));
9795 next_block = webkit_dom_node_get_next_sibling (block);
9797 /* Next block is quoted content */
9798 if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (next_block)) {
9799 e_editor_dom_selection_restore (editor_page);
9800 return FALSE;
9803 /* Delete was pressed in block without any content */
9804 if (webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_start_marker))) {
9805 e_editor_dom_selection_restore (editor_page);
9806 return FALSE;
9809 /* If there is just BR element go ahead */
9810 node = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_end_marker));
9811 if (node && !WEBKIT_DOM_IS_HTML_BR_ELEMENT (node)) {
9812 e_editor_dom_selection_restore (editor_page);
9813 return FALSE;
9814 } else {
9815 if (key_code != ~0) {
9816 e_editor_dom_selection_restore (editor_page);
9817 save_history_for_delete_or_backspace (
9818 editor_page, key_code == HTML_KEY_CODE_DELETE, control_key);
9819 } else
9820 e_editor_dom_selection_restore (editor_page);
9822 /* Remove the empty block and move caret to the right place. */
9823 remove_node (block);
9825 if (delete_key) {
9826 /* To the beginning of the next block. */
9827 while (next_block && WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (next_block))
9828 next_block = webkit_dom_node_get_first_child (next_block);
9830 if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-quoted"))
9831 next_block = webkit_dom_node_get_next_sibling (next_block);
9833 e_editor_dom_move_caret_into_element (editor_page, WEBKIT_DOM_ELEMENT (next_block), TRUE);
9834 } else {
9835 WebKitDOMNode *prev_block;
9837 /* On the end of previous block. */
9838 prev_block = webkit_dom_node_get_previous_sibling (next_block);
9839 while (prev_block && WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (prev_block))
9840 prev_block = webkit_dom_node_get_last_child (prev_block);
9842 if (prev_block)
9843 e_editor_dom_move_caret_into_element (editor_page, WEBKIT_DOM_ELEMENT (prev_block), FALSE);
9846 return TRUE;
9850 e_editor_dom_selection_restore (editor_page);
9852 return FALSE;
9855 static gboolean
9856 split_citation (EEditorPage *editor_page)
9858 WebKitDOMDocument *document;
9859 WebKitDOMElement *element;
9860 EEditorHistoryEvent *ev = NULL;
9861 EEditorUndoRedoManager *manager;
9863 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
9865 document = e_editor_page_get_document (editor_page);
9866 manager = e_editor_page_get_undo_redo_manager (editor_page);
9868 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
9869 WebKitDOMElement *selection_end;
9870 WebKitDOMNode *sibling;
9872 ev = g_new0 (EEditorHistoryEvent, 1);
9873 ev->type = HISTORY_CITATION_SPLIT;
9875 e_editor_dom_selection_save (editor_page);
9877 e_editor_dom_selection_get_coordinates (editor_page, &ev->before.start.x, &ev->before.start.y, &ev->before.end.x, &ev->before.end.y);
9879 if (!e_editor_dom_selection_is_collapsed (editor_page)) {
9880 WebKitDOMRange *range = NULL;
9882 range = e_editor_dom_get_current_range (editor_page);
9883 insert_delete_event (editor_page, range);
9885 g_clear_object (&range);
9887 ev->before.end.x = ev->before.start.x;
9888 ev->before.end.y = ev->before.start.y;
9891 selection_end = webkit_dom_document_get_element_by_id (
9892 document, "-x-evo-selection-end-marker");
9894 sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_end));
9895 if (!sibling || (WEBKIT_DOM_IS_HTML_BR_ELEMENT (sibling) &&
9896 !element_has_class (WEBKIT_DOM_ELEMENT (sibling), "-x-evo-wrap-br"))) {
9897 WebKitDOMDocumentFragment *fragment;
9899 fragment = webkit_dom_document_create_document_fragment (document);
9900 ev->data.fragment = g_object_ref (fragment);
9901 } else
9902 ev->data.fragment = NULL;
9904 e_editor_dom_selection_restore (editor_page);
9907 element = e_editor_dom_insert_new_line_into_citation (editor_page, "");
9909 if (ev) {
9910 e_editor_dom_selection_get_coordinates (editor_page, &ev->after.start.x, &ev->after.start.y, &ev->after.end.x, &ev->after.end.y);
9912 e_editor_undo_redo_manager_insert_history_event (manager, ev);
9915 return element != NULL;
9918 static gboolean
9919 delete_last_character_from_previous_line_in_quoted_block (EEditorPage *editor_page,
9920 glong key_code,
9921 guint state)
9923 WebKitDOMDocument *document;
9924 WebKitDOMDocumentFragment *fragment = NULL;
9925 WebKitDOMElement *element;
9926 WebKitDOMNode *node, *beginning, *prev_sibling;
9927 EEditorHistoryEvent *ev = NULL;
9928 gboolean hidden_space = FALSE;
9930 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
9932 /* We have to be in quoted content. */
9933 if (!e_editor_dom_selection_is_citation (editor_page))
9934 return FALSE;
9936 /* Selection is just caret. */
9937 if (!e_editor_dom_selection_is_collapsed (editor_page))
9938 return FALSE;
9940 document = e_editor_page_get_document (editor_page);
9942 e_editor_dom_selection_save (editor_page);
9944 element = webkit_dom_document_get_element_by_id (
9945 document, "-x-evo-selection-start-marker");
9947 /* Before the caret are just quote characters */
9948 beginning = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element));
9949 if (!(beginning && WEBKIT_DOM_IS_ELEMENT (beginning))) {
9950 WebKitDOMNode *parent;
9952 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
9953 if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent))
9954 beginning = webkit_dom_node_get_previous_sibling (parent);
9955 else
9956 goto out;
9959 /* Before the text is the beginning of line. */
9960 if (!(element_has_class (WEBKIT_DOM_ELEMENT (beginning), "-x-evo-quoted")))
9961 goto out;
9963 /* If we are just on the beginning of the line and not on the beginning of
9964 * the block we need to remove the last character ourselves as well, otherwise
9965 * WebKit will put the caret to wrong position. */
9966 if (!(prev_sibling = webkit_dom_node_get_previous_sibling (beginning)))
9967 goto out;
9969 if (key_code != ~0) {
9970 ev = g_new0 (EEditorHistoryEvent, 1);
9971 ev->type = HISTORY_DELETE;
9973 e_editor_dom_selection_get_coordinates (editor_page,
9974 &ev->before.start.x,
9975 &ev->before.start.y,
9976 &ev->before.end.x,
9977 &ev->before.end.y);
9979 fragment = webkit_dom_document_create_document_fragment (document);
9982 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (prev_sibling)) {
9983 if (key_code != ~0)
9984 webkit_dom_node_append_child (WEBKIT_DOM_NODE (fragment), prev_sibling, NULL);
9985 else
9986 remove_node (prev_sibling);
9989 prev_sibling = webkit_dom_node_get_previous_sibling (beginning);
9990 if (WEBKIT_DOM_IS_ELEMENT (prev_sibling) &&
9991 webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (prev_sibling), "data-hidden-space")) {
9992 hidden_space = TRUE;
9993 if (key_code != ~0)
9994 webkit_dom_node_insert_before (
9995 WEBKIT_DOM_NODE (fragment),
9996 prev_sibling,
9997 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment)),
9998 NULL);
9999 else
10000 remove_node (prev_sibling);
10003 node = webkit_dom_node_get_previous_sibling (beginning);
10005 if (key_code != ~0)
10006 webkit_dom_node_append_child (WEBKIT_DOM_NODE (fragment), beginning, NULL);
10007 else
10008 remove_node (beginning);
10010 if (!hidden_space) {
10011 if (key_code != ~0) {
10012 gchar *data;
10014 data = webkit_dom_character_data_substring_data (
10015 WEBKIT_DOM_CHARACTER_DATA (node),
10016 webkit_dom_character_data_get_length (
10017 WEBKIT_DOM_CHARACTER_DATA (node)) -1,
10019 NULL);
10021 webkit_dom_node_append_child (
10022 WEBKIT_DOM_NODE (fragment),
10023 WEBKIT_DOM_NODE (
10024 webkit_dom_document_create_text_node (document, data)),
10025 NULL);
10027 g_free (data);
10030 webkit_dom_character_data_delete_data (
10031 WEBKIT_DOM_CHARACTER_DATA (node),
10032 webkit_dom_character_data_get_length (
10033 WEBKIT_DOM_CHARACTER_DATA (node)) -1,
10035 NULL);
10038 if (key_code != ~0) {
10039 EEditorUndoRedoManager *manager;
10041 e_editor_dom_selection_get_coordinates (editor_page,
10042 &ev->after.start.x,
10043 &ev->after.start.y,
10044 &ev->after.end.x,
10045 &ev->after.end.y);
10047 ev->data.fragment = g_object_ref (fragment);
10049 manager = e_editor_page_get_undo_redo_manager (editor_page);
10050 e_editor_undo_redo_manager_insert_history_event (manager, ev);
10053 e_editor_dom_selection_restore (editor_page);
10055 return TRUE;
10056 out:
10057 e_editor_dom_selection_restore (editor_page);
10059 return FALSE;
10062 gboolean
10063 e_editor_dom_delete_last_character_on_line_in_quoted_block (EEditorPage *editor_page,
10064 glong key_code,
10065 gboolean control_key)
10067 WebKitDOMDocument *document;
10068 WebKitDOMElement *element;
10069 WebKitDOMNode *node, *beginning, *next_sibling;
10070 gboolean success = FALSE;
10072 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
10074 document = e_editor_page_get_document (editor_page);
10076 /* We have to be in quoted content. */
10077 if (!e_editor_dom_selection_is_citation (editor_page))
10078 return FALSE;
10080 /* Selection is just caret. */
10081 if (!e_editor_dom_selection_is_collapsed (editor_page))
10082 return FALSE;
10084 e_editor_dom_selection_save (editor_page);
10086 element = webkit_dom_document_get_element_by_id (
10087 document, "-x-evo-selection-start-marker");
10089 /* selection end marker */
10090 node = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element));
10092 /* We have to be on the end of line. */
10093 next_sibling = webkit_dom_node_get_next_sibling (node);
10094 if (next_sibling &&
10095 (!WEBKIT_DOM_IS_HTML_BR_ELEMENT (next_sibling) ||
10096 webkit_dom_node_get_next_sibling (next_sibling)))
10097 goto out;
10099 /* Before the caret is just text. */
10100 node = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element));
10101 if (!(node && WEBKIT_DOM_IS_TEXT (node)))
10102 goto out;
10104 /* There is just one character. */
10105 if (webkit_dom_character_data_get_length (WEBKIT_DOM_CHARACTER_DATA (node)) != 1)
10106 goto out;
10108 beginning = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (node));
10109 if (!(beginning && WEBKIT_DOM_IS_ELEMENT (beginning)))
10110 goto out;
10112 /* Before the text is the beginning of line. */
10113 if (!(element_has_class (WEBKIT_DOM_ELEMENT (beginning), "-x-evo-quoted")))
10114 goto out;
10116 if (!webkit_dom_node_get_previous_sibling (beginning))
10117 goto out;
10119 if (key_code != ~0) {
10120 e_editor_dom_selection_restore (editor_page);
10121 save_history_for_delete_or_backspace (
10122 editor_page, key_code == HTML_KEY_CODE_DELETE, control_key);
10123 e_editor_dom_selection_save (editor_page);
10126 element = webkit_dom_node_get_parent_element (beginning);
10127 remove_node (WEBKIT_DOM_NODE (element));
10129 success = TRUE;
10130 out:
10131 e_editor_dom_selection_restore (editor_page);
10133 if (success)
10134 e_editor_dom_insert_new_line_into_citation (editor_page, NULL);
10136 return success;
10139 static gboolean
10140 selection_is_in_empty_list_item (WebKitDOMNode *selection_start_marker)
10142 gchar *text;
10143 WebKitDOMNode *sibling;
10145 /* Selection needs to be collapsed. */
10146 sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_start_marker));
10147 if (!e_editor_dom_is_selection_position_node (sibling))
10148 return FALSE;
10150 /* After the selection end there could be just the BR element. */
10151 sibling = webkit_dom_node_get_next_sibling (sibling);
10152 if (sibling && !WEBKIT_DOM_IS_HTML_BR_ELEMENT (sibling))
10153 return FALSE;
10155 if (sibling && webkit_dom_node_get_next_sibling (sibling))
10156 return FALSE;
10158 sibling = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_start_marker));
10160 if (!sibling)
10161 return TRUE;
10163 /* Only text node with the zero width space character is allowed. */
10164 if (!WEBKIT_DOM_IS_TEXT (sibling))
10165 return FALSE;
10167 if (webkit_dom_node_get_previous_sibling (sibling))
10168 return FALSE;
10170 if (webkit_dom_character_data_get_length (WEBKIT_DOM_CHARACTER_DATA (sibling)) != 1)
10171 return FALSE;
10173 text = webkit_dom_character_data_get_data (WEBKIT_DOM_CHARACTER_DATA (sibling));
10174 if (!(text && g_strcmp0 (text, UNICODE_ZERO_WIDTH_SPACE) == 0)) {
10175 g_free (text);
10176 return FALSE;
10179 g_free (text);
10181 return TRUE;
10184 static gboolean
10185 return_pressed_in_image_wrapper (EEditorPage *editor_page)
10187 WebKitDOMDocument *document;
10188 WebKitDOMDocumentFragment *fragment;
10189 WebKitDOMElement *selection_start_marker;
10190 WebKitDOMNode *parent, *block, *clone;
10191 EEditorHistoryEvent *ev = NULL;
10192 EEditorUndoRedoManager *manager;
10194 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
10196 document = e_editor_page_get_document (editor_page);
10198 if (!e_editor_dom_selection_is_collapsed (editor_page))
10199 return FALSE;
10201 e_editor_dom_selection_save (editor_page);
10203 selection_start_marker = webkit_dom_document_get_element_by_id (
10204 document, "-x-evo-selection-start-marker");
10206 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start_marker));
10207 if (!element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-resizable-wrapper")) {
10208 e_editor_dom_selection_restore (editor_page);
10209 return FALSE;
10212 manager = e_editor_page_get_undo_redo_manager (editor_page);
10214 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
10215 ev = g_new0 (EEditorHistoryEvent, 1);
10216 ev->type = HISTORY_INPUT;
10218 e_editor_dom_selection_get_coordinates (editor_page,
10219 &ev->before.start.x,
10220 &ev->before.start.y,
10221 &ev->before.end.x,
10222 &ev->before.end.y);
10224 fragment = webkit_dom_document_create_document_fragment (document);
10226 g_object_set_data (
10227 G_OBJECT (fragment), "history-return-key", GINT_TO_POINTER (1));
10230 block = e_editor_dom_get_parent_block_node_from_child (
10231 WEBKIT_DOM_NODE (selection_start_marker));
10233 clone = webkit_dom_node_clone_node_with_error (block, FALSE, NULL);
10234 webkit_dom_node_append_child (
10235 clone, WEBKIT_DOM_NODE (webkit_dom_document_create_element (document, "br", NULL)), NULL);
10237 webkit_dom_node_insert_before (
10238 webkit_dom_node_get_parent_node (block),
10239 clone,
10240 block,
10241 NULL);
10243 if (ev) {
10244 webkit_dom_node_append_child (
10245 WEBKIT_DOM_NODE (fragment),
10246 webkit_dom_node_clone_node_with_error (clone, TRUE, NULL),
10247 NULL);
10249 e_editor_dom_selection_get_coordinates (editor_page,
10250 &ev->after.start.x,
10251 &ev->after.start.y,
10252 &ev->after.end.x,
10253 &ev->after.end.y);
10255 ev->data.fragment = g_object_ref (fragment);
10257 e_editor_undo_redo_manager_insert_history_event (manager, ev);
10260 e_editor_page_emit_content_changed (editor_page);
10262 e_editor_dom_selection_restore (editor_page);
10264 return TRUE;
10267 static gboolean
10268 return_pressed_after_h_rule (EEditorPage *editor_page)
10270 WebKitDOMDocument *document;
10271 WebKitDOMDocumentFragment *fragment;
10272 WebKitDOMElement *selection_marker;
10273 WebKitDOMNode *node, *block, *clone, *hr, *insert_before = NULL;
10274 EEditorHistoryEvent *ev = NULL;
10275 EEditorUndoRedoManager *manager;
10277 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
10279 document = e_editor_page_get_document (editor_page);
10281 if (!e_editor_dom_selection_is_collapsed (editor_page))
10282 return FALSE;
10284 e_editor_dom_selection_save (editor_page);
10286 manager = e_editor_page_get_undo_redo_manager (editor_page);
10288 if (e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
10289 selection_marker = webkit_dom_document_get_element_by_id (
10290 document, "-x-evo-selection-end-marker");
10292 hr = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_marker));
10293 hr = webkit_dom_node_get_next_sibling (hr);
10294 node = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_marker));
10295 if (!node || !WEBKIT_DOM_IS_HTML_BR_ELEMENT (node) || !hr ||
10296 !WEBKIT_DOM_IS_HTML_HR_ELEMENT (hr)) {
10297 e_editor_dom_selection_restore (editor_page);
10298 return FALSE;
10301 insert_before = webkit_dom_node_get_next_sibling (hr);
10302 } else {
10303 selection_marker = webkit_dom_document_get_element_by_id (
10304 document, "-x-evo-selection-start-marker");
10306 node = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_marker));
10307 hr = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_marker));
10308 if (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node) ||
10309 !WEBKIT_DOM_IS_HTML_HR_ELEMENT (hr)) {
10310 e_editor_dom_selection_restore (editor_page);
10311 return FALSE;
10314 insert_before = WEBKIT_DOM_NODE (selection_marker);
10316 ev = g_new0 (EEditorHistoryEvent, 1);
10317 ev->type = HISTORY_INPUT;
10319 e_editor_dom_selection_get_coordinates (editor_page,
10320 &ev->before.start.x,
10321 &ev->before.start.y,
10322 &ev->before.end.x,
10323 &ev->before.end.y);
10325 fragment = webkit_dom_document_create_document_fragment (document);
10327 g_object_set_data (
10328 G_OBJECT (fragment), "history-return-key", GINT_TO_POINTER (1));
10331 block = webkit_dom_node_get_previous_sibling (hr);
10333 clone = webkit_dom_node_clone_node_with_error (block, FALSE, NULL);
10335 webkit_dom_node_append_child (
10336 clone, WEBKIT_DOM_NODE (webkit_dom_document_create_element (document, "br", NULL)), NULL);
10338 webkit_dom_node_insert_before (
10339 webkit_dom_node_get_parent_node (hr), clone, insert_before, NULL);
10341 dom_remove_selection_markers (document);
10343 webkit_dom_node_append_child (
10344 WEBKIT_DOM_NODE (clone),
10345 WEBKIT_DOM_NODE (
10346 dom_create_selection_marker (document, TRUE)),
10347 NULL);
10348 webkit_dom_node_append_child (
10349 WEBKIT_DOM_NODE (clone),
10350 WEBKIT_DOM_NODE (
10351 dom_create_selection_marker (document, FALSE)),
10352 NULL);
10354 if (ev) {
10355 webkit_dom_node_append_child (
10356 WEBKIT_DOM_NODE (fragment),
10357 webkit_dom_node_clone_node_with_error (clone, TRUE, NULL),
10358 NULL);
10360 e_editor_dom_selection_get_coordinates (editor_page,
10361 &ev->after.start.x,
10362 &ev->after.start.y,
10363 &ev->after.end.x,
10364 &ev->after.end.y);
10366 ev->data.fragment = g_object_ref (fragment);
10368 e_editor_undo_redo_manager_insert_history_event (manager, ev);
10371 e_editor_page_emit_content_changed (editor_page);
10373 e_editor_dom_selection_restore (editor_page);
10375 return TRUE;
10378 gboolean
10379 e_editor_dom_return_pressed_in_empty_list_item (EEditorPage *editor_page)
10381 WebKitDOMDocument *document;
10382 WebKitDOMElement *selection_start_marker;
10383 WebKitDOMNode *parent;
10385 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
10387 document = e_editor_page_get_document (editor_page);
10389 if (!e_editor_dom_selection_is_collapsed (editor_page))
10390 return FALSE;
10392 e_editor_dom_selection_save (editor_page);
10394 selection_start_marker = webkit_dom_document_get_element_by_id (
10395 document, "-x-evo-selection-start-marker");
10397 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start_marker));
10398 if (!WEBKIT_DOM_IS_HTML_LI_ELEMENT (parent)) {
10399 e_editor_dom_selection_restore (editor_page);
10400 return FALSE;
10403 if (selection_is_in_empty_list_item (WEBKIT_DOM_NODE (selection_start_marker))) {
10404 EEditorHistoryEvent *ev = NULL;
10405 EEditorUndoRedoManager *manager;
10406 WebKitDOMDocumentFragment *fragment;
10407 WebKitDOMElement *paragraph;
10408 WebKitDOMNode *list;
10410 manager = e_editor_page_get_undo_redo_manager (editor_page);
10412 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
10413 ev = g_new0 (EEditorHistoryEvent, 1);
10414 ev->type = HISTORY_INPUT;
10416 e_editor_dom_selection_get_coordinates (editor_page,
10417 &ev->before.start.x,
10418 &ev->before.start.y,
10419 &ev->before.end.x,
10420 &ev->before.end.y);
10422 fragment = webkit_dom_document_create_document_fragment (document);
10424 g_object_set_data (
10425 G_OBJECT (fragment), "history-return-key", GINT_TO_POINTER (1));
10428 list = split_list_into_two (parent, -1);
10430 if (ev) {
10431 webkit_dom_node_append_child (
10432 WEBKIT_DOM_NODE (fragment),
10433 parent,
10434 NULL);
10435 } else {
10436 remove_node (parent);
10439 paragraph = e_editor_dom_prepare_paragraph (editor_page, TRUE);
10441 webkit_dom_node_insert_before (
10442 webkit_dom_node_get_parent_node (list),
10443 WEBKIT_DOM_NODE (paragraph),
10444 list,
10445 NULL);
10447 remove_node_if_empty (list);
10449 if (ev) {
10450 e_editor_dom_selection_get_coordinates (editor_page,
10451 &ev->after.start.x,
10452 &ev->after.start.y,
10453 &ev->after.end.x,
10454 &ev->after.end.y);
10456 ev->data.fragment = g_object_ref (fragment);
10458 e_editor_undo_redo_manager_insert_history_event (manager, ev);
10461 e_editor_dom_selection_restore (editor_page);
10463 e_editor_page_emit_content_changed (editor_page);
10465 return TRUE;
10468 e_editor_dom_selection_restore (editor_page);
10470 return FALSE;
10473 static void
10474 process_smiley_on_delete_or_backspace (EEditorPage *editor_page)
10476 WebKitDOMDocument *document;
10477 WebKitDOMElement *element;
10478 WebKitDOMNode *parent;
10479 gboolean in_smiley = FALSE;
10481 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
10483 document = e_editor_page_get_document (editor_page);
10484 e_editor_dom_selection_save (editor_page);
10485 element = webkit_dom_document_get_element_by_id (
10486 document, "-x-evo-selection-start-marker");
10488 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
10489 if (WEBKIT_DOM_IS_ELEMENT (parent) &&
10490 element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-smiley-text"))
10491 in_smiley = TRUE;
10492 else {
10493 if (e_editor_dom_selection_is_collapsed (editor_page)) {
10494 WebKitDOMNode *prev_sibling;
10496 prev_sibling = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element));
10497 if (prev_sibling && WEBKIT_DOM_IS_TEXT (prev_sibling)) {
10498 gchar *text = webkit_dom_character_data_get_data (
10499 WEBKIT_DOM_CHARACTER_DATA (prev_sibling));
10501 if (g_strcmp0 (text, UNICODE_ZERO_WIDTH_SPACE) == 0) {
10502 WebKitDOMNode *prev_prev_sibling;
10504 prev_prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling);
10505 if (WEBKIT_DOM_IS_ELEMENT (prev_prev_sibling) &&
10506 element_has_class (WEBKIT_DOM_ELEMENT (prev_prev_sibling), "-x-evo-smiley-wrapper")) {
10507 remove_node (prev_sibling);
10508 in_smiley = TRUE;
10509 parent = webkit_dom_node_get_last_child (prev_prev_sibling);
10513 g_free (text);
10515 } else {
10516 element = webkit_dom_document_get_element_by_id (
10517 document, "-x-evo-selection-end-marker");
10519 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
10520 if (WEBKIT_DOM_IS_ELEMENT (parent) &&
10521 element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-smiley-text"))
10522 in_smiley = TRUE;
10526 if (in_smiley) {
10527 WebKitDOMNode *wrapper;
10529 wrapper = webkit_dom_node_get_parent_node (parent);
10530 if (!e_editor_page_get_html_mode (editor_page)) {
10531 WebKitDOMNode *child;
10533 while ((child = webkit_dom_node_get_first_child (parent)))
10534 webkit_dom_node_insert_before (
10535 webkit_dom_node_get_parent_node (wrapper),
10536 child,
10537 wrapper,
10538 NULL);
10540 /* In the HTML mode the whole smiley will be removed. */
10541 remove_node (wrapper);
10542 /* FIXME history will be probably broken here */
10545 e_editor_dom_selection_restore (editor_page);
10548 gboolean
10549 e_editor_dom_key_press_event_process_return_key (EEditorPage *editor_page)
10551 WebKitDOMDocument *document;
10552 WebKitDOMNode *table = NULL;
10553 gboolean first_cell = FALSE;
10555 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
10557 document = e_editor_page_get_document (editor_page);
10558 /* Return pressed in the beginning of the first cell will insert
10559 * new block before the table (and move the caret there) if none
10560 * is already there, otherwise it will act as normal return. */
10561 if (selection_is_in_table (document, &first_cell, &table) && first_cell) {
10562 WebKitDOMNode *node;
10564 node = webkit_dom_node_get_previous_sibling (table);
10565 if (!node) {
10566 node = webkit_dom_node_get_next_sibling (table);
10567 node = webkit_dom_node_clone_node_with_error (node, FALSE, NULL);
10568 webkit_dom_node_append_child (
10569 node,
10570 WEBKIT_DOM_NODE (webkit_dom_document_create_element (
10571 document, "br", NULL)),
10572 NULL);
10573 dom_add_selection_markers_into_element_start (
10574 document, WEBKIT_DOM_ELEMENT (node), NULL, NULL);
10575 webkit_dom_node_insert_before (
10576 webkit_dom_node_get_parent_node (table),
10577 node,
10578 table,
10579 NULL);
10580 e_editor_dom_selection_restore (editor_page);
10581 e_editor_page_emit_content_changed (editor_page);
10582 return TRUE;
10586 /* When user presses ENTER in a citation block, WebKit does
10587 * not break the citation automatically, so we need to use
10588 * the special command to do it. */
10589 if (e_editor_dom_selection_is_citation (editor_page)) {
10590 e_editor_dom_remove_input_event_listener_from_body (editor_page);
10591 if (split_citation (editor_page)) {
10592 e_editor_page_set_return_key_pressed (editor_page, TRUE);
10593 e_editor_dom_check_magic_links (editor_page, FALSE);
10594 e_editor_page_set_return_key_pressed (editor_page, FALSE);
10595 e_editor_page_emit_content_changed (editor_page);
10597 return TRUE;
10599 return FALSE;
10602 /* If the ENTER key is pressed inside an empty list item then the list
10603 * is broken into two and empty paragraph is inserted between lists. */
10604 if (e_editor_dom_return_pressed_in_empty_list_item (editor_page))
10605 return TRUE;
10607 if (return_pressed_in_image_wrapper (editor_page))
10608 return TRUE;
10610 if (return_pressed_after_h_rule (editor_page))
10611 return TRUE;
10613 return FALSE;
10616 static gboolean
10617 remove_empty_bulleted_list_item (EEditorPage *editor_page)
10619 WebKitDOMDocument *document;
10620 WebKitDOMElement *selection_start;
10621 WebKitDOMNode *parent;
10622 EEditorUndoRedoManager *manager;
10624 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
10626 document = e_editor_page_get_document (editor_page);
10627 manager = e_editor_page_get_undo_redo_manager (editor_page);
10628 e_editor_dom_selection_save (editor_page);
10630 selection_start = webkit_dom_document_get_element_by_id (
10631 document, "-x-evo-selection-start-marker");
10633 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start));
10634 while (parent && !node_is_list_or_item (parent))
10635 parent = webkit_dom_node_get_parent_node (parent);
10637 if (!parent)
10638 goto out;
10640 if (selection_is_in_empty_list_item (WEBKIT_DOM_NODE (selection_start))) {
10641 EEditorHistoryEvent *ev = NULL;
10642 WebKitDOMDocumentFragment *fragment;
10643 WebKitDOMNode *prev_item;
10645 prev_item = webkit_dom_node_get_previous_sibling (parent);
10647 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
10648 /* Insert new history event for Return to have the right coordinates.
10649 * The fragment will be added later. */
10650 ev = g_new0 (EEditorHistoryEvent, 1);
10651 ev->type = HISTORY_DELETE;
10653 e_editor_dom_selection_get_coordinates (editor_page,
10654 &ev->before.start.x,
10655 &ev->before.start.y,
10656 &ev->before.end.x,
10657 &ev->before.end.y);
10659 fragment = webkit_dom_document_create_document_fragment (document);
10662 if (ev) {
10663 if (prev_item)
10664 webkit_dom_node_append_child (
10665 WEBKIT_DOM_NODE (fragment),
10666 webkit_dom_node_clone_node_with_error (prev_item, TRUE, NULL),
10667 NULL);
10669 webkit_dom_node_append_child (
10670 WEBKIT_DOM_NODE (fragment),
10671 parent,
10672 NULL);
10673 } else
10674 remove_node (parent);
10676 if (prev_item)
10677 dom_add_selection_markers_into_element_end (
10678 document, WEBKIT_DOM_ELEMENT (prev_item), NULL, NULL);
10680 if (ev) {
10681 e_editor_dom_selection_get_coordinates (editor_page,
10682 &ev->after.start.x,
10683 &ev->after.start.y,
10684 &ev->after.end.x,
10685 &ev->after.end.y);
10687 ev->data.fragment = g_object_ref (fragment);
10689 e_editor_undo_redo_manager_insert_history_event (manager, ev);
10692 e_editor_page_emit_content_changed (editor_page);
10693 e_editor_dom_selection_restore (editor_page);
10695 return TRUE;
10697 out:
10698 e_editor_dom_selection_restore (editor_page);
10700 return FALSE;
10703 gboolean
10704 e_editor_dom_key_press_event_process_backspace_key (EEditorPage *editor_page)
10706 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
10708 /* BackSpace pressed in the beginning of quoted content changes
10709 * format to normal and inserts text into body */
10710 if (e_editor_dom_selection_is_collapsed (editor_page)) {
10711 e_editor_dom_selection_save (editor_page);
10712 if (e_editor_dom_move_quoted_block_level_up (editor_page) || delete_hidden_space (editor_page)) {
10713 e_editor_dom_selection_restore (editor_page);
10714 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
10715 e_editor_page_emit_content_changed (editor_page);
10716 return TRUE;
10718 e_editor_dom_selection_restore (editor_page);
10721 /* BackSpace in indented block decrease indent level by one */
10722 if (e_editor_dom_selection_is_indented (editor_page) &&
10723 e_editor_dom_selection_is_collapsed (editor_page)) {
10724 WebKitDOMDocument *document;
10725 WebKitDOMElement *selection_start;
10726 WebKitDOMNode *prev_sibling;
10728 document = e_editor_page_get_document (editor_page);
10730 e_editor_dom_selection_save (editor_page);
10731 selection_start = webkit_dom_document_get_element_by_id (
10732 document, "-x-evo-selection-start-marker");
10734 /* Empty text node before caret */
10735 prev_sibling = webkit_dom_node_get_previous_sibling (
10736 WEBKIT_DOM_NODE (selection_start));
10737 if (prev_sibling && WEBKIT_DOM_IS_TEXT (prev_sibling))
10738 if (webkit_dom_character_data_get_length (WEBKIT_DOM_CHARACTER_DATA (prev_sibling)) == 0)
10739 prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling);
10741 e_editor_dom_selection_restore (editor_page);
10742 if (!prev_sibling) {
10743 e_editor_dom_selection_unindent (editor_page);
10744 e_editor_page_emit_content_changed (editor_page);
10745 return TRUE;
10749 /* BackSpace pressed in an empty item in the bulleted list removes it. */
10750 if (!e_editor_page_get_html_mode (editor_page) && e_editor_dom_selection_is_collapsed (editor_page) &&
10751 remove_empty_bulleted_list_item (editor_page))
10752 return TRUE;
10755 if (prevent_from_deleting_last_element_in_body (e_editor_page_get_document (editor_page)))
10756 return TRUE;
10758 return FALSE;
10761 static gboolean
10762 deleting_block_starting_in_quoted_content (EEditorPage *editor_page,
10763 glong key_code,
10764 gboolean control_key)
10766 gint citation_level;
10767 WebKitDOMDocument *document;
10768 WebKitDOMElement *element;
10769 WebKitDOMHTMLElement *body;
10770 WebKitDOMNode *node, *parent, *block, *sibling;
10771 WebKitDOMRange *range = NULL;
10773 e_editor_dom_selection_save (editor_page);
10775 document = e_editor_page_get_document (editor_page);
10777 element = webkit_dom_document_get_element_by_id (
10778 document, "-x-evo-selection-start-marker");
10780 /* Before the caret are just quote characters */
10781 sibling = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element));
10782 if (!(sibling && WEBKIT_DOM_IS_ELEMENT (sibling) &&
10783 element_has_class (WEBKIT_DOM_ELEMENT (sibling), "-x-evo-quoted"))) {
10784 goto out;
10787 if (webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (sibling))) {
10788 goto out;
10791 element = webkit_dom_document_get_element_by_id (
10792 document, "-x-evo-selection-end-marker");
10794 citation_level = e_editor_dom_get_citation_level (WEBKIT_DOM_NODE (element));
10796 enable_quote_marks_select (document);
10797 e_editor_dom_selection_restore (editor_page);
10799 if (key_code != ~0)
10800 save_history_for_delete_or_backspace (
10801 editor_page, key_code == HTML_KEY_CODE_DELETE, control_key);
10803 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_DELETE, NULL);
10805 range = e_editor_dom_get_current_range (editor_page);
10806 node = webkit_dom_range_get_end_container (range, NULL);
10808 block = e_editor_dom_get_parent_block_node_from_child (node);
10809 parent = webkit_dom_node_get_parent_node (block);
10810 body = webkit_dom_document_get_body (document);
10811 while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (webkit_dom_node_get_parent_node (parent)))
10812 parent = webkit_dom_node_get_parent_node (parent);
10814 if (!citation_level) {
10815 e_editor_dom_remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (block));
10816 e_editor_dom_remove_quoting_from_element (WEBKIT_DOM_ELEMENT (block));
10818 webkit_dom_node_insert_before (
10819 WEBKIT_DOM_NODE (body),
10820 block,
10821 webkit_dom_node_get_next_sibling (parent),
10822 NULL);
10823 } else {
10824 WebKitDOMNode *last_child = webkit_dom_node_get_last_child (block);
10826 if (WEBKIT_DOM_IS_ELEMENT (last_child) &&
10827 element_has_class (WEBKIT_DOM_ELEMENT (last_child), "-x-evo-quoted")) {
10828 webkit_dom_node_append_child (
10829 block,
10830 WEBKIT_DOM_NODE (webkit_dom_document_create_element (document, "br", NULL)),
10831 NULL);
10836 e_editor_dom_disable_quote_marks_select (editor_page);
10837 e_editor_dom_move_caret_into_element (editor_page, WEBKIT_DOM_ELEMENT (block), TRUE);
10839 g_clear_object (&range);
10841 return TRUE;
10842 out:
10843 e_editor_dom_selection_restore (editor_page);
10845 return FALSE;
10848 gboolean
10849 e_editor_dom_key_press_event_process_delete_or_backspace_key (EEditorPage *editor_page,
10850 glong key_code,
10851 gboolean control_key,
10852 gboolean delete)
10854 WebKitDOMDocument *document;
10855 gboolean html_mode, local_delete, collapsed;
10857 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
10859 document = e_editor_page_get_document (editor_page);
10860 html_mode = e_editor_page_get_html_mode (editor_page);
10861 local_delete = (key_code == HTML_KEY_CODE_DELETE) || delete;
10863 if (e_editor_page_get_magic_smileys_enabled (editor_page)) {
10864 /* If deleting something in a smiley it won't be a smiley
10865 * anymore (at least from Evolution' POV), so remove all
10866 * the elements that are hidden in the wrapper and leave
10867 * just the text. Also this ensures that when a smiley is
10868 * recognized and we press the BackSpace key we won't delete
10869 * the UNICODE_HIDDEN_SPACE, but we will correctly delete
10870 * the last character of smiley. */
10871 process_smiley_on_delete_or_backspace (editor_page);
10874 if (!local_delete && !html_mode &&
10875 e_editor_dom_delete_last_character_on_line_in_quoted_block (editor_page, key_code, control_key))
10876 goto out;
10878 if (!local_delete && !html_mode &&
10879 delete_last_character_from_previous_line_in_quoted_block (editor_page, key_code, control_key))
10880 goto out;
10882 if (!html_mode && e_editor_dom_fix_structure_after_delete_before_quoted_content (editor_page, key_code, control_key, delete))
10883 goto out;
10885 collapsed = e_editor_dom_selection_is_collapsed (editor_page);
10887 if (!html_mode && !collapsed && deleting_block_starting_in_quoted_content (editor_page, key_code, control_key))
10888 goto out;
10890 if (!collapsed) {
10891 /* Let the quote marks be selectable to nearly correctly remove the
10892 * selection. Corrections after are done in body_keyup_event_cb. */
10893 enable_quote_marks_select (document);
10896 if (key_code != ~0)
10897 save_history_for_delete_or_backspace (
10898 editor_page, key_code == HTML_KEY_CODE_DELETE, control_key);
10900 if (local_delete) {
10901 WebKitDOMElement *selection_start_marker;
10902 WebKitDOMNode *sibling, *block, *next_block;
10904 /* This needs to be performed just in plain text mode
10905 * and when the selection is collapsed. */
10906 if (html_mode)
10907 return FALSE;
10909 e_editor_dom_selection_save (editor_page);
10911 selection_start_marker = webkit_dom_document_get_element_by_id (
10912 document, "-x-evo-selection-start-marker");
10913 sibling = webkit_dom_node_get_previous_sibling (
10914 WEBKIT_DOM_NODE (selection_start_marker));
10915 /* Check if the key was pressed in the beginning of block. */
10916 if (!(sibling && WEBKIT_DOM_IS_ELEMENT (sibling) &&
10917 element_has_class (WEBKIT_DOM_ELEMENT (sibling), "-x-evo-quoted"))) {
10918 e_editor_dom_selection_restore (editor_page);
10919 return FALSE;
10922 sibling = webkit_dom_node_get_next_sibling (
10923 WEBKIT_DOM_NODE (selection_start_marker));
10924 sibling = webkit_dom_node_get_next_sibling (sibling);
10926 /* And also the current block was empty. */
10927 if (!(!sibling || (sibling && WEBKIT_DOM_IS_HTML_BR_ELEMENT (sibling) &&
10928 !element_has_class (WEBKIT_DOM_ELEMENT (sibling), "-x-evo-wrap-br")))) {
10929 e_editor_dom_selection_restore (editor_page);
10930 return FALSE;
10933 block = e_editor_dom_get_parent_block_node_from_child (
10934 WEBKIT_DOM_NODE (selection_start_marker));
10935 next_block = webkit_dom_node_get_next_sibling (block);
10937 remove_node (block);
10939 e_editor_dom_move_caret_into_element (editor_page, WEBKIT_DOM_ELEMENT (next_block), TRUE);
10941 goto out;
10942 } else {
10943 /* Concatenating a non-quoted block with Backspace key to the
10944 * previous block that is inside a quoted content. */
10945 WebKitDOMElement *selection_start_marker;
10946 WebKitDOMNode *node, *block, *prev_block, *last_child, *child;
10948 if (html_mode || e_editor_dom_selection_is_citation (editor_page))
10949 return FALSE;
10951 e_editor_dom_selection_save (editor_page);
10953 selection_start_marker = webkit_dom_document_get_element_by_id (
10954 document, "-x-evo-selection-start-marker");
10956 node = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_start_marker));
10957 if (node) {
10958 e_editor_dom_selection_restore (editor_page);
10959 return FALSE;
10962 remove_empty_blocks (document);
10964 block = e_editor_dom_get_parent_block_node_from_child (
10965 WEBKIT_DOM_NODE (selection_start_marker));
10967 prev_block = webkit_dom_node_get_previous_sibling (block);
10968 if (!prev_block || !WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (prev_block)) {
10969 e_editor_dom_selection_restore (editor_page);
10970 return FALSE;
10973 last_child = webkit_dom_node_get_last_child (prev_block);
10974 while (last_child && WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (last_child))
10975 last_child = webkit_dom_node_get_last_child (last_child);
10977 if (!last_child) {
10978 e_editor_dom_selection_restore (editor_page);
10979 return FALSE;
10982 e_editor_dom_remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (last_child));
10983 e_editor_dom_remove_quoting_from_element (WEBKIT_DOM_ELEMENT (last_child));
10985 node = webkit_dom_node_get_last_child (last_child);
10986 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (node))
10987 remove_node (node);
10989 while ((child = webkit_dom_node_get_first_child (block)))
10990 webkit_dom_node_append_child (last_child, child, NULL);
10992 remove_node (block);
10994 if (WEBKIT_DOM_IS_ELEMENT (last_child))
10995 e_editor_dom_wrap_and_quote_element (editor_page, WEBKIT_DOM_ELEMENT (last_child));
10997 e_editor_dom_selection_restore (editor_page);
10999 goto out;
11002 return FALSE;
11003 out:
11004 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
11005 e_editor_page_emit_content_changed (editor_page);
11007 return TRUE;
11010 static gboolean
11011 contains_forbidden_elements (WebKitDOMDocument *document)
11013 WebKitDOMElement *body, *element;
11015 body = WEBKIT_DOM_ELEMENT (webkit_dom_document_get_body (document));
11017 /* Try to find disallowed elements in the plain text mode */
11018 element = webkit_dom_element_query_selector (
11019 body,
11020 ":not("
11021 /* Basic elements used as blocks allowed in the plain text mode */
11022 "[data-evo-paragraph], pre, ul, ol, li, blockquote[type=cite], "
11023 /* Other elements */
11024 "br, a, "
11025 /* Indented elements */
11026 ".-x-evo-indented, "
11027 /* Signature */
11028 ".-x-evo-signature-wrapper, .-x-evo-signature, "
11029 /* Smileys */
11030 ".-x-evo-smiley-wrapper, .-x-evo-smiley-img, .-x-evo-smiley-text, "
11031 /* Selection markers */
11032 "#-x-evo-selection-start-marker, #-x-evo-selection-end-marker"
11033 ")",
11034 NULL);
11036 if (element)
11037 return TRUE;
11039 /* Try to find disallowed elements relationship in the plain text */
11040 element = webkit_dom_element_query_selector (
11041 body,
11042 ":not("
11043 /* Body descendants */
11044 "body > :matches(blockquote[type=cite], .-x-evo-signature-wrapper), "
11045 /* Main blocks and indented blocks */
11046 ":matches(body, .-x-evo-indented) > :matches(pre, ul, ol, .-x-evo-indented, [data-evo-paragraph]), "
11047 /* Blockquote descendants */
11048 "blockquote[type=cite] > :matches(pre, [data-evo-paragraph], blockquote[type=cite]), "
11049 /* Block descendants */
11050 ":matches(pre, [data-evo-paragraph], li) > :matches(br, span, a), "
11051 /* Lists */
11052 ":matches(ul, ol) > :matches(ul, ol, li), "
11053 /* Smileys */
11054 ".-x-evo-smiley-wrapper > :matches(.-x-evo-smiley-img, .-x-evo-smiley-text), "
11055 /* Signature */
11056 ".-x-evo-signature-wrapper > .-x-evo-signature"
11057 ")",
11058 NULL);
11060 return element ? TRUE : FALSE;
11063 gboolean
11064 e_editor_dom_check_if_conversion_needed (EEditorPage *editor_page)
11066 WebKitDOMDocument *document;
11067 gboolean html_mode, convert = FALSE;
11069 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
11071 document = e_editor_page_get_document (editor_page);
11072 html_mode = e_editor_page_get_html_mode (editor_page);
11074 if (html_mode)
11075 convert = contains_forbidden_elements (document);
11077 return convert;
11080 void
11081 e_editor_dom_process_content_after_mode_change (EEditorPage *editor_page)
11083 EEditorUndoRedoManager *manager;
11084 gboolean html_mode;
11086 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11088 html_mode = e_editor_page_get_html_mode (editor_page);
11090 if (html_mode)
11091 process_content_to_html_changing_composer_mode (editor_page);
11092 else
11093 process_content_to_plain_text_changing_composer_mode (editor_page);
11095 manager = e_editor_page_get_undo_redo_manager (editor_page);
11096 e_editor_undo_redo_manager_clean_history (manager);
11099 guint
11100 e_editor_dom_get_caret_offset (EEditorPage *editor_page)
11102 WebKitDOMDocument *document;
11103 WebKitDOMDOMWindow *dom_window = NULL;
11104 WebKitDOMDOMSelection *dom_selection = NULL;
11105 WebKitDOMNode *anchor;
11106 WebKitDOMRange *range = NULL;
11107 guint ret_val;
11108 gchar *text;
11110 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), 0);
11112 document = e_editor_page_get_document (editor_page);
11113 dom_window = webkit_dom_document_get_default_view (document);
11114 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
11115 g_clear_object (&dom_window);
11117 if (webkit_dom_dom_selection_get_range_count (dom_selection) < 1) {
11118 g_clear_object (&dom_selection);
11119 return 0;
11122 webkit_dom_dom_selection_collapse_to_start (dom_selection, NULL);
11123 /* Select the text from the current caret position to the beginning of the line. */
11124 webkit_dom_dom_selection_modify (dom_selection, "extend", "left", "lineBoundary");
11126 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
11127 anchor = webkit_dom_dom_selection_get_anchor_node (dom_selection);
11128 text = webkit_dom_range_to_string (range, NULL);
11129 ret_val = strlen (text);
11130 g_free (text);
11132 webkit_dom_dom_selection_collapse_to_end (dom_selection, NULL);
11134 /* In the plain text mode we need to increase the return value by 2 per
11135 * citation level because of "> ". */
11136 if (!e_editor_page_get_html_mode (editor_page)) {
11137 WebKitDOMNode *parent = anchor;
11139 while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
11140 if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent))
11141 ret_val += 2;
11143 parent = webkit_dom_node_get_parent_node (parent);
11147 g_clear_object (&range);
11148 g_clear_object (&dom_selection);
11150 return ret_val;
11153 guint
11154 e_editor_dom_get_caret_position (EEditorPage *editor_page)
11156 WebKitDOMDocument *document;
11157 WebKitDOMHTMLElement *body;
11158 WebKitDOMDOMWindow *dom_window = NULL;
11159 WebKitDOMDOMSelection *dom_selection = NULL;
11160 WebKitDOMRange *range = NULL, *range_clone = 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 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
11177 range_clone = webkit_dom_range_clone_range (range, NULL);
11179 body = webkit_dom_document_get_body (document);
11180 /* Select the text from the beginning of the body to the current caret. */
11181 webkit_dom_range_set_start_before (
11182 range_clone, webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)), NULL);
11184 /* This is returning a text without new lines! */
11185 text = webkit_dom_range_to_string (range_clone, NULL);
11186 ret_val = strlen (text);
11187 g_free (text);
11189 g_clear_object (&range_clone);
11190 g_clear_object (&range);
11191 g_clear_object (&dom_selection);
11193 return ret_val;
11196 static void
11197 insert_nbsp_history_event (WebKitDOMDocument *document,
11198 EEditorUndoRedoManager *manager,
11199 gboolean delete,
11200 guint x,
11201 guint y)
11203 EEditorHistoryEvent *event;
11204 WebKitDOMDocumentFragment *fragment;
11206 event = g_new0 (EEditorHistoryEvent, 1);
11207 event->type = HISTORY_AND;
11208 e_editor_undo_redo_manager_insert_history_event (manager, event);
11210 fragment = webkit_dom_document_create_document_fragment (document);
11211 webkit_dom_node_append_child (
11212 WEBKIT_DOM_NODE (fragment),
11213 WEBKIT_DOM_NODE (
11214 webkit_dom_document_create_text_node (document, UNICODE_NBSP)),
11215 NULL);
11217 event = g_new0 (EEditorHistoryEvent, 1);
11218 event->type = HISTORY_DELETE;
11220 if (delete)
11221 g_object_set_data (G_OBJECT (fragment), "history-delete-key", GINT_TO_POINTER (1));
11223 event->data.fragment = fragment;
11225 event->before.start.x = x;
11226 event->before.start.y = y;
11227 event->before.end.x = x;
11228 event->before.end.y = y;
11230 event->after.start.x = x;
11231 event->after.start.y = y;
11232 event->after.end.x = x;
11233 event->after.end.y = y;
11235 e_editor_undo_redo_manager_insert_history_event (manager, event);
11237 void
11238 e_editor_dom_save_history_for_drag (EEditorPage *editor_page)
11240 WebKitDOMDocument *document;
11241 WebKitDOMDocumentFragment *fragment;
11242 WebKitDOMDOMSelection *dom_selection = NULL;
11243 WebKitDOMDOMWindow *dom_window = NULL;
11244 WebKitDOMRange *beginning_of_line = NULL;
11245 WebKitDOMRange *range = NULL, *range_clone = NULL;
11246 EEditorHistoryEvent *event;
11247 EEditorUndoRedoManager *manager;
11248 gboolean start_to_start = FALSE, end_to_end = FALSE;
11249 gchar *range_text;
11250 guint x, y;
11252 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11254 document = e_editor_page_get_document (editor_page);
11255 manager = e_editor_page_get_undo_redo_manager (editor_page);
11257 if (!(dom_window = webkit_dom_document_get_default_view (document)))
11258 return;
11260 if (!(dom_selection = webkit_dom_dom_window_get_selection (dom_window))) {
11261 g_clear_object (&dom_window);
11262 return;
11265 g_clear_object (&dom_window);
11267 if (webkit_dom_dom_selection_get_range_count (dom_selection) < 1) {
11268 g_clear_object (&dom_selection);
11269 return;
11272 /* Obtain the dragged content. */
11273 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
11274 range_clone = webkit_dom_range_clone_range (range, NULL);
11276 /* Create the history event for the content that will
11277 * be removed by DnD. */
11278 event = g_new0 (EEditorHistoryEvent, 1);
11279 event->type = HISTORY_DELETE;
11281 e_editor_dom_selection_get_coordinates (editor_page,
11282 &event->before.start.x,
11283 &event->before.start.y,
11284 &event->before.end.x,
11285 &event->before.end.y);
11287 x = event->before.start.x;
11288 y = event->before.start.y;
11290 event->after.start.x = x;
11291 event->after.start.y = y;
11292 event->after.end.x = x;
11293 event->after.end.y = y;
11295 /* Save the content that will be removed. */
11296 fragment = webkit_dom_range_clone_contents (range_clone, NULL);
11298 /* Extend the cloned range to point one character after
11299 * the selection ends to later check if there is a whitespace
11300 * after it. */
11301 webkit_dom_range_set_end (
11302 range_clone,
11303 webkit_dom_range_get_end_container (range_clone, NULL),
11304 webkit_dom_range_get_end_offset (range_clone, NULL) + 1,
11305 NULL);
11306 range_text = webkit_dom_range_get_text (range_clone);
11308 /* Check if the current selection starts on the beginning of line. */
11309 webkit_dom_dom_selection_modify (
11310 dom_selection, "extend", "left", "lineboundary");
11311 beginning_of_line = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
11312 start_to_start = webkit_dom_range_compare_boundary_points (
11313 beginning_of_line, WEBKIT_DOM_RANGE_START_TO_START, range, NULL) == 0;
11315 /* Restore the selection to state before the check. */
11316 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
11317 webkit_dom_dom_selection_add_range (dom_selection, range);
11318 g_clear_object (&beginning_of_line);
11320 /* Check if the current selection end on the end of the line. */
11321 webkit_dom_dom_selection_modify (
11322 dom_selection, "extend", "right", "lineboundary");
11323 beginning_of_line = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
11324 end_to_end = webkit_dom_range_compare_boundary_points (
11325 beginning_of_line, WEBKIT_DOM_RANGE_END_TO_END, range, NULL) == 0;
11327 /* Dragging the whole line. */
11328 if (start_to_start && end_to_end) {
11329 WebKitDOMNode *container, *actual_block, *tmp_block;
11331 /* Select the whole line (to the beginning of the next
11332 * one so we can reuse the undo code while undoing this.
11333 * Because of this we need to special mark the event
11334 * with history-drag-and-drop to correct the selection
11335 * after undoing it (otherwise the beginning of the next
11336 * line will be selected as well. */
11337 webkit_dom_dom_selection_modify (
11338 dom_selection, "extend", "right", "character");
11339 g_clear_object (&beginning_of_line);
11340 beginning_of_line = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
11342 container = webkit_dom_range_get_end_container (range, NULL);
11343 actual_block = e_editor_dom_get_parent_block_node_from_child (container);
11345 tmp_block = webkit_dom_range_get_end_container (beginning_of_line, NULL);
11346 if ((tmp_block = e_editor_dom_get_parent_block_node_from_child (tmp_block))) {
11347 e_editor_dom_selection_get_coordinates (editor_page,
11348 &event->before.start.x,
11349 &event->before.start.y,
11350 &event->before.end.x,
11351 &event->before.end.y);
11353 /* Create the right content for the history event. */
11354 fragment = webkit_dom_document_create_document_fragment (document);
11355 /* The removed line. */
11356 webkit_dom_node_append_child (
11357 WEBKIT_DOM_NODE (fragment),
11358 webkit_dom_node_clone_node_with_error (actual_block, TRUE, NULL),
11359 NULL);
11360 /* The following block, but empty. */
11361 webkit_dom_node_append_child (
11362 WEBKIT_DOM_NODE (fragment),
11363 webkit_dom_node_clone_node_with_error (tmp_block, FALSE, NULL),
11364 NULL);
11365 g_object_set_data (
11366 G_OBJECT (fragment),
11367 "history-drag-and-drop",
11368 GINT_TO_POINTER (1));
11371 /* It should act as a Delete key press. */
11372 g_object_set_data (G_OBJECT (fragment), "history-delete-key", GINT_TO_POINTER (1));
11374 event->data.fragment = fragment;
11375 e_editor_undo_redo_manager_insert_history_event (manager, event);
11377 /* WebKit removes the space (if presented) after selection and
11378 * we need to create a new history event for it. */
11379 if (g_str_has_suffix (range_text, " ") ||
11380 g_str_has_suffix (range_text, UNICODE_NBSP))
11381 insert_nbsp_history_event (document, manager, TRUE, x, y);
11382 else {
11383 /* If there is a space before the selection WebKit will remove
11384 * it as well unless there is a space after the selection. */
11385 gchar *range_text_start;
11386 glong start_offset;
11388 start_offset = webkit_dom_range_get_start_offset (range_clone, NULL);
11389 webkit_dom_range_set_start (
11390 range_clone,
11391 webkit_dom_range_get_start_container (range_clone, NULL),
11392 start_offset > 0 ? start_offset - 1 : 0,
11393 NULL);
11395 range_text_start = webkit_dom_range_get_text (range_clone);
11396 if (g_str_has_prefix (range_text_start, " ") ||
11397 g_str_has_prefix (range_text_start, UNICODE_NBSP)) {
11398 if (!end_to_end) {
11399 webkit_dom_dom_selection_collapse_to_start (dom_selection, NULL);
11400 webkit_dom_dom_selection_modify (
11401 dom_selection, "move", "backward", "character");
11402 e_editor_dom_selection_get_coordinates (editor_page, &x, &y, &x, &y);
11404 insert_nbsp_history_event (document, manager, TRUE, x, y);
11407 g_free (range_text_start);
11410 g_free (range_text);
11412 /* Restore the selection to original state. */
11413 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
11414 webkit_dom_dom_selection_add_range (dom_selection, range);
11415 g_clear_object (&beginning_of_line);
11417 /* All the things above were about removing the content,
11418 * create an AND event to continue later with inserting
11419 * the dropped content. */
11420 event = g_new0 (EEditorHistoryEvent, 1);
11421 event->type = HISTORY_AND;
11422 e_editor_undo_redo_manager_insert_history_event (manager, event);
11424 g_clear_object (&dom_selection);
11426 g_clear_object (&range);
11427 g_clear_object (&range_clone);
11430 void
11431 e_editor_dom_save_history_for_drop (EEditorPage *editor_page)
11433 WebKitDOMDocument *document;
11434 WebKitDOMDocumentFragment *fragment;
11435 WebKitDOMDOMSelection *dom_selection = NULL;
11436 WebKitDOMDOMWindow *dom_window = NULL;
11437 WebKitDOMNodeList *list = NULL;
11438 WebKitDOMRange *range = NULL;
11439 EEditorUndoRedoManager *manager;
11440 EEditorHistoryEvent *event;
11441 gint ii, length;
11443 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11445 document = e_editor_page_get_document (editor_page);
11446 manager = e_editor_page_get_undo_redo_manager (editor_page);
11448 /* When the image is DnD inside the view WebKit removes the wrapper that
11449 * is used for resizing the image, so we have to recreate it again. */
11450 list = webkit_dom_document_query_selector_all (document, ":not(span) > img[data-inline]", NULL);
11451 length = webkit_dom_node_list_get_length (list);
11452 for (ii = 0; ii < length; ii++) {
11453 WebKitDOMElement *element;
11454 WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
11456 element = webkit_dom_document_create_element (document, "span", NULL);
11457 webkit_dom_element_set_class_name (element, "-x-evo-resizable-wrapper");
11459 webkit_dom_node_insert_before (
11460 webkit_dom_node_get_parent_node (node),
11461 WEBKIT_DOM_NODE (element),
11462 node,
11463 NULL);
11465 webkit_dom_node_append_child (WEBKIT_DOM_NODE (element), node, NULL);
11467 g_clear_object (&list);
11469 /* When the image is moved the new selection is created after after it, so
11470 * lets collapse the selection to have the caret right after the image. */
11471 dom_window = webkit_dom_document_get_default_view (document);
11472 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
11473 g_clear_object (&dom_window);
11475 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
11477 event = g_new0 (EEditorHistoryEvent, 1);
11478 event->type = HISTORY_INSERT_HTML;
11480 /* Get the dropped content. It's easy as it is selected by WebKit. */
11481 fragment = webkit_dom_range_clone_contents (range, NULL);
11482 event->data.string.from = NULL;
11483 /* Get the HTML content of the dropped content. */
11484 event->data.string.to = dom_get_node_inner_html (WEBKIT_DOM_NODE (fragment));
11486 e_editor_undo_redo_manager_insert_history_event (manager, event);
11488 g_clear_object (&range);
11489 g_clear_object (&dom_selection);
11492 static void
11493 dom_set_link_color_in_document (EEditorPage *editor_page,
11494 const gchar *color,
11495 gboolean visited)
11497 WebKitDOMDocument *document;
11498 WebKitDOMHTMLHeadElement *head;
11499 WebKitDOMElement *style_element;
11500 WebKitDOMHTMLElement *body;
11501 gchar *color_str = NULL;
11502 const gchar *style_id;
11504 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11505 g_return_if_fail (color != NULL);
11507 style_id = visited ? "-x-evo-a-color-style-visited" : "-x-evo-a-color-style";
11509 document = e_editor_page_get_document (editor_page);
11510 head = webkit_dom_document_get_head (document);
11511 body = webkit_dom_document_get_body (document);
11513 style_element = webkit_dom_document_get_element_by_id (document, style_id);
11514 if (!style_element) {
11515 style_element = webkit_dom_document_create_element (document, "style", NULL);
11516 webkit_dom_element_set_id (style_element, style_id);
11517 webkit_dom_element_set_attribute (style_element, "type", "text/css", NULL);
11518 webkit_dom_node_append_child (
11519 WEBKIT_DOM_NODE (head), WEBKIT_DOM_NODE (style_element), NULL);
11522 color_str = g_strdup_printf (
11523 visited ? "a.-x-evo-visited-link { color: %s; }" : "a { color: %s; }", color);
11524 webkit_dom_element_set_inner_html (style_element, color_str, NULL);
11525 g_free (color_str);
11527 if (visited)
11528 webkit_dom_html_body_element_set_v_link (
11529 WEBKIT_DOM_HTML_BODY_ELEMENT (body), color);
11530 else
11531 webkit_dom_html_body_element_set_link (
11532 WEBKIT_DOM_HTML_BODY_ELEMENT (body), color);
11535 void
11536 e_editor_dom_set_link_color (EEditorPage *editor_page,
11537 const gchar *color)
11539 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11541 dom_set_link_color_in_document (editor_page, color, FALSE);
11544 void
11545 e_editor_dom_set_visited_link_color (EEditorPage *editor_page,
11546 const gchar *color)
11548 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11550 dom_set_link_color_in_document (editor_page, color, TRUE);
11553 void
11554 e_editor_dom_fix_file_uri_images (EEditorPage *editor_page)
11556 WebKitDOMDocument *document;
11557 WebKitDOMNodeList *list = NULL;
11558 gint ii;
11560 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11562 document = e_editor_page_get_document (editor_page);
11564 list = webkit_dom_document_query_selector_all (
11565 document, "img[src^=\"file://\"]", NULL);
11566 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
11567 WebKitDOMNode *node;
11568 gchar *uri;
11570 node = webkit_dom_node_list_item (list, ii);
11571 uri = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "src");
11572 g_free (uri);
11575 g_clear_object (&list);
11578 /* ******************** Selection ******************** */
11580 void
11581 e_editor_dom_replace_base64_image_src (EEditorPage *editor_page,
11582 const gchar *selector,
11583 const gchar *base64_content,
11584 const gchar *filename,
11585 const gchar *uri)
11587 WebKitDOMDocument *document;
11588 WebKitDOMElement *element;
11590 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11592 document = e_editor_page_get_document (editor_page);
11593 element = webkit_dom_document_query_selector (document, selector, NULL);
11595 if (WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (element))
11596 webkit_dom_html_image_element_set_src (
11597 WEBKIT_DOM_HTML_IMAGE_ELEMENT (element),
11598 base64_content);
11599 else
11600 webkit_dom_element_set_attribute (
11601 element, "background", base64_content, NULL);
11603 webkit_dom_element_set_attribute (element, "data-uri", uri, NULL);
11604 webkit_dom_element_set_attribute (element, "data-inline", "", NULL);
11605 webkit_dom_element_set_attribute (
11606 element, "data-name", filename ? filename : "", NULL);
11609 WebKitDOMRange *
11610 e_editor_dom_get_current_range (EEditorPage *editor_page)
11612 WebKitDOMDocument *document;
11613 WebKitDOMDOMWindow *dom_window = NULL;
11614 WebKitDOMDOMSelection *dom_selection = NULL;
11615 WebKitDOMRange *range = NULL;
11617 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
11619 document = e_editor_page_get_document (editor_page);
11620 dom_window = webkit_dom_document_get_default_view (document);
11621 if (!dom_window)
11622 return NULL;
11624 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
11625 if (!WEBKIT_DOM_IS_DOM_SELECTION (dom_selection)) {
11626 g_clear_object (&dom_window);
11627 return NULL;
11630 if (webkit_dom_dom_selection_get_range_count (dom_selection) < 1)
11631 goto exit;
11633 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
11634 exit:
11635 g_clear_object (&dom_selection);
11636 g_clear_object (&dom_window);
11638 return range;
11641 void
11642 e_editor_dom_move_caret_into_element (EEditorPage *editor_page,
11643 WebKitDOMElement *element,
11644 gboolean to_start)
11646 WebKitDOMDocument *document;
11647 WebKitDOMDOMWindow *dom_window = NULL;
11648 WebKitDOMDOMSelection *dom_selection = NULL;
11649 WebKitDOMRange *range = NULL;
11651 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11653 if (!element)
11654 return;
11656 document = e_editor_page_get_document (editor_page);
11657 dom_window = webkit_dom_document_get_default_view (document);
11658 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
11659 range = webkit_dom_document_create_range (document);
11661 webkit_dom_range_select_node_contents (
11662 range, WEBKIT_DOM_NODE (element), NULL);
11663 webkit_dom_range_collapse (range, to_start, NULL);
11664 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
11665 webkit_dom_dom_selection_add_range (dom_selection, range);
11667 g_clear_object (&range);
11668 g_clear_object (&dom_selection);
11669 g_clear_object (&dom_window);
11672 void
11673 e_editor_dom_insert_base64_image (EEditorPage *editor_page,
11674 const gchar *base64_content,
11675 const gchar *filename,
11676 const gchar *uri)
11678 WebKitDOMDocument *document;
11679 WebKitDOMElement *element, *selection_start_marker, *resizable_wrapper;
11680 WebKitDOMText *text;
11681 EEditorHistoryEvent *ev = NULL;
11682 EEditorUndoRedoManager *manager;
11684 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
11686 document = e_editor_page_get_document (editor_page);
11687 manager = e_editor_page_get_undo_redo_manager (editor_page);
11689 if (!e_editor_dom_selection_is_collapsed (editor_page)) {
11690 EEditorHistoryEvent *ev;
11691 WebKitDOMDocumentFragment *fragment;
11692 WebKitDOMRange *range = NULL;
11694 ev = g_new0 (EEditorHistoryEvent, 1);
11695 ev->type = HISTORY_DELETE;
11697 range = e_editor_dom_get_current_range (editor_page);
11698 fragment = webkit_dom_range_clone_contents (range, NULL);
11699 g_clear_object (&range);
11700 ev->data.fragment = g_object_ref (fragment);
11702 e_editor_dom_selection_get_coordinates (editor_page,
11703 &ev->before.start.x,
11704 &ev->before.start.y,
11705 &ev->before.end.x,
11706 &ev->before.end.y);
11708 ev->after.start.x = ev->before.start.x;
11709 ev->after.start.y = ev->before.start.y;
11710 ev->after.end.x = ev->before.start.x;
11711 ev->after.end.y = ev->before.start.y;
11713 e_editor_undo_redo_manager_insert_history_event (manager, ev);
11715 ev = g_new0 (EEditorHistoryEvent, 1);
11716 ev->type = HISTORY_AND;
11718 e_editor_undo_redo_manager_insert_history_event (manager, ev);
11719 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_DELETE, NULL);
11722 e_editor_dom_selection_save (editor_page);
11723 selection_start_marker = webkit_dom_document_get_element_by_id (
11724 document, "-x-evo-selection-start-marker");
11726 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
11727 ev = g_new0 (EEditorHistoryEvent, 1);
11728 ev->type = HISTORY_IMAGE;
11730 e_editor_dom_selection_get_coordinates (editor_page,
11731 &ev->before.start.x,
11732 &ev->before.start.y,
11733 &ev->before.end.x,
11734 &ev->before.end.y);
11737 resizable_wrapper =
11738 webkit_dom_document_create_element (document, "span", NULL);
11739 webkit_dom_element_set_attribute (
11740 resizable_wrapper, "class", "-x-evo-resizable-wrapper", NULL);
11742 element = webkit_dom_document_create_element (document, "img", NULL);
11743 webkit_dom_html_image_element_set_src (
11744 WEBKIT_DOM_HTML_IMAGE_ELEMENT (element),
11745 base64_content);
11746 webkit_dom_element_set_attribute (
11747 WEBKIT_DOM_ELEMENT (element), "data-uri", uri, NULL);
11748 webkit_dom_element_set_attribute (
11749 WEBKIT_DOM_ELEMENT (element), "data-inline", "", NULL);
11750 webkit_dom_element_set_attribute (
11751 WEBKIT_DOM_ELEMENT (element), "data-name",
11752 filename ? filename : "", NULL);
11753 webkit_dom_node_append_child (
11754 WEBKIT_DOM_NODE (resizable_wrapper),
11755 WEBKIT_DOM_NODE (element),
11756 NULL);
11758 webkit_dom_node_insert_before (
11759 webkit_dom_node_get_parent_node (
11760 WEBKIT_DOM_NODE (selection_start_marker)),
11761 WEBKIT_DOM_NODE (resizable_wrapper),
11762 WEBKIT_DOM_NODE (selection_start_marker),
11763 NULL);
11765 /* We have to again use UNICODE_ZERO_WIDTH_SPACE character to restore
11766 * caret on right position */
11767 text = webkit_dom_document_create_text_node (
11768 document, UNICODE_ZERO_WIDTH_SPACE);
11770 webkit_dom_node_insert_before (
11771 webkit_dom_node_get_parent_node (
11772 WEBKIT_DOM_NODE (selection_start_marker)),
11773 WEBKIT_DOM_NODE (text),
11774 WEBKIT_DOM_NODE (selection_start_marker),
11775 NULL);
11777 if (ev) {
11778 WebKitDOMDocumentFragment *fragment;
11779 WebKitDOMNode *node;
11781 fragment = webkit_dom_document_create_document_fragment (document);
11782 node = webkit_dom_node_append_child (
11783 WEBKIT_DOM_NODE (fragment),
11784 webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (resizable_wrapper), TRUE, NULL),
11785 NULL);
11787 webkit_dom_element_insert_adjacent_html (
11788 WEBKIT_DOM_ELEMENT (node), "afterend", "&#8203;", NULL);
11789 ev->data.fragment = g_object_ref (fragment);
11791 e_editor_dom_selection_get_coordinates (editor_page,
11792 &ev->after.start.x,
11793 &ev->after.start.y,
11794 &ev->after.end.x,
11795 &ev->after.end.y);
11797 e_editor_undo_redo_manager_insert_history_event (manager, ev);
11800 e_editor_dom_selection_restore (editor_page);
11801 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
11802 e_editor_dom_scroll_to_caret (editor_page);
11805 /* ************************ image_load_and_insert_async() ************************ */
11807 typedef struct _ImageLoadContext {
11808 EEditorPage *editor_page;
11809 GInputStream *input_stream;
11810 GOutputStream *output_stream;
11811 GFile *file;
11812 GFileInfo *file_info;
11813 goffset total_num_bytes;
11814 gssize bytes_read;
11815 const gchar *content_type;
11816 const gchar *filename;
11817 const gchar *selector;
11818 gchar buffer[4096];
11819 } ImageLoadContext;
11821 /* Forward Declaration */
11822 static void
11823 image_load_stream_read_cb (GInputStream *input_stream,
11824 GAsyncResult *result,
11825 ImageLoadContext *load_context);
11827 static ImageLoadContext *
11828 image_load_context_new (EEditorPage *editor_page)
11830 ImageLoadContext *load_context;
11832 load_context = g_slice_new0 (ImageLoadContext);
11833 load_context->editor_page = editor_page;
11835 return load_context;
11838 static void
11839 image_load_context_free (ImageLoadContext *load_context)
11841 if (load_context->input_stream != NULL)
11842 g_object_unref (load_context->input_stream);
11844 if (load_context->output_stream != NULL)
11845 g_object_unref (load_context->output_stream);
11847 if (load_context->file_info != NULL)
11848 g_object_unref (load_context->file_info);
11850 if (load_context->file != NULL)
11851 g_object_unref (load_context->file);
11853 g_slice_free (ImageLoadContext, load_context);
11856 static void
11857 image_load_finish (ImageLoadContext *load_context)
11859 EEditorPage *editor_page;
11860 GMemoryOutputStream *output_stream;
11861 const gchar *selector;
11862 gchar *base64_encoded, *mime_type, *output, *uri;
11863 gsize size;
11864 gpointer data;
11866 output_stream = G_MEMORY_OUTPUT_STREAM (load_context->output_stream);
11867 editor_page = load_context->editor_page;
11868 mime_type = g_content_type_get_mime_type (load_context->content_type);
11870 data = g_memory_output_stream_get_data (output_stream);
11871 size = g_memory_output_stream_get_data_size (output_stream);
11872 uri = g_file_get_uri (load_context->file);
11874 base64_encoded = g_base64_encode ((const guchar *) data, size);
11875 output = g_strconcat ("data:", mime_type, ";base64,", base64_encoded, NULL);
11876 selector = load_context->selector;
11877 if (selector && *selector)
11878 e_editor_dom_replace_base64_image_src (editor_page, selector, output, load_context->filename, uri);
11879 else
11880 e_editor_dom_insert_base64_image (editor_page, output, load_context->filename, uri);
11882 g_free (base64_encoded);
11883 g_free (output);
11884 g_free (mime_type);
11885 g_free (uri);
11887 image_load_context_free (load_context);
11890 static void
11891 image_load_write_cb (GOutputStream *output_stream,
11892 GAsyncResult *result,
11893 ImageLoadContext *load_context)
11895 GInputStream *input_stream;
11896 gssize bytes_written;
11897 GError *error = NULL;
11899 bytes_written = g_output_stream_write_finish (
11900 output_stream, result, &error);
11902 if (error) {
11903 image_load_context_free (load_context);
11904 return;
11907 input_stream = load_context->input_stream;
11909 if (bytes_written < load_context->bytes_read) {
11910 g_memmove (
11911 load_context->buffer,
11912 load_context->buffer + bytes_written,
11913 load_context->bytes_read - bytes_written);
11914 load_context->bytes_read -= bytes_written;
11916 g_output_stream_write_async (
11917 output_stream,
11918 load_context->buffer,
11919 load_context->bytes_read,
11920 G_PRIORITY_DEFAULT, NULL,
11921 (GAsyncReadyCallback) image_load_write_cb,
11922 load_context);
11923 } else
11924 g_input_stream_read_async (
11925 input_stream,
11926 load_context->buffer,
11927 sizeof (load_context->buffer),
11928 G_PRIORITY_DEFAULT, NULL,
11929 (GAsyncReadyCallback) image_load_stream_read_cb,
11930 load_context);
11933 static void
11934 image_load_stream_read_cb (GInputStream *input_stream,
11935 GAsyncResult *result,
11936 ImageLoadContext *load_context)
11938 GOutputStream *output_stream;
11939 gssize bytes_read;
11940 GError *error = NULL;
11942 bytes_read = g_input_stream_read_finish (
11943 input_stream, result, &error);
11945 if (error) {
11946 image_load_context_free (load_context);
11947 return;
11950 if (bytes_read == 0) {
11951 image_load_finish (load_context);
11952 return;
11955 output_stream = load_context->output_stream;
11956 load_context->bytes_read = bytes_read;
11958 g_output_stream_write_async (
11959 output_stream,
11960 load_context->buffer,
11961 load_context->bytes_read,
11962 G_PRIORITY_DEFAULT, NULL,
11963 (GAsyncReadyCallback) image_load_write_cb,
11964 load_context);
11967 static void
11968 image_load_file_read_cb (GFile *file,
11969 GAsyncResult *result,
11970 ImageLoadContext *load_context)
11972 GFileInputStream *input_stream;
11973 GOutputStream *output_stream;
11974 GError *error = NULL;
11976 /* Input stream might be NULL, so don't use cast macro. */
11977 input_stream = g_file_read_finish (file, result, &error);
11978 load_context->input_stream = (GInputStream *) input_stream;
11980 if (error) {
11981 image_load_context_free (load_context);
11982 return;
11985 /* Load the contents into a GMemoryOutputStream. */
11986 output_stream = g_memory_output_stream_new (
11987 NULL, 0, g_realloc, g_free);
11989 load_context->output_stream = output_stream;
11991 g_input_stream_read_async (
11992 load_context->input_stream,
11993 load_context->buffer,
11994 sizeof (load_context->buffer),
11995 G_PRIORITY_DEFAULT, NULL,
11996 (GAsyncReadyCallback) image_load_stream_read_cb,
11997 load_context);
12000 static void
12001 image_load_query_info_cb (GFile *file,
12002 GAsyncResult *result,
12003 ImageLoadContext *load_context)
12005 GFileInfo *file_info;
12006 GError *error = NULL;
12008 file_info = g_file_query_info_finish (file, result, &error);
12009 if (error) {
12010 image_load_context_free (load_context);
12011 return;
12014 load_context->content_type = g_file_info_get_content_type (file_info);
12015 load_context->total_num_bytes = g_file_info_get_size (file_info);
12016 load_context->filename = g_file_info_get_name (file_info);
12018 g_file_read_async (
12019 file, G_PRIORITY_DEFAULT,
12020 NULL, (GAsyncReadyCallback)
12021 image_load_file_read_cb, load_context);
12024 static void
12025 image_load_and_insert_async (EEditorPage *editor_page,
12026 const gchar *selector,
12027 const gchar *uri)
12029 ImageLoadContext *load_context;
12030 GFile *file;
12032 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
12033 g_return_if_fail (uri && *uri);
12035 file = g_file_new_for_uri (uri);
12036 g_return_if_fail (file != NULL);
12038 load_context = image_load_context_new (editor_page);
12039 load_context->file = file;
12040 if (selector && *selector)
12041 load_context->selector = g_strdup (selector);
12043 g_file_query_info_async (
12044 file, "standard::*",
12045 G_FILE_QUERY_INFO_NONE,G_PRIORITY_DEFAULT,
12046 NULL, (GAsyncReadyCallback)
12047 image_load_query_info_cb, load_context);
12050 void
12051 e_editor_dom_insert_image (EEditorPage *editor_page,
12052 const gchar *uri)
12054 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
12056 if (!e_editor_page_get_html_mode (editor_page))
12057 return;
12059 if (strstr (uri, ";base64,")) {
12060 if (g_str_has_prefix (uri, "data:"))
12061 e_editor_dom_insert_base64_image (editor_page, uri, "", "");
12062 if (strstr (uri, ";data")) {
12063 const gchar *base64_data = strstr (uri, ";") + 1;
12064 gchar *filename;
12065 glong filename_length;
12067 filename_length =
12068 g_utf8_strlen (uri, -1) -
12069 g_utf8_strlen (base64_data, -1) - 1;
12070 filename = g_strndup (uri, filename_length);
12072 e_editor_dom_insert_base64_image (editor_page, base64_data, filename, "");
12073 g_free (filename);
12075 } else
12076 image_load_and_insert_async (editor_page, NULL, uri);
12079 void
12080 e_editor_dom_replace_image_src (EEditorPage *editor_page,
12081 const gchar *selector,
12082 const gchar *uri)
12084 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
12086 if (strstr (uri, ";base64,")) {
12087 if (g_str_has_prefix (uri, "data:"))
12088 e_editor_dom_replace_base64_image_src (
12089 editor_page, selector, uri, "", "");
12090 if (strstr (uri, ";data")) {
12091 const gchar *base64_data = strstr (uri, ";") + 1;
12092 gchar *filename;
12093 glong filename_length;
12095 filename_length =
12096 g_utf8_strlen (uri, -1) -
12097 g_utf8_strlen (base64_data, -1) - 1;
12098 filename = g_strndup (uri, filename_length);
12100 e_editor_dom_replace_base64_image_src (
12101 editor_page, selector, base64_data, filename, "");
12102 g_free (filename);
12104 } else
12105 image_load_and_insert_async (editor_page, selector, uri);
12109 * e_html_editor_selection_unlink:
12110 * @selection: an #EEditorSelection
12112 * Removes any links (&lt;A&gt; elements) from current selection or at current
12113 * cursor position.
12115 void
12116 e_editor_dom_selection_unlink (EEditorPage *editor_page)
12118 WebKitDOMDocument *document;
12119 WebKitDOMDOMWindow *dom_window = NULL;
12120 WebKitDOMDOMSelection *dom_selection = NULL;
12121 WebKitDOMRange *range = NULL;
12122 WebKitDOMElement *link;
12123 EEditorUndoRedoManager *manager;
12124 gchar *text;
12126 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
12128 document = e_editor_page_get_document (editor_page);
12129 dom_window = webkit_dom_document_get_default_view (document);
12130 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
12132 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
12133 link = dom_node_find_parent_element (
12134 webkit_dom_range_get_start_container (range, NULL), "A");
12136 g_clear_object (&dom_selection);
12137 g_clear_object (&dom_window);
12139 if (!link) {
12140 WebKitDOMNode *node;
12142 /* get element that was clicked on */
12143 node = webkit_dom_range_get_common_ancestor_container (range, NULL);
12144 if (node && !WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node)) {
12145 link = dom_node_find_parent_element (node, "A");
12146 if (link && !WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (link)) {
12147 g_clear_object (&range);
12148 return;
12149 } else
12150 link = WEBKIT_DOM_ELEMENT (node);
12154 g_clear_object (&range);
12156 if (!link)
12157 return;
12159 manager = e_editor_page_get_undo_redo_manager (editor_page);
12160 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
12161 EEditorHistoryEvent *ev;
12162 WebKitDOMDocumentFragment *fragment;
12164 ev = g_new0 (EEditorHistoryEvent, 1);
12165 ev->type = HISTORY_REMOVE_LINK;
12167 e_editor_dom_selection_get_coordinates (editor_page,
12168 &ev->before.start.x,
12169 &ev->before.start.y,
12170 &ev->before.end.x,
12171 &ev->before.end.y);
12173 fragment = webkit_dom_document_create_document_fragment (document);
12174 webkit_dom_node_append_child (
12175 WEBKIT_DOM_NODE (fragment),
12176 webkit_dom_node_clone_node_with_error (WEBKIT_DOM_NODE (link), TRUE, NULL),
12177 NULL);
12178 ev->data.fragment = g_object_ref (fragment);
12180 e_editor_undo_redo_manager_insert_history_event (manager, ev);
12183 text = webkit_dom_html_element_get_inner_text (
12184 WEBKIT_DOM_HTML_ELEMENT (link));
12185 webkit_dom_element_set_outer_html (link, text, NULL);
12186 g_free (text);
12190 * e_html_editor_selection_create_link:
12191 * @document: a @WebKitDOMDocument
12192 * @uri: destination of the new link
12194 * Converts current selection into a link pointing to @url.
12196 void
12197 e_editor_dom_create_link (EEditorPage *editor_page,
12198 const gchar *uri)
12200 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
12201 g_return_if_fail (uri != NULL && *uri != '\0');
12203 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_CREATE_LINK, uri);
12206 static gint
12207 get_list_level (WebKitDOMNode *node)
12209 gint level = 0;
12211 while (node && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node)) {
12212 if (node_is_list (node))
12213 level++;
12214 node = webkit_dom_node_get_parent_node (node);
12217 return level;
12220 static void
12221 set_ordered_list_type_to_element (WebKitDOMElement *list,
12222 EContentEditorBlockFormat format)
12224 if (format == E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST)
12225 webkit_dom_element_remove_attribute (list, "type");
12226 else if (format == E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST_ALPHA)
12227 webkit_dom_element_set_attribute (list, "type", "A", NULL);
12228 else if (format == E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST_ROMAN)
12229 webkit_dom_element_set_attribute (list, "type", "I", NULL);
12232 static const gchar *
12233 get_css_alignment_value_class (EContentEditorAlignment alignment)
12235 if (alignment == E_CONTENT_EDITOR_ALIGNMENT_LEFT)
12236 return ""; /* Left is by default on ltr */
12238 if (alignment == E_CONTENT_EDITOR_ALIGNMENT_CENTER)
12239 return "-x-evo-align-center";
12241 if (alignment == E_CONTENT_EDITOR_ALIGNMENT_RIGHT)
12242 return "-x-evo-align-right";
12244 return "";
12248 * e_html_editor_selection_get_alignment:
12249 * @selection: #an EEditorSelection
12251 * Returns alignment of current paragraph
12253 * Returns: #EContentEditorAlignment
12255 static EContentEditorAlignment
12256 dom_get_alignment (EEditorPage *editor_page)
12258 WebKitDOMDocument *document;
12259 WebKitDOMCSSStyleDeclaration *style = NULL;
12260 WebKitDOMDOMWindow *dom_window = NULL;
12261 WebKitDOMElement *element;
12262 WebKitDOMNode *node;
12263 WebKitDOMRange *range = NULL;
12264 EContentEditorAlignment alignment;
12265 gchar *value;
12267 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), E_CONTENT_EDITOR_ALIGNMENT_LEFT);
12269 document = e_editor_page_get_document (editor_page);
12270 range = e_editor_dom_get_current_range (editor_page);
12271 if (!range)
12272 return E_CONTENT_EDITOR_ALIGNMENT_LEFT;
12274 node = webkit_dom_range_get_start_container (range, NULL);
12275 g_clear_object (&range);
12276 if (!node)
12277 return E_CONTENT_EDITOR_ALIGNMENT_LEFT;
12279 if (WEBKIT_DOM_IS_ELEMENT (node))
12280 element = WEBKIT_DOM_ELEMENT (node);
12281 else
12282 element = WEBKIT_DOM_ELEMENT (e_editor_dom_get_parent_block_node_from_child (node));
12284 if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (element)) {
12285 if (element_has_class (element, "-x-evo-align-right"))
12286 alignment = E_CONTENT_EDITOR_ALIGNMENT_RIGHT;
12287 else if (element_has_class (element, "-x-evo-align-center"))
12288 alignment = E_CONTENT_EDITOR_ALIGNMENT_CENTER;
12289 else
12290 alignment = E_CONTENT_EDITOR_ALIGNMENT_LEFT;
12292 return alignment;
12295 dom_window = webkit_dom_document_get_default_view (document);
12296 style = webkit_dom_dom_window_get_computed_style (dom_window, element, NULL);
12297 value = webkit_dom_css_style_declaration_get_property_value (style, "text-align");
12299 if (!value || !*value ||
12300 (g_ascii_strncasecmp (value, "left", 4) == 0)) {
12301 alignment = E_CONTENT_EDITOR_ALIGNMENT_LEFT;
12302 } else if (g_ascii_strncasecmp (value, "center", 6) == 0) {
12303 alignment = E_CONTENT_EDITOR_ALIGNMENT_CENTER;
12304 } else if (g_ascii_strncasecmp (value, "right", 5) == 0) {
12305 alignment = E_CONTENT_EDITOR_ALIGNMENT_RIGHT;
12306 } else {
12307 alignment = E_CONTENT_EDITOR_ALIGNMENT_LEFT;
12310 g_clear_object (&dom_window);
12311 g_clear_object (&style);
12312 g_free (value);
12314 return alignment;
12317 static gint
12318 set_word_wrap_length (EEditorPage *editor_page,
12319 gint user_word_wrap_length)
12321 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), 0);
12323 /* user_word_wrap_length < 0, set block width to word_wrap_length
12324 * user_word_wrap_length == 0, no width limit set,
12325 * user_word_wrap_length > 0, set width limit to given value */
12326 return (user_word_wrap_length < 0) ?
12327 e_editor_page_get_word_wrap_length (editor_page) : user_word_wrap_length;
12330 void
12331 e_editor_dom_set_paragraph_style (EEditorPage *editor_page,
12332 WebKitDOMElement *element,
12333 gint width,
12334 gint offset,
12335 const gchar *style_to_add)
12337 WebKitDOMNode *parent;
12338 gchar *style = NULL;
12339 gint word_wrap_length;
12341 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
12343 word_wrap_length = set_word_wrap_length (editor_page, width);
12344 webkit_dom_element_set_attribute (element, "data-evo-paragraph", "", NULL);
12346 /* Don't set the alignment for nodes as they are handled separately. */
12347 if (!node_is_list (WEBKIT_DOM_NODE (element))) {
12348 EContentEditorAlignment alignment;
12350 alignment = dom_get_alignment (editor_page);
12351 element_add_class (element, get_css_alignment_value_class (alignment));
12354 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
12355 /* Don't set the width limit to sub-blocks as the width limit is inhered
12356 * from its parents. */
12357 if (!e_editor_page_get_html_mode (editor_page) &&
12358 (!parent || WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent))) {
12359 style = g_strdup_printf (
12360 "width: %dch;%s%s",
12361 (word_wrap_length + offset),
12362 style_to_add && *style_to_add ? " " : "",
12363 style_to_add && *style_to_add ? style_to_add : "");
12364 } else {
12365 if (style_to_add && *style_to_add)
12366 style = g_strdup_printf ("%s", style_to_add);
12368 if (style) {
12369 webkit_dom_element_set_attribute (element, "style", style, NULL);
12370 g_free (style);
12374 static WebKitDOMElement *
12375 create_list_element (EEditorPage *editor_page,
12376 EContentEditorBlockFormat format,
12377 gint level,
12378 gboolean html_mode)
12380 WebKitDOMDocument *document;
12381 WebKitDOMElement *list;
12382 gboolean inserting_unordered_list;
12384 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
12386 document = e_editor_page_get_document (editor_page);
12387 inserting_unordered_list = format == E_CONTENT_EDITOR_BLOCK_FORMAT_UNORDERED_LIST;
12389 list = webkit_dom_document_create_element (
12390 document, inserting_unordered_list ? "UL" : "OL", NULL);
12392 if (!inserting_unordered_list)
12393 set_ordered_list_type_to_element (list, format);
12395 if (level >= 0 && !html_mode) {
12396 gint offset;
12398 offset = (level + 1) * SPACES_PER_LIST_LEVEL;
12400 offset += !inserting_unordered_list ?
12401 SPACES_ORDERED_LIST_FIRST_LEVEL - SPACES_PER_LIST_LEVEL: 0;
12403 e_editor_dom_set_paragraph_style (editor_page, list, -1, -offset, NULL);
12406 return list;
12409 static gboolean
12410 indent_list (EEditorPage *editor_page)
12412 WebKitDOMDocument *document;
12413 WebKitDOMElement *selection_start_marker, *selection_end_marker;
12414 WebKitDOMNode *item, *next_item;
12415 gboolean after_selection_end = FALSE;
12417 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
12419 document = e_editor_page_get_document (editor_page);
12420 selection_start_marker = webkit_dom_document_get_element_by_id (
12421 document, "-x-evo-selection-start-marker");
12422 selection_end_marker = webkit_dom_document_get_element_by_id (
12423 document, "-x-evo-selection-end-marker");
12425 item = e_editor_dom_get_parent_block_node_from_child (
12426 WEBKIT_DOM_NODE (selection_start_marker));
12428 if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (item)) {
12429 gboolean html_mode = e_editor_page_get_html_mode (editor_page);
12430 WebKitDOMElement *list;
12431 WebKitDOMNode *source_list = webkit_dom_node_get_parent_node (item);
12432 EContentEditorBlockFormat format;
12434 format = dom_get_list_format_from_node (source_list);
12436 list = create_list_element (
12437 editor_page, format, get_list_level (item), html_mode);
12439 element_add_class (list, "-x-evo-indented");
12441 webkit_dom_node_insert_before (
12442 source_list, WEBKIT_DOM_NODE (list), item, NULL);
12444 while (item && !after_selection_end) {
12445 after_selection_end = webkit_dom_node_contains (
12446 item, WEBKIT_DOM_NODE (selection_end_marker));
12448 next_item = webkit_dom_node_get_next_sibling (item);
12450 webkit_dom_node_append_child (
12451 WEBKIT_DOM_NODE (list), item, NULL);
12453 item = next_item;
12456 merge_lists_if_possible (WEBKIT_DOM_NODE (list));
12459 return after_selection_end;
12462 static void
12463 dom_set_indented_style (EEditorPage *editor_page,
12464 WebKitDOMElement *element,
12465 gint width)
12467 gchar *style;
12468 gint word_wrap_length;
12470 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
12472 word_wrap_length = set_word_wrap_length (editor_page, width);
12473 webkit_dom_element_set_class_name (element, "-x-evo-indented");
12475 if (e_editor_page_get_html_mode (editor_page) || word_wrap_length == 0) {
12476 style = g_strdup_printf ("margin-left: %dch;", SPACES_PER_INDENTATION);
12478 if (word_wrap_length != 0) {
12479 gchar *plain_text_style;
12481 plain_text_style = g_strdup_printf (
12482 "margin-left: %dch; word-wrap: normal; width: %dch;",
12483 SPACES_PER_INDENTATION, word_wrap_length);
12485 webkit_dom_element_set_attribute (
12486 element, "data-plain-text-style", plain_text_style, NULL);
12487 g_free (plain_text_style);
12489 } else {
12490 style = g_strdup_printf (
12491 "margin-left: %dch; word-wrap: normal; width: %dch;",
12492 SPACES_PER_INDENTATION, word_wrap_length);
12495 webkit_dom_element_set_attribute (element, "style", style, NULL);
12496 g_free (style);
12499 static WebKitDOMElement *
12500 dom_get_indented_element (EEditorPage *editor_page,
12501 gint width)
12503 WebKitDOMDocument *document;
12504 WebKitDOMElement *element;
12506 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
12508 document = e_editor_page_get_document (editor_page);
12509 element = webkit_dom_document_create_element (document, "DIV", NULL);
12510 dom_set_indented_style (editor_page, element, width);
12512 return element;
12515 static WebKitDOMNode *
12516 indent_block (EEditorPage *editor_page,
12517 WebKitDOMNode *block,
12518 gint width)
12520 WebKitDOMElement *element;
12521 WebKitDOMNode *sibling, *tmp;
12523 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
12525 sibling = webkit_dom_node_get_previous_sibling (block);
12526 if (WEBKIT_DOM_IS_ELEMENT (sibling) &&
12527 element_has_class (WEBKIT_DOM_ELEMENT (sibling), "-x-evo-indented")) {
12528 element = WEBKIT_DOM_ELEMENT (sibling);
12529 } else {
12530 element = dom_get_indented_element (editor_page, width);
12532 webkit_dom_node_insert_before (
12533 webkit_dom_node_get_parent_node (block),
12534 WEBKIT_DOM_NODE (element),
12535 block,
12536 NULL);
12539 /* Remove style and let the paragraph inherit it from parent */
12540 if (webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (block), "data-evo-paragraph"))
12541 webkit_dom_element_remove_attribute (
12542 WEBKIT_DOM_ELEMENT (block), "style");
12544 tmp = webkit_dom_node_append_child (
12545 WEBKIT_DOM_NODE (element),
12546 block,
12547 NULL);
12549 sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element));
12551 while (WEBKIT_DOM_IS_ELEMENT (sibling) &&
12552 element_has_class (WEBKIT_DOM_ELEMENT (sibling), "-x-evo-indented")) {
12553 WebKitDOMNode *next_sibling;
12554 WebKitDOMNode *child;
12556 next_sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (sibling));
12558 while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (sibling)))) {
12559 webkit_dom_node_append_child (
12560 WEBKIT_DOM_NODE (element),
12561 child,
12562 NULL);
12564 remove_node (sibling);
12565 sibling = next_sibling;
12568 return tmp;
12571 static WebKitDOMNode *
12572 get_list_item_node_from_child (WebKitDOMNode *child)
12574 WebKitDOMNode *parent = webkit_dom_node_get_parent_node (child);
12576 while (parent && !WEBKIT_DOM_IS_HTML_LI_ELEMENT (parent))
12577 parent = webkit_dom_node_get_parent_node (parent);
12579 return parent;
12582 static WebKitDOMNode *
12583 get_list_node_from_child (WebKitDOMNode *child)
12585 WebKitDOMNode *parent = get_list_item_node_from_child (child);
12587 return webkit_dom_node_get_parent_node (parent);
12590 static gboolean
12591 do_format_change_list_to_block (EEditorPage *editor_page,
12592 EContentEditorBlockFormat format,
12593 WebKitDOMNode *item,
12594 const gchar *value)
12596 WebKitDOMDocument *document;
12597 WebKitDOMElement *element, *selection_end;
12598 WebKitDOMNode *node, *source_list;
12599 gboolean after_end = FALSE;
12600 gint level;
12602 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
12604 document = e_editor_page_get_document (editor_page);
12605 selection_end = webkit_dom_document_get_element_by_id (
12606 document, "-x-evo-selection-end-marker");
12608 source_list = webkit_dom_node_get_parent_node (item);
12609 while (source_list) {
12610 WebKitDOMNode *parent;
12612 parent = webkit_dom_node_get_parent_node (source_list);
12613 if (node_is_list (parent))
12614 source_list = parent;
12615 else
12616 break;
12619 if (webkit_dom_node_contains (source_list, WEBKIT_DOM_NODE (selection_end)))
12620 source_list = split_list_into_two (item, -1);
12621 else {
12622 source_list = webkit_dom_node_get_next_sibling (source_list);
12625 /* Process all nodes that are in selection one by one */
12626 while (item && WEBKIT_DOM_IS_HTML_LI_ELEMENT (item)) {
12627 WebKitDOMNode *next_item;
12629 next_item = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (item));
12630 if (!next_item) {
12631 WebKitDOMNode *parent;
12632 WebKitDOMNode *tmp = item;
12634 while (tmp) {
12635 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (tmp));
12636 if (!node_is_list (parent))
12637 break;
12639 next_item = webkit_dom_node_get_next_sibling (parent);
12640 if (node_is_list (next_item)) {
12641 next_item = webkit_dom_node_get_first_child (next_item);
12642 break;
12643 } else if (next_item && !WEBKIT_DOM_IS_HTML_LI_ELEMENT (next_item)) {
12644 next_item = webkit_dom_node_get_next_sibling (next_item);
12645 break;
12646 } else if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (next_item)) {
12647 break;
12649 tmp = parent;
12651 } else if (node_is_list (next_item)) {
12652 next_item = webkit_dom_node_get_first_child (next_item);
12653 } else if (!WEBKIT_DOM_IS_HTML_LI_ELEMENT (next_item)) {
12654 next_item = webkit_dom_node_get_next_sibling (item);
12655 continue;
12658 if (!after_end) {
12659 after_end = webkit_dom_node_contains (item, WEBKIT_DOM_NODE (selection_end));
12661 level = get_indentation_level (WEBKIT_DOM_ELEMENT (item));
12663 if (format == E_CONTENT_EDITOR_BLOCK_FORMAT_PARAGRAPH) {
12664 element = e_editor_dom_get_paragraph_element (editor_page, -1, 0);
12665 } else
12666 element = webkit_dom_document_create_element (
12667 document, value, NULL);
12669 while ((node = webkit_dom_node_get_first_child (item)))
12670 webkit_dom_node_append_child (
12671 WEBKIT_DOM_NODE (element), node, NULL);
12673 webkit_dom_node_insert_before (
12674 webkit_dom_node_get_parent_node (source_list),
12675 WEBKIT_DOM_NODE (element),
12676 source_list,
12677 NULL);
12679 if (level > 0) {
12680 gint final_width = 0;
12682 node = WEBKIT_DOM_NODE (element);
12684 if (webkit_dom_element_has_attribute (element, "data-evo-paragraph"))
12685 final_width = e_editor_page_get_word_wrap_length (editor_page) -
12686 SPACES_PER_INDENTATION * level;
12688 while (level--)
12689 node = indent_block (editor_page, node, final_width);
12692 e_editor_dom_remove_node_and_parents_if_empty (item);
12693 } else
12694 break;
12696 item = next_item;
12699 remove_node_if_empty (source_list);
12701 return after_end;
12704 static void
12705 format_change_list_to_block (EEditorPage *editor_page,
12706 EContentEditorBlockFormat format,
12707 const gchar *value)
12709 WebKitDOMDocument *document;
12710 WebKitDOMElement *selection_start;
12711 WebKitDOMNode *item;
12713 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
12715 document = e_editor_page_get_document (editor_page);
12717 selection_start = webkit_dom_document_get_element_by_id (
12718 document, "-x-evo-selection-start-marker");
12720 item = get_list_item_node_from_child (WEBKIT_DOM_NODE (selection_start));
12722 do_format_change_list_to_block (editor_page, format, item, value);
12725 static WebKitDOMNode *
12726 get_parent_indented_block (WebKitDOMNode *node)
12728 WebKitDOMNode *parent, *block = NULL;
12730 parent = webkit_dom_node_get_parent_node (node);
12731 if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-indented"))
12732 block = parent;
12734 while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
12735 if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-indented"))
12736 block = parent;
12737 parent = webkit_dom_node_get_parent_node (parent);
12740 return block;
12743 static WebKitDOMElement*
12744 get_element_for_inspection (WebKitDOMRange *range)
12746 WebKitDOMNode *node;
12748 node = webkit_dom_range_get_end_container (range, NULL);
12749 /* No selection or whole body selected */
12750 if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node))
12751 return NULL;
12753 return WEBKIT_DOM_ELEMENT (get_parent_indented_block (node));
12756 static EContentEditorAlignment
12757 dom_get_alignment_from_node (WebKitDOMNode *node)
12759 EContentEditorAlignment alignment;
12760 gchar *value;
12761 WebKitDOMCSSStyleDeclaration *style = NULL;
12763 style = webkit_dom_element_get_style (WEBKIT_DOM_ELEMENT (node));
12764 value = webkit_dom_css_style_declaration_get_property_value (style, "text-align");
12766 if (!value || !*value ||
12767 (g_ascii_strncasecmp (value, "left", 4) == 0)) {
12768 alignment = E_CONTENT_EDITOR_ALIGNMENT_LEFT;
12769 } else if (g_ascii_strncasecmp (value, "center", 6) == 0) {
12770 alignment = E_CONTENT_EDITOR_ALIGNMENT_CENTER;
12771 } else if (g_ascii_strncasecmp (value, "right", 5) == 0) {
12772 alignment = E_CONTENT_EDITOR_ALIGNMENT_RIGHT;
12773 } else {
12774 alignment = E_CONTENT_EDITOR_ALIGNMENT_LEFT;
12777 g_clear_object (&style);
12778 g_free (value);
12780 return alignment;
12784 * e_html_editor_selection_indent:
12785 * @selection: an #EEditorSelection
12787 * Indents current paragraph by one level.
12789 void
12790 e_editor_dom_selection_indent (EEditorPage *editor_page)
12792 WebKitDOMDocument *document;
12793 WebKitDOMElement *selection_start_marker, *selection_end_marker;
12794 WebKitDOMNode *block;
12795 EEditorHistoryEvent *ev = NULL;
12796 EEditorUndoRedoManager *manager;
12797 gboolean after_selection_start = FALSE, after_selection_end = FALSE;
12799 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
12801 document = e_editor_page_get_document (editor_page);
12802 e_editor_dom_selection_save (editor_page);
12804 manager = e_editor_page_get_undo_redo_manager (editor_page);
12806 selection_start_marker = webkit_dom_document_get_element_by_id (
12807 document, "-x-evo-selection-start-marker");
12808 selection_end_marker = webkit_dom_document_get_element_by_id (
12809 document, "-x-evo-selection-end-marker");
12811 /* If the selection was not saved, move it into the first child of body */
12812 if (!selection_start_marker || !selection_end_marker) {
12813 WebKitDOMHTMLElement *body;
12814 WebKitDOMNode *child;
12816 body = webkit_dom_document_get_body (document);
12817 child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
12819 dom_add_selection_markers_into_element_start (
12820 document,
12821 WEBKIT_DOM_ELEMENT (child),
12822 &selection_start_marker,
12823 &selection_end_marker);
12826 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
12827 ev = g_new0 (EEditorHistoryEvent, 1);
12828 ev->type = HISTORY_INDENT;
12830 e_editor_dom_selection_get_coordinates (editor_page,
12831 &ev->before.start.x,
12832 &ev->before.start.y,
12833 &ev->before.end.x,
12834 &ev->before.end.y);
12836 ev->data.style.from = 1;
12837 ev->data.style.to = 1;
12840 block = get_parent_indented_block (
12841 WEBKIT_DOM_NODE (selection_start_marker));
12842 if (!block)
12843 block = e_editor_dom_get_parent_block_node_from_child (
12844 WEBKIT_DOM_NODE (selection_start_marker));
12846 while (block && !after_selection_end) {
12847 gint ii, length, level, word_wrap_length, final_width = 0;
12848 WebKitDOMNode *next_block;
12849 WebKitDOMNodeList *list = NULL;
12851 word_wrap_length = e_editor_page_get_word_wrap_length (editor_page);
12853 next_block = webkit_dom_node_get_next_sibling (block);
12855 list = webkit_dom_element_query_selector_all (
12856 WEBKIT_DOM_ELEMENT (block),
12857 ".-x-evo-indented > *:not(.-x-evo-indented):not(li)",
12858 NULL);
12860 after_selection_end = webkit_dom_node_contains (
12861 block, WEBKIT_DOM_NODE (selection_end_marker));
12863 length = webkit_dom_node_list_get_length (list);
12864 if (length == 0 && node_is_list_or_item (block)) {
12865 after_selection_end = indent_list (editor_page);
12866 goto next;
12869 if (length == 0) {
12870 if (!after_selection_start) {
12871 after_selection_start = webkit_dom_node_contains (
12872 block, WEBKIT_DOM_NODE (selection_start_marker));
12873 if (!after_selection_start)
12874 goto next;
12877 if (webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (block), "data-evo-paragraph")) {
12878 level = get_indentation_level (WEBKIT_DOM_ELEMENT (block));
12880 final_width = word_wrap_length - SPACES_PER_INDENTATION * (level + 1);
12881 if (final_width < MINIMAL_PARAGRAPH_WIDTH &&
12882 !e_editor_page_get_html_mode (editor_page))
12883 goto next;
12886 indent_block (editor_page, block, final_width);
12888 if (after_selection_end)
12889 goto next;
12892 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
12893 WebKitDOMNode *block_to_process;
12895 block_to_process = webkit_dom_node_list_item (list, ii);
12897 after_selection_end = webkit_dom_node_contains (
12898 block_to_process, WEBKIT_DOM_NODE (selection_end_marker));
12900 if (!after_selection_start) {
12901 after_selection_start = webkit_dom_node_contains (
12902 block_to_process,
12903 WEBKIT_DOM_NODE (selection_start_marker));
12904 if (!after_selection_start)
12905 continue;
12908 if (webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (block_to_process), "data-evo-paragraph")) {
12909 level = get_indentation_level (
12910 WEBKIT_DOM_ELEMENT (block_to_process));
12912 final_width = word_wrap_length - SPACES_PER_INDENTATION * (level + 1);
12913 if (final_width < MINIMAL_PARAGRAPH_WIDTH &&
12914 !e_editor_page_get_html_mode (editor_page))
12915 continue;
12918 indent_block (editor_page, block_to_process, final_width);
12920 if (after_selection_end)
12921 break;
12924 next:
12925 g_clear_object (&list);
12927 if (!after_selection_end)
12928 block = next_block;
12931 if (ev) {
12932 e_editor_dom_selection_get_coordinates (editor_page,
12933 &ev->after.start.x,
12934 &ev->after.start.y,
12935 &ev->after.end.x,
12936 &ev->after.end.y);
12937 e_editor_undo_redo_manager_insert_history_event (manager, ev);
12940 e_editor_dom_selection_restore (editor_page);
12941 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
12942 e_editor_page_emit_content_changed (editor_page);
12945 static void
12946 unindent_list (WebKitDOMDocument *document)
12948 gboolean after = FALSE;
12949 WebKitDOMElement *new_list;
12950 WebKitDOMElement *selection_start_marker, *selection_end_marker;
12951 WebKitDOMNode *source_list, *source_list_clone, *current_list, *item;
12952 WebKitDOMNode *prev_item;
12954 selection_start_marker = webkit_dom_document_get_element_by_id (
12955 document, "-x-evo-selection-start-marker");
12956 selection_end_marker = webkit_dom_document_get_element_by_id (
12957 document, "-x-evo-selection-end-marker");
12959 if (!selection_start_marker || !selection_end_marker)
12960 return;
12962 /* Copy elements from previous block to list */
12963 item = e_editor_dom_get_parent_block_node_from_child (
12964 WEBKIT_DOM_NODE (selection_start_marker));
12965 source_list = webkit_dom_node_get_parent_node (item);
12966 new_list = WEBKIT_DOM_ELEMENT (
12967 webkit_dom_node_clone_node_with_error (source_list, FALSE, NULL));
12968 current_list = source_list;
12969 source_list_clone = webkit_dom_node_clone_node_with_error (source_list, FALSE, NULL);
12971 webkit_dom_node_insert_before (
12972 webkit_dom_node_get_parent_node (source_list),
12973 WEBKIT_DOM_NODE (source_list_clone),
12974 webkit_dom_node_get_next_sibling (source_list),
12975 NULL);
12977 if (element_has_class (WEBKIT_DOM_ELEMENT (source_list), "-x-evo-indented"))
12978 element_add_class (WEBKIT_DOM_ELEMENT (new_list), "-x-evo-indented");
12980 prev_item = source_list;
12982 while (item) {
12983 WebKitDOMNode *next_item = webkit_dom_node_get_next_sibling (item);
12985 if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (item)) {
12986 if (after)
12987 prev_item = webkit_dom_node_append_child (
12988 source_list_clone, WEBKIT_DOM_NODE (item), NULL);
12989 else
12990 prev_item = webkit_dom_node_insert_before (
12991 webkit_dom_node_get_parent_node (prev_item),
12992 item,
12993 webkit_dom_node_get_next_sibling (prev_item),
12994 NULL);
12997 if (webkit_dom_node_contains (item, WEBKIT_DOM_NODE (selection_end_marker)))
12998 after = TRUE;
13000 if (!next_item) {
13001 if (after)
13002 break;
13004 current_list = webkit_dom_node_get_next_sibling (current_list);
13005 next_item = webkit_dom_node_get_first_child (current_list);
13007 item = next_item;
13010 remove_node_if_empty (source_list_clone);
13011 remove_node_if_empty (source_list);
13014 static void
13015 unindent_block (EEditorPage *editor_page,
13016 WebKitDOMNode *block)
13018 WebKitDOMElement *element;
13019 WebKitDOMElement *prev_blockquote = NULL, *next_blockquote = NULL;
13020 WebKitDOMNode *block_to_process, *node_clone = NULL, *child;
13021 EContentEditorAlignment alignment;
13022 gboolean before_node = TRUE;
13023 gint word_wrap_length, level, width;
13025 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
13027 block_to_process = block;
13029 alignment = dom_get_alignment_from_node (block_to_process);
13030 element = webkit_dom_node_get_parent_element (block_to_process);
13032 if (!WEBKIT_DOM_IS_HTML_DIV_ELEMENT (element) &&
13033 !element_has_class (element, "-x-evo-indented"))
13034 return;
13036 element_add_class (WEBKIT_DOM_ELEMENT (block_to_process), "-x-evo-to-unindent");
13038 level = get_indentation_level (element);
13039 word_wrap_length = e_editor_page_get_word_wrap_length (editor_page);
13040 width = word_wrap_length - SPACES_PER_INDENTATION * level;
13042 /* Look if we have previous siblings, if so, we have to
13043 * create new blockquote that will include them */
13044 if (webkit_dom_node_get_previous_sibling (block_to_process))
13045 prev_blockquote = dom_get_indented_element (editor_page, width);
13047 /* Look if we have next siblings, if so, we have to
13048 * create new blockquote that will include them */
13049 if (webkit_dom_node_get_next_sibling (block_to_process))
13050 next_blockquote = dom_get_indented_element (editor_page, width);
13052 /* Copy nodes that are before / after the element that we want to unindent */
13053 while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)))) {
13054 if (webkit_dom_node_is_equal_node (child, block_to_process)) {
13055 before_node = FALSE;
13056 node_clone = webkit_dom_node_clone_node_with_error (child, TRUE, NULL);
13057 remove_node (child);
13058 continue;
13061 webkit_dom_node_append_child (
13062 before_node ?
13063 WEBKIT_DOM_NODE (prev_blockquote) :
13064 WEBKIT_DOM_NODE (next_blockquote),
13065 child,
13066 NULL);
13069 if (node_clone) {
13070 element_remove_class (WEBKIT_DOM_ELEMENT (node_clone), "-x-evo-to-unindent");
13072 /* Insert blockqoute with nodes that were before the element that we want to unindent */
13073 if (prev_blockquote) {
13074 if (webkit_dom_node_has_child_nodes (WEBKIT_DOM_NODE (prev_blockquote))) {
13075 webkit_dom_node_insert_before (
13076 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
13077 WEBKIT_DOM_NODE (prev_blockquote),
13078 WEBKIT_DOM_NODE (element),
13079 NULL);
13083 if (level == 1 && webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (node_clone), "data-evo-paragraph")) {
13084 e_editor_dom_set_paragraph_style (
13085 editor_page, WEBKIT_DOM_ELEMENT (node_clone), word_wrap_length, 0, NULL);
13086 element_add_class (
13087 WEBKIT_DOM_ELEMENT (node_clone),
13088 get_css_alignment_value_class (alignment));
13091 /* Insert the unindented element */
13092 webkit_dom_node_insert_before (
13093 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
13094 node_clone,
13095 WEBKIT_DOM_NODE (element),
13096 NULL);
13097 } else {
13098 g_warn_if_reached ();
13101 /* Insert blockqoute with nodes that were after the element that we want to unindent */
13102 if (next_blockquote) {
13103 if (webkit_dom_node_has_child_nodes (WEBKIT_DOM_NODE (next_blockquote))) {
13104 webkit_dom_node_insert_before (
13105 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
13106 WEBKIT_DOM_NODE (next_blockquote),
13107 WEBKIT_DOM_NODE (element),
13108 NULL);
13112 /* Remove old blockquote */
13113 remove_node (WEBKIT_DOM_NODE (element));
13117 * dom_unindent:
13118 * @selection: an #EEditorSelection
13120 * Unindents current paragraph by one level.
13122 void
13123 e_editor_dom_selection_unindent (EEditorPage *editor_page)
13125 WebKitDOMDocument *document;
13126 WebKitDOMElement *selection_start_marker, *selection_end_marker;
13127 WebKitDOMNode *block;
13128 EEditorHistoryEvent *ev = NULL;
13129 EEditorUndoRedoManager *manager;
13130 gboolean after_selection_start = FALSE, after_selection_end = FALSE;
13132 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
13134 document = e_editor_page_get_document (editor_page);
13135 e_editor_dom_selection_save (editor_page);
13137 selection_start_marker = webkit_dom_document_get_element_by_id (
13138 document, "-x-evo-selection-start-marker");
13139 selection_end_marker = webkit_dom_document_get_element_by_id (
13140 document, "-x-evo-selection-end-marker");
13142 /* If the selection was not saved, move it into the first child of body */
13143 if (!selection_start_marker || !selection_end_marker) {
13144 WebKitDOMHTMLElement *body;
13145 WebKitDOMNode *child;
13147 body = webkit_dom_document_get_body (document);
13148 child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
13150 dom_add_selection_markers_into_element_start (
13151 document,
13152 WEBKIT_DOM_ELEMENT (child),
13153 &selection_start_marker,
13154 &selection_end_marker);
13157 manager = e_editor_page_get_undo_redo_manager (editor_page);
13158 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
13159 ev = g_new0 (EEditorHistoryEvent, 1);
13160 ev->type = HISTORY_INDENT;
13162 e_editor_dom_selection_get_coordinates (editor_page,
13163 &ev->before.start.x,
13164 &ev->before.start.y,
13165 &ev->before.end.x,
13166 &ev->before.end.y);
13169 block = get_parent_indented_block (
13170 WEBKIT_DOM_NODE (selection_start_marker));
13171 if (!block)
13172 block = e_editor_dom_get_parent_block_node_from_child (
13173 WEBKIT_DOM_NODE (selection_start_marker));
13175 while (block && !after_selection_end) {
13176 gint ii, length;
13177 WebKitDOMNode *next_block;
13178 WebKitDOMNodeList *list = NULL;
13180 next_block = webkit_dom_node_get_next_sibling (block);
13182 list = webkit_dom_element_query_selector_all (
13183 WEBKIT_DOM_ELEMENT (block),
13184 ".-x-evo-indented > *:not(.-x-evo-indented):not(li)",
13185 NULL);
13187 after_selection_end = webkit_dom_node_contains (
13188 block, WEBKIT_DOM_NODE (selection_end_marker));
13190 length = webkit_dom_node_list_get_length (list);
13191 if (length == 0 && node_is_list_or_item (block)) {
13192 unindent_list (document);
13193 goto next;
13196 if (length == 0) {
13197 if (!after_selection_start) {
13198 after_selection_start = webkit_dom_node_contains (
13199 block, WEBKIT_DOM_NODE (selection_start_marker));
13200 if (!after_selection_start)
13201 goto next;
13204 unindent_block (editor_page, block);
13206 if (after_selection_end)
13207 goto next;
13210 for (ii = 0; ii < length; ii++) {
13211 WebKitDOMNode *block_to_process;
13213 block_to_process = webkit_dom_node_list_item (list, ii);
13215 after_selection_end = webkit_dom_node_contains (
13216 block_to_process,
13217 WEBKIT_DOM_NODE (selection_end_marker));
13219 if (!after_selection_start) {
13220 after_selection_start = webkit_dom_node_contains (
13221 block_to_process,
13222 WEBKIT_DOM_NODE (selection_start_marker));
13223 if (!after_selection_start)
13224 continue;
13227 unindent_block (editor_page, block_to_process);
13229 if (after_selection_end)
13230 break;
13232 next:
13233 g_clear_object (&list);
13234 block = next_block;
13237 if (ev) {
13238 e_editor_dom_selection_get_coordinates (editor_page,
13239 &ev->after.start.x,
13240 &ev->after.start.y,
13241 &ev->after.end.x,
13242 &ev->after.end.y);
13243 e_editor_undo_redo_manager_insert_history_event (manager, ev);
13246 e_editor_dom_selection_restore (editor_page);
13248 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
13249 e_editor_page_emit_content_changed (editor_page);
13252 static void
13253 dom_insert_selection_point (WebKitDOMNode *container,
13254 glong offset,
13255 WebKitDOMElement *selection_point)
13257 WebKitDOMNode *parent;
13259 parent = webkit_dom_node_get_parent_node (container);
13261 if (WEBKIT_DOM_IS_TEXT (container) ||
13262 WEBKIT_DOM_IS_COMMENT (container) ||
13263 WEBKIT_DOM_IS_CHARACTER_DATA (container)) {
13264 if (offset != 0) {
13265 WebKitDOMText *split_text;
13267 split_text = webkit_dom_text_split_text (
13268 WEBKIT_DOM_TEXT (container), offset, NULL);
13269 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (split_text));
13271 webkit_dom_node_insert_before (
13272 parent,
13273 WEBKIT_DOM_NODE (selection_point),
13274 WEBKIT_DOM_NODE (split_text),
13275 NULL);
13276 } else {
13277 webkit_dom_node_insert_before (
13278 parent,
13279 WEBKIT_DOM_NODE (selection_point),
13280 container,
13281 NULL);
13283 } else {
13284 gulong child_element_count = 0;
13286 child_element_count =
13287 webkit_dom_element_get_child_element_count (
13288 WEBKIT_DOM_ELEMENT (container));
13290 if (offset == 0) {
13291 /* Selection point is on the beginning of container */
13292 webkit_dom_node_insert_before (
13293 container,
13294 WEBKIT_DOM_NODE (selection_point),
13295 webkit_dom_node_get_first_child (container),
13296 NULL);
13297 } else if (offset != 0 && (offset == child_element_count)) {
13298 /* Selection point is on the end of container */
13299 webkit_dom_node_append_child (
13300 container, WEBKIT_DOM_NODE (selection_point), NULL);
13301 } else {
13302 WebKitDOMElement *child;
13303 gint ii = 0;
13305 child = webkit_dom_element_get_first_element_child (WEBKIT_DOM_ELEMENT (container));
13306 for (ii = 1; ii < child_element_count; ii++)
13307 child = webkit_dom_element_get_next_element_sibling (child);
13309 webkit_dom_node_insert_before (
13310 container,
13311 WEBKIT_DOM_NODE (selection_point),
13312 WEBKIT_DOM_NODE (child),
13313 NULL);
13317 webkit_dom_node_normalize (parent);
13321 * e_html_editor_selection_save:
13322 * @selection: an #EEditorSelection
13324 * Saves current cursor position or current selection range. The selection can
13325 * be later restored by calling e_html_editor_selection_restore().
13327 * Note that calling e_html_editor_selection_save() overwrites previously saved
13328 * position.
13330 * Note that this method inserts special markings into the HTML code that are
13331 * used to later restore the selection. It can happen that by deleting some
13332 * segments of the document some of the markings are deleted too. In that case
13333 * restoring the selection by e_html_editor_selection_restore() can fail. Also by
13334 * moving text segments (Cut & Paste) can result in moving the markings
13335 * elsewhere, thus e_html_editor_selection_restore() will restore the selection
13336 * incorrectly.
13338 * It is recommended to use this method only when you are not planning to make
13339 * bigger changes to content or structure of the document (formatting changes
13340 * are usually OK).
13342 void
13343 e_editor_dom_selection_save (EEditorPage *editor_page)
13345 WebKitDOMDocument *document;
13346 WebKitDOMDOMWindow *dom_window = NULL;
13347 WebKitDOMDOMSelection *dom_selection = NULL;
13348 WebKitDOMRange *range = NULL;
13349 WebKitDOMNode *container;
13350 WebKitDOMNode *anchor;
13351 WebKitDOMElement *start_marker = NULL, *end_marker = NULL;
13352 gboolean collapsed = FALSE;
13353 glong offset, anchor_offset;
13355 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
13357 document = e_editor_page_get_document (editor_page);
13359 /* First remove all markers (if present) */
13360 dom_remove_selection_markers (document);
13362 dom_window = webkit_dom_document_get_default_view (document);
13363 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
13364 g_clear_object (&dom_window);
13366 if (webkit_dom_dom_selection_get_range_count (dom_selection) < 1) {
13367 g_clear_object (&dom_selection);
13368 return;
13371 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
13372 if (!range) {
13373 g_clear_object (&dom_selection);
13374 return;
13377 anchor = webkit_dom_dom_selection_get_anchor_node (dom_selection);
13378 anchor_offset = webkit_dom_dom_selection_get_anchor_offset (dom_selection);
13380 collapsed = webkit_dom_range_get_collapsed (range, NULL);
13381 start_marker = dom_create_selection_marker (document, TRUE);
13383 container = webkit_dom_range_get_start_container (range, NULL);
13384 offset = webkit_dom_range_get_start_offset (range, NULL);
13386 if (webkit_dom_node_is_same_node (anchor, container) && offset == anchor_offset)
13387 webkit_dom_element_set_attribute (start_marker, "data-anchor", "", NULL);
13389 dom_insert_selection_point (container, offset, start_marker);
13391 end_marker = dom_create_selection_marker (document, FALSE);
13393 if (collapsed) {
13394 webkit_dom_node_insert_before (
13395 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (start_marker)),
13396 WEBKIT_DOM_NODE (end_marker),
13397 webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (start_marker)),
13398 NULL);
13399 goto out;
13402 container = webkit_dom_range_get_end_container (range, NULL);
13403 offset = webkit_dom_range_get_end_offset (range, NULL);
13405 if (webkit_dom_node_is_same_node (anchor, container) && offset == anchor_offset)
13406 webkit_dom_element_set_attribute (end_marker, "data-anchor", "", NULL);
13408 dom_insert_selection_point (container, offset, end_marker);
13410 if (!collapsed) {
13411 if (start_marker && end_marker) {
13412 webkit_dom_range_set_start_after (range, WEBKIT_DOM_NODE (start_marker), NULL);
13413 webkit_dom_range_set_end_before (range, WEBKIT_DOM_NODE (end_marker), NULL);
13414 } else {
13415 g_warn_if_reached ();
13418 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
13419 webkit_dom_dom_selection_add_range (dom_selection, range);
13421 out:
13422 g_clear_object (&range);
13423 g_clear_object (&dom_selection);
13426 gboolean
13427 e_editor_dom_is_selection_position_node (WebKitDOMNode *node)
13429 WebKitDOMElement *element;
13431 if (!node || !WEBKIT_DOM_IS_ELEMENT (node))
13432 return FALSE;
13434 element = WEBKIT_DOM_ELEMENT (node);
13436 return element_has_id (element, "-x-evo-selection-start-marker") ||
13437 element_has_id (element, "-x-evo-selection-end-marker");
13441 * e_html_editor_selection_restore:
13442 * @selection: an #EEditorSelection
13444 * Restores cursor position or selection range that was saved by
13445 * e_html_editor_selection_save().
13447 * Note that calling this function without calling e_html_editor_selection_save()
13448 * before is a programming error and the behavior is undefined.
13450 void
13451 e_editor_dom_selection_restore (EEditorPage *editor_page)
13453 WebKitDOMDocument *document;
13454 WebKitDOMElement *marker;
13455 WebKitDOMNode *selection_start_marker, *selection_end_marker;
13456 WebKitDOMNode *parent_start, *parent_end, *anchor;
13457 WebKitDOMRange *range = NULL;
13458 WebKitDOMDOMSelection *dom_selection = NULL;
13459 WebKitDOMDOMWindow *dom_window = NULL;
13460 gboolean start_is_anchor = FALSE;
13461 glong offset;
13463 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
13465 document = e_editor_page_get_document (editor_page);
13466 dom_window = webkit_dom_document_get_default_view (document);
13467 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
13468 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
13469 g_clear_object (&dom_window);
13470 if (!range) {
13471 WebKitDOMHTMLElement *body;
13473 range = webkit_dom_document_create_range (document);
13474 body = webkit_dom_document_get_body (document);
13476 webkit_dom_range_select_node_contents (range, WEBKIT_DOM_NODE (body), NULL);
13477 webkit_dom_range_collapse (range, TRUE, NULL);
13478 webkit_dom_dom_selection_add_range (dom_selection, range);
13481 selection_start_marker = webkit_dom_range_get_start_container (range, NULL);
13482 if (selection_start_marker) {
13483 gboolean ok = FALSE;
13484 selection_start_marker =
13485 webkit_dom_node_get_next_sibling (selection_start_marker);
13487 ok = e_editor_dom_is_selection_position_node (selection_start_marker);
13489 if (ok) {
13490 ok = FALSE;
13491 if (webkit_dom_range_get_collapsed (range, NULL)) {
13492 selection_end_marker = webkit_dom_node_get_next_sibling (
13493 selection_start_marker);
13495 ok = e_editor_dom_is_selection_position_node (selection_end_marker);
13496 if (ok) {
13497 WebKitDOMNode *next_sibling;
13499 next_sibling = webkit_dom_node_get_next_sibling (selection_end_marker);
13501 if (next_sibling && !WEBKIT_DOM_IS_HTML_BR_ELEMENT (next_sibling)) {
13502 parent_start = webkit_dom_node_get_parent_node (selection_end_marker);
13504 remove_node (selection_start_marker);
13505 remove_node (selection_end_marker);
13507 webkit_dom_node_normalize (parent_start);
13508 g_clear_object (&range);
13509 g_clear_object (&dom_selection);
13510 return;
13517 g_clear_object (&range);
13518 range = webkit_dom_document_create_range (document);
13519 if (!range) {
13520 g_clear_object (&dom_selection);
13521 return;
13524 marker = webkit_dom_document_get_element_by_id (
13525 document, "-x-evo-selection-start-marker");
13526 if (!marker) {
13527 marker = webkit_dom_document_get_element_by_id (
13528 document, "-x-evo-selection-end-marker");
13529 if (marker)
13530 remove_node (WEBKIT_DOM_NODE (marker));
13531 g_clear_object (&dom_selection);
13532 g_clear_object (&range);
13533 return;
13536 start_is_anchor = webkit_dom_element_has_attribute (marker, "data-anchor");
13537 parent_start = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (marker));
13539 webkit_dom_range_set_start_after (range, WEBKIT_DOM_NODE (marker), NULL);
13540 remove_node (WEBKIT_DOM_NODE (marker));
13542 marker = webkit_dom_document_get_element_by_id (
13543 document, "-x-evo-selection-end-marker");
13544 if (!marker) {
13545 marker = webkit_dom_document_get_element_by_id (
13546 document, "-x-evo-selection-start-marker");
13547 if (marker)
13548 remove_node (WEBKIT_DOM_NODE (marker));
13549 g_clear_object (&dom_selection);
13550 g_clear_object (&range);
13551 return;
13554 parent_end = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (marker));
13556 webkit_dom_range_set_end_before (range, WEBKIT_DOM_NODE (marker), NULL);
13557 remove_node (WEBKIT_DOM_NODE (marker));
13559 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
13560 if (webkit_dom_node_is_same_node (parent_start, parent_end))
13561 webkit_dom_node_normalize (parent_start);
13562 else {
13563 webkit_dom_node_normalize (parent_start);
13564 webkit_dom_node_normalize (parent_end);
13567 if (start_is_anchor) {
13568 anchor = webkit_dom_range_get_end_container (range, NULL);
13569 offset = webkit_dom_range_get_end_offset (range, NULL);
13571 webkit_dom_range_collapse (range, TRUE, NULL);
13572 } else {
13573 anchor = webkit_dom_range_get_start_container (range, NULL);
13574 offset = webkit_dom_range_get_start_offset (range, NULL);
13576 webkit_dom_range_collapse (range, FALSE, NULL);
13578 webkit_dom_dom_selection_add_range (dom_selection, range);
13579 webkit_dom_dom_selection_extend (dom_selection, anchor, offset, NULL);
13581 g_clear_object (&dom_selection);
13582 g_clear_object (&range);
13585 static gint
13586 find_where_to_break_line (WebKitDOMCharacterData *node,
13587 gint max_length)
13589 gboolean last_break_position_is_dash = FALSE;
13590 gchar *str, *text_start;
13591 gunichar uc;
13592 gint pos = 1, last_break_position = 0, ret_val = 0;
13594 text_start = webkit_dom_character_data_get_data (node);
13596 str = text_start;
13597 do {
13598 uc = g_utf8_get_char (str);
13599 if (!uc) {
13600 ret_val = pos <= max_length ? pos : last_break_position > 0 ? last_break_position - 1 : 0;
13601 goto out;
13604 if ((g_unichar_isspace (uc) && !(g_unichar_break_type (uc) == G_UNICODE_BREAK_NON_BREAKING_GLUE)) ||
13605 *str == '-') {
13606 if ((last_break_position_is_dash = *str == '-')) {
13607 /* There was no space before the dash */
13608 if (pos - 1 != last_break_position) {
13609 gchar *rest;
13611 rest = g_utf8_next_char (str);
13612 if (rest && *rest) {
13613 gunichar next_char;
13615 /* There is no space after the dash */
13616 next_char = g_utf8_get_char (rest);
13617 if (g_unichar_isspace (next_char))
13618 last_break_position_is_dash = FALSE;
13619 else
13620 last_break_position = pos;
13621 } else
13622 last_break_position_is_dash = FALSE;
13623 } else
13624 last_break_position_is_dash = FALSE;
13625 } else
13626 last_break_position = pos;
13629 if ((pos == max_length)) {
13630 /* Look one character after the limit to check if there
13631 * is a space (skip dash) that we are allowed to break at, if so
13632 * break it there. */
13633 if (*str) {
13634 str = g_utf8_next_char (str);
13635 uc = g_utf8_get_char (str);
13637 if ((g_unichar_isspace (uc) &&
13638 !(g_unichar_break_type (uc) == G_UNICODE_BREAK_NON_BREAKING_GLUE)))
13639 last_break_position = ++pos;
13641 break;
13644 pos++;
13645 str = g_utf8_next_char (str);
13646 } while (*str);
13648 if (last_break_position != 0)
13649 ret_val = last_break_position - 1;
13650 out:
13651 g_free (text_start);
13653 /* Always break after the dash character. */
13654 if (last_break_position_is_dash)
13655 ret_val++;
13657 /* No character to break at is found. We should split at max_length, but
13658 * we will leave the decision on caller as it depends on context. */
13659 if (ret_val == 0 && last_break_position == 0)
13660 ret_val = -1;
13662 return ret_val;
13666 * e_html_editor_selection_is_collapsed:
13667 * @selection: an #EEditorSelection
13669 * Returns if selection is collapsed.
13671 * Returns: Whether the selection is collapsed (just caret) or not (someting is selected).
13673 gboolean
13674 e_editor_dom_selection_is_collapsed (EEditorPage *editor_page)
13676 WebKitDOMDocument *document;
13677 WebKitDOMDOMWindow *dom_window = NULL;
13678 WebKitDOMDOMSelection *dom_selection = NULL;
13679 gboolean collapsed;
13681 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
13683 document = e_editor_page_get_document (editor_page);
13684 if (!(dom_window = webkit_dom_document_get_default_view (document)))
13685 return FALSE;
13687 if (!(dom_selection = webkit_dom_dom_window_get_selection (dom_window))) {
13688 g_clear_object (&dom_window);
13689 return FALSE;
13692 collapsed = webkit_dom_dom_selection_get_is_collapsed (dom_selection);
13694 g_clear_object (&dom_selection);
13696 return collapsed;
13699 void
13700 e_editor_dom_scroll_to_caret (EEditorPage *editor_page)
13702 WebKitDOMDocument *document;
13703 WebKitDOMDOMWindow *dom_window = NULL;
13704 WebKitDOMElement *selection_start_marker;
13705 glong element_top, element_left;
13706 glong window_top, window_left, window_right, window_bottom;
13708 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
13710 document = e_editor_page_get_document (editor_page);
13711 e_editor_dom_selection_save (editor_page);
13713 selection_start_marker = webkit_dom_document_get_element_by_id (
13714 document, "-x-evo-selection-start-marker");
13715 if (!selection_start_marker)
13716 return;
13718 dom_window = webkit_dom_document_get_default_view (document);
13720 window_top = webkit_dom_dom_window_get_scroll_y (dom_window);
13721 window_left = webkit_dom_dom_window_get_scroll_x (dom_window);
13722 window_bottom = window_top + webkit_dom_dom_window_get_inner_height (dom_window);
13723 window_right = window_left + webkit_dom_dom_window_get_inner_width (dom_window);
13725 element_left = webkit_dom_element_get_offset_left (selection_start_marker);
13726 element_top = webkit_dom_element_get_offset_top (selection_start_marker);
13728 /* Check if caret is inside viewport, if not move to it */
13729 if (!(element_top >= window_top && element_top <= window_bottom &&
13730 element_left >= window_left && element_left <= window_right)) {
13731 webkit_dom_element_scroll_into_view (selection_start_marker, TRUE);
13734 e_editor_dom_selection_restore (editor_page);
13736 g_clear_object (&dom_window);
13739 static void
13740 mark_and_remove_trailing_space (WebKitDOMDocument *document,
13741 WebKitDOMNode *node)
13743 WebKitDOMElement *element;
13745 element = webkit_dom_document_create_element (document, "SPAN", NULL);
13746 webkit_dom_element_set_attribute (element, "data-hidden-space", "", NULL);
13747 webkit_dom_node_insert_before (
13748 webkit_dom_node_get_parent_node (node),
13749 WEBKIT_DOM_NODE (element),
13750 webkit_dom_node_get_next_sibling (node),
13751 NULL);
13752 webkit_dom_character_data_replace_data (
13753 WEBKIT_DOM_CHARACTER_DATA (node),
13754 webkit_dom_character_data_get_length (WEBKIT_DOM_CHARACTER_DATA (node)),
13757 NULL);
13760 static void
13761 mark_and_remove_leading_space (WebKitDOMDocument *document,
13762 WebKitDOMNode *node)
13764 WebKitDOMElement *element;
13766 element = webkit_dom_document_create_element (document, "SPAN", NULL);
13767 webkit_dom_element_set_attribute (element, "data-hidden-space", "", NULL);
13768 webkit_dom_node_insert_before (
13769 webkit_dom_node_get_parent_node (node),
13770 WEBKIT_DOM_NODE (element),
13771 node,
13772 NULL);
13773 webkit_dom_character_data_replace_data (
13774 WEBKIT_DOM_CHARACTER_DATA (node), 0, 1, "", NULL);
13777 static WebKitDOMElement *
13778 wrap_lines (EEditorPage *editor_page,
13779 WebKitDOMNode *block,
13780 gboolean remove_all_br,
13781 gint length_to_wrap,
13782 gint word_wrap_length)
13784 WebKitDOMDocument *document;
13785 WebKitDOMElement *selection_start_marker, *selection_end_marker;
13786 WebKitDOMNode *node, *start_node, *block_clone = NULL;
13787 WebKitDOMNode *start_point = NULL, *first_child, *last_child;
13788 guint line_length;
13789 gulong length_left;
13790 gchar *text_content;
13791 gboolean compensated = FALSE;
13792 gboolean check_next_node = FALSE;
13794 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
13796 document = e_editor_page_get_document (editor_page);
13798 if (!webkit_dom_node_has_child_nodes (block))
13799 return WEBKIT_DOM_ELEMENT (block);
13801 /* Avoid wrapping when the block contains just the BR element alone
13802 * or with selection markers. */
13803 if ((first_child = webkit_dom_node_get_first_child (block)) &&
13804 WEBKIT_DOM_IS_HTML_BR_ELEMENT (first_child)) {
13805 WebKitDOMNode *next_sibling;
13807 if ((next_sibling = webkit_dom_node_get_next_sibling (first_child))) {
13808 if (e_editor_dom_is_selection_position_node (next_sibling) &&
13809 (next_sibling = webkit_dom_node_get_next_sibling (next_sibling)) &&
13810 e_editor_dom_is_selection_position_node (next_sibling) &&
13811 !webkit_dom_node_get_next_sibling (next_sibling))
13812 return WEBKIT_DOM_ELEMENT (block);
13813 } else
13814 return WEBKIT_DOM_ELEMENT (block);
13817 block_clone = webkit_dom_node_clone_node_with_error (block, TRUE, NULL);
13819 /* When we wrap, we are wrapping just the text after caret, text
13820 * before the caret is already wrapped, so unwrap the text after
13821 * the caret position */
13822 selection_end_marker = webkit_dom_element_query_selector (
13823 WEBKIT_DOM_ELEMENT (block_clone),
13824 "span#-x-evo-selection-end-marker",
13825 NULL);
13827 if (selection_end_marker) {
13828 WebKitDOMNode *nd = WEBKIT_DOM_NODE (selection_end_marker);
13830 while (nd) {
13831 WebKitDOMNode *parent_node;
13832 WebKitDOMNode *next_nd = webkit_dom_node_get_next_sibling (nd);
13834 parent_node = webkit_dom_node_get_parent_node (nd);
13835 if (!next_nd && parent_node && !webkit_dom_node_is_same_node (parent_node, block_clone))
13836 next_nd = webkit_dom_node_get_next_sibling (parent_node);
13838 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (nd)) {
13839 if (remove_all_br)
13840 remove_node (nd);
13841 else if (element_has_class (WEBKIT_DOM_ELEMENT (nd), "-x-evo-wrap-br"))
13842 remove_node (nd);
13843 } else if (WEBKIT_DOM_IS_ELEMENT (nd) &&
13844 webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (nd), "data-hidden-space"))
13845 webkit_dom_html_element_set_outer_text (
13846 WEBKIT_DOM_HTML_ELEMENT (nd), " ", NULL);
13848 nd = next_nd;
13850 } else {
13851 gint ii;
13852 WebKitDOMNodeList *list = NULL;
13854 list = webkit_dom_element_query_selector_all (
13855 WEBKIT_DOM_ELEMENT (block_clone), "span[data-hidden-space]", NULL);
13856 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
13857 WebKitDOMNode *hidden_space_node;
13859 hidden_space_node = webkit_dom_node_list_item (list, ii);
13860 webkit_dom_html_element_set_outer_text (
13861 WEBKIT_DOM_HTML_ELEMENT (hidden_space_node), " ", NULL);
13863 g_clear_object (&list);
13866 /* We have to start from the end of the last wrapped line */
13867 selection_start_marker = webkit_dom_element_query_selector (
13868 WEBKIT_DOM_ELEMENT (block_clone),
13869 "span#-x-evo-selection-start-marker",
13870 NULL);
13872 if (selection_start_marker) {
13873 gboolean first_removed = FALSE;
13874 WebKitDOMNode *nd;
13876 nd = webkit_dom_node_get_previous_sibling (
13877 WEBKIT_DOM_NODE (selection_start_marker));
13878 while (nd) {
13879 WebKitDOMNode *prev_nd = webkit_dom_node_get_previous_sibling (nd);
13881 if (!prev_nd && !webkit_dom_node_is_same_node (webkit_dom_node_get_parent_node (nd), block_clone))
13882 prev_nd = webkit_dom_node_get_previous_sibling (webkit_dom_node_get_parent_node (nd));
13884 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (nd)) {
13885 if (first_removed) {
13886 start_point = nd;
13887 break;
13888 } else {
13889 remove_node (nd);
13890 first_removed = TRUE;
13892 } else if (WEBKIT_DOM_IS_ELEMENT (nd) &&
13893 webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (nd), "data-hidden-space")) {
13894 webkit_dom_html_element_set_outer_text (
13895 WEBKIT_DOM_HTML_ELEMENT (nd), " ", NULL);
13896 } else if (!prev_nd) {
13897 WebKitDOMNode *parent;
13899 parent = webkit_dom_node_get_parent_node (nd);
13900 if (!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent))
13901 start_point = nd;
13904 nd = prev_nd;
13908 webkit_dom_node_normalize (block_clone);
13909 node = webkit_dom_node_get_first_child (block_clone);
13910 if (node) {
13911 text_content = webkit_dom_node_get_text_content (node);
13912 if (g_strcmp0 ("\n", text_content) == 0)
13913 node = webkit_dom_node_get_next_sibling (node);
13914 g_free (text_content);
13917 if (start_point) {
13918 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (start_point))
13919 node = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (start_point));
13920 else
13921 node = start_point;
13922 start_node = block_clone;
13923 } else
13924 start_node = node;
13926 line_length = 0;
13927 while (node) {
13928 gint offset = 0;
13929 WebKitDOMElement *element;
13931 if (WEBKIT_DOM_IS_TEXT (node)) {
13932 const gchar *newline;
13933 WebKitDOMNode *next_sibling;
13935 /* If there is temporary hidden space we remove it */
13936 text_content = webkit_dom_node_get_text_content (node);
13937 if (strstr (text_content, UNICODE_ZERO_WIDTH_SPACE)) {
13938 if (g_str_has_prefix (text_content, UNICODE_ZERO_WIDTH_SPACE))
13939 webkit_dom_character_data_delete_data (
13940 WEBKIT_DOM_CHARACTER_DATA (node),
13943 NULL);
13944 if (g_str_has_suffix (text_content, UNICODE_ZERO_WIDTH_SPACE))
13945 webkit_dom_character_data_delete_data (
13946 WEBKIT_DOM_CHARACTER_DATA (node),
13947 g_utf8_strlen (text_content, -1) - 1,
13949 NULL);
13950 g_free (text_content);
13951 text_content = webkit_dom_node_get_text_content (node);
13953 newline = strstr (text_content, "\n");
13955 next_sibling = node;
13956 while (newline) {
13957 next_sibling = WEBKIT_DOM_NODE (webkit_dom_text_split_text (
13958 WEBKIT_DOM_TEXT (next_sibling),
13959 g_utf8_pointer_to_offset (text_content, newline),
13960 NULL));
13962 if (!next_sibling)
13963 break;
13965 element = webkit_dom_document_create_element (
13966 document, "BR", NULL);
13967 element_add_class (element, "-x-evo-wrap-br");
13969 webkit_dom_node_insert_before (
13970 webkit_dom_node_get_parent_node (next_sibling),
13971 WEBKIT_DOM_NODE (element),
13972 next_sibling,
13973 NULL);
13975 g_free (text_content);
13977 text_content = webkit_dom_node_get_text_content (next_sibling);
13978 if (g_str_has_prefix (text_content, "\n")) {
13979 webkit_dom_character_data_delete_data (
13980 WEBKIT_DOM_CHARACTER_DATA (next_sibling), 0, 1, NULL);
13981 g_free (text_content);
13982 text_content =
13983 webkit_dom_node_get_text_content (next_sibling);
13985 newline = strstr (text_content, "\n");
13987 g_free (text_content);
13988 } else if (WEBKIT_DOM_IS_ELEMENT (node)) {
13989 if (e_editor_dom_is_selection_position_node (node)) {
13990 if (line_length == 0) {
13991 WebKitDOMNode *tmp_node;
13993 tmp_node = webkit_dom_node_get_previous_sibling (node);
13994 /* Only check if there is some node before the selection marker. */
13995 if (tmp_node && !e_editor_dom_is_selection_position_node (tmp_node))
13996 check_next_node = TRUE;
13998 node = webkit_dom_node_get_next_sibling (node);
13999 continue;
14002 check_next_node = FALSE;
14003 /* If element is ANCHOR we wrap it separately */
14004 if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node)) {
14005 glong anchor_length;
14006 WebKitDOMNode *next_sibling;
14008 text_content = webkit_dom_node_get_text_content (node);
14009 anchor_length = g_utf8_strlen (text_content, -1);
14010 g_free (text_content);
14012 next_sibling = webkit_dom_node_get_next_sibling (node);
14013 /* If the anchor doesn't fit on the line move the inner
14014 * nodes out of it and start to wrap them. */
14015 if ((line_length + anchor_length) > length_to_wrap) {
14016 WebKitDOMNode *inner_node;
14018 while ((inner_node = webkit_dom_node_get_first_child (node))) {
14019 g_object_set_data (
14020 G_OBJECT (inner_node),
14021 "-x-evo-anchor-text",
14022 GINT_TO_POINTER (1));
14023 webkit_dom_node_insert_before (
14024 webkit_dom_node_get_parent_node (node),
14025 inner_node,
14026 next_sibling,
14027 NULL);
14029 next_sibling = webkit_dom_node_get_next_sibling (node);
14031 remove_node (node);
14032 node = next_sibling;
14033 continue;
14036 line_length += anchor_length;
14037 node = next_sibling;
14038 continue;
14041 if (element_has_class (WEBKIT_DOM_ELEMENT (node), "Apple-tab-span")) {
14042 WebKitDOMNode *sibling;
14043 gint tab_length;
14045 sibling = webkit_dom_node_get_previous_sibling (node);
14046 if (sibling && WEBKIT_DOM_IS_ELEMENT (sibling) &&
14047 element_has_class (WEBKIT_DOM_ELEMENT (sibling), "Apple-tab-span"))
14048 tab_length = TAB_LENGTH;
14049 else {
14050 tab_length = TAB_LENGTH - (line_length + compensated ? 0 : (word_wrap_length - length_to_wrap)) % TAB_LENGTH;
14051 compensated = TRUE;
14054 if (line_length + tab_length > length_to_wrap) {
14055 if (webkit_dom_node_get_next_sibling (node)) {
14056 element = webkit_dom_document_create_element (
14057 document, "BR", NULL);
14058 element_add_class (element, "-x-evo-wrap-br");
14059 node = webkit_dom_node_insert_before (
14060 webkit_dom_node_get_parent_node (node),
14061 WEBKIT_DOM_NODE (element),
14062 webkit_dom_node_get_next_sibling (node),
14063 NULL);
14065 line_length = 0;
14066 compensated = FALSE;
14067 } else
14068 line_length += tab_length;
14070 sibling = webkit_dom_node_get_next_sibling (node);
14071 node = sibling;
14072 continue;
14074 /* When we are not removing user-entered BR elements (lines wrapped by user),
14075 * we need to skip those elements */
14076 if (!remove_all_br && WEBKIT_DOM_IS_HTML_BR_ELEMENT (node)) {
14077 if (!element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-wrap-br")) {
14078 line_length = 0;
14079 compensated = FALSE;
14080 node = webkit_dom_node_get_next_sibling (node);
14081 continue;
14084 goto next_node;
14085 } else {
14086 WebKitDOMNode *sibling;
14088 sibling = webkit_dom_node_get_next_sibling (node);
14089 node = sibling;
14090 continue;
14093 /* If length of this node + what we already have is still less
14094 * then length_to_wrap characters, then just concatenate it and
14095 * continue to next node */
14096 length_left = webkit_dom_character_data_get_length (
14097 WEBKIT_DOM_CHARACTER_DATA (node));
14099 if ((length_left + line_length) <= length_to_wrap) {
14100 if (check_next_node)
14101 goto check_node;
14102 line_length += length_left;
14103 if (line_length == length_to_wrap) {
14104 line_length = 0;
14106 element = webkit_dom_document_create_element (document, "BR", NULL);
14107 element_add_class (element, "-x-evo-wrap-br");
14109 webkit_dom_node_insert_before (
14110 webkit_dom_node_get_parent_node (node),
14111 WEBKIT_DOM_NODE (element),
14112 webkit_dom_node_get_next_sibling (node),
14113 NULL);
14115 goto next_node;
14118 /* wrap until we have something */
14119 while (node && (length_left + line_length) > length_to_wrap) {
14120 gboolean insert_and_continue;
14121 gint max_length;
14123 check_node:
14124 insert_and_continue = FALSE;
14126 if (!WEBKIT_DOM_IS_CHARACTER_DATA (node))
14127 goto next_node;
14129 element = webkit_dom_document_create_element (document, "BR", NULL);
14130 element_add_class (element, "-x-evo-wrap-br");
14132 max_length = length_to_wrap - line_length;
14133 if (max_length < 0)
14134 max_length = length_to_wrap;
14135 else if (max_length == 0) {
14136 if (check_next_node) {
14137 insert_and_continue = TRUE;
14138 goto check;
14141 /* Break before the current node and continue. */
14142 webkit_dom_node_insert_before (
14143 webkit_dom_node_get_parent_node (node),
14144 WEBKIT_DOM_NODE (element),
14145 node,
14146 NULL);
14147 line_length = 0;
14148 continue;
14151 /* Allow anchors to break on any character. */
14152 if (g_object_steal_data (G_OBJECT (node), "-x-evo-anchor-text"))
14153 offset = max_length;
14154 else {
14155 /* Find where we can line-break the node so that it
14156 * effectively fills the rest of current row. */
14157 offset = find_where_to_break_line (
14158 WEBKIT_DOM_CHARACTER_DATA (node), max_length);
14160 /* When pressing delete on the end of line to concatenate
14161 * the last word from the line and first word from the
14162 * next line we will end with the second word split
14163 * somewhere in the middle (to be precise it will be
14164 * split after the last character that will fit on the
14165 * previous line. To avoid that we need to put the
14166 * concatenated word on the next line. */
14167 if (offset == -1 || check_next_node) {
14168 WebKitDOMNode *prev_sibling;
14170 check:
14171 check_next_node = FALSE;
14172 prev_sibling = webkit_dom_node_get_previous_sibling (node);
14173 if (prev_sibling && e_editor_dom_is_selection_position_node (prev_sibling)) {
14174 WebKitDOMNode *prev_br = NULL;
14176 prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling);
14178 /* Collapsed selection */
14179 if (prev_sibling && e_editor_dom_is_selection_position_node (prev_sibling))
14180 prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling);
14182 if (prev_sibling && WEBKIT_DOM_IS_HTML_BR_ELEMENT (prev_sibling) &&
14183 element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-wrap-br")) {
14184 prev_br = prev_sibling;
14185 prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling);
14188 if (prev_sibling && WEBKIT_DOM_IS_CHARACTER_DATA (prev_sibling)) {
14189 gchar *data;
14190 glong text_length, length = 0;
14192 data = webkit_dom_character_data_get_data (
14193 WEBKIT_DOM_CHARACTER_DATA (prev_sibling));
14194 text_length = webkit_dom_character_data_get_length (
14195 WEBKIT_DOM_CHARACTER_DATA (prev_sibling));
14197 /* Find the last character where we can break. */
14198 while (text_length - length > 0) {
14199 if (strchr (" ", data[text_length - length - 1])) {
14200 length++;
14201 break;
14202 } else if (data[text_length - length - 1] == '-' &&
14203 text_length - length > 1 &&
14204 !strchr (" ", data[text_length - length - 2]))
14205 break;
14206 length++;
14209 if (text_length != length) {
14210 WebKitDOMNode *nd;
14212 webkit_dom_text_split_text (
14213 WEBKIT_DOM_TEXT (prev_sibling),
14214 text_length - length,
14215 NULL);
14217 if ((nd = webkit_dom_node_get_next_sibling (prev_sibling))) {
14218 gchar *nd_content;
14220 nd_content = webkit_dom_node_get_text_content (nd);
14221 if (nd_content && *nd_content) {
14222 if (*nd_content == ' ')
14223 mark_and_remove_leading_space (document, nd);
14225 if (!webkit_dom_node_get_next_sibling (nd) &&
14226 g_str_has_suffix (nd_content, " "))
14227 mark_and_remove_trailing_space (document, nd);
14229 g_free (nd_content);
14232 if (nd) {
14233 if (prev_br)
14234 remove_node (prev_br);
14235 webkit_dom_node_insert_before (
14236 webkit_dom_node_get_parent_node (nd),
14237 WEBKIT_DOM_NODE (element),
14239 NULL);
14241 offset = 0;
14242 line_length = length;
14243 continue;
14249 if (insert_and_continue) {
14250 webkit_dom_node_insert_before (
14251 webkit_dom_node_get_parent_node (node),
14252 WEBKIT_DOM_NODE (element),
14253 node,
14254 NULL);
14256 offset = 0;
14257 line_length = 0;
14258 insert_and_continue = FALSE;
14259 continue;
14262 offset = offset != -1 ? offset : max_length;
14266 if (offset >= 0) {
14267 WebKitDOMNode *nd;
14269 if (offset != length_left && offset != 0) {
14270 webkit_dom_text_split_text (
14271 WEBKIT_DOM_TEXT (node), offset, NULL);
14273 nd = webkit_dom_node_get_next_sibling (node);
14274 } else
14275 nd = node;
14277 if (nd) {
14278 gboolean no_sibling = FALSE;
14279 gchar *nd_content;
14281 nd_content = webkit_dom_node_get_text_content (nd);
14282 if (nd_content && *nd_content) {
14283 if (*nd_content == ' ')
14284 mark_and_remove_leading_space (document, nd);
14286 if (!webkit_dom_node_get_next_sibling (nd) &&
14287 length_left <= length_to_wrap &&
14288 g_str_has_suffix (nd_content, " ")) {
14289 mark_and_remove_trailing_space (document, nd);
14290 no_sibling = TRUE;
14293 g_free (nd_content);
14296 if (!no_sibling)
14297 webkit_dom_node_insert_before (
14298 webkit_dom_node_get_parent_node (node),
14299 WEBKIT_DOM_NODE (element),
14301 NULL);
14303 offset = 0;
14305 nd_content = webkit_dom_node_get_text_content (nd);
14306 if (!*nd_content)
14307 remove_node (nd);
14308 g_free (nd_content);
14310 if (no_sibling) {
14311 node = NULL;
14312 } else {
14313 nd = node;
14315 node = webkit_dom_node_get_next_sibling (
14316 WEBKIT_DOM_NODE (element));
14318 if (nd == node)
14319 node = webkit_dom_node_get_next_sibling (node);
14320 if (nd == node)
14321 node = NULL;
14323 } else {
14324 webkit_dom_node_append_child (
14325 webkit_dom_node_get_parent_node (node),
14326 WEBKIT_DOM_NODE (element),
14327 NULL);
14330 if (node && WEBKIT_DOM_IS_CHARACTER_DATA (node))
14331 length_left = webkit_dom_character_data_get_length (
14332 WEBKIT_DOM_CHARACTER_DATA (node));
14334 line_length = 0;
14335 compensated = FALSE;
14337 line_length += length_left - offset;
14338 next_node:
14339 if (!node)
14340 break;
14342 if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (node)) {
14343 line_length = 0;
14344 compensated = FALSE;
14347 /* Move to next node */
14348 if (webkit_dom_node_has_child_nodes (node)) {
14349 node = webkit_dom_node_get_first_child (node);
14350 } else if (webkit_dom_node_get_next_sibling (node)) {
14351 node = webkit_dom_node_get_next_sibling (node);
14352 } else {
14353 WebKitDOMNode *tmp_parent;
14355 if (webkit_dom_node_is_equal_node (node, start_node))
14356 break;
14358 /* Find a next node that we can process. */
14359 tmp_parent = webkit_dom_node_get_parent_node (node);
14360 if (tmp_parent && webkit_dom_node_get_next_sibling (tmp_parent))
14361 node = webkit_dom_node_get_next_sibling (tmp_parent);
14362 else {
14363 WebKitDOMNode *tmp;
14365 tmp = tmp_parent;
14366 /* Find a node that is not a start node (that would mean
14367 * that we already processed the whole block) and it has
14368 * a sibling that we can process. */
14369 while (tmp && !webkit_dom_node_is_equal_node (tmp, start_node) &&
14370 !webkit_dom_node_get_next_sibling (tmp)) {
14371 tmp = webkit_dom_node_get_parent_node (tmp);
14374 /* If we found a node to process, let's process its
14375 * sibling, otherwise give up. */
14376 if (tmp)
14377 node = webkit_dom_node_get_next_sibling (tmp);
14378 else
14379 break;
14384 last_child = webkit_dom_node_get_last_child (block_clone);
14385 if (last_child && WEBKIT_DOM_IS_HTML_BR_ELEMENT (last_child) &&
14386 element_has_class (WEBKIT_DOM_ELEMENT (last_child), "-x-evo-wrap-br"))
14387 remove_node (last_child);
14389 webkit_dom_node_normalize (block_clone);
14391 node = webkit_dom_node_get_parent_node (block);
14392 if (node) {
14393 /* Replace block with wrapped one */
14394 webkit_dom_node_replace_child (
14395 node, block_clone, block, NULL);
14398 return WEBKIT_DOM_ELEMENT (block_clone);
14401 void
14402 e_editor_dom_remove_wrapping_from_element (WebKitDOMElement *element)
14404 WebKitDOMNodeList *list = NULL;
14405 gint ii;
14407 g_return_if_fail (element != NULL);
14409 list = webkit_dom_element_query_selector_all (
14410 element, "br.-x-evo-wrap-br", NULL);
14411 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
14412 WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
14413 WebKitDOMNode *parent;
14415 parent = e_editor_dom_get_parent_block_node_from_child (node);
14416 if (!webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (parent), "data-user-wrapped"))
14417 remove_node (node);
14420 g_clear_object (&list);
14422 list = webkit_dom_element_query_selector_all (
14423 element, "span[data-hidden-space]", NULL);
14424 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
14425 WebKitDOMNode *hidden_space_node;
14426 WebKitDOMNode *parent;
14428 hidden_space_node = webkit_dom_node_list_item (list, ii);
14429 parent = e_editor_dom_get_parent_block_node_from_child (hidden_space_node);
14430 if (!webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (parent), "data-user-wrapped")) {
14431 webkit_dom_html_element_set_outer_text (
14432 WEBKIT_DOM_HTML_ELEMENT (hidden_space_node), " ", NULL);
14435 g_clear_object (&list);
14437 webkit_dom_node_normalize (WEBKIT_DOM_NODE (element));
14440 void
14441 e_editor_dom_remove_quoting_from_element (WebKitDOMElement *element)
14443 gint ii;
14444 WebKitDOMHTMLCollection *collection = NULL;
14446 g_return_if_fail (element != NULL);
14448 collection = webkit_dom_element_get_elements_by_class_name_as_html_collection (
14449 element, "-x-evo-quoted");
14450 for (ii = webkit_dom_html_collection_get_length (collection); ii--;)
14451 remove_node (webkit_dom_html_collection_item (collection, ii));
14452 g_clear_object (&collection);
14454 collection = webkit_dom_element_get_elements_by_class_name_as_html_collection (
14455 element, "-x-evo-temp-br");
14456 for (ii = webkit_dom_html_collection_get_length (collection); ii--;)
14457 remove_node (webkit_dom_html_collection_item (collection, ii));
14458 g_clear_object (&collection);
14460 webkit_dom_node_normalize (WEBKIT_DOM_NODE (element));
14463 WebKitDOMElement *
14464 e_editor_dom_get_paragraph_element (EEditorPage *editor_page,
14465 gint width,
14466 gint offset)
14468 WebKitDOMDocument *document;
14469 WebKitDOMElement *element;
14471 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
14473 document = e_editor_page_get_document (editor_page);
14474 element = webkit_dom_document_create_element (document, "DIV", NULL);
14475 e_editor_dom_set_paragraph_style (editor_page, element, width, offset, NULL);
14477 return element;
14480 WebKitDOMElement *
14481 e_editor_dom_put_node_into_paragraph (EEditorPage *editor_page,
14482 WebKitDOMNode *node,
14483 gboolean with_input)
14485 WebKitDOMDocument *document;
14486 WebKitDOMRange *range = NULL;
14487 WebKitDOMElement *container;
14489 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
14491 document = e_editor_page_get_document (editor_page);
14492 range = webkit_dom_document_create_range (document);
14493 container = e_editor_dom_get_paragraph_element (editor_page, -1, 0);
14494 webkit_dom_range_select_node (range, node, NULL);
14495 webkit_dom_range_surround_contents (range, WEBKIT_DOM_NODE (container), NULL);
14496 /* We have to move caret position inside this container */
14497 if (with_input)
14498 dom_add_selection_markers_into_element_end (document, container, NULL, NULL);
14500 g_clear_object (&range);
14502 return container;
14505 WebKitDOMElement *
14506 e_editor_dom_wrap_paragraph_length (EEditorPage *editor_page,
14507 WebKitDOMElement *paragraph,
14508 gint length)
14510 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
14511 g_return_val_if_fail (WEBKIT_DOM_IS_ELEMENT (paragraph), NULL);
14512 g_return_val_if_fail (length >= MINIMAL_PARAGRAPH_WIDTH, NULL);
14514 return wrap_lines (editor_page, WEBKIT_DOM_NODE (paragraph), FALSE, length,
14515 e_editor_page_get_word_wrap_length (editor_page));
14519 * e_html_editor_selection_wrap_lines:
14520 * @selection: an #EEditorSelection
14522 * Wraps all lines in current selection to be 71 characters long.
14525 void
14526 e_editor_dom_selection_wrap (EEditorPage *editor_page)
14528 WebKitDOMDocument *document;
14529 WebKitDOMElement *selection_start_marker, *selection_end_marker;
14530 WebKitDOMNode *block, *next_block;
14531 EEditorHistoryEvent *ev = NULL;
14532 EEditorUndoRedoManager *manager;
14533 gboolean after_selection_end = FALSE, html_mode;
14534 gint word_wrap_length;
14536 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
14538 document = e_editor_page_get_document (editor_page);
14539 word_wrap_length = e_editor_page_get_word_wrap_length (editor_page);
14541 e_editor_dom_selection_save (editor_page);
14542 selection_start_marker = webkit_dom_document_get_element_by_id (
14543 document, "-x-evo-selection-start-marker");
14544 selection_end_marker = webkit_dom_document_get_element_by_id (
14545 document, "-x-evo-selection-end-marker");
14547 /* If the selection was not saved, move it into the first child of body */
14548 if (!selection_start_marker || !selection_end_marker) {
14549 WebKitDOMHTMLElement *body;
14550 WebKitDOMNode *child;
14552 body = webkit_dom_document_get_body (document);
14553 child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
14555 dom_add_selection_markers_into_element_start (
14556 document,
14557 WEBKIT_DOM_ELEMENT (child),
14558 &selection_start_marker,
14559 &selection_end_marker);
14562 manager = e_editor_page_get_undo_redo_manager (editor_page);
14563 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
14564 ev = g_new0 (EEditorHistoryEvent, 1);
14565 ev->type = HISTORY_WRAP;
14567 e_editor_dom_selection_get_coordinates (editor_page,
14568 &ev->before.start.x,
14569 &ev->before.start.y,
14570 &ev->before.end.x,
14571 &ev->before.end.y);
14573 ev->data.style.from = 1;
14574 ev->data.style.to = 1;
14577 block = e_editor_dom_get_parent_block_node_from_child (
14578 WEBKIT_DOM_NODE (selection_start_marker));
14580 html_mode = e_editor_page_get_html_mode (editor_page);
14582 /* Process all blocks that are in the selection one by one */
14583 while (block && !after_selection_end) {
14584 gboolean quoted = FALSE;
14585 gint citation_level, quote;
14586 WebKitDOMElement *wrapped_paragraph;
14588 next_block = webkit_dom_node_get_next_sibling (block);
14590 /* Don't try to wrap the 'Normal' blocks as they are already wrapped and*/
14591 /* also skip blocks that we already wrapped with this function. */
14592 if ((!html_mode && webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (block), "data-evo-paragraph")) ||
14593 webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (block), "data-user-wrapped")) {
14594 block = next_block;
14595 continue;
14598 if (webkit_dom_element_query_selector (
14599 WEBKIT_DOM_ELEMENT (block), "span.-x-evo-quoted", NULL)) {
14600 quoted = TRUE;
14601 e_editor_dom_remove_quoting_from_element (WEBKIT_DOM_ELEMENT (block));
14604 if (!html_mode)
14605 e_editor_dom_remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (block));
14607 after_selection_end = webkit_dom_node_contains (
14608 block, WEBKIT_DOM_NODE (selection_end_marker));
14610 citation_level = e_editor_dom_get_citation_level (block);
14611 quote = citation_level ? citation_level * 2 : 0;
14613 wrapped_paragraph = e_editor_dom_wrap_paragraph_length (
14614 editor_page, WEBKIT_DOM_ELEMENT (block), word_wrap_length - quote);
14616 webkit_dom_element_set_attribute (
14617 wrapped_paragraph, "data-user-wrapped", "", NULL);
14619 if (quoted && !html_mode)
14620 e_editor_dom_quote_plain_text_element_after_wrapping (
14621 editor_page, wrapped_paragraph, citation_level);
14623 block = next_block;
14626 if (ev) {
14627 e_editor_dom_selection_get_coordinates (editor_page,
14628 &ev->after.start.x,
14629 &ev->after.start.y,
14630 &ev->after.end.x,
14631 &ev->after.end.y);
14632 e_editor_undo_redo_manager_insert_history_event (manager, ev);
14635 e_editor_dom_selection_restore (editor_page);
14637 e_editor_dom_force_spell_check_in_viewport (editor_page);
14639 e_editor_page_emit_content_changed (editor_page);
14642 void
14643 e_editor_dom_wrap_paragraphs_in_document (EEditorPage *editor_page)
14645 WebKitDOMDocument *document;
14646 WebKitDOMNodeList *list = NULL;
14647 gint ii;
14649 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
14651 document = e_editor_page_get_document (editor_page);
14652 list = webkit_dom_document_query_selector_all (
14653 document, "[data-evo-paragraph]:not(#-x-evo-input-start)", NULL);
14655 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
14656 gint word_wrap_length, quote, citation_level;
14657 WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
14659 citation_level = e_editor_dom_get_citation_level (node);
14660 quote = citation_level ? citation_level * 2 : 0;
14661 word_wrap_length = e_editor_page_get_word_wrap_length (editor_page);
14663 if (node_is_list (node)) {
14664 WebKitDOMNode *item = webkit_dom_node_get_first_child (node);
14666 while (item && WEBKIT_DOM_IS_HTML_LI_ELEMENT (item)) {
14667 e_editor_dom_wrap_paragraph_length (
14668 editor_page, WEBKIT_DOM_ELEMENT (item), word_wrap_length - quote);
14669 item = webkit_dom_node_get_next_sibling (item);
14671 } else {
14672 e_editor_dom_wrap_paragraph_length (
14673 editor_page, WEBKIT_DOM_ELEMENT (node), word_wrap_length - quote);
14676 g_clear_object (&list);
14679 WebKitDOMElement *
14680 e_editor_dom_wrap_paragraph (EEditorPage *editor_page,
14681 WebKitDOMElement *paragraph)
14683 gint indentation_level, citation_level, quote;
14684 gint word_wrap_length, final_width, offset = 0;
14686 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
14687 g_return_val_if_fail (WEBKIT_DOM_IS_ELEMENT (paragraph), NULL);
14689 indentation_level = get_indentation_level (paragraph);
14690 citation_level = e_editor_dom_get_citation_level (WEBKIT_DOM_NODE (paragraph));
14692 if (node_is_list_or_item (WEBKIT_DOM_NODE (paragraph))) {
14693 gint list_level = get_list_level (WEBKIT_DOM_NODE (paragraph));
14694 indentation_level = 0;
14696 if (list_level > 0)
14697 offset = list_level * -SPACES_PER_LIST_LEVEL;
14698 else
14699 offset = -SPACES_PER_LIST_LEVEL;
14702 quote = citation_level ? citation_level * 2 : 0;
14704 word_wrap_length = e_editor_page_get_word_wrap_length (editor_page);
14705 final_width = word_wrap_length - quote + offset;
14706 final_width -= SPACES_PER_INDENTATION * indentation_level;
14708 return e_editor_dom_wrap_paragraph_length (
14709 editor_page, WEBKIT_DOM_ELEMENT (paragraph), final_width);
14712 static gboolean
14713 get_has_style (EEditorPage *editor_page,
14714 const gchar *style_tag)
14716 WebKitDOMNode *node;
14717 WebKitDOMElement *element;
14718 WebKitDOMRange *range = NULL;
14719 gboolean result;
14720 gint tag_len;
14722 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
14724 range = e_editor_dom_get_current_range (editor_page);
14725 if (!range)
14726 return FALSE;
14728 node = webkit_dom_range_get_start_container (range, NULL);
14729 if (WEBKIT_DOM_IS_ELEMENT (node))
14730 element = WEBKIT_DOM_ELEMENT (node);
14731 else
14732 element = webkit_dom_node_get_parent_element (node);
14733 g_clear_object (&range);
14735 tag_len = strlen (style_tag);
14736 result = FALSE;
14737 while (!result && element) {
14738 gchar *element_tag;
14739 gboolean accept_citation = FALSE;
14741 element_tag = webkit_dom_element_get_tag_name (element);
14743 if (g_ascii_strncasecmp (style_tag, "citation", 8) == 0) {
14744 accept_citation = TRUE;
14745 result = WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (element);
14746 if (element_has_class (element, "-x-evo-indented"))
14747 result = FALSE;
14748 } else {
14749 result = ((tag_len == strlen (element_tag)) &&
14750 (g_ascii_strncasecmp (element_tag, style_tag, tag_len) == 0));
14753 /* Special case: <blockquote type=cite> marks quotation, while
14754 * just <blockquote> is used for indentation. If the <blockquote>
14755 * has type=cite, then ignore it unless style_tag is "citation" */
14756 if (result && WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (element)) {
14757 if (webkit_dom_element_has_attribute (element, "type")) {
14758 gchar *type = webkit_dom_element_get_attribute (element, "type");
14759 if (!accept_citation && (type && g_ascii_strncasecmp (type, "cite", 4) == 0)) {
14760 result = FALSE;
14762 g_free (type);
14763 } else {
14764 if (accept_citation)
14765 result = FALSE;
14769 g_free (element_tag);
14771 if (result)
14772 break;
14774 element = webkit_dom_node_get_parent_element (
14775 WEBKIT_DOM_NODE (element));
14778 return result;
14781 typedef gboolean (*IsRightFormatNodeFunc) (WebKitDOMElement *element);
14783 static gboolean
14784 dom_selection_is_font_format (EEditorPage *editor_page,
14785 IsRightFormatNodeFunc func,
14786 gboolean *previous_value)
14788 WebKitDOMDocument *document;
14789 WebKitDOMDOMWindow *dom_window = NULL;
14790 WebKitDOMDOMSelection *dom_selection = NULL;
14791 WebKitDOMNode *start, *end, *sibling;
14792 WebKitDOMRange *range = NULL;
14793 gboolean ret_val = FALSE;
14795 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
14797 if (!e_editor_page_get_html_mode (editor_page))
14798 goto out;
14800 document = e_editor_page_get_document (editor_page);
14801 dom_window = webkit_dom_document_get_default_view (document);
14802 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
14803 g_clear_object (&dom_window);
14805 if (!webkit_dom_dom_selection_get_range_count (dom_selection))
14806 goto out;
14808 range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
14809 if (!range)
14810 goto out;
14812 if (webkit_dom_range_get_collapsed (range, NULL) && previous_value) {
14813 WebKitDOMNode *node;
14814 gchar* text_content;
14816 node = webkit_dom_range_get_common_ancestor_container (range, NULL);
14817 /* If we are changing the format of block we have to re-set the
14818 * format property, otherwise it will be turned off because of no
14819 * text in block. */
14820 text_content = webkit_dom_node_get_text_content (node);
14821 if (g_strcmp0 (text_content, "") == 0) {
14822 g_free (text_content);
14823 ret_val = *previous_value;
14824 goto out;
14826 g_free (text_content);
14829 /* Range without start or end point is a wrong range. */
14830 start = webkit_dom_range_get_start_container (range, NULL);
14831 end = webkit_dom_range_get_end_container (range, NULL);
14832 if (!start || !end)
14833 goto out;
14835 if (WEBKIT_DOM_IS_TEXT (start))
14836 start = webkit_dom_node_get_parent_node (start);
14837 while (start && WEBKIT_DOM_IS_ELEMENT (start) && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (start)) {
14838 /* Find the start point's parent node with given formatting. */
14839 if (func (WEBKIT_DOM_ELEMENT (start))) {
14840 ret_val = TRUE;
14841 break;
14843 start = webkit_dom_node_get_parent_node (start);
14846 /* Start point doesn't have the given formatting. */
14847 if (!ret_val)
14848 goto out;
14850 /* If the selection is collapsed, we can return early. */
14851 if (webkit_dom_range_get_collapsed (range, NULL))
14852 goto out;
14854 /* The selection is in the same node and that node is supposed to have
14855 * the same formatting (otherwise it is split up with formatting element. */
14856 if (webkit_dom_node_is_same_node (
14857 webkit_dom_range_get_start_container (range, NULL),
14858 webkit_dom_range_get_end_container (range, NULL)))
14859 goto out;
14861 ret_val = FALSE;
14863 if (WEBKIT_DOM_IS_TEXT (end))
14864 end = webkit_dom_node_get_parent_node (end);
14865 while (end && WEBKIT_DOM_IS_ELEMENT (end) && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (end)) {
14866 /* Find the end point's parent node with given formatting. */
14867 if (func (WEBKIT_DOM_ELEMENT (end))) {
14868 ret_val = TRUE;
14869 break;
14871 end = webkit_dom_node_get_parent_node (end);
14874 if (!ret_val)
14875 goto out;
14877 ret_val = FALSE;
14879 /* Now go between the end points and check the inner nodes for format validity. */
14880 sibling = start;
14881 while ((sibling = webkit_dom_node_get_next_sibling (sibling))) {
14882 if (webkit_dom_node_is_same_node (sibling, end)) {
14883 ret_val = TRUE;
14884 goto out;
14887 if (WEBKIT_DOM_IS_TEXT (sibling))
14888 goto out;
14889 else if (func (WEBKIT_DOM_ELEMENT (sibling)))
14890 continue;
14891 else if (webkit_dom_node_get_first_child (sibling)) {
14892 WebKitDOMNode *first_child;
14894 first_child = webkit_dom_node_get_first_child (sibling);
14895 if (!webkit_dom_node_get_next_sibling (first_child))
14896 if (WEBKIT_DOM_IS_ELEMENT (first_child) && func (WEBKIT_DOM_ELEMENT (first_child)))
14897 continue;
14898 else
14899 goto out;
14900 else
14901 goto out;
14902 } else
14903 goto out;
14906 sibling = end;
14907 while ((sibling = webkit_dom_node_get_previous_sibling (sibling))) {
14908 if (webkit_dom_node_is_same_node (sibling, start))
14909 break;
14911 if (WEBKIT_DOM_IS_TEXT (sibling))
14912 goto out;
14913 else if (func (WEBKIT_DOM_ELEMENT (sibling)))
14914 continue;
14915 else if (webkit_dom_node_get_first_child (sibling)) {
14916 WebKitDOMNode *first_child;
14918 first_child = webkit_dom_node_get_first_child (sibling);
14919 if (!webkit_dom_node_get_next_sibling (first_child))
14920 if (WEBKIT_DOM_IS_ELEMENT (first_child) && func (WEBKIT_DOM_ELEMENT (first_child)))
14921 continue;
14922 else
14923 goto out;
14924 else
14925 goto out;
14926 } else
14927 goto out;
14930 ret_val = TRUE;
14931 out:
14932 g_clear_object (&range);
14933 g_clear_object (&dom_selection);
14935 return ret_val;
14938 static gboolean
14939 is_underline_element (WebKitDOMElement *element)
14941 if (!element || !WEBKIT_DOM_IS_ELEMENT (element))
14942 return FALSE;
14944 return element_has_tag (element, "u");
14948 * e_html_editor_selection_is_underline:
14949 * @selection: an #EEditorSelection
14951 * Returns whether current selection or letter at current cursor position
14952 * is underlined.
14954 * Returns @TRUE when selection is underlined, @FALSE otherwise.
14956 gboolean
14957 e_editor_dom_selection_is_underline (EEditorPage *editor_page)
14959 gboolean is_underline;
14961 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
14963 is_underline = e_editor_page_get_underline (editor_page);
14964 is_underline = dom_selection_is_font_format (
14965 editor_page, (IsRightFormatNodeFunc) is_underline_element, &is_underline);
14967 return is_underline;
14970 static WebKitDOMElement *
14971 set_font_style (WebKitDOMDocument *document,
14972 const gchar *element_name,
14973 gboolean value)
14975 WebKitDOMElement *element;
14976 WebKitDOMNode *parent, *clone = NULL;
14978 element = webkit_dom_document_get_element_by_id (document, "-x-evo-selection-end-marker");
14979 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
14980 if (value) {
14981 WebKitDOMNode *node;
14982 WebKitDOMElement *el;
14983 gchar *name;
14985 el = webkit_dom_document_create_element (document, element_name, NULL);
14986 webkit_dom_html_element_set_inner_text (
14987 WEBKIT_DOM_HTML_ELEMENT (el), UNICODE_ZERO_WIDTH_SPACE, NULL);
14989 node = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element));
14990 webkit_dom_node_append_child (
14991 WEBKIT_DOM_NODE (el), node, NULL);
14992 name = webkit_dom_element_get_tag_name (WEBKIT_DOM_ELEMENT (parent));
14993 if (g_ascii_strcasecmp (name, element_name) == 0 && g_ascii_strcasecmp (name, "font") != 0)
14994 webkit_dom_node_insert_before (
14995 webkit_dom_node_get_parent_node (parent),
14996 WEBKIT_DOM_NODE (el),
14997 webkit_dom_node_get_next_sibling (parent),
14998 NULL);
14999 else
15000 webkit_dom_node_insert_before (
15001 parent,
15002 WEBKIT_DOM_NODE (el),
15003 WEBKIT_DOM_NODE (element),
15004 NULL);
15005 g_free (name);
15007 webkit_dom_node_append_child (
15008 WEBKIT_DOM_NODE (el), WEBKIT_DOM_NODE (element), NULL);
15010 return el;
15011 } else {
15012 gboolean no_sibling;
15013 WebKitDOMNode *node, *sibling;
15015 node = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element));
15017 /* Turning the formatting in the middle of element. */
15018 sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element));
15019 no_sibling = sibling &&
15020 !WEBKIT_DOM_IS_HTML_BR_ELEMENT (sibling) &&
15021 !webkit_dom_node_get_next_sibling (sibling);
15023 if (no_sibling) {
15024 gboolean do_clone = TRUE;
15025 gchar *text_content = NULL;
15026 WebKitDOMNode *child;
15028 if ((text_content = webkit_dom_node_get_text_content (parent)) &&
15029 (g_strcmp0 (text_content, UNICODE_ZERO_WIDTH_SPACE) == 0))
15030 do_clone = FALSE;
15032 g_free (text_content);
15034 if (do_clone) {
15035 clone = webkit_dom_node_clone_node_with_error (
15036 WEBKIT_DOM_NODE (parent), FALSE, NULL);
15038 while ((child = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element))))
15039 webkit_dom_node_insert_before (
15040 clone,
15041 child,
15042 webkit_dom_node_get_first_child (clone),
15043 NULL);
15045 webkit_dom_node_insert_before (
15046 webkit_dom_node_get_parent_node (parent),
15047 clone,
15048 webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (parent)),
15049 NULL);
15053 webkit_dom_node_insert_before (
15054 webkit_dom_node_get_parent_node (parent),
15055 WEBKIT_DOM_NODE (element),
15056 webkit_dom_node_get_next_sibling (parent),
15057 NULL);
15058 webkit_dom_node_insert_before (
15059 webkit_dom_node_get_parent_node (parent),
15060 node,
15061 webkit_dom_node_get_next_sibling (parent),
15062 NULL);
15064 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (sibling) && !no_sibling) {
15065 webkit_dom_node_insert_before (
15066 webkit_dom_node_get_parent_node (parent),
15067 node,
15068 webkit_dom_node_get_next_sibling (parent),
15069 NULL);
15072 if (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (webkit_dom_node_get_parent_node (parent))) {
15073 WebKitDOMNode *first_child;
15075 if ((first_child = webkit_dom_node_get_first_child (parent))) {
15076 gchar *text_content = NULL;
15078 text_content = webkit_dom_node_get_text_content (first_child);
15080 if (g_strcmp0 (text_content, UNICODE_ZERO_WIDTH_SPACE) != 0)
15081 webkit_dom_element_insert_adjacent_text (
15082 WEBKIT_DOM_ELEMENT (parent),
15083 "afterend",
15084 UNICODE_ZERO_WIDTH_SPACE,
15085 NULL);
15087 g_free (text_content);
15090 remove_node_if_empty (parent);
15091 remove_node_if_empty (clone);
15095 return NULL;
15098 static void
15099 selection_set_font_style (EEditorPage *editor_page,
15100 EContentEditorCommand command,
15101 gboolean value)
15103 EEditorHistoryEvent *ev = NULL;
15104 EEditorUndoRedoManager *manager;
15106 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
15108 e_editor_dom_selection_save (editor_page);
15110 manager = e_editor_page_get_undo_redo_manager (editor_page);
15111 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
15112 ev = g_new0 (EEditorHistoryEvent, 1);
15113 if (command == E_CONTENT_EDITOR_COMMAND_BOLD)
15114 ev->type = HISTORY_BOLD;
15115 else if (command == E_CONTENT_EDITOR_COMMAND_ITALIC)
15116 ev->type = HISTORY_ITALIC;
15117 else if (command == E_CONTENT_EDITOR_COMMAND_UNDERLINE)
15118 ev->type = HISTORY_UNDERLINE;
15119 else if (command == E_CONTENT_EDITOR_COMMAND_STRIKETHROUGH)
15120 ev->type = HISTORY_STRIKETHROUGH;
15122 e_editor_dom_selection_get_coordinates (editor_page,
15123 &ev->before.start.x,
15124 &ev->before.start.y,
15125 &ev->before.end.x,
15126 &ev->before.end.y);
15128 ev->data.style.from = !value;
15129 ev->data.style.to = value;
15132 if (e_editor_dom_selection_is_collapsed (editor_page)) {
15133 const gchar *element_name = NULL;
15135 if (command == E_CONTENT_EDITOR_COMMAND_BOLD)
15136 element_name = "b";
15137 else if (command == E_CONTENT_EDITOR_COMMAND_ITALIC)
15138 element_name = "i";
15139 else if (command == E_CONTENT_EDITOR_COMMAND_UNDERLINE)
15140 element_name = "u";
15141 else if (command == E_CONTENT_EDITOR_COMMAND_STRIKETHROUGH)
15142 element_name = "strike";
15144 if (element_name)
15145 set_font_style (e_editor_page_get_document (editor_page), element_name, value);
15146 e_editor_dom_selection_restore (editor_page);
15148 goto exit;
15150 e_editor_dom_selection_restore (editor_page);
15152 e_editor_dom_exec_command (editor_page, command, NULL);
15153 exit:
15154 if (ev) {
15155 e_editor_dom_selection_get_coordinates (editor_page,
15156 &ev->after.start.x,
15157 &ev->after.start.y,
15158 &ev->after.end.x,
15159 &ev->after.end.y);
15160 e_editor_undo_redo_manager_insert_history_event (manager, ev);
15163 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
15167 * e_html_editor_selection_set_underline:
15168 * @selection: an #EEditorSelection
15169 * @underline: @TRUE to enable underline, @FALSE to disable
15171 * Toggles underline formatting of current selection or letter at current
15172 * cursor position, depending on whether @underline is @TRUE or @FALSE.
15174 void
15175 e_editor_dom_selection_set_underline (EEditorPage *editor_page,
15176 gboolean underline)
15178 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
15180 if (e_editor_dom_selection_is_underline (editor_page) == underline)
15181 return;
15183 selection_set_font_style (
15184 editor_page, E_CONTENT_EDITOR_COMMAND_UNDERLINE, underline);
15187 static gboolean
15188 is_subscript_element (WebKitDOMElement *element)
15190 if (!element || !WEBKIT_DOM_IS_ELEMENT (element))
15191 return FALSE;
15193 return element_has_tag (element, "sub");
15197 * e_html_editor_selection_is_subscript:
15198 * @selection: an #EEditorSelection
15200 * Returns whether current selection or letter at current cursor position
15201 * is in subscript.
15203 * Returns @TRUE when selection is in subscript, @FALSE otherwise.
15205 gboolean
15206 e_editor_dom_selection_is_subscript (EEditorPage *editor_page)
15208 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
15210 return dom_selection_is_font_format (
15211 editor_page, (IsRightFormatNodeFunc) is_subscript_element, NULL);
15215 * e_html_editor_selection_set_subscript:
15216 * @selection: an #EEditorSelection
15217 * @subscript: @TRUE to enable subscript, @FALSE to disable
15219 * Toggles subscript of current selection or letter at current cursor position,
15220 * depending on whether @subscript is @TRUE or @FALSE.
15222 void
15223 e_editor_dom_selection_set_subscript (EEditorPage *editor_page,
15224 gboolean subscript)
15226 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
15228 if (e_editor_dom_selection_is_subscript (editor_page) == subscript)
15229 return;
15231 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_SUBSCRIPT, NULL);
15234 static gboolean
15235 is_superscript_element (WebKitDOMElement *element)
15237 if (!element || !WEBKIT_DOM_IS_ELEMENT (element))
15238 return FALSE;
15240 return element_has_tag (element, "sup");
15244 * e_html_editor_selection_is_superscript:
15245 * @selection: an #EEditorSelection
15247 * Returns whether current selection or letter at current cursor position
15248 * is in superscript.
15250 * Returns @TRUE when selection is in superscript, @FALSE otherwise.
15252 gboolean
15253 e_editor_dom_selection_is_superscript (EEditorPage *editor_page)
15255 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
15257 return dom_selection_is_font_format (
15258 editor_page, (IsRightFormatNodeFunc) is_superscript_element, NULL);
15262 * e_html_editor_selection_set_superscript:
15263 * @selection: an #EEditorSelection
15264 * @superscript: @TRUE to enable superscript, @FALSE to disable
15266 * Toggles superscript of current selection or letter at current cursor position,
15267 * depending on whether @superscript is @TRUE or @FALSE.
15269 void
15270 e_editor_dom_selection_set_superscript (EEditorPage *editor_page,
15271 gboolean superscript)
15273 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
15275 if (e_editor_dom_selection_is_superscript (editor_page) == superscript)
15276 return;
15278 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_SUPERSCRIPT, NULL);
15281 static gboolean
15282 is_strikethrough_element (WebKitDOMElement *element)
15284 if (!element || !WEBKIT_DOM_IS_ELEMENT (element))
15285 return FALSE;
15287 return element_has_tag (element, "strike");
15291 * e_html_editor_selection_is_strikethrough:
15292 * @selection: an #EEditorSelection
15294 * Returns whether current selection or letter at current cursor position
15295 * is striked through.
15297 * Returns @TRUE when selection is striked through, @FALSE otherwise.
15299 gboolean
15300 e_editor_dom_selection_is_strikethrough (EEditorPage *editor_page)
15302 gboolean is_strikethrough;
15304 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
15306 is_strikethrough = e_editor_page_get_strikethrough (editor_page);
15307 is_strikethrough = dom_selection_is_font_format (
15308 editor_page, (IsRightFormatNodeFunc) is_strikethrough_element, &is_strikethrough);
15310 return is_strikethrough;
15314 * e_html_editor_selection_set_strikethrough:
15315 * @selection: an #EEditorSelection
15316 * @strikethrough: @TRUE to enable strikethrough, @FALSE to disable
15318 * Toggles strike through formatting of current selection or letter at current
15319 * cursor position, depending on whether @strikethrough is @TRUE or @FALSE.
15321 void
15322 e_editor_dom_selection_set_strikethrough (EEditorPage *editor_page,
15323 gboolean strikethrough)
15325 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
15327 if (e_editor_dom_selection_is_strikethrough (editor_page) == strikethrough)
15328 return;
15330 selection_set_font_style (
15331 editor_page, E_CONTENT_EDITOR_COMMAND_STRIKETHROUGH, strikethrough);
15334 static gboolean
15335 is_monospace_element (WebKitDOMElement *element)
15337 gchar *value;
15338 gboolean ret_val = FALSE;
15340 if (!element)
15341 return FALSE;
15343 if (!WEBKIT_DOM_IS_HTML_FONT_ELEMENT (element))
15344 return FALSE;
15346 value = webkit_dom_element_get_attribute (element, "face");
15347 if (value && g_strcmp0 (value, "monospace") == 0)
15348 ret_val = TRUE;
15350 g_free (value);
15352 return ret_val;
15356 * e_html_editor_selection_is_monospaced:
15357 * @selection: an #EEditorSelection
15359 * Returns whether current selection or letter at current cursor position
15360 * is monospaced.
15362 * Returns @TRUE when selection is monospaced, @FALSE otherwise.
15364 gboolean
15365 e_editor_dom_selection_is_monospace (EEditorPage *editor_page)
15367 gboolean is_monospace;
15369 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
15371 is_monospace = e_editor_page_get_monospace (editor_page);
15372 is_monospace = dom_selection_is_font_format (
15373 editor_page, (IsRightFormatNodeFunc) is_monospace_element, &is_monospace);
15375 return is_monospace;
15378 static void
15379 monospace_selection (EEditorPage *editor_page,
15380 WebKitDOMElement *monospace_element)
15382 WebKitDOMDocument *document;
15383 WebKitDOMElement *selection_start_marker, *selection_end_marker;
15384 WebKitDOMNode *sibling, *node, *monospace, *block;
15385 WebKitDOMNodeList *list = NULL;
15386 gboolean selection_end = FALSE;
15387 gboolean first = TRUE;
15388 gint ii;
15390 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
15392 document = e_editor_page_get_document (editor_page);
15393 e_editor_dom_selection_save (editor_page);
15395 selection_start_marker = webkit_dom_document_get_element_by_id (
15396 document, "-x-evo-selection-start-marker");
15397 selection_end_marker = webkit_dom_document_get_element_by_id (
15398 document, "-x-evo-selection-end-marker");
15400 block = WEBKIT_DOM_NODE (get_parent_block_element (WEBKIT_DOM_NODE (selection_start_marker)));
15402 monospace = WEBKIT_DOM_NODE (monospace_element);
15403 node = WEBKIT_DOM_NODE (selection_start_marker);
15404 /* Go through first block in selection. */
15405 while (block && node && !webkit_dom_node_is_same_node (block, node)) {
15406 if (webkit_dom_node_get_next_sibling (node)) {
15407 /* Prepare the monospaced element. */
15408 monospace = webkit_dom_node_insert_before (
15409 webkit_dom_node_get_parent_node (node),
15410 first ? monospace : webkit_dom_node_clone_node_with_error (monospace, FALSE, NULL),
15411 first ? node : webkit_dom_node_get_next_sibling (node),
15412 NULL);
15413 } else
15414 break;
15416 /* Move the nodes into monospaced element. */
15417 while (((sibling = webkit_dom_node_get_next_sibling (monospace)))) {
15418 webkit_dom_node_append_child (monospace, sibling, NULL);
15419 if (webkit_dom_node_is_same_node (WEBKIT_DOM_NODE (selection_end_marker), sibling)) {
15420 selection_end = TRUE;
15421 break;
15425 node = webkit_dom_node_get_parent_node (monospace);
15426 first = FALSE;
15429 /* Just one block was selected. */
15430 if (selection_end)
15431 goto out;
15433 /* Middle blocks (blocks not containing the end of the selection. */
15434 block = webkit_dom_node_get_next_sibling (block);
15435 while (block && !selection_end) {
15436 WebKitDOMNode *next_block;
15438 selection_end = webkit_dom_node_contains (
15439 block, WEBKIT_DOM_NODE (selection_end_marker));
15441 if (selection_end)
15442 break;
15444 next_block = webkit_dom_node_get_next_sibling (block);
15446 monospace = webkit_dom_node_insert_before (
15447 block,
15448 webkit_dom_node_clone_node_with_error (monospace, FALSE, NULL),
15449 webkit_dom_node_get_first_child (block),
15450 NULL);
15452 while (((sibling = webkit_dom_node_get_next_sibling (monospace))))
15453 webkit_dom_node_append_child (monospace, sibling, NULL);
15455 block = next_block;
15458 /* Block containing the end of selection. */
15459 node = WEBKIT_DOM_NODE (selection_end_marker);
15460 while (block && node && !webkit_dom_node_is_same_node (block, node)) {
15461 monospace = webkit_dom_node_insert_before (
15462 webkit_dom_node_get_parent_node (node),
15463 webkit_dom_node_clone_node_with_error (monospace, FALSE, NULL),
15464 webkit_dom_node_get_next_sibling (node),
15465 NULL);
15467 while (((sibling = webkit_dom_node_get_previous_sibling (monospace)))) {
15468 webkit_dom_node_insert_before (
15469 monospace,
15470 sibling,
15471 webkit_dom_node_get_first_child (monospace),
15472 NULL);
15475 node = webkit_dom_node_get_parent_node (monospace);
15477 out:
15478 /* Merge all the monospace elements inside other monospace elements. */
15479 list = webkit_dom_document_query_selector_all (
15480 document, "font[face=monospace] > font[face=monospace]", NULL);
15481 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
15482 WebKitDOMNode *item;
15483 WebKitDOMNode *child;
15485 item = webkit_dom_node_list_item (list, ii);
15486 while ((child = webkit_dom_node_get_first_child (item))) {
15487 webkit_dom_node_insert_before (
15488 webkit_dom_node_get_parent_node (item),
15489 child,
15490 item,
15491 NULL);
15493 remove_node (item);
15495 g_clear_object (&list);
15497 /* Merge all the adjacent monospace elements. */
15498 list = webkit_dom_document_query_selector_all (
15499 document, "font[face=monospace] + font[face=monospace]", NULL);
15500 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
15501 WebKitDOMNode *item;
15502 WebKitDOMNode *child;
15504 item = webkit_dom_node_list_item (list, ii);
15505 /* The + CSS selector will return some false positives as it doesn't
15506 * take text between elements into account so it will return this:
15507 * <font face="monospace">xx</font>yy<font face="monospace">zz</font>
15508 * as valid, but it isn't so we have to check if previous node
15509 * is indeed element or not. */
15510 if (WEBKIT_DOM_IS_ELEMENT (webkit_dom_node_get_previous_sibling (item))) {
15511 while ((child = webkit_dom_node_get_first_child (item))) {
15512 webkit_dom_node_append_child (
15513 webkit_dom_node_get_previous_sibling (item), child, NULL);
15515 remove_node (item);
15518 g_clear_object (&list);
15520 e_editor_dom_selection_restore (editor_page);
15523 static void
15524 unmonospace_selection (EEditorPage *editor_page)
15526 WebKitDOMDocument *document;
15527 WebKitDOMElement *selection_start_marker;
15528 WebKitDOMElement *selection_end_marker;
15529 WebKitDOMElement *selection_start_clone;
15530 WebKitDOMElement *selection_end_clone;
15531 WebKitDOMNode *sibling, *node;
15532 WebKitDOMNode *block, *clone, *monospace;
15533 gboolean selection_end = FALSE;
15535 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
15537 document = e_editor_page_get_document (editor_page);
15538 e_editor_dom_selection_save (editor_page);
15540 selection_start_marker = webkit_dom_document_get_element_by_id (
15541 document, "-x-evo-selection-start-marker");
15542 selection_end_marker = webkit_dom_document_get_element_by_id (
15543 document, "-x-evo-selection-end-marker");
15545 block = WEBKIT_DOM_NODE (get_parent_block_element (WEBKIT_DOM_NODE (selection_start_marker)));
15547 node = WEBKIT_DOM_NODE (selection_start_marker);
15548 monospace = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start_marker));
15549 while (monospace && !is_monospace_element (WEBKIT_DOM_ELEMENT (monospace)))
15550 monospace = webkit_dom_node_get_parent_node (monospace);
15552 /* No monospaced element was found as a parent of selection start node. */
15553 if (!monospace)
15554 goto out;
15556 /* Make a clone of current monospaced element. */
15557 clone = webkit_dom_node_clone_node_with_error (monospace, TRUE, NULL);
15559 /* First block */
15560 /* Remove all the nodes that are after the selection start point as they
15561 * will be in the cloned node. */
15562 while (monospace && node && !webkit_dom_node_is_same_node (monospace, node)) {
15563 WebKitDOMNode *tmp;
15564 while (((sibling = webkit_dom_node_get_next_sibling (node))))
15565 remove_node (sibling);
15567 tmp = webkit_dom_node_get_parent_node (node);
15568 if (webkit_dom_node_get_next_sibling (node))
15569 remove_node (node);
15570 node = tmp;
15573 selection_start_clone = webkit_dom_element_query_selector (
15574 WEBKIT_DOM_ELEMENT (clone), "#-x-evo-selection-start-marker", NULL);
15575 selection_end_clone = webkit_dom_element_query_selector (
15576 WEBKIT_DOM_ELEMENT (clone), "#-x-evo-selection-end-marker", NULL);
15578 /* No selection start node in the block where it is supposed to be, return. */
15579 if (!selection_start_clone)
15580 goto out;
15582 /* Remove all the nodes until we hit the selection start point as these
15583 * nodes will stay monospaced and they are already in original element. */
15584 node = webkit_dom_node_get_first_child (clone);
15585 while (node) {
15586 WebKitDOMNode *next_sibling;
15588 next_sibling = webkit_dom_node_get_next_sibling (node);
15589 if (webkit_dom_node_get_first_child (node)) {
15590 if (webkit_dom_node_contains (node, WEBKIT_DOM_NODE (selection_start_clone))) {
15591 node = webkit_dom_node_get_first_child (node);
15592 continue;
15593 } else
15594 remove_node (node);
15595 } else if (webkit_dom_node_is_same_node (node, WEBKIT_DOM_NODE (selection_start_clone)))
15596 break;
15597 else
15598 remove_node (node);
15600 node = next_sibling;
15603 /* Insert the clone into the tree. Do it after the previous clean up. If
15604 * we would do it the other way the line would contain duplicated text nodes
15605 * and the block would be expading and shrinking while we would modify it. */
15606 webkit_dom_node_insert_before (
15607 webkit_dom_node_get_parent_node (monospace),
15608 clone,
15609 webkit_dom_node_get_next_sibling (monospace),
15610 NULL);
15612 /* Move selection start point the right place. */
15613 remove_node (WEBKIT_DOM_NODE (selection_start_marker));
15614 webkit_dom_node_insert_before (
15615 webkit_dom_node_get_parent_node (clone),
15616 WEBKIT_DOM_NODE (selection_start_clone),
15617 clone,
15618 NULL);
15620 /* Move all the nodes the are supposed to lose the monospace formatting
15621 * out of monospaced element. */
15622 node = webkit_dom_node_get_first_child (clone);
15623 while (node) {
15624 WebKitDOMNode *next_sibling;
15626 next_sibling = webkit_dom_node_get_next_sibling (node);
15627 if (webkit_dom_node_get_first_child (node)) {
15628 if (selection_end_clone &&
15629 webkit_dom_node_contains (node, WEBKIT_DOM_NODE (selection_end_clone))) {
15630 node = webkit_dom_node_get_first_child (node);
15631 continue;
15632 } else
15633 webkit_dom_node_insert_before (
15634 webkit_dom_node_get_parent_node (clone),
15635 node,
15636 clone,
15637 NULL);
15638 } else if (selection_end_clone &&
15639 webkit_dom_node_is_same_node (node, WEBKIT_DOM_NODE (selection_end_clone))) {
15640 selection_end = TRUE;
15641 webkit_dom_node_insert_before (
15642 webkit_dom_node_get_parent_node (clone),
15643 node,
15644 clone,
15645 NULL);
15646 break;
15647 } else
15648 webkit_dom_node_insert_before (
15649 webkit_dom_node_get_parent_node (clone),
15650 node,
15651 clone,
15652 NULL);
15654 node = next_sibling;
15657 remove_node_if_empty (clone);
15658 remove_node_if_empty (monospace);
15660 /* Just one block was selected and we hit the selection end point. */
15661 if (selection_end)
15662 goto out;
15664 /* Middle blocks */
15665 block = webkit_dom_node_get_next_sibling (block);
15666 while (block && !selection_end) {
15667 WebKitDOMNode *next_block, *child, *parent;
15668 WebKitDOMElement *monospace_element;
15670 selection_end = webkit_dom_node_contains (
15671 block, WEBKIT_DOM_NODE (selection_end_marker));
15673 if (selection_end)
15674 break;
15676 next_block = webkit_dom_node_get_next_sibling (block);
15678 /* Find the monospaced element and move all the nodes from it and
15679 * finally remove it. */
15680 monospace_element = webkit_dom_element_query_selector (
15681 WEBKIT_DOM_ELEMENT (block), "font[face=monospace]", NULL);
15682 if (!monospace_element)
15683 break;
15685 parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (monospace_element));
15686 while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (monospace_element)))) {
15687 webkit_dom_node_insert_before (
15688 parent, child, WEBKIT_DOM_NODE (monospace_element), NULL);
15691 remove_node (WEBKIT_DOM_NODE (monospace_element));
15693 block = next_block;
15696 /* End block */
15697 node = WEBKIT_DOM_NODE (selection_end_marker);
15698 monospace = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_end_marker));
15699 while (monospace && !is_monospace_element (WEBKIT_DOM_ELEMENT (monospace)))
15700 monospace = webkit_dom_node_get_parent_node (monospace);
15702 /* No monospaced element was found as a parent of selection end node. */
15703 if (!monospace)
15704 return;
15706 clone = WEBKIT_DOM_NODE (monospace);
15707 node = webkit_dom_node_get_first_child (clone);
15708 /* Move all the nodes that are supposed to lose the monospaced formatting
15709 * out of the monospaced element. */
15710 while (node) {
15711 WebKitDOMNode *next_sibling;
15713 next_sibling = webkit_dom_node_get_next_sibling (node);
15714 if (webkit_dom_node_get_first_child (node)) {
15715 if (webkit_dom_node_contains (node, WEBKIT_DOM_NODE (selection_end_marker))) {
15716 node = webkit_dom_node_get_first_child (node);
15717 continue;
15718 } else
15719 webkit_dom_node_insert_before (
15720 webkit_dom_node_get_parent_node (clone),
15721 node,
15722 clone,
15723 NULL);
15724 } else if (webkit_dom_node_is_same_node (node, WEBKIT_DOM_NODE (selection_end_marker))) {
15725 selection_end = TRUE;
15726 webkit_dom_node_insert_before (
15727 webkit_dom_node_get_parent_node (clone),
15728 node,
15729 clone,
15730 NULL);
15731 break;
15732 } else {
15733 webkit_dom_node_insert_before (
15734 webkit_dom_node_get_parent_node (clone),
15735 node,
15736 clone,
15737 NULL);
15740 node = next_sibling;
15743 remove_node_if_empty (clone);
15744 out:
15745 e_editor_dom_selection_restore (editor_page);
15749 * e_html_editor_selection_set_monospaced:
15750 * @selection: an #EEditorSelection
15751 * @monospaced: @TRUE to enable monospaced, @FALSE to disable
15753 * Toggles monospaced formatting of current selection or letter at current cursor
15754 * position, depending on whether @monospaced is @TRUE or @FALSE.
15756 void
15757 e_editor_dom_selection_set_monospace (EEditorPage *editor_page,
15758 gboolean value)
15760 WebKitDOMDocument *document;
15761 WebKitDOMRange *range = NULL;
15762 EEditorHistoryEvent *ev = NULL;
15763 EEditorUndoRedoManager *manager;
15764 guint font_size = 0;
15766 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
15768 if ((e_editor_dom_selection_is_monospace (editor_page) ? 1 : 0) == (value ? 1 : 0))
15769 return;
15771 document = e_editor_page_get_document (editor_page);
15772 range = e_editor_dom_get_current_range (editor_page);
15773 if (!range)
15774 return;
15776 manager = e_editor_page_get_undo_redo_manager (editor_page);
15777 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
15778 ev = g_new0 (EEditorHistoryEvent, 1);
15779 ev->type = HISTORY_MONOSPACE;
15781 e_editor_dom_selection_get_coordinates (editor_page,
15782 &ev->before.start.x,
15783 &ev->before.start.y,
15784 &ev->before.end.x,
15785 &ev->before.end.y);
15787 ev->data.style.from = !value;
15788 ev->data.style.to = value;
15791 font_size = e_editor_page_get_font_size (editor_page);
15792 if (font_size == 0)
15793 font_size = E_CONTENT_EDITOR_FONT_SIZE_NORMAL;
15795 if (value) {
15796 WebKitDOMElement *monospace;
15798 monospace = webkit_dom_document_create_element (
15799 document, "font", NULL);
15800 webkit_dom_element_set_attribute (
15801 monospace, "face", "monospace", NULL);
15802 if (font_size != 0) {
15803 gchar *font_size_str;
15805 font_size_str = g_strdup_printf ("%d", font_size);
15806 webkit_dom_element_set_attribute (
15807 monospace, "size", font_size_str, NULL);
15808 g_free (font_size_str);
15811 if (!webkit_dom_range_get_collapsed (range, NULL))
15812 monospace_selection (editor_page, monospace);
15813 else {
15814 /* https://bugs.webkit.org/show_bug.cgi?id=15256 */
15815 webkit_dom_element_set_inner_html (
15816 monospace,
15817 UNICODE_ZERO_WIDTH_SPACE,
15818 NULL);
15819 webkit_dom_range_insert_node (
15820 range, WEBKIT_DOM_NODE (monospace), NULL);
15822 e_editor_dom_move_caret_into_element (editor_page, monospace, FALSE);
15824 } else {
15825 gboolean is_bold = FALSE, is_italic = FALSE;
15826 gboolean is_underline = FALSE, is_strikethrough = FALSE;
15827 WebKitDOMElement *tt_element;
15828 WebKitDOMNode *node;
15830 node = webkit_dom_range_get_end_container (range, NULL);
15831 if (WEBKIT_DOM_IS_ELEMENT (node) &&
15832 is_monospace_element (WEBKIT_DOM_ELEMENT (node))) {
15833 tt_element = WEBKIT_DOM_ELEMENT (node);
15834 } else {
15835 tt_element = dom_node_find_parent_element (node, "FONT");
15837 if (!is_monospace_element (tt_element)) {
15838 g_clear_object (&range);
15839 g_free (ev);
15840 return;
15844 /* Save current formatting */
15845 is_bold = e_editor_page_get_bold (editor_page);
15846 is_italic = e_editor_page_get_italic (editor_page);
15847 is_underline = e_editor_page_get_underline (editor_page);
15848 is_strikethrough = e_editor_page_get_strikethrough (editor_page);
15850 if (!e_editor_dom_selection_is_collapsed (editor_page))
15851 unmonospace_selection (editor_page);
15852 else {
15853 e_editor_dom_selection_save (editor_page);
15854 set_font_style (document, "", FALSE);
15855 e_editor_dom_selection_restore (editor_page);
15858 /* Re-set formatting */
15859 if (is_bold)
15860 e_editor_dom_selection_set_bold (editor_page, TRUE);
15861 if (is_italic)
15862 e_editor_dom_selection_set_italic (editor_page, TRUE);
15863 if (is_underline)
15864 e_editor_dom_selection_set_underline (editor_page, TRUE);
15865 if (is_strikethrough)
15866 e_editor_dom_selection_set_strikethrough (editor_page, TRUE);
15868 if (font_size)
15869 e_editor_dom_selection_set_font_size (editor_page, font_size);
15872 if (ev) {
15873 e_editor_dom_selection_get_coordinates (editor_page,
15874 &ev->after.start.x,
15875 &ev->after.start.y,
15876 &ev->after.end.x,
15877 &ev->after.end.y);
15878 e_editor_undo_redo_manager_insert_history_event (manager, ev);
15881 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
15883 g_clear_object (&range);
15886 static gboolean
15887 is_bold_element (WebKitDOMElement *element)
15889 if (!element || !WEBKIT_DOM_IS_ELEMENT (element))
15890 return FALSE;
15892 if (element_has_tag (element, "b"))
15893 return TRUE;
15895 /* Headings are bold by default */
15896 return WEBKIT_DOM_IS_HTML_HEADING_ELEMENT (element);
15900 * e_html_editor_selection_is_bold:
15901 * @selection: an #EEditorSelection
15903 * Returns whether current selection or letter at current cursor position
15904 * is bold.
15906 * Returns @TRUE when selection is bold, @FALSE otherwise.
15908 gboolean
15909 e_editor_dom_selection_is_bold (EEditorPage *editor_page)
15911 gboolean is_bold;
15913 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
15915 is_bold = e_editor_page_get_bold (editor_page);
15916 is_bold = dom_selection_is_font_format (
15917 editor_page, (IsRightFormatNodeFunc) is_bold_element, &is_bold);
15919 return is_bold;
15923 * e_html_editor_selection_set_bold:
15924 * @selection: an #EEditorSelection
15925 * @bold: @TRUE to enable bold, @FALSE to disable
15927 * Toggles bold formatting of current selection or letter at current cursor
15928 * position, depending on whether @bold is @TRUE or @FALSE.
15930 void
15931 e_editor_dom_selection_set_bold (EEditorPage *editor_page,
15932 gboolean bold)
15934 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
15936 if (e_editor_dom_selection_is_bold (editor_page) == bold)
15937 return;
15939 selection_set_font_style (
15940 editor_page, E_CONTENT_EDITOR_COMMAND_BOLD, bold);
15942 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
15945 static gboolean
15946 is_italic_element (WebKitDOMElement *element)
15948 if (!element || !WEBKIT_DOM_IS_ELEMENT (element))
15949 return FALSE;
15951 return element_has_tag (element, "i") || element_has_tag (element, "address");
15955 * e_html_editor_selection_is_italic:
15956 * @selection: an #EEditorSelection
15958 * Returns whether current selection or letter at current cursor position
15959 * is italic.
15961 * Returns @TRUE when selection is italic, @FALSE otherwise.
15963 gboolean
15964 e_editor_dom_selection_is_italic (EEditorPage *editor_page)
15966 gboolean is_italic;
15968 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
15970 is_italic = e_editor_page_get_italic (editor_page);
15971 is_italic = dom_selection_is_font_format (
15972 editor_page, (IsRightFormatNodeFunc) is_italic_element, &is_italic);
15974 return is_italic;
15978 * e_html_editor_selection_set_italic:
15979 * @selection: an #EEditorSelection
15980 * @italic: @TRUE to enable italic, @FALSE to disable
15982 * Toggles italic formatting of current selection or letter at current cursor
15983 * position, depending on whether @italic is @TRUE or @FALSE.
15985 void
15986 e_editor_dom_selection_set_italic (EEditorPage *editor_page,
15987 gboolean italic)
15989 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
15991 if (e_editor_dom_selection_is_italic (editor_page) == italic)
15992 return;
15994 selection_set_font_style (
15995 editor_page, E_CONTENT_EDITOR_COMMAND_ITALIC, italic);
15999 * e_html_editor_selection_is_indented:
16000 * @selection: an #EEditorSelection
16002 * Returns whether current paragraph is indented. This does not include
16003 * citations. To check, whether paragraph is a citation, use
16004 * e_html_editor_selection_is_citation().
16006 * Returns: @TRUE when current paragraph is indented, @FALSE otherwise.
16008 gboolean
16009 e_editor_dom_selection_is_indented (EEditorPage *editor_page)
16011 WebKitDOMElement *element;
16012 WebKitDOMRange *range = NULL;
16014 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
16016 range = e_editor_dom_get_current_range (editor_page);
16017 if (!range)
16018 return FALSE;
16020 if (webkit_dom_range_get_collapsed (range, NULL)) {
16021 element = get_element_for_inspection (range);
16022 g_clear_object (&range);
16023 return element_has_class (element, "-x-evo-indented");
16024 } else {
16025 WebKitDOMNode *node;
16026 gboolean ret_val;
16028 node = webkit_dom_range_get_end_container (range, NULL);
16029 /* No selection or whole body selected */
16030 if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node))
16031 goto out;
16033 element = WEBKIT_DOM_ELEMENT (get_parent_indented_block (node));
16034 ret_val = element_has_class (element, "-x-evo-indented");
16035 if (!ret_val)
16036 goto out;
16038 node = webkit_dom_range_get_start_container (range, NULL);
16039 /* No selection or whole body selected */
16040 if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node))
16041 goto out;
16043 element = WEBKIT_DOM_ELEMENT (get_parent_indented_block (node));
16044 ret_val = element_has_class (element, "-x-evo-indented");
16046 g_clear_object (&range);
16048 return ret_val;
16051 out:
16052 g_clear_object (&range);
16054 return FALSE;
16058 * e_html_editor_selection_is_citation:
16059 * @selection: an #EEditorSelection
16061 * Returns whether current paragraph is a citation.
16063 * Returns: @TRUE when current paragraph is a citation, @FALSE otherwise.
16065 gboolean
16066 e_editor_dom_selection_is_citation (EEditorPage *editor_page)
16068 WebKitDOMNode *node;
16069 WebKitDOMRange *range = NULL;
16070 gboolean ret_val;
16071 gchar *value, *text_content;
16073 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
16075 range = e_editor_dom_get_current_range (editor_page);
16076 if (!range)
16077 return FALSE;
16079 node = webkit_dom_range_get_common_ancestor_container (range, NULL);
16080 g_clear_object (&range);
16082 if (WEBKIT_DOM_IS_TEXT (node))
16083 return get_has_style (editor_page, "citation");
16085 text_content = webkit_dom_node_get_text_content (node);
16086 if (g_strcmp0 (text_content, "") == 0) {
16087 g_free (text_content);
16088 return FALSE;
16090 g_free (text_content);
16092 value = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "type");
16093 /* citation == <blockquote type='cite'> */
16094 if (value && strstr (value, "cite"))
16095 ret_val = TRUE;
16096 else
16097 ret_val = get_has_style (editor_page, "citation");
16099 g_free (value);
16100 return ret_val;
16103 static gchar *
16104 get_font_property (EEditorPage *editor_page,
16105 const gchar *font_property)
16107 WebKitDOMRange *range = NULL;
16108 WebKitDOMNode *node;
16109 WebKitDOMElement *element;
16110 gchar *value;
16112 range = e_editor_dom_get_current_range (editor_page);
16113 if (!range)
16114 return NULL;
16116 node = webkit_dom_range_get_common_ancestor_container (range, NULL);
16117 g_clear_object (&range);
16118 element = dom_node_find_parent_element (node, "FONT");
16119 while (element && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (element) &&
16120 !webkit_dom_element_has_attribute (element, font_property)) {
16121 element = dom_node_find_parent_element (
16122 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)), "FONT");
16125 if (!element)
16126 return NULL;
16128 g_object_get (G_OBJECT (element), font_property, &value, NULL);
16130 return value;
16134 * e_editor_dom_selection_get_font_size:
16135 * @selection: an #EEditorSelection
16137 * Returns point size of current selection or of letter at current cursor position.
16139 guint
16140 e_editor_dom_selection_get_font_size (EEditorPage *editor_page)
16142 gchar *size;
16143 guint size_int;
16144 gboolean increment;
16146 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), 0);
16148 size = get_font_property (editor_page, "size");
16149 if (!(size && *size)) {
16150 g_free (size);
16151 return E_CONTENT_EDITOR_FONT_SIZE_NORMAL;
16154 /* We don't support increments, but when going through a content that
16155 * was not written in Evolution we can find it. In this case just report
16156 * the normal size. */
16157 /* FIXME: go through all parent and get the right value. */
16158 increment = size[0] == '+' || size[0] == '-';
16159 size_int = atoi (size);
16160 g_free (size);
16162 if (increment || size_int == 0)
16163 return E_CONTENT_EDITOR_FONT_SIZE_NORMAL;
16165 return size_int;
16169 * e_html_editor_selection_set_font_size:
16170 * @selection: an #EEditorSelection
16171 * @font_size: point size to apply
16173 * Sets font size of current selection or of letter at current cursor position
16174 * to @font_size.
16176 void
16177 e_editor_dom_selection_set_font_size (EEditorPage *editor_page,
16178 EContentEditorFontSize font_size)
16180 WebKitDOMDocument *document;
16181 EEditorUndoRedoManager *manager;
16182 EEditorHistoryEvent *ev = NULL;
16183 gchar *size_str;
16184 guint current_font_size;
16186 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
16188 document = e_editor_page_get_document (editor_page);
16189 current_font_size = e_editor_dom_selection_get_font_size (editor_page);
16190 if (current_font_size == font_size)
16191 return;
16193 e_editor_dom_selection_save (editor_page);
16195 manager = e_editor_page_get_undo_redo_manager (editor_page);
16196 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
16197 ev = g_new0 (EEditorHistoryEvent, 1);
16198 ev->type = HISTORY_FONT_SIZE;
16200 e_editor_dom_selection_get_coordinates (editor_page,
16201 &ev->before.start.x,
16202 &ev->before.start.y,
16203 &ev->before.end.x,
16204 &ev->before.end.y);
16206 ev->data.style.from = current_font_size;
16207 ev->data.style.to = font_size;
16210 size_str = g_strdup_printf ("%d", font_size);
16212 if (e_editor_dom_selection_is_collapsed (editor_page)) {
16213 WebKitDOMElement *font;
16215 font = set_font_style (document, "font", font_size != 3);
16216 if (font)
16217 webkit_dom_element_set_attribute (font, "size", size_str, NULL);
16218 e_editor_dom_selection_restore (editor_page);
16219 goto exit;
16222 e_editor_dom_selection_restore (editor_page);
16224 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_FONT_SIZE, size_str);
16226 /* Text in <font size="3"></font> (size 3 is our default size) is a little
16227 * bit smaller than font outsize it. So move it outside of it. */
16228 if (font_size == E_CONTENT_EDITOR_FONT_SIZE_NORMAL) {
16229 WebKitDOMElement *element;
16231 element = webkit_dom_document_query_selector (document, "font[size=\"3\"]", NULL);
16232 if (element) {
16233 WebKitDOMNode *child;
16235 while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element))))
16236 webkit_dom_node_insert_before (
16237 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
16238 child,
16239 WEBKIT_DOM_NODE (element),
16240 NULL);
16242 remove_node (WEBKIT_DOM_NODE (element));
16246 exit:
16247 g_free (size_str);
16249 if (ev) {
16250 e_editor_dom_selection_get_coordinates (editor_page,
16251 &ev->after.start.x,
16252 &ev->after.start.y,
16253 &ev->after.end.x,
16254 &ev->after.end.y);
16256 e_editor_undo_redo_manager_insert_history_event (manager, ev);
16261 * e_html_editor_selection_set_font_name:
16262 * @selection: an #EEditorSelection
16263 * @font_name: a font name to apply
16265 * Sets font name of current selection or of letter at current cursor position
16266 * to @font_name.
16268 void
16269 e_editor_dom_selection_set_font_name (EEditorPage *editor_page,
16270 const gchar *font_name)
16272 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
16274 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_FONT_NAME, font_name);
16278 * e_html_editor_selection_get_font_name:
16279 * @selection: an #EEditorSelection
16281 * Returns name of font used in current selection or at letter at current cursor
16282 * position.
16284 * Returns: A string with font name. [transfer-none]
16286 gchar *
16287 e_editor_dom_selection_get_font_name (EEditorPage *editor_page)
16289 WebKitDOMNode *node;
16290 WebKitDOMRange *range = NULL;
16291 WebKitDOMCSSStyleDeclaration *css = NULL;
16292 gchar *value;
16294 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
16296 range = e_editor_dom_get_current_range (editor_page);
16297 node = webkit_dom_range_get_common_ancestor_container (range, NULL);
16298 g_clear_object (&range);
16300 css = webkit_dom_element_get_style (WEBKIT_DOM_ELEMENT (node));
16301 value = webkit_dom_css_style_declaration_get_property_value (css, "fontFamily");
16302 g_clear_object (&css);
16304 return value;
16308 * e_html_editor_selection_set_font_color:
16309 * @selection: an #EEditorSelection
16310 * @rgba: a #GdkRGBA
16312 * Sets font color of current selection or letter at current cursor position to
16313 * color defined in @rgba.
16315 void
16316 e_editor_dom_selection_set_font_color (EEditorPage *editor_page,
16317 const gchar *color)
16319 EEditorUndoRedoManager *manager;
16320 EEditorHistoryEvent *ev = NULL;
16322 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
16324 manager = e_editor_page_get_undo_redo_manager (editor_page);
16325 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
16326 ev = g_new0 (EEditorHistoryEvent, 1);
16327 ev->type = HISTORY_FONT_COLOR;
16329 e_editor_dom_selection_get_coordinates (editor_page,
16330 &ev->before.start.x,
16331 &ev->before.start.y,
16332 &ev->before.end.x,
16333 &ev->before.end.y);
16335 ev->data.string.from = g_strdup (e_editor_page_get_font_color (editor_page));
16336 ev->data.string.to = g_strdup (color);
16339 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_FORE_COLOR, color);
16341 if (ev) {
16342 ev->after.start.x = ev->before.start.x;
16343 ev->after.start.y = ev->before.start.y;
16344 ev->after.end.x = ev->before.end.x;
16345 ev->after.end.y = ev->before.end.y;
16347 e_editor_undo_redo_manager_insert_history_event (manager, ev);
16352 * e_html_editor_selection_get_font_color:
16353 * @selection: an #EEditorSelection
16354 * @rgba: a #GdkRGBA object to be set to current font color
16356 * Sets @rgba to contain color of current text selection or letter at current
16357 * cursor position.
16359 gchar *
16360 e_editor_dom_selection_get_font_color (EEditorPage *editor_page)
16362 WebKitDOMDocument *document;
16363 gchar *color;
16365 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
16367 document = e_editor_page_get_document (editor_page);
16368 color = get_font_property (editor_page, "color");
16369 if (!(color && *color)) {
16370 WebKitDOMHTMLElement *body;
16372 body = webkit_dom_document_get_body (document);
16373 g_free (color);
16374 color = webkit_dom_html_body_element_get_text (WEBKIT_DOM_HTML_BODY_ELEMENT (body));
16375 if (!(color && *color)) {
16376 g_free (color);
16377 return g_strdup ("#000000");
16381 return color;
16385 * e_html_editor_selection_get_block_format:
16386 * @selection: an #EEditorSelection
16388 * Returns block format of current paragraph.
16390 * Returns: #EContentEditorBlockFormat
16392 EContentEditorBlockFormat
16393 e_editor_dom_selection_get_block_format (EEditorPage *editor_page)
16395 WebKitDOMNode *node;
16396 WebKitDOMRange *range = NULL;
16397 WebKitDOMElement *element;
16398 EContentEditorBlockFormat result;
16400 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), E_CONTENT_EDITOR_BLOCK_FORMAT_NONE);
16402 range = e_editor_dom_get_current_range (editor_page);
16403 if (!range)
16404 return E_CONTENT_EDITOR_BLOCK_FORMAT_PARAGRAPH;
16406 node = webkit_dom_range_get_start_container (range, NULL);
16408 if ((element = dom_node_find_parent_element (node, "UL"))) {
16409 WebKitDOMElement *tmp_element;
16411 tmp_element = dom_node_find_parent_element (node, "OL");
16412 if (tmp_element) {
16413 if (webkit_dom_node_contains (WEBKIT_DOM_NODE (tmp_element), WEBKIT_DOM_NODE (element)))
16414 result = dom_get_list_format_from_node (WEBKIT_DOM_NODE (element));
16415 else
16416 result = dom_get_list_format_from_node (WEBKIT_DOM_NODE (tmp_element));
16417 } else
16418 result = E_CONTENT_EDITOR_BLOCK_FORMAT_UNORDERED_LIST;
16419 } else if ((element = dom_node_find_parent_element (node, "OL")) != NULL) {
16420 WebKitDOMElement *tmp_element;
16422 tmp_element = dom_node_find_parent_element (node, "UL");
16423 if (tmp_element) {
16424 if (webkit_dom_node_contains (WEBKIT_DOM_NODE (element), WEBKIT_DOM_NODE (tmp_element)))
16425 result = dom_get_list_format_from_node (WEBKIT_DOM_NODE (element));
16426 else
16427 result = dom_get_list_format_from_node (WEBKIT_DOM_NODE (tmp_element));
16428 } else
16429 result = dom_get_list_format_from_node (WEBKIT_DOM_NODE (element));
16430 } else if (dom_node_find_parent_element (node, "PRE")) {
16431 result = E_CONTENT_EDITOR_BLOCK_FORMAT_PRE;
16432 } else if (dom_node_find_parent_element (node, "ADDRESS")) {
16433 result = E_CONTENT_EDITOR_BLOCK_FORMAT_ADDRESS;
16434 } else if (dom_node_find_parent_element (node, "H1")) {
16435 result = E_CONTENT_EDITOR_BLOCK_FORMAT_H1;
16436 } else if (dom_node_find_parent_element (node, "H2")) {
16437 result = E_CONTENT_EDITOR_BLOCK_FORMAT_H2;
16438 } else if (dom_node_find_parent_element (node, "H3")) {
16439 result = E_CONTENT_EDITOR_BLOCK_FORMAT_H3;
16440 } else if (dom_node_find_parent_element (node, "H4")) {
16441 result = E_CONTENT_EDITOR_BLOCK_FORMAT_H4;
16442 } else if (dom_node_find_parent_element (node, "H5")) {
16443 result = E_CONTENT_EDITOR_BLOCK_FORMAT_H5;
16444 } else if (dom_node_find_parent_element (node, "H6")) {
16445 result = E_CONTENT_EDITOR_BLOCK_FORMAT_H6;
16446 } else {
16447 /* Everything else is a paragraph (normal block) for us */
16448 result = E_CONTENT_EDITOR_BLOCK_FORMAT_PARAGRAPH;
16451 g_clear_object (&range);
16453 return result;
16456 static void
16457 change_leading_space_to_nbsp (WebKitDOMNode *block)
16459 WebKitDOMNode *child;
16461 if (!WEBKIT_DOM_IS_HTML_PRE_ELEMENT (block))
16462 return;
16464 if ((child = webkit_dom_node_get_first_child (block)) &&
16465 WEBKIT_DOM_IS_CHARACTER_DATA (child)) {
16466 gchar *data;
16468 data = webkit_dom_character_data_substring_data (
16469 WEBKIT_DOM_CHARACTER_DATA (child), 0, 1, NULL);
16471 if (data && *data == ' ')
16472 webkit_dom_character_data_replace_data (
16473 WEBKIT_DOM_CHARACTER_DATA (child), 0, 1, UNICODE_NBSP, NULL);
16474 g_free (data);
16478 static void
16479 change_trailing_space_in_block_to_nbsp (WebKitDOMNode *block)
16481 WebKitDOMNode *child;
16483 if ((child = webkit_dom_node_get_last_child (block)) &&
16484 WEBKIT_DOM_IS_CHARACTER_DATA (child)) {
16485 gchar *tmp;
16486 gulong length;
16488 length = webkit_dom_character_data_get_length (
16489 WEBKIT_DOM_CHARACTER_DATA (child));
16491 tmp = webkit_dom_character_data_substring_data (
16492 WEBKIT_DOM_CHARACTER_DATA (child), length - 1, 1, NULL);
16493 if (tmp && *tmp == ' ') {
16494 webkit_dom_character_data_replace_data (
16495 WEBKIT_DOM_CHARACTER_DATA (child),
16496 length - 1,
16498 UNICODE_NBSP,
16499 NULL);
16501 g_free (tmp);
16505 static void
16506 change_space_before_selection_to_nbsp (WebKitDOMNode *node)
16508 WebKitDOMNode *prev_sibling;
16510 if ((prev_sibling = webkit_dom_node_get_previous_sibling (node))) {
16511 if (WEBKIT_DOM_IS_CHARACTER_DATA (prev_sibling)) {
16512 gchar *tmp;
16513 gulong length;
16515 length = webkit_dom_character_data_get_length (
16516 WEBKIT_DOM_CHARACTER_DATA (prev_sibling));
16518 tmp = webkit_dom_character_data_substring_data (
16519 WEBKIT_DOM_CHARACTER_DATA (prev_sibling), length - 1, 1, NULL);
16520 if (tmp && *tmp == ' ') {
16521 webkit_dom_character_data_replace_data (
16522 WEBKIT_DOM_CHARACTER_DATA (prev_sibling),
16523 length - 1,
16525 UNICODE_NBSP,
16526 NULL);
16528 g_free (tmp);
16533 static gboolean
16534 process_block_to_block (EEditorPage *editor_page,
16535 EContentEditorBlockFormat format,
16536 const gchar *value,
16537 WebKitDOMNode *block,
16538 WebKitDOMNode *end_block,
16539 WebKitDOMNode *blockquote,
16540 gboolean html_mode)
16542 WebKitDOMDocument *document;
16543 WebKitDOMNode *next_block;
16544 gboolean after_selection_end = FALSE;
16546 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), FALSE);
16548 document = e_editor_page_get_document (editor_page);
16550 while (!after_selection_end && block) {
16551 gboolean quoted = FALSE;
16552 gboolean empty = FALSE;
16553 gchar *content;
16554 gint citation_level = 0;
16555 WebKitDOMNode *child;
16556 WebKitDOMElement *element;
16558 if (e_editor_dom_node_is_citation_node (block)) {
16559 gboolean finished;
16561 next_block = webkit_dom_node_get_next_sibling (block);
16562 finished = process_block_to_block (
16563 editor_page,
16564 format,
16565 value,
16566 webkit_dom_node_get_first_child (block),
16567 end_block,
16568 blockquote,
16569 html_mode);
16571 if (finished)
16572 return TRUE;
16574 block = next_block;
16576 continue;
16579 if (webkit_dom_element_query_selector (
16580 WEBKIT_DOM_ELEMENT (block), "span.-x-evo-quoted", NULL)) {
16581 quoted = TRUE;
16582 e_editor_dom_remove_quoting_from_element (WEBKIT_DOM_ELEMENT (block));
16585 if (!html_mode)
16586 e_editor_dom_remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (block));
16588 after_selection_end = webkit_dom_node_is_same_node (block, end_block);
16590 next_block = webkit_dom_node_get_next_sibling (block);
16592 if (node_is_list (block)) {
16593 WebKitDOMNode *item;
16595 item = webkit_dom_node_get_first_child (block);
16596 while (item && !WEBKIT_DOM_IS_HTML_LI_ELEMENT (item))
16597 item = webkit_dom_node_get_first_child (item);
16599 if (item && do_format_change_list_to_block (editor_page, format, item, value))
16600 return TRUE;
16602 block = next_block;
16604 continue;
16607 if (format == E_CONTENT_EDITOR_BLOCK_FORMAT_PARAGRAPH)
16608 element = e_editor_dom_get_paragraph_element (editor_page, -1, 0);
16609 else
16610 element = webkit_dom_document_create_element (
16611 document, value, NULL);
16613 content = webkit_dom_node_get_text_content (block);
16615 empty = !*content || (g_strcmp0 (content, UNICODE_ZERO_WIDTH_SPACE) == 0);
16616 g_free (content);
16618 change_leading_space_to_nbsp (block);
16619 change_trailing_space_in_block_to_nbsp (block);
16621 while ((child = webkit_dom_node_get_first_child (block))) {
16622 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (child))
16623 empty = FALSE;
16625 webkit_dom_node_append_child (
16626 WEBKIT_DOM_NODE (element), child, NULL);
16629 if (empty) {
16630 WebKitDOMElement *br;
16632 br = webkit_dom_document_create_element (
16633 document, "BR", NULL);
16634 webkit_dom_node_append_child (
16635 WEBKIT_DOM_NODE (element), WEBKIT_DOM_NODE (br), NULL);
16638 webkit_dom_node_insert_before (
16639 webkit_dom_node_get_parent_node (block),
16640 WEBKIT_DOM_NODE (element),
16641 block,
16642 NULL);
16644 remove_node (block);
16646 citation_level = e_editor_dom_get_citation_level (WEBKIT_DOM_NODE (element));
16648 if (!next_block && !after_selection_end && citation_level > 0) {
16649 next_block = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
16650 next_block = webkit_dom_node_get_next_sibling (next_block);
16653 block = next_block;
16655 if (!html_mode && format == E_CONTENT_EDITOR_BLOCK_FORMAT_PARAGRAPH) {
16656 citation_level = e_editor_dom_get_citation_level (WEBKIT_DOM_NODE (element));
16658 if (citation_level > 0) {
16659 gint quote, word_wrap_length;
16661 word_wrap_length =
16662 e_editor_page_get_word_wrap_length (editor_page);
16663 quote = citation_level * 2;
16665 element = e_editor_dom_wrap_paragraph_length (
16666 editor_page, element, word_wrap_length - quote);
16671 if (!html_mode && quoted && citation_level > 0)
16672 e_editor_dom_quote_plain_text_element_after_wrapping (
16673 editor_page, element, citation_level);
16676 return after_selection_end;
16679 static void
16680 format_change_block_to_block (EEditorPage *editor_page,
16681 EContentEditorBlockFormat format,
16682 const gchar *value)
16684 WebKitDOMDocument *document;
16685 WebKitDOMElement *selection_start_marker, *selection_end_marker;
16686 WebKitDOMNode *block, *end_block, *blockquote = NULL;
16687 gboolean html_mode = FALSE;
16689 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
16691 document = e_editor_page_get_document (editor_page);
16692 selection_start_marker = webkit_dom_document_get_element_by_id (
16693 document, "-x-evo-selection-start-marker");
16694 selection_end_marker = webkit_dom_document_get_element_by_id (
16695 document, "-x-evo-selection-end-marker");
16697 /* If the selection was not saved, move it into the first child of body */
16698 if (!selection_start_marker || !selection_end_marker) {
16699 WebKitDOMHTMLElement *body;
16700 WebKitDOMNode *child;
16702 body = webkit_dom_document_get_body (document);
16703 child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
16705 dom_add_selection_markers_into_element_start (
16706 document,
16707 WEBKIT_DOM_ELEMENT (child),
16708 &selection_start_marker,
16709 &selection_end_marker);
16712 block = e_editor_dom_get_parent_block_node_from_child (
16713 WEBKIT_DOM_NODE (selection_start_marker));
16715 html_mode = e_editor_page_get_html_mode (editor_page);
16717 end_block = e_editor_dom_get_parent_block_node_from_child (
16718 WEBKIT_DOM_NODE (selection_end_marker));
16720 /* Process all blocks that are in the selection one by one */
16721 process_block_to_block (
16722 editor_page, format, value, block, end_block, blockquote, html_mode);
16725 static void
16726 format_change_block_to_list (EEditorPage *editor_page,
16727 EContentEditorBlockFormat format)
16729 WebKitDOMDocument *document;
16730 WebKitDOMElement *selection_start_marker, *selection_end_marker, *item, *list;
16731 WebKitDOMNode *block, *next_block;
16732 gboolean after_selection_end = FALSE, in_quote = FALSE;
16733 gboolean html_mode = e_editor_page_get_html_mode (editor_page);
16735 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
16737 document = e_editor_page_get_document (editor_page);
16738 selection_start_marker = webkit_dom_document_get_element_by_id (
16739 document, "-x-evo-selection-start-marker");
16740 selection_end_marker = webkit_dom_document_get_element_by_id (
16741 document, "-x-evo-selection-end-marker");
16743 /* If the selection was not saved, move it into the first child of body */
16744 if (!selection_start_marker || !selection_end_marker) {
16745 WebKitDOMHTMLElement *body;
16746 WebKitDOMNode *child;
16748 body = webkit_dom_document_get_body (document);
16749 child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
16751 dom_add_selection_markers_into_element_start (
16752 document,
16753 WEBKIT_DOM_ELEMENT (child),
16754 &selection_start_marker,
16755 &selection_end_marker);
16758 block = e_editor_dom_get_parent_block_node_from_child (
16759 WEBKIT_DOM_NODE (selection_start_marker));
16761 list = create_list_element (editor_page, format, 0, html_mode);
16763 if (webkit_dom_element_query_selector (
16764 WEBKIT_DOM_ELEMENT (block), "span.-x-evo-quoted", NULL)) {
16765 WebKitDOMElement *element;
16766 WebKitDOMDOMWindow *dom_window = NULL;
16767 WebKitDOMDOMSelection *dom_selection = NULL;
16768 WebKitDOMRange *range = NULL;
16770 in_quote = TRUE;
16772 dom_window = webkit_dom_document_get_default_view (document);
16773 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
16774 range = webkit_dom_document_create_range (document);
16776 webkit_dom_range_select_node (range, block, NULL);
16777 webkit_dom_range_collapse (range, TRUE, NULL);
16778 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
16779 webkit_dom_dom_selection_add_range (dom_selection, range);
16781 g_clear_object (&range);
16782 g_clear_object (&dom_selection);
16783 g_clear_object (&dom_window);
16785 e_editor_dom_remove_input_event_listener_from_body (editor_page);
16786 e_editor_page_block_selection_changed (editor_page);
16788 e_editor_dom_exec_command (
16789 editor_page, E_CONTENT_EDITOR_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT, NULL);
16791 e_editor_dom_register_input_event_listener_on_body (editor_page);
16792 e_editor_page_unblock_selection_changed (editor_page);
16794 element = webkit_dom_document_query_selector (
16795 document, "body>br", NULL);
16797 webkit_dom_node_replace_child (
16798 webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
16799 WEBKIT_DOM_NODE (list),
16800 WEBKIT_DOM_NODE (element),
16801 NULL);
16803 block = e_editor_dom_get_parent_block_node_from_child (
16804 WEBKIT_DOM_NODE (selection_start_marker));
16805 } else
16806 webkit_dom_node_insert_before (
16807 webkit_dom_node_get_parent_node (block),
16808 WEBKIT_DOM_NODE (list),
16809 block,
16810 NULL);
16812 /* Process all blocks that are in the selection one by one */
16813 while (block && !after_selection_end) {
16814 gboolean empty = FALSE, block_is_list;
16815 gchar *content;
16816 WebKitDOMNode *child, *parent;
16818 after_selection_end = webkit_dom_node_contains (
16819 block, WEBKIT_DOM_NODE (selection_end_marker));
16821 next_block = webkit_dom_node_get_next_sibling (
16822 WEBKIT_DOM_NODE (block));
16824 e_editor_dom_remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (block));
16825 e_editor_dom_remove_quoting_from_element (WEBKIT_DOM_ELEMENT (block));
16827 item = webkit_dom_document_create_element (document, "LI", NULL);
16828 content = webkit_dom_node_get_text_content (block);
16830 empty = !*content || (g_strcmp0 (content, UNICODE_ZERO_WIDTH_SPACE) == 0);
16831 g_free (content);
16833 change_leading_space_to_nbsp (block);
16834 change_trailing_space_in_block_to_nbsp (block);
16836 block_is_list = node_is_list_or_item (block);
16838 while ((child = webkit_dom_node_get_first_child (block))) {
16839 if (WEBKIT_DOM_IS_HTML_BR_ELEMENT (child))
16840 empty = FALSE;
16842 webkit_dom_node_append_child (
16843 WEBKIT_DOM_NODE (block_is_list ? list : item), child, NULL);
16846 if (!block_is_list) {
16847 /* We have to use again the hidden space to move caret into newly inserted list */
16848 if (empty) {
16849 WebKitDOMElement *br;
16851 br = webkit_dom_document_create_element (
16852 document, "BR", NULL);
16853 webkit_dom_node_append_child (
16854 WEBKIT_DOM_NODE (item), WEBKIT_DOM_NODE (br), NULL);
16857 webkit_dom_node_append_child (
16858 WEBKIT_DOM_NODE (list), WEBKIT_DOM_NODE (item), NULL);
16861 parent = webkit_dom_node_get_parent_node (block);
16862 remove_node (block);
16864 if (in_quote) {
16865 /* Remove all parents if previously removed node was the
16866 * only one with text content */
16867 content = webkit_dom_node_get_text_content (parent);
16868 while (parent && content && !*content) {
16869 WebKitDOMNode *tmp = webkit_dom_node_get_parent_node (parent);
16871 remove_node (parent);
16872 parent = tmp;
16874 g_free (content);
16875 content = webkit_dom_node_get_text_content (parent);
16877 g_free (content);
16880 block = next_block;
16883 merge_lists_if_possible (WEBKIT_DOM_NODE (list));
16886 static WebKitDOMElement *
16887 do_format_change_list_to_list (WebKitDOMElement *list_to_process,
16888 WebKitDOMElement *new_list_template,
16889 EContentEditorBlockFormat to)
16891 EContentEditorBlockFormat current_format;
16893 current_format = dom_get_list_format_from_node (
16894 WEBKIT_DOM_NODE (list_to_process));
16895 if (to == current_format) {
16896 /* Same format, skip it. */
16897 return list_to_process;
16898 } else if (current_format >= E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST &&
16899 to >= E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST) {
16900 /* Changing from ordered list type to another ordered list type. */
16901 set_ordered_list_type_to_element (list_to_process, to);
16902 return list_to_process;
16903 } else {
16904 WebKitDOMNode *clone, *child;
16906 /* Create new list from template. */
16907 clone = webkit_dom_node_clone_node_with_error (
16908 WEBKIT_DOM_NODE (new_list_template), FALSE, NULL);
16910 /* Insert it before the list that we are processing. */
16911 webkit_dom_node_insert_before (
16912 webkit_dom_node_get_parent_node (
16913 WEBKIT_DOM_NODE (list_to_process)),
16914 clone,
16915 WEBKIT_DOM_NODE (list_to_process),
16916 NULL);
16918 /* Move all it children to the new one. */
16919 while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (list_to_process))))
16920 webkit_dom_node_append_child (clone, child, NULL);
16922 remove_node (WEBKIT_DOM_NODE (list_to_process));
16924 return WEBKIT_DOM_ELEMENT (clone);
16927 return NULL;
16930 static void
16931 format_change_list_from_list (EEditorPage *editor_page,
16932 EContentEditorBlockFormat to,
16933 gboolean html_mode)
16935 WebKitDOMDocument *document;
16936 WebKitDOMElement *selection_start_marker, *selection_end_marker, *new_list;
16937 WebKitDOMNode *source_list, *source_list_clone, *current_list, *item;
16938 gboolean after_selection_end = FALSE;
16940 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
16942 document = e_editor_page_get_document (editor_page);
16943 selection_start_marker = webkit_dom_document_get_element_by_id (
16944 document, "-x-evo-selection-start-marker");
16945 selection_end_marker = webkit_dom_document_get_element_by_id (
16946 document, "-x-evo-selection-end-marker");
16948 if (!selection_start_marker || !selection_end_marker)
16949 return;
16951 /* Copy elements from previous block to list */
16952 item = get_list_item_node_from_child (WEBKIT_DOM_NODE (selection_start_marker));
16953 source_list = webkit_dom_node_get_parent_node (item);
16954 current_list = source_list;
16955 source_list_clone = webkit_dom_node_clone_node_with_error (source_list, FALSE, NULL);
16957 new_list = create_list_element (editor_page, to, 0, html_mode);
16959 if (element_has_class (WEBKIT_DOM_ELEMENT (source_list), "-x-evo-indented"))
16960 element_add_class (WEBKIT_DOM_ELEMENT (new_list), "-x-evo-indented");
16962 while (item) {
16963 gboolean selection_end;
16964 WebKitDOMNode *next_item = webkit_dom_node_get_next_sibling (item);
16966 selection_end = webkit_dom_node_contains (
16967 item, WEBKIT_DOM_NODE (selection_end_marker));
16969 if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (item)) {
16970 /* Actual node is an item, just copy it. */
16971 webkit_dom_node_append_child (
16972 after_selection_end ?
16973 source_list_clone : WEBKIT_DOM_NODE (new_list),
16974 item,
16975 NULL);
16976 } else if (node_is_list (item) && !selection_end && !after_selection_end) {
16977 /* Node is a list and it doesn't contain the selection end
16978 * marker, we can process the whole list. */
16979 gint ii;
16980 WebKitDOMNodeList *list = NULL;
16981 WebKitDOMElement *processed_list;
16983 list = webkit_dom_element_query_selector_all (
16984 WEBKIT_DOM_ELEMENT (item), "ol,ul", NULL);
16985 ii = webkit_dom_node_list_get_length (list);
16986 g_clear_object (&list);
16988 /* Process every sublist separately. */
16989 while (ii) {
16990 WebKitDOMElement *list_to_process;
16992 list_to_process = webkit_dom_element_query_selector (
16993 WEBKIT_DOM_ELEMENT (item), "ol,ul", NULL);
16994 if (list_to_process)
16995 do_format_change_list_to_list (list_to_process, new_list, to);
16996 ii--;
16999 /* Process the current list. */
17000 processed_list = do_format_change_list_to_list (
17001 WEBKIT_DOM_ELEMENT (item), new_list, to);
17003 webkit_dom_node_append_child (
17004 WEBKIT_DOM_NODE (new_list),
17005 WEBKIT_DOM_NODE (processed_list),
17006 NULL);
17007 } else if (node_is_list (item) && !after_selection_end) {
17008 /* Node is a list and it contains the selection end marker,
17009 * thus we have to process it until we find the marker. */
17010 gint ii;
17011 WebKitDOMNodeList *list = NULL;
17013 list = webkit_dom_element_query_selector_all (
17014 WEBKIT_DOM_ELEMENT (item), "ol,ul", NULL);
17015 ii = webkit_dom_node_list_get_length (list);
17016 g_clear_object (&list);
17018 /* No nested lists - process the items. */
17019 if (ii == 0) {
17020 WebKitDOMNode *clone, *child;
17022 clone = webkit_dom_node_clone_node_with_error (
17023 WEBKIT_DOM_NODE (new_list), FALSE, NULL);
17025 webkit_dom_node_append_child (
17026 WEBKIT_DOM_NODE (new_list), clone, NULL);
17028 while ((child = webkit_dom_node_get_first_child (item))) {
17029 webkit_dom_node_append_child (clone, child, NULL);
17030 if (webkit_dom_node_contains (child, WEBKIT_DOM_NODE (selection_end_marker)))
17031 break;
17034 if (webkit_dom_node_get_first_child (item))
17035 webkit_dom_node_append_child (
17036 WEBKIT_DOM_NODE (new_list), item, NULL);
17037 else
17038 remove_node (item);
17039 } else {
17040 gboolean done = FALSE;
17041 WebKitDOMNode *tmp_parent = WEBKIT_DOM_NODE (new_list);
17042 WebKitDOMNode *tmp_item = WEBKIT_DOM_NODE (item);
17044 while (!done) {
17045 WebKitDOMNode *clone, *child;
17047 clone = webkit_dom_node_clone_node_with_error (
17048 WEBKIT_DOM_NODE (new_list), FALSE, NULL);
17050 webkit_dom_node_append_child (
17051 tmp_parent, clone, NULL);
17053 while ((child = webkit_dom_node_get_first_child (tmp_item))) {
17054 if (!webkit_dom_node_contains (child, WEBKIT_DOM_NODE (selection_end_marker))) {
17055 webkit_dom_node_append_child (clone, child, NULL);
17056 } else if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (child)) {
17057 webkit_dom_node_append_child (clone, child, NULL);
17058 done = TRUE;
17059 break;
17060 } else {
17061 tmp_parent = clone;
17062 tmp_item = child;
17063 break;
17068 } else {
17069 webkit_dom_node_append_child (
17070 after_selection_end ?
17071 source_list_clone : WEBKIT_DOM_NODE (new_list),
17072 item,
17073 NULL);
17076 if (selection_end) {
17077 source_list_clone = webkit_dom_node_clone_node_with_error (current_list, FALSE, NULL);
17078 after_selection_end = TRUE;
17081 if (!next_item) {
17082 if (after_selection_end)
17083 break;
17085 current_list = webkit_dom_node_get_next_sibling (current_list);
17086 if (!node_is_list_or_item (current_list))
17087 break;
17088 if (node_is_list (current_list)) {
17089 next_item = webkit_dom_node_get_first_child (current_list);
17090 if (!node_is_list_or_item (next_item))
17091 break;
17092 } else if (WEBKIT_DOM_IS_HTML_LI_ELEMENT (current_list)) {
17093 next_item = current_list;
17094 current_list = webkit_dom_node_get_parent_node (next_item);
17098 item = next_item;
17101 webkit_dom_node_insert_before (
17102 webkit_dom_node_get_parent_node (source_list),
17103 WEBKIT_DOM_NODE (source_list_clone),
17104 webkit_dom_node_get_next_sibling (source_list),
17105 NULL);
17107 if (webkit_dom_node_has_child_nodes (WEBKIT_DOM_NODE (new_list)))
17108 webkit_dom_node_insert_before (
17109 webkit_dom_node_get_parent_node (source_list_clone),
17110 WEBKIT_DOM_NODE (new_list),
17111 source_list_clone,
17112 NULL);
17114 remove_node_if_empty (source_list);
17115 remove_node_if_empty (source_list_clone);
17116 remove_node_if_empty (current_list);
17118 merge_lists_if_possible (WEBKIT_DOM_NODE (new_list));
17121 static void
17122 format_change_list_to_list (EEditorPage *editor_page,
17123 EContentEditorBlockFormat format,
17124 gboolean html_mode)
17126 WebKitDOMDocument *document;
17127 WebKitDOMElement *selection_start_marker, *selection_end_marker;
17128 WebKitDOMNode *prev_list, *current_list, *next_list;
17129 EContentEditorBlockFormat prev = 0, next = 0;
17130 gboolean done = FALSE, indented = FALSE;
17131 gboolean selection_starts_in_first_child, selection_ends_in_last_child;
17133 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17135 document = e_editor_page_get_document (editor_page);
17136 selection_start_marker = webkit_dom_document_get_element_by_id (
17137 document, "-x-evo-selection-start-marker");
17138 selection_end_marker = webkit_dom_document_get_element_by_id (
17139 document, "-x-evo-selection-end-marker");
17141 current_list = get_list_node_from_child (
17142 WEBKIT_DOM_NODE (selection_start_marker));
17144 prev_list = get_list_node_from_child (
17145 WEBKIT_DOM_NODE (selection_start_marker));
17147 next_list = get_list_node_from_child (
17148 WEBKIT_DOM_NODE (selection_end_marker));
17150 selection_starts_in_first_child =
17151 webkit_dom_node_contains (
17152 webkit_dom_node_get_first_child (current_list),
17153 WEBKIT_DOM_NODE (selection_start_marker));
17155 selection_ends_in_last_child =
17156 webkit_dom_node_contains (
17157 webkit_dom_node_get_last_child (current_list),
17158 WEBKIT_DOM_NODE (selection_end_marker));
17160 indented = element_has_class (WEBKIT_DOM_ELEMENT (current_list), "-x-evo-indented");
17162 if (!prev_list || !next_list || indented) {
17163 format_change_list_from_list (editor_page, format, html_mode);
17164 return;
17167 if (webkit_dom_node_is_same_node (prev_list, next_list)) {
17168 prev_list = webkit_dom_node_get_previous_sibling (
17169 webkit_dom_node_get_parent_node (
17170 webkit_dom_node_get_parent_node (
17171 WEBKIT_DOM_NODE (selection_start_marker))));
17172 next_list = webkit_dom_node_get_next_sibling (
17173 webkit_dom_node_get_parent_node (
17174 webkit_dom_node_get_parent_node (
17175 WEBKIT_DOM_NODE (selection_end_marker))));
17176 if (!prev_list || !next_list) {
17177 format_change_list_from_list (editor_page, format, html_mode);
17178 return;
17182 prev = dom_get_list_format_from_node (prev_list);
17183 next = dom_get_list_format_from_node (next_list);
17185 if (format != E_CONTENT_EDITOR_BLOCK_FORMAT_NONE) {
17186 if (format == prev && prev != E_CONTENT_EDITOR_BLOCK_FORMAT_NONE) {
17187 if (selection_starts_in_first_child && selection_ends_in_last_child) {
17188 done = TRUE;
17189 merge_list_into_list (current_list, prev_list, FALSE);
17192 if (format == next && next != E_CONTENT_EDITOR_BLOCK_FORMAT_NONE) {
17193 if (selection_starts_in_first_child && selection_ends_in_last_child) {
17194 done = TRUE;
17195 merge_list_into_list (next_list, prev_list, FALSE);
17200 if (done)
17201 return;
17203 format_change_list_from_list (editor_page, format, html_mode);
17207 * e_html_editor_selection_set_block_format:
17208 * @selection: an #EEditorSelection
17209 * @format: an #EContentEditorBlockFormat value
17211 * Changes block format of current paragraph to @format.
17213 void
17214 e_editor_dom_selection_set_block_format (EEditorPage *editor_page,
17215 EContentEditorBlockFormat format)
17217 WebKitDOMDocument *document;
17218 WebKitDOMRange *range = NULL;
17219 EContentEditorBlockFormat current_format;
17220 EContentEditorAlignment current_alignment;
17221 EEditorUndoRedoManager *manager;
17222 EEditorHistoryEvent *ev = NULL;
17223 const gchar *value;
17224 gboolean from_list = FALSE, to_list = FALSE, html_mode = FALSE;
17226 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17228 document = e_editor_page_get_document (editor_page);
17229 current_format = e_editor_dom_selection_get_block_format (editor_page);
17230 if (current_format == format)
17231 return;
17233 switch (format) {
17234 case E_CONTENT_EDITOR_BLOCK_FORMAT_H1:
17235 value = "H1";
17236 break;
17237 case E_CONTENT_EDITOR_BLOCK_FORMAT_H2:
17238 value = "H2";
17239 break;
17240 case E_CONTENT_EDITOR_BLOCK_FORMAT_H3:
17241 value = "H3";
17242 break;
17243 case E_CONTENT_EDITOR_BLOCK_FORMAT_H4:
17244 value = "H4";
17245 break;
17246 case E_CONTENT_EDITOR_BLOCK_FORMAT_H5:
17247 value = "H5";
17248 break;
17249 case E_CONTENT_EDITOR_BLOCK_FORMAT_H6:
17250 value = "H6";
17251 break;
17252 case E_CONTENT_EDITOR_BLOCK_FORMAT_PARAGRAPH:
17253 value = "DIV";
17254 break;
17255 case E_CONTENT_EDITOR_BLOCK_FORMAT_PRE:
17256 value = "PRE";
17257 break;
17258 case E_CONTENT_EDITOR_BLOCK_FORMAT_ADDRESS:
17259 value = "ADDRESS";
17260 break;
17261 case E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST:
17262 case E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST_ALPHA:
17263 case E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST_ROMAN:
17264 to_list = TRUE;
17265 value = NULL;
17266 break;
17267 case E_CONTENT_EDITOR_BLOCK_FORMAT_UNORDERED_LIST:
17268 to_list = TRUE;
17269 value = NULL;
17270 break;
17271 case E_CONTENT_EDITOR_BLOCK_FORMAT_NONE:
17272 default:
17273 value = NULL;
17274 break;
17277 html_mode = e_editor_page_get_html_mode (editor_page);
17279 from_list =
17280 current_format >= E_CONTENT_EDITOR_BLOCK_FORMAT_UNORDERED_LIST;
17282 range = e_editor_dom_get_current_range (editor_page);
17283 if (!range)
17284 return;
17286 current_alignment = e_editor_page_get_alignment (editor_page);
17288 e_editor_dom_selection_save (editor_page);
17290 manager = e_editor_page_get_undo_redo_manager (editor_page);
17291 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
17292 ev = g_new0 (EEditorHistoryEvent, 1);
17293 ev->type = HISTORY_BLOCK_FORMAT;
17295 e_editor_dom_selection_get_coordinates (editor_page,
17296 &ev->before.start.x,
17297 &ev->before.start.y,
17298 &ev->before.end.x,
17299 &ev->before.end.y);
17301 ev->data.style.from = current_format;
17302 ev->data.style.to = format;
17305 g_clear_object (&range);
17307 if (current_format == E_CONTENT_EDITOR_BLOCK_FORMAT_PRE) {
17308 WebKitDOMElement *selection_marker;
17310 selection_marker = webkit_dom_document_get_element_by_id (
17311 document, "-x-evo-selection-start-marker");
17312 if (selection_marker)
17313 change_space_before_selection_to_nbsp (WEBKIT_DOM_NODE (selection_marker));
17314 selection_marker = webkit_dom_document_get_element_by_id (
17315 document, "-x-evo-selection-end-marker");
17316 if (selection_marker)
17317 change_space_before_selection_to_nbsp (WEBKIT_DOM_NODE (selection_marker));
17320 if (from_list && to_list)
17321 format_change_list_to_list (editor_page, format, html_mode);
17323 if (!from_list && !to_list)
17324 format_change_block_to_block (editor_page, format, value);
17326 if (from_list && !to_list)
17327 format_change_list_to_block (editor_page, format, value);
17329 if (!from_list && to_list)
17330 format_change_block_to_list (editor_page, format);
17332 e_editor_dom_selection_restore (editor_page);
17334 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
17336 /* When changing the format we need to re-set the alignment */
17337 e_editor_dom_selection_set_alignment (editor_page, current_alignment);
17339 e_editor_page_emit_content_changed (editor_page);
17341 if (ev) {
17342 e_editor_dom_selection_get_coordinates (editor_page,
17343 &ev->after.start.x,
17344 &ev->after.start.y,
17345 &ev->after.end.x,
17346 &ev->after.end.y);
17347 e_editor_undo_redo_manager_insert_history_event (manager, ev);
17352 * e_html_editor_selection_get_background_color:
17353 * @selection: an #EEditorSelection
17355 * Returns background color of currently selected text or letter at current
17356 * cursor position.
17358 * Returns: A string with code of current background color.
17360 gchar *
17361 e_editor_dom_selection_get_background_color (EEditorPage *editor_page)
17363 WebKitDOMNode *ancestor;
17364 WebKitDOMRange *range = NULL;
17365 WebKitDOMCSSStyleDeclaration *css = NULL;
17366 gchar *value;
17368 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
17370 range = e_editor_dom_get_current_range (editor_page);
17371 ancestor = webkit_dom_range_get_common_ancestor_container (range, NULL);
17372 css = webkit_dom_element_get_style (WEBKIT_DOM_ELEMENT (ancestor));
17373 /* FIXME WK2
17374 g_free (selection->priv->background_color);
17375 selection->priv->background_color =
17376 webkit_dom_css_style_declaration_get_property_value (
17377 css, "background-color");*/
17379 value = webkit_dom_css_style_declaration_get_property_value (css, "background-color");
17381 g_clear_object (&css);
17382 g_clear_object (&range);
17384 return value;
17388 * e_html_editor_selection_set_background_color:
17389 * @selection: an #EEditorSelection
17390 * @color: code of new background color to set
17392 * Changes background color of current selection or letter at current cursor
17393 * position to @color.
17395 void
17396 e_editor_dom_selection_set_background_color (EEditorPage *editor_page,
17397 const gchar *color)
17399 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17401 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_BACKGROUND_COLOR, color);
17405 * e_html_editor_selection_get_alignment:
17406 * @selection: #an EEditorSelection
17408 * Returns alignment of current paragraph
17410 * Returns: #EContentEditorAlignment
17412 EContentEditorAlignment
17413 e_editor_dom_selection_get_alignment (EEditorPage *editor_page)
17415 WebKitDOMCSSStyleDeclaration *style = NULL;
17416 WebKitDOMElement *element;
17417 WebKitDOMNode *node;
17418 WebKitDOMRange *range = NULL;
17419 EContentEditorAlignment alignment;
17420 gchar *value;
17422 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), E_CONTENT_EDITOR_ALIGNMENT_LEFT);
17424 range = e_editor_dom_get_current_range (editor_page);
17425 if (!range) {
17426 alignment = E_CONTENT_EDITOR_ALIGNMENT_LEFT;
17427 goto out;
17430 node = webkit_dom_range_get_start_container (range, NULL);
17431 g_clear_object (&range);
17432 if (!node) {
17433 alignment = E_CONTENT_EDITOR_ALIGNMENT_LEFT;
17434 goto out;
17437 if (WEBKIT_DOM_IS_ELEMENT (node))
17438 element = WEBKIT_DOM_ELEMENT (node);
17439 else
17440 element = webkit_dom_node_get_parent_element (node);
17442 if (element_has_class (element, "-x-evo-align-right")) {
17443 alignment = E_CONTENT_EDITOR_ALIGNMENT_RIGHT;
17444 goto out;
17445 } else if (element_has_class (element, "-x-evo-align-center")) {
17446 alignment = E_CONTENT_EDITOR_ALIGNMENT_CENTER;
17447 goto out;
17450 style = webkit_dom_element_get_style (element);
17451 value = webkit_dom_css_style_declaration_get_property_value (style, "text-align");
17453 if (!value || !*value ||
17454 (g_ascii_strncasecmp (value, "left", 4) == 0)) {
17455 alignment = E_CONTENT_EDITOR_ALIGNMENT_LEFT;
17456 } else if (g_ascii_strncasecmp (value, "center", 6) == 0) {
17457 alignment = E_CONTENT_EDITOR_ALIGNMENT_CENTER;
17458 } else if (g_ascii_strncasecmp (value, "right", 5) == 0) {
17459 alignment = E_CONTENT_EDITOR_ALIGNMENT_RIGHT;
17460 } else {
17461 alignment = E_CONTENT_EDITOR_ALIGNMENT_LEFT;
17464 g_clear_object (&style);
17465 g_free (value);
17467 out:
17468 return alignment;
17471 static void
17472 set_block_alignment (WebKitDOMElement *element,
17473 const gchar *class)
17475 WebKitDOMElement *parent;
17477 element_remove_class (element, "-x-evo-align-center");
17478 element_remove_class (element, "-x-evo-align-right");
17479 element_add_class (element, class);
17480 parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (element));
17481 while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
17482 element_remove_class (parent, "-x-evo-align-center");
17483 element_remove_class (parent, "-x-evo-align-right");
17484 parent = webkit_dom_node_get_parent_element (
17485 WEBKIT_DOM_NODE (parent));
17490 * e_html_editor_selection_set_alignment:
17491 * @selection: an #EEditorSelection
17492 * @alignment: an #EContentEditorAlignment value to apply
17494 * Sets alignment of current paragraph to give @alignment.
17496 void
17497 e_editor_dom_selection_set_alignment (EEditorPage *editor_page,
17498 EContentEditorAlignment alignment)
17500 WebKitDOMDocument *document;
17501 WebKitDOMElement *selection_start_marker, *selection_end_marker;
17502 WebKitDOMNode *block;
17503 EContentEditorAlignment current_alignment;
17504 EEditorUndoRedoManager *manager;
17505 EEditorHistoryEvent *ev = NULL;
17506 gboolean after_selection_end = FALSE;
17507 const gchar *class = "";
17509 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17511 document = e_editor_page_get_document (editor_page);
17512 current_alignment = e_editor_page_get_alignment (editor_page);
17514 if (current_alignment == alignment)
17515 return;
17517 switch (alignment) {
17518 case E_CONTENT_EDITOR_ALIGNMENT_CENTER:
17519 class = "-x-evo-align-center";
17520 break;
17522 case E_CONTENT_EDITOR_ALIGNMENT_LEFT:
17523 break;
17525 case E_CONTENT_EDITOR_ALIGNMENT_RIGHT:
17526 class = "-x-evo-align-right";
17527 break;
17530 e_editor_dom_selection_save (editor_page);
17532 selection_start_marker = webkit_dom_document_get_element_by_id (
17533 document, "-x-evo-selection-start-marker");
17534 selection_end_marker = webkit_dom_document_get_element_by_id (
17535 document, "-x-evo-selection-end-marker");
17537 if (!selection_start_marker)
17538 return;
17540 manager = e_editor_page_get_undo_redo_manager (editor_page);
17541 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
17542 ev = g_new0 (EEditorHistoryEvent, 1);
17543 ev->type = HISTORY_ALIGNMENT;
17545 e_editor_dom_selection_get_coordinates (editor_page,
17546 &ev->before.start.x,
17547 &ev->before.start.y,
17548 &ev->before.end.x,
17549 &ev->before.end.y);
17550 ev->data.style.from = current_alignment;
17551 ev->data.style.to = alignment;
17554 block = e_editor_dom_get_parent_block_node_from_child (
17555 WEBKIT_DOM_NODE (selection_start_marker));
17557 while (block && !after_selection_end) {
17558 WebKitDOMNode *next_block;
17560 next_block = webkit_dom_node_get_next_sibling (block);
17562 after_selection_end = webkit_dom_node_contains (
17563 block, WEBKIT_DOM_NODE (selection_end_marker));
17565 if (element_has_class (WEBKIT_DOM_ELEMENT (block), "-x-evo-indented")) {
17566 gint ii;
17567 WebKitDOMNodeList *list = NULL;
17569 list = webkit_dom_element_query_selector_all (
17570 WEBKIT_DOM_ELEMENT (block),
17571 ".-x-evo-indented > *:not(.-x-evo-indented):not(li)",
17572 NULL);
17573 for (ii = webkit_dom_node_list_get_length (list); ii--;) {
17574 WebKitDOMNode *item = webkit_dom_node_list_item (list, ii);
17576 set_block_alignment (WEBKIT_DOM_ELEMENT (item), class);
17578 after_selection_end = webkit_dom_node_contains (
17579 item, WEBKIT_DOM_NODE (selection_end_marker));
17580 if (after_selection_end)
17581 break;
17584 g_clear_object (&list);
17585 } else {
17586 set_block_alignment (WEBKIT_DOM_ELEMENT (block), class);
17589 block = next_block;
17592 if (ev) {
17593 e_editor_dom_selection_get_coordinates (editor_page,
17594 &ev->after.start.x,
17595 &ev->after.start.y,
17596 &ev->after.end.x,
17597 &ev->after.end.y);
17598 e_editor_undo_redo_manager_insert_history_event (manager, ev);
17601 e_editor_dom_selection_restore (editor_page);
17603 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
17604 e_editor_page_emit_content_changed (editor_page);
17607 void
17608 e_editor_dom_insert_replace_all_history_event (EEditorPage *editor_page,
17609 const gchar *search_text,
17610 const gchar *replacement)
17612 EEditorUndoRedoManager *manager;
17614 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17616 manager = e_editor_page_get_undo_redo_manager (editor_page);
17618 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
17619 EEditorHistoryEvent *ev = g_new0 (EEditorHistoryEvent, 1);
17620 ev->type = HISTORY_REPLACE_ALL;
17622 ev->data.string.from = g_strdup (search_text);
17623 ev->data.string.to = g_strdup (replacement);
17625 e_editor_undo_redo_manager_insert_history_event (manager, ev);
17630 * e_html_editor_selection_replace:
17631 * @selection: an #EEditorSelection
17632 * @replacement: a string to replace current selection with
17634 * Replaces currently selected text with @replacement.
17636 void
17637 e_editor_dom_selection_replace (EEditorPage *editor_page,
17638 const gchar *replacement)
17640 EEditorHistoryEvent *ev = NULL;
17641 EEditorUndoRedoManager *manager;
17642 WebKitDOMRange *range = NULL;
17644 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17646 manager = e_editor_page_get_undo_redo_manager (editor_page);
17648 if (!(range = e_editor_dom_get_current_range (editor_page)) ||
17649 e_editor_dom_selection_is_collapsed (editor_page))
17650 return;
17652 if (!e_editor_undo_redo_manager_is_operation_in_progress (manager)) {
17653 ev = g_new0 (EEditorHistoryEvent, 1);
17654 ev->type = HISTORY_REPLACE;
17656 e_editor_dom_selection_get_coordinates (editor_page,
17657 &ev->before.start.x,
17658 &ev->before.start.y,
17659 &ev->before.end.x,
17660 &ev->before.end.y);
17662 ev->data.string.from = webkit_dom_range_get_text (range);
17663 ev->data.string.to = g_strdup (replacement);
17666 g_clear_object (&range);
17668 e_editor_dom_exec_command (editor_page, E_CONTENT_EDITOR_COMMAND_INSERT_TEXT, replacement);
17670 if (ev) {
17671 e_editor_dom_selection_get_coordinates (editor_page,
17672 &ev->after.start.x,
17673 &ev->after.start.y,
17674 &ev->after.end.x,
17675 &ev->after.end.y);
17677 e_editor_undo_redo_manager_insert_history_event (manager, ev);
17680 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
17682 e_editor_page_emit_content_changed (editor_page);
17686 * e_html_editor_selection_replace_caret_word:
17687 * @selection: an #EEditorSelection
17688 * @replacement: a string to replace current caret word with
17690 * Replaces current word under cursor with @replacement.
17692 void
17693 e_editor_dom_replace_caret_word (EEditorPage *editor_page,
17694 const gchar *replacement)
17696 WebKitDOMDocument *document;
17697 WebKitDOMDOMWindow *dom_window = NULL;
17698 WebKitDOMDOMSelection *dom_selection = NULL;
17699 WebKitDOMDocumentFragment *fragment;
17700 WebKitDOMNode *node;
17701 WebKitDOMRange *range = NULL;
17703 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17705 document = e_editor_page_get_document (editor_page);
17706 dom_window = webkit_dom_document_get_default_view (document);
17707 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
17708 g_clear_object (&dom_window);
17710 e_editor_page_emit_content_changed (editor_page);
17711 range = e_editor_dom_get_current_range (editor_page);
17712 webkit_dom_range_expand (range, "word", NULL);
17713 webkit_dom_dom_selection_add_range (dom_selection, range);
17715 fragment = webkit_dom_range_extract_contents (range, NULL);
17717 /* Get the text node to replace and leave other formatting nodes
17718 * untouched (font color, boldness, ...). */
17719 webkit_dom_node_normalize (WEBKIT_DOM_NODE (fragment));
17720 node = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment));
17721 if (!WEBKIT_DOM_IS_TEXT (node)) {
17722 while (node && WEBKIT_DOM_IS_ELEMENT (node))
17723 node = webkit_dom_node_get_first_child (node);
17726 if (node && WEBKIT_DOM_IS_TEXT (node)) {
17727 WebKitDOMText *text;
17729 /* Replace the word */
17730 text = webkit_dom_document_create_text_node (document, replacement);
17731 webkit_dom_node_replace_child (
17732 webkit_dom_node_get_parent_node (node),
17733 WEBKIT_DOM_NODE (text),
17734 node,
17735 NULL);
17737 /* Insert the word on current location. */
17738 webkit_dom_range_insert_node (range, WEBKIT_DOM_NODE (fragment), NULL);
17740 webkit_dom_dom_selection_collapse_to_end (dom_selection, NULL);
17743 e_editor_dom_force_spell_check_for_current_paragraph (editor_page);
17745 g_clear_object (&range);
17746 g_clear_object (&dom_selection);
17750 * e_html_editor_selection_get_caret_word:
17751 * @selection: an #EEditorSelection
17753 * Returns word under cursor.
17755 * Returns: A newly allocated string with current caret word or @NULL when there
17756 * is no text under cursor or when selection is active. [transfer-full].
17758 gchar *
17759 e_editor_dom_get_caret_word (EEditorPage *editor_page)
17761 gchar *word;
17762 WebKitDOMRange *range = NULL, *range_clone = NULL;
17764 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
17766 range = e_editor_dom_get_current_range (editor_page);
17768 /* Don't operate on the visible selection */
17769 range_clone = webkit_dom_range_clone_range (range, NULL);
17770 webkit_dom_range_expand (range_clone, "word", NULL);
17771 word = webkit_dom_range_to_string (range_clone, NULL);
17773 g_clear_object (&range);
17774 g_clear_object (&range_clone);
17776 return word;
17780 * e_html_editor_selection_get_list_alignment_from_node:
17781 * @node: #an WebKitDOMNode
17783 * Returns alignment of given list.
17785 * Returns: #EContentEditorAlignment
17787 EContentEditorAlignment
17788 e_editor_dom_get_list_alignment_from_node (WebKitDOMNode *node)
17790 if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-align-center"))
17791 return E_CONTENT_EDITOR_ALIGNMENT_CENTER;
17792 if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-align-right"))
17793 return E_CONTENT_EDITOR_ALIGNMENT_RIGHT;
17794 else
17795 return E_CONTENT_EDITOR_ALIGNMENT_LEFT;
17798 WebKitDOMElement *
17799 e_editor_dom_prepare_paragraph (EEditorPage *editor_page,
17800 gboolean with_selection)
17802 WebKitDOMDocument *document;
17803 WebKitDOMElement *element, *paragraph;
17805 g_return_val_if_fail (E_IS_EDITOR_PAGE (editor_page), NULL);
17807 document = e_editor_page_get_document (editor_page);
17808 paragraph = e_editor_dom_get_paragraph_element (editor_page, -1, 0);
17810 if (with_selection)
17811 dom_add_selection_markers_into_element_start (
17812 document, paragraph, NULL, NULL);
17814 element = webkit_dom_document_create_element (document, "BR", NULL);
17816 webkit_dom_node_append_child (
17817 WEBKIT_DOM_NODE (paragraph), WEBKIT_DOM_NODE (element), NULL);
17819 return paragraph;
17822 void
17823 e_editor_dom_selection_set_on_point (EEditorPage *editor_page,
17824 guint x,
17825 guint y)
17827 WebKitDOMDocument *document;
17828 WebKitDOMRange *range = NULL;
17829 WebKitDOMDOMWindow *dom_window = NULL;
17830 WebKitDOMDOMSelection *dom_selection = NULL;
17832 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17834 document = e_editor_page_get_document (editor_page);
17835 dom_window = webkit_dom_document_get_default_view (document);
17836 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
17838 range = webkit_dom_document_caret_range_from_point (document, x, y);
17839 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
17840 webkit_dom_dom_selection_add_range (dom_selection, range);
17842 g_clear_object (&range);
17843 g_clear_object (&dom_selection);
17844 g_clear_object (&dom_window);
17847 void
17848 e_editor_dom_selection_get_coordinates (EEditorPage *editor_page,
17849 guint *start_x,
17850 guint *start_y,
17851 guint *end_x,
17852 guint *end_y)
17854 WebKitDOMDocument *document;
17855 WebKitDOMElement *element, *parent;
17856 gboolean created_selection_markers = FALSE;
17857 guint local_x = 0, local_y = 0;
17859 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17860 g_return_if_fail (start_x != NULL);
17861 g_return_if_fail (start_y != NULL);
17862 g_return_if_fail (end_x != NULL);
17863 g_return_if_fail (end_y != NULL);
17865 document = e_editor_page_get_document (editor_page);
17866 element = webkit_dom_document_get_element_by_id (
17867 document, "-x-evo-selection-start-marker");
17868 if (!element) {
17869 created_selection_markers = TRUE;
17870 e_editor_dom_selection_save (editor_page);
17871 element = webkit_dom_document_get_element_by_id (
17872 document, "-x-evo-selection-start-marker");
17873 if (!element)
17874 return;
17877 parent = element;
17878 while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
17879 local_x += (guint) webkit_dom_element_get_offset_left (parent);
17880 local_y += (guint) webkit_dom_element_get_offset_top (parent);
17881 parent = webkit_dom_element_get_offset_parent (parent);
17884 *start_x = local_x;
17885 *start_y = local_y;
17887 if (e_editor_dom_selection_is_collapsed (editor_page)) {
17888 *end_x = local_x;
17889 *end_y = local_y;
17891 if (created_selection_markers)
17892 e_editor_dom_selection_restore (editor_page);
17894 goto workaroud;
17897 element = webkit_dom_document_get_element_by_id (
17898 document, "-x-evo-selection-end-marker");
17900 local_x = 0;
17901 local_y = 0;
17903 parent = element;
17904 while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
17905 local_x += (guint) webkit_dom_element_get_offset_left (parent);
17906 local_y += (guint) webkit_dom_element_get_offset_top (parent);
17907 parent = webkit_dom_element_get_offset_parent (parent);
17910 *end_x = local_x;
17911 *end_y = local_y;
17913 if (created_selection_markers)
17914 e_editor_dom_selection_restore (editor_page);
17916 workaroud:
17917 /* Workaround for bug 749712 on the Evolution side. The cause of the bug
17918 * is that WebKit is having problems determining the right line height
17919 * for some fonts and font sizes (the right and wrong value differ by 1).
17920 * To fix this we will add an extra one to the final top offset. This is
17921 * safe to do even for fonts and font sizes that don't behave badly as we
17922 * will still get the right element as we use fonts bigger than 1 pixel. */
17923 *start_y += 1;
17924 *end_y += 1;
17927 WebKitDOMRange *
17928 e_editor_dom_get_range_for_point (WebKitDOMDocument *document,
17929 EEditorSelectionPoint point)
17931 glong scroll_left, scroll_top;
17932 WebKitDOMHTMLElement *body;
17933 WebKitDOMRange *range = NULL;
17935 body = webkit_dom_document_get_body (document);
17936 scroll_left = webkit_dom_element_get_scroll_left (WEBKIT_DOM_ELEMENT (body));
17937 scroll_top = webkit_dom_element_get_scroll_top (WEBKIT_DOM_ELEMENT (body));
17939 range = webkit_dom_document_caret_range_from_point (
17940 document, point.x - scroll_left, point.y - scroll_top);
17942 /* The point is outside the viewport, scroll to it. */
17943 if (!range) {
17944 WebKitDOMDOMWindow *dom_window = NULL;
17946 dom_window = webkit_dom_document_get_default_view (document);
17947 webkit_dom_dom_window_scroll_to (dom_window, point.x, point.y);
17949 scroll_left = webkit_dom_element_get_scroll_left (WEBKIT_DOM_ELEMENT (body));
17950 scroll_top = webkit_dom_element_get_scroll_top (WEBKIT_DOM_ELEMENT (body));
17951 range = webkit_dom_document_caret_range_from_point (
17952 document, point.x - scroll_left, point.y - scroll_top);
17953 g_clear_object (&dom_window);
17956 return range;
17959 void
17960 e_editor_dom_selection_restore_to_history_event_state (EEditorPage *editor_page,
17961 EEditorSelection selection_state)
17963 WebKitDOMDocument *document;
17964 WebKitDOMDOMWindow *dom_window = NULL;
17965 WebKitDOMDOMSelection *dom_selection = NULL;
17966 WebKitDOMElement *element, *tmp;
17967 WebKitDOMRange *range = NULL;
17968 gboolean was_collapsed = FALSE;
17970 g_return_if_fail (E_IS_EDITOR_PAGE (editor_page));
17972 document = e_editor_page_get_document (editor_page);
17973 dom_window = webkit_dom_document_get_default_view (document);
17974 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
17975 g_clear_object (&dom_window);
17977 /* Restore the selection how it was before the event occured. */
17978 range = e_editor_dom_get_range_for_point (document, selection_state.start);
17979 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
17980 webkit_dom_dom_selection_add_range (dom_selection, range);
17981 g_clear_object (&range);
17983 was_collapsed = selection_state.start.x == selection_state.end.x;
17984 was_collapsed = was_collapsed && selection_state.start.y == selection_state.end.y;
17985 if (was_collapsed) {
17986 g_clear_object (&dom_selection);
17987 return;
17990 e_editor_dom_selection_save (editor_page);
17992 element = webkit_dom_document_get_element_by_id (
17993 document, "-x-evo-selection-end-marker");
17995 remove_node (WEBKIT_DOM_NODE (element));
17997 element = webkit_dom_document_get_element_by_id (
17998 document, "-x-evo-selection-start-marker");
18000 webkit_dom_element_remove_attribute (element, "id");
18002 range = e_editor_dom_get_range_for_point (document, selection_state.end);
18003 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
18004 webkit_dom_dom_selection_add_range (dom_selection, range);
18005 g_clear_object (&range);
18007 e_editor_dom_selection_save (editor_page);
18009 tmp = webkit_dom_document_get_element_by_id (
18010 document, "-x-evo-selection-start-marker");
18012 remove_node (WEBKIT_DOM_NODE (tmp));
18014 webkit_dom_element_set_id (
18015 element, "-x-evo-selection-start-marker");
18017 e_editor_dom_selection_restore (editor_page);
18019 g_clear_object (&dom_selection);