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 }