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 */
23 #include <string.h> /* For strlen */
25 #include "prefs_common.h"
31 typedef struct _UndoInfo UndoInfo;
39 gfloat window_position;
45 * compose_undo_free_list:
46 * @list_pointer: list to be freed
48 * frees and undo structure list
50 void compose_undo_free_list (GList ** list_pointer)
54 GList *list = * list_pointer;
61 debug_print("length of list: %d\n", g_list_length (list));
64 /* a better list traversing should be implemented, here
65 it is O(n^2), it can be O(n) */
66 for (n=0; n < g_list_length (list); n++)
68 nth_redo = g_list_nth_data (list, n);
70 g_warning ("nth_redo==NULL");
72 g_free (nth_redo->text);
83 * Change the sensivity of the menuentries undo and redo
85 static /* DINH V.Hoa */
86 void compose_set_undo (Compose *compose, gint undo_state, gint redo_state)
88 GtkItemFactory *ifactory;
90 debug_print ("\nSet_undo. view:0x%x UNDO:%i REDO:%i\n",
95 g_return_if_fail (compose != NULL);
97 ifactory = gtk_item_factory_from_widget(compose->menubar);
102 case UNDO_STATE_TRUE:
103 if (!compose->undo_state)
105 compose->undo_state = TRUE;
106 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
109 case UNDO_STATE_FALSE:
110 if (compose->undo_state)
112 compose->undo_state = FALSE;
113 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
116 case UNDO_STATE_UNCHANGED:
118 case UNDO_STATE_REFRESH:
119 menu_set_sensitive(ifactory, "/Edit/Undo", compose->undo_state);
122 g_warning ("Undo state not recognized");
128 case UNDO_STATE_TRUE:
129 if (!compose->redo_state)
131 compose->redo_state = TRUE;
132 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
135 case UNDO_STATE_FALSE:
136 if (compose->redo_state)
138 compose->redo_state = FALSE;
139 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
142 case UNDO_STATE_UNCHANGED:
144 case UNDO_STATE_REFRESH:
145 menu_set_sensitive(ifactory, "/Edit/Redo", compose->redo_state);
148 g_warning ("Redo state not recognized");
153 * compose_undo_check_size:
154 * @compose: document to check
156 * Checks that the size of compose->undo does not excede settings->undo_levels and
157 * frees any undo level above sett->undo_level.
162 /* remove only the first-inserted elements if necessary
163 whenever we add a new element
164 remove before inserting a new element */
166 static void compose_undo_check_size (Compose *compose)
170 gint undo_levels = 100;
175 /* No need to check for the redo list size since the undo
176 list gets freed on any call to compose_undo_add */
177 if (g_list_length (compose->undo) > undo_levels && undo_levels > 0)
182 start = g_list_length (compose->undo);
184 for (n = start; n >= end; n--)
186 nth_undo = g_list_nth_data (compose->undo, n - 1);
187 compose->undo = g_list_remove (compose->undo, nth_undo);
188 g_free (nth_undo->text);
196 * compose_undo_merge:
202 * This function tries to merge the undo object at the top of
203 * the stack with a new set of data. So when we undo for example
204 * typing, we can undo the whole word and not each letter by itself
206 * Return Value: TRUE is merge was sucessful, FALSE otherwise
208 static gint compose_undo_merge (GList *list, guint start_pos, guint end_pos, gint action, const guchar* text)
210 guchar * temp_string;
211 UndoInfo * last_undo;
213 /* This are the cases in which we will NOT merge :
214 1. if (last_undo->mergeable == FALSE)
215 [mergeable = FALSE when the size of the undo data was not 1.
216 or if the data was size = 1 but = '\n' or if the undo object
217 has been "undone" already ]
218 2. The size of text is not 1
219 3. If the new merging data is a '\n'
220 4. If the last char of the undo_last data is a space/tab
221 and the new char is not a space/tab ( so that we undo
222 words and not chars )
223 5. If the type (action) of undo is different from the last one
230 /* g_list_first or just list->data */
232 last_undo = g_list_nth_data (list, 0);
234 if (!last_undo->mergeable)
239 if (end_pos-start_pos != 1)
241 last_undo->mergeable = FALSE;
246 /* goto are bad coding */
249 goto compose_undo_do_not_merge;
251 if (action != last_undo->action)
252 goto compose_undo_do_not_merge;
254 if (action == UNDO_ACTION_DELETE)
256 if (last_undo->start_pos!=end_pos && last_undo->start_pos != start_pos)
257 goto compose_undo_do_not_merge;
259 if (last_undo->start_pos == start_pos)
261 /* Deleted with the delete key */
262 if ( text[0]!=' ' && text[0]!='\t' &&
263 (last_undo->text [last_undo->end_pos-last_undo->start_pos - 1] ==' '
264 || last_undo->text [last_undo->end_pos-last_undo->start_pos - 1] == '\t'))
265 goto compose_undo_do_not_merge;
267 temp_string = g_strdup_printf ("%s%s", last_undo->text, text);
268 g_free (last_undo->text);
269 last_undo->end_pos += 1;
270 last_undo->text = temp_string;
274 /* Deleted with the backspace key */
275 if ( text[0]!=' ' && text[0]!='\t' &&
276 (last_undo->text [0] == ' '
277 || last_undo->text [0] == '\t'))
278 goto compose_undo_do_not_merge;
280 temp_string = g_strdup_printf ("%s%s", text, last_undo->text);
281 g_free (last_undo->text);
282 last_undo->start_pos = start_pos;
283 last_undo->text = temp_string;
286 else if (action == UNDO_ACTION_INSERT)
288 if (last_undo->end_pos != start_pos)
289 goto compose_undo_do_not_merge;
291 /* if ( text[0]!=' ' && text[0]!='\t' &&
292 (last_undo->text [last_undo->end_pos-last_undo->start_pos - 1] ==' '
293 || last_undo->text [last_undo->end_pos-last_undo->start_pos - 1] == '\t'))
294 goto compose_undo_do_not_merge;
296 temp_string = g_strdup_printf ("%s%s", last_undo->text, text);
297 g_free (last_undo->text);
298 last_undo->end_pos = end_pos;
299 last_undo->text = temp_string;
302 debug_print ("Unknown action [%i] inside undo merge encountered", action);
304 debug_print ("Merged: %s\n", text);
307 compose_undo_do_not_merge:
308 last_undo->mergeable = FALSE;
317 * @action: either UNDO_ACTION_INSERT or UNDO_ACTION_DELETE
319 * @view: The view so that we save the scroll bar position.
321 * Adds text to the undo stack. It also performs test to limit the number
322 * of undo levels and deltes the redo list
325 void compose_undo_add (const gchar *text, gint start_pos, gint end_pos,
326 UndoAction action, Compose *compose)
330 debug_print ("undo_add(%i)*%s*\n", strlen (text), text);
332 g_return_if_fail (text != NULL);
333 g_return_if_fail (end_pos >= start_pos);
335 compose_undo_free_list (&compose->redo);
337 /* Set the redo sensitivity */
338 compose_set_undo (compose, UNDO_STATE_UNCHANGED, UNDO_STATE_FALSE);
340 if (compose_undo_merge (compose->undo, start_pos, end_pos, action, text))
343 debug_print ("New: %s\n", text);
346 /* build undo objects allocator and destructor functions */
348 undo = g_new (UndoInfo, 1);
349 undo->text = g_strdup (text);
350 undo->start_pos = start_pos;
351 undo->end_pos = end_pos;
352 undo->action = action;
354 undo->window_position = GTK_ADJUSTMENT(GTK_STEXT(compose->text)->vadj)->value;
356 if (end_pos-start_pos!=1 || text[0]=='\n')
357 undo->mergeable = FALSE;
359 undo->mergeable = TRUE;
361 compose->undo = g_list_prepend (compose->undo, undo);
364 /* removal of elements should be done first */
366 compose_undo_check_size (compose);
368 compose_set_undo (compose, UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED);
376 * Executes an undo request on the current document
378 void compose_undo_undo (Compose *compose, gpointer data)
381 guint start_pos, end_pos;
383 if (compose->undo == NULL)
386 g_return_if_fail (compose!=NULL);
389 /* The undo data we need is always at the top op the
390 stack. So, therefore, the first one */
391 undo = g_list_nth_data (compose->undo, 0);
392 g_return_if_fail (undo!=NULL);
393 undo->mergeable = FALSE;
394 compose->redo = g_list_prepend (compose->redo, undo);
395 compose->undo = g_list_remove (compose->undo, undo);
397 /* Check if there is a selection active */
398 start_pos = GTK_EDITABLE(compose->text)->selection_start_pos;
399 end_pos = GTK_EDITABLE(compose->text)->selection_end_pos;
400 if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos))
401 gtk_editable_select_region (GTK_EDITABLE(compose->text), 0, 0);
403 /* Move the view (scrollbars) to the correct position */
404 gtk_adjustment_set_value (GTK_ADJUSTMENT(GTK_STEXT(compose->text)->vadj), undo->window_position);
406 switch (undo->action){
407 case UNDO_ACTION_DELETE:
408 gtk_stext_set_point(GTK_STEXT(compose->text), undo->start_pos);
409 gtk_stext_insert (GTK_STEXT(compose->text), NULL, NULL, NULL, undo->text, -1);
410 debug_print("UNDO_ACTION_DELETE %s\n",undo->text);
412 case UNDO_ACTION_INSERT:
413 gtk_stext_set_point(GTK_STEXT(compose->text), undo->end_pos);
414 gtk_stext_backward_delete (GTK_STEXT(compose->text), undo->end_pos-undo->start_pos);
415 debug_print("UNDO_ACTION_INSERT %d\n",undo->end_pos-undo->start_pos);
418 g_assert_not_reached ();
421 compose_set_undo (compose, UNDO_STATE_UNCHANGED, UNDO_STATE_TRUE);
422 if (g_list_length (compose->undo) == 0)
423 compose_set_undo (compose, UNDO_STATE_FALSE, UNDO_STATE_UNCHANGED);
431 * executes a redo request on the current document
433 void compose_undo_redo (Compose *compose, gpointer data)
436 guint start_pos, end_pos;
438 if (compose->redo == NULL)
444 redo = g_list_nth_data (compose->redo, 0);
445 g_return_if_fail (redo!=NULL);
446 compose->undo = g_list_prepend (compose->undo, redo);
447 compose->redo = g_list_remove (compose->redo, redo);
449 /* Check if there is a selection active */
450 start_pos = GTK_EDITABLE(compose->text)->selection_start_pos;
451 end_pos = GTK_EDITABLE(compose->text)->selection_end_pos;
452 if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos))
453 gtk_editable_select_region (GTK_EDITABLE(compose->text), 0, 0);
455 /* Move the view to the right position. */
456 gtk_adjustment_set_value (GTK_ADJUSTMENT(GTK_STEXT(compose->text)->vadj), redo->window_position);
458 switch (redo->action){
459 case UNDO_ACTION_INSERT:
460 gtk_stext_set_point(GTK_STEXT(compose->text), redo->start_pos);
461 gtk_stext_insert (GTK_STEXT(compose->text), NULL, NULL, NULL, redo->text, -1);
462 debug_print("UNDO_ACTION_DELETE %s\n",redo->text);
464 case UNDO_ACTION_DELETE:
465 gtk_stext_set_point(GTK_STEXT(compose->text), redo->end_pos);
466 gtk_stext_backward_delete (GTK_STEXT(compose->text), redo->end_pos-redo->start_pos);
467 debug_print("UNDO_ACTION_INSERT %d\n",redo->end_pos-redo->start_pos);
470 g_assert_not_reached ();
473 compose_set_undo (compose, UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED);
474 if (g_list_length (compose->redo) == 0)
475 compose_set_undo (compose, UNDO_STATE_UNCHANGED, UNDO_STATE_FALSE);