source: trunk/third/gtkhtml/src/htmlundo.c @ 18136

Revision 18136, 14.6 KB checked in by ghudson, 22 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r18135, 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 Helix Code, Inc.
5
6   This library is free software; you can redistribute it and/or
7   modify it under the terms of the GNU Library General Public
8   License as published by the Free Software Foundation; either
9   version 2 of the License, or (at your option) any later version.
10
11   This library is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHcANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   Library General Public License for more details.
15
16   You should have received a copy of the GNU Library General Public License
17   along with this library; see the file COPYING.LIB.  If not, write to
18   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19   Boston, MA 02111-1307, USA.
20*/
21
22#include <config.h>
23#include "htmlcursor.h"
24#include "htmlengine.h"
25#include "htmlundo.h"
26
27struct _HTMLUndoStack {
28        GList *stack;
29        guint  size;
30};
31typedef struct _HTMLUndoStack HTMLUndoStack;
32
33struct _HTMLUndo {
34        HTMLUndoStack undo;
35        HTMLUndoStack redo;
36        HTMLUndoStack undo_used;
37
38        /* these lists are stacks containing other
39           levels undo/redo after calling html_undo_level_start */
40        GSList   *undo_levels;
41        GSList   *redo_levels;
42        guint     level;
43        guint     in_redo;
44        gint      step_counter;
45};
46
47#ifdef UNDO_DEBUG
48static void html_undo_debug (HTMLUndo *undo);
49#define DEBUG(x) html_undo_debug (x)
50#else
51#define DEBUG(x)
52#endif
53
54static void add_used_and_redo_to_undo (HTMLUndo *undo);
55
56inline static void
57stack_copy (HTMLUndoStack *src, HTMLUndoStack *dst)
58{
59        dst->stack = src->stack;
60        dst->size  = src->size;
61}
62
63inline static void
64stack_dup (HTMLUndoStack *src, HTMLUndoStack *dst)
65{
66        dst->stack = g_list_copy (src->stack);
67        dst->size  = src->size;
68}
69
70static void
71destroy_action_list (GList *lp)
72{
73        GList *p;
74
75        for (p = lp; p != NULL; p = p->next)
76                html_undo_action_destroy ((HTMLUndoAction *) p->data);
77}
78
79
80HTMLUndo *
81html_undo_new (void)
82{
83        HTMLUndo *new_undo;
84
85        new_undo = g_new0 (HTMLUndo, 1);
86
87        return new_undo;
88}
89
90void
91html_undo_destroy  (HTMLUndo *undo)
92{
93        g_return_if_fail (undo != NULL);
94
95        destroy_action_list (undo->undo.stack);
96        destroy_action_list (undo->undo_used.stack);
97        destroy_action_list (undo->redo.stack);
98
99        g_free (undo);
100}
101
102
103static void
104action_do_and_destroy_redo (HTMLEngine *engine, HTMLUndo *undo, GList **stack, HTMLUndoDirection dir)
105{
106        HTMLUndoAction *action;
107        GList *first;
108
109        first  = *stack;
110        action = HTML_UNDO_ACTION (first->data);
111
112        html_cursor_jump_to_position (engine->cursor, engine, action->position);
113        (* action->function) (engine, action->data, dir, action->position_after);
114        html_cursor_jump_to_position (engine->cursor, engine, action->position_after);
115
116        *stack = g_list_remove (first, first->data);
117        if (undo->level == 0) {
118                html_undo_action_destroy (action);
119
120                first = undo->undo_used.stack;
121                html_undo_action_destroy (HTML_UNDO_ACTION (first->data));
122                undo->undo_used.stack = g_list_remove (first, first->data);
123        }
124}
125
126static void
127action_do_and_destroy_undo (HTMLEngine *engine, HTMLUndo *undo, HTMLUndoDirection dir)
128{
129        HTMLUndoAction *action;
130        GList *first;
131
132        first  = undo->undo.stack;
133        action = HTML_UNDO_ACTION (first->data);
134
135        html_cursor_jump_to_position (engine->cursor, engine, action->position);
136        (* action->function) (engine, action->data, dir, action->position_after);
137        html_cursor_jump_to_position (engine->cursor, engine, action->position_after);
138
139        undo->undo.stack = g_list_remove (first, first->data);
140        if (undo->level == 0) {
141                undo->undo_used.stack = g_list_prepend (undo->undo_used.stack, action);
142                undo->step_counter --;
143        }
144}
145
146void
147html_undo_do_undo (HTMLUndo *undo,
148                   HTMLEngine *engine)
149{
150        g_return_if_fail (undo != NULL);
151        g_return_if_fail (engine != NULL);
152
153        if (undo->undo.size > 0) {
154#ifdef UNDO_DEBUG
155                if (!undo->level) {
156                        printf ("UNDO begin\n");
157                        DEBUG (undo);
158                }
159#endif
160                engine->block_events ++;
161                action_do_and_destroy_undo (engine, undo, HTML_UNDO_UNDO);
162                undo->undo.size--;
163                engine->block_events --;
164#ifdef UNDO_DEBUG
165                if (!undo->level) {
166                        DEBUG (undo);
167                        printf ("UNDO end\n");
168                }
169#endif
170        }
171}
172
173void
174html_undo_do_redo (HTMLUndo *undo,
175                   HTMLEngine *engine)
176{
177        g_return_if_fail (undo != NULL);
178        g_return_if_fail (engine != NULL);
179
180        if (undo->redo.size > 0) {
181#ifdef UNDO_DEBUG
182                if (!undo->level) {
183                        printf ("REDO begin\n");
184                        DEBUG (undo);
185                }
186#endif
187                undo->in_redo ++;
188                engine->block_events ++;
189                action_do_and_destroy_redo (engine, undo, &undo->redo.stack, HTML_UNDO_REDO);
190                undo->redo.size--;
191                engine->block_events --;
192                undo->in_redo --;
193
194#ifdef UNDO_DEBUG
195                if (!undo->level) {
196                        DEBUG (undo);
197                        printf ("REDO end\n");
198                }
199#endif
200        }
201}
202
203
204void
205html_undo_discard_redo (HTMLUndo *undo)
206{
207        g_return_if_fail (undo != NULL);
208
209        if (undo->redo.stack == NULL)
210                return;
211
212        destroy_action_list (undo->redo.stack);
213
214        undo->redo.stack = NULL;
215        undo->redo.size = 0;
216}
217
218void
219html_undo_add_undo_action  (HTMLUndo *undo, HTMLUndoAction *action)
220{
221        g_return_if_fail (undo != NULL);
222        g_return_if_fail (action != NULL);
223
224        if (undo->level == 0) {
225                if (undo->in_redo == 0 && undo->redo.size > 0)
226                        add_used_and_redo_to_undo (undo);
227
228                if (undo->undo.size >= HTML_UNDO_LIMIT) {
229                        HTMLUndoAction *last_action;
230                        GList *last;
231
232                        last = g_list_last (undo->undo.stack);
233                        last_action = (HTMLUndoAction *) last->data;
234
235                        undo->undo.stack = g_list_remove_link (undo->undo.stack, last);
236                        g_list_free (last);
237
238                        html_undo_action_destroy (last_action);
239
240                        undo->undo.size--;
241                }
242
243                undo->step_counter ++;
244        }
245
246        undo->undo.stack = g_list_prepend (undo->undo.stack, action);
247        undo->undo.size ++;
248
249#ifdef UNDO_DEBUG
250        if (!undo->level) {
251                printf ("ADD UNDO\n");
252                DEBUG (undo);
253        }
254#endif
255}
256
257void
258html_undo_add_redo_action  (HTMLUndo *undo,
259                            HTMLUndoAction *action)
260{
261        g_return_if_fail (undo != NULL);
262        g_return_if_fail (action != NULL);
263
264        undo->redo.stack = g_list_prepend (undo->redo.stack, action);
265        undo->redo.size ++;
266}
267
268void
269html_undo_add_action  (HTMLUndo *undo, HTMLUndoAction *action, HTMLUndoDirection dir)
270{
271        if (dir == HTML_UNDO_UNDO)
272                html_undo_add_undo_action (undo, action);
273        else
274                html_undo_add_redo_action (undo, action);
275}
276
277/*
278  undo levels
279
280  * IDEA: it closes number of undo steps into one
281  * examples: paste
282               - it first cuts active selection and then inserts objects
283                 from cut_buffer on actual cursor position
284               - if you don't use udo levels, it will generate two undo steps/actions
285              replace
286               - replace uses paste operation, so when it replaces N occurences,
287                 it generates 2*N steps (without using undo levels in paste and replace)
288
289  * usage is simple - just call html_undo_level_begin before using functions with undo
290    and html_undo_level_end after them
291
292*/
293
294#define HTML_UNDO_LEVEL(x) ((HTMLUndoLevel *) x)
295struct _HTMLUndoLevel {
296        HTMLUndoData data;
297
298        HTMLUndo      *parent_undo;
299        HTMLUndoStack  stack;
300
301        gchar    *description [HTML_UNDO_END];
302};
303typedef struct _HTMLUndoLevel HTMLUndoLevel;
304
305static void undo_step_action (HTMLEngine *e, HTMLUndoData *data, HTMLUndoDirection dir, guint position_after);
306static void redo_level_begin (HTMLUndo *undo, const gchar *redo_desription, const gchar *undo_desription);
307static void redo_level_end   (HTMLUndo *undo);
308
309static void
310level_destroy (HTMLUndoData *data)
311{
312        HTMLUndoLevel *level;
313
314        g_assert (data);
315
316        level = HTML_UNDO_LEVEL (data);
317
318        for (; level->stack.stack; level->stack.stack = level->stack.stack->next)
319                html_undo_action_destroy (HTML_UNDO_ACTION (level->stack.stack->data));
320
321        g_free (level->description [HTML_UNDO_UNDO]);
322        g_free (level->description [HTML_UNDO_REDO]);
323}
324
325static HTMLUndoLevel *
326level_new (HTMLUndo *undo, HTMLUndoStack *stack, const gchar *undo_description, const gchar *redo_description)
327{
328        HTMLUndoLevel *nl = g_new (HTMLUndoLevel, 1);
329
330        html_undo_data_init (HTML_UNDO_DATA (nl));
331
332        stack_copy (stack, &nl->stack);
333
334        nl->data.destroy                 = level_destroy;
335        nl->parent_undo                  = undo;
336        nl->description [HTML_UNDO_UNDO] = g_strdup (undo_description);
337        nl->description [HTML_UNDO_REDO] = g_strdup (redo_description);
338
339        return nl;
340}
341
342void
343html_undo_level_begin (HTMLUndo *undo, const gchar *undo_desription, const gchar *redo_desription)
344{
345        undo->undo_levels = g_slist_prepend (undo->undo_levels, level_new (undo, &undo->undo,
346                                                                           undo_desription, redo_desription));
347        undo->undo.stack  = NULL;
348        undo->undo.size   = 0;
349
350        undo->level ++;
351}
352
353static void
354redo_level_begin (HTMLUndo *undo, const gchar *redo_desription, const gchar *undo_desription)
355{
356        undo->redo_levels = g_slist_prepend (undo->redo_levels, level_new (undo, &undo->redo,
357                                                                           undo_desription, redo_desription));
358        undo->redo.stack  = NULL;
359        undo->redo.size   = 0;
360
361        undo->level ++;
362}
363
364static void
365redo_level_end (HTMLUndo *undo)
366{
367        HTMLUndoLevel *level;
368        HTMLUndoStack  save_redo;
369        GSList *head;
370
371        g_assert (undo->redo_levels);
372
373        undo->level --;
374
375        /* preserve current redo stack */
376        stack_copy (&undo->redo, &save_redo);
377
378        /* restore last level from levels stack */
379        level = HTML_UNDO_LEVEL (undo->redo_levels->data);
380        stack_copy (&level->stack, &undo->redo);
381
382        /* fill level with current redo step */
383        stack_copy (&save_redo, &level->stack);
384
385        /* add redo step redo action */
386        if (save_redo.size) {
387                HTMLUndoAction *action;
388
389                /* we use position from last redo action on the stack */
390                action = (HTMLUndoAction *) save_redo.stack->data;
391                html_undo_add_redo_action (undo, action = html_undo_action_new (level->description [HTML_UNDO_REDO],
392                                                                                undo_step_action,
393                                                                                HTML_UNDO_DATA (level),
394                                                                                action->position, action->position_after));
395#ifdef UNDO_DEBUG
396                action->is_level = TRUE;
397#endif
398        } else
399                html_undo_data_unref (HTML_UNDO_DATA (level));
400
401        head = undo->redo_levels;
402        undo->redo_levels = g_slist_remove_link (undo->redo_levels, head);
403        g_slist_free (head);
404}
405
406void
407html_undo_level_end (HTMLUndo *undo)
408{
409        HTMLUndoLevel *level;
410        HTMLUndoStack  save_undo;
411        GSList *head;
412
413        g_assert (undo->undo_levels);
414        g_assert (undo->level);
415
416        undo->level--;
417
418        /* preserve current redo stack */
419        stack_copy (&undo->undo, &save_undo);
420
421        /* restore last level from levels stack */
422        level = HTML_UNDO_LEVEL (undo->undo_levels->data);
423        stack_copy (&level->stack, &undo->undo);
424
425        /* fill level with current undo step */
426        stack_copy (&save_undo, &level->stack);
427
428        /* add undo step undo action */
429        if (save_undo.size) {
430                HTMLUndoAction *action;
431
432
433                /* we use position from last undo action on the stack */
434                action = html_undo_action_new (level->description [HTML_UNDO_UNDO],
435                                               undo_step_action,
436                                               HTML_UNDO_DATA (level),
437                                               HTML_UNDO_ACTION (save_undo.stack->data)->position,
438                                               HTML_UNDO_ACTION (save_undo.stack->data)->position_after);
439#ifdef UNDO_DEBUG
440                action->is_level = TRUE;
441#endif
442                html_undo_add_undo_action (undo, action);
443        } else
444                html_undo_data_unref (HTML_UNDO_DATA (level));
445
446        head = undo->undo_levels;
447        undo->undo_levels = g_slist_remove_link (undo->undo_levels, head);
448        g_slist_free (head);
449}
450
451static void
452undo_step_action (HTMLEngine *e, HTMLUndoData *data, HTMLUndoDirection dir, guint position_after)
453{
454        HTMLUndo      *undo;
455        HTMLUndoLevel *level;
456        HTMLUndoStack  save;
457        HTMLUndoStack *stack;
458
459        level = HTML_UNDO_LEVEL (data);
460        undo  = level->parent_undo;
461        stack = dir == HTML_UNDO_UNDO ? &undo->undo : &undo->redo;
462
463        /* prepare undo/redo step */
464        if (dir == HTML_UNDO_UNDO)
465                redo_level_begin (undo, level->description [HTML_UNDO_REDO], level->description [HTML_UNDO_UNDO]);
466        else
467                html_undo_level_begin (undo, level->description [HTML_UNDO_UNDO], level->description [HTML_UNDO_REDO]);
468
469        /* preserve current undo/redo stack */
470        stack_copy (stack, &save);
471
472        /* set this level */
473        stack_dup (&level->stack, stack);
474
475        undo->level ++;
476        if (dir == HTML_UNDO_UNDO)
477                while (undo->undo.size)
478                        html_undo_do_undo (undo, e);
479        else
480                while (undo->redo.size)
481                        html_undo_do_redo (undo, e);
482        undo->level --;
483
484        /* restore current undo/redo stack */
485        stack_copy (&save, stack);
486
487        /* end redo/undo step */
488        if (dir == HTML_UNDO_UNDO)
489                redo_level_end (undo);
490        else
491                html_undo_level_end (undo);
492}
493
494void
495html_undo_data_init (HTMLUndoData   *data)
496{
497        data->ref_count = 1;
498        data->destroy   = NULL;
499}
500
501void
502html_undo_data_ref (HTMLUndoData *data)
503{
504        g_assert (data);
505
506        data->ref_count ++;
507}
508
509void
510html_undo_data_unref (HTMLUndoData *data)
511{
512        g_assert (data);
513        g_assert (data->ref_count > 0);
514
515        data->ref_count --;
516
517        if (data->ref_count == 0) {
518                if (data->destroy)
519                        (*data->destroy) (data);
520                g_free (data);
521        }
522}
523
524HTMLUndoDirection
525html_undo_direction_reverse (HTMLUndoDirection dir)
526{
527        return dir == HTML_UNDO_UNDO ? HTML_UNDO_REDO : HTML_UNDO_UNDO;
528}
529
530static void
531add_used_and_redo_to_undo (HTMLUndo *undo)
532{
533        GList *stack;
534        GList *cur;
535
536        stack            = g_list_reverse (undo->redo.stack);
537        undo->redo.stack = NULL;
538        undo->redo.size  = 0;
539
540        /* add undo_used */
541        for (cur = undo->undo_used.stack; cur; cur = cur->next)
542                html_undo_add_undo_action (undo, HTML_UNDO_ACTION (cur->data));
543        g_list_free (undo->undo_used.stack);
544        undo->undo_used.stack = NULL;
545
546        for (cur = stack; cur; cur = cur->next)
547                html_undo_add_undo_action (undo, HTML_UNDO_ACTION (cur->data));
548        g_list_free (stack);
549
550#ifdef UNDO_DEBUG
551        printf ("REVERSED\n");
552        DEBUG (undo);
553#endif
554}
555
556#ifdef UNDO_DEBUG
557
558static void
559print_stack (GList *stack, guint size, gint l)
560{
561        gint i;
562
563        for (; stack; stack = stack->next) {
564                HTMLUndoAction *action;
565                for (i = 0; i < l; i++) printf ("  ");
566                action = HTML_UNDO_ACTION (stack->data);
567                printf ("%s\n", action->description);
568                if (action->is_level) {
569                        HTMLUndoLevel *level;
570
571                        level = (HTMLUndoLevel *) action->data;
572
573                        print_stack (level->stack.stack, level->stack.size, l + 1);
574                }
575        }
576}
577
578static void
579html_undo_debug (HTMLUndo *undo)
580{
581        printf ("Undo stack (%d):\n", undo->undo.size);
582        print_stack (undo->undo.stack, undo->undo.size, 1);
583        printf ("Redo stack (%d):\n", undo->redo.size);
584        print_stack (undo->redo.stack, undo->redo.size, 1);
585        printf ("Used stack (%d):\n", undo->undo_used.size);
586        print_stack (undo->undo_used.stack, undo->undo_used.size, 1);
587        printf ("--\n");
588}
589
590#endif
591
592void
593html_undo_reset (HTMLUndo *undo)
594{
595        g_return_if_fail (undo != NULL);
596        g_return_if_fail (undo->level == 0);
597
598        destroy_action_list (undo->undo.stack);
599        destroy_action_list (undo->undo_used.stack);
600        destroy_action_list (undo->redo.stack);
601
602        undo->undo.stack = NULL;
603        undo->undo.size  = 0;
604        undo->undo_used.stack = NULL;
605        undo->undo_used.size  = 0;
606        undo->redo.stack = NULL;
607        undo->redo.size  = 0;
608
609        undo->step_counter = 0;
610}
611
612gboolean
613html_undo_has_undo_steps (HTMLUndo *undo)
614{
615        g_assert (undo->step_counter >= 0);
616
617        return undo->step_counter > 0;
618}
619
620gint
621html_undo_get_step_count (HTMLUndo *undo)
622{
623        return undo->step_counter;
624}
Note: See TracBrowser for help on using the repository browser.