Fix crash with auto-indent
[claws.git] / src / undo.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2012 Hiroyuki Yamamoto and the Claws Mail team
4  *
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.
9  *
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.
14  *
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/>.
17  * 
18  */
19
20 /* code ported from gedit */
21 /* This is for my patient girlfirend Regina */
22
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #include "claws-features.h"
26 #endif
27
28 #include <glib.h>
29
30 #include <string.h> /* for strlen */
31 #include <stdlib.h> /* for mbstowcs */
32
33 #include "undo.h"
34 #include "utils.h"
35 #include "prefs_common.h"
36
37 typedef struct _UndoInfo UndoInfo;
38
39 struct _UndoInfo 
40 {
41         UndoAction action;
42         gchar *text;
43         gint start_pos;
44         gint end_pos;
45         gfloat window_position;
46         gint mergeable;
47 };
48
49 struct _UndoWrap
50 {
51         gint lock;
52         gchar *pre_wrap_content;
53         gint start_pos;
54         gint end_pos;
55         gint len_change;
56 };
57
58 static void undo_free_list      (GList         **list_pointer);
59 static void undo_check_size     (UndoMain       *undostruct);
60 static gint undo_merge          (GList          *list,
61                                  guint           start_pos,
62                                  guint           end_pos,
63                                  gint            action,
64                                  const guchar   *text);
65 static void undo_add            (const gchar    *text,
66                                  gint            start_pos,
67                                  gint            end_pos,
68                                  UndoAction      action,
69                                  UndoMain       *undostruct);
70 static gint undo_get_selection  (GtkTextView    *textview,
71                                  guint          *start,
72                                  guint          *end);
73 static void undo_insert_text_cb (GtkTextBuffer  *textbuf,
74                                  GtkTextIter    *iter,
75                                  gchar          *new_text,
76                                  gint           new_text_length,
77                                  UndoMain       *undostruct);
78 static void undo_delete_text_cb (GtkTextBuffer  *textbuf,
79                                  GtkTextIter    *start,
80                                  GtkTextIter    *end,
81                                  UndoMain       *undostruct);
82
83 static void undo_paste_clipboard_cb     (GtkTextView    *textview,
84                                          UndoMain       *undostruct);
85
86 void undo_undo                  (UndoMain       *undostruct);
87 void undo_redo                  (UndoMain       *undostruct);
88
89
90 UndoMain *undo_init(GtkWidget *text) 
91 {
92         UndoMain *undostruct;
93         GtkTextView *textview = GTK_TEXT_VIEW(text); 
94         GtkTextBuffer *textbuf = gtk_text_view_get_buffer(textview);
95
96         cm_return_val_if_fail(text != NULL, NULL);
97
98         undostruct = g_new0(UndoMain, 1);
99         undostruct->textview = textview;
100         undostruct->undo = NULL;
101         undostruct->redo = NULL;
102         undostruct->paste = 0;
103         undostruct->undo_state = FALSE;
104         undostruct->redo_state = FALSE;
105
106         g_signal_connect(G_OBJECT(textbuf), "insert-text",
107                          G_CALLBACK(undo_insert_text_cb), undostruct);
108         g_signal_connect(G_OBJECT(textbuf), "delete-range",
109                          G_CALLBACK(undo_delete_text_cb), undostruct);
110         g_signal_connect(G_OBJECT(textview), "paste-clipboard",
111                          G_CALLBACK(undo_paste_clipboard_cb), undostruct);
112
113         return undostruct;
114 }
115
116 void undo_destroy (UndoMain *undostruct) 
117 {
118         undo_free_list(&undostruct->undo);
119         undo_free_list(&undostruct->redo);
120         g_free(undostruct);
121 }
122
123 static UndoInfo *undo_object_new(gchar *text, gint start_pos, gint end_pos, 
124                                  UndoAction action, gfloat window_position) 
125 {
126         UndoInfo *undoinfo;
127         undoinfo = g_new (UndoInfo, 1);
128         undoinfo->text      = text;
129         undoinfo->start_pos = start_pos;
130         undoinfo->end_pos   = end_pos;
131         undoinfo->action    = action;
132         undoinfo->window_position = window_position;
133         return undoinfo;
134 }
135
136 static void undo_object_free(UndoInfo *undo) 
137 {
138         g_free (undo->text);
139         g_free (undo);
140 }
141
142 /**
143  * undo_free_list:
144  * @list_pointer: list to be freed
145  *
146  * frees and undo structure list
147  **/
148 static void undo_free_list(GList **list_pointer) 
149 {
150         UndoInfo *undo;
151         GList *cur, *list = *list_pointer;
152
153         if (list == NULL) return;
154
155         for (cur = list; cur != NULL; cur = cur->next) {
156                 undo = (UndoInfo *)cur->data;
157                 undo_object_free(undo);
158         }
159
160         g_list_free(list);
161         *list_pointer = NULL;
162 }
163
164 void undo_set_change_state_func(UndoMain *undostruct, UndoChangeStateFunc func,
165                                 gpointer data)
166 {
167         cm_return_if_fail(undostruct != NULL);
168
169         undostruct->change_state_func = func;
170         undostruct->change_state_data = data;
171 }
172
173 /**
174  * undo_check_size:
175  * @compose: document to check
176  *
177  * Checks that the size of compose->undo does not excede settings->undo_levels and
178  * frees any undo level above sett->undo_level.
179  *
180  **/
181 static void undo_check_size(UndoMain *undostruct) 
182 {
183         UndoInfo *last_undo;
184         guint length;
185
186         if (prefs_common.undolevels < 1) return;
187
188         /* No need to check for the redo list size since the undo
189            list gets freed on any call to compose_undo_add */
190         length = g_list_length(undostruct->undo);
191         if (length >= prefs_common.undolevels && prefs_common.undolevels > 0) {
192                 last_undo = (UndoInfo *)g_list_last(undostruct->undo)->data;
193                 undostruct->undo = g_list_remove(undostruct->undo, last_undo);
194                 undo_object_free(last_undo);
195         }
196 }
197
198 /**
199  * undo_merge:
200  * @last_undo:
201  * @start_pos:
202  * @end_pos:
203  * @action:
204  *
205  * This function tries to merge the undo object at the top of
206  * the stack with a new set of data. So when we undo for example
207  * typing, we can undo the whole word and not each letter by itself
208  *
209  * Return Value: TRUE is merge was sucessful, FALSE otherwise
210  **/
211 static gint undo_merge(GList *list, guint start_pos, guint end_pos,
212                        gint action, const guchar *text) 
213 {
214         guchar *temp_string;
215         UndoInfo *last_undo;
216
217         /* This are the cases in which we will NOT merge :
218            1. if (last_undo->mergeable == FALSE)
219            [mergeable = FALSE when the size of the undo data was not 1.
220            or if the data was size = 1 but = '\n' or if the undo object
221            has been "undone" already ]
222            2. The size of text is not 1
223            3. If the new merging data is a '\n'
224            4. If the last char of the undo_last data is a space/tab
225            and the new char is not a space/tab ( so that we undo
226            words and not chars )
227            5. If the type (action) of undo is different from the last one
228            Chema */
229
230         if (list == NULL) return FALSE;
231
232         last_undo = list->data;
233
234         if (!last_undo->mergeable) return FALSE;
235
236         if (end_pos - start_pos != 1 ||
237             text[0] == '\n' ||
238             action != last_undo->action ||
239             action == UNDO_ACTION_REPLACE_INSERT ||
240             action == UNDO_ACTION_REPLACE_DELETE) {
241                 last_undo->mergeable = FALSE;
242                 return FALSE;
243         }
244
245         if (action == UNDO_ACTION_DELETE) {
246                 if (last_undo->start_pos != end_pos &&
247                     last_undo->start_pos != start_pos) {
248                         last_undo->mergeable = FALSE;
249                         return FALSE;
250                 } else if (last_undo->start_pos == start_pos) {
251                         /* Deleted with the delete key */
252                         temp_string = g_strdup_printf("%s%s", last_undo->text, text);
253                         last_undo->end_pos++;
254                         g_free(last_undo->text);
255                         last_undo->text = temp_string;
256                 } else {
257                         /* Deleted with the backspace key */
258                         temp_string = g_strdup_printf("%s%s", text, last_undo->text);
259                         last_undo->start_pos = start_pos;
260                         g_free(last_undo->text);
261                         last_undo->text = temp_string;
262                 }
263         } else if (action == UNDO_ACTION_INSERT) {
264                 if (last_undo->end_pos != start_pos) {
265                         last_undo->mergeable = FALSE;
266                         return FALSE;
267                 } else {
268                         temp_string = g_strdup_printf("%s%s", last_undo->text, text);
269                         g_free(last_undo->text);
270                         last_undo->end_pos = end_pos;
271                         last_undo->text = temp_string;
272                 }
273         } else
274                 debug_print("Unknown action [%i] inside undo merge encountered", action);
275
276         return TRUE;
277 }
278
279 /**
280  * compose_undo_add:
281  * @text:
282  * @start_pos:
283  * @end_pos:
284  * @action: either UNDO_ACTION_INSERT or UNDO_ACTION_DELETE
285  * @compose:
286  * @view: The view so that we save the scroll bar position.
287  *
288  * Adds text to the undo stack. It also performs test to limit the number
289  * of undo levels and deltes the redo list
290  **/
291
292 static void undo_add(const gchar *text, 
293                      gint start_pos, gint end_pos,
294                      UndoAction action, UndoMain *undostruct) 
295 {
296         UndoInfo *undoinfo;
297         GtkAdjustment *vadj;
298
299         cm_return_if_fail(text != NULL);
300         cm_return_if_fail(end_pos >= start_pos);
301
302         undo_free_list(&undostruct->redo);
303
304         /* Set the redo sensitivity */
305         undostruct->change_state_func(undostruct,
306                                       UNDO_STATE_UNCHANGED, UNDO_STATE_FALSE,
307                                       undostruct->change_state_data);
308
309         if (undostruct->paste != 0) {
310                 if (action == UNDO_ACTION_INSERT)
311                         action = UNDO_ACTION_REPLACE_INSERT;
312                 else 
313                         action = UNDO_ACTION_REPLACE_DELETE;
314                 undostruct->paste = undostruct->paste + 1;
315                 if (undostruct->paste == 3) 
316                         undostruct->paste = 0;
317         }
318
319         if (undo_merge(undostruct->undo, start_pos, end_pos, action, text))
320                 return;
321
322         undo_check_size(undostruct);
323
324         vadj = GTK_ADJUSTMENT(gtk_text_view_get_vadjustment(
325                                 GTK_TEXT_VIEW(undostruct->textview)));
326         undoinfo = undo_object_new(g_strdup(text), start_pos, end_pos, action,
327                                    gtk_adjustment_get_value(vadj));
328
329         if (end_pos - start_pos != 1 || text[0] == '\n')
330                 undoinfo->mergeable = FALSE;
331         else
332                 undoinfo->mergeable = TRUE;
333
334         undostruct->undo = g_list_prepend(undostruct->undo, undoinfo);
335
336         undostruct->change_state_func(undostruct,
337                                       UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED,
338                                       undostruct->change_state_data);
339 }
340
341 /**
342  * undo_undo:
343  * @w: not used
344  * @data: not used
345  *
346  * Executes an undo request on the current document
347  **/
348 void undo_undo(UndoMain *undostruct) 
349 {
350         UndoInfo *undoinfo;
351         GtkTextView *textview;
352         GtkTextBuffer *buffer;
353         GtkTextIter iter, start_iter, end_iter;
354         GtkTextMark *mark;
355
356         cm_return_if_fail(undostruct != NULL);
357
358         if (undostruct->undo == NULL) return;
359
360         /* The undo data we need is always at the top op the
361            stack. So, therefore, the first one */
362         undoinfo = (UndoInfo *)undostruct->undo->data;
363         cm_return_if_fail(undoinfo != NULL);
364         undoinfo->mergeable = FALSE;
365         undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
366         undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
367
368         textview = undostruct->textview;
369         buffer = gtk_text_view_get_buffer(textview);
370
371         undo_block(undostruct);
372
373         /* Check if there is a selection active */
374         mark = gtk_text_buffer_get_insert(buffer);
375         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
376         gtk_text_buffer_place_cursor(buffer, &iter);
377
378         /* Move the view (scrollbars) to the correct position */
379         gtk_adjustment_set_value
380                 (GTK_ADJUSTMENT(gtk_text_view_get_vadjustment(textview)),
381                  undoinfo->window_position);
382         
383         switch (undoinfo->action) {
384         case UNDO_ACTION_DELETE:
385                 gtk_text_buffer_get_iter_at_offset(buffer, &iter, undoinfo->start_pos);
386                 gtk_text_buffer_insert(buffer, &iter, undoinfo->text, -1);
387                 break;
388         case UNDO_ACTION_INSERT:
389                 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, undoinfo->start_pos);
390                 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, undoinfo->end_pos);
391                 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
392                 break;
393         case UNDO_ACTION_REPLACE_INSERT:
394                 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, undoinfo->start_pos);
395                 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, undoinfo->end_pos);
396                 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
397                 /* "pull" previous matching DELETE data structure from the list */
398                 if (undostruct->undo){
399                         undoinfo = (UndoInfo *)undostruct->undo->data;
400                         undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
401                         undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
402                         cm_return_if_fail(undoinfo != NULL);
403                         cm_return_if_fail(undoinfo->action == UNDO_ACTION_REPLACE_DELETE);
404                         gtk_text_buffer_insert(buffer, &start_iter, undoinfo->text, -1);
405                 }
406                 break;
407         case UNDO_ACTION_REPLACE_DELETE:
408                 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, undoinfo->start_pos);
409                 gtk_text_buffer_insert(buffer, &start_iter, undoinfo->text, -1);
410                 /* "pull" previous matching INSERT data structure from the list */
411                 if (undostruct->undo){
412                         undoinfo = (UndoInfo *)undostruct->undo->data;
413                         undostruct->redo = g_list_prepend(undostruct->redo, undoinfo);
414                         undostruct->undo = g_list_remove(undostruct->undo, undoinfo);
415                         cm_return_if_fail(undoinfo != NULL);
416                         cm_return_if_fail(undoinfo->action == UNDO_ACTION_REPLACE_INSERT);
417                         gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, undoinfo->start_pos);
418                         gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, undoinfo->end_pos);
419                         gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
420                 }
421                 break;
422         default:
423                 g_assert_not_reached();
424                 break;
425         }
426         
427         undostruct->change_state_func(undostruct,
428                                       UNDO_STATE_UNCHANGED, UNDO_STATE_TRUE,
429                                       undostruct->change_state_data);
430
431         if (undostruct->undo == NULL)
432                 undostruct->change_state_func(undostruct,
433                                               UNDO_STATE_FALSE,
434                                               UNDO_STATE_UNCHANGED,
435                                               undostruct->change_state_data);
436
437         undo_unblock(undostruct);
438 }
439
440 /**
441  * undo_redo:
442  * @w: not used
443  * @data: not used
444  *
445  * executes a redo request on the current document
446  **/
447 void undo_redo(UndoMain *undostruct) 
448 {
449         UndoInfo *redoinfo;
450         GtkTextView *textview;
451         GtkTextBuffer *buffer;
452         GtkTextIter iter, start_iter, end_iter;
453         GtkTextMark *mark;
454
455         cm_return_if_fail(undostruct != NULL);
456
457         if (undostruct->redo == NULL) return;
458
459         redoinfo = (UndoInfo *)undostruct->redo->data;
460         cm_return_if_fail (redoinfo != NULL);
461         undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
462         undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
463
464         textview = undostruct->textview;
465         buffer = gtk_text_view_get_buffer(textview);
466
467         undo_block(undostruct);
468
469         /* Check if there is a selection active */
470         mark = gtk_text_buffer_get_insert(buffer);
471         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
472         gtk_text_buffer_place_cursor(buffer, &iter);
473
474         /* Move the view to the right position. */
475         gtk_adjustment_set_value(gtk_text_view_get_vadjustment(textview), 
476                                  redoinfo->window_position);
477
478         switch (redoinfo->action) {
479         case UNDO_ACTION_INSERT:
480                 gtk_text_buffer_get_iter_at_offset(buffer, &iter, redoinfo->start_pos);
481                 gtk_text_buffer_insert(buffer, &iter, redoinfo->text, -1);
482                 break;
483         case UNDO_ACTION_DELETE:
484                 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, redoinfo->start_pos);
485                 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, redoinfo->end_pos);
486                 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
487                 break;
488         case UNDO_ACTION_REPLACE_DELETE:
489                 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, redoinfo->start_pos);
490                 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, redoinfo->end_pos);
491                 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
492                 debug_print("UNDO_ACTION_REPLACE %s\n", redoinfo->text);
493                 /* "pull" previous matching INSERT data structure from the list */
494                 redoinfo = (UndoInfo *)undostruct->redo->data;
495                 cm_return_if_fail(redoinfo != NULL);
496                 undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
497                 undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
498                 cm_return_if_fail(redoinfo->action == UNDO_ACTION_REPLACE_INSERT);
499                 gtk_text_buffer_insert(buffer, &start_iter, redoinfo->text, -1);
500                 break;
501         case UNDO_ACTION_REPLACE_INSERT:
502                 gtk_text_buffer_get_iter_at_offset(buffer, &iter, redoinfo->start_pos);
503                 gtk_text_buffer_insert(buffer, &iter, redoinfo->text, -1);
504                 /* "pull" previous matching DELETE structure from the list */
505                 redoinfo = (UndoInfo *)undostruct->redo->data;
506                 /* Do nothing if we redo from a middle-click button
507                  * and next action is not UNDO_ACTION_REPLACE_DELETE */
508                 if (redoinfo && redoinfo->action == UNDO_ACTION_REPLACE_DELETE) {
509                         undostruct->undo = g_list_prepend(undostruct->undo, redoinfo);
510                         undostruct->redo = g_list_remove(undostruct->redo, redoinfo);
511                         gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, redoinfo->start_pos);
512                         gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, redoinfo->end_pos);
513                         gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
514                 }
515                 break;
516         default:
517                 g_assert_not_reached();
518                 break;
519         }
520
521         undostruct->change_state_func(undostruct,
522                                       UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED, 
523                                       undostruct->change_state_data);
524
525         if (undostruct->redo == NULL)
526                 undostruct->change_state_func(undostruct,
527                                               UNDO_STATE_UNCHANGED,
528                                               UNDO_STATE_FALSE,
529                                               undostruct->change_state_data);
530
531         undo_unblock(undostruct);
532 }
533
534 void undo_block(UndoMain *undostruct)
535 {
536         GtkTextBuffer *buffer;
537
538         cm_return_if_fail(GTK_IS_TEXT_VIEW(undostruct->textview));
539
540         buffer = gtk_text_view_get_buffer(undostruct->textview);
541         g_signal_handlers_block_by_func(buffer, undo_insert_text_cb, undostruct);
542         g_signal_handlers_block_by_func(buffer, undo_delete_text_cb, undostruct);
543         g_signal_handlers_block_by_func(buffer, undo_paste_clipboard_cb,
544                                           undostruct);
545 }
546
547 void undo_unblock(UndoMain *undostruct)
548 {
549         GtkTextBuffer *buffer;
550
551         cm_return_if_fail(GTK_IS_TEXT_VIEW(undostruct->textview));
552
553         buffer = gtk_text_view_get_buffer(undostruct->textview);
554         g_signal_handlers_unblock_by_func(buffer, undo_insert_text_cb, undostruct);
555         g_signal_handlers_unblock_by_func(buffer, undo_delete_text_cb, undostruct);
556         g_signal_handlers_unblock_by_func(buffer, undo_paste_clipboard_cb,
557                                           undostruct);
558 }
559
560 /* Init the WrapInfo structure */
561 static void init_wrap_undo(UndoMain *undostruct)
562 {
563         GtkTextBuffer *buffer;
564         GtkTextIter start, end;
565
566         cm_return_if_fail(undostruct != NULL);
567         cm_return_if_fail(undostruct->wrap_info == NULL);
568
569         undostruct->wrap_info = g_new0(UndoWrap, 1);
570
571         /* Save the whole buffer as original contents. We'll retain the
572          * changed region when exiting wrap mode.
573          */
574         buffer = gtk_text_view_get_buffer(undostruct->textview);
575         gtk_text_buffer_get_start_iter(buffer, &start);
576         gtk_text_buffer_get_end_iter(buffer, &end);
577         undostruct->wrap_info->pre_wrap_content
578                 = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
579
580         undostruct->wrap_info->lock = 0;
581
582         /* start_pos == -1 means nothing changed yet. */
583         undostruct->wrap_info->start_pos = -1;
584         undostruct->wrap_info->end_pos = -1;
585         undostruct->wrap_info->len_change = 0;
586 }
587
588 static void end_wrap_undo(UndoMain *undostruct)
589 {
590         GtkTextBuffer *buffer;
591         GtkTextIter start, end;
592         gchar *old_contents = NULL;
593         gchar *cur_contents = NULL;
594         gchar *new_contents = NULL;
595
596         cm_return_if_fail(undostruct != NULL);
597         cm_return_if_fail(undostruct->wrap_info != NULL);
598
599         /* If start_pos is still == -1, it means nothing changed. */
600         if (undostruct->wrap_info->start_pos == -1)
601                 goto cleanup;
602
603         cm_return_if_fail(undostruct->wrap_info->end_pos > undostruct->wrap_info->start_pos);
604         cm_return_if_fail(undostruct->wrap_info->end_pos - undostruct->wrap_info->len_change > undostruct->wrap_info->start_pos);
605
606         /* get the whole new (wrapped) contents */
607         buffer = gtk_text_view_get_buffer(undostruct->textview);
608         gtk_text_buffer_get_start_iter(buffer, &start);
609         gtk_text_buffer_get_end_iter(buffer, &end);
610         cur_contents = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
611
612         debug_print("wrapping done from %d to %d, len change: %d\n",
613                 undostruct->wrap_info->start_pos,
614                 undostruct->wrap_info->end_pos,
615                 undostruct->wrap_info->len_change);
616
617         /* keep the relevant old unwrapped part, which is what
618          * was between start_pos & end_pos - len_change
619          */
620         old_contents = g_utf8_substring(
621                         undostruct->wrap_info->pre_wrap_content,
622                         undostruct->wrap_info->start_pos,
623                         undostruct->wrap_info->end_pos
624                         - undostruct->wrap_info->len_change);
625
626         /* and get the changed contents, from start_pos to end_pos. */
627         new_contents = g_utf8_substring(
628                         cur_contents,
629                         undostruct->wrap_info->start_pos,
630                         undostruct->wrap_info->end_pos);
631
632         /* add the deleted (unwrapped) text to the undo pile */
633         undo_add(old_contents,
634                  undostruct->wrap_info->start_pos,
635                  undostruct->wrap_info->end_pos
636                   - undostruct->wrap_info->len_change,
637                  UNDO_ACTION_REPLACE_DELETE,
638                  undostruct);
639
640         /* add the inserted (wrapped) text to the undo pile */
641         undo_add(new_contents,
642                  undostruct->wrap_info->start_pos,
643                  undostruct->wrap_info->end_pos,
644                  UNDO_ACTION_REPLACE_INSERT,
645                  undostruct);
646
647         g_free(old_contents);
648         g_free(cur_contents);
649         g_free(new_contents);
650 cleanup:
651         g_free(undostruct->wrap_info->pre_wrap_content);
652         g_free(undostruct->wrap_info);
653         undostruct->wrap_info = NULL;
654 }
655
656 static void update_wrap_undo(UndoMain *undostruct, const gchar *text, int start, 
657                              int end, UndoAction action)
658 {
659         gint len = end - start;
660
661         /* If we don't yet have a start position, or farther than
662          * current, store it.
663          */
664         if (undostruct->wrap_info->start_pos == -1
665          || start < undostruct->wrap_info->start_pos) {
666                 undostruct->wrap_info->start_pos = start;
667         }
668
669         if (action == UNDO_ACTION_INSERT) {
670                 /* If inserting, the end of the region is at the end of the
671                  * change, and the total length of the changed region
672                  * increases.
673                  */
674                 if (end > undostruct->wrap_info->end_pos) {
675                         undostruct->wrap_info->end_pos = end;
676                 }
677                 undostruct->wrap_info->len_change += len;
678         } else if (action == UNDO_ACTION_DELETE) {
679                 /* If deleting, the end of the region is at the start of the
680                  * change, and the total length of the changed region
681                  * decreases.
682                  */
683                 if (start > undostruct->wrap_info->end_pos) {
684                         undostruct->wrap_info->end_pos = start;
685                 }
686                 undostruct->wrap_info->len_change -= len;
687         }
688 }
689
690 /* Set wrapping mode, in which changes are agglomerated until
691  * the end of wrapping mode.
692  */
693 void undo_wrapping(UndoMain *undostruct, gboolean wrap)
694 {
695         if (wrap) {
696                 /* Start (or go deeper in) wrapping mode */
697                 if (undostruct->wrap_info == NULL)
698                         init_wrap_undo(undostruct);
699                 undostruct->wrap_info->lock++;
700         } else if (undostruct->wrap_info != NULL) {
701                 /* exit (& possible stop) one level of wrapping mode */
702                 undostruct->wrap_info->lock--;
703                 if (undostruct->wrap_info->lock == 0)
704                         end_wrap_undo(undostruct);
705         } else {
706                 g_warning("undo already out of wrap mode");
707         }
708 }
709
710 void undo_insert_text_cb(GtkTextBuffer *textbuf, GtkTextIter *iter,
711                          gchar *new_text, gint new_text_length,
712                          UndoMain *undostruct) 
713 {
714         gchar *text_to_insert;
715         gint pos;
716         glong utf8_len;
717
718         if (prefs_common.undolevels <= 0) return;
719
720         pos = gtk_text_iter_get_offset(iter);
721         Xstrndup_a(text_to_insert, new_text, new_text_length, return);
722         utf8_len = g_utf8_strlen(text_to_insert, -1);
723
724         if (undostruct->wrap_info != NULL) {
725                 update_wrap_undo(undostruct, text_to_insert, 
726                                  pos, pos + utf8_len, UNDO_ACTION_INSERT);
727                 return;
728         }
729
730         debug_print("add:undo add %d-%ld\n", pos, utf8_len);
731         undo_add(text_to_insert, pos, pos + utf8_len,
732                  UNDO_ACTION_INSERT, undostruct);
733 }
734
735 void undo_delete_text_cb(GtkTextBuffer *textbuf, GtkTextIter *start,
736                          GtkTextIter *end, UndoMain *undostruct) 
737 {
738         gchar *text_to_delete;
739         gint start_pos, end_pos;
740
741         if (prefs_common.undolevels <= 0) return;
742
743         text_to_delete = gtk_text_buffer_get_text(textbuf, start, end, FALSE);
744         if (!text_to_delete || !*text_to_delete) return;
745
746         start_pos = gtk_text_iter_get_offset(start);
747         end_pos   = gtk_text_iter_get_offset(end);
748
749         if (undostruct->wrap_info != NULL) {
750                 update_wrap_undo(undostruct, text_to_delete, start_pos, end_pos, UNDO_ACTION_DELETE);
751                 return;
752         } 
753         debug_print("del:undo add %d-%d\n", start_pos, end_pos);
754         undo_add(text_to_delete, start_pos, end_pos, UNDO_ACTION_DELETE,
755                  undostruct);
756         g_free(text_to_delete);
757 }
758
759 void undo_paste_clipboard(GtkTextView *textview, UndoMain *undostruct)
760 {
761         undo_paste_clipboard_cb(textview, undostruct);
762 }
763
764 static void undo_paste_clipboard_cb(GtkTextView *textview, UndoMain *undostruct)
765 {
766         if (prefs_common.undolevels > 0)
767                 if (undo_get_selection(textview, NULL, NULL))
768                         undostruct->paste = TRUE;
769 }
770
771 /**
772  * undo_get_selection:
773  * @text: Text to get the selection from
774  * @start: return here the start position of the selection
775  * @end: return here the end position of the selection
776  *
777  * Gets the current selection for View
778  *
779  * Return Value: TRUE if there is a selection active, FALSE if not
780  **/
781 static gint undo_get_selection(GtkTextView *textview, guint *start, guint *end) 
782 {
783         GtkTextBuffer *buffer;
784         GtkTextIter start_iter, end_iter;
785         guint start_pos, end_pos;
786
787         buffer = gtk_text_view_get_buffer(textview);
788         gtk_text_buffer_get_selection_bounds(buffer, &start_iter, &end_iter);
789
790         start_pos = gtk_text_iter_get_offset(&start_iter);
791         end_pos   = gtk_text_iter_get_offset(&end_iter);
792
793         /* The user can select from end to start too. If so, swap it*/
794         if (end_pos < start_pos) {
795                 guint swap_pos;
796                 swap_pos  = end_pos;
797                 end_pos   = start_pos;
798                 start_pos = swap_pos;
799         }
800
801         if (start != NULL)
802                 *start = start_pos;
803                 
804         if (end != NULL)
805                 *end = end_pos;
806
807         if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos))
808                 return TRUE;
809         else
810                 return FALSE;
811 }