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 
undo_init(GtkWidget * text)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 
undo_destroy(UndoMain * undostruct)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 
undo_object_new(gchar * text,gint start_pos,gint end_pos,UndoAction action,gfloat window_position)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 
undo_object_free(UndoInfo * undo)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  **/
undo_free_list(GList ** list_pointer)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 
undo_set_change_state_func(UndoMain * undostruct,UndoChangeStateFunc func,gpointer data)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  **/
undo_check_size(UndoMain * undostruct)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  **/
undo_merge(GList * list,guint start_pos,guint end_pos,gint action,const guchar * text)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 
undo_add(const gchar * text,gint start_pos,gint end_pos,UndoAction action,UndoMain * undostruct)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  **/
undo_undo(UndoMain * undostruct)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  **/
undo_redo(UndoMain * undostruct)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 
undo_block(UndoMain * undostruct)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 
undo_unblock(UndoMain * undostruct)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 */
init_wrap_undo(UndoMain * undostruct)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 
end_wrap_undo(UndoMain * undostruct)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 
update_wrap_undo(UndoMain * undostruct,const gchar * text,int start,int end,UndoAction action)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  */
undo_wrapping(UndoMain * undostruct,gboolean wrap)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 
undo_insert_text_cb(GtkTextBuffer * textbuf,GtkTextIter * iter,gchar * new_text,gint new_text_length,UndoMain * undostruct)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 
undo_delete_text_cb(GtkTextBuffer * textbuf,GtkTextIter * start,GtkTextIter * end,UndoMain * undostruct)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 
undo_paste_clipboard(GtkTextView * textview,UndoMain * undostruct)758 void undo_paste_clipboard(GtkTextView *textview, UndoMain *undostruct)
759 {
760 	undo_paste_clipboard_cb(textview, undostruct);
761 }
762 
undo_paste_clipboard_cb(GtkTextView * textview,UndoMain * undostruct)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  **/
undo_get_selection(GtkTextView * textview,guint * start,guint * end)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 }
811