/*
 * Electric(tm) VLSI Design System
 *
 * File: usrcomln.c
 * User interface aid: command handler for L through N
 * 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 "egraphics.h"
#include "usr.h"
#include "usrtrack.h"
#include "efunction.h"
#include "edialogs.h"
#include "conlay.h"
#include "tecart.h"
#include "tecgen.h"
#if SIMAID
#  include "sim.h"
#endif

void us_lambda(INTSML count, char *par[])
{
	REGISTER char *pp;
	REGISTER INTBIG lam, oldlam, newunit, how;
	REGISTER INTSML l;
	REGISTER WINDOWPART *w;
	REGISTER NODEPROTO *np;
	REGISTER LIBRARY *lib;
	REGISTER TECHNOLOGY *tech;
	TECHNOLOGY *techarray[1];
	INTBIG newlam[1];
	extern COMCOMP us_lambdachp, us_noyesp;

	if (count > 0)
	{
		l = strlen(pp = par[0]);

		/* handle display unit setting */
		if (namesamen(pp, "display-units", l) == 0)
		{
			if (count >= 2)
			{
				l = strlen(pp = par[1]);
				if (namesamen(pp, "lambda", l) == 0) newunit = DISPUNITLAMBDA; else
				if (namesamen(pp, "inch", l) == 0) newunit = DISPUNITINCH; else
				if (namesamen(pp, "centimeter", l) == 0 && l >= 7) newunit = DISPUNITCM; else
				if (namesamen(pp, "millimeter", l) == 0 && l >= 7) newunit = DISPUNITMM; else
				if (namesamen(pp, "mil", l) == 0 && l >= 3) newunit = DISPUNITMIL; else
				if (namesamen(pp, "micron", l) == 0 && l >= 3) newunit = DISPUNITMIC; else
				if (namesamen(pp, "centimicron", l) == 0 && l >= 7) newunit = DISPUNITCMIC; else
				if (namesamen(pp, "millimicron", l) == 0 && l >= 7) newunit = DISPUNITMMIC; else
				{
					us_abortcommand(_("Unknown display unit: %s"), pp);
					return;
				}
				setvalkey((INTBIG)us_aid, VAID, us_displayunits, newunit, VINTEGER);
			}
			switch (el_units&DISPLAYUNITS)
			{
				case DISPUNITLAMBDA:
					ttyputverbose(_("Distance expressed in lambda (currently %ld %ss)"),
						el_curlib->lambda[el_curtech->techindex], unitsname(el_units));
					break;
				case DISPUNITINCH:
					ttyputverbose(_("Distance expressed in inches"));
					break;
				case DISPUNITCM:
					ttyputverbose(_("Distance expressed in centimeters"));
					break;
				case DISPUNITMM:
					ttyputverbose(_("Distance expressed in millimeters"));
					break;
				case DISPUNITMIL:
					ttyputverbose(_("Distance expressed in mils"));
					break;
				case DISPUNITMIC:
					ttyputverbose(_("Distance expressed in microns"));
					break;
				case DISPUNITCMIC:
					ttyputverbose(_("Distance expressed in centimicrons"));
					break;
				case DISPUNITMMIC:
					ttyputverbose(_("Distance expressed in millimicrons"));
					break;
			}
			return;
		}

		/* handle internal unit setting */
		if (namesamen(pp, "internal-units", l) == 0)
		{
			if (count >= 2)
			{
				l = strlen(pp = par[1]);
				if (namesamen(pp, "half-decimicron", l) == 0 && l >= 6)
				{
					changeinternalunits(NOLIBRARY, el_units, INTUNITHDMIC);
				} else if (namesamen(pp, "half-millimicron", l) == 0 && l >= 6)
				{
					changeinternalunits(NOLIBRARY, el_units, INTUNITHMMIC);
				} else
				{
					us_abortcommand(_("Unknown internal unit: %s"), pp);
					return;
				}
			}
			ttyputverbose(_("Smallest internal unit is %s"), unitsname(el_units));
			return;
		}

		if (namesamen(pp, "change-tech", l) == 0 && l >= 8) how = 0; else
			if (namesamen(pp, "change-lib", l) == 0 && l >= 8) how = 1; else
				if (namesamen(pp, "change-all-libs", l) == 0 && l >= 8) how = 2; else
		{
			ttyputusage("lambda change-tech|change-lib|change-all-libs VALUE");
			return;
		}

		if (count <= 1)
		{
			count = ttygetparam(_("Lambda value: "), &us_lambdachp, MAXPARS-1, &par[1]) + 1;
			if (count == 1)
			{
				us_abortedmsg();
				return;
			}
		}

		lam = atoi(par[1]);
		if (lam <= 0)
		{
			us_abortcommand(_("Lambda value must be positive"));
			return;
		}
		if (lam / 4 * 4 != lam)
		{
			count = ttygetparam(_("Instability may result if lambda is not divisible by four.  Continue? [n]"),
				&us_noyesp, MAXPARS, par);
			if (count <= 0 || namesamen(par[0], "yes", strlen(par[0])) != 0)
			{
				ttyputverbose(_("No change made"));
				return;
			}
		}

		/* save highlighting */
		us_pushhighlight();
		us_clearhighlightcount();

		oldlam = el_curlib->lambda[el_curtech->techindex];
		techarray[0] = el_curtech;
		newlam[0] = lam;
		changelambda(1, techarray, newlam, el_curlib, how);
		us_setlambda(NOWINDOWFRAME);
		us_adjustlambda(oldlam, lam);
		for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
		{
			if (w->curnodeproto == NONODEPROTO) continue;
			if (w->curnodeproto->tech != el_curtech) continue;
			if (how == 2 || (how == 1 && w->curnodeproto->cell->lib == el_curlib))
			{
				w->screenlx = muldiv(w->screenlx, lam, oldlam);
				w->screenhx = muldiv(w->screenhx, lam, oldlam);
				w->screenly = muldiv(w->screenly, lam, oldlam);
				w->screenhy = muldiv(w->screenhy, lam, oldlam);
				computewindowscale(w);
			} else us_redisplay(w);
		}

		/* restore highlighting */
		(void)us_pophighlight(0);
	}

	/* determine which technologies to report */
	for(tech = el_technologies; tech != NOTECHNOLOGY; tech = tech->nexttechnology) tech->temp1 = 0;
	for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
		for(np = lib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
			np->tech->temp1++;
	el_curtech->temp1++;
	for(tech = el_technologies; tech != NOTECHNOLOGY; tech = tech->nexttechnology)
	{
		if (tech->temp1 == 0) continue;
		ttyputverbose(_("Lambda size for %s is %s"), tech->techname,
			latoa(el_curlib->lambda[tech->techindex]));
	}
}

void us_library(INTSML count, char *par[])
{
	REGISTER LIBRARY *lib, *olib, *lastlib, *mergelib;
	REGISTER INTSML i, l, makecurrent, multifile, found, total, stripopts;
	REGISTER INTBIG retval, arg;
	REGISTER char *pp, *pt, *style, *libf, *oldlibfile, save;
	REGISTER NODEPROTO *np;
	REGISTER NODEINST *ni;
	REGISTER VARIABLE *var;
	REGISTER EDITOR *ed;
	REGISTER WINDOWPART *w, *curw, *ow;
	INTBIG lx, hx, ly, hy;
	INTSML dummy;
	char *newpar[5], *libn, *extn;
	extern COMCOMP us_libraryp, us_yesnop;

	if (count == 0)
	{
		count = ttygetparam(_("Library option: "), &us_libraryp, MAXPARS, par);
		if (count == 0)
		{
			us_abortedmsg();
			return;
		}
	}
	l = strlen(pp = par[0]);

	if (namesamen(pp, "default-path", l) == 0 && l >= 9)
	{
		if (count < 2)
		{
			ttyputusage("library default-path PATHNAME");
			return;
		}
		setlibdir(par[1]);
		ttyputverbose(_("Default library path is now '%s'"), el_libdir);
		return;
	}

	if (namesamen(pp, "purge", l) == 0 && l >= 1)
	{
		/* delete all facets that are not the most recent and are unused */
		found = 1;
		total = 0;
		while (found != 0)
		{
			found = 0;
			for(np = el_curlib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
			{
				if (np->newestversion == np) continue;
				if (np->firstinst != NONODEINST) continue;
				ttyputmsg(_("Deleting facet %s"), describenodeproto(np));
				us_dokillfacet(np);
				found++;
				total++;
				break;
			}
		}
		if (total == 0) ttyputmsg(_("No unused old facet versions to delete")); else
			ttyputmsg(_("Deleted %d facets"), total);
		return;
	}

	if (namesamen(pp, "kill", l) == 0 && l >= 1)
	{
		if (count <= 1) lib = el_curlib; else
		{
			lib = getlibrary(par[1]);
			if (lib == NOLIBRARY)
			{
				us_abortcommand(_("No library called %s"), par[1]);
				return;
			}
		}
		if (lib == el_curlib && lib->nextlibrary == NOLIBRARY)
		{
			us_abortcommand(_("Cannot kill last library"));
			return;
		}

		/* make sure this library isn't referenced by others */
		for(olib = el_curlib; olib != NOLIBRARY; olib = olib->nextlibrary)
		{
			if (olib == lib) continue;
			for(np = olib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
			{
				for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
				{
					if (ni->proto->primindex != 0) continue;
					if (ni->proto->cell->lib == lib)
					{
						us_abortcommand(_("Cannot delete this library because it is referenced in library '%s'"),
							olib->libname);
						return;
					}
				}
			}
		}

		/* make sure it hasn't changed (unless "safe" option is given) */
		if (count <= 2 || namesamen(par[2], "safe", strlen(par[2])) != 0)
		{
			if (us_preventloss(lib, "kill", 1)) return;
		}

		/* switch to another library if killing current one */
		if (lib == el_curlib)
		{
			us_switchtolibrary(el_curlib->nextlibrary);
			ttyputverbose(_("Current library is now %s"), el_curlib->libname);
		} else ttyputverbose(_("Library %s killed"), lib->libname);

		/* remove display of all facets in this library */
		for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
		{
			if (w->curnodeproto == NONODEPROTO) continue;
			if (w->curnodeproto->cell->lib == lib) us_clearwindow(w);
		}

		/* kill the library */
		killlibrary(lib);

		/*
		 * the facet structure has changed
		 *
		 * this wouldn't be necessary if the "killlibrary()" routine did a
		 * broadcast.  Then it would be detected properly in "us_killobject"
		 */
		us_redoexplorerwindow();
		return;
	}

	if (namesamen(pp, "read", l) == 0 && l >= 1)
	{
		if (count < 2)
		{
			ttyputusage("library read FILENAME [options]");
			return;
		}

		/* get style of input */
		libf = par[1];
		makecurrent = 0;
		style = "binary";
		extn = ".elib";
		mergelib = NOLIBRARY;
		while (count >= 3)
		{
			l = strlen(pp = par[2]);
			if (namesamen(pp, "binary", l) == 0 && l >= 1) { style = "binary"; extn = ".elib"; } else
			if (namesamen(pp, "cif",    l) == 0 && l >= 1) { style = "cif";    extn = ".cif";  } else
			if (namesamen(pp, "def",    l) == 0 && l >= 1) { style = "def";    extn = ".def";  } else
			if (namesamen(pp, "dxf",    l) == 0 && l >= 1) { style = "dxf";    extn = ".dxf";  } else
			if (namesamen(pp, "edif",   l) == 0 && l >= 1) { style = "edif";   extn = ".edif"; } else
			if (namesamen(pp, "gds",    l) == 0 && l >= 1) { style = "gds";    extn = ".gds";  } else
			if (namesamen(pp, "lef",    l) == 0 && l >= 1) { style = "lef";    extn = ".lef";  } else
			if (namesamen(pp, "sdf",    l) == 0 && l >= 2) { style = "sdf";    extn = ".sdf"; } else
			if (namesamen(pp, "sue",    l) == 0 && l >= 2) { style = "sue";    extn = ".sue"; } else
			if (namesamen(pp, "text",   l) == 0 && l >= 1) { style = "text";   extn = ".txt";  } else
			if (namesamen(pp, "vhdl",   l) == 0 && l >= 1) { style = "vhdl";   extn = ".vhdl";  } else
			if (namesamen(pp, "make-current", l) == 0 && l >= 2) makecurrent++; else
			if (namesamen(pp, "merge", l) == 0 && l >= 2)
			{
				if (count < 4)
				{
					ttyputusage("library read FILENAME merge LIBRARYNAME");
					return;
				}
				mergelib = getlibrary(par[3]);
				if (mergelib == NOLIBRARY)
				{
					us_abortcommand(_("Cannot find merge library %s"), par[3]);
					return;
				}
				count--;
				par++;
			} else
			{
				us_abortcommand(_("Read options: binary|cif|def|dxf|edif|gds|lef|sdf|sue|text|vhdl|make-current|(merge LIB)"));
				return;
			}
			count--;
			par++;
		}

		/* turn off highlighting */
		us_clearhighlightcount();

		/* see if multiple files were selected */
		multifile = 0;
		for(pt = libf; *pt != 0; pt++) if (*pt == NONFILECH) multifile++;

		/* loop through all files */
		retval = 0;
		for(i=0; ; i++)
		{
			/* get the next file into "libf" */
			for(pt = libf; *pt != 0 && *pt != NONFILECH; pt++) ;
			save = *pt;
			*pt = 0;

			/* get file name and remove extension from library name */
			(void)allocstring(&libn, skippath(libf), el_tempcluster);
			l = strlen(libn) - strlen(extn);
			if (namesame(&libn[l], extn) == 0)
			{
				libn[l] = 0;

				/* remove extension from library file if not binary */
				if (strcmp(extn, ".elib") != 0) libf[strlen(libf) - strlen(extn)] = 0;
			}

			if (multifile != 0)
			{
				/* place the new library file name onto the current library */
				lib = el_curlib;
				oldlibfile = lib->libfile;
				lib->libfile = libf;
			} else if (mergelib != NOLIBRARY)
			{
				/* place the new library file name onto the merge library */
				lib = mergelib;
				oldlibfile = lib->libfile;
				lib->libfile = libf;
			} else
			{
				/* reading a new library (or replacing an old one) */
				lib = getlibrary(libn);
				if (lib == NOLIBRARY)
				{
					/* create the library if it does not already exist in memory */
					lib = newlibrary(libn, libf);
					if (lib == NOLIBRARY)
					{
						us_abortcommand(_("Cannot create library %s"), libn);
						efree(libn);
						return;
					}
				} else
				{
					/* not a new library: make sure the old library is saved */
					if (us_preventloss(lib, "replacing", 1) != 0)
					{
						efree(libn);
						return;
					}

					/* erase the old library */
					eraselibrary(lib);
					if (reallocstring(&lib->libfile, libf, lib->cluster)) ttyputnomemory();
					for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
					{
						w->curnodeproto = NONODEPROTO;
						us_redisplaynow(w, 1);
					}
				}

				/* remove any GETPROTOs to facets in this library */
				us_removegetfacets();
			}

			/* read the library */
			if (multifile == 0) arg = 0; else
			{
				arg = 2;
				if (i == 0) arg = 1; else
					if (i == multifile) arg = 3;
			}
			if (askaid(io_aid, "read", (INTBIG)lib, (INTBIG)style, arg) != 0)
				retval = 1;
			if (multifile != 0 || mergelib != NOLIBRARY) lib->libfile = oldlibfile;

			/* advance to the next file in the list */
			*pt = (char)save;
			if (save == 0) break;
			libf = pt + 1;
			if (retval != 0) break;
		}

		if (retval == 0)
		{
			if (makecurrent != 0 || lib == el_curlib)
			{
				/* these operations are done directly to prevent undo */
				us_alignment = muldiv(us_alignment, lib->lambda[el_curtech->techindex],
					el_curtech->deflambda);
				nextvarchangequiet();
				(void)setvalkey((INTBIG)us_aid, VAID, us_alignment_obj, us_alignment,
					VINTEGER|VDONTSAVE);
				us_edgealignment = muldiv(us_edgealignment, lib->lambda[el_curtech->techindex],
					el_curtech->deflambda);
				nextvarchangequiet();
				(void)setvalkey((INTBIG)us_aid, VAID, us_alignment_edge, us_edgealignment,
					VINTEGER|VDONTSAVE);
				us_setalignment(NOWINDOWFRAME);
#if SIMAID
				sim_window_stopsimulation();
#endif

				selectlibrary(lib);
				us_setlambda(NOWINDOWFRAME);
				if (us_curnodeproto == NONODEPROTO || us_curnodeproto->primindex == 0)
				{
					nextvarchangequiet();
					(void)setvalkey((INTBIG)us_aid, VAID, us_current_node,
						(INTBIG)el_curtech->firstnodeproto, VNODEPROTO|VDONTSAVE);
					us_shadownodeproto(NOWINDOWFRAME, el_curtech->firstnodeproto);
				}
				np = el_curlib->curnodeproto;
				curw = el_curwindowpart;
				if (curw != NOWINDOWPART && (curw->state&WINDOWTYPE) == EXPLORERWINDOW)
				{
					for(ow = el_topwindowpart; ow != NOWINDOWPART; ow = ow->nextwindowpart)
						if (ow != curw && ow->frame == curw->frame) break;
					if (ow != NOWINDOWPART) curw = ow;
				}
				if (np != NONODEPROTO)
				{
					if (curw == NOWINDOWPART)
					{
						curw = us_wantnewwindow(0);
						if (curw == NOWINDOWPART)
						{
							us_abortcommand(_("Cannot create new window"));
							return;
						}
					} else
					{
						if (curw->termhandler != 0)
							(*curw->termhandler)(curw);
						if ((curw->state&WINDOWTYPE) == POPTEXTWINDOW)
							(void)screenrestorebox(curw->editor->savedbox, -1);
						if ((curw->state&WINDOWTYPE) == TEXTWINDOW ||
							(curw->state&WINDOWTYPE) == POPTEXTWINDOW)
								us_freeeditor(curw->editor);
					}
					if ((np->cellview->viewstate&TEXTVIEW) != 0)
					{
						/* text window: make an editor */
						if (us_makeeditor(curw, describenodeproto(np), &dummy, &dummy) == NOWINDOWPART)
						{
							efree(libn);
							return;
						}

						/* get the text that belongs here */
						var = getvalkey((INTBIG)np, VNODEPROTO, VSTRING|VISARRAY, el_facet_message);
						if (var == NOVARIABLE)
						{
							newpar[0] = "";
							var = setvalkey((INTBIG)np, VNODEPROTO, el_facet_message,
								(INTBIG)newpar, VSTRING|VISARRAY|(1<<VLENGTHSH));
							if (var == NOVARIABLE)
							{
								efree(libn);
								return;
							}
						}

						/* setup for editing */
						curw->curnodeproto = np;
						ed = curw->editor;
						ed->editobjaddr = (char *)np;
						ed->editobjtype = VNODEPROTO;
						ed->editobjqual = "FACET_message";
						ed->editobjvar = var;

						/* load the text into the window */
						us_suspendgraphics(curw);
						l = (INTSML)getlength(var);
						for(i=0; i<l; i++)
						{
							pp = ((char **)var->addr)[i];
							if (i == l-1 && *pp == 0) continue;
							us_addline(curw, i, pp);
						}
						us_resumegraphics(curw);
						curw->changehandler = us_textfacetchanges;
						us_setfacetname(curw);
					} else
					{
						/* adjust the new window to account for borders in the old one */
						if ((curw->state&WINDOWTYPE) != DISPWINDOW)
						{
							curw->usehx -= DISPLAYSLIDERSIZE;
							curw->usely += DISPLAYSLIDERSIZE;
						}
						if ((curw->state&WINDOWTYPE) == WAVEFORMWINDOW)
						{
							curw->uselx -= DISPLAYSLIDERSIZE;
							curw->usely -= DISPLAYSLIDERSIZE;
						}
						if ((curw->state&WINDOWSIMULATING) != 0)
						{
							curw->uselx -= SIMULATINGBORDERSIZE;
							curw->usehx += SIMULATINGBORDERSIZE;
							curw->usely -= SIMULATINGBORDERSIZE;
							curw->usehy += SIMULATINGBORDERSIZE;
						}
						curw->curnodeproto = np;
						curw->state = (curw->state & ~(WINDOWTYPE|WINDOWSIMULATING)) | DISPWINDOW;
						curw->editor = NOEDITOR;
						curw->buttonhandler = DEFAULTBUTTONHANDLER;
						curw->charhandler = DEFAULTCHARHANDLER;
						curw->changehandler = DEFAULTCHANGEHANDLER;
						curw->termhandler = DEFAULTTERMHANDLER;
						curw->redisphandler = DEFAULTREDISPHANDLER;
						us_fullview(np, &lx, &hx, &ly, &hy);
						us_squarescreen(curw, NOWINDOWPART, 0, &lx, &hx, &ly, &hy);
						curw->screenlx = lx;
						curw->screenhx = hx;
						curw->screenly = ly;
						curw->screenhy = hy;
						computewindowscale(curw);
						us_setfacetname(curw);
						us_setfacetsize(curw);
						us_setgridsize(curw);
						if (curw->redisphandler != 0)
							(*curw->redisphandler)(curw);
						us_state |= CURFACETCHANGED;
					}
				}

				/* redisplay the explorer window */
				us_redoexplorerwindow();
			}
		} else if (mergelib == NOLIBRARY)
		{
			/* library is incomplete: remove it from the list */
			if (el_curlib != lib)
			{
				/* this is not the current library: safe to delete */
				lastlib = NOLIBRARY;
				for(olib = el_curlib; olib != NOLIBRARY; olib = olib->nextlibrary)
				{
					if (olib == lib) break;
					lastlib = olib;
				}
				if (lastlib != NOLIBRARY) lastlib->nextlibrary = lib->nextlibrary;
			} else
			{
				/* current library is incomplete */
				if (el_curlib->nextlibrary == NOLIBRARY)
				{
					/* must create a new library: it is the only one */
					el_curlib = alloclibrary();
					(void)allocstring(&el_curlib->libname, lib->libname, el_curlib->cluster);
					(void)allocstring(&el_curlib->libfile, lib->libfile, el_curlib->cluster);
					el_curlib->nextlibrary = NOLIBRARY;
				} else el_curlib = el_curlib->nextlibrary;

				us_clearhighlightcount();
				us_adjustlambda(el_curtech->deflambda, el_curlib->lambda[el_curtech->techindex]);
				us_setlambda(NOWINDOWFRAME);
				if (us_curnodeproto == NONODEPROTO || us_curnodeproto->primindex == 0)
				{
					if ((us_state&NONPERSISTENTCURNODE) != 0) us_setnodeproto(NONODEPROTO); else
						us_setnodeproto(el_curtech->firstnodeproto);
				}
				if (el_curlib->curnodeproto != NONODEPROTO)
				{
					newpar[0] = describenodeproto(el_curlib->curnodeproto);
					us_editfacet(1, newpar);
				}
			}
		}
		efree(libn);
		noundoallowed();
		return;
	}

	if (namesamen(pp, "use", l) == 0 && l >= 1)
	{
		if (count <= 1)
		{
			ttyputusage("library use LIBNAME");
			return;
		}

		/* find actual library name */
		libn = skippath(par[1]);

		lib = getlibrary(libn);
		if (lib != NOLIBRARY)
		{
			if (el_curlib == lib) return;
			us_switchtolibrary(lib);
			return;
		}

		/* create new library */
		lib = newlibrary(libn, par[1]);
		if (lib == NOLIBRARY)
		{
			us_abortcommand(_("Cannot create library %s"), libn);
			return;
		}

		/* make this library the current one */
		us_switchtolibrary(lib);
		ttyputverbose(_("New library: %s"), libn);
		return;
	}

	if (namesamen(pp, "new", l) == 0 && l >= 1)
	{
		if (count <= 1)
		{
			ttyputusage("library new LIBNAME");
			return;
		}

		/* find actual library name */
		libn = skippath(par[1]);

		lib = getlibrary(libn);
		if (lib != NOLIBRARY)
		{
			us_abortcommand(_("Already a library called '%s'"), libn);
			return;
		}

		/* create new library */
		lib = newlibrary(libn, par[1]);
		if (lib == NOLIBRARY)
		{
			us_abortcommand(_("Cannot create library %s"), libn);
			return;
		}

		ttyputverbose(_("New library: %s"), libn);
		return;
	}

	if (namesamen(pp, "save", l) == 0 && l >= 2)
	{
		/* save every library that has been modified */
		for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
		{
			if ((lib->userbits&(LIBCHANGEDMAJOR | LIBCHANGEDMINOR)) == 0) continue;

			/* save the library in binary format */
			makeoptionstemporary(lib);
			retval = askaid(io_aid, "write", (INTBIG)lib, (INTBIG)"binary");
			restoreoptionstate();
			if (retval != 0) return;
		}

		/* rewind session logging if it is on */
		if (us_logrecord != NULL)
		{
			logfinishrecord();
			logstartrecord();
		}
		return;
	}

	if (namesamen(pp, "write", l) == 0 && l >= 1)
	{
		if (count < 2)
		{
			ttyputusage("library write LIBNAME [options]");
			return;
		}

		/* get style of output */
		style = "binary";
		if (count >= 3)
		{
			l = strlen(pp = par[2]);
			if (l >= 1 && namesamen(pp, "binary", l) == 0) style = "binary"; else
			if (l >= 1 && namesamen(pp, "cif", l) == 0) style = "cif"; else
			if (l >= 1 && namesamen(pp, "dxf", l) == 0) style = "dxf"; else
			if (l >= 2 && namesamen(pp, "eagle", l) == 0) style = "eagle"; else
			if (l >= 2 && namesamen(pp, "ecad", l) == 0) style = "ecad"; else
			if (l >= 2 && namesamen(pp, "edif", l) == 0) style = "edif"; else
			if (l >= 1 && namesamen(pp, "gds", l) == 0) style = "gds"; else
			if (l >= 1 && namesamen(pp, "hpgl", l) == 0) style = "hpgl"; else
			if (l >= 2 && namesamen(pp, "lef", l) == 0) style = "lef"; else
			if (l >= 1 && namesamen(pp, "l", l) == 0) style = "l"; else
			if (l >= 2 && namesamen(pp, "pads", l) == 0) style = "pads"; else
			if (l >= 2 && namesamen(pp, "postscript", l) == 0) style = "postscript"; else
			if (l >= 2 && namesamen(pp, "printed-postscript", l) == 0) style = "printed-postscript"; else
			if (l >= 1 && namesamen(pp, "quickdraw", l) == 0) style = "quickdraw"; else
			if (l >= 1 && namesamen(pp, "skill", l) == 0) style = "skill"; else
			if (l >= 1 && namesamen(pp, "text", l) == 0) style = "text"; else
			{
				us_abortcommand(_("File formats: binary|cif|dxf|eagle|ecad|edif|gds|hpgl|l|lef|pads|postscript|printed-postscript|quickdraw|skill|text"));
				return;
			}
		}

		/* get library to write */
		lib = getlibrary(par[1]);
		if (lib == NOLIBRARY)
		{
			us_abortcommand(_("No library called %s"), par[1]);
			return;
		}

		/* do it */
		if (namesame(style, "binary") == 0 || namesame(style, "text") == 0) stripopts = 1; else
			stripopts = 0;
		if (stripopts != 0) makeoptionstemporary(lib);
		retval = askaid(io_aid, "write", (INTBIG)lib, (INTBIG)style);
		if (stripopts != 0) restoreoptionstate();
		if (retval != 0) return;

		/* rewind session recording if writing in permanent format */
		if ((*style == 'b' || *style == 't') && us_logrecord != NULL)
		{
			logfinishrecord();
			logstartrecord();
		}

		/* offer to save any referenced libraries */
		if (namesame(style, "binary") == 0)
		{
			for(olib = el_curlib; olib != NOLIBRARY; olib = olib->nextlibrary)
				for(np = olib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
					np->temp2 = 0;
			for(np = lib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
				for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
					if (ni->proto->primindex == 0)
						ni->proto->temp2 = 1;
			for(olib = el_curlib; olib != NOLIBRARY; olib = olib->nextlibrary)
			{
				if (olib == lib) continue;
				for(np = olib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
					if (np->temp2 != 0) break;
				if (np == NONODEPROTO) continue;
				if ((olib->userbits&(LIBCHANGEDMAJOR | LIBCHANGEDMINOR)) == 0) continue;

				(void)initinfstr();
				if ((olib->userbits&LIBCHANGEDMAJOR) == 0)
				{
					(void)formatinfstr(_("Also save referenced library %s (which has changed insignificantly)?"),
						olib->libname);
				} else
				{
					(void)formatinfstr(_("Also save referenced library %s (which has changed significantly)?"),
						olib->libname);
				}
				i = ttygetparam(returninfstr(), &us_yesnop, 3, newpar);
				if (i == 1 && newpar[0][0] == 'n') continue;
				makeoptionstemporary(olib);
				(void)askaid(io_aid, "write", (INTBIG)olib, (INTBIG)style);
				restoreoptionstate();
			}
		}
		return;
	}

	ttyputbadusage("library");
}

void us_macbegin(INTSML count, char *par[])
{
	REGISTER INTSML i, l, verbose, execute;
	REGISTER INTBIG key;
	REGISTER char *ch, *pp;
	char *newmacro[3];
	extern COMCOMP us_macbeginnp;
	REGISTER VARIABLE *var;

	var = getvalkey((INTBIG)us_aid, VAID, VSTRING, us_macrobuilding);
	if (var != NOVARIABLE)
	{
		us_abortcommand(_("Already defining a macro"));
		return;
	}

	/* get macro name */
	if (count == 0)
	{
		count = ttygetparam(_("Macro name: "), &us_macbeginnp, MAXPARS, par);
		if (count == 0)
		{
			us_abortedmsg();
			return;
		}
	}
	pp = par[0];

	/* get switches */
	verbose = 0;   execute = 2;
	for(i=1; i<count; i++)
	{
		ch = par[i];
		l = strlen(ch);
		if (namesamen(ch, "verbose", l) == 0) verbose = 1; else
		if (namesamen(ch, "no-execute", l) == 0) execute = 0; else
		{
			ttyputusage("macbegin MACRONAME [verbose|no-execute]");
			return;
		}
	}

	/* make sure macro name isn't overloading existing command */
	for(i=0; us_lcommand[i].name != 0; i++)
		if (namesame(pp, us_lcommand[i].name) == 0)
	{
		us_abortcommand(_("There is a command with that name"));
		return;
	}

	/* see if macro name already exists */
	if (us_getmacro(pp) == NOVARIABLE)
	{
		/* new macro name, check for validity */
		for(ch = pp; *ch != 0; ch++) if (*ch == ' ' || *ch == '\t')
		{
			us_abortcommand(_("Macro name must not have embedded spaces"));
			return;
		}
	}

	/* create the macro variable */
	(void)initinfstr();
	(void)addstringtoinfstr("USER_macro_");
	(void)addstringtoinfstr(pp);
	key = makekey(returninfstr());

	/* build the first two lines of the macro */
	(void)initinfstr();
	if (us_curmacropack != NOMACROPACK)
	{
		(void)addstringtoinfstr("macropack=");
		(void)addstringtoinfstr(us_curmacropack->packname);
	}
	if (verbose != 0) (void)addstringtoinfstr(" verbose");
	if (execute == 0) (void)addstringtoinfstr(" noexecute");
	newmacro[0] = returninfstr();
	newmacro[1] = "";		/* no parameters yet */
	newmacro[2] = "";		/* no parsing structures yet */
	(void)setvalkey((INTBIG)us_aid, VAID, key, (INTBIG)newmacro,
		VSTRING|VISARRAY|(3<<VLENGTHSH)|VDONTSAVE);

	/* turn on macro remembering */
	(void)setvalkey((INTBIG)us_aid, VAID, us_macrobuilding, (INTBIG)pp, VSTRING|VDONTSAVE);
	(void)setvalkey((INTBIG)us_aid, VAID, us_macrorunning, (INTBIG)pp, VSTRING|VDONTSAVE);

	ttyputverbose(_("Remembering %s..."), pp);
}

void us_macdone(INTSML count, char *par[])
{
	REGISTER VARIABLE *var;

	var = getvalkey((INTBIG)us_aid, VAID, VSTRING, us_macrorunning);
	if (var == NOVARIABLE)
	{
		us_abortcommand(_("Can only stop execution of a macro if one is running"));
		return;
	}
	us_state |= MACROTERMINATED;
}

void us_macend(INTSML count, char *par[])
{
	REGISTER VARIABLE *var;

	var = getvalkey((INTBIG)us_aid, VAID, VSTRING, us_macrobuilding);
	if (var == NOVARIABLE)
	{
		us_abortcommand(_("Not in a macro"));
		return;
	}
	ttyputverbose(_("%s defined"), (char *)var->addr);
	(void)delvalkey((INTBIG)us_aid, VAID, us_macrobuilding);
	if (getvalkey((INTBIG)us_aid, VAID, VSTRING, us_macrorunning) != NOVARIABLE)
		(void)delvalkey((INTBIG)us_aid, VAID, us_macrorunning);
}

void us_menu(INTSML count, char *par[])
{
	REGISTER INTBIG l, s, i, total, changed, position, x, y, varkey,
		j, len, arctot, pintot, comptot, puretot, fun;
	REGISTER INTSML autoset, coms;
	INTSML butstate, wid, hei;
	REGISTER char *pp, **temp;
	char number[10], *pt, *implicit[6];
	POPUPMENU *pm, *cpm;
	REGISTER POPUPMENUITEM *mi, *miret;
	NODEPROTO *np;
	ARCPROTO *ap;
	REGISTER USERCOM *uc;
	REGISTER VARIABLE *var;
	COMCOMP *comarray[MAXPARS];
	COMMANDBINDING commandbinding;
	extern COMCOMP us_menup;
	extern KEYWORD *us_pathiskey;

	/* with no arguments, toggle the menu state */
	if (count == 0)
	{
		count = ttygetparam(_("Menu display option: "), &us_menup, MAXPARS, par);
		if (count == 0)
		{
			us_abortedmsg();
			return;
		}
	}

	/* check for simple changes of the menu display */
	l = strlen(pp = par[0]);
	if (namesamen(pp, "on", l) == 0 && l >= 2)
	{
		if ((us_aid->aidstate&MENUON) != 0)
		{
			us_abortcommand(_("Menu is already displayed"));
			return;
		}

		/* save highlighting */
		us_pushhighlight();
		us_clearhighlightcount();

		startobjectchange((INTBIG)us_aid, VAID);
		(void)setval((INTBIG)us_aid, VAID, "aidstate", us_aid->aidstate|MENUON,
			VINTEGER);
		endobjectchange((INTBIG)us_aid, VAID);

		/* restore highlighting */
		(void)us_pophighlight(0);
		return;
	}

	if (namesamen(pp, "off", l) == 0 && l >= 2)
	{
		if ((us_aid->aidstate&MENUON) == 0)
		{
			us_abortcommand(_("Menu is not displayed"));
			return;
		}

		/* save highlighting */
		us_pushhighlight();
		us_clearhighlightcount();

		startobjectchange((INTBIG)us_aid, VAID);
		(void)setval((INTBIG)us_aid, VAID, "aidstate", us_aid->aidstate & ~MENUON, VINTEGER);
		endobjectchange((INTBIG)us_aid, VAID);

		/* restore highlighting */
		(void)us_pophighlight(0);
		return;
	}

	if (namesamen(pp, "dopopup", l) == 0 && l >= 1)
	{
		if (count <= 1)
		{
			implicit[0] = "tellaid";
			implicit[1] = "user";
			uc = us_buildcommand(2, implicit);
		} else uc = us_buildcommand((INTSML)(count-1), &par[1]);
		if (uc == NOUSERCOM)
		{
			us_abortcommand(_("Command '%s' not found"), par[1]);
			return;
		}

		/* loop through the popup menus of this command */
		for(;;)
		{
			/* convert to a string of command completion objects */
			total = us_fillcomcomp(uc, comarray);
			if (total <= uc->count) break;

			/* count the number of options */
			us_pathiskey = comarray[uc->count]->ifmatch;
			pt = "";
			(void)(*comarray[uc->count]->toplist)(&pt);
			for(coms=0; (pp = (*comarray[uc->count]->nextcomcomp)()); coms++) ;
			if ((comarray[uc->count]->interpret&INCLUDENOISE) != 0) coms++;

			/* build the prompt */
			(void)initinfstr();
			(void)addstringtoinfstr(comarray[uc->count]->noise);

			/* if there are no choices, prompt directly */
			if (coms == 0)
			{
				(void)addstringtoinfstr(": ");
				pp = ttygetline(returninfstr());
				if (pp == 0) break;
				(void)allocstring(&uc->word[uc->count], pp, us_aid->cluster);
				uc->count++;
				continue;
			}

			/* create the popup menu */
			pm = (POPUPMENU *)emalloc(sizeof(POPUPMENU), el_tempcluster);
			if (pm == 0) return;
			pm->name = "noname";
			(void)allocstring(&pm->header, returninfstr(), el_tempcluster);
			pm->total = coms;
			mi = (POPUPMENUITEM *)emalloc((coms * (sizeof (POPUPMENUITEM))), el_tempcluster);
			if (mi == 0) return;
			pm->list = mi;

			/* fill the popup menu */
			pt = "";
			(void)(*comarray[uc->count]->toplist)(&pt);
			for(i=0; i<coms; i++)
			{
				mi[i].valueparse = NOCOMCOMP;
				mi[i].response = NOUSERCOM;

				/* if noise is includable, do it first */
				if (i == 0 && (comarray[uc->count]->interpret&INCLUDENOISE) != 0)
				{
					mi[i].attribute = comarray[uc->count]->noise;
					if (comarray[uc->count]->def != 0)
					{
						mi[i].maxlen = (INTSML)maxi(20, strlen(comarray[uc->count]->def));
						mi[i].value = (char *)emalloc((mi[i].maxlen+1), el_tempcluster);
						(void)strcpy(mi[i].value, comarray[uc->count]->def);
					} else
					{
						mi[i].maxlen = 20;
						mi[i].value = (char *)emalloc((mi[i].maxlen+1), el_tempcluster);
						(void)strcpy(mi[i].value, "*");
					}
					continue;
				}

				/* normal entry */
				mi[i].attribute = (*comarray[uc->count]->nextcomcomp)();
				mi[i].value = 0;
				mi[i].maxlen = -1;

				/* see what would come next with this keyword */
				(void)allocstring(&uc->word[uc->count], mi[i].attribute, el_tempcluster);
				uc->count++;
				total = us_fillcomcomp(uc, comarray);
				uc->count--;
				efree(uc->word[uc->count]);
				if (total > uc->count+1)
				{
					if ((comarray[uc->count+1]->interpret&INPUTOPT) != 0)
					{
						mi[i].valueparse = comarray[uc->count+1];
						if (comarray[uc->count+1]->def != 0)
						{
							mi[i].maxlen = (INTSML)maxi(20, strlen(comarray[uc->count+1]->def));
							mi[i].value = (char *)emalloc((mi[i].maxlen+1), el_tempcluster);
							(void)strcpy(mi[i].value, comarray[uc->count+1]->def);
						} else
						{
							mi[i].maxlen = 20;
							mi[i].value = (char *)emalloc((mi[i].maxlen+1), el_tempcluster);
							(void)strcpy(mi[i].value, "*");
						}
					}
				}
			}

			/* invoke the popup menu */
			butstate = 1;
			cpm = pm;
			miret = us_popupmenu(&cpm, &butstate, 1, -1, -1, 0);
			if (miret == 0) us_abortcommand(_("Sorry, this display cannot support popup menus"));
			if (miret == NOPOPUPMENUITEM || miret == 0)
			{
				efree((char *)mi);
				efree((char *)pm->header);
				efree((char *)pm);
				break;
			}

			/* see if multiple answers were given */
			changed = 0;
			for(i=0; i<pm->total; i++) if (mi[i].changed != 0) changed++;
			if (changed > 1 || (changed == 1 && miret->changed == 0))
			{
				if ((comarray[uc->count]->interpret&MULTIOPT) == 0)
				{
					us_abortcommand(_("Multiple commands cannot be selected"));
					return;
				}

				/* tack on all of the answers */
				for(i=0; i<pm->total; i++) if (mi[i].changed != 0)
				{
					if (uc->count >= MAXPARS-1) break;
					(void)allocstring(&uc->word[uc->count++], mi[i].attribute, us_aid->cluster);
					(void)allocstring(&uc->word[uc->count++], mi[i].value, us_aid->cluster);
				}
				if (miret->changed == 0 && uc->count < MAXPARS)
					(void)allocstring(&uc->word[uc->count++], miret->attribute, us_aid->cluster);

				for(i=0; i<pm->total; i++)
					if (mi[i].maxlen >= 0) efree(mi[i].value);
				efree((char *)mi);
				efree((char *)pm);
				break;
			}

			/* if noise was mentioned, copy the value */
			if (miret == mi && miret->changed != 0 &&
				(comarray[uc->count]->interpret&INCLUDENOISE) != 0)
			{
				(void)allocstring(&uc->word[uc->count++], miret->value, us_aid->cluster);
			} else
			{
				(void)allocstring(&uc->word[uc->count++], miret->attribute, us_aid->cluster);
				if (miret->changed != 0)
					(void)allocstring(&uc->word[uc->count++], miret->value, us_aid->cluster);
			}
			for(i=0; i<pm->total; i++)
				if (mi[i].maxlen >= 0) efree(mi[i].value);
			efree((char *)mi);
			efree((char *)pm);
		}

		/* issue the completed command */
		us_execute(uc, us_aid->aidstate&ECHOBIND, 0, 1);
		us_freeusercom(uc);
		return;
	}

	if (namesamen(pp, "popup", l) == 0 && l >= 1)
	{
		if (count <= 2)
		{
			ttyputusage("menu popup NAME OPTIONS");
			return;
		}

		/* check out the name of the pop-up menu */
		pm = us_getpopupmenu(par[1]);

		/* determine what to do with the menu */
		l = strlen(pp = par[2]);
		if (namesamen(pp, "header", l) == 0 && l >= 1)
		{
			/* set header: ensure it is specified */
			if (count <= 3)
			{
				ttyputusage("menu popup header MESSAGE");
				return;
			}
			if (pm == NOPOPUPMENU)
			{
				us_abortcommand(_("No popup menu called %s"), par[1]);
				return;
			}
			(void)initinfstr();
			(void)addstringtoinfstr("USER_binding_popup_");
			(void)addstringtoinfstr(par[1]);
			varkey = makekey(returninfstr());
			var = getvalkey((INTBIG)us_aid, VAID, VSTRING|VISARRAY, varkey);
			if (var == NOVARIABLE)
			{
				us_abortcommand(_("Cannot find the menu"));
				return;
			}
			(void)setindkey((INTBIG)us_aid, VAID, varkey, 0, (INTBIG)par[3]);
			return;
		}
		if (namesamen(pp, "size", l) == 0 && l >= 1)
		{
			/* set size: ensure it is specified */
			if (count <= 3)
			{
				ttyputusage("menu popup NAME size SIZE");
				return;
			}
			s = (INTSML)myatoi(par[3]);
			if (s <= 0)
			{
				us_abortcommand(_("Popup menu size must be positive"));
				return;
			}

			/* set size of menu */
			if (pm == NOPOPUPMENU)
			{
				/* create the variable with the popup menu */
				temp = (char **)emalloc(((s+1) * (sizeof (char *))), el_tempcluster);
				(void)allocstring(&temp[0], "MENU!", el_tempcluster);
				for(i=0; i<s; i++)
				{
					(void)initinfstr();
					(void)addstringtoinfstr("inputpopup=");
					(void)addstringtoinfstr(par[1]);
					(void)addstringtoinfstr(" command=bind set popup ");
					(void)addstringtoinfstr(par[1]);
					(void)sprintf(number, " %d", i);
					(void)addstringtoinfstr(number);
					(void)allocstring(&temp[i+1], returninfstr(), el_tempcluster);
				}
				(void)initinfstr();
				(void)addstringtoinfstr("USER_binding_popup_");
				(void)addstringtoinfstr(par[1]);
				varkey = makekey(returninfstr());
				(void)setvalkey((INTBIG)us_aid, VAID, varkey, (INTBIG)temp,
					VSTRING|VISARRAY|VDONTSAVE|((s+1)<<VLENGTHSH));
				for(i=0; i<=s; i++) efree(temp[i]);
				efree((char *)temp);
			} else
			{
				/* get the former popup menu */
				(void)initinfstr();
				(void)addstringtoinfstr("USER_binding_popup_");
				(void)addstringtoinfstr(par[1]);
				varkey = makekey(returninfstr());
				var = getvalkey((INTBIG)us_aid, VAID, VSTRING|VISARRAY, varkey);
				if (var == NOVARIABLE)
				{
					us_abortcommand(_("Cannot find popup menu %s"), par[1]);
					return;
				}
				len = (INTSML)getlength(var);

				/* create the new popup menu */
				temp = (char **)emalloc(((s+1) * (sizeof (char *))), el_tempcluster);
				if (temp == 0) return;
				(void)allocstring(&temp[0], ((char **)var->addr)[0], el_tempcluster);

				/* copy the existing menu entries */
				for(i=1; i<mini((s+1), len); i++)
				{
					us_parsebinding(((char **)var->addr)[i], &commandbinding);
					(void)initinfstr();
					if (commandbinding.inputpopup != 0) (void)addstringtoinfstr("input");
					(void)addstringtoinfstr("popup=");
					(void)addstringtoinfstr(par[1]);
					if (commandbinding.menumessage != 0)
					{
						(void)addstringtoinfstr(" message=\"");
						(void)addstringtoinfstr(commandbinding.menumessage);
						(void)addtoinfstr('"');
					}
					(void)addstringtoinfstr(" command=");
					(void)addstringtoinfstr(commandbinding.command);
					(void)allocstring(&temp[i], returninfstr(), el_tempcluster);
					us_freebindingparse(&commandbinding);
				}

				for(j=i; j<s+1; j++)
				{
					(void)initinfstr();
					(void)addstringtoinfstr("inputpopup=");
					(void)addstringtoinfstr(par[1]);
					(void)addstringtoinfstr(" command=bind set popup ");
					(void)addstringtoinfstr(par[1]);
					(void)sprintf(number, " %d", j);
					(void)addstringtoinfstr(number);
					(void)allocstring(&temp[j], returninfstr(), el_tempcluster);
				}
				(void)setvalkey((INTBIG)us_aid, VAID, varkey, (INTBIG)temp,
					VSTRING|VISARRAY|VDONTSAVE|((s+1)<<VLENGTHSH));
				for(i=0; i<=s; i++) efree(temp[i]);
				efree((char *)temp);
			}
			return;
		}
		ttyputbadusage("menu popup");
		return;
	}

	if (namesamen(pp, "setmenubar", l) == 0 && l >= 2)
	{
		/* free former menubar information */
		if (us_pulldownmenucount != 0)
		{
			efree((char *)us_pulldowns);
			efree((char *)us_pulldownmenupos);
		}
		us_pulldownmenucount = 0;

		/* allocate new menubar information */
		if (count > 1)
		{
			us_pulldowns = (POPUPMENU **)emalloc((count-1) * (sizeof (POPUPMENU *)), us_aid->cluster);
			us_pulldownmenupos = (INTSML *)emalloc((count-1) * SIZEOFINTSML, us_aid->cluster);
			if (us_pulldowns == 0 || us_pulldownmenupos == 0) return;
			us_pulldownmenucount = count-1;
		}

		/* load the menubar information */
		for(i=1; i<count; i++)
		{
			/* check out the name of the pop-up menu */
			pm = us_getpopupmenu(par[i]);
			if (pm == NOPOPUPMENU)
			{
				ttyputbadusage("menu setmenubar");
				return;
			}
			us_pulldowns[i-1] = pm;
		}

		/* see if platform has native menu handling */
		if (nativemenuload((INTSML)(count-1), &par[1]) != 0)
		{
			/* no native menus: do them by hand */
			if (count > 1)
			{
				if (us_needwindow()) return;

				/* turn on the menu bar with these popups */
				us_menubarsize = MENUBARHEIGHT;

				/* load the information */
				screensettextsize(el_curwindowpart, TXTMENU);
				x = 0;
				for(i=1; i<count; i++)
				{
					pm = us_pulldowns[i-1];
					screengettextsize(el_curwindowpart, us_stripampersand(pm->header), &wid, &hei);
					x += wid + 12;
					us_pulldownmenupos[i-1] = (INTSML)x;
				}

				/* display the pulldown menu */
				us_drawmenu(0, NOWINDOWFRAME);
			} else
			{
				/* turn off the menu bar */
				if (us_menubarsize == 0) return;
				us_menubarsize = 0;
				us_drawmenu(0, NOWINDOWFRAME);
			}
		}
		return;
	}

	/* see if new fixed menu location has been specified */
	position = us_menupos;
	x = us_menux;   y = us_menuy;
	if (namesamen(pp, "top", l) == 0 && l >= 1)
	{
		position = 0;
		count--;   par++;
	} else if (namesamen(pp, "bottom", l) == 0 && l >= 1)
	{
		position = 1;
		count--;   par++;
	} else if (namesamen(pp, "left", l) == 0 && l >= 1)
	{
		position = 2;
		count--;   par++;
	} else if (namesamen(pp, "right", l) == 0 && l >= 1)
	{
		position = 3;
		count--;   par++;
	}

	/* check for new fixed menu size */
	autoset = 0;
	if (count > 0)
	{
		if (namesamen(par[0], "size", strlen(par[0])) != 0)
		{
			ttyputbadusage("menu");
			return;
		}
		if (count == 2 && namesamen(par[1], "auto", strlen(par[1])) == 0)
		{
			autoset = 1;
			arctot = pintot = comptot = puretot = 0;
			for(ap = el_curtech->firstarcproto; ap != NOARCPROTO; ap = ap->nextarcproto)
			{
				np = getpinproto(ap);
				if (np != NONODEPROTO && (np->userbits & NNOTUSED) != 0) continue;
				arctot++;
			}
			for(np = el_curtech->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
			{
				if ((np->userbits & NNOTUSED) != 0) continue;
				fun = (INTSML)((np->userbits&NFUNCTION) >> NFUNCTIONSH);
				if (fun == NPPIN) pintot++; else
					if (fun == NPNODE) puretot++; else
						comptot++;
			}
			if (pintot + comptot == 0) pintot = puretot;
			x = arctot + pintot + comptot;   y = 1;
			if (x > 40)
			{
				x = (x+2) / 3;
				y = 3;
			} else if (x > 20)
			{
				x = (x+1) / 2;
				y = 2;
			}
			if (position > 1) { i = x;   x = y;   y = i; }
		} else if (count == 3)
		{
			x = atoi(par[1]);
			y = atoi(par[2]);
			if (x <= 0 || y <= 0)
			{
				us_abortcommand(_("Menu sizes must be positive numbers"));
				return;
			}
		} else
		{
			ttyputusage("menu size (ROWS COLUMNS) | auto");
			return;
		}
	}

	/* set the fixed menu up */
	if (x == us_menux && y == us_menuy && position == us_menupos &&
		(us_aid->aidstate&MENUON) != 0 && autoset == 0)
	{
		ttyputverbose(_("Menu has not changed"));
		return;
	}

	startobjectchange((INTBIG)us_aid, VAID);
	if (x != us_menux || y != us_menuy || position != us_menupos || autoset != 0)
		(void)us_setmenusize((INTSML)x, (INTSML)y, (INTSML)position, autoset);
	if ((us_aid->aidstate&MENUON) == 0)
		(void)setval((INTBIG)us_aid, VAID, "aidstate", us_aid->aidstate|MENUON, VINTEGER);
	endobjectchange((INTBIG)us_aid, VAID);
}

void us_mirror(INTSML count, char *par[])
{
	REGISTER NODEINST *ni, *theni, *subni, **nilist, **newnilist;
	REGISTER INTSML amt, gamt, i;
	REGISTER INTBIG nicount, lx, hx, ly, hy, dist, bestdist, aicount, newnicount;
	REGISTER char *pt;
	REGISTER VARIABLE *var;
	REGISTER ARCINST *ai, **ailist, **newailist;
	REGISTER NODEPROTO *np;
	REGISTER PORTPROTO *thepp, *pp;
	REGISTER GEOM **list, *geom;
	REGISTER HIGHLIGHT *high;
	XARRAY transtz, rot, transfz, t1, t2;
	INTBIG gx, gy, cx, cy, x, y, thex, they;
	extern COMCOMP us_mirrorp;

	list = us_gethighlighted(OBJARCINST|OBJNODEINST);
	if (list[0] == NOGEOM)
	{
		us_abortcommand(_("Must select objects to mirror"));
		return;
	}
	np = geomparent(list[0]);

	/* figure out which nodes get mirrored */
	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		ni->temp1 = 0;
	for(i=0; list[i] != NOGEOM; i++)
	{
		geom = list[i];
		if (geom->entrytype == OBJNODEINST)
		{
			ni = geom->entryaddr.ni;
			if (us_canedit(np, ni->proto, 1) != 0) continue;
			ni->temp1 = 1;
		} else
		{
			if (us_canedit(np, NONODEPROTO, 1) != 0) return;
			ai = geom->entryaddr.ai;
			ai->end[0].nodeinst->temp1 = 1;
			ai->end[1].nodeinst->temp1 = 1;
		}
	}

	/* find the one node that is to be mirrored */
	nicount = 0;
	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		if (ni->temp1 == 0) continue;
		if (nicount == 0)
		{
			lx = ni->lowx;   hx = ni->highx;
			ly = ni->lowy;   hy = ni->highy;
		} else
		{
			if (ni->lowx < lx) lx = ni->lowx;
			if (ni->highx > hx) hx = ni->highx;
			if (ni->lowy < ly) ly = ni->lowy;
			if (ni->highy > hy) hy = ni->highy;
		}
		theni = ni;
		nicount++;
	}
	if (nicount > 1)
	{
		/* multiple nodes: find the center one */
		theni = NONODEINST;
		for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		{
			if (ni->temp1 == 0) continue;
			dist = computedistance((lx+hx)/2, (ly+hy)/2, (ni->lowx+ni->highx)/2,
				(ni->lowy+ni->highy)/2);
			if (theni == NONODEINST || dist < bestdist)
			{
				theni = ni;
				bestdist = dist;
			}
		}
	}

	if (count == 0)
	{
		count = ttygetparam(_("Horizontally or vertically? "), &us_mirrorp, MAXPARS, par);
		if (count == 0)
		{
			us_abortedmsg();
			return;
		}
	}
	pt = par[0];
	if (*pt != 'h' && *pt != 'v')
	{
		ttyputusage("mirror horizontal|vertical");
		return;
	}

	/* determine amount of rotation to effect the mirror */
	if (*pt == 'h')
	{
		if (theni->transpose == 0) amt = 2700; else amt = 900;
	} else
	{
		if (theni->transpose == 0) amt = 900; else amt = 2700;
	}

	/* determine translation when mirroring about grab or trace point */
	if (count >= 2) i = strlen(par[1]);
	if (count >= 2 && namesamen(par[1], "sensibly", i) == 0)
	{
		if (nicount == 1)
		{
			if (theni->proto->primindex == 0)
			{
				var = getvalkey((INTBIG)theni->proto, VNODEPROTO, VINTEGER|VISARRAY, el_prototype_center);
				if (var != NOVARIABLE)
				{
					par[1] = "about-grab-point";
				}
			}
		}
	}
	if (count >= 2 && namesamen(par[1], "about-grab-point", i) == 0 && i >= 7)
	{
		if (nicount != 1)
		{
			us_abortcommand(_("Must select 1 node to mirror about its grab point"));
			return;
		}

		/* find the grab point */
		corneroffset(theni, theni->proto, theni->rotation, theni->transpose, &gx, &gy, 0);
		gx += theni->lowx;   gy += theni->lowy;

		/* build transformation for this operation */
		transid(transtz);   transtz[2][0] = -gx;   transtz[2][1] = -gy;
		if (*pt == 'h') gamt = 2700; else gamt = 900;
		makeangle(gamt, 1, rot);
		transid(transfz);   transfz[2][0] = gx;   transfz[2][1] = gy;
		transmult(transtz, rot, t1);
		transmult(t1, transfz, t2);

		/* find out where true center moves */
		cx = (theni->lowx+theni->highx)/2;   cy = (theni->lowy+theni->highy)/2;
		xform(cx, cy, &gx, &gy, t2);
		gx -= cx;   gy -= cy;
	} else if (count >= 2 && namesamen(par[1], "about-trace-point", i) == 0 && i >= 7)
	{
		if (nicount != 1)
		{
			us_abortcommand(_("Must select 1 node to mirror about its outline point"));
			return;
		}

		/* get the trace information */
		var = gettrace(theni);
		if (var == NOVARIABLE)
		{
			us_abortcommand(_("Highlighted node must have outline information"));
			return;
		}

		/* find the pivot point */
		high = us_getonehighlight();
		i = high->frompoint;   if (i != 0) i--;
		makerot(theni, t1);
		gx = (theni->highx + theni->lowx) / 2;
		gy = (theni->highy + theni->lowy) / 2;
		xform(((INTBIG *)var->addr)[i*2]+gx, ((INTBIG *)var->addr)[i*2+1]+gy, &gx, &gy, t1);

		/* build transformation for this operation */
		transid(transtz);   transtz[2][0] = -gx;   transtz[2][1] = -gy;
		if (*pt == 'h') gamt = 2700; else gamt = 900;
		makeangle(gamt, 1, rot);
		transid(transfz);   transfz[2][0] = gx;   transfz[2][1] = gy;
		transmult(transtz, rot, t1);
		transmult(t1, transfz, t2);

		/* find out where true center moves */
		cx = (theni->lowx+theni->highx)/2;   cy = (theni->lowy+theni->highy)/2;
		xform(cx, cy, &gx, &gy, t2);
		gx -= cx;   gy -= cy;
	} else gx = gy = 0;

	/* now make sure that it is all connected */
	for(ai = np->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
		ai->userbits &= ~SPREADA;
	aicount = newnicount = 0;
	for(;;)
	{
		for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
			ni->userbits &= ~SPREADN;
		us_nettravel(theni, SPREADN);
		for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		{
			if (ni->temp1 == 0) continue;
			if ((ni->userbits&SPREADN) == 0) break;
		}
		if (ni == NONODEINST) break;

		/* this node is unconnected: connect it */
		thepp = theni->proto->firstportproto;
		if (thepp == NOPORTPROTO)
		{
			/* no port on the facet: create one */
			subni = newnodeinst(gen_univpinprim, theni->proto->lowx, theni->proto->highx,
				theni->proto->lowy, theni->proto->highy, 0, 0, theni->proto);
			if (subni == NONODEINST) break;
			thepp = newportproto(theni->proto, subni, subni->proto->firstportproto, "temp");

			/* add to the list of temporary nodes */
			newnilist = (NODEINST **)emalloc((newnicount+1) * (sizeof (NODEINST *)), el_tempcluster);
			if (newnilist == 0) break;
			for(i=0; i<newnicount; i++) newnilist[i] = nilist[i];
			if (newnicount > 0) efree((char *)nilist);
			nilist = newnilist;
			nilist[newnicount] = subni;
			newnicount++;
		}
		pp = ni->proto->firstportproto;
		portposition(theni, thepp, &thex, &they);
		portposition(ni, pp, &x, &y);
		ai = newarcinst(gen_invisiblearc, 0, 0, theni, thepp, thex, they,
			ni, pp, x, y, np);
		if (ai == NOARCINST) break;

		/* add to the list of temporary arcs */
		newailist = (ARCINST **)emalloc((aicount+1) * (sizeof (ARCINST *)), el_tempcluster);
		if (newailist == 0) break;
		for(i=0; i<aicount; i++) newailist[i] = ailist[i];
		if (aicount > 0) efree((char *)ailist);
		ailist = newailist;
		ailist[aicount] = ai;
		aicount++;
	}

	/* save highlighting */
	us_pushhighlight();
	us_clearhighlightcount();

	/* set arc constraints appropriately */
	for(ai = np->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
	{
		if (ai->end[0].nodeinst->temp1 != 0 && ai->end[1].nodeinst->temp1 != 0)
		{
			/* arc connecting two mirrored nodes: make rigid */
			(void)(*el_curconstraint->setobject)((INTBIG)ai, VARCINST, CHANGETYPETEMPRIGID, 0);
		}
	}

	/* mirror the node */
	startobjectchange((INTBIG)theni, VNODEINST);
	modifynodeinst(theni, gx, gy, gx, gy, amt, (INTSML)(1-theni->transpose*2));
	endobjectchange((INTBIG)theni, VNODEINST);

	/* delete intermediate arcs used to constrain */
	for(i=0; i<aicount; i++)
		killarcinst(ailist[i]);
	if (aicount > 0) efree((char *)ailist);

	/* delete intermediate nodes used to constrain */
	for(i=0; i<newnicount; i++)
	{
		killportproto(nilist[i]->parent, nilist[i]->firstportexpinst->exportproto);
		killnodeinst(nilist[i]);
	}
	if (newnicount > 0) efree((char *)nilist);

	/* restore highlighting */
	(void)us_pophighlight(1);

	if (*pt == 'h') ttyputverbose(_("Node mirrored horizontally")); else
		ttyputverbose(_("Node mirrored vertically"));
}

static DIALOGITEM us_movetodialogitems[] =
{
 /*  1 */ {0, {64,96,88,160}, BUTTON, N_("OK")},
 /*  2 */ {0, {64,8,88,72}, BUTTON, N_("Cancel")},
 /*  3 */ {0, {8,48,24,142}, EDITTEXT, ""},
 /*  4 */ {0, {8,20,24,42}, MESSAGE, N_("X:")},
 /*  5 */ {0, {32,48,48,142}, EDITTEXT, ""},
 /*  6 */ {0, {32,20,48,42}, MESSAGE, N_("Y:")}
};
DIALOG us_movetodialog = {{50,75,147,244}, N_("Move To Location"), 0, 6, us_movetodialogitems};

void us_move(INTSML count, char *par[])
{
	REGISTER NODEINST *ni, **nodelist;
	REGISTER NODEPROTO *np;
	REGISTER ARCINST *ai;
	REGISTER INTSML ang;
	REGISTER INTBIG l, total, i, wid, len, itemHit, amt;
	INTBIG dx, dy, xcur, ycur, lx, hx, ly, hy, bestlx, bestly, p1x, p1y, p2x, p2y,
		snapx, snapy;
	HIGHLIGHT high;
	REGISTER GEOM **list;
	HIGHLIGHT thishigh, otherhigh;
	REGISTER char *pp;
	static POLYGON *poly = NOPOLYGON;
	REGISTER VARIABLE *var;

	/* get polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, us_aid->cluster);

	/* make sure there is a current facet */
	np = us_needfacet();
	if (np == NONODEPROTO) return;

	/* get the objects to be moved */
	list = us_gethighlighted(OBJNODEINST | OBJARCINST);

	/* see if a text object is highlighted */
	var = getvalkey((INTBIG)us_aid, VAID, VSTRING|VISARRAY, us_highlighted);
	if (var != NOVARIABLE)
	{
		if (getlength(var) == 1)
		{
			(void)us_makehighlight(((char **)var->addr)[0], &high);
			if ((high.status&HIGHTYPE) == HIGHTEXT)
			{
				us_movetext(&high, count, par);
				return;
			}
			if ((high.status&HIGHTYPE) == HIGHBBOX) list = us_gethighlighted(OBJNODEINST);
		}
	}

	if (list[0] == NOGEOM)
	{
		us_abortcommand(_("First select objects to move"));
		return;
	}

	/* make sure they are all in the same facet */
	for(i=0; list[i] != NOGEOM; i++) if (np != geomparent(list[i]))
	{
		us_abortcommand(_("All moved objects must be in the same facet"));
		return;
	}

	/* mark all nodes (including those touched by highlighted arcs) */
	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		ni->temp1 = 0;
	for(i=0; list[i] != NOGEOM; i++) if (list[i]->entrytype == OBJARCINST)
	{
		ai = list[i]->entryaddr.ai;
		ai->end[0].nodeinst->temp1 = ai->end[1].nodeinst->temp1 = 1;
	}
	for(i=0; list[i] != NOGEOM; i++) if (list[i]->entrytype == OBJNODEINST)
		list[i]->entryaddr.ni->temp1 = 1;

	/* count the number of nodes */
	for(total=0, ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		if (ni->temp1 == 0) continue;
		if (us_canedit(np, ni->proto, 1) != 0) return;
		total++;
	}
	if (total == 0) return;

	/* build a list that includes all nodes touching moved arcs */
	nodelist = (NODEINST **)emalloc((total * (sizeof (NODEINST *))), el_tempcluster);
	if (nodelist == 0) return;
	for(i=0, ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		if (ni->temp1 != 0) nodelist[i++] = ni;

	/* figure out lower-left corner of this collection of objects */
	us_getlowleft(nodelist[0], &bestlx, &bestly);
	for(i=1; i<total; i++)
	{
		us_getlowleft(nodelist[i], &lx, &ly);
		if (lx < bestlx) bestlx = lx;
		if (ly < bestly) bestly = ly;
	}
	for(i=0; list[i] != NOGEOM; i++) if (list[i]->entrytype == OBJARCINST)
	{
		ai = list[i]->entryaddr.ai;
		wid = ai->width - arcwidthoffset(ai);
		makearcpoly(ai->length, wid, ai, poly, FILLED);
		getbbox(poly, &lx, &hx, &ly, &hy);
		if (lx < bestlx) bestlx = lx;
		if (ly < bestly) bestly = ly;
	}

	/* special case when moving one node: account for facet center */
	if (total == 1 && list[1] == NOGEOM)
	{
		/* get standard corner offset */
		ni = nodelist[0];
		corneroffset(ni, ni->proto, ni->rotation, ni->transpose, &lx, &ly,
			(INTSML)((us_useroptions&CENTEREDPRIMITIVES) != 0 ? 1 : 0));
		bestlx = ni->lowx + lx;
		bestly = ni->lowy + ly;
	}

	/* special case if there is exactly one snap point */
	if (us_getonesnappoint(&snapx, &snapy) != 0)
	{
		bestlx = snapx;   bestly = snapy;
	}

	/* special case if two objects are selected with snap points */
	if (count == 0)
	{
		var = getvalkey((INTBIG)us_aid, VAID, VSTRING|VISARRAY, us_highlighted);
		if (var != NOVARIABLE)
		{
			len = getlength(var);
			if (len == 2)
			{
				if (us_makehighlight(((char **)var->addr)[0], &thishigh) == 0 &&
					us_makehighlight(((char **)var->addr)[1], &otherhigh) == 0)
				{
					/* describe these two objects */
					if ((thishigh.status&HIGHSNAP) != 0 && (otherhigh.status&HIGHSNAP) != 0)
					{
						/* move first to second */
						us_getsnappoint(&thishigh, &p1x, &p1y);
						us_getsnappoint(&otherhigh, &p2x, &p2y);
						dx = p2x - p1x;
						dy = p2y - p1y;
						if (dx == 0 && dy == 0)
						{
							us_abortcommand(_("Points are already aligned"));
							return;
						}
						ni = thishigh.fromgeom->entryaddr.ni;
						us_pushhighlight();
						us_clearhighlightcount();
						startobjectchange((INTBIG)ni, VNODEINST);
						modifynodeinst(ni, dx, dy, dx, dy, 0, 0);
						endobjectchange((INTBIG)ni, VNODEINST);
						(void)us_pophighlight(1);
						return;
					}
				}
			}
		}
	}

	/* save and turn off all highlighting */
	us_pushhighlight();
	us_clearhighlightcount();

	/* no arguments: simple motion */
	if (count == 0)
	{
		if ((us_aid->aidstate&INTERACTIVE) != 0)
		{
			/* interactive motion: track cursor */
			us_multidraginit(bestlx, bestly, list, nodelist, total, 0);
			trackcursor(0, us_ignoreup, us_multidragbegin, us_multidragdown,
				us_stoponchar, us_multidragup, TRACKDRAGGING);
			if (el_pleasestop != 0)
			{
				efree((char *)nodelist);
				(void)us_pophighlight(0);
				return;
			}
		}

		/* get co-ordinates of cursor */
		if (us_demandxy(&xcur, &ycur))
		{
			efree((char *)nodelist);
			(void)us_pophighlight(0);
			return;
		}
		gridalign(&xcur, &ycur, us_alignment);

		/* make the move if it is valid */
		if (xcur == bestlx && ycur == bestly) ttyputverbose(_("Null motion")); else
			us_manymove(list, nodelist, total, xcur-bestlx, ycur-bestly);
		efree((char *)nodelist);
		(void)us_pophighlight(1);
		return;
	}

	l = strlen(pp = par[0]);

	/* handle absolute motion option "move to X Y" */
	if (namesamen(pp, "to", l) == 0 && l >= 1)
	{
		/* get absolute position to place object */
		if (count == 2 && namesamen(par[1], "dialog", strlen(par[1])) == 0)
		{
			/* get coordinates from dialog */
			if (DiaInitDialog(&us_movetodialog) != 0) return;
			DiaSetText(3, latoa(bestlx));
			DiaSetText(5, latoa(bestly));
			for(;;)
			{
				itemHit = DiaNextHit();
				if (itemHit == CANCEL || itemHit == OK) break;
			}
			xcur = atola(DiaGetText(3));
			ycur = atola(DiaGetText(5));
			DiaDoneDialog();
			if (itemHit == CANCEL) return;
		} else
		{
			/* get coordinates from command line */
			if (count < 3)
			{
				ttyputusage("move to X Y");
				efree((char *)nodelist);
				(void)us_pophighlight(0);
				return;
			}
			xcur = atola(par[1]);
			ycur = atola(par[2]);
		}

		/* make the move if it is valid */
		if (xcur == bestlx && ycur == bestly) ttyputverbose(_("Null motion")); else
			us_manymove(list, nodelist, total, xcur-bestlx, ycur-bestly);
		efree((char *)nodelist);
		(void)us_pophighlight(1);
		return;
	}

	/* handle motion to cursor along an angle: "move angle ANG" */
	if (namesamen(pp, "angle", l) == 0 && l >= 1)
	{
		/* get co-ordinates of cursor */
		if (us_demandxy(&xcur, &ycur))
		{
			efree((char *)nodelist);
			(void)us_pophighlight(0);
			return;
		}
		gridalign(&xcur, &ycur, us_alignment);

		/* get angle to align along */
		if (count < 2)
		{
			ttyputusage("move angle ANGLE");
			efree((char *)nodelist);
			(void)us_pophighlight(0);
			return;
		}
		ang = atofr(par[1])*10/WHOLE;

		/* adjust the cursor position if selecting interactively */
		if ((us_aid->aidstate&INTERACTIVE) != 0)
		{
			us_multidraginit(bestlx, bestly, list, nodelist, total, ang);
			trackcursor(0, us_ignoreup, us_multidragbegin, us_multidragdown,
				us_stoponchar, us_multidragup, TRACKDRAGGING);
			if (el_pleasestop != 0)
			{
				efree((char *)nodelist);
				(void)us_pophighlight(0);
				return;
			}
			if (us_demandxy(&xcur, &ycur) != 0) return;
			gridalign(&xcur, &ycur, us_alignment);
		}

		/* compute sliding for this highlighting */
		us_getslide(ang, bestlx, bestly, xcur, ycur, &dx, &dy);

		/* make the move if it is nonnull */
		if (dx == bestlx && dy == bestly) ttyputverbose(_("Null motion")); else
			us_manymove(list, nodelist, total, dx-bestlx, dy-bestly);
		efree((char *)nodelist);
		(void)us_pophighlight(1);
		return;
	}

	/* if direction is specified, get it */
	dx = dy = 0;
	if (namesamen(pp, "up", l) == 0 && l >= 1)
	{
		if (count < 2) amt = el_curlib->lambda[el_curtech->techindex]; else
		{
			l = el_units;
			el_units = (el_units & ~DISPLAYUNITS) | DISPUNITLAMBDA;
			amt = atola(par[1]);
			el_units = l;
		}
		dy += amt;
	} else if (namesamen(pp, "down", l) == 0 && l >= 1)
	{
		if (count < 2) amt = el_curlib->lambda[el_curtech->techindex]; else
		{
			l = el_units;
			el_units = (el_units & ~DISPLAYUNITS) | DISPUNITLAMBDA;
			amt = atola(par[1]);
			el_units = l;
		}
		dy -= amt;
	} else if (namesamen(pp, "left", l) == 0 && l >= 1)
	{
		if (count < 2) amt = el_curlib->lambda[el_curtech->techindex]; else
		{
			l = el_units;
			el_units = (el_units & ~DISPLAYUNITS) | DISPUNITLAMBDA;
			amt = atola(par[1]);
			el_units = l;
		}
		dx -= amt;
	} else if (namesamen(pp, "right", l) == 0 && l >= 1)
	{
		if (count < 2) amt = el_curlib->lambda[el_curtech->techindex]; else
		{
			l = el_units;
			el_units = (el_units & ~DISPLAYUNITS) | DISPUNITLAMBDA;
			amt = atola(par[1]);
			el_units = l;
		}
		dx += amt;
	} else
	{
		ttyputbadusage("move");
		efree((char *)nodelist);
		(void)us_pophighlight(0);
		return;
	}

	/* now move the object */
	if (dx != 0 || dy != 0) us_manymove(list, nodelist, total, dx, dy);
	efree((char *)nodelist);
	(void)us_pophighlight(1);
}

void us_node(INTSML count, char *par[])
{
	REGISTER INTBIG amt, i, *newlist, x, y, curlamb, inside, outside;
	REGISTER INTSML l, negated, size, nogood, total, waitfordown, addpoint,
		deletepoint, whichpoint, p, movepoint, pointgiven, segs, degs, nextpoint, prevpoint;
	XARRAY trans;
	INTBIG d[2], xcur, ycur;
	REGISTER char *pt, *ch;
	REGISTER GEOM **list;
	extern COMCOMP us_nodep;
	REGISTER NODEPROTO *np;
	REGISTER NODEINST *ni, *store;
	REGISTER HIGHLIGHT *high;
	HIGHLIGHT newhigh;
	REGISTER VARIABLE *var;

	/* disallow node modification if lock is on */
	np = us_needfacet();
	if (np == NONODEPROTO) return;

	if (count == 0)
	{
		count = ttygetparam(_("Node option: "), &us_nodep, MAXPARS, par);
		if (count == 0)
		{
			us_abortedmsg();
			return;
		}
	}
	l = strlen(pt = par[0]);

	/* handle negation */
	if (namesamen(pt, "not", l) == 0 && l >= 2)
	{
		negated = 1;
		count--;
		par++;
		l = strlen(pt = par[0]);
	} else negated = 0;

	if (namesamen(pt, "expand", l) == 0 && l >= 1)
	{
		if (count < 2) amt = HUGEINT; else amt = atoi(par[1]);
		list = us_gethighlighted(OBJNODEINST);
		if (list[0] == NOGEOM)
		{
			us_abortcommand(_("Select nodes to be expanded"));
			return;
		}

		/* save highlighting */
		us_pushhighlight();
		us_clearhighlightcount();

		/* pre-compute when un-expanding */
		if (negated) for(i=0; list[i] != NOGEOM; i++)
		{
			ni = list[i]->entryaddr.ni;
			if ((ni->userbits & NEXPAND) != 0) (void)us_setunexpand(ni, amt);
		}

		/* do the change */
		for(i=0; list[i] != NOGEOM; i++)
		{
			ni = list[i]->entryaddr.ni;
			if (negated == 0) us_doexpand(ni, amt, 0); else
				us_dounexpand(ni);
		}

		/* restore highlighting */
		(void)us_pophighlight(0);
		return;
	}

	if (namesamen(pt, "name", l) == 0 && l >= 2)
	{
		ni = (NODEINST *)us_getobject(OBJNODEINST, 0);
		if (ni == NONODEINST) return;

		if (negated == 0)
		{
			if (count < 2)
			{
				ttyputusage("node name NODENAME");
				return;
			}
			ch = par[1];

			/* sensibility check for multiple nodes with the same name */
			for(store = ni->parent->firstnodeinst; store != NONODEINST; store = store->nextnodeinst)
			{
				if (store == ni) continue;
				var = getvalkey((INTBIG)store, VNODEINST, VSTRING, el_node_name);
				if (var == NOVARIABLE) continue;
				if (namesame(ch, (char *)var->addr) == 0)
				{
					ttyputmsg(_("Warning: already a node in this facet called %s"), ch);
					break;
				}
			}
		}

		/* save highlighting */
		us_pushhighlight();
		us_clearhighlightcount();

		/* change the name of the nodeinst */
		startobjectchange((INTBIG)ni, VNODEINST);
		if (negated != 0) (void)delvalkey((INTBIG)ni, VNODEINST, el_node_name); else
		{
			var = setvalkey((INTBIG)ni, VNODEINST, el_node_name, (INTBIG)ch, VSTRING|VDISPLAY);

			/* shift text down if on a facet instance */
			if (var != NOVARIABLE && ni->proto->primindex == 0)
			{
				var->textdescript = us_setdescriptoffset(var->textdescript,
					0, (ni->highy-ni->lowy) / el_curlib->lambda[el_curtech->techindex]);
			}
		}
		endobjectchange((INTBIG)ni, VNODEINST);

		/* restore highlighting */
		(void)us_pophighlight(0);

		/* report results and quit */
		if (negated != 0) ttyputverbose(_("Node name removed")); else
			ttyputverbose(_("Node name is '%s'"), ch);
		return;
	}

	/* everything after this point is structural and must check for locks */
	if (us_canedit(np, NONODEPROTO, 1) != 0) return;

	if (namesamen(pt, "cover-implant", l) == 0 && l >= 1)
	{
		us_coverimplant();
		return;
	}

	if (namesamen(pt, "regrid-facet", l) == 0 && l >= 1)
	{
		us_regridfacet();
		return;
	}

	if (namesamen(pt, "trace", l) == 0 && l >= 1)
	{
		/* special case for converting text to layout */
		if (count < 2)
		{
			ttyputusage("node trace OPTIONS");
			return;
		}
		l = strlen(pt = par[1]);
		if (namesamen(pt, "place-text", l) == 0 && l >= 2)
		{
			if (count < 6)
			{
				ttyputusage("node trace place-text LAYER FONT SCALE MESSAGE");
				return;
			}
			us_layouttext(par[2], par[3], atoi(par[4]), par[5]);
			return;
		}

		/* special case for filleting */
		if (namesamen(pt, "fillet", l) == 0)
		{
			us_dofillet();
			return;
		}

		/* special case for annulus construction */
		if (namesamen(pt, "construct-annulus", l) == 0)
		{
			if (count < 4)
			{
				ttyputusage("node trace construct-annulus INSIDE OUTSIDE [RESOLUTION] [DEGREES]");
				return;
			}
			inside = atola(par[2]);
			outside = atola(par[3]);
			segs = 16;
			if (count >= 5) segs = (INTSML)myatoi(par[4]);
			if (segs < 4) segs = 4;
			degs = 3600;
			if (count >= 6) degs = atofr(par[5])*10/WHOLE;
			if (degs <= 0) degs = 3600;
			if (degs > 3600) degs = 3600;

			/* make sure node can handle trace information */
			store = (NODEINST *)us_getobject(OBJNODEINST, 0);
			if (store == NONODEINST) return;
			if ((store->proto->userbits&HOLDSTRACE) == 0)
			{
				us_abortcommand(_("Sorry, %s nodes cannot hold outline information"),
					describenodeproto(store->proto));
				return;
			}

			/* allocate space for the trace */
			newlist = emalloc(((segs+1)*4*SIZEOFINTBIG), el_tempcluster);
			if (newlist == 0)
			{
				ttyputnomemory();
				return;
			}

			l = 0;
			for(i=0; i<=segs; i++)
			{
				p = degs*i/segs;
				x = mult(inside, cosine(p)) + (store->lowx+store->highx)/2;
				y = mult(inside, sine(p)) + (store->lowy+store->highy)/2;
				newlist[l++] = x;   newlist[l++] = y;
			}
			for(i=segs; i>=0; i--)
			{
				p = degs*i/segs;
				x = mult(outside, cosine(p)) + (store->lowx+store->highx)/2;
				y = mult(outside, sine(p)) + (store->lowy+store->highy)/2;
				newlist[l++] = x;   newlist[l++] = y;
			}

			/* save highlighting, set trace, restore highlighting */
			us_pushhighlight();
			us_clearhighlightcount();
			us_settrace(store, newlist, (INTSML)((segs+1)*2));
			(void)us_pophighlight(0);
			efree((char *)newlist);
			return;
		}

		/* parse the options */
		store = NONODEINST;
		waitfordown = addpoint = deletepoint = movepoint = pointgiven = nextpoint = prevpoint = 0;
		for(i=1; i<count; i++)
		{
			l = strlen(pt = par[i]);

			if (namesamen(pt, "store-trace", l) == 0 && l >= 1)
			{
				store = (NODEINST *)us_getobject(OBJNODEINST, 0);
				if (store == NONODEINST) return;
			} else if (namesamen(pt, "add-point", l) == 0 && l >= 1)
			{
				addpoint++;
				store = (NODEINST *)us_getobject(OBJNODEINST, 0);
				if (store == NONODEINST) return;
				high = us_getonehighlight();
				whichpoint = high->frompoint;
				if (count > i+2 && isanumber(par[i+1]) != 0 && isanumber(par[i+2]) != 0)
				{
					xcur = atola(par[i+1]);
					ycur = atola(par[i+2]);
					i += 2;
					pointgiven++;
				}
			} else if (namesamen(pt, "move-point", l) == 0 && l >= 1)
			{
				movepoint++;
				store = (NODEINST *)us_getobject(OBJNODEINST, 0);
				if (store == NONODEINST) return;
				high = us_getonehighlight();
				whichpoint = high->frompoint;
				if (count > i+2 && isanumber(par[i+1]) != 0 && isanumber(par[i+2]) != 0)
				{
					xcur = atola(par[i+1]);
					ycur = atola(par[i+2]);
					i += 2;
					pointgiven++;
				}
			} else if (namesamen(pt, "delete-point", l) == 0 && l >= 1)
			{
				deletepoint++;
				store = (NODEINST *)us_getobject(OBJNODEINST, 0);
				if (store == NONODEINST) return;
				high = us_getonehighlight();
				whichpoint = high->frompoint;
			} else if (namesamen(pt, "next-point", l) == 0 && l >= 1)
			{
				nextpoint++;
				store = (NODEINST *)us_getobject(OBJNODEINST, 0);
				if (store == NONODEINST) return;
				high = us_getonehighlight();
				whichpoint = high->frompoint;
			} else if (namesamen(pt, "prev-point", l) == 0 && l >= 2)
			{
				prevpoint++;
				store = (NODEINST *)us_getobject(OBJNODEINST, 0);
				if (store == NONODEINST) return;
				high = us_getonehighlight();
				whichpoint = high->frompoint;
			} else if (namesamen(pt, "wait-for-down", l) == 0 && l >= 1)
			{
				waitfordown++;
			} else
			{
				ttyputbadusage("node trace");
				return;
			}
		}

		if (addpoint + deletepoint + movepoint + nextpoint + prevpoint > 1)
		{
			us_abortcommand(_("Can only add OR delete OR move OR change point"));
			return;
		}

		/* make sure node can handle trace information */
		if (store != NONODEINST)
		{
			if ((store->proto->userbits&HOLDSTRACE) == 0)
			{
				us_abortcommand(_("Sorry, %s nodes cannot hold outline information"),
					describenodeproto(store->proto));
				return;
			}
		}

		/* handle moving around the trace */
		if (nextpoint != 0)
		{
			var = gettrace(store);
			if (var == NOVARIABLE)
			{
				us_abortcommand(_("No points on this node"));
				return;
			}
			size = getlength(var) / 2;
			whichpoint++;
			if (whichpoint >= size) whichpoint = 0;

			us_clearhighlightcount();
			newhigh.status = HIGHFROM;
			newhigh.fromgeom = store->geom;
			newhigh.fromport = NOPORTPROTO;
			newhigh.frompoint = whichpoint;
			newhigh.facet = store->parent;
			(void)us_addhighlight(&newhigh);
			return;
		}
		if (prevpoint != 0)
		{
			var = gettrace(store);
			if (var == NOVARIABLE)
			{
				us_abortcommand(_("No points on this node"));
				return;
			}
			size = getlength(var) / 2;
			whichpoint--;
			if (whichpoint < 0) whichpoint = size-1;

			us_clearhighlightcount();
			newhigh.status = HIGHFROM;
			newhigh.fromgeom = store->geom;
			newhigh.fromport = NOPORTPROTO;
			newhigh.frompoint = whichpoint;
			newhigh.facet = store->parent;
			(void)us_addhighlight(&newhigh);
			return;
		}

		/* handle freeform cursor traces */
		if (addpoint == 0 && deletepoint == 0 && movepoint == 0)
		{
			/* just do tracking if no storage requested */
			if (store == NONODEINST)
			{
				/* save highlighting */
				us_pushhighlight();
				us_clearhighlightcount();

				trackcursor(waitfordown, us_ignoreup, us_tracebegin,
					us_tracedown, us_stoponchar, us_traceup, TRACKDRAWING);

				/* restore highlighting */
				(void)us_pophighlight(0);
				return;
			}

			/* clear highlighting */
			us_clearhighlightcount();

			/* get the trace */
			trackcursor(waitfordown, us_ignoreup, us_tracebegin, us_tracedown,
				us_stoponchar, us_traceup, TRACKDRAWING);
			if (el_pleasestop != 0) return;

			/* get the trace data in the "%T" variable */
			var = getval((INTBIG)us_aid, VAID, VINTEGER|VISARRAY, us_commandvarname('T'));
			if (var == NOVARIABLE)
			{
				us_abortcommand(_("Invalid outline information"));
				return;
			}
			size = getlength(var) / 2;

			/* create a place to keep this data */
			newlist = emalloc((size*2*SIZEOFINTBIG), el_tempcluster);
			if (newlist == 0)
			{
				ttyputnomemory();
				return;
			}

			/* copy the trace from "%T" to "newlist" */
			curlamb = el_curlib->lambda[el_curtech->techindex];
			el_curlib->lambda[el_curtech->techindex] = 4;
			nogood = 0;
			for(i=0; i<size; i++)
			{
				x = ((INTBIG *)var->addr)[i*2];
				y = ((INTBIG *)var->addr)[i*2+1];
				nogood += us_setxy((INTSML)x, (INTSML)y);
				(void)getxy(&xcur, &ycur);
				newlist[i*2] = xcur;   newlist[i*2+1] = ycur;
			}
			el_curlib->lambda[el_curtech->techindex] = curlamb;

			/* if data is valid, store it in the node */
			if (nogood != 0) ttyputerr(_("Outline not inside window")); else
			{
				us_settrace(store, newlist, size);

				/* highlighting the node */
				newhigh.status = HIGHFROM;
				newhigh.fromgeom = store->geom;
				newhigh.fromport = NOPORTPROTO;
				newhigh.frompoint = size+1;
				newhigh.facet = store->parent;
				(void)us_addhighlight(&newhigh);
			}
			efree((char *)newlist);
			return;
		}

		/* add a point to a trace on this object */
		if (addpoint != 0)
		{
			/* clear highlighting */
			us_clearhighlightcount();

			if (pointgiven == 0)
			{
				if (us_demandxy(&xcur, &ycur)) return;
				gridalign(&xcur, &ycur, us_alignment);

				/* adjust the cursor position if selecting interactively */
				if ((us_aid->aidstate&INTERACTIVE) != 0)
				{
					us_pointinit(store, whichpoint);
					trackcursor(0, us_ignoreup, us_pointbegin, us_addpdown,
						us_stoponchar, us_dragup, TRACKDRAGGING);
					if (el_pleasestop != 0) return;
					if (us_demandxy(&xcur, &ycur) != 0) return;
					gridalign(&xcur, &ycur, us_alignment);
				}
			}

			var = gettrace(store);
			if (var == NOVARIABLE)
			{
				d[0] = xcur;   d[1] = ycur;
				us_settrace(store, d, 1);
				whichpoint = 1;
			} else
			{
				size = getlength(var) / 2;
				newlist = emalloc(((size+1)*2*SIZEOFINTBIG), el_tempcluster);
				if (newlist == 0)
				{
					ttyputnomemory();
					return;
				}
				p = 0;
				makerot(store, trans);
				x = (store->highx + store->lowx) / 2;
				y = (store->highy + store->lowy) / 2;
				for(i=0; i<size; i++)
				{
					xform(((INTBIG *)var->addr)[i*2]+x, ((INTBIG *)var->addr)[i*2+1]+y, &newlist[p],
						&newlist[p+1], trans);
					p += 2;
					if (i+1 == whichpoint)
					{
						newlist[p++] = xcur;
						newlist[p++] = ycur;
					}
				}

				/* now re-draw this trace */
				us_settrace(store, newlist, (INTSML)(size+1));
				whichpoint++;
				efree((char *)newlist);
			}

			/* highlighting the node */
			newhigh.status = HIGHFROM;
			newhigh.fromgeom = store->geom;
			newhigh.fromport = NOPORTPROTO;
			newhigh.frompoint = whichpoint;
			newhigh.facet = store->parent;
			(void)us_addhighlight(&newhigh);
			return;
		}

		/* delete the current point */
		if (deletepoint != 0)
		{
			var = gettrace(store);
			if (var == NOVARIABLE)
			{
				us_abortcommand(_("No outline data to delete"));
				return;
			}
			if (whichpoint == 0)
			{
				us_abortcommand(_("Highlight a point or line to delete"));
				return;
			}
			size = getlength(var) / 2;
			if (size <= 1)
			{
				us_abortcommand(_("Outline must retain at least one point"));
				return;
			}
			newlist = emalloc(((size-1)*2*SIZEOFINTBIG), el_tempcluster);
			if (newlist == 0)
			{
				ttyputnomemory();
				return;
			}
			p = 0;
			makerot(store, trans);
			x = (store->highx + store->lowx) / 2;
			y = (store->highy + store->lowy) / 2;
			total = 0;
			for(i=0; i<size; i++)
			{
				if (i+1 == whichpoint) { total++;   continue;}
				xform(((INTBIG *)var->addr)[i*2]+x, ((INTBIG *)var->addr)[i*2+1]+y,
					&newlist[p], &newlist[p+1], trans);
				p += 2;
			}

			/* clear highlighting */
			us_clearhighlightcount();

			/* now re-draw this trace */
			us_settrace(store, newlist, (INTSML)(size-total));
			whichpoint = (INTSML)maxi(whichpoint-total, 1);
			efree((char *)newlist);

			/* highlighting the node */
			newhigh.status = HIGHFROM;
			newhigh.fromgeom = store->geom;
			newhigh.fromport = NOPORTPROTO;
			newhigh.frompoint = whichpoint;
			newhigh.facet = store->parent;
			(void)us_addhighlight(&newhigh);
			return;
		}

		/* move a point on a trace on this object */
		if (movepoint != 0)
		{
			var = gettrace(store);
			if (var == NOVARIABLE)
			{
				us_abortcommand(_("No outline data to move"));
				return;
			}
			if (whichpoint == 0)
			{
				us_abortcommand(_("Highlight a point or line to move"));
				return;
			}

			/* save highlighting */
			us_pushhighlight();
			us_clearhighlightcount();

			if (pointgiven == 0)
			{
				if (us_demandxy(&xcur, &ycur)) return;
				gridalign(&xcur, &ycur, us_alignment);

				/* adjust the cursor position if selecting interactively */
				if ((us_aid->aidstate&INTERACTIVE) != 0)
				{
					us_pointinit(store, whichpoint);
					trackcursor(0, us_ignoreup, us_pointbegin, us_movepdown,
						us_stopandpoponchar, us_dragup, TRACKDRAGGING);
					if (el_pleasestop != 0) return;
					if (us_demandxy(&xcur, &ycur) != 0) return;
					gridalign(&xcur, &ycur, us_alignment);
				}
			}

			size = getlength(var) / 2;
			newlist = emalloc((size*2*SIZEOFINTBIG), el_tempcluster);
			if (newlist == 0)
			{
				ttyputnomemory();
				(void)us_pophighlight(0);
				return;
			}
			makerot(store, trans);
			x = (store->highx + store->lowx) / 2;
			y = (store->highy + store->lowy) / 2;
			for(i=0; i<size; i++)
			{
				if (i+1 == whichpoint)
				{
					newlist[i*2] = xcur;
					newlist[i*2+1] = ycur;
				} else xform(((INTBIG *)var->addr)[i*2]+x, ((INTBIG *)var->addr)[i*2+1]+y,
					&newlist[i*2], &newlist[i*2+1], trans);
			}

			/* now re-draw this trace */
			us_settrace(store, newlist, size);
			efree((char *)newlist);

			/* restore highlighting */
			(void)us_pophighlight(1);
			return;
		}
		return;
	}

	ttyputbadusage("node");
}
