1 /****************************************************************************
2 Copyright (C) 1987-2015 by Jeffery P. Hansen
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License along
15 with this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 ****************************************************************************/
18 /*###########################################################################*
19 *
20 * The operations declared in this file are used to implement the
21 * undo/redo features of tkgate. The basic approach is to make backup
22 * copies of all objects which are modified over the course of executing
23 * a command. From the perspective of the undo manager, a command is all
24 * of the operations that occur within a frame, A frame is started by
25 * a call to ob_begin_frame() (or related frame starter) and ended by
26 * a call to ob_end_frame().
27 *
28 * Guidelines for frame names.
29 * 1) Frame names that begin with a '-' are weak, frame names that begin
30 * with a '+' are strong, and frame names with no qualifier are normal.
31 * 2) The ob_suggest_name() function can be used to override the name of
32 * the current frame, but only if the supplied name is "stronger" than
33 * the current name. This can be used to delay naming of a frame until
34 * after the frame has started.
35 *
36 * Guidelines for code modification.
37 *
38 * 1) All objects should be created with ob_malloc(), ob_calloc(),
39 * ob_realloc() or ob_stdrup().
40 * 2) All objects should be freed with ob_free()
41 * 3) ob_touch() should be called on all objects before modification.
42 * 4) The code for one undoable action should be enclosed in ob_begin_frame("name")
43 * and ob_end_frame() calls. Alternatively, ob_append_frame("name") can be
44 * used in place of ob_begin_frame to cause the new frame to be appended to
45 * the previous frame. For example, individual scrolling steps are appended into
46 * a single undoable scrolling action.
47 *
48 * Here are some examples of how to use ob_touch()
49 *
50 * Example 1)
51 * ob_touch(g);
52 * g->xpos += 10;
53 *
54 * Example 2)
55 * ob_touch(g);
56 * g->wires[i] = w;
57 *
58 * g->wires[] is an static array declared as part of the 'g' (GCElement) object
59 * so no ob_touch() should be used.
60 *
61 * Example 3)
62 * ob_touch(w->nodes);
63 * w->nodes->x = x;
64 *
65 * Example 4)
66 * ob_touch(w->nodes);
67 * set_position(&w->nodes->x,&w->nodes->y);
68 *
69 * Example 5)
70 * ob_touch(g)
71 * g->u.basic.extbar = n;
72 *
73 * u.basic.extbar is part of g (GCElement).
74 *
75 * Any memory that is allocated and freed wholy within the execution of a command
76 * can use the standard malloc()/free() to reduce the amount of data that needs
77 * to be backed up for a command.
78 *
79 *###########################################################################*/
80
81 #include <stdlib.h>
82 #include <assert.h>
83
84 #include "tkgate.h"
85
86 #define OBJECT_DEBUG 0 /* Debugging display of object handling */
87 #define OBJECT_SHOWMODS 0 /* More debugging display of object handling */
88 #define OBJECT_STOPONERROR 0 /* Abort as soon as we hit an object error */
89 #define OBJECT_MEGADEBUG 0 /* Print out all calls to malloc/free */
90 #define OBJECT_HARSHMEM 0 /* Put bogus data in malloc/free'ed memory */
91 #define OBJECT_TRACKUSAGE 1 /* Track memory usage */
92
93 #define OBJECT_UNNAMED "Action" /* Name to use when we get an action without a name */
94
95 void undo_sync(int is_after);
96
97 /*****************************************************************************
98 * Object flags.
99 *****************************************************************************/
100 typedef enum {
101 OF_DELETED = 0x1, /* Object is marked for deletion */
102 OF_NOUNDO = 0x2, /* Object can not be undone */
103 } ObjectFlag_t;
104
105 /*****************************************************************************
106 * Object change type codes
107 *****************************************************************************/
108 typedef enum {
109 OC_MALLOC = 1,
110 OC_FREE,
111 OC_MODIFY
112 } ObjectChangeType_t;
113
114 /*****************************************************************************
115 *
116 * typedef classes for object handler (used only in this file).
117 *
118 *****************************************************************************/
119 typedef struct objectmanager_str ObjectManager;
120 typedef struct objectframe_str ObjectFrame;
121 typedef struct objectchange_str ObjectChange;
122 typedef struct object_str Object;
123
124 typedef struct {
125 unsigned ob; /* Allocation of ob_ memory */
126 unsigned actual; /* Actual outstanding memory usage */
127 } Memusage_t;
128
129 /*****************************************************************************
130 * The object manager.
131 *****************************************************************************/
132 struct objectmanager_str {
133 unsigned om_started; /* Object management has been started */
134 ob_OMMode_t om_mode; /* Current manager mode */
135 unsigned om_mark; /* Current mark value */
136 int om_numUndo; /* Number of "visible" undo frames */
137 List om_undo; /* The undo stack */
138 List om_redo; /* The redo stack */
139 ObjectFrame *om_cur; /* The current frame */
140 #if OBJECT_DEBUG
141 Memusage_t memusage;
142 #endif // OBJECT_DEBUG
143 };
144
145 /*****************************************************************************
146 * ObjectFrame - A collection of modifications to objects that constitute
147 * a single undoable action.
148 *****************************************************************************/
149 struct objectframe_str {
150 FrameFlags_t of_flags; /* Frame flags */
151 char *of_name; /* Command name of this frame */
152 int of_level; /* Level of frame */
153 ObjectFrame *of_next; /* The next item on the stack */
154 int of_num_changes; /* Number of changes */
155 ObjectChange *of_changes; /* Changes in this frame */
156 };
157
158 /*****************************************************************************
159 * ObjectChange - Represents the changes that occured to a single object
160 * within a frame.
161 *****************************************************************************/
162 struct objectchange_str {
163 ObjectChangeType_t oc_type; /* Type of change */
164 Object *oc_ptr; /* Pointer to object location */
165 Object *oc_backup; /* Backup copy of object */
166 ObjectChange *oc_next; /* Next in list of changes */
167 };
168
169 /*****************************************************************************
170 * Object - This structure contains "hidden" information about an object that
171 * is stored in front of each object returned by ob_malloc()
172 *****************************************************************************/
173 struct object_str {
174 const char *o_type; /* Object type */
175 unsigned o_size; /* Size of this object */
176 unsigned o_mark; /* Object change mark */
177 unsigned o_flags; /* Change type */
178 };
179
180 /*****************************************************************************/
181
182 int ob_max_undo = 10;
183
184 static ObjectManager objm;
185
186 #if OBJECT_MEGADEBUG
xmalloc(size_t s)187 void *xmalloc(size_t s)
188 {
189 void *o = malloc(s);
190 printf("%p = malloc(%d)\n",o,s);
191 return o;
192 }
193
xfree(void * o)194 void xfree(void *o)
195 {
196 printf("free(%p)\n",o);
197 free(o);
198 }
199
200 #define malloc(s) xmalloc(s)
201 #define free(o) xfree(o)
202 #endif
203
ob_init()204 void ob_init()
205 {
206 #if OBJECT_DEBUG
207 printf("ob_init()\n");
208 #endif // OBJECT_DEBUG
209 objm.om_started = 0;
210 objm.om_mode = OM_DISABLED;
211 objm.om_mark = 1;
212 List_init(&objm.om_undo);
213 List_init(&objm.om_redo);
214 objm.om_numUndo = 0;
215 objm.om_cur = 0;
216 #if OBJECT_DEBUG
217 objm.memusage.actual = 0;
218 objm.memusage.ob = 0;
219 #endif // OBJECT_DEBUG
220 }
221
ObjectFrame_init(ObjectFrame * self,const char * name,unsigned flags)222 void ObjectFrame_init(ObjectFrame *self,const char *name,unsigned flags)
223 {
224 self->of_flags = flags;
225 if (name) {
226 #if OBJECT_DEBUG
227 objm.memusage.actual += strlen(name)+1;
228 #endif // OBJECT_DEBUG
229 self->of_name = strdup(name);
230 }
231 else
232 self->of_name = NULL;
233 self->of_next = 0;
234 self->of_level = 1;
235 self->of_num_changes = 0;
236 self->of_changes = 0;
237 }
238
239 /*****************************************************************************
240 *
241 * Create a new object frame.
242 *
243 *****************************************************************************/
new_ObjectFrame(const char * name,unsigned flags)244 ObjectFrame *new_ObjectFrame(const char *name,unsigned flags)
245 {
246 ObjectFrame *o = (ObjectFrame*) malloc(sizeof(ObjectFrame));
247 #if OBJECT_DEBUG
248 objm.memusage.actual += sizeof(ObjectFrame);
249 #endif // OBJECT_DEBUG
250 ObjectFrame_init(o,name,flags);
251
252 return o;
253 }
254
255 /*****************************************************************************
256 *
257 * Destructor for the ObjectChange
258 *
259 *****************************************************************************/
ObjectChange_destroy(ObjectChange * self)260 void ObjectChange_destroy(ObjectChange *self)
261 {
262 if (self->oc_backup) {
263 #if OBJECT_DEBUG
264 assert(objm.memusage.actual >= self->oc_backup->o_size);
265 objm.memusage.actual -= self->oc_backup->o_size;
266 #endif // OBJECT_DEBUG
267 free(self->oc_backup);
268 }
269 }
270
delete_ObjectChange(ObjectChange * self)271 void delete_ObjectChange(ObjectChange *self)
272 {
273 ObjectChange_destroy(self);
274 #if OBJECT_DEBUG
275 assert(objm.memusage.actual >= sizeof(ObjectChange));
276 objm.memusage.actual -= sizeof(ObjectChange);
277 #endif
278 free(self);
279 }
280
281 /*****************************************************************************
282 *
283 * Destructor for the ObjectFrame
284 *
285 *****************************************************************************/
ObjectFrame_destroy(ObjectFrame * self)286 void ObjectFrame_destroy(ObjectFrame *self)
287 {
288 ObjectChange *C,*NC;
289
290 for (C = self->of_changes;C;C = NC) {
291 NC = C->oc_next;
292 delete_ObjectChange(C);
293 }
294 if (self->of_name) {
295 #if OBJECT_DEBUG
296 assert(objm.memusage.actual >= strlen(self->of_name)+1);
297 objm.memusage.actual -= strlen(self->of_name)+1;
298 #endif
299 free(self->of_name);
300 }
301 }
302
delete_ObjectFrame(ObjectFrame * self)303 void delete_ObjectFrame(ObjectFrame *self)
304 {
305 ObjectFrame_destroy(self);
306 #if OBJECT_DEBUG
307 assert(objm.memusage.actual >= sizeof(ObjectFrame));
308 objm.memusage.actual -= sizeof(ObjectFrame);
309 #endif
310 free(self);
311 }
312
313 /*****************************************************************************
314 *
315 * Check to see if this is an "invisible" frame. An invisible frame
316 * is one that is explicitly marked "transparent", is weakly named,
317 * or has no object modifications. Invisible frames do not appear
318 * in the undo/redo lists, and are automatically undone/redone when
319 * applying doing an undo or redo.
320 *
321 *****************************************************************************/
ObjectFrame_isInvisible(ObjectFrame * F)322 static int ObjectFrame_isInvisible(ObjectFrame *F)
323 {
324 if ((F->of_flags & FF_TRANSPARENT)) return 1;
325 if (F->of_name && *F->of_name == '-') return 1;
326 if (!F->of_changes) return 1;
327 return 0;
328 }
329
330 /*****************************************************************************
331 *
332 * Discard an edit frame and reclaim memory for it.
333 *
334 *****************************************************************************/
ob_discard_frame(ObjectFrame * F)335 static void ob_discard_frame(ObjectFrame *F)
336 {
337 ObjectChange *C,*NC;
338
339 for (C = F->of_changes;C;C = NC) {
340 NC = C->oc_next;
341 if (C->oc_ptr && (C->oc_type == OC_FREE)) {
342 #if OBJECT_DEBUG
343 assert(objm.memusage.actual >= C->oc_ptr->o_size);
344 objm.memusage.actual -= C->oc_ptr->o_size;
345 #endif
346 free(C->oc_ptr);
347 }
348 }
349 delete_ObjectFrame(F);
350 }
351
352 /*****************************************************************************
353 *
354 * Pop an undo frame off the stack and return it.
355 *
356 *****************************************************************************/
ob_popUndoFrame()357 static ObjectFrame *ob_popUndoFrame()
358 {
359 ObjectFrame *of = List_popHead(&objm.om_undo);
360
361 if (of && !ObjectFrame_isInvisible(of)) objm.om_numUndo--;
362
363 return of;
364 }
365
366 /*****************************************************************************
367 *
368 * Push an undo frame on the stack.
369 *
370 *****************************************************************************/
ob_pushUndoFrame(ObjectFrame * of)371 static void ob_pushUndoFrame(ObjectFrame *of)
372 {
373 if (!ObjectFrame_isInvisible(of)) objm.om_numUndo++;
374 List_addToHead(&objm.om_undo,of);
375
376 while (objm.om_numUndo > ob_max_undo) {
377 of = List_popTail(&objm.om_undo);
378 if (!ObjectFrame_isInvisible(of)) objm.om_numUndo--;
379 ob_discard_frame(of);
380 }
381 }
382
383
384 /*****************************************************************************
385 *
386 * Add a change descriptor of the specified type for the specified object.
387 *
388 *****************************************************************************/
ObjectFrame_add_change(ObjectFrame * F,int chg_type,Object * o)389 static void ObjectFrame_add_change(ObjectFrame *F,int chg_type,Object *o)
390 {
391 ObjectChange *C = (ObjectChange*) malloc(sizeof(ObjectChange));
392
393 #if OBJECT_DEBUG
394 objm.memusage.actual += sizeof(ObjectChange);
395 #endif // OBJECT_DEBUG
396
397 C->oc_type = chg_type;
398 C->oc_ptr = o;
399 C->oc_backup = 0;
400 C->oc_next = F->of_changes;
401 F->of_changes = C;
402 F->of_num_changes++;
403
404 switch (chg_type) {
405 case OC_MALLOC :
406 break;
407 case OC_FREE :
408 break;
409 case OC_MODIFY :
410 C->oc_backup = (Object*) malloc(o->o_size);
411 #if OBJECT_DEBUG
412 objm.memusage.actual += o->o_size;
413 #endif
414 memcpy(C->oc_backup,o,o->o_size);
415 o->o_mark = objm.om_mark; /* Mark the object as saved */
416 break;
417 }
418 }
419
ob_set_mode(ob_OMMode_t m)420 void ob_set_mode(ob_OMMode_t m)
421 {
422 if (!objm.om_started) {
423 if (m == OM_START)
424 m = OM_ENABLED;
425 else
426 return;
427 }
428 objm.om_started = 1;
429
430 if (objm.om_mode != m)
431 ob_clear();
432
433 objm.om_mode = m;
434
435 #if OBJECT_DEBUG
436 printf("ob_mode(%d)\n",m);
437 #endif
438 }
439
ob_get_mode()440 ob_OMMode_t ob_get_mode()
441 {
442 return objm.om_mode;
443 }
444
445 /*****************************************************************************
446 *
447 * Clear all undo actions and do the deferred free() calls.
448 *
449 *****************************************************************************/
ob_clear_list(List * L)450 static void ob_clear_list(List *L)
451 {
452 ListElem *E;
453
454 for (E = List_first(L);E;E = List_next(L,E)) {
455 ObjectFrame *F = (ObjectFrame*)ListElem_obj(E);
456 ob_discard_frame(F);
457 }
458 List_flush(L);
459 }
460
461 /*****************************************************************************
462 *
463 * Clear all undo/redo objects on the undo stack.
464 *
465 *****************************************************************************/
ob_clear()466 void ob_clear()
467 {
468 ob_clear_list(&objm.om_undo);
469 ob_clear_list(&objm.om_redo);
470 objm.om_numUndo = 0;
471 }
472
473 /*****************************************************************************
474 *
475 * Unapply the changes in a frame, and return a new frame which
476 * is the inverse of the applied frame.
477 *
478 *****************************************************************************/
ob_apply(ObjectFrame * f)479 ObjectFrame *ob_apply(ObjectFrame *f)
480 {
481 ObjectFrame *inv_f = new_ObjectFrame(f->of_name,f->of_flags);
482 ObjectChange *oc;
483
484 #if OBJECT_DEBUG
485 printf("ob_apply(%s) -> %p\n",f->of_name, inv_f);
486 #endif
487
488 /*
489 * Inverse frames should be marked as level 0.
490 */
491 inv_f->of_level = 0;
492
493 for (oc = f->of_changes;oc;oc = oc->oc_next) {
494 switch (oc->oc_type) {
495 case OC_MALLOC :
496 ObjectFrame_add_change(inv_f,OC_FREE,oc->oc_ptr);
497 oc->oc_ptr->o_flags |= OF_DELETED; /* Undo the malloc() */
498 break;
499 case OC_FREE :
500 ObjectFrame_add_change(inv_f,OC_MALLOC,oc->oc_ptr);
501 oc->oc_ptr->o_flags &= ~OF_DELETED; /* Undo the free() */
502 break;
503 case OC_MODIFY :
504 ObjectFrame_add_change(inv_f,OC_MODIFY,oc->oc_ptr);
505 memcpy(oc->oc_ptr,oc->oc_backup,oc->oc_backup->o_size);
506 break;
507 }
508 }
509 delete_ObjectFrame(f);
510
511 return inv_f;
512 }
513
514 /*****************************************************************************
515 *
516 * Synchronize states of undo buttons
517 *
518 *****************************************************************************/
ob_sync_undo_buttons()519 void ob_sync_undo_buttons()
520 {
521 DoTcl("Menu::setFlags %s U %s R",
522 (ob_getUndoList(0,0) != 0 ? "-set" : "-clear"),
523 (ob_getRedoList(0,0) != 0 ? "-set" : "-clear"));
524 }
525
526 /*****************************************************************************
527 *
528 * Undo last 'count' operations.
529 *
530 *****************************************************************************/
ob_undo(int count)531 void ob_undo(int count)
532 {
533 undo_sync(0); /* Synchronize Tcl/Tk undo/redo list */
534 while (count-- > 0) {
535 ObjectFrame *of;
536
537 if (List_numElems(&objm.om_undo) == 0) break;
538
539 of = (ObjectFrame*) ob_popUndoFrame();
540 if (ObjectFrame_isInvisible(of)) count++; /* Do the next frame too */
541 of = ob_apply(of);
542 if (of) List_addToHead(&objm.om_redo,of);
543 }
544 undo_sync(1); /* Synchronize Tcl/Tk undo/redo list */
545 ob_sync_undo_buttons();
546 }
547
548 /*****************************************************************************
549 *
550 * Redo last 'count' operations. Before we do the redo operation, we must
551 * undo any background frames to ensure we are in the correct state to do the
552 * redo.
553 *
554 *****************************************************************************/
ob_redo(int count)555 void ob_redo(int count)
556 {
557 ObjectFrame *of;
558
559 undo_sync(0);
560
561 /*
562 * Undo any background frames on the undo stack.
563 */
564 while (List_numElems(&objm.om_undo) == 0) {
565 of = (ObjectFrame*) List_nth(&objm.om_undo,0);
566 if ((of->of_flags & FF_BACKGROUND)) {
567 #if OBJECT_DEBUG
568 printf("ob_redo: undoing background %s\n",of->of_name);
569 #endif
570 of = (ObjectFrame*) List_popHead(&objm.om_undo);
571 of = ob_apply(of);
572 ob_discard_frame(of);
573 } else
574 break;
575 }
576
577
578 while (count-- > 0) {
579 if (List_numElems(&objm.om_redo) == 0) break;
580
581 of = (ObjectFrame*) List_popHead(&objm.om_redo);
582 if (ObjectFrame_isInvisible(of)) count++; /* Do the next frame too */
583 of = ob_apply(of);
584 if (of) ob_pushUndoFrame(of);
585 }
586 undo_sync(1);
587 ob_sync_undo_buttons();
588 }
589
590 /*****************************************************************************
591 *
592 * Get the list of undo actions and place them in the array L which
593 * contains at least N elements. Returns the actual number of undo
594 * actions.
595 *
596 *****************************************************************************/
ob_getUndoList(const char ** L,int N)597 int ob_getUndoList(const char **L,int N)
598 {
599 ListElem *E;
600 int c = 0;
601
602 #if OBJECT_DEBUG
603 printf("getUndoList:\n");
604 #endif
605 for (E = List_first(&objm.om_undo);E;E = List_next(&objm.om_undo,E)) {
606 ObjectFrame *F = (ObjectFrame*)ListElem_obj(E);
607
608 #if OBJECT_DEBUG
609 printf(" %2d: %8s - level=%d changes=%d invisible=%d -\n",c,F->of_name,F->of_level,F->of_num_changes,
610 ObjectFrame_isInvisible(F));
611 #endif
612
613 if (ObjectFrame_isInvisible(F)) continue; /* Ignore this frame */
614 if (c < N) {
615 L[c] = F->of_name ? F->of_name : OBJECT_UNNAMED;
616 if (*L[c] == '+' || *L[c] == '-') L[c]++;
617 }
618 c++;
619 }
620
621 if (objm.om_numUndo != c) {
622 printf("[unexpected frame count (numUndo=%d c=%d)]\n",objm.om_numUndo,c);
623 }
624
625 return c;
626 }
627
628 /*****************************************************************************
629 *
630 * Get the list of redo actions and place them in the array L which
631 * contains at least N elements. Returns the actual number of undo
632 * actions.
633 *
634 *****************************************************************************/
ob_getRedoList(const char ** L,int N)635 int ob_getRedoList(const char **L,int N)
636 {
637 ListElem *E;
638 int c = 0;
639
640 #if OBJECT_DEBUG
641 printf("getRedoList:\n");
642 #endif
643 for (E = List_first(&objm.om_redo);E;E = List_next(&objm.om_redo,E)) {
644 ObjectFrame *F = (ObjectFrame*)ListElem_obj(E);
645
646 #if OBJECT_DEBUG
647 printf(" %2d: %8s - level=%d changes=%d invisible=%d -\n",c,F->of_name,F->of_level,F->of_num_changes,
648 ObjectFrame_isInvisible(F));
649 #endif
650
651 if (ObjectFrame_isInvisible(F)) continue; /* Ignore this frame */
652 if (c < N) {
653 L[c] = F->of_name ? F->of_name : OBJECT_UNNAMED;
654 if (*L[c] == '+' || *L[c] == '-') L[c]++;
655 }
656 c++;
657 }
658 return c;
659 }
660
ob_malloc(size_t s,const char * name)661 void *ob_malloc(size_t s,const char *name)
662 {
663 size_t oSize = s+sizeof(Object);
664 Object *o = (Object*) malloc(oSize);
665
666 #if OBJECT_DEBUG
667 objm.memusage.actual += oSize;
668 objm.memusage.ob += oSize;
669 #endif // OBJECT_DEBUG
670
671 o->o_size = oSize;
672 o->o_type = name;
673 o->o_mark = objm.om_mark; /* Objects start out marked */
674 o->o_flags = 0;
675
676 #if OBJECT_HARSHMEM
677 memset(o+1,0xa7,s);
678 #endif
679
680 #if OBJECT_SHOWMODS
681 printf("[malloc %s::%p (%d)]\n",(o->o_type?o->o_type:"?"),o,s);
682 #endif
683
684 if (objm.om_mode == OM_DISABLED || !objm.om_cur)
685 return o+1;
686
687 ObjectFrame_add_change(objm.om_cur,OC_MALLOC,o);
688
689 return o+1;
690 }
691
ob_calloc(unsigned n,size_t s,const char * name)692 void *ob_calloc(unsigned n,size_t s,const char *name)
693 {
694 void *o = ob_malloc(s*n,name);
695 memset(o,0,s*n);
696 return o;
697 }
698
ob_realloc(void * vo,size_t s)699 void *ob_realloc(void *vo,size_t s)
700 {
701 Object *o = ((Object*)vo)-1;
702 void *c = vo;
703 if (o->o_size-sizeof(Object) < s) {
704 c = ob_malloc(s,o->o_type);
705 memcpy(c,vo,o->o_size-sizeof(Object));
706 ob_free(vo);
707 }
708 #if OBJECT_DEBUG
709 else {
710 objm.memusage.actual -= o->o_size-sizeof(Object) - s;
711 }
712 #endif
713
714 return c;
715 }
716
ob_free(void * vo)717 void ob_free(void *vo)
718 {
719 Object *o = ((Object*)vo)-1;
720 int do_ack_frame = 0;
721
722 assert(!(o->o_flags & OF_DELETED));
723 if ((o->o_flags & OF_DELETED)) {
724 fprintf(stderr, "[ob_free of deleted %s object!]\n",o->o_type);
725 return;
726 }
727 #if OBJECT_DEBUG
728 assert(objm.memusage.ob >= o->o_size);
729 objm.memusage.ob -= o->o_size;
730 #endif // OBJECT_DEBUG
731
732 #if OBJECT_SHOWMODS
733 printf("[free %s::%p (%d)]\n",(o->o_type?o->o_type:"?"),o,o->o_size);
734 #endif
735
736 #if OBJECT_HARSHMEM
737 ob_touch(vo);
738 memset(vo,0xa7,o->o_size-sizeof(Object));
739 #endif
740
741 if (objm.om_mode == OM_DISABLED) {
742 #if OBJECT_DEBUG
743 assert(objm.memusage.actual >= (sizeof(Object) + o->o_size));
744 objm.memusage.actual -= sizeof(Object) + o->o_size;
745 #endif // OBJECT_DEBUG
746 free(o);
747 return;
748 }
749 if (!objm.om_cur) {
750 logError(ERL_ERROR,"object freed outside frame context");
751 ob_begin_framef("BadFree",FF_STICKY);
752 do_ack_frame = 1;
753 #if OBJECT_STOPONERROR
754 abort();
755 #endif
756 }
757
758 ObjectFrame_add_change(objm.om_cur,OC_FREE,o);
759
760 if (do_ack_frame)
761 ob_end_frame();
762 }
763
764 /*****************************************************************************
765 *
766 * Suggest an alternate name for the current frame.
767 *
768 *****************************************************************************/
ob_suggest_name(const char * name)769 void ob_suggest_name(const char *name)
770 {
771 if (objm.om_mode == OM_DISABLED) return;
772 if (!objm.om_cur) return;
773 if (!name) return;
774
775 if (objm.om_cur->of_name) {
776 int x1 =(*objm.om_cur->of_name == '+') ? 1 : (*objm.om_cur->of_name == '-' ? -1 : 0);
777 int x2 =(*name == '+') ? 1 : (*name == '-' ? -1 : 0);
778
779 if (x2 <= x1) {
780 #if OBJECT_DEBUG
781 printf("ob_suggest_name(%s) [ignored]\n",name);
782 #endif
783 return;
784 }
785
786 #if OBJECT_DEBUG
787 assert(objm.memusage.actual >= (strlen(objm.om_cur->of_name)+1));
788 objm.memusage.actual -= strlen(objm.om_cur->of_name)+1;
789 #endif // OBJECT_DEBUG
790
791 free(objm.om_cur->of_name);
792 }
793 objm.om_cur->of_name = strdup(name);
794 #if OBJECT_DEBUG
795 printf("ob_suggest_name(%s) [updated]\n",name);
796 objm.memusage.actual += strlen(name)+1;
797 #endif // OBJECT_DEBUG
798 }
799
800 /*****************************************************************************
801 *
802 * Unset the sticky flag on the top object of the undo stack.
803 *
804 *****************************************************************************/
ob_unstick()805 void ob_unstick()
806 {
807 if (objm.om_mode == OM_DISABLED) return;
808
809 if (List_first(&objm.om_undo)) {
810 ObjectFrame *undo_top = (ObjectFrame*) ListElem_obj(List_first(&objm.om_undo));
811 undo_top->of_flags &= ~FF_STICKY;
812 }
813 }
814
815 /*****************************************************************************
816 *
817 * Begin a set of undoable changes with the specified name. Options can be
818 * specified in the 'flags' parameter. If the FF_STICKY flag is present and
819 * the frame on the top of the stack is a sticky frame, then the new set of
820 * actions will be appended to the current frame rather than creating a new
821 * frame. This is useful for actions such as dragging an object to prevent
822 * each individual movement from becoming a separate frame.
823 *
824 *****************************************************************************/
ob_begin_framef(const char * name,unsigned flags)825 void ob_begin_framef(const char *name,unsigned flags)
826 {
827 ObjectFrame *undo_top = 0;
828
829 if (objm.om_mode == OM_DISABLED) return;
830
831 /*
832 * If this is not a background frame, then clear the redo stack, and clear
833 * the background flag of any other frames on the undo stack. We assume that
834 * any background frames are on the top of the stack.
835 */
836 if (!(flags & FF_BACKGROUND)) {
837 ListElem *E;
838
839 ob_clear_list(&objm.om_redo);
840
841 for (E = List_first(&objm.om_undo);E;E = List_next(&objm.om_undo,E)) {
842 ObjectFrame *F = (ObjectFrame*)ListElem_obj(E);
843 if ((F->of_flags & FF_BACKGROUND))
844 F->of_flags &= ~FF_BACKGROUND;
845 else
846 break;
847 }
848 }
849
850 if (List_first(&objm.om_undo))
851 undo_top = (ObjectFrame*) ListElem_obj(List_first(&objm.om_undo));
852
853 if ((flags & FF_STICKY) && undo_top && (undo_top->of_flags & FF_STICKY)) {
854 ob_append_frame(name);
855 return;
856 }
857
858 if (objm.om_cur) {
859 objm.om_cur->of_level++;
860 #if OBJECT_DEBUG
861 printf("(%d) ob_begin_frame(%s, 0x%x) in %s\n",objm.om_cur->of_level,name,flags,objm.om_cur->of_name);
862 #endif
863 return;
864 }
865
866 objm.om_mark++; /* Clears all marked objects */
867 objm.om_cur = new_ObjectFrame(name,flags);
868 #if OBJECT_DEBUG
869 printf("(%d) ob_begin_frame(%s, 0x%x)\n",objm.om_cur->of_level,name,flags);
870 #endif
871 }
872
873 /*****************************************************************************
874 *
875 * Append a frame to the current top-level frame. If there is no current frame
876 * then we start a new frame.
877 *
878 *****************************************************************************/
ob_append_frame(const char * name)879 void ob_append_frame(const char *name)
880 {
881 if (objm.om_mode == OM_DISABLED) return;
882
883 ob_clear_list(&objm.om_redo);
884
885 if (objm.om_cur) {
886 ob_begin_frame(name);
887 return;
888 }
889
890 objm.om_cur = (ObjectFrame*) ob_popUndoFrame();
891
892 if (!objm.om_cur) {
893 ob_begin_frame(name);
894 return;
895 }
896
897 objm.om_cur->of_level++;
898 if (name) {
899 if (objm.om_cur->of_name) {
900 #if OBJECT_DEBUG
901 assert(objm.memusage.actual >= (strlen(objm.om_cur->of_name)+1));
902 objm.memusage.actual -= strlen(objm.om_cur->of_name)+1;
903 #endif // OBJECT_DEBUG
904 free(objm.om_cur->of_name);
905 }
906 objm.om_cur->of_name = strdup(name);
907 #if OBJECT_DEBUG
908 objm.memusage.actual += strlen(name)+1;
909 #endif // OBJECT_DEBUG
910 }
911 #if OBJECT_DEBUG
912 printf("(%d) ob_append_frame(%s)\n",objm.om_cur->of_level,objm.om_cur->of_name);
913 #endif
914 }
915
916 /*****************************************************************************
917 *
918 * Begin a frame with default flag values.
919 *
920 *****************************************************************************/
ob_begin_frame(const char * name)921 void ob_begin_frame(const char *name)
922 {
923 ob_begin_framef(name,0);
924 }
925
926 /*****************************************************************************
927 *
928 * End a frame.
929 *
930 *****************************************************************************/
ob_end_frame()931 void ob_end_frame()
932 {
933 if (objm.om_mode == OM_DISABLED) return;
934
935 if (!objm.om_cur) {
936 printf("ob_error: ob_end_frame with no matching ob_begin_frame()");
937 abort();
938 }
939
940 if (!(objm.om_cur->of_flags & FF_BACKGROUND)) {
941 ob_clear_list(&objm.om_redo);
942 }
943
944 #if OBJECT_DEBUG
945 printf("(%d) ob_end_frame(%s) [%d]\n",objm.om_cur->of_level,objm.om_cur->of_name,objm.om_cur->of_num_changes);
946 #endif
947
948 if (--objm.om_cur->of_level != 0) /* End of nested frame */
949 return;
950
951 ob_pushUndoFrame(objm.om_cur);
952 objm.om_cur = 0;
953 ob_sync_undo_buttons();
954 }
955
956 /*****************************************************************************
957 *
958 * This function is called on an object before making any changes to it. If
959 * this is the first call to ob_touch in the current frame, then we make a
960 * backup copy of the object. If the object has already been touched in
961 * the current frame, we do nothing.
962 *
963 *****************************************************************************/
ob_touch(void * vo)964 void ob_touch(void *vo)
965 {
966 Object *o;
967 int do_ack_frame = 0;
968
969 if (objm.om_mode == OM_DISABLED) return;
970
971 if (!vo) return;
972
973 if (!objm.om_cur) {
974 ob_begin_framef("BadTouch",FF_STICKY);
975 do_ack_frame = 1;
976 logError(ERL_ERROR,"object modified outside frame context");
977 #if OBJECT_STOPONERROR
978 abort();
979 #endif
980 }
981
982 o = ((Object*)vo)-1;
983 if (o->o_mark == objm.om_mark)
984 return;
985
986 #if OBJECT_SHOWMODS
987 printf("[touch %s::%p (%d)]\n",(o->o_type?o->o_type:"?"),o,o->o_size);
988 #endif
989
990 if (!o->o_type) {
991 printf("[object with no type tag!]\n");
992 assert (o->o_type);
993 }
994
995 ObjectFrame_add_change(objm.om_cur,OC_MODIFY,o);
996
997 if (do_ack_frame)
998 ob_end_frame();
999 }
1000
ob_strdup(const char * s)1001 char *ob_strdup(const char *s)
1002 {
1003 int l = strlen(s);
1004 char *d = ob_malloc(l+1,"char*");
1005 strcpy(d,s);
1006 return d;
1007 }
1008