Replace strcmp() with purple_strequal()
[pidgin-git.git] / pidgin / gtksourceundomanager.c
blob2c2a63e7a64cc123ffca4345208a9bfa23e157e9
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * gtksourceundomanager.c
4 * This file is part of GtkSourceView
6 * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
7 * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
8 * Copyright (C) 2002-2005 Paolo Maggi
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02111-1301, USA.
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
30 #include <glib.h>
31 #include <stdlib.h>
32 #include <string.h>
34 #include "gtksourceundomanager.h"
35 #include "gtksourceview-marshal.h"
38 #define DEFAULT_MAX_UNDO_LEVELS 25
41 typedef struct _GtkSourceUndoAction GtkSourceUndoAction;
42 typedef struct _GtkSourceUndoInsertAction GtkSourceUndoInsertAction;
43 typedef struct _GtkSourceUndoDeleteAction GtkSourceUndoDeleteAction;
44 typedef struct _GtkSourceUndoInsertAnchorAction GtkSourceUndoInsertAnchorAction;
46 typedef enum {
47 GTK_SOURCE_UNDO_ACTION_INSERT,
48 GTK_SOURCE_UNDO_ACTION_DELETE,
49 GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR,
50 } GtkSourceUndoActionType;
53 * We use offsets instead of GtkTextIters because the last ones
54 * require to much memory in this context without giving us any advantage.
57 struct _GtkSourceUndoInsertAction
59 gint pos;
60 gchar *text;
61 gint length;
62 gint chars;
65 struct _GtkSourceUndoDeleteAction
67 gint start;
68 gint end;
69 gchar *text;
70 gboolean forward;
73 struct _GtkSourceUndoInsertAnchorAction
75 gint pos;
76 GtkTextChildAnchor *anchor;
79 struct _GtkSourceUndoAction
81 GtkSourceUndoActionType action_type;
83 union {
84 GtkSourceUndoInsertAction insert;
85 GtkSourceUndoDeleteAction delete;
86 GtkSourceUndoInsertAnchorAction insert_anchor;
87 } action;
89 gint order_in_group;
91 /* It is TRUE whether the action can be merged with the following action. */
92 guint mergeable : 1;
94 /* It is TRUE whether the action is marked as "modified".
95 * An action is marked as "modified" if it changed the
96 * state of the buffer from "not modified" to "modified". Only the first
97 * action of a group can be marked as modified.
98 * There can be a single action marked as "modified" in the actions list.
100 guint modified : 1;
103 struct _GtkSourceUndoManagerPrivate
105 GtkTextBuffer *document;
107 GList* actions;
108 gint next_redo;
110 gint actions_in_current_group;
112 gint running_not_undoable_actions;
114 gint num_of_groups;
116 gint max_undo_levels;
118 guint can_undo : 1;
119 guint can_redo : 1;
121 /* It is TRUE whether, while undoing an action of the current group (with order_in_group > 1),
122 * the state of the buffer changed from "not modified" to "modified".
124 guint modified_undoing_group : 1;
126 /* Pointer to the action (in the action list) marked as "modified".
127 * It is NULL when no action is marked as "modified". */
128 GtkSourceUndoAction *modified_action;
131 enum {
132 CAN_UNDO,
133 CAN_REDO,
134 LAST_SIGNAL
137 static void gtk_source_undo_manager_class_init (GtkSourceUndoManagerClass *klass);
138 static void gtk_source_undo_manager_init (GtkSourceUndoManager *um);
139 static void gtk_source_undo_manager_finalize (GObject *object);
141 static void gtk_source_undo_manager_insert_text_handler (GtkTextBuffer *buffer,
142 GtkTextIter *pos,
143 const gchar *text,
144 gint length,
145 GtkSourceUndoManager *um);
146 static void gtk_source_undo_manager_insert_anchor_handler (GtkTextBuffer *buffer,
147 GtkTextIter *pos,
148 GtkTextChildAnchor *anchor,
149 GtkSourceUndoManager *um);
150 static void gtk_source_undo_manager_delete_range_handler (GtkTextBuffer *buffer,
151 GtkTextIter *start,
152 GtkTextIter *end,
153 GtkSourceUndoManager *um);
154 static void gtk_source_undo_manager_begin_user_action_handler (GtkTextBuffer *buffer,
155 GtkSourceUndoManager *um);
156 static void gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer *buffer,
157 GtkSourceUndoManager *um);
159 static void gtk_source_undo_manager_free_action_list (GtkSourceUndoManager *um);
161 static void gtk_source_undo_manager_add_action (GtkSourceUndoManager *um,
162 const GtkSourceUndoAction *undo_action);
163 static void gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager *um,
164 gint n);
165 static void gtk_source_undo_manager_check_list_size (GtkSourceUndoManager *um);
167 static gboolean gtk_source_undo_manager_merge_action (GtkSourceUndoManager *um,
168 const GtkSourceUndoAction *undo_action);
170 static GObjectClass *parent_class = NULL;
171 static guint undo_manager_signals [LAST_SIGNAL] = { 0 };
173 GType
174 gtk_source_undo_manager_get_type (void)
176 static GType undo_manager_type = 0;
178 if (undo_manager_type == 0)
180 static const GTypeInfo our_info =
182 sizeof (GtkSourceUndoManagerClass),
183 NULL, /* base_init */
184 NULL, /* base_finalize */
185 (GClassInitFunc) gtk_source_undo_manager_class_init,
186 NULL, /* class_finalize */
187 NULL, /* class_data */
188 sizeof (GtkSourceUndoManager),
189 0, /* n_preallocs */
190 (GInstanceInitFunc) gtk_source_undo_manager_init,
191 NULL /* value_table */
194 undo_manager_type = g_type_register_static (G_TYPE_OBJECT,
195 "GtkSourceUndoManager",
196 &our_info,
200 return undo_manager_type;
203 static void
204 gtk_source_undo_manager_class_init (GtkSourceUndoManagerClass *klass)
206 GObjectClass *object_class = G_OBJECT_CLASS (klass);
208 parent_class = g_type_class_peek_parent (klass);
210 object_class->finalize = gtk_source_undo_manager_finalize;
212 klass->can_undo = NULL;
213 klass->can_redo = NULL;
215 undo_manager_signals[CAN_UNDO] =
216 g_signal_new ("can_undo",
217 G_OBJECT_CLASS_TYPE (object_class),
218 G_SIGNAL_RUN_LAST,
219 G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_undo),
220 NULL, NULL,
221 gtksourceview_marshal_VOID__BOOLEAN,
222 G_TYPE_NONE,
224 G_TYPE_BOOLEAN);
226 undo_manager_signals[CAN_REDO] =
227 g_signal_new ("can_redo",
228 G_OBJECT_CLASS_TYPE (object_class),
229 G_SIGNAL_RUN_LAST,
230 G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_redo),
231 NULL, NULL,
232 gtksourceview_marshal_VOID__BOOLEAN,
233 G_TYPE_NONE,
235 G_TYPE_BOOLEAN);
238 static void
239 gtk_source_undo_manager_init (GtkSourceUndoManager *um)
241 um->priv = g_new0 (GtkSourceUndoManagerPrivate, 1);
243 um->priv->actions = NULL;
244 um->priv->next_redo = 0;
246 um->priv->can_undo = FALSE;
247 um->priv->can_redo = FALSE;
249 um->priv->running_not_undoable_actions = 0;
251 um->priv->num_of_groups = 0;
253 um->priv->max_undo_levels = DEFAULT_MAX_UNDO_LEVELS;
255 um->priv->modified_action = NULL;
257 um->priv->modified_undoing_group = FALSE;
260 static void
261 gtk_source_undo_manager_finalize (GObject *object)
263 GtkSourceUndoManager *um;
265 g_return_if_fail (object != NULL);
266 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (object));
268 um = GTK_SOURCE_UNDO_MANAGER (object);
270 g_return_if_fail (um->priv != NULL);
272 if (um->priv->actions != NULL)
274 gtk_source_undo_manager_free_action_list (um);
277 g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
278 G_CALLBACK (gtk_source_undo_manager_delete_range_handler),
279 um);
281 g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
282 G_CALLBACK (gtk_source_undo_manager_insert_text_handler),
283 um);
285 g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
286 G_CALLBACK (gtk_source_undo_manager_insert_anchor_handler),
287 um);
289 g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
290 G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler),
291 um);
293 g_free (um->priv);
295 G_OBJECT_CLASS (parent_class)->finalize (object);
298 GtkSourceUndoManager*
299 gtk_source_undo_manager_new (GtkTextBuffer* buffer)
301 GtkSourceUndoManager *um;
303 um = GTK_SOURCE_UNDO_MANAGER (g_object_new (GTK_SOURCE_TYPE_UNDO_MANAGER, NULL));
305 g_return_val_if_fail (um->priv != NULL, NULL);
306 um->priv->document = buffer;
308 g_signal_connect (G_OBJECT (buffer), "insert_text",
309 G_CALLBACK (gtk_source_undo_manager_insert_text_handler),
310 um);
312 g_signal_connect (G_OBJECT (buffer), "insert_child_anchor",
313 G_CALLBACK (gtk_source_undo_manager_insert_anchor_handler),
314 um);
316 g_signal_connect (G_OBJECT (buffer), "delete_range",
317 G_CALLBACK (gtk_source_undo_manager_delete_range_handler),
318 um);
320 g_signal_connect (G_OBJECT (buffer), "begin_user_action",
321 G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler),
322 um);
324 g_signal_connect (G_OBJECT (buffer), "modified_changed",
325 G_CALLBACK (gtk_source_undo_manager_modified_changed_handler),
326 um);
327 return um;
330 void
331 gtk_source_undo_manager_begin_not_undoable_action (GtkSourceUndoManager *um)
333 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
334 g_return_if_fail (um->priv != NULL);
336 ++um->priv->running_not_undoable_actions;
339 static void
340 gtk_source_undo_manager_end_not_undoable_action_internal (GtkSourceUndoManager *um)
342 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
343 g_return_if_fail (um->priv != NULL);
345 g_return_if_fail (um->priv->running_not_undoable_actions > 0);
347 --um->priv->running_not_undoable_actions;
350 void
351 gtk_source_undo_manager_end_not_undoable_action (GtkSourceUndoManager *um)
353 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
354 g_return_if_fail (um->priv != NULL);
356 gtk_source_undo_manager_end_not_undoable_action_internal (um);
358 if (um->priv->running_not_undoable_actions == 0)
360 gtk_source_undo_manager_free_action_list (um);
362 um->priv->next_redo = -1;
364 if (um->priv->can_undo)
366 um->priv->can_undo = FALSE;
367 g_signal_emit (G_OBJECT (um),
368 undo_manager_signals [CAN_UNDO],
370 FALSE);
373 if (um->priv->can_redo)
375 um->priv->can_redo = FALSE;
376 g_signal_emit (G_OBJECT (um),
377 undo_manager_signals [CAN_REDO],
379 FALSE);
384 gboolean
385 gtk_source_undo_manager_can_undo (const GtkSourceUndoManager *um)
387 g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE);
388 g_return_val_if_fail (um->priv != NULL, FALSE);
390 return um->priv->can_undo;
393 gboolean
394 gtk_source_undo_manager_can_redo (const GtkSourceUndoManager *um)
396 g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE);
397 g_return_val_if_fail (um->priv != NULL, FALSE);
399 return um->priv->can_redo;
402 static void
403 set_cursor (GtkTextBuffer *buffer, gint cursor)
405 GtkTextIter iter;
407 /* Place the cursor at the requested position */
408 gtk_text_buffer_get_iter_at_offset (buffer, &iter, cursor);
409 gtk_text_buffer_place_cursor (buffer, &iter);
412 static void
413 insert_text (GtkTextBuffer *buffer, gint pos, const gchar *text, gint len)
415 GtkTextIter iter;
417 gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos);
418 gtk_text_buffer_insert (buffer, &iter, text, len);
421 static void
422 insert_anchor (GtkTextBuffer *buffer, gint pos, GtkTextChildAnchor *anchor)
424 GtkTextIter iter;
426 gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos);
427 gtk_text_buffer_insert_child_anchor (buffer, &iter, anchor);
430 static void
431 delete_text (GtkTextBuffer *buffer, gint start, gint end)
433 GtkTextIter start_iter;
434 GtkTextIter end_iter;
436 gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
438 if (end < 0)
439 gtk_text_buffer_get_end_iter (buffer, &end_iter);
440 else
441 gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
443 gtk_text_buffer_delete (buffer, &start_iter, &end_iter);
446 static gchar*
447 get_chars (GtkTextBuffer *buffer, gint start, gint end)
449 GtkTextIter start_iter;
450 GtkTextIter end_iter;
452 gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
454 if (end < 0)
455 gtk_text_buffer_get_end_iter (buffer, &end_iter);
456 else
457 gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
459 return gtk_text_buffer_get_slice (buffer, &start_iter, &end_iter, TRUE);
462 void
463 gtk_source_undo_manager_undo (GtkSourceUndoManager *um)
465 GtkSourceUndoAction *undo_action;
466 gboolean modified = FALSE;
468 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
469 g_return_if_fail (um->priv != NULL);
470 g_return_if_fail (um->priv->can_undo);
472 um->priv->modified_undoing_group = FALSE;
474 gtk_source_undo_manager_begin_not_undoable_action (um);
478 undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo + 1);
479 g_return_if_fail (undo_action != NULL);
481 /* undo_action->modified can be TRUE only if undo_action->order_in_group <= 1 */
482 g_return_if_fail ((undo_action->order_in_group <= 1) ||
483 ((undo_action->order_in_group > 1) && !undo_action->modified));
485 if (undo_action->order_in_group <= 1)
487 /* Set modified to TRUE only if the buffer did not change its state from
488 * "not modified" to "modified" undoing an action (with order_in_group > 1)
489 * in current group. */
490 modified = (undo_action->modified && !um->priv->modified_undoing_group);
493 switch (undo_action->action_type)
495 case GTK_SOURCE_UNDO_ACTION_DELETE:
496 insert_text (
497 um->priv->document,
498 undo_action->action.delete.start,
499 undo_action->action.delete.text,
500 strlen (undo_action->action.delete.text));
502 if (undo_action->action.delete.forward)
503 set_cursor (
504 um->priv->document,
505 undo_action->action.delete.start);
506 else
507 set_cursor (
508 um->priv->document,
509 undo_action->action.delete.end);
511 break;
513 case GTK_SOURCE_UNDO_ACTION_INSERT:
514 delete_text (
515 um->priv->document,
516 undo_action->action.insert.pos,
517 undo_action->action.insert.pos +
518 undo_action->action.insert.chars);
520 set_cursor (
521 um->priv->document,
522 undo_action->action.insert.pos);
523 break;
525 case GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR:
526 delete_text (
527 um->priv->document,
528 undo_action->action.insert_anchor.pos,
529 undo_action->action.insert_anchor.pos + 1);
530 undo_action->action.insert_anchor.anchor->segment = NULL; /* XXX: This may be a bug in GTK+ */
531 break;
532 default:
533 /* Unknown action type. */
534 g_return_if_reached ();
537 ++um->priv->next_redo;
539 } while (undo_action->order_in_group > 1);
541 if (modified)
543 --um->priv->next_redo;
544 gtk_text_buffer_set_modified (um->priv->document, FALSE);
545 ++um->priv->next_redo;
548 gtk_source_undo_manager_end_not_undoable_action_internal (um);
550 um->priv->modified_undoing_group = FALSE;
552 if (!um->priv->can_redo)
554 um->priv->can_redo = TRUE;
555 g_signal_emit (G_OBJECT (um),
556 undo_manager_signals [CAN_REDO],
558 TRUE);
561 if (um->priv->next_redo >= (gint)(g_list_length (um->priv->actions) - 1))
563 um->priv->can_undo = FALSE;
564 g_signal_emit (G_OBJECT (um),
565 undo_manager_signals [CAN_UNDO],
567 FALSE);
571 void
572 gtk_source_undo_manager_redo (GtkSourceUndoManager *um)
574 GtkSourceUndoAction *undo_action;
575 gboolean modified = FALSE;
577 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
578 g_return_if_fail (um->priv != NULL);
579 g_return_if_fail (um->priv->can_redo);
581 undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo);
582 g_return_if_fail (undo_action != NULL);
584 gtk_source_undo_manager_begin_not_undoable_action (um);
588 if (undo_action->modified)
590 g_return_if_fail (undo_action->order_in_group <= 1);
591 modified = TRUE;
594 --um->priv->next_redo;
596 switch (undo_action->action_type)
598 case GTK_SOURCE_UNDO_ACTION_DELETE:
599 delete_text (
600 um->priv->document,
601 undo_action->action.delete.start,
602 undo_action->action.delete.end);
604 set_cursor (
605 um->priv->document,
606 undo_action->action.delete.start);
608 break;
610 case GTK_SOURCE_UNDO_ACTION_INSERT:
611 set_cursor (
612 um->priv->document,
613 undo_action->action.insert.pos);
615 insert_text (
616 um->priv->document,
617 undo_action->action.insert.pos,
618 undo_action->action.insert.text,
619 undo_action->action.insert.length);
621 break;
623 case GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR:
624 set_cursor (
625 um->priv->document,
626 undo_action->action.insert_anchor.pos);
628 insert_anchor (
629 um->priv->document,
630 undo_action->action.insert_anchor.pos,
631 undo_action->action.insert_anchor.anchor);
632 break;
634 default:
635 /* Unknown action type */
636 ++um->priv->next_redo;
637 g_return_if_reached ();
640 if (um->priv->next_redo < 0)
641 undo_action = NULL;
642 else
643 undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo);
645 } while ((undo_action != NULL) && (undo_action->order_in_group > 1));
647 if (modified)
649 ++um->priv->next_redo;
650 gtk_text_buffer_set_modified (um->priv->document, FALSE);
651 --um->priv->next_redo;
654 gtk_source_undo_manager_end_not_undoable_action_internal (um);
656 if (um->priv->next_redo < 0)
658 um->priv->can_redo = FALSE;
659 g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE);
662 if (!um->priv->can_undo)
664 um->priv->can_undo = TRUE;
665 g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, TRUE);
669 static void
670 gtk_source_undo_action_free (GtkSourceUndoAction *action)
672 if (action == NULL)
673 return;
675 if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
676 g_free (action->action.insert.text);
677 else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
678 g_free (action->action.delete.text);
679 else if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR)
680 g_object_unref(action->action.insert_anchor.anchor);
681 else {
682 g_free (action);
683 g_return_if_reached ();
686 g_free (action);
689 static void
690 gtk_source_undo_manager_free_action_list (GtkSourceUndoManager *um)
692 GList *l;
694 l = um->priv->actions;
696 while (l != NULL)
698 GtkSourceUndoAction *action = l->data;
700 if (action->order_in_group == 1)
701 --um->priv->num_of_groups;
703 if (action->modified)
704 um->priv->modified_action = NULL;
706 gtk_source_undo_action_free (action);
708 l = g_list_next (l);
711 g_list_free (um->priv->actions);
712 um->priv->actions = NULL;
715 static void
716 gtk_source_undo_manager_insert_text_handler (GtkTextBuffer *buffer,
717 GtkTextIter *pos,
718 const gchar *text,
719 gint length,
720 GtkSourceUndoManager *um)
722 GtkSourceUndoAction undo_action;
724 if (um->priv->running_not_undoable_actions > 0)
725 return;
727 undo_action.action_type = GTK_SOURCE_UNDO_ACTION_INSERT;
729 undo_action.action.insert.pos = gtk_text_iter_get_offset (pos);
730 undo_action.action.insert.text = (gchar*) text;
731 undo_action.action.insert.length = length;
732 undo_action.action.insert.chars = g_utf8_strlen (text, length);
734 if ((undo_action.action.insert.chars > 1) || (g_utf8_get_char (text) == '\n'))
736 undo_action.mergeable = FALSE;
737 else
738 undo_action.mergeable = TRUE;
740 undo_action.modified = FALSE;
742 gtk_source_undo_manager_add_action (um, &undo_action);
745 static void gtk_source_undo_manager_insert_anchor_handler (GtkTextBuffer *buffer,
746 GtkTextIter *pos,
747 GtkTextChildAnchor *anchor,
748 GtkSourceUndoManager *um)
750 GtkSourceUndoAction undo_action;
752 if (um->priv->running_not_undoable_actions > 0)
753 return;
755 undo_action.action_type = GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR;
757 undo_action.action.insert_anchor.pos = gtk_text_iter_get_offset (pos);
758 undo_action.action.insert_anchor.anchor = g_object_ref (anchor);
760 undo_action.mergeable = FALSE;
761 undo_action.modified = FALSE;
763 gtk_source_undo_manager_add_action (um, &undo_action);
766 static void
767 gtk_source_undo_manager_delete_range_handler (GtkTextBuffer *buffer,
768 GtkTextIter *start,
769 GtkTextIter *end,
770 GtkSourceUndoManager *um)
772 GtkSourceUndoAction undo_action;
773 GtkTextIter insert_iter;
775 if (um->priv->running_not_undoable_actions > 0)
776 return;
778 undo_action.action_type = GTK_SOURCE_UNDO_ACTION_DELETE;
780 gtk_text_iter_order (start, end);
782 undo_action.action.delete.start = gtk_text_iter_get_offset (start);
783 undo_action.action.delete.end = gtk_text_iter_get_offset (end);
785 undo_action.action.delete.text = get_chars (
786 buffer,
787 undo_action.action.delete.start,
788 undo_action.action.delete.end);
790 /* figure out if the user used the Delete or the Backspace key */
791 gtk_text_buffer_get_iter_at_mark (buffer, &insert_iter,
792 gtk_text_buffer_get_insert (buffer));
793 if (gtk_text_iter_get_offset (&insert_iter) <= undo_action.action.delete.start)
794 undo_action.action.delete.forward = TRUE;
795 else
796 undo_action.action.delete.forward = FALSE;
798 if (((undo_action.action.delete.end - undo_action.action.delete.start) > 1) ||
799 (g_utf8_get_char (undo_action.action.delete.text ) == '\n'))
800 undo_action.mergeable = FALSE;
801 else
802 undo_action.mergeable = TRUE;
804 undo_action.modified = FALSE;
806 gtk_source_undo_manager_add_action (um, &undo_action);
808 g_free (undo_action.action.delete.text);
812 static void
813 gtk_source_undo_manager_begin_user_action_handler (GtkTextBuffer *buffer, GtkSourceUndoManager *um)
815 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
816 g_return_if_fail (um->priv != NULL);
818 if (um->priv->running_not_undoable_actions > 0)
819 return;
821 um->priv->actions_in_current_group = 0;
824 static void
825 gtk_source_undo_manager_add_action (GtkSourceUndoManager *um,
826 const GtkSourceUndoAction *undo_action)
828 GtkSourceUndoAction* action;
830 if (um->priv->next_redo >= 0)
832 gtk_source_undo_manager_free_first_n_actions (um, um->priv->next_redo + 1);
835 um->priv->next_redo = -1;
837 if (!gtk_source_undo_manager_merge_action (um, undo_action))
839 action = g_new (GtkSourceUndoAction, 1);
840 *action = *undo_action;
842 if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
843 action->action.insert.text = g_strndup (undo_action->action.insert.text, undo_action->action.insert.length);
844 else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
845 action->action.delete.text = g_strdup (undo_action->action.delete.text);
846 else if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR)
848 /* Nothing needs to be done */
850 else
852 g_free (action);
853 g_return_if_reached ();
856 ++um->priv->actions_in_current_group;
857 action->order_in_group = um->priv->actions_in_current_group;
859 if (action->order_in_group == 1)
860 ++um->priv->num_of_groups;
862 um->priv->actions = g_list_prepend (um->priv->actions, action);
865 gtk_source_undo_manager_check_list_size (um);
867 if (!um->priv->can_undo)
869 um->priv->can_undo = TRUE;
870 g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, TRUE);
873 if (um->priv->can_redo)
875 um->priv->can_redo = FALSE;
876 g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE);
880 static void
881 gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager *um,
882 gint n)
884 gint i;
886 if (um->priv->actions == NULL)
887 return;
889 for (i = 0; i < n; i++)
891 GtkSourceUndoAction *action = g_list_first (um->priv->actions)->data;
893 if (action->order_in_group == 1)
894 --um->priv->num_of_groups;
896 if (action->modified)
897 um->priv->modified_action = NULL;
899 gtk_source_undo_action_free (action);
901 um->priv->actions = g_list_delete_link (um->priv->actions,
902 um->priv->actions);
904 if (um->priv->actions == NULL)
905 return;
909 static void
910 gtk_source_undo_manager_check_list_size (GtkSourceUndoManager *um)
912 gint undo_levels;
914 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
915 g_return_if_fail (um->priv != NULL);
917 undo_levels = gtk_source_undo_manager_get_max_undo_levels (um);
919 if (undo_levels < 1)
920 return;
922 if (um->priv->num_of_groups > undo_levels)
924 GtkSourceUndoAction *undo_action;
925 GList *last;
927 last = g_list_last (um->priv->actions);
928 undo_action = (GtkSourceUndoAction*) last->data;
932 GList *tmp;
934 if (undo_action->order_in_group == 1)
935 --um->priv->num_of_groups;
937 if (undo_action->modified)
938 um->priv->modified_action = NULL;
940 gtk_source_undo_action_free (undo_action);
942 tmp = g_list_previous (last);
943 um->priv->actions = g_list_delete_link (um->priv->actions, last);
944 last = tmp;
945 g_return_if_fail (last != NULL);
947 undo_action = (GtkSourceUndoAction*) last->data;
949 } while ((undo_action->order_in_group > 1) ||
950 (um->priv->num_of_groups > undo_levels));
955 * gtk_source_undo_manager_merge_action:
956 * @um: a #GtkSourceUndoManager.
957 * @undo_action: a #GtkSourceUndoAction.
959 * This function tries to merge the undo action at the top of
960 * the stack with a new undo action. So when we undo for example
961 * typing, we can undo the whole word and not each letter by itself.
963 * Return Value: %TRUE is merge was successful, %FALSE otherwise.
965 static gboolean
966 gtk_source_undo_manager_merge_action (GtkSourceUndoManager *um,
967 const GtkSourceUndoAction *undo_action)
969 GtkSourceUndoAction *last_action;
971 g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE);
972 g_return_val_if_fail (um->priv != NULL, FALSE);
974 if (um->priv->actions == NULL)
975 return FALSE;
977 last_action = (GtkSourceUndoAction*) g_list_nth_data (um->priv->actions, 0);
979 if (!last_action->mergeable)
980 return FALSE;
982 if ((!undo_action->mergeable) ||
983 (undo_action->action_type != last_action->action_type))
985 last_action->mergeable = FALSE;
986 return FALSE;
989 if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
991 const GtkSourceUndoDeleteAction *last_del, *undo_del;
993 last_del = &last_action->action.delete;
994 undo_del = &undo_action->action.delete;
996 if (last_del->forward != undo_del->forward ||
997 (last_del->start != undo_del->start && last_del->start != undo_del->end))
999 last_action->mergeable = FALSE;
1000 return FALSE;
1003 if (last_action->action.delete.start == undo_action->action.delete.start)
1005 gchar *str;
1007 #define L (last_action->action.delete.end - last_action->action.delete.start - 1)
1008 #define g_utf8_get_char_at(p,i) g_utf8_get_char(g_utf8_offset_to_pointer((p),(i)))
1010 /* Deleted with the delete key */
1011 if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') &&
1012 (g_utf8_get_char (undo_action->action.delete.text) != '\t') &&
1013 ((g_utf8_get_char_at (last_action->action.delete.text, L) == ' ') ||
1014 (g_utf8_get_char_at (last_action->action.delete.text, L) == '\t')))
1016 last_action->mergeable = FALSE;
1017 return FALSE;
1020 str = g_strdup_printf ("%s%s", last_action->action.delete.text,
1021 undo_action->action.delete.text);
1023 g_free (last_action->action.delete.text);
1024 last_action->action.delete.end += (undo_action->action.delete.end -
1025 undo_action->action.delete.start);
1026 last_action->action.delete.text = str;
1028 else
1030 gchar *str;
1032 /* Deleted with the backspace key */
1033 if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') &&
1034 (g_utf8_get_char (undo_action->action.delete.text) != '\t') &&
1035 ((g_utf8_get_char (last_action->action.delete.text) == ' ') ||
1036 (g_utf8_get_char (last_action->action.delete.text) == '\t')))
1038 last_action->mergeable = FALSE;
1039 return FALSE;
1042 str = g_strdup_printf ("%s%s", undo_action->action.delete.text,
1043 last_action->action.delete.text);
1045 g_free (last_action->action.delete.text);
1046 last_action->action.delete.start = undo_action->action.delete.start;
1047 last_action->action.delete.text = str;
1050 else if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
1052 gchar* str;
1054 #define I (last_action->action.insert.chars - 1)
1056 if ((undo_action->action.insert.pos !=
1057 (last_action->action.insert.pos + last_action->action.insert.chars)) ||
1058 ((g_utf8_get_char (undo_action->action.insert.text) != ' ') &&
1059 (g_utf8_get_char (undo_action->action.insert.text) != '\t') &&
1060 ((g_utf8_get_char_at (last_action->action.insert.text, I) == ' ') ||
1061 (g_utf8_get_char_at (last_action->action.insert.text, I) == '\t')))
1064 last_action->mergeable = FALSE;
1065 return FALSE;
1068 str = g_strdup_printf ("%s%s", last_action->action.insert.text,
1069 undo_action->action.insert.text);
1071 g_free (last_action->action.insert.text);
1072 last_action->action.insert.length += undo_action->action.insert.length;
1073 last_action->action.insert.text = str;
1074 last_action->action.insert.chars += undo_action->action.insert.chars;
1077 else if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR)
1079 /* Nothing needs to be done */
1081 else
1082 /* Unknown action inside undo merge encountered */
1083 g_return_val_if_reached (TRUE);
1085 return TRUE;
1088 gint
1089 gtk_source_undo_manager_get_max_undo_levels (GtkSourceUndoManager *um)
1091 g_return_val_if_fail (um != NULL, 0);
1092 g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), 0);
1094 return um->priv->max_undo_levels;
1097 void
1098 gtk_source_undo_manager_set_max_undo_levels (GtkSourceUndoManager *um,
1099 gint max_undo_levels)
1101 gint old_levels;
1103 g_return_if_fail (um != NULL);
1104 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
1106 old_levels = um->priv->max_undo_levels;
1107 um->priv->max_undo_levels = max_undo_levels;
1109 if (max_undo_levels < 1)
1110 return;
1112 if (old_levels > max_undo_levels)
1114 /* strip redo actions first */
1115 while (um->priv->next_redo >= 0 && (um->priv->num_of_groups > max_undo_levels))
1117 gtk_source_undo_manager_free_first_n_actions (um, 1);
1118 um->priv->next_redo--;
1121 /* now remove undo actions if necessary */
1122 gtk_source_undo_manager_check_list_size (um);
1124 /* emit "can_undo" and/or "can_redo" if appropiate */
1125 if (um->priv->next_redo < 0 && um->priv->can_redo)
1127 um->priv->can_redo = FALSE;
1128 g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE);
1131 if (um->priv->can_undo &&
1132 um->priv->next_redo >= (gint)(g_list_length (um->priv->actions) - 1))
1134 um->priv->can_undo = FALSE;
1135 g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, FALSE);
1140 static void
1141 gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer *buffer,
1142 GtkSourceUndoManager *um)
1144 GtkSourceUndoAction *action;
1145 GList *list;
1147 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
1148 g_return_if_fail (um->priv != NULL);
1150 if (um->priv->actions == NULL)
1151 return;
1153 list = g_list_nth (um->priv->actions, um->priv->next_redo + 1);
1155 if (list != NULL)
1156 action = (GtkSourceUndoAction*) list->data;
1157 else
1158 action = NULL;
1160 if (gtk_text_buffer_get_modified (buffer) == FALSE)
1162 if (action != NULL)
1163 action->mergeable = FALSE;
1165 if (um->priv->modified_action != NULL)
1167 um->priv->modified_action->modified = FALSE;
1168 um->priv->modified_action = NULL;
1171 return;
1174 if (action == NULL)
1176 g_return_if_fail (um->priv->running_not_undoable_actions > 0);
1178 return;
1181 /* gtk_text_buffer_get_modified (buffer) == TRUE */
1183 g_return_if_fail (um->priv->modified_action == NULL);
1185 if (action->order_in_group > 1)
1186 um->priv->modified_undoing_group = TRUE;
1188 while (action->order_in_group > 1)
1190 list = g_list_next (list);
1191 g_return_if_fail (list != NULL);
1193 action = (GtkSourceUndoAction*) list->data;
1194 g_return_if_fail (action != NULL);
1197 action->modified = TRUE;
1198 um->priv->modified_action = action;