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"
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
;
142 if (list
== NULL
) return;
144 for (cur
= list
; cur
!= NULL
; cur
= cur
->next
) {
145 undo
= (UndoInfo
*)cur
->data
;
146 undo_object_free(undo
);
150 *list_pointer
= NULL
;
153 void undo_set_change_state_func(UndoMain
*undostruct
, UndoChangeStateFunc func
,
156 g_return_if_fail(undostruct
!= NULL
);
158 undostruct
->change_state_func
= func
;
159 undostruct
->change_state_data
= data
;
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
)
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
);
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
)
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
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 ||
228 action
!= last_undo
->action
||
229 action
== UNDO_ACTION_REPLACE_INSERT
||
230 action
== UNDO_ACTION_REPLACE_DELETE
) {
231 last_undo
->mergeable
= 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
;
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'))
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
;
254 /* Deleted with the backspace key */
255 if (text
[0] != ' ' && text
[0] != '\t' &&
256 (last_undo
->text
[0] == ' ' ||
257 last_undo
->text
[0] == '\t'))
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
;
267 last_undo
->mergeable
= FALSE
;
270 } else if (action
== UNDO_ACTION_INSERT
) {
271 if (last_undo
->end_pos
!= start_pos
) {
272 last_undo
->mergeable
= FALSE
;
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
;
281 debug_print("Unknown action [%i] inside undo merge encountered", action
);
283 debug_print("Merged: %s\n", text
);
292 * @action: either UNDO_ACTION_INSERT or UNDO_ACTION_DELETE
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
)
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
;
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
))
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
;
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
);
355 * Executes an undo request on the current document
357 void undo_undo(UndoMain
*undostruct
)
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
),
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
);
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
);
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
);
411 case UNDO_ACTION_REPLACE_DELETE
:
412 g_warning("This should not happen. UNDO_REPLACE_DELETE");
415 g_assert_not_reached();
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
,
426 UNDO_STATE_UNCHANGED
,
427 undostruct
->change_state_data
);
435 * executes a redo request on the current document
437 void undo_redo(UndoMain
*undostruct
)
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
);
469 case UNDO_ACTION_DELETE
:
470 gtk_stext_set_point(GTK_STEXT(undostruct
->text
),
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
);
478 case UNDO_ACTION_REPLACE_DELETE
:
479 gtk_stext_set_point(GTK_STEXT(undostruct
->text
),
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);
495 case UNDO_ACTION_REPLACE_INSERT
:
496 g_warning("This should not happen. Redo: UNDO_REPLACE_INSERT");
499 g_assert_not_reached();
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
,
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
;
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) {
527 wstr
= g_new(wchar_t, new_text_length
+ 1);
528 wlen
= mbstowcs(wstr
, text_to_insert
, new_text_length
+ 1);
530 if (wlen
< 0) return;
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
),
548 undo_add(text_to_delete
, start_pos
, end_pos
, UNDO_ACTION_DELETE
,
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
) {
586 start_pos
= swap_pos
;
595 if ((start_pos
> 0 || end_pos
> 0) && (start_pos
!= end_pos
))