talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

undo.c (24314B)


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