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