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