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