/*
 * Electric(tm) VLSI Design System
 *
 * File: usrmisc.c
 * User interface aid: miscellaneous control
 * 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 "efunction.h"
#include "usr.h"
#include "usreditemacs.h"
#include "usreditpac.h"
#include "usrtrack.h"
#include "tecgen.h"
#include "tecart.h"
#include "sim.h"
#include <math.h>

#define ALLTEXTWIDTH	70		/* width of side menu when it is all text */

/******************** INITIALIZATION ********************/

#define	INITIALMENUX    18	/* default height of component menu */
#define	INITIALMENUY    2	/* default width of component menu */

/*
 * default tablet state: the table below shows the set of commands
 * that will be bound to the tablet buttons.  If there is a button
 * with the name in "us_tablet[i].but[0]" or "us_tablet[i].but[1]"
 * then that button will be bound to the command "us_tablet[i].com".
 * If no tablet button exists for an entry "i", then the command
 * there will be bound to the key "us_tablet[i].key".  Thus, tablets
 * with varying numbers of buttons can be handled by placing commands
 * on the keys if they don't fit on the buttons.
 */
struct
{
	char  *but[3];		/* the button names for this command */
	char  *key;			/* the key name for this command */
	char  *com;			/* the actual command */
	INTSML count;		/* number of parameters to the command */
	char  *args[2];		/* parameters to the command */
	INTSML used;		/* set when the command is bound */
} us_initialbutton[] =
{
	/* left/middle/right (white/yellow/blue) are basic commands */
	{{"LEFT","WHITE","BUTTON"},"f", "find",    2, {"port",          "extra-info"}, 0},
	{{"RIGHT","YELLOW",""},    "m", "move",    0, {"",              ""},           0},
	{{"MIDDLE","BLUE",""},     "n", "create",  0, {"",              ""},           0},

	/* on four-button puck, add one more basic command */
	{{"GREEN","",""},          "o", "find",    2, {"another",       "port"},       0},

	/* on mice with shift-buttons, add in others still */
	{{"SLEFT","",""},          "",  "find",    1, {"more",          ""},           0},
	{{"SRIGHT","",""},         "",  "var",     2, {"textedit",      "~"},          0},
	{{"SMIDDLE","",""},        "",  "create",  1, {"join-angle",    ""},           0},
	{{NULL, NULL, NULL}, NULL, NULL, 0, {NULL, NULL}} /* 0 */
};

/*
 * default keyboard state: the table below shows the set of commands
 * that will be bound to the keyboard keys.
 */
struct
{
	char  *key;
	char  *command;
	INTSML count;
	char  *args[2];
} us_initialkeyboard[] =
{
	{"a",    "move",      1, {"left",           ""}},
	{"A",    "move",      2, {"left",           "8"}},
	{"b",    "size",      1, {"corner-fixed",   ""}},
	{"c",    "window",    1, {"cursor-centered",""}},
	{"d",    "create",    1, {"join-angle",     ""}},
	{"e",    "erase",     0, {"",               ""}},
	{"E",    "erase",     1, {"pass-through",   ""}},
	{"g",    "grid",      0, {"",               ""}},
	{"G",    "text",      1, {"style",          ""}},
	{"i",    "show",      2, {"object",         "short"}},
	{"I",    "create",    1, {"insert",         ""}},
	{"k",    "text",      2, {"size",           "down"}},
	{"K",    "text",      2, {"size",           "up"}},
	{"l",    "move",      1, {"angle",          ""}},
	{"p",    "window",    1, {"peek",           ""}},
	{"s",    "move",      1, {"right",          ""}},
	{"S",    "move",      2, {"right",          "8"}},
	{"t",    "duplicate", 0, {"",               ""}},
	{"T",    "getproto",  1, {"this-proto",     ""}},
	{"u",    "undo",      0, {"",               ""}},
	{"v",    "window",    1, {"in-zoom",        ""}},
	{"V",    "window",    1, {"out-zoom",       ""}},
	{"w",    "move",      1, {"up",             ""}},
	{"W",    "move",      2, {"up",             "8"}},
	{"x",    "port",      1, {"export",         ""}},
	{"z",    "move",      1, {"down",           ""}},
	{"Z",    "move",      2, {"down",           "8"}},
	{" ",    "getproto",  1, {"next-proto",     ""}},
	{"^^",   "getproto",  1, {"prev-proto",     ""}},
	{"^014", "redraw",	  0, {"",               ""}},
	{"^015", "echo",	  1, {"Electric",       ""}},
	{"?",    "show",      2, {"bindings",       "short"}},
	{"&",    "iterate",   0, {"",               ""}},
	{"!",    "system",    1, {"*",              ""}},
	{"-",    "tellaid",   1, {"user",           ""}},
	{"=",    "tellaid",   2, {"network",        "highlight"}},
	{"[",    "macbegin",  1, {"macro",          ""}},
	{"]",    "macend",    0, {"",               ""}},
	{"%",    "macro",     0, {"",               ""}},
	{",",    "find",      1, {"area-move",      ""}},
	{".",    "find",      1, {"area-size",      ""}},
	{"~",    "arc",       2, {"not",            "rigid"}},
	{"|",    "arc",       1, {"rigid",          ""}},
	{"+",    "arc",       1, {"fixed-angle",    ""}},
	{"/",    "arc",       2, {"not",            "fixed-angle"}},
	{NULL, NULL, 0, {NULL, NULL}} /* 0 */
};

/*
 * When information, detected during broadcast, evokes a reaction that causes
 * change, that change must be queued until the next slice.  For example:
 * deletion of the variable associated with a text window.
 * These routines queue the changes and then execute them when requested
 */
#define	NOUBCHANGE ((UBCHANGE *)-1)
#define	UBKILLFM     1		/* remove facet_message variable */

typedef struct Iubchange
{
	INTBIG     change;		/* type of change */
	void      *object;		/* object that is being changed */
	struct Iubchange *nextubchange;
} UBCHANGE;
static UBCHANGE *us_ubchangefree = NOUBCHANGE;
static UBCHANGE *us_ubchanges = NOUBCHANGE;

static NODEPROTO *us_layouttextprim;

/* prototypes for local routines */
void us_splitwindownames(char*, char*, char*, char*, char*);
WINDOWPART *us_editdummakeeditor(WINDOWPART*, char*, INTSML*, INTSML*);
void us_editdumterminate(EDITOR*);
INTBIG us_editdumtotallines(WINDOWPART*);
char *us_editdumgetline(WINDOWPART*, INTBIG);
void us_editdumaddline(WINDOWPART*, INTBIG, char*);
void us_editdumreplaceline(WINDOWPART*, INTBIG, char*);
void us_editdumdeleteline(WINDOWPART*, INTBIG);
void us_editdumhighlightline(WINDOWPART*, INTBIG);
void us_editdumsuspendgraphics(WINDOWPART*);
void us_editdumresumegraphics(WINDOWPART*);
void us_editdumwritetextfile(WINDOWPART*, char*);
void us_editdumreadtextfile(WINDOWPART*, char*);
void us_editdumeditorterm(WINDOWPART*);
void us_editdumshipchanges(WINDOWPART*);
INTSML us_editdumgotchar(WINDOWPART*, INTSML);
void us_editdumcut(WINDOWPART*);
void us_editdumcopy(WINDOWPART*);
void us_editdumpaste(WINDOWPART*);
void us_editdumundo(WINDOWPART*);
void us_editdumsearch(WINDOWPART*, char*, INTSML);
INTBIG us_shifttextdescriptor(INTBIG, INTBIG, INTBIG, INTBIG, INTBIG, GEOM*);
INTSML us_newubchange(INTBIG, void*);
void us_freeubchange(UBCHANGE*);
INTSML us_pointonexparc(INTBIG cx, INTBIG cy, INTBIG sx, INTBIG sy, INTBIG ex, INTBIG ey, INTBIG x, INTBIG y);
INTBIG us_rotatedescriptArb(GEOM *geom, INTBIG descript, INTSML invert);
void us_scanquickkeys(POPUPMENU *pm, char **quickkeylist, INTBIG quickkeycount);
void us_layouttextpolygon(INTSML layer, TECHNOLOGY *tech, INTBIG *x, INTBIG *y, INTSML count);
INTBIG us_inheritaddress(INTBIG addr, INTBIG type, VARIABLE *var);

/*
 * Routine to free all memory associated with this module.
 */
void us_freemiscmemory(void)
{
	REGISTER EDITOR *e;

	while (us_editorfree != NOEDITOR)
	{
		e = us_editorfree;
		us_editorfree = us_editorfree->nexteditor;
		(*us_editortable[us_currenteditor].terminate)(e);
	}
}

/*
 * initialization routine to bind keys and buttons to functions
 * returns nonzero upon error
 */
INTSML us_initialbinding(void)
{
	REGISTER INTSML i, k, menusave, menux, menuy;
	INTSML j;
	char si[50], sj[20], *par[MAXPARS+7];
	REGISTER char **temp;

	/* make the variables with the bindings */
	i = (INTSML)maxi(maxi(NUMKEYS, NUMBUTS), INITIALMENUX*INITIALMENUY);
	temp = (char **)emalloc(i * (sizeof (char *)), el_tempcluster);
	if (temp == 0) return(1);
	for(j=0; j<i; j++) temp[j] = "";
	(void)setvalkey((INTBIG)us_aid, VAID, us_binding_keys, (INTBIG)temp,
		VSTRING|VISARRAY|VDONTSAVE|(NUMKEYS<<VLENGTHSH));
	(void)setvalkey((INTBIG)us_aid, VAID, us_binding_buttons, (INTBIG)temp,
		VSTRING|VISARRAY|VDONTSAVE|(NUMBUTS<<VLENGTHSH));
	(void)setvalkey((INTBIG)us_aid, VAID, us_binding_menu, (INTBIG)temp,
		VSTRING|VISARRAY|VDONTSAVE|((INITIALMENUX*INITIALMENUY)<<VLENGTHSH));
	efree((char *)temp);

	/* bind the keys */
	for(i=0; us_initialkeyboard[i].key != 0; i++)
	{
		par[0] = "set";   par[1] = "key";
		par[2] = us_initialkeyboard[i].key;
		par[3] = us_initialkeyboard[i].command;
		for(j=0; j<us_initialkeyboard[i].count; j++)
			par[j+4] = us_initialkeyboard[i].args[j];
		us_bind((INTSML)(us_initialkeyboard[i].count+4), par);
	}

	/* bind the mouse commands that fit on the mouse */
	for(i=0; i<buttoncount(); i++)
	{
		(void)strcpy(si, buttonname(i, &j));
		for(j=0; us_initialbutton[j].but[0] != 0; j++)
			if (us_initialbutton[j].used == 0)
		{
			for(k=0; k<3; k++)
				if (namesame(si, us_initialbutton[j].but[k]) == 0)
			{
				par[0] = "set";   par[1] = "button";   par[2] = si;
				par[3] = us_initialbutton[j].com;
				for(k=0; k<us_initialbutton[j].count; k++)
					par[k+4] = us_initialbutton[j].args[k];
				us_bind((INTSML)(us_initialbutton[j].count+4), par);
				us_initialbutton[j].used = 1;
				break;
			}
		}
	}

	/* now bind those mouse commands that can't fit on the mouse */
	for(j=0; us_initialbutton[j].but[0] != 0; j++)
		if (us_initialbutton[j].used == 0 && *us_initialbutton[j].key != 0)
	{
		par[0] = "set";   par[1] = "key";   par[2] = us_initialbutton[j].key;
		par[3] = us_initialbutton[j].com;
		for(k=0; k<us_initialbutton[j].count; k++)
			par[k+4] = us_initialbutton[j].args[k];
		us_bind((INTSML)(us_initialbutton[j].count+4), par);
	}

	/* bind the component menu entries to all "getproto" */
	if (us_menupos <= 1)
	{
		menux = INITIALMENUX;
		menuy = INITIALMENUY;
	} else
	{
		menux = INITIALMENUY;
		menuy = INITIALMENUX;
	}
	j = us_setmenusize((INTSML)menux, (INTSML)menuy, (INTSML)us_menupos, 0);
	menusave = us_aid->aidstate&MENUON;   us_aid->aidstate &= ~MENUON;
	for(i=0; i<(INITIALMENUX*INITIALMENUY); i++)
	{
		par[0] = "set";   par[1] = "menu";
		(void)sprintf(si, "%d", i%INITIALMENUX);
		(void)sprintf(sj, "%d", i/INITIALMENUX);
		if (us_menupos <= 1)
		{
			par[2] = sj;  par[3] = si;
		} else
		{
			par[2] = si;  par[3] = sj;
		}
		par[4] = "getproto";
		us_bind(5, par);
	}

	/* now fill in the "getproto" commands properly */
	us_setmenunodearcs();

	if (menusave != 0) us_aid->aidstate |= MENUON; else
		us_aid->aidstate &= ~MENUON;
	return(0);
}

/*
 * routine to determine for technology "tech" which node and arc prototypes
 * have opaque layers and set the bits in the prototype->userbits.
 * The rules for layer orderings are that the overlappable layers
 * must come first followed by the opaque layers.  The field that is
 * set in the "userbits" is then the index of the first opaque layer.
 */
void us_figuretechopaque(TECHNOLOGY *tech)
{
	REGISTER INTSML j;
	REGISTER INTBIG k;
	REGISTER NODEPROTO *np;
	REGISTER ARCPROTO *ap;
	REGISTER NODEINST *node;
	REGISTER ARCINST *arc;
	static POLYGON *poly = NOPOLYGON;

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

	for(np = tech->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
	{
		np->userbits &= ~NHASOPA;
		node = dummynode();
		node->proto = np;
		node->lowx = np->lowx;   node->highx = np->highx;
		node->lowy = np->lowy;   node->highy = np->highy;
		j = nodepolys(node);
		for(k=0; k<j; k++)
		{
			shapenodepoly(node, (INTSML)k, poly);
			if (poly->desc->bits == LAYERN) continue;
			if ((poly->desc->bits & ~(LAYERT1|LAYERT2|LAYERT3|LAYERT4|LAYERT5)) == 0)
			{
				/* overlappable layer found, make sure it is at start */
				if ((np->userbits&NHASOPA) != 0)
					ttyputerr("%s: node %s has layers out of order!", tech->techname, np->primname);
				continue;
			}

			/* opaque layer found, mark its index if it is the first */
			if ((np->userbits&NHASOPA) == 0)
				np->userbits = (np->userbits & ~NFIRSTOPA) | (k << NFIRSTOPASH);
			np->userbits |= NHASOPA;
		}
	}
	for(ap = tech->firstarcproto; ap != NOARCPROTO; ap = ap->nextarcproto)
	{
		ap->userbits &= ~AHASOPA;
		arc = dummyarc();
		arc->proto = ap;
		arc->userbits = ISDIRECTIONAL;
		j = arcpolys(arc);
		for(k=0; k<j; k++)
		{
			shapearcpoly(arc, (INTSML)k, poly);
			if (poly->desc->bits == LAYERN) continue;
			if ((poly->desc->bits & ~(LAYERT1|LAYERT2|LAYERT3|LAYERT4|LAYERT5)) == 0)
			{
				/* overlappable layer found, make sure it is at start */
				if ((ap->userbits&AHASOPA) != 0)
					ttyputerr("Arc %s:%s has layers out of order!", tech->techname, ap->protoname);
				continue;
			}

			/* opaque layer found, mark its index if it is the first */
			if ((ap->userbits&AHASOPA) == 0)
				ap->userbits = (ap->userbits & ~AFIRSTOPA) | (k << AFIRSTOPASH);
			ap->userbits |= AHASOPA;
		}
	}
}

/*
 * routine to examine the current window structure and fit their sizes
 * to the screen.  If "placemenu" is nonzero, set the menu location too.
 */
void us_windowfit(WINDOWFRAME *whichframe, INTSML placemenu, INTSML scaletofit)
{
	REGISTER WINDOWPART *w;
	REGISTER INTBIG lowy, highy, ulx, uhx, uly, uhy, drawlx, drawhx, drawly, drawhy,
		mtop, mleft, alltext;
	INTSML swid, shei, mwid, mhei, pwid;
	REGISTER INTBIG i, total, newwid, newhei, offx, offy;
	INTBIG slx, shx, sly, shy;
	REGISTER WINDOWFRAME *frame;
	REGISTER VARIABLE *var;
	COMMANDBINDING commandbinding;

	for(frame = el_firstwindowframe; frame != NOWINDOWFRAME; frame = frame->nextwindowframe)
	{
		if (whichframe != NOWINDOWFRAME && whichframe != frame) continue;
		getwindowframesize(frame, &swid, &shei);
		lowy = 0;   highy = shei - 1 - us_menubarsize;

		/* presume that there is no menu */
		drawlx = 0;      drawhx = swid-1;
		drawly = lowy;   drawhy = highy;

		/* if there is a menu, figure it out */
		if ((us_aid->aidstate&MENUON) != 0)
		{
			/* see if the menu is all text */
			alltext = 0;
			var = getvalkey((INTBIG)us_aid, VAID, VSTRING|VISARRAY, us_binding_menu);
			if (var != NOVARIABLE)
			{
				total = us_menux*us_menuy;
				for(i=0; i<total; i++)
				{
					us_parsebinding(((char **)var->addr)[i], &commandbinding);
					if (*commandbinding.command == 0 || commandbinding.nodeglyph != NONODEPROTO ||
						commandbinding.arcglyph != NOARCPROTO)
					{
						us_freebindingparse(&commandbinding);
						break;
					}
					us_freebindingparse(&commandbinding);
				}
				if (i >= total) alltext = 1;
			}
			getpaletteparameters(&mwid, &mhei, &pwid);
			if (us_menuframe == NOWINDOWFRAME)
			{
				/* menus come out of the only editor window */
				switch (us_menupos)
				{
					case 0:		/* menu at top */
						us_menuxsz = us_menuysz = swid / us_menux;
						us_menulx = (swid - us_menux*us_menuxsz)/2;
						us_menuhx = swid - us_menulx;
						us_menuly = highy - us_menuysz*us_menuy;
						us_menuhy = highy;
						drawlx = 0;      drawhx = swid-1;
						drawly = lowy;   drawhy = us_menuly-1;
						break;
					case 1:		/* menu at bottom */
						us_menuxsz = us_menuysz = swid / us_menux;
						us_menulx = (swid - us_menux*us_menuxsz)/2;
						us_menuhx = swid - us_menulx;
						us_menuly = lowy;
						us_menuhy = lowy + us_menuysz * us_menuy;
						drawlx = 0;             drawhx = swid-1;
						drawly = us_menuhy+1;   drawhy = highy;
						break;
					case 2:		/* menu on left */
						us_menuxsz = us_menuysz = (highy-lowy) / us_menuy;

						/* if the menu is all text, allow nonsquare menus */
						if (alltext != 0) us_menuxsz = ALLTEXTWIDTH / us_menux;

						us_menulx = 0;
						us_menuhx = us_menuxsz * us_menux;
						us_menuly = ((highy-lowy) - us_menuy*us_menuysz)/2 + lowy;
						us_menuhy = us_menuly + us_menuy*us_menuysz;
						drawlx = us_menuhx+1;   drawhx = swid-1;
						drawly = lowy;          drawhy = highy;
						break;
					case 3:		/* menu on right */
						us_menuxsz = us_menuysz = (highy-lowy) / us_menuy;

						/* if the menu is all text, allow nonsquare menus */
						if (alltext != 0) us_menuxsz = ALLTEXTWIDTH / us_menux;

						us_menulx = swid - us_menuxsz * us_menux;
						us_menuhx = swid-1;
						us_menuly = ((highy-lowy) - us_menuy*us_menuysz)/2 + lowy;
						us_menuhy = us_menuly + us_menuy*us_menuysz;
						drawlx = 0;      drawhx = us_menulx-1;
						drawly = lowy;   drawhy = highy;
						break;
				}
			} else
			{
				/* floating menu window */
				if (frame == us_menuframe && placemenu != 0)
				{
					getpaletteparameters(&mwid, &mhei, &pwid);
					switch (us_menupos)
					{
						case 0:		/* menu at top */
						case 1:		/* menu at bottom */
							us_menuxsz = us_menuysz = mwid / us_menux;
							if (us_menuysz * us_menuy > pwid)
								us_menuxsz = us_menuysz = pwid / us_menuy;
							us_menulx = 0;
							us_menuhx = us_menux * us_menuxsz;
							us_menuly = 0;
							us_menuhy = us_menuy * us_menuysz;
							mleft = 0;
							if (us_menupos == 0)
							{
								/* menu on the top */
								mtop = 1;
							} else
							{
								/* menu on the bottom */
								mtop = mhei - us_menuysz*us_menuy - 3;
							}
							break;

						case 2:		/* menu on left */
						case 3:		/* menu on right */
							/* determine size of menu entries */
							us_menuxsz = us_menuysz = mhei / us_menuy;
							if (us_menuxsz * us_menux > pwid)
								us_menuxsz = us_menuysz = pwid / us_menux;

							/* if the menu is all text, allow nonsquare menus */
							if (alltext != 0) us_menuxsz = ALLTEXTWIDTH / us_menux;

							/* compute menu parameters */
							us_menuly = 0;
							us_menuhy = us_menuy * us_menuysz;
							us_menulx = 0;
							us_menuhx = us_menux * us_menuxsz;
							mtop = 0;
							if (us_menupos == 2)
							{
								/* menu on the left */
								mleft = 0;
							} else
							{
								/* menu on the right */
								mleft = mwid - us_menuxsz * us_menux - 2;
							}
							break;
					}
					sizewindowframe(us_menuframe, (INTSML)(us_menuhx-us_menulx), (INTSML)(us_menuhy-us_menuly));
					movewindowframe(us_menuframe, (INTSML)mleft, (INTSML)mtop);
				}
			}
		}

		/* now fit the windows in the remaining space */
		for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
		{
			/* this window must be on the right frame */
			if (w->frame != frame) continue;

			/* entire window is handled simply */
			if (strcmp(w->location, "entire") == 0)
			{
				ulx = drawlx;              uhx = drawhx;
				uly = drawly;              uhy = drawhy;
			} else if (strncmp(w->location, "top", 3) == 0)
			{
				ulx = drawlx;              uhx = drawhx;
				uly = (drawhy-drawly)*(100-w->vratio)/100;
				uhy = drawhy;
			} else if (strncmp(w->location, "bot", 3) == 0)
			{
				ulx = drawlx;              uhx = drawhx;
				uly = drawly;              uhy = (drawhy-drawly)*w->vratio/100;
			} else if (strcmp(w->location, "left") == 0)
			{
				ulx = drawlx;              uhx = (drawhx-drawlx)*w->hratio/100;
				uly = drawly;              uhy = drawhy;
			} else if (strcmp(w->location, "right") == 0)
			{
				ulx = (drawhx-drawlx)*(100-w->hratio)/100;
				uhx = drawhx;
				uly = drawly;              uhy = drawhy;
			}

			/* subdivide for fractions of half windows */
			i = 3;
			while (w->location[i] == '-')
			{
				switch (w->location[i+1])
				{
					case 'l': uhx = (uhx - ulx)*w->hratio/100;         break;
					case 'r': ulx = (uhx - ulx)*(100-w->hratio)/100;   break;
					case 't': uly = (uhy - uly)*(100-w->vratio)/100;   break;
					case 'b': uhy = (uhy - uly)*w->vratio/100;         break;
				}
				i += 2;
			}
			if (strcmp(w->location, "entire") != 0)
			{
				ulx++;   uhx--;   uly++;   uhy--;
			}

			/* make sure window has some size */
			if (ulx >= uhx) uhx = ulx + 1;
			if (uly >= uhy) uhy = uly + 1;

			/* make room for border if simulating */
			if ((w->state&WINDOWSIMULATING) != 0)
			{
				ulx += SIMULATINGBORDERSIZE;   uhx -= SIMULATINGBORDERSIZE;
				uly += SIMULATINGBORDERSIZE;   uhy -= SIMULATINGBORDERSIZE;
			}

			/* make room for sliders if a display window */
			if ((w->state&WINDOWTYPE) == DISPWINDOW)
			{
				uhx -= DISPLAYSLIDERSIZE;
				uly += DISPLAYSLIDERSIZE;
			}
			if ((w->state&WINDOWTYPE) == WAVEFORMWINDOW)
			{
				ulx += DISPLAYSLIDERSIZE;
				uly += DISPLAYSLIDERSIZE;
			}

			/* update if the extent changed */
			if (w->uselx != ulx || w->usehx != uhx ||
				w->usely != uly || w->usehy != uhy)
			{
				/* set the window extent */
				w->uselx = (INTSML)ulx;      w->usehx = (INTSML)uhx;
				w->usely = (INTSML)uly;      w->usehy = (INTSML)uhy;

				/* now adjust the database extents of the window */
				slx = w->screenlx;   shx = w->screenhx;
				sly = w->screenly;   shy = w->screenhy;
				if (scaletofit > 0)
				{
					us_squarescreen(w, NOWINDOWPART, 0, &slx, &shx, &sly, &shy);
				} else if (scaletofit < 0)
				{
					newwid = (INTBIG)(((float)(uhx - ulx)) / w->scalex + 0.5);
					newhei = (INTBIG)(((float)(uhy - uly)) / w->scaley + 0.5);
					offx = newwid - (shx - slx);
					offy = newhei - (shy - sly);
					slx -= offx / 2;
					shx = slx + newwid;
					sly -= offy / 2;
					shy = sly + newhei;
				}
				w->screenlx = slx;   w->screenhx = shx;
				w->screenly = sly;   w->screenhy = shy;
				computewindowscale(w);
			}
		}
	}
}

/*
 * routine to adjust the actual drawing area of window "win" to account for
 * the appearance or disappearance of the red simulation border
 */
void us_setwindowsimulation(WINDOWPART *win, INTSML on)
{
	if (on != 0)
	{
		/* simulation beginning: shrink window to make room for red border */
		win->uselx += SIMULATINGBORDERSIZE;   win->usehx -= SIMULATINGBORDERSIZE;
		win->usely += SIMULATINGBORDERSIZE;   win->usehy -= SIMULATINGBORDERSIZE;
		win->state |= WINDOWSIMULATING;
	} else
	{
		/* simulation ending: expand window to remove red border */
		win->uselx -= SIMULATINGBORDERSIZE;   win->usehx += SIMULATINGBORDERSIZE;
		win->usely -= SIMULATINGBORDERSIZE;   win->usehy += SIMULATINGBORDERSIZE;
		win->state &= ~WINDOWSIMULATING;
	}
}

/*
 * routine to tell the names of the windows that result when the window
 * with name "w" is split.  The strings "hwind1" and "hwind2" are filled
 * with the names if the window is split horizontally.  The strings "vwind1"
 * and "vwind2" are filled with the names if the window is split verticaly.
 */
void us_splitwindownames(char *w, char *hwind1, char *hwind2, char *vwind1, char *vwind2)
{
	REGISTER INTSML i;

	if (strcmp(w, "entire") == 0)
	{
		(void)strcpy(hwind1, "top");   (void)strcpy(hwind2, "bottom");
		(void)strcpy(vwind1, "left");  (void)strcpy(vwind2, "right");
		return;
	}
	if (strcmp(w, "top") == 0)
	{
		(void)strcpy(hwind1, "top-l"); (void)strcpy(hwind2, "top-r");
		(void)strcpy(vwind1, "");      (void)strcpy(vwind2, "");
		return;
	}
	if (strcmp(w, "bottom") == 0)
	{
		(void)strcpy(hwind1, "bot-l"); (void)strcpy(hwind2, "bot-r");
		(void)strcpy(vwind1, "");      (void)strcpy(vwind2, "");
		return;
	}
	if (strcmp(w, "left") == 0)
	{
		(void)strcpy(vwind1, "top-l"); (void)strcpy(vwind2, "bot-l");
		(void)strcpy(hwind1, "");      (void)strcpy(hwind2, "");
		return;
	}
	if (strcmp(w, "right") == 0)
	{
		(void)strcpy(vwind1, "top-r"); (void)strcpy(vwind2, "bot-r");
		(void)strcpy(hwind1, "");      (void)strcpy(hwind2, "");
		return;
	}
	(void)strcpy(hwind1, w);   (void)strcpy(hwind2, w);
	(void)strcpy(vwind1, w);   (void)strcpy(vwind2, w);
	i = w[strlen(w)-1];
	if (i == 'l' || i == 'r')
	{
		(void)strcat(vwind1, "-t");  (void)strcat(vwind2, "-b");
		(void)strcpy(hwind1, "");    (void)strcpy(hwind2, "");
	} else
	{
		(void)strcat(hwind1, "-l");  (void)strcat(hwind2, "-r");
		(void)strcpy(vwind1, "");    (void)strcpy(vwind2, "");
	}
}

/*
 * Routine to create a new window with whatever method is available on the
 * current machine (new window in its own frame or just a split of the current
 * window).  If "orientation" is 1, make it a horizontal window; if 2,
 * make it vertical.  Otherwise use any configuration.  Prints an error and
 * returns NOWINDOWPART on failure.
 */
WINDOWPART *us_wantnewwindow(INTSML orientation)
{
	REGISTER WINDOWPART *w;
	WINDOWFRAME *wf;

	if (graphicshas(CANUSEFRAMES))
	{
		/* create a default window space on this frame */
		wf = newwindowframe(0, 0);
		if (wf == NOWINDOWFRAME) wf = getwindowframe(0);
		w = newwindowpart("entire", NOWINDOWPART);
		if (w == NOWINDOWPART)
		{
			us_abortcommand("Cannot create new window");
			return(NOWINDOWPART);
		}
		w->frame = wf;
		w->buttonhandler = DEFAULTBUTTONHANDLER;
		w->charhandler = DEFAULTCHARHANDLER;
		w->changehandler = DEFAULTCHANGEHANDLER;
		w->termhandler = DEFAULTTERMHANDLER;
		w->redisphandler = DEFAULTREDISPHANDLER;
		(void)setvalkey((INTBIG)us_aid, VAID, us_current_window, (INTBIG)w,
			VWINDOWPART|VDONTSAVE);

		/* now draw everything */
		us_drawmenu(0, wf);
	} else
	{
		if (us_needwindow()) return(NOWINDOWPART);
		w = us_splitcurrentwindow(orientation, 0);
	}
	return(w);
}

/*
 * routine to split the current window into two windows.  If "splitkey" is zero,
 * nature of split is unspecified.  If "splitkey" is 1, split horizontally
 * (only when splitting top window).  If "splitkey" is 2, split vertically.
 * If "fillboth" is nonzero, fill both windows with the contents of the
 * old one.  Otherwise, leave the new current window empty.  Returns the address
 * of the new current window (NOWINDOWPART on error).
 */
WINDOWPART *us_splitcurrentwindow(INTSML splitkey, INTSML fillboth)
{
	char wind1[40], wind2[40], vwind1[40], vwind2[40];
	REGISTER char *win1, *win2;
	WINDOWPART windowsave;
	REGISTER WINDOWPART *w2, *w3, *w, *retwin;
	REGISTER INTSML curwx, curwy, horizsplit;
	REGISTER INTBIG x, y, l;
	REGISTER NODEPROTO *np;

	/* figure out new name of windows */
	horizsplit = 1;
	us_splitwindownames(el_curwindowpart->location, wind1, wind2, vwind1, vwind2);

	/* use the horizontal window split unless there is none */
	if (*wind1 == 0) win1 = vwind1; else win1 = wind1;
	if (*wind2 == 0) win2 = vwind2; else win2 = wind2;

	/* special case when splitting just one window: which way to split */
	if (strcmp(el_curwindowpart->location, "entire") == 0)
	{
		/* see if a "horizontal" or "vertical" parameter was given */
		if (splitkey == 2)
		{
			/* vertical window specified explicitly */
			win1 = vwind1;   win2 = vwind2;
			horizsplit = 0;
		} else if (splitkey == 0)
		{
			/* make a guess about window splitting */
			np = el_curwindowpart->curnodeproto;
			if (np != NONODEPROTO)
			{
				curwx = el_curwindowpart->usehx - el_curwindowpart->uselx;
				curwy = el_curwindowpart->usehy - el_curwindowpart->usely;
				x = np->highx - np->lowx;
				y = np->highy - np->lowy;
				l = el_curtech->deflambda;
				if (muldiv(x, curwy/2, l) + muldiv(y, curwx, l) >=
					muldiv(x, curwy, l) + muldiv(y, curwx/2, l))
				{
					/* vertical window makes more sense */
					win1 = vwind1;   win2 = vwind2;
					horizsplit = 0;
				}
			}
		}
	} else
	{
		l = strlen(wind1) - 1;
		if (wind1[l] == 'l' || wind1[l] == 'r') horizsplit = 0;
	}

	/* turn off object and window highlighting */
	us_pushhighlight();
	us_clearhighlightcount();
	w = el_curwindowpart;
	copywindowpart(&windowsave, el_curwindowpart);

	/* make two new windows in "w2" and "w3" to replace "el_curwindowpart" */
	(void)setvalkey((INTBIG)us_aid, VAID, us_current_window, (INTBIG)NOWINDOWPART, VWINDOWPART|VDONTSAVE);
	startobjectchange((INTBIG)us_aid, VAID);
	w2 = newwindowpart(win1, w);
	w3 = newwindowpart(win2, w);
	if (w2 == NOWINDOWPART || w3 == NOWINDOWPART)
	{
		ttyputerr("No memory for windows");
		return(NOWINDOWPART);
	}

	/* make sure the split is even in the split direction */
	if (horizsplit != 0) w2->vratio = w3->vratio = 50; else
		w2->hratio = w3->hratio = 50;

	/* if splitting an editor window, move the editor structure */
	if ((w->state&WINDOWTYPE) == TEXTWINDOW || (w->state&WINDOWTYPE) == POPTEXTWINDOW)
	{
		(void)setval((INTBIG)w3, VWINDOWPART, "editor", (INTBIG)w->editor, VADDRESS);
		(void)setval((INTBIG)w, VWINDOWPART, "editor", -1, VADDRESS);
	}

	/* zap window 2 if both are not to be filled */
	if ((windowsave.state&WINDOWTYPE) != DISPWINDOW) fillboth = 0;
	if (fillboth == 0)
	{
		w2->state = (w2->state & ~(WINDOWTYPE|GRIDON|WINDOWSIMULATING)) | DISPWINDOW;
		w2->buttonhandler = DEFAULTBUTTONHANDLER;
		w2->charhandler = DEFAULTCHARHANDLER;
		w2->changehandler = DEFAULTCHANGEHANDLER;
		w2->termhandler = DEFAULTTERMHANDLER;
		w2->redisphandler = DEFAULTREDISPHANDLER;
		w2->curnodeproto = NONODEPROTO;
		w2->editor = NOEDITOR;
	}

	/* free the current window */
	killwindowpart(w);

	/* set the window extents */
	us_windowfit(w2->frame, 0, 1);

	/* make sure that "w3" is the larger window */
	retwin = w2;
	if (w2->usehx - w2->uselx > w3->usehx - w3->uselx ||
		w2->usehy - w2->usely > w3->usehy - w3->usely)
	{
		retwin = w3;
		w = w2;   w2 = w3;   w3 = w;
	}

	/* use former window for scaling */
	w = &windowsave;

	/* windows might have got bigger: see if grid can be drawn */
	if ((w2->state&GRIDTOOSMALL) != 0) us_gridset(w2, GRIDON);
	if ((w2->state&GRIDTOOSMALL) != 0) us_gridset(w3, GRIDON);

	endobjectchange((INTBIG)us_aid, VAID);
	(void)setvalkey((INTBIG)us_aid, VAID, us_current_window, (INTBIG)w2, VWINDOWPART|VDONTSAVE);

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

/*
 * routine to kill a window.  Kills the current window if "thisw" is nonzero.
 * Kills the other window, making the current one larger, if "thisw" is zero.
 */
void us_killcurrentwindow(INTSML thisw)
{
	REGISTER WINDOWPART *w1, *w2, *wnew, *w;
	WINDOWPART windowsave;
	REGISTER INTBIG windows;
	char windcomb[40], windother[40], wind1[40], wind2[40], vwind1[40], vwind2[40];
	REGISTER WINDOWFRAME *wf;

	w1 = el_curwindowpart;

	/* if this is the only partition, see if the window frame can be deleted */
	if (strcmp(w1->location, "entire") == 0)
	{
		if (graphicshas(CANHAVENOWINDOWS) == 0)
		{
			/* disallow deletion if this is the last window */
			windows = 0;
			for(wf = el_firstwindowframe; wf != NOWINDOWFRAME; wf = wf->nextwindowframe)
				if (wf->floating == 0) windows++;
			if (windows <= 1)
			{
				ttyputerr("Sorry, cannot delete the last window");
				return;
			}
		}
		if (graphicshas(CANUSEFRAMES) != 0)
		{
			/* save highlighting and turn it off */
			us_pushhighlight();
			us_clearhighlightcount();

			/* kill the window */
			startobjectchange((INTBIG)us_aid, VAID);
			us_killwindowpickanother(w1);
			endobjectchange((INTBIG)us_aid, VAID);

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

	/* figure out which other window to merge this with */
	if (strcmp(w1->location, "top") == 0 || strcmp(w1->location, "bottom") == 0 ||
		strcmp(w1->location, "left") == 0 || strcmp(w1->location, "right") == 0)
			(void)strcpy(windcomb, "entire"); else
	{
		(void)strcpy(windcomb, w1->location);
		windcomb[strlen(windcomb)-2] = 0;
		if (strcmp(windcomb, "bot") == 0) (void)strcpy(windcomb, "bottom");
	}

	/* see what divisions this higher window typically makes */
	us_splitwindownames(windcomb, wind1, wind2, vwind1, vwind2);

	/* look for the other window of the typical split */
	(void)strcpy(windother, "");
	if (strcmp(wind2, w1->location) == 0) (void)strcpy(windother, wind1);
	if (strcmp(wind1, w1->location) == 0) (void)strcpy(windother, wind2);
	if (strcmp(vwind2, w1->location) == 0) (void)strcpy(windother, vwind1);
	if (strcmp(vwind1, w1->location) == 0) (void)strcpy(windother, vwind2);

	/* see if there is a window with that name */
	for(w2 = el_topwindowpart; w2 != NOWINDOWPART; w2 = w2->nextwindowpart)
		if (strcmp(w2->location, windother) == 0) break;

	/* if the other window can't be found, try one more hack */
	if (w2 == NOWINDOWPART)
	{
		/* special case for quadrants that get split strangely */
		if ((strncmp(w1->location, "top-", 4) == 0 || strncmp(w1->location, "bot-", 4) == 0) &&
			strlen(w1->location) == 5)
		{
			if (*w1->location == 't') (void)strcpy(windother, "bot-l"); else
				(void)strcpy(windother, "top-l");
			windother[4] = w1->location[4];
			if (windother[4] == 'l') (void)strcpy(windcomb, "left"); else
				(void)strcpy(windcomb, "right");
			for(w2 = el_topwindowpart; w2 != NOWINDOWPART; w2 = w2->nextwindowpart)
				if (strcmp(w2->location, windother) == 0) break;
		}
	}
	if (w2 == NOWINDOWPART)
	{
		us_abortcommand("Cannot kill the current window");
		return;
	}

	/* if the other window is to be killed, swap them */
	if (thisw == 0)
	{
		w = w1;   w1 = w2;   w2 = w;
	}

	/* turn off highlighting */
	us_pushhighlight();
	us_clearhighlightcount();

	/* create a new window to cover both */
	(void)setvalkey((INTBIG)us_aid, VAID, us_current_window, (INTBIG)NOWINDOWPART, VWINDOWPART|VDONTSAVE);
	startobjectchange((INTBIG)us_aid, VAID);
	wnew = newwindowpart(windcomb, w2);
	if (wnew == NOWINDOWPART) return;

	/* save information from the old window */
	copywindowpart(&windowsave, w2);

	/* if merging an editor window, move the editor structure */
	if ((w2->state&WINDOWTYPE) == TEXTWINDOW || (w2->state&WINDOWTYPE) == POPTEXTWINDOW)
	{
		(void)setval((INTBIG)wnew, VWINDOWPART, "editor", (INTBIG)w2->editor, VADDRESS);
		(void)setval((INTBIG)w2, VWINDOWPART, "editor", -1, VADDRESS);
	}

	/* remove old windows */
	killwindowpart(w1);
	killwindowpart(w2);

	/* set window extents */
	us_windowfit(wnew->frame, 0, 1);

	/* use former window for scaling */
	w = &windowsave;

	/* window might have got bigger: see if grid can be drawn */
	if ((wnew->state&GRIDTOOSMALL) != 0) us_gridset(wnew, GRIDON);

	(void)setval((INTBIG)el_curlib, VLIBRARY, "curnodeproto", (INTBIG)wnew->curnodeproto, VNODEPROTO);
	endobjectchange((INTBIG)us_aid, VAID);
	(void)setvalkey((INTBIG)us_aid, VAID, us_current_window, (INTBIG)wnew, VWINDOWPART|VDONTSAVE);

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

/*
 * routine to determine whether the division of window "w1" into windows
 * "w2" and "w3" can be done with block transfer.  This requires that
 * the windows have identical aspect ratios in one axis
 */
INTSML us_windowcansplit(WINDOWPART *w1, WINDOWPART *w2, WINDOWPART *w3)
{
	REGISTER INTBIG new2l, new2h;

	if (w2->usehx-w2->uselx == w3->usehx-w3->uselx &&
		w2->screenhx-w2->screenlx == w3->screenhx-w3->screenlx)
	{
		/* first see if it is an obvious split */
		if (w2->usehx-w2->uselx == w1->usehx-w1->uselx &&
			w2->screenhx-w2->screenlx == w1->screenhx-w1->screenlx)
				return(1);

		/* now see if it is a relative fit for changed window size */
		new2l = muldiv(((w1->usehx-w1->uselx) - (w2->usehx-w2->uselx))/2,
			w1->screenhx-w1->screenlx, w1->usehx-w1->uselx) + w1->screenlx;
		new2h = w2->screenlx + muldiv(w2->usehx-w2->uselx,
			w1->screenhx-w1->screenlx, w1->usehx-w1->uselx);
		if (new2l == w2->screenlx && new2h == w2->screenhx) return(1);
	}
	if (w2->usehy-w2->usely == w3->usehy-w3->usely &&
		w2->screenhy-w2->screenly == w3->screenhy-w3->screenly)
	{
		/* first see if it is an obvious split */
		if (w2->usehy-w2->usely == w1->usehy-w1->usely &&
			w2->screenhy-w2->screenly == w1->screenhy-w1->screenly)
				return(1);

		/* now see if it is a relative fit for changed window size */
		new2l = muldiv(((w1->usehy-w1->usely) - (w2->usehy-w2->usely))/2,
			w1->screenhy-w1->screenly, w1->usehy-w1->usely) + w1->screenly;
		new2h = w2->screenly + muldiv(w2->usehy-w2->usely,
			w1->screenhy-w1->screenly, w1->usehy-w1->usely);
		if (new2l == w2->screenly && new2h == w2->screenhy) return(1);
	}
	return(0);
}

/*
 * routine to ensure that the facet "np"
 * is displayed somewhere on the screen.  If not, it
 * is displayed in the current window
 */
void us_ensurewindow(NODEPROTO *np)
{
	REGISTER WINDOWPART *w;
	char *par[1];

	/* if nothing specified, quit */
	if (np == NONODEPROTO) return;

	/* see if that facet is in a window */
	for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
		if (w->curnodeproto == np) break;

	/* if the facet is not in a window, put it there */
	if (w == NOWINDOWPART)
	{
		par[0] = describenodeproto(np);
		us_editfacet(1, par);
		us_endchanges(NOWINDOWPART);
	}
}

/*
 * Routine to kill window "w" and set the current window to some other.
 */
void us_killwindowpickanother(WINDOWPART *w)
{
	REGISTER NODEPROTO *np;

	killwindowpart(w);

	if (w != el_curwindowpart) return;

	w = el_topwindowpart;
	(void)setvalkey((INTBIG)us_aid, VAID, us_current_window, (INTBIG)w, VWINDOWPART|VDONTSAVE);
	if (w != NOWINDOWPART) np = w->curnodeproto; else np = NONODEPROTO;
	(void)setval((INTBIG)el_curlib, VLIBRARY, "curnodeproto", (INTBIG)np, VNODEPROTO);
}

/*
 * routine to adjust the coordinate values in (x, y) from screen space to
 * the space of window "w"
 */
void us_scaletowindow(INTBIG *x, INTBIG *y, WINDOWPART *w)
{
	*x = muldiv(*x - w->uselx, w->screenhx - w->screenlx, w->usehx - w->uselx) + w->screenlx;
	*y = muldiv(*y - w->usely, w->screenhy - w->screenly, w->usehy - w->usely) + w->screenly;
}

/******************** TEXT EDITING ********************/

/* dummy editor routines */
WINDOWPART *us_editdummakeeditor(WINDOWPART *oriwin, char *header, INTSML *chars, INTSML *lines)
{
	ttyputmsg("Sorry, this is not a working text editor");
	return(NOWINDOWPART);
}
void us_editdumterminate(EDITOR *e) {}
INTBIG us_editdumtotallines(WINDOWPART *win) { return(0); }
char *us_editdumgetline(WINDOWPART *win, INTBIG lindex) { return(""); }
void us_editdumaddline(WINDOWPART *win, INTBIG lindex, char *str) {}
void us_editdumreplaceline(WINDOWPART *win, INTBIG lindex, char *str) {}
void us_editdumdeleteline(WINDOWPART *win, INTBIG lindex) {}
void us_editdumhighlightline(WINDOWPART *win, INTBIG lindex) {}
void us_editdumsuspendgraphics(WINDOWPART *win) {}
void us_editdumresumegraphics(WINDOWPART *win) {}
void us_editdumwritetextfile(WINDOWPART *win, char *file) {}
void us_editdumreadtextfile(WINDOWPART *win, char *file) {}
void us_editdumeditorterm(WINDOWPART *w) {}
void us_editdumshipchanges(WINDOWPART *w) {}
INTSML us_editdumgotchar(WINDOWPART *w, INTSML i) { return(0); }
void us_editdumcut(WINDOWPART *w) {}
void us_editdumcopy(WINDOWPART *w) {}
void us_editdumpaste(WINDOWPART *w) {}
void us_editdumundo(WINDOWPART *w) {}
void us_editdumsearch(WINDOWPART *w, char *str, INTSML fromtop) {}

EDITORTABLE us_editortable[] =
{
	/* the point-and-click editor */
	{"Point-and-click",
	us_editpacmakeeditor, us_editpacterminate, us_editpactotallines, us_editpacgetline,
	us_editpacaddline, us_editpacreplaceline, us_editpacdeleteline, us_editpachighlightline,
	us_editpacsuspendgraphics, us_editpacresumegraphics,
	us_editpacwritetextfile, us_editpacreadtextfile,
	us_editpaceditorterm, us_editpacshipchanges, us_editpacgotchar,
	us_editpaccut, us_editpaccopy, us_editpacpaste,
	us_editpacundo, us_editpacsearch},

	/* the EMACS-like editor */
	{"EMACS",
	us_editemacsmakeeditor, us_editemacsterminate, us_editemacstotallines, us_editemacsgetline,
	us_editemacsaddline, us_editemacsreplaceline, us_editemacsdeleteline, us_editemacshighlightline,
	us_editemacssuspendgraphics, us_editemacsresumegraphics,
	us_editemacswritetextfile, us_editemacsreadtextfile,
	us_editemacseditorterm, us_editemacsshipchanges, us_editemacsgotchar,
	us_editemacscut, us_editemacscopy, us_editemacspaste,
	us_editemacsundo, us_editemacssearch},

	/* the nonexistant editor */
	{"Null",
	us_editdummakeeditor, us_editdumterminate, us_editdumtotallines, us_editdumgetline,
	us_editdumaddline, us_editdumreplaceline, us_editdumdeleteline, us_editdumhighlightline,
	us_editdumsuspendgraphics, us_editdumresumegraphics,
	us_editdumwritetextfile, us_editdumreadtextfile,
	us_editdumeditorterm, us_editdumshipchanges, us_editdumgotchar,
	us_editdumcut, us_editdumcopy, us_editdumpaste,
	us_editdumundo, us_editdumsearch},

	{NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL,
	NULL, NULL,
	NULL, NULL,
	NULL, NULL, NULL,
	NULL, NULL} /* 0 */
};

/*
 * dispatch routine to describe this editor
 */
void us_describeeditor(char **name)
{
	*name = us_editortable[us_currenteditor].editorname;
}

/*
 * dispatch routine for creating a new editor
 */
WINDOWPART *us_makeeditor(WINDOWPART *oriwin, char *header, INTSML *chars, INTSML *lines)
{
	return((*us_editortable[us_currenteditor].makeeditor)(oriwin,header,chars,lines));
}

/*
 * dispatch routine to return the total number of valid lines in the edit buffer
 */
INTBIG us_totallines(WINDOWPART *win)
{
	return((*us_editortable[us_currenteditor].totallines)(win));
}

/*
 * dispatch routine to get the string on line "lindex" (0 based).  A negative line
 * returns the current line.  Returns -1 if the index is beyond the file limit
 */
char *us_getline(WINDOWPART *win, INTBIG lindex)
{
	return((*us_editortable[us_currenteditor].getline)(win, lindex));
}

/*
 * dispatch routine to add line "str" to the text facet to become line "lindex"
 */
void us_addline(WINDOWPART *win, INTBIG lindex, char *str)
{
	(*us_editortable[us_currenteditor].addline)(win, lindex, str);
}

/*
 * dispatch routine to replace the line number "lindex" with the string "str".
 */
void us_replaceline(WINDOWPART *win, INTBIG lindex, char *str)
{
	(*us_editortable[us_currenteditor].replaceline)(win, lindex, str);
}

/*
 * dispatch routine to delete line number "lindex"
 */
void us_deleteline(WINDOWPART *win, INTBIG lindex)
{
	(*us_editortable[us_currenteditor].deleteline)(win, lindex);
}

/*
 * dispatch routine to highlight line "lindex" in the text window
 */
void us_highlightline(WINDOWPART *win, INTBIG lindex)
{
	(*us_editortable[us_currenteditor].highlightline)(win, lindex);
}

/*
 * dispatch routine to stop the graphic display of changes (for batching)
 */
void us_suspendgraphics(WINDOWPART *win)
{
	(*us_editortable[us_currenteditor].suspendgraphics)(win);
}

/*
 * dispatch routine to restart the graphic display of changes and redisplay (for batching)
 */
void us_resumegraphics(WINDOWPART *win)
{
	(*us_editortable[us_currenteditor].resumegraphics)(win);
}

/*
 * dispatch routine to write the text file to "file"
 */
void us_writetextfile(WINDOWPART *win, char *file)
{
	(*us_editortable[us_currenteditor].writetextfile)(win, file);
}

/*
 * dispatch routine to read the text file "file"
 */
void us_readtextfile(WINDOWPART *win, char *file)
{
	(*us_editortable[us_currenteditor].readtextfile)(win, file);
}

/*
 * dispatch routine to get the next character
 */
void us_editorterm(WINDOWPART *w)
{
	(*us_editortable[us_currenteditor].editorterm)(w);
}

/*
 * dispatch routine to force changes from the editor in window "w"
 */
void us_shipchanges(WINDOWPART *w)
{
	(*us_editortable[us_currenteditor].shipchanges)(w);
}

/*
 * dispatch routine to get the next character
 */
INTSML us_gotchar(WINDOWPART *w, INTSML i)
{
	return((*us_editortable[us_currenteditor].gotchar)(w, i));
}

/*
 * dispatch routine to cut text
 */
void us_cuttext(WINDOWPART *w)
{
	(*us_editortable[us_currenteditor].cut)(w);
}

/*
 * dispatch routine to copy text
 */
void us_copytext(WINDOWPART *w)
{
	(*us_editortable[us_currenteditor].copy)(w);
}

/*
 * dispatch routine to paste text
 */
void us_pastetext(WINDOWPART *w)
{
	(*us_editortable[us_currenteditor].paste)(w);
}

/*
 * dispatch routine to undo text changes
 */
void us_undotext(WINDOWPART *w)
{
	(*us_editortable[us_currenteditor].undo)(w);
}

/*
 * dispatch routine to search for text
 */
void us_searchtext(WINDOWPART *w, char *str, INTSML fromtop)
{
	(*us_editortable[us_currenteditor].search)(w, str, fromtop);
}

/*
 * support routine to allocate a new editor from the pool (if any) or memory
 * routine returns NOEDITOR upon error
 */
EDITOR *us_alloceditor(void)
{
	REGISTER EDITOR *e;

	if (us_editorfree == NOEDITOR)
	{
		e = (EDITOR *)emalloc((sizeof (EDITOR)), us_aid->cluster);
		if (e == 0) return(NOEDITOR);
		e->state = 0;
	} else
	{
		/* take editor from free list */
		e = us_editorfree;
		us_editorfree = (EDITOR *)e->nexteditor;
	}
	e->nexteditor = NOEDITOR;
	e->editobjvar = NOVARIABLE;
	return(e);
}

/*
 * support routine to return editor "e" to the pool of free editors
 */
void us_freeeditor(EDITOR *e)
{
	if (e == 0 || e == NOEDITOR) return;
	efree(e->header);
	e->nexteditor = us_editorfree;
	us_editorfree = e;
}

/******************** SPECIAL WINDOW HANDLERS ********************/

/*
 * routine to accept changes in an edit window examining a variable.  If "nature" is:
 *  REPLACETEXTLINE  line "changed" goes from "oldline" to "newline"
 *  DELETETEXTLINE   line "changed deleted (was "oldline")
 *  INSERTTEXTLINE   line "newline" inserted before line "changed"
 *  REPLACEALLTEXT   "changed" lines "newline" replace all text
 */
void us_varchanges(WINDOWPART *w, INTSML nature, char *oldline, char *newline, INTBIG changed)
{
	REGISTER INTSML j, l, save, res;
	REGISTER INTBIG newval, i, len;
	REGISTER char **newlist, *pt;
	float newfloat;
	REGISTER EDITOR *ed;

	ed = w->editor;
	if (ed->editobjvar == NOVARIABLE) return;
	if ((ed->editobjvar->type&VCANTSET) != 0)
	{
		ttyputerr("This variable cannot be changed");
		ed->editobjvar = NOVARIABLE;
		return;
	}
	len = getlength(ed->editobjvar);

	/* when replacing the entire text, reduce to individual calls */
	if (nature == REPLACEALLTEXT)
	{
		newlist = (char **)newline;
		for(i=0; i<changed; i++)
			us_varchanges(w, REPLACETEXTLINE, "", newlist[i], i);
		for(i=len-1; i>=changed; i--)
			us_varchanges(w, DELETETEXTLINE, "", "", i);
		return;
	}

	if (nature == DELETETEXTLINE && len == 1)
	{
		/* delete of last entry: instead, replace it with a null */
		newline = "";
		nature = REPLACETEXTLINE;
	} else if (nature == REPLACETEXTLINE && changed >= len)
	{
		/* change of line beyond end: instead make an insert */
		nature = INSERTTEXTLINE;
	}

	/* disallow deletions and insertions if the number of lines is fixed */
	if (nature == DELETETEXTLINE || nature == INSERTTEXTLINE)
	{
		if ((ed->state&LINESFIXED) != 0) return;
	}

	/* get the value */
	if (nature == REPLACETEXTLINE || nature == INSERTTEXTLINE)
	{
		pt = newline;
		while (*pt == ' ' || *pt == '\t') pt++;
		switch (ed->editobjvar->type&VTYPE)
		{
			case VINTEGER:
			case VSHORT:
			case VADDRESS:
				newval = myatoi(pt);
				break;
			case VFRACT:
				newval = atofr(pt);
				break;
			case VFLOAT:
			case VDOUBLE:
				newfloat = (float)atof(pt);
				newval = castint(newfloat);
				break;
			case VSTRING:
				j = l = strlen(newline);
				if (strcmp(&newline[l-3], " */") == 0)
				{
					for(j = l-5; j >= 0; j--)
						if (strncmp(&newline[j], "/* ", 3) == 0) break;
					while (j > 0 && newline[j-1] == ' ') j--;
					if (j < 0) j = l;
				}
				save = newline[j];
				newline[j] = 0;
				newval = (INTBIG)newline;
				break;
			default:
				ttyputmsg("Cannot update this type of variable (0%o)", ed->editobjvar->type);
				break;
		}
	}

	/* make the change */
	res = 1;
	switch (nature)
	{
		case REPLACETEXTLINE:
			if (changed < 0 || changed >= len) return;
			res = setind((INTBIG)ed->editobjaddr, ed->editobjtype, ed->editobjqual, changed, newval);
			break;
		case DELETETEXTLINE:
			if (changed < 0 || changed >= len) return;
			res = delind((INTBIG)ed->editobjaddr, ed->editobjtype, ed->editobjqual, changed);
			break;
		case INSERTTEXTLINE:
			if (changed <= 0 || changed > len) return;
			res = insind((INTBIG)ed->editobjaddr, ed->editobjtype, ed->editobjqual, changed, newval);

			/* why is this next line necessary? */
			ed->editobjvar = getval((INTBIG)ed->editobjaddr, ed->editobjtype, -1, ed->editobjqual);
			break;
	}

	/* clean-up string if one was passed */
	if ((nature == REPLACETEXTLINE || nature == INSERTTEXTLINE) &&
		(ed->editobjvar->type&VTYPE) == VSTRING) newline[j] = (char)save;

	if (res != 0)
	{
		ttyputerr("Error changing variable: ignoring further changes");
		ed->editobjvar = NOVARIABLE;
	}
}

/*
 * routine to accept changes in an edit window examining a textual facet.  If "nature" is:
 *  REPLACETEXTLINE  line "changed" goes from "oldline" to "newline"
 *  DELETETEXTLINE   line "changed" deleted (was "oldline")
 *  INSERTTEXTLINE   line "newline" inserted before line "changed"
 *  REPLACEALLTEXT   "changed" lines "newline" replace all text
 */
void us_textfacetchanges(WINDOWPART *w, INTSML nature, char *oldline, char *newline, INTBIG changed)
{
	REGISTER INTBIG len;
	REGISTER INTSML res;
	REGISTER EDITOR *ed;

	ed = w->editor;
	if (ed->editobjvar == NOVARIABLE) return;
	if ((ed->editobjvar->type&VTYPE) != VSTRING) return;
	if ((ed->editobjvar->type&VCANTSET) != 0) return;
	len = getlength(ed->editobjvar);

	if (nature == DELETETEXTLINE && len == 1)
	{
		/* delete of last line: instead, replace it with a blank */
		newline = "";
		nature = REPLACETEXTLINE;
	} else if (nature == REPLACETEXTLINE && changed >= len)
	{
		if (changed >= len)
		{
			/* change of line one beyond end: instead make an insert */
			nature = INSERTTEXTLINE;
		}
	}

	res = 1;
	switch (nature)
	{
		case REPLACETEXTLINE:
			if (changed < 0 || changed >= len) return;
			res = setindkey((INTBIG)ed->editobjaddr, VNODEPROTO, el_facet_message,
				changed, (INTBIG)newline);
			break;
		case DELETETEXTLINE:
			if (changed < 0 || changed >= len) return;
			res = delindkey((INTBIG)ed->editobjaddr, VNODEPROTO, el_facet_message,
				changed);
			break;
		case INSERTTEXTLINE:
			if (changed <= 0 || changed > len) return;
			res = insindkey((INTBIG)ed->editobjaddr, VNODEPROTO, el_facet_message,
				changed, (INTBIG)newline);
			break;
		case REPLACEALLTEXT:
			if (setvalkey((INTBIG)ed->editobjaddr, VNODEPROTO, el_facet_message,
				(INTBIG)newline, VSTRING | VISARRAY | (changed << VLENGTHSH)) != NOVARIABLE)
					res = 0;
			break;
	}

	if (res != 0)
	{
		ttyputerr("Error changing variable: ignoring further changes");
		ed->editobjvar = NOVARIABLE;
	}
}

/*
 * private character handler for the text window.  This routine normally
 * passes all commands to the editor's character handler.  However, it
 * interprets M(=) which is for editing the facet on the current line
 */
INTSML us_facetedithandler(WINDOWPART *w, INTSML ch)
{
	char *newpar[2], *str;
	REGISTER INTSML i, meta;
	REGISTER EDITOR *ed;
	extern INTSML us_lastemacschar;

	/* the EMACS text editor must be running */
	us_describeeditor(&str);
	if (namesame(str, "emacs") != 0) return(us_gotchar(w, ch));

	/* see if the meta key is held down (serious black magic) */
	meta = 0;
	if ((ch&0200) != 0) meta = 1;
	if ((us_lastemacschar&2) != 0) meta = 1;

	/* pass character on to the editor if not M(=) */
	if (meta == 0 || (ch&0177) != '=') return(us_gotchar(w, ch));

	/* M(=) typed: parse current line to edit named facet */
	ed = w->editor;
	(void)allocstring(&str, us_getline(w, ed->curline), el_tempcluster);

	/* first drop everything past the first space character */
	for(i=0; str[i] != 0; i++) if (str[i] == ' ') break;
	if (str[i] != 0) str[i] = 0;

	if (str[0] == 0) ttyputerr("No facet specified on this line"); else
	{
		/* issue the "editfacet" command */
		newpar[0] = "editfacet";
		newpar[1] = str;
		(void)tellaid(us_aid, 2, newpar);
		setactivity("Facet Selection");
	}

	/* clean up */
	efree(str);
	return(0);
}

/******************** COMMAND SUPPORT ********************/

INTSML us_demandxy(INTBIG *x, INTBIG *y)
{
	INTSML ret;
	ret = getxy(x, y);
	if (ret != 0) ttyputverbose("Cursor must be in an editing window");
	return(ret);
}

static INTBIG us_curx, us_cury;

/*
 * routine to get the co-ordinates of the cursor into the reference parameters
 * "x" and "y".  If "GOTXY" is set in the global variable "us_state" then
 * this has already been done.  The routine returns nonzero if there is not a
 * valid cursor position.
 */
INTSML getxy(INTBIG *x, INTBIG *y)
{
	INTSML gx, gy;
	REGISTER INTSML ret;

	if ((us_state&GOTXY) == 0)
	{
		readtablet(&gx, &gy);
		ret = us_setxy(gx, gy);
	} else ret = 0;
	*x = us_curx;
	*y = us_cury;
	return(ret);
}

/*
 * routine to take the values (realx, realy) from the tablet and store
 * them in the variables (us_curx, us_cury) which are in design-space
 * co-ordinates.  "GOTXY" in the global variable "us_state" is set to indicate
 * that the co-ordinates are valid.  The current window is set according
 * to the cursor position.  The routine returns nonzero if the position
 * is not in a window.
 */
INTSML us_setxy(INTSML x, INTSML y)
{
	REGISTER WINDOWPART *w;
	REGISTER WINDOWFRAME *wf;

	us_curx = x;   us_cury = y;

	/* figure out which window it is in */
	wf = getwindowframe(0);
	for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
	{
		if (w->frame != wf) continue;
		if (x >= w->uselx && x <= w->usehx && y >= w->usely && y <= w->usehy)
		{
			/* make this window the current one */
			if (w != el_curwindowpart)
			{
				(void)setvalkey((INTBIG)us_aid, VAID, us_current_window, (INTBIG)w, VWINDOWPART|VDONTSAVE);
				(void)setval((INTBIG)el_curlib, VLIBRARY, "curnodeproto",
					(INTBIG)w->curnodeproto, VNODEPROTO);
			}
			us_scaletowindow(&us_curx, &us_cury, w);
			us_state |= GOTXY;
			return(0);
		}
	}
	return(1);
}

/*
 * routine to force the parameters "xcur" and "ycur" to align to the
 * nearest "alignment" units
 */
void gridalign(INTBIG *xcur, INTBIG *ycur, INTBIG alignment)
{
	INTBIG otheralign;
	REGISTER INTBIG val;

	val = us_alignvalue(*xcur, alignment, &otheralign);
	if (abs(*xcur-val) < abs(*xcur-otheralign)) *xcur = val; else
		*xcur = otheralign;
	val = us_alignvalue(*ycur, alignment, &otheralign);
	if (abs(*ycur-val) < abs(*ycur-otheralign)) *ycur = val; else
		*ycur = otheralign;
}

/*
 * routine to return "value", aligned to the nearest "alignment" units.
 * The next closest alignment value (if "value" is not on the grid)
 * is returned in "otheralign".
 */
INTBIG us_alignvalue(INTBIG value, INTBIG alignment, INTBIG *otheralign)
{
	REGISTER INTBIG i, v1, v2;
	REGISTER INTSML sign;

	/* determine the sign of the value */
	if (value < 0) { sign = -1; value = -value; } else sign = 1;

	/* compute the two aligned values */
	if (alignment == 0) v1 = value; else
		v1 = value / alignment * alignment;
	if (v1 == value) v2 = value; else v2 = v1 + alignment;
	v1 *= sign;   v2 *= sign;

	/* make sure "v1" is the closest aligned value */
	if (abs(v1-value) > abs(v2-value)) { i = v1;   v1 = v2;   v2 = i; }

	*otheralign = v2;
	return(v1);
}

/* routine to ensure that a current window exists */
INTSML us_needwindow(void)
{
	if (el_curwindowpart != NOWINDOWPART) return(0);
	us_abortcommand("No current window");
	return(1);
}

/* routine to ensure that a facet exists in the current window */
NODEPROTO *us_needfacet(void)
{
	REGISTER NODEPROTO *np;

	np = getcurfacet();
	if (np != NONODEPROTO) return(np);
	if (el_curwindowpart == NOWINDOWPART)
	{
		us_abortcommand("No current window (select one with Facets/Edit Facet)");
	} else
	{
		if ((us_aid->aidstate&NODETAILS) != 0)
			us_abortcommand("No facet in this window (select one with Facets/Edit Facet)"); else
				us_abortcommand("No facet in this window (select one with '-editfacet')");
	}
	return(NONODEPROTO);
}

/*
 * Routine to ensure that nodes of type "item" can be modified in facet "facet".
 * If "item" is NONODEPROTO, check that facet "facet" can be modified at all.
 * Returns nonzero if the edit cannot be done.
 */
INTSML us_canedit(NODEPROTO *facet, NODEPROTO *item, INTSML giveerror)
{
	REGISTER INTSML count;
	extern COMCOMP us_noyesalwaysp;
	char *pars[5];

	/* if a prototype is specified, check it */
	if (item != NONODEPROTO)
	{
		if (item->primindex != 0)
		{
			/* see if a primitive is locked */
			if ((item->userbits&LOCKEDPRIM) != 0 &&
				(us_useroptions&NOPRIMCHANGES) != 0)
			{
				if (giveerror == 0) return(1);
				(void)initinfstr();
				(void)addstringtoinfstr("Changes to locked primitives (such as ");
				(void)addstringtoinfstr(describenodeproto(item));
				(void)addstringtoinfstr(") are disallowed.  Change anyway?");
				count = ttygetparam(returninfstr(), &us_noyesalwaysp, 5, pars);
				if (count <= 0 || namesamen(pars[0], "no", strlen(pars[0])) == 0) return(1);
				if (namesamen(pars[0], "always", strlen(pars[0])) == 0)
					(void)setvalkey((INTBIG)us_aid, VAID, us_optionflags,
						 us_useroptions & ~NOPRIMCHANGES, VINTEGER);
			}
		} else
		{
			/* see if this type of facet is locked */
			if ((facet->userbits&NILOCKED) != 0)
			{
				if (giveerror == 0) return(1);
				(void)initinfstr();
				(void)addstringtoinfstr("Instances in facet ");
				(void)addstringtoinfstr(describenodeproto(facet));
				(void)addstringtoinfstr(" are locked.  Change anyway?");
				count = ttygetparam(returninfstr(), &us_noyesalwaysp, 5, pars);
				if (count <= 0 || namesamen(pars[0], "no", strlen(pars[0])) == 0) return(1);
				if (namesamen(pars[0], "always", strlen(pars[0])) == 0)
					setval((INTBIG)facet, VNODEPROTO, "userbits",
						facet->userbits & ~NILOCKED, VINTEGER);
			}

			/* see if all facet instances are locked */
			if ((us_useroptions&NOFACETCHANGES) != 0)
			{
				if (giveerror == 0) return(1);
				(void)initinfstr();
				(void)addstringtoinfstr("Changes to facet instances (such as ");
				(void)addstringtoinfstr(describenodeproto(facet));
				(void)addstringtoinfstr(") are disallowed.  Change anyway?");
				count = ttygetparam(returninfstr(), &us_noyesalwaysp, 5, pars);
				if (count <= 0 || namesamen(pars[0], "no", strlen(pars[0])) == 0) return(1);
				if (namesamen(pars[0], "always", strlen(pars[0])) == 0)
					(void)setvalkey((INTBIG)us_aid, VAID, us_optionflags,
						 us_useroptions & ~NOFACETCHANGES, VINTEGER);
			}
		}
	}

	/* check for general changes to the facet */
	if ((facet->userbits&NLOCKED) != 0)
	{
		if (giveerror == 0) return(1);
		(void)initinfstr();
		(void)addstringtoinfstr("Changes to facet ");
		(void)addstringtoinfstr(describenodeproto(facet));
		(void)addstringtoinfstr(" are locked.  Change anyway?");
		count = ttygetparam(returninfstr(), &us_noyesalwaysp, 5, pars);
		if (count <= 0 || namesamen(pars[0], "no", strlen(pars[0])) == 0) return(1);
		if (namesamen(pars[0], "always", strlen(pars[0])) == 0)
			setval((INTBIG)facet, VNODEPROTO, "userbits", facet->userbits & ~NLOCKED, VINTEGER);
	}
	return(0);
}

/*
 * routine to determine the proper position of the cursor given that
 * it must adjust to the nearest "angle" tenth-degree radial coming out of
 * the point (tx,ty) and that it is currently at (nx, ny).  The
 * adjusted point is placed into (fx, fy) and the proper radial starting
 * point in "poly" is placed in (tx, ty).
 */
void us_getslide(INTSML angle, INTBIG tx, INTBIG ty, INTBIG nx, INTBIG ny, INTBIG *fx,
	INTBIG *fy)
{
	REGISTER INTSML ang;
	INTBIG ix, iy;

	/* if angle is unconstrained, use the exact cursor position */
	if (angle <= 0)
	{
		*fx = nx;   *fy = ny;
		return;
	}

	/* check all permissable angles */
	for(ang = 0; ang < 3600; ang += angle)
	{
		/* get close point to (nx,ny) on "ang" tenth-degree radial from (tx,ty) */
		(void)intersect(tx, ty, ang, nx, ny, (ang+900)%3600, &ix, &iy);

		/* accumulate the intersection closest to the cursor */
		if (ang != 0 && abs(*fx-nx) + abs(*fy-ny) < abs(ix-nx) + abs(iy-ny)) continue;
		*fx = ix;   *fy = iy;
	}
}

/*
 * routine to convert command interpreter letter "letter" to the full
 * variable name on the user aid object
 */
char *us_commandvarname(INTSML letter)
{
	static char varname[20];

	if (isupper(letter))
	{
		(void)strcpy(varname, "USER_local_capX");
		varname[14] = tolower(letter);
	} else
	{
		(void)strcpy(varname, "USER_local_X");
		varname[11] = (char)letter;
	}
	return(varname);
}

/*
 * routine to parse the variable path in "str" and return the object address
 * and type on which this variable resides along with the variable name.
 * The object address and type are placed in "objaddr" and "objtype";
 * the variable name is placed in "varname".  "comvar" is set to nonzero
 * if the variable is a command-interpreter variable (as opposed to a
 * database variable).  If an array index specification is given (a "[]")
 * then the index value is returned in "aindex" (otherwise the value is set
 * to -1).  The routine returns nonzero if the variable path is invalid.
 */
INTSML us_getvar(char *str, INTBIG *objaddr, INTBIG *objtype, char **varname,
	INTSML *comvar, INTSML *aindex)
{
	REGISTER INTSML i;
	static char fullvarname[50];

	/* see if an array index is specified */
	*aindex = -1;
	i = strlen(str);
	if (str[i-1] == ']') for(i--; i >= 0; i--) if (str[i] == '[')
	{
		*aindex = (INTSML)myatoi(&str[i+1]);
		break;
	}

	/* see if this is a command interpreter variable */
	*comvar = 0;
	if (str[1] == 0 || str[1] == '[')
	{
		if (str[0] >= 'a' && str[0] <= 'z') *comvar = 1;
		if (str[0] >= 'A' && str[0] <= 'Z') *comvar = 1;
	}

	/* replace the actual name for command interpreter variables */
	if (*comvar != 0)
	{
		(void)sprintf(fullvarname, "aid:user.%s", us_commandvarname(str[0]));
		str = fullvarname;
	}

	/* pick apart the variable path */
	return(us_evaluatevariable(str, objaddr, objtype, varname));
}

void us_adjustlambda(INTBIG oldlam, INTBIG newlam)
{
	REGISTER WINDOWPART *w;
	REGISTER INTBIG i;

	i = muldiv(us_alignment, newlam, oldlam);
	(void)setvalkey((INTBIG)us_aid, VAID, us_alignment_obj, i, VINTEGER|VDONTSAVE);
	i = muldiv(us_edgealignment, newlam, oldlam);
	(void)setvalkey((INTBIG)us_aid, VAID, us_alignment_edge, i, VINTEGER|VDONTSAVE);
	for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
	{
		(void)setval((INTBIG)w, VWINDOWPART, "gridx", muldiv(w->gridx, newlam, oldlam), VINTEGER);
		(void)setval((INTBIG)w, VWINDOWPART, "gridy", muldiv(w->gridy, newlam, oldlam), VINTEGER);
	}
}

/*
 * routine to determine whether the command bound to key "key" is the
 * last instance of the "tellaid user" command that is bound to a key.
 * This is important to know because if the last "tellaid user" is unbound,
 * there is no way to execute any long commands!
 */
INTSML us_islasteval(INTSML key)
{
	REGISTER INTSML j, keytotal, retval, foundanother;
	REGISTER VARIABLE *var;
	COMMANDBINDING commandbindingthis, commandbindingother;

	/* get the command on this key */
	retval = 0;
	var = getvalkey((INTBIG)us_aid, VAID, VSTRING|VISARRAY, us_binding_keys);
	if (var == NOVARIABLE) return(0);
	us_parsebinding(((char **)var->addr)[key], &commandbindingthis);
	if (*commandbindingthis.command != 0)
	{
		/* see if it is "tellaid user" */
		if (namesame(commandbindingthis.command, "tellaid user") == 0)
		{
			/* this is "tellaid user"...check all other keys for this command */
			keytotal = (INTSML)getlength(var);
			foundanother = 0;
			for(j=0; j<keytotal; j++)
			{
				if (j == key) continue;
				us_parsebinding(((char **)var->addr)[j], &commandbindingother);
				if (*commandbindingother.command != 0 &&
					namesame(commandbindingother.command, "tellaid user") == 0) foundanother++;
				us_freebindingparse(&commandbindingother);
				if (foundanother != 0) break;
			}
			if (foundanother == 0) retval = 1;
		}
	}
	us_freebindingparse(&commandbindingthis);
	return(retval);
}

/*
 * routine to set the trace information in the "size" coordinate pairs in
 * "newlist" onto the node "ni".
 */
void us_settrace(NODEINST *ni, INTBIG *newlist, INTSML size)
{
	REGISTER INTBIG lx, hx, ly, hy, x, y;
	REGISTER INTSML i;
	INTBIG lxo, hxo, lyo, hyo;

	/* get the extent of the data */
	lx = hx = newlist[0];   ly = hy = newlist[1];
	for(i=1; i<size; i++)
	{
		x = newlist[i*2];
		y = newlist[i*2+1];
		lx = mini(lx, x);   hx = maxi(hx, x);
		ly = mini(ly, y);   hy = maxi(hy, y);
	}

	/* make these co-ordinates relative to the center */
	x = (hx+lx) / 2;   y = (hy+ly) / 2;
	for(i=0; i<size; i++)
	{
		newlist[i*2] = newlist[i*2] - x;
		newlist[i*2+1] = newlist[i*2+1] - y;
	}

	/* adjust size for node size offset */
	nodesizeoffset(ni, &lxo, &lyo, &hxo, &hyo);
	lx -= lxo;   hx += hxo;
	ly -= lyo;   hy += hyo;

	/* erase the node instance */
	startobjectchange((INTBIG)ni, VNODEINST);

	/* change the trace data */
	(void)setvalkey((INTBIG)ni, VNODEINST, el_trace, (INTBIG)newlist,
		VINTEGER|VISARRAY|((size*2)<<VLENGTHSH));

	/* scale the node (which redraws too) */
	if (ni->proto->primindex != 0 && (lx != ni->lowx || hx != ni->highx || ly != ni->lowy ||
		hy != ni->highy || ni->rotation != 0 || ni->transpose != 0))
			modifynodeinst(ni, lx-ni->lowx, ly-ni->lowy, hx-ni->highx, hy-ni->highy,
				(INTSML)(-ni->rotation), ni->transpose);

	/* redisplay */
	endobjectchange((INTBIG)ni, VNODEINST);
}

/*
 * routine to scale the trace information on node "ni" given that it will change
 * in size to "nlx"->"nhx" and "nly"->"nhy".
 */
void us_scaletraceinfo(NODEINST *ni, INTBIG nlx, INTBIG nhx, INTBIG nly, INTBIG nhy)
{
	REGISTER VARIABLE *var;
	REGISTER INTBIG *newlist, oldx, oldy;
	REGISTER INTSML len, i;

	/* stop now if no trace information */
	var = gettrace(ni);
	if (var == NOVARIABLE) return;

	/* get new array for new trace */
	len = (INTSML)getlength(var);
	newlist = (INTBIG *)emalloc(len * SIZEOFINTBIG, el_tempcluster);
	if (newlist == 0) return;

	/* copy the data and scale it */
	for(i=0; i<len; i += 2)
	{
		oldx = ((INTBIG *)var->addr)[i];
		oldy = ((INTBIG *)var->addr)[i+1];
		oldx = muldiv(oldx, nhx - nlx, ni->highx - ni->lowx);
		oldy = muldiv(oldy, nhy - nly, ni->highy - ni->lowy);
		newlist[i] = oldx;   newlist[i+1] = oldy;
	}

	/* store the new list */
	(void)setvalkey((INTBIG)ni, VNODEINST, el_trace, (INTBIG)newlist,
		VINTEGER|VISARRAY|(len<<VLENGTHSH));
	efree((char *)newlist);
}

/*
 * Routine to fillet the two highlighted objects
 */
void us_dofillet(void)
{
	REGISTER VARIABLE *var;
	HIGHLIGHT thishigh, otherhigh;
	REGISTER NODEINST *ni1, *ni2, *swapni;
	double startoffset, endangle, srot, erot, newangle, dx, dy;
	INTBIG ix, iy, ix1, iy1, ix2, iy2;
	REGISTER INTSML ang1, ang2, size1, size2, arc1, arc2, swapsize, icount, on1, on2, newrot;
	REGISTER INTBIG *newlist1, *newlist2, *line1xs, *line1ys, *line1xe, *line1ye,
		*line2xs, *line2ys, *line2xe, *line2ye, x, y, i, *swaplist;
	XARRAY trans;

	/* must be exactly two nodes selected */
	var = getvalkey((INTBIG)us_aid, VAID, VSTRING|VISARRAY, us_highlighted);
	if (var == NOVARIABLE)
	{
		us_abortcommand("Must select two nodes before filleting");
		return;
	}
	if (getlength(var) != 2)
	{
		us_abortcommand("Must select two nodes before filleting");
		return;
	}

	if (us_makehighlight(((char **)var->addr)[0], &thishigh) != 0 ||
		us_makehighlight(((char **)var->addr)[1], &otherhigh) != 0) return;

	/* get the two objects */
	if ((thishigh.status&HIGHTYPE) != HIGHFROM || (otherhigh.status&HIGHTYPE) != HIGHFROM)
	{
		us_abortcommand("Must select two nodes before filleting");
		return;
	}

	if (thishigh.fromgeom->entrytype != OBJNODEINST || otherhigh.fromgeom->entrytype != OBJNODEINST)
	{
		us_abortcommand("Must select two nodes before filleting");
		return;
	}

	/* get description of first node */
	ni1 = thishigh.fromgeom->entryaddr.ni;
	if (ni1->proto == art_circleprim)
	{
		getarcdegrees(ni1, &startoffset, &endangle);
		if (startoffset == 0.0 && endangle == 0.0)
		{
			us_abortcommand("Must select arcs, not circles before filleting");
			return;
		}
		newlist1 = emalloc((6*SIZEOFINTBIG), el_tempcluster);
		if (newlist1 == 0) return;
		newlist1[0] = (ni1->lowx + ni1->highx) / 2;
		newlist1[1] = (ni1->lowy + ni1->highy) / 2;
		getarcendpoints(ni1, startoffset, endangle, &newlist1[2], &newlist1[3],
			&newlist1[4], &newlist1[5]);
		arc1 = 1;
	} else if (ni1->proto == art_openedpolygonprim || ni1->proto == art_openeddottedpolygonprim ||
		ni1->proto == art_openeddashedpolygonprim || ni1->proto == art_openedfardottepolygonprim ||
		ni1->proto == art_closedpolygonprim)
	{
		var = gettrace(ni1);
		if (var == NOVARIABLE)
		{
			us_abortcommand("Must select nodes with outline information before filleting");
			return;
		}

		/* transform the traces */
		size1 = getlength(var) / 2;
		newlist1 = emalloc((size1*2*SIZEOFINTBIG), el_tempcluster);
		if (newlist1 == 0) return;
		makerot(ni1, trans);
		x = (ni1->highx + ni1->lowx) / 2;
		y = (ni1->highy + ni1->lowy) / 2;
		for(i=0; i<size1; i++)
			xform(((INTBIG *)var->addr)[i*2]+x, ((INTBIG *)var->addr)[i*2+1]+y, &newlist1[i*2],
				&newlist1[i*2+1], trans);
		arc1 = 0;
	} else
	{
		us_abortcommand("Node %s cannot be filleted", describenodeinst(ni1));
		return;
	}

	/* get description of second node */
	ni2 = otherhigh.fromgeom->entryaddr.ni;
	if (ni2->proto == art_circleprim)
	{
		getarcdegrees(ni2, &startoffset, &endangle);
		if (startoffset == 0.0 && endangle == 0.0)
		{
			us_abortcommand("Must select arcs, not circles before filleting");
			return;
		}
		newlist2 = emalloc((6*SIZEOFINTBIG), el_tempcluster);
		if (newlist2 == 0) return;
		newlist2[0] = (ni2->lowx + ni2->highx) / 2;
		newlist2[1] = (ni2->lowy + ni2->highy) / 2;
		getarcendpoints(ni2, startoffset, endangle, &newlist2[2], &newlist2[3],
			&newlist2[4], &newlist2[5]);
		arc2 = 1;
	} else if (ni2->proto == art_openedpolygonprim || ni2->proto == art_openeddottedpolygonprim ||
		ni2->proto == art_openeddashedpolygonprim || ni2->proto == art_openedfardottepolygonprim ||
		ni2->proto == art_closedpolygonprim)
	{
		var = gettrace(ni2);
		if (var == NOVARIABLE)
		{
			us_abortcommand("Must select nodes with outline information before filleting");
			return;
		}

		/* transform the traces */
		size2 = getlength(var) / 2;
		newlist2 = emalloc((size2*2*SIZEOFINTBIG), el_tempcluster);
		if (newlist2 == 0) return;
		makerot(ni2, trans);
		x = (ni2->highx + ni2->lowx) / 2;
		y = (ni2->highy + ni2->lowy) / 2;
		for(i=0; i<size2; i++)
			xform(((INTBIG *)var->addr)[i*2]+x, ((INTBIG *)var->addr)[i*2+1]+y, &newlist2[i*2],
				&newlist2[i*2+1], trans);
		arc2 = 0;
	} else
	{
		us_abortcommand("Node %s cannot be filleted", describenodeinst(ni2));
		return;
	}

	/* handle different types of filleting */
	if (arc1 != 0 && arc2 != 0)
	{
		/* cannot handle arc-to-arc filleting */
		us_abortcommand("Cannot fillet two curves");
	} else if (arc1 == 0 && arc2 == 0)
	{
		/* handle line-to-line filleting: find out which endpoints are closest */
		if (computedistance(x,y, newlist1[0],newlist1[1]) <
			computedistance(x,y, newlist1[size1*2-2],newlist1[size1*2-1]))
		{
			line1xs = &newlist1[0];
			line1ys = &newlist1[1];
			line1xe = &newlist1[2];
			line1ye = &newlist1[3];
		} else
		{
			line1xs = &newlist1[size1*2-2];
			line1ys = &newlist1[size1*2-1];
			line1xe = &newlist1[size1*2-4];
			line1ye = &newlist1[size1*2-3];
		}
		if (computedistance(*line1xs,*line1ys, newlist2[0],newlist2[1]) <
			computedistance(*line1xs,*line1ys, newlist2[size2*2-2],newlist2[size2*2-1]))
		{
			line2xs = &newlist2[0];
			line2ys = &newlist2[1];
			line2xe = &newlist2[2];
			line2ye = &newlist2[3];
		} else
		{
			line2xs = &newlist2[size2*2-2];
			line2ys = &newlist2[size2*2-1];
			line2xe = &newlist2[size2*2-4];
			line2ye = &newlist2[size2*2-3];
		}

		/* compute intersection point */
		ang1 = figureangle(*line1xs, *line1ys, *line1xe, *line1ye);
		ang2 = figureangle(*line2xs, *line2ys, *line2xe, *line2ye);
		if (intersect(*line1xs, *line1ys, ang1, *line2xs, *line2ys, ang2, &ix, &iy) != 0)
			us_abortcommand("Lines do not intersect"); else
		{
			*line1xs = ix;   *line1ys = iy;
			*line2xs = ix;   *line2ys = iy;
			us_pushhighlight();
			us_clearhighlightcount();
			us_settrace(ni1, newlist1, (INTSML)size1);
			us_settrace(ni2, newlist2, (INTSML)size2);
			(void)us_pophighlight(1);
		}
	} else
	{
		/* handle arc-to-line filleting */
		if (arc1 == 0)
		{
			swaplist = newlist1;   newlist1 = newlist2;   newlist2 = swaplist;
			swapsize = size1;      size1 = size2;         size2 = swapsize;
			swapni = ni1;          ni1 = ni2;             ni2 = swapni;
		}

		/* "newlist1" describes the arc, "newlist2" describes the line */
		if (computedistance(newlist1[0],newlist1[1], newlist2[0],newlist2[1]) <
			computedistance(newlist1[0],newlist1[1], newlist2[size2*2-2],newlist2[size2*2-1]))
		{
			line2xs = &newlist2[0];
			line2ys = &newlist2[1];
			line2xe = &newlist2[2];
			line2ye = &newlist2[3];
		} else
		{
			line2xs = &newlist2[size2*2-2];
			line2ys = &newlist2[size2*2-1];
			line2xe = &newlist2[size2*2-4];
			line2ye = &newlist2[size2*2-3];
		}
		icount = circlelineintersection(newlist1[0],newlist1[1], newlist1[2],newlist1[3],
			*line2xs, *line2ys, *line2xe, *line2ye, &ix1, &iy1, &ix2, &iy2, 0);
		if (icount == 0)
		{
			us_abortcommand("Line does not intersect arc: cannot fillet");
		} else
		{
			if (icount == 2)
			{
				on1 = us_pointonexparc(newlist1[0],newlist1[1], newlist1[2],newlist1[3],
					newlist1[4],newlist1[5], ix1, iy1);
				on2 = us_pointonexparc(newlist1[0],newlist1[1], newlist1[2],newlist1[3],
					newlist1[4],newlist1[5], ix2, iy2);
				if (on1 == 0 && on2 != 0)
				{
					icount = 1;
					ix1 = ix2;   iy1 = iy2;
				} else if (on1 != 0 && on2 == 0)
				{
					icount = 1;
				}
			}
			if (icount == 2)
			{
				x = (*line2xs + *line2xe) / 2;
				y = (*line2ys + *line2ye) / 2;
				if (computedistance(ix1,iy1, x,y) > computedistance(ix2,iy2, x,y))
				{
					ix1 = ix2;   iy1 = iy2;
				}
			}

			/* make them fillet at (ix1,iy1) */
			us_pushhighlight();
			us_clearhighlightcount();

			/* adjust the arc (node ni1) */
			dx = (double)(newlist1[2]-newlist1[0]);   dy = (double)(newlist1[3]-newlist1[1]);
			if (dx == 0.0 && dy == 0.0)
			{
				us_abortcommand("Domain error during fillet");
				return;
			}
			srot = atan2(dy, dx);
			if (srot < 0.0) srot += EPI*2.0;

			dx = (double)(newlist1[4]-newlist1[0]);   dy = (double)(newlist1[5]-newlist1[1]);
			if (dx == 0.0 && dy == 0.0)
			{
				us_abortcommand("Domain error during fillet");
				return;
			}
			erot = atan2(dy, dx);
			if (erot < 0.0) erot += EPI*2.0;

			dx = (double)(ix1-newlist1[0]);   dy = (double)(iy1-newlist1[1]);
			if (dx == 0.0 && dy == 0.0)
			{
				us_abortcommand("Domain error during fillet");
				return;
			}
			newangle = atan2(dy, dx);
			if (newangle < 0.0) newangle += EPI*2.0;
			if (computedistance(ix1,iy1, newlist1[2],newlist1[3]) <
				computedistance(ix1,iy1, newlist1[4],newlist1[5])) srot = newangle; else
					erot = newangle;
			erot -= srot;
			if (erot < 0.0) erot += EPI*2.0;
			newrot = (INTSML)rounddouble(srot * 1800.0 / EPI);
			srot -= ((double)newrot) * EPI / 1800.0;
			startobjectchange((INTBIG)ni1, VNODEINST);
			modifynodeinst(ni1, 0, 0, 0, 0, (INTSML)(newrot - ni1->rotation), 0);
			setarcdegrees(ni1, srot, erot);
			endobjectchange((INTBIG)ni1, VNODEINST);

			/* adjust the line (node ni2) */
			*line2xs = ix1;   *line2ys = iy1;
			us_settrace(ni2, newlist2, (INTSML)size2);

			/* restore highlighting */
			(void)us_pophighlight(1);
		}
	}
	efree((char *)newlist1);
	efree((char *)newlist2);
}

/*
 * Houtine to convert the text in "msg" to bits on the display.
 */
void us_layouttext(char *layer, char *font, INTBIG scale, char *msg)
{
	REGISTER NODEPROTO *np;
	REGISTER INTSML err, x, y;
	REGISTER INTBIG cx, cy, bx, by, tsize;
	INTSML wid, hei;
	char **rowstart, *data;
	static POLYGON *poly = NOPOLYGON;

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

	/* set the text size */
	tsize = atoi(font);
	if (tsize < 4 || tsize > 20)
	{
		us_abortcommand("Choose a size between 4 and 20");
		return;
	}
	tsize = (tsize-4)/2 + TXT4P;
	screensettextsize(el_curwindowpart, (INTSML)tsize);

	/* convert the text to bits */
	err = gettextbits(el_curwindowpart, msg, &wid, &hei, &rowstart);
	if (err != 0)
	{
		us_abortcommand("Sorry, this system cannot layout text");
		return;
	}

	/* determine the primitive to use for the layout */
	for(us_layouttextprim = el_curtech->firstnodeproto; us_layouttextprim != NONODEPROTO;
		us_layouttextprim = us_layouttextprim->nextnodeproto)
			if (namesame(us_layouttextprim->primname, layer) == 0) break;
	if (us_layouttextprim == NONODEPROTO)
	{
		us_abortcommand("Cannot find '%s' node", layer);
		return;
	}

	bx = (el_curwindowpart->screenlx+el_curwindowpart->screenhx -
		wid * el_curtech->deflambda * scale) / 2;
	by = (el_curwindowpart->screenly+el_curwindowpart->screenhy -
		hei * el_curtech->deflambda * scale) / 2;
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, us_aid->cluster);
	mergeinit();
	for(y=0; y<hei; y++)
	{
		cy = by - y * el_curtech->deflambda * scale;
		data = rowstart[y];
		for(x=0; x<wid; x++)
		{
			cx = bx + x * el_curtech->deflambda * scale;
			if (*data != 0)
			{
				poly->xv[0] = cx - el_curtech->deflambda * scale / 2;
				poly->yv[0] = cy - el_curtech->deflambda * scale / 2;
				poly->xv[1] = cx + el_curtech->deflambda * scale / 2;
				poly->yv[1] = cy + el_curtech->deflambda * scale / 2;
				poly->style = FILLEDRECT;
				poly->count = 2;
				mergestorepolygon(0, el_curtech, poly);
			}
			data++;
		}
	}
	us_clearhighlightcount();
	mergedone(us_layouttextpolygon);
}

/*
 * Helper routine for "us_layouttext" to process a polygon and convert it to layout.
 */
void us_layouttextpolygon(INTSML layer, TECHNOLOGY *tech, INTBIG *x, INTBIG *y, INTSML count)
{
	REGISTER NODEPROTO *facet;
	REGISTER NODEINST *ni;
	REGISTER INTBIG lx, hx, ly, hy, cx, cy, *newlist;
	REGISTER INTSML i;
	HIGHLIGHT high;

	facet = us_needfacet();
	if (facet == NONODEPROTO) return;
	lx = hx = x[0];
	ly = hy = y[0];
	for(i=1; i<count; i++)
	{
		if (x[i] < lx) lx = x[i];
		if (x[i] > hx) hx = x[i];
		if (y[i] < ly) ly = y[i];
		if (y[i] > hy) hy = y[i];
	}
	cx = (lx+hx) / 2;   cy = (ly+hy) / 2;
	ni = newnodeinst(us_layouttextprim, lx, hx, ly, hy, 0, 0, facet);
	newlist = (INTBIG *)emalloc(count * 2 * SIZEOFINTBIG, el_tempcluster);
	if (newlist == 0) return;
	for(i=0; i<count; i++)
	{
		newlist[i*2] = x[i] - cx;
		newlist[i*2+1] = y[i] - cy;
	}
	(void)setvalkey((INTBIG)ni, VNODEINST, el_trace, (INTBIG)newlist,
		VINTEGER|VISARRAY|((count*2)<<VLENGTHSH));
	endobjectchange((INTBIG)ni, VNODEINST);
	high.status = HIGHFROM;
	high.fromgeom = ni->geom;
	high.fromport = NOPORTPROTO;
	high.frompoint = 0;
	high.facet = facet;
	(void)us_addhighlight(&high);
}

/*
 * Routine to determine whether the point (x,y) is on the arc centered at (cx,cy), starting
 * at (sx,sy), and ending at (ex,ey).  Returns nonzero if on the arc.
 */
INTSML us_pointonexparc(INTBIG cx, INTBIG cy, INTBIG sx, INTBIG sy, INTBIG ex, INTBIG ey, INTBIG x, INTBIG y)
{
	REGISTER INTSML as, ae, a;

	as = figureangle(cx, cy, sx, sy);
	ae = figureangle(cx, cy, ex, ey);
	a = figureangle(cx, cy, x, y);

	if (ae > as)
	{
		if (a >= as && a <= ae) return(1);
	} else
	{
		if (a >= as || a <= ae) return(1);
	}
	return(0);
}

/*
 * routine to recursively check sub-facet revision times
 * P. Attfield
 */
void us_check_facet_date(NODEPROTO *np, UINTBIG rev_time)
{
	REGISTER NODEPROTO *np2;
	REGISTER NODEINST *ni;

	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		np2 = ni->proto;
		if (np2->primindex != 0) continue; /* ignore if primitive */
		if (np2->temp1 != 0) continue; /* ignore if already seen */
		us_check_facet_date(np2, rev_time); /* recurse */
	}

	/* check this facet */
	np->temp1++; /* flag that we have seen this one */
	if (np->revisiondate <= rev_time) return;

	/* possible error in hierarchy */
	ttyputerr("WARNING: sub-facet '%s' has been edited", describenodeproto(np));
	ttyputmsg("         since the last revision to the current facet");
}

/*
 * routine to switch to library "lib"
 */
void us_switchtolibrary(LIBRARY *lib)
{
	char *newpar[2];

	/* first remove any "getproto" menu entries to facets in lib */
	us_removegetfacets();

	/* next select the new library */
	us_clearhighlightcount();
	us_adjustlambda(el_curtech->deflambda, lib->lambda[el_curtech->techindex]);
	selectlibrary(lib);
	us_setlambda(NOWINDOWFRAME);
	if ((us_curnodeproto == NONODEPROTO || us_curnodeproto->primindex == 0) &&
		(us_state&NONPERSISTENTCURNODE) == 0)
			us_setnodeproto(el_curtech->firstnodeproto);

	/* edit the top facet of the new library if there is one */
	if (el_curlib->curnodeproto != NONODEPROTO)
	{
		newpar[0] = describenodeproto(el_curlib->curnodeproto);
		us_editfacet(1, newpar);
	}

	/* redo the explorer window (if it is up) */
	us_redoexplorerwindow();
}

/******************** TEXT OBJECTS ********************/

/*
 * routine to recompute the text descriptor in "descript" to change the
 * grab-point according to the location of (xcur, ycur), given that the
 * text centers at (xc, yc) and is "xw" by "yw" in size.  The new text
 * descriptor is returned.
 */
INTBIG us_figuregrabpoint(INTBIG descript, INTBIG xcur, INTBIG ycur, INTBIG xc,
	INTBIG yc, INTBIG xw, INTBIG yw)
{
	if (xcur < xc - xw/2)
	{
		/* grab-point is on the bottom left */
		if (ycur < yc - yw/2) return((descript & ~VTPOSITION) | VTPOSUPRIGHT);

		/* grab-point is on the top left */
		if (ycur > yc + yw/2) return((descript & ~VTPOSITION) | VTPOSDOWNRIGHT);

		/* grab-point is on the left */
		return((descript & ~VTPOSITION) | VTPOSRIGHT);
	}

	if (xcur > xc + xw/2)
	{
		/* grab-point is on the bottom right */
		if (ycur < yc - yw/2) return((descript & ~VTPOSITION) | VTPOSUPLEFT);

		/* grab-point is on the top right */
		if (ycur > yc + yw/2) return((descript & ~VTPOSITION) | VTPOSDOWNLEFT);

		/* grab-point is on the right */
		return((descript & ~VTPOSITION) | VTPOSLEFT);
	}

	/* grab-point is on the bottom */
	if (ycur < yc - yw/2) return((descript & ~VTPOSITION) | VTPOSUP);

	/* grab-point is on the top */
	if (ycur > yc + yw/2) return((descript & ~VTPOSITION) | VTPOSDOWN);

	/* grab-point is in the center: check for VERY center */
	if (ycur >= yc - yw/6 && ycur <= yc + yw/6 && xcur >= xc - xw/6 &&
		xcur <= xc + xw/6) return((descript & ~VTPOSITION) | VTPOSBOXED);

	/* grab-point is simply centered */
	return((descript & ~VTPOSITION) | VTPOSCENT);
}

/*
 * routine to return the new text descriptor field, given that the old is in
 * "formerdesc".  The instructions for changing this variable are in "count"
 * and "par", where "count" where the first two values are the X and Y offset,
 * and the third value is the text position.  Returns -1 on error.
 */
INTBIG us_figurevariableplace(INTBIG formerdesc, INTSML count, char *par[])
{
	INTBIG xval, yval;
	REGISTER INTBIG grab;

	if (count >= 2)
	{
		xval = atofr(par[0]) * 4 / WHOLE;
		yval = atofr(par[1]) * 4 / WHOLE;
		formerdesc = us_setdescriptoffset(formerdesc, xval, yval);
	}
	if (count >= 3)
	{
		grab = us_gettextposition(par[2]);
		if (grab < 0) return(-1);
		formerdesc = (formerdesc & ~VTPOSITION) | grab;
	}
	return(formerdesc);
}

/*
 * routine to change the X and Y offset factors in the text descriptor
 * "formerdesc" to "xval" and "yval".  Returns the new text descriptor.
 */
INTBIG us_setdescriptoffset(INTBIG formerdesc, INTBIG xval, INTBIG yval)
{
	/* make sure the range is proper */
	if (abs(xval) >= 512 || abs(yval) >= 512)
	{
		ttyputmsg("Text clipped to within 128 lambda of object");
		xval = mini(xval, 511);   xval = maxi(xval, -511);
		yval = mini(yval, 511);   yval = maxi(yval, -511);
	}

	formerdesc &= ~(VTXOFF|VTXOFFNEG|VTYOFF|VTYOFFNEG);
	formerdesc |= (abs(xval) << VTXOFFSH) & VTXOFF;
	if (xval < 0) formerdesc |= VTXOFFNEG;
	formerdesc |= (abs(yval) << VTYOFFSH) & VTYOFF;
	if (yval < 0) formerdesc |= VTYOFFNEG;
	return(formerdesc);
}

/*
 * routine to rotate the text descriptor in "descript" to account for
 * the rotation of the object on which it resides: "geom".  The new
 * text descriptor is returned.
 */
INTBIG us_rotatedescript(GEOM *geom, INTBIG descript)
{
	return(us_rotatedescriptArb(geom, descript, 0));
}

/*
 * routine to undo the rotation of the text descriptor in "descript" to account for
 * the rotation of the object on which it resides: "geom".  The new
 * text descriptor is returned.
 */
INTBIG us_rotatedescriptI(GEOM *geom, INTBIG descript)
{
	return(us_rotatedescriptArb(geom, descript, 1));
}

/*
 * routine to rotate the text descriptor in "descript" to account for
 * the rotation of the object on which it resides: "geom".  Inverts the
 * sense of the rotation if "invert" is nonzero.  The new
 * text descriptor is returned.
 */
INTBIG us_rotatedescriptArb(GEOM *geom, INTBIG descript, INTSML invert)
{
	REGISTER INTBIG style;
	REGISTER NODEINST *ni;
	XARRAY trans;

	/* arcs do not rotate */
	if (geom->entrytype != OBJNODEINST) return(descript);

	switch (descript&VTPOSITION)
	{
		case VTPOSCENT:
		case VTPOSBOXED:     return(descript);
		case VTPOSUP:        style = TEXTBOT;       break;
		case VTPOSDOWN:      style = TEXTTOP;       break;
		case VTPOSLEFT:      style = TEXTRIGHT;     break;
		case VTPOSRIGHT:     style = TEXTLEFT;      break;
		case VTPOSUPLEFT:    style = TEXTBOTRIGHT;  break;
		case VTPOSUPRIGHT:   style = TEXTBOTLEFT;   break;
		case VTPOSDOWNLEFT:  style = TEXTTOPRIGHT;  break;
		case VTPOSDOWNRIGHT: style = TEXTTOPLEFT;   break;
	}
	ni = geom->entryaddr.ni;
	if (invert != 0)
	{
		if (ni->transpose == 0) makeangle((INTSML)((3600 - ni->rotation)%3600), 0, trans); else
			makeangle(ni->rotation, ni->transpose, trans);
	} else
	{
		makeangle(ni->rotation, ni->transpose, trans);
	}
	style = rotatelabel((INTSML)style, trans);
	switch (style)
	{
		case TEXTBOT:       style = VTPOSUP;          break;
		case TEXTTOP:       style = VTPOSDOWN;        break;
		case TEXTRIGHT:     style = VTPOSLEFT;        break;
		case TEXTLEFT:      style = VTPOSRIGHT;       break;
		case TEXTBOTRIGHT:  style = VTPOSUPLEFT;      break;
		case TEXTBOTLEFT:   style = VTPOSUPRIGHT;     break;
		case TEXTTOPRIGHT:  style = VTPOSDOWNLEFT;    break;
		case TEXTTOPLEFT:   style = VTPOSDOWNRIGHT;   break;
	}
	return((descript & ~VTPOSITION) | style);
}

/*
 * routine to adjust the displayable text on node "ni" to account for new
 * size/rotation factors in the node.  This is only done for invisible pins
 * in the generic technology, where the displayable text must track the node.
 */
void us_adjustdisplayabletext(NODEINST *ni)
{
	REGISTER INTSML i;
	REGISTER INTBIG descript, halfx, halfy;
	REGISTER VARIABLE *var;

	/* make sure this is the invisible pin */
	if (ni->proto != gen_invispinprim) return;

	/* search for displayable text */
	for(i=0; i<ni->numvar; i++)
	{
		var = &ni->firstvar[i];
		if ((var->type&VDISPLAY) == 0) continue;

		/* compute the proper display offset */
		descript = var->textdescript;
		halfx = (ni->highx - ni->lowx) * 2 / ni->proto->tech->deflambda;
		halfy = (ni->highy - ni->lowy) * 2 / ni->proto->tech->deflambda;
		switch (descript&VTPOSITION)
		{
			case VTPOSCENT:
			case VTPOSBOXED:
				modifydescript((INTBIG)ni, VNODEINST, var, us_setdescriptoffset(descript, 0, 0));
				break;
			case VTPOSUP:
				modifydescript((INTBIG)ni, VNODEINST, var, us_setdescriptoffset(descript, 0, -halfy));
				break;
			case VTPOSDOWN:
				modifydescript((INTBIG)ni, VNODEINST, var, us_setdescriptoffset(descript, 0, halfy));
				break;
			case VTPOSLEFT:
				modifydescript((INTBIG)ni, VNODEINST, var, us_setdescriptoffset(descript, halfx, 0));
				break;
			case VTPOSRIGHT:
				modifydescript((INTBIG)ni, VNODEINST, var, us_setdescriptoffset(descript, -halfx, 0));
				break;
			case VTPOSUPLEFT:
				modifydescript((INTBIG)ni, VNODEINST, var, us_setdescriptoffset(descript, halfx, -halfy));
				break;
			case VTPOSUPRIGHT:
				modifydescript((INTBIG)ni, VNODEINST, var, us_setdescriptoffset(descript, -halfx, -halfy));
				break;
			case VTPOSDOWNLEFT:
				modifydescript((INTBIG)ni, VNODEINST, var, us_setdescriptoffset(descript, halfx, halfy));
				break;
			case VTPOSDOWNRIGHT:
				modifydescript((INTBIG)ni, VNODEINST, var, us_setdescriptoffset(descript, -halfx, halfy));
				break;
		}
	}
}

/*
 * routine to parse the "grab-point" specification in "pp" and return the
 * code.  Prints an error message and returns -1 on error.
 */
INTBIG us_gettextposition(char *pp)
{
	REGISTER INTSML l;

	l = strlen(pp);
	if (namesamen(pp, "centered", l) == 0 && l >= 1) return(VTPOSCENT);
	if (namesamen(pp, "boxed", l) == 0 && l >= 1) return(VTPOSBOXED);
	if (namesame(pp, "up") == 0) return(VTPOSUP);
	if (namesame(pp, "down") == 0) return(VTPOSDOWN);
	if (namesamen(pp, "left", l) == 0 && l >= 1) return(VTPOSLEFT);
	if (namesamen(pp, "right", l) == 0 && l >= 1) return(VTPOSRIGHT);
	if (namesamen(pp, "up-left", l) == 0 && l >= 4) return(VTPOSUPLEFT);
	if (namesamen(pp, "up-right", l) == 0 && l >= 4) return(VTPOSUPRIGHT);
	if (namesamen(pp, "down-left", l) == 0 && l >= 6) return(VTPOSDOWNLEFT);
	if (namesamen(pp, "down-right", l) == 0 && l >= 6) return(VTPOSDOWNRIGHT);
	us_abortcommand("Unrecognized grab-point: %s", pp);
	return(-1);
}

/*
 * routine to parse the "text size" specification in "pp" and return the
 * code.  Prints an error message and returns -1 on error.
 */
INTBIG us_gettextsize(char *pp, INTBIG old)
{
	REGISTER INTSML l;

	l = strlen(pp);
	if (namesamen(pp, "4p", l) == 0 && l >= 1) return(TXT4P);
	if (namesamen(pp, "6p", l) == 0 && l >= 1) return(TXT6P);
	if (namesamen(pp, "8p", l) == 0 && l >= 1) return(TXT8P);
	if (namesamen(pp, "10p", l) == 0 && l >= 1) return(TXT10P);
	if (namesamen(pp, "12p", l) == 0 && l >= 1) return(TXT12P);
	if (namesamen(pp, "14p", l) == 0 && l >= 1) return(TXT14P);
	if (namesamen(pp, "16p", l) == 0 && l >= 1) return(TXT16P);
	if (namesamen(pp, "18p", l) == 0 && l >= 1) return(TXT18P);
	if (namesamen(pp, "20p", l) == 0 && l >= 1) return(TXT20P);
	if (namesamen(pp, "small", l) == 0 && l >= 1) return(TXTSMALL);
	if (namesamen(pp, "medium", l) == 0 && l >= 1) return(TXTMEDIUM);
	if (namesamen(pp, "large", l) == 0 && l >= 1) return(TXTLARGE);
	if (namesamen(pp, "up", l) == 0 && l >= 1)
	{
		if (old >= TXT4P && old < TXT20P) old++; else
			if (old >= TXTSMALL && old < TXTLARGE) old++;
		return(old);
	} else if (namesamen(pp, "down", l) == 0 && l >= 1)
	{
		if (old > TXT4P && old <= TXT20P) old--; else
			if (old > TXTSMALL && old <= TXTLARGE) old--;
		return(old);
	}
	us_abortcommand("Unrecognized text size: %s", pp);
	return(-1);
}

/************************ IN-PLACE VARIABLE EDITING ************************/

INTBIG      us_editvarstartline, us_editvarstartchar;
INTBIG      us_editvarlabellen;
INTBIG      us_editvarendline, us_editvarendchar;
INTBIG      us_editvarclickline, us_editvarclickchar;
VARIABLE   *us_editvariable;
INTBIG      us_editvarlength;
INTBIG      us_editvarlineheight;
INTBIG      us_editvarobjtype;
INTBIG      us_editvarobjaddr;
INTSML      us_editvariabledoubleclick;
char      **us_editvarlines;
char       *us_editvaroneline[1];
char       *us_editvarvarname = 0;
TECHNOLOGY *us_editvartech;

void us_editvariabletexthighlight(void);
INTSML us_editvariabletexteachdown(INTBIG x, INTBIG y);
INTSML us_editvariabletextfindpos(INTBIG x, INTBIG y, INTBIG *line, INTBIG *chr);
void us_editvariableforcefullwords(INTBIG *startline, INTBIG *startchar, INTBIG *endline, INTBIG *endchar);
INTSML us_editvariabletexthandlechar(INTSML chr);
void us_editvariabletextreplacetext(char *replace);

void us_editvariabletext(VARIABLE *var, INTBIG objtype, INTBIG objaddr, char *varname)
{
	INTSML oldnormalcursor, x, y, but, tsx, tsy;
	INTBIG line, chr;
	REGISTER INTSML font, tf;

	/* set I-beam cursor */
	oldnormalcursor = us_normalcursor;
	setnormalcursor(IBEAMCURSOR);

	/* preserve information in globals */
	us_editvariable = var;
	us_editvarobjtype = objtype & VTYPE;
	us_editvarobjaddr = objaddr;
	if (us_editvarvarname == 0) allocstring(&us_editvarvarname, varname, us_aid->cluster); else
		reallocstring(&us_editvarvarname, varname, us_aid->cluster);
	us_editvarlabellen = 0;
	if ((us_editvariable->type&VISARRAY) == 0)
	{
		us_editvarlength = 1;
		(void)allocstring(&us_editvaroneline[0], describevariable(var, -1, -1), us_aid->cluster);
		if ((var->textdescript&VTDISPLAYPART) == VTDISPLAYNAMEVALUE)
		{
			us_editvarlabellen = strlen(us_editvaroneline[0]) - strlen((char *)var->addr);
		}
		us_editvarlines = us_editvaroneline;
	} else
	{
		us_editvarlength = getlength(var);
		us_editvarlines = (char **)var->addr;
	}
	switch (objtype)
	{
		case VNODEINST:
			us_editvartech = whattech(((NODEINST *)objaddr)->proto);
			break;
		case VARCINST:
			us_editvartech = ((ARCINST *)objaddr)->proto->tech;
			break;
		case VPORTPROTO:
			us_editvartech = whattech(((PORTPROTO *)objaddr)->subnodeinst->proto);
			break;
		case VNODEPROTO:
			us_editvartech = whattech((NODEPROTO *)objaddr);
			break;
	}

	/* flush graphics */
	us_endchanges(NOWINDOWPART);

	/* determine height of a line of text */
	font = (INTSML)((us_editvariable->textdescript & VTSIZE) >> VTSIZESH);
	tf = truefontsize(font, el_curwindowpart, us_editvartech);
	screensettextsize(el_curwindowpart, tf);
	screengettextsize(el_curwindowpart, "X", &tsx, &tsy);
	us_editvarlineheight = tsy;

	/* set highlighting to cover all text */
	us_editvarstartline = 0;
	us_editvarstartchar = us_editvarlabellen;
	us_editvarendline = us_editvarlength-1;
	us_editvarendchar = strlen(us_editvarlines[us_editvarendline]);
	us_editvariabletexthighlight();

	/* loop while editing text */
	for(;;)
	{
		if (ttydataready() != 0)
		{
			chr = ttygetchar();
			if (us_editvariabletexthandlechar((INTSML)chr) != 0) break;
			continue;
		}
		waitforbutton(&x, &y, &but);
		if (but < 0) continue;
		us_editvariabledoubleclick = doublebutton(but);
		if (us_editvariabletextfindpos(x, y, &line, &chr) == 0) break;
		us_editvariabletexthighlight();
		if (line == 0 && chr < us_editvarlabellen) chr = us_editvarlabellen;
		us_editvarstartline = line;
		us_editvarstartchar = chr;
		us_editvarendline = line;
		us_editvarendchar = chr;
		us_editvarclickline = line;
		us_editvarclickchar = chr;
		if (us_editvariabledoubleclick != 0)
			us_editvariableforcefullwords(&us_editvarstartline, &us_editvarstartchar,
				&us_editvarendline, &us_editvarendchar);
		us_editvariabletexthighlight();

		trackcursor(0, us_ignoreup, us_nullvoid,
			us_editvariabletexteachdown, us_stoponchar, us_nullvoid,
				TRACKNORMAL);
	}

	/* remove highlighting */
	us_editvariabletexthighlight();

	/* restore curosr */
	setnormalcursor(oldnormalcursor);
}

INTSML us_editvariabletexthandlechar(INTSML chr)
{
	char replace[2], *pt;
	REGISTER INTBIG startchar, endchar, i, j;

	if (chr == 034)		/* left arrow */
	{
		us_editvariabletexthighlight();
		if (us_editvarstartline == us_editvarendline &&
			us_editvarstartchar == us_editvarendchar)
		{
			if (us_editvarstartchar > 0)
			{
				if (us_editvarstartline != 0 || us_editvarstartchar > us_editvarlabellen)
					us_editvarstartchar--;
			} else
			{
				if (us_editvarstartline > 0)
				{
					us_editvarstartline--;
					us_editvarstartchar = strlen(us_editvarlines[us_editvarstartline]);
				}
			}
		}
		us_editvarendline = us_editvarstartline;
		us_editvarendchar = us_editvarstartchar;
		us_editvariabletexthighlight();
		return(0);
	}
	if (chr == 035)		/* right arrow */
	{
		us_editvariabletexthighlight();
		if (us_editvarstartline == us_editvarendline &&
			us_editvarstartchar == us_editvarendchar)
		{
			if (us_editvarendchar < (INTBIG)strlen(us_editvarlines[us_editvarendline]))
				us_editvarendchar++; else
			{
				if (us_editvarendline < us_editvarlength-1)
				{
					us_editvarendline++;
					us_editvarendchar = 0;
				}
			}
		}
		us_editvarstartline = us_editvarendline;
		us_editvarstartchar = us_editvarendchar;
		us_editvariabletexthighlight();
		return(0);
	}
	if (chr == 036)		/* up arrow */
	{
		us_editvariabletexthighlight();
		if (us_editvarstartline > 0)
		{
			us_editvarstartline--;
			if (us_editvarstartchar > (INTBIG)strlen(us_editvarlines[us_editvarstartline]))
				us_editvarstartchar = strlen(us_editvarlines[us_editvarstartline]);
		}
		us_editvarendline = us_editvarstartline;
		us_editvarendchar = us_editvarstartchar;
		us_editvariabletexthighlight();
		return(0);
	}
	if (chr == 037)		/* down arrow */
	{
		us_editvariabletexthighlight();
		if (us_editvarendline < us_editvarlength)
		{
			us_editvarendline++;
			if (us_editvarendchar > (INTBIG)strlen(us_editvarlines[us_editvarendline]))
				us_editvarendchar = strlen(us_editvarlines[us_editvarendline]);
		}
		us_editvarstartline = us_editvarendline;
		us_editvarstartchar = us_editvarendchar;
		us_editvariabletexthighlight();
		return(0);
	}

	/* handle paste */
	if (chr == 0200+'v' || chr == 0200+'V')
	{
		pt = getcutbuffer();
		us_editvariabletexthighlight();
		us_editvariabletextreplacetext(pt);
		if ((us_editvariable->type&VISARRAY) == 0)
		{
			(void)reallocstring(&us_editvaroneline[0], describevariable(us_editvariable, -1, -1),
				us_aid->cluster);
			us_editvarlines = us_editvaroneline;
		} else
		{
			us_editvarlength = getlength(us_editvariable);
			us_editvarlines = (char **)us_editvariable->addr;
		}
		us_editvariabletexthighlight();
		return(0);
	}

	/* handle copy/cut */
	if (chr == 0200+'c' || chr == 0200+'C' ||
		chr == 0200+'x' || chr == 0200+'X')
	{
		(void)initinfstr();
		for(i=us_editvarstartline; i<=us_editvarendline; i++)
		{
			if (i > us_editvarstartline) (void)addtoinfstr('\n');
			startchar = 0;
			endchar = strlen(us_editvarlines[i]);
			if (i == us_editvarstartline) startchar = us_editvarstartchar;
			if (i == us_editvarendline) endchar = us_editvarendchar;
			for(j=startchar; j<endchar; j++)
				(void)addtoinfstr(us_editvarlines[i][j]);
		}
		setcutbuffer(returninfstr());
		if (chr == 0200+'c' || chr == 0200+'C') return(0);
		chr = 0;
	}

	/* delete what is selected and insert what was typed */
	if (chr == '\n' || chr == '\r')
	{
		/* cannot insert second line if text is not an array */
		if ((us_editvariable->type&VISARRAY) == 0) return(1);
	}
	us_editvariabletexthighlight();
	if (chr == 0177 || chr == 010)
	{
		chr = 0;
		if (us_editvarstartline == us_editvarendline &&
			us_editvarstartchar == us_editvarendchar)
		{
			if (us_editvarstartchar > 0)
			{
				if (us_editvarstartline != 0 || us_editvarstartchar > us_editvarlabellen)
					us_editvarstartchar--;
			} else
			{
				if (us_editvarstartline > 0)
				{
					us_editvarstartline--;
					us_editvarstartchar = strlen(us_editvarlines[us_editvarstartline]);
				}
			}
		}
	}
	replace[0] = (char)chr;
	replace[1] = 0;
	us_editvariabletextreplacetext(replace);
	if ((us_editvariable->type&VISARRAY) == 0)
	{
		(void)reallocstring(&us_editvaroneline[0], describevariable(us_editvariable, -1, -1),
			us_aid->cluster);
		us_editvarlines = us_editvaroneline;
	} else
	{
		us_editvarlength = getlength(us_editvariable);
		us_editvarlines = (char **)us_editvariable->addr;
	}
	us_editvariabletexthighlight();
	return(0);
}

void us_editvariabletextreplacetext(char *replace)
{
	void *stringarray;
	REGISTER INTBIG i, newtype, newline, newchar;
	char **newtext;
	REGISTER VARIABLE *var;
	INTBIG count;

	stringarray = newstringarray(el_tempcluster);

	/* add all lines before the start of selection */
	newline = 0;
	for(i=0; i<us_editvarstartline; i++)
	{
		if (i != 0) addtostringarray(stringarray, us_editvarlines[i]); else
			addtostringarray(stringarray, &us_editvarlines[i][us_editvarlabellen]);			
		newline++;
	}

	/* build the line with the selection start */
	if (newline == 0) newchar = us_editvarlabellen; else newchar = 0;
	(void)initinfstr();
	for(i=newchar; i<us_editvarstartchar; i++)
	{
		(void)addtoinfstr(us_editvarlines[us_editvarstartline][i]);
		newchar++;
	}

	/* now add the replacement text */
	for(i=0; i<(INTBIG)strlen(replace); i++)
	{
		if (replace[i] == '\n' || replace[i] == '\r')
		{
			addtostringarray(stringarray, returninfstr());
			(void)initinfstr();
			newline++;
			newchar = 0;
		} else
		{
			(void)addtoinfstr(replace[i]);
			newchar++;
		}
	}

	/* now add the line with the selection end */
	for(i=us_editvarendchar; i<(INTBIG)strlen(us_editvarlines[us_editvarendline]); i++)
		(void)addtoinfstr(us_editvarlines[us_editvarendline][i]);
	addtostringarray(stringarray, returninfstr());

	/* add all lines after the end of selection */
	for(i=us_editvarendline+1; i<us_editvarlength; i++)
		addtostringarray(stringarray, us_editvarlines[i]);

	/* get the new text and put it on the object */
	newtext = getstringarray(stringarray, &count);
	if (us_editvarobjtype != VPORTPROTO)
		startobjectchange(us_editvarobjaddr, us_editvarobjtype); else
			startobjectchange((INTBIG)(((PORTPROTO *)us_editvarobjaddr)->subnodeinst), VNODEINST);
	if (us_editvarobjtype == VNODEPROTO)
		us_undrawfacetvariable(us_editvariable, (NODEPROTO *)us_editvarobjaddr);
	if ((us_editvariable->type&VISARRAY) == 0)
	{
		var = setval(us_editvarobjaddr, us_editvarobjtype,
			us_editvarvarname, (INTBIG)newtext[0], us_editvariable->type);
	} else
	{
		newtype = (us_editvariable->type & ~VLENGTH) | (count << VLENGTHSH);
		var = setval(us_editvarobjaddr, us_editvarobjtype,
			us_editvarvarname, (INTBIG)newtext, newtype);
	}
	if (var != NOVARIABLE) us_editvariable = var;
	if (us_editvarobjtype == VNODEPROTO)
		us_drawfacetvariable(us_editvariable, (NODEPROTO *)us_editvarobjaddr);
	if (us_editvarobjtype != VPORTPROTO)
		endobjectchange(us_editvarobjaddr, us_editvarobjtype); else
			endobjectchange((INTBIG)(((PORTPROTO *)us_editvarobjaddr)->subnodeinst), VNODEINST);
	us_endchanges(NOWINDOWPART);
	killstringarray(stringarray);

	/* set the new selection point */
	us_editvarstartline = us_editvarendline = newline;
	us_editvarstartchar = us_editvarendchar = newchar;
}

INTSML us_editvariabletexteachdown(INTBIG x, INTBIG y)
{
	INTBIG line, chr, startline, startchar, endline, endchar;

	if (us_editvariabletextfindpos(x, y, &line, &chr) == 0) return(0);
	if (line == 0 && chr < us_editvarlabellen) chr = us_editvarlabellen;
	startline = us_editvarstartline;
	startchar = us_editvarstartchar;
	endline = us_editvarendline;
	endchar = us_editvarendchar;
	if (line > us_editvarclickline || (line == us_editvarclickline && chr > us_editvarclickchar))
	{
		startline = us_editvarclickline;
		startchar = us_editvarclickchar;
		endline = line;
		endchar = chr;
		if (us_editvariabledoubleclick != 0)
			us_editvariableforcefullwords(&startline, &startchar, &endline, &endchar);
	}
	if (line < us_editvarclickline || (line == us_editvarclickline && chr < us_editvarclickchar))
	{
		startline = line;
		startchar = chr;
		endline = us_editvarclickline;
		endchar = us_editvarclickchar;
		if (us_editvariabledoubleclick != 0)
			us_editvariableforcefullwords(&startline, &startchar, &endline, &endchar);
	}
	if (startline != us_editvarstartline || startchar != us_editvarstartchar ||
		endline != us_editvarendline || endchar != us_editvarendchar)
	{
		us_editvariabletexthighlight();
		us_editvarstartline = startline;
		us_editvarstartchar = startchar;
		us_editvarendline = endline;
		us_editvarendchar = endchar;
		us_editvariabletexthighlight();
	}
	return(0);
}

void us_editvariableforcefullwords(INTBIG *startline, INTBIG *startchar, INTBIG *endline, INTBIG *endchar)
{
	char *pt;
	INTBIG len;

	pt = us_editvarlines[*startline];
	while (*startchar > 0 && isalnum(pt[*startchar - 1]))
		(*startchar)--;

	pt = us_editvarlines[*endline];
	len = strlen(pt);
	while (*endchar < len && isalnum(pt[*endchar]))
		(*endchar)++;
}

INTSML us_editvariabletextfindpos(INTBIG xp, INTBIG yp, INTBIG *line, INTBIG *chr)
{
	REGISTER INTBIG i, j, screenlx, screenhx, screenly, screenhy;
	char save;
	INTBIG x, y;
	INTSML tsx, tsy;
	REGISTER INTSML font, tf, lasttsx, charwid;

	/* determine text size */
	font = (INTSML)((us_editvariable->textdescript & VTSIZE) >> VTSIZESH);
	tf = truefontsize(font, el_curwindowpart, us_editvartech);
	screensettextsize(el_curwindowpart, tf);
	for(i = 0; i < us_editvarlength; i++)
	{
		getdisparrayvarlinepos(us_editvarobjaddr, us_editvarobjtype, us_editvartech,
			el_curwindowpart, us_editvariable, i, &x, &y, 1);
		screenlx = applyxscale(el_curwindowpart, x - el_curwindowpart->screenlx) +
			el_curwindowpart->uselx;
		screenly = applyyscale(el_curwindowpart, y - el_curwindowpart->screenly) +
			el_curwindowpart->usely;
		screengettextsize(el_curwindowpart, us_editvarlines[i], &tsx, &tsy);
		screenhx = screenlx + tsx;
		screenhy = screenly + us_editvarlineheight;
		if (yp < screenly || yp > screenhy) continue;
		if (xp < screenlx-us_editvarlineheight ||
			xp > screenhx+us_editvarlineheight) continue;
		*line = i;
		lasttsx = 0;
		for(j=1; j<=(INTBIG)strlen(us_editvarlines[i]); j++)
		{
			save = us_editvarlines[i][j];
			us_editvarlines[i][j] = 0;
			screengettextsize(el_curwindowpart, us_editvarlines[i], &tsx, &tsy);
			charwid = tsx - lasttsx;
			lasttsx = tsx;
			us_editvarlines[i][j] = save;
			if (xp < screenlx + tsx - charwid/2) break;
		}
		*chr = j-1;
		return(1);
	}
	return(0);
}

void us_editvariabletexthighlight(void)
{
	REGISTER INTBIG i, j, screenlx, screenhx, screenly, screenhy, startch;
	char save;
	INTBIG x, y;
	INTSML tsx, tsy;
	REGISTER INTSML font, tf;

	/* determine text size */
	font = (INTSML)((us_editvariable->textdescript & VTSIZE) >> VTSIZESH);
	tf = truefontsize(font, el_curwindowpart, us_editvartech);
	screensettextsize(el_curwindowpart, tf);
	for(i = us_editvarstartline; i <= us_editvarendline; i++)
	{
		getdisparrayvarlinepos(us_editvarobjaddr, us_editvarobjtype, us_editvartech,
			el_curwindowpart, us_editvariable, i, &x, &y, 1);
		screenlx = applyxscale(el_curwindowpart, x - el_curwindowpart->screenlx) +
			el_curwindowpart->uselx;
		screenly = applyyscale(el_curwindowpart, y - el_curwindowpart->screenly) +
			el_curwindowpart->usely;
		startch = 0;
		if (i == us_editvarstartline && us_editvarstartchar != 0)
		{
			save = us_editvarlines[i][us_editvarstartchar];
			us_editvarlines[i][us_editvarstartchar] = 0;
			screengettextsize(el_curwindowpart, us_editvarlines[i], &tsx, &tsy);
			screenlx += tsx;
			us_editvarlines[i][us_editvarstartchar] = save;
			startch = us_editvarstartchar;
		}
		if (i == us_editvarendline) j = us_editvarendchar; else
			j = strlen(us_editvarlines[i]);
		save = us_editvarlines[i][j];
		us_editvarlines[i][j] = 0;
		screengettextsize(el_curwindowpart, &us_editvarlines[i][startch], &tsx, &tsy);
		screenhx = screenlx + tsx;
		us_editvarlines[i][j] = save;
		screenhy = screenly + us_editvarlineheight;
		if (screenlx <= el_curwindowpart->uselx) screenlx = el_curwindowpart->uselx+1;
		if (screenhx > el_curwindowpart->usehx) screenhx = el_curwindowpart->usehx;
		if (screenly < el_curwindowpart->usely) screenly = el_curwindowpart->usely;
		if (screenhy > el_curwindowpart->usehy) screenhy = el_curwindowpart->usehy;
		screeninvertbox(el_curwindowpart, (INTSML)(screenlx-1), (INTSML)(screenhx-1),
			(INTSML)screenly, (INTSML)(screenhy-1));
	}
}

/******************** USER-BROADCAST CHANGES ********************/

/*
 * routine to allocate a new ubchange from the pool (if any) or memory,
 * fill in the "facet", "change", "x", and "y" fields, and link it to the
 * global list.  Returns nonzero on error.
 */
INTSML us_newubchange(INTBIG change, void *object)
{
	REGISTER UBCHANGE *d;

	if (us_ubchangefree == NOUBCHANGE)
	{
		d = (UBCHANGE *)emalloc((sizeof (UBCHANGE)), us_aid->cluster);
		if (d == 0) return(-1);
	} else
	{
		/* take ubchange from free list */
		d = us_ubchangefree;
		us_ubchangefree = (UBCHANGE *)d->nextubchange;
	}
	d->object = object;
	d->change = change;
	d->nextubchange = us_ubchanges;
	us_ubchanges = d;
	return(0);
}

/*
 * routine to return ubchange "d" to the pool of free ubchanges
 */
void us_freeubchange(UBCHANGE *d)
{
	d->nextubchange = us_ubchangefree;
	us_ubchangefree = d;
}

/*
 * routine to remove all queued user broadcast changes to facet "np"
 * because it was deleted
 */
void us_removeubchange(NODEPROTO *np)
{
	REGISTER UBCHANGE *d, *lastd, *nextd;
	REGISTER NODEPROTO *thisnp;

	lastd = NOUBCHANGE;
	for(d = us_ubchanges; d != NOUBCHANGE; d = nextd)
	{
		nextd = d->nextubchange;
		if (d->change == UBKILLFM) thisnp = (NODEPROTO *)d->object; else
			thisnp = ((ARCINST *)d->object)->parent;
		if (thisnp == np)
		{
			if (lastd == NOUBCHANGE) us_ubchanges = nextd; else
				lastd->nextubchange = nextd;
			us_freeubchange(d);
			continue;
		}
		lastd = d;
	}
}

/*
 * routine to remove variable "FACET_message" from facet "np".
 */
void us_delfacetmessage(NODEPROTO *np)
{
	(void)us_newubchange(UBKILLFM, np);
}

/*
 * routine to implement all user broadcast changes queued during the last broadcast
 */
void us_doubchanges(void)
{
	REGISTER UBCHANGE *d, *nextd;
	REGISTER WINDOWPART *w;
	REGISTER NODEPROTO *np;
	REGISTER EDITOR *ed;
	REGISTER VARIABLE *var;

	for(d = us_ubchanges; d != NOUBCHANGE; d = nextd)
	{
		nextd = d->nextubchange;

		switch (d->change)
		{
			case UBKILLFM:	/* remove facet_message */
				np = (NODEPROTO *)d->object;
				for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
				{
					if (w->curnodeproto != np) continue;
					if ((w->state&WINDOWTYPE) != TEXTWINDOW) continue;

					/* see if the window still has a valid variable */
					ed = w->editor;
					if (ed->editobjvar == NOVARIABLE) continue;
					var = getval((INTBIG)ed->editobjaddr, ed->editobjtype, -1, ed->editobjqual);
					if (var == NOVARIABLE)
					{
						(void)newwindowpart(w->location, w);
						killwindowpart(w);
					}
				}
				break;
		}

		/* cleanup */
		us_freeubchange(d);
	}
	us_ubchanges = NOUBCHANGE;
}

/******************** COLOR ********************/

/*
 * setup the color map for the graphics of technology "tech".  "style" is:
 *  COLORSEXISTING  continue existing colors
 *  COLORSDEFAULT   use default nonoverlappable colors
 *  COLORSBLACK     use black background colors
 *  COLORSWHITE     use white background colors
 * A set of overlappable colors is obtained from technology "tech", combined
 * with the appropriate nonoverlappable colors, and set into the proper
 *  variables on the "user" aid (and subsequently displayed).
 * The 256 entries are organized thusly:
 * Bit 0 is for highlighting; bit 1 is an escape for
 * opaque colors, the next 5 bits are the overlappable colors (if the opaque
 * escape is off) or the opaque value (if the bit is set).
 * The last bit is for the grid, although it may not appear if there are 128 entries.
 * This routine uses the database variable "USER_color_map" on the
 * technologies.
 */
void us_getcolormap(TECHNOLOGY *tech, INTSML style, INTSML broadcast)
{
	static TECH_COLORMAP colmap[38] =
	{
		{255,255,255}, /*   4(0004) WHITE:   white                           */
		{  0,  0,  0}, /*  12(0014) BLACK:   black                           */
		{255,  0,  0}, /*  20(0024) RED:     red                             */
		{  0,  0,255}, /*  28(0034) BLUE:    blue                            */
		{  0,255,  0}, /*  36(0044) GREEN:   green                           */
		{  0,255,255}, /*  44(0054) CYAN:    cyan                            */
		{255,  0,255}, /*  52(0064) MAGENTA: magenta                         */
		{255,255,  0}, /*  60(0074) YELLOW:  yellow                          */
		{  0,  0,  0}, /*  68(0104) FACETTXT: facet and port names           */
		{  0,  0,  0}, /*  76(0114) FACETOUT: facet outline                  */
		{  0,  0,  0}, /*  84(0124) WINBOR:  window border color             */
		{  0,255,  0}, /*  92(0134) HWINBOR: highlighted window border color */
		{  0,  0,  0}, /* 100(0144) MENBOR:  menu border color               */
		{255,255,255}, /* 108(0154) HMENBOR: highlighted menu border color   */
		{  0,  0,  0}, /* 116(0164) MENTXT:  menu text color                 */
		{  0,  0,  0}, /* 124(0174) MENGLY:  menu glyph color                */
		{  0,  0,  0}, /* 132(0204) CURSOR:  cursor color                    */
		{180,180,180}, /* 140(0214) GRAY:    gray                            */
		{255,190,  6}, /* 148(0224) ORANGE:  orange                          */
		{186,  0,255}, /* 156(0234) PURPLE:  purple                          */
		{139, 99, 46}, /* 164(0244) BROWN:   brown                           */
		{230,230,230}, /* 172(0254) LGRAY:   light gray                      */
		{100,100,100}, /* 180(0264) DGRAY:   dark gray                       */
		{255,150,150}, /* 188(0274) LRED:    light red                       */
		{159, 80, 80}, /* 196(0304) DRED:    dark red                        */
		{175,255,175}, /* 204(0314) LGREEN:  light green                     */
		{ 89,159, 85}, /* 212(0324) DGREEN:  dark green                      */
		{150,150,255}, /* 220(0334) LBLUE:   light blue                      */
		{  2, 15,159}, /* 228(0344) DBLUE:   dark blue                       */
		{  0,  0,  0}, /* 236(0354)          unassigned                      */
		{  0,  0,  0}, /* 244(0364)          unassigned                      */
		{  0,  0,  0}, /* 252(0374)          unassigned                      */
		{  0,  0,  0}, /*                    grid                            */
		{255,255,255}, /*                    highlight                       */
		{255,  0,  0}, /*                    black background highlight      */
		{255,  0,  0}, /*                    white background highlight      */
		{255,255,255}, /*                    black background cursor         */
		{  0,  0,  0}  /*                    white background cursor         */
	};
	static TECH_COLORMAP default_colmap[32] =
	{                  /*     overlap4 overlap3 overlap2 overlap1 overlap0 */
		{200,200,200}, /* 0:                                               */
		{  0,  0,200}, /* 1:                                      overlap0 */
		{220,  0,120}, /* 2:                             overlap1          */
		{ 80,  0,160}, /* 3:                             overlap1+overlap0 */
		{ 70,250, 70}, /* 4:                    overlap2                   */
		{  0,140,140}, /* 5:                    overlap2+         overlap0 */
		{180,130,  0}, /* 6:                    overlap2+overlap1          */
		{ 55, 70,140}, /* 7:                    overlap2+overlap1+overlap0 */
		{250,250,  0}, /* 8:           overlap3                            */
		{ 85,105,160}, /* 9:           overlap3+                  overlap0 */
		{190, 80,100}, /* 10:          overlap3+         overlap1          */
		{ 70, 50,150}, /* 11:          overlap3+         overlap1+overlap0 */
		{ 80,210,  0}, /* 12:          overlap3+overlap2                   */
		{ 50,105,130}, /* 13:          overlap3+overlap2+         overlap0 */
		{170,110,  0}, /* 14:          overlap3+overlap2+overlap1          */
		{ 60, 60,130}, /* 15:          overlap3+overlap2+overlap1+overlap0 */
		{180,180,180}, /* 16: overlap4+                                    */
		{  0,  0,180}, /* 17: overlap4+                           overlap0 */
		{200,  0,100}, /* 18: overlap4+                  overlap1          */
		{ 60,  0,140}, /* 19: overlap4+                  overlap1+overlap0 */
		{ 50,230, 50}, /* 20: overlap4+         overlap2                   */
		{  0,120,120}, /* 21: overlap4+         overlap2+         overlap0 */
		{160,110,  0}, /* 22: overlap4+         overlap2+overlap1          */
		{ 35, 50,120}, /* 23: overlap4+         overlap2+overlap1+overlap0 */
		{230,230,  0}, /* 24: overlap4+overlap3                            */
		{ 65, 85,140}, /* 25: overlap4+overlap3+                  overlap0 */
		{170, 60, 80}, /* 26: overlap4+overlap3+         overlap1          */
		{ 50, 30,130}, /* 27: overlap4+overlap3+         overlap1+overlap0 */
		{ 60,190,  0}, /* 28: overlap4+overlap3+overlap2                   */
		{ 30, 85,110}, /* 29: overlap4+overlap3+overlap2+         overlap0 */
		{150, 90,  0}, /* 30: overlap4+overlap3+overlap2+overlap1          */
		{ 40, 40,110}, /* 31: overlap4+overlap3+overlap2+overlap1+overlap0 */
	};

	TECH_COLORMAP *mapptr, *thisptr;
	REGISTER INTSML i;
	REGISTER VARIABLE *var, *rvar, *gvar, *bvar;
	static INTBIG USER_color_map = 0;
	INTBIG red[256], green[256], blue[256];
	extern GRAPHICS us_gbox;

	/* get the technology's color information */
	if (USER_color_map == 0) USER_color_map = makekey("USER_color_map");
	var = getvalkey((INTBIG)tech, VTECHNOLOGY, VCHAR|VISARRAY, USER_color_map);
	if (var != NOVARIABLE) mapptr = (TECH_COLORMAP *)var->addr; else mapptr = 0;

	/* get existing color information */
	rvar = getvalkey((INTBIG)us_aid, VAID, VINTEGER|VISARRAY, us_colormap_red);
	gvar = getvalkey((INTBIG)us_aid, VAID, VINTEGER|VISARRAY, us_colormap_green);
	bvar = getvalkey((INTBIG)us_aid, VAID, VINTEGER|VISARRAY, us_colormap_blue);

	/* must have some colors */
	if (rvar == NOVARIABLE && gvar == NOVARIABLE && bvar == NOVARIABLE && var == NOVARIABLE)
	{
		mapptr = default_colmap;
	}

	if (style == COLORSEXISTING)
	{
		/* not resetting, get the old color values */
		for(i=0; i<256; i++)
		{
			red[i] = ((INTBIG *)rvar->addr)[i];
			green[i] = ((INTBIG *)gvar->addr)[i];
			blue[i] = ((INTBIG *)bvar->addr)[i];
			if ((i&(LAYERH|LAYERG|LAYEROE)) != 0) continue;
			if (mapptr == 0) continue;
			if (i == 0) continue;
			thisptr = &mapptr[i>>2];
			red[i]  = thisptr->red;   green[i]   = thisptr->green;
			blue[i] = thisptr->blue;
		}
	} else
	{
#if SIMAID
		/* update simulation window colors */
		switch (style)
		{
			case COLORSWHITE:
			case COLORSDEFAULT:
				sim_window_displaycolor(OFF_STRENGTH, BLUE);
				sim_window_displaycolor(NODE_STRENGTH, GREEN);
				sim_window_displaycolor(GATE_STRENGTH, MAGENTA);
				sim_window_displaycolor(VDD_STRENGTH, BLACK);
				sim_window_displaycolor(LOGIC_LOW, BLUE);
				sim_window_displaycolor(LOGIC_HIGH, MAGENTA);
				sim_window_displaycolor(LOGIC_X, BLACK);
				break;
			case COLORSBLACK:
				sim_window_displaycolor(OFF_STRENGTH, GREEN);
				sim_window_displaycolor(NODE_STRENGTH, CYAN);
				sim_window_displaycolor(GATE_STRENGTH, MAGENTA);
				sim_window_displaycolor(VDD_STRENGTH, LRED);
				sim_window_displaycolor(LOGIC_LOW, GREEN);
				sim_window_displaycolor(LOGIC_HIGH, MAGENTA);
				sim_window_displaycolor(LOGIC_X, LRED);
				break;
		}
#endif

		/* resetting: load entirely new color map */
		for(i=0; i<256; i++)
		{
			if ((i&LAYERH) != 0)
			{
				switch (style)
				{
					case COLORSDEFAULT: thisptr = &colmap[33];   break;	/* white */
					case COLORSBLACK:   thisptr = &colmap[34];   break;	/* red */
					case COLORSWHITE:   thisptr = &colmap[35];   break;	/* red */
				}
			} else if ((i&LAYERG) != 0)
			{
				switch (style)
				{
					case COLORSDEFAULT: thisptr = &colmap[32];   break;	/* black */
					case COLORSBLACK:   thisptr = &colmap[33];   break;	/* white */
					case COLORSWHITE:   thisptr = &colmap[32];   break;	/* black */
				}
			} else if ((i&LAYEROE) != 0)
			{
				thisptr = &colmap[i>>2];

				if (i == HMENBOR) switch (style)
				{
					case COLORSBLACK: thisptr = &colmap[2];   break;	/* red */
					case COLORSWHITE: thisptr = &colmap[2];   break;	/* red */
				}
				if (i == CURSOR) switch (style)
				{
					case COLORSDEFAULT: thisptr = &colmap[16];   break;	/* default */
					case COLORSBLACK:   thisptr = &colmap[36];   break;	/* white */
					case COLORSWHITE:   thisptr = &colmap[37];   break;	/* black */
				}

				/* reverse black and white when using black background */
				if (style == COLORSBLACK)
				{
					switch (i)
					{
						case FACETTXT:
						case FACETOUT:
						case WINBOR:
						case MENBOR:
						case MENTXT:
						case MENGLY:
						case BLACK:    thisptr = &colmap[33];   break;		/* white */
						case WHITE:    thisptr = &colmap[37];   break;		/* black */
					}
				}
			} else
			{
				if (rvar != NOVARIABLE) red[i] = ((INTBIG *)rvar->addr)[i];
				if (gvar != NOVARIABLE) green[i] = ((INTBIG *)gvar->addr)[i];
				if (bvar != NOVARIABLE) blue[i] = ((INTBIG *)bvar->addr)[i];
				if (mapptr != 0) thisptr = &mapptr[i>>2]; else thisptr = 0;
				if (i == ALLOFF) switch (style)
				{
					case COLORSBLACK: thisptr = &colmap[32];   break;	/* black */
					case COLORSWHITE: thisptr = &colmap[33];   break;	/* white */
				}
				if (thisptr == 0) continue;
			}
			red[i]  = thisptr->red;   green[i]   = thisptr->green;
			blue[i] = thisptr->blue;
		}

		/* also set the grid color appropriately if it doesn't have its own bitplane */
		if (el_maplength < 256)
		{
			switch (style)
			{
				case COLORSDEFAULT: us_gbox.col = BLACK;   break;	/* black */
				case COLORSBLACK:   us_gbox.col = WHITE;   break;	/* white */
				case COLORSWHITE:   us_gbox.col = BLACK;   break;	/* black */
			}
		}
	}

	/* set the color map */
	if (broadcast != 0)
		startobjectchange((INTBIG)us_aid, VAID);
	if (broadcast == 0) nextvarchangequiet();
	(void)setvalkey((INTBIG)us_aid, VAID, us_colormap_red, (INTBIG)red,
		VINTEGER|VISARRAY|(256<<VLENGTHSH));
	if (broadcast == 0) nextvarchangequiet();
	(void)setvalkey((INTBIG)us_aid, VAID, us_colormap_green, (INTBIG)green,
		VINTEGER|VISARRAY|(256<<VLENGTHSH));
	if (broadcast == 0) nextvarchangequiet();
	(void)setvalkey((INTBIG)us_aid, VAID, us_colormap_blue, (INTBIG)blue,
		VINTEGER|VISARRAY|(256<<VLENGTHSH));
	if (broadcast != 0)
		endobjectchange((INTBIG)us_aid, VAID);
}

/*
 * routine to load entry "entry" of the global color map entries with the value
 * (red, green, blue), letter "letter".  Handles highlight and grid layers
 * right if "spread" is nonzero.
 */
void us_setcolorentry(INTSML entry1, INTSML red, INTSML green, INTSML blue, INTSML letter,
	INTSML spread)
{
	REGISTER INTSML j;

	startobjectchange((INTBIG)us_aid, VAID);
	(void)setindkey((INTBIG)us_aid, VAID, us_colormap_red, entry1, red);
	(void)setindkey((INTBIG)us_aid, VAID, us_colormap_green, entry1, green);
	(void)setindkey((INTBIG)us_aid, VAID, us_colormap_blue, entry1, blue);

	/* place in other entries if special */
	if ((entry1&LAYERH) == LAYERH && spread != 0)
	{
		/* set all highlight colors */
		for(j=0; j<256; j++) if ((j&LAYERH) == LAYERH)
		{
			(void)setindkey((INTBIG)us_aid, VAID, us_colormap_red, j, red);
			(void)setindkey((INTBIG)us_aid, VAID, us_colormap_green, j, green);
			(void)setindkey((INTBIG)us_aid, VAID, us_colormap_blue, j, blue);
		}
	} else if ((entry1&(LAYERG|LAYERH)) == LAYERG && spread != 0)
	{
		/* set all grid colors */
		for(j=0; j<256; j++) if ((j&(LAYERG|LAYERH)) == LAYERG)
		{
			(void)setindkey((INTBIG)us_aid, VAID, us_colormap_red, j, red);
			(void)setindkey((INTBIG)us_aid, VAID, us_colormap_green, j, green);
			(void)setindkey((INTBIG)us_aid, VAID, us_colormap_blue, j, blue);
		}
	}
	endobjectchange((INTBIG)us_aid, VAID);
}

/*
 * routine to convert a red/green/blue color in (ir,ig,ib) to a hue/saturation/
 * intensity color in (h,s,i)
 */
void us_rgbtohsv(INTSML ir, INTSML ig, INTSML ib, float *h, float *s, float *i)
{
	float x, r, g, b, rdot, gdot, bdot;

	r = ir / 255.0f;
	g = ig / 255.0f;
	b = ib / 255.0f;

	/* "i" is maximum of "r", "g", and "b" */
	if (r > g) *i = r; else *i = g;
	if (b > *i) *i = b;

	/* "x" is minimum of "r", "g", and "b" */
	if (r < g) x = r; else x = g;
	if (b < x) x = b;

	/* "saturation" is (i-x)/i */
	if (*i == 0.0) *s = 0.0; else *s = (*i - x) / *i;

	if (*s == 0.0) *h = 0.0; else
	{
		rdot = (*i - r) / (*i - x);
		gdot = (*i - g) / (*i - x);
		bdot = (*i - b) / (*i - x);
		if (b == x && r == *i) *h = (1.0f - gdot) / 6.0f; else
		if (b == x && g == *i) *h = (1.0f + rdot) / 6.0f; else
		if (r == x && g == *i) *h = (3.0f - bdot) / 6.0f; else
		if (r == x && b == *i) *h = (3.0f + gdot) / 6.0f; else
		if (g == x && b == *i) *h = (5.0f - rdot) / 6.0f; else
		if (g == x && r == *i) *h = (5.0f + bdot) / 6.0f; else
			ttyputmsg("Cannot convert (%d,%d,%d), for x=%f i=%f s=%f", ir, ig, ib, x, *i, *s);
	}
}

/*
 * routine to convert a hue/saturation/intensity color in (h,s,v) to a red/
 * green/blue color in (r,g,b)
 */
void us_hsvtorgb(float h, float s, float v, INTBIG *r, INTBIG *g, INTBIG *b)
{
	REGISTER INTBIG i;
	REGISTER float f, m, n, k;

	h = h * 6.0f;
	i = (INTBIG)h;
	f = h - (float)i;
	m = v * (1.0f - s);
	n = v * (1.0f - s * f);
	k = v * (1.0f - s * (1.0f - f));
	switch (i)
	{
		case 0:
			*r = (INTBIG)(v*255.0); *g = (INTBIG)(k*255.0); *b = (INTBIG)(m*255.0);
			break;
		case 1:
			*r = (INTBIG)(n*255.0); *g = (INTBIG)(v*255.0); *b = (INTBIG)(m*255.0);
			break;
		case 2:
			*r = (INTBIG)(m*255.0); *g = (INTBIG)(v*255.0); *b = (INTBIG)(k*255.0);
			break;
		case 3:
			*r = (INTBIG)(m*255.0); *g = (INTBIG)(n*255.0); *b = (INTBIG)(v*255.0);
			break;
		case 4:
			*r = (INTBIG)(k*255.0); *g = (INTBIG)(m*255.0); *b = (INTBIG)(v*255.0);
			break;
		case 5:
			*r = (INTBIG)(v*255.0); *g = (INTBIG)(m*255.0); *b = (INTBIG)(n*255.0);
			break;
	}
	if (*r < 0 || *r > 255 || *g < 0 || *g > 255 || *b < 0 || *b > 255)
		ttyputmsg("(%f,%f,%f) -> (%ld,%ld,%ld) (i=%ld)",h, s, v, *r, *g, *b, i);
}

/******************** MISCELLANEOUS ********************/

/*
 * routine to put facet center (x, y) on facet "np".
 */
void us_setnodeprotocenter(INTBIG x, INTBIG y, NODEPROTO *np)
{
	INTBIG position[2];

	position[0] = x;   position[1] = y;
	nextvarchangequiet();
	(void)setvalkey((INTBIG)np, VNODEPROTO, el_prototype_center,
		(INTBIG)position, VINTEGER|VISARRAY|(2<<VLENGTHSH));
}

/*
 * routine to remove facet center from facet "np".
 */
void us_delnodeprotocenter(NODEPROTO *np)
{
	nextvarchangequiet();
	(void)delvalkey((INTBIG)np, VNODEPROTO, el_prototype_center);
}

void us_getlowleft(NODEINST *ni, INTBIG *x, INTBIG *y)
{
	INTBIG lx, ly, hx, hy;
	XARRAY trans;
	static POLYGON *poly = NOPOLYGON;

	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, us_aid->cluster);

	nodesizeoffset(ni, &lx, &ly, &hx, &hy);
	maketruerectpoly(ni->lowx+lx, ni->highx-hx, ni->lowy+ly, ni->highy-hy, poly);
	if (ni->rotation != 0 || ni->transpose != 0)
	{
		makerot(ni, trans);
		xformpoly(poly, trans);
	}
	getbbox(poly, x, &hx, y, &hy);
}

/*
 * routine to modify the text descriptor in the highlighted object "high"
 */
void us_modifytextdescript(HIGHLIGHT *high, INTBIG descript)
{
	if (high->fromvar != NOVARIABLE)
	{
		if (descript != (INTBIG)high->fromvar->textdescript)
		{
			if (high->fromport != NOPORTPROTO)
			{
				modifydescript((INTBIG)high->fromport, VPORTPROTO, high->fromvar, descript);
			} else if (high->fromgeom == NOGEOM)
			{
				modifydescript((INTBIG)high->facet, VNODEPROTO, high->fromvar, descript);
			} else
			{
				modifydescript((INTBIG)high->fromgeom->entryaddr.blind,
					high->fromgeom->entrytype == OBJNODEINST ? VNODEINST : VARCINST,
						high->fromvar, descript);
			}
		}
		return;
	}
	if (high->fromport != NOPORTPROTO)
	{
		(void)setval((INTBIG)high->fromport, VPORTPROTO, "textdescript", descript, VINTEGER);
		return;
	}
	if (high->fromgeom->entrytype == OBJNODEINST)
	{
		(void)setval((INTBIG)high->fromgeom->entryaddr.ni, VNODEINST, "textdescript", descript,
			VINTEGER);
		return;
	}
}

/*
 * Routine to adjust the quick keys according to the variable "var".
 */
void us_adjustquickkeys(VARIABLE *var)
{
	REGISTER INTBIG len, i, j, key, clen;
	REGISTER char *keybinding, *menuname, *menucommand, *pt, **quickkeylist;
	REGISTER POPUPMENU *pm;
	REGISTER POPUPMENUITEM *mi;

	/* first scan all existing menus and make sure they are still in the quick key list */
	len = getlength(var);
	quickkeylist = (char **)var->addr;
	for(i=0; i<us_pulldownmenucount; i++)
		us_scanquickkeys(us_pulldowns[i], quickkeylist, len);

	/* next scan the list of quick keys and make sure they are attached to menus */
	for(i=0; i<len; i++)
	{
		keybinding = quickkeylist[i];
		key = keybinding[0] & 0xFF;
		if (keybinding[1] != '/') continue;
		menuname = &keybinding[2];
		for(pt = menuname; *pt != 0 && *pt != '/'; pt++) ;
		if (*pt == 0) continue;
		*pt = 0;
		pm = us_getpopupmenu(menuname);
		*pt = '/';
		if (pm == NOPOPUPMENU) continue;
		menucommand = pt + 1;
		for(j=0; j<pm->total; j++)
		{
			mi = &pm->list[j];
			if (namesame(us_removeampersand(mi->attribute), menucommand) == 0) break;
		}
		if (j >= pm->total) continue;

		/* see if this menu item has the quick key */
		clen = strlen(mi->attribute);
		if (clen > 2 && mi->attribute[clen-1] == key && mi->attribute[clen-2] == '/')
			continue;

		/* not there: see if there is another key bound */
		if (clen > 2 && mi->attribute[clen-2] == '/')
		{
			mi->attribute[clen-1] = (char)key;
		} else
		{
			(void)initinfstr();
			(void)addstringtoinfstr(mi->attribute);
			(void)addtoinfstr('/');
			(void)addtoinfstr((char)key);
			pt = returninfstr();
			(void)reallocstring(&mi->attribute, pt, us_aid->cluster);
		}
		nativemenurename(pm, (INTSML)j);

		/* make it the Meta key */
		if (isupper(key)) key = tolower(key);
		key |= 0200;
		(void)initinfstr();
		(void)addstringtoinfstr("command=");
		(void)addstringtoinfstr(mi->response->comname);
		(void)us_appendargs(mi->response);
		nextvarchangequiet();
		pt = returninfstr();
		(void)setindkey((INTBIG)us_aid, VAID, us_binding_keys, key, (INTBIG)pt);
	}
}

/*
 * Helper routine for "us_quickkeydlog" to recursively examine menu "pm" and
 * load the quick keys tables.
 */
void us_scanquickkeys(POPUPMENU *pm, char **quickkeylist, INTBIG quickkeycount)
{
	REGISTER INTBIG j, i, key;
	REGISTER POPUPMENUITEM *mi;
	REGISTER USERCOM *uc;
	REGISTER POPUPMENU *thispm;
	REGISTER char *pt, *menuname;

	for(i=0; i<pm->total; i++)
	{
		mi = &pm->list[i];
		uc = mi->response;
		if (uc->menu != NOPOPUPMENU)
		{
			us_scanquickkeys(uc->menu, quickkeylist, quickkeycount);
			continue;
		}
		if (uc->active < 0 && *mi->attribute == 0) continue;

		/* see if this item has a quick key */
		for(pt = mi->attribute; *pt != 0; pt++)
			if (*pt == '/') break;
		if (*pt == 0) continue;

		/* item has a quick key: see if it is in the list */
		for(j=0; j<quickkeycount; j++)
		{
			menuname = &quickkeylist[j][2];
			for(pt = menuname; *pt != 0 && *pt != '/'; pt++) ;
			if (*pt == 0) continue;
			*pt = 0;
			thispm = us_getpopupmenu(menuname);
			*pt = '/';
			if (thispm != pm) continue;
			if (namesame(&pt[1], us_removeampersand(mi->attribute)) == 0) break;
		}
		if (j < quickkeycount) continue;

		/* this menu entry is not in the quick key list: remove its quick key */
		j = strlen(mi->attribute);
		mi->attribute[j-2] = 0;
		nativemenurename(pm, (INTSML)i);

		/* remove the Meta key */
		key = mi->attribute[j-1];
		if (isupper(key)) key = tolower(key);
		key |= 0200;
		nextvarchangequiet();
		(void)setindkey((INTBIG)us_aid, VAID, us_binding_keys, key, (INTBIG)"");
	}
}

/*
 * Helper routine to remove special characters from menu item "name".
 */
char *us_removeampersand(char *name)
{
	REGISTER char *pt;
	REGISTER INTBIG len;

	len = strlen(name);
	if (name[len-1] == '<' && name[0] == '>') name++;
	(void)initinfstr();
	for(pt = name; *pt != 0; pt++)
	{
		if (*pt == '/') break;
		if (*pt == '<' || *pt == '&') continue;
		(void)addtoinfstr(*pt);
	}
	return(returninfstr());
}

/*
 * Helper routine to determine the proper "address" field to use from variable "var".
 * Normally, it is simply "var->addr", but if it is a string with the "++" or "--"
 * sequence in it, then it auto-increments/decrements a numeric value, and so the
 * "++"/"--" are removed, and the original variable (which resides on "addr"/"type")
 * is modified.
 */
INTBIG us_inheritaddress(INTBIG addr, INTBIG type, VARIABLE *var)
{
	REGISTER char *str;
	char line[30];
	REGISTER INTBIG i, j, len, incrpoint, retval, curval;

	/* if it isn't a string, just return its address */
	if ((var->type & VTYPE) != VSTRING) return(var->addr);
	if ((var->type & VISARRAY) != 0) return(var->addr);

	str = (char *)var->addr;
	len = strlen(str);
	for(i=0; i<len; i++)
	{
		if (str[i] == '+' && str[i+1] == '+') break;
		if (str[i] == '-' && str[i+1] == '-') break;
	}
	if (i >= len) return(var->addr);

	/* construct the proper inherited string and increment the variable */
	(void)initinfstr();
	for(i=0; i<len; i++)
	{
		if (str[i] == '+' && str[i+1] == '+')
		{
			incrpoint = i;
			i++;
			continue;
		}
		if (str[i] == '-' && str[i+1] == '-')
		{
			incrpoint = i;
			i++;
			continue;
		}
		(void)addtoinfstr(str[i]);
	}

	/* get the new value */
	retval = (INTBIG)returninfstr();

	/* increment the variable */
	for(i = incrpoint-1; i>0; i--)
		if (!isdigit(str[i])) break;
	i++;
	str[incrpoint] = 0;
	curval = myatoi(&str[i]);
	str[incrpoint] = str[incrpoint+1];
	if (str[incrpoint] == '+') curval++; else curval--;
	(void)initinfstr();
	for(j=0; j<i; j++)
		(void)addtoinfstr(str[j]);
	sprintf(line, "%ld", curval);
	(void)addstringtoinfstr(line);
	(void)addstringtoinfstr(&str[incrpoint]);
	(void)setval(addr, type, makename(var->key), (INTBIG)returninfstr(), var->type); 

	return(retval);
}

/*
 * Routine to inherit all prototype attributes down to instance "ni".
 */
void us_inheritattributes(NODEINST *ni)
{
	REGISTER NODEPROTO *np;
	REGISTER INTSML first;
	REGISTER INTBIG i, dx, dy, descript, lambda;
	INTBIG x, y;
	REGISTER VARIABLE *var, *newvar;
	REGISTER PORTPROTO *pp;
	REGISTER char *pt, *attrname;
	REGISTER TECHNOLOGY *tech;

	np = ni->proto;
	first = 1;
	for(i=0; i<np->numvar; i++)
	{
		var = &np->firstvar[i];
		if ((var->textdescript&VTINHERIT) == 0) continue;

		/* see if the attribute is already there */
		newvar = getvalkey((INTBIG)ni, VNODEINST, -1, var->key);
		if (newvar != NOVARIABLE) continue;

		/* set the attribute */
		if (first != 0)
		{
			first = 0;
			startobjectchange((INTBIG)ni, VNODEINST);
		}
		newvar = setvalkey((INTBIG)ni, VNODEINST, var->key,
			us_inheritaddress((INTBIG)np, VNODEPROTO, var), var->type);
		if (newvar != NOVARIABLE)
			newvar->textdescript = var->textdescript & ~VTINHERIT;
	}
	for(pp = np->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
	{
		for(i=0; i<pp->numvar; i++)
		{
			var = &pp->firstvar[i];
			if ((var->textdescript&VTINHERIT) == 0) continue;
			(void)initinfstr();
			(void)addstringtoinfstr("ATTRP_");
			(void)addstringtoinfstr(pp->protoname);
			(void)addstringtoinfstr("_");
			pt = makename(var->key);
			(void)addstringtoinfstr(&pt[5]);
			attrname = returninfstr();

			/* see if the attribute is already there */
			newvar = getval((INTBIG)ni, VNODEINST, -1, attrname);
			if (newvar != NOVARIABLE) continue;

			/* set the attribute */
			if (first != 0)
			{
				first = 0;
				startobjectchange((INTBIG)ni, VNODEINST);
			}
			newvar = setval((INTBIG)ni, VNODEINST, attrname,
				us_inheritaddress((INTBIG)pp, VPORTPROTO, var), var->type);
			if (newvar != NOVARIABLE)
			{
				tech = whattech(ni->proto);
				lambda = tech->deflambda;
				dx = (var->textdescript&VTXOFF) >> VTXOFFSH;
				if ((var->textdescript&VTXOFFNEG) != 0) dx = -dx;
				dx = dx * lambda / 4;
				dy = (var->textdescript&VTYOFF) >> VTYOFFSH;
				if ((var->textdescript&VTYOFFNEG) != 0) dy = -dy;
				dy = dy * lambda / 4;

				portposition(ni, pp, &x, &y);
				x = x - (ni->lowx + ni->highx) / 2 + dx;
				y = y - (ni->lowy + ni->highy) / 2 + dy;
				x = x * 4 / lambda;
				y = y * 4 / lambda;
				descript = var->textdescript & ~(VTXOFF|VTXOFFNEG|VTYOFF|VTYOFFNEG|VTINHERIT);
				if (x < 0) { descript |= VTXOFFNEG;   x = -x; }
				if (y < 0) { descript |= VTYOFFNEG;   y = -y; }
				descript |= x << VTXOFFSH;
				descript |= y << VTYOFFSH;
				newvar->textdescript = descript;
			}
		}
	}
	if (first == 0)
		endobjectchange((INTBIG)ni, VNODEINST);
}
