fixed GTKHTML detection
[k8lowj.git] / src / undo.c
bloba6d6ba840a7fbb86455485a8fab6127d0f53b137
1 /* logjam - a GTK client for LiveJournal.
2 * Copyright (C) 2000-2003 Evan Martin <evan@livejournal.com>
3 * Ported to LogJam by Ari Pollak <ari@debian.org> in May 2003
5 * This file is based on gedit-undo-manager.c from gEdit 2.2.1.
6 * Original authors:
7 * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
8 * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
9 */
11 #include "gtk-all.h"
12 #include <stdlib.h>
13 #include <string.h>
15 #include "undo.h"
17 typedef struct _UndoAction UndoAction;
18 typedef struct _UndoInsertAction UndoInsertAction;
19 typedef struct _UndoDeleteAction UndoDeleteAction;
21 typedef enum {
22 UNDO_ACTION_INSERT,
23 UNDO_ACTION_DELETE
24 } UndoActionType;
27 * We use offsets instead of GtkTextIters because the last ones
28 * require to much memory in this context without giving us any advantage.
31 struct _UndoInsertAction
33 gint pos;
34 gchar *text;
35 gint length;
36 gint chars;
39 struct _UndoDeleteAction
41 gint start;
42 gint end;
43 gchar *text;
46 struct _UndoAction
48 UndoActionType action_type;
50 union {
51 UndoInsertAction insert;
52 UndoDeleteAction delete;
53 } action;
55 GObject *object;
57 gboolean mergeable;
59 gint order_in_group;
62 struct _UndoMgrPrivate
64 GList* widgets; /* queue of all widgets which are attached */
66 GList* actions;
67 gint next_redo;
69 gint actions_in_current_group;
71 gboolean can_undo;
72 gboolean can_redo;
74 gint running_not_undoable_actions;
76 gint num_of_groups;
79 enum {
80 CAN_UNDO,
81 CAN_REDO,
82 LAST_SIGNAL
85 static void undomgr_class_init (UndoMgrClass *klass);
86 static void undomgr_init (UndoMgr *um);
87 static void undomgr_finalize (GObject *object);
89 static void undomgr_textbuffer_insert_text_handler (GtkTextBuffer *buffer, GtkTextIter *pos,
90 const gchar *text, gint length,
91 UndoMgr *um);
92 static void undomgr_textbuffer_delete_range_handler (GtkTextBuffer *buffer, GtkTextIter *start,
93 GtkTextIter *end, UndoMgr *um);
94 static void undomgr_textbuffer_begin_user_action_handler (GtkTextBuffer *buffer, UndoMgr *um);
95 static void undomgr_textbuffer_end_user_action_handler (GtkTextBuffer *buffer, UndoMgr *um);
97 static void undomgr_free_action_list (UndoMgr *um);
99 static void undomgr_free_widget (UndoMgr *um, GtkWidget *w);
100 static void undomgr_free_widget_list (UndoMgr *um);
102 static void undomgr_add_textbuffer_action (UndoMgr *um,
103 UndoAction undo_action,
104 GtkTextBuffer *buffer);
106 static gboolean undomgr_merge_textbuffer_action (UndoMgr *um,
107 UndoAction *undo_action,
108 GtkTextBuffer *buffer);
110 static void undomgr_free_first_n_actions (UndoMgr *um, gint n);
111 static void undomgr_check_list_size (UndoMgr *um);
115 static GObjectClass *parent_class = NULL;
116 static guint undomgr_signals [LAST_SIGNAL] = { 0 };
118 GType
119 undomgr_get_type (void)
121 static GType undomgr_type = 0;
123 if (undomgr_type == 0)
125 static const GTypeInfo our_info =
127 sizeof (UndoMgrClass),
128 NULL, /* base_init */
129 NULL, /* base_finalize */
130 (GClassInitFunc) undomgr_class_init,
131 NULL, /* class_finalize */
132 NULL, /* class_data */
133 sizeof (UndoMgr),
134 0, /* n_preallocs */
135 (GInstanceInitFunc) undomgr_init
138 undomgr_type = g_type_register_static (G_TYPE_OBJECT,
139 "UndoMgr",
140 &our_info,
144 return undomgr_type;
147 static void
148 undomgr_class_init (UndoMgrClass *klass)
150 GObjectClass *object_class = G_OBJECT_CLASS (klass);
152 parent_class = g_type_class_peek_parent (klass);
154 object_class->finalize = undomgr_finalize;
156 klass->can_undo = NULL;
157 klass->can_redo = NULL;
159 undomgr_signals[CAN_UNDO] =
160 g_signal_new ("can_undo",
161 G_OBJECT_CLASS_TYPE (object_class),
162 G_SIGNAL_RUN_LAST,
163 G_STRUCT_OFFSET (UndoMgrClass, can_undo),
164 NULL, NULL,
165 g_cclosure_marshal_VOID__BOOLEAN,
166 G_TYPE_NONE,
168 G_TYPE_BOOLEAN);
170 undomgr_signals[CAN_REDO] =
171 g_signal_new ("can_redo",
172 G_OBJECT_CLASS_TYPE (object_class),
173 G_SIGNAL_RUN_LAST,
174 G_STRUCT_OFFSET (UndoMgrClass, can_redo),
175 NULL, NULL,
176 g_cclosure_marshal_VOID__BOOLEAN,
177 G_TYPE_NONE,
179 G_TYPE_BOOLEAN);
183 static void
184 undomgr_init (UndoMgr *um)
186 um->priv = g_new0 (UndoMgrPrivate, 1);
188 um->priv->actions = NULL;
189 um->priv->widgets = NULL;
190 um->priv->next_redo = 0;
192 um->priv->can_undo = FALSE;
193 um->priv->can_redo = FALSE;
195 um->priv->running_not_undoable_actions = 0;
197 um->priv->num_of_groups = 0;
200 static void
201 undomgr_finalize (GObject *object)
203 UndoMgr *um;
205 g_return_if_fail (object != NULL);
206 g_return_if_fail (IS_UNDOMGR (object));
208 um = UNDOMGR (object);
210 g_return_if_fail (um->priv != NULL);
212 if (um->priv->actions != NULL)
213 undomgr_free_action_list (um);
215 if (um->priv->actions != NULL)
216 undomgr_free_widget_list (um);
218 g_free (um->priv);
220 G_OBJECT_CLASS (parent_class)->finalize (object);
223 GObject*
224 undomgr_new ()
226 UndoMgr *um;
228 um = UNDOMGR (g_object_new (TYPE_UNDOMGR, NULL));
230 g_return_val_if_fail (um->priv != NULL, NULL);
232 return G_OBJECT(um);
235 void
236 undomgr_attach (UndoMgr *um, GtkWidget *widget) {
238 /* Return if the widget is already in the list */
239 if(g_list_find(um->priv->widgets, widget) != NULL)
240 return;
242 /* FIXME for all widget types! */
244 if(GTK_IS_TEXT_VIEW(widget)) {
245 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
247 g_signal_connect (G_OBJECT (buffer), "insert_text",
248 G_CALLBACK (undomgr_textbuffer_insert_text_handler),
249 um);
251 g_signal_connect (G_OBJECT (buffer), "delete_range",
252 G_CALLBACK (undomgr_textbuffer_delete_range_handler),
253 um);
255 g_signal_connect (G_OBJECT (buffer), "begin_user_action",
256 G_CALLBACK (undomgr_textbuffer_begin_user_action_handler),
257 um);
259 g_signal_connect (G_OBJECT (buffer), "end_user_action",
260 G_CALLBACK (undomgr_textbuffer_end_user_action_handler),
261 um);
263 um->priv->widgets = g_list_append(um->priv->widgets, widget);
264 /*g_print("Added widget textbuffer\n");*/
265 } else {
266 g_printerr("Unable to handle widget type in undomgr_attach.\n Widget: %s\n", G_OBJECT_TYPE_NAME(G_OBJECT(widget)));
270 void undomgr_detach(UndoMgr *um, GtkWidget *widget) {
271 g_return_if_fail (IS_UNDOMGR (um));
272 g_return_if_fail (um->priv != NULL);
273 g_return_if_fail (um->priv->widgets != NULL);
275 g_return_if_fail (widget != NULL);
276 g_return_if_fail (g_list_find(um->priv->widgets, widget));
278 undomgr_reset(um);
279 undomgr_free_widget(um, widget);
280 um->priv->widgets = g_list_remove(um->priv->widgets, widget);
283 void
284 undomgr_begin_not_undoable_action (UndoMgr *um)
286 g_return_if_fail (IS_UNDOMGR (um));
287 g_return_if_fail (um->priv != NULL);
289 ++um->priv->running_not_undoable_actions;
292 static void
293 undomgr_end_not_undoable_action_internal (UndoMgr *um)
295 g_return_if_fail (IS_UNDOMGR (um));
296 g_return_if_fail (um->priv != NULL);
298 g_return_if_fail (um->priv->running_not_undoable_actions > 0);
300 --um->priv->running_not_undoable_actions;
303 void
304 undomgr_end_not_undoable_action (UndoMgr *um)
306 g_return_if_fail (IS_UNDOMGR (um));
307 g_return_if_fail (um->priv != NULL);
309 undomgr_end_not_undoable_action_internal (um);
311 if (um->priv->running_not_undoable_actions == 0)
313 undomgr_free_action_list (um);
315 um->priv->next_redo = -1;
317 if (um->priv->can_undo)
319 um->priv->can_undo = FALSE;
320 g_signal_emit (G_OBJECT (um), undomgr_signals [CAN_UNDO], 0, FALSE);
323 if (um->priv->can_redo)
325 um->priv->can_redo = FALSE;
326 g_signal_emit (G_OBJECT (um), undomgr_signals [CAN_REDO], 0, FALSE);
332 gboolean
333 undomgr_can_undo (const UndoMgr *um)
335 g_return_val_if_fail (IS_UNDOMGR (um), FALSE);
336 g_return_val_if_fail (um->priv != NULL, FALSE);
338 return um->priv->can_undo;
341 gboolean
342 undomgr_can_redo (const UndoMgr *um)
344 g_return_val_if_fail (IS_UNDOMGR (um), FALSE);
345 g_return_val_if_fail (um->priv != NULL, FALSE);
347 return um->priv->can_redo;
350 void
351 undomgr_undo (UndoMgr *um)
353 UndoAction *undo_action;
355 g_return_if_fail (IS_UNDOMGR (um));
356 g_return_if_fail (um->priv != NULL);
357 g_return_if_fail (um->priv->can_undo);
359 undomgr_begin_not_undoable_action (um);
363 ++um->priv->next_redo;
365 undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo);
366 g_return_if_fail (undo_action != NULL);
368 /* FIXME for more widget types! */
369 if(GTK_IS_TEXT_BUFFER(undo_action->object)) {
370 GtkTextBuffer *buffer = GTK_TEXT_BUFFER(undo_action->object);
371 GtkTextIter start, end;
373 switch (undo_action->action_type)
375 case UNDO_ACTION_DELETE:
376 gtk_text_buffer_get_iter_at_offset(
377 buffer,
378 &start,
379 undo_action->action.delete.start);
381 gtk_text_buffer_place_cursor(buffer, &start);
383 gtk_text_buffer_insert(
384 buffer,
385 &start,
386 undo_action->action.delete.text,
387 (int)strlen(undo_action->action.delete.text));
389 break;
391 case UNDO_ACTION_INSERT:
392 gtk_text_buffer_get_iter_at_offset(
393 buffer,
394 &start,
395 undo_action->action.insert.pos);
396 gtk_text_buffer_get_iter_at_offset(
397 buffer,
398 &end,
399 undo_action->action.insert.pos +
400 undo_action->action.insert.chars);
402 gtk_text_buffer_delete(buffer, &start, &end);
404 gtk_text_buffer_place_cursor(buffer, &start);
406 break;
408 default:
409 g_warning ("This should not happen.");
410 return;
412 } else {
413 g_warning("Don't know how to handle action.");
415 } while (undo_action->order_in_group > 1);
417 undomgr_end_not_undoable_action_internal (um);
419 if (!um->priv->can_redo)
421 um->priv->can_redo = TRUE;
422 g_signal_emit (G_OBJECT (um), undomgr_signals [CAN_REDO], 0, TRUE);
425 if (um->priv->next_redo >= (gint)(g_list_length (um->priv->actions) - 1))
427 um->priv->can_undo = FALSE;
428 g_signal_emit (G_OBJECT (um), undomgr_signals [CAN_UNDO], 0, FALSE);
432 void
433 undomgr_redo (UndoMgr *um)
435 UndoAction *undo_action;
437 g_return_if_fail (IS_UNDOMGR (um));
438 g_return_if_fail (um->priv != NULL);
439 g_return_if_fail (um->priv->can_redo);
441 undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo);
442 g_return_if_fail (undo_action != NULL);
444 undomgr_begin_not_undoable_action (um);
448 /* FIXME for multiple widget types! */
449 if(GTK_IS_TEXT_BUFFER(undo_action->object)) {
450 GtkTextBuffer *buffer = GTK_TEXT_BUFFER(undo_action->object);
451 GtkTextIter start, end;
453 switch (undo_action->action_type)
455 case UNDO_ACTION_DELETE:
456 gtk_text_buffer_get_iter_at_offset(
457 buffer,
458 &start,
459 undo_action->action.delete.start);
460 gtk_text_buffer_get_iter_at_offset(
461 buffer,
462 &end,
463 undo_action->action.delete.end);
465 gtk_text_buffer_delete(buffer, &start, &end);
467 gtk_text_buffer_place_cursor(buffer, &start);
469 break;
471 case UNDO_ACTION_INSERT:
472 gtk_text_buffer_get_iter_at_offset(
473 buffer,
474 &start,
475 undo_action->action.insert.pos);
477 gtk_text_buffer_place_cursor(buffer, &start);
479 gtk_text_buffer_insert(buffer,
480 &start,
481 undo_action->action.insert.text,
482 undo_action->action.insert.length);
484 break;
486 default:
487 g_warning ("This should not happen.");
488 return;
490 } else {
491 g_warning("Redoing unknown widget type.");
493 --um->priv->next_redo;
495 if (um->priv->next_redo < 0)
496 undo_action = NULL;
497 else
498 undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo);
500 } while ((undo_action != NULL) && (undo_action->order_in_group > 1));
502 undomgr_end_not_undoable_action_internal (um);
504 if (um->priv->next_redo < 0)
506 um->priv->can_redo = FALSE;
507 g_signal_emit (G_OBJECT (um), undomgr_signals [CAN_REDO], 0, FALSE);
510 if (!um->priv->can_undo)
512 um->priv->can_undo = TRUE;
513 g_signal_emit (G_OBJECT (um), undomgr_signals [CAN_UNDO], 0, TRUE);
518 static void
519 undomgr_free_action_list (UndoMgr *um)
521 gint n, len;
523 g_return_if_fail (IS_UNDOMGR (um));
524 g_return_if_fail (um->priv != NULL);
526 if (um->priv->actions == NULL)
528 return;
530 len = g_list_length (um->priv->actions);
532 for (n = 0; n < len; n++)
534 UndoAction *undo_action =
535 (UndoAction *)(g_list_nth_data (um->priv->actions, n));
537 /* gedit_debug (DEBUG_UNDO, "Free action (type %s) %d/%d",
538 (undo_action->action_type == GEDIT_UNDO_ACTION_INSERT) ? "insert":
539 "delete", n, len); */
541 if (undo_action->action_type == UNDO_ACTION_INSERT)
542 g_free (undo_action->action.insert.text);
543 else if (undo_action->action_type == UNDO_ACTION_DELETE)
544 g_free (undo_action->action.delete.text);
545 else
546 g_return_if_fail (FALSE);
548 if (undo_action->order_in_group == 1)
549 --um->priv->num_of_groups;
551 g_free (undo_action);
554 g_list_free (um->priv->actions);
555 um->priv->actions = NULL;
558 static void
559 undomgr_free_widget(UndoMgr *um, GtkWidget *w)
561 /* FIXME for all widget types! */
562 if(GTK_IS_TEXT_VIEW(w)) {
563 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(w));
565 g_signal_handlers_disconnect_by_func (
566 G_OBJECT (buffer),
567 G_CALLBACK (undomgr_textbuffer_delete_range_handler),
568 um);
570 g_signal_handlers_disconnect_by_func (
571 G_OBJECT (buffer),
572 G_CALLBACK (undomgr_textbuffer_insert_text_handler),
573 um);
575 g_signal_handlers_disconnect_by_func (
576 G_OBJECT (buffer),
577 G_CALLBACK (undomgr_textbuffer_begin_user_action_handler),
578 um);
580 g_signal_handlers_disconnect_by_func (
581 G_OBJECT (buffer),
582 G_CALLBACK (undomgr_textbuffer_end_user_action_handler),
583 um);
584 } else {
585 g_printerr("Unable to handle widget type in undomgr_free_widget_list.\n Widget: %s\n", G_OBJECT_TYPE_NAME(G_OBJECT(w)));
589 static void
590 undomgr_free_widget_list (UndoMgr *um)
592 gint n, len;
594 g_return_if_fail (IS_UNDOMGR (um));
595 g_return_if_fail (um->priv != NULL);
596 g_return_if_fail (um->priv->widgets != NULL);
598 len = g_list_length (um->priv->actions);
599 for(n = 0; n < len; n++) {
600 GtkWidget *w = (GtkWidget *)(g_list_nth_data (um->priv->actions, n));
602 undomgr_free_widget(um, w);
605 g_list_free (um->priv->actions);
606 um->priv->actions = NULL;
609 static void
610 undomgr_textbuffer_insert_text_handler (GtkTextBuffer *buffer,
611 GtkTextIter *pos,
612 const gchar *text, gint length, UndoMgr *um)
614 UndoAction undo_action;
616 if (um->priv->running_not_undoable_actions > 0)
617 return;
619 g_return_if_fail (strlen (text) == (guint)length);
621 undo_action.action_type = UNDO_ACTION_INSERT;
623 undo_action.action.insert.pos = gtk_text_iter_get_offset (pos);
624 undo_action.action.insert.text = (gchar*) text;
625 undo_action.action.insert.length = length;
626 undo_action.action.insert.chars = g_utf8_strlen (text, length);
628 if ((undo_action.action.insert.chars > 1) || (g_utf8_get_char (text) == '\n'))
630 undo_action.mergeable = FALSE;
631 else
632 undo_action.mergeable = TRUE;
634 undomgr_add_textbuffer_action (um, undo_action, buffer);
637 static void
638 undomgr_textbuffer_delete_range_handler (GtkTextBuffer *buffer,
639 GtkTextIter *start,
640 GtkTextIter *end, UndoMgr *um)
642 UndoAction undo_action;
644 if (um->priv->running_not_undoable_actions > 0)
645 return;
647 undo_action.action_type = UNDO_ACTION_DELETE;
649 gtk_text_iter_order (start, end);
651 undo_action.action.delete.start = gtk_text_iter_get_offset (start);
652 undo_action.action.delete.end = gtk_text_iter_get_offset (end);
654 undo_action.action.delete.text = gtk_text_buffer_get_slice (
655 buffer,
656 start,
657 end,
658 TRUE);
660 if (((undo_action.action.delete.end - undo_action.action.delete.start) > 1) ||
661 (g_utf8_get_char (undo_action.action.delete.text ) == '\n'))
662 undo_action.mergeable = FALSE;
663 else
664 undo_action.mergeable = TRUE;
666 /* g_print ("START: %d\n", undo_action.action.delete.start);
667 g_print ("END: %d\n", undo_action.action.delete.end);
668 g_print ("TEXT: %s\n", undo_action.action.delete.text); */
670 undomgr_add_textbuffer_action (um, undo_action, buffer);
672 g_free (undo_action.action.delete.text);
675 static void
676 undomgr_textbuffer_begin_user_action_handler (GtkTextBuffer *buffer,
677 UndoMgr *um)
679 g_return_if_fail (IS_UNDOMGR (um));
680 g_return_if_fail (um->priv != NULL);
682 if (um->priv->running_not_undoable_actions > 0)
683 return;
685 um->priv->actions_in_current_group = 0;
688 static void
689 undomgr_textbuffer_end_user_action_handler (GtkTextBuffer *buffer,
690 UndoMgr *um)
692 if (um->priv->running_not_undoable_actions > 0)
693 return;
695 /* TODO: is it needed ? */
698 /* FIXME: change prototype to use UndoAction *undo_action : Paolo */
699 static void
700 undomgr_add_textbuffer_action (UndoMgr *um, UndoAction undo_action,
701 GtkTextBuffer *buffer)
703 UndoAction* action;
705 g_return_if_fail(GTK_IS_TEXT_BUFFER(buffer));
707 if (um->priv->next_redo >= 0)
709 undomgr_free_first_n_actions (um, um->priv->next_redo + 1);
712 um->priv->next_redo = -1;
714 if (!undomgr_merge_textbuffer_action (um, &undo_action, buffer))
716 action = g_new (UndoAction, 1);
717 *action = undo_action;
719 action->object = G_OBJECT(buffer);
721 if (action->action_type == UNDO_ACTION_INSERT)
722 action->action.insert.text = g_strdup (undo_action.action.insert.text);
723 else if (action->action_type == UNDO_ACTION_DELETE)
724 action->action.delete.text = g_strdup (undo_action.action.delete.text);
725 else
727 g_free (action);
728 g_return_if_fail (FALSE);
731 ++um->priv->actions_in_current_group;
732 action->order_in_group = um->priv->actions_in_current_group;
734 if (action->order_in_group == 1)
735 ++um->priv->num_of_groups;
737 um->priv->actions = g_list_prepend (um->priv->actions, action);
740 undomgr_check_list_size (um);
742 if (!um->priv->can_undo)
744 um->priv->can_undo = TRUE;
745 g_signal_emit (G_OBJECT (um), undomgr_signals [CAN_UNDO], 0, TRUE);
748 if (um->priv->can_redo)
750 um->priv->can_redo = FALSE;
751 g_signal_emit (G_OBJECT (um), undomgr_signals [CAN_REDO], 0, FALSE);
755 static void
756 undomgr_free_first_n_actions (UndoMgr *um, gint n)
758 gint i;
760 g_return_if_fail (IS_UNDOMGR (um));
761 g_return_if_fail (um->priv != NULL);
763 if (um->priv->actions == NULL)
764 return;
766 for (i = 0; i < n; i++)
768 UndoAction *undo_action =
769 (UndoAction *)(g_list_first (um->priv->actions)->data);
771 if (undo_action->action_type == UNDO_ACTION_INSERT)
772 g_free (undo_action->action.insert.text);
773 else if (undo_action->action_type == UNDO_ACTION_DELETE)
774 g_free (undo_action->action.delete.text);
775 else
776 g_return_if_fail (FALSE);
778 if (undo_action->order_in_group == 1)
779 --um->priv->num_of_groups;
781 g_free (undo_action);
783 um->priv->actions = g_list_delete_link (um->priv->actions, um->priv->actions);
785 if (um->priv->actions == NULL)
786 return;
790 static void
791 undomgr_check_list_size (UndoMgr *um)
793 gint undo_levels;
795 g_return_if_fail (IS_UNDOMGR (um));
796 g_return_if_fail (um->priv != NULL);
798 /* FIXME: should this be a preference? */
799 undo_levels = 25;
801 if (undo_levels < 1)
802 return;
804 if (um->priv->num_of_groups > undo_levels)
806 UndoAction *undo_action;
807 GList* last;
809 last = g_list_last (um->priv->actions);
810 undo_action = (UndoAction*) last->data;
814 if (undo_action->action_type == UNDO_ACTION_INSERT)
815 g_free (undo_action->action.insert.text);
816 else if (undo_action->action_type == UNDO_ACTION_DELETE)
817 g_free (undo_action->action.delete.text);
818 else
819 g_return_if_fail (FALSE);
821 if (undo_action->order_in_group == 1)
822 --um->priv->num_of_groups;
824 g_free (undo_action);
826 um->priv->actions = g_list_delete_link (um->priv->actions, last);
827 g_return_if_fail (um->priv->actions != NULL);
829 last = g_list_last (um->priv->actions);
830 undo_action = (UndoAction*) last->data;
832 } while ((undo_action->order_in_group > 1) ||
833 (um->priv->num_of_groups > undo_levels));
838 * undomgr_merge_textbuffer_action:
839 * @um: an #UndoMgr
840 * @undo_action:
841 * @buffer:
843 * This function tries to merge the undo action at the top of
844 * the stack with a new undo action. So when we undo for example
845 * typing, we can undo the whole word and not each letter by itself
847 * Return Value: TRUE is merge was sucessful, FALSE otherwise
849 static gboolean
850 undomgr_merge_textbuffer_action (UndoMgr *um, UndoAction *undo_action,
851 GtkTextBuffer *buffer)
853 UndoAction *last_action;
855 g_return_val_if_fail (GTK_IS_TEXT_BUFFER(buffer), FALSE);
856 g_return_val_if_fail (IS_UNDOMGR (um), FALSE);
857 g_return_val_if_fail (um->priv != NULL, FALSE);
859 if (um->priv->actions == NULL)
860 return FALSE;
862 last_action = (UndoAction*) g_list_nth_data (um->priv->actions, 0);
864 if (!last_action->mergeable)
865 return FALSE;
867 if ((!undo_action->mergeable) ||
868 (undo_action->action_type != last_action->action_type))
870 last_action->mergeable = FALSE;
871 return FALSE;
874 if (undo_action->action_type == UNDO_ACTION_DELETE)
876 if ((last_action->action.delete.start != undo_action->action.delete.start) &&
877 (last_action->action.delete.start != undo_action->action.delete.end))
879 last_action->mergeable = FALSE;
880 return FALSE;
883 if (last_action->action.delete.start == undo_action->action.delete.start)
885 gchar *str;
887 #define L (last_action->action.delete.end - last_action->action.delete.start - 1)
888 #define g_utf8_get_char_at(p,i) g_utf8_get_char(g_utf8_offset_to_pointer((p),(i)))
890 /* Deleted with the delete key */
891 if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') &&
892 (g_utf8_get_char (undo_action->action.delete.text) != '\t') &&
893 ((g_utf8_get_char_at (last_action->action.delete.text, L) == ' ') ||
894 (g_utf8_get_char_at (last_action->action.delete.text, L) == '\t')))
896 last_action->mergeable = FALSE;
897 return FALSE;
900 str = g_strdup_printf ("%s%s",
901 last_action->action.delete.text,
902 undo_action->action.delete.text);
904 g_free (last_action->action.delete.text);
905 last_action->action.delete.end +=
906 (undo_action->action.delete.end -
907 undo_action->action.delete.start);
908 last_action->action.delete.text = str;
910 else
912 gchar *str;
914 /* Deleted with the backspace key */
915 if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') &&
916 (g_utf8_get_char (undo_action->action.delete.text) != '\t') &&
917 ((g_utf8_get_char (last_action->action.delete.text) == ' ') ||
918 (g_utf8_get_char (last_action->action.delete.text) == '\t')))
920 last_action->mergeable = FALSE;
921 return FALSE;
924 str = g_strdup_printf ("%s%s",
925 undo_action->action.delete.text,
926 last_action->action.delete.text);
928 g_free (last_action->action.delete.text);
929 last_action->action.delete.start = undo_action->action.delete.start;
930 last_action->action.delete.text = str;
933 return TRUE;
935 else if (undo_action->action_type == UNDO_ACTION_INSERT)
937 gchar* str;
939 #define I (last_action->action.insert.chars - 1)
941 if ((undo_action->action.insert.pos !=
942 (last_action->action.insert.pos + last_action->action.insert.chars)) ||
943 ((g_utf8_get_char (undo_action->action.insert.text) != ' ') &&
944 (g_utf8_get_char (undo_action->action.insert.text) != '\t') &&
945 ((g_utf8_get_char_at (last_action->action.insert.text, I) == ' ') ||
946 (g_utf8_get_char_at (last_action->action.insert.text, I) == '\t')))
949 last_action->mergeable = FALSE;
950 return FALSE;
953 str = g_strdup_printf ("%s%s", last_action->action.insert.text,
954 undo_action->action.insert.text);
956 g_free (last_action->action.insert.text);
957 last_action->action.insert.length += undo_action->action.insert.length;
958 last_action->action.insert.text = str;
959 last_action->action.insert.chars += undo_action->action.insert.chars;
961 return TRUE;
962 } else {
963 g_warning ("Unknown action inside undo merge encountered");
964 return FALSE;
968 void
969 undomgr_reset (UndoMgr *um) {
970 undomgr_free_action_list(um);
972 um->priv->can_undo = FALSE;
973 g_signal_emit(G_OBJECT(um), undomgr_signals[CAN_UNDO], 0, FALSE);
975 um->priv->can_redo = FALSE;
976 g_signal_emit(G_OBJECT(um), undomgr_signals[CAN_REDO], 0, FALSE);