Dutch translation updated (Gideon van Melle)
[pidgin-git.git] / pidgin / gtksourceundomanager.c
blob1368b0bbdb649c3fc59f04f5bdd48408089304eb
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 /* INVALID is a pointer to an invalid action */
104 #define INVALID ((void *) "IA")
106 struct _GtkSourceUndoManagerPrivate
108 GtkTextBuffer *document;
110 GList* actions;
111 gint next_redo;
113 gint actions_in_current_group;
115 gint running_not_undoable_actions;
117 gint num_of_groups;
119 gint max_undo_levels;
121 guint can_undo : 1;
122 guint can_redo : 1;
124 /* It is TRUE whether, while undoing an action of the current group (with order_in_group > 1),
125 * the state of the buffer changed from "not modified" to "modified".
127 guint modified_undoing_group : 1;
129 /* Pointer to the action (in the action list) marked as "modified".
130 * It is NULL when no action is marked as "modified".
131 * It is INVALID when the action marked as "modified" has been removed
132 * from the action list (freeing the list or resizing it) */
133 GtkSourceUndoAction *modified_action;
136 enum {
137 CAN_UNDO,
138 CAN_REDO,
139 LAST_SIGNAL
142 static void gtk_source_undo_manager_class_init (GtkSourceUndoManagerClass *klass);
143 static void gtk_source_undo_manager_init (GtkSourceUndoManager *um);
144 static void gtk_source_undo_manager_finalize (GObject *object);
146 static void gtk_source_undo_manager_insert_text_handler (GtkTextBuffer *buffer,
147 GtkTextIter *pos,
148 const gchar *text,
149 gint length,
150 GtkSourceUndoManager *um);
151 static void gtk_source_undo_manager_insert_anchor_handler (GtkTextBuffer *buffer,
152 GtkTextIter *pos,
153 GtkTextChildAnchor *anchor,
154 GtkSourceUndoManager *um);
155 static void gtk_source_undo_manager_delete_range_handler (GtkTextBuffer *buffer,
156 GtkTextIter *start,
157 GtkTextIter *end,
158 GtkSourceUndoManager *um);
159 static void gtk_source_undo_manager_begin_user_action_handler (GtkTextBuffer *buffer,
160 GtkSourceUndoManager *um);
161 static void gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer *buffer,
162 GtkSourceUndoManager *um);
164 static void gtk_source_undo_manager_free_action_list (GtkSourceUndoManager *um);
166 static void gtk_source_undo_manager_add_action (GtkSourceUndoManager *um,
167 const GtkSourceUndoAction *undo_action);
168 static void gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager *um,
169 gint n);
170 static void gtk_source_undo_manager_check_list_size (GtkSourceUndoManager *um);
172 static gboolean gtk_source_undo_manager_merge_action (GtkSourceUndoManager *um,
173 const GtkSourceUndoAction *undo_action);
175 static GObjectClass *parent_class = NULL;
176 static guint undo_manager_signals [LAST_SIGNAL] = { 0 };
178 GType
179 gtk_source_undo_manager_get_type (void)
181 static GType undo_manager_type = 0;
183 if (undo_manager_type == 0)
185 static const GTypeInfo our_info =
187 sizeof (GtkSourceUndoManagerClass),
188 NULL, /* base_init */
189 NULL, /* base_finalize */
190 (GClassInitFunc) gtk_source_undo_manager_class_init,
191 NULL, /* class_finalize */
192 NULL, /* class_data */
193 sizeof (GtkSourceUndoManager),
194 0, /* n_preallocs */
195 (GInstanceInitFunc) gtk_source_undo_manager_init,
196 NULL /* value_table */
199 undo_manager_type = g_type_register_static (G_TYPE_OBJECT,
200 "GtkSourceUndoManager",
201 &our_info,
205 return undo_manager_type;
208 static void
209 gtk_source_undo_manager_class_init (GtkSourceUndoManagerClass *klass)
211 GObjectClass *object_class = G_OBJECT_CLASS (klass);
213 parent_class = g_type_class_peek_parent (klass);
215 object_class->finalize = gtk_source_undo_manager_finalize;
217 klass->can_undo = NULL;
218 klass->can_redo = NULL;
220 undo_manager_signals[CAN_UNDO] =
221 g_signal_new ("can_undo",
222 G_OBJECT_CLASS_TYPE (object_class),
223 G_SIGNAL_RUN_LAST,
224 G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_undo),
225 NULL, NULL,
226 gtksourceview_marshal_VOID__BOOLEAN,
227 G_TYPE_NONE,
229 G_TYPE_BOOLEAN);
231 undo_manager_signals[CAN_REDO] =
232 g_signal_new ("can_redo",
233 G_OBJECT_CLASS_TYPE (object_class),
234 G_SIGNAL_RUN_LAST,
235 G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_redo),
236 NULL, NULL,
237 gtksourceview_marshal_VOID__BOOLEAN,
238 G_TYPE_NONE,
240 G_TYPE_BOOLEAN);
243 static void
244 gtk_source_undo_manager_init (GtkSourceUndoManager *um)
246 um->priv = g_new0 (GtkSourceUndoManagerPrivate, 1);
248 um->priv->actions = NULL;
249 um->priv->next_redo = 0;
251 um->priv->can_undo = FALSE;
252 um->priv->can_redo = FALSE;
254 um->priv->running_not_undoable_actions = 0;
256 um->priv->num_of_groups = 0;
258 um->priv->max_undo_levels = DEFAULT_MAX_UNDO_LEVELS;
260 um->priv->modified_action = NULL;
262 um->priv->modified_undoing_group = FALSE;
265 static void
266 gtk_source_undo_manager_finalize (GObject *object)
268 GtkSourceUndoManager *um;
270 g_return_if_fail (object != NULL);
271 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (object));
273 um = GTK_SOURCE_UNDO_MANAGER (object);
275 g_return_if_fail (um->priv != NULL);
277 if (um->priv->actions != NULL)
279 gtk_source_undo_manager_free_action_list (um);
282 g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
283 G_CALLBACK (gtk_source_undo_manager_delete_range_handler),
284 um);
286 g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
287 G_CALLBACK (gtk_source_undo_manager_insert_text_handler),
288 um);
290 g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
291 G_CALLBACK (gtk_source_undo_manager_insert_anchor_handler),
292 um);
294 g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
295 G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler),
296 um);
298 g_free (um->priv);
300 G_OBJECT_CLASS (parent_class)->finalize (object);
303 GtkSourceUndoManager*
304 gtk_source_undo_manager_new (GtkTextBuffer* buffer)
306 GtkSourceUndoManager *um;
308 um = GTK_SOURCE_UNDO_MANAGER (g_object_new (GTK_SOURCE_TYPE_UNDO_MANAGER, NULL));
310 g_return_val_if_fail (um->priv != NULL, NULL);
311 um->priv->document = buffer;
313 g_signal_connect (G_OBJECT (buffer), "insert_text",
314 G_CALLBACK (gtk_source_undo_manager_insert_text_handler),
315 um);
317 g_signal_connect (G_OBJECT (buffer), "insert_child_anchor",
318 G_CALLBACK (gtk_source_undo_manager_insert_anchor_handler),
319 um);
321 g_signal_connect (G_OBJECT (buffer), "delete_range",
322 G_CALLBACK (gtk_source_undo_manager_delete_range_handler),
323 um);
325 g_signal_connect (G_OBJECT (buffer), "begin_user_action",
326 G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler),
327 um);
329 g_signal_connect (G_OBJECT (buffer), "modified_changed",
330 G_CALLBACK (gtk_source_undo_manager_modified_changed_handler),
331 um);
332 return um;
335 void
336 gtk_source_undo_manager_begin_not_undoable_action (GtkSourceUndoManager *um)
338 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
339 g_return_if_fail (um->priv != NULL);
341 ++um->priv->running_not_undoable_actions;
344 static void
345 gtk_source_undo_manager_end_not_undoable_action_internal (GtkSourceUndoManager *um)
347 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
348 g_return_if_fail (um->priv != NULL);
350 g_return_if_fail (um->priv->running_not_undoable_actions > 0);
352 --um->priv->running_not_undoable_actions;
355 void
356 gtk_source_undo_manager_end_not_undoable_action (GtkSourceUndoManager *um)
358 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
359 g_return_if_fail (um->priv != NULL);
361 gtk_source_undo_manager_end_not_undoable_action_internal (um);
363 if (um->priv->running_not_undoable_actions == 0)
365 gtk_source_undo_manager_free_action_list (um);
367 um->priv->next_redo = -1;
369 if (um->priv->can_undo)
371 um->priv->can_undo = FALSE;
372 g_signal_emit (G_OBJECT (um),
373 undo_manager_signals [CAN_UNDO],
375 FALSE);
378 if (um->priv->can_redo)
380 um->priv->can_redo = FALSE;
381 g_signal_emit (G_OBJECT (um),
382 undo_manager_signals [CAN_REDO],
384 FALSE);
389 gboolean
390 gtk_source_undo_manager_can_undo (const GtkSourceUndoManager *um)
392 g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE);
393 g_return_val_if_fail (um->priv != NULL, FALSE);
395 return um->priv->can_undo;
398 gboolean
399 gtk_source_undo_manager_can_redo (const GtkSourceUndoManager *um)
401 g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE);
402 g_return_val_if_fail (um->priv != NULL, FALSE);
404 return um->priv->can_redo;
407 static void
408 set_cursor (GtkTextBuffer *buffer, gint cursor)
410 GtkTextIter iter;
412 /* Place the cursor at the requested position */
413 gtk_text_buffer_get_iter_at_offset (buffer, &iter, cursor);
414 gtk_text_buffer_place_cursor (buffer, &iter);
417 static void
418 insert_text (GtkTextBuffer *buffer, gint pos, const gchar *text, gint len)
420 GtkTextIter iter;
422 gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos);
423 gtk_text_buffer_insert (buffer, &iter, text, len);
426 static void
427 insert_anchor (GtkTextBuffer *buffer, gint pos, GtkTextChildAnchor *anchor)
429 GtkTextIter iter;
431 gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos);
432 gtk_text_buffer_insert_child_anchor (buffer, &iter, anchor);
435 static void
436 delete_text (GtkTextBuffer *buffer, gint start, gint end)
438 GtkTextIter start_iter;
439 GtkTextIter end_iter;
441 gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
443 if (end < 0)
444 gtk_text_buffer_get_end_iter (buffer, &end_iter);
445 else
446 gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
448 gtk_text_buffer_delete (buffer, &start_iter, &end_iter);
451 static gchar*
452 get_chars (GtkTextBuffer *buffer, gint start, gint end)
454 GtkTextIter start_iter;
455 GtkTextIter end_iter;
457 gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
459 if (end < 0)
460 gtk_text_buffer_get_end_iter (buffer, &end_iter);
461 else
462 gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
464 return gtk_text_buffer_get_slice (buffer, &start_iter, &end_iter, TRUE);
467 void
468 gtk_source_undo_manager_undo (GtkSourceUndoManager *um)
470 GtkSourceUndoAction *undo_action;
471 gboolean modified = FALSE;
473 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
474 g_return_if_fail (um->priv != NULL);
475 g_return_if_fail (um->priv->can_undo);
477 um->priv->modified_undoing_group = FALSE;
479 gtk_source_undo_manager_begin_not_undoable_action (um);
483 undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo + 1);
484 g_return_if_fail (undo_action != NULL);
486 /* undo_action->modified can be TRUE only if undo_action->order_in_group <= 1 */
487 g_return_if_fail ((undo_action->order_in_group <= 1) ||
488 ((undo_action->order_in_group > 1) && !undo_action->modified));
490 if (undo_action->order_in_group <= 1)
492 /* Set modified to TRUE only if the buffer did not change its state from
493 * "not modified" to "modified" undoing an action (with order_in_group > 1)
494 * in current group. */
495 modified = (undo_action->modified && !um->priv->modified_undoing_group);
498 switch (undo_action->action_type)
500 case GTK_SOURCE_UNDO_ACTION_DELETE:
501 insert_text (
502 um->priv->document,
503 undo_action->action.delete.start,
504 undo_action->action.delete.text,
505 strlen (undo_action->action.delete.text));
507 if (undo_action->action.delete.forward)
508 set_cursor (
509 um->priv->document,
510 undo_action->action.delete.start);
511 else
512 set_cursor (
513 um->priv->document,
514 undo_action->action.delete.end);
516 break;
518 case GTK_SOURCE_UNDO_ACTION_INSERT:
519 delete_text (
520 um->priv->document,
521 undo_action->action.insert.pos,
522 undo_action->action.insert.pos +
523 undo_action->action.insert.chars);
525 set_cursor (
526 um->priv->document,
527 undo_action->action.insert.pos);
528 break;
530 case GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR:
531 delete_text (
532 um->priv->document,
533 undo_action->action.insert_anchor.pos,
534 undo_action->action.insert_anchor.pos + 1);
535 undo_action->action.insert_anchor.anchor->segment = NULL; /* XXX: This may be a bug in GTK+ */
536 break;
537 default:
538 /* Unknown action type. */
539 g_return_if_reached ();
542 ++um->priv->next_redo;
544 } while (undo_action->order_in_group > 1);
546 if (modified)
548 --um->priv->next_redo;
549 gtk_text_buffer_set_modified (um->priv->document, FALSE);
550 ++um->priv->next_redo;
553 gtk_source_undo_manager_end_not_undoable_action_internal (um);
555 um->priv->modified_undoing_group = FALSE;
557 if (!um->priv->can_redo)
559 um->priv->can_redo = TRUE;
560 g_signal_emit (G_OBJECT (um),
561 undo_manager_signals [CAN_REDO],
563 TRUE);
566 if (um->priv->next_redo >= (gint)(g_list_length (um->priv->actions) - 1))
568 um->priv->can_undo = FALSE;
569 g_signal_emit (G_OBJECT (um),
570 undo_manager_signals [CAN_UNDO],
572 FALSE);
576 void
577 gtk_source_undo_manager_redo (GtkSourceUndoManager *um)
579 GtkSourceUndoAction *undo_action;
580 gboolean modified = FALSE;
582 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
583 g_return_if_fail (um->priv != NULL);
584 g_return_if_fail (um->priv->can_redo);
586 undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo);
587 g_return_if_fail (undo_action != NULL);
589 gtk_source_undo_manager_begin_not_undoable_action (um);
593 if (undo_action->modified)
595 g_return_if_fail (undo_action->order_in_group <= 1);
596 modified = TRUE;
599 --um->priv->next_redo;
601 switch (undo_action->action_type)
603 case GTK_SOURCE_UNDO_ACTION_DELETE:
604 delete_text (
605 um->priv->document,
606 undo_action->action.delete.start,
607 undo_action->action.delete.end);
609 set_cursor (
610 um->priv->document,
611 undo_action->action.delete.start);
613 break;
615 case GTK_SOURCE_UNDO_ACTION_INSERT:
616 set_cursor (
617 um->priv->document,
618 undo_action->action.insert.pos);
620 insert_text (
621 um->priv->document,
622 undo_action->action.insert.pos,
623 undo_action->action.insert.text,
624 undo_action->action.insert.length);
626 break;
628 case GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR:
629 set_cursor (
630 um->priv->document,
631 undo_action->action.insert_anchor.pos);
633 insert_anchor (
634 um->priv->document,
635 undo_action->action.insert_anchor.pos,
636 undo_action->action.insert_anchor.anchor);
637 break;
639 default:
640 /* Unknown action type */
641 ++um->priv->next_redo;
642 g_return_if_reached ();
645 if (um->priv->next_redo < 0)
646 undo_action = NULL;
647 else
648 undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo);
650 } while ((undo_action != NULL) && (undo_action->order_in_group > 1));
652 if (modified)
654 ++um->priv->next_redo;
655 gtk_text_buffer_set_modified (um->priv->document, FALSE);
656 --um->priv->next_redo;
659 gtk_source_undo_manager_end_not_undoable_action_internal (um);
661 if (um->priv->next_redo < 0)
663 um->priv->can_redo = FALSE;
664 g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE);
667 if (!um->priv->can_undo)
669 um->priv->can_undo = TRUE;
670 g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, TRUE);
674 static void
675 gtk_source_undo_action_free (GtkSourceUndoAction *action)
677 if (action == NULL)
678 return;
680 if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
681 g_free (action->action.insert.text);
682 else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
683 g_free (action->action.delete.text);
684 else if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR)
685 g_object_unref(action->action.insert_anchor.anchor);
686 else {
687 g_free (action);
688 g_return_if_reached ();
691 g_free (action);
694 static void
695 gtk_source_undo_manager_free_action_list (GtkSourceUndoManager *um)
697 GList *l;
699 l = um->priv->actions;
701 while (l != NULL)
703 GtkSourceUndoAction *action = l->data;
705 if (action->order_in_group == 1)
706 --um->priv->num_of_groups;
708 if (action->modified)
709 um->priv->modified_action = INVALID;
711 gtk_source_undo_action_free (action);
713 l = g_list_next (l);
716 g_list_free (um->priv->actions);
717 um->priv->actions = NULL;
720 static void
721 gtk_source_undo_manager_insert_text_handler (GtkTextBuffer *buffer,
722 GtkTextIter *pos,
723 const gchar *text,
724 gint length,
725 GtkSourceUndoManager *um)
727 GtkSourceUndoAction undo_action;
729 if (um->priv->running_not_undoable_actions > 0)
730 return;
732 undo_action.action_type = GTK_SOURCE_UNDO_ACTION_INSERT;
734 undo_action.action.insert.pos = gtk_text_iter_get_offset (pos);
735 undo_action.action.insert.text = (gchar*) text;
736 undo_action.action.insert.length = length;
737 undo_action.action.insert.chars = g_utf8_strlen (text, length);
739 if ((undo_action.action.insert.chars > 1) || (g_utf8_get_char (text) == '\n'))
741 undo_action.mergeable = FALSE;
742 else
743 undo_action.mergeable = TRUE;
745 undo_action.modified = FALSE;
747 gtk_source_undo_manager_add_action (um, &undo_action);
750 static void gtk_source_undo_manager_insert_anchor_handler (GtkTextBuffer *buffer,
751 GtkTextIter *pos,
752 GtkTextChildAnchor *anchor,
753 GtkSourceUndoManager *um)
755 GtkSourceUndoAction undo_action;
757 if (um->priv->running_not_undoable_actions > 0)
758 return;
760 undo_action.action_type = GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR;
762 undo_action.action.insert_anchor.pos = gtk_text_iter_get_offset (pos);
763 undo_action.action.insert_anchor.anchor = g_object_ref (anchor);
765 undo_action.mergeable = FALSE;
766 undo_action.modified = FALSE;
768 gtk_source_undo_manager_add_action (um, &undo_action);
771 static void
772 gtk_source_undo_manager_delete_range_handler (GtkTextBuffer *buffer,
773 GtkTextIter *start,
774 GtkTextIter *end,
775 GtkSourceUndoManager *um)
777 GtkSourceUndoAction undo_action;
778 GtkTextIter insert_iter;
780 if (um->priv->running_not_undoable_actions > 0)
781 return;
783 undo_action.action_type = GTK_SOURCE_UNDO_ACTION_DELETE;
785 gtk_text_iter_order (start, end);
787 undo_action.action.delete.start = gtk_text_iter_get_offset (start);
788 undo_action.action.delete.end = gtk_text_iter_get_offset (end);
790 undo_action.action.delete.text = get_chars (
791 buffer,
792 undo_action.action.delete.start,
793 undo_action.action.delete.end);
795 /* figure out if the user used the Delete or the Backspace key */
796 gtk_text_buffer_get_iter_at_mark (buffer, &insert_iter,
797 gtk_text_buffer_get_insert (buffer));
798 if (gtk_text_iter_get_offset (&insert_iter) <= undo_action.action.delete.start)
799 undo_action.action.delete.forward = TRUE;
800 else
801 undo_action.action.delete.forward = FALSE;
803 if (((undo_action.action.delete.end - undo_action.action.delete.start) > 1) ||
804 (g_utf8_get_char (undo_action.action.delete.text ) == '\n'))
805 undo_action.mergeable = FALSE;
806 else
807 undo_action.mergeable = TRUE;
809 undo_action.modified = FALSE;
811 gtk_source_undo_manager_add_action (um, &undo_action);
813 g_free (undo_action.action.delete.text);
817 static void
818 gtk_source_undo_manager_begin_user_action_handler (GtkTextBuffer *buffer, GtkSourceUndoManager *um)
820 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
821 g_return_if_fail (um->priv != NULL);
823 if (um->priv->running_not_undoable_actions > 0)
824 return;
826 um->priv->actions_in_current_group = 0;
829 static void
830 gtk_source_undo_manager_add_action (GtkSourceUndoManager *um,
831 const GtkSourceUndoAction *undo_action)
833 GtkSourceUndoAction* action;
835 if (um->priv->next_redo >= 0)
837 gtk_source_undo_manager_free_first_n_actions (um, um->priv->next_redo + 1);
840 um->priv->next_redo = -1;
842 if (!gtk_source_undo_manager_merge_action (um, undo_action))
844 action = g_new (GtkSourceUndoAction, 1);
845 *action = *undo_action;
847 if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
848 action->action.insert.text = g_strndup (undo_action->action.insert.text, undo_action->action.insert.length);
849 else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
850 action->action.delete.text = g_strdup (undo_action->action.delete.text);
851 else if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR)
853 /* Nothing needs to be done */
855 else
857 g_free (action);
858 g_return_if_reached ();
861 ++um->priv->actions_in_current_group;
862 action->order_in_group = um->priv->actions_in_current_group;
864 if (action->order_in_group == 1)
865 ++um->priv->num_of_groups;
867 um->priv->actions = g_list_prepend (um->priv->actions, action);
870 gtk_source_undo_manager_check_list_size (um);
872 if (!um->priv->can_undo)
874 um->priv->can_undo = TRUE;
875 g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, TRUE);
878 if (um->priv->can_redo)
880 um->priv->can_redo = FALSE;
881 g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE);
885 static void
886 gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager *um,
887 gint n)
889 gint i;
891 if (um->priv->actions == NULL)
892 return;
894 for (i = 0; i < n; i++)
896 GtkSourceUndoAction *action = g_list_first (um->priv->actions)->data;
898 if (action->order_in_group == 1)
899 --um->priv->num_of_groups;
901 if (action->modified)
902 um->priv->modified_action = INVALID;
904 gtk_source_undo_action_free (action);
906 um->priv->actions = g_list_delete_link (um->priv->actions,
907 um->priv->actions);
909 if (um->priv->actions == NULL)
910 return;
914 static void
915 gtk_source_undo_manager_check_list_size (GtkSourceUndoManager *um)
917 gint undo_levels;
919 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
920 g_return_if_fail (um->priv != NULL);
922 undo_levels = gtk_source_undo_manager_get_max_undo_levels (um);
924 if (undo_levels < 1)
925 return;
927 if (um->priv->num_of_groups > undo_levels)
929 GtkSourceUndoAction *undo_action;
930 GList *last;
932 last = g_list_last (um->priv->actions);
933 undo_action = (GtkSourceUndoAction*) last->data;
937 GList *tmp;
939 if (undo_action->order_in_group == 1)
940 --um->priv->num_of_groups;
942 if (undo_action->modified)
943 um->priv->modified_action = INVALID;
945 gtk_source_undo_action_free (undo_action);
947 tmp = g_list_previous (last);
948 um->priv->actions = g_list_delete_link (um->priv->actions, last);
949 last = tmp;
950 g_return_if_fail (last != NULL);
952 undo_action = (GtkSourceUndoAction*) last->data;
954 } while ((undo_action->order_in_group > 1) ||
955 (um->priv->num_of_groups > undo_levels));
960 * gtk_source_undo_manager_merge_action:
961 * @um: a #GtkSourceUndoManager.
962 * @undo_action: a #GtkSourceUndoAction.
964 * This function tries to merge the undo action at the top of
965 * the stack with a new undo action. So when we undo for example
966 * typing, we can undo the whole word and not each letter by itself.
968 * Return Value: %TRUE is merge was successful, %FALSE otherwise.
970 static gboolean
971 gtk_source_undo_manager_merge_action (GtkSourceUndoManager *um,
972 const GtkSourceUndoAction *undo_action)
974 GtkSourceUndoAction *last_action;
976 g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE);
977 g_return_val_if_fail (um->priv != NULL, FALSE);
979 if (um->priv->actions == NULL)
980 return FALSE;
982 last_action = (GtkSourceUndoAction*) g_list_nth_data (um->priv->actions, 0);
984 if (!last_action->mergeable)
985 return FALSE;
987 if ((!undo_action->mergeable) ||
988 (undo_action->action_type != last_action->action_type))
990 last_action->mergeable = FALSE;
991 return FALSE;
994 if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
996 if ((last_action->action.delete.forward != undo_action->action.delete.forward) ||
997 ((last_action->action.delete.start != undo_action->action.delete.start) &&
998 (last_action->action.delete.start != undo_action->action.delete.end)))
1000 last_action->mergeable = FALSE;
1001 return FALSE;
1004 if (last_action->action.delete.start == undo_action->action.delete.start)
1006 gchar *str;
1008 #define L (last_action->action.delete.end - last_action->action.delete.start - 1)
1009 #define g_utf8_get_char_at(p,i) g_utf8_get_char(g_utf8_offset_to_pointer((p),(i)))
1011 /* Deleted with the delete key */
1012 if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') &&
1013 (g_utf8_get_char (undo_action->action.delete.text) != '\t') &&
1014 ((g_utf8_get_char_at (last_action->action.delete.text, L) == ' ') ||
1015 (g_utf8_get_char_at (last_action->action.delete.text, L) == '\t')))
1017 last_action->mergeable = FALSE;
1018 return FALSE;
1021 str = g_strdup_printf ("%s%s", last_action->action.delete.text,
1022 undo_action->action.delete.text);
1024 g_free (last_action->action.delete.text);
1025 last_action->action.delete.end += (undo_action->action.delete.end -
1026 undo_action->action.delete.start);
1027 last_action->action.delete.text = str;
1029 else
1031 gchar *str;
1033 /* Deleted with the backspace key */
1034 if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') &&
1035 (g_utf8_get_char (undo_action->action.delete.text) != '\t') &&
1036 ((g_utf8_get_char (last_action->action.delete.text) == ' ') ||
1037 (g_utf8_get_char (last_action->action.delete.text) == '\t')))
1039 last_action->mergeable = FALSE;
1040 return FALSE;
1043 str = g_strdup_printf ("%s%s", undo_action->action.delete.text,
1044 last_action->action.delete.text);
1046 g_free (last_action->action.delete.text);
1047 last_action->action.delete.start = undo_action->action.delete.start;
1048 last_action->action.delete.text = str;
1051 else if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
1053 gchar* str;
1055 #define I (last_action->action.insert.chars - 1)
1057 if ((undo_action->action.insert.pos !=
1058 (last_action->action.insert.pos + last_action->action.insert.chars)) ||
1059 ((g_utf8_get_char (undo_action->action.insert.text) != ' ') &&
1060 (g_utf8_get_char (undo_action->action.insert.text) != '\t') &&
1061 ((g_utf8_get_char_at (last_action->action.insert.text, I) == ' ') ||
1062 (g_utf8_get_char_at (last_action->action.insert.text, I) == '\t')))
1065 last_action->mergeable = FALSE;
1066 return FALSE;
1069 str = g_strdup_printf ("%s%s", last_action->action.insert.text,
1070 undo_action->action.insert.text);
1072 g_free (last_action->action.insert.text);
1073 last_action->action.insert.length += undo_action->action.insert.length;
1074 last_action->action.insert.text = str;
1075 last_action->action.insert.chars += undo_action->action.insert.chars;
1078 else if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR)
1080 /* Nothing needs to be done */
1082 else
1083 /* Unknown action inside undo merge encountered */
1084 g_return_val_if_reached (TRUE);
1086 return TRUE;
1089 gint
1090 gtk_source_undo_manager_get_max_undo_levels (GtkSourceUndoManager *um)
1092 g_return_val_if_fail (um != NULL, 0);
1093 g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), 0);
1095 return um->priv->max_undo_levels;
1098 void
1099 gtk_source_undo_manager_set_max_undo_levels (GtkSourceUndoManager *um,
1100 gint max_undo_levels)
1102 gint old_levels;
1104 g_return_if_fail (um != NULL);
1105 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
1107 old_levels = um->priv->max_undo_levels;
1108 um->priv->max_undo_levels = max_undo_levels;
1110 if (max_undo_levels < 1)
1111 return;
1113 if (old_levels > max_undo_levels)
1115 /* strip redo actions first */
1116 while (um->priv->next_redo >= 0 && (um->priv->num_of_groups > max_undo_levels))
1118 gtk_source_undo_manager_free_first_n_actions (um, 1);
1119 um->priv->next_redo--;
1122 /* now remove undo actions if necessary */
1123 gtk_source_undo_manager_check_list_size (um);
1125 /* emit "can_undo" and/or "can_redo" if appropiate */
1126 if (um->priv->next_redo < 0 && um->priv->can_redo)
1128 um->priv->can_redo = FALSE;
1129 g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE);
1132 if (um->priv->can_undo &&
1133 um->priv->next_redo >= (gint)(g_list_length (um->priv->actions) - 1))
1135 um->priv->can_undo = FALSE;
1136 g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, FALSE);
1141 static void
1142 gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer *buffer,
1143 GtkSourceUndoManager *um)
1145 GtkSourceUndoAction *action;
1146 GList *list;
1148 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
1149 g_return_if_fail (um->priv != NULL);
1151 if (um->priv->actions == NULL)
1152 return;
1154 list = g_list_nth (um->priv->actions, um->priv->next_redo + 1);
1156 if (list != NULL)
1157 action = (GtkSourceUndoAction*) list->data;
1158 else
1159 action = NULL;
1161 if (gtk_text_buffer_get_modified (buffer) == FALSE)
1163 if (action != NULL)
1164 action->mergeable = FALSE;
1166 if (um->priv->modified_action != NULL)
1168 if (um->priv->modified_action != INVALID)
1169 um->priv->modified_action->modified = FALSE;
1171 um->priv->modified_action = NULL;
1174 return;
1177 if (action == NULL)
1179 g_return_if_fail (um->priv->running_not_undoable_actions > 0);
1181 return;
1184 /* gtk_text_buffer_get_modified (buffer) == TRUE */
1186 g_return_if_fail (um->priv->modified_action == NULL);
1188 if (action->order_in_group > 1)
1189 um->priv->modified_undoing_group = TRUE;
1191 while (action->order_in_group > 1)
1193 list = g_list_next (list);
1194 g_return_if_fail (list != NULL);
1196 action = (GtkSourceUndoAction*) list->data;
1197 g_return_if_fail (action != NULL);
1200 action->modified = TRUE;
1201 um->priv->modified_action = action;