root / src / undo.c @ 411
History | View | Annotate | Download (18.1 kB)
| 1 | /*
|
|---|---|
| 2 | * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client |
| 3 | * Copyright (C) 1999-2001 Hiroyuki Yamamoto |
| 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 2 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, write to the Free Software |
| 17 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| 18 | */ |
| 19 | |
| 20 | /* code ported from gedit */
|
| 21 | /* This is for my patient girlfirend Regina */
|
| 22 | |
| 23 | #ifdef HAVE_CONFIG_H
|
| 24 | # include "config.h" |
| 25 | #endif
|
| 26 | |
| 27 | #include <glib.h> |
| 28 | #include <gtk/gtktextview.h> |
| 29 | |
| 30 | #include <string.h> /* for strlen */ |
| 31 | #include <stdlib.h> /* for mbstowcs */ |
| 32 | |
| 33 | #include "undo.h" |
| 34 | #include "utils.h" |
| 35 | #include "prefs_common.h" |
| 36 | |
| 37 | typedef struct _UndoInfo UndoInfo; |
| 38 | |
| 39 | struct _UndoInfo
|
| 40 | {
|
| 41 | UndoAction action; |
| 42 | gchar *text; |
| 43 | gint start_pos; |
| 44 | gint end_pos; |
| 45 | gfloat window_position; |
| 46 | gint mergeable; |
| 47 | }; |
| 48 | |
| 49 | static void undo_free_list (GList **list_pointer); |
| 50 | static void undo_check_size (UndoMain *undostruct); |
| 51 | static gint undo_merge (GList *list,
|
| 52 | guint start_pos, |
| 53 | guint end_pos, |
| 54 | gint action, |
| 55 | const guchar *text);
|
| 56 | static void undo_add (const gchar *text, |
| 57 | gint start_pos, |
| 58 | gint end_pos, |
| 59 | UndoAction action, |
| 60 | UndoMain *undostruct); |
| 61 | static gint undo_get_selection (GtkTextView *textview,
|
| 62 | guint *start, |
| 63 | guint *end); |
| 64 | static void undo_insert_text_cb (GtkTextBuffer *textbuf, |
| 65 | GtkTextIter *iter, |
| 66 | gchar *new_text, |
| 67 | gint new_text_length, |
| 68 | UndoMain *undostruct); |
| 69 | static void undo_delete_text_cb (GtkTextBuffer *textbuf, |
| 70 | GtkTextIter *start, |
| 71 | GtkTextIter *end, |
| 72 | UndoMain *undostruct); |
| 73 | |
| 74 | static void undo_paste_clipboard_cb (GtkTextView *textview, |
| 75 | UndoMain *undostruct); |
| 76 | |
| 77 | void undo_undo (UndoMain *undostruct);
|
| 78 | void undo_redo (UndoMain *undostruct);
|
| 79 | |
| 80 | |
| 81 | UndoMain *undo_init(GtkWidget *text) |
| 82 | {
|
| 83 | UndoMain *undostruct; |
| 84 | GtkTextView *textview = GTK_TEXT_VIEW(text); |
| 85 | GtkTextBuffer *textbuf; |
| 86 | |
| 87 | g_return_val_if_fail(text != NULL, NULL); |
| 88 | |
| 89 | textbuf = gtk_text_view_get_buffer(textview); |
| 90 | |
| 91 | undostruct = g_new(UndoMain, 1);
|
| 92 | undostruct->textview = textview; |
| 93 | undostruct->undo = NULL;
|
| 94 | undostruct->redo = NULL;
|
| 95 | undostruct->paste = 0;
|
| 96 | undostruct->undo_state = FALSE; |
| 97 | undostruct->redo_state = FALSE; |
| 98 | |
| 99 | g_signal_connect(G_OBJECT(textbuf), "insert-text",
|
| 100 | G_CALLBACK(undo_insert_text_cb), undostruct); |
| 101 | g_signal_connect(G_OBJECT(textbuf), "delete-range",
|
| 102 | G_CALLBACK(undo_delete_text_cb), undostruct); |
| 103 | g_signal_connect(G_OBJECT(textview), "paste-clipboard",
|
| 104 | G_CALLBACK(undo_paste_clipboard_cb), undostruct); |
| 105 | |
| 106 | return undostruct;
|
| 107 | } |
| 108 | |
| 109 | void undo_destroy (UndoMain *undostruct)
|
| 110 | {
|
| 111 | undo_free_list(&undostruct->undo); |
| 112 | undo_free_list(&undostruct->redo); |
| 113 | g_free(undostruct); |
| 114 | } |
| 115 | |
| 116 | static UndoInfo *undo_object_new(gchar *text, gint start_pos, gint end_pos,
|
| 117 | UndoAction action, gfloat window_position) |
| 118 | {
|
| 119 | UndoInfo *undoinfo; |
| 120 | undoinfo = g_new (UndoInfo, 1);
|
| 121 | undoinfo->text = text; |
| 122 | undoinfo->start_pos = start_pos; |
| 123 | undoinfo->end_pos = end_pos; |
| 124 | undoinfo->action = action; |
| 125 | undoinfo->window_position = window_position; |
| 126 | return undoinfo;
|
| 127 | } |
| 128 | |
| 129 | static void undo_object_free(UndoInfo *undo) |
| 130 | {
|
| 131 | g_free (undo->text); |
| 132 | g_free (undo); |
| 133 | } |
| 134 | |
| 135 | /**
|
| 136 | * undo_free_list: |
| 137 | * @list_pointer: list to be freed |
| 138 | * |
| 139 | * frees and undo structure list |
| 140 | **/ |
| 141 | static void undo_free_list(GList **list_pointer) |
| 142 | {
|
| 143 | UndoInfo *undo; |
| 144 | GList *cur, *list = *list_pointer; |
| 145 | |
| 146 | if (list == NULL) return; |
| 147 | |
| 148 | for (cur = list; cur != NULL; cur = cur->next) { |
| 149 | undo = (UndoInfo *)cur->data; |
| 150 | undo_object_free(undo); |
| 151 | } |
| 152 | |
| 153 | g_list_free(list); |
| 154 | *list_pointer = NULL;
|
| 155 | } |
| 156 | |
| 157 | void undo_set_change_state_func(UndoMain *undostruct, UndoChangeStateFunc func,
|
| 158 | gpointer data) |
| 159 | {
|
| 160 | g_return_if_fail(undostruct != NULL);
|
| 161 | |
| 162 | undostruct->change_state_func = func; |
| 163 | undostruct->change_state_data = data; |
| 164 | } |
| 165 | |
| 166 | /**
|
| 167 | * undo_check_size: |
| 168 | * @compose: document to check |
| 169 | * |
| 170 | * Checks that the size of compose->undo does not excede settings->undo_levels and |
| 171 | * frees any undo level above sett->undo_level. |
| 172 | * |
| 173 | **/ |
| 174 | static void undo_check_size(UndoMain *undostruct) |
| 175 | {
|
| 176 | UndoInfo *last_undo; |
| 177 | guint length; |
| 178 | |
| 179 | if (prefs_common.undolevels < 1) return; |
| 180 | |
| 181 | /* No need to check for the redo list size since the undo
|
| 182 | list gets freed on any call to compose_undo_add */ |
| 183 | length = g_list_length(undostruct->undo); |
| 184 | if (length >= prefs_common.undolevels && prefs_common.undolevels > 0) { |
| 185 | last_undo = (UndoInfo *)g_list_last(undostruct->undo)->data; |
| 186 | undostruct->undo = g_list_remove(undostruct->undo, last_undo); |
| 187 | undo_object_free(last_undo); |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | /**
|
| 192 | * undo_merge: |
| 193 | * @last_undo: |
| 194 | * @start_pos: |
| 195 | * @end_pos: |
| 196 | * @action: |
| 197 | * |
| 198 | * This function tries to merge the undo object at the top of |
| 199 | * the stack with a new set of data. So when we undo for example |
| 200 | * typing, we can undo the whole word and not each letter by itself |
| 201 | * |
| 202 | * Return Value: TRUE is merge was sucessful, FALSE otherwise |
| 203 | **/ |
| 204 | static gint undo_merge(GList *list, guint start_pos, guint end_pos,
|
| 205 | gint action, const guchar *text)
|
| 206 | {
|
| 207 | guchar *temp_string; |
| 208 | UndoInfo *last_undo; |
| 209 | |
| 210 | /* This are the cases in which we will NOT merge :
|
| 211 | 1. if (last_undo->mergeable == FALSE) |
| 212 | [mergeable = FALSE when the size of the undo data was not 1. |
| 213 | or if the data was size = 1 but = '\n' or if the undo object |
| 214 | has been "undone" already ] |
| 215 | 2. The size of text is not 1 |
| 216 | 3. If the new merging data is a '\n' |
| 217 | 4. If the last char of the undo_last data is a space/tab |
| 218 | and the new char is not a space/tab ( so that we undo |
| 219 | words and not chars ) |
| 220 | 5. If the type (action) of undo is different from the last one |
| 221 | Chema */ |
| 222 | |
| 223 | if (list == NULL) return FALSE; |
| 224 | |
| 225 | last_undo = list->data; |
| 226 | |
| 227 | if (!last_undo->mergeable) return FALSE; |
| 228 | |
| 229 | if (end_pos - start_pos != 1 || |
| 230 | text[0] == '\n' || |
| 231 | action != last_undo->action || |
| 232 | action == UNDO_ACTION_REPLACE_INSERT || |
| 233 | action == UNDO_ACTION_REPLACE_DELETE) {
|
| 234 | last_undo->mergeable = FALSE; |
| 235 | return FALSE;
|
| 236 | } |
| 237 | |
| 238 | if (action == UNDO_ACTION_DELETE) {
|
| 239 | gboolean checkit = TRUE; |
| 240 | |
| 241 | if (last_undo->start_pos != end_pos &&
|
| 242 | last_undo->start_pos != start_pos) {
|
| 243 | last_undo->mergeable = FALSE; |
| 244 | return FALSE;
|
| 245 | } else if (last_undo->start_pos == start_pos) { |
| 246 | /* Deleted with the delete key */
|
| 247 | if (text[0] != ' ' && text[0] != '\t' && |
| 248 | (last_undo->text[last_undo->end_pos - last_undo->start_pos - 1] == ' ' || |
| 249 | last_undo->text[last_undo->end_pos - last_undo->start_pos - 1] == '\t')) |
| 250 | checkit = FALSE; |
| 251 | |
| 252 | temp_string = g_strdup_printf("%s%s", last_undo->text, text);
|
| 253 | last_undo->end_pos++; |
| 254 | g_free(last_undo->text); |
| 255 | last_undo->text = temp_string; |
| 256 | } else {
|
| 257 | /* Deleted with the backspace key */
|
| 258 | if (text[0] != ' ' && text[0] != '\t' && |
| 259 | (last_undo->text[0] == ' ' || |
| 260 | last_undo->text[0] == '\t')) |
| 261 | checkit = FALSE; |
| 262 | |
| 263 | temp_string = g_strdup_printf("%s%s", text, last_undo->text);
|
| 264 | last_undo->start_pos = start_pos; |
| 265 | g_free(last_undo->text); |
| 266 | last_undo->text = temp_string; |
| 267 | } |
| 268 | |
| 269 | if (!checkit) {
|
| 270 | last_undo->mergeable = FALSE; |
| 271 | return FALSE;
|
| 272 | } |
| 273 | } else if (action == UNDO_ACTION_INSERT) { |
| 274 | if (last_undo->end_pos != start_pos) {
|
| 275 | last_undo->mergeable = FALSE; |
| 276 | return FALSE;
|
| 277 | } else {
|
| 278 | temp_string = g_strdup_printf("%s%s", last_undo->text, text);
|
| 279 | g_free(last_undo->text); |
| 280 | last_undo->end_pos = end_pos; |
| 281 | last_undo->text = temp_string; |
| 282 | } |
| 283 | } else
|
| 284 | debug_print("Unknown action [%i] inside undo merge encountered", action);
|
| 285 | |
| 286 | return TRUE;
|
| 287 | } |
| 288 | |
| 289 | /**
|
| 290 | * compose_undo_add: |
| 291 | * @text: |
| 292 | * @start_pos: |
| 293 | * @end_pos: |
| 294 | * @action: either UNDO_ACTION_INSERT or UNDO_ACTION_DELETE |
| 295 | * @compose: |
| 296 | * @view: The view so that we save the scroll bar position. |
| 297 | * |
| 298 | * Adds text to the undo stack. It also performs test to limit the number |
| 299 | * of undo levels and deltes the redo list |
| 300 | **/ |
| 301 | |
| 302 | static void undo_add(const gchar *text, |
| 303 | gint start_pos, gint end_pos, |
| 304 | UndoAction action, UndoMain *undostruct) |
| 305 | {
|
| 306 | UndoInfo *undoinfo; |
| 307 | GtkAdjustment *vadj; |
| 308 | |
| 309 | g_return_if_fail(text != NULL);
|
| 310 | g_return_if_fail(end_pos >= start_pos); |
| 311 | |
| 312 | undo_free_list(&undostruct->redo); |
| 313 | |
| 314 | /* Set the redo sensitivity */
|
| 315 | undostruct->change_state_func(undostruct, |
| 316 | UNDO_STATE_UNCHANGED, UNDO_STATE_FALSE, |
| 317 | undostruct->change_state_data); |
| 318 | |
| 319 | if (undostruct->paste != 0) { |
| 320 | if (action == UNDO_ACTION_INSERT)
|
| 321 | action = UNDO_ACTION_REPLACE_INSERT; |
| 322 | else
|
| 323 | action = UNDO_ACTION_REPLACE_DELETE; |
| 324 | undostruct->paste = undostruct->paste + 1;
|
| 325 | if (undostruct->paste == 3) |
| 326 | undostruct->paste = 0;
|
| 327 | } |
| 328 | |
| 329 | if (undo_merge(undostruct->undo, start_pos, end_pos, action, text))
|
| 330 | return;
|
| 331 | |
| 332 | undo_check_size(undostruct); |
| 333 | |
| 334 | vadj = GTK_ADJUSTMENT(GTK_TEXT_VIEW(undostruct->textview)->vadjustment); |
| 335 | undoinfo = undo_object_new(g_strdup(text), start_pos, end_pos, action, |
| 336 | vadj->value); |
| 337 | |
| 338 | if (end_pos - start_pos != 1 || text[0] == '\n') |
| 339 | undoinfo->mergeable = FALSE; |
| 340 | else
|
| 341 | undoinfo->mergeable = TRUE; |
| 342 | |
| 343 | undostruct->undo = g_list_prepend(undostruct->undo, undoinfo); |
| 344 | |
| 345 | undostruct->change_state_func(undostruct, |
| 346 | UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED, |
| 347 | undostruct->change_state_data); |
| 348 | } |
| 349 | |
| 350 | /**
|
| 351 | * undo_undo: |
| 352 | * @w: not used |
| 353 | * @data: not used |
| 354 | * |
| 355 | * Executes an undo request on the current document |
| 356 | **/ |
| 357 | void undo_undo(UndoMain *undostruct)
|
| 358 | {
|
| 359 | UndoInfo *undoinfo; |
| 360 | GtkTextView *textview; |
| 361 | GtkTextBuffer *buffer; |
| 362 | GtkTextIter iter, start_iter, end_iter; |
| 363 | GtkTextMark *mark; |
| 364 | |
| 365 | g_return_if_fail(undostruct != NULL);
|
| 366 | |
| 367 | if (undostruct->undo == NULL) return; |
| 368 | |
| 369 | /* The undo data we need is always at the top op the
|
| 370 | stack. So, therefore, the first one */ |
| 371 | undoinfo = (UndoInfo *)undostruct->undo->data; |
| 372 | g_return_if_fail(undoinfo != NULL);
|
| 373 | undoinfo->mergeable = FALSE; |
| 374 | undostruct->redo = g_list_prepend(undostruct->redo, undoinfo); |
| 375 | undostruct->undo = g_list_remove(undostruct->undo, undoinfo); |
| 376 | |
| 377 | textview = undostruct->textview; |
| 378 | buffer = gtk_text_view_get_buffer(textview); |
| 379 | |
| 380 | undo_block(undostruct); |
| 381 | |
| 382 | /* Check if there is a selection active */
|
| 383 | mark = gtk_text_buffer_get_insert(buffer); |
| 384 | gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark); |
| 385 | gtk_text_buffer_place_cursor(buffer, &iter); |
| 386 | |
| 387 | /* Move the view (scrollbars) to the correct position */
|
| 388 | gtk_adjustment_set_value(GTK_ADJUSTMENT(textview->vadjustment), |
| 389 | undoinfo->window_position); |
| 390 | |
| 391 | switch (undoinfo->action) {
|
| 392 | case UNDO_ACTION_DELETE:
|
| 393 | gtk_text_buffer_get_iter_at_offset |
| 394 | (buffer, &iter, undoinfo->start_pos); |
| 395 | gtk_text_buffer_insert(buffer, &iter, undoinfo->text, -1);
|
| 396 | debug_print("UNDO_ACTION_DELETE %s\n", undoinfo->text);
|
| 397 | break;
|
| 398 | case UNDO_ACTION_INSERT:
|
| 399 | gtk_text_buffer_get_iter_at_offset |
| 400 | (buffer, &start_iter, undoinfo->start_pos); |
| 401 | gtk_text_buffer_get_iter_at_offset |
| 402 | (buffer, &end_iter, undoinfo->end_pos); |
| 403 | gtk_text_buffer_delete(buffer, &start_iter, &end_iter); |
| 404 | debug_print("UNDO_ACTION_INSERT %d\n", undoinfo->end_pos-undoinfo->start_pos);
|
| 405 | break;
|
| 406 | case UNDO_ACTION_REPLACE_INSERT:
|
| 407 | gtk_text_buffer_get_iter_at_offset |
| 408 | (buffer, &start_iter, undoinfo->start_pos); |
| 409 | gtk_text_buffer_get_iter_at_offset |
| 410 | (buffer, &end_iter, undoinfo->end_pos); |
| 411 | debug_print("UNDO_ACTION_REPLACE %s\n", undoinfo->text);
|
| 412 | /* "pull" another data structure from the list */
|
| 413 | undoinfo = (UndoInfo *)undostruct->undo->data; |
| 414 | g_return_if_fail(undoinfo != NULL);
|
| 415 | undostruct->redo = g_list_prepend(undostruct->redo, undoinfo); |
| 416 | undostruct->undo = g_list_remove(undostruct->undo, undoinfo); |
| 417 | g_return_if_fail(undoinfo->action == UNDO_ACTION_REPLACE_DELETE); |
| 418 | gtk_text_buffer_insert(buffer, &start_iter, undoinfo->text, -1);
|
| 419 | debug_print("UNDO_ACTION_REPLACE %s\n", undoinfo->text);
|
| 420 | break;
|
| 421 | case UNDO_ACTION_REPLACE_DELETE:
|
| 422 | g_warning("This should not happen. UNDO_REPLACE_DELETE");
|
| 423 | break;
|
| 424 | default:
|
| 425 | g_assert_not_reached(); |
| 426 | break;
|
| 427 | } |
| 428 | |
| 429 | undostruct->change_state_func(undostruct, |
| 430 | UNDO_STATE_UNCHANGED, UNDO_STATE_TRUE, |
| 431 | undostruct->change_state_data); |
| 432 | |
| 433 | if (undostruct->undo == NULL) |
| 434 | undostruct->change_state_func(undostruct, |
| 435 | UNDO_STATE_FALSE, |
| 436 | UNDO_STATE_UNCHANGED, |
| 437 | undostruct->change_state_data); |
| 438 | |
| 439 | undo_unblock(undostruct); |
| 440 | } |
| 441 | |
| 442 | /**
|
| 443 | * undo_redo: |
| 444 | * @w: not used |
| 445 | * @data: not used |
| 446 | * |
| 447 | * executes a redo request on the current document |
| 448 | **/ |
| 449 | void undo_redo(UndoMain *undostruct)
|
| 450 | {
|
| 451 | UndoInfo *redoinfo; |
| 452 | GtkTextView *textview; |
| 453 | GtkTextBuffer *buffer; |
| 454 | GtkTextIter iter, start_iter, end_iter; |
| 455 | GtkTextMark *mark; |
| 456 | |
| 457 | g_return_if_fail(undostruct != NULL);
|
| 458 | |
| 459 | if (undostruct->redo == NULL) return; |
| 460 | |
| 461 | redoinfo = (UndoInfo *)undostruct->redo->data; |
| 462 | g_return_if_fail (redoinfo != NULL);
|
| 463 | undostruct->undo = g_list_prepend(undostruct->undo, redoinfo); |
| 464 | undostruct->redo = g_list_remove(undostruct->redo, redoinfo); |
| 465 | |
| 466 | textview = undostruct->textview; |
| 467 | buffer = gtk_text_view_get_buffer(textview); |
| 468 | |
| 469 | undo_block(undostruct); |
| 470 | |
| 471 | /* Check if there is a selection active */
|
| 472 | mark = gtk_text_buffer_get_insert(buffer); |
| 473 | gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark); |
| 474 | gtk_text_buffer_place_cursor(buffer, &iter); |
| 475 | |
| 476 | /* Move the view to the right position. */
|
| 477 | gtk_adjustment_set_value(textview->vadjustment, |
| 478 | redoinfo->window_position); |
| 479 | |
| 480 | switch (redoinfo->action) {
|
| 481 | case UNDO_ACTION_INSERT:
|
| 482 | gtk_text_buffer_get_iter_at_offset |
| 483 | (buffer, &iter, redoinfo->start_pos); |
| 484 | gtk_text_buffer_insert(buffer, &iter, redoinfo->text, -1);
|
| 485 | debug_print("UNDO_ACTION_DELETE %s\n",redoinfo->text);
|
| 486 | break;
|
| 487 | case UNDO_ACTION_DELETE:
|
| 488 | gtk_text_buffer_get_iter_at_offset |
| 489 | (buffer, &start_iter, redoinfo->start_pos); |
| 490 | gtk_text_buffer_get_iter_at_offset |
| 491 | (buffer, &end_iter, redoinfo->end_pos); |
| 492 | gtk_text_buffer_delete(buffer, &start_iter, &end_iter); |
| 493 | debug_print("UNDO_ACTION_INSERT %d\n",
|
| 494 | redoinfo->end_pos-redoinfo->start_pos); |
| 495 | break;
|
| 496 | case UNDO_ACTION_REPLACE_DELETE:
|
| 497 | gtk_text_buffer_get_iter_at_offset |
| 498 | (buffer, &start_iter, redoinfo->start_pos); |
| 499 | gtk_text_buffer_get_iter_at_offset |
| 500 | (buffer, &end_iter, redoinfo->end_pos); |
| 501 | gtk_text_buffer_delete(buffer, &start_iter, &end_iter); |
| 502 | debug_print("UNDO_ACTION_REPLACE %s\n", redoinfo->text);
|
| 503 | /* "pull" another data structure from the list */
|
| 504 | redoinfo = (UndoInfo *)undostruct->redo->data; |
| 505 | g_return_if_fail(redoinfo != NULL);
|
| 506 | undostruct->undo = g_list_prepend(undostruct->undo, redoinfo); |
| 507 | undostruct->redo = g_list_remove(undostruct->redo, redoinfo); |
| 508 | g_return_if_fail(redoinfo->action == UNDO_ACTION_REPLACE_INSERT); |
| 509 | gtk_text_buffer_insert(buffer, &start_iter, redoinfo->text, -1);
|
| 510 | break;
|
| 511 | case UNDO_ACTION_REPLACE_INSERT:
|
| 512 | g_warning("This should not happen. Redo: UNDO_REPLACE_INSERT");
|
| 513 | break;
|
| 514 | default:
|
| 515 | g_assert_not_reached(); |
| 516 | break;
|
| 517 | } |
| 518 | |
| 519 | undostruct->change_state_func(undostruct, |
| 520 | UNDO_STATE_TRUE, UNDO_STATE_UNCHANGED, |
| 521 | undostruct->change_state_data); |
| 522 | |
| 523 | if (undostruct->redo == NULL) |
| 524 | undostruct->change_state_func(undostruct, |
| 525 | UNDO_STATE_UNCHANGED, |
| 526 | UNDO_STATE_FALSE, |
| 527 | undostruct->change_state_data); |
| 528 | |
| 529 | undo_unblock(undostruct); |
| 530 | } |
| 531 | |
| 532 | void undo_block(UndoMain *undostruct)
|
| 533 | {
|
| 534 | GtkTextBuffer *buffer; |
| 535 | |
| 536 | g_return_if_fail(GTK_IS_TEXT_VIEW(undostruct->textview)); |
| 537 | |
| 538 | buffer = gtk_text_view_get_buffer(undostruct->textview); |
| 539 | g_signal_handlers_block_by_func |
| 540 | (buffer, undo_insert_text_cb, undostruct); |
| 541 | g_signal_handlers_block_by_func |
| 542 | (buffer, undo_delete_text_cb, undostruct); |
| 543 | g_signal_handlers_block_by_func |
| 544 | (buffer, undo_paste_clipboard_cb, undostruct); |
| 545 | } |
| 546 | |
| 547 | void undo_unblock(UndoMain *undostruct)
|
| 548 | {
|
| 549 | GtkTextBuffer *buffer; |
| 550 | |
| 551 | g_return_if_fail(GTK_IS_TEXT_VIEW(undostruct->textview)); |
| 552 | |
| 553 | buffer = gtk_text_view_get_buffer(undostruct->textview); |
| 554 | g_signal_handlers_unblock_by_func |
| 555 | (buffer, undo_insert_text_cb, undostruct); |
| 556 | g_signal_handlers_unblock_by_func |
| 557 | (buffer, undo_delete_text_cb, undostruct); |
| 558 | g_signal_handlers_unblock_by_func |
| 559 | (buffer, undo_paste_clipboard_cb, undostruct); |
| 560 | } |
| 561 | |
| 562 | void undo_insert_text_cb(GtkTextBuffer *textbuf, GtkTextIter *iter,
|
| 563 | gchar *new_text, gint new_text_length, |
| 564 | UndoMain *undostruct) |
| 565 | {
|
| 566 | gchar *text_to_insert; |
| 567 | gint pos; |
| 568 | |
| 569 | if (prefs_common.undolevels <= 0) return; |
| 570 | |
| 571 | pos = gtk_text_iter_get_offset(iter); |
| 572 | |
| 573 | Xstrndup_a(text_to_insert, new_text, new_text_length, return);
|
| 574 | undo_add(text_to_insert, pos, pos + g_utf8_strlen(text_to_insert, -1),
|
| 575 | UNDO_ACTION_INSERT, undostruct); |
| 576 | } |
| 577 | |
| 578 | void undo_delete_text_cb(GtkTextBuffer *textbuf, GtkTextIter *start,
|
| 579 | GtkTextIter *end, UndoMain *undostruct) |
| 580 | {
|
| 581 | gchar *text_to_delete; |
| 582 | gint start_pos, end_pos; |
| 583 | |
| 584 | if (prefs_common.undolevels <= 0) return; |
| 585 | |
| 586 | text_to_delete = gtk_text_buffer_get_text(textbuf, start, end, FALSE); |
| 587 | if (!text_to_delete || !*text_to_delete) return; |
| 588 | |
| 589 | start_pos = gtk_text_iter_get_offset(start); |
| 590 | end_pos = gtk_text_iter_get_offset(end); |
| 591 | |
| 592 | undo_add(text_to_delete, start_pos, end_pos, UNDO_ACTION_DELETE, |
| 593 | undostruct); |
| 594 | g_free(text_to_delete); |
| 595 | } |
| 596 | |
| 597 | void undo_paste_clipboard_cb(GtkTextView *textview, UndoMain *undostruct)
|
| 598 | {
|
| 599 | debug_print("before Paste: %d\n", undostruct->paste);
|
| 600 | if (prefs_common.undolevels > 0) |
| 601 | if (undo_get_selection(textview, NULL, NULL)) |
| 602 | undostruct->paste = TRUE; |
| 603 | debug_print("after Paste: %d\n", undostruct->paste);
|
| 604 | } |
| 605 | |
| 606 | /**
|
| 607 | * undo_get_selection: |
| 608 | * @text: Text to get the selection from |
| 609 | * @start: return here the start position of the selection |
| 610 | * @end: return here the end position of the selection |
| 611 | * |
| 612 | * Gets the current selection for View |
| 613 | * |
| 614 | * Return Value: TRUE if there is a selection active, FALSE if not |
| 615 | **/ |
| 616 | static gint undo_get_selection(GtkTextView *textview, guint *start, guint *end)
|
| 617 | {
|
| 618 | GtkTextBuffer *buffer; |
| 619 | GtkTextIter start_iter, end_iter; |
| 620 | guint start_pos, end_pos; |
| 621 | |
| 622 | buffer = gtk_text_view_get_buffer(textview); |
| 623 | gtk_text_buffer_get_selection_bounds(buffer, &start_iter, &end_iter); |
| 624 | |
| 625 | start_pos = gtk_text_iter_get_offset(&start_iter); |
| 626 | end_pos = gtk_text_iter_get_offset(&end_iter); |
| 627 | |
| 628 | /* The user can select from end to start too. If so, swap it*/
|
| 629 | if (end_pos < start_pos) {
|
| 630 | guint swap_pos; |
| 631 | swap_pos = end_pos; |
| 632 | end_pos = start_pos; |
| 633 | start_pos = swap_pos; |
| 634 | } |
| 635 | |
| 636 | if (start != NULL) |
| 637 | *start = start_pos; |
| 638 | |
| 639 | if (end != NULL) |
| 640 | *end = end_pos; |
| 641 | |
| 642 | if ((start_pos > 0 || end_pos > 0) && (start_pos != end_pos)) |
| 643 | return TRUE;
|
| 644 | else
|
| 645 | return FALSE;
|
| 646 | } |