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