1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*  This file is part of the GtkHTML library.
3  *
4  *  Copyright (C) 1999, 2000 Helix Code, Inc.
5  *  Copyright (C) 2001 Ximian, Inc.
6  *
7  *  This library is free software; you can redistribute it and/or
8  *  modify it under the terms of the GNU Library General Public
9  *  License as published by the Free Software Foundation; either
10  *  version 2 of the License, or (at your option) any later version.
11  *
12  *  This library is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *  Library General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Library General Public License
18  *  along with this library; see the file COPYING.LIB.  If not, write to
19  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  *  Boston, MA 02110-1301, USA.
21  *
22  *  Authors: Ettore Perazzoli <ettore@helixcode.com>
23  *           Radek Doulik     <rodo@ximian.com>
24 */
25 
26 
27 #include <config.h>
28 #include <ctype.h>
29 #include <string.h>
30 #include <glib.h>
31 
32 #include "gtkhtml.h"
33 #include "gtkhtml-properties.h"
34 
35 #include "htmlclueflow.h"
36 #include "htmlcolorset.h"
37 #include "htmlcursor.h"
38 #include "htmlobject.h"
39 #include "htmltable.h"
40 #include "htmltablecell.h"
41 #include "htmltext.h"
42 #include "htmltextslave.h"
43 #include "htmlimage.h"
44 #include "htmlinterval.h"
45 #include "htmlselection.h"
46 #include "htmlsettings.h"
47 #include "htmlundo.h"
48 
49 #include "htmlengine-edit.h"
50 #include "htmlengine-edit-cut-and-paste.h"
51 #include "htmlengine-edit-cursor.h"
52 #include "htmlengine-edit-movement.h"
53 #include "htmlengine-edit-selection-updater.h"
54 #include "htmlengine-edit-table.h"
55 #include "htmlengine-edit-tablecell.h"
56 
57 
58 void
html_engine_undo(HTMLEngine * e)59 html_engine_undo (HTMLEngine *e)
60 {
61 	HTMLUndo *undo;
62 
63 	g_return_if_fail (e != NULL);
64 	g_return_if_fail (HTML_IS_ENGINE (e));
65 	g_return_if_fail (e->undo != NULL);
66 	g_return_if_fail (e->editable);
67 
68 	html_engine_unselect_all (e);
69 
70 	undo = e->undo;
71 	html_undo_do_undo (undo, e);
72 }
73 
74 void
html_engine_redo(HTMLEngine * e)75 html_engine_redo (HTMLEngine *e)
76 {
77 	HTMLUndo *undo;
78 
79 	g_return_if_fail (e != NULL);
80 	g_return_if_fail (HTML_IS_ENGINE (e));
81 	g_return_if_fail (e->undo != NULL);
82 
83 	html_engine_unselect_all (e);
84 
85 	undo = e->undo;
86 	html_undo_do_redo (undo, e);
87 }
88 
89 
90 void
html_engine_set_mark(HTMLEngine * e)91 html_engine_set_mark (HTMLEngine *e)
92 {
93 	g_return_if_fail (e != NULL);
94 	g_return_if_fail (HTML_IS_ENGINE (e));
95 	g_return_if_fail (e->editable || e->caret_mode);
96 
97 	if (e->mark != NULL) {
98 		html_engine_unselect_all (e);
99 		html_cursor_destroy (e->mark);
100 	}
101 
102 	e->mark = html_cursor_dup (e->cursor);
103 
104 	html_engine_edit_selection_updater_reset (e->selection_updater);
105 	html_engine_edit_selection_updater_schedule (e->selection_updater);
106 }
107 
108 void
html_engine_clipboard_push(HTMLEngine * e)109 html_engine_clipboard_push (HTMLEngine *e)
110 {
111 	e->clipboard_stack = g_list_prepend (e->clipboard_stack, GUINT_TO_POINTER (e->clipboard_len));
112 	e->clipboard_stack = g_list_prepend (e->clipboard_stack, e->clipboard);
113 	e->clipboard       = NULL;
114 }
115 
116 void
html_engine_clipboard_pop(HTMLEngine * e)117 html_engine_clipboard_pop (HTMLEngine *e)
118 {
119 	g_assert (e->clipboard_stack);
120 
121 	e->clipboard       = HTML_OBJECT (e->clipboard_stack->data);
122 	e->clipboard_stack = g_list_remove (e->clipboard_stack, e->clipboard_stack->data);
123 	e->clipboard_len   = GPOINTER_TO_UINT (e->clipboard_stack->data);
124 	e->clipboard_stack = g_list_remove (e->clipboard_stack, e->clipboard_stack->data);
125 }
126 
127 typedef struct
128 {
129 	gboolean active;
130 	gint cursor;
131 	gint mark;
132 } Selection;
133 
134 void
html_engine_selection_push(HTMLEngine * e)135 html_engine_selection_push (HTMLEngine *e)
136 {
137 	Selection *selection = g_new (Selection, 1);
138 
139 	if (html_engine_is_selection_active (e)) {
140 		selection->active = TRUE;
141 		selection->cursor = html_cursor_get_position (e->cursor);
142 		selection->mark = html_cursor_get_position (e->mark);
143 	} else {
144 		selection->active = FALSE;
145 		selection->cursor = -1;
146 		selection->mark = -1;
147 	}
148 
149 	e->selection_stack = g_list_prepend (e->selection_stack, selection);
150 }
151 
152 void
html_engine_selection_pop(HTMLEngine * e)153 html_engine_selection_pop (HTMLEngine *e)
154 {
155 	Selection *selection;
156 
157 	g_assert (e->selection_stack);
158 
159 	selection = e->selection_stack->data;
160 	e->selection_stack = g_list_delete_link (e->selection_stack, e->selection_stack);
161 
162 	html_engine_disable_selection (e);
163 
164 	if (selection->active) {
165 		html_cursor_jump_to_position (e->cursor, e, selection->mark);
166 		html_engine_set_mark (e);
167 		html_cursor_jump_to_position (e->cursor, e, selection->cursor);
168 	}
169 	html_engine_edit_selection_updater_update_now (e->selection_updater);
170 
171 	g_free (selection);
172 }
173 
174 gboolean
html_engine_selection_stack_top(HTMLEngine * e,gint * cpos,gint * mpos)175 html_engine_selection_stack_top (HTMLEngine *e,
176                                  gint *cpos,
177                                  gint *mpos)
178 {
179 	Selection *selection = e->selection_stack ? e->selection_stack->data : NULL;
180 
181 	if (selection && selection->active) {
182 		if (cpos)
183 			*cpos = selection->cursor;
184 		if (mpos)
185 			*mpos = selection->mark;
186 
187 		return TRUE;
188 	}
189 
190 	return FALSE;
191 }
192 
193 gboolean
html_engine_selection_stack_top_modify(HTMLEngine * e,gint delta)194 html_engine_selection_stack_top_modify (HTMLEngine *e,
195                                         gint delta)
196 {
197 	Selection *selection = e->selection_stack ? e->selection_stack->data : NULL;
198 
199 	if (selection && selection->active) {
200 		selection->cursor += delta;
201 		selection->mark += delta;
202 
203 		return TRUE;
204 	}
205 
206 	return FALSE;
207 }
208 
209 static void
spell_check_object(HTMLObject * o,HTMLEngine * e,gpointer data)210 spell_check_object (HTMLObject *o,
211                     HTMLEngine *e,
212                     gpointer data)
213 {
214 	if (HTML_OBJECT_TYPE (o) == HTML_TYPE_CLUEFLOW)
215 		html_clueflow_spell_check (HTML_CLUEFLOW (o), e, (HTMLInterval *) data);
216 }
217 
218 void
html_engine_spell_check_range(HTMLEngine * e,HTMLCursor * begin,HTMLCursor * end)219 html_engine_spell_check_range (HTMLEngine *e,
220                                HTMLCursor *begin,
221                                HTMLCursor *end)
222 {
223 	HTMLInterval *i;
224 	gboolean cited;
225 
226 	e->need_spell_check = FALSE;
227 
228 	if (!e->widget->editor_api || !gtk_html_get_inline_spelling (e->widget) || !begin->object->parent)
229 		return;
230 
231 	begin = html_cursor_dup (begin);
232 	end   = html_cursor_dup (end);
233 
234 	cited = FALSE;
235 	while (html_selection_spell_word (html_cursor_get_prev_char (begin), &cited) || cited) {
236 		if (html_cursor_backward (begin, e)) { ; }
237 		cited = FALSE;
238 	}
239 
240 	cited = FALSE;
241 	while (html_selection_spell_word (html_cursor_get_current_char (end), &cited) || cited) {
242 		if (html_cursor_forward (end, e)) { ; }
243 		cited = FALSE;
244 	}
245 
246 	i = html_interval_new_from_cursor (begin, end);
247 	if (begin->object->parent != end->object->parent)
248 		html_interval_forall (i, e, spell_check_object, i);
249 	else if (HTML_IS_CLUEFLOW (begin->object->parent))
250 		html_clueflow_spell_check (HTML_CLUEFLOW (begin->object->parent), e, i);
251 	html_interval_destroy (i);
252 	html_cursor_destroy (begin);
253 	html_cursor_destroy (end);
254 }
255 
256 gboolean
html_is_in_word(gunichar uc)257 html_is_in_word (gunichar uc)
258 {
259 	/* printf ("test %d %c => %d\n", uc, uc, g_unichar_isalnum (uc) || uc == '\''); */
260 	return g_unichar_isalpha (uc) || uc == '\'';
261 }
262 
263 void
html_engine_select_word_editable(HTMLEngine * e)264 html_engine_select_word_editable (HTMLEngine *e)
265 {
266 	while (html_selection_word (html_cursor_get_prev_char (e->cursor)))
267 		html_cursor_backward (e->cursor, e);
268 	html_engine_set_mark (e);
269 	while (html_selection_word (html_cursor_get_current_char (e->cursor)))
270 		html_cursor_forward (e->cursor, e);
271 }
272 
273 void
html_engine_select_spell_word_editable(HTMLEngine * e)274 html_engine_select_spell_word_editable (HTMLEngine *e)
275 {
276 	gboolean cited, cited2;
277 
278 	cited = cited2 = FALSE;
279 	while (html_selection_spell_word (html_cursor_get_prev_char (e->cursor), &cited) || cited) {
280 		html_cursor_backward (e->cursor, e);
281 		cited2 = cited;
282 		cited = FALSE;
283 	}
284 
285 	if (cited2) {
286 		html_cursor_forward (e->cursor, e);
287 		cited = TRUE;
288 	}
289 
290 	html_engine_set_mark (e);
291 	while (html_selection_spell_word (html_cursor_get_current_char (e->cursor), &cited2) || (!cited && cited2)) {
292 		html_cursor_forward (e->cursor, e);
293 		cited2 = FALSE;
294 	}
295 }
296 
297 void
html_engine_select_line_editable(HTMLEngine * e)298 html_engine_select_line_editable (HTMLEngine *e)
299 {
300 	html_engine_beginning_of_line (e);
301 	html_engine_set_mark (e);
302 	html_engine_end_of_line (e);
303 }
304 
305 void
html_engine_select_paragraph_editable(HTMLEngine * e)306 html_engine_select_paragraph_editable (HTMLEngine *e)
307 {
308 	html_engine_beginning_of_paragraph (e);
309 	html_engine_set_mark (e);
310 	html_engine_end_of_paragraph (e);
311 }
312 
313 void
html_engine_select_paragraph_extended(HTMLEngine * e)314 html_engine_select_paragraph_extended (HTMLEngine *e)
315 {
316 	gboolean fw;
317 
318 	html_engine_hide_cursor (e);
319 	html_engine_beginning_of_paragraph (e);
320 	fw = html_cursor_backward (e->cursor, e);
321 	html_engine_set_mark (e);
322 	if (fw)
323 		html_cursor_forward (e->cursor, e);
324 	html_engine_end_of_paragraph (e);
325 	html_cursor_forward (e->cursor, e);
326 	html_engine_show_cursor (e);
327 
328 	html_engine_update_selection_if_necessary (e);
329 }
330 
331 void
html_engine_select_all_editable(HTMLEngine * e)332 html_engine_select_all_editable (HTMLEngine *e)
333 {
334 	html_engine_beginning_of_document (e);
335 	html_engine_set_mark (e);
336 	html_engine_end_of_document (e);
337 }
338 
339 struct SetData {
340 	HTMLType      object_type;
341 	const gchar  *key;
342 	const gchar  *value;
343 };
344 
345 static void
set_data(HTMLObject * o,HTMLEngine * e,gpointer p)346 set_data (HTMLObject *o,
347           HTMLEngine *e,
348           gpointer p)
349 {
350 	struct SetData *data = (struct SetData *) p;
351 
352 	if (HTML_OBJECT_TYPE (o) == data->object_type) {
353 		/* printf ("set data %s --> %p\n", data->key, data->value); */
354 		html_object_set_data (o, data->key, data->value);
355 	}
356 }
357 
358 void
html_engine_set_data_by_type(HTMLEngine * e,HTMLType object_type,const gchar * key,const gchar * value)359 html_engine_set_data_by_type (HTMLEngine *e,
360                               HTMLType object_type,
361                               const gchar *key,
362                               const gchar *value)
363 {
364 	struct SetData *data = g_new (struct SetData, 1);
365 
366 	/* printf ("html_engine_set_data_by_type %s\n", key); */
367 
368 	data->object_type = object_type;
369 	data->key         = key;
370 	data->value       = value;
371 
372 	html_object_forall (e->clue, NULL, set_data, data);
373 
374 	g_free (data);
375 }
376 
377 void
html_engine_clipboard_clear(HTMLEngine * e)378 html_engine_clipboard_clear (HTMLEngine *e)
379 {
380 	if (e->clipboard) {
381 		html_object_destroy (e->clipboard);
382 		e->clipboard = NULL;
383 	}
384 }
385 
386 HTMLObject *
html_engine_new_text(HTMLEngine * e,const gchar * text,gint len)387 html_engine_new_text (HTMLEngine *e,
388                       const gchar *text,
389                       gint len)
390 {
391 	HTMLObject *to;
392 
393 	to = html_text_new_with_len (text, len, e->insertion_font_style, e->insertion_color);
394 	if (e->insertion_font_style != GTK_HTML_FONT_STYLE_DEFAULT)
395 		html_text_set_style_in_range (HTML_TEXT (to), e->insertion_font_style, e, 0, HTML_TEXT (to)->text_bytes);
396 	if (e->insertion_color &&
397 	    e->insertion_color != html_colorset_get_color (e->settings->color_set, HTMLTextColor))
398 		html_text_set_color_in_range (HTML_TEXT (to), e->insertion_color, 0, HTML_TEXT (to)->text_bytes);
399 	if (e->insertion_url)
400 		html_text_append_link (HTML_TEXT (to), e->insertion_url, e->insertion_target, 0, HTML_TEXT (to)->text_len);
401 
402 	return to;
403 }
404 
405 HTMLObject *
html_engine_new_link(HTMLEngine * e,const gchar * text,gint len,gchar * url)406 html_engine_new_link (HTMLEngine *e,
407                       const gchar *text,
408                       gint len,
409                       gchar *url)
410 {
411 	HTMLObject *link;
412 	gchar *real_url, *real_target;
413 
414 	real_target = strchr (text, '#');
415 	if (real_target) {
416 		real_url = g_strndup (url, real_target - url);
417 		real_target++;
418 	} else
419 		real_url = url;
420 
421 	link = html_text_new_with_len (text, len, e->insertion_font_style,
422 				       html_colorset_get_color (e->settings->color_set, HTMLLinkColor));
423 	html_text_append_link (HTML_TEXT (link), real_url, real_target, 0, HTML_TEXT (link)->text_len);
424 
425 	if (real_target)
426 		g_free (real_url);
427 
428 	return link;
429 }
430 
431 HTMLObject *
html_engine_new_text_empty(HTMLEngine * e)432 html_engine_new_text_empty (HTMLEngine *e)
433 {
434 	return html_engine_new_text (e, "", 0);
435 }
436 
437 gboolean
html_engine_cursor_on_bop(HTMLEngine * e)438 html_engine_cursor_on_bop (HTMLEngine *e)
439 {
440 	g_assert (e);
441 	g_assert (e->cursor);
442 	g_assert (e->cursor->object);
443 
444 	return e->cursor->offset == 0 && html_object_prev_not_slave (e->cursor->object) == NULL;
445 }
446 
447 guint
html_engine_get_indent(HTMLEngine * e)448 html_engine_get_indent (HTMLEngine *e)
449 {
450 	g_assert (e);
451 	g_assert (e->cursor);
452 	g_assert (e->cursor->object);
453 
454 	return e->cursor->object && e->cursor->object->parent
455 		&& HTML_OBJECT_TYPE (e->cursor->object->parent) == HTML_TYPE_CLUEFLOW
456 		? html_clueflow_get_indentation (HTML_CLUEFLOW (e->cursor->object->parent)) : 0;
457 }
458 
459 #define LINE_LEN 71
460 
461 static inline guint
inc_line_offset(guint line_offset,gunichar uc)462 inc_line_offset (guint line_offset,
463                  gunichar uc)
464 {
465 	return uc == '\t'
466 		? line_offset + 8 - (line_offset % 8)
467 		: line_offset + 1;
468 }
469 
470 static guint
try_break_this_line(HTMLEngine * e,guint line_offset,guint last_space)471 try_break_this_line (HTMLEngine *e,
472                      guint line_offset,
473                      guint last_space)
474 {
475 	HTMLObject *flow;
476 	gunichar uc;
477 
478 	flow = e->cursor->object->parent;
479 
480 	while (html_cursor_forward (e->cursor, e) && e->cursor->object->parent == flow) {
481 		uc = html_cursor_get_current_char (e->cursor);
482 		line_offset = inc_line_offset (line_offset, uc);
483 		if (uc == ' ' || uc == '\t')
484 			last_space = line_offset;
485 		if (uc && line_offset >= LINE_LEN) {
486 			if (last_space) {
487 				html_cursor_backward_n (e->cursor, e, line_offset - last_space);
488 				uc = ' ';
489 			} else {
490 				/* go to end of word */
491 				while (html_cursor_forward (e->cursor, e)) {
492 					line_offset = inc_line_offset (line_offset, uc);
493 					uc = html_cursor_get_current_char (e->cursor);
494 					if (uc == ' ' || uc == '\t' || !uc)
495 						break;
496 				}
497 			}
498 			if (uc == ' ' || uc == '\t') {
499 				gchar c;
500 
501 				html_engine_insert_empty_paragraph (e);
502 				html_engine_delete_n (e, 1, TRUE);
503 
504 				while (c = html_cursor_get_current_char (e->cursor), c == ' ' || c == '\t')
505 					html_engine_delete_n (e, 1, TRUE);
506 
507 				flow        = e->cursor->object->parent;
508 				last_space  = 0;
509 				line_offset = 0;
510 			}
511 		}
512 		if (!uc)
513 			return line_offset;
514 	}
515 
516 	return line_offset;
517 }
518 
519 static void
go_to_begin_of_para(HTMLEngine * e)520 go_to_begin_of_para (HTMLEngine *e)
521 {
522 	HTMLObject *prev;
523 
524 	do {
525 		gint offset;
526 		html_cursor_beginning_of_paragraph (e->cursor, e);
527 		offset = 0;
528 		prev = html_object_prev_cursor (e->cursor->object, &offset);
529 		if (prev && !html_object_is_container (prev) && html_object_get_length (prev)
530 		    && html_clueflow_style_equals (HTML_CLUEFLOW (e->cursor->object->parent), HTML_CLUEFLOW (prev->parent)))
531 			html_cursor_backward (e->cursor, e);
532 		else
533 			break;
534 	} while (1);
535 }
536 
537 void
html_engine_indent_paragraph(HTMLEngine * e)538 html_engine_indent_paragraph (HTMLEngine *e)
539 {
540 	guint position;
541 	guint line_offset;
542 	guint last_space;
543 	guint i, selection_start, selection_len;
544 
545 	g_assert (e->cursor->object);
546 	if (!HTML_IS_CLUEFLOW (e->cursor->object->parent))
547 		return;
548 
549 	position = e->cursor->position;
550 
551 	if (e->selection) {
552 		HTMLInterval selection = *e->selection;
553 
554 		html_cursor_jump_to (e->cursor, e, selection.from.object, selection.from.offset);
555 		selection_start = e->cursor->position;
556 
557 		html_cursor_jump_to (e->cursor, e, selection.to.object, selection.to.offset);
558 		selection_len = e->cursor->position - selection_start;
559 	} else {
560 		selection_start = -1;
561 		selection_len = 0;
562 	}
563 
564 	html_engine_disable_selection (e);
565 
566 	if (selection_start == -1)
567 		selection_start = position;
568 
569 	html_undo_level_begin (e->undo, "Indent paragraph", "Reverse paragraph indentation");
570 	html_engine_freeze (e);
571 
572 	for (i = 0; i <= selection_len; i++) {
573 		html_cursor_jump_to_position (e->cursor, e, selection_start + selection_len - i);
574 
575 		go_to_begin_of_para (e);
576 
577 		i = selection_start + selection_len - e->cursor->position;
578 
579 		line_offset = 0;
580 		last_space  = 0;
581 		do {
582 			HTMLObject *flow;
583 
584 			line_offset = try_break_this_line (e, line_offset, last_space);
585 			flow = e->cursor->object->parent;
586 			if (html_cursor_forward (e->cursor, e)
587 			    && e->cursor->offset == 0 && html_object_get_length (e->cursor->object)
588 			    && !html_object_is_container (e->cursor->object)
589 			    && html_clueflow_style_equals (HTML_CLUEFLOW (e->cursor->object->parent), HTML_CLUEFLOW (flow))
590 			    && html_object_prev_not_slave (e->cursor->object) == NULL) {
591 				if (line_offset < LINE_LEN - 1) {
592 					gunichar prev;
593 					html_engine_delete_n (e, 1, FALSE);
594 					prev = html_cursor_get_prev_char (e->cursor);
595 					if (prev != ' ' && prev != '\t') {
596 						html_engine_insert_text (e, " ", 1);
597 						line_offset++;
598 					} else if (position > e->cursor->position)
599 						position--;
600 					last_space = line_offset - 1;
601 				} else {
602 					line_offset = 0;
603 					last_space  = 0;
604 				}
605 			} else
606 				break;
607 		} while (1);
608 	}
609 
610 	html_cursor_jump_to_position (e->cursor, e, position);
611 	html_engine_thaw (e);
612 	html_undo_level_end (e->undo, e);
613 }
614 
615 void
html_engine_indent_pre_line(HTMLEngine * e)616 html_engine_indent_pre_line (HTMLEngine *e)
617 {
618 	guint position;
619 	guint line_offset;
620 	guint last_space;
621 	HTMLObject *flow;
622 	gunichar uc;
623 
624 	g_assert (e->cursor->object);
625 	if (HTML_OBJECT_TYPE (e->cursor->object->parent) != HTML_TYPE_CLUEFLOW
626 	    || html_clueflow_get_style (HTML_CLUEFLOW (e->cursor->object->parent)) != HTML_CLUEFLOW_STYLE_PRE)
627 		return;
628 
629 	html_engine_disable_selection (e);
630 	position = e->cursor->position;
631 
632 	html_undo_level_begin (e->undo, "Indent PRE paragraph", "Reverse paragraph indentation");
633 	html_engine_freeze (e);
634 
635 	last_space  = 0;
636 	line_offset = 0;
637 
638 	html_cursor_beginning_of_paragraph (e->cursor, e);
639 
640 	flow = e->cursor->object->parent;
641 	while (html_cursor_forward (e->cursor, e) && e->cursor->object->parent == flow) {
642 		uc = html_cursor_get_current_char (e->cursor);
643 		line_offset = inc_line_offset (line_offset, uc);
644 
645 		if (uc == ' ' || uc == '\t') {
646 			last_space = line_offset;
647 		}
648 
649 		if (line_offset >= LINE_LEN) {
650 			if (last_space) {
651 				html_cursor_backward_n (e->cursor, e, line_offset - last_space);
652 
653 				html_cursor_forward (e->cursor, e);
654 				if ((uc = html_cursor_get_current_char (e->cursor))) {
655 					html_engine_insert_empty_paragraph (e);
656 					if (position >= e->cursor->position)
657 						position++;
658 				}
659 			}
660 		}
661 		if (!uc)
662 			break;
663 	}
664 
665 	html_cursor_jump_to_position (e->cursor, e, position);
666 	html_engine_thaw (e);
667 	html_undo_level_end (e->undo, e);
668 }
669 
670 void
html_engine_fill_pre_line(HTMLEngine * e)671 html_engine_fill_pre_line (HTMLEngine *e)
672 {
673 	guint position;
674 	guint line_offset;
675 	guint last_space;
676 	gunichar uc;
677 
678 	g_assert (e->cursor->object);
679 	position = e->cursor->position;
680 
681 	if (HTML_OBJECT_TYPE (e->cursor->object->parent) != HTML_TYPE_CLUEFLOW
682 	    || html_clueflow_get_style (HTML_CLUEFLOW (e->cursor->object->parent)) != HTML_CLUEFLOW_STYLE_PRE) {
683 		return;
684 	}
685 
686 	last_space  = 0;
687 	line_offset = 0;
688 
689 	html_cursor_beginning_of_paragraph (e->cursor, e);
690 
691 	while (html_cursor_forward (e->cursor, e) && (e->cursor->position < position - 1)) {
692 		uc = html_cursor_get_current_char (e->cursor);
693 		line_offset = inc_line_offset (line_offset, uc);
694 
695 		if (uc == ' ' || uc == '\t') {
696 			last_space = line_offset;
697 		}
698 
699 		if (line_offset >= LINE_LEN) {
700 			if (last_space) {
701 				html_cursor_backward_n (e->cursor, e, line_offset - last_space);
702 
703 				html_cursor_forward (e->cursor, e);
704 				if ((uc = html_cursor_get_current_char (e->cursor))) {
705 					html_engine_insert_empty_paragraph (e);
706 					if (position >= e->cursor->position)
707 						position++;
708 
709 					line_offset = 0;
710 					last_space = 0;
711 				}
712 			}
713 		}
714 		if (!uc)
715 			break;
716 	}
717 	html_cursor_jump_to_position (e->cursor, e, position);
718 }
719 
720 void
html_engine_space_and_fill_line(HTMLEngine * e)721 html_engine_space_and_fill_line (HTMLEngine *e)
722 {
723 
724 	g_assert (e->cursor->object);
725 	html_undo_level_begin (e->undo, "insert and fill", "reverse insert and fill");
726 
727 	html_engine_disable_selection (e);
728 	html_engine_freeze (e);
729 	html_engine_insert_text (e, " ", 1);
730 
731 	html_engine_fill_pre_line (e);
732 
733 	html_engine_thaw (e);
734 	html_undo_level_end (e->undo, e);
735 }
736 
737 void
html_engine_break_and_fill_line(HTMLEngine * e)738 html_engine_break_and_fill_line (HTMLEngine *e)
739 {
740 	html_undo_level_begin (e->undo, "break and fill", "reverse break and fill");
741 
742 	html_engine_disable_selection (e);
743 	html_engine_freeze (e);
744 
745 	html_engine_fill_pre_line (e);
746 
747 	html_engine_insert_empty_paragraph (e);
748 	html_engine_thaw (e);
749 	html_undo_level_end (e->undo, e);
750 }
751 
752 gboolean
html_engine_next_cell(HTMLEngine * e,gboolean create)753 html_engine_next_cell (HTMLEngine *e,
754                        gboolean create)
755 {
756 	HTMLTableCell *cell, *current_cell;
757 
758 	cell = html_engine_get_table_cell (e);
759 	if (cell) {
760 		html_engine_hide_cursor (e);
761 		do {
762 			html_cursor_end_of_line (e->cursor, e);
763 			html_cursor_forward (e->cursor, e);
764 			current_cell = html_engine_get_table_cell (e);
765 		} while (current_cell == cell);
766 
767 		if (create && HTML_IS_TABLE (e->cursor->object)) {
768 			html_cursor_backward (e->cursor, e);
769 			html_engine_insert_table_row (e, TRUE);
770 			html_cursor_forward (e->cursor, e);
771 		}
772 		html_engine_show_cursor (e);
773 
774 		return TRUE;
775 	}
776 
777 	return FALSE;
778 }
779 
780 gboolean
html_engine_prev_cell(HTMLEngine * e)781 html_engine_prev_cell (HTMLEngine *e)
782 {
783 	HTMLTableCell *cell, *current_cell;
784 
785 	cell = html_engine_get_table_cell (e);
786 	if (cell) {
787 		html_engine_hide_cursor (e);
788 		do {
789 			html_cursor_beginning_of_line (e->cursor, e);
790 			html_cursor_backward (e->cursor, e);
791 			current_cell = html_engine_get_table_cell (e);
792 		} while (current_cell == cell);
793 
794 		html_engine_show_cursor (e);
795 
796 		return TRUE;
797 	}
798 
799 	return FALSE;
800 }
801 
802 void
html_engine_set_title(HTMLEngine * e,const gchar * title)803 html_engine_set_title (HTMLEngine *e,
804                        const gchar *title)
805 {
806 	if (e->title)
807 		g_string_free (e->title, TRUE);
808 	e->title = g_string_new (title);
809 	g_signal_emit_by_name (e, "title_changed");
810 }
811 
html_engine_edit_set_direction(HTMLEngine * e,HTMLDirection dir)812 void html_engine_edit_set_direction (HTMLEngine *e,
813 				     HTMLDirection dir)
814 {
815 	HTMLClueFlow *cf = html_object_get_flow (e->cursor->object);
816 
817 	if (cf && cf->dir != dir && html_clueflow_is_empty (cf)) {
818 		html_engine_freeze (e);
819 		cf->dir = dir;
820 		html_engine_thaw (e);
821 	}
822 }
823 
824 gint
html_engine_get_insert_level_for_object(HTMLEngine * e,HTMLObject * o)825 html_engine_get_insert_level_for_object (HTMLEngine *e,
826                                          HTMLObject *o)
827 {
828 	gint cursor_level = 3, level = html_object_get_insert_level (o);
829 
830 	if (level > 3) {
831 		if (e && e->cursor->object && e->cursor->object->parent && e->cursor->object->parent->parent && html_object_is_clue (e->cursor->object->parent->parent)) {
832 			HTMLObject *clue = e->cursor->object->parent->parent;
833 
834 			while (clue && clue->parent && (HTML_IS_CLUEV (clue->parent) || HTML_IS_TABLE_CELL (clue->parent))) {
835 				clue = clue->parent;
836 				cursor_level++;
837 			}
838 		}
839 	}
840 
841 	return MIN (level, cursor_level);
842 }
843