2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2012 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 3 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, see <http://www.gnu.org/licenses/>.
20 /* code ported from gedit */
21 /* This is for my patient girlfirend Regina */
25 #include "claws-features.h"
30 #include <string.h> /* for strlen */
31 #include <stdlib.h> /* for mbstowcs */
35 #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 (GtkTextView *textview,
64 static void undo_insert_text_cb (GtkTextBuffer *textbuf,
68 UndoMain *undostruct);
69 static void undo_delete_text_cb (GtkTextBuffer *textbuf,
72 UndoMain *undostruct);
74 static void undo_paste_clipboard_cb (GtkTextView *textview,
75 UndoMain *undostruct);
77 void undo_undo (UndoMain *undostruct);
78 void undo_redo (UndoMain *undostruct);
81 UndoMain *undo_init(GtkWidget *text)
84 GtkTextView *textview = GTK_TEXT_VIEW(text);
85 GtkTextBuffer *textbuf = gtk_text_view_get_buffer(textview);
87 cm_return_val_if_fail(text != NULL, NULL);
89 undostruct = g_new0(UndoMain, 1);
90 undostruct->textview = textview;
91 undostruct->undo = NULL;
92 undostruct->redo = NULL;
93 undostruct->paste = 0;
94 undostruct->undo_state = FALSE;
95 undostruct->redo_state = FALSE;
97 g_signal_connect(G_OBJECT(textbuf), "insert-text",
98 G_CALLBACK(undo_insert_text_cb), undostruct);
99 g_signal_connect(G_OBJECT(textbuf), "delete-range",
100 G_CALLBACK(undo_delete_text_cb), undostruct);
101 g_signal_connect(G_OBJECT(textview), "paste-clipboard",
102 G_CALLBACK(undo_paste_clipboard_cb), undostruct);
107 void undo_destroy (UndoMain *undostruct)
109 undo_free_list(&undostruct->undo);
110 undo_free_list(&undostruct->redo);
114 static UndoInfo *undo_object_new(gchar *text, gint start_pos, gint end_pos,
115 UndoAction action, gfloat window_position)
118 undoinfo = g_new (UndoInfo, 1);
119 undoinfo->text = text;
120 undoinfo->start_pos = start_pos;
121 undoinfo->end_pos = end_pos;
122 undoinfo->action = action;
123 undoinfo->window_position = window_position;
127 static void undo_object_free(UndoInfo *undo)
135 * @list_pointer: list to be freed
137 * frees and undo structure list
139 static void undo_free_list(GList **list_pointer)
142 GList *cur, *list = *list_pointer;
144 if (list == NULL) return;
146 for (cur = list; cur != NULL; cur = cur->next) {
147 undo = (UndoInfo *)cur->data;
148 undo_object_free(undo);
152 *list_pointer = NULL;
155 void undo_set_change_state_func(UndoMain *undostruct, UndoChangeStateFunc func,
158 cm_return_if_fail(undostruct != NULL);
160 undostruct->change_state_func = func;
161 undostruct->change_state_data = data;
166 * @compose: document to check
168 * Checks that the size of compose->undo does not excede settings->undo_levels and
169 * frees any undo level above sett->undo_level.
172 static void undo_check_size(UndoMain *undostruct)
177 if (prefs_common.undolevels < 1) return;
179 /* No need to check for the redo list size since the undo
180 list gets freed on any call to compose_undo_add */
181 length = g_list_length(undostruct->undo);
182 if (length >= prefs_common.undolevels && prefs_common.undolevels > 0) {
183 last_undo = (UndoInfo *)g_list_last(undostruct->undo)->data;
184 undostruct->undo = g_list_remove(undostruct->undo, last_undo);
185 undo_object_free(last_undo);
196 * This function tries to merge the undo object at the top of
197 * the stack with a new set of data. So when we undo for example
198 * typing, we can undo the whole word and not each letter by itself
200 * Return Value: TRUE is merge was sucessful, FALSE otherwise
202 static gint undo_merge(GList *list, guint start_pos, guint end_pos,
203 gint action, const guchar *text)
208 /* This are the cases in which we will NOT merge :
209 1. if (last_undo->mergeable == FALSE)
210 [mergeable = FALSE when the size of the undo data was not 1.
211 or if the data was size = 1 but = '\n' or if the undo object
212 has been "undone" already ]
213 2. The size of text is not 1
214 3. If the new merging data is a '\n'
215 4. If the last char of the undo_last data is a space/tab
216 and the new char is not a space/tab ( so that we undo
217 words and not chars )
218 5. If the type (action) of undo is different from the last one
221 if (list == NULL) return FALSE;
223 last_undo = list->data;
225 if (!last_undo->mergeable) return FALSE;
227 if (end_pos - start_pos != 1 ||
229 action != last_undo->action ||
230 action == UNDO_ACTION_REPLACE_INSERT ||
231 action == UNDO_ACTION_REPLACE_DELETE) {
232 last_undo->mergeable = FALSE;
236 if (action == UNDO_ACTION_DELETE) {
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 temp_string = g_strdup_printf("%s%s", last_undo->text, text);
244 last_undo->end_pos++;
245 g_free(last_undo->text);
246 last_undo->text = temp_string;
248 /* Deleted with the backspace key */
249 temp_string = g_strdup_printf("%s%s", text, last_undo->text);
250 last_undo->start_pos = start_pos;
251 g_free(last_undo->text);
252 last_undo->text = temp_string;
254 } else if (action == UNDO_ACTION_INSERT) {
255 if (last_undo->end_pos != start_pos) {
256 last_undo->mergeable = FALSE;
259 temp_string = g_strdup_printf("%s%s", last_undo->text, text);
260 g_free(last_undo->text);
261 last_undo->end_pos = end_pos;
262 last_undo->text = temp_string;
265 debug_print("Unknown action [%i] inside undo merge encountered", action);
275 * @action: either UNDO_ACTION_INSERT or UNDO_ACTION_DELETE
277 * @view: The view so that we save the scroll bar position.
279 * Adds text to the undo stack. It also performs test to limit the number
280 * of undo levels and deltes the redo list
283 static void undo_add(const gchar *text,
284 gint start_pos, gint end_pos,
285 UndoAction action, UndoMain *undostruct)
290 cm_return_if_fail(text != NULL);
291 cm_return_if_fail(end_pos >= start_pos);
293 undo_free_list(&undostruct->redo);
295 /* Set the redo sensitivity */
296 undostruct->change_state_func(undostruct,
297 UNDO_STATE_UNCHANGED, UNDO_STATE_FALSE,
298 undostruct->change_state_data);
300 if (undostruct->paste != 0) {
301 if (action == UNDO_ACTION_INSERT)
302 action = UNDO_ACTION_REPLACE_INSERT;
304 action = UNDO_ACTION_REPLACE_DELETE;
305 undostruct->paste = undostruct->paste + 1;
306 if (undostruct->paste == 3)
307 undostruct->paste = 0;
310 if (undo_merge(undostruct->undo, start_pos, end_pos, action, text))
313 undo_check_size(undostruct);
315 vadj = GTK_ADJUSTMENT(gtk_text_view_get_vadjustment(
316 GTK_TEXT_VIEW(undostruct->textview)));
317 undoinfo = undo_object_new(g_strdup(text), start_pos, end_pos, action,
318 gtk_adjustment_get_value(vadj));
320 if (end_pos - start_pos != 1 || text[0] == '\n')
321 undoinfo->mergeable = FALSE;
323 undoinfo->mergeable = TRUE;
325 undostruct->undo = g_list_prepend(undostruct->undo, undoinfo);
327 undostruct->change_state_func(undostruct,
328 UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED,
329 undostruct->change_state_data);
337 * Executes an undo request on the current document
339 void undo_undo(UndoMain *undostruct)
342 GtkTextView *textview;
343 GtkTextBuffer *buffer;
344 GtkTextIter iter, start_iter, end_iter;
347 cm_return_if_fail(undostruct != NULL);
349 if (undostruct->undo == NULL) return;
351 /* The undo data we need is always at the top op the
352 stack. So, therefore, the first one */
353 undoinfo = (UndoInfo *)undostruct->undo->data;
354 cm_return_if_fail(undoinfo != NULL);
355 undoinfo->mergeable = FALSE;
356 undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
357 undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
359 textview = undostruct->textview;
360 buffer = gtk_text_view_get_buffer(textview);
362 undo_block(undostruct);
364 /* Check if there is a selection active */
365 mark = gtk_text_buffer_get_insert(buffer);
366 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
367 gtk_text_buffer_place_cursor(buffer, &iter);
369 /* Move the view (scrollbars) to the correct position */
370 gtk_adjustment_set_value
371 (GTK_ADJUSTMENT(gtk_text_view_get_vadjustment(textview)),
372 undoinfo->window_position);
374 switch (undoinfo->action) {
375 case UNDO_ACTION_DELETE:
376 gtk_text_buffer_get_iter_at_offset(buffer, &iter, undoinfo->start_pos);
377 gtk_text_buffer_insert(buffer, &iter, undoinfo->text, -1);
379 case UNDO_ACTION_INSERT:
380 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, undoinfo->start_pos);
381 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, undoinfo->end_pos);
382 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
384 case UNDO_ACTION_REPLACE_INSERT:
385 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, undoinfo->start_pos);
386 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, undoinfo->end_pos);
387 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
388 /* "pull" another data structure from the list */
389 if (undostruct->undo){
390 undoinfo = (UndoInfo *)undostruct->undo->data;
391 undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
392 undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
393 cm_return_if_fail(undoinfo != NULL);
394 cm_return_if_fail(undoinfo->action == UNDO_ACTION_REPLACE_DELETE);
395 gtk_text_buffer_insert(buffer, &start_iter, undoinfo->text, -1);
398 case UNDO_ACTION_REPLACE_DELETE:
399 g_warning("This should not happen. UNDO_REPLACE_DELETE");
402 g_assert_not_reached();
406 undostruct->change_state_func(undostruct,
407 UNDO_STATE_UNCHANGED, UNDO_STATE_TRUE,
408 undostruct->change_state_data);
410 if (undostruct->undo == NULL)
411 undostruct->change_state_func(undostruct,
413 UNDO_STATE_UNCHANGED,
414 undostruct->change_state_data);
416 undo_unblock(undostruct);
424 * executes a redo request on the current document
426 void undo_redo(UndoMain *undostruct)
429 GtkTextView *textview;
430 GtkTextBuffer *buffer;
431 GtkTextIter iter, start_iter, end_iter;
434 cm_return_if_fail(undostruct != NULL);
436 if (undostruct->redo == NULL) return;
438 redoinfo = (UndoInfo *)undostruct->redo->data;
439 cm_return_if_fail (redoinfo != NULL);
440 undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
441 undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
443 textview = undostruct->textview;
444 buffer = gtk_text_view_get_buffer(textview);
446 undo_block(undostruct);
448 /* Check if there is a selection active */
449 mark = gtk_text_buffer_get_insert(buffer);
450 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
451 gtk_text_buffer_place_cursor(buffer, &iter);
453 /* Move the view to the right position. */
454 gtk_adjustment_set_value(gtk_text_view_get_vadjustment(textview),
455 redoinfo->window_position);
457 switch (redoinfo->action) {
458 case UNDO_ACTION_INSERT:
459 gtk_text_buffer_get_iter_at_offset(buffer, &iter, redoinfo->start_pos);
460 gtk_text_buffer_insert(buffer, &iter, redoinfo->text, -1);
462 case UNDO_ACTION_DELETE:
463 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, redoinfo->start_pos);
464 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, redoinfo->end_pos);
465 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
467 case UNDO_ACTION_REPLACE_DELETE:
468 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, redoinfo->start_pos);
469 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, redoinfo->end_pos);
470 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
471 debug_print("UNDO_ACTION_REPLACE %s\n", redoinfo->text);
472 /* "pull" another data structure from the list */
473 redoinfo = (UndoInfo *)undostruct->redo->data;
474 cm_return_if_fail(redoinfo != NULL);
475 undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
476 undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
477 cm_return_if_fail(redoinfo->action == UNDO_ACTION_REPLACE_INSERT);
478 gtk_text_buffer_insert(buffer, &start_iter, redoinfo->text, -1);
480 case UNDO_ACTION_REPLACE_INSERT:
481 /* This is needed only if we redo from a middle-click button */
482 gtk_text_buffer_get_iter_at_offset(buffer, &iter, redoinfo->start_pos);
483 gtk_text_buffer_insert(buffer, &iter, redoinfo->text, -1);
486 g_assert_not_reached();
490 undostruct->change_state_func(undostruct,
491 UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED,
492 undostruct->change_state_data);
494 if (undostruct->redo == NULL)
495 undostruct->change_state_func(undostruct,
496 UNDO_STATE_UNCHANGED,
498 undostruct->change_state_data);
500 undo_unblock(undostruct);
503 void undo_block(UndoMain *undostruct)
505 GtkTextBuffer *buffer;
507 cm_return_if_fail(GTK_IS_TEXT_VIEW(undostruct->textview));
509 buffer = gtk_text_view_get_buffer(undostruct->textview);
510 g_signal_handlers_block_by_func(buffer, undo_insert_text_cb, undostruct);
511 g_signal_handlers_block_by_func(buffer, undo_delete_text_cb, undostruct);
512 g_signal_handlers_block_by_func(buffer, undo_paste_clipboard_cb,
516 void undo_unblock(UndoMain *undostruct)
518 GtkTextBuffer *buffer;
520 cm_return_if_fail(GTK_IS_TEXT_VIEW(undostruct->textview));
522 buffer = gtk_text_view_get_buffer(undostruct->textview);
523 g_signal_handlers_unblock_by_func(buffer, undo_insert_text_cb, undostruct);
524 g_signal_handlers_unblock_by_func(buffer, undo_delete_text_cb, undostruct);
525 g_signal_handlers_unblock_by_func(buffer, undo_paste_clipboard_cb,
529 void undo_wrapping(UndoMain *undostruct, gboolean wrap)
531 // debug_print("undo wrapping now %d\n", wrap);
532 undostruct->wrap = wrap;
535 void undo_insert_text_cb(GtkTextBuffer *textbuf, GtkTextIter *iter,
536 gchar *new_text, gint new_text_length,
537 UndoMain *undostruct)
539 gchar *text_to_insert;
541 if (prefs_common.undolevels <= 0) return;
543 pos = gtk_text_iter_get_offset(iter);
544 if (undostruct->wrap && undostruct->undo) {
545 UndoInfo *last_undo = undostruct->undo->data;
546 if (last_undo && (last_undo->action == UNDO_ACTION_INSERT
547 || last_undo->action == UNDO_ACTION_REPLACE_INSERT)
548 && last_undo->start_pos < pos && last_undo->end_pos > pos) {
549 GtkTextIter start,end;
550 last_undo->end_pos += g_utf8_strlen(new_text, -1);
551 gtk_text_buffer_get_iter_at_offset(textbuf, &start, last_undo->start_pos);
552 gtk_text_buffer_get_iter_at_offset(textbuf, &end, last_undo->end_pos);
553 g_free(last_undo->text);
554 last_undo->text = gtk_text_buffer_get_text(textbuf, &start, &end, FALSE);
555 debug_print("add:undo upd %d-%d\n", last_undo->start_pos, last_undo->end_pos);
557 } else if (last_undo)
558 debug_print("add:last: %d, %d-%d (%d)\n", last_undo->action,
559 last_undo->start_pos, last_undo->end_pos, pos);
561 Xstrndup_a(text_to_insert, new_text, new_text_length, return);
562 debug_print("add:undo add %d-%ld\n", pos, pos + g_utf8_strlen(text_to_insert, -1));
563 undo_add(text_to_insert, pos, pos + g_utf8_strlen(text_to_insert, -1),
564 UNDO_ACTION_INSERT, undostruct);
567 void undo_delete_text_cb(GtkTextBuffer *textbuf, GtkTextIter *start,
568 GtkTextIter *end, UndoMain *undostruct)
570 gchar *text_to_delete;
571 gint start_pos, end_pos;
573 if (prefs_common.undolevels <= 0) return;
575 text_to_delete = gtk_text_buffer_get_text(textbuf, start, end, FALSE);
576 if (!text_to_delete || !*text_to_delete) return;
578 start_pos = gtk_text_iter_get_offset(start);
579 end_pos = gtk_text_iter_get_offset(end);
581 if (undostruct->wrap && undostruct->undo) {
582 UndoInfo *last_undo = undostruct->undo->data;
583 if (last_undo && (last_undo->action == UNDO_ACTION_INSERT
584 || last_undo->action == UNDO_ACTION_REPLACE_INSERT)
585 && last_undo->start_pos < start_pos && last_undo->end_pos > end_pos) {
586 GtkTextIter start,end;
587 last_undo->end_pos -= g_utf8_strlen(text_to_delete, -1);
588 gtk_text_buffer_get_iter_at_offset(textbuf, &start, last_undo->start_pos);
589 gtk_text_buffer_get_iter_at_offset(textbuf, &end, last_undo->end_pos);
590 g_free(last_undo->text);
591 last_undo->text = gtk_text_buffer_get_text(textbuf, &start, &end, FALSE);
592 debug_print("del:undo upd %d-%d\n", last_undo->start_pos, last_undo->end_pos);
594 } else if (last_undo)
595 debug_print("del:last: %d, %d-%d (%d)\n", last_undo->action,
596 last_undo->start_pos, last_undo->end_pos, start_pos);
599 debug_print("del:undo add %d-%d\n", start_pos, end_pos);
600 undo_add(text_to_delete, start_pos, end_pos, UNDO_ACTION_DELETE,
602 g_free(text_to_delete);
605 void undo_paste_clipboard(GtkTextView *textview, UndoMain *undostruct)
607 undo_paste_clipboard_cb(textview, undostruct);
610 static void undo_paste_clipboard_cb(GtkTextView *textview, UndoMain *undostruct)
612 if (prefs_common.undolevels > 0)
613 if (undo_get_selection(textview, NULL, NULL))
614 undostruct->paste = TRUE;
618 * undo_get_selection:
619 * @text: Text to get the selection from
620 * @start: return here the start position of the selection
621 * @end: return here the end position of the selection
623 * Gets the current selection for View
625 * Return Value: TRUE if there is a selection active, FALSE if not
627 static gint undo_get_selection(GtkTextView *textview, guint *start, guint *end)
629 GtkTextBuffer *buffer;
630 GtkTextIter start_iter, end_iter;
631 guint start_pos, end_pos;
633 buffer = gtk_text_view_get_buffer(textview);
634 gtk_text_buffer_get_selection_bounds(buffer, &start_iter, &end_iter);
636 start_pos = gtk_text_iter_get_offset(&start_iter);
637 end_pos = gtk_text_iter_get_offset(&end_iter);
639 /* The user can select from end to start too. If so, swap it*/
640 if (end_pos < start_pos) {
644 start_pos = swap_pos;
653 if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos))