/*
 * Electric(tm) VLSI Design System
 *
 * File: drc.c
 * Design-rule check tool
 * 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 "config.h"
#if DRCTOOL

#include "global.h"
#include "egraphics.h"
#include "drc.h"
#include "efunction.h"
#include "tech.h"
#include "edialogs.h"
#include "usr.h"
#include "usrdiacom.h"

/* the DRC tool table */
static COMCOMP dr_drccp = {NOKEYWORD, topoffacets, nextfacets, NOPARAMS,
	NOBACKUP, INPUTOPT, " \t", M_("facet to be rechecked"), M_("check current facet")};
static KEYWORD drcnotopt[] =
{
	{"verbose",        0,{NOKEY,NOKEY,NOKEY,NOKEY,NOKEY}},
	{"pointout",       0,{NOKEY,NOKEY,NOKEY,NOKEY,NOKEY}},
	{"reasonable",     0,{NOKEY,NOKEY,NOKEY,NOKEY,NOKEY}},
	TERMKEY
};
static COMCOMP drcnotp = {drcnotopt, NOTOPLIST, NONEXTLIST, NOPARAMS,
	NOBACKUP, INPUTOPT, " \t", M_("Design Rule Checker negative action"), 0};
static COMCOMP drcfip = {NOKEYWORD, topoffacets, nextfacets, NOPARAMS,
	NOBACKUP, INPUTOPT, " \t", M_("facet to ignored for short check DRC"), 0};
static COMCOMP drcfup = {NOKEYWORD, topoffacets, nextfacets, NOPARAMS,
	NOBACKUP, INPUTOPT, " \t", M_("facet to un-ignored for short check DRC"), 0};
static KEYWORD drcfopt[] =
{
	{"run",          0,{NOKEY,NOKEY,NOKEY,NOKEY,NOKEY}},
	{"ignore",       1,{&drcfip,NOKEY,NOKEY,NOKEY,NOKEY}},
	{"unignore",     1,{&drcfup,NOKEY,NOKEY,NOKEY,NOKEY}},
	TERMKEY
};
static COMCOMP drcfp = {drcfopt, NOTOPLIST, NONEXTLIST, NOPARAMS,
	NOBACKUP, INPUTOPT, " \t", M_("Short Detecting Design Rule Checker action"), 0};
static KEYWORD drcnopt[] =
{
	{"run",               0,{NOKEY,NOKEY,NOKEY,NOKEY,NOKEY}},
	{"use-dialog",        0,{NOKEY,NOKEY,NOKEY,NOKEY,NOKEY}},
	{"no-dialog",         0,{NOKEY,NOKEY,NOKEY,NOKEY,NOKEY}},
	{"verbose",           0,{NOKEY,NOKEY,NOKEY,NOKEY,NOKEY}},
	{"reset-check-dates", 0,{NOKEY,NOKEY,NOKEY,NOKEY,NOKEY}},
	{"just-1-error",      0,{NOKEY,NOKEY,NOKEY,NOKEY,NOKEY}},
	{"all-errors",        0,{NOKEY,NOKEY,NOKEY,NOKEY,NOKEY}},
	TERMKEY
};
static COMCOMP drcbatchp = {drcnopt, NOTOPLIST, NONEXTLIST, NOPARAMS,
	NOBACKUP, INPUTOPT, " \t", M_("Batch Design Rule Checker action"), 0};
static KEYWORD drcopt[] =
{
	{"check",                 1,{&dr_drccp,NOKEY,NOKEY,NOKEY,NOKEY}},
	{"forget-ignored-errors", 0,{NOKEY,NOKEY,NOKEY,NOKEY,NOKEY}},
	{"not",                   1,{&drcnotp,NOKEY,NOKEY,NOKEY,NOKEY}},
	{"verbose",               0,{NOKEY,NOKEY,NOKEY,NOKEY,NOKEY}},
	{"pointout",              0,{NOKEY,NOKEY,NOKEY,NOKEY,NOKEY}},
	{"batch",                 1,{&drcbatchp,NOKEY,NOKEY,NOKEY,NOKEY}},
	{"shortcheck",            1,{&drcfp,NOKEY,NOKEY,NOKEY,NOKEY}},
	{"ecadcheck",             0,{NOKEY,NOKEY,NOKEY,NOKEY,NOKEY}},
	{"reasonable",            0,{NOKEY,NOKEY,NOKEY,NOKEY,NOKEY}},
	TERMKEY
};
COMCOMP dr_drcp = {drcopt, NOTOPLIST, NONEXTLIST, NOPARAMS, NOBACKUP,
	0, " \t", M_("Design Rule Checker action"), M_("show defaults")};

/* dcheck modules */
#define NODCHECK ((DCHECK *)-1)

#define NODENEW    1					/* this nodeinst was created */
#define ARCNEW     2					/* this arcinst was created */
#define NODEMOD    3					/* this nodeinst was modified */
#define ARCMOD     4					/* this arcinst was modified */
#define NODEKILL   5					/* this nodeinst was deleted */
#define ARCKILL    6					/* this arcinst was deleted */

typedef struct Idcheck
{
	NODEPROTO *facet;					/* facet containing object to be checked */
	INTBIG     entrytype;				/* type of check */
	void      *entryaddr;				/* object being checked */
	struct Idcheck *nextdcheck;			/* next in list */
} DCHECK;
static DCHECK   *dr_firstdcheck;
static DCHECK   *dr_dcheckfree;

/*
 * The meaning of "tool:drc.DRC_pointout" is as follows:
 *  0: not identifying errors
 *  1: errors being identified
 * >1: some errors have already been identified
 * -2: do not identify any more errors in this session
 */
static INTBIG    dr_pointoutkey;					/* key for "DRC_pointout" */
static INTBIG    dr_pointout;						/* cached value of "DRC_pointout" */
static INTBIG    dr_verbosekey;						/* key for "DRC_verbose" */
       INTBIG    dr_ignore_listkey;					/* key for "DRC_ignore_list" */
       INTBIG    dr_lastgooddrckey;					/* key for "DRC_last_good_drc" */
static INTBIG    dr_incrementalonkey;				/* key for "DRC_incrementalon" */
static INTBIG    dr_optionskey;						/* key for "DRC_options" */
       INTBIG    dr_options;						/* cached value of "DRC_options" */
       INTBIG    dr_max_distanceskey;				/* key for "DRC_max_distances" */
       INTBIG    dr_wide_limitkey;					/* key for "DRC_wide_limit" */
       INTBIG    dr_min_widthkey;					/* key for "DRC_min_width" */
       INTBIG    dr_min_width_rulekey;				/* key for "DRC_min_width_rule" */
       INTBIG    dr_min_node_sizekey;				/* key for "DRC_min_node_size" */
       INTBIG    dr_min_node_size_rulekey;			/* key for "DRC_min_node_size_rule" */
       INTBIG    dr_connected_distanceskey;			/* key for "DRC_min_connected_distances" */
       INTBIG    dr_connected_distances_rulekey;	/* key for "DRC_min_connected_distances_rule" */
       INTBIG    dr_unconnected_distanceskey;		/* key for "DRC_min_unconnected_distances" */
       INTBIG    dr_unconnected_distances_rulekey;	/* key for "DRC_min_unconnected_distances_rule" */
       INTBIG    dr_connected_distancesWkey;		/* key for "DRC_min_connected_distances_wide" */
       INTBIG    dr_connected_distancesW_rulekey;	/* key for "DRC_min_connected_distances_wide_rule" */
       INTBIG    dr_unconnected_distancesWkey;		/* key for "DRC_min_unconnected_distances_wide" */
       INTBIG    dr_unconnected_distancesW_rulekey;	/* key for "DRC_min_unconnected_distances_wide_rule" */
       INTBIG    dr_connected_distancesMkey;		/* key for "DRC_min_connected_distances_multi" */
       INTBIG    dr_connected_distancesM_rulekey;	/* key for "DRC_min_connected_distances_multi_rule" */
       INTBIG    dr_unconnected_distancesMkey;		/* key for "DRC_min_unconnected_distances_multi" */
       INTBIG    dr_unconnected_distancesM_rulekey;	/* key for "DRC_min_unconnected_distances_multi_rule" */
       INTBIG    dr_edge_distanceskey;				/* key for "DRC_min_edge_distances" */
       INTBIG    dr_edge_distances_rulekey;			/* key for "DRC_min_edge_distances_rule" */
       TOOL     *dr_tool;							/* the DRC tool object */
char *dr_variablenames[] =
{
	"DRC_max_distances",
	"DRC_wide_limit",
	"DRC_min_width",
	"DRC_min_width_rule",
	"DRC_min_node_size",
	"DRC_min_node_size_rule",
	"DRC_min_connected_distances",
	"DRC_min_connected_distances_rule",
	"DRC_min_unconnected_distances",
	"DRC_min_unconnected_distances_rule",
	"DRC_min_connected_distances_wide",
	"DRC_min_connected_distances_wide_rule",
	"DRC_min_unconnected_distances_wide",
	"DRC_min_unconnected_distances_wide_rule",
	"DRC_min_connected_distances_multi",
	"DRC_min_connected_distances_multi_rule",
	"DRC_min_unconnected_distances_multi",
	"DRC_min_unconnected_distances_multi_rule",
	"DRC_min_edge_distances",
	"DRC_min_edge_distances_rule",
	0
};

/* prototypes for local routines */
static DCHECK    *dr_allocdcheck(void);
static void       dr_batchdlog(NODEPROTO *facet);
static NODEPROTO *dr_checkthisfacet(INTBIG, char*);
static char      *dr_errordlog(char *prompt);
static void       dr_freedcheck(DCHECK*);
static void       dr_optionsdlog(void);
static void       dr_rulesdlog(void);
static void       dr_queuecheck(void*, NODEPROTO*, INTBIG);
static void       dr_reporterrors(void);
static void       dr_setignore(GEOM*, GEOM*);
static void       dr_unignore(NODEPROTO*, BOOLEAN, void*);
static void       dr_unqueuecheck(LIBRARY *lib);
static INTBIG     dr_examinefacet(NODEPROTO *np, BOOLEAN report);

/*
 * routine to allocate a new dcheck module from the pool (if any) or memory
 */
DCHECK *dr_allocdcheck(void)
{
	REGISTER DCHECK *d;

	if (dr_dcheckfree == NODCHECK)
	{
		d = (DCHECK *)emalloc(sizeof (DCHECK), dr_tool->cluster);
		if (d == 0) return(NODCHECK);
	} else
	{
		/* take module from free list */
		d = dr_dcheckfree;
		dr_dcheckfree = (DCHECK *)d->nextdcheck;
	}
	return(d);
}

/*
 * routine to return dcheck module "d" to the pool of free modules
 */
void dr_freedcheck(DCHECK *d)
{
	d->nextdcheck = dr_dcheckfree;
	dr_dcheckfree = d;
}

void dr_queuecheck(void *addr, NODEPROTO *facet, INTBIG function)
{
	REGISTER DCHECK *d;

	/* do not examine anything in a hidden library (such as the clipboard) */
	if ((facet->cell->lib->userbits&HIDDENLIBRARY) != 0) return;

	d = dr_allocdcheck();
	if (d == NODCHECK)
	{
		ttyputnomemory();
		return;
	}
	d->entrytype = function;
	d->facet = facet;
	d->entryaddr = addr;
	d->nextdcheck = dr_firstdcheck;
	dr_firstdcheck = d;
}

/*
 * Routine to remove all check objects that are in library "lib"
 * (because the library has been deleted)
 */
void dr_unqueuecheck(LIBRARY *lib)
{
	REGISTER DCHECK *d, *nextd, *lastd;

	lastd = NODCHECK;
	for(d = dr_firstdcheck; d != NODCHECK; d = nextd)
	{
		nextd = d->nextdcheck;
		if (d->facet == NONODEPROTO || d->facet->cell->lib == lib)
		{
			if (lastd == NODCHECK) dr_firstdcheck = nextd; else
				lastd->nextdcheck = nextd;
			dr_freedcheck(d);
			continue;
		}
		lastd = d;
	}
}

/******************** CONTROL ********************/

void dr_init(INTBIG *argc, char *argv[], TOOL *thistool)
{
	REGISTER VARIABLE *var;

	/* only initialize during pass 1 */
	if (thistool == NOTOOL || thistool == 0) return;

	dr_tool = thistool;

	/* initialize the design-rule checker lists */
	dr_dcheckfree = NODCHECK;
	dr_firstdcheck = NODCHECK;

	dr_pointoutkey = makekey("DRC_pointout");
	dr_verbosekey = makekey("DRC_verbose");
	dr_ignore_listkey = makekey("DRC_ignore_list");
	dr_lastgooddrckey = makekey("DRC_last_good_drc");
	dr_incrementalonkey = makekey("DRC_incrementalon");
	dr_optionskey = makekey("DRC_options");
	var = getvalkey((INTBIG)dr_tool, VTOOL, VINTEGER, dr_optionskey);
	if (var == NOVARIABLE) dr_options = 0; else
		dr_options = var->addr;
	nextchangequiet();
	(void)setvalkey((INTBIG)dr_tool, VTOOL, dr_pointoutkey, 0, VINTEGER|VDONTSAVE);
	dr_pointout = 0;
	DiaDeclareHook("drcopt", &dr_drcp, dr_optionsdlog);
	DiaDeclareHook("drcrules", &dr_drccp, dr_rulesdlog);
}

void dr_done(void)
{
#ifdef DEBUGMEMORY
	REGISTER DCHECK *d;

	while (dr_firstdcheck != NODCHECK)
	{
		d = dr_firstdcheck;
		dr_firstdcheck = dr_firstdcheck->nextdcheck;
		dr_freedcheck(d);
	}
	while (dr_dcheckfree != NODCHECK)
	{
		d = dr_dcheckfree;
		dr_dcheckfree = dr_dcheckfree->nextdcheck;
		efree((char *)d);
	}
	drcb_term();
#endif
}

void dr_set(INTBIG count, char *par[])
{
	REGISTER BOOLEAN negate;
	REGISTER INTBIG l, i, errs;
	REGISTER char *pp;
	REGISTER FILE *f;
	REGISTER NODEPROTO *np;
	REGISTER VARIABLE *var;
	REGISTER TECHNOLOGY *tech;
	char *newpar[3], *truename, *abortseq;
	static INTBIG filetypedrac = -1;

	if (count == 0)
	{
		var = getvalkey((INTBIG)dr_tool, VTOOL, VINTEGER, dr_verbosekey);
		if (var == NOVARIABLE) i = 0; else i = var->addr;
		if (i == 0) ttyputmsg(M_("Design rule checker is brief")); else
 			ttyputmsg(M_("Design rule checker is verbose"));
		var = getvalkey((INTBIG)dr_tool, VTOOL, VINTEGER, dr_pointoutkey);
		if (var == NOVARIABLE) i = 0; else i = var->addr;
		if (i == 0) ttyputmsg(M_("Errors will not be highlighted")); else
			ttyputmsg(M_("Errors will be highlighted"));
		return;
	}

	l = strlen(pp = par[0]);
	negate = FALSE;
	if (namesamen(pp, "not", l) == 0)
	{
		negate = TRUE;
		if (count <= 1)
		{
			count = ttygetparam(M_("DRC negate option:"), &drcnotp, MAXPARS-1, &par[1]) + 1;
			if (count <= 1)
			{
				ttyputerr(M_("Aborted"));
				return;
			}
		}
		l = strlen(pp = par[1]);
	}

	if (namesamen(pp, "forget-ignored-errors", l) == 0)
	{
		np = dr_checkthisfacet(count, par[1]);
		if (np == NONODEPROTO) return;

		/* erase all ignore lists on all objects */
		var = getvalkey((INTBIG)np, VNODEPROTO, VGEOM|VISARRAY, dr_ignore_listkey);
		if (var != NOVARIABLE)
			(void)delvalkey((INTBIG)np, VNODEPROTO, dr_ignore_listkey);

		ttyputmsg(M_("Ignored violations turned back on."));
		return;
	}
	if (namesamen(pp, "check", l) == 0)
	{
		/* make sure network tool is on */
		if ((net_tool->toolstate&TOOLON) == 0)
		{
			ttyputerr(M_("Network tool must be running...turning it on"));
			toolturnon(net_tool, FALSE);
			ttyputerr(M_("...now reissue the DRC command"));
			return;
		}

		np = dr_checkthisfacet(count, par[1]);
		if (np == NONODEPROTO) return;
		errs = dr_examinefacet(np, TRUE);
		return;
	}

	if (namesamen(pp, "ecadcheck", l) == 0)
	{
		np = getcurfacet();
		if (np == NONODEPROTO)
		{
			ttyputerr(_("Must be editing a facet to check with ECAD's Dracula"));
			return;
		}
		tech = np->tech;
		var = getval((INTBIG)tech, VTECHNOLOGY, VSTRING|VISARRAY, "DRC_ecad_deck");
		if (var == NOVARIABLE)
		{
			ttyputerr(_("Cannot find an ECAD deck in the %s technology"), tech->techname);
			return;
		}

		/* create the file */
		if (filetypedrac < 0)
			filetypedrac = setupfiletype("rul", "*.map", MACFSTAG('TEXT'), FALSE, "drac", _("Dracula"));
		f = xcreate("ecaddrc.RUL", filetypedrac, _("ECAD DRC Control File"), &truename);
		if (f == NULL)
		{
			if (truename != 0) ttyputerr(_("Cannot write %s"), truename);
			return;
		}

		/* write the deck */
		l = getlength(var);
		for(i=0; i<l; i++)
		{
			pp = ((char **)var->addr)[i];
			if (strncmp(pp, " PRIMARY =", 10) == 0)
			{
				xprintf(f, " PRIMARY = %s\n", np->cell->cellname);
				continue;
			}
			if (strncmp(pp, " INDISK =", 9) == 0)
			{
				xprintf(f, " INDISK = %s.cif\n", np->cell->cellname);
				continue;
			}
			xprintf(f, "%s\n", pp);
		}

		/* finished with control deck */
		xclose(f);
		ttyputverbose(M_("Wrote 'ecaddrc.RUL'.  Now generating CIF for facet %s"), describenodeproto(np));

		/* tell I/O to write CIF */
		newpar[0] = "cif";
		newpar[1] = "output";
		newpar[2] = "include-cloak-layer";
		telltool(io_tool, 3, newpar);
		(void)asktool(io_tool, "write", (INTBIG)np->cell->lib, (INTBIG)"cif");
		newpar[0] = "cif";
		newpar[1] = "output";
		newpar[2] = "ignore-cloak-layer";
		telltool(io_tool, 3, newpar);
		return;
	}

	if (namesamen(pp, "batch", l) == 0)
	{
		if (count == 1)
		{
			ttyputusage("telltool drc batch OPTIONS");
			return;
		}
		l = strlen(pp = par[1]);
		if (namesamen(pp, "run", l) == 0)
		{
			/* make sure network tool is on */
			if ((net_tool->toolstate&TOOLON) == 0)
			{
				ttyputerr(M_("Network tool must be running...turning it on"));
				toolturnon(net_tool, FALSE);
				ttyputerr(M_("...now reissue the DRC command"));
				return;
			}

			/* make sure there is a facet to check */
			np = getcurfacet();
			if (np == NONODEPROTO)
			{
				ttyputerr(_("No current facet"));
				return;
			}

			/* see if a dialog should be displayed */
			if ((dr_options&DRCHIERDIALOG) != 0)
			{
				dr_batchdlog(np);
			} else
			{
				(void)initinfstr();
				(void)addstringtoinfstr(_("Checking hierarchically..."));
				abortseq = getinterruptkey();
				if (abortseq != 0)
					(void)formatinfstr(_(" (type %s to abort)"), abortseq);
				ttyputmsg(returninfstr());
				(void)drcb_check(np, TRUE);
			}
			return;
		}
		if (namesamen(pp, "use-dialog", l) == 0)
		{
			(void)setvalkey((INTBIG)dr_tool, VTOOL, dr_optionskey,
				dr_options | DRCHIERDIALOG, VINTEGER);
			ttyputmsg(M_("Hierarchical DRC will use a dialog interface"));
			return;
		}
		if (namesamen(pp, "no-dialog", l) == 0)
		{
			(void)setvalkey((INTBIG)dr_tool, VTOOL, dr_optionskey,
				dr_options & ~DRCHIERDIALOG, VINTEGER);
			ttyputmsg(M_("Hierarchical DRC will not use a dialog interface"));
			return;
		}
		if (namesamen(pp, "just-1-error", l) == 0)
		{
			(void)setvalkey((INTBIG)dr_tool, VTOOL, dr_optionskey,
				dr_options | DRCFIRSTERROR, VINTEGER);
			ttyputmsg(M_("Hierarchical DRC will stop after first error in a facet"));
			return;
		}
		if (namesamen(pp, "all-errors", l) == 0)
		{
			(void)setvalkey((INTBIG)dr_tool, VTOOL, dr_optionskey,
				dr_options & ~DRCFIRSTERROR, VINTEGER);
			ttyputmsg(M_("Hierarchical DRC will find all errors in a facet"));
			return;
		}
		if (namesamen(pp, "verbose", l) == 0)
		{
			drcb_debug();
			return;
		}
		if (namesamen(pp, "reset-check-dates", l) == 0)
		{
			drcb_reset_dates();
			ttyputmsg(M_("All date information about valid DRC is reset"));
			return;
		}
		ttyputbadusage("telltool drc batch");
		return;
	}

	if (namesamen(pp, "shortcheck", l) == 0)
	{
		if (count < 2)
		{
			ttyputusage("telltool drc shortcheck (run|ignore|unignore)");
			return;
		}
		l = strlen(pp = par[1]);
		if (namesamen(pp, "run", l) == 0)
		{
			/* make sure network tool is on */
			if ((net_tool->toolstate&TOOLON) == 0)
			{
				ttyputerr(M_("Network tool must be running...turning it on"));
				toolturnon(net_tool, FALSE);
				ttyputerr(M_("...now reissue the DRC command"));
				return;
			}
			dr_flatwrite(getcurfacet());
			return;
		}
		if (namesamen(pp, "ignore", l) == 0)
		{
			if (count < 3)
			{
				ttyputusage("telltool drc shortcheck ignore FACET");
				return;
			}
			dr_flatignore(par[2]);
			return;
		}
		if (namesamen(pp, "unignore", l) == 0)
		{
			if (count < 3)
			{
				ttyputusage("telltool drc shortcheck unignore FACET");
				return;
			}
			dr_flatunignore(par[2]);
			return;
		}
		ttyputbadusage("telltool drc shortcheck");
		return;
	}

	if (namesamen(pp, "verbose", l) == 0)
	{
		if (!negate)
		{
			(void)setvalkey((INTBIG)dr_tool, VTOOL, dr_verbosekey, 1, VINTEGER|VDONTSAVE);
			ttyputverbose(M_("DRC verbose"));
		} else
		{
			if (getvalkey((INTBIG)dr_tool, VTOOL, VINTEGER, dr_verbosekey) != NOVARIABLE)
				(void)delvalkey((INTBIG)dr_tool, VTOOL, dr_verbosekey);
			ttyputverbose(M_("DRC brief"));
		}
		return;
	}

	if (namesamen(pp, "reasonable", l) == 0)
	{
		if (!negate)
		{
			(void)setvalkey((INTBIG)dr_tool, VTOOL, dr_optionskey,
				dr_options | DRCREASONABLE, VINTEGER);
			ttyputverbose(M_("DRC will consider only reasonable number of polygons"));
		} else
		{
			(void)setvalkey((INTBIG)dr_tool, VTOOL, dr_optionskey,
				dr_options & ~DRCREASONABLE, VINTEGER);
			ttyputverbose(M_("DRC will consider all polygons"));
		}
		return;
	}

	if (namesamen(pp, "pointout", l) == 0)
	{
		if (!negate)
		{
			(void)setvalkey((INTBIG)dr_tool, VTOOL, dr_pointoutkey, 1, VINTEGER);
			ttyputverbose(M_("DRC will identify errors"));
		} else
		{
			(void)setvalkey((INTBIG)dr_tool, VTOOL, dr_pointoutkey, 0, VINTEGER);
			ttyputverbose(M_("DRC will not identify errors"));
		}
		return;
	}
	ttyputbadusage("telltool drc");
}

void dr_examinenodeproto(NODEPROTO *np)
{
	if (dr_examinefacet(np, FALSE) != 0)
	{
		dr_reporterrors();
	}
}

/*
 * Routine to examine facet "np" nonhierarchically and return the number of errors found.
 */
INTBIG dr_examinefacet(NODEPROTO *np, BOOLEAN report)
{
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	REGISTER VARIABLE *var;
	REGISTER INTBIG errorcount;

	if (stopping(STOPREASONDRC)) return(0);

	/* do not check things in hidden libraries (such as the clipboard) */
	if ((np->cell->lib->userbits&HIDDENLIBRARY) != 0) return(0);

	/* make sure network tool is on */
	if ((net_tool->toolstate&TOOLON) == 0) return(0);

	/* setup for identifying errors */
	var = getvalkey((INTBIG)dr_tool, VTOOL, VINTEGER, dr_pointoutkey);
	if (var != NOVARIABLE) dr_pointout = var->addr; else
		dr_pointout = 0;

	/* clear errors */
	initerrorlogging(_("DRC"));

	drcb_initincrementalcheck(np);
	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		(void)drcb_checkincremental(ni->geom, TRUE);
	for(ai = np->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
		(void)drcb_checkincremental(ai->geom, TRUE);
	errorcount = numerrors();
	if (report)
		ttyputmsg(_("Facet %s checked at this level only (%ld errors)"),
			describenodeproto(np), errorcount);
	termerrorlogging(TRUE);
	return(errorcount);
}

void dr_slice(void)
{
	REGISTER DCHECK *d, *nextd;
	REGISTER NODEPROTO *np;
	REGISTER BOOLEAN didcheck;
	REGISTER VARIABLE *var;

	/* see if the tool should be off */
	var = getvalkey((INTBIG)dr_tool, VTOOL, VINTEGER, dr_incrementalonkey);
	if (var != NOVARIABLE && var->addr == 0)
	{
		toolturnoff(dr_tool, FALSE);
		return;
	}

	if (dr_firstdcheck == NODCHECK) return;

	/* make sure network tool is on */
	if ((net_tool->toolstate&TOOLON) == 0)
	{
		ttyputerr(M_("Network tool must be running...turning it on"));
		toolturnon(net_tool, FALSE);
		return;
	}

	/* mark this activity */
	setactivity(_("DRC"));

	/* first clear the ignore information on any objects that changed */
	for(d = dr_firstdcheck; d != NODCHECK; d = d->nextdcheck)
	{
		if (d->entrytype == NODEMOD || d->entrytype == NODEKILL)
			dr_unignore(d->facet, TRUE, d->entryaddr);
		if (d->entrytype == ARCMOD || d->entrytype == ARCKILL)
			dr_unignore(d->facet, FALSE, d->entryaddr);
	}

	/* clear errors */
	initerrorlogging(_("DRC"));

	np = NONODEPROTO;
	didcheck = FALSE;
	for(d = dr_firstdcheck; d != NODCHECK; d = nextd)
	{
		nextd = d->nextdcheck;
		if (!stopping(STOPREASONDRC))
		{
			if (d->entrytype == NODEMOD || d->entrytype == NODENEW)
			{
				NODEINST *ni;

				ni = (NODEINST *)d->entryaddr;
				if ((ni->userbits&DEADN) == 0)
				{
					if (d->facet != np)
					{
						np = ni->parent;
						drcb_initincrementalcheck(np);
						didcheck = TRUE;
					}
					(void)drcb_checkincremental(ni->geom, FALSE);
				}
			}
			if (d->entrytype == ARCMOD || d->entrytype == ARCNEW)
			{
				ARCINST *ai;

				ai = (ARCINST *)d->entryaddr;
				if ((ai->userbits&DEADA) == 0)
				{
					if (d->facet != np)
					{
						np = ai->parent;
						drcb_initincrementalcheck(np);
						didcheck = TRUE;
					}
					(void)drcb_checkincremental(ai->geom, FALSE);
				}
			}
		}
		dr_freedcheck(d);
	}
	dr_firstdcheck = NODCHECK;
	if (didcheck)
	{
		termerrorlogging(FALSE);
		dr_reporterrors();
	}
}

void dr_reporterrors(void)
{
	REGISTER INTBIG i, errs, len;
	REGISTER char *pt, *answer;
	REGISTER VARIABLE *var;
	GEOM *geom1, *geom2;

	/* setup for identifying errors */
	var = getvalkey((INTBIG)dr_tool, VTOOL, VINTEGER, dr_pointoutkey);
	if (var != NOVARIABLE) dr_pointout = var->addr; else
		dr_pointout = 0;

	errs = numerrors();
	for(i=0; i<errs; i++)
	{
		if (dr_pointout <= 0)
		{
			pt = reportnexterror(0, 0, 0);
			ttyputmsg("%s", pt);
		} else
		{
			/* save highlight features if this is the first error */
			pt = reportnexterror(1, &geom1, &geom2);
			dr_pointout++;
			answer = dr_errordlog(pt);
			if (answer == 0) continue;
			len = strlen(answer);
			if (namesamen(answer, "quit-checking", len) == 0) break;
			if (namesamen(answer, "continue-silently", len) == 0)
			{
				dr_pointout = -2;
				continue;
			}
			if (namesamen(answer, "highlight-off", len) == 0)
			{
				dr_pointout = 0;
				setvalkey((INTBIG)dr_tool, VTOOL, dr_pointoutkey, 0, VINTEGER|VDONTSAVE);
				continue;
			}
			if (namesamen(answer, "drc-off", len) == 0)
			{
				toolturnoff(dr_tool, FALSE);
				continue;
			}
			if (namesamen(answer, "ignore-error", len) == 0)
			{
				/* mark objects "geom1" and "geom2" as ok */
				dr_setignore(geom1, geom2);
				ttyputverbose(M_("Ignored"));
				continue;
			}
		}
	}

	/* reset error pointers to start of list */
	termerrorlogging(FALSE);

	/* restore highlighting features */
	i = abs(dr_pointout);
	if (i > 1) ttyputverbose(M_("DRC done"));
}

/******************** DATABASE CHANGES ********************/

void dr_modifynodeinst(NODEINST *ni,INTBIG oldlx,INTBIG oldly,INTBIG oldhx,INTBIG oldhy,
	INTBIG oldrot,INTBIG oldtran)
{
	dr_queuecheck(ni, ni->parent, NODEMOD);
}

void dr_modifyarcinst(ARCINST *ai,INTBIG oldxA, INTBIG oldyA, INTBIG oldxB, INTBIG oldyB,
	INTBIG oldwid, INTBIG oldlen)
{
	dr_queuecheck(ai, ai->parent, ARCMOD);
}

void dr_newobject(INTBIG addr, INTBIG type)
{
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;

	if ((type&VTYPE) == VNODEINST)
	{
		ni = (NODEINST *)addr;
		dr_queuecheck(ni, ni->parent, NODENEW);
	} else if ((type&VTYPE) == VARCINST)
	{
		ai = (ARCINST *)addr;
		dr_queuecheck(ai, ai->parent, ARCNEW);
	}
}

void dr_killobject(INTBIG addr, INTBIG type)
{
	REGISTER DCHECK *d, *lastd, *nextd;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;

	/* remove any others in the list that point to this deleted object */
	lastd = NODCHECK;
	for(d = dr_firstdcheck; d != NODCHECK; d = nextd)
	{
		nextd = d->nextdcheck;
		if (d->entryaddr == (void *)addr)
		{
			if (lastd == NODCHECK) dr_firstdcheck = nextd; else
				lastd->nextdcheck = nextd;
			dr_freedcheck(d);
			continue;
		}
		lastd = d;
	}

	if ((type&VTYPE) == VNODEINST)
	{
		ni = (NODEINST *)addr;
		dr_queuecheck(ni, ni->parent, NODEKILL);
	} else if ((type&VTYPE) == VARCINST)
	{
		ai = (ARCINST *)addr;
		dr_queuecheck(ai, ai->parent, ARCKILL);
	}
}

void dr_eraselibrary(LIBRARY *lib)
{
	dr_unqueuecheck(lib);
}

void dr_newvariable(INTBIG addr, INTBIG type, INTBIG key, INTBIG newtype)
{
	REGISTER VARIABLE *var;

	if (type == VTOOL)
	{
		if (key == dr_optionskey)
		{
			var = getvalkey((INTBIG)dr_tool, VTOOL, VINTEGER, dr_optionskey);
			if (var != NOVARIABLE) dr_options = var->addr;
		}
	} else if (type == VTECHNOLOGY)
	{
		if (key == el_techstate_key)
			dr_curtech = NOTECHNOLOGY;
	}
}

void dr_readlibrary(LIBRARY *lib)
{
	REGISTER VARIABLE *var;

	/* recache DRC variables */
	var = getvalkey((INTBIG)dr_tool, VTOOL, VINTEGER, dr_optionskey);
	if (var != NOVARIABLE) dr_options = var->addr;
}

/******************** SUPPORT ROUTINES ********************/

/*
 * routine to add DRC ignore information to object "pos1" so that it will
 * ignore errors with object "pos2" (if "pos2" is not NOGEOM)
 */
void dr_setignore(GEOM *pos1, GEOM *pos2)
{
	REGISTER GEOM **ignorelist, *p1, *p2;
	GEOM *dummylist[2];
	REGISTER VARIABLE *var;
	REGISTER INTBIG i, len;
	REGISTER NODEPROTO *np;

	np = geomparent(pos1);
	var = getvalkey((INTBIG)np, VNODEPROTO, VGEOM|VISARRAY, dr_ignore_listkey);

	/* if the list is empty: create it with one entry */
	if (var == NOVARIABLE)
	{
		dummylist[0] = pos1;   dummylist[1] = pos2;
		(void)setvalkey((INTBIG)np, VNODEPROTO, dr_ignore_listkey, (INTBIG)dummylist,
			VGEOM|VISARRAY|(2<<VLENGTHSH)|VDONTSAVE);
		return;
	}

	/* search list to see if this entry is on it */
	len = getlength(var);
	for(i=0; i<len; i += 2)
	{
		p1 = ((GEOM **)var->addr)[i];
		p2 = ((GEOM **)var->addr)[i+1];
		if (pos1 == p1 && pos2 == p2) return;
		if (pos1 == p2 && pos2 == p1) return;
	}

	/* entry not in list: add it */
	ignorelist = (GEOM **)emalloc(((len+2) * (sizeof (GEOM *))), el_tempcluster);
	if (ignorelist == 0)
	{
		ttyputnomemory();
		return;
	}

	/* update the list */
	for(i=0; i<len; i++) ignorelist[i] = ((GEOM **)var->addr)[i];
	ignorelist[len] = pos1;   ignorelist[len+1] = pos2;
	(void)setvalkey((INTBIG)np, VNODEPROTO, dr_ignore_listkey, (INTBIG)ignorelist,
		(INTBIG)(VGEOM|VISARRAY|((len+2)<<VLENGTHSH)|VDONTSAVE));
	efree((char *)ignorelist);
}

/*
 * routine to delete DRC ignore information on object "pos"
 */
void dr_unignore(NODEPROTO *np, BOOLEAN objisnode, void *addr)
{
	REGISTER GEOM **ignorelist, *p1, *p2, *geom;
	REGISTER VARIABLE *var;
	REGISTER INTBIG i, len, pt;

	var = getvalkey((INTBIG)np, VNODEPROTO, VGEOM|VISARRAY, dr_ignore_listkey);

	/* if the list is empty there is nothing to do */
	if (var == NOVARIABLE) return;

	/* see if this entry is mentioned in the list */
	len = getlength(var);
	for(i=0; i<len; i++)
	{
		geom = ((GEOM **)var->addr)[i];
		if (geom->entryisnode == objisnode && geom->entryaddr.blind == addr) break;
	}
	if (i >= len) return;

	/* create a new list without the entry */
	ignorelist = (GEOM **)emalloc((len * (sizeof (GEOM *))), el_tempcluster);
	if (ignorelist == 0)
	{
		ttyputnomemory();
		return;
	}

	/* search list and remove entries that mention this module */
	pt = 0;
	for(i=0; i<len; i += 2)
	{
		p1 = ((GEOM **)var->addr)[i];
		p2 = ((GEOM **)var->addr)[i+1];
		if (p1->entryisnode == objisnode && p1->entryaddr.blind == addr) continue;
		if (p2->entryisnode == objisnode && p2->entryaddr.blind == addr) continue;
		ignorelist[pt++] = p1;   ignorelist[pt++] = p2;
	}

	/* set the list back in place */
	if (pt > 0) (void)setvalkey((INTBIG)np, VNODEPROTO, dr_ignore_listkey,
		(INTBIG)ignorelist, (INTBIG)(VGEOM|VISARRAY|(pt<<VLENGTHSH)|VDONTSAVE)); else
			(void)delvalkey((INTBIG)np, VNODEPROTO, dr_ignore_listkey);
	efree((char *)ignorelist);
}

/*
 * routine to determine the node prototype specified in "name" (given that
 * there were "count" parameters to the command and that "name" should be in
 * the second).  Returns NONODEPROTO if there is an error.
 */
NODEPROTO *dr_checkthisfacet(INTBIG count, char *name)
{
	REGISTER NODEPROTO *np;

	if (count < 2)
	{
		np = getcurfacet();
		if (np == NONODEPROTO) ttyputerr(_("No current facet"));
		return(np);
	}
	np = getnodeproto(name);
	if (np == NONODEPROTO)
	{
		ttyputerr(_("No facet named %s"), name);
		return(NONODEPROTO);
	}
	if (np->primindex != 0)
	{
		ttyputerr(_("Can only check facets, not primitives"));
		return(NONODEPROTO);
	}
	return(np);
}

/****************************** DIALOGS ******************************/

/* Hierarchical DRC */
static DIALOGITEM dr_hierdialoglist[] =
{
 /*  1 */ {0, {8,8,32,72}, BUTTON, N_("Check")},
 /*  2 */ {0, {8,344,32,408}, BUTTON, N_("Done")},
 /*  3 */ {0, {38,8,132,408}, MESSAGE, ""},
 /*  4 */ {0, {8,214,32,326}, BUTTON, N_("Show Prev Error")},
 /*  5 */ {0, {8,94,32,206}, BUTTON, N_("Show Next Error")}
};
static DIALOG dr_hierdialog = {{50,75,191,492}, N_("Hierarchical DRC"), 0, 5, dr_hierdialoglist};

/* special items for the "hierarchical drc" dialog: */
#define DHDR_CHECK    1		/* Check (button) */
#define DHDR_DONE     2		/* Done (button) */
#define DHDR_ERRMSG   3		/* Error Message (stat text) */
#define DHDR_PREVERR  4		/* Show Prev Error (button) */
#define DHDR_NXTERR   5		/* Show Next Error (button) */

void dr_batchdlog(NODEPROTO *facet)
{
	INTBIG itemHit, errs, oldplease;
	char *msg, lne[50];
	float elapsed;

	/* display the hierarchical DRC dialog box */
	if (DiaInitDialog(&dr_hierdialog)) return;
	errs = numerrors();
	if (errs == 0)
	{
		DiaDimItem(DHDR_PREVERR);
		DiaDimItem(DHDR_NXTERR);
	} else
	{
		DiaUnDimItem(DHDR_PREVERR);
		DiaUnDimItem(DHDR_NXTERR);
	}

	/* loop until done */
	oldplease = el_pleasestop;
	for(;;)
	{
		el_pleasestop = oldplease;
		itemHit = DiaNextHit();
		if (itemHit == DHDR_DONE) break;
		if (itemHit == DHDR_CHECK)
		{
			/* "check" button */
			DiaSetText(DHDR_ERRMSG, _("Checking..."));
			starttimer();
			errs = drcb_check(facet, FALSE);
			elapsed = endtimer();
			if (elapsed > 60.0 && (us_useroptions&BEEPAFTERLONGJOB) != 0)
				ttybeep(1);
			(void)sprintf(lne, _("%ld errors found (took %s)"), errs, explainduration(elapsed));
			DiaSetText(DHDR_ERRMSG, lne);
			DiaUnDimItem(DHDR_PREVERR);
			DiaUnDimItem(DHDR_NXTERR);
			continue;
		}
		if (itemHit == DHDR_NXTERR)
		{
			/* "show next error" button */
			msg = reportnexterror(1, 0, 0);
			if (msg == 0) msg = "";
			DiaSetText(DHDR_ERRMSG, msg);
			continue;
		}
		if (itemHit == DHDR_PREVERR)
		{
			/* "show prev error" button */
			msg = reportpreverror();
			if (msg == 0) msg = "";
			DiaSetText(DHDR_ERRMSG, msg);
			continue;
		}
	}

	DiaDoneDialog();
}

/* DRC: Options */
static DIALOGITEM dr_optionsdialogitems[] =
{
 /*  1 */ {0, {240,220,264,284}, BUTTON, N_("OK")},
 /*  2 */ {0, {176,220,200,284}, BUTTON, N_("Cancel")},
 /*  3 */ {0, {88,32,104,192}, CHECK, N_("Highlights errors")},
 /*  4 */ {0, {248,32,264,160}, BUTTON, N_("Edit Rules Deck")},
 /*  5 */ {0, {64,32,80,117}, CHECK, N_("On")},
 /*  6 */ {0, {144,32,160,192}, CHECK, N_("Use dialog")},
 /*  7 */ {0, {40,8,56,169}, MESSAGE, N_("Incremental DRC:")},
 /*  8 */ {0, {120,8,136,169}, MESSAGE, N_("Hierarchical DRC:")},
 /*  9 */ {0, {224,8,240,169}, MESSAGE, N_("Dracula DRC Interface:")},
 /* 10 */ {0, {192,32,208,192}, BUTTON, N_("Clear valid DRC dates")},
 /* 11 */ {0, {168,32,184,192}, CHECK, N_("Just 1 error per facet")},
 /* 12 */ {0, {8,8,24,248}, CHECK, N_("Ignore center cuts in large contacts")}
};
static DIALOG dr_optionsdialog = {{50,75,323,368}, N_("DRC Options"), 0, 12, dr_optionsdialogitems};

/* special items for the "DRC Options" dialog: */
#define DDRO_HIGHERRORS    3
#define DDRO_DRACULADECK   4
#define DDRO_DRCON         5
#define DDRO_HIERDIALOG    6
#define DDRO_CLEARDATES   10
#define DDRO_JUST1ERROR   11
#define DDRO_FEWERCUTS    12

void dr_optionsdlog(void)
{
	REGISTER INTBIG itemHit, options, higherrors,
		i, l, newhigherrors;
	REGISTER char *qual;
	INTBIG dummy, clearvaliddrcdates;
	char header[200], *dummyfile[1];
	REGISTER VARIABLE *var;
	REGISTER WINDOWPART *w;
	REGISTER EDITOR *ed;

	if (DiaInitDialog(&dr_optionsdialog)) return;

	var = getvalkey((INTBIG)dr_tool, VTOOL, VINTEGER, dr_pointoutkey);
	if (var == NOVARIABLE) higherrors = 0; else higherrors = var->addr;
	if (higherrors != 0) DiaSetControl(DDRO_HIGHERRORS, 1);
	if ((dr_options&DRCHIERDIALOG) != 0) DiaSetControl(DDRO_HIERDIALOG, 1);
	if ((dr_options&DRCFIRSTERROR) != 0) DiaSetControl(DDRO_JUST1ERROR, 1);
	if ((dr_options&DRCREASONABLE) != 0) DiaSetControl(DDRO_FEWERCUTS, 1);
	if ((dr_tool->toolstate & TOOLON) != 0) DiaSetControl(DDRO_DRCON, 1);

	/* loop until done */
	for(;;)
	{
		itemHit = DiaNextHit();
		if (itemHit == OK || itemHit == CANCEL || itemHit == DDRO_DRACULADECK) break;
		if (itemHit == DDRO_HIGHERRORS || itemHit == DDRO_DRCON ||
			itemHit == DDRO_HIERDIALOG || itemHit == DDRO_JUST1ERROR ||
			itemHit == DDRO_FEWERCUTS)
		{
			DiaSetControl(itemHit, 1 - DiaGetControl(itemHit));
			continue;
		}
		if (itemHit == DDRO_CLEARDATES)
		{
			clearvaliddrcdates = 1;
			DiaDimItem(DDRO_CLEARDATES);
			continue;
		}
	}

	if (itemHit != CANCEL)
	{
		if (DiaGetControl(DDRO_HIGHERRORS) != 0) newhigherrors = 1; else
			newhigherrors = 0;
		if (newhigherrors != higherrors)
			(void)setvalkey((INTBIG)dr_tool, VTOOL, dr_pointoutkey,
				newhigherrors, VINTEGER);
		options = 0;
		if (DiaGetControl(DDRO_HIERDIALOG) != 0) options |= DRCHIERDIALOG;
		if (DiaGetControl(DDRO_JUST1ERROR) != 0) options |= DRCFIRSTERROR;
		if (DiaGetControl(DDRO_FEWERCUTS) != 0) options |= DRCREASONABLE;
		if (options != dr_options)
	 		(void)setvalkey((INTBIG)dr_tool, VTOOL, dr_optionskey, options, VINTEGER);

		/* change state of DRC */
		if (DiaGetControl(DDRO_DRCON) != 0)
		{
			if ((dr_tool->toolstate & TOOLON) == 0)
			{
				toolturnon(dr_tool, FALSE);
				setvalkey((INTBIG)dr_tool, VTOOL, dr_incrementalonkey, 1, VINTEGER);
			}
		} else
		{
			if ((dr_tool->toolstate & TOOLON) != 0)
			{
				toolturnoff(dr_tool, FALSE);
				setvalkey((INTBIG)dr_tool, VTOOL, dr_incrementalonkey, 0, VINTEGER);
			}
		}

		/* clear valid DRC dates if requested */
		if (clearvaliddrcdates != 0) drcb_reset_dates();
	}
	DiaDoneDialog();

	if (itemHit == DDRO_DRACULADECK)
	{
		/* now edit the dracula */
		qual = "DRC_ecad_deck";
		sprintf(header, _("ECAD deck for technology %s"), el_curtech->techname);

		var = getval((INTBIG)el_curtech, VTECHNOLOGY, -1, qual);
		if (var == NOVARIABLE)
		{
			dummyfile[0] = "";
			var = setval((INTBIG)el_curtech, VTECHNOLOGY, qual, (INTBIG)dummyfile,
				VSTRING|VISARRAY|(1<<VLENGTHSH));
			if (var == NOVARIABLE)
			{
				ttyputerr(_("Cannot create DRC_ecad_deck on the technology"));
				return;
			}
		} else
			var->type &= ~VDONTSAVE;

		/* get a new window, put an editor in it */
		w = us_wantnewwindow(0);
		if (w == NOWINDOWPART) return;
		if (us_makeeditor(w, header, &dummy, &dummy) == NOWINDOWPART) return;
		ed = w->editor;
		ed->editobjqual = qual;
		ed->editobjaddr = (char *)el_curtech;
		ed->editobjtype = VTECHNOLOGY;
		ed->editobjvar = var;
		us_suspendgraphics(w);

		l = getlength(var);
		for(i=0; i<l; i++)
		{
			(void)initinfstr();
			(void)addstringtoinfstr(describevariable(var, i, -1));
			us_addline(w, i, returninfstr());
		}
		us_resumegraphics(w);
		w->changehandler = us_varchanges;
	}
}

/*
 * Routine to invoke the Design Rule Editing dialog on the current technology.
 */
void dr_rulesdlog(void)
{
	REGISTER INTBIG i, changed;
	REGISTER VARIABLE *varcon, *varuncon, *varconW, *varunconW, *varconM, *varunconM,
		*varedge, *varconR, *varunconR, *varconWR, *varunconWR, *varconMR, *varunconMR,
		*varedgeR, *varmindist, *varmindistR, *var;
	REGISTER DRCRULES *rules;

	/* create a RULES structure */
	rules = dr_allocaterules(el_curtech->layercount, el_curtech->techname);
	if (rules == NODRCRULES) return;

	/* fill in the technology name and layer names */
	for(i=0; i<rules->numlayers; i++)
		(void)allocstring(&rules->layernames[i], layername(el_curtech, i), el_tempcluster);

	/* get the distances */
	varmindist = getvalkey((INTBIG)el_curtech, VTECHNOLOGY, VFRACT|VISARRAY,
		dr_min_widthkey);
	varmindistR = getvalkey((INTBIG)el_curtech, VTECHNOLOGY, VSTRING|VISARRAY,
		dr_min_width_rulekey);
	varcon = getvalkey((INTBIG)el_curtech, VTECHNOLOGY, VFRACT|VISARRAY,
		dr_connected_distanceskey);
	varuncon = getvalkey((INTBIG)el_curtech, VTECHNOLOGY, VFRACT|VISARRAY,
		dr_unconnected_distanceskey);
	varconW = getvalkey((INTBIG)el_curtech, VTECHNOLOGY, VFRACT|VISARRAY,
		dr_connected_distancesWkey);
	varunconW = getvalkey((INTBIG)el_curtech, VTECHNOLOGY, VFRACT|VISARRAY,
		dr_unconnected_distancesWkey);
	varconM = getvalkey((INTBIG)el_curtech, VTECHNOLOGY, VFRACT|VISARRAY,
		dr_connected_distancesMkey);
	varunconM = getvalkey((INTBIG)el_curtech, VTECHNOLOGY, VFRACT|VISARRAY,
		dr_unconnected_distancesMkey);
	varedge = getvalkey((INTBIG)el_curtech, VTECHNOLOGY, VFRACT|VISARRAY,
		dr_edge_distanceskey);
	varconR = getvalkey((INTBIG)el_curtech, VTECHNOLOGY, VSTRING|VISARRAY,
		dr_connected_distances_rulekey);
	varunconR = getvalkey((INTBIG)el_curtech, VTECHNOLOGY, VSTRING|VISARRAY,
		dr_unconnected_distances_rulekey);
	varconWR = getvalkey((INTBIG)el_curtech, VTECHNOLOGY, VSTRING|VISARRAY,
		dr_connected_distancesW_rulekey);
	varunconWR = getvalkey((INTBIG)el_curtech, VTECHNOLOGY, VSTRING|VISARRAY,
		dr_unconnected_distancesW_rulekey);
	varconMR = getvalkey((INTBIG)el_curtech, VTECHNOLOGY, VSTRING|VISARRAY,
		dr_connected_distancesM_rulekey);
	varunconMR = getvalkey((INTBIG)el_curtech, VTECHNOLOGY, VSTRING|VISARRAY,
		dr_unconnected_distancesM_rulekey);
	varedgeR = getvalkey((INTBIG)el_curtech, VTECHNOLOGY, VSTRING|VISARRAY,
		dr_edge_distances_rulekey);

	/* put the distances into the rules structure */
	for(i=0; i<rules->numlayers; i++)
	{
		if (varmindist != NOVARIABLE)
			rules->minwidth[i] = ((INTBIG *)varmindist->addr)[i];
		if (varmindistR != NOVARIABLE && ((char **)varmindistR->addr)[i][0] != 0)
			(void)reallocstring(&rules->minwidthR[i], ((char **)varmindistR->addr)[i], el_tempcluster);
	}
	for(i=0; i<rules->utsize; i++)
	{
		if (varcon != NOVARIABLE)
			rules->conlist[i] = ((INTBIG *)varcon->addr)[i];
		if (varuncon != NOVARIABLE)
			rules->unconlist[i] = ((INTBIG *)varuncon->addr)[i];
		if (varconW != NOVARIABLE)
			rules->conlistW[i] = ((INTBIG *)varconW->addr)[i];
		if (varunconW != NOVARIABLE)
			rules->unconlistW[i] = ((INTBIG *)varunconW->addr)[i];
		if (varconM != NOVARIABLE)
			rules->conlistM[i] = ((INTBIG *)varconM->addr)[i];
		if (varunconM != NOVARIABLE)
			rules->unconlistM[i] = ((INTBIG *)varunconM->addr)[i];
		if (varedge != NOVARIABLE)
			rules->edgelist[i] = ((INTBIG *)varedge->addr)[i];

		if (varconR != NOVARIABLE && ((char **)varconR->addr)[i][0] != 0)
			(void)reallocstring(&rules->conlistR[i], ((char **)varconR->addr)[i], el_tempcluster);
		if (varunconR != NOVARIABLE && ((char **)varunconR->addr)[i][0] != 0)
			(void)reallocstring(&rules->unconlistR[i], ((char **)varunconR->addr)[i], el_tempcluster);
		if (varconWR != NOVARIABLE && ((char **)varconWR->addr)[i][0] != 0)
			(void)reallocstring(&rules->conlistWR[i], ((char **)varconWR->addr)[i], el_tempcluster);
		if (varunconWR != NOVARIABLE && ((char **)varunconWR->addr)[i][0] != 0)
			(void)reallocstring(&rules->unconlistWR[i], ((char **)varunconWR->addr)[i], el_tempcluster);
		if (varconMR != NOVARIABLE && ((char **)varconMR->addr)[i][0] != 0)
			(void)reallocstring(&rules->conlistMR[i], ((char **)varconMR->addr)[i], el_tempcluster);
		if (varunconMR != NOVARIABLE && ((char **)varunconMR->addr)[i][0] != 0)
			(void)reallocstring(&rules->unconlistMR[i], ((char **)varunconMR->addr)[i], el_tempcluster);
		if (varedgeR != NOVARIABLE && ((char **)varedgeR->addr)[i][0] != 0)
			(void)reallocstring(&rules->edgelistR[i], ((char **)varedgeR->addr)[i], el_tempcluster);
	}
	var = getvalkey((INTBIG)el_curtech, VTECHNOLOGY, VFRACT, dr_wide_limitkey);
	if (var != NOVARIABLE) rules->widelimit = var->addr;

	/* run the DRC editing dialog */
	changed = dr_rulesdialog(el_curtech, rules);

	/* if values changed, update variables */
	if (changed != 0)
	{
		if ((changed&RULECHANGEMINWID) != 0)
			setvalkey((INTBIG)el_curtech, VTECHNOLOGY, dr_min_widthkey,
				(INTBIG)rules->minwidth, VFRACT|VISARRAY|(rules->numlayers<<VLENGTHSH));
		if ((changed&RULECHANGEMINWIDR) != 0)
			setvalkey((INTBIG)el_curtech, VTECHNOLOGY, dr_min_width_rulekey,
				(INTBIG)rules->minwidthR, VSTRING|VISARRAY|(rules->numlayers<<VLENGTHSH));
		if ((changed&RULECHANGECONSPA) != 0)
			setvalkey((INTBIG)el_curtech, VTECHNOLOGY, dr_connected_distanceskey,
				(INTBIG)rules->conlist, VFRACT|VISARRAY|(rules->utsize<<VLENGTHSH));
		if ((changed&RULECHANGEUCONSPA) != 0)
			setvalkey((INTBIG)el_curtech, VTECHNOLOGY, dr_unconnected_distanceskey,
				(INTBIG)rules->unconlist, VFRACT|VISARRAY|(rules->utsize<<VLENGTHSH));
		if ((changed&RULECHANGECONSPAW) != 0)
			setvalkey((INTBIG)el_curtech, VTECHNOLOGY, dr_connected_distancesWkey,
				(INTBIG)rules->conlistW, VFRACT|VISARRAY|(rules->utsize<<VLENGTHSH));
		if ((changed&RULECHANGEUCONSPAW) != 0)
			setvalkey((INTBIG)el_curtech, VTECHNOLOGY, dr_unconnected_distancesWkey,
				(INTBIG)rules->unconlistW, VFRACT|VISARRAY|(rules->utsize<<VLENGTHSH));
		if ((changed&RULECHANGECONSPAM) != 0)
			setvalkey((INTBIG)el_curtech, VTECHNOLOGY, dr_connected_distancesMkey,
				(INTBIG)rules->conlistM, VFRACT|VISARRAY|(rules->utsize<<VLENGTHSH));
		if ((changed&RULECHANGEUCONSPAM) != 0)
			setvalkey((INTBIG)el_curtech, VTECHNOLOGY, dr_unconnected_distancesMkey,
				(INTBIG)rules->unconlistM, VFRACT|VISARRAY|(rules->utsize<<VLENGTHSH));
		if ((changed&RULECHANGEEDGESPA) != 0)
			setvalkey((INTBIG)el_curtech, VTECHNOLOGY, dr_edge_distanceskey,
				(INTBIG)rules->edgelist, VFRACT|VISARRAY|(rules->utsize<<VLENGTHSH));
		if ((changed&RULECHANGECONSPAR) != 0)
			setvalkey((INTBIG)el_curtech, VTECHNOLOGY, dr_connected_distances_rulekey,
				(INTBIG)rules->conlistR, VSTRING|VISARRAY|(rules->utsize<<VLENGTHSH));
		if ((changed&RULECHANGEUCONSPAR) != 0)
			setvalkey((INTBIG)el_curtech, VTECHNOLOGY, dr_unconnected_distances_rulekey,
				(INTBIG)rules->unconlistR, VSTRING|VISARRAY|(rules->utsize<<VLENGTHSH));
		if ((changed&RULECHANGECONSPAWR) != 0)
			setvalkey((INTBIG)el_curtech, VTECHNOLOGY, dr_connected_distancesW_rulekey,
				(INTBIG)rules->conlistWR, VSTRING|VISARRAY|(rules->utsize<<VLENGTHSH));
		if ((changed&RULECHANGEUCONSPAWR) != 0)
			setvalkey((INTBIG)el_curtech, VTECHNOLOGY, dr_unconnected_distancesW_rulekey,
				(INTBIG)rules->unconlistWR, VSTRING|VISARRAY|(rules->utsize<<VLENGTHSH));
		if ((changed&RULECHANGECONSPAMR) != 0)
			setvalkey((INTBIG)el_curtech, VTECHNOLOGY, dr_connected_distancesM_rulekey,
				(INTBIG)rules->conlistMR, VSTRING|VISARRAY|(rules->utsize<<VLENGTHSH));
		if ((changed&RULECHANGEUCONSPAMR) != 0)
			setvalkey((INTBIG)el_curtech, VTECHNOLOGY, dr_unconnected_distancesM_rulekey,
				(INTBIG)rules->unconlistMR, VSTRING|VISARRAY|(rules->utsize<<VLENGTHSH));
		if ((changed&RULECHANGEEDGESPAR) != 0)
			setvalkey((INTBIG)el_curtech, VTECHNOLOGY, dr_edge_distances_rulekey,
				(INTBIG)rules->edgelistR, VSTRING|VISARRAY|(rules->utsize<<VLENGTHSH));
		if ((changed&RULECHANGEWIDLIMIT) != 0)
			setvalkey((INTBIG)el_curtech, VTECHNOLOGY, dr_wide_limitkey,
				rules->widelimit, VFRACT);

		/* reset valid DRC dates */
		drcb_reset_dates();
	}

	/* free the rules structure */
	dr_freerules(rules);
}

/*
 * Routine to allocate the space for a DRC rules object that describes "layercount" layers.
 * Returns NODRCRULES on error.
 */
DRCRULES *dr_allocaterules(INTBIG layercount, char *techname)
{
	DRCRULES *rules;
	REGISTER INTBIG i;

	rules = (DRCRULES *)emalloc(sizeof (DRCRULES), el_tempcluster);
	if (rules == 0) return(NODRCRULES);

	(void)allocstring(&rules->techname, techname, el_tempcluster);

	/* cache the spacing rules for this technology */
	rules->numlayers = layercount;
	rules->utsize = (layercount * layercount - layercount) / 2 + layercount;
	rules->layernames = (char **)emalloc(rules->numlayers * (sizeof (char *)), el_tempcluster);
	if (rules->layernames == 0) return(NODRCRULES);
	rules->minwidth = (INTBIG *)emalloc(rules->numlayers * SIZEOFINTBIG, el_tempcluster);
	if (rules->minwidth == 0) return(NODRCRULES);
	rules->minwidthR = (char **)emalloc(rules->numlayers * (sizeof (char *)), el_tempcluster);
	if (rules->minwidthR == 0) return(NODRCRULES);
	rules->conlist = (INTBIG *)emalloc(rules->utsize * SIZEOFINTBIG, el_tempcluster);
	if (rules->conlist == 0) return(NODRCRULES);
	rules->unconlist = (INTBIG *)emalloc(rules->utsize * SIZEOFINTBIG, el_tempcluster);
	if (rules->unconlist == 0) return(NODRCRULES);
	rules->conlistW = (INTBIG *)emalloc(rules->utsize * SIZEOFINTBIG, el_tempcluster);
	if (rules->conlistW == 0) return(NODRCRULES);
	rules->unconlistW = (INTBIG *)emalloc(rules->utsize * SIZEOFINTBIG, el_tempcluster);
	if (rules->unconlistW == 0) return(NODRCRULES);
	rules->conlistM = (INTBIG *)emalloc(rules->utsize * SIZEOFINTBIG, el_tempcluster);
	if (rules->conlistM == 0) return(NODRCRULES);
	rules->unconlistM = (INTBIG *)emalloc(rules->utsize * SIZEOFINTBIG, el_tempcluster);
	if (rules->unconlistM == 0) return(NODRCRULES);
	rules->edgelist = (INTBIG *)emalloc(rules->utsize * SIZEOFINTBIG, el_tempcluster);
	if (rules->edgelist == 0) return(NODRCRULES);

	rules->conlistR = (char **)emalloc(rules->utsize * (sizeof (char *)), el_tempcluster);
	if (rules->conlistR == 0) return(NODRCRULES);
	rules->unconlistR = (char **)emalloc(rules->utsize * (sizeof (char *)), el_tempcluster);
	if (rules->unconlistR == 0) return(NODRCRULES);
	rules->conlistWR = (char **)emalloc(rules->utsize * (sizeof (char *)), el_tempcluster);
	if (rules->conlistWR == 0) return(NODRCRULES);
	rules->unconlistWR = (char **)emalloc(rules->utsize * (sizeof (char *)), el_tempcluster);
	if (rules->unconlistWR == 0) return(NODRCRULES);
	rules->conlistMR = (char **)emalloc(rules->utsize * (sizeof (char *)), el_tempcluster);
	if (rules->conlistMR == 0) return(NODRCRULES);
	rules->unconlistMR = (char **)emalloc(rules->utsize * (sizeof (char *)), el_tempcluster);
	if (rules->unconlistMR == 0) return(NODRCRULES);
	rules->edgelistR = (char **)emalloc(rules->utsize * (sizeof (char *)), el_tempcluster);
	if (rules->edgelistR == 0) return(NODRCRULES);

	for(i=0; i<rules->numlayers; i++)
	{
		rules->minwidth[i] = XX;
		(void)allocstring(&rules->minwidthR[i], "", el_tempcluster);
		rules->layernames[i] = 0;
	}
	for(i=0; i<rules->utsize; i++)
	{
		rules->conlist[i] = XX;
		rules->unconlist[i] = XX;
		rules->conlistW[i] = XX;
		rules->unconlistW[i] = XX;
		rules->conlistM[i] = XX;
		rules->unconlistM[i] = XX;
		rules->edgelist[i] = XX;
		(void)allocstring(&rules->conlistR[i], "", el_tempcluster);
		(void)allocstring(&rules->unconlistR[i], "", el_tempcluster);
		(void)allocstring(&rules->conlistWR[i], "", el_tempcluster);
		(void)allocstring(&rules->unconlistWR[i], "", el_tempcluster);
		(void)allocstring(&rules->conlistMR[i], "", el_tempcluster);
		(void)allocstring(&rules->unconlistMR[i], "", el_tempcluster);
		(void)allocstring(&rules->edgelistR[i], "", el_tempcluster);
	}
	rules->widelimit = K10;
	return(rules);
}

/*
 * Routine to free the DRCRULES structure in "rules".
 */
void dr_freerules(DRCRULES *rules)
{
	REGISTER INTBIG i;

	efree((char *)rules->techname);
	efree((char *)rules->conlist);
	efree((char *)rules->unconlist);
	efree((char *)rules->conlistW);
	efree((char *)rules->unconlistW);
	efree((char *)rules->conlistM);
	efree((char *)rules->unconlistM);
	efree((char *)rules->edgelist);
	efree((char *)rules->minwidth);
	for(i=0; i<rules->utsize; i++)
	{
		efree((char *)rules->conlistR[i]);
		efree((char *)rules->unconlistR[i]);
		efree((char *)rules->conlistWR[i]);
		efree((char *)rules->unconlistWR[i]);
		efree((char *)rules->conlistMR[i]);
		efree((char *)rules->unconlistMR[i]);
		efree((char *)rules->edgelistR[i]);
	}
	for(i=0; i<rules->numlayers; i++)
	{
		efree((char *)rules->minwidthR[i]);
		if (rules->layernames[i] != 0)
			efree((char *)rules->layernames[i]);
	}
	efree((char *)rules->conlistR);
	efree((char *)rules->unconlistR);
	efree((char *)rules->conlistWR);
	efree((char *)rules->unconlistWR);
	efree((char *)rules->conlistMR);
	efree((char *)rules->unconlistMR);
	efree((char *)rules->edgelistR);
	efree((char *)rules->minwidthR);
	efree((char *)rules->layernames);
}

/****************************** DESIGN-RULE EDITING DIALOG ******************************/

/* DRC: Rules */
static DIALOGITEM dr_rulesdialogitems[] =
{
 /*  1 */ {0, {8,516,32,580}, BUTTON, N_("OK")},
 /*  2 */ {0, {40,516,64,580}, BUTTON, N_("Cancel")},
 /*  3 */ {0, {32,100,188,278}, SCROLL, ""},
 /*  4 */ {0, {32,8,48,95}, MESSAGE, N_("For Layer:")},
 /*  5 */ {0, {212,8,435,300}, SCROLL, ""},
 /*  6 */ {0, {8,100,24,294}, MESSAGE|INACTIVE, ""},
 /*  7 */ {0, {8,8,24,95}, MESSAGE, N_("Technology:")},
 /*  8 */ {0, {192,8,208,88}, MESSAGE, N_("To Layer:")},
 /*  9 */ {0, {88,452,104,523}, MESSAGE, N_("Distance")},
 /* 10 */ {0, {88,528,104,583}, MESSAGE, N_("Rule")},
 /* 11 */ {0, {112,308,128,424}, MESSAGE, N_("Minimum Width:")},
 /* 12 */ {0, {112,454,128,502}, EDITTEXT, ""},
 /* 13 */ {0, {112,514,128,596}, EDITTEXT, ""},
 /* 14 */ {0, {180,308,196,387}, MESSAGE, N_("Normal:")},
 /* 15 */ {0, {204,324,220,450}, MESSAGE, N_("When connected:")},
 /* 16 */ {0, {204,454,220,502}, EDITTEXT, ""},
 /* 17 */ {0, {204,514,220,595}, EDITTEXT, ""},
 /* 18 */ {0, {232,324,248,450}, MESSAGE, N_("Not connected:")},
 /* 19 */ {0, {232,454,248,502}, EDITTEXT, ""},
 /* 20 */ {0, {232,514,248,595}, EDITTEXT, ""},
 /* 21 */ {0, {288,308,304,520}, MESSAGE, N_("Wide (when bigger than this):")},
 /* 22 */ {0, {312,324,328,450}, MESSAGE, N_("When connected:")},
 /* 23 */ {0, {312,454,328,502}, EDITTEXT, ""},
 /* 24 */ {0, {312,514,328,595}, EDITTEXT, ""},
 /* 25 */ {0, {340,324,356,450}, MESSAGE, N_("Not connected:")},
 /* 26 */ {0, {340,454,356,502}, EDITTEXT, ""},
 /* 27 */ {0, {340,514,356,595}, EDITTEXT, ""},
 /* 28 */ {0, {368,308,384,448}, MESSAGE, N_("Multiple cuts:")},
 /* 29 */ {0, {392,324,408,450}, MESSAGE, N_("When connected:")},
 /* 30 */ {0, {392,454,408,502}, EDITTEXT, ""},
 /* 31 */ {0, {392,514,408,595}, EDITTEXT, ""},
 /* 32 */ {0, {420,324,436,450}, MESSAGE, N_("Not connected:")},
 /* 33 */ {0, {420,454,436,502}, EDITTEXT, ""},
 /* 34 */ {0, {420,514,436,595}, EDITTEXT, ""},
 /* 35 */ {0, {180,452,196,523}, MESSAGE, N_("Distance")},
 /* 36 */ {0, {180,528,196,583}, MESSAGE, N_("Rule")},
 /* 37 */ {0, {24,328,48,488}, BUTTON, N_("Factory Reset of Rules")},
 /* 38 */ {0, {288,526,304,574}, EDITTEXT, ""},
 /* 39 */ {0, {260,324,276,450}, MESSAGE, N_("Edge:")},
 /* 40 */ {0, {260,454,276,502}, EDITTEXT, ""},
 /* 41 */ {0, {260,514,276,595}, EDITTEXT, ""},
 /* 42 */ {0, {192,112,208,296}, CHECK, N_("Show only lines with rules")}
};
static DIALOG dr_rulesdialogdesc = {{50,75,495,680}, N_("Design Rules"), 0, 42, dr_rulesdialogitems};

static void       dr_loaddrcdialog(DRCRULES *rules, INTBIG *validlayers);
static void       dr_loaddrcdistance(DRCRULES *rules);
static char      *dr_loadoptlistline(DRCRULES *rules, INTBIG dindex, INTBIG lindex, INTBIG onlyvalid);
static INTBIG     dr_rulesdialoggetlayer(DRCRULES *rules, INTBIG item);

/* special items for the "DRC Rules" dialog: */
#define DDRR_FROMLAYER     3		/* From layer (scroll) */
#define DDRR_TOLAYER       5		/* To layer (scroll) */
#define DDRR_TECHNAME      6		/* Technology name (stat text) */
#define DDRR_MINWIDTH     12		/* Min width (edit text) */
#define DDRR_MINWIDTHR    13		/* Min width rule (edit text) */
#define DDRR_CONDIST      16		/* Connected distance (edit text) */
#define DDRR_CONDISTR     17		/* Connected distance rule (edit text) */
#define DDRR_UCONDIST     19		/* Unconnected distance (edit text) */
#define DDRR_UCONDISTR    20		/* Unconnected distance rule (edit text) */
#define DDRR_CONDISTW     23		/* Connected wide distance (edit text) */
#define DDRR_CONDISTWR    24		/* Connected wide distance rule (edit text) */
#define DDRR_UCONDISTW    26		/* Unconnected wide distance (edit text) */
#define DDRR_UCONDISTWR   27		/* Unconnected wide distance rule (edit text) */
#define DDRR_CONDISTM     30		/* Connected multi distance (edit text) */
#define DDRR_CONDISTMR    31		/* Connected multi distance rule (edit text) */
#define DDRR_UCONDISTM    33		/* Unconnected multi distance (edit text) */
#define DDRR_UCONDISTMR   34		/* Unconnected multi distance rule (edit text) */
#define DDRR_FACTORYRST   37		/* Factor Reset (button) */
#define DDRR_WIDELIMIT    38		/* Wide limit (edit text) */
#define DDRR_EDGEDIST     40		/* Edge distance (edit text) */
#define DDRR_EDGEDISTR    41		/* Edge distance rule (edit text) */
#define DDRR_VALIDRULES   42		/* Only show lines with rules (check) */

/*
 * Routine to implement a dialog for design rule editing.  This is called from the design-rule checker
 * to edit current rules, and it is called by the technology editor to edit rules from technologies
 * under construction.  Takes a technology name ("techname"), number of layers ("numlayers"), and
 * the layer names ("layernames").  Takes all of the design-rule tables:
 *   "minwidth" and "minwidthR", the minimum width and rule for each layer
 *   "conlist" and "conlistR", the distances and rules between connected layers
 *   "unconlist" and "unconlistR", the distances and rules between unconnected layers
 *   "conlistW" and "conlistWR", the distances and rules between connected layers that are wide
 *   "unconlistW" and "unconlistWR", the distances and rules between unconnected layers that are wide
 *   "conlistM" and "conlistMR", the distances and rules between connected layers on multi-cut contacts
 *   "unconlistM" and "unconlistMR", the distances and rules between unconnected layers on multi-cut contacts
 * Returns bits that indicate which values changed.
 */
INTBIG dr_rulesdialog(TECHNOLOGY *tech, DRCRULES *rules)
{
	REGISTER INTBIG itemHit, i, j, dist, layer1, layer2, temp, dindex, *validlayers, changed;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	REGISTER NODEPROTO *np;
	REGISTER ARCPROTO *ap;
	REGISTER char *pt, *varname;
	static POLYGON *poly = NOPOLYGON;

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

	/* determine which layers are valid */
	validlayers = (INTBIG *)emalloc(rules->numlayers * SIZEOFINTBIG, dr_tool->cluster);
	if (validlayers == 0) return(0);
	if (tech == NOTECHNOLOGY)
	{
		/* designing rules in technology edit: all layers are valid */
		for(i=0; i<rules->numlayers; i++) validlayers[i] = 1;
	} else
	{
		/* editing rules for technology: find used layers only */
		for(i=0; i<rules->numlayers; i++) validlayers[i] = 0;
		for(np = tech->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
		{
			if ((np->userbits&NNOTUSED) != 0) continue;
			ni = dummynode();
			ni->proto = np;
			ni->lowx = np->lowx;   ni->highx = np->highx;
			ni->lowy = np->lowy;   ni->highy = np->highy;
			j = nodepolys(ni, 0, NOWINDOWPART);
			for(i=0; i<j; i++)
			{
				shapenodepoly(ni, i, poly);
				if (poly->tech != tech) continue;
				validlayers[poly->layer] = 1;
			}
		}
		for(ap = tech->firstarcproto; ap != NOARCPROTO; ap = ap->nextarcproto)
		{
			if ((ap->userbits&ANOTUSED) != 0) continue;
			ai = dummyarc();
			ai->proto = ap;
			ai->width = ap->nominalwidth;
			j = arcpolys(ai, NOWINDOWPART);
			for(i=0; i<j; i++)
			{
				shapearcpoly(ai, i, poly);
				if (poly->tech != tech) continue;
				validlayers[poly->layer] = 1;
			}
		}
	}

	if (DiaInitDialog(&dr_rulesdialogdesc)) return(0);
	DiaInitTextDialog(DDRR_FROMLAYER, DiaNullDlogList, DiaNullDlogItem, DiaNullDlogDone, 0,
		SCSELMOUSE|SCREPORT);
	DiaInitTextDialog(DDRR_TOLAYER, DiaNullDlogList, DiaNullDlogItem, DiaNullDlogDone, 0,
		SCSELMOUSE|SCREPORT);
	if (tech == NOTECHNOLOGY) DiaDimItem(DDRR_FACTORYRST);

	/* make the layer list */
	for(i=0; i<rules->numlayers; i++)
	{
		if (validlayers[i] == 0) continue;
		DiaStuffLine(DDRR_FROMLAYER, rules->layernames[i]);
	}
	DiaSelectLine(DDRR_FROMLAYER, 0);

	DiaSetText(DDRR_TECHNAME, rules->techname);
	DiaSetText(DDRR_WIDELIMIT, frtoa(rules->widelimit));

	dr_loaddrcdialog(rules, validlayers);
	dr_loaddrcdistance(rules);
	changed = 0;

	/* loop until done */
	for(;;)
	{
		itemHit = DiaNextHit();
		if (itemHit == OK || itemHit == CANCEL || itemHit == DDRR_FACTORYRST) break;
		if (itemHit == DDRR_CONDIST || itemHit == DDRR_UCONDIST ||
			itemHit == DDRR_CONDISTW || itemHit == DDRR_UCONDISTW ||
			itemHit == DDRR_CONDISTM || itemHit == DDRR_UCONDISTM ||
			itemHit == DDRR_EDGEDIST)
		{
			/* typed into distance field */
			i = dr_rulesdialoggetlayer(rules, DDRR_TOLAYER);
			if (i < 0) continue;
			pt = DiaGetText(itemHit);
			while (*pt == ' ' || *pt == '\t') pt++;
			if (*pt == 0) dist = -WHOLE; else
				dist = atofr(pt);
			layer1 = dr_rulesdialoggetlayer(rules, DDRR_FROMLAYER);
			if (layer1 < 0) continue;
			layer2 = i;
			if (layer1 > layer2) { temp = layer1; layer1 = layer2;  layer2 = temp; }
			dindex = (layer1+1) * (layer1/2) + (layer1&1) * ((layer1+1)/2);
			dindex = layer2 + rules->numlayers * layer1 - dindex;
			if (itemHit == DDRR_CONDIST)
			{
				if (rules->conlist[dindex] != dist) changed |= RULECHANGECONSPA;
				rules->conlist[dindex] = dist;
			} else if (itemHit == DDRR_UCONDIST)
			{
				if (rules->unconlist[dindex] != dist) changed |= RULECHANGEUCONSPA;
				rules->unconlist[dindex] = dist;
			} else if (itemHit == DDRR_CONDISTW)
			{
				if (rules->conlistW[dindex] != dist) changed |= RULECHANGECONSPAW;
				rules->conlistW[dindex] = dist;
			} else if (itemHit == DDRR_UCONDISTW)
			{
				if (rules->unconlistW[dindex] != dist) changed |= RULECHANGEUCONSPAW;
				rules->unconlistW[dindex] = dist;
			} else if (itemHit == DDRR_CONDISTM)
			{
				if (rules->conlistM[dindex] != dist) changed |= RULECHANGECONSPAM;
				rules->conlistM[dindex] = dist;
			} else if (itemHit == DDRR_UCONDISTM)
			{
				if (rules->unconlistM[dindex] != dist) changed |= RULECHANGEUCONSPAM;
				rules->unconlistM[dindex] = dist;
			} else if (itemHit == DDRR_EDGEDIST)
			{
				if (rules->edgelist[dindex] != dist) changed |= RULECHANGEEDGESPA;
				rules->edgelist[dindex] = dist;
			}

			pt = dr_loadoptlistline(rules, dindex, i, 0);
			DiaSetScrollLine(DDRR_TOLAYER, DiaGetCurLine(DDRR_TOLAYER), pt);
			continue;
		}
		if (itemHit == DDRR_CONDISTR || itemHit == DDRR_UCONDISTR ||
			itemHit == DDRR_CONDISTWR || itemHit == DDRR_UCONDISTWR ||
			itemHit == DDRR_CONDISTMR || itemHit == DDRR_UCONDISTMR ||
			itemHit == DDRR_EDGEDISTR)
		{
			/* typed into rule field */
			i = dr_rulesdialoggetlayer(rules, DDRR_TOLAYER);
			if (i < 0) continue;
			pt = DiaGetText(itemHit);
			layer1 = dr_rulesdialoggetlayer(rules, DDRR_FROMLAYER);
			if (layer1 < 0) continue;
			layer2 = i;
			if (layer1 > layer2) { temp = layer1; layer1 = layer2;  layer2 = temp; }
			dindex = (layer1+1) * (layer1/2) + (layer1&1) * ((layer1+1)/2);
			dindex = layer2 + rules->numlayers * layer1 - dindex;
			if (itemHit == DDRR_CONDISTR)
			{
				if (strcmp(rules->conlistR[dindex], pt) != 0) changed |= RULECHANGECONSPAR;
				(void)reallocstring(&rules->conlistR[dindex], pt, el_tempcluster);
			} else if (itemHit == DDRR_UCONDISTR)
			{
				if (strcmp(rules->unconlistR[dindex], pt) != 0) changed |= RULECHANGEUCONSPAR;
				(void)reallocstring(&rules->unconlistR[dindex], pt, el_tempcluster);
			} else if (itemHit == DDRR_CONDISTWR)
			{
				if (strcmp(rules->conlistWR[dindex], pt) != 0) changed |= RULECHANGECONSPAWR;
				(void)reallocstring(&rules->conlistWR[dindex], pt, el_tempcluster);
			} else if (itemHit == DDRR_UCONDISTWR)
			{
				if (strcmp(rules->unconlistWR[dindex], pt) != 0) changed |= RULECHANGEUCONSPAWR;
				(void)reallocstring(&rules->unconlistWR[dindex], pt, el_tempcluster);
			} else if (itemHit == DDRR_CONDISTMR)
			{
				if (strcmp(rules->conlistMR[dindex], pt) != 0) changed |= RULECHANGECONSPAMR;
				(void)reallocstring(&rules->conlistMR[dindex], pt, el_tempcluster);
			} else if (itemHit == DDRR_UCONDISTMR)
			{
				if (strcmp(rules->unconlistMR[dindex], pt) != 0) changed |= RULECHANGEUCONSPAMR;
				(void)reallocstring(&rules->unconlistMR[dindex], pt, el_tempcluster);
			} else if (itemHit == DDRR_EDGEDISTR)
			{
				if (strcmp(rules->edgelistR[dindex], pt) != 0) changed |= RULECHANGEEDGESPAR;
				(void)reallocstring(&rules->edgelistR[dindex], pt, el_tempcluster);
			}
			continue;
		}
		if (itemHit == DDRR_WIDELIMIT)
		{
			pt = DiaGetText(DDRR_WIDELIMIT);
			dist = atofr(pt);
			if (rules->widelimit != dist) changed |= RULECHANGEWIDLIMIT;
			rules->widelimit = dist;
			continue;
		}
		if (itemHit == DDRR_MINWIDTH)
		{
			/* typed into min-width field */
			pt = DiaGetText(DDRR_MINWIDTH);
			while (*pt == ' ' || *pt == '\t') pt++;
			if (*pt == 0) dist = -WHOLE; else
				dist = atofr(pt);
			layer1 = dr_rulesdialoggetlayer(rules, DDRR_FROMLAYER);
			if (rules->minwidth[layer1] != dist) changed |= RULECHANGEMINWID;
			rules->minwidth[layer1] = dist;
			continue;
		}
		if (itemHit == DDRR_MINWIDTHR)
		{
			/* typed into min-width rule field */
			pt = DiaGetText(DDRR_MINWIDTHR);
			while (*pt == ' ' || *pt == '\t') pt++;
			if (strcmp(rules->minwidthR[dindex], pt) != 0) changed |= RULECHANGEMINWIDR;
			(void)reallocstring(&rules->minwidthR[dindex], pt, el_tempcluster);
			continue;
		}
		if (itemHit == DDRR_FROMLAYER)
		{
			/* changed layer popup */
			dr_loaddrcdialog(rules, validlayers);
			dr_loaddrcdistance(rules);
			continue;
		}
		if (itemHit == DDRR_TOLAYER)
		{
			/* clicked on new entry in scroll list of "to" layers */
			dr_loaddrcdistance(rules);
			continue;
		}
		if (itemHit == DDRR_VALIDRULES)
		{
			DiaSetControl(itemHit, 1-DiaGetControl(itemHit));
			dr_loaddrcdialog(rules, validlayers);
			dr_loaddrcdistance(rules);
			continue;
		}
	}

	DiaDoneDialog();

	if (itemHit != OK) changed = 0;
	if (itemHit == DDRR_FACTORYRST)
	{
		/* "factory reset" was clicked, start by deleting all DRC variables */
		for(j=0; dr_variablenames[j] != 0; j++)
		{
			if (getval((INTBIG)tech, VTECHNOLOGY, -1, dr_variablenames[j]) == NOVARIABLE) continue;
			(void)delval((INTBIG)tech, VTECHNOLOGY, dr_variablenames[j]);
		}

		/* set all technology-defined DRC variables */
		for(i=0; tech->variables[i].name != 0; i++)
		{
			varname = tech->variables[i].name;
			for(j=0; dr_variablenames[j] != 0; j++)
				if (namesame(varname, dr_variablenames[j]) == 0) break;
			if (dr_variablenames[j] == 0) continue;

			/* reset the variable */
			if (setval((INTBIG)tech, VTECHNOLOGY, tech->variables[i].name,
				(INTBIG)tech->variables[i].value, tech->variables[i].type & ~VDONTSAVE) == NOVARIABLE) break;
		}

		/* call the technology-specific routine to do any extra work */
		(void)asktech(tech, "factory-reset");
	}
	efree((char *)validlayers);
	return(changed);
}

void dr_loaddrcdialog(DRCRULES *rules, INTBIG *validlayers)
{
	REGISTER INTBIG i, j, layer1, layer2, temp, dindex, onlyvalid, count;
	REGISTER char *line;

	onlyvalid = DiaGetControl(DDRR_VALIDRULES);
	j = dr_rulesdialoggetlayer(rules, DDRR_FROMLAYER);
	if (rules->minwidth[j] < 0) DiaSetText(DDRR_MINWIDTH, ""); else
		DiaSetText(DDRR_MINWIDTH, frtoa(rules->minwidth[j]));
	DiaSetText(DDRR_MINWIDTHR, rules->minwidthR[j]);
	DiaLoadTextDialog(DDRR_TOLAYER, DiaNullDlogList, DiaNullDlogItem, DiaNullDlogDone, 0);
	count = 0;
	for(i=0; i<rules->numlayers; i++)
	{
		if (validlayers[i] == 0) continue;
		layer1 = j;
		layer2 = i;
		if (layer1 > layer2) { temp = layer1; layer1 = layer2;  layer2 = temp; }
		dindex = (layer1+1) * (layer1/2) + (layer1&1) * ((layer1+1)/2);
		dindex = layer2 + rules->numlayers * layer1 - dindex;

		line = dr_loadoptlistline(rules, dindex, i, onlyvalid);
		if (*line == 0) continue;
		DiaStuffLine(DDRR_TOLAYER, line);
		count++;
	}
	if (count > 0) DiaSelectLine(DDRR_TOLAYER, 0);
}

char *dr_loadoptlistline(DRCRULES *rules, INTBIG dindex, INTBIG lindex, INTBIG onlyvalid)
{
	REGISTER char *condist, *uncondist, *wcondist, *wuncondist,
		*mcondist, *muncondist, *edgedist;

	if (rules->conlist[dindex] < 0) condist = ""; else
		condist = frtoa(rules->conlist[dindex]);
	if (rules->unconlist[dindex] < 0) uncondist = ""; else
		uncondist = frtoa(rules->unconlist[dindex]);
	if (rules->conlistW[dindex] < 0) wcondist = ""; else
		wcondist = frtoa(rules->conlistW[dindex]);
	if (rules->unconlistW[dindex] < 0) wuncondist = ""; else
		wuncondist = frtoa(rules->unconlistW[dindex]);
	if (rules->conlistM[dindex] < 0) mcondist = ""; else
		mcondist = frtoa(rules->conlistM[dindex]);
	if (rules->unconlistM[dindex] < 0) muncondist = ""; else
		muncondist = frtoa(rules->unconlistM[dindex]);
	if (rules->edgelist[dindex] < 0) edgedist = ""; else
		edgedist = frtoa(rules->edgelist[dindex]);
	if (onlyvalid != 0)
	{
		if (*condist == 0 && *uncondist == 0 && *wcondist == 0 &&
			*wuncondist == 0 && *mcondist == 0 && *muncondist == 0 &&
			*edgedist == 0) return("");
	}
	(void)initinfstr();
	(void)formatinfstr("%s (%s/%s/%s/%s/%s/%s/%s)", rules->layernames[lindex],
		condist, uncondist, wcondist, wuncondist, mcondist, muncondist, edgedist);
	return(returninfstr());
}

INTBIG dr_rulesdialoggetlayer(DRCRULES *rules, INTBIG item)
{
	REGISTER INTBIG i, layer;
	REGISTER char *lname, save, *endpos;

	i = DiaGetCurLine(item);
	if (i < 0) return(-1);
	lname = DiaGetScrollLine(item, i);
	for(endpos = lname; *endpos != 0; endpos++)
		if (endpos[0] == ' ' && endpos[1] == '(') break;
	save = *endpos;
	*endpos = 0;
	for(layer=0; layer<rules->numlayers; layer++)
		if (namesame(lname, rules->layernames[layer]) == 0) break;
	*endpos = save;
	if (layer >= rules->numlayers) return(-1);
	return(layer);
}

void dr_loaddrcdistance(DRCRULES *rules)
{
	REGISTER INTBIG layer1, layer2, temp, dindex;

	/* clear rules */
	DiaSetText(DDRR_CONDIST, "");    DiaSetText(DDRR_CONDISTR, "");
	DiaSetText(DDRR_UCONDIST, "");   DiaSetText(DDRR_UCONDISTR, "");
	DiaSetText(DDRR_CONDISTW, "");   DiaSetText(DDRR_CONDISTWR, "");
	DiaSetText(DDRR_UCONDISTW, "");  DiaSetText(DDRR_UCONDISTWR, "");
	DiaSetText(DDRR_CONDISTM, "");   DiaSetText(DDRR_CONDISTMR, "");
	DiaSetText(DDRR_UCONDISTM, "");  DiaSetText(DDRR_UCONDISTMR, "");
	DiaSetText(DDRR_EDGEDIST, "");   DiaSetText(DDRR_EDGEDISTR, "");

	layer1 = dr_rulesdialoggetlayer(rules, DDRR_FROMLAYER);
	if (layer1 < 0) return;
	layer2 = dr_rulesdialoggetlayer(rules, DDRR_TOLAYER);
	if (layer2 < 0) return;

	if (layer1 > layer2) { temp = layer1; layer1 = layer2;  layer2 = temp; }
	dindex = (layer1+1) * (layer1/2) + (layer1&1) * ((layer1+1)/2);
	dindex = layer2 + rules->numlayers * layer1 - dindex;

	if (rules->conlist[dindex] >= 0)
		DiaSetText(DDRR_CONDIST, frtoa(rules->conlist[dindex]));
	if (rules->unconlist[dindex] >= 0)
		DiaSetText(DDRR_UCONDIST, frtoa(rules->unconlist[dindex]));
	if (rules->conlistW[dindex] >= 0)
		DiaSetText(DDRR_CONDISTW, frtoa(rules->conlistW[dindex]));
	if (rules->unconlistW[dindex] >= 0)
		DiaSetText(DDRR_UCONDISTW, frtoa(rules->unconlistW[dindex]));
	if (rules->conlistM[dindex] >= 0)
		DiaSetText(DDRR_CONDISTM, frtoa(rules->conlistM[dindex]));
	if (rules->unconlistM[dindex] >= 0)
		DiaSetText(DDRR_UCONDISTM, frtoa(rules->unconlistM[dindex]));
	if (rules->edgelist[dindex] >= 0)
		DiaSetText(DDRR_EDGEDIST, frtoa(rules->edgelist[dindex]));

	DiaSetText(DDRR_CONDISTR, rules->conlistR[dindex]);
	DiaSetText(DDRR_UCONDISTR, rules->unconlistR[dindex]);
	DiaSetText(DDRR_CONDISTWR, rules->conlistWR[dindex]);
	DiaSetText(DDRR_UCONDISTWR, rules->unconlistWR[dindex]);
	DiaSetText(DDRR_CONDISTMR, rules->conlistMR[dindex]);
	DiaSetText(DDRR_UCONDISTMR, rules->unconlistMR[dindex]);
	DiaSetText(DDRR_EDGEDISTR, rules->edgelistR[dindex]);
}

/* DRC error */
static DIALOGITEM dr_errdialogitems[] =
{
 /*  1 */ {0, {88,40,112,104}, BUTTON, N_("OK")},
 /*  2 */ {0, {128,232,152,360}, BUTTON, N_("Stop Checking")},
 /*  3 */ {0, {88,184,112,312}, BUTTON, N_("Ignore this error")},
 /*  4 */ {0, {128,96,152,224}, BUTTON, N_("Continue Silently")},
 /*  5 */ {0, {8,8,76,360}, MESSAGE, ""},
 /*  6 */ {0, {132,4,148,84}, MESSAGE|INACTIVE, N_("For now:")},
 /*  7 */ {0, {164,4,180,84}, MESSAGE|INACTIVE, N_("Permanently:")},
 /*  8 */ {0, {160,232,184,360}, BUTTON, N_("Stop Checking")},
 /*  9 */ {0, {160,96,184,224}, BUTTON, N_("Continue Silently")}
};
static DIALOG dr_errdialog = {{50,75,243,444}, N_("DRC Violation"), 0, 9, dr_errdialogitems};

/* special items for "DRC error" dialog: */
#define DDRE_QUITNOW      2		/* Quit-checking, for now (button) */
#define DDRE_IGNORE       3		/* Ignore-error (button) */
#define DDRE_CONTINUENOW  4		/* Continue-silently, for now (button) */
#define DDRE_ERRMSG       5		/* Error message (stat text) */
#define DDRE_QUITPERM     8		/* Quit-checking, permanently (button) */
#define DDRE_CONTINUEPERM 9		/* Continue-silently, permanently (button) */

char *dr_errordlog(char *prompt)
{
	INTBIG itemHit, oldplease;
	char *ret;

	/* display the DRC error dialog box */
	if (DiaInitDialog(&dr_errdialog)) return(0);

	/* load the message */
	DiaSetText(DDRE_ERRMSG, prompt);

	/* loop until done */
	oldplease = el_pleasestop;
	for(;;)
	{
		itemHit = DiaNextHit();
		if (itemHit == OK || itemHit == DDRE_QUITNOW ||
			itemHit == DDRE_IGNORE || itemHit == DDRE_CONTINUENOW ||
			itemHit == DDRE_QUITPERM || itemHit == DDRE_CONTINUEPERM) break;
	}
	el_pleasestop = oldplease;

	ret = 0;
	switch (itemHit)
	{
		case DDRE_QUITNOW:      ret = "quit-checking";       break;
		case DDRE_IGNORE:       ret = "ignore-error";        break;
		case DDRE_CONTINUENOW:  ret = "continue-silently";   break;
		case DDRE_QUITPERM:     ret = "drc-off";             break;
		case DDRE_CONTINUEPERM: ret = "highlight-off";       break;
		default:                ret = "acknowledge";         break;
	}
	DiaDoneDialog();
	return(ret);
}

#endif  /* DRCTOOL - at top */
