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) 2000, 2001, 2002 Ximian, Inc.
5  *  Authors:                       Radek Doulik (rodo@ximian.com)
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 */
23 
24 #include <config.h>
25 #include <stdio.h>
26 #include <string.h>
27 
28 #include "gtkhtmldebug.h"
29 #include "gtkhtml-private.h"
30 #include "gtkhtml-properties.h"
31 
32 #include "htmlclue.h"
33 #include "htmlcluealigned.h"
34 #include "htmlclueflow.h"
35 #include "htmlcursor.h"
36 #include "htmlcolorset.h"
37 #include "htmlengine.h"
38 #include "htmlengine-edit.h"
39 #include "htmlengine-edit-clueflowstyle.h"
40 #include "htmlengine-edit-cursor.h"
41 #include "htmlengine-edit-cut-and-paste.h"
42 #include "htmlengine-edit-fontstyle.h"
43 #include "htmlengine-edit-movement.h"
44 #include "htmlengine-edit-selection-updater.h"
45 #include "htmlimage.h"
46 #include "htmlinterval.h"
47 #include "htmlobject.h"
48 #include "htmlplainpainter.h"
49 #include "htmltable.h"
50 #include "htmltablecell.h"
51 #include "htmlselection.h"
52 #include "htmlsettings.h"
53 #include "htmltext.h"
54 #include "htmlundo.h"
55 #include "htmlundo-action.h"
56 
57 static gint        delete_object (HTMLEngine *e, HTMLObject **ret_object, guint *ret_len, HTMLUndoDirection dir,
58 				  gboolean add_prop);
59 static void        insert_object_for_undo (HTMLEngine *e, HTMLObject *obj, guint len, guint position_after, gint level,
60 					   HTMLUndoDirection dir, gboolean check);
61 static void        append_object (HTMLEngine *e, HTMLObject *o, guint len, HTMLUndoDirection dir);
62 static void        insert_empty_paragraph (HTMLEngine *e, HTMLUndoDirection dir, gboolean add_undo);
63 static void        insert_setup_undo (HTMLEngine *e, guint len, guint position_before, HTMLUndoDirection dir,
64 				      gboolean delete_paragraph_before, gboolean delete_paragraph_after);
65 
66 /* helper functions -- need refactor */
67 
68 /* #define OP_DEBUG */
69 
70 static void
html_cursor_get_left(HTMLCursor * cursor,HTMLObject ** obj,gint * off)71 html_cursor_get_left (HTMLCursor *cursor,
72                       HTMLObject **obj,
73                       gint *off)
74 {
75 	if (cursor->offset == 0) {
76 		*obj = html_object_prev_not_slave (cursor->object);
77 		if (*obj) {
78 			*off = html_object_get_length (*obj);
79 			return;
80 		}
81 	}
82 	*obj = cursor->object;
83 	*off = cursor->offset;
84 }
85 
86 static void
html_point_get_left(HTMLPoint * source,HTMLPoint * dest)87 html_point_get_left (HTMLPoint *source,
88                      HTMLPoint *dest)
89 {
90 	if (source->offset == 0) {
91 		dest->object = html_object_prev_not_slave (source->object);
92 		if (dest->object) {
93 			dest->offset = html_object_get_length (dest->object);
94 			return;
95 		}
96 	}
97 
98 	*dest = *source;
99 }
100 
101 static void
html_point_get_right(HTMLPoint * source,HTMLPoint * dest)102 html_point_get_right (HTMLPoint *source,
103                       HTMLPoint *dest)
104 {
105 	if (source->offset >= html_object_get_length (source->object)) {
106 		dest->object = html_object_next_not_slave (source->object);
107 		if (dest->object) {
108 			dest->offset = 0;
109 			return;
110 		}
111 	}
112 
113 	*dest = *source;
114 }
115 
116 static void
object_get_parent_list(HTMLObject * o,gint level,GList ** list)117 object_get_parent_list (HTMLObject *o,
118                         gint level,
119                         GList **list)
120 {
121 	while (level > 0 && o) {
122 		*list = g_list_prepend (*list, o);
123 		o = o->parent;
124 		level--;
125 	}
126 }
127 
128 static GList *
point_get_parent_list(HTMLPoint * point,gint level,gboolean include_offset)129 point_get_parent_list (HTMLPoint *point,
130                        gint level,
131                        gboolean include_offset)
132 {
133 	GList *list;
134 
135 	list = include_offset ? g_list_prepend (NULL, GINT_TO_POINTER (point->offset)) : NULL;
136 
137 	object_get_parent_list (point->object, level, &list);
138 
139 	return list;
140 }
141 
142 static gint
get_parent_depth(HTMLObject * o,HTMLObject * parent)143 get_parent_depth (HTMLObject *o,
144                   HTMLObject *parent)
145 {
146 	gint level = 1;
147 
148 	while (o && parent && o != parent) {
149 		o = o->parent;
150 		level++;
151 	}
152 
153 	return level;
154 }
155 
156 static gboolean
is_parent(HTMLObject * o,HTMLObject * parent)157 is_parent (HTMLObject *o,HTMLObject *parent)
158 {
159 	while (o) {
160 		if (o == parent)
161 			return TRUE;
162 		o = o->parent;
163 	}
164 
165 	return FALSE;
166 }
167 
168 static HTMLObject *
try_find_common_parent_of(HTMLObject * child,HTMLObject * parent)169 try_find_common_parent_of (HTMLObject *child,
170                            HTMLObject *parent)
171 {
172 	while (parent) {
173 		if (is_parent (child, parent))
174 			return parent;
175 		parent = parent->parent;
176 	}
177 
178 	return NULL;
179 }
180 
181 static HTMLObject *
get_common_parent(HTMLObject * from,HTMLObject * to)182 get_common_parent (HTMLObject *from,
183                    HTMLObject *to)
184 {
185 	HTMLObject *parent;
186 
187 	parent = try_find_common_parent_of (from, to);
188 
189 	return parent ? parent : try_find_common_parent_of (to, from);
190 }
191 
192 static gint
prepare_delete_bounds(HTMLEngine * e,GList ** from_list,GList ** to_list,GList ** bound_left,GList ** bound_right)193 prepare_delete_bounds (HTMLEngine *e,
194                        GList **from_list,
195                        GList **to_list,
196                        GList **bound_left,
197                        GList **bound_right)
198 {
199 	HTMLPoint b_left, b_right, begin, end;
200 	HTMLObject *common_parent;
201 	gint ret_level;
202 
203 	g_assert (e->selection);
204 
205 	html_point_get_right (&e->selection->from, &begin);
206 	html_point_get_left  (&e->selection->to,   &end);
207 
208 	common_parent = get_common_parent (begin.object, end.object);
209 	ret_level     = html_object_get_parent_level (common_parent);
210 #ifdef OP_DEBUG
211 	printf ("common parent level: %d\n", ret_level);
212 #endif
213 	*from_list = point_get_parent_list (&begin, get_parent_depth (begin.object, common_parent), TRUE);
214 	*to_list   = point_get_parent_list (&end,   get_parent_depth (end.object, common_parent),   TRUE);
215 
216 	if (bound_left && bound_right) {
217 		gint level;
218 
219 		html_point_get_left  (&e->selection->from, &b_left);
220 		html_point_get_right (&e->selection->to,   &b_right);
221 
222 		common_parent = get_common_parent (b_left.object, b_right.object);
223 
224 		level = get_parent_depth (b_left.object, common_parent);
225 		*bound_left  = b_left.object  ? point_get_parent_list (&b_left, level - 1, FALSE) : NULL;
226 		if (level > 1 && *bound_left)
227 			*bound_left  = g_list_prepend (*bound_left, NULL);
228 
229 		level = get_parent_depth (b_right.object, common_parent);
230 		*bound_right = b_right.object ? point_get_parent_list (&b_right, level - 1, FALSE) : NULL;
231 		if (level > 1 && *bound_right)
232 			*bound_right = g_list_prepend (*bound_right, NULL);
233 	}
234 
235 	return ret_level;
236 }
237 
238 static void
remove_empty_and_merge(HTMLEngine * e,gboolean merge,GList * left_orig,GList * right_orig,HTMLCursor * c)239 remove_empty_and_merge (HTMLEngine *e,
240                         gboolean merge,
241                         GList *left_orig,
242                         GList *right_orig,
243                         HTMLCursor *c)
244 {
245 	HTMLObject *lo, *ro, *prev;
246 
247 	GList *left, *right;
248 	GList *left_old, *right_old;
249 
250 	left_old = g_list_copy (left_orig);
251 	right_old = g_list_copy (right_orig);
252 
253 	left = left_old;
254 	right = right_old;
255 
256 #ifdef OP_DEBUG
257 	/* HTMLObject *left_orig = left->data; */
258 	printf ("before merge\n");
259 	gtk_html_debug_dump_tree_simple (e->clue, 0);
260 	if (left && left->data) {
261 		printf ("left\n");
262 		gtk_html_debug_dump_tree_simple (left->data, 0);
263 	}
264 
265 	if (right && right->data) {
266 		printf ("right\n");
267 		gtk_html_debug_dump_tree_simple (right->data, 0);
268 	}
269 #endif
270 	while (left && left->data && right && right->data) {
271 
272 		lo  = HTML_OBJECT (left->data);
273 		ro  = HTML_OBJECT (right->data);
274 
275 		left  = left->next;
276 		right = right->next;
277 
278 		if (HTML_IS_CLUEALIGNED (lo) && !HTML_IS_CLUEALIGNED (ro) && html_object_is_text (HTML_CLUE (lo)->head)) {
279 			HTMLObject *nlo = lo->prev;
280 
281 			if (e->cursor->object->parent && e->cursor->object->parent == lo) {
282 				e->cursor->object = ro;
283 				e->cursor->offset = 0;
284 			}
285 			if (c && c->object->parent && c->object->parent == lo) {
286 				c->object = ro;
287 				c->offset = 0;
288 			}
289 
290 			html_object_remove_child (lo->parent, lo);
291 			html_object_destroy (lo);
292 			lo = nlo;
293 			if (!nlo)
294 				break;
295 		} else if (HTML_IS_CLUEALIGNED (ro) && !HTML_IS_CLUEALIGNED (lo) && html_object_is_text (HTML_CLUE (ro)->head)) {
296 			HTMLObject *nro = ro->next;
297 
298 			if (e->cursor->object->parent && e->cursor->object->parent == ro) {
299 				e->cursor->object = lo;
300 				e->cursor->offset = html_object_get_length (lo);
301 			}
302 			html_object_remove_child (ro->parent, ro);
303 			html_object_destroy (ro);
304 			ro = nro;
305 			if (!nro)
306 				break;
307 		}
308 
309 		if (html_object_is_text (lo) && !*HTML_TEXT (lo)->text && (html_object_prev_not_slave (lo) || merge)) {
310 			HTMLObject *nlo = html_object_prev_not_slave (lo);
311 
312 			if (e->cursor->object == lo) {
313 				e->cursor->object = ro;
314 				e->cursor->offset = 0;
315 			}
316 			if (c && c->object == lo) {
317 				c->object = ro;
318 				c->offset = 0;
319 			}
320 
321 			html_object_remove_child (lo->parent, lo);
322 			html_object_destroy (lo);
323 			lo = nlo;
324 		} else if (html_object_is_text (ro) && !*HTML_TEXT (ro)->text && (html_object_next_not_slave (ro) || merge)) {
325 			HTMLObject *nro = html_object_next_not_slave (ro);
326 
327 			if (e->cursor->object == ro) {
328 				e->cursor->object = lo;
329 				e->cursor->offset = html_object_get_length (lo);
330 			}
331 
332 			html_object_remove_child (ro->parent, ro);
333 			html_object_destroy (ro);
334 			ro = nro;
335 		}
336 
337 		if (merge && lo && ro) {
338 			GList *left_copy, *right_copy;
339 			gboolean merge_flag;
340 
341 			left_copy = g_list_copy (left);
342 			right_copy = g_list_copy (right);
343 			merge_flag = html_object_merge (lo, ro, e, &left_copy, &right_copy, c);
344 
345 			g_list_free (left_old);
346 			g_list_free (right_old);
347 
348 			left = left_copy;
349 			right = right_copy;
350 			left_old = left;
351 			right_old = right;
352 
353 			if (!merge_flag)
354 				break;
355 			if (ro == e->cursor->object) {
356 				e->cursor->object  = lo;
357 				e->cursor->offset += html_object_get_length (lo);;
358 			}
359 		}
360 	}
361 
362 	prev = html_object_prev_not_slave (e->cursor->object);
363 	if (prev && e->cursor->offset == 0) {
364 		e->cursor->object = prev;
365 		e->cursor->offset = html_object_get_length (e->cursor->object);
366 	}
367 
368 	g_list_free (left_old);
369 	g_list_free (right_old);
370 #ifdef OP_DEBUG
371 	/* printf ("-- finished\n");
372 	 * gtk_html_debug_dump_tree_simple (left_orig, 0); */
373 	printf ("-- after\n");
374 	gtk_html_debug_dump_tree_simple (e->clue, 0);
375 	printf ("-- END merge\n");
376 #endif
377 }
378 
379 static void
split_and_add_empty_texts(HTMLEngine * e,gint level,GList ** left,GList ** right)380 split_and_add_empty_texts (HTMLEngine *e,
381                            gint level,
382                            GList **left,
383                            GList **right)
384 {
385 #ifdef OP_DEBUG
386 	printf ("-- SPLIT begin\ne->clue:\n");
387 	gtk_html_debug_dump_tree_simple (e->clue, 0);
388 	printf ("-- SPLIT middle\n");
389 #endif
390 	html_object_split (e->cursor->object, e, *right ? HTML_OBJECT ((*right)->data) : NULL,
391 			   e->cursor->offset, level, left, right);
392 #ifdef OP_DEBUG
393 	printf ("-- SPLIT middle\ne->clue:\n");
394 	gtk_html_debug_dump_tree_simple (e->clue, 0);
395 	printf ("-- SPLIT finish\n");
396 	if (*left && (*left)->data) {
397 		printf ("left:\n");
398 		gtk_html_debug_dump_tree_simple (HTML_OBJECT ((*left)->data), 0);
399 	}
400 	if (*right && (*right)->data) {
401 		printf ("right:\n");
402 		gtk_html_debug_dump_tree_simple (HTML_OBJECT ((*right)->data), 0);
403 	}
404 	printf ("-- SPLIT end\n");
405 #endif
406 }
407 
408 /* end of helper */
409 
410 void
html_engine_copy_object(HTMLEngine * e,HTMLObject ** o,guint * len)411 html_engine_copy_object (HTMLEngine *e,
412                          HTMLObject **o,
413                          guint *len)
414 {
415 	if (e->clue && HTML_CLUE (e->clue)->head && html_engine_is_selection_active (e)) {
416 		GList *from = NULL, *to = NULL;
417 
418 		prepare_delete_bounds (e, &from, &to, NULL, NULL);
419 		*len = 0;
420 		*o    = html_object_op_copy (HTML_OBJECT (from->data), NULL, e,
421 					     from->next, to->next, len);
422 #ifdef OP_DEBUG
423 		printf ("copy len: %d (parent %p)\n", *len, (*o)->parent);
424 		gtk_html_debug_dump_tree_simple (*o, 0);
425 #endif
426 		g_list_free (from);
427 		g_list_free (to);
428 	} else {
429 		*len = 0;
430 		*o = NULL;
431 	}
432 }
433 
434 void
html_engine_copy(HTMLEngine * e)435 html_engine_copy (HTMLEngine *e)
436 {
437 	html_engine_copy_object (e, &e->clipboard, &e->clipboard_len);
438 }
439 
440 struct _DeleteUndo {
441 	HTMLUndoData data;
442 
443 	HTMLObject *buffer;
444 	guint       buffer_len;
445 	gint        level;
446 };
447 typedef struct _DeleteUndo DeleteUndo;
448 
449 static void
delete_undo_destroy(HTMLUndoData * data)450 delete_undo_destroy (HTMLUndoData *data)
451 {
452 	DeleteUndo *undo = (DeleteUndo *) data;
453 
454 	if (undo->buffer)
455 		html_object_destroy (undo->buffer);
456 }
457 
458 static void
delete_undo_action(HTMLEngine * e,HTMLUndoData * data,HTMLUndoDirection dir,guint position_after)459 delete_undo_action (HTMLEngine *e,
460                     HTMLUndoData *data,
461                     HTMLUndoDirection dir,
462                     guint position_after)
463 {
464 	DeleteUndo *undo;
465 	HTMLObject *buffer;
466 	guint       len = 0;
467 
468 	undo         = (DeleteUndo *) data;
469 	buffer       = html_object_op_copy (undo->buffer, NULL, e, NULL, NULL, &len);
470 	insert_object_for_undo (e, buffer, undo->buffer_len, position_after, undo->level, html_undo_direction_reverse (dir), TRUE);
471 }
472 
473 static void
delete_setup_undo(HTMLEngine * e,HTMLObject * buffer,guint len,guint position_after,gint level,HTMLUndoDirection dir)474 delete_setup_undo (HTMLEngine *e,
475                    HTMLObject *buffer,
476                    guint len,
477                    guint position_after,
478                    gint level,
479                    HTMLUndoDirection dir)
480 {
481 	DeleteUndo *undo;
482 
483 	undo = g_new (DeleteUndo, 1);
484 
485 #ifdef OP_DEBUG
486 	printf ("cursor level: %d undo level: %d\n", html_object_get_parent_level (e->cursor->object), level);
487 #endif
488 	html_undo_data_init (HTML_UNDO_DATA (undo));
489 	undo->data.destroy = delete_undo_destroy;
490 	undo->buffer       = buffer;
491 	undo->buffer_len   = len;
492 	undo->level        = level;
493 
494 #ifdef OP_DEBUG
495 	printf ("delete undo len %d\n", len);
496 #endif
497 	html_undo_add_action (e->undo, e,
498 			      html_undo_action_new ("Delete object", delete_undo_action,
499 						    HTML_UNDO_DATA (undo), html_cursor_get_position (e->cursor),
500 						    position_after),
501 			      dir);
502 }
503 
504 static void
move_cursor_before_delete(HTMLEngine * e)505 move_cursor_before_delete (HTMLEngine *e)
506 {
507 	if (e->cursor->offset == 0) {
508 		if (html_object_prev_not_slave (e->cursor->object)) {
509 			HTMLObject *obj;
510 			gint off;
511 
512 			html_cursor_get_left (e->cursor, &obj, &off);
513 			if (obj) {
514 				e->cursor->object = obj;
515 				e->cursor->offset = off;
516 			}
517 		}
518 	}
519 }
520 
521 static void
place_cursor_before_mark(HTMLEngine * e)522 place_cursor_before_mark (HTMLEngine *e)
523 {
524 	if (e->mark->position < e->cursor->position) {
525 		HTMLCursor *tmp;
526 
527 		tmp = e->cursor;
528 		e->cursor = e->mark;
529 		e->mark = tmp;
530 	}
531 }
532 
533 static gboolean
haligns_equal(HTMLHAlignType a1,HTMLHAlignType a2)534 haligns_equal (HTMLHAlignType a1,
535                HTMLHAlignType a2)
536 {
537 	return a1 == a2
538 		|| (a1 == HTML_HALIGN_LEFT && a2 == HTML_HALIGN_NONE)
539 		|| (a1 == HTML_HALIGN_NONE && a2 == HTML_HALIGN_LEFT);
540 
541 }
542 
543 static gboolean
levels_equal(HTMLClueFlow * me,HTMLClueFlow * you)544 levels_equal (HTMLClueFlow *me,
545               HTMLClueFlow *you)
546 {
547 	if (!you)
548 		return FALSE;
549 
550 	if (me->levels->len != you->levels->len)
551 		return FALSE;
552 
553 	if (me->levels->len == 0)
554 		return TRUE;
555 
556 	return !memcmp (me->levels->data, you->levels->data, you->levels->len);
557 }
558 
559 static void
check_flows(HTMLEngine * e,HTMLUndoDirection dir)560 check_flows (HTMLEngine *e,
561              HTMLUndoDirection dir)
562 {
563 	/* I assume here that cursor is before mark */
564 	HTMLClueFlow *flow1, *flow2;
565 	gint level1, level2;
566 
567 	g_return_if_fail (e->cursor);
568 	g_return_if_fail (e->cursor->object);
569 	g_return_if_fail (e->cursor->object->parent);
570 	g_return_if_fail (e->mark);
571 	g_return_if_fail (e->mark->object);
572 	g_return_if_fail (e->mark->object->parent);
573 	g_return_if_fail (e->cursor->position <= e->mark->position);
574 
575 	if (e->cursor->offset || e->cursor->object->parent == e->mark->object->parent
576 	    || !HTML_IS_CLUEFLOW (e->cursor->object->parent) || !HTML_IS_CLUEFLOW (e->mark->object->parent)
577 	    || e->cursor->object != HTML_CLUE (e->cursor->object->parent)->head)
578 		return;
579 
580 	level1 = html_object_get_parent_level (e->cursor->object->parent);
581 	level2 = html_object_get_parent_level (e->mark->object->parent);
582 
583 	flow1 = HTML_CLUEFLOW (e->cursor->object->parent);
584 	flow2 = HTML_CLUEFLOW (e->mark->object->parent);
585 
586 	if (level1 == level2
587 	    && (flow1->style != flow2->style
588 		|| (flow1->style == HTML_CLUEFLOW_STYLE_LIST_ITEM && flow1->item_type != flow2->item_type)
589 		|| !levels_equal (flow1, flow2)
590 		|| !haligns_equal (HTML_CLUE (flow1)->halign, HTML_CLUE (flow2)->halign))) {
591 		HTMLCursor *dest, *source;
592 
593 		dest = html_cursor_dup (e->cursor);
594 		source = html_cursor_dup (e->mark);
595 
596 		html_engine_selection_push (e);
597 		html_engine_disable_selection (e);
598 		html_cursor_jump_to_position_no_spell (e->cursor, e, dest->position);
599 		html_engine_set_clueflow_style (e,
600 						HTML_CLUEFLOW (source->object->parent)->style,
601 						HTML_CLUEFLOW (source->object->parent)->item_type,
602 						HTML_CLUE     (source->object->parent)->halign,
603 						HTML_CLUEFLOW (source->object->parent)->levels->len,
604 						HTML_CLUEFLOW (source->object->parent)->levels->data,
605 						HTML_ENGINE_SET_CLUEFLOW_INDENTATION_ALL,
606 						dir, TRUE);
607 		html_engine_selection_pop (e);
608 		html_cursor_destroy (source);
609 		html_cursor_destroy (dest);
610 	}
611 }
612 
613 static gint
delete_object_do(HTMLEngine * e,HTMLObject ** object,guint * len,HTMLUndoDirection dir,gboolean add_undo)614 delete_object_do (HTMLEngine *e,
615                   HTMLObject **object,
616                   guint *len,
617                   HTMLUndoDirection dir,
618                   gboolean add_undo)
619 {
620 	GList *from, *to, *left, *right;
621 	guint position;
622 	gint level;
623 
624 	html_engine_freeze (e);
625 	level = prepare_delete_bounds (e, &from, &to, &left, &right);
626 	place_cursor_before_mark (e);
627 	if (add_undo)
628 		check_flows (e, dir);
629 	move_cursor_before_delete (e);
630 	html_engine_disable_selection (e);
631 	*len     = 0;
632 	*object  = html_object_op_cut  (HTML_OBJECT (from->data), e, from->next, to->next, left, right, len);
633 	position = e->cursor->position;
634 	remove_empty_and_merge (e, TRUE, left ? left->next : NULL, right ? right->next : NULL, NULL);
635 
636 	g_list_free (from);
637 	g_list_free (to);
638 	g_list_free (left);
639 	g_list_free (right);
640 
641 	e->cursor->position = position;
642 	html_engine_spell_check_range (e, e->cursor, e->cursor);
643 	html_engine_thaw (e);
644 
645 	return level;
646 }
647 
648 static void
check_table_0(HTMLEngine * e)649 check_table_0 (HTMLEngine *e)
650 {
651 	HTMLCursor *tail;
652 
653 	tail = e->mark->position < e->cursor->position ? e->cursor : e->mark;
654 
655 	if (html_cursor_backward (tail, e) && (!HTML_IS_TABLE (tail->object) || tail->offset))
656 		html_cursor_forward (tail, e);
657 	while (tail->offset == 0 && HTML_IS_TABLE (tail->object) && e->mark->position != e->cursor->position)
658 		html_cursor_backward (tail, e);
659 }
660 
661 static void
check_table_1(HTMLEngine * e)662 check_table_1 (HTMLEngine *e)
663 {
664 	HTMLCursor *head;
665 
666 	head = e->mark->position > e->cursor->position ? e->cursor : e->mark;
667 
668 	if (html_cursor_forward (head, e) && (!HTML_IS_TABLE (head->object) || head->offset == 0))
669 		html_cursor_backward (head, e);
670 	while (head->offset == 1 && HTML_IS_TABLE (head->object) && e->mark->position != e->cursor->position)
671 		html_cursor_forward (head, e);
672 }
673 
674 static gboolean
validate_tables(HTMLEngine * e,HTMLUndoDirection dir,gboolean add_undo,gboolean * fix_para)675 validate_tables (HTMLEngine *e,
676                  HTMLUndoDirection dir,
677                  gboolean add_undo,
678                  gboolean *fix_para)
679 {
680 	HTMLObject *next = html_object_next_not_slave (e->cursor->object);
681 
682 	*fix_para = FALSE;
683 
684 	if (next && HTML_IS_TABLE (next)) {
685 		insert_empty_paragraph (e, dir, add_undo);
686 		*fix_para = FALSE;
687 
688 		return TRUE;
689 	} else if (!next) {
690 		gint steps = 0;
691 
692 		while (html_cursor_forward (e->cursor, e)) {
693 			steps++;
694 			if (HTML_IS_TABLE (e->cursor->object)) {
695 				next = html_object_next_not_slave (e->cursor->object);
696 				if (next) {
697 					insert_empty_paragraph (e, dir, FALSE);
698 					*fix_para = TRUE;
699 					steps++;
700 					break;
701 				}
702 			} else
703 				break;
704 		}
705 
706 		if (steps)
707 			html_cursor_backward_n (e->cursor, e, steps);
708 	}
709 
710 	return FALSE;
711 }
712 
713 static inline gboolean
in_aligned(HTMLCursor * cursor)714 in_aligned (HTMLCursor *cursor)
715 {
716 	return cursor->object->parent && HTML_IS_CLUEALIGNED (cursor->object->parent);
717 }
718 
719 struct _FixEmptyAlignedUndo {
720 	HTMLUndoData data;
721 
722 	HTMLObject *ac;
723 };
724 typedef struct _FixEmptyAlignedUndo FixEmptyAlignedUndo;
725 
726 static void
fix_empty_aligned_undo_destroy(HTMLUndoData * data)727 fix_empty_aligned_undo_destroy (HTMLUndoData *data)
728 {
729 	FixEmptyAlignedUndo *undo = (FixEmptyAlignedUndo *) data;
730 
731 	if (undo->ac)
732 		html_object_destroy (undo->ac);
733 }
734 
735 static void
fix_empty_aligned_undo_action(HTMLEngine * e,HTMLUndoData * data,HTMLUndoDirection dir,guint position_after)736 fix_empty_aligned_undo_action (HTMLEngine *e,
737                                HTMLUndoData *data,
738                                HTMLUndoDirection dir,
739                                guint position_after)
740 {
741 	HTMLObject *ac, *flow;
742 
743 	g_return_if_fail (html_object_is_text (e->cursor->object) && HTML_TEXT (e->cursor->object)->text_len == 0
744 			  && e->cursor->object->parent && HTML_IS_CLUEFLOW (e->cursor->object->parent));
745 
746 	ac = ((FixEmptyAlignedUndo *) data)->ac;
747 	((FixEmptyAlignedUndo *) data)->ac = NULL;
748 
749 	html_engine_freeze (e);
750 	flow = e->cursor->object->parent;
751 	html_clue_remove_text_slaves (HTML_CLUE (flow));
752 	html_clue_append_after (HTML_CLUE (flow), ac, e->cursor->object);
753 	html_object_remove_child (flow, e->cursor->object);
754 	html_clue_append (HTML_CLUE (ac), e->cursor->object);
755 	html_object_change_set_down (flow, HTML_CHANGE_ALL);
756 	html_engine_thaw (e);
757 }
758 
759 static void
fix_empty_aligned_setup_undo(HTMLEngine * e,HTMLUndoDirection dir,HTMLObject * ac)760 fix_empty_aligned_setup_undo (HTMLEngine *e,
761                               HTMLUndoDirection dir,
762                               HTMLObject *ac)
763 {
764 	FixEmptyAlignedUndo *undo;
765 
766 	undo = g_new (FixEmptyAlignedUndo, 1);
767 
768 	html_undo_data_init (HTML_UNDO_DATA (undo));
769 	undo->data.destroy = fix_empty_aligned_undo_destroy;
770 	undo->ac       = ac;
771 
772 	html_undo_add_action (e->undo, e,
773 			      html_undo_action_new ("Remove empty aligned", fix_empty_aligned_undo_action,
774 						    HTML_UNDO_DATA (undo), html_cursor_get_position (e->cursor),
775 						    html_cursor_get_position (e->cursor)),
776 			      dir);
777 }
778 
779 static void
fix_empty_aligned(HTMLEngine * e,HTMLUndoDirection dir,gboolean add_undo)780 fix_empty_aligned (HTMLEngine *e,
781                    HTMLUndoDirection dir,
782                    gboolean add_undo)
783 {
784 	if (html_object_is_text (e->cursor->object) && e->cursor->object->parent && HTML_IS_CLUEALIGNED (e->cursor->object->parent)) {
785 		HTMLObject *ac = e->cursor->object->parent;
786 
787 		if (ac->parent && HTML_IS_CLUEFLOW (ac->parent)) {
788 			html_engine_freeze (e);
789 			html_clue_remove_text_slaves (HTML_CLUE (ac));
790 			html_object_remove_child (ac, e->cursor->object);
791 			html_clue_append_after (HTML_CLUE (ac->parent), e->cursor->object, ac);
792 			html_object_change_set_down (ac->parent, HTML_CHANGE_ALL);
793 			html_object_remove_child (ac->parent, ac);
794 			if (add_undo)
795 				fix_empty_aligned_setup_undo (e, dir, ac);
796 			html_engine_thaw (e);
797 		}
798 	}
799 }
800 
801 static gint
delete_object(HTMLEngine * e,HTMLObject ** ret_object,guint * ret_len,HTMLUndoDirection dir,gboolean add_undo)802 delete_object (HTMLEngine *e,
803                HTMLObject **ret_object,
804                guint *ret_len,
805                HTMLUndoDirection dir,
806                gboolean add_undo)
807 {
808 	html_engine_edit_selection_updater_update_now (e->selection_updater);
809 	if (html_engine_is_selection_active (e)) {
810 		HTMLObject *object;
811 		guint len, position_before, saved_position, end_position;
812 		gint level;
813 		gboolean backward;
814 		gboolean fix_para;
815 
816 		end_position = MIN (e->cursor->position, e->mark->position);
817 		if (HTML_IS_TABLE (e->cursor->object)
818 		    || (e->cursor->object->parent && e->cursor->object->parent->parent && HTML_IS_TABLE_CELL (e->cursor->object->parent->parent))
819 		    || HTML_IS_TABLE (e->mark->object)
820 		    || (e->mark->object->parent && e->mark->object->parent->parent && HTML_IS_TABLE_CELL (e->mark->object->parent->parent))) {
821 			check_table_0 (e);
822 			check_table_1 (e);
823 			html_engine_edit_selection_updater_update_now (e->selection_updater);
824 		}
825 		if (!html_engine_is_selection_active (e) || e->cursor->position == e->mark->position) {
826 			html_engine_disable_selection (e);
827 			html_cursor_jump_to_position (e->cursor, e, end_position);
828 			return 0;
829 		}
830 
831 		position_before = MAX (e->cursor->position, e->mark->position);
832 		level = delete_object_do (e, &object, &len, dir, add_undo);
833 		if (ret_object && ret_len) {
834 			*ret_object = html_object_op_copy (object, NULL, e, NULL, NULL, ret_len);
835 			*ret_len    = len;
836 		}
837 		backward = validate_tables (e, dir, add_undo, &fix_para);
838 		if (fix_para) {
839 			saved_position = e->cursor->position;
840 			e->cursor->position = position_before + 1;
841 			insert_setup_undo (e, 1, position_before, dir, FALSE, FALSE);
842 			e->cursor->position = saved_position;
843 		}
844 		level = html_object_get_parent_level (e->cursor->object) - level + 1;
845 		if (add_undo) {
846 			delete_setup_undo (e, object, len, position_before + (backward ? 1 : 0), level, dir);
847 		} else
848 			html_object_destroy (object);
849 
850 		if (backward)
851 			html_cursor_backward (e->cursor, e);
852 		gtk_html_editor_event (e->widget, GTK_HTML_EDITOR_EVENT_DELETE, NULL);
853 		fix_empty_aligned (e, dir, add_undo);
854 
855 		return level;
856 	}
857 
858 	return 0;
859 }
860 
861 gint
html_engine_cut(HTMLEngine * e)862 html_engine_cut (HTMLEngine *e)
863 {
864 	gint rv;
865 
866 	html_engine_clipboard_clear (e);
867 	html_undo_level_begin (e->undo, "Cut", "Uncut");
868 	if (html_engine_is_selection_active (e)) {
869 		HTMLCursor *start = html_cursor_dup (e->mark->position < e->cursor->position ? e->mark : e->cursor);
870 		HTMLCursor *end = html_cursor_dup (e->mark->position < e->cursor->position ? e->cursor : e->mark);
871 		gint start_position = start->position;
872 		gint end_position = end->position;
873 		if (end_position - start_position > 0) {
874 			gint len = end_position - start_position;
875 			g_signal_emit_by_name (e->widget, "object_delete", start_position, len);
876 		}
877 		html_cursor_destroy (start);
878 		html_cursor_destroy (end);
879 	}
880 
881 	rv = delete_object (e, &e->clipboard, &e->clipboard_len, HTML_UNDO_UNDO, TRUE);
882 	html_undo_level_end (e->undo, e);
883 
884 #ifdef OP_DEBUG
885 	printf ("cut  len: %d\n", e->clipboard_len);
886 	gtk_html_debug_dump_tree_simple (e->clipboard, 0);
887 #endif
888 
889 	return rv;
890 }
891 
892 /*
893  * PASTE/INSERT
894  */
895 
896 static void
set_cursor_at_end_of_object(HTMLEngine * e,HTMLObject * o,guint len)897 set_cursor_at_end_of_object (HTMLEngine *e,
898                              HTMLObject *o,
899                              guint len)
900 {
901 	guint save_position;
902 	gboolean need_spell_check;
903 
904 	save_position       = e->cursor->position;
905 	e->cursor->object   = html_object_get_tail_leaf (o);
906 	need_spell_check = e->need_spell_check;
907 	e->need_spell_check = FALSE;
908 	while (html_cursor_forward (e->cursor, e))
909 		;
910 	e->need_spell_check = need_spell_check;
911 	e->cursor->position = save_position + len;
912 	e->cursor->offset   = html_object_get_length (e->cursor->object);
913 }
914 
915 static inline void
isolate_tables(HTMLEngine * e,HTMLUndoDirection dir,guint position_before,guint position_after,gboolean * delete_paragraph_before,gboolean * delete_paragraph_after)916 isolate_tables (HTMLEngine *e,
917                 HTMLUndoDirection dir,
918                 guint position_before,
919                 guint position_after,
920                   gboolean *delete_paragraph_before,
921                 gboolean *delete_paragraph_after)
922 {
923 	HTMLObject *next;
924 
925 	*delete_paragraph_after  = FALSE;
926 	*delete_paragraph_before = FALSE;
927 
928 	html_cursor_jump_to_position_no_spell (e->cursor, e, position_after);
929 	next = html_object_next_not_slave (e->cursor->object);
930 	if (next && e->cursor->offset == html_object_get_length (e->cursor->object)
931 	    && (HTML_IS_TABLE (e->cursor->object) || HTML_IS_TABLE (next))) {
932 		insert_empty_paragraph (e, dir, FALSE);
933 		*delete_paragraph_after = TRUE;
934 	}
935 
936 	html_cursor_jump_to_position_no_spell (e->cursor, e, position_before);
937 	next = html_object_next_not_slave (e->cursor->object);
938 	if (next && e->cursor->offset == html_object_get_length (e->cursor->object)
939 	    && (HTML_IS_TABLE (e->cursor->object) || HTML_IS_TABLE (next))) {
940 		insert_empty_paragraph (e, dir, FALSE);
941 		*delete_paragraph_before = TRUE;
942 	}
943 }
944 
945 static inline void
insert_object_do(HTMLEngine * e,HTMLObject * obj,guint * len,gint level,guint position_after,gboolean check,HTMLUndoDirection dir)946 insert_object_do (HTMLEngine *e,
947                   HTMLObject *obj,
948                   guint *len,
949                   gint level,
950                   guint position_after,
951                   gboolean check,
952                   HTMLUndoDirection dir)
953 {
954 	HTMLCursor *orig;
955 	GList *left = NULL, *right = NULL;
956 	GList *first = NULL, *last = NULL;
957 	guint position_before;
958 
959 	html_engine_freeze (e);
960 	position_before = e->cursor->position;
961 	html_object_change_set_down (obj, HTML_CHANGE_ALL);
962 	split_and_add_empty_texts (e, level, &left, &right);
963 	orig = html_cursor_dup (e->cursor);
964 	orig->position = position_before;
965 	first = html_object_heads_list (obj);
966 	last  = html_object_tails_list (obj);
967 	set_cursor_at_end_of_object (e, obj, *len);
968 
969 	if ((left && left->data) || (right && (right->data))) {
970 		HTMLObject *parent, *where;
971 		if (left && left->data) {
972 			where  = HTML_OBJECT (left->data);
973 			parent = where->parent;
974 		} else {
975 			where  = NULL;
976 			parent = HTML_OBJECT (right->data)->parent;
977 		}
978 		if (parent && html_object_is_clue (parent))
979 			html_clue_append_after (HTML_CLUE (parent), obj, where);
980 	}
981 
982 #ifdef OP_DEBUG
983 	printf ("position before merge %d\n", e->cursor->position);
984 #endif
985 	remove_empty_and_merge (e, TRUE, last, right, orig);
986 	remove_empty_and_merge (e, TRUE, left, first, orig);
987 
988 	g_list_free (first);
989 	g_list_free (last);
990 	g_list_free (left);
991 	g_list_free (right);
992 
993 #ifdef OP_DEBUG
994 	printf ("position after merge %d\n", e->cursor->position);
995 #endif
996 
997 	html_cursor_copy (e->cursor, orig);
998 	html_cursor_jump_to_position_no_spell (e->cursor, e, position_after);
999 
1000 	if (check)
1001 		html_engine_spell_check_range (e, orig, e->cursor);
1002 	html_cursor_destroy (orig);
1003 	html_engine_thaw (e);
1004 }
1005 
1006 struct _InsertUndo {
1007 	HTMLUndoData data;
1008 
1009 	guint len;
1010 	gboolean delete_paragraph_before;
1011 	gboolean delete_paragraph_after;
1012 };
1013 typedef struct _InsertUndo InsertUndo;
1014 
1015 static void
insert_undo_action(HTMLEngine * e,HTMLUndoData * data,HTMLUndoDirection dir,guint position_after)1016 insert_undo_action (HTMLEngine *e,
1017                     HTMLUndoData *data,
1018                     HTMLUndoDirection dir,
1019                     guint position_after)
1020 {
1021 	InsertUndo *undo;
1022 
1023 	undo = (InsertUndo *) data;
1024 
1025 	html_engine_set_mark (e);
1026 	html_cursor_jump_to_position (e->cursor, e, position_after);
1027 	delete_object (e, NULL, NULL, html_undo_direction_reverse (dir), TRUE);
1028 
1029 	if (undo->delete_paragraph_after || undo->delete_paragraph_before) {
1030 		html_cursor_jump_to_position (e->cursor, e, position_after);
1031 		if (undo->delete_paragraph_before) {
1032 			html_cursor_backward (e->cursor, e);
1033 		}
1034 		html_engine_set_mark (e);
1035 		if (undo->delete_paragraph_before) {
1036 			html_cursor_forward (e->cursor, e);
1037 		}
1038 		if (undo->delete_paragraph_after) {
1039 			html_cursor_forward (e->cursor, e);
1040 		}
1041 		delete_object (e, NULL, NULL, HTML_UNDO_UNDO, FALSE);
1042 	}
1043 }
1044 
1045 static void
insert_setup_undo(HTMLEngine * e,guint len,guint position_before,HTMLUndoDirection dir,gboolean delete_paragraph_before,gboolean delete_paragraph_after)1046 insert_setup_undo (HTMLEngine *e,
1047                    guint len,
1048                    guint position_before,
1049                    HTMLUndoDirection dir,
1050                    gboolean delete_paragraph_before,
1051                    gboolean delete_paragraph_after)
1052 {
1053 	InsertUndo *undo;
1054 
1055 	undo = g_new (InsertUndo, 1);
1056 
1057 	html_undo_data_init (HTML_UNDO_DATA (undo));
1058 	undo->len = len;
1059 	undo->delete_paragraph_before = delete_paragraph_before;
1060 	undo->delete_paragraph_after  = delete_paragraph_after;
1061 
1062 	/* printf ("insert undo len %d\n", len); */
1063 
1064 	html_undo_add_action (e->undo, e,
1065 			      html_undo_action_new ("Insert", insert_undo_action,
1066 						    HTML_UNDO_DATA (undo),
1067 						    html_cursor_get_position (e->cursor),
1068 						    position_before),
1069 			      dir);
1070 }
1071 
1072 static gboolean fix_aligned_position (HTMLEngine *e, guint *position_after, HTMLUndoDirection dir);
1073 
1074 static void
fix_aligned_redo_action(HTMLEngine * e,HTMLUndoData * data,HTMLUndoDirection dir,guint position_after)1075 fix_aligned_redo_action (HTMLEngine *e,
1076                          HTMLUndoData *data,
1077                          HTMLUndoDirection dir,
1078                          guint position_after)
1079 {
1080 	guint pa;
1081 
1082 	fix_aligned_position (e, &pa, html_undo_direction_reverse (dir));
1083 }
1084 
1085 static void
fix_aligned_undo_action(HTMLEngine * e,HTMLUndoData * data,HTMLUndoDirection dir,guint position_after)1086 fix_aligned_undo_action (HTMLEngine *e,
1087                          HTMLUndoData *data,
1088                          HTMLUndoDirection dir,
1089                          guint position_after)
1090 {
1091 	HTMLObject *cf = e->cursor->object->parent;
1092 	HTMLUndoData *undo;
1093 	guint position_before = e->cursor->position;
1094 
1095 	undo = g_new (HTMLUndoData, 1);
1096 
1097 	if (!html_cursor_forward (e->cursor, e))
1098 		g_assert (html_cursor_backward (e->cursor, e));
1099 	else
1100 		e->cursor->position--;
1101 
1102 	html_clue_remove (HTML_CLUE (cf->parent), cf);
1103 	html_object_destroy (cf);
1104 
1105 	html_undo_add_action (e->undo, e,
1106 			      html_undo_action_new ("Fix aligned", fix_aligned_redo_action,
1107 						    undo, html_cursor_get_position (e->cursor),
1108 						    position_before),
1109 			      html_undo_direction_reverse (dir));
1110 }
1111 
1112 static void
fix_align_setup_undo(HTMLEngine * e,guint position_before,HTMLUndoDirection dir)1113 fix_align_setup_undo (HTMLEngine *e,
1114                       guint position_before,
1115                       HTMLUndoDirection dir)
1116 {
1117 	HTMLUndoData *undo;
1118 
1119 	undo = g_new (HTMLUndoData, 1);
1120 
1121 	html_undo_data_init (HTML_UNDO_DATA (undo));
1122 	/* printf ("insert undo len %d\n", len); */
1123 
1124 	html_undo_add_action (e->undo, e,
1125 			      html_undo_action_new ("Undo aligned fix", fix_aligned_undo_action,
1126 						    undo, html_cursor_get_position (e->cursor),
1127 						    position_before),
1128 			      dir);
1129 }
1130 
1131 static gboolean
fix_aligned_position(HTMLEngine * e,guint * position_after,HTMLUndoDirection dir)1132 fix_aligned_position (HTMLEngine *e,
1133                       guint *position_after,
1134                       HTMLUndoDirection dir)
1135 {
1136 	gboolean rv = FALSE;
1137 	if (in_aligned (e->cursor)) {
1138 		/* printf ("in aligned\n"); */
1139 		if (e->cursor->offset) {
1140 			if (html_cursor_forward (e->cursor, e))
1141 				(*position_after) ++;
1142 			if (in_aligned (e->cursor)) {
1143 				HTMLObject *cf;
1144 				HTMLObject *cluev;
1145 				HTMLObject *flow;
1146 
1147 				/* printf ("aligned: needs fixing\n"); */
1148 				html_engine_freeze (e);
1149 				cf = html_clueflow_new_from_flow (HTML_CLUEFLOW (e->cursor->object->parent->parent));
1150 				flow = e->cursor->object->parent->parent;
1151 				cluev = flow->parent;
1152 				e->cursor->object = html_engine_new_text_empty (e);
1153 				html_clue_append (HTML_CLUE (cf), e->cursor->object);
1154 				html_clue_append_after (HTML_CLUE (cluev), cf, flow);
1155 				e->cursor->offset = 0;
1156 				e->cursor->position++;
1157 				(*position_after) ++;
1158 #ifdef OP_DEBUG
1159 				gtk_html_debug_dump_tree_simple (e->clue, 0);
1160 #endif
1161 				fix_align_setup_undo (e, e->cursor->position, dir);
1162 				html_engine_thaw (e);
1163 				rv = TRUE;
1164 				if (e->cursor->object->parent && HTML_IS_CLUEALIGNED (e->cursor->object->parent))
1165 					html_cursor_forward (e->cursor, e);
1166 
1167 			}
1168 		} else {
1169 			if (html_cursor_backward (e->cursor, e))
1170 				(*position_after) --;
1171 			if (in_aligned (e->cursor)) {
1172 				HTMLObject *cf;
1173 				HTMLObject *cluev;
1174 				HTMLObject *flow;
1175 
1176 				/* printf ("aligned: needs fixing\n"); */
1177 				html_engine_freeze (e);
1178 				cf = html_clueflow_new_from_flow (HTML_CLUEFLOW (e->cursor->object->parent->parent));
1179 				flow = e->cursor->object->parent->parent;
1180 				cluev = flow->parent;
1181 				e->cursor->object = html_engine_new_text_empty (e);
1182 				html_clue_append (HTML_CLUE (cf), e->cursor->object);
1183 				if (flow->prev)
1184 					html_clue_append_after (HTML_CLUE (cluev), cf, flow->prev);
1185 				else
1186 					html_clue_prepend (HTML_CLUE (cluev), cf);
1187 				e->cursor->offset = 0;
1188 #ifdef OP_DEBUG
1189 				gtk_html_debug_dump_tree_simple (e->clue, 0);
1190 #endif
1191 				fix_align_setup_undo (e, e->cursor->position, dir);
1192 				html_engine_thaw (e);
1193 				rv = TRUE;
1194 			}
1195 		}
1196 	}
1197 
1198 	return rv;
1199 }
1200 
1201 static void
insert_object_for_undo(HTMLEngine * e,HTMLObject * obj,guint len,guint position_after,gint level,HTMLUndoDirection dir,gboolean check)1202 insert_object_for_undo (HTMLEngine *e,
1203                         HTMLObject *obj,
1204                         guint len,
1205                         guint position_after,
1206                         gint level,
1207                HTMLUndoDirection dir,
1208                         gboolean check)
1209 {
1210 	gboolean delete_paragraph_before = FALSE;
1211 	gboolean delete_paragraph_after = FALSE;
1212 	guint position_before;
1213 
1214 	position_before = e->cursor->position;
1215 	insert_object_do (e, obj, &len, level, position_after, check, dir);
1216 	isolate_tables (e, dir, position_before, position_after, &delete_paragraph_before, &delete_paragraph_after);
1217 	html_cursor_jump_to_position_no_spell (e->cursor, e, position_after + (delete_paragraph_before ? 1 : 0));
1218 	insert_setup_undo (e, len, position_before + (delete_paragraph_before ? 1 : 0),
1219 			   dir, delete_paragraph_before, delete_paragraph_after);
1220 	g_signal_emit_by_name (e->widget, "object_inserted", position_before, len);
1221 }
1222 
1223 static void
insert_object(HTMLEngine * e,HTMLObject * obj,guint len,guint position_after,gint level,HTMLUndoDirection dir,gboolean check)1224 insert_object (HTMLEngine *e,
1225                HTMLObject *obj,
1226                guint len,
1227                guint position_after,
1228                gint level,
1229                HTMLUndoDirection dir,
1230                gboolean check)
1231 {
1232 	fix_aligned_position (e, &position_after, dir);
1233 	insert_object_for_undo (e, obj, len, position_after, level, dir, check);
1234 }
1235 
1236 void
html_engine_insert_object(HTMLEngine * e,HTMLObject * o,guint len,gint level)1237 html_engine_insert_object (HTMLEngine *e,
1238                            HTMLObject *o,
1239                            guint len,
1240                            gint level)
1241 {
1242 	insert_object (e, o, len, e->cursor->position + len, level, HTML_UNDO_UNDO, TRUE);
1243 }
1244 
1245 void
html_engine_paste_object(HTMLEngine * e,HTMLObject * o,guint len)1246 html_engine_paste_object (HTMLEngine *e,
1247                           HTMLObject *o,
1248                           guint len)
1249 {
1250 	html_undo_level_begin (e->undo, "Paste", "Paste");
1251 	html_engine_delete (e);
1252 	html_engine_insert_object (e, o, len, html_engine_get_insert_level_for_object (e, o));
1253 	html_undo_level_end (e->undo, e);
1254 }
1255 
1256 void
html_engine_paste(HTMLEngine * e)1257 html_engine_paste (HTMLEngine *e)
1258 {
1259 	if (e->clipboard) {
1260 		HTMLObject *copy;
1261 		guint len = 0;
1262 
1263 		copy = html_object_op_copy (e->clipboard, NULL, e, NULL, NULL, &len);
1264 		html_engine_paste_object (e, copy, e->clipboard_len);
1265 	}
1266 }
1267 
1268 static void
check_magic_link(HTMLEngine * e,const gchar * text,guint len)1269 check_magic_link (HTMLEngine *e,
1270                   const gchar *text,
1271                   guint len)
1272 {
1273 	if (HTML_IS_TEXT (e->cursor->object)
1274 	    && gtk_html_get_magic_links (e->widget)
1275 	    && len == 1
1276 	    && (*text == ' ' || text[0] == '\n' || text[0] == '>' || text[0] == ')'))
1277 		html_text_magic_link (HTML_TEXT (e->cursor->object), e, 1);
1278 }
1279 
1280 static void
insert_empty_paragraph(HTMLEngine * e,HTMLUndoDirection dir,gboolean add_undo)1281 insert_empty_paragraph (HTMLEngine *e,
1282                         HTMLUndoDirection dir,
1283                         gboolean add_undo)
1284 {
1285 	GList *left = NULL, *right = NULL;
1286 	HTMLCursor *orig;
1287 	guint position_before;
1288 	guint position_after;
1289 
1290 	if (dir == HTML_UNDO_UNDO)
1291 		if (fix_aligned_position (e, &position_after, dir))
1292 			return;
1293 
1294 	html_engine_freeze (e);
1295 
1296 	position_before = e->cursor->position;
1297 	orig = html_cursor_dup (e->cursor);
1298 	split_and_add_empty_texts (e, 2, &left, &right);
1299 	remove_empty_and_merge (e, FALSE, left, right, orig);
1300 
1301 	/* replace empty link in empty flow by text with the same style */
1302 	/* FIXME-link if (HTML_IS_LINK_TEXT (e->cursor->object) && html_clueflow_is_empty (HTML_CLUEFLOW (e->cursor->object->parent))) {
1303 		HTMLObject *flow = e->cursor->object->parent;
1304 		HTMLObject *new_text;
1305  *
1306 		new_text = html_link_text_to_text (HTML_LINK_TEXT (e->cursor->object), e);
1307 		html_clue_remove (HTML_CLUE (flow), e->cursor->object);
1308 		html_object_destroy (e->cursor->object);
1309 		if (orig->object == e->cursor->object) {
1310 			orig->object = NULL;
1311 		}
1312 		e->cursor->object = new_text;
1313 		if (!orig->object) {
1314 			orig->object = e->cursor->object;
1315 		}
1316 		html_clue_append (HTML_CLUE (flow), e->cursor->object);
1317 		} */
1318 
1319 	html_cursor_forward (e->cursor, e);
1320 
1321 	/* replace empty text in new empty flow by text with current style */
1322 	if (html_clueflow_is_empty (HTML_CLUEFLOW (e->cursor->object->parent))) {
1323 		HTMLObject *flow = e->cursor->object->parent;
1324 
1325 		html_clue_remove (HTML_CLUE (flow), e->cursor->object);
1326 		html_object_destroy (e->cursor->object);
1327 		e->cursor->object = html_engine_new_text_empty (e);
1328 		html_clue_append (HTML_CLUE (flow), e->cursor->object);
1329 	}
1330 
1331 	if (add_undo) {
1332 		html_undo_level_begin (e->undo, "Insert paragraph", "Delete paragraph");
1333 		insert_setup_undo (e, 1, position_before, dir, FALSE, FALSE);
1334 	}
1335 	g_list_free (left);
1336 	g_list_free (right);
1337 	html_engine_spell_check_range (e, orig, e->cursor);
1338 	html_cursor_destroy (orig);
1339 
1340 	html_cursor_backward (e->cursor, e);
1341 	check_magic_link (e, "\n", 1);
1342 	html_cursor_forward (e->cursor, e);
1343 
1344 	gtk_html_editor_event_command (e->widget, GTK_HTML_COMMAND_INSERT_PARAGRAPH, FALSE);
1345 	if (add_undo)
1346 		html_undo_level_end (e->undo, e);
1347 
1348 	html_engine_thaw (e);
1349 	g_signal_emit_by_name (e->widget, "object_inserted", 0, 0);
1350 }
1351 
1352 void
html_engine_insert_empty_paragraph(HTMLEngine * e)1353 html_engine_insert_empty_paragraph (HTMLEngine *e)
1354 {
1355 	HTMLClueFlow *cf;
1356 	HTMLClueFlowStyle cfs;
1357 
1358 	html_engine_freeze (e);
1359 	insert_empty_paragraph (e, HTML_UNDO_UNDO, TRUE);
1360 	cf = html_object_get_flow (e->cursor->object);
1361 	cfs = html_clueflow_get_style (cf);
1362 	if (cfs == HTML_CLUEFLOW_STYLE_H1 || cfs == HTML_CLUEFLOW_STYLE_H2 || cfs == HTML_CLUEFLOW_STYLE_H3 || cfs == HTML_CLUEFLOW_STYLE_H4 || cfs == HTML_CLUEFLOW_STYLE_H5 || cfs == HTML_CLUEFLOW_STYLE_H6)
1363 		html_clueflow_set_style (cf, e, HTML_CLUEFLOW_STYLE_NORMAL);
1364 	if (cf) {
1365 		cf->dir = html_text_direction_pango_to_html (gdk_keymap_get_direction (gdk_keymap_get_for_display (gtk_widget_get_display (GTK_WIDGET (e->widget)))));
1366 	}
1367 	html_engine_thaw (e);
1368 }
1369 
1370 static const gchar *picto_chars =
1371 	/*  0 */ "DO)(|/PQ*!"
1372 	/* 10 */ "S\0:-\0:\0:-\0"
1373 	/* 20 */ ":\0:;=-\"\0:;"
1374 	/* 30 */ "B\"|\0:-'\0:X"
1375 	/* 40 */ "\0:\0:-\0:\0:-"
1376 	/* 50 */ "\0:\0:-\0:\0:-"
1377 	/* 60 */ "\0:\0:\0:-\0:\0"
1378 	/* 70 */ ":-\0:\0:-\0:\0";
1379 static gint picto_states[] = {
1380 	/*  0 */  12,  17,  22,  34,  43,  48,  53,  58,  65,  70,
1381 	/* 10 */  75,   0, -15,  15,   0, -15,   0, -17,  20,   0,
1382 	/* 20 */ -17,   0, -14, -20, -14,  28,  63,   0, -14, -20,
1383 	/* 30 */  -3,  63, -18,   0, -12,  38,  41,   0, -12,  -2,
1384 	/* 40 */   0,  -4,   0, -10,  46,   0, -10,   0, -19,  51,
1385 	/* 50 */   0, -19,   0, -11,  56,   0, -11,   0, -13,  61,
1386 	/* 60 */   0, -13,   0,  -6,   0,  68,  -7,   0,  -7,   0,
1387 	/* 70 */ -16,  73,   0, -16,   0, -21,  78,   0, -21,   0 };
1388 static const gchar *picto_icon_names[] = {
1389 	"face-angel",
1390 	"face-angry",
1391 	"face-cool",
1392 	"face-crying",
1393 	"face-devilish",
1394 	"face-embarrassed",
1395 	"face-kiss",
1396 	"face-laugh",		/* not used */
1397 	"face-monkey",		/* not used */
1398 	"face-plain",
1399 	"face-raspberry",
1400 	"face-sad",
1401 	"face-sick",
1402 	"face-smile",
1403 	"face-smile-big",
1404 	"face-smirk",
1405 	"face-surprise",
1406 	"face-tired",
1407 	"face-uncertain",
1408 	"face-wink",
1409 	"face-worried"
1410 };
1411 
1412 static void
use_pictograms(HTMLEngine * e)1413 use_pictograms (HTMLEngine *e)
1414 {
1415 	gint pos;
1416 	gint state;
1417 	gint relative;
1418 	gunichar uc;
1419 
1420 	if (!html_object_is_text (e->cursor->object) || !gtk_html_get_magic_smileys (e->widget))
1421 		return;
1422 
1423 	pos = e->cursor->offset - 1;
1424 	state = 0;
1425 	while (pos >= 0) {
1426 		uc = html_text_get_char (HTML_TEXT (e->cursor->object), pos);
1427 		relative = 0;
1428 		while (picto_chars[state + relative]) {
1429 			if (picto_chars[state + relative] == uc)
1430 				break;
1431 			relative++;
1432 		}
1433 		state = picto_states[state + relative];
1434 		/* 0 .. not found, -n .. found n-th */
1435 		if (state <= 0)
1436 			break;
1437 		pos--;
1438 	}
1439 
1440 	/* Special case needed to recognize angel and devilish. */
1441 	if (pos > 0 && state == -14) {
1442 		uc = html_text_get_char (HTML_TEXT (e->cursor->object), pos - 1);
1443 		if (uc == 'O') {
1444 			state = -1;
1445 			pos--;
1446 		} else if (uc == '>') {
1447 			state = -5;
1448 			pos--;
1449 		}
1450 	}
1451 
1452 	if (state < 0) {
1453 		HTMLObject *image;
1454 		GtkIconInfo *icon_info;
1455 		const gchar *filename;
1456 		gchar *filename_uri;
1457 		gchar *alt;
1458 		gint len;
1459 
1460 		if (pos > 0) {
1461 			uc = html_text_get_char (HTML_TEXT (e->cursor->object), pos - 1);
1462 			if (uc != ' ' && uc != '\t')
1463 				return;
1464 		}
1465 		/* printf ("found %d\n", -state); */
1466 		len = e->cursor->offset  - pos;
1467 		alt = g_strndup (html_text_get_text (HTML_TEXT (e->cursor->object), pos), len);
1468 		html_cursor_backward_n (e->cursor, e, len);
1469 		html_engine_set_mark (e);
1470 		html_cursor_forward_n (e->cursor, e, len);
1471 
1472 		/* Convert a named icon to a file URI. */
1473 		icon_info = gtk_icon_theme_lookup_icon (
1474 			gtk_icon_theme_get_default (),
1475 			picto_icon_names[-state - 1], 16, 0);
1476 		g_return_if_fail (icon_info != NULL);
1477 		filename = gtk_icon_info_get_filename (icon_info);
1478 		g_return_if_fail (filename != NULL);
1479 		filename_uri = g_filename_to_uri (filename, NULL, NULL);
1480 		image = html_image_new (
1481 			html_engine_get_image_factory (e),
1482 			filename_uri, NULL, NULL, -1, -1, FALSE, FALSE,
1483 			0, NULL, HTML_VALIGN_MIDDLE, FALSE);
1484 
1485 		html_image_set_alt (HTML_IMAGE (image), alt);
1486 		html_object_set_data (HTML_OBJECT (image), "picto", alt);
1487 
1488 		html_engine_paste_object (
1489 			e, image, html_object_get_length (image));
1490 
1491 		g_free (alt);
1492 		g_free (filename_uri);
1493 		gtk_icon_info_free (icon_info);
1494 	}
1495 }
1496 
1497 void
html_engine_insert_text_with_extra_attributes(HTMLEngine * e,const gchar * ptext,gint len,PangoAttrList * attrs)1498 html_engine_insert_text_with_extra_attributes (HTMLEngine *e,
1499                                                const gchar *ptext,
1500                                                gint len,
1501                                                PangoAttrList *attrs)
1502 {
1503 	gchar *nl, *sanitized_text = NULL;
1504 	const gchar *text;
1505 	gint alen;
1506 	gsize bytes;
1507 
1508 	bytes = html_text_sanitize (ptext, &sanitized_text, &len);
1509 	if (!len || !sanitized_text) {
1510 		g_free (sanitized_text);
1511 		return;
1512 	}
1513 
1514 	text = sanitized_text;
1515 
1516 	html_undo_level_begin (e->undo, "Insert text", "Delete text");
1517 	/* FIXME add insert text event */
1518 	gtk_html_editor_event_command (e->widget, GTK_HTML_COMMAND_INSERT_PARAGRAPH, TRUE);
1519 
1520 	do {
1521 		nl   = memchr (text, '\n', bytes);
1522 		alen = nl ? g_utf8_pointer_to_offset (text, nl) : len;
1523 		if (alen) {
1524 			HTMLObject *o;
1525 			gboolean check = FALSE;
1526 
1527 			check_magic_link (e, text, alen);
1528 
1529 			/* stop inserting links after space */
1530 			if (*text == ' ')
1531 				html_engine_set_insertion_link (e, NULL, NULL);
1532 
1533 			o = html_engine_new_text (e, text, alen);
1534 			if (attrs)
1535 				HTML_TEXT (o)->extra_attr_list = pango_attr_list_copy (attrs);
1536 			html_text_convert_nbsp (HTML_TEXT (o), TRUE);
1537 
1538 			if (alen == 1 && html_is_in_word (html_text_get_char (HTML_TEXT (o), 0))
1539 			    && !html_is_in_word (html_cursor_get_current_char (e->cursor))) {
1540 				/* printf ("need_spell_check\n"); */
1541 				e->need_spell_check = TRUE;
1542 			} else {
1543 				check = TRUE;
1544 			}
1545 			insert_object (e, o, html_object_get_length (o), e->cursor->position + html_object_get_length (o),
1546 				       1, HTML_UNDO_UNDO, check);
1547 			if (alen == 1 && !HTML_IS_PLAIN_PAINTER (e->painter))
1548 				use_pictograms (e);
1549 		}
1550 		if (nl) {
1551 			html_engine_insert_empty_paragraph (e);
1552 			len -= alen + 1;
1553 			bytes -= (nl - text) + 1;
1554 			text = nl + 1;
1555 		}
1556 	} while (nl);
1557 	html_undo_level_end (e->undo, e);
1558 
1559 	g_free (sanitized_text);
1560 }
1561 
1562 void
html_engine_insert_text(HTMLEngine * e,const gchar * text,gint len)1563 html_engine_insert_text (HTMLEngine *e,
1564                          const gchar *text,
1565                          gint len)
1566 {
1567 	html_engine_insert_text_with_extra_attributes (e, text, len, NULL);
1568 }
1569 
1570 void
html_engine_paste_text_with_extra_attributes(HTMLEngine * e,const gchar * text,guint len,PangoAttrList * attrs)1571 html_engine_paste_text_with_extra_attributes (HTMLEngine *e,
1572                                               const gchar *text,
1573                                               guint len,
1574                                               PangoAttrList *attrs)
1575 {
1576 	gchar *undo_name = g_strdup_printf ("Paste text: '%s'", text);
1577 	gchar *redo_name = g_strdup_printf ("Unpaste text: '%s'", text);
1578 
1579 	html_undo_level_begin (e->undo, undo_name, redo_name);
1580 	g_free (undo_name);
1581 	g_free (redo_name);
1582 	html_engine_delete (e);
1583 	html_engine_insert_text_with_extra_attributes (e, text, len, attrs);
1584 	html_undo_level_end (e->undo, e);
1585 }
1586 
1587 void
html_engine_paste_text(HTMLEngine * e,const gchar * text,guint len)1588 html_engine_paste_text (HTMLEngine *e,
1589                         const gchar *text,
1590                         guint len)
1591 {
1592 	html_engine_paste_text_with_extra_attributes (e, text, len, NULL);
1593 }
1594 
1595 void
html_engine_paste_link(HTMLEngine * e,const gchar * text,gint len,const gchar * complete_url)1596 html_engine_paste_link (HTMLEngine *e,
1597                         const gchar *text,
1598                         gint len,
1599                         const gchar *complete_url)
1600 {
1601 	gchar *url, *target;
1602 
1603 	if (len == -1)
1604 		len = g_utf8_strlen (text, -1);
1605 
1606 	url = g_strdup (complete_url);
1607 	target = strrchr (url, '#');
1608 	if (target) {
1609 		*target = 0;
1610 		target++;
1611 	}
1612 
1613 	html_engine_paste_text (e, text, len);
1614 	html_text_add_link (HTML_TEXT (e->cursor->object), e, url, target, e->cursor->offset >= len ? e->cursor->offset - len : 0, e->cursor->offset);
1615 
1616 	g_free (url);
1617 }
1618 
1619 void
html_engine_delete_container(HTMLEngine * e)1620 html_engine_delete_container (HTMLEngine *e)
1621 {
1622 	g_assert (HTML_IS_ENGINE (e));
1623 	g_assert (e->cursor->object);
1624 	g_assert (html_object_is_container (e->cursor->object));
1625 
1626 	html_engine_set_mark (e);
1627 	html_engine_update_selection_if_necessary (e);
1628 	html_engine_freeze (e);
1629 	if (e->cursor->offset)
1630 		html_cursor_beginning_of_line (e->cursor, e);
1631 	else
1632 		html_cursor_end_of_line (e->cursor, e);
1633 	html_engine_delete (e);
1634 	html_engine_thaw (e);
1635 }
1636 
1637 void
html_engine_delete_n(HTMLEngine * e,guint len,gboolean forward)1638 html_engine_delete_n (HTMLEngine *e,
1639                       guint len,
1640                       gboolean forward)
1641 {
1642 	if (html_engine_is_selection_active (e))
1643 		html_engine_delete (e);
1644 	else {
1645 		html_engine_block_selection (e);
1646 		html_engine_set_mark (e);
1647 		html_engine_update_selection_if_necessary (e);
1648 		html_engine_freeze (e);
1649 		/* Remove magic smiley */
1650 		if (!forward && len == 1 && gtk_html_get_magic_smileys (e->widget)) {
1651 			HTMLObject *object = html_object_get_tail_leaf (e->cursor->object);
1652 
1653 			if (HTML_IS_IMAGE (object) && html_object_get_data (object, "picto") != NULL) {
1654 				gchar *picto = g_strdup (html_object_get_data (object, "picto"));
1655 				html_undo_level_begin (e->undo, "Remove Magic Smiley", "Undo Remove Magic Smiley");
1656 				html_cursor_backward (e->cursor, e);
1657 				html_engine_delete (e);
1658 				html_engine_insert_text (e, picto, -1);
1659 				html_undo_level_end (e->undo, e);
1660 				g_free (picto);
1661 
1662 				html_engine_unblock_selection (e);
1663 				html_engine_thaw (e);
1664 				return;
1665 			}
1666 		}
1667 		if (forward) {
1668 			gint i;
1669 
1670 			for (i = len; i > 0; i--)
1671 				html_cursor_forward (e->cursor, e);
1672 			html_engine_delete (e);
1673 		} else {
1674 			html_object_backspace (e->cursor->object, e->cursor, e);
1675 		}
1676 		html_engine_unblock_selection (e);
1677 		html_engine_thaw (e);
1678 	}
1679 }
1680 
1681 void
html_engine_cut_line(HTMLEngine * e)1682 html_engine_cut_line (HTMLEngine *e)
1683 {
1684 	g_return_if_fail (e != NULL);
1685 	g_return_if_fail (HTML_IS_ENGINE (e));
1686 
1687 	html_undo_level_begin (e->undo, "Cut Line", "Undo Cut Line");
1688 	html_engine_set_mark (e);
1689 	html_engine_end_of_line (e);
1690 
1691 	if (e->cursor->position == e->mark->position)
1692 		html_cursor_forward (e->cursor, e);
1693 
1694 	html_engine_cut (e);
1695 	html_undo_level_end (e->undo, e);
1696 }
1697 
1698 typedef struct {
1699 	HTMLColor   *color;
1700 	const gchar *url;
1701 	const gchar *target;
1702 } HTMLEngineLinkInsertData;
1703 
1704 static void
change_link(HTMLObject * o,HTMLEngine * e,gpointer data)1705 change_link (HTMLObject *o,
1706              HTMLEngine *e,
1707              gpointer data)
1708 {
1709 	HTMLObject *changed;
1710 	HTMLEngineLinkInsertData *d = (HTMLEngineLinkInsertData *) data;
1711 
1712 	changed = d->url ? html_object_set_link (o, d->color, d->url, d->target) : html_object_remove_link (o, d->color);
1713 	if (changed) {
1714 		if (o->parent) {
1715 			g_assert (HTML_OBJECT_TYPE (o->parent) == HTML_TYPE_CLUEFLOW);
1716 
1717 			html_clue_append_after (HTML_CLUE (o->parent), changed, o);
1718 			html_clue_remove (HTML_CLUE (o->parent), o);
1719 			html_object_destroy (o);
1720 			if (changed->prev)
1721 				html_object_merge (changed->prev, changed, e, NULL, NULL, NULL);
1722 		} else {
1723 			html_object_destroy (e->clipboard);
1724 			e->clipboard     = changed;
1725 			e->clipboard_len = html_object_get_length (changed);
1726 		}
1727 	}
1728 }
1729 
1730 void
html_engine_set_insertion_link(HTMLEngine * e,const gchar * url,const gchar * target)1731 html_engine_set_insertion_link (HTMLEngine *e,
1732                                 const gchar *url,
1733                                 const gchar *target)
1734 {
1735 	html_engine_set_url    (e, url);
1736 	html_engine_set_target (e, target);
1737 	if (!url && e->insertion_color == html_colorset_get_color (e->settings->color_set, HTMLLinkColor))
1738 		html_engine_set_color (e, html_colorset_get_color (e->settings->color_set, HTMLTextColor));
1739 	else if (url)
1740 		html_engine_set_color (e, html_colorset_get_color (e->settings->color_set, HTMLLinkColor));
1741 }
1742 
1743 void
html_engine_edit_set_link(HTMLEngine * e,const gchar * url,const gchar * target)1744 html_engine_edit_set_link (HTMLEngine *e,
1745                            const gchar *url,
1746                            const gchar *target)
1747 {
1748 	if (html_engine_is_selection_active (e)) {
1749 		HTMLEngineLinkInsertData data;
1750 
1751 		data.url    = url;
1752 		data.target = target;
1753 		data.color  = url
1754 			? html_colorset_get_color (e->settings->color_set, HTMLLinkColor)
1755 			: html_colorset_get_color (e->settings->color_set, HTMLTextColor);
1756 		html_engine_cut_and_paste (e,
1757 					   url ? "Insert link" : "Remove link",
1758 					   url ? "Remove link" : "Insert link",
1759 					   change_link, &data);
1760 	} else
1761 		html_engine_set_insertion_link (e, url, target);
1762 }
1763 
1764 static void
prepare_empty_flow(HTMLEngine * e,HTMLUndoDirection dir)1765 prepare_empty_flow (HTMLEngine *e,
1766                     HTMLUndoDirection dir)
1767 {
1768 	if (!html_clueflow_is_empty (HTML_CLUEFLOW (e->cursor->object->parent))) {
1769 		insert_empty_paragraph (e, dir, TRUE);
1770 		if (e->cursor->object->parent->prev
1771 		    && html_clueflow_is_empty (HTML_CLUEFLOW (e->cursor->object->parent->prev))) {
1772 			html_cursor_backward (e->cursor, e);
1773 		} else if (!html_clueflow_is_empty (HTML_CLUEFLOW (e->cursor->object->parent))) {
1774 			insert_empty_paragraph (e, dir, TRUE);
1775 			html_cursor_backward (e->cursor, e);
1776 		}
1777 	}
1778 }
1779 
1780 static void
append_object(HTMLEngine * e,HTMLObject * o,guint len,HTMLUndoDirection dir)1781 append_object (HTMLEngine *e,
1782                HTMLObject *o,
1783                guint len,
1784                HTMLUndoDirection dir)
1785 {
1786 	HTMLObject *c, *cn;
1787 	HTMLClue *clue;
1788 	guint position_before;
1789 
1790 	html_engine_freeze (e);
1791 	prepare_empty_flow (e, dir);
1792 	position_before = e->cursor->position;
1793 
1794 	g_return_if_fail (html_clueflow_is_empty (HTML_CLUEFLOW (e->cursor->object->parent)));
1795 
1796 	clue = HTML_CLUE (e->cursor->object->parent);
1797 	for (c = clue->head; c; c = cn) {
1798 		cn = c->next;
1799 		html_object_destroy (c);
1800 	}
1801 	clue->head = clue->tail = o;
1802 	e->cursor->object = o;
1803 	e->cursor->offset = 0;
1804 	o->parent = HTML_OBJECT (clue);
1805 
1806 	html_cursor_forward_n (e->cursor, e, len);
1807 	html_object_change_set (o, HTML_CHANGE_ALL_CALC);
1808 	html_engine_thaw (e);
1809 
1810 	insert_setup_undo (e, len, position_before, dir, FALSE, FALSE);
1811 
1812 	return;
1813 }
1814 
1815 void
html_engine_append_object(HTMLEngine * e,HTMLObject * o,guint len)1816 html_engine_append_object (HTMLEngine *e,
1817                            HTMLObject *o,
1818                            guint len)
1819 {
1820 	html_undo_level_begin (e->undo, "Append object", "Remove appended object");
1821 	append_object (e, o, len, HTML_UNDO_UNDO);
1822 	html_undo_level_end (e->undo, e);
1823 }
1824 
1825 static void
replace_objects_in_clue_from_another(HTMLClue * dest,HTMLClue * src)1826 replace_objects_in_clue_from_another (HTMLClue *dest,
1827                                       HTMLClue *src)
1828 {
1829 	HTMLObject *cur, *next;
1830 
1831 	for (cur = dest->head; cur; cur = next) {
1832 		next = cur->next;
1833 		html_object_remove_child (cur->parent, cur);
1834 		html_object_destroy (cur);
1835 	}
1836 
1837 	for (cur = src->head; cur; cur = next) {
1838 		next = cur->next;
1839 		html_object_remove_child (cur->parent, cur);
1840 		html_clue_append (dest, cur);
1841 	}
1842 }
1843 
1844 static void
append_flow(HTMLEngine * e,HTMLObject * o,guint len,HTMLUndoDirection dir)1845 append_flow (HTMLEngine *e,
1846              HTMLObject *o,
1847              guint len,
1848              HTMLUndoDirection dir)
1849 {
1850 	HTMLObject *where;
1851 	guint position, position_before;
1852 
1853 	html_engine_freeze (e);
1854 
1855 	position_before = e->cursor->position;
1856 	prepare_empty_flow (e, dir);
1857 
1858 	g_return_if_fail (html_clueflow_is_empty (HTML_CLUEFLOW (e->cursor->object->parent)));
1859 
1860 	where = e->cursor->object->parent;
1861 	html_object_change_set (o, HTML_CHANGE_ALL_CALC);
1862 
1863 	e->cursor->object = html_object_get_head_leaf (o);
1864 	e->cursor->offset = 0;
1865 
1866 	/* be sure we have valid cursor position (like when there is a focusable container) */
1867 	position = e->cursor->position;
1868 	while (html_cursor_backward (e->cursor, e))
1869 		;
1870 	e->cursor->position = position;
1871 
1872 	/* we move objects between flows to preserve attributes as indentation, ... */
1873 	if (HTML_IS_CLUEFLOW (o)) {
1874 		replace_objects_in_clue_from_another (HTML_CLUE (where), HTML_CLUE (o));
1875 		html_object_destroy (o);
1876 	} else {
1877 		html_clue_append_after (HTML_CLUE (where->parent), o, where);
1878 		html_object_remove_child (where->parent, where);
1879 		html_object_destroy (where);
1880 	}
1881 
1882 	html_cursor_forward_n (e->cursor, e, len);
1883 	html_engine_thaw (e);
1884 
1885 	insert_setup_undo (e, len, position_before, dir, FALSE, FALSE);
1886 
1887 	return;
1888 }
1889 
1890 void
html_engine_append_flow(HTMLEngine * e,HTMLObject * o,guint len)1891 html_engine_append_flow (HTMLEngine *e,
1892                          HTMLObject *o,
1893                          guint len)
1894 {
1895 	html_undo_level_begin (e->undo, "Append flow", "Remove appended flow");
1896 	append_flow (e, o, len, HTML_UNDO_UNDO);
1897 	html_undo_level_end (e->undo, e);
1898 }
1899 
1900 void
html_engine_cut_and_paste_begin(HTMLEngine * e,const gchar * undo_op_name,const gchar * redo_op_name)1901 html_engine_cut_and_paste_begin (HTMLEngine *e,
1902                                  const gchar *undo_op_name,
1903                                  const gchar *redo_op_name)
1904 {
1905 	guint position;
1906 	gint level;
1907 
1908 	html_engine_hide_cursor (e);
1909 	html_engine_selection_push (e);
1910 	html_engine_clipboard_push (e);
1911 	html_undo_level_begin (e->undo, undo_op_name, redo_op_name);
1912 	position = e->mark ? MAX (e->cursor->position, e->mark->position) : e->cursor->position;
1913 	level = html_engine_cut (e);
1914 
1915 	e->cut_and_paste_stack = g_list_prepend (e->cut_and_paste_stack, GINT_TO_POINTER (level));
1916 	e->cut_and_paste_stack = g_list_prepend (e->cut_and_paste_stack, GUINT_TO_POINTER (position));
1917 }
1918 
1919 void
html_engine_cut_and_paste_end(HTMLEngine * e)1920 html_engine_cut_and_paste_end (HTMLEngine *e)
1921 {
1922 	guint position;
1923 	gint level;
1924 
1925 	position = GPOINTER_TO_UINT (e->cut_and_paste_stack->data);
1926 	e->cut_and_paste_stack = g_list_remove (e->cut_and_paste_stack, e->cut_and_paste_stack->data);
1927 	level    = GPOINTER_TO_INT (e->cut_and_paste_stack->data);
1928 	e->cut_and_paste_stack = g_list_remove (e->cut_and_paste_stack, e->cut_and_paste_stack->data);
1929 
1930 	if (e->clipboard) {
1931 		insert_object (e, e->clipboard, e->clipboard_len, position, level, HTML_UNDO_UNDO, TRUE);
1932 		e->clipboard = NULL;
1933 	}
1934 	html_undo_level_end (e->undo, e);
1935 	html_engine_clipboard_pop (e);
1936 	html_engine_selection_pop (e);
1937 	html_engine_show_cursor (e);
1938 }
1939 
1940 void
html_engine_cut_and_paste(HTMLEngine * e,const gchar * undo_op_name,const gchar * redo_op_name,HTMLObjectForallFunc iterator,gpointer data)1941 html_engine_cut_and_paste (HTMLEngine *e,
1942                            const gchar *undo_op_name,
1943                            const gchar *redo_op_name,
1944                            HTMLObjectForallFunc iterator,
1945                            gpointer data)
1946 {
1947 	html_engine_edit_selection_updater_update_now (e->selection_updater);
1948 	html_engine_cut_and_paste_begin (e, undo_op_name, redo_op_name);
1949 	if (e->clipboard)
1950 		html_object_forall (e->clipboard, e, iterator, data);
1951 	html_engine_cut_and_paste_end (e);
1952 }
1953 
1954 static void
delete_upto(HTMLEngine * e,HTMLCursor ** start,HTMLCursor ** end,HTMLObject * object,guint offset)1955 delete_upto (HTMLEngine *e,
1956              HTMLCursor **start,
1957              HTMLCursor **end,
1958              HTMLObject *object,
1959              guint offset)
1960 {
1961 	guint position;
1962 
1963 	if (e->mark)
1964 		html_cursor_destroy (e->mark);
1965 	e->mark = *start;
1966 	html_cursor_jump_to (e->cursor, e, object, offset);
1967 	position = e->cursor->position;
1968 	delete_object (e, NULL, NULL, HTML_UNDO_UNDO, TRUE);
1969 	*start = html_cursor_dup (e->cursor);
1970 	html_cursor_forward (*start, e);
1971 	(*end)->position -= position - e->cursor->position;
1972 }
1973 
1974 static gboolean
check_for_simple_containers(HTMLObject * child,HTMLObject * parent)1975 check_for_simple_containers (HTMLObject *child,
1976                              HTMLObject *parent)
1977 {
1978 	while (child && child != parent) {
1979 		if (html_object_is_container (child)) {
1980 			switch (child->klass->type) {
1981 			case HTML_TYPE_CLUEFLOW:
1982 			case HTML_TYPE_CLUEV:
1983 			case HTML_TYPE_TABLECELL:
1984 			case HTML_TYPE_TABLE:
1985 				break;
1986 			default:
1987 				return FALSE;
1988 			}
1989 		}
1990 		child = child->parent;
1991 	}
1992 
1993 	return TRUE;
1994 }
1995 
1996 static gboolean
check_for_simple_delete(HTMLObject * start,HTMLObject * end)1997 check_for_simple_delete (HTMLObject *start,
1998                          HTMLObject *end)
1999 {
2000 	HTMLObject *common_parent = get_common_parent (start, end);
2001 
2002 	if (common_parent && check_for_simple_containers (start->parent, common_parent) && check_for_simple_containers (end->parent, common_parent))
2003 		return TRUE;
2004 
2005 	return FALSE;
2006 }
2007 
2008 void
html_engine_delete(HTMLEngine * e)2009 html_engine_delete (HTMLEngine *e)
2010 {
2011 	html_undo_level_begin (e->undo, "Delete", "Undelete");
2012 	html_engine_edit_selection_updater_update_now (e->selection_updater);
2013 	if (html_engine_is_selection_active (e)) {
2014 		HTMLCursor *start = html_cursor_dup (e->mark->position < e->cursor->position ? e->mark : e->cursor);
2015 		HTMLCursor *end = html_cursor_dup (e->mark->position < e->cursor->position ? e->cursor : e->mark);
2016 		gint start_position = start->position;
2017 		gint end_position = end->position;
2018 
2019 		if (end_position - start_position > 0) {
2020 			gint len = end_position - start_position;
2021 			g_signal_emit_by_name (e->widget, "object_delete", start_position, len);
2022 		}
2023 
2024 		while (start->position < end->position) {
2025 			if (check_for_simple_delete (start->object, end->object)) {
2026 				if (e->mark)
2027 					html_cursor_destroy (e->mark);
2028 				html_cursor_destroy (e->cursor);
2029 				e->mark = start;
2030 				e->cursor = end;
2031 				start = end = NULL;
2032 				delete_object (e, NULL, NULL, HTML_UNDO_UNDO, TRUE);
2033 				break;
2034 			} else {
2035 				HTMLObject *prev = NULL, *cur = start->object;
2036 
2037 				/* go thru current cluev */
2038 				do {
2039 					/* go thru current flow */
2040 					while (cur) {
2041 						/* lets look if container is whole contained in the selection */
2042 						if (html_object_is_container (cur)) {
2043 							html_cursor_jump_to (e->cursor, e, cur, html_object_get_length (cur));
2044 							if (e->cursor->position > end->position) {
2045 								/* it's not => delete upto this container */
2046 
2047 								delete_upto (e, &start, &end, cur, 0);
2048 								prev = NULL;
2049 								break;
2050 							}
2051 						}
2052 						prev = cur;
2053 						cur = html_object_next_not_slave (cur);
2054 					}
2055 				} while (prev && prev->parent->next && (cur = html_object_head (prev->parent->next)));
2056 
2057 				if (prev) {
2058 					/* cluev end is in the selection. Lets handle this case just like simple delete
2059 					since text is the selection itself */
2060 					if (e->mark)
2061 						html_cursor_destroy (e->mark);
2062 					html_cursor_destroy (e->cursor);
2063 					e->mark = start;
2064 					e->cursor = end;
2065 					start = end = NULL;
2066 					delete_object (e, NULL, NULL, HTML_UNDO_UNDO, TRUE);
2067 					break;
2068 				}
2069 			}
2070 		}
2071 
2072 		if (start)
2073 			html_cursor_destroy (start);
2074 		if (end)
2075 			html_cursor_destroy (end);
2076 		html_cursor_jump_to_position (e->cursor, e, start_position);
2077 
2078 	}
2079 	html_undo_level_end (e->undo, e);
2080 }
2081