| 2 /* |
2 /* |
| 3 * gtksourceundomanager.c |
3 * gtksourceundomanager.c |
| 4 * This file is part of GtkSourceView |
4 * This file is part of GtkSourceView |
| 5 * |
5 * |
| 6 * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence |
6 * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence |
| 7 * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi |
7 * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi |
| 8 * Copyright (C) 2002-2005 Paolo Maggi |
8 * Copyright (C) 2002-2005 Paolo Maggi |
| 9 * |
9 * |
| 10 * This program is free software; you can redistribute it and/or modify |
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 |
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 |
12 * the Free Software Foundation; either version 2 of the License, or |
| 13 * (at your option) any later version. |
13 * (at your option) any later version. |
| 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 18 * GNU General Public License for more details. |
18 * GNU General Public License for more details. |
| 19 * |
19 * |
| 20 * You should have received a copy of the GNU General Public License |
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 |
21 * along with this program; if not, write to the Free Software |
| 22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, |
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| 23 * Boston, MA 02111-1301, USA. |
23 * Boston, MA 02111-1301, USA. |
| 24 */ |
24 */ |
| 25 |
25 |
| 26 #ifdef HAVE_CONFIG_H |
26 #ifdef HAVE_CONFIG_H |
| 27 #include <config.h> |
27 #include <config.h> |
| 104 #define INVALID ((void *) "IA") |
104 #define INVALID ((void *) "IA") |
| 105 |
105 |
| 106 struct _GtkSourceUndoManagerPrivate |
106 struct _GtkSourceUndoManagerPrivate |
| 107 { |
107 { |
| 108 GtkTextBuffer *document; |
108 GtkTextBuffer *document; |
| 109 |
109 |
| 110 GList* actions; |
110 GList* actions; |
| 111 gint next_redo; |
111 gint next_redo; |
| 112 |
112 |
| 113 gint actions_in_current_group; |
113 gint actions_in_current_group; |
| 114 |
114 |
| 115 gint running_not_undoable_actions; |
115 gint running_not_undoable_actions; |
| 116 |
116 |
| 117 gint num_of_groups; |
117 gint num_of_groups; |
| 118 |
118 |
| 119 gint max_undo_levels; |
119 gint max_undo_levels; |
| 120 |
120 |
| 121 guint can_undo : 1; |
121 guint can_undo : 1; |
| 122 guint can_redo : 1; |
122 guint can_redo : 1; |
| 123 |
123 |
| 124 /* It is TRUE whether, while undoing an action of the current group (with order_in_group > 1), |
124 /* It is TRUE whether, while undoing an action of the current group (with order_in_group > 1), |
| 125 * the state of the buffer changed from "not modified" to "modified". |
125 * the state of the buffer changed from "not modified" to "modified". |
| 126 */ |
126 */ |
| 127 guint modified_undoing_group : 1; |
127 guint modified_undoing_group : 1; |
| 128 |
128 |
| 129 /* Pointer to the action (in the action list) marked as "modified". |
129 /* Pointer to the action (in the action list) marked as "modified". |
| 130 * It is NULL when no action is marked as "modified". |
130 * It is NULL when no action is marked as "modified". |
| 131 * It is INVALID when the action marked as "modified" has been removed |
131 * It is INVALID when the action marked as "modified" has been removed |
| 132 * from the action list (freeing the list or resizing it) */ |
132 * from the action list (freeing the list or resizing it) */ |
| 133 GtkSourceUndoAction *modified_action; |
133 GtkSourceUndoAction *modified_action; |
| 134 }; |
134 }; |
| 135 |
135 |
| 136 enum { |
136 enum { |
| 141 |
141 |
| 142 static void gtk_source_undo_manager_class_init (GtkSourceUndoManagerClass *klass); |
142 static void gtk_source_undo_manager_class_init (GtkSourceUndoManagerClass *klass); |
| 143 static void gtk_source_undo_manager_init (GtkSourceUndoManager *um); |
143 static void gtk_source_undo_manager_init (GtkSourceUndoManager *um); |
| 144 static void gtk_source_undo_manager_finalize (GObject *object); |
144 static void gtk_source_undo_manager_finalize (GObject *object); |
| 145 |
145 |
| 146 static void gtk_source_undo_manager_insert_text_handler (GtkTextBuffer *buffer, |
146 static void gtk_source_undo_manager_insert_text_handler (GtkTextBuffer *buffer, |
| 147 GtkTextIter *pos, |
147 GtkTextIter *pos, |
| 148 const gchar *text, |
148 const gchar *text, |
| 149 gint length, |
149 gint length, |
| 150 GtkSourceUndoManager *um); |
150 GtkSourceUndoManager *um); |
| 151 static void gtk_source_undo_manager_insert_anchor_handler (GtkTextBuffer *buffer, |
151 static void gtk_source_undo_manager_insert_anchor_handler (GtkTextBuffer *buffer, |
| 152 GtkTextIter *pos, |
152 GtkTextIter *pos, |
| 153 GtkTextChildAnchor *anchor, |
153 GtkTextChildAnchor *anchor, |
| 154 GtkSourceUndoManager *um); |
154 GtkSourceUndoManager *um); |
| 155 static void gtk_source_undo_manager_delete_range_handler (GtkTextBuffer *buffer, |
155 static void gtk_source_undo_manager_delete_range_handler (GtkTextBuffer *buffer, |
| 156 GtkTextIter *start, |
156 GtkTextIter *start, |
| 157 GtkTextIter *end, |
157 GtkTextIter *end, |
| 158 GtkSourceUndoManager *um); |
158 GtkSourceUndoManager *um); |
| 159 static void gtk_source_undo_manager_begin_user_action_handler (GtkTextBuffer *buffer, |
159 static void gtk_source_undo_manager_begin_user_action_handler (GtkTextBuffer *buffer, |
| 160 GtkSourceUndoManager *um); |
160 GtkSourceUndoManager *um); |
| 161 static void gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer *buffer, |
161 static void gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer *buffer, |
| 162 GtkSourceUndoManager *um); |
162 GtkSourceUndoManager *um); |
| 163 |
163 |
| 164 static void gtk_source_undo_manager_free_action_list (GtkSourceUndoManager *um); |
164 static void gtk_source_undo_manager_free_action_list (GtkSourceUndoManager *um); |
| 165 |
165 |
| 166 static void gtk_source_undo_manager_add_action (GtkSourceUndoManager *um, |
166 static void gtk_source_undo_manager_add_action (GtkSourceUndoManager *um, |
| 167 const GtkSourceUndoAction *undo_action); |
167 const GtkSourceUndoAction *undo_action); |
| 168 static void gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager *um, |
168 static void gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager *um, |
| 169 gint n); |
169 gint n); |
| 170 static void gtk_source_undo_manager_check_list_size (GtkSourceUndoManager *um); |
170 static void gtk_source_undo_manager_check_list_size (GtkSourceUndoManager *um); |
| 171 |
171 |
| 172 static gboolean gtk_source_undo_manager_merge_action (GtkSourceUndoManager *um, |
172 static gboolean gtk_source_undo_manager_merge_action (GtkSourceUndoManager *um, |
| 173 const GtkSourceUndoAction *undo_action); |
173 const GtkSourceUndoAction *undo_action); |
| 174 |
174 |
| 175 static GObjectClass *parent_class = NULL; |
175 static GObjectClass *parent_class = NULL; |
| 176 static guint undo_manager_signals [LAST_SIGNAL] = { 0 }; |
176 static guint undo_manager_signals [LAST_SIGNAL] = { 0 }; |
| 177 |
177 |
| 267 { |
267 { |
| 268 GtkSourceUndoManager *um; |
268 GtkSourceUndoManager *um; |
| 269 |
269 |
| 270 g_return_if_fail (object != NULL); |
270 g_return_if_fail (object != NULL); |
| 271 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (object)); |
271 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (object)); |
| 272 |
272 |
| 273 um = GTK_SOURCE_UNDO_MANAGER (object); |
273 um = GTK_SOURCE_UNDO_MANAGER (object); |
| 274 |
274 |
| 275 g_return_if_fail (um->priv != NULL); |
275 g_return_if_fail (um->priv != NULL); |
| 276 |
276 |
| 277 if (um->priv->actions != NULL) |
277 if (um->priv->actions != NULL) |
| 278 { |
278 { |
| 279 gtk_source_undo_manager_free_action_list (um); |
279 gtk_source_undo_manager_free_action_list (um); |
| 280 } |
280 } |
| 281 |
281 |
| 282 g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), |
282 g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), |
| 283 G_CALLBACK (gtk_source_undo_manager_delete_range_handler), |
283 G_CALLBACK (gtk_source_undo_manager_delete_range_handler), |
| 284 um); |
284 um); |
| 285 |
285 |
| 286 g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), |
286 g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), |
| 287 G_CALLBACK (gtk_source_undo_manager_insert_text_handler), |
287 G_CALLBACK (gtk_source_undo_manager_insert_text_handler), |
| 288 um); |
288 um); |
| 289 |
289 |
| 290 g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), |
290 g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), |
| 291 G_CALLBACK (gtk_source_undo_manager_insert_anchor_handler), |
291 G_CALLBACK (gtk_source_undo_manager_insert_anchor_handler), |
| 292 um); |
292 um); |
| 293 |
293 |
| 294 g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), |
294 g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), |
| 295 G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler), |
295 G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler), |
| 296 um); |
296 um); |
| 297 |
297 |
| 298 g_free (um->priv); |
298 g_free (um->priv); |
| 299 |
299 |
| 300 G_OBJECT_CLASS (parent_class)->finalize (object); |
300 G_OBJECT_CLASS (parent_class)->finalize (object); |
| 309 |
309 |
| 310 g_return_val_if_fail (um->priv != NULL, NULL); |
310 g_return_val_if_fail (um->priv != NULL, NULL); |
| 311 um->priv->document = buffer; |
311 um->priv->document = buffer; |
| 312 |
312 |
| 313 g_signal_connect (G_OBJECT (buffer), "insert_text", |
313 g_signal_connect (G_OBJECT (buffer), "insert_text", |
| 314 G_CALLBACK (gtk_source_undo_manager_insert_text_handler), |
314 G_CALLBACK (gtk_source_undo_manager_insert_text_handler), |
| 315 um); |
315 um); |
| 316 |
316 |
| 317 g_signal_connect (G_OBJECT (buffer), "insert_child_anchor", |
317 g_signal_connect (G_OBJECT (buffer), "insert_child_anchor", |
| 318 G_CALLBACK (gtk_source_undo_manager_insert_anchor_handler), |
318 G_CALLBACK (gtk_source_undo_manager_insert_anchor_handler), |
| 319 um); |
319 um); |
| 320 |
320 |
| 321 g_signal_connect (G_OBJECT (buffer), "delete_range", |
321 g_signal_connect (G_OBJECT (buffer), "delete_range", |
| 322 G_CALLBACK (gtk_source_undo_manager_delete_range_handler), |
322 G_CALLBACK (gtk_source_undo_manager_delete_range_handler), |
| 323 um); |
323 um); |
| 324 |
324 |
| 325 g_signal_connect (G_OBJECT (buffer), "begin_user_action", |
325 g_signal_connect (G_OBJECT (buffer), "begin_user_action", |
| 326 G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler), |
326 G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler), |
| 327 um); |
327 um); |
| 328 |
328 |
| 329 g_signal_connect (G_OBJECT (buffer), "modified_changed", |
329 g_signal_connect (G_OBJECT (buffer), "modified_changed", |
| 330 G_CALLBACK (gtk_source_undo_manager_modified_changed_handler), |
330 G_CALLBACK (gtk_source_undo_manager_modified_changed_handler), |
| 331 um); |
331 um); |
| 332 return um; |
332 return um; |
| 333 } |
333 } |
| 334 |
334 |
| 335 void |
335 void |
| 336 gtk_source_undo_manager_begin_not_undoable_action (GtkSourceUndoManager *um) |
336 gtk_source_undo_manager_begin_not_undoable_action (GtkSourceUndoManager *um) |
| 337 { |
337 { |
| 338 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); |
338 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); |
| 339 g_return_if_fail (um->priv != NULL); |
339 g_return_if_fail (um->priv != NULL); |
| 340 |
340 |
| 341 ++um->priv->running_not_undoable_actions; |
341 ++um->priv->running_not_undoable_actions; |
| 342 } |
342 } |
| 343 |
343 |
| 344 static void |
344 static void |
| 345 gtk_source_undo_manager_end_not_undoable_action_internal (GtkSourceUndoManager *um) |
345 gtk_source_undo_manager_end_not_undoable_action_internal (GtkSourceUndoManager *um) |
| 346 { |
346 { |
| 347 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); |
347 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); |
| 348 g_return_if_fail (um->priv != NULL); |
348 g_return_if_fail (um->priv != NULL); |
| 349 |
349 |
| 350 g_return_if_fail (um->priv->running_not_undoable_actions > 0); |
350 g_return_if_fail (um->priv->running_not_undoable_actions > 0); |
| 351 |
351 |
| 352 --um->priv->running_not_undoable_actions; |
352 --um->priv->running_not_undoable_actions; |
| 353 } |
353 } |
| 354 |
354 |
| 355 void |
355 void |
| 356 gtk_source_undo_manager_end_not_undoable_action (GtkSourceUndoManager *um) |
356 gtk_source_undo_manager_end_not_undoable_action (GtkSourceUndoManager *um) |
| 357 { |
357 { |
| 358 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); |
358 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); |
| 359 g_return_if_fail (um->priv != NULL); |
359 g_return_if_fail (um->priv != NULL); |
| 360 |
360 |
| 361 gtk_source_undo_manager_end_not_undoable_action_internal (um); |
361 gtk_source_undo_manager_end_not_undoable_action_internal (um); |
| 362 |
362 |
| 363 if (um->priv->running_not_undoable_actions == 0) |
363 if (um->priv->running_not_undoable_actions == 0) |
| 364 { |
364 { |
| 365 gtk_source_undo_manager_free_action_list (um); |
365 gtk_source_undo_manager_free_action_list (um); |
| 366 |
366 |
| 367 um->priv->next_redo = -1; |
367 um->priv->next_redo = -1; |
| 368 |
368 |
| 369 if (um->priv->can_undo) |
369 if (um->priv->can_undo) |
| 370 { |
370 { |
| 371 um->priv->can_undo = FALSE; |
371 um->priv->can_undo = FALSE; |
| 372 g_signal_emit (G_OBJECT (um), |
372 g_signal_emit (G_OBJECT (um), |
| 373 undo_manager_signals [CAN_UNDO], |
373 undo_manager_signals [CAN_UNDO], |
| 374 0, |
374 0, |
| 375 FALSE); |
375 FALSE); |
| 376 } |
376 } |
| 377 |
377 |
| 378 if (um->priv->can_redo) |
378 if (um->priv->can_redo) |
| 379 { |
379 { |
| 380 um->priv->can_redo = FALSE; |
380 um->priv->can_redo = FALSE; |
| 381 g_signal_emit (G_OBJECT (um), |
381 g_signal_emit (G_OBJECT (um), |
| 382 undo_manager_signals [CAN_REDO], |
382 undo_manager_signals [CAN_REDO], |
| 383 0, |
383 0, |
| 384 FALSE); |
384 FALSE); |
| 385 } |
385 } |
| 386 } |
386 } |
| 387 } |
387 } |
| 388 |
388 |
| 406 |
406 |
| 407 static void |
407 static void |
| 408 set_cursor (GtkTextBuffer *buffer, gint cursor) |
408 set_cursor (GtkTextBuffer *buffer, gint cursor) |
| 409 { |
409 { |
| 410 GtkTextIter iter; |
410 GtkTextIter iter; |
| 411 |
411 |
| 412 /* Place the cursor at the requested position */ |
412 /* Place the cursor at the requested position */ |
| 413 gtk_text_buffer_get_iter_at_offset (buffer, &iter, cursor); |
413 gtk_text_buffer_get_iter_at_offset (buffer, &iter, cursor); |
| 414 gtk_text_buffer_place_cursor (buffer, &iter); |
414 gtk_text_buffer_place_cursor (buffer, &iter); |
| 415 } |
415 } |
| 416 |
416 |
| 417 static void |
417 static void |
| 418 insert_text (GtkTextBuffer *buffer, gint pos, const gchar *text, gint len) |
418 insert_text (GtkTextBuffer *buffer, gint pos, const gchar *text, gint len) |
| 419 { |
419 { |
| 420 GtkTextIter iter; |
420 GtkTextIter iter; |
| 421 |
421 |
| 422 gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos); |
422 gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos); |
| 423 gtk_text_buffer_insert (buffer, &iter, text, len); |
423 gtk_text_buffer_insert (buffer, &iter, text, len); |
| 424 } |
424 } |
| 425 |
425 |
| 426 static void |
426 static void |
| 427 insert_anchor (GtkTextBuffer *buffer, gint pos, GtkTextChildAnchor *anchor) |
427 insert_anchor (GtkTextBuffer *buffer, gint pos, GtkTextChildAnchor *anchor) |
| 428 { |
428 { |
| 429 GtkTextIter iter; |
429 GtkTextIter iter; |
| 430 |
430 |
| 431 gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos); |
431 gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos); |
| 432 gtk_text_buffer_insert_child_anchor (buffer, &iter, anchor); |
432 gtk_text_buffer_insert_child_anchor (buffer, &iter, anchor); |
| 433 } |
433 } |
| 434 |
434 |
| 435 static void |
435 static void |
| 436 delete_text (GtkTextBuffer *buffer, gint start, gint end) |
436 delete_text (GtkTextBuffer *buffer, gint start, gint end) |
| 437 { |
437 { |
| 438 GtkTextIter start_iter; |
438 GtkTextIter start_iter; |
| 439 GtkTextIter end_iter; |
439 GtkTextIter end_iter; |
| 440 |
440 |
| 462 gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end); |
462 gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end); |
| 463 |
463 |
| 464 return gtk_text_buffer_get_slice (buffer, &start_iter, &end_iter, TRUE); |
464 return gtk_text_buffer_get_slice (buffer, &start_iter, &end_iter, TRUE); |
| 465 } |
465 } |
| 466 |
466 |
| 467 void |
467 void |
| 468 gtk_source_undo_manager_undo (GtkSourceUndoManager *um) |
468 gtk_source_undo_manager_undo (GtkSourceUndoManager *um) |
| 469 { |
469 { |
| 470 GtkSourceUndoAction *undo_action; |
470 GtkSourceUndoAction *undo_action; |
| 471 gboolean modified = FALSE; |
471 gboolean modified = FALSE; |
| 472 |
472 |
| 473 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); |
473 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); |
| 474 g_return_if_fail (um->priv != NULL); |
474 g_return_if_fail (um->priv != NULL); |
| 475 g_return_if_fail (um->priv->can_undo); |
475 g_return_if_fail (um->priv->can_undo); |
| 476 |
476 |
| 477 um->priv->modified_undoing_group = FALSE; |
477 um->priv->modified_undoing_group = FALSE; |
| 478 |
478 |
| 479 gtk_source_undo_manager_begin_not_undoable_action (um); |
479 gtk_source_undo_manager_begin_not_undoable_action (um); |
| 480 |
480 |
| 481 do |
481 do |
| 482 { |
482 { |
| 483 undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo + 1); |
483 undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo + 1); |
| 484 g_return_if_fail (undo_action != NULL); |
484 g_return_if_fail (undo_action != NULL); |
| 485 |
485 |
| 486 /* undo_action->modified can be TRUE only if undo_action->order_in_group <= 1 */ |
486 /* undo_action->modified can be TRUE only if undo_action->order_in_group <= 1 */ |
| 487 g_return_if_fail ((undo_action->order_in_group <= 1) || |
487 g_return_if_fail ((undo_action->order_in_group <= 1) || |
| 488 ((undo_action->order_in_group > 1) && !undo_action->modified)); |
488 ((undo_action->order_in_group > 1) && !undo_action->modified)); |
| 489 |
489 |
| 490 if (undo_action->order_in_group <= 1) |
490 if (undo_action->order_in_group <= 1) |
| 491 { |
491 { |
| 492 /* Set modified to TRUE only if the buffer did not change its state from |
492 /* Set modified to TRUE only if the buffer did not change its state from |
| 493 * "not modified" to "modified" undoing an action (with order_in_group > 1) |
493 * "not modified" to "modified" undoing an action (with order_in_group > 1) |
| 494 * in current group. */ |
494 * in current group. */ |
| 495 modified = (undo_action->modified && !um->priv->modified_undoing_group); |
495 modified = (undo_action->modified && !um->priv->modified_undoing_group); |
| 496 } |
496 } |
| 497 |
497 |
| 498 switch (undo_action->action_type) |
498 switch (undo_action->action_type) |
| 499 { |
499 { |
| 500 case GTK_SOURCE_UNDO_ACTION_DELETE: |
500 case GTK_SOURCE_UNDO_ACTION_DELETE: |
| 501 insert_text ( |
501 insert_text ( |
| 502 um->priv->document, |
502 um->priv->document, |
| 503 undo_action->action.delete.start, |
503 undo_action->action.delete.start, |
| 504 undo_action->action.delete.text, |
504 undo_action->action.delete.text, |
| 505 strlen (undo_action->action.delete.text)); |
505 strlen (undo_action->action.delete.text)); |
| 506 |
506 |
| 507 if (undo_action->action.delete.forward) |
507 if (undo_action->action.delete.forward) |
| 508 set_cursor ( |
508 set_cursor ( |
| 509 um->priv->document, |
509 um->priv->document, |
| 510 undo_action->action.delete.start); |
510 undo_action->action.delete.start); |
| 511 else |
511 else |
| 512 set_cursor ( |
512 set_cursor ( |
| 513 um->priv->document, |
513 um->priv->document, |
| 514 undo_action->action.delete.end); |
514 undo_action->action.delete.end); |
| 515 |
515 |
| 516 break; |
516 break; |
| 517 |
517 |
| 518 case GTK_SOURCE_UNDO_ACTION_INSERT: |
518 case GTK_SOURCE_UNDO_ACTION_INSERT: |
| 519 delete_text ( |
519 delete_text ( |
| 520 um->priv->document, |
520 um->priv->document, |
| 521 undo_action->action.insert.pos, |
521 undo_action->action.insert.pos, |
| 522 undo_action->action.insert.pos + |
522 undo_action->action.insert.pos + |
| 523 undo_action->action.insert.chars); |
523 undo_action->action.insert.chars); |
| 524 |
524 |
| 525 set_cursor ( |
525 set_cursor ( |
| 526 um->priv->document, |
526 um->priv->document, |
| 527 undo_action->action.insert.pos); |
527 undo_action->action.insert.pos); |
| 528 break; |
528 break; |
| 529 |
529 |
| 530 case GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR: |
530 case GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR: |
| 531 delete_text ( |
531 delete_text ( |
| 549 gtk_text_buffer_set_modified (um->priv->document, FALSE); |
549 gtk_text_buffer_set_modified (um->priv->document, FALSE); |
| 550 ++um->priv->next_redo; |
550 ++um->priv->next_redo; |
| 551 } |
551 } |
| 552 |
552 |
| 553 gtk_source_undo_manager_end_not_undoable_action_internal (um); |
553 gtk_source_undo_manager_end_not_undoable_action_internal (um); |
| 554 |
554 |
| 555 um->priv->modified_undoing_group = FALSE; |
555 um->priv->modified_undoing_group = FALSE; |
| 556 |
556 |
| 557 if (!um->priv->can_redo) |
557 if (!um->priv->can_redo) |
| 558 { |
558 { |
| 559 um->priv->can_redo = TRUE; |
559 um->priv->can_redo = TRUE; |
| 560 g_signal_emit (G_OBJECT (um), |
560 g_signal_emit (G_OBJECT (um), |
| 561 undo_manager_signals [CAN_REDO], |
561 undo_manager_signals [CAN_REDO], |
| 562 0, |
562 0, |
| 563 TRUE); |
563 TRUE); |
| 564 } |
564 } |
| 565 |
565 |
| 566 if (um->priv->next_redo >= (gint)(g_list_length (um->priv->actions) - 1)) |
566 if (um->priv->next_redo >= (gint)(g_list_length (um->priv->actions) - 1)) |
| 567 { |
567 { |
| 568 um->priv->can_undo = FALSE; |
568 um->priv->can_undo = FALSE; |
| 569 g_signal_emit (G_OBJECT (um), |
569 g_signal_emit (G_OBJECT (um), |
| 570 undo_manager_signals [CAN_UNDO], |
570 undo_manager_signals [CAN_UNDO], |
| 571 0, |
571 0, |
| 572 FALSE); |
572 FALSE); |
| 573 } |
573 } |
| 574 } |
574 } |
| 575 |
575 |
| 576 void |
576 void |
| 577 gtk_source_undo_manager_redo (GtkSourceUndoManager *um) |
577 gtk_source_undo_manager_redo (GtkSourceUndoManager *um) |
| 578 { |
578 { |
| 579 GtkSourceUndoAction *undo_action; |
579 GtkSourceUndoAction *undo_action; |
| 580 gboolean modified = FALSE; |
580 gboolean modified = FALSE; |
| 581 |
581 |
| 582 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); |
582 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); |
| 583 g_return_if_fail (um->priv != NULL); |
583 g_return_if_fail (um->priv != NULL); |
| 584 g_return_if_fail (um->priv->can_redo); |
584 g_return_if_fail (um->priv->can_redo); |
| 585 |
585 |
| 586 undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo); |
586 undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo); |
| 587 g_return_if_fail (undo_action != NULL); |
587 g_return_if_fail (undo_action != NULL); |
| 588 |
588 |
| 589 gtk_source_undo_manager_begin_not_undoable_action (um); |
589 gtk_source_undo_manager_begin_not_undoable_action (um); |
| 590 |
590 |
| 595 g_return_if_fail (undo_action->order_in_group <= 1); |
595 g_return_if_fail (undo_action->order_in_group <= 1); |
| 596 modified = TRUE; |
596 modified = TRUE; |
| 597 } |
597 } |
| 598 |
598 |
| 599 --um->priv->next_redo; |
599 --um->priv->next_redo; |
| 600 |
600 |
| 601 switch (undo_action->action_type) |
601 switch (undo_action->action_type) |
| 602 { |
602 { |
| 603 case GTK_SOURCE_UNDO_ACTION_DELETE: |
603 case GTK_SOURCE_UNDO_ACTION_DELETE: |
| 604 delete_text ( |
604 delete_text ( |
| 605 um->priv->document, |
605 um->priv->document, |
| 606 undo_action->action.delete.start, |
606 undo_action->action.delete.start, |
| 607 undo_action->action.delete.end); |
607 undo_action->action.delete.end); |
| 608 |
608 |
| 609 set_cursor ( |
609 set_cursor ( |
| 610 um->priv->document, |
610 um->priv->document, |
| 611 undo_action->action.delete.start); |
611 undo_action->action.delete.start); |
| 612 |
612 |
| 613 break; |
613 break; |
| 614 |
614 |
| 615 case GTK_SOURCE_UNDO_ACTION_INSERT: |
615 case GTK_SOURCE_UNDO_ACTION_INSERT: |
| 616 set_cursor ( |
616 set_cursor ( |
| 617 um->priv->document, |
617 um->priv->document, |
| 618 undo_action->action.insert.pos); |
618 undo_action->action.insert.pos); |
| 619 |
619 |
| 620 insert_text ( |
620 insert_text ( |
| 621 um->priv->document, |
621 um->priv->document, |
| 622 undo_action->action.insert.pos, |
622 undo_action->action.insert.pos, |
| 623 undo_action->action.insert.text, |
623 undo_action->action.insert.text, |
| 624 undo_action->action.insert.length); |
624 undo_action->action.insert.length); |
| 625 |
625 |
| 626 break; |
626 break; |
| 627 |
627 |
| 843 *action = *undo_action; |
843 *action = *undo_action; |
| 844 |
844 |
| 845 if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT) |
845 if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT) |
| 846 action->action.insert.text = g_strndup (undo_action->action.insert.text, undo_action->action.insert.length); |
846 action->action.insert.text = g_strndup (undo_action->action.insert.text, undo_action->action.insert.length); |
| 847 else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE) |
847 else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE) |
| 848 action->action.delete.text = g_strdup (undo_action->action.delete.text); |
848 action->action.delete.text = g_strdup (undo_action->action.delete.text); |
| 849 else if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR) |
849 else if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR) |
| 850 { |
850 { |
| 851 /* Nothing needs to be done */ |
851 /* Nothing needs to be done */ |
| 852 } |
852 } |
| 853 else |
853 else |
| 854 { |
854 { |
| 855 g_free (action); |
855 g_free (action); |
| 856 g_return_if_reached (); |
856 g_return_if_reached (); |
| 857 } |
857 } |
| 858 |
858 |
| 859 ++um->priv->actions_in_current_group; |
859 ++um->priv->actions_in_current_group; |
| 860 action->order_in_group = um->priv->actions_in_current_group; |
860 action->order_in_group = um->priv->actions_in_current_group; |
| 861 |
861 |
| 862 if (action->order_in_group == 1) |
862 if (action->order_in_group == 1) |
| 863 ++um->priv->num_of_groups; |
863 ++um->priv->num_of_groups; |
| 864 |
864 |
| 865 um->priv->actions = g_list_prepend (um->priv->actions, action); |
865 um->priv->actions = g_list_prepend (um->priv->actions, action); |
| 866 } |
866 } |
| 867 |
867 |
| 868 gtk_source_undo_manager_check_list_size (um); |
868 gtk_source_undo_manager_check_list_size (um); |
| 869 |
869 |
| 870 if (!um->priv->can_undo) |
870 if (!um->priv->can_undo) |
| 871 { |
871 { |
| 872 um->priv->can_undo = TRUE; |
872 um->priv->can_undo = TRUE; |
| 902 gtk_source_undo_action_free (action); |
902 gtk_source_undo_action_free (action); |
| 903 |
903 |
| 904 um->priv->actions = g_list_delete_link (um->priv->actions, |
904 um->priv->actions = g_list_delete_link (um->priv->actions, |
| 905 um->priv->actions); |
905 um->priv->actions); |
| 906 |
906 |
| 907 if (um->priv->actions == NULL) |
907 if (um->priv->actions == NULL) |
| 908 return; |
908 return; |
| 909 } |
909 } |
| 910 } |
910 } |
| 911 |
911 |
| 912 static void |
912 static void |
| 913 gtk_source_undo_manager_check_list_size (GtkSourceUndoManager *um) |
913 gtk_source_undo_manager_check_list_size (GtkSourceUndoManager *um) |
| 914 { |
914 { |
| 915 gint undo_levels; |
915 gint undo_levels; |
| 916 |
916 |
| 917 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); |
917 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); |
| 918 g_return_if_fail (um->priv != NULL); |
918 g_return_if_fail (um->priv != NULL); |
| 919 |
919 |
| 920 undo_levels = gtk_source_undo_manager_get_max_undo_levels (um); |
920 undo_levels = gtk_source_undo_manager_get_max_undo_levels (um); |
| 921 |
921 |
| 922 if (undo_levels < 1) |
922 if (undo_levels < 1) |
| 923 return; |
923 return; |
| 924 |
924 |
| 925 if (um->priv->num_of_groups > undo_levels) |
925 if (um->priv->num_of_groups > undo_levels) |
| 926 { |
926 { |
| 927 GtkSourceUndoAction *undo_action; |
927 GtkSourceUndoAction *undo_action; |
| 928 GList *last; |
928 GList *last; |
| 929 |
929 |
| 930 last = g_list_last (um->priv->actions); |
930 last = g_list_last (um->priv->actions); |
| 931 undo_action = (GtkSourceUndoAction*) last->data; |
931 undo_action = (GtkSourceUndoAction*) last->data; |
| 932 |
932 |
| 933 do |
933 do |
| 934 { |
934 { |
| 935 GList *tmp; |
935 GList *tmp; |
| 936 |
936 |
| 937 if (undo_action->order_in_group == 1) |
937 if (undo_action->order_in_group == 1) |
| 938 --um->priv->num_of_groups; |
938 --um->priv->num_of_groups; |
| 939 |
939 |
| 940 if (undo_action->modified) |
940 if (undo_action->modified) |
| 941 um->priv->modified_action = INVALID; |
941 um->priv->modified_action = INVALID; |
| 943 gtk_source_undo_action_free (undo_action); |
943 gtk_source_undo_action_free (undo_action); |
| 944 |
944 |
| 945 tmp = g_list_previous (last); |
945 tmp = g_list_previous (last); |
| 946 um->priv->actions = g_list_delete_link (um->priv->actions, last); |
946 um->priv->actions = g_list_delete_link (um->priv->actions, last); |
| 947 last = tmp; |
947 last = tmp; |
| 948 g_return_if_fail (last != NULL); |
948 g_return_if_fail (last != NULL); |
| 949 |
949 |
| 950 undo_action = (GtkSourceUndoAction*) last->data; |
950 undo_action = (GtkSourceUndoAction*) last->data; |
| 951 |
951 |
| 952 } while ((undo_action->order_in_group > 1) || |
952 } while ((undo_action->order_in_group > 1) || |
| 953 (um->priv->num_of_groups > undo_levels)); |
953 (um->priv->num_of_groups > undo_levels)); |
| 954 } |
954 } |
| 955 } |
955 } |
| 956 |
956 |
| 957 /** |
957 /** |
| 958 * gtk_source_undo_manager_merge_action: |
958 * gtk_source_undo_manager_merge_action: |
| 959 * @um: a #GtkSourceUndoManager. |
959 * @um: a #GtkSourceUndoManager. |
| 960 * @undo_action: a #GtkSourceUndoAction. |
960 * @undo_action: a #GtkSourceUndoAction. |
| 961 * |
961 * |
| 962 * This function tries to merge the undo action at the top of |
962 * This function tries to merge the undo action at the top of |
| 963 * the stack with a new undo action. So when we undo for example |
963 * the stack with a new undo action. So when we undo for example |
| 964 * typing, we can undo the whole word and not each letter by itself. |
964 * typing, we can undo the whole word and not each letter by itself. |
| 965 * |
965 * |
| 966 * Return Value: %TRUE is merge was sucessful, %FALSE otherwise.² |
966 * Return Value: %TRUE is merge was successful, %FALSE otherwise.² |
| 967 **/ |
967 **/ |
| 968 static gboolean |
968 static gboolean |
| 969 gtk_source_undo_manager_merge_action (GtkSourceUndoManager *um, |
969 gtk_source_undo_manager_merge_action (GtkSourceUndoManager *um, |
| 970 const GtkSourceUndoAction *undo_action) |
970 const GtkSourceUndoAction *undo_action) |
| 971 { |
971 { |
| 972 GtkSourceUndoAction *last_action; |
972 GtkSourceUndoAction *last_action; |
| 973 |
973 |
| 974 g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE); |
974 g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE); |
| 975 g_return_val_if_fail (um->priv != NULL, FALSE); |
975 g_return_val_if_fail (um->priv != NULL, FALSE); |
| 976 |
976 |
| 977 if (um->priv->actions == NULL) |
977 if (um->priv->actions == NULL) |
| 978 return FALSE; |
978 return FALSE; |
| 988 last_action->mergeable = FALSE; |
988 last_action->mergeable = FALSE; |
| 989 return FALSE; |
989 return FALSE; |
| 990 } |
990 } |
| 991 |
991 |
| 992 if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE) |
992 if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE) |
| 993 { |
993 { |
| 994 if ((last_action->action.delete.forward != undo_action->action.delete.forward) || |
994 if ((last_action->action.delete.forward != undo_action->action.delete.forward) || |
| 995 ((last_action->action.delete.start != undo_action->action.delete.start) && |
995 ((last_action->action.delete.start != undo_action->action.delete.start) && |
| 996 (last_action->action.delete.start != undo_action->action.delete.end))) |
996 (last_action->action.delete.start != undo_action->action.delete.end))) |
| 997 { |
997 { |
| 998 last_action->mergeable = FALSE; |
998 last_action->mergeable = FALSE; |
| 999 return FALSE; |
999 return FALSE; |
| 1000 } |
1000 } |
| 1001 |
1001 |
| 1002 if (last_action->action.delete.start == undo_action->action.delete.start) |
1002 if (last_action->action.delete.start == undo_action->action.delete.start) |
| 1003 { |
1003 { |
| 1004 gchar *str; |
1004 gchar *str; |
| 1005 |
1005 |
| 1006 #define L (last_action->action.delete.end - last_action->action.delete.start - 1) |
1006 #define L (last_action->action.delete.end - last_action->action.delete.start - 1) |
| 1007 #define g_utf8_get_char_at(p,i) g_utf8_get_char(g_utf8_offset_to_pointer((p),(i))) |
1007 #define g_utf8_get_char_at(p,i) g_utf8_get_char(g_utf8_offset_to_pointer((p),(i))) |
| 1008 |
1008 |
| 1009 /* Deleted with the delete key */ |
1009 /* Deleted with the delete key */ |
| 1010 if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') && |
1010 if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') && |
| 1011 (g_utf8_get_char (undo_action->action.delete.text) != '\t') && |
1011 (g_utf8_get_char (undo_action->action.delete.text) != '\t') && |
| 1012 ((g_utf8_get_char_at (last_action->action.delete.text, L) == ' ') || |
1012 ((g_utf8_get_char_at (last_action->action.delete.text, L) == ' ') || |
| 1013 (g_utf8_get_char_at (last_action->action.delete.text, L) == '\t'))) |
1013 (g_utf8_get_char_at (last_action->action.delete.text, L) == '\t'))) |
| 1014 { |
1014 { |
| 1015 last_action->mergeable = FALSE; |
1015 last_action->mergeable = FALSE; |
| 1016 return FALSE; |
1016 return FALSE; |
| 1017 } |
1017 } |
| 1018 |
1018 |
| 1019 str = g_strdup_printf ("%s%s", last_action->action.delete.text, |
1019 str = g_strdup_printf ("%s%s", last_action->action.delete.text, |
| 1020 undo_action->action.delete.text); |
1020 undo_action->action.delete.text); |
| 1021 |
1021 |
| 1022 g_free (last_action->action.delete.text); |
1022 g_free (last_action->action.delete.text); |
| 1023 last_action->action.delete.end += (undo_action->action.delete.end - |
1023 last_action->action.delete.end += (undo_action->action.delete.end - |
| 1024 undo_action->action.delete.start); |
1024 undo_action->action.delete.start); |
| 1025 last_action->action.delete.text = str; |
1025 last_action->action.delete.text = str; |
| 1026 } |
1026 } |
| 1027 else |
1027 else |
| 1028 { |
1028 { |
| 1029 gchar *str; |
1029 gchar *str; |
| 1030 |
1030 |
| 1031 /* Deleted with the backspace key */ |
1031 /* Deleted with the backspace key */ |
| 1032 if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') && |
1032 if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') && |
| 1033 (g_utf8_get_char (undo_action->action.delete.text) != '\t') && |
1033 (g_utf8_get_char (undo_action->action.delete.text) != '\t') && |
| 1034 ((g_utf8_get_char (last_action->action.delete.text) == ' ') || |
1034 ((g_utf8_get_char (last_action->action.delete.text) == ' ') || |
| 1035 (g_utf8_get_char (last_action->action.delete.text) == '\t'))) |
1035 (g_utf8_get_char (last_action->action.delete.text) == '\t'))) |
| 1036 { |
1036 { |
| 1037 last_action->mergeable = FALSE; |
1037 last_action->mergeable = FALSE; |
| 1038 return FALSE; |
1038 return FALSE; |
| 1039 } |
1039 } |
| 1040 |
1040 |
| 1041 str = g_strdup_printf ("%s%s", undo_action->action.delete.text, |
1041 str = g_strdup_printf ("%s%s", undo_action->action.delete.text, |
| 1042 last_action->action.delete.text); |
1042 last_action->action.delete.text); |
| 1043 |
1043 |
| 1044 g_free (last_action->action.delete.text); |
1044 g_free (last_action->action.delete.text); |
| 1045 last_action->action.delete.start = undo_action->action.delete.start; |
1045 last_action->action.delete.start = undo_action->action.delete.start; |
| 1046 last_action->action.delete.text = str; |
1046 last_action->action.delete.text = str; |
| 1047 } |
1047 } |
| 1048 } |
1048 } |
| 1049 else if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT) |
1049 else if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT) |
| 1050 { |
1050 { |
| 1051 gchar* str; |
1051 gchar* str; |
| 1052 |
1052 |
| 1053 #define I (last_action->action.insert.chars - 1) |
1053 #define I (last_action->action.insert.chars - 1) |
| 1054 |
1054 |
| 1055 if ((undo_action->action.insert.pos != |
1055 if ((undo_action->action.insert.pos != |
| 1056 (last_action->action.insert.pos + last_action->action.insert.chars)) || |
1056 (last_action->action.insert.pos + last_action->action.insert.chars)) || |
| 1057 ((g_utf8_get_char (undo_action->action.insert.text) != ' ') && |
1057 ((g_utf8_get_char (undo_action->action.insert.text) != ' ') && |
| 1058 (g_utf8_get_char (undo_action->action.insert.text) != '\t') && |
1058 (g_utf8_get_char (undo_action->action.insert.text) != '\t') && |
| 1059 ((g_utf8_get_char_at (last_action->action.insert.text, I) == ' ') || |
1059 ((g_utf8_get_char_at (last_action->action.insert.text, I) == ' ') || |
| 1060 (g_utf8_get_char_at (last_action->action.insert.text, I) == '\t'))) |
1060 (g_utf8_get_char_at (last_action->action.insert.text, I) == '\t'))) |
| 1096 void |
1096 void |
| 1097 gtk_source_undo_manager_set_max_undo_levels (GtkSourceUndoManager *um, |
1097 gtk_source_undo_manager_set_max_undo_levels (GtkSourceUndoManager *um, |
| 1098 gint max_undo_levels) |
1098 gint max_undo_levels) |
| 1099 { |
1099 { |
| 1100 gint old_levels; |
1100 gint old_levels; |
| 1101 |
1101 |
| 1102 g_return_if_fail (um != NULL); |
1102 g_return_if_fail (um != NULL); |
| 1103 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); |
1103 g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); |
| 1104 |
1104 |
| 1105 old_levels = um->priv->max_undo_levels; |
1105 old_levels = um->priv->max_undo_levels; |
| 1106 um->priv->max_undo_levels = max_undo_levels; |
1106 um->priv->max_undo_levels = max_undo_levels; |
| 1107 |
1107 |
| 1108 if (max_undo_levels < 1) |
1108 if (max_undo_levels < 1) |
| 1109 return; |
1109 return; |
| 1110 |
1110 |
| 1111 if (old_levels > max_undo_levels) |
1111 if (old_levels > max_undo_levels) |
| 1112 { |
1112 { |
| 1113 /* strip redo actions first */ |
1113 /* strip redo actions first */ |
| 1114 while (um->priv->next_redo >= 0 && (um->priv->num_of_groups > max_undo_levels)) |
1114 while (um->priv->next_redo >= 0 && (um->priv->num_of_groups > max_undo_levels)) |
| 1115 { |
1115 { |
| 1116 gtk_source_undo_manager_free_first_n_actions (um, 1); |
1116 gtk_source_undo_manager_free_first_n_actions (um, 1); |
| 1117 um->priv->next_redo--; |
1117 um->priv->next_redo--; |
| 1118 } |
1118 } |
| 1119 |
1119 |
| 1120 /* now remove undo actions if necessary */ |
1120 /* now remove undo actions if necessary */ |
| 1121 gtk_source_undo_manager_check_list_size (um); |
1121 gtk_source_undo_manager_check_list_size (um); |
| 1122 |
1122 |
| 1123 /* emit "can_undo" and/or "can_redo" if appropiate */ |
1123 /* emit "can_undo" and/or "can_redo" if appropiate */ |
| 1124 if (um->priv->next_redo < 0 && um->priv->can_redo) |
1124 if (um->priv->next_redo < 0 && um->priv->can_redo) |