Mercurial > pidgin.yaz
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 } |