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

#include "global.h"
#include "egraphics.h"
#include "usr.h"
#include "drc.h"
#include "network.h"
#include "efunction.h"
#include "tecgen.h"

static INTBIG *us_layer_letter_data = 0;	/* cache for "us_layerletters" */

/* working memory for "us_uniqueportname()" */
static char *us_uniqueretstr = NOSTRING;

/* structure for "usrbits" information parsing */
struct explanation
{
	INTBIG bit;			/* bit mask in word */
	INTSML shift;		/* shift of bit mask (-1 if a flag) */
	char  *name;		/* name of the field */
};

/* prototypes for local routines */
void us_addobjects(NODEPROTO*);
INTSML us_wildmatch(char*, char*);
void us_explainaid(INTBIG, struct explanation[]);
void us_saveoptions(void);

STATUSFIELD *us_clearstatuslines[3] = {0, 0, 0};

/*
 * Routine to free all memory associated with this module.
 */
void us_freestatusmemory(void)
{
	REGISTER INTBIG i, lines;

	if (us_layer_letter_data != 0) efree((char *)us_layer_letter_data);
	if (us_statusalign != 0) ttyfreestatusfield(us_statusalign);
	if (us_statusangle != 0) ttyfreestatusfield(us_statusangle);
	if (us_statusarc != 0) ttyfreestatusfield(us_statusarc);
	if (us_statusfacet != 0) ttyfreestatusfield(us_statusfacet);
	if (us_statusfacetsize != 0) ttyfreestatusfield(us_statusfacetsize);
	if (us_statusgridsize != 0) ttyfreestatusfield(us_statusgridsize);
	if (us_statuslambda != 0) ttyfreestatusfield(us_statuslambda);
	if (us_statusnetwork != 0) ttyfreestatusfield(us_statusnetwork);
	if (us_statusnode != 0) ttyfreestatusfield(us_statusnode);
	if (us_statustechnology != 0) ttyfreestatusfield(us_statustechnology);
	if (us_statusxpos != 0) ttyfreestatusfield(us_statusxpos);
	if (us_statusypos != 0) ttyfreestatusfield(us_statusypos);
	if (us_statusproject != 0) ttyfreestatusfield(us_statusproject);
	if (us_statusroot != 0) ttyfreestatusfield(us_statusroot);
	if (us_statuspart != 0) ttyfreestatusfield(us_statuspart);
	if (us_statuspackage != 0) ttyfreestatusfield(us_statuspackage);
	if (us_statusselection != 0) ttyfreestatusfield(us_statusselection);
	lines = ttynumstatuslines();
	for(i=0; i<lines; i++) ttyfreestatusfield(us_clearstatuslines[i]);

	if (us_uniqueretstr != NOSTRING) efree(us_uniqueretstr);
}

/******************** STATUS DISPLAY MANIPULATION ********************/

/* routine to determine the state of the status fields */
void us_initstatus(void)
{
	REGISTER INTSML lines, i;
	static INTSML first = 1;

	if (first != 0)
	{
		first = 0;
		lines = ttynumstatuslines();
		for(i=0; i<lines; i++) us_clearstatuslines[i] = ttydeclarestatusfield((INTSML)(i+1), 0, 100, "");
		us_statusfacet = us_statusnode = us_statusarc = us_statusnetwork = 0;
		us_statuslambda = us_statustechnology = us_statusxpos = us_statusypos = 0;
		us_statusangle = us_statusalign = us_statusfacetsize = us_statusgridsize = 0;
		us_statusproject = us_statusroot = us_statuspart = us_statuspackage = 0;
		us_statusselection = 0;
		us_statusfacet      = ttydeclarestatusfield(0,  0,   0, "");
		us_statusnode       = ttydeclarestatusfield(1,  0,  22, "NODE: ");
		us_statusarc        = ttydeclarestatusfield(1, 22,  44, "ARC: ");
		us_statusfacetsize  = ttydeclarestatusfield(1, 44,  65, "SIZE: ");
		us_statuslambda     = ttydeclarestatusfield(1, 65,  80, "LAMBDA: ");
		us_statustechnology = ttydeclarestatusfield(1, 80, 100, "TECHNOLOGY: ");
		us_statusxpos       = ttydeclarestatusfield(1, 67,  83, "X: ");
		us_statusypos       = ttydeclarestatusfield(1, 83, 100, "Y: ");
		if (lines > 1)
		{
			us_statusangle     = ttydeclarestatusfield(2,  0,  25, "ANGLE: ");
			us_statusalign     = ttydeclarestatusfield(2, 25,  50, "ALIGN: ");
			us_statusgridsize  = ttydeclarestatusfield(2, 75, 100, "GRID: ");
		}
	}
}

/*
 * Routine to create a field in the status display.  "line" is the status line number (line 0
 * is the window header).  "startper" and "endper" are the starting and ending percentages
 * of the line to use (ignored for the window header).  "label" is the prefix to use.
 * Returns an object to use with subsequent calls to "ttysetstatusfield" (zero on error).
 */
STATUSFIELD *ttydeclarestatusfield(INTSML line, INTSML startper, INTSML endper, char *label)
{
	STATUSFIELD *sf;

	sf = (STATUSFIELD *)emalloc((sizeof (STATUSFIELD)), us_aid->cluster);
	if (sf == 0) return(0);
	if (line < 0 || line > ttynumstatuslines()) return(0);
	if (line > 0 && (startper < 0 || endper > 100 || startper >= endper)) return(0);
	sf->line = line;
	sf->startper = startper;
	sf->endper = endper;
	(void)allocstring(&sf->label, label, us_aid->cluster);
	return(sf);
}

/* write the current grid alignment from "us_alignment" */
void us_setalignment(WINDOWFRAME *frame)
{
	char valstring[60];

	(void)strcpy(valstring, latoa(us_alignment));
	if (us_edgealignment != 0)
	{
		(void)strcat(valstring, "/");
		(void)strcat(valstring, latoa(us_edgealignment));
	}
	ttysetstatusfield(frame, us_statusalign, valstring, 0);
}

/* write the current lambda value from "el_curtech->deflambda" in microns */
void us_setlambda(WINDOWFRAME *frame)
{
	REGISTER INTSML len;
	float lambdainmicrons;
	char hold[50];

	/* show nothing in X/Y mode */
	if ((us_aid->aidstate&SHOWXY) != 0) return;

	lambdainmicrons = scaletodispunit(el_curtech->deflambda, DISPUNITMIC);
	(void)sprintf(hold, "%g", lambdainmicrons);
	for(;;)
	{
		len = strlen(hold)-1;
		if (hold[len] != '0') break;
		hold[len] = 0;
	}
	if (hold[len] == '.') hold[len] = 0;
	(void)strcat(hold, "u");
	ttysetstatusfield(NOWINDOWFRAME, us_statuslambda, hold, 0);
}

/* write the name of the current technology from "el_curtech->techname" */
void us_settechname(WINDOWFRAME *frame)
{
	/* show nothing in XY mode */
	if ((us_aid->aidstate&SHOWXY) != 0) return;

	ttysetstatusfield(NOWINDOWFRAME, us_statustechnology, el_curtech->techname, 1);
}

/* write the cursor coordinates */
void us_setcursorpos(WINDOWFRAME *frame, INTBIG x, INTBIG y)
{
	static INTBIG lastx=0, lasty=0;

	/* show nothing in non XY mode */
	if ((us_aid->aidstate&SHOWXY) == 0) return;

	if (lastx != x)
	{
		ttysetstatusfield(frame, us_statusxpos, latoa(x), 0);
		lastx = x;
	}

	if (lasty != y)
	{
		ttysetstatusfield(frame, us_statusypos, latoa(y), 0);
		lasty = y;
	}
}

/* set the current nodeproto */
void us_setnodeproto(NODEPROTO *np)
{
	(void)setvalkey((INTBIG)us_aid, VAID, us_current_node, (INTBIG)np, VNODEPROTO|VDONTSAVE);
}

/*
 * set the current arcproto to "ap".  If "always" is zero, do this only if sensible.
 */
void us_setarcproto(ARCPROTO *ap, INTSML always)
{
	/* don't do it if setting universal arc when not in generic technology */
	if (always == 0 && ap != NOARCPROTO && ap->tech != el_curtech)
	{
		if (ap == gen_universalarc) return;
	}
	(void)setvalkey((INTBIG)us_aid, VAID, us_current_arc, (INTBIG)ap, VARCPROTO|VDONTSAVE);
}

/* shadow changes to the current nodeproto */
void us_shadownodeproto(WINDOWFRAME *frame, NODEPROTO *np)
{
	REGISTER char *pt;

	if (us_curnodeproto == np) return;
	us_curnodeproto = np;

	if (np == NONODEPROTO) pt = ""; else
		pt = describenodeproto(np);
	ttysetstatusfield(frame, us_statusnode, pt, 1);

	/* highlight the menu entry */
	us_showcurrentnodeproto();
}

/*
 * routine to draw highlighting around the menu entry with the current
 * node prototype
 */
void us_showcurrentnodeproto(void)
{
	REGISTER char *str;
	REGISTER INTBIG x, y;
	REGISTER VARIABLE *var;
	REGISTER NODEPROTO *np;
	COMMANDBINDING commandbinding;

	if (us_curnodeproto == NONODEPROTO) return;
	if ((us_aid->aidstate&MENUON) == 0) return;
	if ((us_aid->aidstate&ONESHOTMENU) != 0) return;
	if (us_menuhnx >= 0) us_highlightmenu(us_menuhnx, us_menuhny, el_colmenbor);
	us_menuhnx = -1;
	var = getvalkey((INTBIG)us_aid, VAID, VSTRING|VISARRAY, us_binding_menu);
	if (var != NOVARIABLE) for(x=0; x<us_menux; x++) for(y=0; y<us_menuy; y++)
	{
		if (us_menupos <= 1) str = ((char **)var->addr)[y * us_menux + x]; else
			str = ((char **)var->addr)[x * us_menuy + y];
		us_parsebinding(str, &commandbinding);
		np = commandbinding.nodeglyph;
		us_freebindingparse(&commandbinding);
		if (np == us_curnodeproto)
		{
			us_menuhnx = x;   us_menuhny = y;
			us_highlightmenu(us_menuhnx, us_menuhny, el_colhmenbor);
			break;
		}
	}
}

/* shadow changes to the current arcproto */
void us_shadowarcproto(WINDOWFRAME *frame, ARCPROTO *ap)
{
	if (us_curarcproto == ap) return;
	us_curarcproto = ap;

	ttysetstatusfield(frame, us_statusarc, describearcproto(ap), 1);
	us_showcurrentarcproto();
}

/*
 * routine to draw highlighting around the menu entry with the current
 * arc prototype
 */
void us_showcurrentarcproto(void)
{
	REGISTER char *str;
	REGISTER INTSML x, y;
	REGISTER VARIABLE *var;
	REGISTER ARCPROTO *ap;
	COMMANDBINDING commandbinding;

	/* highlight the menu entry */
	if (us_curarcproto == NOARCPROTO) return;
	if ((us_aid->aidstate&MENUON) == 0) return;
	if ((us_aid->aidstate&ONESHOTMENU) != 0) return;
	if (us_menuhax >= 0) us_highlightmenu(us_menuhax, us_menuhay, el_colmenbor);
	var = getvalkey((INTBIG)us_aid, VAID, VSTRING|VISARRAY, us_binding_menu);
	if (var != NOVARIABLE) for(x=0; x<us_menux; x++) for(y=0; y<us_menuy; y++)
	{
		if (us_menupos <= 1) str = ((char **)var->addr)[y * us_menux + x]; else
			str = ((char **)var->addr)[x * us_menuy + y];
		us_parsebinding(str, &commandbinding);
		ap = commandbinding.arcglyph;
		us_freebindingparse(&commandbinding);
		if (ap == us_curarcproto)
		{
			us_menuhax = x;   us_menuhay = y;
			us_highlightmenu(us_menuhax, us_menuhay, el_colhmenbor);
			break;
		}
	}
}

/* show the current network name */
void us_shownetworkname(WINDOWFRAME *frame)
{
	REGISTER NETWORK *net;
	REGISTER char *hold;

	net = net_gethighlightednet(0, 0);
	if (net == NONETWORK) hold = ""; else
		hold = describenetwork(net);
	ttysetstatusfield(frame, us_statusnetwork, hold, 0);
}

/* write the default placement angle from "aid:user.USER_placement_angle" */
void us_setnodeangle(WINDOWFRAME *frame)
{
	char hold[5];
	REGISTER VARIABLE *var;
	REGISTER INTSML pangle;

	var = getvalkey((INTBIG)us_aid, VAID, VINTEGER, us_placement_angle);
	if (var == NOVARIABLE) pangle = 0; else pangle = (INTSML)var->addr;
	(void)strcpy(hold, frtoa(pangle%3600*WHOLE/10));
	if (pangle >= 3600) (void)strcat(hold, "T");
	ttysetstatusfield(frame, us_statusangle, hold, 0);
}

/* write the name of the facet in window "w" */
void us_setfacetname(WINDOWPART *w)
{
	REGISTER char *npname;
	REGISTER INTSML len;
	REGISTER EDITOR *ed;

	/* do not show name of explorer window when it is split */
	if ((w->state&WINDOWTYPE) == EXPLORERWINDOW &&
		strcmp(w->location, "entire") != 0) return;

	/* write the facet name */
	(void)initinfstr();
	switch (w->state&WINDOWTYPE)
	{
		case TEXTWINDOW:
		case POPTEXTWINDOW:
			ed = w->editor;
			if (ed != NOEDITOR)
			{
				(void)addstringtoinfstr(ed->header);
				len = strlen(ed->header);
				break;
			}
		case DISPWINDOW:
		case DISP3DWINDOW:
			if (w->curnodeproto == NONODEPROTO) len = 0; else
			{
				npname = describenodeproto(w->curnodeproto);
				(void)addstringtoinfstr(npname);
				len = strlen(npname);
			}
			break;
		case EXPLORERWINDOW:
			(void)addstringtoinfstr("Facet Explorer");
			break;
		case WAVEFORMWINDOW:
			(void)addstringtoinfstr("Waveform");
			len = 8;
			if (w->curnodeproto != NONODEPROTO)
			{
				npname = describenodeproto(w->curnodeproto);
				(void)addstringtoinfstr(" of ");
				(void)addstringtoinfstr(npname);
				len += strlen(npname) + 4;
			}
			break;
	}

	ttysetstatusfield(w->frame, us_statusfacet, returninfstr(), 1);
}

/*
 * write the size of the facet in window "w" from the size of "w->curnodeproto"
 */
void us_setfacetsize(WINDOWPART *w)
{
	REGISTER NODEPROTO *np;
	char valstring[50];

	np = w->curnodeproto;
	if (np == NONODEPROTO) valstring[0] = 0; else
	{
		(void)strcpy(valstring, latoa(np->highx - np->lowx));
		(void)strcat(valstring, "x");
		(void)strcat(valstring, latoa(np->highy - np->lowy));
	}
	ttysetstatusfield(w->frame, us_statusfacetsize, valstring, 0);
}

/* write the grid size of window "w" from "w->gridx" and "w->gridy" */
void us_setgridsize(WINDOWPART *w)
{
	char valstring[50];

	if (w->curnodeproto == NONODEPROTO) valstring[0] = 0; else
	{
		(void)strcpy(valstring, latoa(w->gridx));
		(void)strcat(valstring, "x");
		(void)strcat(valstring, latoa(w->gridy));
	}
	ttysetstatusfield(NOWINDOWFRAME, us_statusgridsize, valstring, 0);
}

/* routine to re-write the status information (on all frames if frame is 0) */
void us_redostatus(WINDOWFRAME *frame)
{
	REGISTER WINDOWPART *w;
	REGISTER ARCPROTO *ap;
	REGISTER NODEPROTO *np;
	REGISTER INTSML lines, i;

	/* clear the status bar */
	lines = ttynumstatuslines();
	for(i=0; i<lines; i++) ttysetstatusfield(NOWINDOWFRAME, us_clearstatuslines[i], "", 0);

	/* node and arc */
	ap = us_curarcproto;   np = us_curnodeproto;
	us_curarcproto = (ARCPROTO *)0;   us_curnodeproto = (NODEPROTO *)0;
	us_shadownodeproto(NOWINDOWFRAME, np);
	us_shadowarcproto(NOWINDOWFRAME, ap);

	/* other fields */
	us_shownetworkname(frame);
	us_setnodeangle(frame);
	us_settechname(frame);
	us_setalignment(frame);
	us_setlambda(frame);

	/* write the window information on the window lines */
	for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
	{
		us_setfacetname(w);
		if (graphicshas(CANSTATUSPERFRAME) != 0 || w->frame == frame)
			us_setfacetsize(w);
		us_setgridsize(w);
	}
}

/******************** OUTPUT PREPARATION ********************/

/*
 * routine to list macros by the package of origin
 */
void us_printmacros(void)
{
	INTSML len, coms, keyboard, gotany, i, j, count;
	char line[150], *build[MAXPARS];
	REGISTER char *name, *comname, *inter;
	REGISTER MACROPACK *pack, *thispack;
	REGISTER VARIABLE *var;

	/* count the number of macros in each package */
	for(pack=us_macropacktop; pack!=NOMACROPACK; pack=pack->nextmacropack)
		pack->total = 0;
	keyboard = gotany = 0;
	for(i=0; i<us_aid->numvar; i++)
	{
		if (namesamen(makename(us_aid->firstvar[i].key), "USER_macro_", 11) == 0)
		{
			var = &us_aid->firstvar[i];
			if (getlength(var) <= 3) continue;
			count = us_parsecommand(((char **)var->addr)[0], build);
			for(j=0; j<count; j++)
				if (namesamen(build[j], "macropack=", 10) == 0)
			{
				pack = us_getmacropack(&build[j][10]);
				if (pack != NOMACROPACK) pack->total++;
				break;
			}
			if (j >= count) keyboard++;
			gotany++;
		}
	}
	if (gotany == 0)
	{
		ttyputmsg("No macros defined");
		return;
	}

	ttyputmsg("---------- Macros by Package ----------");

	/* now print the macros by package */
	for(pack = us_macropacktop; ; pack = pack->nextmacropack)
	{
		if (pack == NOMACROPACK)
		{
			if (keyboard == 0) break;
			name = "Keyboard-defined";
		} else
		{
			if (pack->total == 0) continue;
			name = pack->packname;
		}
		(void)strcpy(line, name);
		(void)strcat(line, " macros:");
		len = strlen(line);
		inter = "";
		for(i=0; i<us_aid->numvar; i++)
			if (namesamen(makename(us_aid->firstvar[i].key), "USER_macro_", 11) == 0)
		{
			var = &us_aid->firstvar[i];
			if (getlength(var) <= 3) continue;
			count = us_parsecommand(((char **)var->addr)[0], build);
			thispack = NOMACROPACK;
			for(j=0; j<count; j++)
				if (namesamen(build[j], "macropack=", 10) == 0)
			{
				thispack = us_getmacropack(&build[j][10]);
				break;
			}
			if (thispack != pack) continue;
			(void)strcat(line, inter);  len += strlen(inter);
			inter = ",";
			comname = &makename(var->key)[11];
			coms = strlen(comname) + 2;
			if (len + coms >= MESSAGESWIDTH)
			{
				ttyputmsg(line);  (void)strcpy(line, "  ");  len = 2;
			}
			(void)strcat(line, " ");
			(void)strcat(line, comname);  len += coms;

		}
		if (len > 2) ttyputmsg(line);
		if (pack == NOMACROPACK) break;
	}
}

/*
 * print macro "mac".  Show every command in the macro.
 */
void us_printmacro(VARIABLE *mac)
{
	REGISTER INTSML j, len;

	if (mac == NOVARIABLE) return;
	len = (INTSML)getlength(mac);
	if (len <= 3)
	{
		ttyputmsg("Macro '%s' is not defined", &makename(mac->key)[11]);
		return;
	}

	ttyputmsg("Macro '%s' has %d %s", &makename(mac->key)[11], len-3,
		makeplural("command", len-3));
	for(j=3; j<len; j++) ttyputmsg("   %s", ((char **)mac->addr)[j]);
}

struct
{
	char *typestring;
	INTBIG typecode;
} us_vartypename[] =
{
	{"Unknown",      VUNKNOWN},
	{"Integer",      VINTEGER},
	{"Address",      VADDRESS},
	{"Character",    VCHAR},
	{"String",       VSTRING},
	{"Float",        VFLOAT},
	{"Double",       VDOUBLE},
	{"Nodeinst",     VNODEINST},
	{"Nodeproto",    VNODEPROTO},
	{"Portarcinst",  VPORTARCINST},
	{"Portexpinst",  VPORTEXPINST},
	{"Portproto",    VPORTPROTO},
	{"Arcinst",      VARCINST},
	{"Arcproto",     VARCPROTO},
	{"Geometry",     VGEOM},
	{"Library",      VLIBRARY},
	{"Technology",   VTECHNOLOGY},
	{"Aid",          VAID},
	{"R-tree",       VRTNODE},
	{"Fixed-Point",  VFRACT},
	{"Network",      VNETWORK},
	{"Cell",         VCELL},
	{"View",         VVIEW},
	{"WindowPart",   VWINDOWPART},
	{"Graphics",     VGRAPHICS},
	{"Short",        VSHORT},
	{"Constraint",   VCONSTRAINT},
	{"General",      VGENERAL},
	{"Window-Frame", VWINDOWFRAME},
	{NULL, 0}
};

char *us_variabletypename(INTBIG type)
{
	INTSML i;

	for(i=0; us_vartypename[i].typestring != 0; i++)
		if (us_vartypename[i].typecode == (type&VTYPE))
			return(us_vartypename[i].typestring);
	return("unknown");
}

INTBIG us_variabletypevalue(char *name)
{
	INTSML i;

	for(i=0; us_vartypename[i].typestring != 0; i++)
		if (namesame(name, us_vartypename[i].typestring) == 0)
			return(us_vartypename[i].typecode);
	return(0);
}

/*
 * routine to describe variable "var".  If "index" is nonnegative then describe
 * entry "index" of an array variable.  The description is returned.
 */
char *us_variableattributes(VARIABLE *var, INTSML aindex)
{
	char *arr, line[30];
	REGISTER INTSML len;

	if ((var->type & VISARRAY) == 0 || aindex >= 0) arr = ""; else
	{
		len = (INTSML)getlength(var);
		if (len == 0) arr = " array[]"; else
		{
			if ((var->type&VTYPE) == VGENERAL) len /= 2;
			(void)sprintf(line, " array[%d]", len);
			arr = line;
		}
	}
	(void)initinfstr();
	if ((var->type&VDONTSAVE) != 0) (void)addstringtoinfstr("Temporary ");
	if ((var->type&VDISPLAY) != 0)
	{
		(void)addstringtoinfstr("Displayable(");
		(void)addstringtoinfstr(us_describetextdescript(var->textdescript));
		(void)addstringtoinfstr(") ");
	}
	if ((var->type&(VCODE1|VCODE2)) != 0)
	{
		switch (var->type&(VCODE1|VCODE2))
		{
			case VLISP: (void)addstringtoinfstr("LISP ");  break;
			case VTCL:  (void)addstringtoinfstr("TCL ");   break;
			case VJAVA: (void)addstringtoinfstr("Java ");  break;
		}
	}
	(void)addstringtoinfstr(us_variabletypename(var->type));
	(void)addstringtoinfstr(arr);
	return(returninfstr());
}

/* routine to recursively count and print the nodes in nodeinst "ni" */
void us_describecontents(NODEINST *ni)
{
	REGISTER INTSML i;
	REGISTER NODEPROTO *np;
	REGISTER LIBRARY *lib;
	REGISTER TECHNOLOGY *curtech, *printtech, *tech;
	REGISTER NODEPROTO **sorted;

	/* first zero the count of each nodeproto */
	for(tech = el_technologies; tech != NOTECHNOLOGY; tech = tech->nexttechnology)
		for(np = tech->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
			np->temp1 = 0;
	for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
		for(np = lib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
			np->temp1 = 0;

	/* now look at every object recursively in this facet */
	us_addobjects(ni->proto);

	/* print the totals */
	ttyputmsg("Contents of facet %s:", describenodeproto(ni->proto));
	printtech = el_curtech;
	for(curtech = el_technologies; curtech != NOTECHNOLOGY; curtech = curtech->nexttechnology)
	{
		for(np = curtech->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
			if (np->temp1 != 0)
		{
			if (curtech != printtech)
			{
				ttyputmsg("%s technology:", curtech->techname);
				printtech = curtech;
			}
			ttyputmsg("%6d nodes of prototype %s", np->temp1, describenodeproto(np));
		}
	}
	sorted = us_sortlib(el_curlib, (char *)0, 0);
	if (sorted == 0) ttyputerr("No memory for list"); else
	{
		for(i = 0; sorted[i] != NONODEPROTO; i++)
		{
			np = sorted[i];
			if (np->temp1 == 0) continue;
			ttyputmsg("%6d facets of prototype %s", np->temp1, describenodeproto(np));
		}
		efree((char *)sorted);
	}
}

struct
{
	char *menumessage;
	char *statusmessage;
} us_specialstatus[] =
{
	{"SCHEM_transistor_type.nmos",  "nMOS"},
	{"SCHEM_transistor_type.pmos",  "pMOS"},
	{"SCHEM_transistor_type.dmos",  "dMOS"},
	{"SCHEM_transistor_type.npn",   "NPN"},
	{"SCHEM_transistor_type.pnp",   "PNP"},
	{"SCHEM_transistor_type.emes",  "EMES"},
	{"SCHEM_transistor_type.dmes",  "DMES"},
	{"SCHEM_transistor_type.njfet", "NJFET"},
	{"SCHEM_transistor_type.pjfet", "PJFET"},
	{"SCHEM_twoport_type.h",        "CCVS"},
	{"SCHEM_twoport_type.u",        "VCVS"},
	{"SCHEM_twoport_type.f",        "CCCS"},
	{"SCHEM_twoport_type.g",        "VCCS"},
	{"SCHEM_twoport_type.l",        "Transmission"},
	{"SCHEM_diode.z",               "Zener"},
	{"SCHEM_capacitance.e",         "Electrolytic"},
	{"SCHEM_flipflop_type.rs",      "RS"},
	{"SCHEM_flipflop_type.jk",      "JK"},
	{"SCHEM_flipflop_type.d",       "D"},
	{"SCHEM_flipflop_type.t",       "T"},
	{0, 0}
};

/*
 * Routine to describe the node in the commandbinding structure "commandbinding".
 */
char *us_describemenunode(COMMANDBINDING *commandbinding)
{
	REGISTER INTBIG i;

	(void)initinfstr();
	(void)addstringtoinfstr(describenodeproto(commandbinding->nodeglyph));
	if (commandbinding->menumessage != 0)
	{
		for(i=0; us_specialstatus[i].menumessage != 0; i++)
			if (namesame(commandbinding->menumessage, us_specialstatus[i].menumessage) == 0) break;
		if (us_specialstatus[i].menumessage != 0)
		{
			(void)addstringtoinfstr(" (");
			(void)addstringtoinfstr(us_specialstatus[i].statusmessage);
			(void)addstringtoinfstr(")");
		}
	}
	return(returninfstr());
}

/*
 * routine to recursively examine facet "np" and update the number of
 * instantiated primitive nodeprotos in the "temp1" field of the nodeprotos.
 */
void us_addobjects(NODEPROTO *np)
{
	REGISTER NODEINST *ni;

	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		ni->proto->temp1++;
		if (ni->proto->primindex == 0) us_addobjects(ni->proto);
	}
}

/*
 * routine to sort the facets in library "lib" by facet name and return an
 * array of NODEPROTO pointers (terminating with NONODEPROTO).  If "matchspec"
 * is nonzero, it is a string that forces wildcard matching of facet names.
 * If "shortlist" is nonzero, only list one facet of every cell.  Remember
 * to free the returned array when done.  Routine returns zero upon error.
 */
NODEPROTO **us_sortlib(LIBRARY *lib, char *matchspec, INTSML shortlist)
{
	REGISTER NODEPROTO **sortindex, *np, *tempnt;
	REGISTER CELL *c;
	REGISTER INTSML total, i, j;
	REGISTER char *pt;

	/* remove library name if it is part of spec */
	if (matchspec != 0)
	{
		for(pt = matchspec; *pt != 0; pt++) if (*pt == ':') break;
		if (*pt == ':') matchspec = pt+1;
	}

	/* count the number of facets in the library */
	total = 0;
	if (shortlist != 0)
	{
		for(c = lib->firstcell; c != NOCELL; c = c->nextcell)
			if (us_wildmatch(c->cellname, matchspec) != 0) total++;
	} else
	{
		for(np = lib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
			if (us_wildmatch(nldescribenodeproto(np), matchspec) != 0) total++;
	}

	/* allocate array for sorting entries */
	sortindex = (NODEPROTO **)emalloc(((total+1) * (sizeof (NODEPROTO *))), el_tempcluster);
	if (sortindex == 0) return(0);
	if (shortlist != 0)
	{
		for(c = lib->firstcell, i = 0; c != NOCELL; c = c->nextcell)
			if (us_wildmatch(c->cellname, matchspec) != 0)
				sortindex[i++] = c->firstincell;
	} else
	{
		for(np = lib->firstnodeproto, i = 0; np != NONODEPROTO; np = np->nextnodeproto)
			if (us_wildmatch(nldescribenodeproto(np), matchspec) != 0)
				sortindex[i++] = np;
	}
	sortindex[i] = NONODEPROTO;

	/* sort the facet names (this could be more efficient) */
	i = 1;
	while (i)
	{
		i = 0;
		for(j=1; j<total; j++)
		{
			if (strcmp(nldescribenodeproto(sortindex[j-1]), nldescribenodeproto(sortindex[j])) <= 0)
				continue;
			tempnt = sortindex[j-1];   sortindex[j-1] = sortindex[j];
			sortindex[j] = tempnt;
			i++;
		}
	}
	return(sortindex);
}

/*
 * routine to match a name in "name" with a wildcard specification in "spec".
 * If "spec" is zero, all names are valid.  Returns nonzero if the name matches.
 */
INTSML us_wildmatch(char *name, char *spec)
{
	REGISTER char *pt, *pt2;
	REGISTER INTSML c1, c2;

	if (spec == 0) return(1);

	pt = name;
	pt2 = spec;
	for(;;)
	{
		if (*pt == 0 && *pt2 == 0) break;
		if (*pt != 0 && *pt2 == '?')
		{
			pt++;
			pt2++;
			continue;
		}
		if (*pt2 == '*')
		{
			for(;;)
			{
				if (us_wildmatch(pt, &pt2[1]) != 0) return(1);
				if (*pt == 0) break;
				pt++;
			}
			return(0);
		}
		if (isupper(*pt)) c1 = tolower(*pt); else c1 = *pt;
		if (isupper(*pt2)) c2 = tolower(*pt2); else c2 = *pt2;
		if (c1 != c2) return(0);
		pt++;
		pt2++;
	}
	return(1);
}

/* routine to print the aid information about nodeinst "ni" */
void us_printnodeaidinfo(NODEINST *ni)
{
	static struct explanation explain[] =
	{
		{DEADN,       -1,      "dead"},
		{NEXPAND,     -1,      "expand"},
		{WIPED,       -1,      "wiped"},
		{NSHORT,      -1,      "short"},
		{0, -1, NULL}
	};
	us_explainaid(ni->userbits, explain);
}

/* routine to print the aid information about arcinst "ai" */
void us_printarcaidinfo(ARCINST *ai)
{
	static struct explanation explain[] =
	{
		{FIXED,         -1,                 "rigid"},
		{CANTSLIDE,     -1,                 "rigid-in-ports"},
		{FIXANG,        -1,                 "fixed-angle"},
		{DEADA,         -1,                 "dead"},
		{AANGLE,        AANGLESH,           "angle"},
		{ASHORT,        -1,                 "short"},
		{AFUNCTION,     APBUS<<AFUNCTIONSH, "bus"},
		{NOEXTEND,      -1,                 "ends-shortened"},
		{ISNEGATED,     -1,                 "negated"},
		{ISDIRECTIONAL, -1,                 "directional"},
		{NOTEND0,       -1,                 "skip-tail"},
		{NOTEND1,       -1,                 "skip-head"},
		{REVERSEEND,    -1,                 "direction-reversed"},
		{0, -1, NULL}
	};
	us_explainaid(ai->userbits, explain);
}

/* helper routine for "us_printnodeaidinfo" and "us_printarcaidinfo" */
void us_explainaid(INTBIG bits, struct explanation explain[])
{
	REGISTER INTSML j, k;
	char partial[40];

	/* print the aid bit information */
	(void)initinfstr();
	(void)sprintf(partial, "User state = %ld", bits);
	(void)addstringtoinfstr(partial);
	k = 0;
	for(j=0; explain[j].bit != 0; j++)
	{
		if (explain[j].shift != -1)
		{
			if (k == 0) (void)addtoinfstr('('); else
				(void)addstringtoinfstr(", ");
			(void)addstringtoinfstr(explain[j].name);
			(void)addtoinfstr('=');
			(void)sprintf(partial, "%ld",(bits&explain[j].bit)>>explain[j].shift);
			(void)addstringtoinfstr(partial);
			k++;
			continue;
		}
		if ((bits & explain[j].bit) == 0) continue;
		if (k == 0) (void)addtoinfstr('('); else (void)addstringtoinfstr(", ");
		(void)addstringtoinfstr(explain[j].name);
		k++;
	}
	if (k != 0) (void)addtoinfstr(')');
	ttyputmsg("%s", returninfstr());
}

/*
 * routine to print information about port prototype "pp" on nodeinst "ni".
 * If the port prototype's "temp1" is nonzero, this port has already been
 * mentioned and should not be done again.
 */
void us_chatportproto(NODEINST *ni, PORTPROTO *pp)
{
	REGISTER PORTARCINST *pi;
	REGISTER PORTEXPINST *pe;
	REGISTER ARCINST *ai;
	REGISTER INTSML i;
	INTBIG lx, hx, ly, hy;
	REGISTER char *activity;
	char locx[40], locy[40];
	static POLYGON *poly = NOPOLYGON;

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

	/* if this port has already been discussed, quit now */
	if (pp->temp1 != 0) return;
	pp->temp1++;

	/* talk about the port prototype */
	(void)initinfstr();
	activity = us_describeportbits(pp);
	if (*activity != 0)
	{
		(void)addstringtoinfstr(activity);
		(void)addstringtoinfstr(" port ");
	} else (void)addstringtoinfstr("Port ");
	(void)addstringtoinfstr(pp->protoname);
	(void)addstringtoinfstr(" connects to ");
	for(i=0; pp->connects[i] != NOARCPROTO; i++)
	{
		if (i != 0) (void)addstringtoinfstr(", ");
		(void)addstringtoinfstr(pp->connects[i]->protoname);
	}
	ttyputmsg("%s", returninfstr());

	if (pp->subnodeinst == NONODEINST)
	{
		shapeportpoly(ni, pp, poly, 0);

		/* see if the port is a single point */
		for(i=1; i<poly->count; i++)
			if (poly->xv[i] != poly->xv[i-1] || poly->yv[i] != poly->yv[i-1]) break;
		if (i >= poly->count)
		{
			ttyputmsg("  Located at (%s, %s)", latoa(poly->xv[0]), latoa(poly->yv[0]));
		} else if (isbox(poly, &lx,&hx, &ly,&hy))
		{
			if (lx == hx) (void)sprintf(locx, "at %s", latoa(lx)); else
				(void)sprintf(locx, "from %s to %s", latoa(lx), latoa(hx));
			if (ly == hy) (void)sprintf(locy, "at %s", latoa(ly)); else
				(void)sprintf(locy, "from %s to %s", latoa(ly), latoa(hy));
			ttyputmsg("  Located %s in X and %s in Y", locx, locy);
		} else
		{
			(void)initinfstr();
			for(i=0; i<poly->count; i++)
			{
				(void)addstringtoinfstr(" (");
				(void)addstringtoinfstr(latoa(poly->xv[i]));
				(void)addtoinfstr(',');
				(void)addstringtoinfstr(latoa(poly->yv[i]));
				(void)addtoinfstr(')');
			}
			ttyputmsg("  Located inside polygon%s", returninfstr());
		}
	} else ttyputmsg("  Located at subnode %s subport %s",
		describenodeinst(pp->subnodeinst), pp->subportproto->protoname);

	/* talk about any arcs on this prototype */
	for(pi=ni->firstportarcinst; pi!=NOPORTARCINST; pi=pi->nextportarcinst)
	{
		if (pi->proto != pp) continue;
		ai = pi->conarcinst;
		if (ai->end[0].portarcinst == pi) i = 0; else i = 1;
		ttyputmsg("  Connected at (%s,%s) to %s", latoa(ai->end[i].xpos),
			latoa(ai->end[i].ypos), describearcinst(ai));
	}

	/* talk about any exported ports of this prototype */
	for(pe=ni->firstportexpinst; pe!=NOPORTEXPINST; pe=pe->nextportexpinst)
		if (pe->proto == pp)
	{
		(void)initinfstr();
		(void)addstringtoinfstr("  Exported as ");
		(void)addstringtoinfstr(us_describeportbits(pe->exportproto));
		(void)addstringtoinfstr(" port '");
		(void)addstringtoinfstr(pe->exportproto->protoname);
		(void)addstringtoinfstr("' (label ");
		(void)addstringtoinfstr(us_describetextdescript(pe->exportproto->textdescript));
		if ((pe->exportproto->userbits&PORTDRAWN) != 0) (void)addstringtoinfstr(",always-drawn");
		if ((pe->exportproto->userbits&BODYONLY) != 0) (void)addstringtoinfstr(",only-on-body");
		(void)addstringtoinfstr(") ");
		ttyputmsg("%s", returninfstr());
	}
}

char *us_describeportbits(PORTPROTO *pp)
{
	switch (pp->userbits&STATEBITS)
	{
		case INPORT:     return("Input");
		case OUTPORT:    return("Output");
		case BIDIRPORT:  return("Bidirectional");
		case PWRPORT:    return("Power");
		case GNDPORT:    return("Ground");
		case CLKPORT:    return("Clock");
		case C1PORT:     return("Clock Phase 1");
		case C2PORT:     return("Clock Phase 2");
		case C3PORT:     return("Clock Phase 3");
		case C4PORT:     return("Clock Phase 4");
		case C5PORT:     return("Clock Phase 5");
		case C6PORT:     return("Clock Phase 6");
		case REFOUTPORT: return("Reference Output");
		case REFINPORT:  return("Reference Input");
	}
	return("");
}

/*
 * routine to construct a string that describes the text descriptor field
 * in "descript".  The string has the form "POSITION+(DX,DY),SIZE" if there
 * is an offset, or "POSITION,SIZE" if there is not.  The string is returned.
 */
char *us_describetextdescript(INTBIG descript)
{
	REGISTER INTBIG xdist, ydist;
	char offset[50];

	(void)initinfstr();
	(void)addstringtoinfstr(us_describestyle(descript&VTPOSITION));
	if ((descript&(VTXOFF|VTYOFF)) != 0)
	{
		xdist = (descript&VTXOFF) >> VTXOFFSH;
		if ((descript&VTXOFFNEG) != 0) xdist = -xdist;
		ydist = (descript&VTYOFF) >> VTYOFFSH;
		if ((descript&VTYOFFNEG) != 0) ydist = -ydist;
		(void)sprintf(offset, "+(%s,%s)", frtoa(xdist*WHOLE/4), frtoa(ydist*WHOLE/4));
		(void)addstringtoinfstr(offset);
	}
	(void)addtoinfstr(',');
	(void)addstringtoinfstr(us_describefont((descript&VTSIZE) >> VTSIZESH));
	return(returninfstr());
}

/*
 * routine to return a string describing text style "style".
 */
char *us_describestyle(INTBIG style)
{
	switch (style)
	{
		case VTPOSCENT:      return("centered");
		case VTPOSBOXED:     return("boxed");
		case VTPOSUP:        return("up");
		case VTPOSDOWN:      return("down");
		case VTPOSLEFT:      return("left");
		case VTPOSRIGHT:     return("right");
		case VTPOSUPLEFT:    return("up-left");
		case VTPOSUPRIGHT:   return("up-right");
		case VTPOSDOWNLEFT:  return("down-left");
		case VTPOSDOWNRIGHT: return("down-right");
	}
	return("?");
}

/*
 * routine to return a string describing text font "font".
 */
char *us_describefont(INTBIG font)
{
	switch (font)
	{
		case TXT4P:     return("4-point");
		case TXT6P:     return("6-point");
		case TXT8P:     return("8-point");
		case TXT10P:    return("10-point");
		case TXT12P:    return("12-point");
		case TXT14P:    return("14-point");
		case TXT16P:    return("16-point");
		case TXT18P:    return("18-point");
		case TXT20P:    return("20-point");
		case TXTSMALL:  return("small");
		case TXTMEDIUM: return("medium");
		case TXTLARGE:  return("large");
	}
	return("?");
}

/*
 * routine to convert a key index (from 0 to 255) into a string describing
 * that key
 */
char *us_keyname(INTSML key)
{
	static char line[10];
	REGISTER char *pt;

	pt = line;
	if (key >= 0200)
	{
		if (key >= FUNCTIONF1 && key <= FUNCTIONF12)
		{
			sprintf(pt, "F%ld", key - FUNCTIONF1 + 1);
			return(line);
		}

		*pt++ = 'M';
		*pt++ = '-';
		key -= 0200;
	}
	if (key < 040)
	{
		if (key > 0 && key < 033) (void)sprintf(pt, "^%c", key + 0100); else
			(void)sprintf(pt, "%03o", key);
	} else
	{
		pt[0] = (char)key;
		pt[1] = 0;
	}
	return(line);
}

/*
 * routine to print a description of color map entry "ind"
 */
void us_printcolorvalue(INTSML ind)
{
	GRAPHICS *desc;
	char line[40];
	REGISTER INTSML i, j, high;
	REGISTER VARIABLE *varred, *vargreen, *varblue;

	/* get color arrays */
	varred = getvalkey((INTBIG)us_aid, VAID, VINTEGER|VISARRAY, us_colormap_red);
	vargreen = getvalkey((INTBIG)us_aid, VAID, VINTEGER|VISARRAY, us_colormap_green);
	varblue = getvalkey((INTBIG)us_aid, VAID, VINTEGER|VISARRAY, us_colormap_blue);
	if (varred == NOVARIABLE || vargreen == NOVARIABLE || varblue == NOVARIABLE)
	{
		ttyputerr("Cannot get current color map");
		return;
	}

	(void)initinfstr();
	if ((ind&(LAYERH|LAYERG)) == 0)
	{
		(void)sprintf(line, "Entry %d", ind);
		(void)addstringtoinfstr(line);
		j = 0;
		high = el_curtech->layercount;
		for(i=0; i<high; i++)
		{
			desc = el_curtech->layers[i];
			if (desc->bits == LAYERO && desc->col != ind) continue;
			if (desc->bits != LAYERO && ((desc->col&ind) == 0 || (ind&LAYEROE) != 0)) continue;
			if (j == 0) (void)addstringtoinfstr(" ("); else
				(void)addtoinfstr(',');
			j++;
			(void)addstringtoinfstr(layername(el_curtech, i));
		}
		if (j != 0) (void)addtoinfstr(')');
		(void)addstringtoinfstr(" is");
	} else
	{
		if ((ind&LAYERG) != 0) (void)addstringtoinfstr("Grid entry is"); else
			if ((ind&LAYERH) != 0) (void)addstringtoinfstr("Highlight entry is");
	}
	(void)sprintf(line, " color (%ld,%ld,%ld)", ((INTBIG *)varred->addr)[ind],
		((INTBIG *)vargreen->addr)[ind], ((INTBIG *)varblue->addr)[ind]);
	(void)addstringtoinfstr(line);
	ttyputmsg("%s", returninfstr());
}

/*
 * routine to make a line of text that describes facet "np".  The text
 * includes the facet name, version, creation, and revision dates.  The
 * amount of space to leave for the facet name is "maxlen" characters
 * (which is negative if dumping to a file).
 */
char *us_makefacetline(NODEPROTO *np, INTSML maxlen)
{
	char line[40];
	REGISTER INTSML l, i, gooddrc;
	INTSML year, month, mday, hour, minute, second;
	REGISTER INTBIG len, total;
	REGISTER char *pt;
	REGISTER VARIABLE *var;
	REGISTER NODEINST *ni;
	REGISTER UINTBIG lastgooddate, lastchangedate;

	(void)initinfstr();
	pt = nldescribenodeproto(np);
	(void)addstringtoinfstr(pt);
	if (maxlen < 0) (void)addtoinfstr('\t'); else
	{
		l = strlen(pt);
		for(i=l; i<maxlen; i++) (void)addtoinfstr(' ');
	}

	/* add the version number */
	(void)sprintf(line, "%5d", np->version);
	(void)addstringtoinfstr(line);
	if (maxlen < 0) (void)addtoinfstr('\t'); else
		(void)addstringtoinfstr("   ");

	/* add the creation date */
	if (np->creationdate == 0)
	{
		if (maxlen < 0) (void)addstringtoinfstr("UNRECORDED"); else
			(void)addstringtoinfstr("     UNRECORDED     ");
	} else
	{
		if (maxlen < 0)
		{
			parsetime(np->creationdate, &year, &month, &mday, &hour, &minute, &second);
			sprintf(line, "%4d-%02d-%02d %02d:%02d:%02d", year+1900, month, mday, hour,
				minute, second);
			(void)addstringtoinfstr(line);
		} else
		{
			pt = timetostring(np->creationdate);
			if (pt == NULL)
			{
				(void)addstringtoinfstr("     UNAVAILABLE     ");
			} else
			{
				for(i=4; i<=9; i++) (void)addtoinfstr(pt[i]);
				for(i=19; i<=23; i++) (void)addtoinfstr(pt[i]);
				for(i=10; i<=18; i++) (void)addtoinfstr(pt[i]);
			}
		}
	}

	/* add the revision date */
	if (maxlen < 0) (void)addtoinfstr('\t'); else
		(void)addstringtoinfstr("  ");
	if (np->revisiondate == 0)
	{
		if (maxlen < 0) (void)addstringtoinfstr("UNRECORDED"); else
			(void)addstringtoinfstr("     UNRECORDED     ");
	} else
	{
		if (maxlen < 0)
		{
			parsetime(np->revisiondate, &year, &month, &mday, &hour, &minute, &second);
			sprintf(line, "%4d-%02d-%02d %02d:%02d:%02d", year+1900, month, mday, hour,
				minute, second);
			(void)addstringtoinfstr(line);
		} else
		{
			pt = timetostring(np->revisiondate);
			if (pt == NULL)
			{
				(void)addstringtoinfstr("     UNAVAILABLE     ");
			} else
			{
				for(i=4; i<=9; i++) (void)addtoinfstr(pt[i]);
				for(i=19; i<=23; i++) (void)addtoinfstr(pt[i]);
				for(i=10; i<=18; i++) (void)addtoinfstr(pt[i]);
			}
		}
	}

	/* add the size */
	if (maxlen < 0) (void)addtoinfstr('\t'); else
		(void)addstringtoinfstr("  ");
	if ((np->cellview->viewstate&TEXTVIEW) != 0)
	{
		var = getvalkey((INTBIG)np, VNODEPROTO, VSTRING|VISARRAY, el_facet_message);
		if (var == NOVARIABLE) len = 0; else
			len = getlength(var);
		if (maxlen < 0) sprintf(line, "%ld lines", len); else
			sprintf(line, "%8ld lines   ", len);
		(void)addstringtoinfstr(line);
	} else
	{
		strcpy(line, latoa(np->highx-np->lowx));
		if (maxlen >= 0)
		{
			for(i = strlen(line); i<8; i++) (void)addtoinfstr(' ');
		}
		(void)addstringtoinfstr(line);
		(void)addtoinfstr('x');
		strcpy(line, latoa(np->highy-np->lowy));
		(void)addstringtoinfstr(line);
		if (maxlen >= 0)
		{
			for(i = strlen(line); i<8; i++) (void)addtoinfstr(' ');
		}
	}
	if (maxlen < 0) (void)addtoinfstr('\t');

	/* count the number of instances */
	total = 0;
	for(ni = np->firstinst; ni != NONODEINST; ni = ni->nextinst) total++;
	if (maxlen < 0) sprintf(line, "%ld", total); else
		sprintf(line, "%4ld", total);
	(void)addstringtoinfstr(line);

	/* show other factors about the facet */
	if (maxlen < 0) (void)addtoinfstr('\t'); else
		(void)addstringtoinfstr("   ");
	if ((np->userbits&NLOCKED) != 0) (void)addstringtoinfstr("L"); else
		(void)addstringtoinfstr(" ");
	if (maxlen < 0) (void)addtoinfstr('\t'); else
		(void)addstringtoinfstr(" ");
	if ((np->userbits&NILOCKED) != 0) (void)addstringtoinfstr("I"); else
		(void)addstringtoinfstr(" ");
	if (maxlen < 0) (void)addtoinfstr('\t'); else
		(void)addstringtoinfstr(" ");
	if ((np->userbits&INCELLLIBRARY) != 0) (void)addstringtoinfstr("C"); else
		(void)addstringtoinfstr(" ");
	if (maxlen < 0) (void)addtoinfstr('\t'); else
		(void)addstringtoinfstr(" ");
	gooddrc = 0;
	var = getvalkey((INTBIG)np, VNODEPROTO, VINTEGER, dr_lastgooddrckey);
	if (var != NOVARIABLE)
	{
		lastgooddate = (UINTBIG)var->addr;
		lastchangedate = np->revisiondate;
		if (lastchangedate <= lastgooddate) gooddrc = 1;
	}
	if (gooddrc != 0) (void)addstringtoinfstr("D"); else
		(void)addstringtoinfstr(" ");
	return(returninfstr());
}

/******************** KEYBOARD SUPPORT ********************/

/*
 * routine to check to see if library "lib" has changed and warn the
 * user prior to destroying changes to that library.  If "lib" is NOLIBRARY, all
 * libraries are checked.  "action" is the type of action to be performed.
 * If the operation can be cancelled, "cancancel" is nonzero.  Returns nonzero
 * if the operation is to be aborted.
 */
INTSML us_preventloss(LIBRARY *lib, char *action, INTSML cancancel)
{
	REGISTER INTBIG i, count;
	char *par[10];
	REGISTER LIBRARY *l;
	extern COMCOMP us_quitp, us_yesnop;
	extern AIDENTRY *io_aid;

	i = 0;
	for(l = el_curlib; l != NOLIBRARY; l = l->nextlibrary)
	{
		if ((l->userbits&HIDDENLIBRARY) != 0) continue;
		if (lib != NOLIBRARY && lib != l) continue;
		if ((l->userbits&(LIBCHANGEDMAJOR|LIBCHANGEDMINOR)) == 0) continue;

		(void)initinfstr();
		(void)addstringtoinfstr("Library ");
		(void)addstringtoinfstr(l->libname);
		(void)addstringtoinfstr(" has changed");
		if ((l->userbits&LIBCHANGEDMAJOR) != 0)
			(void)addstringtoinfstr(" significantly"); else
				(void)addstringtoinfstr(" insignificantly");
		(void)addstringtoinfstr(".  ");
		if (cancancel != 0)
		{
			(void)addstringtoinfstr("Save before ");
			(void)addstringtoinfstr(action);
			(void)addstringtoinfstr("?");
			count = ttygetparam(returninfstr(), &us_quitp, MAXPARS, par);
			if (count > 0 && namesamen(par[0], "yes", strlen(par[0])) == 0)
			{
				/* do nothing: ignoring the library changes */
			} else if (count > 0 && namesamen(par[0], "save", strlen(par[0])) == 0)
			{
				/* save the library */
				(void)askaid(io_aid, "write", (INTBIG)l, (INTBIG)"binary");
			} else
			{
				ttyputverbose("Keep working");
				return(1);
			}
		} else
		{
			(void)addstringtoinfstr("save?");
			count = ttygetparam(returninfstr(), &us_yesnop, MAXPARS, par);
			if (count > 0 && namesamen(par[0], "yes", strlen(par[0])) == 0)
			{
				/* save the library */
				(void)askaid(io_aid, "write", (INTBIG)l, (INTBIG)"binary");
			}
		}
	}

	/* also check for option changes on quit */
	if (us_optionschanged != 0 && namesame(action, "Quit") == 0)
	{
		if (cancancel != 0)
		{
			count = ttygetparam("Options have changed, Save?", &us_quitp, MAXPARS, par);
			if (count > 0 && namesamen(par[0], "yes", strlen(par[0])) == 0)
			{
				/* do nothing: ignoring the option changes */
			} else if (count > 0 && namesamen(par[0], "save", strlen(par[0])) == 0)
			{
				/* save the options */
				us_saveoptions();
			} else
			{
				ttyputverbose("Keep working");
				return(1);
			}
		} else
		{
			count = ttygetparam("Options have changed, Save?", &us_yesnop, MAXPARS, par);
			if (count > 0 && namesamen(par[0], "yes", strlen(par[0])) == 0)
			{
				/* save the options */
				us_saveoptions();
			}
		}
	}
	return(0);
}

void us_saveoptions(void)
{
	REGISTER INTBIG retval;
	REGISTER LIBRARY *l;
	REGISTER char *libname, *libfile;
	extern AIDENTRY *io_aid;

	libname = us_tempoptionslibraryname();
	libfile = optionsfilepath();
	l = newlibrary(libname, libfile);
	if (l == NOLIBRARY)
	{
		ttyputerr("Cannot create options library %s", libfile);
		return;
	}
	l->userbits |= READFROMDISK;
	retval = askaid(io_aid, "write", (INTBIG)l, (INTBIG)"binary");
	killlibrary(l);
	if (retval != 0)
		ttyputerr("Could not save options library");
}

/*
 * Routine to construct a temporary library name that doesn't already exist.
 */
char *us_tempoptionslibraryname(void)
{
	static char libname[256];
	REGISTER LIBRARY *lib;
	REGISTER INTBIG i;

	for(i=1; ; i++)
	{
		sprintf(libname, "options%ld", i);
		for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
			if (namesame(libname, lib->libname) == 0) break;
		if (lib == NOLIBRARY) break;
	}
	return(libname);
}

/*
 * routine to determine the color map entry that corresponds to the string
 * "pp".  If the string is a number then return that number.  If the string
 * contains layer letters from the current technology, return that index.
 * The value of "purpose" determines how to interpret the layer letters: 0
 * means that the letters indicate entries in the color map and 1 means that
 * only one letter is allowed and its layer number should be returned.  If no
 * entry can be determined, the routine issues an error and returns -1.
 */
INTSML us_getcolormapentry(char *pp, INTSML purpose)
{
	char *ch, *pt1;
	REGISTER INTSML color, i, high;
	GRAPHICS *desc;

	/* first see if the string is all digits and return that value if so */
	if (isanumber(pp) != 0) return((INTSML)myatoi(pp));

	/* for layer conversion, the job is simple */
	high = el_curtech->layercount;
	if (purpose != 0)
	{
		if (pp[0] == 0 || pp[1] != 0)
		{
			us_abortcommand("Can only give one layer letter");
			return(-1);
		}
		for(i=0; i<high; i++)
		{
			for(ch = us_layerletters(el_curtech, i); *ch != 0; ch++)
				if (*ch == pp[0]) return(i);
		}
		us_abortcommand("Letter '%s' is not a valid layer", pp);
		return(-1);
	}

	/* accumulate the desired color */
	color = 0;
	for(pt1 = pp; *pt1 != 0; pt1++)
	{
		/* find the layer that corresponds to letter "*pt1" */
		for(i=0; i<high; i++)
		{
			/* see if this layer has the right letter */
			for(ch = us_layerletters(el_curtech, i); *ch != 0; ch++)
				if (*ch == *pt1) break;
			if (*ch == 0) continue;

			/* get the color characteristics of this layer */
			desc = el_curtech->layers[i];
			if ((desc->bits & color) != 0)
			{
				us_abortcommand("No single color for the letters '%s'", pp);
				return(-1);
			}
			color |= desc->col;
			break;
		}
		if (i == high)
		{
			us_abortcommand("Letter '%c' is not a valid layer", *pt1);
			return(-1);
		}
	}
	return(color);
}

/*
 * routine to return a unique port prototype name in facet "facet" given that
 * a new prototype wants to be named "name".  The routine allocates space
 * for the string that is returned so this must be freed when done.
 */
char *us_uniqueportname(char *name, NODEPROTO *facet)
{
	REGISTER PORTPROTO *pp;
	char addon[10], *newname, *trailingpart;
	REGISTER INTSML baselen, nextindex, i, adddash;

	if (us_uniqueretstr != NOSTRING) efree(us_uniqueretstr);

	/* first see if the name is unique */
	for(pp = facet->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
		if (namesame(name, pp->protoname) == 0) break;
	if (pp == NOPORTPROTO)
	{
		(void)allocstring(&us_uniqueretstr, name, us_aid->cluster);
		return(us_uniqueretstr);
	}

	/* see if there is a number in the port name which can be incremented */
	baselen = strlen(name);
	nextindex = 1;
	trailingpart = 0;
	adddash = 1;
	if (name[baselen-1] == ']')
	{
		/* see if there is a single number inside square brackets */
		baselen--;
		trailingpart = &name[baselen];
		while (baselen > 0 && isdigit(name[baselen-1]) != 0) baselen--;
		for(i=baselen-1; i>0; i--)
			if (name[i] == '[' || name[i] == ':' || name[i] == ',') break;
		if (name[i] != '[')
		{
			/* did not find a single entry inside square brackets */
			while (baselen > 0 && name[baselen] != '[') baselen--;
			if (baselen == 0) baselen = strlen(name); else
			{
				trailingpart = &name[baselen];
				while (baselen > 0 && isdigit(name[baselen-1]) != 0) baselen--;
				if (isdigit(name[baselen]) != 0)
				{
					nextindex = atoi(&name[baselen]) + 1;
					if (baselen > 0 && ispunct(name[baselen-1])) baselen--;
				}
			}
		} else
		{
			/* found a valid index inside square brackets */
			nextindex = atoi(&name[baselen]) + 1;
			adddash = 0;
		}
	} else
	{
		/* not an arrayed name: look for a number at the end */
		while (baselen > 0 && isdigit(name[baselen-1]) != 0) baselen--;
		nextindex = atoi(&name[baselen]) + 1;
		if (baselen > 0 && ispunct(name[baselen-1])) baselen--;
	}

	for(; ; nextindex++)
	{
		(void)initinfstr();
		for(i=0; i<baselen; i++) (void)addtoinfstr(name[i]);
		if (adddash != 0) (void)addtoinfstr((char)us_separatechar);
		(void)sprintf(addon, "%d", nextindex);
		(void)addstringtoinfstr(addon);
		if (trailingpart != 0) (void)addstringtoinfstr(trailingpart);
		newname = returninfstr();
		for(pp = facet->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
			if (namesame(newname, pp->protoname) == 0) break;
		if (pp == NOPORTPROTO) break;
	}
	(void)allocstring(&us_uniqueretstr, newname, us_aid->cluster);
	return(us_uniqueretstr);
}

/*
 * routine to initialize the database variable "USER_layer_letters".  This
 * is called once at initialization and again whenever the array is changed.
 */
void us_initlayerletters(void)
{
	REGISTER VARIABLE *var;
	REGISTER TECHNOLOGY *t;

	if (us_layer_letter_data != 0) efree((char *)us_layer_letter_data);
	us_layer_letter_data = emalloc((el_maxtech * SIZEOFINTBIG), us_aid->cluster);
	if (us_layer_letter_data == 0) return;
	for(t = el_technologies; t != NOTECHNOLOGY; t = t->nexttechnology)
	{
		var = getvalkey((INTBIG)t, VTECHNOLOGY, VSTRING|VISARRAY, us_layer_letters);
		us_layer_letter_data[t->techindex] = (var == NOVARIABLE ? 0 : var->addr);
	}
}

/*
 * routine to return a string of unique letters describing layer "layer"
 * in technology "tech".  The letters for all layers of a given technology
 * must not intersect.  This routine accesses the "USER_layer_letters"
 * variable on the technology objects.
 */
char *us_layerletters(TECHNOLOGY *tech, INTSML layer)
{
	REGISTER INTBIG addr;

	if (us_layer_letter_data == 0)
	{
		us_initlayerletters();
		if (us_layer_letter_data == 0) return("");
	}

	addr = us_layer_letter_data[tech->techindex];
	if (addr == 0) return("");
	return(((char **)addr)[layer]);
}

/*
 * routine to change an aid state.  The name of the aid is in "pt" (if "pt" is
 * null then an aid name is prompted).  The state of the aid is set to "state"
 * (0 for off, 1 for permanently off, 2 for on, 3 for on without catchup).
 */
void us_setaid(INTSML count, char *par[], INTSML state)
{
	REGISTER INTSML i;
	REGISTER char *pt;
	extern COMCOMP us_onoffaidp;
	REGISTER AIDENTRY *aid;

	if (count == 0)
	{
		count = ttygetparam("Which aid: ", &us_onoffaidp, MAXPARS, par);
		if (count == 0)
		{
			us_abortcommand("Specify an aid to control");
			return;
		}
	}
	pt = par[0];

	for(i=0; i<el_maxaid; i++)
		if (namesame(el_aids[i].aidname, pt) == 0) break;
	if (i >= el_maxaid)
	{
		us_abortcommand("No aid called %s", pt);
		return;
	}
	aid = &el_aids[i];

	if (aid == us_aid && state <= 1)
	{
		us_abortcommand("No! I won't go!");
		return;
	}
	if ((aid->aidstate&AIDON) == 0 && state <= 1)
	{
		ttyputverbose("%s already off", pt);
		return;
	}
	if ((aid->aidstate&AIDON) != 0 && state > 1)
	{
		ttyputverbose("%s already on", pt);
		return;
	}
	if (state <= 1) ttyputverbose("%s turned off", aid->aidname); else
	{
		if ((aid->aidstate&AIDINCREMENTAL) == 0 || state == 3)
			ttyputverbose("%s turned on", aid->aidname); else
				ttyputmsg("%s turned on, catching up on changes...", aid->aidname);
	}

	if (state <= 1) aidturnoff(aid, state); else
	{
		if (state == 2) aidturnon(aid, 0); else aidturnon(aid, 1);
		if ((aid->aidstate&AIDINCREMENTAL) != 0 && state != 3)
			ttyputmsg("...%s caught up", aid->aidname);
	}
}
