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