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 */
29 #include <string.h> /* for strlen */
30 #include <stdlib.h> /* for mbstowcs */
35 #include "prefs_common.h"
37 typedef struct _UndoInfo UndoInfo;
45 gfloat window_position;
49 static void undo_free_list (GList **list_pointer);
50 static void undo_check_size (UndoMain *undostruct);
51 static gint undo_merge (GList *list,
56 static void undo_add (const gchar *text,
60 UndoMain *undostruct);
61 static gint undo_get_selection (GtkEditable *text,
64 static void undo_insert_text_cb (GtkEditable *editable,
68 UndoMain *undostruct);
69 static void undo_delete_text_cb (GtkEditable *editable,
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)
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);
105 void undo_destroy (UndoMain *undostruct)
107 undo_free_list(&undostruct->undo);
108 undo_free_list(&undostruct->redo);
112 static UndoInfo *undo_object_new(gchar *text, gint start_pos, gint end_pos,
113 UndoAction action, gfloat window_position)
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;
125 static void undo_object_free(UndoInfo *undo)
133 * @list_pointer: list to be freed
135 * frees and undo structure list
137 static void undo_free_list(GList **list_pointer)
140 GList *cur, *list = *list_pointer;
145 debug_print("length of list: %d\n", g_list_length(list));
147 for (cur = list; cur != NULL; cur = cur->next) {
148 nth_redo = cur->data;
149 undo_object_free(nth_redo);
153 *list_pointer = NULL;
156 void undo_set_undo_change_funct(UndoMain *undostruct, UndoChangeState func,
157 GtkWidget *changewidget)
159 g_return_if_fail(undostruct != NULL);
160 undostruct->change_func = func;
161 undostruct->changewidget = changewidget;
166 * @compose: document to check
168 * Checks that the size of compose->undo does not excede settings->undo_levels and
169 * frees any undo level above sett->undo_level.
172 static void undo_check_size(UndoMain *undostruct)
176 if (prefs_common.undolevels < 1)
179 /* No need to check for the redo list size since the undo
180 list gets freed on any call to compose_undo_add */
181 if (g_list_length(undostruct->undo) >= prefs_common.undolevels && prefs_common.undolevels > 0) {
182 nth_undo = g_list_nth_data(undostruct->undo, g_list_length(undostruct->undo) - 1);
183 undostruct->undo = g_list_remove(undostruct->undo, nth_undo);
184 g_free (nth_undo->text);
187 debug_print("g_list_length (undostruct->undo): %d\n", g_list_length(undostruct->undo));
197 * This function tries to merge the undo object at the top of
198 * the stack with a new set of data. So when we undo for example
199 * typing, we can undo the whole word and not each letter by itself
201 * Return Value: TRUE is merge was sucessful, FALSE otherwise
203 static gint undo_merge (GList *list, guint start_pos, guint end_pos, gint action, const guchar* text)
207 gboolean checkit = TRUE;
209 /* This are the cases in which we will NOT merge :
210 1. if (last_undo->mergeable == FALSE)
211 [mergeable = FALSE when the size of the undo data was not 1.
212 or if the data was size = 1 but = '\n' or if the undo object
213 has been "undone" already ]
214 2. The size of text is not 1
215 3. If the new merging data is a '\n'
216 4. If the last char of the undo_last data is a space/tab
217 and the new char is not a space/tab ( so that we undo
218 words and not chars )
219 5. If the type (action) of undo is different from the last one
225 last_undo = list->data;
227 if (!last_undo->mergeable)
230 if (end_pos-start_pos != 1) {
231 last_undo->mergeable = FALSE;
238 if (action != last_undo->action)
241 if (action == UNDO_ACTION_REPLACE_INSERT || action == UNDO_ACTION_REPLACE_DELETE)
244 if (action == UNDO_ACTION_DELETE && checkit) {
245 if (last_undo->start_pos!=end_pos && last_undo->start_pos != start_pos && checkit)
248 if (last_undo->start_pos == start_pos && checkit) {
249 /* Deleted with the delete key */
250 if ( text[0] != ' ' && text[0] != '\t' && checkit &&
251 (last_undo->text[last_undo->end_pos-last_undo->start_pos - 1] == ' '
252 || last_undo->text[last_undo->end_pos-last_undo->start_pos - 1] == '\t'))
255 temp_string = g_strdup_printf("%s%s", last_undo->text, text);
256 g_free(last_undo->text);
257 last_undo->end_pos += 1;
258 last_undo->text = temp_string;
261 /* Deleted with the backspace key */
262 if ( text[0] != ' ' && text[0] != '\t' && checkit &&
263 (last_undo->text[0] == ' '
264 || last_undo->text[0] == '\t'))
267 temp_string = g_strdup_printf("%s%s", text, last_undo->text);
268 g_free(last_undo->text);
269 last_undo->start_pos = start_pos;
270 last_undo->text = temp_string;
273 else if (action == UNDO_ACTION_INSERT && checkit) {
274 if (last_undo->end_pos != start_pos && checkit)
277 /* if ( text[0]!=' ' && text[0]!='\t' &&
278 (last_undo->text [last_undo->end_pos-last_undo->start_pos - 1] ==' '
279 || last_undo->text [last_undo->end_pos-last_undo->start_pos - 1] == '\t'))
280 goto compose_undo_do_not_merge;
283 temp_string = g_strdup_printf("%s%s", last_undo->text, text);
284 g_free(last_undo->text);
285 last_undo->end_pos = end_pos;
286 last_undo->text = temp_string;
290 debug_print("Unknown action [%i] inside undo merge encountered", action);
293 debug_print("Merged: %s\n", text);
297 last_undo->mergeable = FALSE;
307 * @action: either UNDO_ACTION_INSERT or UNDO_ACTION_DELETE
309 * @view: The view so that we save the scroll bar position.
311 * Adds text to the undo stack. It also performs test to limit the number
312 * of undo levels and deltes the redo list
315 static void undo_add(const gchar *text,
316 gint start_pos, gint end_pos,
317 UndoAction action, UndoMain *undostruct)
321 debug_print("undo_add(%i)*%s*\n", strlen (text), text);
323 g_return_if_fail(text != NULL);
324 g_return_if_fail(end_pos >= start_pos);
326 undo_free_list(&undostruct->redo);
328 /* Set the redo sensitivity */
329 undostruct->change_func(undostruct, UNDO_STATE_UNCHANGED, UNDO_STATE_FALSE,
330 undostruct->changewidget);
332 if (undostruct->paste != 0) {
333 if (action == UNDO_ACTION_INSERT)
334 action = UNDO_ACTION_REPLACE_INSERT;
336 action = UNDO_ACTION_REPLACE_DELETE;
337 undostruct->paste = undostruct->paste + 1;
338 if (undostruct->paste == 3)
339 undostruct->paste = 0;
342 if (undo_merge(undostruct->undo, start_pos, end_pos, action, text))
345 undo_check_size(undostruct);
347 debug_print("New: %s Action: %d Paste: %d\n", text, action, undostruct->paste);
349 undoinfo = undo_object_new(g_strdup(text), start_pos, end_pos, action,
350 GTK_ADJUSTMENT(GTK_STEXT(undostruct->text)->vadj)->value);
352 if (end_pos-start_pos != 1 || text[0] == '\n')
353 undoinfo->mergeable = FALSE;
355 undoinfo->mergeable = TRUE;
357 undostruct->undo = g_list_prepend(undostruct->undo, undoinfo);
359 undostruct->change_func(undostruct, UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED, undostruct->changewidget);
367 * Executes an undo request on the current document
369 void undo_undo(UndoMain *undostruct)
372 guint start_pos, end_pos;
374 if (undostruct->undo == NULL)
377 g_return_if_fail(undostruct != NULL);
380 /* The undo data we need is always at the top op the
381 stack. So, therefore, the first one */
382 undoinfo = g_list_nth_data(undostruct->undo, 0);
383 g_return_if_fail(undoinfo != NULL);
384 undoinfo->mergeable = FALSE;
385 undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
386 undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
388 /* Check if there is a selection active */
389 start_pos = GTK_EDITABLE(undostruct->text)->selection_start_pos;
390 end_pos = GTK_EDITABLE(undostruct->text)->selection_end_pos;
391 if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos))
392 gtk_editable_select_region(GTK_EDITABLE(undostruct->text), 0, 0);
394 /* Move the view (scrollbars) to the correct position */
395 gtk_adjustment_set_value(GTK_ADJUSTMENT(GTK_STEXT(undostruct->text)->vadj), undoinfo->window_position);
397 switch (undoinfo->action) {
398 case UNDO_ACTION_DELETE:
399 gtk_stext_set_point(GTK_STEXT(undostruct->text), undoinfo->start_pos);
400 gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL, NULL, undoinfo->text, -1);
401 debug_print("UNDO_ACTION_DELETE %s\n", undoinfo->text);
403 case UNDO_ACTION_INSERT:
404 gtk_stext_set_point(GTK_STEXT(undostruct->text), undoinfo->end_pos);
405 gtk_stext_backward_delete(GTK_STEXT(undostruct->text), undoinfo->end_pos-undoinfo->start_pos);
406 debug_print("UNDO_ACTION_INSERT %d\n", undoinfo->end_pos-undoinfo->start_pos);
408 case UNDO_ACTION_REPLACE_INSERT:
409 gtk_stext_set_point(GTK_STEXT(undostruct->text), undoinfo->end_pos);
410 gtk_stext_backward_delete(GTK_STEXT(undostruct->text), undoinfo->end_pos-undoinfo->start_pos);
411 debug_print("UNDO_ACTION_REPLACE %s\n", undoinfo->text);
412 /* "pull" another data structure from the list */
413 undoinfo = g_list_nth_data(undostruct->undo, 0);
414 g_return_if_fail(undoinfo != NULL);
415 undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
416 undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
417 g_return_if_fail(undoinfo->action == UNDO_ACTION_REPLACE_DELETE);
418 gtk_stext_set_point(GTK_STEXT(undostruct->text), undoinfo->start_pos);
419 gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL, NULL, undoinfo->text, -1);
420 debug_print("UNDO_ACTION_REPLACE %s\n", undoinfo->text);
422 case UNDO_ACTION_REPLACE_DELETE:
423 g_warning("This should not happen. UNDO_REPLACE_DELETE");
426 g_assert_not_reached();
430 undostruct->change_func(undostruct, UNDO_STATE_UNCHANGED,
431 UNDO_STATE_TRUE, undostruct->changewidget);
433 if (g_list_length (undostruct->undo) == 0)
434 undostruct->change_func(undostruct, UNDO_STATE_FALSE,
435 UNDO_STATE_UNCHANGED,
436 undostruct->changewidget);
444 * executes a redo request on the current document
446 void undo_redo(UndoMain *undostruct)
449 guint start_pos, end_pos;
451 if (undostruct->redo == NULL)
454 if (undostruct==NULL)
457 redoinfo = g_list_nth_data(undostruct->redo, 0);
458 g_return_if_fail (redoinfo!=NULL);
459 undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
460 undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
462 /* Check if there is a selection active */
463 start_pos = GTK_EDITABLE(undostruct->text)->selection_start_pos;
464 end_pos = GTK_EDITABLE(undostruct->text)->selection_end_pos;
465 if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos))
466 gtk_editable_select_region(GTK_EDITABLE(undostruct->text), 0, 0);
468 /* Move the view to the right position. */
469 gtk_adjustment_set_value(GTK_ADJUSTMENT(GTK_STEXT(undostruct->text)->vadj),
470 redoinfo->window_position);
472 switch (redoinfo->action) {
473 case UNDO_ACTION_INSERT:
474 gtk_stext_set_point(GTK_STEXT(undostruct->text), redoinfo->start_pos);
475 gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL,
476 NULL, redoinfo->text, -1);
477 debug_print("UNDO_ACTION_DELETE %s\n",redoinfo->text);
479 case UNDO_ACTION_DELETE:
480 gtk_stext_set_point(GTK_STEXT(undostruct->text), redoinfo->end_pos);
481 gtk_stext_backward_delete(GTK_STEXT(undostruct->text),
482 redoinfo->end_pos-redoinfo->start_pos);
483 debug_print("UNDO_ACTION_INSERT %d\n",
484 redoinfo->end_pos-redoinfo->start_pos);
486 case UNDO_ACTION_REPLACE_DELETE:
487 gtk_stext_set_point(GTK_STEXT(undostruct->text), redoinfo->end_pos);
488 gtk_stext_backward_delete(GTK_STEXT(undostruct->text),
489 redoinfo->end_pos-redoinfo->start_pos);
490 /* "pull" another data structure from the list */
491 redoinfo = g_list_nth_data(undostruct->redo, 0);
492 g_return_if_fail(redoinfo != NULL);
493 undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
494 undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
495 g_return_if_fail(redoinfo->action==UNDO_ACTION_REPLACE_INSERT);
496 gtk_stext_set_point(GTK_STEXT(undostruct->text), redoinfo->start_pos);
497 gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL,
498 NULL, redoinfo->text, -1);
500 case UNDO_ACTION_REPLACE_INSERT:
501 g_warning("This should not happen. Redo: UNDO_REPLACE_INSERT");
504 g_assert_not_reached();
508 undostruct->change_func(undostruct, UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED,
509 undostruct->changewidget);
511 if (g_list_length(undostruct->redo) == 0)
512 undostruct->change_func(undostruct, UNDO_STATE_UNCHANGED,
513 UNDO_STATE_FALSE, undostruct->changewidget);
516 void undo_insert_text_cb(GtkEditable *editable, gchar *new_text,
517 gint new_text_length, gint *position,
518 UndoMain *undostruct)
520 guchar *text_to_insert;
523 if (prefs_common.undolevels <= 0) return;
525 Xstrndup_a(text_to_insert, new_text, new_text_length, return);
526 if (MB_CUR_MAX > 1) {
529 Xalloca(wstr, sizeof(wchar_t) * (new_text_length + 1), return);
530 wlen = mbstowcs(wstr, text_to_insert, new_text_length + 1);
531 if (wlen < 0) return;
533 wlen = new_text_length;
535 undo_add(text_to_insert, *position, *position + wlen,
536 UNDO_ACTION_INSERT, undostruct);
539 void undo_delete_text_cb(GtkEditable *editable, gint start_pos,
540 gint end_pos, UndoMain *undostruct)
542 guchar *text_to_delete;
544 if (prefs_common.undolevels <= 0) return;
545 if (start_pos == end_pos) return;
547 text_to_delete = gtk_editable_get_chars(GTK_EDITABLE(editable),
549 undo_add(text_to_delete, start_pos, end_pos, UNDO_ACTION_DELETE,
551 g_free (text_to_delete);
554 void undo_paste_clipboard_cb (GtkEditable *editable, UndoMain *undostruct)
556 debug_print("befor Paste: %d\n", undostruct->paste);
557 if (prefs_common.undolevels > 0)
558 if (undo_get_selection(editable, NULL, NULL))
559 undostruct->paste = TRUE;
560 debug_print("after Paste: %d\n", undostruct->paste);
564 * undo_get_selection:
565 * @text: Text to get the selection from
566 * @start: return here the start position of the selection
567 * @end: return here the end position of the selection
569 * Gets the current selection for View
571 * Return Value: TRUE if there is a selection active, FALSE if not
573 static gint undo_get_selection(GtkEditable *text, guint *start, guint *end)
575 guint start_pos, end_pos;
577 start_pos = text->selection_start_pos;
578 end_pos = text->selection_end_pos;
580 /* The user can select from end to start too. If so, swap it*/
581 if (end_pos < start_pos) {
585 start_pos = swap_pos;
594 if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos))