/*
 * Electric(tm) VLSI Design System
 *
 * File: dbchange.c
 * Database change manager
 * Written by: Steven M. Rubin, Static Free Software
 *
 * Copyright (c) 2000 Static Free Software.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Electric(tm); see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, Mass 02111-1307, USA.
 *
 * Static Free Software
 * 4119 Alpine Road
 * Portola Valley, California 94028
 * info@staticfreesoft.com
 */

#include "global.h"
#include "database.h"
#include "edialogs.h"
#include "usr.h"

/* prototypes for local routines */
CHANGE *db_allocchange(void);
void db_freechange(CHANGE*);
CHANGEFACET *db_allocchangefacet(void);
CHANGEBATCH *db_allocchangebatch(void);
void db_freechangebatch(CHANGEBATCH*);
void db_killbatch(CHANGEBATCH*);
char *db_namevariable(INTBIG, INTBIG);
CHANGEBATCH *db_preparenewbatch(void);
void db_erasebatch(CHANGEBATCH*);
void db_freechangefacet(CHANGEFACET*);
void db_loadhistorylist(short);
void db_broadcastchange(CHANGE*, INTSML, INTSML);
INTSML db_reversechange(CHANGE*);
char *db_describechange(INTSML, INTBIG, INTBIG, INTBIG, INTBIG, INTBIG, INTBIG, INTBIG);

/* Undo dialog */
DIALOGITEM db_undodialogitems[] =
{
 /*  1 */ {0, {468,608,492,680}, BUTTON, N_("OK")},
 /*  2 */ {0, {468,16,492,88}, BUTTON, N_("Undo")},
 /*  3 */ {0, {32,8,455,690}, SCROLL, ""},
 /*  4 */ {0, {8,8,24,241}, MESSAGE, N_("These are the recent changes:")},
 /*  5 */ {0, {8,344,24,456}, CHECK, N_("Show details")},
 /*  6 */ {0, {468,108,492,180}, BUTTON, N_("Redo")}
};
DIALOG db_undodialog = {{50,75,555,774}, N_("Change History"), 0, 6, db_undodialogitems};

INTBIG       db_batchtally;					/* number of batches in list */
INTBIG       db_maximumbatches;				/* limit of number of batches */
INTSML       db_broadcasting = 0;			/* nonzero if broadcasting */
INTBIG       db_batchnumber;				/* batch number counter */
CHANGEBATCH *db_currentbatch;				/* current batch of changes */
CHANGEBATCH *db_headbatch;					/* most recent change in the list */
CHANGEBATCH *db_tailbatch;					/* oldest change in the list */
CHANGEBATCH *db_donebatch;					/* last done batch in the list */
CHANGEBATCH *db_changebatchfree;			/* head of free change batches */
CHANGE      *db_changefree;					/* head of free change modules */
CHANGEFACET *db_changefacetfree;			/* head of free change facet modules */
AIDENTRY    *db_currenttool;				/* current tool being given a slice */

/*
 * Routine to free all memory associated with this module.
 */
void db_freechangememory(void)
{
	REGISTER CHANGE *c;
	REGISTER CHANGEFACET *cf;
	REGISTER CHANGEBATCH *b;

	while (db_changefree != NOCHANGE)
	{
		c = db_changefree;
		db_changefree = db_changefree->nextchange;
		efree((char *)c);
	}
	while (db_changefacetfree != NOCHANGEFACET)
	{
		cf = db_changefacetfree;
		db_changefacetfree = db_changefacetfree->nextchangefacet;
		efree((char *)cf);
	}
	while (db_changebatchfree != NOCHANGEBATCH)
	{
		b = db_changebatchfree;
		db_changebatchfree = db_changebatchfree->nextchangebatch;
		efree((char *)b);
	}
}

/************************* CHANGE CONTROL *************************/

/*
 * Routine to initialize the change control system.
 */
void db_initializechanges(void)
{
	/* initialize change lists */
	db_changefree = NOCHANGE;
	db_changefacetfree = NOCHANGEFACET;
	db_changebatchfree = NOCHANGEBATCH;

	/* initialize changes */
	db_batchtally = 0;
	db_batchnumber = 1;
	db_maximumbatches = maxi(el_maxaid + el_maxaid/2 + 1, 20);
	db_currentbatch = db_headbatch = db_tailbatch = db_donebatch = NOCHANGEBATCH;
}

/*
 * Routine to record and broadcast a change.  The change is to object "obj" and the type of
 * change is in "change".  The arguments to the change depend on the type, and are in "a1"
 * through "a6".  Returns NOCHANGE upon error
 */
CHANGE *db_change(INTBIG obj, INTSML change, INTBIG a1, INTBIG a2, INTBIG a3, INTBIG a4,
	INTBIG a5, INTBIG a6)
{
	REGISTER INTSML i, firstchange, major;
	REGISTER PORTPROTO *pp;
	REGISTER NODEPROTO *np;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	REGISTER CHANGE *c;
	REGISTER LIBRARY *lib;
	REGISTER VARIABLE *var;
	static char *broadcasttype[] = {N_("nodeinstnew"), N_("nodeinstkill"), N_("nodeinstmod"),
		N_("arcinstnew"), N_("arcinstkill"), N_("arcinstmod"), N_("portprotonew"),
		N_("portprotokill"), N_("portprotomod"), N_("nodeprotonew"), N_("nodeprotokill"),
		N_("nodeprotomod"), N_("objectstart"), N_("objectend"), N_("objectnew"), N_("objectkill"),
		N_("variablenew"), N_("variablekill"), N_("variablemod"), N_("variableinsert"),
		N_("variabledelete"), N_("descriptmod")};

	if (db_broadcasting != 0)
	{
		ttyputerr(_("Recieved this change during broadcast of type %s:"),
			_(broadcasttype[db_broadcasting-1]));
		ttyputmsg("%s", db_describechange(change, obj, a1, a2, a3, a4, a5, a6));
	}

	/* determine what needs to be updated */
	np = NONODEPROTO;
	lib = NOLIBRARY;
	major = 0;
	switch (change)
	{
		case NODEINSTNEW:
		case NODEINSTKILL:
		case NODEINSTMOD:
			ni = (NODEINST *)obj;
			np = ni->parent;
			lib = np->cell->lib;
			major = 1;
			break;
		case ARCINSTNEW:
		case ARCINSTKILL:
		case ARCINSTMOD:
			ai = (ARCINST *)obj;
			np = ai->parent;
			lib = np->cell->lib;
			major = 1;
			break;
		case PORTPROTONEW:
		case PORTPROTOKILL:
		case PORTPROTOMOD:
			pp = (PORTPROTO *)obj;
			np = pp->parent;
			lib = np->cell->lib;
			major = 1;
			break;
		case NODEPROTONEW:
			np = (NODEPROTO *)obj;
			lib = np->cell->lib;
			major = 1;
			break;
		case NODEPROTOKILL:
			major = 1;
		case NODEPROTOMOD:
			lib = ((NODEPROTO *)obj)->cell->lib;
			break;
		case OBJECTNEW:
		case OBJECTKILL:
			np = db_whichnodeproto(obj, a1);
			lib = db_whichlibrary(obj, a1);
			break;
		case VARIABLENEW:
		case VARIABLEMOD:
		case VARIABLEINS:
		case VARIABLEDEL:
			if ((a3&VDONTSAVE) != 0) { lib = NOLIBRARY;   np = NONODEPROTO; } else
			{
				np = db_whichnodeproto(obj, a1);
				lib = db_whichlibrary(obj, a1);
			}
			break;
		case VARIABLEKILL:
			if ((a4&VDONTSAVE) != 0) { lib = NOLIBRARY;   np = NONODEPROTO; } else
			{
				np = db_whichnodeproto(obj, a1);
				lib = db_whichlibrary(obj, a1);
			}
			break;
		case DESCRIPTMOD:
			var = (VARIABLE *)a2;
			if ((var->type&VDONTSAVE) != 0) { lib = NOLIBRARY;   np = NONODEPROTO; } else
			{
				np = db_whichnodeproto(obj, a1);
				lib = db_whichlibrary(obj, a1);
			}
			break;
	}

	/* set "changed" and "dirty" bits */
	if (np != NONODEPROTO)
	{
		if (major != 0) np->revisiondate = getcurrenttime();
		np->adirty = 0;
		for(i=0; i<el_maxaid; i++)
			if ((el_aids[i].aidstate & AIDON) == 0) np->adirty |= 1 << i;
	}
	if (lib != NOLIBRARY)
	{
		if (major != 0) lib->userbits |= LIBCHANGEDMAJOR; else
			lib->userbits |= LIBCHANGEDMINOR;
	}

	/* get change module */
	c = db_allocchange();
	if (c == NOCHANGE)
	{
		ttyputnomemory();
		return(NOCHANGE);
	}

	/* insert new change module into linked list */
	firstchange = 0;
	if (db_currentbatch == NOCHANGEBATCH)
	{
		db_currentbatch = db_preparenewbatch();
		if (db_currentbatch == NOCHANGEBATCH)
		{
			ttyputnomemory();
			return(NOCHANGE);
		}
		firstchange = 1;
		db_currentbatch->changehead = c;
		c->prevchange = NOCHANGE;
	} else
	{
		c->prevchange = db_currentbatch->changetail;
		db_currentbatch->changetail->nextchange = c;
	}
	db_currentbatch->changetail = c;
	c->nextchange = NOCHANGE;

	/* save the change information */
	c->entryaddr = obj;         c->changetype = change;
	c->p1 = a1;   c->p2 = a2;   c->p3 = a3;
	c->p4 = a4;   c->p5 = a5;   c->p6 = a6;

	/* broadcast the change */
	db_broadcastchange(c, firstchange, 0);
	return(c);
}

/*
 * Routine to broadcast change "c" to all tools that are on.  If "firstchange" is nonzero,
 * this is the first change of a batch, and a "startbatch" change will also be broadcast.
 * "undoredo" is nonzero if this is an undo/redo batch.
 */
void db_broadcastchange(CHANGE *c, INTSML firstchange, INTSML undoredo)
{
	REGISTER INTSML i, oldbroadcasting;

	/* start the batch if this is the first change */
	oldbroadcasting = db_broadcasting;
	db_broadcasting = c->changetype+1;
	if (firstchange != 0)
	{
		/* broadcast a start-batch on the first change */
		for(i=0; i<el_maxaid; i++)
			if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].startbatch != 0)
				(*el_aids[i].startbatch)(db_currentbatch->aid, undoredo);
	}
	switch (c->changetype)
	{
		case NODEINSTNEW:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].newobject != 0)
					(*el_aids[i].newobject)(c->entryaddr, VNODEINST);
			break;
		case NODEINSTKILL:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].killobject != 0)
					(*el_aids[i].killobject)(c->entryaddr, VNODEINST);
			break;
		case NODEINSTMOD:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].modifynodeinst != 0)
					(*el_aids[i].modifynodeinst)((NODEINST *)c->entryaddr, c->p1, c->p2,
						c->p3, c->p4, (INTSML)c->p5, (INTSML)c->p6);
			break;
		case ARCINSTNEW:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].newobject != 0)
					(*el_aids[i].newobject)(c->entryaddr, VARCINST);
			break;
		case ARCINSTKILL:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].killobject != 0)
					(*el_aids[i].killobject)(c->entryaddr, VARCINST);
			break;
		case ARCINSTMOD:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].modifyarcinst != 0)
					(*el_aids[i].modifyarcinst)((ARCINST *)c->entryaddr, c->p1, c->p2,
						c->p3, c->p4, c->p5, c->p6);
			break;
		case PORTPROTONEW:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].newobject != 0)
					(*el_aids[i].newobject)(c->entryaddr, VPORTPROTO);
			break;
		case PORTPROTOKILL:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].killobject != 0)
					(*el_aids[i].killobject)(c->entryaddr, VPORTPROTO);
			break;
		case PORTPROTOMOD:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].modifyportproto != 0)
					(*el_aids[i].modifyportproto)((PORTPROTO *)c->entryaddr, (NODEINST *)c->p1,
						(PORTPROTO *)c->p2);
			break;
		case NODEPROTONEW:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].newobject != 0)
					(*el_aids[i].newobject)(c->entryaddr, VNODEPROTO);
			break;
		case NODEPROTOKILL:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].killobject != 0)
					(*el_aids[i].killobject)(c->entryaddr, VNODEPROTO);
			break;
		case NODEPROTOMOD:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].modifynodeproto != 0)
					(*el_aids[i].modifynodeproto)((NODEPROTO *)c->entryaddr);
			break;
		case OBJECTSTART:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].startobjectchange != 0)
					(*el_aids[i].startobjectchange)(c->entryaddr, c->p1);
			break;
		case OBJECTEND:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].endobjectchange != 0)
					(*el_aids[i].endobjectchange)(c->entryaddr, c->p1);
			break;
		case OBJECTNEW:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].newobject != 0)
					(*el_aids[i].newobject)(c->entryaddr, c->p1);
			break;
		case OBJECTKILL:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].killobject != 0)
					(*el_aids[i].killobject)(c->entryaddr, c->p1);
			break;
		case VARIABLENEW:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].newvariable != 0)
					(*el_aids[i].newvariable)(c->entryaddr, c->p1, c->p2, c->p3);
			break;
		case VARIABLEKILL:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].killvariable != 0)
					(*el_aids[i].killvariable)(c->entryaddr, c->p1, c->p2, c->p3, c->p4, c->p5);
			break;
		case VARIABLEMOD:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].modifyvariable != 0)
					(*el_aids[i].modifyvariable)(c->entryaddr, c->p1, c->p2, c->p3, c->p4, c->p5);
			break;
		case VARIABLEINS:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].insertvariable != 0)
					(*el_aids[i].insertvariable)(c->entryaddr, c->p1, c->p2, c->p3, c->p4);
			break;
		case VARIABLEDEL:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].deletevariable != 0)
					(*el_aids[i].deletevariable)(c->entryaddr, c->p1, c->p2, c->p3, c->p4, c->p5);
			break;
		case DESCRIPTMOD:
			for(i=0; i<el_maxaid; i++)
				if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].modifydescript != 0)
					(*el_aids[i].modifydescript)(c->entryaddr, c->p1, (VARIABLE *)c->p2, c->p3);
			break;
	}
	db_broadcasting = oldbroadcasting;
}

/*
 * Routine to terminate a batch of changes by broadcasting the endbatch
 * and clearing the "change" words.
 */
void db_endbatch(void)
{
	REGISTER INTSML i;

	/* if no changes were recorded, stop */
	if (db_currentbatch == NOCHANGEBATCH) return;

	/* changes made: apply final constraints to this batch of changes */
	(*el_curconstraint->solve)(NONODEPROTO);

	for(i=0; i<el_maxaid; i++)
		if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].endbatch != 0)
			(*el_aids[i].endbatch)();

	db_currentbatch = NOCHANGEBATCH;
}

/************************* BATCH CONTROL *************************/

/*
 * Routine to allocate and initialize a new batch of changes.
 * Returns NOCHANGEBATCH on error.
 */
CHANGEBATCH *db_preparenewbatch(void)
{
	REGISTER CHANGEBATCH *killbatch, *thisbatch;

	/* first kill off any undone batches */
	noredoallowed();

	/* allocate a new change batch */
	thisbatch = db_allocchangebatch();
	if (thisbatch == NOCHANGEBATCH) return(NOCHANGEBATCH);
	thisbatch->changehead = NOCHANGE;
	thisbatch->batchnumber = db_batchnumber++;
	thisbatch->done = 1;
	thisbatch->nextchangebatch = NOCHANGEBATCH;

	/* put at head of list */
	if (db_headbatch == NOCHANGEBATCH)
	{
		thisbatch->lastchangebatch = NOCHANGEBATCH;
		db_headbatch = db_tailbatch = thisbatch;
	} else
	{
		thisbatch->lastchangebatch = db_headbatch;
		db_headbatch->nextchangebatch = thisbatch;
		db_headbatch = thisbatch;
	}
	db_donebatch = thisbatch;

	/* kill last batch if list is full */
	db_batchtally++;
	if (db_batchtally > db_maximumbatches)
	{
		killbatch = db_tailbatch;
		db_tailbatch = db_tailbatch->nextchangebatch;
		db_tailbatch->lastchangebatch = NOCHANGEBATCH;
		db_killbatch(killbatch);
	}

	/* miscellaneous initialization */
	thisbatch->firstchangefacet = NOCHANGEFACET;
	thisbatch->aid = db_currenttool;
	thisbatch->activity = 0;
	return(thisbatch);
}

/*
 * Routine to completely delete change batch "batch".
 */
void db_killbatch(CHANGEBATCH *batch)
{
	db_erasebatch(batch);
	db_batchtally--;
	db_freechangebatch(batch);
}

/*
 * routine to erase the contents of change batch "batch"
 */
void db_erasebatch(CHANGEBATCH *batch)
{
	REGISTER CHANGE *c, *nextc;
	REGISTER CHANGEFACET *cc, *nextcc;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	REGISTER NODEPROTO *np;
	REGISTER PORTPROTO *pp;
	REGISTER CELL *cell;
	REGISTER WINDOWPART *w;
	REGISTER EDITOR *e;

	/* erase the change information */
	for(c = batch->changehead; c != NOCHANGE; c = nextc)
	{
		nextc = c->nextchange;
		if (c->changetype == NODEINSTKILL)
		{
			/* now free the nodeinst */
			ni = (NODEINST *)c->entryaddr;
			freegeom(ni->geom);
			freenodeinst(ni);
		} else if (c->changetype == ARCINSTKILL)
		{
			ai = (ARCINST *)c->entryaddr;
			freeportarcinst(ai->end[0].portarcinst);
			freeportarcinst(ai->end[1].portarcinst);
			freegeom(ai->geom);
			freearcinst(ai);
		} else if (c->changetype == PORTPROTOKILL)
		{
			pp = (PORTPROTO *)c->entryaddr;
			efree(pp->protoname);
			freeportproto(pp);
		} else if (c->changetype == NODEPROTOKILL)
		{
			np = (NODEPROTO *)c->entryaddr;
			freenodeproto(np);
		} else if (c->changetype == VARIABLEKILL)
		{
			db_freevar(c->p3, c->p4);
		} else if (c->changetype == OBJECTKILL)
		{
			switch (c->p1&VTYPE)
			{
				case VVIEW:   freeview((VIEW *)c->entryaddr);      break;
				case VWINDOWPART:
					w = (WINDOWPART *)c->entryaddr;
					if ((w->state&WINDOWTYPE) == TEXTWINDOW || (w->state&WINDOWTYPE) == POPTEXTWINDOW)
					{
						e = w->editor;
						if ((w->state&WINDOWTYPE) == POPTEXTWINDOW)
							(void)screenrestorebox(e->savedbox, -1);
						us_freeeditor(e);
					}
					freewindowpart(w);
					break;
				case VCELL:
					cell = (CELL *)c->entryaddr;
					efree(cell->cellname);
					freecell(cell);
					break;
			}
		} else if (c->changetype == VARIABLEMOD || c->changetype == VARIABLEDEL)
		{
			/* free the array entry if it is allocated */
			if ((c->p3&(VCODE1|VCODE2)) != 0 || (c->p3&VTYPE) == VSTRING)
				efree((char *)c->p5);
		}
		db_freechange(c);
	}
	batch->changehead = batch->changetail = NOCHANGE;

	/* erase the change facet information */
	for(cc = batch->firstchangefacet; cc != NOCHANGEFACET; cc = nextcc)
	{
		nextcc = cc->nextchangefacet;
		db_freechangefacet(cc);
	}
	batch->firstchangefacet = NOCHANGEFACET;

	/* erase the activity information */
	if (batch->activity != 0) efree(batch->activity);
	batch->activity = 0;
}

/*
 * Routine to prevent undo by deleting all batches.
 */
void noundoallowed(void)
{
	REGISTER CHANGEBATCH *batch, *prevbatch;

	/* properly terminate the current batch */
	db_endbatch();

	/* kill them all */
	for(batch = db_headbatch; batch != NOCHANGEBATCH; batch = prevbatch)
	{
		prevbatch = batch->lastchangebatch;
		db_killbatch(batch);
	}

	/* clear pointers */
	db_currentbatch = NOCHANGEBATCH;
	db_headbatch = NOCHANGEBATCH;
	db_tailbatch = NOCHANGEBATCH;
	db_donebatch = NOCHANGEBATCH;
}

/*
 * Routine to prevent redo by deleting all undone batches.
 */
void noredoallowed(void)
{
	REGISTER CHANGEBATCH *killbatch;

	while (db_headbatch != NOCHANGEBATCH && db_headbatch != db_donebatch)
	{
		killbatch = db_headbatch;
		db_headbatch = db_headbatch->lastchangebatch;
		if (db_headbatch != NOCHANGEBATCH) db_headbatch->nextchangebatch = NOCHANGEBATCH;
		db_killbatch(killbatch);
	}
}

/*
 * routine to set the size of the history list to "newsize".  The size of
 * the list is returned.  If "newsize" is not positive, the size is not
 * changed, just returned.
 */
INTBIG historylistsize(INTBIG newsize)
{
	REGISTER CHANGEBATCH *batch;
	REGISTER INTBIG oldsize;

	if (newsize <= 0) return((INTSML)db_maximumbatches);

	oldsize = db_maximumbatches;
	db_maximumbatches = newsize;
	while (db_batchtally > db_maximumbatches)
	{
		batch = db_tailbatch;
		db_tailbatch = db_tailbatch->nextchangebatch;
		db_tailbatch->lastchangebatch = NOCHANGEBATCH;
		db_killbatch(batch);
	}
	return(oldsize);
}

/*
 * routine to set the tool for the current (or next) batch of changes
 */
void db_setcurrenttool(AIDENTRY *aid)
{
	db_currenttool = aid;
}

/*
 * routine to get the current batch of changes
 */
CHANGEBATCH *db_getcurrentbatch(void)
{
	return(db_currentbatch);
}

/*
 * routine to get the identifying number for the current batch of changes
 */
INTBIG getcurrentbatchnumber(void)
{
	if (db_currentbatch == NOCHANGEBATCH) return(-1);
	return(db_currentbatch->batchnumber);
}

/*
 * routine to set the activity message for the current batch of changes
 */
void setactivity(char *message)
{
	if (db_currentbatch == NOCHANGEBATCH) return;		/* should save for later !!! */
	if (db_currentbatch->activity != 0) efree(db_currentbatch->activity);
	(void)allocstring(&db_currentbatch->activity, message, db_cluster);
}

/************************* UNDO/REDO *************************/

/*
 * Routine to return the nature of the next possible undo/redo batch.
 * If "undo" is nonzero, examine the next undo batch, otherwise the next redo batch.
 * Returns zero if there is no batch available.
 * Returns positive if there is a batch with major changes in it.
 * Returns negative if there is a batch with minor changes in it.
 */
INTSML undonature(INTSML undo)
{
	REGISTER CHANGEBATCH *batch;
	REGISTER CHANGE *c;
	REGISTER INTSML retval;

	if (undo != 0)
	{
		/* examine the next batch to be undone */
		if (db_donebatch == NOCHANGEBATCH) return(0);
		batch = db_donebatch;
	} else
	{
		/* examine the next batch to be redone */
		if (db_donebatch == NOCHANGEBATCH) return(0);
		batch = db_donebatch->nextchangebatch;
		if (batch == NOCHANGEBATCH) return(0);
	}

	/* determine the nature of the batch */
	retval = -1;
	for(c = batch->changetail; c != NOCHANGE; c = c->prevchange)
	{
		switch (c->changetype)
		{
			case NODEINSTNEW:
			case NODEINSTKILL:
			case NODEINSTMOD:
			case ARCINSTNEW:
			case ARCINSTKILL:
			case ARCINSTMOD:
			case PORTPROTONEW:
			case PORTPROTOKILL:
			case PORTPROTOMOD:
			case NODEPROTONEW:
			case NODEPROTOKILL:
			case NODEPROTOMOD:
			case DESCRIPTMOD:
				retval = 1;
				break;
			case VARIABLENEW:
				if ((c->p3&VCREF) != 0) retval = 1;
				break;
		}
	}
	return(retval);
}

/*
 * routine to undo backward through the list of change batches.  If there is no batch to
 * undo, the routine returns zero.  If there is a batch, it is undone and the
 * routine returns the batch number (a positive value) if the batch involved changes to
 * nodes/arcs/ports/facets or the negative batch number if the batch only involves minor
 * changes (variables, etc.).  The aid that produced the batch is returned in "aid".
 */
INTSML undoabatch(AIDENTRY **aid)
{
	REGISTER CHANGEBATCH *batch, *savebatch;
	REGISTER CHANGE *c;
	REGISTER INTSML retval, firstchange, i;

	/* get the most recent batch of changes */
	if (db_donebatch == NOCHANGEBATCH) return(0);
	batch = db_donebatch;
	db_donebatch = db_donebatch->lastchangebatch;
	*aid = batch->aid;

	/* look through the changes in this batch */
	firstchange = 1;
	retval = (INTSML)(-batch->batchnumber);
	savebatch = db_currentbatch;
	for(c = batch->changetail; c != NOCHANGE; c = c->prevchange)
	{
		/* reverse the change */
		if (db_reversechange(c) != 0)
			retval = (INTSML)batch->batchnumber;

		/* now broadcast this change */
		db_currentbatch = batch;
		db_broadcastchange(c, firstchange, 1);
		firstchange = 0;
	}

	/* broadcast the end-batch */
	for(i=0; i<el_maxaid; i++)
		if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].endbatch != 0)
			(*el_aids[i].endbatch)();
	db_currentbatch = savebatch;

	/* mark that this batch is undone */
	batch->done = 0;
	return(retval);
}

/*
 * routine to redo forward through the list of change batches.  If there is no batch to
 * redo, the routine returns zero.  If there is a batch, it is redone and the
 * routine returns the batch number (a positive value) if the batch involved changes to
 * nodes/arcs/ports/facets or the negative batch number if the batch only involves minor
 * changes (variables, etc.).  The aid that produced the batch is returned in "aid".
 */
INTSML redoabatch(AIDENTRY **aid)
{
	REGISTER CHANGEBATCH *batch, *savebatch;
	REGISTER CHANGE *c;
	REGISTER INTSML retval, firstchange, i;

	/* get the most recent batch of changes */
	if (db_donebatch == NOCHANGEBATCH)
	{
		if (db_tailbatch == NOCHANGEBATCH) return(0);
		batch = db_tailbatch;
	} else
	{
		batch = db_donebatch->nextchangebatch;
		if (batch == NOCHANGEBATCH) return(0);
	}
	db_donebatch = batch;
	*aid = batch->aid;

	/* look through the changes in this batch */
	firstchange = 1;
	retval = (INTSML)(-batch->batchnumber);
	savebatch = db_currentbatch;
	for(c = batch->changehead; c != NOCHANGE; c = c->nextchange)
	{
		/* reverse the change */
		if (db_reversechange(c) != 0)
			retval = (INTSML)batch->batchnumber;

		/* now broadcast this change */
		db_currentbatch = batch;
		db_broadcastchange(c, firstchange, 1);
		firstchange = 0;
	}

	/* broadcast the end-batch */
	for(i=0; i<el_maxaid; i++)
		if ((el_aids[i].aidstate & AIDON) != 0 && el_aids[i].endbatch != 0)
			(*el_aids[i].endbatch)();
	db_currentbatch = savebatch;

	/* mark that this batch is redone */
	batch->done = 1;
	return(retval);
}

/*
 * Routine to undo the effects of change "c".
 * Returns nonzero if the change is important.
 */
INTSML db_reversechange(CHANGE *c)
{
	REGISTER PORTPROTO *pp, *oldsubpp;
	REGISTER NODEPROTO *np;
	REGISTER NODEINST *ni, *oldsubni;
	REGISTER ARCINST *ai;
	REGISTER VARIABLE *var;
	REGISTER VIEW *v, *lastv, *view;
	REGISTER WINDOWPART *w;
	REGISTER char *attname;
	INTBIG oldval;

	switch (c->changetype)
	{
		case NODEINSTNEW:
			ni = (NODEINST *)c->entryaddr;
			db_retractnodeinst(ni);
			c->changetype = NODEINSTKILL;
			return(1);
		case NODEINSTKILL:
			ni = (NODEINST *)c->entryaddr;
			db_enternodeinst(ni);
			c->changetype = NODEINSTNEW;
			return(1);
		case NODEINSTMOD:
			ni = (NODEINST *)c->entryaddr;
			oldval = ni->lowx;       ni->lowx = c->p1;       c->p1 = oldval;
			oldval = ni->lowy;       ni->lowy = c->p2;       c->p2 = oldval;
			oldval = ni->highx;      ni->highx = c->p3;      c->p3 = oldval;
			oldval = ni->highy;      ni->highy = c->p4;      c->p4 = oldval;
			oldval = ni->rotation;   ni->rotation = (INTSML)c->p5;   c->p5 = oldval;
			oldval = ni->transpose;  ni->transpose = (INTSML)c->p6;  c->p6 = oldval;
			/* don't need to call "updategeom()" because it is done by "OBJECTSTART" */
			return(1);
		case ARCINSTNEW:
			ai = (ARCINST *)c->entryaddr;
			db_retractarcinst(ai);
			c->changetype = ARCINSTKILL;
			return(1);
		case ARCINSTKILL:
			ai = (ARCINST *)c->entryaddr;
			db_addportarcinst(ai->end[0].nodeinst, ai->end[0].portarcinst);
			db_addportarcinst(ai->end[1].nodeinst, ai->end[1].portarcinst);
			db_enterarcinst(ai);
			c->changetype = ARCINSTNEW;
			return(1);
		case ARCINSTMOD:
			ai = (ARCINST *)c->entryaddr;
			oldval = ai->end[0].xpos;  ai->end[0].xpos = c->p1;   c->p1 = oldval;
			oldval = ai->end[0].ypos;  ai->end[0].ypos = c->p2;   c->p2 = oldval;
			oldval = ai->end[1].xpos;  ai->end[1].xpos = c->p3;   c->p3 = oldval;
			oldval = ai->end[1].ypos;  ai->end[1].ypos = c->p4;   c->p4 = oldval;
			oldval = ai->width;        ai->width = c->p5;         c->p5 = oldval;
			oldval = ai->length;       ai->length = c->p6;        c->p6 = oldval;
			determineangle(ai);
			(void)setshrinkvalue(ai, 1);
			/* don't need to call "updategeom()" because it is done by "OBJECTSTART" */
			return(1);
		case PORTPROTONEW:
			pp = (PORTPROTO *)c->entryaddr;
			db_retractportproto(pp);
			c->changetype = PORTPROTOKILL;
			return(1);
		case PORTPROTOKILL:
			pp = (PORTPROTO *)c->entryaddr;
			db_enterportproto(pp);
			c->changetype = PORTPROTONEW;
			return(1);
		case PORTPROTOMOD:
			pp = (PORTPROTO *)c->entryaddr;
			oldsubni = pp->subnodeinst;
			oldsubpp = pp->subportproto;
			db_changeport(pp, (NODEINST *)c->p1, (PORTPROTO *)c->p2);
			c->p1 = (INTBIG)oldsubni;   c->p2 = (INTBIG)oldsubpp;
			return(1);
		case NODEPROTONEW:
			np = (NODEPROTO *)c->entryaddr;
			db_retractnodeproto(np);
			c->changetype = NODEPROTOKILL;
			return(1);
		case NODEPROTOKILL:
			np = (NODEPROTO *)c->entryaddr;
			db_insertnodeproto(np);
			c->changetype = NODEPROTONEW;
			return(1);
		case NODEPROTOMOD:
			np = (NODEPROTO *)c->entryaddr;
			oldval = np->lowx;    np->lowx = c->p1;    c->p1 = oldval;
			oldval = np->highx;   np->highx = c->p2;   c->p2 = oldval;
			oldval = np->lowy;    np->lowy = c->p3;    c->p3 = oldval;
			oldval = np->highy;   np->highy = c->p4;   c->p4 = oldval;
			return(1);
		case OBJECTSTART:
			c->changetype = OBJECTEND;
			if (c->p1 == VNODEINST)
			{
				ni = (NODEINST *)c->entryaddr;
				updategeom(ni->geom, ni->parent);
			} else if (c->p1 == VARCINST)
			{
				ai = (ARCINST *)c->entryaddr;
				updategeom(ai->geom, ai->parent);
			}
			break;
		case OBJECTEND:
			c->changetype = OBJECTSTART;
			break;
		case OBJECTNEW:
			/* args: addr, type */
			switch (c->p1&VTYPE)
			{
				case VVIEW:
					/* find the view */
					view = (VIEW *)c->entryaddr;
					lastv = NOVIEW;
					for(v = el_views; v != NOVIEW; v = v->nextview)
					{
						if (v == view) break;
						lastv = v;
					}
					if (v != NOVIEW)
					{
						/* delete the view */
						if (lastv == NOVIEW) el_views = v->nextview; else
							lastv->nextview = v->nextview;
					}
					break;
				case VWINDOWPART:
					w = (WINDOWPART *)c->entryaddr;
					db_retractwindowpart(w);
					break;
			}
			c->changetype = OBJECTKILL;
			break;
		case OBJECTKILL:
			/* args: addr, type */
			switch (c->p1&VTYPE)
			{
				case VVIEW:
					view = (VIEW *)c->entryaddr;
					view->nextview = el_views;
					el_views = view;
					break;
				case VWINDOWPART:
					w = (WINDOWPART *)c->entryaddr;
					(void)db_enterwindowpart(w);
					break;
			}
			c->changetype = OBJECTNEW;
			break;
		case VARIABLENEW:
			/* args: addr, type, key, newtype */
			if ((c->p3&VCREF) != 0)
			{
				/* change to fixed attribute */
				attname = changedvariablename(c->p1, c->p2, c->p3);
				var = getval(c->entryaddr, c->p1, c->p3 & (VTYPE|VISARRAY), attname);
				if (var == NOVARIABLE)
				{
					ttyputmsg(_("Warning: Could not find attribute %s on object %s"),
						attname, db_namevariable(c->entryaddr, c->p1));
					break;
				}
				c->p3 = var->addr;
				c->p4 = var->type;
				c->p5 = var->textdescript;
				c->changetype = VARIABLEKILL;
				if (c->p1 == VNODEINST || c->p1 == VARCINST) return(1);
				if (c->p1 == VPORTPROTO)
				{
					if (strcmp(attname, "textdescript") == 0 ||
						strcmp(attname, "userbits") == 0 ||
						strcmp(attname, "protoname") == 0) return(1);
				}
				break;
			}

			/* change to variable attribute: get current value */
			var = getvalkey(c->entryaddr, c->p1, c->p3&(VTYPE|VISARRAY), c->p2);
			if (var == NOVARIABLE)
			{
				ttyputmsg(_("Warning: Could not find attribute %s on object %s"),
					makename(c->p2), db_namevariable(c->entryaddr, c->p1));
				break;
			}
			c->p3 = var->addr;
			c->p4 = var->type;
			c->p5 = var->textdescript;
			c->changetype = VARIABLEKILL;
			var->type = VINTEGER;		/* fake it out so no memory is deallocated */
			nextvarchangequiet();
			(void)delvalkey(c->entryaddr, c->p1, c->p2);
			if (c->p1 == VNODEINST || c->p1 == VARCINST) return(1);
			break;
		case VARIABLEKILL:
			/* args: addr, type, key, oldaddr, oldtype, olddescript */
			nextvarchangequiet();
			if ((c->p4&VCREF) != 0)
			{
				attname = changedvariablename(c->p1, c->p2, c->p4);
				var = setval(c->entryaddr, c->p1, attname, c->p3, c->p4);
				if (var == NOVARIABLE)
				{
					ttyputmsg(_("Warning: Could not set attribute %s on object %s"),
						attname, db_namevariable(c->entryaddr, c->p1));
					break;
				}
			} else
			{
				var = setvalkey(c->entryaddr, c->p1, c->p2, c->p3, c->p4);
				if (var == NOVARIABLE)
				{
					ttyputmsg(_("Warning: Could not set attribute %s on object %s"),
						makename(c->p2), db_namevariable(c->entryaddr, c->p1));
					break;
				}
				var->textdescript = c->p5;
			}
			db_freevar(c->p3, c->p4);
			c->p3 = c->p4;
			c->changetype = VARIABLENEW;
			if (c->p1 == VNODEINST || c->p1 == VARCINST) return(1);
			break;
		case VARIABLEMOD:
			/* args: addr, type, key, vartype, aindex, oldvalue */
			nextvarchangequiet();
			if ((c->p3&VCREF) != 0)
			{
				attname = changedvariablename(c->p1, c->p2, c->p4);
				if (getind(c->entryaddr, c->p1, attname, c->p4, &oldval) != 0)
				{
					ttyputmsg(_("Warning: Could not find index %ld of attribute %s on object %s"),
						c->p4, attname, db_namevariable(c->entryaddr, c->p1));
					break;
				}
				(void)setind(c->entryaddr, c->p1, attname, c->p4, c->p5);
				c->p5 = oldval;
			} else
			{
				if (getindkey(c->entryaddr, c->p1, c->p2, c->p4, &oldval) != 0)
				{
					ttyputmsg(_("Warning: Could not find index %ld of attribute %s on object %s"),
						c->p4, makename(c->p2), db_namevariable(c->entryaddr, c->p1));
					break;
				}
				(void)setindkey(c->entryaddr, c->p1, c->p2, c->p4, c->p5);
				c->p5 = oldval;
			}
			if (c->p1 == VNODEINST || c->p1 == VARCINST) return(1);
			break;
		case VARIABLEINS:
			/* args: addr, type, key, vartype, aindex */
			if (getindkey(c->entryaddr, c->p1, c->p2, c->p4, &oldval) != 0)
			{
				ttyputmsg(_("Warning: Could not find index %ld of attribute %s on object %s"),
					c->p4, makename(c->p2), db_namevariable(c->entryaddr, c->p1));
				break;
			}
			nextvarchangequiet();
			(void)delindkey(c->entryaddr, c->p1, c->p2, c->p4);
			c->changetype = VARIABLEDEL;
			c->p5 = oldval;
			if (c->p1 == VNODEINST || c->p1 == VARCINST) return(1);
			break;
		case VARIABLEDEL:
			/* args: addr, type, key, vartype, aindex, oldvalue */
			nextvarchangequiet();
			(void)insindkey(c->entryaddr, c->p1, c->p2, c->p4, c->p5);
			c->changetype = VARIABLEINS;
			if (c->p1 == VNODEINST || c->p1 == VARCINST) return(1);
			break;
		case DESCRIPTMOD:
			var = (VARIABLE *)c->p2;
			if (var == NOVARIABLE) break;
			oldval = var->textdescript;   var->textdescript = c->p3;   c->p3 = oldval;
			return(1);
	}
	return(0);
}

/************************* CHANGE FACETS *************************/

/*
 * routine to add facet "facet" to the list of facets that are being
 * changed in this batch.
 */
void db_setchangefacet(NODEPROTO *facet)
{
	REGISTER CHANGEFACET *cc;

	if (db_currentbatch == NOCHANGEBATCH) return;
	if (db_currentbatch->firstchangefacet == NOCHANGEFACET ||
		db_currentbatch->firstchangefacet->changefacet != facet)
	{
		cc = db_allocchangefacet();
		cc->nextchangefacet = db_currentbatch->firstchangefacet;
		db_currentbatch->firstchangefacet = cc;
		cc->changefacet = facet;
		cc->forcedlook = 0;
	}
}

void db_removechangefacet(NODEPROTO *np)
{
	REGISTER CHANGEFACET *cc, *lastcc, *nextcc;

	if (db_currentbatch == NOCHANGEBATCH) return;
	lastcc = NOCHANGEFACET;
	for(cc = db_currentbatch->firstchangefacet; cc != NOCHANGEFACET; cc = nextcc)
	{
		nextcc = cc->nextchangefacet;
		if (cc->changefacet == np)
		{
			if (lastcc == NOCHANGEFACET) db_currentbatch->firstchangefacet = nextcc; else
				lastcc->nextchangefacet = nextcc;
			db_freechangefacet(cc);
			return;
		}
		lastcc = cc;
	}
}

/*
 * Routine to ensure that facet "np" is given a hierarchical analysis by the
 * constraint system.
 */
void db_forcehierarchicalanalysis(NODEPROTO *np)
{
	REGISTER CHANGEFACET *cc;

	if (db_currentbatch == NOCHANGEBATCH) return;
	for(cc = db_currentbatch->firstchangefacet; cc != NOCHANGEFACET; cc = cc->nextchangefacet)
		if (cc->changefacet == np)
	{
		cc->forcedlook = 1;
		return;
	}

	/* if not in the list, create the entry and try again */
	db_setchangefacet(np);
	db_forcehierarchicalanalysis(np);
}

/************************* USER INTERFACE *************************/

/*
 * routine to display history list entry "which" (if "which" is negative,
 * display the entire list
 */
void showhistorylist(INTSML which)
{
	REGISTER INTSML node, arcinst, portproto, nproto, object, variable;
	REGISTER INTBIG lobatch, hibatch;
	REGISTER CHANGEBATCH *batchreport;
	REGISTER CHANGE *c;
	char line[50];

	/* specific display of a batch */
	if (which >= 0)
	{
		lobatch = hibatch = db_tailbatch->batchnumber;
		for(batchreport = db_tailbatch; batchreport != NOCHANGEBATCH;
			batchreport = batchreport->nextchangebatch)
		{
			if (batchreport->batchnumber < lobatch) lobatch = batchreport->batchnumber;
			if (batchreport->batchnumber > hibatch) hibatch = batchreport->batchnumber;
			if (batchreport->batchnumber != which) continue;
			(void)initinfstr();
			(void)formatinfstr(_("Batch %ld from %s aid"), batchreport->batchnumber,
				batchreport->aid->aidname);
			if (batchreport->activity != 0)
				(void)formatinfstr(_(", caused by '%s'"), batchreport->activity);
			(void)addstringtoinfstr(_(" is:"));
			ttyputmsg("%s", returninfstr());

			for(c = batchreport->changehead; c != NOCHANGE; c = c->nextchange)
				ttyputmsg("%s", db_describechange(c->changetype, c->entryaddr, c->p1, c->p2,
					c->p3, c->p4, c->p5, c->p6));
			return;
		}
		ttyputmsg(_("Batch %d is not in the list (range is %d to %d)"), which, lobatch, hibatch);
		return;
	}

	/* display the change batches */
	ttyputmsg(_("%ld batches (limit is %ld):"), db_batchtally-1, db_maximumbatches);
	for(batchreport = db_tailbatch; batchreport != NOCHANGEBATCH;
		batchreport = batchreport->nextchangebatch)
	{
		node = arcinst = portproto = nproto = object = variable = 0;
		for(c = batchreport->changehead; c != NOCHANGE; c = c->nextchange)
			switch (c->changetype)
		{
				case NODEINSTNEW:
				case NODEINSTKILL:
				case NODEINSTMOD:
					node++;
					break;
				case ARCINSTNEW:
				case ARCINSTKILL:
				case ARCINSTMOD:
					arcinst++;
					break;
				case PORTPROTONEW:
				case PORTPROTOKILL:
				case PORTPROTOMOD:
					portproto++;
					break;
				case NODEPROTONEW:
				case NODEPROTOKILL:
				case NODEPROTOMOD:
					nproto++;
					break;
				case OBJECTNEW:
				case OBJECTKILL:
					object++;
					break;
				case VARIABLENEW:
				case VARIABLEKILL:
				case VARIABLEMOD:
				case VARIABLEINS:
				case VARIABLEDEL:
					variable++;
					break;
				case DESCRIPTMOD:
					if ((VARIABLE *)c->p1 != NOVARIABLE) variable++; else
						if ((PORTPROTO *)c->p3 != NOPORTPROTO) portproto++; else
							node++;
					break;
		}

		(void)initinfstr();
		(void)sprintf(line, "%ld {", batchreport->batchnumber);
		(void)addstringtoinfstr(line);
		if (batchreport->activity != 0) (void)addstringtoinfstr(batchreport->activity);
		(void)addstringtoinfstr(_("} affects"));

		if (node != 0)
		{
			(void)sprintf(line, " %d %s", node, makeplural(_("node"), node));
			(void)addstringtoinfstr(line);
		}
		if (arcinst != 0)
		{
			(void)sprintf(line, " %d %s", arcinst, makeplural(_("arc"), arcinst));
			(void)addstringtoinfstr(line);
		}
		if (portproto != 0)
		{
			(void)sprintf(line, " %d %s", portproto, makeplural(_("port"), portproto));
			(void)addstringtoinfstr(line);
		}
		if (nproto != 0)
		{
			(void)sprintf(line, " %d %s", nproto, makeplural(_("facet"), nproto));
			(void)addstringtoinfstr(line);
		}
		if (object != 0)
		{
			(void)sprintf(line, " %d %s", object, makeplural(_("object"), object));
			(void)addstringtoinfstr(line);
		}
		if (variable != 0)
		{
			(void)sprintf(line, " %d %s", variable, makeplural(_("variable"), variable));
			(void)addstringtoinfstr(line);
		}
		ttyputmsg("%s", returninfstr());
	}
}

char *db_describechange(INTSML changetype, INTBIG entryaddr, INTBIG p1, INTBIG p2,
	INTBIG p3, INTBIG p4, INTBIG p5, INTBIG p6)
{
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	REGISTER PORTPROTO *pp;
	REGISTER NODEPROTO *np;
	REGISTER VARIABLE *var;
	VARIABLE myvar;

	switch (changetype)
	{
		case NODEINSTNEW:
			ni = (NODEINST *)entryaddr;
			(void)initinfstr();
			(void)formatinfstr(_(" Node '%s' created in facet %s"), describenodeinst(ni),
				describenodeproto(ni->parent));
			return(returninfstr());
		case NODEINSTKILL:
			ni = (NODEINST *)entryaddr;
			(void)initinfstr();
			(void)formatinfstr(_(" Node '%s' killed in facet %s"), describenodeinst(ni),
				describenodeproto(ni->parent));
			return(returninfstr());
		case NODEINSTMOD:
			ni = (NODEINST *)entryaddr;
			(void)initinfstr();
			(void)formatinfstr(_(" Node '%s' changed in facet %s [was %sx%s, center (%s,%s), rotated %ld"),
				describenodeinst(ni), describenodeproto(ni->parent), latoa(p3-p1), latoa(p4-p2),
				latoa((p1+p3)/2), latoa((p2+p4)/2), p5);
			if (p6 != 0) (void)addstringtoinfstr(_(", transposed"));
			(void)addstringtoinfstr("]");
			return(returninfstr());
		case ARCINSTNEW:
			ai = (ARCINST *)entryaddr;
			(void)initinfstr();
			(void)formatinfstr(_(" Arc '%s' created in facet %s"), describearcinst(ai),
				describenodeproto(ai->parent));
			return(returninfstr());
		case ARCINSTKILL:
			ai = (ARCINST *)entryaddr;
			(void)initinfstr();
			(void)formatinfstr(_(" Arc '%s' killed in facet %s"), describearcinst(ai),
				describenodeproto(ai->parent));
			return(returninfstr());
		case ARCINSTMOD:
			ai = (ARCINST *)entryaddr;
			(void)initinfstr();
			(void)formatinfstr(_(" Arc '%s' modified in facet %s [was %s wide %s long from (%s,%s) to (%s,%s)]"),
				describearcinst(ai), describenodeproto(ai->parent), latoa(p5), latoa(p6), latoa(p1), latoa(p2),
				latoa(p3), latoa(p4));
			return(returninfstr());
		case PORTPROTONEW:
			pp = (PORTPROTO *)entryaddr;
			(void)initinfstr();
			(void)formatinfstr(_(" Port '%s' created in facet %s"), pp->protoname,
				describenodeproto(pp->parent));
			return(returninfstr());
		case PORTPROTOKILL:
			pp = (PORTPROTO *)entryaddr;
			(void)initinfstr();
			(void)formatinfstr(_(" Port '%s' killed in facet %s"), pp->protoname,
				describenodeproto(pp->parent));
			return(returninfstr());
		case PORTPROTOMOD:
			pp = (PORTPROTO *)entryaddr;
			(void)initinfstr();
			(void)formatinfstr(_(" Port '%s' moved in facet %s [was on node %s, subport %s]"),
				pp->protoname, describenodeproto(pp->parent), describenodeinst((NODEINST *)p1),
				((PORTPROTO *)p2)->protoname);
			return(returninfstr());
		case NODEPROTONEW:
			np = (NODEPROTO *)entryaddr;
			(void)initinfstr();
			(void)formatinfstr(_(" Facet '%s' created"), describenodeproto(np));
			return(returninfstr());
		case NODEPROTOKILL:
			np = (NODEPROTO *)entryaddr;
			(void)initinfstr();
			(void)formatinfstr(_(" Facet %s' killed"), describenodeproto(np));
			return(returninfstr());
		case NODEPROTOMOD:
			np = (NODEPROTO *)entryaddr;
			(void)initinfstr();
			(void)formatinfstr(_(" Facet '%s' modified [was from %s<=X<=%s %s<=Y<=%s]"),
				describenodeproto(np), latoa(p1), latoa(p2), latoa(p3), latoa(p4));
			return(returninfstr());
		case OBJECTSTART:
			/* args: addr, type */
			(void)initinfstr();
			(void)formatinfstr(_(" Start change to object %s"), db_namevariable(entryaddr, p1));
			return(returninfstr());
		case OBJECTEND:
			/* args: addr, type */
			(void)initinfstr();
			(void)formatinfstr(_(" End change to object %s"), db_namevariable(entryaddr, p1));
			return(returninfstr());
		case OBJECTNEW:
			/* args: addr, type */
			(void)initinfstr();
			(void)formatinfstr(_(" Object %s created"), db_namevariable(entryaddr, p1));
			return(returninfstr());
		case OBJECTKILL:
			/* args: addr, type */
			(void)initinfstr();
			(void)formatinfstr(_(" Object %s deleted"), db_namevariable(entryaddr, p1));
			return(returninfstr());
		case VARIABLENEW:
			/* args: addr, type, key, newtype */
			(void)initinfstr();
			(void)formatinfstr(_(" Variable '%s' created on %s"), changedvariablename(p1, p2, p3),
				db_namevariable(entryaddr, p1));
			return(returninfstr());
		case VARIABLEKILL:
			/* args: addr, type, key, oldaddr, oldtype, olddescript */
			(void)initinfstr();
			myvar.key = p2;  myvar.type = p4;  myvar.addr = p3;
			(void)formatinfstr(_(" Variable '%s' killed on %s [was %s]"), changedvariablename(p1, p2, p4),
				db_namevariable(entryaddr, p1), describevariable(&myvar, -1, -1));
			return(returninfstr());
		case VARIABLEMOD:
			/* args: addr, type, key, vartype, aindex, oldvalue */
			(void)initinfstr();
			(void)formatinfstr(_(" Variable '%s[%ld]' changed on %s"), changedvariablename(p1, p2, p3), p4,
				db_namevariable(entryaddr, p1));
			if ((p3&VCREF) == 0)
			{
				myvar.key = p2;  myvar.type = p3 & ~VISARRAY;
				myvar.addr = p5;
				(void)formatinfstr(_(" [was '%s']"), describevariable(&myvar, -1, -1));
			}
			return(returninfstr());
		case VARIABLEINS:
			/* args: addr, type, key, vartype, aindex */
			(void)initinfstr();
			(void)formatinfstr(_(" Variable '%s[%ld]' inserted on %s"), changedvariablename(p1, p2, p3), p4,
				db_namevariable(entryaddr, p1));
			return(returninfstr());
		case VARIABLEDEL:
			/* args: addr, type, key, vartype, aindex, oldvalue */
			(void)initinfstr();
			(void)formatinfstr(_(" Variable '%s[%ld]' deleted on %s"), changedvariablename(p1, p2, p3), p4,
				db_namevariable(entryaddr, p1));
			return(returninfstr());
		case DESCRIPTMOD:
			var = (VARIABLE *)p2;
			if (var != NOVARIABLE)
			{
				(void)initinfstr();
				(void)formatinfstr(_(" Text descriptor on variable %s changed [was 0%lo]"),
					makename(var->key), p3);
				return(returninfstr());
			}
	}
	return("?");
}

/*
 * routine to name the variable at "addr" of type "type".  It is assumed
 * to be an object that can hold other variables
 */
char *db_namevariable(INTBIG addr, INTBIG type)
{
	REGISTER NODEINST *ni;
	REGISTER NODEPROTO *np;
	REGISTER PORTARCINST *pi;
	REGISTER PORTEXPINST *pe;
	REGISTER PORTPROTO *pp;
	REGISTER ARCINST *ai;
	REGISTER ARCPROTO *ap;
	REGISTER GEOM *geom;
	REGISTER LIBRARY *lib;
	REGISTER TECHNOLOGY *tech;
	REGISTER AIDENTRY *aid;
	REGISTER RTNODE *rtn;
	REGISTER CELL *c;
	REGISTER VIEW *v;
	REGISTER WINDOWPART *win;
	REGISTER WINDOWFRAME *wf;
	REGISTER GRAPHICS *gra;
	REGISTER CONSTRAINT *con;
	REGISTER POLYGON *poly;

	(void)initinfstr();
	switch (type&VTYPE)
	{
		case VNODEINST:
			ni = (NODEINST *)addr;
			(void)formatinfstr("NodeInstance(%s)", describenodeinst(ni));
			break;
		case VNODEPROTO:
			np = (NODEPROTO *)addr;
			if (np->primindex == 0) (void)formatinfstr("Facet(%s)", describenodeproto(np)); else
				(void)formatinfstr("Primitive(%s)", describenodeproto(np));
			break;
		case VPORTARCINST:
			pi = (PORTARCINST *)addr;
			if (pi == NOPORTARCINST) (void)addstringtoinfstr("PortArcInstance(NULL)"); else
				(void)formatinfstr("PortArcInstance(%ld)", (INTBIG)pi);
			break;
		case VPORTEXPINST:
			pe = (PORTEXPINST *)addr;
			if (pe == NOPORTEXPINST) (void)addstringtoinfstr("PortExpInstance(NULL)"); else
				(void)formatinfstr("PortExpInstance(%ld)", (INTBIG)pe);
			break;
		case VPORTPROTO:
			pp = (PORTPROTO *)addr;
			if (pp == NOPORTPROTO) (void)addstringtoinfstr("PortPrototype(NULL)"); else
				(void)formatinfstr("PortPrototype(%s)", pp->protoname);
			break;
		case VARCINST:
			ai = (ARCINST *)addr;
			(void)formatinfstr("ArcInstance(%s)", describearcinst(ai));
			break;
		case VARCPROTO:
			ap = (ARCPROTO *)addr;
			(void)formatinfstr("ArcPrototype(%s)", describearcproto(ap));
			break;
		case VGEOM:
			geom = (GEOM *)addr;
			if (geom == NOGEOM) (void)addstringtoinfstr("Geom(NULL)"); else
				(void)formatinfstr("Geom(%ld)", (INTBIG)geom);
			break;
		case VLIBRARY:
			lib = (LIBRARY *)addr;
			if (lib == NOLIBRARY) (void)addstringtoinfstr("Library(NULL)"); else
				(void)formatinfstr("Library(%s)", lib->libname);
			break;
		case VTECHNOLOGY:
			tech = (TECHNOLOGY *)addr;
			if (tech == NOTECHNOLOGY) (void)addstringtoinfstr("Technology(NULL)"); else
				(void)formatinfstr("Technology(%s)", tech->techname);
			break;
		case VAID:
			aid = (AIDENTRY *)addr;
			if (aid == NOAID) (void)addstringtoinfstr("Aid(NULL)"); else
				(void)formatinfstr("Aid(%s)", aid->aidname);
			break;
		case VRTNODE:
			rtn = (RTNODE *)addr;
			if (rtn == NORTNODE) (void)addstringtoinfstr("Rtnode(NULL)"); else
				(void)formatinfstr("Rtnode(%ld)", (INTBIG)rtn);
			break;
		case VCELL:
			c = (CELL *)addr;
			if (c == NOCELL) (void)addstringtoinfstr("Cell(NULL)"); else
				(void)formatinfstr("Cell(%s)", c->cellname);
			break;
		case VVIEW:
			v = (VIEW *)addr;
			if (v == NOVIEW) (void)addstringtoinfstr("View(NULL)"); else
				(void)formatinfstr("View(%s)", v->viewname);
			break;
		case VWINDOWPART:
			win = (WINDOWPART *)addr;
			if (win != NOWINDOWPART) (void)formatinfstr("WindowPart(%s)", win->location); else
				(void)formatinfstr("WindowPart(%ld)", (INTBIG)win);
			break;
		case VGRAPHICS:
			gra = (GRAPHICS *)addr;
			if (gra == NOGRAPHICS) (void)addstringtoinfstr("Graphics(NULL)"); else
				(void)formatinfstr("Graphics(%ld)", (INTBIG)gra);
			break;
		case VCONSTRAINT:
			con = (CONSTRAINT *)addr;
			if (con == NOCONSTRAINT) (void)addstringtoinfstr("Constraint(NULL)"); else
				(void)formatinfstr("Constraint(%s)", con->conname);
			break;
		case VWINDOWFRAME:
			wf = (WINDOWFRAME *)addr;
			(void)formatinfstr("WindowFrame(%ld)", (INTBIG)wf);
			break;
		case VPOLYGON:
			poly = (POLYGON *)addr;
			(void)formatinfstr("Polygon(%ld)", (INTBIG)poly);
			break;
		default:
			(void)addstringtoinfstr("UNKNOWN(?)");
			break;
	}
	return(returninfstr());
}

/* routine to display the "undo" menu
 * Change list = 3 (user item)
 * Undo        = 2 (button)
 * Redo        = 6 (button)
 * Show detail = 5 (check box)
 */
void db_undodlog(void)
{
	INTBIG itemHit;
	INTSML details;
	AIDENTRY *aid;

	/* display the undo dialog box */
	if (DiaInitDialog(&db_undodialog) != 0) return;
	DiaInitTextDialog(3, DiaNullDlogList, DiaNullDlogItem, DiaNullDlogDone,
		-1, SCHORIZBAR|SCSMALLFONT);
	details = 0;
	db_loadhistorylist(details);

	/* default number of changes to undo */
	DiaSelectLine(3, 1000);

	/* loop until done */
	for(;;)
	{
		itemHit = DiaNextHit();
		if (itemHit == OK) break;
		if (itemHit == 5)
		{
			details = 1 - details;
			DiaSetControl(5, details);
			db_loadhistorylist(details);
			continue;
		}
		if (itemHit == 2)
		{
			undoabatch(&aid);
			db_loadhistorylist(details);
			continue;
		}
		if (itemHit == 6)
		{
			redoabatch(&aid);
			db_loadhistorylist(details);
			continue;
		}
	}

	DiaDoneDialog();
}

/*
 * helper routines for undo dialog
 */
void db_loadhistorylist(short details)
{
	REGISTER INTSML node, arcinst, portproto, nproto, object, variable, lineno, curline;
	REGISTER CHANGE *c;
	char line[50];
	CHANGEBATCH *batch;

	DiaLoadTextDialog(3, DiaNullDlogList, DiaNullDlogItem, DiaNullDlogDone, -1);
	lineno = 0;
	curline = -1;
	for(batch = db_tailbatch; batch != NOCHANGEBATCH; batch = batch->nextchangebatch)
	{
		node = arcinst = portproto = nproto = object = variable = 0;
		for(c = batch->changehead; c != NOCHANGE; c = c->nextchange)
			switch (c->changetype)
		{
				case NODEINSTNEW:
				case NODEINSTKILL:
				case NODEINSTMOD:
					node++;
					break;
				case ARCINSTNEW:
				case ARCINSTKILL:
				case ARCINSTMOD:
					arcinst++;
					break;
				case PORTPROTONEW:
				case PORTPROTOKILL:
				case PORTPROTOMOD:
					portproto++;
					break;
				case NODEPROTONEW:
				case NODEPROTOKILL:
				case NODEPROTOMOD:
					nproto++;
					break;
				case OBJECTNEW:
				case OBJECTKILL:
					object++;
					break;
				case VARIABLENEW:
				case VARIABLEKILL:
				case VARIABLEMOD:
				case VARIABLEINS:
				case VARIABLEDEL:
					variable++;
					break;
				case DESCRIPTMOD:
					if ((VARIABLE *)c->entryaddr != NOVARIABLE) variable++; else
						if ((PORTPROTO *)c->p1 != NOPORTPROTO) portproto++; else
							node++;
					break;
		}

		(void)initinfstr();
		if (details != 0) (void)addstringtoinfstr(_("***** BATCH "));
		(void)sprintf(line, "%ld {", batch->batchnumber);
		(void)addstringtoinfstr(line);
		if (batch->activity != 0) (void)addstringtoinfstr(batch->activity);
		(void)addstringtoinfstr(_("} affects"));

		if (node != 0)
		{
			(void)sprintf(line, " %d %s", node, makeplural(_("node"), node));
			(void)addstringtoinfstr(line);
		}
		if (arcinst != 0)
		{
			(void)sprintf(line, " %d %s", arcinst, makeplural(_("arc"), arcinst));
			(void)addstringtoinfstr(line);
		}
		if (portproto != 0)
		{
			(void)sprintf(line, " %d %s", portproto, makeplural(_("port"), portproto));
			(void)addstringtoinfstr(line);
		}
		if (nproto != 0)
		{
			(void)sprintf(line, " %d %s", nproto, makeplural(_("facet"), nproto));
			(void)addstringtoinfstr(line);
		}
		if (object != 0)
		{
			(void)sprintf(line, " %d %s", object, makeplural(_("object"), object));
			(void)addstringtoinfstr(line);
		}
		if (variable != 0)
		{
			(void)sprintf(line, " %d %s", variable, makeplural(_("variable"), variable));
			(void)addstringtoinfstr(line);
		}
		DiaStuffLine(3, returninfstr());
		if (batch == db_donebatch) curline = lineno;
		lineno++;

		if (details != 0)
		{
			for(c = batch->changehead; c != NOCHANGE; c = c->nextchange)
			{
				DiaStuffLine(3, db_describechange(c->changetype, c->entryaddr, c->p1, c->p2,
					c->p3, c->p4, c->p5, c->p6));
				lineno++;
			}
		}
	}
	DiaSelectLine(3, curline);
}

/************************* MEMORY ALLOCATION *************************/

/*
 * routine to allocate a change module from the pool (if any) or memory
 * the routine returns NOCHANGE if allocation fails.
 */
CHANGE *db_allocchange(void)
{
	REGISTER CHANGE *c;

	if (db_changefree == NOCHANGE)
	{
		c = (CHANGE *)emalloc((sizeof (CHANGE)), db_cluster);
		if (c == 0) return(NOCHANGE);
		return(c);
	}

	/* take change module from free list */
	c = db_changefree;
	db_changefree = db_changefree->nextchange;
	return(c);
}

/*
 * routine to free a change module
 */
void db_freechange(CHANGE *c)
{
	c->nextchange = db_changefree;
	db_changefree = c;
}

/*
 * routine to allocate a changefacet module from the pool (if any) or memory
 * the routine returns NOCHANGEFACET if allocation fails.
 */
CHANGEFACET *db_allocchangefacet(void)
{
	REGISTER CHANGEFACET *c;

	if (db_changefacetfree == NOCHANGEFACET)
	{
		c = (CHANGEFACET *)emalloc((sizeof (CHANGEFACET)), db_cluster);
		if (c == 0) return(NOCHANGEFACET);
		return(c);
	}

	/* take change module from free list */
	c = db_changefacetfree;
	db_changefacetfree = db_changefacetfree->nextchangefacet;
	return(c);
}

/*
 * routine to free a changefacet module
 */
void db_freechangefacet(CHANGEFACET *c)
{
	c->nextchangefacet = db_changefacetfree;
	db_changefacetfree = c;
}

/*
 * routine to allocate a change batch from the pool (if any) or memory
 * the routine returns NOCHANGEBATCH if allocation fails.
 */
CHANGEBATCH *db_allocchangebatch(void)
{
	REGISTER CHANGEBATCH *b;

	if (db_changebatchfree == NOCHANGEBATCH)
	{
		/* no free change batches...allocate one */
		b = (CHANGEBATCH *)emalloc((sizeof (CHANGEBATCH)), db_cluster);
		if (b == 0) return(NOCHANGEBATCH);
	} else
	{
		/* take batch from free list */
		b = db_changebatchfree;
		db_changebatchfree = db_changebatchfree->nextchangebatch;
	}
	return(b);
}

/*
 * routine to free a change batch
 */
void db_freechangebatch(CHANGEBATCH *b)
{
	b->nextchangebatch = db_changebatchfree;
	db_changebatchfree = b;
}
