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
);
688 g_return_if_reached ();
695 gtk_source_undo_manager_free_action_list (GtkSourceUndoManager
*um
)
699 l
= um
->priv
->actions
;
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
);
716 g_list_free (um
->priv
->actions
);
717 um
->priv
->actions
= NULL
;
721 gtk_source_undo_manager_insert_text_handler (GtkTextBuffer
*buffer
,
725 GtkSourceUndoManager
*um
)
727 GtkSourceUndoAction undo_action
;
729 if (um
->priv
->running_not_undoable_actions
> 0)
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
;
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
,
752 GtkTextChildAnchor
*anchor
,
753 GtkSourceUndoManager
*um
)
755 GtkSourceUndoAction undo_action
;
757 if (um
->priv
->running_not_undoable_actions
> 0)
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
);
772 gtk_source_undo_manager_delete_range_handler (GtkTextBuffer
*buffer
,
775 GtkSourceUndoManager
*um
)
777 GtkSourceUndoAction undo_action
;
778 GtkTextIter insert_iter
;
780 if (um
->priv
->running_not_undoable_actions
> 0)
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 (
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
;
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
;
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
);
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)
826 um
->priv
->actions_in_current_group
= 0;
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 */
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
);
886 gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager
*um
,
891 if (um
->priv
->actions
== NULL
)
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
,
909 if (um
->priv
->actions
== NULL
)
915 gtk_source_undo_manager_check_list_size (GtkSourceUndoManager
*um
)
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
);
927 if (um
->priv
->num_of_groups
> undo_levels
)
929 GtkSourceUndoAction
*undo_action
;
932 last
= g_list_last (um
->priv
->actions
);
933 undo_action
= (GtkSourceUndoAction
*) last
->data
;
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
);
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.
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
)
982 last_action
= (GtkSourceUndoAction
*) g_list_nth_data (um
->priv
->actions
, 0);
984 if (!last_action
->mergeable
)
987 if ((!undo_action
->mergeable
) ||
988 (undo_action
->action_type
!= last_action
->action_type
))
990 last_action
->mergeable
= 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
;
1004 if (last_action
->action
.delete.start
== undo_action
->action
.delete.start
)
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
;
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
;
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
;
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
)
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
;
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 */
1083 /* Unknown action inside undo merge encountered */
1084 g_return_val_if_reached (TRUE
);
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
;
1099 gtk_source_undo_manager_set_max_undo_levels (GtkSourceUndoManager
*um
,
1100 gint max_undo_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)
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
);
1142 gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer
*buffer
,
1143 GtkSourceUndoManager
*um
)
1145 GtkSourceUndoAction
*action
;
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
)
1154 list
= g_list_nth (um
->priv
->actions
, um
->priv
->next_redo
+ 1);
1157 action
= (GtkSourceUndoAction
*) list
->data
;
1161 if (gtk_text_buffer_get_modified (buffer
) == FALSE
)
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
;
1179 g_return_if_fail (um
->priv
->running_not_undoable_actions
> 0);
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
;