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 */
24 #include <string.h> /* For strlen */
30 typedef struct _UndoInfo UndoInfo;
38 gfloat window_position;
42 static void undo_free_list (GList **list_pointer);
43 static void undo_check_size (UndoMain *undostruct);
44 static gint undo_merge (GList *list, guint start_pos,
45 guint end_pos, gint action,
47 static void undo_add (const gchar *text, gint start_pos, gint end_pos,
48 UndoAction action, UndoMain *undostruct);
49 static gint undo_get_selection (GtkEditable *text, guint *start, guint *end);
50 static void undo_insert_text_cb (GtkEditable *editable, gchar *new_text,
51 gint new_text_length, gint *position,
52 UndoMain *undostruct);
53 static void undo_delete_text_cb (GtkEditable *editable, gint start_pos,
54 gint end_pos, UndoMain *undostruct);
55 static void undo_paste_clipboard_cb (GtkEditable *editable, UndoMain *undostruct);
56 void undo_undo (UndoMain *undostruct);
57 void undo_redo (UndoMain *undostruct);
60 UndoMain *undo_init (GtkWidget *text)
64 g_return_if_fail(text != NULL);
66 undostruct = g_new(UndoMain, 1);
67 undostruct->text = text;
68 undostruct->undo = NULL;
69 undostruct->redo = NULL;
70 undostruct->paste = 0;
71 undostruct->undo_state = FALSE;
72 undostruct->redo_state = FALSE;
73 debug_print("undostruct: %x\n", &undostruct);
75 gtk_signal_connect(GTK_OBJECT(text), "insert-text",
76 GTK_SIGNAL_FUNC(undo_insert_text_cb), undostruct);
77 gtk_signal_connect(GTK_OBJECT(text), "delete-text",
78 GTK_SIGNAL_FUNC(undo_delete_text_cb), undostruct);
79 gtk_signal_connect(GTK_OBJECT(text), "paste-clipboard",
80 GTK_SIGNAL_FUNC(undo_paste_clipboard_cb), undostruct);
85 void undo_destroy (UndoMain *undostruct)
87 undo_free_list(&undostruct->undo);
88 undo_free_list(&undostruct->redo);
92 static UndoInfo *undo_object_new(gchar *text, gint start_pos, gint end_pos,
93 UndoAction action, gfloat window_position)
96 undoinfo = g_new (UndoInfo, 1);
97 undoinfo->text = text;
98 undoinfo->start_pos = start_pos;
99 undoinfo->end_pos = end_pos;
100 undoinfo->action = action;
101 undoinfo->window_position = window_position;
105 static void undo_object_free(UndoInfo *undo)
113 * @list_pointer: list to be freed
115 * frees and undo structure list
117 static void undo_free_list(GList **list_pointer)
121 GList *cur, *list = *list_pointer;
126 debug_print("length of list: %d\n", g_list_length(list));
128 for (cur = list; cur != NULL; cur = cur->next) {
129 nth_redo = cur->data;
130 undo_object_free(nth_redo);
134 *list_pointer = NULL;
137 void undo_set_undo_change_funct(UndoMain *undostruct, UndoChangeState func,
138 GtkWidget *changewidget)
140 g_return_if_fail(undostruct != NULL);
141 undostruct->change_func = func;
142 undostruct->changewidget = changewidget;
147 * @compose: document to check
149 * Checks that the size of compose->undo does not excede settings->undo_levels and
150 * frees any undo level above sett->undo_level.
153 static void undo_check_size(UndoMain *undostruct)
157 gint undo_levels = 50;
162 /* No need to check for the redo list size since the undo
163 list gets freed on any call to compose_undo_add */
164 if (g_list_length(undostruct->undo) >= undo_levels && undo_levels > 0) {
165 nth_undo = g_list_nth_data(undostruct->undo, g_list_length(undostruct->undo) - 1);
166 undostruct->undo = g_list_remove(undostruct->undo, nth_undo);
167 g_free (nth_undo->text);
170 debug_print("g_list_length (undostruct->undo): %d\n", g_list_length(undostruct->undo));
180 * This function tries to merge the undo object at the top of
181 * the stack with a new set of data. So when we undo for example
182 * typing, we can undo the whole word and not each letter by itself
184 * Return Value: TRUE is merge was sucessful, FALSE otherwise
186 static gint undo_merge (GList *list, guint start_pos, guint end_pos, gint action, const guchar* text)
190 gboolean checkit = TRUE;
192 /* This are the cases in which we will NOT merge :
193 1. if (last_undo->mergeable == FALSE)
194 [mergeable = FALSE when the size of the undo data was not 1.
195 or if the data was size = 1 but = '\n' or if the undo object
196 has been "undone" already ]
197 2. The size of text is not 1
198 3. If the new merging data is a '\n'
199 4. If the last char of the undo_last data is a space/tab
200 and the new char is not a space/tab ( so that we undo
201 words and not chars )
202 5. If the type (action) of undo is different from the last one
208 last_undo = list->data;
210 if (!last_undo->mergeable)
213 if (end_pos-start_pos != 1) {
214 last_undo->mergeable = FALSE;
221 if (action != last_undo->action)
224 if (action == UNDO_ACTION_REPLACE_INSERT || action == UNDO_ACTION_REPLACE_DELETE)
227 if (action == UNDO_ACTION_DELETE && checkit) {
228 if (last_undo->start_pos!=end_pos && last_undo->start_pos != start_pos && checkit)
231 if (last_undo->start_pos == start_pos && checkit) {
232 /* Deleted with the delete key */
233 if ( text[0] != ' ' && text[0] != '\t' && checkit &&
234 (last_undo->text[last_undo->end_pos-last_undo->start_pos - 1] == ' '
235 || last_undo->text[last_undo->end_pos-last_undo->start_pos - 1] == '\t'))
238 temp_string = g_strdup_printf("%s%s", last_undo->text, text);
239 g_free(last_undo->text);
240 last_undo->end_pos += 1;
241 last_undo->text = temp_string;
244 /* Deleted with the backspace key */
245 if ( text[0] != ' ' && text[0] != '\t' && checkit &&
246 (last_undo->text[0] == ' '
247 || last_undo->text[0] == '\t'))
250 temp_string = g_strdup_printf("%s%s", text, last_undo->text);
251 g_free(last_undo->text);
252 last_undo->start_pos = start_pos;
253 last_undo->text = temp_string;
256 else if (action == UNDO_ACTION_INSERT && checkit) {
257 if (last_undo->end_pos != start_pos && checkit)
260 /* if ( text[0]!=' ' && text[0]!='\t' &&
261 (last_undo->text [last_undo->end_pos-last_undo->start_pos - 1] ==' '
262 || last_undo->text [last_undo->end_pos-last_undo->start_pos - 1] == '\t'))
263 goto compose_undo_do_not_merge;
266 temp_string = g_strdup_printf("%s%s", last_undo->text, text);
267 g_free(last_undo->text);
268 last_undo->end_pos = end_pos;
269 last_undo->text = temp_string;
273 debug_print("Unknown action [%i] inside undo merge encountered", action);
276 debug_print("Merged: %s\n", text);
280 last_undo->mergeable = FALSE;
290 * @action: either UNDO_ACTION_INSERT or UNDO_ACTION_DELETE
292 * @view: The view so that we save the scroll bar position.
294 * Adds text to the undo stack. It also performs test to limit the number
295 * of undo levels and deltes the redo list
298 static void undo_add(const gchar *text,
299 gint start_pos, gint end_pos,
300 UndoAction action, UndoMain *undostruct)
304 debug_print("undo_add(%i)*%s*\n", strlen (text), text);
306 g_return_if_fail(text != NULL);
307 g_return_if_fail(end_pos >= start_pos);
309 undo_free_list(&undostruct->redo);
311 /* Set the redo sensitivity */
312 undostruct->change_func(undostruct, UNDO_STATE_UNCHANGED, UNDO_STATE_FALSE,
313 undostruct->changewidget);
315 if (undostruct->paste != 0) {
316 if (action == UNDO_ACTION_INSERT)
317 action = UNDO_ACTION_REPLACE_INSERT;
319 action = UNDO_ACTION_REPLACE_DELETE;
320 undostruct->paste = undostruct->paste + 1;
321 if (undostruct->paste == 3)
322 undostruct->paste = 0;
325 if (undo_merge(undostruct->undo, start_pos, end_pos, action, text))
328 undo_check_size(undostruct);
330 debug_print("New: %s Action: %d Paste: %d\n", text, action, undostruct->paste);
332 undoinfo = undo_object_new(g_strdup(text), start_pos, end_pos, action,
333 GTK_ADJUSTMENT(GTK_STEXT(undostruct->text)->vadj)->value);
335 if (end_pos-start_pos != 1 || text[0] == '\n')
336 undoinfo->mergeable = FALSE;
338 undoinfo->mergeable = TRUE;
340 undostruct->undo = g_list_prepend(undostruct->undo, undoinfo);
342 undostruct->change_func(undostruct, UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED, undostruct->changewidget);
350 * Executes an undo request on the current document
352 void undo_undo(UndoMain *undostruct)
355 guint start_pos, end_pos;
357 if (undostruct->undo == NULL)
360 g_return_if_fail(undostruct != NULL);
363 /* The undo data we need is always at the top op the
364 stack. So, therefore, the first one */
365 undoinfo = g_list_nth_data(undostruct->undo, 0);
366 g_return_if_fail(undoinfo != NULL);
367 undoinfo->mergeable = FALSE;
368 undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
369 undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
371 /* Check if there is a selection active */
372 start_pos = GTK_EDITABLE(undostruct->text)->selection_start_pos;
373 end_pos = GTK_EDITABLE(undostruct->text)->selection_end_pos;
374 if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos))
375 gtk_editable_select_region(GTK_EDITABLE(undostruct->text), 0, 0);
377 /* Move the view (scrollbars) to the correct position */
378 gtk_adjustment_set_value(GTK_ADJUSTMENT(GTK_STEXT(undostruct->text)->vadj), undoinfo->window_position);
380 switch (undoinfo->action) {
381 case UNDO_ACTION_DELETE:
382 gtk_stext_set_point(GTK_STEXT(undostruct->text), undoinfo->start_pos);
383 gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL, NULL, undoinfo->text, -1);
384 debug_print("UNDO_ACTION_DELETE %s\n", undoinfo->text);
386 case UNDO_ACTION_INSERT:
387 gtk_stext_set_point(GTK_STEXT(undostruct->text), undoinfo->end_pos);
388 gtk_stext_backward_delete(GTK_STEXT(undostruct->text), undoinfo->end_pos-undoinfo->start_pos);
389 debug_print("UNDO_ACTION_INSERT %d\n", undoinfo->end_pos-undoinfo->start_pos);
391 case UNDO_ACTION_REPLACE_INSERT:
392 gtk_stext_set_point(GTK_STEXT(undostruct->text), undoinfo->end_pos);
393 gtk_stext_backward_delete(GTK_STEXT(undostruct->text), undoinfo->end_pos-undoinfo->start_pos);
394 debug_print("UNDO_ACTION_REPLACE %s\n", undoinfo->text);
395 /* "pull" another data structure from the list */
396 undoinfo = g_list_nth_data(undostruct->undo, 0);
397 g_return_if_fail(undoinfo != NULL);
398 undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
399 undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
400 g_return_if_fail(undoinfo->action == UNDO_ACTION_REPLACE_DELETE);
401 gtk_stext_set_point(GTK_STEXT(undostruct->text), undoinfo->start_pos);
402 gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL, NULL, undoinfo->text, -1);
403 debug_print("UNDO_ACTION_REPLACE %s\n", undoinfo->text);
405 case UNDO_ACTION_REPLACE_DELETE:
406 g_warning("This should not happen. UNDO_REPLACE_DELETE");
409 g_assert_not_reached();
413 undostruct->change_func(undostruct, UNDO_STATE_UNCHANGED,
414 UNDO_STATE_TRUE, undostruct->changewidget);
416 if (g_list_length (undostruct->undo) == 0)
417 undostruct->change_func(undostruct, UNDO_STATE_FALSE,
418 UNDO_STATE_UNCHANGED,
419 undostruct->changewidget);
427 * executes a redo request on the current document
429 void undo_redo(UndoMain *undostruct)
432 guint start_pos, end_pos;
434 if (undostruct->redo == NULL)
437 if (undostruct==NULL)
440 redoinfo = g_list_nth_data(undostruct->redo, 0);
441 g_return_if_fail (redoinfo!=NULL);
442 undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
443 undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
445 /* Check if there is a selection active */
446 start_pos = GTK_EDITABLE(undostruct->text)->selection_start_pos;
447 end_pos = GTK_EDITABLE(undostruct->text)->selection_end_pos;
448 if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos))
449 gtk_editable_select_region(GTK_EDITABLE(undostruct->text), 0, 0);
451 /* Move the view to the right position. */
452 gtk_adjustment_set_value(GTK_ADJUSTMENT(GTK_STEXT(undostruct->text)->vadj),
453 redoinfo->window_position);
455 switch (redoinfo->action) {
456 case UNDO_ACTION_INSERT:
457 gtk_stext_set_point(GTK_STEXT(undostruct->text), redoinfo->start_pos);
458 gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL,
459 NULL, redoinfo->text, -1);
460 debug_print("UNDO_ACTION_DELETE %s\n",redoinfo->text);
462 case UNDO_ACTION_DELETE:
463 gtk_stext_set_point(GTK_STEXT(undostruct->text), redoinfo->end_pos);
464 gtk_stext_backward_delete(GTK_STEXT(undostruct->text),
465 redoinfo->end_pos-redoinfo->start_pos);
466 debug_print("UNDO_ACTION_INSERT %d\n",
467 redoinfo->end_pos-redoinfo->start_pos);
469 case UNDO_ACTION_REPLACE_DELETE:
470 gtk_stext_set_point(GTK_STEXT(undostruct->text), redoinfo->end_pos);
471 gtk_stext_backward_delete(GTK_STEXT(undostruct->text),
472 redoinfo->end_pos-redoinfo->start_pos);
473 /* "pull" another data structure from the list */
474 redoinfo = g_list_nth_data(undostruct->redo, 0);
475 g_return_if_fail(redoinfo != NULL);
476 undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
477 undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
478 g_return_if_fail(redoinfo->action==UNDO_ACTION_REPLACE_INSERT);
479 gtk_stext_set_point(GTK_STEXT(undostruct->text), redoinfo->start_pos);
480 gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL,
481 NULL, redoinfo->text, -1);
483 case UNDO_ACTION_REPLACE_INSERT:
484 g_warning("This should not happen. Redo: UNDO_REPLACE_INSERT");
487 g_assert_not_reached();
491 undostruct->change_func(undostruct, UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED,
492 undostruct->changewidget);
494 if (g_list_length(undostruct->redo) == 0)
495 undostruct->change_func(undostruct, UNDO_STATE_UNCHANGED,
496 UNDO_STATE_FALSE, undostruct->changewidget);
499 void undo_insert_text_cb(GtkEditable *editable, gchar *new_text,
500 gint new_text_length, gint *position,
501 UndoMain *undostruct)
503 guchar *text_to_insert;
505 text_to_insert = g_strndup(new_text, new_text_length);
506 undo_add(text_to_insert, *position, (*position + new_text_length),
507 UNDO_ACTION_INSERT, undostruct);
508 g_free (text_to_insert);
511 void undo_delete_text_cb(GtkEditable *editable, gint start_pos,
512 gint end_pos, UndoMain *undostruct)
514 guchar *text_to_delete;
516 if (start_pos == end_pos )
519 text_to_delete = gtk_editable_get_chars(GTK_EDITABLE(editable),
521 undo_add(text_to_delete, start_pos, end_pos, UNDO_ACTION_DELETE, undostruct);
522 g_free (text_to_delete);
525 void undo_paste_clipboard_cb (GtkEditable *editable, UndoMain *undostruct)
527 debug_print("befor Paste: %d\n", undostruct->paste);
528 if (undo_get_selection(editable, NULL, NULL))
529 undostruct->paste = TRUE;
530 debug_print("after Paste: %d\n", undostruct->paste);
534 * undo_get_selection:
535 * @text: Text to get the selection from
536 * @start: return here the start position of the selection
537 * @end: return here the end position of the selection
539 * Gets the current selection for View
541 * Return Value: TRUE if there is a selection active, FALSE if not
543 static gint undo_get_selection(GtkEditable *text, guint *start, guint *end)
545 guint start_pos, end_pos;
547 start_pos = text->selection_start_pos;
548 end_pos = text->selection_end_pos;
550 /* The user can select from end to start too. If so, swap it*/
551 if (end_pos < start_pos) {
555 start_pos = swap_pos;
564 if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos))