source: trunk/third/gtkhtml3/src/htmlengine-edit-cut-and-paste.c @ 21116

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