comparison pidgin/gtksourceundomanager.c @ 17411:674d8bc2b980

Undo/Redo in GtkImHtml from GtkSourceView. This may not be adaquate enough for us.
author Sean Egan <seanegan@gmail.com>
date Fri, 25 May 2007 21:53:49 +0000
parents
children a92cadf6a978
comparison
equal deleted inserted replaced
17410:fe8a1051aa0a 17411:674d8bc2b980
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
5 *
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
9 *
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.
14 *
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.
19 *
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., 59 Temple Place, Suite 330,
23 * Boston, MA 02111-1307, USA.
24 */
25
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
30 #include <glib.h>
31 #include <stdlib.h>
32 #include <string.h>
33
34 #include "gtksourceundomanager.h"
35 #include "gtksourceview-marshal.h"
36
37
38 #define DEFAULT_MAX_UNDO_LEVELS 25
39
40
41 typedef struct _GtkSourceUndoAction GtkSourceUndoAction;
42 typedef struct _GtkSourceUndoInsertAction GtkSourceUndoInsertAction;
43 typedef struct _GtkSourceUndoDeleteAction GtkSourceUndoDeleteAction;
44
45 typedef enum {
46 GTK_SOURCE_UNDO_ACTION_INSERT,
47 GTK_SOURCE_UNDO_ACTION_DELETE
48 } GtkSourceUndoActionType;
49
50 /*
51 * We use offsets instead of GtkTextIters because the last ones
52 * require to much memory in this context without giving us any advantage.
53 */
54
55 struct _GtkSourceUndoInsertAction
56 {
57 gint pos;
58 gchar *text;
59 gint length;
60 gint chars;
61 };
62
63 struct _GtkSourceUndoDeleteAction
64 {
65 gint start;
66 gint end;
67 gchar *text;
68 gboolean forward;
69 };
70
71 struct _GtkSourceUndoAction
72 {
73 GtkSourceUndoActionType action_type;
74
75 union {
76 GtkSourceUndoInsertAction insert;
77 GtkSourceUndoDeleteAction delete;
78 } action;
79
80 gint order_in_group;
81
82 /* It is TRUE whether the action can be merged with the following action. */
83 guint mergeable : 1;
84
85 /* It is TRUE whether the action is marked as "modified".
86 * An action is marked as "modified" if it changed the
87 * state of the buffer from "not modified" to "modified". Only the first
88 * action of a group can be marked as modified.
89 * There can be a single action marked as "modified" in the actions list.
90 */
91 guint modified : 1;
92 };
93
94 /* INVALID is a pointer to an invalid action */
95 #define INVALID ((void *) "IA")
96
97 struct _GtkSourceUndoManagerPrivate
98 {
99 GtkTextBuffer *document;
100
101 GList* actions;
102 gint next_redo;
103
104 gint actions_in_current_group;
105
106 gint running_not_undoable_actions;
107
108 gint num_of_groups;
109
110 gint max_undo_levels;
111
112 guint can_undo : 1;
113 guint can_redo : 1;
114
115 /* It is TRUE whether, while undoing an action of the current group (with order_in_group > 1),
116 * the state of the buffer changed from "not modified" to "modified".
117 */
118 guint modified_undoing_group : 1;
119
120 /* Pointer to the action (in the action list) marked as "modified".
121 * It is NULL when no action is marked as "modified".
122 * It is INVALID when the action marked as "modified" has been removed
123 * from the action list (freeing the list or resizing it) */
124 GtkSourceUndoAction *modified_action;
125 };
126
127 enum {
128 CAN_UNDO,
129 CAN_REDO,
130 LAST_SIGNAL
131 };
132
133 static void gtk_source_undo_manager_class_init (GtkSourceUndoManagerClass *klass);
134 static void gtk_source_undo_manager_init (GtkSourceUndoManager *um);
135 static void gtk_source_undo_manager_finalize (GObject *object);
136
137 static void gtk_source_undo_manager_insert_text_handler (GtkTextBuffer *buffer,
138 GtkTextIter *pos,
139 const gchar *text,
140 gint length,
141 GtkSourceUndoManager *um);
142 static void gtk_source_undo_manager_delete_range_handler (GtkTextBuffer *buffer,
143 GtkTextIter *start,
144 GtkTextIter *end,
145 GtkSourceUndoManager *um);
146 static void gtk_source_undo_manager_begin_user_action_handler (GtkTextBuffer *buffer,
147 GtkSourceUndoManager *um);
148 static void gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer *buffer,
149 GtkSourceUndoManager *um);
150
151 static void gtk_source_undo_manager_free_action_list (GtkSourceUndoManager *um);
152
153 static void gtk_source_undo_manager_add_action (GtkSourceUndoManager *um,
154 const GtkSourceUndoAction *undo_action);
155 static void gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager *um,
156 gint n);
157 static void gtk_source_undo_manager_check_list_size (GtkSourceUndoManager *um);
158
159 static gboolean gtk_source_undo_manager_merge_action (GtkSourceUndoManager *um,
160 const GtkSourceUndoAction *undo_action);
161
162 static GObjectClass *parent_class = NULL;
163 static guint undo_manager_signals [LAST_SIGNAL] = { 0 };
164
165 GType
166 gtk_source_undo_manager_get_type (void)
167 {
168 static GType undo_manager_type = 0;
169
170 if (undo_manager_type == 0)
171 {
172 static const GTypeInfo our_info =
173 {
174 sizeof (GtkSourceUndoManagerClass),
175 NULL, /* base_init */
176 NULL, /* base_finalize */
177 (GClassInitFunc) gtk_source_undo_manager_class_init,
178 NULL, /* class_finalize */
179 NULL, /* class_data */
180 sizeof (GtkSourceUndoManager),
181 0, /* n_preallocs */
182 (GInstanceInitFunc) gtk_source_undo_manager_init
183 };
184
185 undo_manager_type = g_type_register_static (G_TYPE_OBJECT,
186 "GtkSourceUndoManager",
187 &our_info,
188 0);
189 }
190
191 return undo_manager_type;
192 }
193
194 static void
195 gtk_source_undo_manager_class_init (GtkSourceUndoManagerClass *klass)
196 {
197 GObjectClass *object_class = G_OBJECT_CLASS (klass);
198
199 parent_class = g_type_class_peek_parent (klass);
200
201 object_class->finalize = gtk_source_undo_manager_finalize;
202
203 klass->can_undo = NULL;
204 klass->can_redo = NULL;
205
206 undo_manager_signals[CAN_UNDO] =
207 g_signal_new ("can_undo",
208 G_OBJECT_CLASS_TYPE (object_class),
209 G_SIGNAL_RUN_LAST,
210 G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_undo),
211 NULL, NULL,
212 gtksourceview_marshal_VOID__BOOLEAN,
213 G_TYPE_NONE,
214 1,
215 G_TYPE_BOOLEAN);
216
217 undo_manager_signals[CAN_REDO] =
218 g_signal_new ("can_redo",
219 G_OBJECT_CLASS_TYPE (object_class),
220 G_SIGNAL_RUN_LAST,
221 G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_redo),
222 NULL, NULL,
223 gtksourceview_marshal_VOID__BOOLEAN,
224 G_TYPE_NONE,
225 1,
226 G_TYPE_BOOLEAN);
227 }
228
229 static void
230 gtk_source_undo_manager_init (GtkSourceUndoManager *um)
231 {
232 um->priv = g_new0 (GtkSourceUndoManagerPrivate, 1);
233
234 um->priv->actions = NULL;
235 um->priv->next_redo = 0;
236
237 um->priv->can_undo = FALSE;
238 um->priv->can_redo = FALSE;
239
240 um->priv->running_not_undoable_actions = 0;
241
242 um->priv->num_of_groups = 0;
243
244 um->priv->max_undo_levels = DEFAULT_MAX_UNDO_LEVELS;
245
246 um->priv->modified_action = NULL;
247
248 um->priv->modified_undoing_group = FALSE;
249 }
250
251 static void
252 gtk_source_undo_manager_finalize (GObject *object)
253 {
254 GtkSourceUndoManager *um;
255
256 g_return_if_fail (object != NULL);
257 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (object));
258
259 um = GTK_SOURCE_UNDO_MANAGER (object);
260
261 g_return_if_fail (um->priv != NULL);
262
263 if (um->priv->actions != NULL)
264 {
265 gtk_source_undo_manager_free_action_list (um);
266 }
267
268 g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
269 G_CALLBACK (gtk_source_undo_manager_delete_range_handler),
270 um);
271
272 g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
273 G_CALLBACK (gtk_source_undo_manager_insert_text_handler),
274 um);
275
276 g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
277 G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler),
278 um);
279
280 g_free (um->priv);
281
282 G_OBJECT_CLASS (parent_class)->finalize (object);
283 }
284
285 GtkSourceUndoManager*
286 gtk_source_undo_manager_new (GtkTextBuffer* buffer)
287 {
288 GtkSourceUndoManager *um;
289
290 um = GTK_SOURCE_UNDO_MANAGER (g_object_new (GTK_SOURCE_TYPE_UNDO_MANAGER, NULL));
291
292 g_return_val_if_fail (um->priv != NULL, NULL);
293 um->priv->document = buffer;
294
295 g_signal_connect (G_OBJECT (buffer), "insert_text",
296 G_CALLBACK (gtk_source_undo_manager_insert_text_handler),
297 um);
298
299 g_signal_connect (G_OBJECT (buffer), "delete_range",
300 G_CALLBACK (gtk_source_undo_manager_delete_range_handler),
301 um);
302
303 g_signal_connect (G_OBJECT (buffer), "begin_user_action",
304 G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler),
305 um);
306
307 g_signal_connect (G_OBJECT (buffer), "modified_changed",
308 G_CALLBACK (gtk_source_undo_manager_modified_changed_handler),
309 um);
310 return um;
311 }
312
313 void
314 gtk_source_undo_manager_begin_not_undoable_action (GtkSourceUndoManager *um)
315 {
316 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
317 g_return_if_fail (um->priv != NULL);
318
319 ++um->priv->running_not_undoable_actions;
320 }
321
322 static void
323 gtk_source_undo_manager_end_not_undoable_action_internal (GtkSourceUndoManager *um)
324 {
325 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
326 g_return_if_fail (um->priv != NULL);
327
328 g_return_if_fail (um->priv->running_not_undoable_actions > 0);
329
330 --um->priv->running_not_undoable_actions;
331 }
332
333 void
334 gtk_source_undo_manager_end_not_undoable_action (GtkSourceUndoManager *um)
335 {
336 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
337 g_return_if_fail (um->priv != NULL);
338
339 gtk_source_undo_manager_end_not_undoable_action_internal (um);
340
341 if (um->priv->running_not_undoable_actions == 0)
342 {
343 gtk_source_undo_manager_free_action_list (um);
344
345 um->priv->next_redo = -1;
346
347 if (um->priv->can_undo)
348 {
349 um->priv->can_undo = FALSE;
350 g_signal_emit (G_OBJECT (um),
351 undo_manager_signals [CAN_UNDO],
352 0,
353 FALSE);
354 }
355
356 if (um->priv->can_redo)
357 {
358 um->priv->can_redo = FALSE;
359 g_signal_emit (G_OBJECT (um),
360 undo_manager_signals [CAN_REDO],
361 0,
362 FALSE);
363 }
364 }
365 }
366
367 gboolean
368 gtk_source_undo_manager_can_undo (const GtkSourceUndoManager *um)
369 {
370 g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE);
371 g_return_val_if_fail (um->priv != NULL, FALSE);
372
373 return um->priv->can_undo;
374 }
375
376 gboolean
377 gtk_source_undo_manager_can_redo (const GtkSourceUndoManager *um)
378 {
379 g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE);
380 g_return_val_if_fail (um->priv != NULL, FALSE);
381
382 return um->priv->can_redo;
383 }
384
385 static void
386 set_cursor (GtkTextBuffer *buffer, gint cursor)
387 {
388 GtkTextIter iter;
389
390 /* Place the cursor at the requested position */
391 gtk_text_buffer_get_iter_at_offset (buffer, &iter, cursor);
392 gtk_text_buffer_place_cursor (buffer, &iter);
393 }
394
395 static void
396 insert_text (GtkTextBuffer *buffer, gint pos, const gchar *text, gint len)
397 {
398 GtkTextIter iter;
399
400 gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos);
401 gtk_text_buffer_insert (buffer, &iter, text, len);
402 }
403
404 static void
405 delete_text (GtkTextBuffer *buffer, gint start, gint end)
406 {
407 GtkTextIter start_iter;
408 GtkTextIter end_iter;
409
410 gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
411
412 if (end < 0)
413 gtk_text_buffer_get_end_iter (buffer, &end_iter);
414 else
415 gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
416
417 gtk_text_buffer_delete (buffer, &start_iter, &end_iter);
418 }
419
420 static gchar*
421 get_chars (GtkTextBuffer *buffer, gint start, gint end)
422 {
423 GtkTextIter start_iter;
424 GtkTextIter end_iter;
425
426 gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
427
428 if (end < 0)
429 gtk_text_buffer_get_end_iter (buffer, &end_iter);
430 else
431 gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
432
433 return gtk_text_buffer_get_slice (buffer, &start_iter, &end_iter, TRUE);
434 }
435
436 void
437 gtk_source_undo_manager_undo (GtkSourceUndoManager *um)
438 {
439 GtkSourceUndoAction *undo_action;
440 gboolean modified = FALSE;
441
442 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
443 g_return_if_fail (um->priv != NULL);
444 g_return_if_fail (um->priv->can_undo);
445
446 um->priv->modified_undoing_group = FALSE;
447
448 gtk_source_undo_manager_begin_not_undoable_action (um);
449
450 do
451 {
452 undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo + 1);
453 g_return_if_fail (undo_action != NULL);
454
455 /* undo_action->modified can be TRUE only if undo_action->order_in_group <= 1 */
456 g_return_if_fail ((undo_action->order_in_group <= 1) ||
457 ((undo_action->order_in_group > 1) && !undo_action->modified));
458
459 if (undo_action->order_in_group <= 1)
460 {
461 /* Set modified to TRUE only if the buffer did not change its state from
462 * "not modified" to "modified" undoing an action (with order_in_group > 1)
463 * in current group. */
464 modified = (undo_action->modified && !um->priv->modified_undoing_group);
465 }
466
467 switch (undo_action->action_type)
468 {
469 case GTK_SOURCE_UNDO_ACTION_DELETE:
470 insert_text (
471 um->priv->document,
472 undo_action->action.delete.start,
473 undo_action->action.delete.text,
474 strlen (undo_action->action.delete.text));
475
476 if (undo_action->action.delete.forward)
477 set_cursor (
478 um->priv->document,
479 undo_action->action.delete.start);
480 else
481 set_cursor (
482 um->priv->document,
483 undo_action->action.delete.end);
484
485 break;
486
487 case GTK_SOURCE_UNDO_ACTION_INSERT:
488 delete_text (
489 um->priv->document,
490 undo_action->action.insert.pos,
491 undo_action->action.insert.pos +
492 undo_action->action.insert.chars);
493
494 set_cursor (
495 um->priv->document,
496 undo_action->action.insert.pos);
497 break;
498
499 default:
500 /* Unknown action type. */
501 g_return_if_reached ();
502 }
503
504 ++um->priv->next_redo;
505
506 } while (undo_action->order_in_group > 1);
507
508 if (modified)
509 {
510 --um->priv->next_redo;
511 gtk_text_buffer_set_modified (um->priv->document, FALSE);
512 ++um->priv->next_redo;
513 }
514
515 gtk_source_undo_manager_end_not_undoable_action_internal (um);
516
517 um->priv->modified_undoing_group = FALSE;
518
519 if (!um->priv->can_redo)
520 {
521 um->priv->can_redo = TRUE;
522 g_signal_emit (G_OBJECT (um),
523 undo_manager_signals [CAN_REDO],
524 0,
525 TRUE);
526 }
527
528 if (um->priv->next_redo >= (gint)(g_list_length (um->priv->actions) - 1))
529 {
530 um->priv->can_undo = FALSE;
531 g_signal_emit (G_OBJECT (um),
532 undo_manager_signals [CAN_UNDO],
533 0,
534 FALSE);
535 }
536 }
537
538 void
539 gtk_source_undo_manager_redo (GtkSourceUndoManager *um)
540 {
541 GtkSourceUndoAction *undo_action;
542 gboolean modified = FALSE;
543
544 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
545 g_return_if_fail (um->priv != NULL);
546 g_return_if_fail (um->priv->can_redo);
547
548 undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo);
549 g_return_if_fail (undo_action != NULL);
550
551 gtk_source_undo_manager_begin_not_undoable_action (um);
552
553 do
554 {
555 if (undo_action->modified)
556 {
557 g_return_if_fail (undo_action->order_in_group <= 1);
558 modified = TRUE;
559 }
560
561 --um->priv->next_redo;
562
563 switch (undo_action->action_type)
564 {
565 case GTK_SOURCE_UNDO_ACTION_DELETE:
566 delete_text (
567 um->priv->document,
568 undo_action->action.delete.start,
569 undo_action->action.delete.end);
570
571 set_cursor (
572 um->priv->document,
573 undo_action->action.delete.start);
574
575 break;
576
577 case GTK_SOURCE_UNDO_ACTION_INSERT:
578 set_cursor (
579 um->priv->document,
580 undo_action->action.insert.pos);
581
582 insert_text (
583 um->priv->document,
584 undo_action->action.insert.pos,
585 undo_action->action.insert.text,
586 undo_action->action.insert.length);
587
588 break;
589
590 default:
591 /* Unknown action type */
592 ++um->priv->next_redo;
593 g_return_if_reached ();
594 }
595
596 if (um->priv->next_redo < 0)
597 undo_action = NULL;
598 else
599 undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo);
600
601 } while ((undo_action != NULL) && (undo_action->order_in_group > 1));
602
603 if (modified)
604 {
605 ++um->priv->next_redo;
606 gtk_text_buffer_set_modified (um->priv->document, FALSE);
607 --um->priv->next_redo;
608 }
609
610 gtk_source_undo_manager_end_not_undoable_action_internal (um);
611
612 if (um->priv->next_redo < 0)
613 {
614 um->priv->can_redo = FALSE;
615 g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE);
616 }
617
618 if (!um->priv->can_undo)
619 {
620 um->priv->can_undo = TRUE;
621 g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, TRUE);
622 }
623 }
624
625 static void
626 gtk_source_undo_action_free (GtkSourceUndoAction *action)
627 {
628 if (action == NULL)
629 return;
630
631 if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
632 g_free (action->action.insert.text);
633 else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
634 g_free (action->action.delete.text);
635 else
636 g_return_if_reached ();
637
638 g_free (action);
639 }
640
641 static void
642 gtk_source_undo_manager_free_action_list (GtkSourceUndoManager *um)
643 {
644 GList *l;
645
646 l = um->priv->actions;
647
648 while (l != NULL)
649 {
650 GtkSourceUndoAction *action = l->data;
651
652 if (action->order_in_group == 1)
653 --um->priv->num_of_groups;
654
655 if (action->modified)
656 um->priv->modified_action = INVALID;
657
658 gtk_source_undo_action_free (action);
659
660 l = g_list_next (l);
661 }
662
663 g_list_free (um->priv->actions);
664 um->priv->actions = NULL;
665 }
666
667 static void
668 gtk_source_undo_manager_insert_text_handler (GtkTextBuffer *buffer,
669 GtkTextIter *pos,
670 const gchar *text,
671 gint length,
672 GtkSourceUndoManager *um)
673 {
674 GtkSourceUndoAction undo_action;
675
676 if (um->priv->running_not_undoable_actions > 0)
677 return;
678
679 g_return_if_fail (strlen (text) >= (guint)length);
680
681 undo_action.action_type = GTK_SOURCE_UNDO_ACTION_INSERT;
682
683 undo_action.action.insert.pos = gtk_text_iter_get_offset (pos);
684 undo_action.action.insert.text = (gchar*) text;
685 undo_action.action.insert.length = length;
686 undo_action.action.insert.chars = g_utf8_strlen (text, length);
687
688 if ((undo_action.action.insert.chars > 1) || (g_utf8_get_char (text) == '\n'))
689
690 undo_action.mergeable = FALSE;
691 else
692 undo_action.mergeable = TRUE;
693
694 undo_action.modified = FALSE;
695
696 gtk_source_undo_manager_add_action (um, &undo_action);
697 }
698
699 static void
700 gtk_source_undo_manager_delete_range_handler (GtkTextBuffer *buffer,
701 GtkTextIter *start,
702 GtkTextIter *end,
703 GtkSourceUndoManager *um)
704 {
705 GtkSourceUndoAction undo_action;
706 GtkTextIter insert_iter;
707
708 if (um->priv->running_not_undoable_actions > 0)
709 return;
710
711 undo_action.action_type = GTK_SOURCE_UNDO_ACTION_DELETE;
712
713 gtk_text_iter_order (start, end);
714
715 undo_action.action.delete.start = gtk_text_iter_get_offset (start);
716 undo_action.action.delete.end = gtk_text_iter_get_offset (end);
717
718 undo_action.action.delete.text = get_chars (
719 buffer,
720 undo_action.action.delete.start,
721 undo_action.action.delete.end);
722
723 /* figure out if the user used the Delete or the Backspace key */
724 gtk_text_buffer_get_iter_at_mark (buffer, &insert_iter,
725 gtk_text_buffer_get_insert (buffer));
726 if (gtk_text_iter_get_offset (&insert_iter) <= undo_action.action.delete.start)
727 undo_action.action.delete.forward = TRUE;
728 else
729 undo_action.action.delete.forward = FALSE;
730
731 if (((undo_action.action.delete.end - undo_action.action.delete.start) > 1) ||
732 (g_utf8_get_char (undo_action.action.delete.text ) == '\n'))
733 undo_action.mergeable = FALSE;
734 else
735 undo_action.mergeable = TRUE;
736
737 undo_action.modified = FALSE;
738
739 gtk_source_undo_manager_add_action (um, &undo_action);
740
741 g_free (undo_action.action.delete.text);
742
743 }
744
745 static void
746 gtk_source_undo_manager_begin_user_action_handler (GtkTextBuffer *buffer, GtkSourceUndoManager *um)
747 {
748 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
749 g_return_if_fail (um->priv != NULL);
750
751 if (um->priv->running_not_undoable_actions > 0)
752 return;
753
754 um->priv->actions_in_current_group = 0;
755 }
756
757 static void
758 gtk_source_undo_manager_add_action (GtkSourceUndoManager *um,
759 const GtkSourceUndoAction *undo_action)
760 {
761 GtkSourceUndoAction* action;
762
763 if (um->priv->next_redo >= 0)
764 {
765 gtk_source_undo_manager_free_first_n_actions (um, um->priv->next_redo + 1);
766 }
767
768 um->priv->next_redo = -1;
769
770 if (!gtk_source_undo_manager_merge_action (um, undo_action))
771 {
772 action = g_new (GtkSourceUndoAction, 1);
773 *action = *undo_action;
774
775 if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
776 action->action.insert.text = g_strdup (undo_action->action.insert.text);
777 else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
778 action->action.delete.text = g_strdup (undo_action->action.delete.text);
779 else
780 {
781 g_free (action);
782 g_return_if_reached ();
783 }
784
785 ++um->priv->actions_in_current_group;
786 action->order_in_group = um->priv->actions_in_current_group;
787
788 if (action->order_in_group == 1)
789 ++um->priv->num_of_groups;
790
791 um->priv->actions = g_list_prepend (um->priv->actions, action);
792 }
793
794 gtk_source_undo_manager_check_list_size (um);
795
796 if (!um->priv->can_undo)
797 {
798 um->priv->can_undo = TRUE;
799 g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, TRUE);
800 }
801
802 if (um->priv->can_redo)
803 {
804 um->priv->can_redo = FALSE;
805 g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE);
806 }
807 }
808
809 static void
810 gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager *um,
811 gint n)
812 {
813 gint i;
814
815 if (um->priv->actions == NULL)
816 return;
817
818 for (i = 0; i < n; i++)
819 {
820 GtkSourceUndoAction *action = g_list_first (um->priv->actions)->data;
821
822 if (action->order_in_group == 1)
823 --um->priv->num_of_groups;
824
825 if (action->modified)
826 um->priv->modified_action = INVALID;
827
828 gtk_source_undo_action_free (action);
829
830 um->priv->actions = g_list_delete_link (um->priv->actions,
831 um->priv->actions);
832
833 if (um->priv->actions == NULL)
834 return;
835 }
836 }
837
838 static void
839 gtk_source_undo_manager_check_list_size (GtkSourceUndoManager *um)
840 {
841 gint undo_levels;
842
843 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
844 g_return_if_fail (um->priv != NULL);
845
846 undo_levels = gtk_source_undo_manager_get_max_undo_levels (um);
847
848 if (undo_levels < 1)
849 return;
850
851 if (um->priv->num_of_groups > undo_levels)
852 {
853 GtkSourceUndoAction *undo_action;
854 GList *last;
855
856 last = g_list_last (um->priv->actions);
857 undo_action = (GtkSourceUndoAction*) last->data;
858
859 do
860 {
861 GList *tmp;
862
863 if (undo_action->order_in_group == 1)
864 --um->priv->num_of_groups;
865
866 if (undo_action->modified)
867 um->priv->modified_action = INVALID;
868
869 gtk_source_undo_action_free (undo_action);
870
871 tmp = g_list_previous (last);
872 um->priv->actions = g_list_delete_link (um->priv->actions, last);
873 last = tmp;
874 g_return_if_fail (last != NULL);
875
876 undo_action = (GtkSourceUndoAction*) last->data;
877
878 } while ((undo_action->order_in_group > 1) ||
879 (um->priv->num_of_groups > undo_levels));
880 }
881 }
882
883 /**
884 * gtk_source_undo_manager_merge_action:
885 * @um: a #GtkSourceUndoManager.
886 * @undo_action: a #GtkSourceUndoAction.
887 *
888 * This function tries to merge the undo action at the top of
889 * the stack with a new undo action. So when we undo for example
890 * typing, we can undo the whole word and not each letter by itself.
891 *
892 * Return Value: %TRUE is merge was sucessful, %FALSE otherwise.²
893 **/
894 static gboolean
895 gtk_source_undo_manager_merge_action (GtkSourceUndoManager *um,
896 const GtkSourceUndoAction *undo_action)
897 {
898 GtkSourceUndoAction *last_action;
899
900 g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE);
901 g_return_val_if_fail (um->priv != NULL, FALSE);
902
903 if (um->priv->actions == NULL)
904 return FALSE;
905
906 last_action = (GtkSourceUndoAction*) g_list_nth_data (um->priv->actions, 0);
907
908 if (!last_action->mergeable)
909 return FALSE;
910
911 if ((!undo_action->mergeable) ||
912 (undo_action->action_type != last_action->action_type))
913 {
914 last_action->mergeable = FALSE;
915 return FALSE;
916 }
917
918 if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
919 {
920 if ((last_action->action.delete.forward != undo_action->action.delete.forward) ||
921 ((last_action->action.delete.start != undo_action->action.delete.start) &&
922 (last_action->action.delete.start != undo_action->action.delete.end)))
923 {
924 last_action->mergeable = FALSE;
925 return FALSE;
926 }
927
928 if (last_action->action.delete.start == undo_action->action.delete.start)
929 {
930 gchar *str;
931
932 #define L (last_action->action.delete.end - last_action->action.delete.start - 1)
933 #define g_utf8_get_char_at(p,i) g_utf8_get_char(g_utf8_offset_to_pointer((p),(i)))
934
935 /* Deleted with the delete key */
936 if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') &&
937 (g_utf8_get_char (undo_action->action.delete.text) != '\t') &&
938 ((g_utf8_get_char_at (last_action->action.delete.text, L) == ' ') ||
939 (g_utf8_get_char_at (last_action->action.delete.text, L) == '\t')))
940 {
941 last_action->mergeable = FALSE;
942 return FALSE;
943 }
944
945 str = g_strdup_printf ("%s%s", last_action->action.delete.text,
946 undo_action->action.delete.text);
947
948 g_free (last_action->action.delete.text);
949 last_action->action.delete.end += (undo_action->action.delete.end -
950 undo_action->action.delete.start);
951 last_action->action.delete.text = str;
952 }
953 else
954 {
955 gchar *str;
956
957 /* Deleted with the backspace key */
958 if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') &&
959 (g_utf8_get_char (undo_action->action.delete.text) != '\t') &&
960 ((g_utf8_get_char (last_action->action.delete.text) == ' ') ||
961 (g_utf8_get_char (last_action->action.delete.text) == '\t')))
962 {
963 last_action->mergeable = FALSE;
964 return FALSE;
965 }
966
967 str = g_strdup_printf ("%s%s", undo_action->action.delete.text,
968 last_action->action.delete.text);
969
970 g_free (last_action->action.delete.text);
971 last_action->action.delete.start = undo_action->action.delete.start;
972 last_action->action.delete.text = str;
973 }
974 }
975 else if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
976 {
977 gchar* str;
978
979 #define I (last_action->action.insert.chars - 1)
980
981 if ((undo_action->action.insert.pos !=
982 (last_action->action.insert.pos + last_action->action.insert.chars)) ||
983 ((g_utf8_get_char (undo_action->action.insert.text) != ' ') &&
984 (g_utf8_get_char (undo_action->action.insert.text) != '\t') &&
985 ((g_utf8_get_char_at (last_action->action.insert.text, I) == ' ') ||
986 (g_utf8_get_char_at (last_action->action.insert.text, I) == '\t')))
987 )
988 {
989 last_action->mergeable = FALSE;
990 return FALSE;
991 }
992
993 str = g_strdup_printf ("%s%s", last_action->action.insert.text,
994 undo_action->action.insert.text);
995
996 g_free (last_action->action.insert.text);
997 last_action->action.insert.length += undo_action->action.insert.length;
998 last_action->action.insert.text = str;
999 last_action->action.insert.chars += undo_action->action.insert.chars;
1000
1001 }
1002 else
1003 /* Unknown action inside undo merge encountered */
1004 g_return_val_if_reached (TRUE);
1005
1006 return TRUE;
1007 }
1008
1009 gint
1010 gtk_source_undo_manager_get_max_undo_levels (GtkSourceUndoManager *um)
1011 {
1012 g_return_val_if_fail (um != NULL, 0);
1013 g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), 0);
1014
1015 return um->priv->max_undo_levels;
1016 }
1017
1018 void
1019 gtk_source_undo_manager_set_max_undo_levels (GtkSourceUndoManager *um,
1020 gint max_undo_levels)
1021 {
1022 gint old_levels;
1023
1024 g_return_if_fail (um != NULL);
1025 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
1026
1027 old_levels = um->priv->max_undo_levels;
1028 um->priv->max_undo_levels = max_undo_levels;
1029
1030 if (max_undo_levels < 1)
1031 return;
1032
1033 if (old_levels > max_undo_levels)
1034 {
1035 /* strip redo actions first */
1036 while (um->priv->next_redo >= 0 && (um->priv->num_of_groups > max_undo_levels))
1037 {
1038 gtk_source_undo_manager_free_first_n_actions (um, 1);
1039 um->priv->next_redo--;
1040 }
1041
1042 /* now remove undo actions if necessary */
1043 gtk_source_undo_manager_check_list_size (um);
1044
1045 /* emit "can_undo" and/or "can_redo" if appropiate */
1046 if (um->priv->next_redo < 0 && um->priv->can_redo)
1047 {
1048 um->priv->can_redo = FALSE;
1049 g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE);
1050 }
1051
1052 if (um->priv->can_undo &&
1053 um->priv->next_redo >= (gint)(g_list_length (um->priv->actions) - 1))
1054 {
1055 um->priv->can_undo = FALSE;
1056 g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, FALSE);
1057 }
1058 }
1059 }
1060
1061 static void
1062 gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer *buffer,
1063 GtkSourceUndoManager *um)
1064 {
1065 GtkSourceUndoAction *action;
1066 GList *list;
1067
1068 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
1069 g_return_if_fail (um->priv != NULL);
1070
1071 if (um->priv->actions == NULL)
1072 return;
1073
1074 list = g_list_nth (um->priv->actions, um->priv->next_redo + 1);
1075
1076 if (list != NULL)
1077 action = (GtkSourceUndoAction*) list->data;
1078 else
1079 action = NULL;
1080
1081 if (gtk_text_buffer_get_modified (buffer) == FALSE)
1082 {
1083 if (action != NULL)
1084 action->mergeable = FALSE;
1085
1086 if (um->priv->modified_action != NULL)
1087 {
1088 if (um->priv->modified_action != INVALID)
1089 um->priv->modified_action->modified = FALSE;
1090
1091 um->priv->modified_action = NULL;
1092 }
1093
1094 return;
1095 }
1096
1097 if (action == NULL)
1098 {
1099 g_return_if_fail (um->priv->running_not_undoable_actions > 0);
1100
1101 return;
1102 }
1103
1104 /* gtk_text_buffer_get_modified (buffer) == TRUE */
1105
1106 g_return_if_fail (um->priv->modified_action == NULL);
1107
1108 if (action->order_in_group > 1)
1109 um->priv->modified_undoing_group = TRUE;
1110
1111 while (action->order_in_group > 1)
1112 {
1113 list = g_list_next (list);
1114 g_return_if_fail (list != NULL);
1115
1116 action = (GtkSourceUndoAction*) list->data;
1117 g_return_if_fail (action != NULL);
1118 }
1119
1120 action->modified = TRUE;
1121 um->priv->modified_action = action;
1122 }