2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2006 Hiroyuki Yamamoto and the Claws Mail team
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 (GtkTextView
*textview
,
63 static void undo_insert_text_cb (GtkTextBuffer
*textbuf
,
67 UndoMain
*undostruct
);
68 static void undo_delete_text_cb (GtkTextBuffer
*textbuf
,
71 UndoMain
*undostruct
);
73 static void undo_paste_clipboard_cb (GtkTextView
*textview
,
74 UndoMain
*undostruct
);
76 void undo_undo (UndoMain
*undostruct
);
77 void undo_redo (UndoMain
*undostruct
);
80 UndoMain
*undo_init(GtkWidget
*text
)
83 GtkTextView
*textview
= GTK_TEXT_VIEW(text
);
84 GtkTextBuffer
*textbuf
= gtk_text_view_get_buffer(textview
);
86 g_return_val_if_fail(text
!= NULL
, NULL
);
88 undostruct
= g_new0(UndoMain
, 1);
89 undostruct
->textview
= textview
;
90 undostruct
->undo
= NULL
;
91 undostruct
->redo
= NULL
;
92 undostruct
->paste
= 0;
93 undostruct
->undo_state
= FALSE
;
94 undostruct
->redo_state
= FALSE
;
96 g_signal_connect(G_OBJECT(textbuf
), "insert-text",
97 G_CALLBACK(undo_insert_text_cb
), undostruct
);
98 g_signal_connect(G_OBJECT(textbuf
), "delete-range",
99 G_CALLBACK(undo_delete_text_cb
), undostruct
);
100 g_signal_connect(G_OBJECT(textview
), "paste-clipboard",
101 G_CALLBACK(undo_paste_clipboard_cb
), undostruct
);
106 void undo_destroy (UndoMain
*undostruct
)
108 undo_free_list(&undostruct
->undo
);
109 undo_free_list(&undostruct
->redo
);
113 static UndoInfo
*undo_object_new(gchar
*text
, gint start_pos
, gint end_pos
,
114 UndoAction action
, gfloat window_position
)
117 undoinfo
= g_new (UndoInfo
, 1);
118 undoinfo
->text
= text
;
119 undoinfo
->start_pos
= start_pos
;
120 undoinfo
->end_pos
= end_pos
;
121 undoinfo
->action
= action
;
122 undoinfo
->window_position
= window_position
;
126 static void undo_object_free(UndoInfo
*undo
)
134 * @list_pointer: list to be freed
136 * frees and undo structure list
138 static void undo_free_list(GList
**list_pointer
)
141 GList
*cur
, *list
= *list_pointer
;
143 if (list
== NULL
) return;
145 for (cur
= list
; cur
!= NULL
; cur
= cur
->next
) {
146 undo
= (UndoInfo
*)cur
->data
;
147 undo_object_free(undo
);
151 *list_pointer
= NULL
;
154 void undo_set_change_state_func(UndoMain
*undostruct
, UndoChangeStateFunc func
,
157 g_return_if_fail(undostruct
!= NULL
);
159 undostruct
->change_state_func
= func
;
160 undostruct
->change_state_data
= data
;
165 * @compose: document to check
167 * Checks that the size of compose->undo does not excede settings->undo_levels and
168 * frees any undo level above sett->undo_level.
171 static void undo_check_size(UndoMain
*undostruct
)
176 if (prefs_common
.undolevels
< 1) return;
178 /* No need to check for the redo list size since the undo
179 list gets freed on any call to compose_undo_add */
180 length
= g_list_length(undostruct
->undo
);
181 if (length
>= prefs_common
.undolevels
&& prefs_common
.undolevels
> 0) {
182 last_undo
= (UndoInfo
*)g_list_last(undostruct
->undo
)->data
;
183 undostruct
->undo
= g_list_remove(undostruct
->undo
, last_undo
);
184 undo_object_free(last_undo
);
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 if (last_undo
->start_pos
!= end_pos
&&
237 last_undo
->start_pos
!= start_pos
) {
238 last_undo
->mergeable
= FALSE
;
240 } else if (last_undo
->start_pos
== start_pos
) {
241 /* Deleted with the delete key */
242 temp_string
= g_strdup_printf("%s%s", last_undo
->text
, text
);
243 last_undo
->end_pos
++;
244 g_free(last_undo
->text
);
245 last_undo
->text
= temp_string
;
247 /* Deleted with the backspace key */
248 temp_string
= g_strdup_printf("%s%s", text
, last_undo
->text
);
249 last_undo
->start_pos
= start_pos
;
250 g_free(last_undo
->text
);
251 last_undo
->text
= temp_string
;
253 } else if (action
== UNDO_ACTION_INSERT
) {
254 if (last_undo
->end_pos
!= start_pos
) {
255 last_undo
->mergeable
= FALSE
;
258 temp_string
= g_strdup_printf("%s%s", last_undo
->text
, text
);
259 g_free(last_undo
->text
);
260 last_undo
->end_pos
= end_pos
;
261 last_undo
->text
= temp_string
;
264 debug_print("Unknown action [%i] inside undo merge encountered", action
);
274 * @action: either UNDO_ACTION_INSERT or UNDO_ACTION_DELETE
276 * @view: The view so that we save the scroll bar position.
278 * Adds text to the undo stack. It also performs test to limit the number
279 * of undo levels and deltes the redo list
282 static void undo_add(const gchar
*text
,
283 gint start_pos
, gint end_pos
,
284 UndoAction action
, UndoMain
*undostruct
)
289 g_return_if_fail(text
!= NULL
);
290 g_return_if_fail(end_pos
>= start_pos
);
292 undo_free_list(&undostruct
->redo
);
294 /* Set the redo sensitivity */
295 undostruct
->change_state_func(undostruct
,
296 UNDO_STATE_UNCHANGED
, UNDO_STATE_FALSE
,
297 undostruct
->change_state_data
);
299 if (undostruct
->paste
!= 0) {
300 if (action
== UNDO_ACTION_INSERT
)
301 action
= UNDO_ACTION_REPLACE_INSERT
;
303 action
= UNDO_ACTION_REPLACE_DELETE
;
304 undostruct
->paste
= undostruct
->paste
+ 1;
305 if (undostruct
->paste
== 3)
306 undostruct
->paste
= 0;
309 if (undo_merge(undostruct
->undo
, start_pos
, end_pos
, action
, text
))
312 undo_check_size(undostruct
);
314 vadj
= GTK_ADJUSTMENT(GTK_TEXT_VIEW(undostruct
->textview
)->vadjustment
);
315 undoinfo
= undo_object_new(g_strdup(text
), start_pos
, end_pos
, action
,
318 if (end_pos
- start_pos
!= 1 || text
[0] == '\n')
319 undoinfo
->mergeable
= FALSE
;
321 undoinfo
->mergeable
= TRUE
;
323 undostruct
->undo
= g_list_prepend(undostruct
->undo
, undoinfo
);
325 undostruct
->change_state_func(undostruct
,
326 UNDO_STATE_TRUE
, UNDO_STATE_UNCHANGED
,
327 undostruct
->change_state_data
);
335 * Executes an undo request on the current document
337 void undo_undo(UndoMain
*undostruct
)
340 GtkTextView
*textview
;
341 GtkTextBuffer
*buffer
;
342 GtkTextIter iter
, start_iter
, end_iter
;
345 g_return_if_fail(undostruct
!= NULL
);
347 if (undostruct
->undo
== NULL
) return;
349 /* The undo data we need is always at the top op the
350 stack. So, therefore, the first one */
351 undoinfo
= (UndoInfo
*)undostruct
->undo
->data
;
352 g_return_if_fail(undoinfo
!= NULL
);
353 undoinfo
->mergeable
= FALSE
;
354 undostruct
->redo
= g_list_prepend(undostruct
->redo
, undoinfo
);
355 undostruct
->undo
= g_list_remove(undostruct
->undo
, undoinfo
);
357 textview
= undostruct
->textview
;
358 buffer
= gtk_text_view_get_buffer(textview
);
360 undo_block(undostruct
);
362 /* Check if there is a selection active */
363 mark
= gtk_text_buffer_get_insert(buffer
);
364 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
365 gtk_text_buffer_place_cursor(buffer
, &iter
);
367 /* Move the view (scrollbars) to the correct position */
368 gtk_adjustment_set_value
369 (GTK_ADJUSTMENT(textview
->vadjustment
),
370 undoinfo
->window_position
);
372 switch (undoinfo
->action
) {
373 case UNDO_ACTION_DELETE
:
374 gtk_text_buffer_get_iter_at_offset(buffer
, &iter
, undoinfo
->start_pos
);
375 gtk_text_buffer_insert(buffer
, &iter
, undoinfo
->text
, -1);
377 case UNDO_ACTION_INSERT
:
378 gtk_text_buffer_get_iter_at_offset(buffer
, &start_iter
, undoinfo
->start_pos
);
379 gtk_text_buffer_get_iter_at_offset(buffer
, &end_iter
, undoinfo
->end_pos
);
380 gtk_text_buffer_delete(buffer
, &start_iter
, &end_iter
);
382 case UNDO_ACTION_REPLACE_INSERT
:
383 gtk_text_buffer_get_iter_at_offset(buffer
, &start_iter
, undoinfo
->start_pos
);
384 gtk_text_buffer_get_iter_at_offset(buffer
, &end_iter
, undoinfo
->end_pos
);
385 gtk_text_buffer_delete(buffer
, &start_iter
, &end_iter
);
386 /* "pull" another data structure from the list */
387 undoinfo
= (UndoInfo
*)undostruct
->undo
->data
;
388 g_return_if_fail(undoinfo
!= NULL
);
389 undostruct
->redo
= g_list_prepend(undostruct
->redo
, undoinfo
);
390 undostruct
->undo
= g_list_remove(undostruct
->undo
, undoinfo
);
391 g_return_if_fail(undoinfo
->action
== UNDO_ACTION_REPLACE_DELETE
);
392 gtk_text_buffer_insert(buffer
, &start_iter
, undoinfo
->text
, -1);
394 case UNDO_ACTION_REPLACE_DELETE
:
395 g_warning("This should not happen. UNDO_REPLACE_DELETE");
398 g_assert_not_reached();
402 undostruct
->change_state_func(undostruct
,
403 UNDO_STATE_UNCHANGED
, UNDO_STATE_TRUE
,
404 undostruct
->change_state_data
);
406 if (undostruct
->undo
== NULL
)
407 undostruct
->change_state_func(undostruct
,
409 UNDO_STATE_UNCHANGED
,
410 undostruct
->change_state_data
);
412 undo_unblock(undostruct
);
420 * executes a redo request on the current document
422 void undo_redo(UndoMain
*undostruct
)
425 GtkTextView
*textview
;
426 GtkTextBuffer
*buffer
;
427 GtkTextIter iter
, start_iter
, end_iter
;
430 g_return_if_fail(undostruct
!= NULL
);
432 if (undostruct
->redo
== NULL
) return;
434 redoinfo
= (UndoInfo
*)undostruct
->redo
->data
;
435 g_return_if_fail (redoinfo
!= NULL
);
436 undostruct
->undo
= g_list_prepend(undostruct
->undo
, redoinfo
);
437 undostruct
->redo
= g_list_remove(undostruct
->redo
, redoinfo
);
439 textview
= undostruct
->textview
;
440 buffer
= gtk_text_view_get_buffer(textview
);
442 undo_block(undostruct
);
444 /* Check if there is a selection active */
445 mark
= gtk_text_buffer_get_insert(buffer
);
446 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
447 gtk_text_buffer_place_cursor(buffer
, &iter
);
449 /* Move the view to the right position. */
450 gtk_adjustment_set_value(textview
->vadjustment
,
451 redoinfo
->window_position
);
453 switch (redoinfo
->action
) {
454 case UNDO_ACTION_INSERT
:
455 gtk_text_buffer_get_iter_at_offset(buffer
, &iter
, redoinfo
->start_pos
);
456 gtk_text_buffer_insert(buffer
, &iter
, redoinfo
->text
, -1);
458 case UNDO_ACTION_DELETE
:
459 gtk_text_buffer_get_iter_at_offset(buffer
, &start_iter
, redoinfo
->start_pos
);
460 gtk_text_buffer_get_iter_at_offset(buffer
, &end_iter
, redoinfo
->end_pos
);
461 gtk_text_buffer_delete(buffer
, &start_iter
, &end_iter
);
463 case UNDO_ACTION_REPLACE_DELETE
:
464 gtk_text_buffer_get_iter_at_offset(buffer
, &start_iter
, redoinfo
->start_pos
);
465 gtk_text_buffer_get_iter_at_offset(buffer
, &end_iter
, redoinfo
->end_pos
);
466 gtk_text_buffer_delete(buffer
, &start_iter
, &end_iter
);
467 debug_print("UNDO_ACTION_REPLACE %s\n", redoinfo
->text
);
468 /* "pull" another data structure from the list */
469 redoinfo
= (UndoInfo
*)undostruct
->redo
->data
;
470 g_return_if_fail(redoinfo
!= NULL
);
471 undostruct
->undo
= g_list_prepend(undostruct
->undo
, redoinfo
);
472 undostruct
->redo
= g_list_remove(undostruct
->redo
, redoinfo
);
473 g_return_if_fail(redoinfo
->action
== UNDO_ACTION_REPLACE_INSERT
);
474 gtk_text_buffer_insert(buffer
, &start_iter
, redoinfo
->text
, -1);
476 case UNDO_ACTION_REPLACE_INSERT
:
477 g_warning("This should not happen. Redo: UNDO_REPLACE_INSERT");
480 g_assert_not_reached();
484 undostruct
->change_state_func(undostruct
,
485 UNDO_STATE_TRUE
, UNDO_STATE_UNCHANGED
,
486 undostruct
->change_state_data
);
488 if (undostruct
->redo
== NULL
)
489 undostruct
->change_state_func(undostruct
,
490 UNDO_STATE_UNCHANGED
,
492 undostruct
->change_state_data
);
494 undo_unblock(undostruct
);
497 void undo_block(UndoMain
*undostruct
)
499 GtkTextBuffer
*buffer
;
501 g_return_if_fail(GTK_IS_TEXT_VIEW(undostruct
->textview
));
503 buffer
= gtk_text_view_get_buffer(undostruct
->textview
);
504 g_signal_handlers_block_by_func(buffer
, undo_insert_text_cb
, undostruct
);
505 g_signal_handlers_block_by_func(buffer
, undo_delete_text_cb
, undostruct
);
506 g_signal_handlers_block_by_func(buffer
, undo_paste_clipboard_cb
,
510 void undo_unblock(UndoMain
*undostruct
)
512 GtkTextBuffer
*buffer
;
514 g_return_if_fail(GTK_IS_TEXT_VIEW(undostruct
->textview
));
516 buffer
= gtk_text_view_get_buffer(undostruct
->textview
);
517 g_signal_handlers_unblock_by_func(buffer
, undo_insert_text_cb
, undostruct
);
518 g_signal_handlers_unblock_by_func(buffer
, undo_delete_text_cb
, undostruct
);
519 g_signal_handlers_unblock_by_func(buffer
, undo_paste_clipboard_cb
,
523 void undo_wrapping(UndoMain
*undostruct
, gboolean wrap
)
525 debug_print("undo wrapping now %d\n", wrap
);
526 undostruct
->wrap
= wrap
;
529 void undo_insert_text_cb(GtkTextBuffer
*textbuf
, GtkTextIter
*iter
,
530 gchar
*new_text
, gint new_text_length
,
531 UndoMain
*undostruct
)
533 gchar
*text_to_insert
;
535 if (prefs_common
.undolevels
<= 0) return;
537 pos
= gtk_text_iter_get_offset(iter
);
538 if (undostruct
->wrap
&& undostruct
->undo
) {
539 UndoInfo
*last_undo
= undostruct
->undo
->data
;
540 if (last_undo
&& (last_undo
->action
== UNDO_ACTION_INSERT
541 || last_undo
->action
== UNDO_ACTION_REPLACE_INSERT
)
542 && last_undo
->start_pos
< pos
&& last_undo
->end_pos
> pos
) {
543 GtkTextIter start
,end
;
544 last_undo
->end_pos
+= g_utf8_strlen(new_text
, -1);
545 gtk_text_buffer_get_iter_at_offset(textbuf
, &start
, last_undo
->start_pos
);
546 gtk_text_buffer_get_iter_at_offset(textbuf
, &end
, last_undo
->end_pos
);
547 g_free(last_undo
->text
);
548 last_undo
->text
= gtk_text_buffer_get_text(textbuf
, &start
, &end
, FALSE
);
549 debug_print("add:undo upd %d-%d\n", last_undo
->start_pos
, last_undo
->end_pos
);
551 } else debug_print("add:last: %d, %d-%d (%d)\n", last_undo
->action
,
552 last_undo
->start_pos
, last_undo
->end_pos
, pos
);
554 Xstrndup_a(text_to_insert
, new_text
, new_text_length
, return);
555 debug_print("add:undo add %d-%ld\n", pos
, pos
+ g_utf8_strlen(text_to_insert
, -1));
556 undo_add(text_to_insert
, pos
, pos
+ g_utf8_strlen(text_to_insert
, -1),
557 UNDO_ACTION_INSERT
, undostruct
);
560 void undo_delete_text_cb(GtkTextBuffer
*textbuf
, GtkTextIter
*start
,
561 GtkTextIter
*end
, UndoMain
*undostruct
)
563 gchar
*text_to_delete
;
564 gint start_pos
, end_pos
;
566 if (prefs_common
.undolevels
<= 0) return;
568 text_to_delete
= gtk_text_buffer_get_text(textbuf
, start
, end
, FALSE
);
569 if (!text_to_delete
|| !*text_to_delete
) return;
571 start_pos
= gtk_text_iter_get_offset(start
);
572 end_pos
= gtk_text_iter_get_offset(end
);
574 if (undostruct
->wrap
&& undostruct
->undo
) {
575 UndoInfo
*last_undo
= undostruct
->undo
->data
;
576 if (last_undo
&& (last_undo
->action
== UNDO_ACTION_INSERT
577 || last_undo
->action
== UNDO_ACTION_REPLACE_INSERT
)
578 && last_undo
->start_pos
< start_pos
&& last_undo
->end_pos
> end_pos
) {
579 GtkTextIter start
,end
;
580 last_undo
->end_pos
-= g_utf8_strlen(text_to_delete
, -1);
581 gtk_text_buffer_get_iter_at_offset(textbuf
, &start
, last_undo
->start_pos
);
582 gtk_text_buffer_get_iter_at_offset(textbuf
, &end
, last_undo
->end_pos
);
583 g_free(last_undo
->text
);
584 last_undo
->text
= gtk_text_buffer_get_text(textbuf
, &start
, &end
, FALSE
);
585 debug_print("del:undo upd %d-%d\n", last_undo
->start_pos
, last_undo
->end_pos
);
587 } else debug_print("del:last: %d, %d-%d (%d)\n", last_undo
->action
,
588 last_undo
->start_pos
, last_undo
->end_pos
, start_pos
);
591 debug_print("del:undo add %d-%d\n", start_pos
, end_pos
);
592 undo_add(text_to_delete
, start_pos
, end_pos
, UNDO_ACTION_DELETE
,
594 g_free(text_to_delete
);
597 void undo_paste_clipboard(GtkTextView
*textview
, UndoMain
*undostruct
)
599 undo_paste_clipboard_cb(textview
, undostruct
);
602 static void undo_paste_clipboard_cb(GtkTextView
*textview
, UndoMain
*undostruct
)
604 if (prefs_common
.undolevels
> 0)
605 if (undo_get_selection(textview
, NULL
, NULL
))
606 undostruct
->paste
= TRUE
;
610 * undo_get_selection:
611 * @text: Text to get the selection from
612 * @start: return here the start position of the selection
613 * @end: return here the end position of the selection
615 * Gets the current selection for View
617 * Return Value: TRUE if there is a selection active, FALSE if not
619 static gint
undo_get_selection(GtkTextView
*textview
, guint
*start
, guint
*end
)
621 GtkTextBuffer
*buffer
;
622 GtkTextIter start_iter
, end_iter
;
623 guint start_pos
, end_pos
;
625 buffer
= gtk_text_view_get_buffer(textview
);
626 gtk_text_buffer_get_selection_bounds(buffer
, &start_iter
, &end_iter
);
628 start_pos
= gtk_text_iter_get_offset(&start_iter
);
629 end_pos
= gtk_text_iter_get_offset(&end_iter
);
631 /* The user can select from end to start too. If so, swap it*/
632 if (end_pos
< start_pos
) {
636 start_pos
= swap_pos
;
645 if ((start_pos
> 0 || end_pos
> 0) && (start_pos
!= end_pos
))