29
/* This struct is used to carry info about a undo/redo steps.
30
* ->next holds a pointer to the next element in the stack.
31
See the GTrashStack docs for more (vague) info.
32
* ->command holds the type of action, backspace, delete, or inset.
33
* ->start is the _character_ offset of the beginning of the step
34
* ->end is offset for the end of the step, mostly for deletions
35
* ->seq is a flag for cases (like overwriting text with a paste)
36
* where one user level undo involves multiple steps. For
37
* example, overwriting text via a paste involves an deletion
38
* and an insertion. The insertion will be at the top of the
39
* stack with the seq flag set, the deletion below it, with
40
* the flag unset. If a step with the flag set is undone or
41
* redone, we perform the next step as well, until we undo
42
* or redo a step without the flag set
43
* ->sty, the text of the change
33
gboolean seq; /* sequency flag */
54
/* For the command field in UndoInfo*/
43
61
static GtkWidget *undo_menu_item = NULL;
44
62
static GtkWidget *redo_menu_item = NULL;
46
static GList *undo_list = NULL;
47
static GList *redo_list = NULL;
64
static GTrashStack *undo_stack = NULL;
65
static GTrashStack *redo_stack = NULL;
66
/*undo_gstr acts like a temp for UndoInfo->str, growing or shrinking
67
with insertions until a word boundry is set, and it's copied to
48
69
static GString *undo_gstr;
70
/*we keep one of these around to "build up" before copying it
71
into the undo list when a word boundry is met*/
49
72
static UndoInfo *ui_tmp;
73
/*doesn't seem to do anything but store 0 so that we can know when we're
74
out of data in the undo_list EXPME*/
50
75
static gint step_modif;
51
76
static GtkWidget *view;
77
/*I dunno why we track whether or not overwrite is on. I mean, it
78
seems like a good idea, I guess EXPME*/
52
79
static gboolean overwrite_mode = FALSE;
80
/*instead of passing stuff in through arguments, we use locals. Fun*/
53
81
static guint keyval;
82
/*on occasion we need the previous keyval to determine whether or not we've
54
84
static guint prev_keyval;/*, keyevent_setval(0); */
56
86
static void cb_toggle_overwrite(void)
62
92
static void undo_clear_undo_info(void)
64
while (g_list_length(undo_list)) {
65
g_free(((UndoInfo *)undo_list->data)->str);
66
g_free(undo_list->data);
67
undo_list = g_list_delete_link(undo_list, undo_list);
96
/*Peeking is O(1) while checking the height is O(N)*/
97
while (g_trash_stack_peek(&undo_stack)) {
98
ui = (UndoInfo *)g_trash_stack_pop(&undo_stack);
71
104
static void undo_clear_redo_info(void)
73
while (g_list_length(redo_list)) {
74
g_free(((UndoInfo *)redo_list->data)->str);
75
g_free(redo_list->data);
76
redo_list = g_list_delete_link(redo_list, redo_list);
108
while (g_trash_stack_peek(&redo_stack)) {
109
ri = (UndoInfo *)g_trash_stack_pop(&redo_stack);
116
/*takes command, offsets and a string, and builds a UndoInfo object, which
117
we stuff into the undo_list. Note that you can't set ->seq this way.
118
Also (FIXME) we have no use for the buffer passed to us*/
80
119
static void undo_append_undo_info(GtkTextBuffer *buffer, gint command, gint start, gint end, gchar *str)
82
121
UndoInfo *ui = g_malloc(sizeof(UndoInfo));
89
undo_list = g_list_append(undo_list, ui);
90
DV(g_print("undo_cb: %d %s (%d-%d)\n", command, str, start, end));
128
g_trash_stack_push(&undo_stack, ui);
129
DV(g_print("undo_append_undo_info: command: %d string: %s range: (%d-%d)\n", command, str, start, end));
132
/* Here it is. The monster that drives everything around here and does
133
* most of the work. The various callbacks hooked into the buffer pass
134
* the buck here. Since this function will be called for every little
135
* change to the buffer it attempts to determine if the current change
136
* is part of an ongoing action the user will want to undo/redo in one
137
* go (confusingly, also called a sequence) or the start of a new
138
* sequence. If it's part of an ongoing sequence, it builds up data in
139
* the undo_gstr or ui_tmp variables. If it's the start of a new
140
* sequence, it copies the ui_tmp and undo_gstr data into the
141
* undo_info list (via undo_append_undo_info), and then clears ui_tmp
142
* and undo_gstr for the next run.
144
* Note that a sequence is not just a series of insertions and a word
145
* boundry, but also a linefeed, a deletion or a backspace
93
147
static void undo_create_undo_info(GtkTextBuffer *buffer, gint command, gint start, gint end)
95
149
GtkTextIter start_iter, end_iter;
186
243
end = gtk_text_iter_get_offset(end_iter);
188
245
DV(g_print("delete-range: keyval = 0x%x\n", keyval));
247
/*Overwriting text from the menubar or context menu. I have
248
no idea why ovewriting from the context menu generates a keyevent
249
but inserting does not (see comments in the inset-text callback*/
250
if (!keyval && prev_keyval)
251
undo_set_sequency(TRUE);
190
if (!keyval && prev_keyval)
191
undo_set_sequency(TRUE);
192
253
if (keyval == 0x10000 && prev_keyval > 0x10000) /* for Ctrl+V overwrite */
193
254
undo_set_sequency(TRUE);
194
255
if (keyval == GDK_BackSpace)
239
292
set_main_window_title_with_asterisk(TRUE);
295
/*I'm this is only called by init, and when we save. We call it from
296
the save call back, so that we can properly handle the asterisk in the
297
titlebar and warn that the document is altered after a save, but
298
retain the undo history. The leads to (FIXME) bug 2730*/
242
299
void undo_reset_step_modif(void)
244
step_modif = g_list_length(undo_list);
301
step_modif = g_trash_stack_height(&undo_stack);
245
302
DV(g_print("undo_reset_step_modif: Reseted step_modif by %d\n", step_modif));
248
305
static void undo_check_step_modif(GtkTextBuffer *buffer)
250
if (g_list_length(undo_list) == step_modif) {
307
if (g_trash_stack_height(&undo_stack) == step_modif) {
251
308
g_signal_handlers_block_by_func(G_OBJECT(buffer), G_CALLBACK(cb_modified_changed), NULL);
252
309
gtk_text_buffer_set_modified(buffer, FALSE);
253
310
g_signal_handlers_unblock_by_func(G_OBJECT(buffer), G_CALLBACK(cb_modified_changed), NULL);
266
328
G_CALLBACK(cb_modified_changed), NULL);
331
/*Sets up this party. It also tracks if it's been called multiple times,
332
and cleans up after itself (sorta) if it has. Which is odd, as only
333
we only call it during startup.*/
269
334
void undo_init(GtkWidget *textview, GtkTextBuffer *buffer, GtkWidget *menubar)
271
336
GtkItemFactory *ifactory;
272
337
static guint init_flag = 0; /* TODO: divide to undo_clear() */
339
if (undo_stack != NULL)
275
340
undo_clear_undo_info();
341
if (redo_stack != NULL)
277
342
undo_clear_redo_info();
278
343
undo_reset_step_modif();
279
344
DV(g_print("undo_init: list reseted\n"));
281
347
if (!undo_menu_item) {
282
348
ifactory = gtk_item_factory_from_widget(menubar);
283
349
undo_menu_item = gtk_item_factory_get_widget(ifactory, "/Edit/Undo");
296
362
/* ui_tmp->str = g_strdup(""); */
297
363
undo_gstr = g_string_new("");
365
/*Not sure why we do this here, and not in the insert callback*/
299
366
ui_tmp->command = INS;
300
367
undo_gstr = g_string_erase(undo_gstr, 0, -1);
370
/*Straight forward. When performing the undo/redo, we don't want our
371
deletion and insertion callbacks getting called and going all wonky
372
so we provide a couple of functions to block and unblock. This
373
constitutes all the code in this file which doesn't suck*/
303
374
gint undo_block_signal(GtkTextBuffer *buffer)
316
387
g_signal_handlers_unblock_by_func(G_OBJECT(buffer), G_CALLBACK(cb_insert_text), buffer);
319
gint undo_disconnect_signal(GtkTextBuffer *buffer) /*must be not needed */
322
g_signal_handlers_disconnect_by_func(G_OBJECT(buffer), G_CALLBACK(cb_delete_range), buffer) +
323
/* g_signal_handlers_disconnect_by_func(G_OBJECT(buffer), G_CALLBACK(cb_modified_changed), buffer) + */
324
g_signal_handlers_disconnect_by_func(G_OBJECT(buffer), G_CALLBACK(cb_insert_text), buffer);
391
/* More in the world of the bizarre sequency flag. If we ever need to set the
392
flag, by definition the UndoInfo struct for the undo step is already on
393
the stack, and a new temp object already being filled up - probably
394
getting ready to be saved itself. So we need a way to set the flag
395
on the UndoInfo struct at the top of the stack.
327
398
void undo_set_sequency(gboolean seq)
331
if (g_list_length(undo_list)) {
332
ui = g_list_last(undo_list)->data;
402
ui = g_trash_stack_peek(&undo_stack);
335
406
DV(g_print("<undo_set_sequency: %d>\n", seq));
338
gboolean undo_undo(GtkTextBuffer *buffer)
409
/* Pretty straightforward, believe it or not. First thing it does is
410
* put any data from undo_gstr in a UndoInfo struct on the stack.
411
* Once that's done, it sees if there is anything on the stack,
412
* pops it, blocks the undo callbacks on the buffer, and inspects
413
* the command on the UndoInfo struct. If were undoing an insertion
414
* then we need to delete text. Grab iterators from the offsets
415
* in the UndoInfo object, and delete that sucker. If it is a
416
* deletion were undoing, then insert the text back in. REMEMBER:
417
* overwritten text winds up being handled via a two part process
418
* with the sequency flag. If the sequency flag is set for the
419
* NEXT step in the stack, then (Lord Help Us and also FIXME)
420
* we return true, so we tell the caller to call us again so
421
* we can do it all over again. If, on the other hand, the
422
* sequency flag is not set, then we tidy up after ourselves,
423
* putting the curser in the right place, and scrolling the textview,
424
* setting the redo menu item sensitive, fixing the title bar if
425
* necessary and finally set the undo item insensitive if we've
426
* emptied the stack. Whew!
428
void undo_undo(GtkTextBuffer *buffer)
340
430
GtkTextIter start_iter, end_iter;
343
433
if (undo_gstr->len) {
434
DV(g_print("undo_undo_real: ui_tmp->command %d", ui_tmp->command));
344
435
undo_append_undo_info(buffer, ui_tmp->command, ui_tmp->start, ui_tmp->end, g_strdup(undo_gstr->str));
345
436
undo_gstr = g_string_erase(undo_gstr, 0, -1);
347
if (g_list_length(undo_list)) {
438
if ((g_trash_stack_peek(&undo_stack)) != NULL) {
348
439
undo_block_signal(buffer);
349
ui = g_list_last(undo_list)->data;
440
ui = (UndoInfo *)(g_trash_stack_pop(&undo_stack));
441
DV(g_print("undo_undo_real: ui->command %d", ui->command));
350
442
gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, ui->start);
351
443
switch (ui->command) {
357
449
gtk_text_buffer_insert(buffer, &start_iter, ui->str, -1);
359
redo_list = g_list_append(redo_list, ui);
360
undo_list = g_list_delete_link(undo_list, g_list_last(undo_list));
361
DV(g_print("cb_edit_undo: undo list left %d\n", g_list_length(undo_list)));
451
g_trash_stack_push(&redo_stack, ui);
452
DV(g_print("cb_edit_undo: undo list left %d\n", g_trash_stack_height(&undo_stack)));
362
453
undo_unblock_signal(buffer);
363
454
undo_check_step_modif(buffer);
364
if (g_list_length(undo_list)) {
365
if (((UndoInfo *)g_list_last(undo_list)->data)->seq)
455
if (undo_stack != NULL) {
456
if (((UndoInfo *)g_trash_stack_peek(&undo_stack))->seq) {
368
461
gtk_widget_set_sensitive(undo_menu_item, FALSE);
369
462
gtk_widget_set_sensitive(redo_menu_item, TRUE);
373
466
gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(view), &start_iter,
374
467
0.1, FALSE, 0.5, 0.5);
379
gboolean undo_redo(GtkTextBuffer *buffer)
472
/*yeah, so, basically this is exactly like undo_undo, except simpler*/
473
void undo_redo(GtkTextBuffer *buffer)
381
475
GtkTextIter start_iter, end_iter;
384
if (g_list_length(redo_list)) {
478
if (redo_stack != NULL) {
385
479
undo_block_signal(buffer);
386
ri = g_list_last(redo_list)->data;
480
ri = (UndoInfo *)(g_trash_stack_pop(&redo_stack));
387
481
gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, ri->start);
388
482
switch (ri->command) {
393
487
gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, ri->end);
394
488
gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
396
undo_list = g_list_append(undo_list, ri);
397
redo_list = g_list_delete_link(redo_list, g_list_last(redo_list));
398
DV(g_print("cb_edit_redo: redo list left %d\n", g_list_length(redo_list)));
490
g_trash_stack_push(&undo_stack, ri);
491
DV(g_print("cb_edit_redo: redo list left %d\n", g_trash_stack_height(&redo_stack)));
399
492
undo_unblock_signal(buffer);
400
493
undo_check_step_modif(buffer);
403
if (!g_list_length(redo_list))
498
if (redo_stack == NULL)
404
499
gtk_widget_set_sensitive(redo_menu_item, FALSE);
405
500
gtk_widget_set_sensitive(undo_menu_item, TRUE);
406
501
gtk_text_buffer_place_cursor(buffer, &start_iter);
407
502
gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(view), &start_iter,
408
503
0.1, FALSE, 0.5, 0.5);