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 */
34 #include "prefs_common.h"
36 typedef struct _UndoInfo UndoInfo;
44 gfloat window_position;
48 static void undo_free_list (GList **list_pointer);
49 static void undo_check_size (UndoMain *undostruct);
50 static gint undo_merge (GList *list,
55 static void undo_add (const gchar *text,
59 UndoMain *undostruct);
60 static gint undo_get_selection (GtkEditable *text,
63 static void undo_insert_text_cb (GtkEditable *editable,
67 UndoMain *undostruct);
68 static void undo_delete_text_cb (GtkEditable *editable,
71 UndoMain *undostruct);
73 static void undo_paste_clipboard_cb (GtkEditable *editable,
74 UndoMain *undostruct);
76 void undo_undo (UndoMain *undostruct);
77 void undo_redo (UndoMain *undostruct);
80 UndoMain *undo_init(GtkWidget *text)
84 g_return_val_if_fail(text != NULL, NULL);
86 undostruct = g_new(UndoMain, 1);
87 undostruct->text = text;
88 undostruct->undo = NULL;
89 undostruct->redo = NULL;
90 undostruct->paste = 0;
91 undostruct->undo_state = FALSE;
92 undostruct->redo_state = FALSE;
94 gtk_signal_connect(GTK_OBJECT(text), "insert-text",
95 GTK_SIGNAL_FUNC(undo_insert_text_cb), undostruct);
96 gtk_signal_connect(GTK_OBJECT(text), "delete-text",
97 GTK_SIGNAL_FUNC(undo_delete_text_cb), undostruct);
98 gtk_signal_connect(GTK_OBJECT(text), "paste-clipboard",
99 GTK_SIGNAL_FUNC(undo_paste_clipboard_cb), undostruct);
104 void undo_destroy (UndoMain *undostruct)
106 undo_free_list(&undostruct->undo);
107 undo_free_list(&undostruct->redo);
111 static UndoInfo *undo_object_new(gchar *text, gint start_pos, gint end_pos,
112 UndoAction action, gfloat window_position)
115 undoinfo = g_new (UndoInfo, 1);
116 undoinfo->text = text;
117 undoinfo->start_pos = start_pos;
118 undoinfo->end_pos = end_pos;
119 undoinfo->action = action;
120 undoinfo->window_position = window_position;
124 static void undo_object_free(UndoInfo *undo)
132 * @list_pointer: list to be freed
134 * frees and undo structure list
136 static void undo_free_list(GList **list_pointer)
139 GList *cur, *list = *list_pointer;
141 if (list == NULL) return;
143 for (cur = list; cur != NULL; cur = cur->next) {
144 undo = (UndoInfo *)cur->data;
145 undo_object_free(undo);
149 *list_pointer = NULL;
152 void undo_set_change_state_func(UndoMain *undostruct, UndoChangeStateFunc func,
155 g_return_if_fail(undostruct != NULL);
157 undostruct->change_state_func = func;
158 undostruct->change_state_data = data;
163 * @compose: document to check
165 * Checks that the size of compose->undo does not excede settings->undo_levels and
166 * frees any undo level above sett->undo_level.
169 static void undo_check_size(UndoMain *undostruct)
174 if (prefs_common.undolevels < 1) return;
176 /* No need to check for the redo list size since the undo
177 list gets freed on any call to compose_undo_add */
178 length = g_list_length(undostruct->undo);
179 if (length >= prefs_common.undolevels && prefs_common.undolevels > 0) {
180 last_undo = (UndoInfo *)g_list_last(undostruct->undo)->data;
181 undostruct->undo = g_list_remove(undostruct->undo, last_undo);
182 undo_object_free(last_undo);
184 debug_print("g_list_length(undostruct->undo): %d\n", length);
194 * This function tries to merge the undo object at the top of
195 * the stack with a new set of data. So when we undo for example
196 * typing, we can undo the whole word and not each letter by itself
198 * Return Value: TRUE is merge was sucessful, FALSE otherwise
200 static gint undo_merge(GList *list, guint start_pos, guint end_pos,
201 gint action, const guchar *text)
206 /* This are the cases in which we will NOT merge :
207 1. if (last_undo->mergeable == FALSE)
208 [mergeable = FALSE when the size of the undo data was not 1.
209 or if the data was size = 1 but = '\n' or if the undo object
210 has been "undone" already ]
211 2. The size of text is not 1
212 3. If the new merging data is a '\n'
213 4. If the last char of the undo_last data is a space/tab
214 and the new char is not a space/tab ( so that we undo
215 words and not chars )
216 5. If the type (action) of undo is different from the last one
219 if (list == NULL) return FALSE;
221 last_undo = list->data;
223 if (!last_undo->mergeable) return FALSE;
225 if (end_pos - start_pos != 1 ||
227 action != last_undo->action ||
228 action == UNDO_ACTION_REPLACE_INSERT ||
229 action == UNDO_ACTION_REPLACE_DELETE) {
230 last_undo->mergeable = FALSE;
234 if (action == UNDO_ACTION_DELETE) {
235 gboolean checkit = TRUE;
237 if (last_undo->start_pos != end_pos &&
238 last_undo->start_pos != start_pos) {
239 last_undo->mergeable = FALSE;
241 } else if (last_undo->start_pos == start_pos) {
242 /* Deleted with the delete key */
243 if (text[0] != ' ' && text[0] != '\t' &&
244 (last_undo->text[last_undo->end_pos - last_undo->start_pos - 1] == ' ' ||
245 last_undo->text[last_undo->end_pos - last_undo->start_pos - 1] == '\t'))
248 temp_string = g_strdup_printf("%s%s", last_undo->text, text);
249 last_undo->end_pos++;
250 g_free(last_undo->text);
251 last_undo->text = temp_string;
253 /* Deleted with the backspace key */
254 if (text[0] != ' ' && text[0] != '\t' &&
255 (last_undo->text[0] == ' ' ||
256 last_undo->text[0] == '\t'))
259 temp_string = g_strdup_printf("%s%s", text, last_undo->text);
260 last_undo->start_pos = start_pos;
261 g_free(last_undo->text);
262 last_undo->text = temp_string;
266 last_undo->mergeable = FALSE;
269 } else if (action == UNDO_ACTION_INSERT) {
270 if (last_undo->end_pos != start_pos) {
271 last_undo->mergeable = FALSE;
274 temp_string = g_strdup_printf("%s%s", last_undo->text, text);
275 g_free(last_undo->text);
276 last_undo->end_pos = end_pos;
277 last_undo->text = temp_string;
280 debug_print("Unknown action [%i] inside undo merge encountered", action);
282 debug_print("Merged: %s\n", text);
291 * @action: either UNDO_ACTION_INSERT or UNDO_ACTION_DELETE
293 * @view: The view so that we save the scroll bar position.
295 * Adds text to the undo stack. It also performs test to limit the number
296 * of undo levels and deltes the redo list
299 static void undo_add(const gchar *text,
300 gint start_pos, gint end_pos,
301 UndoAction action, UndoMain *undostruct)
305 debug_print("undo_add(%i)*%s*\n", strlen (text), text);
307 g_return_if_fail(text != NULL);
308 g_return_if_fail(end_pos >= start_pos);
310 undo_free_list(&undostruct->redo);
312 /* Set the redo sensitivity */
313 undostruct->change_state_func(undostruct,
314 UNDO_STATE_UNCHANGED, UNDO_STATE_FALSE,
315 undostruct->change_state_data);
317 if (undostruct->paste != 0) {
318 if (action == UNDO_ACTION_INSERT)
319 action = UNDO_ACTION_REPLACE_INSERT;
321 action = UNDO_ACTION_REPLACE_DELETE;
322 undostruct->paste = undostruct->paste + 1;
323 if (undostruct->paste == 3)
324 undostruct->paste = 0;
327 if (undo_merge(undostruct->undo, start_pos, end_pos, action, text))
330 undo_check_size(undostruct);
332 debug_print("New: %s Action: %d Paste: %d\n", text, action, undostruct->paste);
334 undoinfo = undo_object_new(g_strdup(text), start_pos, end_pos, action,
335 GTK_ADJUSTMENT(GTK_STEXT(undostruct->text)->vadj)->value);
337 if (end_pos - start_pos != 1 || text[0] == '\n')
338 undoinfo->mergeable = FALSE;
340 undoinfo->mergeable = TRUE;
342 undostruct->undo = g_list_prepend(undostruct->undo, undoinfo);
344 undostruct->change_state_func(undostruct,
345 UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED,
346 undostruct->change_state_data);
354 * Executes an undo request on the current document
356 void undo_undo(UndoMain *undostruct)
359 guint start_pos, end_pos;
361 g_return_if_fail(undostruct != NULL);
363 if (undostruct->undo == NULL) return;
365 /* The undo data we need is always at the top op the
366 stack. So, therefore, the first one */
367 undoinfo = (UndoInfo *)undostruct->undo->data;
368 g_return_if_fail(undoinfo != NULL);
369 undoinfo->mergeable = FALSE;
370 undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
371 undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
373 /* Check if there is a selection active */
374 start_pos = GTK_EDITABLE(undostruct->text)->selection_start_pos;
375 end_pos = GTK_EDITABLE(undostruct->text)->selection_end_pos;
376 if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos))
377 gtk_editable_select_region(GTK_EDITABLE(undostruct->text),
380 /* Move the view (scrollbars) to the correct position */
381 gtk_adjustment_set_value
382 (GTK_ADJUSTMENT(GTK_STEXT(undostruct->text)->vadj),
383 undoinfo->window_position);
385 switch (undoinfo->action) {
386 case UNDO_ACTION_DELETE:
387 gtk_stext_set_point(GTK_STEXT(undostruct->text), undoinfo->start_pos);
388 gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL, NULL, undoinfo->text, -1);
389 debug_print("UNDO_ACTION_DELETE %s\n", undoinfo->text);
391 case UNDO_ACTION_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_INSERT %d\n", undoinfo->end_pos-undoinfo->start_pos);
396 case UNDO_ACTION_REPLACE_INSERT:
397 gtk_stext_set_point(GTK_STEXT(undostruct->text), undoinfo->end_pos);
398 gtk_stext_backward_delete(GTK_STEXT(undostruct->text), undoinfo->end_pos-undoinfo->start_pos);
399 debug_print("UNDO_ACTION_REPLACE %s\n", undoinfo->text);
400 /* "pull" another data structure from the list */
401 undoinfo = (UndoInfo *)undostruct->undo->data;
402 g_return_if_fail(undoinfo != NULL);
403 undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
404 undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
405 g_return_if_fail(undoinfo->action == UNDO_ACTION_REPLACE_DELETE);
406 gtk_stext_set_point(GTK_STEXT(undostruct->text), undoinfo->start_pos);
407 gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL, NULL, undoinfo->text, -1);
408 debug_print("UNDO_ACTION_REPLACE %s\n", undoinfo->text);
410 case UNDO_ACTION_REPLACE_DELETE:
411 g_warning("This should not happen. UNDO_REPLACE_DELETE");
414 g_assert_not_reached();
418 undostruct->change_state_func(undostruct,
419 UNDO_STATE_UNCHANGED, UNDO_STATE_TRUE,
420 undostruct->change_state_data);
422 if (undostruct->undo == NULL)
423 undostruct->change_state_func(undostruct,
425 UNDO_STATE_UNCHANGED,
426 undostruct->change_state_data);
434 * executes a redo request on the current document
436 void undo_redo(UndoMain *undostruct)
439 guint start_pos, end_pos;
441 g_return_if_fail(undostruct != NULL);
443 if (undostruct->redo == NULL) return;
445 redoinfo = (UndoInfo *)undostruct->redo->data;
446 g_return_if_fail (redoinfo != NULL);
447 undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
448 undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
450 /* Check if there is a selection active */
451 start_pos = GTK_EDITABLE(undostruct->text)->selection_start_pos;
452 end_pos = GTK_EDITABLE(undostruct->text)->selection_end_pos;
453 if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos))
454 gtk_editable_select_region(GTK_EDITABLE(undostruct->text), 0, 0);
456 /* Move the view to the right position. */
457 gtk_adjustment_set_value(GTK_ADJUSTMENT(GTK_STEXT(undostruct->text)->vadj),
458 redoinfo->window_position);
460 switch (redoinfo->action) {
461 case UNDO_ACTION_INSERT:
462 gtk_stext_set_point(GTK_STEXT(undostruct->text),
463 redoinfo->start_pos);
464 gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL,
465 NULL, redoinfo->text, -1);
466 debug_print("UNDO_ACTION_DELETE %s\n",redoinfo->text);
468 case UNDO_ACTION_DELETE:
469 gtk_stext_set_point(GTK_STEXT(undostruct->text),
471 gtk_stext_backward_delete
472 (GTK_STEXT(undostruct->text),
473 redoinfo->end_pos - redoinfo->start_pos);
474 debug_print("UNDO_ACTION_INSERT %d\n",
475 redoinfo->end_pos-redoinfo->start_pos);
477 case UNDO_ACTION_REPLACE_DELETE:
478 gtk_stext_set_point(GTK_STEXT(undostruct->text),
480 gtk_stext_backward_delete
481 (GTK_STEXT(undostruct->text),
482 redoinfo->end_pos - redoinfo->start_pos);
483 /* "pull" another data structure from the list */
484 redoinfo = (UndoInfo *)undostruct->redo->data;
485 g_return_if_fail(redoinfo != NULL);
486 undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
487 undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
488 g_return_if_fail(redoinfo->action == UNDO_ACTION_REPLACE_INSERT);
489 gtk_stext_set_point(GTK_STEXT(undostruct->text),
490 redoinfo->start_pos);
491 gtk_stext_insert(GTK_STEXT(undostruct->text), NULL, NULL,
492 NULL, redoinfo->text, -1);
494 case UNDO_ACTION_REPLACE_INSERT:
495 g_warning("This should not happen. Redo: UNDO_REPLACE_INSERT");
498 g_assert_not_reached();
502 undostruct->change_state_func(undostruct,
503 UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED,
504 undostruct->change_state_data);
506 if (undostruct->redo == NULL)
507 undostruct->change_state_func(undostruct,
508 UNDO_STATE_UNCHANGED,
510 undostruct->change_state_data);
513 void undo_insert_text_cb(GtkEditable *editable, gchar *new_text,
514 gint new_text_length, gint *position,
515 UndoMain *undostruct)
517 gchar *text_to_insert;
520 if (prefs_common.undolevels <= 0) return;
522 Xstrndup_a(text_to_insert, new_text, new_text_length, return);
523 if (MB_CUR_MAX > 1) {
526 wstr = g_new(wchar_t, new_text_length + 1);
527 wlen = mbstowcs(wstr, text_to_insert, new_text_length + 1);
529 if (wlen < 0) return;
531 wlen = new_text_length;
533 undo_add(text_to_insert, *position, *position + wlen,
534 UNDO_ACTION_INSERT, undostruct);
537 void undo_delete_text_cb(GtkEditable *editable, gint start_pos,
538 gint end_pos, UndoMain *undostruct)
540 gchar *text_to_delete;
542 if (prefs_common.undolevels <= 0) return;
543 if (start_pos == end_pos) return;
545 text_to_delete = gtk_editable_get_chars(GTK_EDITABLE(editable),
547 undo_add(text_to_delete, start_pos, end_pos, UNDO_ACTION_DELETE,
549 g_free(text_to_delete);
552 void undo_paste_clipboard_cb(GtkEditable *editable, UndoMain *undostruct)
554 if (editable->clipboard_text == NULL) return;
556 debug_print("before 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))