source: trunk/third/gtkhtml3/src/htmlundo.c @ 21460

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