~ubuntu-branches/ubuntu/wily/mousepad/wily-proposed

« back to all changes in this revision

Viewing changes to src/undo.c

  • Committer: Bazaar Package Importer
  • Author(s): Gauvain Pocentek
  • Date: 2007-01-24 11:22:03 UTC
  • mfrom: (1.1.5 upstream)
  • Revision ID: james.westby@ubuntu.com-20070124112203-usrd5u2kfovivkw7
Tags: 0.2.12-0ubuntu1
* New upstream release, part of Xfce 4.4
* debian/control: tighten libxfcegui4-dev version

Show diffs side-by-side

added added

removed removed

Lines of Context:
26
26
 
27
27
#define DV(x)
28
28
 
 
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
 
44
 */
29
45
typedef struct {
 
46
        GTrashStack *next;
30
47
        gint command;
31
48
        gint start;
32
49
        gint end;
33
 
        gboolean seq; /* sequency flag */
 
50
        gboolean seq; 
34
51
        gchar *str;
35
52
} UndoInfo;
36
53
 
 
54
/* For the command field in UndoInfo*/
37
55
enum {
38
56
        BS = 0,
39
57
        DEL,
43
61
static GtkWidget *undo_menu_item = NULL;
44
62
static GtkWidget *redo_menu_item = NULL;
45
63
 
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
 
68
  ui->str */
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
 
83
hit a word boundry*/
54
84
static guint prev_keyval;/*, keyevent_setval(0); */
55
85
 
56
86
static void cb_toggle_overwrite(void)
61
91
 
62
92
static void undo_clear_undo_info(void)
63
93
{
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);
 
94
        UndoInfo *ui;
 
95
        
 
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);
 
99
                g_free(ui->str);
 
100
                g_free(ui);
68
101
        }
69
102
}
70
103
 
71
104
static void undo_clear_redo_info(void)
72
105
{
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);
 
106
        UndoInfo *ri;
 
107
        
 
108
        while (g_trash_stack_peek(&redo_stack)) {
 
109
                ri = (UndoInfo *)g_trash_stack_pop(&redo_stack);
 
110
                g_free(ri->str);
 
111
                g_free(ri);
77
112
        }
 
113
        
78
114
}
79
115
 
 
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)
81
120
{
82
121
        UndoInfo *ui = g_malloc(sizeof(UndoInfo));
86
125
        ui->end = end;
87
126
        ui->seq = FALSE;
88
127
        ui->str = str;
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));
91
130
}
92
131
 
 
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. 
 
143
 *
 
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
 
146
 */ 
93
147
static void undo_create_undo_info(GtkTextBuffer *buffer, gint command, gint start, gint end)
94
148
{
95
149
        GtkTextIter start_iter, end_iter;
98
152
        
99
153
        gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
100
154
        gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
 
155
        
 
156
        /*The callbacks have the text - why do we get it from the buffer? 
 
157
          For redo, maybe? EXPME*/
101
158
        str = gtk_text_buffer_get_text(buffer, &start_iter, &end_iter, FALSE);
102
159
        
103
160
        if (undo_gstr->len) {
168
225
                g_string_append(undo_gstr, str);
169
226
        } else 
170
227
                undo_append_undo_info(buffer, command, start, end, g_strdup(str));
171
 
        
 
228
DV(g_print("undo_create_undo_info: ui_tmp->command: %d", ui_tmp->command));
172
229
        undo_clear_redo_info();
173
230
        prev_keyval = keyval;
174
231
        keyevent_setval(0);
186
243
        end = gtk_text_iter_get_offset(end_iter);
187
244
        
188
245
DV(g_print("delete-range: keyval = 0x%x\n", keyval));
 
246
 
 
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);
189
252
        
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)
206
267
        end = gtk_text_iter_get_offset(iter);
207
268
        start = end - g_utf8_strlen(str, -1);
208
269
        
209
 
DV(g_print("insert-text: keyval = 0x%x\n", keyevent_getval()));
210
 
        
211
 
        if (!keyval && prev_keyval)
212
 
                undo_set_sequency(TRUE);
213
 
#if 0
214
 
        if (keyval == 0x10000 && prev_keyval > 0x10000) /* don't need probably */
215
 
                undo_set_sequency(TRUE);
216
 
#endif
217
270
        undo_create_undo_info(buffer, INS, start, end);
218
271
}
219
272
 
239
292
        set_main_window_title_with_asterisk(TRUE);
240
293
}
241
294
 
 
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)
243
300
{
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));
246
303
}
247
304
 
248
305
static void undo_check_step_modif(GtkTextBuffer *buffer)
249
306
{
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);
255
312
        }
256
313
}
257
314
 
 
315
/*used by undo_init to attach the various callbacks we need
 
316
  to generate the undo history. Of course, it is a bit of 
 
317
  a misnomer, as it only connects tp signals from the buffer, 
 
318
  undo_init connects to signals emitted by the textview
 
319
 */
258
320
static gint undo_connect_signal(GtkTextBuffer *buffer)
259
321
{
260
322
        g_signal_connect(G_OBJECT(buffer), "delete-range",
266
328
                G_CALLBACK(cb_modified_changed), NULL);
267
329
}
268
330
 
 
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)
270
335
{
271
336
        GtkItemFactory *ifactory;
272
337
        static guint init_flag = 0; /* TODO: divide to undo_clear() */
273
338
        
274
 
        if (undo_list)
 
339
        if (undo_stack != NULL)
275
340
                undo_clear_undo_info();
276
 
        if (redo_list)
 
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"));
280
345
        
 
346
        /*This is retarded*/
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");
288
354
        
289
355
        if (!init_flag) {
290
356
                g_signal_connect(G_OBJECT(textview), "toggle-overwrite",
291
 
                        G_CALLBACK(cb_toggle_overwrite), NULL);
 
357
                        G_CALLBACK(cb_toggle_overwrite), NULL); /*I dunno why we care*/
292
358
                view = textview;
293
359
                init_flag = undo_connect_signal(buffer);
294
360
                keyevent_setval(0);
296
362
                /* ui_tmp->str = g_strdup(""); */
297
363
                undo_gstr = g_string_new("");
298
364
        }
 
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);
301
368
}
302
369
 
 
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)
304
375
{
305
376
        return 
316
387
        g_signal_handlers_unblock_by_func(G_OBJECT(buffer), G_CALLBACK(cb_insert_text), buffer);
317
388
}
318
389
 
319
 
gint undo_disconnect_signal(GtkTextBuffer *buffer) /*must be not needed */
320
 
{
321
 
        return 
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);
325
 
}
326
390
 
 
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.
 
396
   TaaDaa!
 
397
  */
327
398
void undo_set_sequency(gboolean seq)
328
399
{
329
400
        UndoInfo *ui;
330
 
        
331
 
        if (g_list_length(undo_list)) {
332
 
                ui = g_list_last(undo_list)->data;
 
401
 
 
402
        ui = g_trash_stack_peek(&undo_stack);
 
403
        if (ui != NULL) {
333
404
                ui->seq = seq;
334
405
        }
335
406
DV(g_print("<undo_set_sequency: %d>\n", seq));  
336
407
}
337
408
 
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!
 
427
 */
 
428
void undo_undo(GtkTextBuffer *buffer)
339
429
{
340
430
        GtkTextIter start_iter, end_iter;
341
431
        UndoInfo *ui;
342
432
        
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);
346
437
        }
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) {
352
444
                case INS:
356
448
                default:
357
449
                        gtk_text_buffer_insert(buffer, &start_iter, ui->str, -1);
358
450
                }
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)
366
 
                                return TRUE;
 
455
                if (undo_stack != NULL) {
 
456
                        if (((UndoInfo *)g_trash_stack_peek(&undo_stack))->seq) {
 
457
                                undo_undo(buffer);
 
458
                                return;
 
459
                        }
367
460
                } else
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);
375
468
        }
376
 
        return FALSE;
 
469
        return;
377
470
}
378
471
 
379
 
gboolean undo_redo(GtkTextBuffer *buffer)
 
472
/*yeah, so, basically this is exactly like undo_undo, except simpler*/
 
473
void undo_redo(GtkTextBuffer *buffer)
380
474
{
381
475
        GtkTextIter start_iter, end_iter;
382
476
        UndoInfo *ri;
383
477
        
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) {
389
483
                case INS:
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);
395
489
                }
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);
401
 
                if (ri->seq)
402
 
                        return TRUE;
403
 
                if (!g_list_length(redo_list))
 
494
                if (ri->seq) {
 
495
                        undo_redo(buffer);
 
496
                        return;
 
497
                }
 
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);
409
504
        }
410
 
        return FALSE;
 
505
        return;
411
506
}