This commit was manufactured by cvs2svn to create tag 'LAST_STABLE'.
[claws.git] / src / undo.c
blobf14c9e4707070c891d8584cfa590bdae9c7881db
1 /*
2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2001 Hiroyuki Yamamoto
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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"
35 #include "gtkstext.h"
37 typedef struct _UndoInfo UndoInfo;
39 struct _UndoInfo
41 UndoAction action;
42 gchar *text;
43 gint start_pos;
44 gint end_pos;
45 gfloat window_position;
46 gint mergeable;
49 static void undo_free_list (GList **list_pointer);
50 static void undo_check_size (UndoMain *undostruct);
51 static gint undo_merge (GList *list,
52 guint start_pos,
53 guint end_pos,
54 gint action,
55 const guchar *text);
56 static void undo_add (const gchar *text,
57 gint start_pos,
58 gint end_pos,
59 UndoAction action,
60 UndoMain *undostruct);
61 static gint undo_get_selection (GtkEditable *text,
62 guint *start,
63 guint *end);
64 static void undo_insert_text_cb (GtkEditable *editable,
65 gchar *new_text,
66 gint new_text_length,
67 gint *position,
68 UndoMain *undostruct);
69 static void undo_delete_text_cb (GtkEditable *editable,
70 gint start_pos,
71 gint end_pos,
72 UndoMain *undostruct);
74 static void undo_paste_clipboard_cb (GtkEditable *editable,
75 UndoMain *undostruct);
77 void undo_undo (UndoMain *undostruct);
78 void undo_redo (UndoMain *undostruct);
81 UndoMain *undo_init(GtkWidget *text)
83 UndoMain *undostruct;
85 g_return_val_if_fail(text != NULL, NULL);
87 undostruct = g_new(UndoMain, 1);
88 undostruct->text = text;
89 undostruct->undo = NULL;
90 undostruct->redo = NULL;
91 undostruct->paste = 0;
92 undostruct->undo_state = FALSE;
93 undostruct->redo_state = FALSE;
95 gtk_signal_connect(GTK_OBJECT(text), "insert-text",
96 GTK_SIGNAL_FUNC(undo_insert_text_cb), undostruct);
97 gtk_signal_connect(GTK_OBJECT(text), "delete-text",
98 GTK_SIGNAL_FUNC(undo_delete_text_cb), undostruct);
99 gtk_signal_connect(GTK_OBJECT(text), "paste-clipboard",
100 GTK_SIGNAL_FUNC(undo_paste_clipboard_cb), undostruct);
102 return undostruct;
105 void undo_destroy (UndoMain *undostruct)
107 undo_free_list(&undostruct->undo);
108 undo_free_list(&undostruct->redo);
109 g_free(undostruct);
112 static UndoInfo *undo_object_new(gchar *text, gint start_pos, gint end_pos,
113 UndoAction action, gfloat window_position)
115 UndoInfo *undoinfo;
116 undoinfo = g_new (UndoInfo, 1);
117 undoinfo->text = text;
118 undoinfo->start_pos = start_pos;
119 undoinfo->end_pos = end_pos;
120 undoinfo->action = action;
121 undoinfo->window_position = window_position;
122 return undoinfo;
125 static void undo_object_free(UndoInfo *undo)
127 g_free (undo->text);
128 g_free (undo);
132 * undo_free_list:
133 * @list_pointer: list to be freed
135 * frees and undo structure list
137 static void undo_free_list(GList **list_pointer)
139 UndoInfo *undo;
140 GList *cur, *list = *list_pointer;
142 if (list == NULL) return;
144 for (cur = list; cur != NULL; cur = cur->next) {
145 undo = (UndoInfo *)cur->data;
146 undo_object_free(undo);
149 g_list_free(list);
150 *list_pointer = NULL;
153 void undo_set_change_state_func(UndoMain *undostruct, UndoChangeStateFunc func,
154 gpointer data)
156 g_return_if_fail(undostruct != NULL);
158 undostruct->change_state_func = func;
159 undostruct->change_state_data = data;
163 * undo_check_size:
164 * @compose: document to check
166 * Checks that the size of compose->undo does not excede settings->undo_levels and
167 * frees any undo level above sett->undo_level.
170 static void undo_check_size(UndoMain *undostruct)
172 UndoInfo *last_undo;
173 guint length;
175 if (prefs_common.undolevels < 1) return;
177 /* No need to check for the redo list size since the undo
178 list gets freed on any call to compose_undo_add */
179 length = g_list_length(undostruct->undo);
180 if (length >= prefs_common.undolevels && prefs_common.undolevels > 0) {
181 last_undo = (UndoInfo *)g_list_last(undostruct->undo)->data;
182 undostruct->undo = g_list_remove(undostruct->undo, last_undo);
183 undo_object_free(last_undo);
185 debug_print("g_list_length(undostruct->undo): %d\n", length);
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 gboolean checkit = TRUE;
238 if (last_undo->start_pos != end_pos &&
239 last_undo->start_pos != start_pos) {
240 last_undo->mergeable = FALSE;
241 return FALSE;
242 } else if (last_undo->start_pos == start_pos) {
243 /* Deleted with the delete key */
244 if (text[0] != ' ' && text[0] != '\t' &&
245 (last_undo->text[last_undo->end_pos - last_undo->start_pos - 1] == ' ' ||
246 last_undo->text[last_undo->end_pos - last_undo->start_pos - 1] == '\t'))
247 checkit = FALSE;
249 temp_string = g_strdup_printf("%s%s", last_undo->text, text);
250 last_undo->end_pos++;
251 g_free(last_undo->text);
252 last_undo->text = temp_string;
253 } else {
254 /* Deleted with the backspace key */
255 if (text[0] != ' ' && text[0] != '\t' &&
256 (last_undo->text[0] == ' ' ||
257 last_undo->text[0] == '\t'))
258 checkit = FALSE;
260 temp_string = g_strdup_printf("%s%s", text, last_undo->text);
261 last_undo->start_pos = start_pos;
262 g_free(last_undo->text);
263 last_undo->text = temp_string;
266 if (!checkit) {
267 last_undo->mergeable = FALSE;
268 return FALSE;
270 } else if (action == UNDO_ACTION_INSERT) {
271 if (last_undo->end_pos != start_pos) {
272 last_undo->mergeable = FALSE;
273 return FALSE;
274 } else {
275 temp_string = g_strdup_printf("%s%s", last_undo->text, text);
276 g_free(last_undo->text);
277 last_undo->end_pos = end_pos;
278 last_undo->text = temp_string;
280 } else
281 debug_print("Unknown action [%i] inside undo merge encountered", action);
283 debug_print("Merged: %s\n", text);
284 return TRUE;
288 * compose_undo_add:
289 * @text:
290 * @start_pos:
291 * @end_pos:
292 * @action: either UNDO_ACTION_INSERT or UNDO_ACTION_DELETE
293 * @compose:
294 * @view: The view so that we save the scroll bar position.
296 * Adds text to the undo stack. It also performs test to limit the number
297 * of undo levels and deltes the redo list
300 static void undo_add(const gchar *text,
301 gint start_pos, gint end_pos,
302 UndoAction action, UndoMain *undostruct)
304 UndoInfo *undoinfo;
306 debug_print("undo_add(%i)*%s*\n", strlen (text), text);
308 g_return_if_fail(text != NULL);
309 g_return_if_fail(end_pos >= start_pos);
311 undo_free_list(&undostruct->redo);
313 /* Set the redo sensitivity */
314 undostruct->change_state_func(undostruct,
315 UNDO_STATE_UNCHANGED, UNDO_STATE_FALSE,
316 undostruct->change_state_data);
318 if (undostruct->paste != 0) {
319 if (action == UNDO_ACTION_INSERT)
320 action = UNDO_ACTION_REPLACE_INSERT;
321 else
322 action = UNDO_ACTION_REPLACE_DELETE;
323 undostruct->paste = undostruct->paste + 1;
324 if (undostruct->paste == 3)
325 undostruct->paste = 0;
328 if (undo_merge(undostruct->undo, start_pos, end_pos, action, text))
329 return;
331 undo_check_size(undostruct);
333 debug_print("New: %s Action: %d Paste: %d\n", text, action, undostruct->paste);
335 undoinfo = undo_object_new(g_strdup(text), start_pos, end_pos, action,
336 GTK_ADJUSTMENT(GTK_STEXT(undostruct->text)->vadj)->value);
338 if (end_pos - start_pos != 1 || text[0] == '\n')
339 undoinfo->mergeable = FALSE;
340 else
341 undoinfo->mergeable = TRUE;
343 undostruct->undo = g_list_prepend(undostruct->undo, undoinfo);
345 undostruct->change_state_func(undostruct,
346 UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED,
347 undostruct->change_state_data);
351 * undo_undo:
352 * @w: not used
353 * @data: not used
355 * Executes an undo request on the current document
357 void undo_undo(UndoMain *undostruct)
359 UndoInfo *undoinfo;
360 guint start_pos, end_pos;
362 g_return_if_fail(undostruct != NULL);
364 if (undostruct->undo == NULL) return;
366 /* The undo data we need is always at the top op the
367 stack. So, therefore, the first one */
368 undoinfo = (UndoInfo *)undostruct->undo->data;
369 g_return_if_fail(undoinfo != NULL);
370 undoinfo->mergeable = FALSE;
371 undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
372 undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
374 /* Check if there is a selection active */
375 start_pos = GTK_EDITABLE(undostruct->text)->selection_start_pos;
376 end_pos = GTK_EDITABLE(undostruct->text)->selection_end_pos;
377 if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos))
378 gtk_editable_select_region(GTK_EDITABLE(undostruct->text),
379 0, 0);
381 /* Move the view (scrollbars) to the correct position */
382 gtk_adjustment_set_value
383 (GTK_ADJUSTMENT(GTK_STEXT(undostruct->text)->vadj),
384 undoinfo->window_position);
386 switch (undoinfo->action) {
387 case UNDO_ACTION_DELETE:
388 gtk_stext_set_point(GTK_STEXT(undostruct->text), undoinfo->start_pos);
389 gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL, NULL, undoinfo->text, -1);
390 debug_print("UNDO_ACTION_DELETE %s\n", undoinfo->text);
391 break;
392 case UNDO_ACTION_INSERT:
393 gtk_stext_set_point(GTK_STEXT(undostruct->text), undoinfo->end_pos);
394 gtk_stext_backward_delete(GTK_STEXT(undostruct->text), undoinfo->end_pos-undoinfo->start_pos);
395 debug_print("UNDO_ACTION_INSERT %d\n", undoinfo->end_pos-undoinfo->start_pos);
396 break;
397 case UNDO_ACTION_REPLACE_INSERT:
398 gtk_stext_set_point(GTK_STEXT(undostruct->text), undoinfo->end_pos);
399 gtk_stext_backward_delete(GTK_STEXT(undostruct->text), undoinfo->end_pos-undoinfo->start_pos);
400 debug_print("UNDO_ACTION_REPLACE %s\n", undoinfo->text);
401 /* "pull" another data structure from the list */
402 undoinfo = (UndoInfo *)undostruct->undo->data;
403 g_return_if_fail(undoinfo != NULL);
404 undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
405 undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
406 g_return_if_fail(undoinfo->action == UNDO_ACTION_REPLACE_DELETE);
407 gtk_stext_set_point(GTK_STEXT(undostruct->text), undoinfo->start_pos);
408 gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL, NULL, undoinfo->text, -1);
409 debug_print("UNDO_ACTION_REPLACE %s\n", undoinfo->text);
410 break;
411 case UNDO_ACTION_REPLACE_DELETE:
412 g_warning("This should not happen. UNDO_REPLACE_DELETE");
413 break;
414 default:
415 g_assert_not_reached();
416 break;
419 undostruct->change_state_func(undostruct,
420 UNDO_STATE_UNCHANGED, UNDO_STATE_TRUE,
421 undostruct->change_state_data);
423 if (undostruct->undo == NULL)
424 undostruct->change_state_func(undostruct,
425 UNDO_STATE_FALSE,
426 UNDO_STATE_UNCHANGED,
427 undostruct->change_state_data);
431 * undo_redo:
432 * @w: not used
433 * @data: not used
435 * executes a redo request on the current document
437 void undo_redo(UndoMain *undostruct)
439 UndoInfo *redoinfo;
440 guint start_pos, end_pos;
442 g_return_if_fail(undostruct != NULL);
444 if (undostruct->redo == NULL) return;
446 redoinfo = (UndoInfo *)undostruct->redo->data;
447 g_return_if_fail (redoinfo != NULL);
448 undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
449 undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
451 /* Check if there is a selection active */
452 start_pos = GTK_EDITABLE(undostruct->text)->selection_start_pos;
453 end_pos = GTK_EDITABLE(undostruct->text)->selection_end_pos;
454 if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos))
455 gtk_editable_select_region(GTK_EDITABLE(undostruct->text), 0, 0);
457 /* Move the view to the right position. */
458 gtk_adjustment_set_value(GTK_ADJUSTMENT(GTK_STEXT(undostruct->text)->vadj),
459 redoinfo->window_position);
461 switch (redoinfo->action) {
462 case UNDO_ACTION_INSERT:
463 gtk_stext_set_point(GTK_STEXT(undostruct->text),
464 redoinfo->start_pos);
465 gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL,
466 NULL, redoinfo->text, -1);
467 debug_print("UNDO_ACTION_DELETE %s\n",redoinfo->text);
468 break;
469 case UNDO_ACTION_DELETE:
470 gtk_stext_set_point(GTK_STEXT(undostruct->text),
471 redoinfo->end_pos);
472 gtk_stext_backward_delete
473 (GTK_STEXT(undostruct->text),
474 redoinfo->end_pos - redoinfo->start_pos);
475 debug_print("UNDO_ACTION_INSERT %d\n",
476 redoinfo->end_pos-redoinfo->start_pos);
477 break;
478 case UNDO_ACTION_REPLACE_DELETE:
479 gtk_stext_set_point(GTK_STEXT(undostruct->text),
480 redoinfo->end_pos);
481 gtk_stext_backward_delete
482 (GTK_STEXT(undostruct->text),
483 redoinfo->end_pos - redoinfo->start_pos);
484 /* "pull" another data structure from the list */
485 redoinfo = (UndoInfo *)undostruct->redo->data;
486 g_return_if_fail(redoinfo != NULL);
487 undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
488 undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
489 g_return_if_fail(redoinfo->action == UNDO_ACTION_REPLACE_INSERT);
490 gtk_stext_set_point(GTK_STEXT(undostruct->text),
491 redoinfo->start_pos);
492 gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL,
493 NULL, redoinfo->text, -1);
494 break;
495 case UNDO_ACTION_REPLACE_INSERT:
496 g_warning("This should not happen. Redo: UNDO_REPLACE_INSERT");
497 break;
498 default:
499 g_assert_not_reached();
500 break;
503 undostruct->change_state_func(undostruct,
504 UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED,
505 undostruct->change_state_data);
507 if (undostruct->redo == NULL)
508 undostruct->change_state_func(undostruct,
509 UNDO_STATE_UNCHANGED,
510 UNDO_STATE_FALSE,
511 undostruct->change_state_data);
514 void undo_insert_text_cb(GtkEditable *editable, gchar *new_text,
515 gint new_text_length, gint *position,
516 UndoMain *undostruct)
518 gchar *text_to_insert;
519 size_t wlen;
521 if (prefs_common.undolevels <= 0) return;
523 Xstrndup_a(text_to_insert, new_text, new_text_length, return);
524 if (MB_CUR_MAX > 1) {
525 wchar_t *wstr;
527 wstr = g_new(wchar_t, new_text_length + 1);
528 wlen = mbstowcs(wstr, text_to_insert, new_text_length + 1);
529 g_free(wstr);
530 if (wlen < 0) return;
531 } else
532 wlen = new_text_length;
534 undo_add(text_to_insert, *position, *position + wlen,
535 UNDO_ACTION_INSERT, undostruct);
538 void undo_delete_text_cb(GtkEditable *editable, gint start_pos,
539 gint end_pos, UndoMain *undostruct)
541 gchar *text_to_delete;
543 if (prefs_common.undolevels <= 0) return;
544 if (start_pos == end_pos) return;
546 text_to_delete = gtk_editable_get_chars(GTK_EDITABLE(editable),
547 start_pos, end_pos);
548 undo_add(text_to_delete, start_pos, end_pos, UNDO_ACTION_DELETE,
549 undostruct);
550 g_free(text_to_delete);
553 void undo_paste_clipboard_cb(GtkEditable *editable, UndoMain *undostruct)
555 if (editable->clipboard_text == NULL) return;
557 debug_print("before Paste: %d\n", undostruct->paste);
558 if (prefs_common.undolevels > 0)
559 if (undo_get_selection(editable, NULL, NULL))
560 undostruct->paste = TRUE;
561 debug_print("after Paste: %d\n", undostruct->paste);
565 * undo_get_selection:
566 * @text: Text to get the selection from
567 * @start: return here the start position of the selection
568 * @end: return here the end position of the selection
570 * Gets the current selection for View
572 * Return Value: TRUE if there is a selection active, FALSE if not
574 static gint undo_get_selection(GtkEditable *text, guint *start, guint *end)
576 guint start_pos, end_pos;
578 start_pos = text->selection_start_pos;
579 end_pos = text->selection_end_pos;
581 /* The user can select from end to start too. If so, swap it*/
582 if (end_pos < start_pos) {
583 guint swap_pos;
584 swap_pos = end_pos;
585 end_pos = start_pos;
586 start_pos = swap_pos;
589 if (start != NULL)
590 *start = start_pos;
592 if (end != NULL)
593 *end = end_pos;
595 if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos))
596 return TRUE;
597 else
598 return FALSE;