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