/*
 * Electric(tm) VLSI Design System
 *
 * File: dbnoproto.c
 * Database facet manipulation module
 * 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 "egraphics.h"
#include "usr.h"

/* hierarchical traversal data structures */
NODEINST **db_hierpath;
INTSML     db_hierdepth = 0;
INTSML     db_hierlimit = 0;

/* prototypes for local routines */
INTSML db_schematic_pages(NODEPROTO*);
char *db_getframedesc(NODEPROTO*);
INTSML db_copyxlibvars(INTBIG, INTBIG, INTBIG, INTBIG, NODEPROTO*, LIBRARY*);
INTSML db_portassociate(NODEINST*, NODEINST*);
INTSML db_doesntconnect(ARCPROTO*[], PORTPROTO*);

/*
 * Routine to free all memory associated with this module.
 */
void db_freenoprotomemory(void)
{
	if (db_hierlimit > 0) efree((char *)db_hierpath);
}

/************************* CELLS *************************/

/*
 * routine to allocate a cell from memory cluster "cluster" and return
 * its address.  If "cluster" is NOCLUSTER, a new cluster is created for this
 * cell and the cell is placed inside of it.  The routine returns NOCELL
 * if allocation fails.
 */
CELL *alloccell(CLUSTER *cluster)
{
	REGISTER CELL *c;
	REGISTER CLUSTER *clus;

	if (cluster != NOCLUSTER) clus = cluster; else clus = alloccluster("");
	if (clus == NOCLUSTER) c = 0; else
		c = (CELL *)emalloc((sizeof (CELL)), clus);
	if (c == 0) return((CELL *)db_error(DBNOMEM|DBALLOCCELL));
	c->cluster = clus;
	c->cellname = NOSTRING;
	c->nextcell = NOCELL;
	c->firstincell = NONODEPROTO;
	c->lib = NOLIBRARY;
	c->freenetwork = NONETWORK;
	c->temp1 = c->temp2 = 0;
	c->numvar = 0;
	c->firstvar = NOVARIABLE;
	return(c);
}

/*
 * routine to return cell "c" to the pool of free cells
 */
void freecell(CELL *c)
{
	REGISTER CLUSTER *clus;
	REGISTER NETWORK *net, *nextnet;

	if (c == NOCELL) return;
	if (c->numvar != 0) db_freevars(&c->firstvar, &c->numvar);
	for(net = c->freenetwork; net != NONETWORK; net = nextnet)
	{
		nextnet = net->nextnetwork;
		efree((char *)net);
	}
	clus = c->cluster;
	c->cluster = NOCLUSTER;
	efree((char *)c);
	freecluster(clus);
}

/************************* NODEPROTOS *************************/

/*
 * routine to allocate a nodeproto from memory cluster "cluster" and return
 * its address.  The routine returns NONODEPROTO if allocation fails.
 */
NODEPROTO *allocnodeproto(CLUSTER *cluster)
{
	REGISTER NODEPROTO *np;

	if (cluster == NOCLUSTER)
		return((NODEPROTO *)db_error(DBNOMEM|DBALLOCNODEPROTO));
	np = (NODEPROTO *)emalloc((sizeof (NODEPROTO)), cluster);
	if (np == 0) return((NODEPROTO *)db_error(DBNOMEM|DBALLOCNODEPROTO));
	np->primname = "ERROR!";
	np->changeaddr = (char *)NOCHANGE;
	np->userbits = np->temp1 = np->temp2 = 0;
	np->numvar = 0;
	np->firstvar = NOVARIABLE;
	np->firstinst = NONODEINST;
	np->firstnodeinst = NONODEINST;
	np->firstarcinst = NOARCINST;
	np->firstportproto = NOPORTPROTO;
	np->tech = NOTECHNOLOGY;
	np->nextnodeproto = NONODEPROTO;
	np->cell = NOCELL;
	np->cellview = NOVIEW;
	np->version = 1;
	np->lastversion = NONODEPROTO;
	np->newestversion = NONODEPROTO;
	np->nextincell = NONODEPROTO;
	np->creationdate = 0;
	np->revisiondate = 0;
	np->firstnetwork = NONETWORK;
	np->rtree = NORTNODE;
	np->primindex = 0;
	np->lowx = np->highx = np->lowy = np->highy = 0;
	np->lastnodeproto = NONODEPROTO;
	np->adirty = 0;
	return(np);
}

/*
 * routine to return nodeproto "np" to the pool of free nodes
 */
void freenodeproto(NODEPROTO *np)
{
	if (np == NONODEPROTO) return;
	if (np->numvar != 0) db_freevars(&np->firstvar, &np->numvar);
	efree((char *)np);
}

/*
 * routine to create a new facet named "fname", in library
 * "lib".  The address of the facet is returned (NONODEPROTO if an error occurs).
 */
NODEPROTO *newnodeproto(char *fname, LIBRARY *lib)
{
	REGISTER NODEPROTO *np, *onp;
	REGISTER char *ptr, *opt, *cpt, *nameend;
	REGISTER INTSML save;
	REGISTER INTBIG explicitversion;
	REGISTER CELL *c;
	REGISTER VIEW *view, *v;

	/* copy the name to a temporary location so that it can be modified */
	(void)initinfstr();
	(void)addstringtoinfstr(fname);
	fname = returninfstr();

	/* search for view specification */
	view = el_unknownview;
	nameend = 0;
	for(ptr = fname; *ptr != 0; ptr++) if (*ptr == '{') break;
	if (*ptr == '{')
	{
		nameend = ptr;
		for(opt = ptr+1; *opt != 0; opt++) if (*opt == '}') break;
		if (*opt == 0 || opt[1] != 0)
			return((NODEPROTO *)db_error(DBBADNAME|DBNEWNODEPROTO));
		*opt = 0;
		for(v = el_views; v != NOVIEW; v = v->nextview)
			if (namesame(&ptr[1], v->sviewname) == 0) break;
		if (v == NOVIEW) for(v = el_views; v != NOVIEW; v = v->nextview)
			if (namesame(&ptr[1], v->viewname) == 0) break;

		/* special case of schematic pages: create a view */
		if (v == NOVIEW && (ptr[1] == 'p' || ptr[1] == 'P') && isanumber(&ptr[2]) != 0)
		{
			/* create a new schematic page view */
			cpt = (char *)emalloc((16+strlen(&ptr[2])), el_tempcluster);
			if (cpt == 0) return(NONODEPROTO);
			(void)strcpy(cpt, "schematic-page-");
			(void)strcat(cpt, &ptr[2]);
			v = newview(cpt, &ptr[1]);
			efree(cpt);
		}

		*opt = '}';
		if (v == NOVIEW) return((NODEPROTO *)db_error(DBBADNAME|DBNEWNODEPROTO));
		view = v;
	}

	/* search for version specification */
	explicitversion = 0;
	for(ptr = fname; *ptr != 0; ptr++) if (*ptr == ';') break;
	if (*ptr == ';')
	{
		if (nameend == 0 || ptr < nameend) nameend = ptr;
		explicitversion = atoi(&ptr[1]);
		if (explicitversion <= 0) return((NODEPROTO *)db_error(DBBADNAME|DBNEWNODEPROTO));
	}
	if (nameend == 0) nameend = ptr;

	/* scan main facet name for illegal characters */
	if (*fname == 0) return((NODEPROTO *)db_error(DBBADNAME|DBNEWNODEPROTO));
	for(opt = fname; opt != ptr; opt++)
		if (*opt <= ' ' || *opt == ':' || *opt >= 0177)
			return((NODEPROTO *)db_error(DBBADNAME|DBNEWNODEPROTO));

	/* see if the cell name exists */
	save = *nameend;
	*nameend = 0;
	for(c = lib->firstcell; c != NOCELL; c = c->nextcell)
		if (namesame(c->cellname, fname) == 0) break;
	if (c == NOCELL)
	{
		/* new cell name, create it */
		c = alloccell(NOCLUSTER);
		if (c == NOCELL)
		{
			*nameend = (char)save;
			return(NONODEPROTO);
		}
		c->lib = lib;
		*ptr = 0;
		(void)allocstring(&c->cellname, fname, c->cluster);
		(void)strncpy(c->cluster->clustername, fname, 29);
		*ptr = (char)save;
		(void)db_change((INTBIG)c, OBJECTNEW, VCELL, 0, 0, 0, 0, 0);
	}
	*nameend = (char)save;

	/* create the facet */
	np = allocnodeproto(c->cluster);
	if (np == NONODEPROTO) return(NONODEPROTO);

	/* initialize */
	np->primindex = 0;
	np->lowx = np->highx = 0;
	np->lowy = np->highy = 0;
	np->firstinst = NONODEINST;
	np->firstnodeinst = NONODEINST;
	np->firstarcinst = NOARCINST;
	np->tech = NOTECHNOLOGY;
	np->firstportproto = NOPORTPROTO;
	np->adirty = 0;
	np->cell = c;
	np->cellview = view;
	np->creationdate = getcurrenttime();
	np->revisiondate = getcurrenttime();

	/* determine version number of this facet */
	if (explicitversion > 0)
	{
		np->version = (INTSML)explicitversion;
		for(onp = c->firstincell; onp != NONODEPROTO; onp = onp->nextincell)
			if (onp->cellview == view && onp->version == explicitversion)
				return((NODEPROTO *)db_error(DBBADNAME|DBNEWNODEPROTO));
	} else
	{
		np->version = 1;
		for(onp = c->firstincell; onp != NONODEPROTO; onp = onp->nextincell)
			if (onp->cellview == view) np->version = onp->version + 1;
	}

	/* insert in the library and facet structures */
	db_insertnodeproto(np);

	/* create initial R-tree data */
	if (geomstructure(np) != 0) return(NONODEPROTO);

	/* report the new facet */
	np->changeaddr = (char *)db_change((INTBIG)np, NODEPROTONEW, 0, 0, 0, 0, 0, 0);

	/* tell constraint system about new facet */
	(*el_curconstraint->newobject)((INTBIG)np, VNODEPROTO);

	db_setchangefacet(np);

	/* tell which facet it is */
	return(np);
}

/*
 * routine to create a new primitive nodeproto with name "fname", default
 * size "sx" by "sy", and primitive index "pi".  The nodeproto is placed in
 * technology "tech".  The address of the primitive nodeproto is returned.
 * Upon error, the value NONODEPROTO is returned.
 */
NODEPROTO *db_newprimnodeproto(char *fname, INTBIG sx, INTBIG sy, INTSML pi,
	TECHNOLOGY *tech)
{
	REGISTER NODEPROTO *np, *ent;
	REGISTER char *pp;

	/* check for errors */
	for(np = tech->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
	{
		if (np->primindex == pi)
		{
			ttyputmsg("Index %d of technology %s in use", pi, tech->techname);
			return(NONODEPROTO);
		}
		if (namesame(fname, np->primname) == 0)
		{
			ttyputmsg("Error: Two technologies with same name: %s and %s", fname, tech->techname);
			return(NONODEPROTO);
		}
	}
	if (pi <= 0)
	{
		ttyputmsg("Error: Component index must be positive but %s:%s has %d",
			tech->techname, fname, pi);
		return(NONODEPROTO);
	}
	if (sx < 0 || sy < 0)
	{
		ttyputmsg("Error: Component size must be positive, but %s:%s is %ldx%ld",
			tech->techname, fname, sx, sy);
		return(NONODEPROTO);
	}
	for(pp = fname; *pp != 0; pp++) if (*pp <= ' ' || *pp == ':' || *pp >= 0177)
	{
		ttyputmsg("Error: Primitive name must not contain ':' and others but '%s:%s' does",
			tech->techname, fname);
		return(NONODEPROTO);
	}

	/* create the nodeproto */
	np = allocnodeproto(tech->cluster);
	if (np == NONODEPROTO)
	{
		ttyputmsg("Error: no memory for primitive %s in technology %s", fname, tech->techname);
		return(NONODEPROTO);
	}

	/* insert at the end of the list of primitives */
	if (tech->firstnodeproto == NONODEPROTO)
	{
		tech->firstnodeproto = np;   ent = NONODEPROTO;
	} else
	{
		ent = tech->firstnodeproto;
		while (ent->nextnodeproto != NONODEPROTO) ent = ent->nextnodeproto;
		ent->nextnodeproto = np;
	}
	np->lastnodeproto = ent;
	np->nextnodeproto = NONODEPROTO;

	/* initialize name and size */
	if (allocstring(&np->primname, fname, tech->cluster) != 0)
	{
		ttyputmsg("Error: No memory for name in %s:%s", tech->techname, fname);
		return(NONODEPROTO);
	}
	np->primindex = pi;
	np->lowx = -sx/2;   np->highx = np->lowx + sx;
	np->lowy = -sy/2;   np->highy = np->lowy + sy;
	np->firstinst = NONODEINST;
	np->firstnodeinst = NONODEINST;
	np->firstarcinst = NOARCINST;
	np->tech = tech;
	np->firstportproto = NOPORTPROTO;
	np->adirty = 0;

	/* tell which nodeproto it is */
	return(np);
}

/*
 * routine to kill nodeproto "np".  Returns 0 if sucessful, nonzero
 * otherwise
 */
INTSML killnodeproto(NODEPROTO *np)
{
	REGISTER NODEPROTO *lnt;
	REGISTER PORTPROTO *pp, *npt;

	/* cannot delete primitive nodeprotos */
	if (np == NONODEPROTO)
		return((INTSML)db_error(DBBADPROTO|DBKILLNODEPROTO));
	if (np->primindex != 0)
		return((INTSML)db_error(DBPRIMITIVE|DBKILLNODEPROTO));

	/* search the library for the nodeproto */
	for(lnt = np->cell->lib->firstnodeproto; lnt != NONODEPROTO; lnt = lnt->nextnodeproto)
		if (np == lnt) break;
	if (lnt == NONODEPROTO)
		return((INTSML)db_error(DBBADFACET|DBKILLNODEPROTO));

	/* cannot delete nodeproto with any instances */
	if (np->firstinst != NONODEINST)
		return((INTSML)db_error(DBHASINSTANCES|DBKILLNODEPROTO));

	/* delete everything in the facet */
	for(pp = np->firstportproto; pp != NOPORTPROTO; pp = npt)
	{
		npt = pp->nextportproto;
		if (killportproto(np, pp)) ttyputerr("killportproto oops");
	}
	while (np->firstarcinst != NOARCINST) db_killarcinst(np->firstarcinst);
	while (np->firstnodeinst != NONODEINST) db_killnodeinst(np->firstnodeinst);

	/* remove nodeproto from library */
	db_retractnodeproto(np);

	/* remove any "changefacet" modules for this facet */
	db_removechangefacet(np);

	/* record the change to the facet for broadcasting */
	np->changeaddr = (char *)db_change((INTBIG)np, NODEPROTOKILL, 0, 0, 0, 0, 0, 0);
	if (np->cell->firstincell == NONODEPROTO)
		(void)db_change((INTBIG)np->cell, OBJECTKILL, VCELL, 0, 0, 0, 0, 0);

	/* tell constraint system about killed facet */
	(*el_curconstraint->killobject)((INTBIG)np, VNODEPROTO);

	return(0);
}

/*
 * routine to add nodeproto "np" to the list of cells and facets in the library
 */
void db_insertnodeproto(NODEPROTO *np)
{
	REGISTER NODEPROTO *ent, *onp, *lastnp;
	REGISTER CELL *oc;

	/* first append to the list of facets in the library */
	if (np->cell->lib->firstnodeproto == NONODEPROTO)
	{
		np->cell->lib->firstnodeproto = np;   ent = NONODEPROTO;
	} else
	{
		ent = np->cell->lib->firstnodeproto;
		while (ent->nextnodeproto != NONODEPROTO) ent = ent->nextnodeproto;
		ent->nextnodeproto = np;
	}
	np->lastnodeproto = ent;
	np->nextnodeproto = NONODEPROTO;

	/* if this is the first facet in the cell, insert the cell structure */
	if (np->cell->firstincell == NONODEPROTO)
	{
		if (np->cell->lib->firstcell == NOCELL) np->cell->lib->firstcell = np->cell; else
		{
			oc = np->cell->lib->firstcell;
			while (oc->nextcell != NOCELL) oc = oc->nextcell;
			oc->nextcell = np->cell;
		}
		np->cell->nextcell = NOCELL;
	}

	/* see if there is already a facet with this view */
	lastnp = NONODEPROTO;
	for(onp = np->cell->firstincell; onp != NONODEPROTO; onp = onp->nextincell)
	{
		if (onp->cellview == np->cellview) break;
		lastnp = onp;
	}
	if (onp != NONODEPROTO)
	{
		/* new version of a cell/view, find place in list */
		if (np->version >= onp->version)
		{
			/* place on top of list */
			if (lastnp == NONODEPROTO) np->cell->firstincell = np; else
				lastnp->nextincell = np;
			np->nextincell = onp->nextincell;
			onp->nextincell = NONODEPROTO;
			np->lastversion = onp;
			for(; onp != NONODEPROTO; onp = onp->lastversion)
				onp->newestversion = np;
			np->newestversion = np;
		} else
		{
			/* find place in list of versions */
			np->newestversion = onp;
			ent = NONODEPROTO;
			for(; onp != NONODEPROTO; onp = onp->lastversion)
			{
				if (np->version >= onp->version) break;
				ent = onp;
			}
			ent->lastversion = np;
			np->lastversion = onp;
		}
	} else
	{
		/* this view is new */
		np->lastversion = NONODEPROTO;
		np->newestversion = np;
		np->nextincell = np->cell->firstincell;
		np->cell->firstincell = np;
	}
}

/*
 * routine to remove facet "np" from all lists in the library and facet
 */
void db_retractnodeproto(NODEPROTO *np)
{
	REGISTER NODEPROTO *onp, *lastnp;
	REGISTER CELL *c, *lastc, *lc;

	/* remove facet from list in the library */
	if (np->lastnodeproto == NONODEPROTO)
		np->cell->lib->firstnodeproto = np->nextnodeproto; else
			np->lastnodeproto->nextnodeproto = np->nextnodeproto;
	if (np->nextnodeproto != NONODEPROTO)
		np->nextnodeproto->lastnodeproto = np->lastnodeproto;

	/* remove facet from list */
	c = np->cell;

	if (np->newestversion != np)
	{
		/* easy to handle removal of old version */
		lastnp = NONODEPROTO;
		for(onp = np->newestversion; onp != NONODEPROTO; onp = onp->lastversion)
		{
			if (onp == np) break;
			lastnp = onp;
		}
		if (lastnp != NONODEPROTO) lastnp->lastversion = np->lastversion;
			else ttyputerr("Unusual version link in database!");
	} else
	{
		/* removing latest version of facet */
		lastnp = NONODEPROTO;
		for(onp = c->firstincell; onp != NONODEPROTO; onp = onp->nextincell)
		{
			if (onp == np) break;
			lastnp = onp;
		}
		if (np->lastversion != NONODEPROTO)
		{
			/* bring older version to front */
			if (lastnp == NONODEPROTO) c->firstincell = np->lastversion; else
				lastnp->nextincell = np->lastversion;
			np->lastversion->nextincell = np->nextincell;
			for(onp = np->lastversion; onp != NONODEPROTO; onp = onp->lastversion)
				onp->newestversion = np->lastversion;
		} else
		{
			/* delete this view */
			if (lastnp == NONODEPROTO) c->firstincell = np->nextincell; else
				lastnp->nextincell = np->nextincell;
		}
	}

	/* remove cell from library if it now stands alone */
	if (c->firstincell == NONODEPROTO)
	{
		lastc = NOCELL;
		for(lc = c->lib->firstcell; lc != NOCELL; lc = lc->nextcell)
		{
			if (c == lc) break;
			lastc = lc;
		}
		if (lastc == NOCELL) c->lib->firstcell = c->nextcell; else
			lastc->nextcell = c->nextcell;
	}
}

/*
 * routine to return the current facet (the one in the current window).
 * returns NONODEPROTO if there is none.
 */
NODEPROTO *getcurfacet(void)
{
	if (el_curwindowpart != NOWINDOWPART) return(el_curwindowpart->curnodeproto);
	return(NONODEPROTO);
}

/******************** HIERARCHICAL TRAVERSER ********************/

/*
 * routine to return the current path to this location in the traversal.  The depth is put
 * in "depth" and the list of NODEINSTs is put in "nilist".  The first entry in the array
 * is the top of the hierarchy, and the last entry is the instance in whose definition
 * the traversal currently resides.
 */
void gettraversalpath(NODEINST ***nilist, INTSML *depth)
{
	*nilist = db_hierpath;
	*depth = db_hierdepth;
}

/*
 * routine to begin the traversal of the hierarchy, starting at facet "np".  At each level of the
 * traversal, the routine "hierroutine" is called with the facet and the user data.  The
 * initial packet to pass is in "data".
 */
void begintraversehierarchy(void)
{
	db_hierdepth = 0;
}

/*
 * routine to traversal down the hierarchy, into nodeinst "ni"
 */
void downhierarchy(NODEINST *ni)
{
	REGISTER INTSML i;
	REGISTER NODEINST **nilist;

	/* make sure there is room in the list of nodes traversed */
	if (db_hierdepth >= db_hierlimit)
	{
		nilist = (NODEINST **)emalloc((db_hierlimit+10) * (sizeof (NODEINST *)), db_cluster);
		if (nilist == 0) return;
		for(i=0; i<db_hierdepth; i++) nilist[i] = db_hierpath[i];
		if (db_hierlimit > 0) efree((char *)db_hierpath);
		db_hierpath = nilist;
		db_hierlimit += 10;
	}

	/* add this node to the traversal stack */
	db_hierpath[db_hierdepth] = ni;
	db_hierdepth++;

	/* invoke parameterization if any */
	if ((ni->proto->userbits & HASPARAMS) != 0)
	{
		if (ni->proto->userbits != 0)
		{
			if (ni->proto->tech->paramnode != 0)
				(*(ni->proto->tech->paramnode))(ni);
		}
	}
}

/*
 * routine to back out of a hierarchy traversal
 */
void uphierarchy(void)
{
	if (db_hierdepth <= 0) return;

	/* pop the node off the traversal stack */
	db_hierdepth--;
}

/*
 * Routine to determine the node instance that is the proper parent of facet "np".
 * This is done by examining the variable "USER_descent_path" on the facet, which
 * is created when the user goes "down the hierarchy" into this facet.  The routine
 * not only extracts this information, but it verifies that the instance still
 * exists.  Returns NONODEINST if there is no parent.
 */
NODEINST *descentparent(NODEPROTO *np)
{
	REGISTER VARIABLE *var;
	REGISTER NODEINST *ni, *vni;
	REGISTER NODEPROTO *inp;

	/* find this level in the stack */
	var = getvalkey((INTBIG)np, VNODEPROTO, VNODEINST, us_facet_descent_path);
	if (var == NOVARIABLE) return(NONODEINST);
	ni = (NODEINST *)var->addr;

	/* validate this pointer: node must be a current instance of this facet */
	for(vni = np->firstinst; vni != NONODEINST; vni = vni->nextinst)
		if (vni == ni) return(ni);

	/* might be an icon */
	inp = iconview(np);
	if (inp != NONODEPROTO)
	{
		for(vni = inp->firstinst; vni != NONODEINST; vni = vni->nextinst)
			if (vni == ni) return(ni);
	}
	return(NONODEINST);
}

/************************* VIEWS *************************/

/*
 * routine to allocate a view and return its address.  The routine returns
 * NOVIEW if allocation fails.
 */
VIEW *allocview(void)
{
	REGISTER VIEW *v;

	v = (VIEW *)emalloc((sizeof (VIEW)), db_cluster);
	if (v == 0) return((VIEW *)db_error(DBNOMEM|DBALLOCVIEW));
	v->viewname = v->sviewname = NOSTRING;
	v->nextview = NOVIEW;
	v->temp1 = v->temp2 = 0;
	v->viewstate = 0;
	v->firstvar = NOVARIABLE;
	v->numvar = 0;
	return(v);
}

/*
 * routine to return view "v" to free memory
 */
void freeview(VIEW *v)
{
	if (v == NOVIEW) return;
	if (v->numvar != 0) db_freevars(&v->firstvar, &v->numvar);
	efree((char *)v);
}

/*
 * routine to create a new view with name "vname" and short view name "svname".
 * The address of the view is returned (NOVIEW if an error occurs).
 */
VIEW *newview(char *vname, char *svname)
{
	REGISTER VIEW *v;
	REGISTER char *ptr;

	/* error checks */
	for(ptr = vname; *ptr != 0; ptr++)
		if (*ptr <= ' ' || *ptr == ':' || *ptr >= 0177)
			return((VIEW *)db_error(DBBADNAME|DBNEWVIEW));
	for(ptr = svname; *ptr != 0; ptr++)
		if (*ptr <= ' ' || *ptr == ':' || *ptr >= 0177)
			return((VIEW *)db_error(DBBADNAME|DBNEWVIEW));
	for(v = el_views; v != NOVIEW; v = v->nextview)
		if (namesame(v->viewname, vname) == 0 || namesame(v->sviewname, svname) == 0)
			return((VIEW *)db_error(DBDUPLICATE|DBNEWVIEW));

	/* create the view */
	v = allocview();
	if (v == NOVIEW) return(NOVIEW);

	/* initialize view names */
	if (allocstring(&v->viewname, vname, db_cluster) != 0) return(NOVIEW);
	if (allocstring(&v->sviewname, svname, db_cluster) != 0) return(NOVIEW);
	if (namesamen(vname, "Schematic-Page-", 15) == 0) v->viewstate |= MULTIPAGEVIEW;

	/* insert in list of views */
	v->nextview = el_views;
	el_views = v;

	(void)db_change((INTBIG)v, OBJECTNEW, VVIEW, 0, 0, 0, 0, 0);

	/* return the view */
	return(v);
}

/*
 * routine to remove view "view".  Returns nonzero on error
 */
INTSML killview(VIEW *view)
{
	REGISTER VIEW *v, *lastv;
	REGISTER LIBRARY *lib;
	REGISTER NODEPROTO *np;

	/* make sure it is not one of the permanent (undeletable) views */
	if ((view->viewstate&PERMANENTVIEW) != 0) return(1);

	/* make sure it is not in any facet */
	for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
		for(np = lib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
			if (np->cellview == view) return(1);

	/* find the view */
	lastv = NOVIEW;
	for(v = el_views; v != NOVIEW; v = v->nextview)
	{
		if (v == view) break;
		lastv = v;
	}
	if (v == NOVIEW) return(1);

	/* delete the view */
	if (lastv == NOVIEW) el_views = v->nextview; else
		lastv->nextview = v->nextview;

	(void)db_change((INTBIG)v, OBJECTKILL, VVIEW, 0, 0, 0, 0, 0);
	return(0);
}

/*
 * routine to change the view type of facet "np" to "view".  Returns nonzero
 * upon error.
 */
INTSML changefacetview(NODEPROTO *np, VIEW *view)
{
	REGISTER NODEPROTO *rnp;

	if ((np->cellview->viewstate&TEXTVIEW) != 0 && (view->viewstate&TEXTVIEW) == 0)
	{
		ttyputerr("Sorry, textual views cannot be made nontextual");
		return(1);
	}
	if ((np->cellview->viewstate&TEXTVIEW) == 0 && (view->viewstate&TEXTVIEW) != 0)
	{
		ttyputerr("Sorry, nontextual views cannot be made textual");
		return(1);
	}

	/* remove this facet from the linked lists */
	db_retractnodeproto(np);

	/* assign a newer version number if there are others with the new view */
	for(rnp = np->cell->firstincell; rnp != NONODEPROTO; rnp = rnp->nextincell)
		if (rnp->cellview == view)
	{
		if (np->version <= rnp->version) np->version = rnp->version + 1;
		break;
	}

	/* set the view (sorry, not undoable) */
	np->cellview = view;

	/* reinsert the facet in the linked lists */
	db_insertnodeproto(np);
	return(0);
}

/*
 * routine to see if facet "np" has an icon, and if so, return that facet.
 * Returns NONODEPROTO if the facet does not have an icon
 */
NODEPROTO *iconview(NODEPROTO *np)
{
	REGISTER NODEPROTO *rnp;

	/* primitives have no icon view */
	if (np->primindex != 0) return(NONODEPROTO);

	/* iconview of an icon is itself */
	if (np->cellview == el_iconview) return(np);

	for(rnp = np->cell->firstincell; rnp != NONODEPROTO; rnp = rnp->nextincell)
		if (rnp->cellview == el_iconview) return(rnp);
	return(NONODEPROTO);
}

/*
 * routine to see if facet "np" has a VHDL view, and if so, return that facet.
 * Returns NONODEPROTO if the facet does not have a VHDL view
 */
NODEPROTO *vhdlview(NODEPROTO *np)
{
	REGISTER NODEPROTO *rnp;

	/* primitives have no VHDL view */
	if (np->primindex != 0) return(NONODEPROTO);

	/* VHDL view of VHDL is itself */
	if (np->cellview == el_vhdlview) return(np);

	for(rnp = np->cell->firstincell; rnp != NONODEPROTO; rnp = rnp->nextincell)
		if (rnp->cellview == el_vhdlview) return(rnp);
	return(NONODEPROTO);
}

/*
 * routine to see if facet "np" has a Verilog view, and if so, return that facet.
 * Returns NONODEPROTO if the facet does not have a Verilog view
 */
NODEPROTO *verilogview(NODEPROTO *np)
{
	REGISTER NODEPROTO *rnp;

	/* primitives have no Verilog view */
	if (np->primindex != 0) return(NONODEPROTO);

	/* Verilog view of Verilog is itself */
	if (np->cellview == el_verilogview) return(np);

	for(rnp = np->cell->firstincell; rnp != NONODEPROTO; rnp = rnp->nextincell)
		if (rnp->cellview == el_verilogview) return(rnp);
	return(NONODEPROTO);
}

/*
 * routine to see if facet "np" is an icon or a skeleton, and if so, return its
 * true contents facet.  Returns NONODEPROTO if the facet is not an icon
 */
NODEPROTO *contentsview(NODEPROTO *np)
{
	REGISTER NODEPROTO *rnp;

	/* primitives have no contents view */
	if (np == NONODEPROTO) return(NONODEPROTO);
	if (np->primindex != 0) return(NONODEPROTO);

	/* can only consider contents if this facet is an icon */
	if (np->cellview != el_iconview && np->cellview != el_skeletonview)
		return(NONODEPROTO);

	/* first check to see if there is a schematics link */
	for(rnp = np->cell->firstincell; rnp != NONODEPROTO; rnp = rnp->nextincell)
	{
		if (rnp->cellview == el_schematicview) return(rnp);
		if ((rnp->cellview->viewstate&MULTIPAGEVIEW) != 0) return(rnp);
	}

	/* now check to see if there is any layout link */
	for(rnp = np->cell->firstincell; rnp != NONODEPROTO; rnp = rnp->nextincell)
		if (rnp->cellview == el_layoutview) return(rnp);

	/* finally check to see if there is any "unknown" link */
	for(rnp = np->cell->firstincell; rnp != NONODEPROTO; rnp = rnp->nextincell)
		if (rnp->cellview == el_unknownview) return(rnp);

	/* no contents found */
	return(NONODEPROTO);
}

/* function to return a layout view if there is one */
NODEPROTO *layoutview(NODEPROTO *np)
{
	REGISTER NODEPROTO *rnp;

	if (np == NONODEPROTO) return(NONODEPROTO);

	/* must be complex */
	if (np->primindex != 0) return (NONODEPROTO);

	/* Now look for views */

	/* layout has first precedence */
	for(rnp = np->cell->firstincell; rnp != NONODEPROTO; rnp = rnp->nextincell)
		if (rnp->cellview == el_layoutview) return(rnp);

	/* schematics come next */
	for(rnp = np->cell->firstincell; rnp != NONODEPROTO; rnp = rnp->nextincell)
	{
		if (rnp->cellview == el_schematicview) return(rnp);
		if ((rnp->cellview->viewstate&MULTIPAGEVIEW) != 0) return(rnp);
	}

	/* then look for unknown */
	for(rnp = np->cell->firstincell; rnp != NONODEPROTO; rnp = rnp->nextincell)
		if (rnp->cellview == el_unknownview) return(rnp);

	/* keep what we have */
	return(NONODEPROTO);
}

/*
 * routine to obtain the equivalent port on a facet with an alternate view.
 * The original facet is "np" with port "pp".  The routine returns the port
 * on equivalent facet "enp".  If the port association cannot be found,
 * the routine returns NOPORTPROTO and prints an error message.
 */
PORTPROTO *equivalentport(NODEPROTO *np, PORTPROTO *pp, NODEPROTO *enp)
{
	REGISTER PORTPROTO *epp;

	/* don't waste time searching if the two views are the same */
	if (np == enp) return(pp);
	for(epp = enp->firstportproto; epp != NOPORTPROTO; epp = epp->nextportproto)
		if (namesame(epp->protoname, pp->protoname) == 0) return(epp);

	/* don't report errors for global ports not on icons */
	if (enp->cellview != el_iconview || (pp->userbits&BODYONLY) == 0)
		ttyputerr("Warning: no port in facet %s corresponding to port %s in facet %s",
			describenodeproto(enp), pp->protoname, describenodeproto(np));
	return(NOPORTPROTO);
}

/************************* SCHEMATIC WINDOWS *************************/

static GRAPHICS tech_frame = {LAYERO, MENTXT, SOLIDC, SOLIDC,
	{0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF}, NOVARIABLE, 0};

#define FRAMESCALE  700.0f
#define ASCHXSIZE   (11.0f  / FRAMESCALE)
#define ASCHYSIZE   ( 8.5f  / FRAMESCALE)
#define BSCHXSIZE   (17.0f  / FRAMESCALE)
#define BSCHYSIZE   (11.0f  / FRAMESCALE)
#define CSCHXSIZE   (24.0f  / FRAMESCALE)
#define CSCHYSIZE   (17.0f  / FRAMESCALE)
#define DSCHXSIZE   (36.0f  / FRAMESCALE)
#define DSCHYSIZE   (24.0f  / FRAMESCALE)
#define ESCHXSIZE   (48.0f  / FRAMESCALE)
#define ESCHYSIZE   (36.0f  / FRAMESCALE)
#define FRAMEWID    ( 0.15f / FRAMESCALE)
#define XLOGOBOX    ( 2.0f  / FRAMESCALE)
#define YLOGOBOX    ( 1.0f  / FRAMESCALE)

/*
 * routine to determine whether facet "np" should have a schematic frame drawn in it.
 * If so, returns zero and sets "x" and "y" to the size of the frame.  If not,
 * returns nonzero.
 */
INTSML framesize(INTBIG *x, INTBIG *y, NODEPROTO *np)
{
	char *size;
	float wid, hei;

	size = db_getframedesc(np);
	if (*size == 0) return(1);
	switch (size[0])
	{
		case 'a': wid = ASCHXSIZE;   hei = ASCHYSIZE;   break;
		case 'b': wid = BSCHXSIZE;   hei = BSCHYSIZE;   break;
		case 'c': wid = CSCHXSIZE;   hei = CSCHYSIZE;   break;
		case 'd': wid = DSCHXSIZE;   hei = DSCHYSIZE;   break;
		case 'e': wid = ESCHXSIZE;   hei = ESCHYSIZE;   break;
		default:  wid = DSCHXSIZE;   hei = DSCHYSIZE;   break;
	}
	if (size[1] == 'v')
	{
		*x = scalefromdispunit(hei - FRAMEWID, DISPUNITINCH);
		*y = scalefromdispunit(wid - FRAMEWID, DISPUNITINCH);
	} else
	{
		*x = scalefromdispunit(wid - FRAMEWID, DISPUNITINCH);
		*y = scalefromdispunit(hei - FRAMEWID, DISPUNITINCH);
	}
	return(0);
}

INTSML framepolys(NODEPROTO *np)
{
	REGISTER INTSML xsections, ysections, total;
	char *size;

	size = db_getframedesc(np);
	if (*size == 0) return(0);
	if (size[1] == 'v')
	{
		xsections = 4;
		ysections = 8;
	} else
	{
		xsections = 8;
		ysections = 4;
	}

	/* compute number of polygons */
	total = 2+(xsections-1)*2+(ysections-1)*2+xsections*2+ysections*2+10;
	return(total);
}

void framepoly(INTSML boxnum, POLYGON *poly, NODEPROTO *np)
{
	REGISTER INTSML pindex, xsections, ysections;
	REGISTER INTBIG xsecsize, ysecsize, schxsize, schysize, framewid, xlogobox, ylogobox;
	float wid, hei;
	REGISTER VARIABLE *var;
	static char line[10];
	static char ndstring[320];
	char *size;

	/* get true sizes */
	size = db_getframedesc(np);
	switch (size[0])
	{
		case 'a': wid = ASCHXSIZE;   hei = ASCHYSIZE;   break;
		case 'b': wid = BSCHXSIZE;   hei = BSCHYSIZE;   break;
		case 'c': wid = CSCHXSIZE;   hei = CSCHYSIZE;   break;
		case 'd': wid = DSCHXSIZE;   hei = DSCHYSIZE;   break;
		case 'e': wid = ESCHXSIZE;   hei = ESCHYSIZE;   break;
		default:  wid = DSCHXSIZE;   hei = DSCHYSIZE;   break;
	}
	framewid = scalefromdispunit(FRAMEWID, DISPUNITINCH);
	xlogobox = scalefromdispunit(XLOGOBOX, DISPUNITINCH);
	ylogobox = scalefromdispunit(YLOGOBOX, DISPUNITINCH);
	if (size[1] == 'v')
	{
		xsections = 4;
		ysections = 8;
		schxsize = scalefromdispunit(hei, DISPUNITINCH) - framewid;
		schysize = scalefromdispunit(wid, DISPUNITINCH) - framewid;
	} else
	{
		xsections = 8;
		ysections = 4;
		schxsize = scalefromdispunit(wid, DISPUNITINCH) - framewid;
		schysize = scalefromdispunit(hei, DISPUNITINCH) - framewid;
	}

	if (poly->limit < 4) (void)extendpolygon(poly, 4);

	xsecsize = (schxsize - framewid*2) / xsections;
	ysecsize = (schysize - framewid*2) / ysections;
	if (boxnum <= 1)
	{
		/* draw the frame */
		if (boxnum == 0)
		{
			poly->xv[0] = -schxsize/2;   poly->yv[0] = -schysize/2;
			poly->xv[1] =  schxsize/2;   poly->yv[1] =  schysize/2;
		} else
		{
			poly->xv[0] = -schxsize/2 + framewid;
			poly->yv[0] = -schysize/2 + framewid;
			poly->xv[1] =  schxsize/2 - framewid;
			poly->yv[1] =  schysize/2 - framewid;
		}
		poly->count = 2;
		poly->style = CLOSEDRECT;
	} else if (boxnum < xsections*2)
	{
		/* tick marks along top and bottom sides */
		pindex = (boxnum-2) % (xsections-1) + 1;
		poly->xv[0] = poly->xv[1] = pindex * xsecsize - (schxsize/2 - framewid);
		if (boxnum <= xsections)
		{
			poly->yv[0] = schysize/2 - framewid;
			poly->yv[1] = schysize/2 - framewid/2;
		} else
		{
			poly->yv[0] = -schysize/2 + framewid;
			poly->yv[1] = -schysize/2 + framewid/2;
		}
		poly->count = 2;
		poly->style = OPENED;
	} else if (boxnum < xsections*2+ysections*2-2)
	{
		/* tick marks along left and right sides */
		pindex = (boxnum-2-(xsections-1)*2) % (ysections-1) + 1;
		poly->yv[0] = poly->yv[1] = pindex * ysecsize - (schysize/2 - framewid);
		if (boxnum <= (xsections-1)*2+ysections)
		{
			poly->xv[0] = schxsize/2 - framewid;
			poly->xv[1] = schxsize/2 - framewid/2;
		} else
		{
			poly->xv[0] = -schxsize/2 + framewid;
			poly->xv[1] = -schxsize/2 + framewid/2;
		}
		poly->count = 2;
		poly->style = OPENED;
	} else if (boxnum < xsections*2+ysections*2-2+xsections*2)
	{
		/* section numbers along top and bottom */
		pindex = (boxnum-2-(xsections-1)*2-(ysections-1)*2) % xsections;
		poly->xv[0] = poly->xv[1] = pindex * xsecsize - (schxsize/2 - framewid);
		poly->xv[2] = poly->xv[3] = poly->xv[0] + xsecsize;
		if (boxnum <= (xsections-1)*2+(ysections-1)*2+xsections+1)
		{
			poly->yv[0] = poly->yv[3] = schysize/2 - framewid;
			poly->yv[1] = poly->yv[2] = schysize/2;
		} else
		{
			poly->yv[0] = poly->yv[3] = -schysize/2;
			poly->yv[1] = poly->yv[2] = -schysize/2 + framewid;
		}
		poly->count = 4;
		poly->style = TEXTBOX;
		poly->font = TXTMEDIUM;
		poly->tech = el_curtech;
		(void)sprintf(line, "%d", xsections-pindex);
		poly->string = line;
	} else if (boxnum < xsections*2+ysections*2-2+xsections*2+ysections*2)
	{
		/* section numbers along top and bottom */
		pindex = (boxnum-2-(xsections-1)*2-(ysections-1)*2-xsections*2) % ysections;
		poly->yv[0] = poly->yv[1] = pindex * ysecsize - (schysize/2 - framewid);
		poly->yv[2] = poly->yv[3] = poly->yv[0] + ysecsize;
		if (boxnum <= (xsections-1)*2+(ysections-1)*2+xsections*2+ysections+1)
		{
			poly->xv[0] = poly->xv[3] = schxsize/2 - framewid;
			poly->xv[1] = poly->xv[2] = schxsize/2;
		} else
		{
			poly->xv[0] = poly->xv[3] = -schxsize/2;
			poly->xv[1] = poly->xv[2] = -schxsize/2 + framewid;
		}
		poly->count = 4;
		poly->style = TEXTBOX;
		poly->font = TXTMEDIUM;
		poly->tech = el_curtech;
		line[0] = 'A' + pindex;
		line[1] = 0;
		poly->string = line;
	} else
	{
		switch (boxnum-(2+(xsections-1)*2+(ysections-1)*2+xsections*2+ysections*2))
		{
			case 0:			/* frame around facet name/version */
				poly->xv[0] =  schxsize/2 - framewid - xlogobox;
				poly->yv[0] = -schysize/2 + framewid + ylogobox / 3;
				poly->xv[1] =  schxsize/2 - framewid;
				poly->yv[1] = -schysize/2 + framewid + ylogobox / 2;
				poly->count = 2;
				poly->style = CLOSEDRECT;
				break;
			case 1:			/* facet name/version */
				if (np == NONODEPROTO) break;
				if ((np->cellview->viewstate&MULTIPAGEVIEW) == 0)
					(void)sprintf(ndstring, "Name: %s - - Page 1 of 1", np->cell->cellname); else
						(void)sprintf(ndstring, "Name: %s - - Page %s of %d", np->cell->cellname,
							&np->cellview->viewname[15], db_schematic_pages(np));
				poly->xv[0] = poly->xv[3] = schxsize/2 - framewid - xlogobox;
				poly->xv[1] = poly->xv[2] = schxsize/2 - framewid;
				poly->yv[0] = poly->yv[1] = -schysize/2 + framewid + ylogobox / 3;
				poly->yv[2] = poly->yv[3] = poly->yv[0] + ylogobox / 6;
				poly->count = 4;
				poly->style = TEXTBOX;
				poly->font = TXTMEDIUM;
				poly->tech = el_curtech;
				poly->string = ndstring;
				break;
			case 2:			/* frame around designer name */
				poly->xv[0] =  schxsize/2 - framewid - xlogobox;
				poly->yv[0] = -schysize/2 + framewid + ylogobox / 2;
				poly->xv[1] =  schxsize/2 - framewid;
				poly->yv[1] = -schysize/2 + framewid + ylogobox / 4 * 3;
				poly->count = 2;
				poly->style = CLOSEDRECT;
				break;
			case 3:			/* designer name */
				var = getval((INTBIG)us_aid, VAID, VSTRING, "USER_drawing_designer_name");
				if (var == NOVARIABLE) break;
				poly->xv[0] = poly->xv[3] = schxsize/2 - framewid - xlogobox;
				poly->xv[1] = poly->xv[2] = schxsize/2 - framewid;
				poly->yv[0] = poly->yv[1] = -schysize/2 + framewid + ylogobox / 2;
				poly->yv[2] = poly->yv[3] = poly->yv[0] + ylogobox / 4;
				poly->count = 4;
				poly->style = TEXTBOX;
				poly->font = TXTLARGE;
				poly->tech = el_curtech;
				poly->string = (char *)var->addr;
				break;
			case 4:			/* frame around creation date */
				poly->xv[0] =  schxsize/2 - framewid - xlogobox;
				poly->yv[0] = -schysize/2 + framewid + ylogobox / 6;
				poly->xv[1] =  schxsize/2 - framewid;
				poly->yv[1] = -schysize/2 + framewid + ylogobox / 3;
				poly->count = 2;
				poly->style = CLOSEDRECT;
				break;
			case 5:			/* creation date */
				if (np == NONODEPROTO) break;
				(void)sprintf(ndstring, "Created: %s", timetostring(np->creationdate));
				poly->xv[0] = poly->xv[3] = schxsize/2 - framewid - xlogobox;
				poly->xv[1] = poly->xv[2] = schxsize/2 - framewid;
				poly->yv[0] = poly->yv[1] = -schysize/2 + framewid + ylogobox / 6;
				poly->yv[2] = poly->yv[3] = poly->yv[0] + ylogobox / 6;
				poly->count = 4;
				poly->style = TEXTBOX;
				poly->font = TXTMEDIUM;
				poly->tech = el_curtech;
				poly->string = ndstring;
				break;
			case 6:			/* frame around revision date */
				poly->xv[0] =  schxsize/2 - framewid - xlogobox;
				poly->yv[0] = -schysize/2 + framewid;
				poly->xv[1] =  schxsize/2 - framewid;
				poly->yv[1] = -schysize/2 + framewid + ylogobox / 6;
				poly->count = 2;
				poly->style = CLOSEDRECT;
				break;
			case 7:			/* revision date */
				if (np == NONODEPROTO) break;
				(void)sprintf(ndstring, "Revised: %s", timetostring(np->revisiondate));
				poly->xv[0] = poly->xv[3] = schxsize/2 - framewid - xlogobox;
				poly->xv[1] = poly->xv[2] = schxsize/2 - framewid;
				poly->yv[0] = poly->yv[1] = -schysize/2 + framewid;
				poly->yv[2] = poly->yv[3] = poly->yv[0] + ylogobox / 6;
				poly->count = 4;
				poly->style = TEXTBOX;
				poly->font = TXTMEDIUM;
				poly->tech = el_curtech;
				poly->string = ndstring;
				break;
			case 8:			/* frame around company name */
				poly->xv[0] =  schxsize/2 - framewid - xlogobox;
				poly->yv[0] = -schysize/2 + framewid + ylogobox / 4 * 3;
				poly->xv[1] =  schxsize/2 - framewid;
				poly->yv[1] = -schysize/2 + framewid + ylogobox;
				poly->count = 2;
				poly->style = CLOSEDRECT;
				break;
			case 9:			/* company name */
				var = getval((INTBIG)us_aid, VAID, VSTRING, "USER_drawing_company_name");
				if (var == NOVARIABLE) break;
				poly->xv[0] = poly->xv[3] = schxsize/2 - framewid - xlogobox;
				poly->xv[1] = poly->xv[2] = schxsize/2 - framewid;
				poly->yv[0] = poly->yv[1] = -schysize/2 + framewid + ylogobox / 4 * 3;
				poly->yv[2] = poly->yv[3] = poly->yv[0] + ylogobox / 4;
				poly->count = 4;
				poly->style = TEXTBOX;
				poly->font = TXTLARGE;
				poly->tech = el_curtech;
				poly->string = (char *)var->addr;
				break;
		}
	}
	poly->layer = -1;
	poly->desc = &tech_frame;
}

/*
 * count the total number of pages in this facet's multipage schematic view
 */
INTSML db_schematic_pages(NODEPROTO *np)
{
	REGISTER NODEPROTO *onp;
	REGISTER INTSML count, maxpage, thispage;

	count = maxpage = 0;
	for (onp = np->cell->firstincell; onp != NONODEPROTO; onp = onp->nextincell)
		if ((onp->cellview->viewstate&MULTIPAGEVIEW) != 0)
	{
		count++;
		thispage = atoi(&onp->cellview->viewname[15]);
		if (thispage > maxpage) maxpage = thispage;
		if (thispage < 1)
			ttyputerr("Schematic page %d found in %s - should be positive integer",
				thispage, onp->cellview->viewname);
	}
	if (count < maxpage) ttyputerr("Missing schematic pages in %s", np->cell->cellname);
	return(maxpage);
}

char *db_getframedesc(NODEPROTO *np)
{
	static char line[10];
	REGISTER VARIABLE *var;
	REGISTER char *pt;

	(void)strcpy(line, "");
	if (np != NONODEPROTO)
	{
		var = getvalkey((INTBIG)np, VNODEPROTO, VSTRING, el_schematic_page_size);
		if (var != NOVARIABLE)
		{
			(void)strcpy(line, (char *)var->addr);
			for(pt = line; *pt != 0; pt++) if (isupper(*pt)) *pt = tolower(*pt);
		}
	}
	return(line);
}

/************************* COPYING AND REPLACING *************************/

/*
 * routine to copy nodeproto "fromnt" to the library "tolib" with the nodeproto
 * name "toname".  Returns address of new nodeproto copy if sucessful,
 * NONODEPROTO if not.
 */
NODEPROTO *copynodeproto(NODEPROTO *fromnp, LIBRARY *tolib, char *toname)
{
	NODEINST *ono[2];
	PORTPROTO *opt[2];
	REGISTER NODEINST *ni, *toni;
	REGISTER ARCINST *ai, *toai;
	REGISTER NODEPROTO *np, *lnt;
	REGISTER PORTARCINST *pi;
	REGISTER PORTEXPINST *pe;
	REGISTER CELL *c;
	REGISTER INTSML i, failures, res;
	REGISTER PORTPROTO *pp, *ppt, *p1, *p2;
	REGISTER char *ptr, *facetname;
	REGISTER LIBRARY *destlib;
	INTBIG lx, hx, ly, hy;

	/* check for validity */
	if (fromnp == NONODEPROTO) return((NODEPROTO *)db_error(DBBADFACET|DBCOPYNODEPROTO));
	if (tolib == NOLIBRARY) return((NODEPROTO *)db_error(DBBADLIB|DBCOPYNODEPROTO));
	if (fromnp->primindex != 0) return((NODEPROTO *)db_error(DBPRIMITIVE|DBCOPYNODEPROTO));

	/* make sure name of new facet is valid */
	for(ptr = toname; *ptr != 0; ptr++)
		if (*ptr <= ' ' || *ptr == ':' || *ptr >= 0177)
			return((NODEPROTO *)db_error(DBBADNAME|DBCOPYNODEPROTO));

	/* determine whether this copy is to a different library */
	if (tolib == fromnp->cell->lib) destlib = NOLIBRARY; else destlib = tolib;

	/* ensure that all copied facets are the same in the destination library */
	if (destlib != NOLIBRARY)
		for(ni = fromnp->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		if (ni->proto->primindex != 0) continue;

		/* keep cross-library references */
		if (ni->proto->cell->lib != fromnp->cell->lib) continue;

		/* search for facet with same name and view in new library */
		for(lnt = tolib->firstnodeproto; lnt != NONODEPROTO; lnt = lnt->nextnodeproto)
			if (namesame(lnt->cell->cellname, ni->proto->cell->cellname) == 0 &&
				lnt->cellview == ni->proto->cellview) break;
		if (lnt == NONODEPROTO)
		{
			ttyputerr("Can't find %s facet in destination library", describenodeproto(ni->proto));
			return(NONODEPROTO);
		}

		/* make sure all used ports can be found on the uncopied facet */
		for(pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
		{
			pp = pi->proto;
			for(ppt = lnt->firstportproto; ppt != NOPORTPROTO; ppt = ppt->nextportproto)
				if (namesame(pp->protoname, ppt->protoname) == 0 && pp->connects == ppt->connects)
					break;
			if (ppt == NOPORTPROTO)
			{
				ttyputerr("Cannot find port %s in facet %s", pp->protoname, describenodeproto(lnt));
				return(NONODEPROTO);
			}
		}
		for(pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
		{
			pp = pe->proto;
			for(ppt = lnt->firstportproto; ppt != NOPORTPROTO; ppt = ppt->nextportproto)
				if (namesame(pp->protoname, ppt->protoname) == 0 && pp->connects == ppt->connects)
					break;
			if (ppt == NOPORTPROTO)
			{
				ttyputerr("Cannot find port %s in facet %s", pp->protoname, describenodeproto(lnt));
				return(NONODEPROTO);
			}
		}
	}

	/* create the nodeproto */
	if (toname[strlen(toname)-1] == '}' || fromnp->cellview->sviewname[0] == 0)
		facetname = toname; else
	{
		(void)initinfstr();
		(void)addstringtoinfstr(toname);
		(void)addtoinfstr('{');
		(void)addstringtoinfstr(fromnp->cellview->sviewname);
		(void)addtoinfstr('}');
		facetname = returninfstr();
	}
	np = newnodeproto(facetname, tolib);
	if (np == NONODEPROTO) return(NONODEPROTO);
	np->userbits = fromnp->userbits;

	/* zero the count of variables that failed to copy */
	failures = 0;

	/* copy nodes */
	for(ni = fromnp->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		/* figure out nodeproto */
		if (ni->proto->primindex != 0 || destlib == NOLIBRARY ||
			ni->proto->cell->lib != fromnp->cell->lib)
		{
			/*
			 * primitive, copying within a library, or already a cross-lib reference:
			 * use same prototype
			 */
			lnt = ni->proto;
		} else
		{
			/* using facet from destination library: find it */

			/* first find the cell by name */
			for(c = tolib->firstcell; c != NOCELL; c = c->nextcell)
				if (namesame(ni->proto->cell->cellname, c->cellname) == 0) break;
			if (c == NOCELL)
			{
				ttyputerr("Error: no cell %s (used in facet %s)", ni->proto->cell->cellname,
					describenodeproto(fromnp));
				return(NONODEPROTO);
			}

			/* now find the proper view of the cell */
			for(lnt = c->firstincell; lnt != NONODEPROTO; lnt = lnt->nextincell)
				if (lnt->cellview == ni->proto->cellview) break;
			if (lnt == NONODEPROTO)
			{
				ttyputerr("Error: no %s view of cell %s (used in facet %s)",
					ni->proto->cellview->viewname, ni->proto->cell->cellname,
						describenodeproto(fromnp));
				return(NONODEPROTO);
			}
		}

		/* create the new nodeinst */
		toni = db_newnodeinst(lnt, ni->lowx, ni->highx, ni->lowy, ni->highy,
			ni->transpose, ni->rotation, np);
		if (toni == NONODEINST) return(NONODEPROTO);

		/* save the new nodeinst address in the old nodeinst */
		ni->temp1 = (UINTBIG)toni;

		/* copy miscellaneous information */
		toni->textdescript = ni->textdescript;
		toni->userbits = ni->userbits;
	}

	/* now copy the variables on the nodes */
	for(ni = fromnp->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		toni = (NODEINST *)ni->temp1;
		res = db_copyxlibvars((INTBIG)ni, VNODEINST, (INTBIG)toni, VNODEINST, fromnp, destlib);
		if (res < 0) return(NONODEPROTO);
		failures += res;

		/* variables may affect geometry size */
		boundobj(toni->geom, &lx, &hx, &ly, &hy);
		if (lx != toni->geom->lowx || hx != toni->geom->highx ||
			ly != toni->geom->lowy || hy != toni->geom->highy)
				updategeom(toni->geom, toni->parent);
	}

	/* copy arcs */
	for(ai = fromnp->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
	{
		/* find the nodeinst and portinst connections for this arcinst */
		for(i=0; i<2; i++)
		{
			opt[i] = NOPORTPROTO;
			ono[i] = (NODEINST *)ai->end[i].nodeinst->temp1;
			if (ono[i]->proto->primindex != 0)
			{
				/* primitives associate ports directly */
				for(pp = ai->end[i].nodeinst->proto->firstportproto,
					ppt = ono[i]->proto->firstportproto; pp != NOPORTPROTO;
						pp = pp->nextportproto, ppt = ppt->nextportproto)
							if (pp == ai->end[i].portarcinst->proto)
				{
					opt[i] = ppt;
					break;
				}
			} else
			{
				/* facets associate ports by name */
				pp = ai->end[i].portarcinst->proto;
				for(ppt = ono[i]->proto->firstportproto; ppt != NOPORTPROTO; ppt = ppt->nextportproto)
					if (namesame(ppt->protoname, pp->protoname) == 0)
				{
					opt[i] = ppt;
					break;
				}
			}
			if (opt[i] == NOPORTPROTO)
				ttyputerr("Error: no port for %s arc on %s node", describearcproto(ai->proto),
					describenodeproto(ai->end[i].nodeinst->proto));
		}

		/* create the arcinst */
		toai = db_newarcinst(ai->proto, ai->width, ai->userbits, ono[0],opt[0],
			ai->end[0].xpos, ai->end[0].ypos, ono[1],opt[1], ai->end[1].xpos, ai->end[1].ypos, np);
		if (toai == NOARCINST) return(NONODEPROTO);

		/* copy arcinst variables */
		res = db_copyxlibvars((INTBIG)ai, VARCINST, (INTBIG)toai, VARCINST, fromnp, destlib);
		if (res < 0) return(NONODEPROTO);
		failures += res;
		res = db_copyxlibvars((INTBIG)ai->end[0].portarcinst, VPORTARCINST,
			(INTBIG)toai->end[0].portarcinst, VPORTARCINST, fromnp, destlib);
		if (res < 0) return(NONODEPROTO);
		failures += res;
		res = db_copyxlibvars((INTBIG)ai->end[1].portarcinst, VPORTARCINST,
			(INTBIG)toai->end[1].portarcinst, VPORTARCINST, fromnp, destlib);
		if (res < 0) return(NONODEPROTO);
		failures += res;

		/* copy miscellaneous information */
		toai->userbits = ai->userbits;

		/* variables may affect geometry size */
		boundobj(toai->geom, &lx, &hx, &ly, &hy);
		if (lx != toai->geom->lowx || hx != toai->geom->highx ||
			ly != toai->geom->lowy || hy != toai->geom->highy)
				updategeom(toai->geom, toai->parent);
	}

	/* copy the portprotos */
	for(pp = fromnp->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
	{
		/* match sub-portproto in old nodeinst to sub-portproto in new one */
		ni = (NODEINST *)pp->subnodeinst->temp1;
		for(p1 = ni->proto->firstportproto, p2 = pp->subnodeinst->proto->firstportproto;
			p1 != NOPORTPROTO && p2 != NOPORTPROTO;
				p1 = p1->nextportproto, p2 = p2->nextportproto)
					if (pp->subportproto == p2) break;
		if (pp->subportproto != p2)
			ttyputerr("Error: no port on %s facet", describenodeproto(pp->subnodeinst->proto));

		/* create the nodeinst portinst */
		ppt = db_newportproto(np, ni, p1, pp->protoname);
		if (ppt == NOPORTPROTO) return(NONODEPROTO);

		/* copy portproto variables */
		res = db_copyxlibvars((INTBIG)pp, VPORTPROTO, (INTBIG)ppt, VPORTPROTO, fromnp, destlib);
		if (res < 0) return(NONODEPROTO);
		failures += res;
		res = db_copyxlibvars((INTBIG)pp->subportexpinst, VPORTEXPINST,
			(INTBIG)ppt->subportexpinst, VPORTEXPINST, fromnp, destlib);
		if (res < 0) return(NONODEPROTO);
		failures += res;

		/* copy miscellaneous information */
		ppt->userbits = pp->userbits;
		ppt->textdescript = pp->textdescript;
	}

	/* copy facet variables */
	res = db_copyxlibvars((INTBIG)fromnp, VNODEPROTO, (INTBIG)np, VNODEPROTO, fromnp, destlib);
	if (res < 0) return(NONODEPROTO);
	failures += res;

	/* report failures to copy variables across libraries */
	if (failures != 0)
		ttyputmsg("WARNING: cross-library copy of facet %s deleted %d variables",
			describenodeproto(fromnp), failures);

	/* reset (copy) date information */
	np->creationdate = fromnp->creationdate;
	np->revisiondate = fromnp->revisiondate;

	return(np);
}

/*
 * helper routine for "copynodeproto()" to copy the variables in the list
 * "fromfirstvar"/"fromnumvar" to "tofirstvar"/"tonumvar".  The variables are
 * originally part of facet "facet",  If "destlib" is not NOLIBRARY,
 * this copy is from a different library and, therefore, pointers to objects in one library
 * should be converted if possible, or not copied.  Returns negative on error, positive
 * to indicate the number of variables that could not be copied (because of cross-
 * library reference inablilties), and zero for complete success.
 *
 * Note that there is only a limited number of cross-library conversion functions
 * implemented, specifically those that are used elsewhere in Electric.
 * At this time, the routine can copy NODEPROTO pointers (scalar and array)
 * and it can copy NODEINST pointers (scalar only).
 */
INTSML db_copyxlibvars(INTBIG fromaddr, INTBIG fromtype, INTBIG toaddr,
	INTBIG totype, NODEPROTO *facet, LIBRARY *destlib)
{
	REGISTER INTSML i, j, skipit, failures;
	REGISTER INTBIG key, addr, type, len, *newaddr;
	INTSML *numvar;
	VARIABLE **firstvar;
	REGISTER VARIABLE *var;
	REGISTER NODEPROTO *np, *onp;
	REGISTER NODEINST *ni;

	if (db_getvarptr(fromaddr, fromtype, &firstvar, &numvar) != 0) return(-1);

	failures = 0;
	for(i=0; i<(*numvar); i++)
	{
		key = (*firstvar)[i].key;
		addr = (*firstvar)[i].addr;
		type = (*firstvar)[i].type;
		newaddr = 0;
		skipit = 0;
		if (destlib != NOLIBRARY)
		{
			switch (type&VTYPE)
			{
				case VNODEPROTO:
					if ((type&VISARRAY) == 0)
					{
						np = (NODEPROTO *)addr;
						if (np == NONODEPROTO) break;
						if (np->primindex != 0) break;
						for(onp = destlib->firstnodeproto; onp != NONODEPROTO; onp = onp->nextnodeproto)
							if (namesame(onp->cell->cellname, np->cell->cellname) == 0 &&
								onp->cellview == np->cellview) break;
						if (onp != NONODEPROTO)
						{
							addr = (INTBIG)onp;
							break;
						}
					} else
					{
						len = (type&VLENGTH) >> VLENGTHSH;
						if (len != 0)
						{
							newaddr = (INTBIG *)emalloc(len * SIZEOFINTBIG, el_tempcluster);
							if (newaddr == 0) return(-1);
							for(j=0; j<len; j++)
							{
								np = ((NODEPROTO **)addr)[j];
								newaddr[j] = (INTBIG)np;
								if (np == NONODEPROTO) continue;
								if (np->primindex != 0) continue;
								for(onp = destlib->firstnodeproto; onp != NONODEPROTO;
									onp = onp->nextnodeproto)
										if (namesame(onp->cell->cellname, np->cell->cellname) == 0 &&
											onp->cellview == np->cellview) break;
								newaddr[j] = (INTBIG)onp;
								if (onp == NONODEPROTO) failures++;
							}
							addr = (INTBIG)newaddr;
							break;
						}
					}
					skipit = 1;
					failures++;
					break;
				case VPORTARCINST:
				case VPORTEXPINST:
				case VPORTPROTO:
				case VARCINST:
				case VGEOM:
				case VRTNODE:
				case VLIBRARY:
					skipit = 1;
					failures++;
					break;
			}
		}

		/* always convert NODEINST references, regardless of destination library */
		if ((type&VTYPE) == VNODEINST)
		{
			if ((type&VISARRAY) == 0)
			{
				ni = (NODEINST *)addr;
				if (ni != NONODEINST)
				{
					if (ni->parent == facet) addr = ni->temp1;
				}
			} else
			{
				skipit = 1;
				failures++;
			}
		}

		if (skipit != 0) continue;
		var = setvalkey(toaddr, totype, key, addr, type);
		if (var == NOVARIABLE) return(-1);
		var->textdescript = (*firstvar)[i].textdescript;
		if (newaddr != 0) efree((char *)newaddr);
	}
	return(failures);
}

/*
 * routine to replace nodeinst "ni" with one of type "np", leaving all arcs
 * intact.  The routine returns the address of the new replacing nodeinst if
 * successful, NONODEINST if the replacement cannot be done.
 */
NODEINST *replacenodeinst(NODEINST *ni, NODEPROTO *np)
{
	REGISTER PORTPROTO *pp, *opt;
	INTBIG endx[2], endy[2], bx, by, cx, cy, lxo, hxo, lyo, hyo, lx, hx, ly, hy, psx, psy,
		arcdx, arcdy, arccount, thisend, xp, yp;
	REGISTER INTSML i, zigzag, ang, fixedalignment;
	XARRAY trans;
	REGISTER VARIABLE *varold, *varnew;
	NODEINST *endnodeinst[2];
	PORTPROTO *endportproto[2];
	REGISTER PORTARCINST *pi, *nextpi;
	REGISTER PORTEXPINST *pe;
	REGISTER ARCINST *ai, *newai, *newai2, *aiswap;
	REGISTER NODEPROTO *pinnp;
	REGISTER NODEINST *newno, *newni;
	static POLYGON *poly = NOPOLYGON, *opoly = NOPOLYGON;

	/* make sure there is a polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, db_cluster);
	if (opoly == NOPOLYGON) opoly = allocstaticpolygon(4, db_cluster);

	/* check for recursion */
	if (isachildof(ni->parent, np))
		return((NODEINST *)db_error(DBRECURSIVE|DBREPLACENODEINST));

	/* set the new node size */
	fixedalignment = 0;
	if (np->primindex == 0)
	{
		/* facet replacement: determine location of replacement */
		lx = ni->lowx;   hx = lx + np->highx - np->lowx;
		ly = ni->lowy;   hy = ly + np->highy - np->lowy;

		/* adjust position for facet center alignment */
		varold = getvalkey((INTBIG)ni->proto, VNODEPROTO, VINTEGER|VISARRAY, el_prototype_center);
		varnew = getvalkey((INTBIG)np, VNODEPROTO, VINTEGER|VISARRAY, el_prototype_center);
		if (varold != NOVARIABLE && varnew != NOVARIABLE) fixedalignment = 1;
		corneroffset(ni, ni->proto, ni->rotation, ni->transpose, &bx, &by, 0);
		corneroffset(NONODEINST, np, ni->rotation, ni->transpose, &cx, &cy, 0);
		lx += bx-cx;   hx += bx-cx;
		ly += by-cy;   hy += by-cy;
	} else
	{
		/* primitive replacement: compute proper size of new part */
		nodesizeoffset(ni, &lxo, &lyo, &hxo, &hyo);
		nodeprotosizeoffset(np, &lx, &ly, &hx, &hy);
		lx = ni->lowx + lxo - lx;   hx = ni->highx - hxo + hx;
		ly = ni->lowy + lyo - ly;   hy = ni->highy - hyo + hy;
	}

	/* first create the new nodeinst in place */
	newno = db_newnodeinst(np, lx, hx, ly, hy, ni->transpose, ni->rotation, ni->parent);
	if (newno == NONODEINST) return(NONODEINST);

	/* set the change facet environment */
	db_setchangefacet(ni->parent);

	/* draw new node expanded if appropriate */
	newno->userbits |= ni->userbits&NEXPAND;

	/* associate the ports between these nodes */
	if (db_portassociate(ni, newno) != 0)
	{
		db_killnodeinst(newno);
		return((NODEINST *)db_error(DBNOMEM|DBREPLACENODEINST));
	}

	/* see if the old arcs can connect to ports */
	arcdx = arcdy = 0;
	arccount = 0;
	for(pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
	{
		/* make sure there is an association for this port */
		opt = (PORTPROTO *)pi->proto->temp1;
		if (opt == NOPORTPROTO)
		{
			if (db_printerrors != 0)
				ttyputmsg("No port on new node corresponds to old port %s", pi->proto->protoname);
			db_killnodeinst(newno);
			return((NODEINST *)db_error(DBPORTMM|DBREPLACENODEINST));
		}

		/* make sure the arc can connect to this type of port */
		ai = pi->conarcinst;
		for(i = 0; opt->connects[i] != NOARCPROTO; i++)
			if (opt->connects[i] == ai->proto) break;
		if (opt->connects[i] == NOARCPROTO)
		{
			if (db_printerrors != 0)
				ttyputmsg("%s arc on old port %s cannot connect to new port %s",
					describearcinst(ai), pi->proto->protoname, opt->protoname);
			db_killnodeinst(newno);
			return((NODEINST *)db_error(DBPORTMM|DBREPLACENODEINST));
		}

		/* see if the arc fits in the new port */
		if (ai->end[0].portarcinst == pi) thisend = 0; else thisend = 1;
		shapeportpoly(newno, opt, poly, 0);
		if (isinside(ai->end[thisend].xpos, ai->end[thisend].ypos, poly) == 0)
		{
			/* arc doesn't fit: accumulate error distance */
			portposition(newno, opt, &xp, &yp);
			arcdx += xp - ai->end[thisend].xpos;
			arcdy += yp - ai->end[thisend].ypos;
		}
		arccount++;
	}
	if (fixedalignment == 0)
	{
		if (arccount > 0)
		{
			arcdx /= arccount;   arcdy /= arccount;
			if (arcdx != 0 || arcdy != 0)
			{
				makeangle(newno->rotation, newno->transpose, trans);
				xform(arcdx, arcdy, &arcdx, &arcdy, trans);
				modifynodeinst(newno, arcdx, arcdy, arcdx, arcdy, 0, 0);
			}
		}
	}

	/* see if the old export ports are the same */
	for(pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
	{
		/* make sure there is an association for this port */
		opt = (PORTPROTO *)pe->proto->temp1;
		if (opt == NOPORTPROTO)
		{
			if (db_printerrors != 0)
				ttyputmsg("No port on new node corresponds to old port %s", pe->proto->protoname);
			db_killnodeinst(newno);
			return((NODEINST *)db_error(DBPORTMM|DBREPLACENODEINST));
		}

		/* ensure that all arcs connected at exported sites still connect */
		if (db_doesntconnect(opt->connects, pe->exportproto) != 0)
		{
			db_killnodeinst(newno);
			return((NODEINST *)db_error(DBPORTMM|DBREPLACENODEINST));
		}
	}

	/* replace the arcs */
	for(pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = nextpi)
	{
		nextpi = pi->nextportarcinst;
		ai = pi->conarcinst;
		if ((ai->userbits&DEADA) != 0) continue;
		for(i=0; i<2; i++)
		{
			if (ai->end[i].nodeinst == ni)
			{
				endnodeinst[i] = newno;
				endportproto[i] = (PORTPROTO *)pi->proto->temp1;
				endx[i] = ai->end[i].xpos;   endy[i] = ai->end[i].ypos;
				shapeportpoly(newno, endportproto[i], poly, 0);
				if (isinside(endx[i], endy[i], poly) == 0) getcenter(poly, &endx[i], &endy[i]);
			} else
			{
				endnodeinst[i] = ai->end[i].nodeinst;
				endportproto[i] = ai->end[i].portarcinst->proto;
				endx[i] = ai->end[i].xpos;   endy[i] = ai->end[i].ypos;
			}
		}
		if (endportproto[0] == NOPORTPROTO || endportproto[1] == NOPORTPROTO)
		{
			ttyputerr("Cannot re-connect %s arc", describearcinst(ai));
			continue;
		}

		/* see if a bend must be made in the wire */
		zigzag = 0;
		if ((ai->userbits&FIXANG) != 0)
		{
			if (endx[0] != endx[1] || endy[0] != endy[1])
			{
				i = figureangle(endx[0],endy[0], endx[1],endy[1]);
				ang = ((INTSML)((ai->userbits&AANGLE) >> AANGLESH)) * 10;
				if (i%1800 != ang%1800) zigzag = 1;
			}
		}
		if (zigzag != 0)
		{
			/* make that two wires */
			cx = endx[0];   cy = endy[1];
			pinnp = getpinproto(ai->proto);
			defaultnodesize(pinnp, &psx, &psy);
			lx = cx - psx / 2;
			hx = lx + psx;
			ly = cy - psy / 2;
			hy = ly + psy;
			newni = db_newnodeinst(pinnp, lx,hx, ly,hy, 0, 0, ni->parent);
			newai = db_newarcinst(ai->proto, ai->width, ai->userbits, endnodeinst[0],
				endportproto[0], endx[0],endy[0], newni, pinnp->firstportproto, cx, cy, ni->parent);
			i = (INTSML)ai->userbits;
			if ((i&ISNEGATED) != 0) i &= ~ISNEGATED;
			newai2 = db_newarcinst(ai->proto, ai->width, i, newni, pinnp->firstportproto, cx, cy,
				endnodeinst[1], endportproto[1], endx[1],endy[1], ni->parent);
			if (newai == NOARCINST || newai2 == NOARCINST) return(NONODEINST);
			if (copyvars((INTBIG)ai->end[0].portarcinst, VPORTARCINST,
				(INTBIG)newai->end[0].portarcinst, VPORTARCINST) != 0)
					return((NODEINST *)db_error(DBREPLACENODEINST|DBNOMEM));
			if (copyvars((INTBIG)ai->end[1].portarcinst, VPORTARCINST,
				(INTBIG)newai2->end[1].portarcinst, VPORTARCINST) != 0)
					return((NODEINST *)db_error(DBREPLACENODEINST|DBNOMEM));
			if (endnodeinst[1] == ni)
			{
				aiswap = newai;   newai = newai2;   newai2 = aiswap;
			}
			if (copyvars((INTBIG)ai, VARCINST, (INTBIG)newai, VARCINST) != 0)
				return((NODEINST *)db_error(DBREPLACENODEINST|DBNOMEM));
		} else
		{
			/* replace the arc with another arc */
			newai = db_newarcinst(ai->proto, ai->width, ai->userbits, endnodeinst[0],
				endportproto[0], endx[0],endy[0], endnodeinst[1], endportproto[1], endx[1],endy[1],
					ni->parent);
			if (newai == NOARCINST) return(NONODEINST);
			if (copyvars((INTBIG)ai, VARCINST, (INTBIG)newai, VARCINST) != 0)
				return((NODEINST *)db_error(DBREPLACENODEINST|DBNOMEM));
			for(i=0; i<2; i++)
				if (copyvars((INTBIG)ai->end[i].portarcinst, VPORTARCINST,
					(INTBIG)newai->end[i].portarcinst, VPORTARCINST) != 0)
						return((NODEINST *)db_error(DBREPLACENODEINST|DBNOMEM));
		}
		db_killarcinst(ai);
	}

	/* now replace the exported ports */
	for(;;)
	{
		for(pp = ni->parent->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
		{
			if (pp->subnodeinst != ni) continue;
			if (moveportproto(ni->parent, pp, newno,
				(PORTPROTO *)pp->subportexpinst->proto->temp1) == 0) break;
		}
		if (pp == NOPORTPROTO) break;
	}

	/* copy all variables on the nodeinst */
	(void)copyvars((INTBIG)ni, VNODEINST, (INTBIG)newno, VNODEINST);
	newno->textdescript = ni->textdescript;

	/* now delete the original nodeinst */
	db_killnodeinst(ni);
	return(newno);
}

/*
 * routine to associate the ports on node instances "ni1" and "ni2".
 * Each port prototype on "ni1" will have the address of the corresponding
 * port prototype on "ni2" in its "temp1" field (NOPORTPROTO if the match
 * cannot be found).  The routine returns nonzero if there is an error.
 */
INTSML db_portassociate(NODEINST *ni1, NODEINST *ni2)
{
	REGISTER INTSML i, j, total2;
	REGISTER INTBIG *xpos2, *ypos2;
	INTBIG xpos1, ypos1;
	REGISTER PORTPROTO *pp1, *pp2, *mpt;
	static POLYGON *poly1 = NOPOLYGON, *poly2 = NOPOLYGON;

	/* make sure there is a polygon */
	if (poly1 == NOPOLYGON) poly1 = allocstaticpolygon(4, db_cluster);
	if (poly2 == NOPOLYGON) poly2 = allocstaticpolygon(4, db_cluster);

	/* initialize */
	for(total2 = 0, pp2 = ni2->proto->firstportproto; pp2 != NOPORTPROTO; pp2 = pp2->nextportproto)
	{
		pp2->temp1 = (INTBIG)NOPORTPROTO;
		total2++;
	}
	for(pp1 = ni1->proto->firstportproto; pp1 != NOPORTPROTO; pp1 = pp1->nextportproto)
		pp1->temp1 = (INTBIG)NOPORTPROTO;

	/* create center-position arrays for the ports on node 2 */
	xpos2 = emalloc((total2 * SIZEOFINTBIG), el_tempcluster);
	if (xpos2 == 0) return(1);
	ypos2 = emalloc((total2 * SIZEOFINTBIG), el_tempcluster);
	if (ypos2 == 0) return(1);
	for(i = 0, pp2 = ni2->proto->firstportproto; pp2 != NOPORTPROTO; pp2 = pp2->nextportproto, i++)
	{
		shapeportpoly(ni2, pp2, poly2, 0);
		getcenter(poly2, &xpos2[i], &ypos2[i]);
	}

	/* associate on port name matches */
	for(pp1 = ni1->proto->firstportproto; pp1 != NOPORTPROTO; pp1 = pp1->nextportproto)
	{
		for(pp2 = ni2->proto->firstportproto; pp2 != NOPORTPROTO; pp2 = pp2->nextportproto)
			if (pp2->temp1 == -1)
		{
			/* stop if the ports have different name */
			if (namesame(pp2->protoname, pp1->protoname) != 0) continue;

			/* store the correct association of ports */
			pp1->temp1 = (INTBIG)pp2;
			pp2->temp1 = (INTBIG)pp1;
		}
	}

	/* associate ports that are in the same position */
	for(pp1 = ni1->proto->firstportproto; pp1 != NOPORTPROTO; pp1 = pp1->nextportproto)
		if ((PORTPROTO *)pp1->temp1 == NOPORTPROTO)
	{
		shapeportpoly(ni1, pp1, poly1, 0);
		getcenter(poly1, &xpos1, &ypos1);
		for(i = 0, pp2 = ni2->proto->firstportproto; pp2 != NOPORTPROTO; pp2 = pp2->nextportproto, i++)
		{
			/* if this port is already associated, ignore it */
			if (pp2->temp1 != -1) continue;

			/* if the port centers are different, go no further */
			if (xpos2[i] != xpos1 || ypos2[i] != ypos1) continue;

			/* compare actual polygons to be sure */
			shapeportpoly(ni2, pp2, poly2, 0);
			if (polysame(poly1, poly2) == 0) continue;

			/* handle confusion if multiple ports have the same polygon */
			if ((PORTPROTO *)pp1->temp1 != NOPORTPROTO)
			{
				mpt = (PORTPROTO *)pp1->temp1;

				/* see if one of the associations has the same connectivity */
				for(j=0; mpt->connects[j] != NOARCPROTO && pp1->connects[j] != NOARCPROTO; j++)
					if (mpt->connects[j] != pp1->connects[j]) break;
				if (mpt->connects[j] == NOARCPROTO && pp1->connects[j] == NOARCPROTO) continue;
			}

			/* store the correct association of ports */
			if ((PORTPROTO *)pp1->temp1 != NOPORTPROTO)
				((PORTPROTO *)pp1->temp1)->temp1 = (INTBIG)NOPORTPROTO;
			pp1->temp1 = (INTBIG)pp2;
			pp2->temp1 = (INTBIG)pp1;
		}
	}

	/* finally, associate ports that have the same center */
	for(pp1 = ni1->proto->firstportproto; pp1 != NOPORTPROTO; pp1 = pp1->nextportproto)
		if ((PORTPROTO *)pp1->temp1 == NOPORTPROTO)
	{
		shapeportpoly(ni1, pp1, poly1, 0);
		getcenter(poly1, &xpos1, &ypos1);
		for(i = 0, pp2 = ni2->proto->firstportproto; pp2 != NOPORTPROTO; pp2 = pp2->nextportproto, i++)
		{
			/* if this port is already associated, ignore it */
			if (pp2->temp1 != -1) continue;

			/* if the port centers are different, go no further */
			if (xpos2[i] != xpos1 || ypos2[i] != ypos1) continue;

			/* handle confusion if multiple ports have the same polygon */
			if ((PORTPROTO *)pp1->temp1 != NOPORTPROTO)
			{
				mpt = (PORTPROTO *)pp1->temp1;

				/* see if one of the associations has the same connectivity */
				for(j=0; mpt->connects[j] != NOARCPROTO && pp1->connects[j] != NOARCPROTO; j++)
					if (mpt->connects[j] != pp1->connects[j]) break;
				if (mpt->connects[j] == NOARCPROTO && pp1->connects[j] == NOARCPROTO) continue;
			}

			/* store the correct association of ports */
			if ((PORTPROTO *)pp1->temp1 != NOPORTPROTO)
				((PORTPROTO *)pp1->temp1)->temp1 = (INTBIG)NOPORTPROTO;
			pp1->temp1 = (INTBIG)pp2;
			pp2->temp1 = (INTBIG)pp1;
		}
	}

	/* free the port center information */
	efree((char *)xpos2);
	efree((char *)ypos2);
	return(0);
}

/*
 * helper routine to ensure that all arcs connected to port "pp" or any of
 * its exported sites can connect to the list in "conn".  Returns nonzero
 * if the connection cannot be made
 */
INTSML db_doesntconnect(ARCPROTO *conn[], PORTPROTO *pp)
{
	REGISTER NODEINST *ni;
	REGISTER PORTARCINST *pi;
	REGISTER PORTEXPINST *pe;
	REGISTER INTSML i;

	/* check every instance of this node */
	for(ni = pp->parent->firstinst; ni != NONODEINST; ni = ni->nextinst)
	{
		/* make sure all arcs on this port can connect */
		for(pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
		{
			if (pi->proto != pp) continue;
			for(i=0; conn[i] != NOARCPROTO; i++)
				if (conn[i] == pi->conarcinst->proto) break;
			if (conn[i] == NOARCPROTO)
			{
				if (db_printerrors != 0)
					ttyputmsg("%s arc in facet %s cannot connect to port %s",
						describearcinst(pi->conarcinst), describenodeproto(ni->parent), pp->protoname);
				return(1);
			}
		}

		/* make sure all further exports are still valid */
		for(pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
		{
			if (pe->proto != pp) continue;
			if (db_doesntconnect(conn, pe->exportproto) != 0) return(1);
		}
	}
	return(0);
}

/*
 * routine to move an exported portproto to a different nodeinst in the facet.
 * The routine expects both ports to be in the same place and simply shifts
 * the arcs without re-constraining them. The portproto is currently "oldpt"
 * in facet "facet".  It is to be moved to nodeinst "newno", subportproto
 * "newsubpt".  The routine returns nonzero if there is an error
 */
INTSML moveportproto(NODEPROTO *facet, PORTPROTO *oldpt, NODEINST *newno, PORTPROTO *newsubpt)
{
	REGISTER NODEINST *oldni;
	REGISTER PORTPROTO *oldpp;

	/* error checks */
	if (oldpt == NOPORTPROTO)
		return((INTSML)db_error(DBBADPROTO|DBMOVEPORTPROTO));
	if (facet == NONODEPROTO)
		return((INTSML)db_error(DBBADFACET|DBMOVEPORTPROTO));
	if (oldpt->parent != facet)
		return((INTSML)db_error(DBBADFACET|DBMOVEPORTPROTO));
	if (newno == NONODEINST)
		return((INTSML)db_error(DBBADINST|DBMOVEPORTPROTO));
	if (newsubpt == NOPORTPROTO)
		return((INTSML)db_error(DBBADSUBPORT|DBMOVEPORTPROTO));
	if (newno->parent != oldpt->parent)
		return((INTSML)db_error(DBBADPARENT|DBMOVEPORTPROTO));
	if (newsubpt->parent != newno->proto)
		return((INTSML)db_error(DBBADSUBPORT|DBMOVEPORTPROTO));
	if (db_doesntconnect(newsubpt->connects, oldpt) != 0)
		return((INTSML)db_error(DBPORTMM|DBMOVEPORTPROTO));

	/* remember old state */
	oldni = oldpt->subnodeinst;
	oldpp = oldpt->subportproto;

	/* change the port origin */
	db_changeport(oldpt, newno, newsubpt);

	/* announce the change */
	oldpt->changeaddr = (char *)db_change((INTBIG)oldpt, PORTPROTOMOD, (INTBIG)oldni,
		(INTBIG)oldpp, 0, 0, 0, 0);

	/* tell constraint system about modified port */
	(*el_curconstraint->modifyportproto)(oldpt, oldni, oldpp);

	/* mark this as changed */
	db_setchangefacet(oldpt->parent);
	db_forcehierarchicalanalysis(oldpt->parent);

	return(0);
}

/*
 * routine to change the origin of complex port "pp" to subnode "newsubno",
 * subport "newsubpt"
 */
void db_changeport(PORTPROTO *pp, NODEINST *newsubno, PORTPROTO *newsubpt)
{
	REGISTER PORTEXPINST *pe;

	/* remove the old linkage */
	db_removeportexpinst(pp);

	/* create the new linkage */
	pp->subnodeinst = newsubno;
	pp->subportproto = newsubpt;
	pp->userbits = (pp->userbits & STATEBITS) |
		(newsubpt->userbits & (PORTANGLE|PORTARANGE|PORTNET|PORTISOLATED));
	pp->connects = newsubpt->connects;
	pe = allocportexpinst(pp->parent->cell->cluster);
	pe->proto = newsubpt;
	db_addportexpinst(newsubno, pe);
	pe->exportproto = pp;
	pp->subportexpinst = pe;

	/* update all port characteristics exported from this one */
	changeallports(pp);
}

/*
 * routine to recursively alter the "userbits" and "connects" fields of
 * port "pp", given that it changed or moved.
 */
void changeallports(PORTPROTO *pp)
{
	REGISTER NODEINST *ni;
	REGISTER PORTEXPINST *pe;

	/* look at all instances of the facet that had port motion */
	for(ni = pp->parent->firstinst; ni != NONODEINST; ni = ni->nextinst)
	{
		/* see if an instance reexports the port */
		for(pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
		{
			if (pe->proto != pp) continue;

			/* change this port and recurse up the hierarchy */
			if (pe->exportproto->userbits != pp->userbits)
			{
				setval((INTBIG)pe->exportproto, VPORTPROTO, "userbits",
					pp->userbits, VINTEGER);
			}
			pe->exportproto->connects = pp->connects;
			changeallports(pe->exportproto);
		}
	}
}

/*
 * routine to replace arcinst "ai" with one of type "ap".  The routine
 * returns the address of the new replaced arcinst if successful, NOARCINST
 * if the replacement cannot be done.
 */
ARCINST *replacearcinst(ARCINST *ai, ARCPROTO *ap)
{
	REGISTER PORTPROTO *pp1, *pp2;
	REGISTER INTSML i;
	REGISTER INTBIG newwid;
	REGISTER NODEINST *ni1, *ni2;
	REGISTER ARCINST *newar;

	/* check for connection allowance */
	ni1 = ai->end[0].nodeinst;   ni2 = ai->end[1].nodeinst;
	pp1 = ai->end[0].portarcinst->proto;
	pp2 = ai->end[1].portarcinst->proto;
	for(i=0; pp1->connects[i] != NOARCPROTO; i++)
		if (pp1->connects[i] == ap) break;
	if (pp1->connects[i] == NOARCPROTO)
		return((ARCINST *)db_error(DBBADENDAC|DBREPLACEARCINST));
	for(i=0; pp2->connects[i] != NOARCPROTO; i++)
		if (pp2->connects[i] == ap) break;
	if (pp2->connects[i] == NOARCPROTO)
		return((ARCINST *)db_error(DBBADENDBC|DBREPLACEARCINST));

	/* compute the new width */
	newwid = ai->width - arcwidthoffset(ai) + arcprotowidthoffset(ap);

	/* first create the new nodeinst in place */
	newar = db_newarcinst(ap, newwid, ai->userbits, ni1,pp1, ai->end[0].xpos,ai->end[0].ypos,
		ni2,pp2,ai->end[1].xpos, ai->end[1].ypos, ai->parent);
	if (newar == NOARCINST) return(NOARCINST);

	/* copy all variables on the arcinst */
	(void)copyvars((INTBIG)ai, VARCINST, (INTBIG)newar, VARCINST);

	/* set the change facet environment */
	db_setchangefacet(ai->parent);

	/* now delete the original nodeinst */
	db_killarcinst(ai);
	return(newar);
}
