2006-12-12 [paul] 2.6.1cvs21
[claws.git] / src / undo.c
blob509d6ea8112dd6bfb69217eab5412ed373589e72
1 /*
2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2006 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 /* code ported from gedit */
21 /* This is for my patient girlfirend Regina */
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #endif
27 #include <glib.h>
29 #include <string.h> /* for strlen */
30 #include <stdlib.h> /* for mbstowcs */
32 #include "undo.h"
33 #include "utils.h"
34 #include "prefs_common.h"
36 typedef struct _UndoInfo UndoInfo;
38 struct _UndoInfo
40 UndoAction action;
41 gchar *text;
42 gint start_pos;
43 gint end_pos;
44 gfloat window_position;
45 gint mergeable;
48 static void undo_free_list (GList **list_pointer);
49 static void undo_check_size (UndoMain *undostruct);
50 static gint undo_merge (GList *list,
51 guint start_pos,
52 guint end_pos,
53 gint action,
54 const guchar *text);
55 static void undo_add (const gchar *text,
56 gint start_pos,
57 gint end_pos,
58 UndoAction action,
59 UndoMain *undostruct);
60 static gint undo_get_selection (GtkTextView *textview,
61 guint *start,
62 guint *end);
63 static void undo_insert_text_cb (GtkTextBuffer *textbuf,
64 GtkTextIter *iter,
65 gchar *new_text,
66 gint new_text_length,
67 UndoMain *undostruct);
68 static void undo_delete_text_cb (GtkTextBuffer *textbuf,
69 GtkTextIter *start,
70 GtkTextIter *end,
71 UndoMain *undostruct);
73 static void undo_paste_clipboard_cb (GtkTextView *textview,
74 UndoMain *undostruct);
76 void undo_undo (UndoMain *undostruct);
77 void undo_redo (UndoMain *undostruct);
80 UndoMain *undo_init(GtkWidget *text)
82 UndoMain *undostruct;
83 GtkTextView *textview = GTK_TEXT_VIEW(text);
84 GtkTextBuffer *textbuf = gtk_text_view_get_buffer(textview);
86 g_return_val_if_fail(text != NULL, NULL);
88 undostruct = g_new0(UndoMain, 1);
89 undostruct->textview = textview;
90 undostruct->undo = NULL;
91 undostruct->redo = NULL;
92 undostruct->paste = 0;
93 undostruct->undo_state = FALSE;
94 undostruct->redo_state = FALSE;
96 g_signal_connect(G_OBJECT(textbuf), "insert-text",
97 G_CALLBACK(undo_insert_text_cb), undostruct);
98 g_signal_connect(G_OBJECT(textbuf), "delete-range",
99 G_CALLBACK(undo_delete_text_cb), undostruct);
100 g_signal_connect(G_OBJECT(textview), "paste-clipboard",
101 G_CALLBACK(undo_paste_clipboard_cb), undostruct);
103 return undostruct;
106 void undo_destroy (UndoMain *undostruct)
108 undo_free_list(&undostruct->undo);
109 undo_free_list(&undostruct->redo);
110 g_free(undostruct);
113 static UndoInfo *undo_object_new(gchar *text, gint start_pos, gint end_pos,
114 UndoAction action, gfloat window_position)
116 UndoInfo *undoinfo;
117 undoinfo = g_new (UndoInfo, 1);
118 undoinfo->text = text;
119 undoinfo->start_pos = start_pos;
120 undoinfo->end_pos = end_pos;
121 undoinfo->action = action;
122 undoinfo->window_position = window_position;
123 return undoinfo;
126 static void undo_object_free(UndoInfo *undo)
128 g_free (undo->text);
129 g_free (undo);
133 * undo_free_list:
134 * @list_pointer: list to be freed
136 * frees and undo structure list
138 static void undo_free_list(GList **list_pointer)
140 UndoInfo *undo;
141 GList *cur, *list = *list_pointer;
143 if (list == NULL) return;
145 for (cur = list; cur != NULL; cur = cur->next) {
146 undo = (UndoInfo *)cur->data;
147 undo_object_free(undo);
150 g_list_free(list);
151 *list_pointer = NULL;
154 void undo_set_change_state_func(UndoMain *undostruct, UndoChangeStateFunc func,
155 gpointer data)
157 g_return_if_fail(undostruct != NULL);
159 undostruct->change_state_func = func;
160 undostruct->change_state_data = data;
164 * undo_check_size:
165 * @compose: document to check
167 * Checks that the size of compose->undo does not excede settings->undo_levels and
168 * frees any undo level above sett->undo_level.
171 static void undo_check_size(UndoMain *undostruct)
173 UndoInfo *last_undo;
174 guint length;
176 if (prefs_common.undolevels < 1) return;
178 /* No need to check for the redo list size since the undo
179 list gets freed on any call to compose_undo_add */
180 length = g_list_length(undostruct->undo);
181 if (length >= prefs_common.undolevels && prefs_common.undolevels > 0) {
182 last_undo = (UndoInfo *)g_list_last(undostruct->undo)->data;
183 undostruct->undo = g_list_remove(undostruct->undo, last_undo);
184 undo_object_free(last_undo);
189 * undo_merge:
190 * @last_undo:
191 * @start_pos:
192 * @end_pos:
193 * @action:
195 * This function tries to merge the undo object at the top of
196 * the stack with a new set of data. So when we undo for example
197 * typing, we can undo the whole word and not each letter by itself
199 * Return Value: TRUE is merge was sucessful, FALSE otherwise
201 static gint undo_merge(GList *list, guint start_pos, guint end_pos,
202 gint action, const guchar *text)
204 guchar *temp_string;
205 UndoInfo *last_undo;
207 /* This are the cases in which we will NOT merge :
208 1. if (last_undo->mergeable == FALSE)
209 [mergeable = FALSE when the size of the undo data was not 1.
210 or if the data was size = 1 but = '\n' or if the undo object
211 has been "undone" already ]
212 2. The size of text is not 1
213 3. If the new merging data is a '\n'
214 4. If the last char of the undo_last data is a space/tab
215 and the new char is not a space/tab ( so that we undo
216 words and not chars )
217 5. If the type (action) of undo is different from the last one
218 Chema */
220 if (list == NULL) return FALSE;
222 last_undo = list->data;
224 if (!last_undo->mergeable) return FALSE;
226 if (end_pos - start_pos != 1 ||
227 text[0] == '\n' ||
228 action != last_undo->action ||
229 action == UNDO_ACTION_REPLACE_INSERT ||
230 action == UNDO_ACTION_REPLACE_DELETE) {
231 last_undo->mergeable = FALSE;
232 return FALSE;
235 if (action == UNDO_ACTION_DELETE) {
236 if (last_undo->start_pos != end_pos &&
237 last_undo->start_pos != start_pos) {
238 last_undo->mergeable = FALSE;
239 return FALSE;
240 } else if (last_undo->start_pos == start_pos) {
241 /* Deleted with the delete key */
242 temp_string = g_strdup_printf("%s%s", last_undo->text, text);
243 last_undo->end_pos++;
244 g_free(last_undo->text);
245 last_undo->text = temp_string;
246 } else {
247 /* Deleted with the backspace key */
248 temp_string = g_strdup_printf("%s%s", text, last_undo->text);
249 last_undo->start_pos = start_pos;
250 g_free(last_undo->text);
251 last_undo->text = temp_string;
253 } else if (action == UNDO_ACTION_INSERT) {
254 if (last_undo->end_pos != start_pos) {
255 last_undo->mergeable = FALSE;
256 return FALSE;
257 } else {
258 temp_string = g_strdup_printf("%s%s", last_undo->text, text);
259 g_free(last_undo->text);
260 last_undo->end_pos = end_pos;
261 last_undo->text = temp_string;
263 } else
264 debug_print("Unknown action [%i] inside undo merge encountered", action);
266 return TRUE;
270 * compose_undo_add:
271 * @text:
272 * @start_pos:
273 * @end_pos:
274 * @action: either UNDO_ACTION_INSERT or UNDO_ACTION_DELETE
275 * @compose:
276 * @view: The view so that we save the scroll bar position.
278 * Adds text to the undo stack. It also performs test to limit the number
279 * of undo levels and deltes the redo list
282 static void undo_add(const gchar *text,
283 gint start_pos, gint end_pos,
284 UndoAction action, UndoMain *undostruct)
286 UndoInfo *undoinfo;
287 GtkAdjustment *vadj;
289 g_return_if_fail(text != NULL);
290 g_return_if_fail(end_pos >= start_pos);
292 undo_free_list(&undostruct->redo);
294 /* Set the redo sensitivity */
295 undostruct->change_state_func(undostruct,
296 UNDO_STATE_UNCHANGED, UNDO_STATE_FALSE,
297 undostruct->change_state_data);
299 if (undostruct->paste != 0) {
300 if (action == UNDO_ACTION_INSERT)
301 action = UNDO_ACTION_REPLACE_INSERT;
302 else
303 action = UNDO_ACTION_REPLACE_DELETE;
304 undostruct->paste = undostruct->paste + 1;
305 if (undostruct->paste == 3)
306 undostruct->paste = 0;
309 if (undo_merge(undostruct->undo, start_pos, end_pos, action, text))
310 return;
312 undo_check_size(undostruct);
314 vadj = GTK_ADJUSTMENT(GTK_TEXT_VIEW(undostruct->textview)->vadjustment);
315 undoinfo = undo_object_new(g_strdup(text), start_pos, end_pos, action,
316 vadj->value);
318 if (end_pos - start_pos != 1 || text[0] == '\n')
319 undoinfo->mergeable = FALSE;
320 else
321 undoinfo->mergeable = TRUE;
323 undostruct->undo = g_list_prepend(undostruct->undo, undoinfo);
325 undostruct->change_state_func(undostruct,
326 UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED,
327 undostruct->change_state_data);
331 * undo_undo:
332 * @w: not used
333 * @data: not used
335 * Executes an undo request on the current document
337 void undo_undo(UndoMain *undostruct)
339 UndoInfo *undoinfo;
340 GtkTextView *textview;
341 GtkTextBuffer *buffer;
342 GtkTextIter iter, start_iter, end_iter;
343 GtkTextMark *mark;
345 g_return_if_fail(undostruct != NULL);
347 if (undostruct->undo == NULL) return;
349 /* The undo data we need is always at the top op the
350 stack. So, therefore, the first one */
351 undoinfo = (UndoInfo *)undostruct->undo->data;
352 g_return_if_fail(undoinfo != NULL);
353 undoinfo->mergeable = FALSE;
354 undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
355 undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
357 textview = undostruct->textview;
358 buffer = gtk_text_view_get_buffer(textview);
360 undo_block(undostruct);
362 /* Check if there is a selection active */
363 mark = gtk_text_buffer_get_insert(buffer);
364 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
365 gtk_text_buffer_place_cursor(buffer, &iter);
367 /* Move the view (scrollbars) to the correct position */
368 gtk_adjustment_set_value
369 (GTK_ADJUSTMENT(textview->vadjustment),
370 undoinfo->window_position);
372 switch (undoinfo->action) {
373 case UNDO_ACTION_DELETE:
374 gtk_text_buffer_get_iter_at_offset(buffer, &iter, undoinfo->start_pos);
375 gtk_text_buffer_insert(buffer, &iter, undoinfo->text, -1);
376 break;
377 case UNDO_ACTION_INSERT:
378 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, undoinfo->start_pos);
379 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, undoinfo->end_pos);
380 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
381 break;
382 case UNDO_ACTION_REPLACE_INSERT:
383 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, undoinfo->start_pos);
384 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, undoinfo->end_pos);
385 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
386 /* "pull" another data structure from the list */
387 undoinfo = (UndoInfo *)undostruct->undo->data;
388 g_return_if_fail(undoinfo != NULL);
389 undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
390 undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
391 g_return_if_fail(undoinfo->action == UNDO_ACTION_REPLACE_DELETE);
392 gtk_text_buffer_insert(buffer, &start_iter, undoinfo->text, -1);
393 break;
394 case UNDO_ACTION_REPLACE_DELETE:
395 g_warning("This should not happen. UNDO_REPLACE_DELETE");
396 break;
397 default:
398 g_assert_not_reached();
399 break;
402 undostruct->change_state_func(undostruct,
403 UNDO_STATE_UNCHANGED, UNDO_STATE_TRUE,
404 undostruct->change_state_data);
406 if (undostruct->undo == NULL)
407 undostruct->change_state_func(undostruct,
408 UNDO_STATE_FALSE,
409 UNDO_STATE_UNCHANGED,
410 undostruct->change_state_data);
412 undo_unblock(undostruct);
416 * undo_redo:
417 * @w: not used
418 * @data: not used
420 * executes a redo request on the current document
422 void undo_redo(UndoMain *undostruct)
424 UndoInfo *redoinfo;
425 GtkTextView *textview;
426 GtkTextBuffer *buffer;
427 GtkTextIter iter, start_iter, end_iter;
428 GtkTextMark *mark;
430 g_return_if_fail(undostruct != NULL);
432 if (undostruct->redo == NULL) return;
434 redoinfo = (UndoInfo *)undostruct->redo->data;
435 g_return_if_fail (redoinfo != NULL);
436 undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
437 undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
439 textview = undostruct->textview;
440 buffer = gtk_text_view_get_buffer(textview);
442 undo_block(undostruct);
444 /* Check if there is a selection active */
445 mark = gtk_text_buffer_get_insert(buffer);
446 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
447 gtk_text_buffer_place_cursor(buffer, &iter);
449 /* Move the view to the right position. */
450 gtk_adjustment_set_value(textview->vadjustment,
451 redoinfo->window_position);
453 switch (redoinfo->action) {
454 case UNDO_ACTION_INSERT:
455 gtk_text_buffer_get_iter_at_offset(buffer, &iter, redoinfo->start_pos);
456 gtk_text_buffer_insert(buffer, &iter, redoinfo->text, -1);
457 break;
458 case UNDO_ACTION_DELETE:
459 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, redoinfo->start_pos);
460 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, redoinfo->end_pos);
461 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
462 break;
463 case UNDO_ACTION_REPLACE_DELETE:
464 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, redoinfo->start_pos);
465 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, redoinfo->end_pos);
466 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
467 debug_print("UNDO_ACTION_REPLACE %s\n", redoinfo->text);
468 /* "pull" another data structure from the list */
469 redoinfo = (UndoInfo *)undostruct->redo->data;
470 g_return_if_fail(redoinfo != NULL);
471 undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
472 undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
473 g_return_if_fail(redoinfo->action == UNDO_ACTION_REPLACE_INSERT);
474 gtk_text_buffer_insert(buffer, &start_iter, redoinfo->text, -1);
475 break;
476 case UNDO_ACTION_REPLACE_INSERT:
477 g_warning("This should not happen. Redo: UNDO_REPLACE_INSERT");
478 break;
479 default:
480 g_assert_not_reached();
481 break;
484 undostruct->change_state_func(undostruct,
485 UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED,
486 undostruct->change_state_data);
488 if (undostruct->redo == NULL)
489 undostruct->change_state_func(undostruct,
490 UNDO_STATE_UNCHANGED,
491 UNDO_STATE_FALSE,
492 undostruct->change_state_data);
494 undo_unblock(undostruct);
497 void undo_block(UndoMain *undostruct)
499 GtkTextBuffer *buffer;
501 g_return_if_fail(GTK_IS_TEXT_VIEW(undostruct->textview));
503 buffer = gtk_text_view_get_buffer(undostruct->textview);
504 g_signal_handlers_block_by_func(buffer, undo_insert_text_cb, undostruct);
505 g_signal_handlers_block_by_func(buffer, undo_delete_text_cb, undostruct);
506 g_signal_handlers_block_by_func(buffer, undo_paste_clipboard_cb,
507 undostruct);
510 void undo_unblock(UndoMain *undostruct)
512 GtkTextBuffer *buffer;
514 g_return_if_fail(GTK_IS_TEXT_VIEW(undostruct->textview));
516 buffer = gtk_text_view_get_buffer(undostruct->textview);
517 g_signal_handlers_unblock_by_func(buffer, undo_insert_text_cb, undostruct);
518 g_signal_handlers_unblock_by_func(buffer, undo_delete_text_cb, undostruct);
519 g_signal_handlers_unblock_by_func(buffer, undo_paste_clipboard_cb,
520 undostruct);
523 void undo_wrapping(UndoMain *undostruct, gboolean wrap)
525 debug_print("undo wrapping now %d\n", wrap);
526 undostruct->wrap = wrap;
529 void undo_insert_text_cb(GtkTextBuffer *textbuf, GtkTextIter *iter,
530 gchar *new_text, gint new_text_length,
531 UndoMain *undostruct)
533 gchar *text_to_insert;
534 gint pos;
535 if (prefs_common.undolevels <= 0) return;
537 pos = gtk_text_iter_get_offset(iter);
538 if (undostruct->wrap && undostruct->undo) {
539 UndoInfo *last_undo = undostruct->undo->data;
540 if (last_undo && (last_undo->action == UNDO_ACTION_INSERT
541 || last_undo->action == UNDO_ACTION_REPLACE_INSERT)
542 && last_undo->start_pos < pos && last_undo->end_pos > pos) {
543 GtkTextIter start,end;
544 last_undo->end_pos += g_utf8_strlen(new_text, -1);
545 gtk_text_buffer_get_iter_at_offset(textbuf, &start, last_undo->start_pos);
546 gtk_text_buffer_get_iter_at_offset(textbuf, &end, last_undo->end_pos);
547 g_free(last_undo->text);
548 last_undo->text = gtk_text_buffer_get_text(textbuf, &start, &end, FALSE);
549 debug_print("add:undo upd %d-%d\n", last_undo->start_pos, last_undo->end_pos);
550 return;
551 } else debug_print("add:last: %d, %d-%d (%d)\n", last_undo->action,
552 last_undo->start_pos, last_undo->end_pos, pos);
554 Xstrndup_a(text_to_insert, new_text, new_text_length, return);
555 debug_print("add:undo add %d-%ld\n", pos, pos + g_utf8_strlen(text_to_insert, -1));
556 undo_add(text_to_insert, pos, pos + g_utf8_strlen(text_to_insert, -1),
557 UNDO_ACTION_INSERT, undostruct);
560 void undo_delete_text_cb(GtkTextBuffer *textbuf, GtkTextIter *start,
561 GtkTextIter *end, UndoMain *undostruct)
563 gchar *text_to_delete;
564 gint start_pos, end_pos;
566 if (prefs_common.undolevels <= 0) return;
568 text_to_delete = gtk_text_buffer_get_text(textbuf, start, end, FALSE);
569 if (!text_to_delete || !*text_to_delete) return;
571 start_pos = gtk_text_iter_get_offset(start);
572 end_pos = gtk_text_iter_get_offset(end);
574 if (undostruct->wrap && undostruct->undo) {
575 UndoInfo *last_undo = undostruct->undo->data;
576 if (last_undo && (last_undo->action == UNDO_ACTION_INSERT
577 || last_undo->action == UNDO_ACTION_REPLACE_INSERT)
578 && last_undo->start_pos < start_pos && last_undo->end_pos > end_pos) {
579 GtkTextIter start,end;
580 last_undo->end_pos -= g_utf8_strlen(text_to_delete, -1);
581 gtk_text_buffer_get_iter_at_offset(textbuf, &start, last_undo->start_pos);
582 gtk_text_buffer_get_iter_at_offset(textbuf, &end, last_undo->end_pos);
583 g_free(last_undo->text);
584 last_undo->text = gtk_text_buffer_get_text(textbuf, &start, &end, FALSE);
585 debug_print("del:undo upd %d-%d\n", last_undo->start_pos, last_undo->end_pos);
586 return;
587 } else debug_print("del:last: %d, %d-%d (%d)\n", last_undo->action,
588 last_undo->start_pos, last_undo->end_pos, start_pos);
591 debug_print("del:undo add %d-%d\n", start_pos, end_pos);
592 undo_add(text_to_delete, start_pos, end_pos, UNDO_ACTION_DELETE,
593 undostruct);
594 g_free(text_to_delete);
597 void undo_paste_clipboard(GtkTextView *textview, UndoMain *undostruct)
599 undo_paste_clipboard_cb(textview, undostruct);
602 static void undo_paste_clipboard_cb(GtkTextView *textview, UndoMain *undostruct)
604 if (prefs_common.undolevels > 0)
605 if (undo_get_selection(textview, NULL, NULL))
606 undostruct->paste = TRUE;
610 * undo_get_selection:
611 * @text: Text to get the selection from
612 * @start: return here the start position of the selection
613 * @end: return here the end position of the selection
615 * Gets the current selection for View
617 * Return Value: TRUE if there is a selection active, FALSE if not
619 static gint undo_get_selection(GtkTextView *textview, guint *start, guint *end)
621 GtkTextBuffer *buffer;
622 GtkTextIter start_iter, end_iter;
623 guint start_pos, end_pos;
625 buffer = gtk_text_view_get_buffer(textview);
626 gtk_text_buffer_get_selection_bounds(buffer, &start_iter, &end_iter);
628 start_pos = gtk_text_iter_get_offset(&start_iter);
629 end_pos = gtk_text_iter_get_offset(&end_iter);
631 /* The user can select from end to start too. If so, swap it*/
632 if (end_pos < start_pos) {
633 guint swap_pos;
634 swap_pos = end_pos;
635 end_pos = start_pos;
636 start_pos = swap_pos;
639 if (start != NULL)
640 *start = start_pos;
642 if (end != NULL)
643 *end = end_pos;
645 if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos))
646 return TRUE;
647 else
648 return FALSE;