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 struct _GtkSourceUndoManagerPrivate
105 GtkTextBuffer
*document
;
110 gint actions_in_current_group
;
112 gint running_not_undoable_actions
;
116 gint max_undo_levels
;
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
;
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
,
145 GtkSourceUndoManager
*um
);
146 static void gtk_source_undo_manager_insert_anchor_handler (GtkTextBuffer
*buffer
,
148 GtkTextChildAnchor
*anchor
,
149 GtkSourceUndoManager
*um
);
150 static void gtk_source_undo_manager_delete_range_handler (GtkTextBuffer
*buffer
,
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
,
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 };
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
),
190 (GInstanceInitFunc
) gtk_source_undo_manager_init
,
191 NULL
/* value_table */
194 undo_manager_type
= g_type_register_static (G_TYPE_OBJECT
,
195 "GtkSourceUndoManager",
200 return undo_manager_type
;
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
),
219 G_STRUCT_OFFSET (GtkSourceUndoManagerClass
, can_undo
),
221 gtksourceview_marshal_VOID__BOOLEAN
,
226 undo_manager_signals
[CAN_REDO
] =
227 g_signal_new ("can_redo",
228 G_OBJECT_CLASS_TYPE (object_class
),
230 G_STRUCT_OFFSET (GtkSourceUndoManagerClass
, can_redo
),
232 gtksourceview_marshal_VOID__BOOLEAN
,
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
;
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
),
281 g_signal_handlers_disconnect_by_func (G_OBJECT (um
->priv
->document
),
282 G_CALLBACK (gtk_source_undo_manager_insert_text_handler
),
285 g_signal_handlers_disconnect_by_func (G_OBJECT (um
->priv
->document
),
286 G_CALLBACK (gtk_source_undo_manager_insert_anchor_handler
),
289 g_signal_handlers_disconnect_by_func (G_OBJECT (um
->priv
->document
),
290 G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler
),
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
),
312 g_signal_connect (G_OBJECT (buffer
), "insert_child_anchor",
313 G_CALLBACK (gtk_source_undo_manager_insert_anchor_handler
),
316 g_signal_connect (G_OBJECT (buffer
), "delete_range",
317 G_CALLBACK (gtk_source_undo_manager_delete_range_handler
),
320 g_signal_connect (G_OBJECT (buffer
), "begin_user_action",
321 G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler
),
324 g_signal_connect (G_OBJECT (buffer
), "modified_changed",
325 G_CALLBACK (gtk_source_undo_manager_modified_changed_handler
),
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
;
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
;
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
],
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
],
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
;
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
;
403 set_cursor (GtkTextBuffer
*buffer
, gint cursor
)
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
);
413 insert_text (GtkTextBuffer
*buffer
, gint pos
, const gchar
*text
, gint len
)
417 gtk_text_buffer_get_iter_at_offset (buffer
, &iter
, pos
);
418 gtk_text_buffer_insert (buffer
, &iter
, text
, len
);
422 insert_anchor (GtkTextBuffer
*buffer
, gint pos
, GtkTextChildAnchor
*anchor
)
426 gtk_text_buffer_get_iter_at_offset (buffer
, &iter
, pos
);
427 gtk_text_buffer_insert_child_anchor (buffer
, &iter
, anchor
);
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
);
439 gtk_text_buffer_get_end_iter (buffer
, &end_iter
);
441 gtk_text_buffer_get_iter_at_offset (buffer
, &end_iter
, end
);
443 gtk_text_buffer_delete (buffer
, &start_iter
, &end_iter
);
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
);
455 gtk_text_buffer_get_end_iter (buffer
, &end_iter
);
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
);
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
:
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
)
505 undo_action
->action
.delete.start
);
509 undo_action
->action
.delete.end
);
513 case GTK_SOURCE_UNDO_ACTION_INSERT
:
516 undo_action
->action
.insert
.pos
,
517 undo_action
->action
.insert
.pos
+
518 undo_action
->action
.insert
.chars
);
522 undo_action
->action
.insert
.pos
);
525 case GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR
:
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+ */
533 /* Unknown action type. */
534 g_return_if_reached ();
537 ++um
->priv
->next_redo
;
539 } while (undo_action
->order_in_group
> 1);
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
],
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
],
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);
594 --um
->priv
->next_redo
;
596 switch (undo_action
->action_type
)
598 case GTK_SOURCE_UNDO_ACTION_DELETE
:
601 undo_action
->action
.delete.start
,
602 undo_action
->action
.delete.end
);
606 undo_action
->action
.delete.start
);
610 case GTK_SOURCE_UNDO_ACTION_INSERT
:
613 undo_action
->action
.insert
.pos
);
617 undo_action
->action
.insert
.pos
,
618 undo_action
->action
.insert
.text
,
619 undo_action
->action
.insert
.length
);
623 case GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR
:
626 undo_action
->action
.insert_anchor
.pos
);
630 undo_action
->action
.insert_anchor
.pos
,
631 undo_action
->action
.insert_anchor
.anchor
);
635 /* Unknown action type */
636 ++um
->priv
->next_redo
;
637 g_return_if_reached ();
640 if (um
->priv
->next_redo
< 0)
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));
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
);
670 gtk_source_undo_action_free (GtkSourceUndoAction
*action
)
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
);
683 g_return_if_reached ();
690 gtk_source_undo_manager_free_action_list (GtkSourceUndoManager
*um
)
694 l
= um
->priv
->actions
;
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
);
711 g_list_free (um
->priv
->actions
);
712 um
->priv
->actions
= NULL
;
716 gtk_source_undo_manager_insert_text_handler (GtkTextBuffer
*buffer
,
720 GtkSourceUndoManager
*um
)
722 GtkSourceUndoAction undo_action
;
724 if (um
->priv
->running_not_undoable_actions
> 0)
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
;
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
,
747 GtkTextChildAnchor
*anchor
,
748 GtkSourceUndoManager
*um
)
750 GtkSourceUndoAction undo_action
;
752 if (um
->priv
->running_not_undoable_actions
> 0)
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
);
767 gtk_source_undo_manager_delete_range_handler (GtkTextBuffer
*buffer
,
770 GtkSourceUndoManager
*um
)
772 GtkSourceUndoAction undo_action
;
773 GtkTextIter insert_iter
;
775 if (um
->priv
->running_not_undoable_actions
> 0)
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 (
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
;
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
;
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
);
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)
821 um
->priv
->actions_in_current_group
= 0;
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 */
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
);
881 gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager
*um
,
886 if (um
->priv
->actions
== NULL
)
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
,
904 if (um
->priv
->actions
== NULL
)
910 gtk_source_undo_manager_check_list_size (GtkSourceUndoManager
*um
)
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
);
922 if (um
->priv
->num_of_groups
> undo_levels
)
924 GtkSourceUndoAction
*undo_action
;
927 last
= g_list_last (um
->priv
->actions
);
928 undo_action
= (GtkSourceUndoAction
*) last
->data
;
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
);
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.
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
)
977 last_action
= (GtkSourceUndoAction
*) g_list_nth_data (um
->priv
->actions
, 0);
979 if (!last_action
->mergeable
)
982 if ((!undo_action
->mergeable
) ||
983 (undo_action
->action_type
!= last_action
->action_type
))
985 last_action
->mergeable
= 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
;
1003 if (last_action
->action
.delete.start
== undo_action
->action
.delete.start
)
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
;
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
;
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
;
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
)
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
;
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 */
1082 /* Unknown action inside undo merge encountered */
1083 g_return_val_if_reached (TRUE
);
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
;
1098 gtk_source_undo_manager_set_max_undo_levels (GtkSourceUndoManager
*um
,
1099 gint max_undo_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)
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
);
1141 gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer
*buffer
,
1142 GtkSourceUndoManager
*um
)
1144 GtkSourceUndoAction
*action
;
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
)
1153 list
= g_list_nth (um
->priv
->actions
, um
->priv
->next_redo
+ 1);
1156 action
= (GtkSourceUndoAction
*) list
->data
;
1160 if (gtk_text_buffer_get_modified (buffer
) == FALSE
)
1163 action
->mergeable
= FALSE
;
1165 if (um
->priv
->modified_action
!= NULL
)
1167 um
->priv
->modified_action
->modified
= FALSE
;
1168 um
->priv
->modified_action
= NULL
;
1176 g_return_if_fail (um
->priv
->running_not_undoable_actions
> 0);
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
;