1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
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.
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
;
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
65 struct _GtkSourceUndoDeleteAction
73 struct _GtkSourceUndoInsertAnchorAction
76 GtkTextChildAnchor
*anchor
;
79 struct _GtkSourceUndoAction
81 GtkSourceUndoActionType action_type
;
84 GtkSourceUndoInsertAction insert
;
85 GtkSourceUndoDeleteAction
delete;
86 GtkSourceUndoInsertAnchorAction insert_anchor
;
91 /* It is TRUE whether the action can be merged with the following action. */
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.
103 /* INVALID is a pointer to an invalid action */
104 #define INVALID ((void *) "IA")
106 struct _GtkSourceUndoManagerPrivate
108 GtkTextBuffer
*document
;
113 gint actions_in_current_group
;
115 gint running_not_undoable_actions
;
119 gint max_undo_levels
;
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
;
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
,
150 GtkSourceUndoManager
*um
);
151 static void gtk_source_undo_manager_insert_anchor_handler (GtkTextBuffer
*buffer
,
153 GtkTextChildAnchor
*anchor
,
154 GtkSourceUndoManager
*um
);
155 static void gtk_source_undo_manager_delete_range_handler (GtkTextBuffer
*buffer
,
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
,
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 };
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
),
195 (GInstanceInitFunc
) gtk_source_undo_manager_init
,
196 NULL
/* value_table */
199 undo_manager_type
= g_type_register_static (G_TYPE_OBJECT
,
200 "GtkSourceUndoManager",
205 return undo_manager_type
;
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
),
224 G_STRUCT_OFFSET (GtkSourceUndoManagerClass
, can_undo
),
226 gtksourceview_marshal_VOID__BOOLEAN
,
231 undo_manager_signals
[CAN_REDO
] =
232 g_signal_new ("can_redo",
233 G_OBJECT_CLASS_TYPE (object_class
),
235 G_STRUCT_OFFSET (GtkSourceUndoManagerClass
, can_redo
),
237 gtksourceview_marshal_VOID__BOOLEAN
,
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
;
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
),
286 g_signal_handlers_disconnect_by_func (G_OBJECT (um
->priv
->document
),
287 G_CALLBACK (gtk_source_undo_manager_insert_text_handler
),
290 g_signal_handlers_disconnect_by_func (G_OBJECT (um
->priv
->document
),
291 G_CALLBACK (gtk_source_undo_manager_insert_anchor_handler
),
294 g_signal_handlers_disconnect_by_func (G_OBJECT (um
->priv
->document
),
295 G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler
),
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
),
317 g_signal_connect (G_OBJECT (buffer
), "insert_child_anchor",
318 G_CALLBACK (gtk_source_undo_manager_insert_anchor_handler
),
321 g_signal_connect (G_OBJECT (buffer
), "delete_range",
322 G_CALLBACK (gtk_source_undo_manager_delete_range_handler
),
325 g_signal_connect (G_OBJECT (buffer
), "begin_user_action",
326 G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler
),
329 g_signal_connect (G_OBJECT (buffer
), "modified_changed",
330 G_CALLBACK (gtk_source_undo_manager_modified_changed_handler
),
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
;
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
;
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
],
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
],
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
;
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
;
408 set_cursor (GtkTextBuffer
*buffer
, gint cursor
)
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
);
418 insert_text (GtkTextBuffer
*buffer
, gint pos
, const gchar
*text
, gint len
)
422 gtk_text_buffer_get_iter_at_offset (buffer
, &iter
, pos
);
423 gtk_text_buffer_insert (buffer
, &iter
, text
, len
);
427 insert_anchor (GtkTextBuffer
*buffer
, gint pos
, GtkTextChildAnchor
*anchor
)
431 gtk_text_buffer_get_iter_at_offset (buffer
, &iter
, pos
);
432 gtk_text_buffer_insert_child_anchor (buffer
, &iter
, anchor
);
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
);
444 gtk_text_buffer_get_end_iter (buffer
, &end_iter
);
446 gtk_text_buffer_get_iter_at_offset (buffer
, &end_iter
, end
);
448 gtk_text_buffer_delete (buffer
, &start_iter
, &end_iter
);
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
);
460 gtk_text_buffer_get_end_iter (buffer
, &end_iter
);
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
);
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
:
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
)
510 undo_action
->action
.delete.start
);
514 undo_action
->action
.delete.end
);
518 case GTK_SOURCE_UNDO_ACTION_INSERT
:
521 undo_action
->action
.insert
.pos
,
522 undo_action
->action
.insert
.pos
+
523 undo_action
->action
.insert
.chars
);
527 undo_action
->action
.insert
.pos
);
530 case GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR
:
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+ */
538 /* Unknown action type. */
539 g_return_if_reached ();
542 ++um
->priv
->next_redo
;
544 } while (undo_action
->order_in_group
> 1);
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
],
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
],
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);
599 --um
->priv
->next_redo
;
601 switch (undo_action
->action_type
)
603 case GTK_SOURCE_UNDO_ACTION_DELETE
:
606 undo_action
->action
.delete.start
,
607 undo_action
->action
.delete.end
);
611 undo_action
->action
.delete.start
);
615 case GTK_SOURCE_UNDO_ACTION_INSERT
:
618 undo_action
->action
.insert
.pos
);
622 undo_action
->action
.insert
.pos
,
623 undo_action
->action
.insert
.text
,
624 undo_action
->action
.insert
.length
);
628 case GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR
:
631 undo_action
->action
.insert_anchor
.pos
);
635 undo_action
->action
.insert_anchor
.pos
,
636 undo_action
->action
.insert_anchor
.anchor
);
640 /* Unknown action type */
641 ++um
->priv
->next_redo
;
642 g_return_if_reached ();
645 if (um
->priv
->next_redo
< 0)
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));
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
);
675 gtk_source_undo_action_free (GtkSourceUndoAction
*action
)
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
);
687 g_return_if_reached ();
693 gtk_source_undo_manager_free_action_list (GtkSourceUndoManager
*um
)
697 l
= um
->priv
->actions
;
701 GtkSourceUndoAction
*action
= l
->data
;
703 if (action
->order_in_group
== 1)
704 --um
->priv
->num_of_groups
;
706 if (action
->modified
)
707 um
->priv
->modified_action
= INVALID
;
709 gtk_source_undo_action_free (action
);
714 g_list_free (um
->priv
->actions
);
715 um
->priv
->actions
= NULL
;
719 gtk_source_undo_manager_insert_text_handler (GtkTextBuffer
*buffer
,
723 GtkSourceUndoManager
*um
)
725 GtkSourceUndoAction undo_action
;
727 if (um
->priv
->running_not_undoable_actions
> 0)
730 undo_action
.action_type
= GTK_SOURCE_UNDO_ACTION_INSERT
;
732 undo_action
.action
.insert
.pos
= gtk_text_iter_get_offset (pos
);
733 undo_action
.action
.insert
.text
= (gchar
*) text
;
734 undo_action
.action
.insert
.length
= length
;
735 undo_action
.action
.insert
.chars
= g_utf8_strlen (text
, length
);
737 if ((undo_action
.action
.insert
.chars
> 1) || (g_utf8_get_char (text
) == '\n'))
739 undo_action
.mergeable
= FALSE
;
741 undo_action
.mergeable
= TRUE
;
743 undo_action
.modified
= FALSE
;
745 gtk_source_undo_manager_add_action (um
, &undo_action
);
748 static void gtk_source_undo_manager_insert_anchor_handler (GtkTextBuffer
*buffer
,
750 GtkTextChildAnchor
*anchor
,
751 GtkSourceUndoManager
*um
)
753 GtkSourceUndoAction undo_action
;
755 if (um
->priv
->running_not_undoable_actions
> 0)
758 undo_action
.action_type
= GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR
;
760 undo_action
.action
.insert_anchor
.pos
= gtk_text_iter_get_offset (pos
);
761 undo_action
.action
.insert_anchor
.anchor
= g_object_ref (anchor
);
763 undo_action
.mergeable
= FALSE
;
764 undo_action
.modified
= FALSE
;
766 gtk_source_undo_manager_add_action (um
, &undo_action
);
770 gtk_source_undo_manager_delete_range_handler (GtkTextBuffer
*buffer
,
773 GtkSourceUndoManager
*um
)
775 GtkSourceUndoAction undo_action
;
776 GtkTextIter insert_iter
;
778 if (um
->priv
->running_not_undoable_actions
> 0)
781 undo_action
.action_type
= GTK_SOURCE_UNDO_ACTION_DELETE
;
783 gtk_text_iter_order (start
, end
);
785 undo_action
.action
.delete.start
= gtk_text_iter_get_offset (start
);
786 undo_action
.action
.delete.end
= gtk_text_iter_get_offset (end
);
788 undo_action
.action
.delete.text
= get_chars (
790 undo_action
.action
.delete.start
,
791 undo_action
.action
.delete.end
);
793 /* figure out if the user used the Delete or the Backspace key */
794 gtk_text_buffer_get_iter_at_mark (buffer
, &insert_iter
,
795 gtk_text_buffer_get_insert (buffer
));
796 if (gtk_text_iter_get_offset (&insert_iter
) <= undo_action
.action
.delete.start
)
797 undo_action
.action
.delete.forward
= TRUE
;
799 undo_action
.action
.delete.forward
= FALSE
;
801 if (((undo_action
.action
.delete.end
- undo_action
.action
.delete.start
) > 1) ||
802 (g_utf8_get_char (undo_action
.action
.delete.text
) == '\n'))
803 undo_action
.mergeable
= FALSE
;
805 undo_action
.mergeable
= TRUE
;
807 undo_action
.modified
= FALSE
;
809 gtk_source_undo_manager_add_action (um
, &undo_action
);
811 g_free (undo_action
.action
.delete.text
);
816 gtk_source_undo_manager_begin_user_action_handler (GtkTextBuffer
*buffer
, GtkSourceUndoManager
*um
)
818 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um
));
819 g_return_if_fail (um
->priv
!= NULL
);
821 if (um
->priv
->running_not_undoable_actions
> 0)
824 um
->priv
->actions_in_current_group
= 0;
828 gtk_source_undo_manager_add_action (GtkSourceUndoManager
*um
,
829 const GtkSourceUndoAction
*undo_action
)
831 GtkSourceUndoAction
* action
;
833 if (um
->priv
->next_redo
>= 0)
835 gtk_source_undo_manager_free_first_n_actions (um
, um
->priv
->next_redo
+ 1);
838 um
->priv
->next_redo
= -1;
840 if (!gtk_source_undo_manager_merge_action (um
, undo_action
))
842 action
= g_new (GtkSourceUndoAction
, 1);
843 *action
= *undo_action
;
845 if (action
->action_type
== GTK_SOURCE_UNDO_ACTION_INSERT
)
846 action
->action
.insert
.text
= g_strndup (undo_action
->action
.insert
.text
, undo_action
->action
.insert
.length
);
847 else if (action
->action_type
== GTK_SOURCE_UNDO_ACTION_DELETE
)
848 action
->action
.delete.text
= g_strdup (undo_action
->action
.delete.text
);
849 else if (action
->action_type
== GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR
)
851 /* Nothing needs to be done */
856 g_return_if_reached ();
859 ++um
->priv
->actions_in_current_group
;
860 action
->order_in_group
= um
->priv
->actions_in_current_group
;
862 if (action
->order_in_group
== 1)
863 ++um
->priv
->num_of_groups
;
865 um
->priv
->actions
= g_list_prepend (um
->priv
->actions
, action
);
868 gtk_source_undo_manager_check_list_size (um
);
870 if (!um
->priv
->can_undo
)
872 um
->priv
->can_undo
= TRUE
;
873 g_signal_emit (G_OBJECT (um
), undo_manager_signals
[CAN_UNDO
], 0, TRUE
);
876 if (um
->priv
->can_redo
)
878 um
->priv
->can_redo
= FALSE
;
879 g_signal_emit (G_OBJECT (um
), undo_manager_signals
[CAN_REDO
], 0, FALSE
);
884 gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager
*um
,
889 if (um
->priv
->actions
== NULL
)
892 for (i
= 0; i
< n
; i
++)
894 GtkSourceUndoAction
*action
= g_list_first (um
->priv
->actions
)->data
;
896 if (action
->order_in_group
== 1)
897 --um
->priv
->num_of_groups
;
899 if (action
->modified
)
900 um
->priv
->modified_action
= INVALID
;
902 gtk_source_undo_action_free (action
);
904 um
->priv
->actions
= g_list_delete_link (um
->priv
->actions
,
907 if (um
->priv
->actions
== NULL
)
913 gtk_source_undo_manager_check_list_size (GtkSourceUndoManager
*um
)
917 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um
));
918 g_return_if_fail (um
->priv
!= NULL
);
920 undo_levels
= gtk_source_undo_manager_get_max_undo_levels (um
);
925 if (um
->priv
->num_of_groups
> undo_levels
)
927 GtkSourceUndoAction
*undo_action
;
930 last
= g_list_last (um
->priv
->actions
);
931 undo_action
= (GtkSourceUndoAction
*) last
->data
;
937 if (undo_action
->order_in_group
== 1)
938 --um
->priv
->num_of_groups
;
940 if (undo_action
->modified
)
941 um
->priv
->modified_action
= INVALID
;
943 gtk_source_undo_action_free (undo_action
);
945 tmp
= g_list_previous (last
);
946 um
->priv
->actions
= g_list_delete_link (um
->priv
->actions
, last
);
948 g_return_if_fail (last
!= NULL
);
950 undo_action
= (GtkSourceUndoAction
*) last
->data
;
952 } while ((undo_action
->order_in_group
> 1) ||
953 (um
->priv
->num_of_groups
> undo_levels
));
958 * gtk_source_undo_manager_merge_action:
959 * @um: a #GtkSourceUndoManager.
960 * @undo_action: a #GtkSourceUndoAction.
962 * This function tries to merge the undo action at the top of
963 * the stack with a new undo action. So when we undo for example
964 * typing, we can undo the whole word and not each letter by itself.
966 * Return Value: %TRUE is merge was successful, %FALSE otherwise.
969 gtk_source_undo_manager_merge_action (GtkSourceUndoManager
*um
,
970 const GtkSourceUndoAction
*undo_action
)
972 GtkSourceUndoAction
*last_action
;
974 g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um
), FALSE
);
975 g_return_val_if_fail (um
->priv
!= NULL
, FALSE
);
977 if (um
->priv
->actions
== NULL
)
980 last_action
= (GtkSourceUndoAction
*) g_list_nth_data (um
->priv
->actions
, 0);
982 if (!last_action
->mergeable
)
985 if ((!undo_action
->mergeable
) ||
986 (undo_action
->action_type
!= last_action
->action_type
))
988 last_action
->mergeable
= FALSE
;
992 if (undo_action
->action_type
== GTK_SOURCE_UNDO_ACTION_DELETE
)
994 if ((last_action
->action
.delete.forward
!= undo_action
->action
.delete.forward
) ||
995 ((last_action
->action
.delete.start
!= undo_action
->action
.delete.start
) &&
996 (last_action
->action
.delete.start
!= undo_action
->action
.delete.end
)))
998 last_action
->mergeable
= FALSE
;
1002 if (last_action
->action
.delete.start
== undo_action
->action
.delete.start
)
1006 #define L (last_action->action.delete.end - last_action->action.delete.start - 1)
1007 #define g_utf8_get_char_at(p,i) g_utf8_get_char(g_utf8_offset_to_pointer((p),(i)))
1009 /* Deleted with the delete key */
1010 if ((g_utf8_get_char (undo_action
->action
.delete.text
) != ' ') &&
1011 (g_utf8_get_char (undo_action
->action
.delete.text
) != '\t') &&
1012 ((g_utf8_get_char_at (last_action
->action
.delete.text
, L
) == ' ') ||
1013 (g_utf8_get_char_at (last_action
->action
.delete.text
, L
) == '\t')))
1015 last_action
->mergeable
= FALSE
;
1019 str
= g_strdup_printf ("%s%s", last_action
->action
.delete.text
,
1020 undo_action
->action
.delete.text
);
1022 g_free (last_action
->action
.delete.text
);
1023 last_action
->action
.delete.end
+= (undo_action
->action
.delete.end
-
1024 undo_action
->action
.delete.start
);
1025 last_action
->action
.delete.text
= str
;
1031 /* Deleted with the backspace key */
1032 if ((g_utf8_get_char (undo_action
->action
.delete.text
) != ' ') &&
1033 (g_utf8_get_char (undo_action
->action
.delete.text
) != '\t') &&
1034 ((g_utf8_get_char (last_action
->action
.delete.text
) == ' ') ||
1035 (g_utf8_get_char (last_action
->action
.delete.text
) == '\t')))
1037 last_action
->mergeable
= FALSE
;
1041 str
= g_strdup_printf ("%s%s", undo_action
->action
.delete.text
,
1042 last_action
->action
.delete.text
);
1044 g_free (last_action
->action
.delete.text
);
1045 last_action
->action
.delete.start
= undo_action
->action
.delete.start
;
1046 last_action
->action
.delete.text
= str
;
1049 else if (undo_action
->action_type
== GTK_SOURCE_UNDO_ACTION_INSERT
)
1053 #define I (last_action->action.insert.chars - 1)
1055 if ((undo_action
->action
.insert
.pos
!=
1056 (last_action
->action
.insert
.pos
+ last_action
->action
.insert
.chars
)) ||
1057 ((g_utf8_get_char (undo_action
->action
.insert
.text
) != ' ') &&
1058 (g_utf8_get_char (undo_action
->action
.insert
.text
) != '\t') &&
1059 ((g_utf8_get_char_at (last_action
->action
.insert
.text
, I
) == ' ') ||
1060 (g_utf8_get_char_at (last_action
->action
.insert
.text
, I
) == '\t')))
1063 last_action
->mergeable
= FALSE
;
1067 str
= g_strdup_printf ("%s%s", last_action
->action
.insert
.text
,
1068 undo_action
->action
.insert
.text
);
1070 g_free (last_action
->action
.insert
.text
);
1071 last_action
->action
.insert
.length
+= undo_action
->action
.insert
.length
;
1072 last_action
->action
.insert
.text
= str
;
1073 last_action
->action
.insert
.chars
+= undo_action
->action
.insert
.chars
;
1076 else if (undo_action
->action_type
== GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR
)
1078 /* Nothing needs to be done */
1081 /* Unknown action inside undo merge encountered */
1082 g_return_val_if_reached (TRUE
);
1088 gtk_source_undo_manager_get_max_undo_levels (GtkSourceUndoManager
*um
)
1090 g_return_val_if_fail (um
!= NULL
, 0);
1091 g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um
), 0);
1093 return um
->priv
->max_undo_levels
;
1097 gtk_source_undo_manager_set_max_undo_levels (GtkSourceUndoManager
*um
,
1098 gint max_undo_levels
)
1102 g_return_if_fail (um
!= NULL
);
1103 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um
));
1105 old_levels
= um
->priv
->max_undo_levels
;
1106 um
->priv
->max_undo_levels
= max_undo_levels
;
1108 if (max_undo_levels
< 1)
1111 if (old_levels
> max_undo_levels
)
1113 /* strip redo actions first */
1114 while (um
->priv
->next_redo
>= 0 && (um
->priv
->num_of_groups
> max_undo_levels
))
1116 gtk_source_undo_manager_free_first_n_actions (um
, 1);
1117 um
->priv
->next_redo
--;
1120 /* now remove undo actions if necessary */
1121 gtk_source_undo_manager_check_list_size (um
);
1123 /* emit "can_undo" and/or "can_redo" if appropiate */
1124 if (um
->priv
->next_redo
< 0 && um
->priv
->can_redo
)
1126 um
->priv
->can_redo
= FALSE
;
1127 g_signal_emit (G_OBJECT (um
), undo_manager_signals
[CAN_REDO
], 0, FALSE
);
1130 if (um
->priv
->can_undo
&&
1131 um
->priv
->next_redo
>= (gint
)(g_list_length (um
->priv
->actions
) - 1))
1133 um
->priv
->can_undo
= FALSE
;
1134 g_signal_emit (G_OBJECT (um
), undo_manager_signals
[CAN_UNDO
], 0, FALSE
);
1140 gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer
*buffer
,
1141 GtkSourceUndoManager
*um
)
1143 GtkSourceUndoAction
*action
;
1146 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um
));
1147 g_return_if_fail (um
->priv
!= NULL
);
1149 if (um
->priv
->actions
== NULL
)
1152 list
= g_list_nth (um
->priv
->actions
, um
->priv
->next_redo
+ 1);
1155 action
= (GtkSourceUndoAction
*) list
->data
;
1159 if (gtk_text_buffer_get_modified (buffer
) == FALSE
)
1162 action
->mergeable
= FALSE
;
1164 if (um
->priv
->modified_action
!= NULL
)
1166 if (um
->priv
->modified_action
!= INVALID
)
1167 um
->priv
->modified_action
->modified
= FALSE
;
1169 um
->priv
->modified_action
= NULL
;
1177 g_return_if_fail (um
->priv
->running_not_undoable_actions
> 0);
1182 /* gtk_text_buffer_get_modified (buffer) == TRUE */
1184 g_return_if_fail (um
->priv
->modified_action
== NULL
);
1186 if (action
->order_in_group
> 1)
1187 um
->priv
->modified_undoing_group
= TRUE
;
1189 while (action
->order_in_group
> 1)
1191 list
= g_list_next (list
);
1192 g_return_if_fail (list
!= NULL
);
1194 action
= (GtkSourceUndoAction
*) list
->data
;
1195 g_return_if_fail (action
!= NULL
);
1198 action
->modified
= TRUE
;
1199 um
->priv
->modified_action
= action
;