/*
 * CmdCD.c --
 *
 * Commands with names beginning with the letters C through D.
 *
 *     *********************************************************************
 *     * Copyright (C) 1985, 1990 Regents of the University of California. *
 *     * Permission to use, copy, modify, and distribute this              *
 *     * software and its documentation for any purpose and without        *
 *     * fee is hereby granted, provided that the above copyright          *
 *     * notice appear in all copies.  The University of California        *
 *     * makes no representations about the suitability of this            *
 *     * software for any purpose.  It is provided "as is" without         *
 *     * express or implied warranty.  Export of this software outside     *
 *     * of the United States of America may require an export license.    *
 *     *********************************************************************
 */

#ifndef lint
static const char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/commands/CmdCD.c,v 1.7 2010/06/24 12:37:15 tim Exp $";
#endif  /* not lint */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>

#include "tcltk/tclmagic.h"
#include "utils/magic.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "windows/windows.h"
#include "dbwind/dbwind.h"
#include "utils/main.h"
#include "commands/commands.h"
#include "utils/utils.h"
#include "utils/magic_zlib.h"
#include "textio/textio.h"
#include "drc/drc.h"
#include "graphics/graphics.h"
#include "textio/txcommands.h"
#include "textio/textio.h"
#include "cif/cif.h"
#include "calma/calma.h"
#include "utils/styles.h"
#include "router/rtrDcmpose.h"
#include "select/select.h"
#include "utils/signals.h"
#include "utils/malloc.h"
#include "cif/CIFint.h"
#include "cif/CIFread.h"
#include "calma/calmaInt.h"

/* C99 compat */
#include "dbwind/dbwtech.h"

/* The following structure is used by CmdCorner to keep track of
 * areas to be filled.
 */

struct cmdCornerArea
{
    Rect cca_area;			/* Area to paint. */
    TileType cca_type;			/* Type of material. */
    struct cmdCornerArea *cca_next;	/* Next in list of areas to paint. */
};

/* Forward declarations */
int cmdDumpFunc(Rect *rect, char *name, Label *label, Point *point);
bool cmdDumpParseArgs(char *cmdName, MagWindow *w, TxCommand *cmd, CellUse *dummy, SearchContext *scx);
#ifdef CALMA_MODULE

/*
 * ----------------------------------------------------------------------------
 *
 * CmdCalma --
 *
 * Implement the "gds" or "calma" command.
 *
 * Usage:
 *	gds option args
 *	calma option args
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	There are no side effects on the circuit.  Currently, there
 *	is only a single option, "write", to write a CALMA stream
 *	file.
 *
 * ----------------------------------------------------------------------------
 */

#define CALMA_HELP	0
#define CALMA_ABSTRACT	1
#define CALMA_ADDENDUM	2
#define CALMA_ARRAYS	3
#define CALMA_COMPRESS  4
#define CALMA_CONTACTS	5
#define CALMA_DATESTAMP	6
#define CALMA_DRCCHECK	7
#define	CALMA_FLATTEN	8
#define	CALMA_FLATGLOB  9
#define	CALMA_LABELS	10
#define	CALMA_LIBRARY	11
#define	CALMA_LOWER	12
#define CALMA_MAGSCALE  13
#define CALMA_MASKHINTS	14
#define CALMA_MERGE	15
#define CALMA_NO_STAMP	16
#define CALMA_NO_DUP	17
#define CALMA_ORDERING	18
#define CALMA_READ	19
#define CALMA_READONLY	20
#define CALMA_RESCALE	21
#define CALMA_SAVEPATHS	22
#define CALMA_WARNING	23
#define CALMA_WRITE	24
#define CALMA_POLYS	25
#define CALMA_PATHS	26
#define CALMA_UNDEFINED	27
#define CALMA_UNIQUE	28

#define CALMA_WARN_HELP CIF_WARN_END	/* undefined by CIF module */

void
CmdCalma(
    MagWindow *w,
    TxCommand *cmd)
{
    int option, ext, value;
    const char * const *msg;
    char *namep, *dotptr;
    char writeMode[3];
    CellDef *rootDef;
    FILETYPE f;
    FILE *fp;
#ifdef HAVE_ZLIB
    gzFile fz;
#endif

    static const char * const gdsExts[] = {".gds", ".gds.gz", ".gds2", ".strm", "", NULL};
    static const char * const cmdCalmaYesNo[] = {
		"no", "false", "off", "0", "yes", "true", "on", "1", 0 };
    static const char * const cmdCalmaAllowDisallow[] = {"disallow", "0", "allow", "1", 0};
    static const char * const cmdCalmaPolygonType[] = {"none", "temporary", "keep", 0};
    static const char * const cmdCalmaWarnOptions[] = { "default", "none", "align",
		"limit", "redirect", "help", 0 };
    static const char * const cmdCalmaOption[] =
    {
	"help		print this help information",
	"abstract [allow|disallow]\n"
	"		[dis]allow writing of GDS with calls to abstract cells",
	"addendum [yes|no]	output only cells that are not type \"readonly\"",
	"arrays [yes|no]	output arrays as individual subuses (like in CIF)",
	"compress [value]	compress output with zlib compression 0 to 6",
	"contacts [yes|no]	optimize output by arraying contacts as subcells",
	"datestamp [yes|value]	use current time or value as the creation date stamp",
	"drccheck [yes|no]	mark all cells as needing DRC checking",
	"flatten [yes|no|limit]	flatten simple cells (e.g., contacts) on input",
	"flatglob [<name>|none]	flatten cells by name with glob patterning",
	"labels [yes|no]	cause labels to be output when writing GDS-II",
	"library [yes|no]	do not output the top level, only subcells",
	"lower [yes|no]		allow both upper and lower case in labels",
	"magscale [value]	scale to interpret text magnification 1 in microns",
	"maskhints [yes|no]	generate mask hint properties on input",
	"merge [yes|no]		merge tiles into polygons in the output",
	"nodatestamp [yes|no]	write a zero value creation date stamp",
	"noduplicates [yes|no]	do not read cells that exist before reading GDS",
	"ordering [on|off]	cause cells to be read in post-order",
	"read file		read Calma GDS-II format from \"file\"\n"
	"		into edit cell",
	"readonly [yes|no]	set cell as read-only and generate output from GDS file",
	"rescale [yes|no]	allow or disallow internal grid subdivision",
	"savepaths [yes|no]	save path centerlines as cell properties",
	"warning [option]	set warning information level",
	"write file		output Calma GDS-II format to \"file\"\n"
	"		for the window's root cell",
	"polygon subcells [yes|no]\n"
	"		put non-Manhattan polygons into subcells",
	"path subcells [yes|no]\n"
	"		put wire paths into individual subcells",
	"undefined [allow|disallow]\n"
	"		[dis]allow writing of GDS with calls to undefined cells",
	"unique [yes|no]	rename any cells with names duplicated in the GDS",
	NULL
    };

    if (cmd->tx_argc == 1)
	option = CALMA_WRITE;
    else
    {
	option = Lookup(cmd->tx_argv[1], cmdCalmaOption);
	if (option < 0)
	{
	    TxError("\"%s\" isn't a valid gds option.\n", cmd->tx_argv[1]);
	    option = CALMA_HELP;
	    cmd->tx_argc = 2;
	}
    }

    /* Only check for a window on options requiring one */

    switch (option)
    {
	case CALMA_READ: case CALMA_WRITE:
	    windCheckOnlyWindow(&w, DBWclientID);
	    if (w == (MagWindow *) NULL)
	    {
		TxError("Point to a window first\n");
		return;
	    }
	    rootDef = ((CellUse *) w->w_surfaceID)->cu_def;

	    if (cmd->tx_argc == 1)
	    {
		namep = strrchr(rootDef->cd_name, '/');
		if (namep == (char *) NULL)
		    namep = rootDef->cd_name;
		goto outputCalma;
	    }
	    break;
    }

    switch (option)
    {
	case CALMA_HELP:
	    TxPrintf("GDS commands have the form \":gds option\",");
	    TxPrintf(" where option is one of:\n");
	    for (msg = &(cmdCalmaOption[0]); *msg != NULL; msg++)
	    {
		if (**msg == '*') continue;
		TxPrintf("    %s\n", *msg);
	    }
	    TxPrintf("If no option is given, a CALMA GDS-II stream file is\n");
	    TxPrintf("    produced for the root cell.\n");
	    TxPrintf("The current CIF output style (\"cif ostyle\") is used\n");
	    TxPrintf("    to select the mask layers output by :gds write.\n");
	    TxPrintf("The current CIF input style (\"cif istyle\") is used\n");
	    TxPrintf("    to select the mask layers read by :gds read.\n");
	    return;

	case CALMA_LABELS:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CalmaDoLabels));
#else
		TxPrintf("Labels will %sbe output to the GDS file.\n",
			(CalmaDoLabels) ?  "" : "not ");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
	    {
		wrongNumArgs:
		TxError("Wrong number of arguments in \"%s\" command.",
			cmd->tx_argv[0]);
		TxError("  Try \":%s help\" for help.\n", cmd->tx_argv[0]);
		return;
	    }

	    option = Lookup(cmd->tx_argv[2], cmdCalmaYesNo);
	    if (option < 0)
		goto wrongNumArgs;
	    CalmaDoLabels = (option < 4) ? FALSE : TRUE;
	    return;

	case CALMA_LIBRARY:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CalmaDoLibrary));
#else
		TxPrintf("The top-level cell will %sbe output to the GDS file.\n",
			(CalmaDoLibrary) ?  "not " : "");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[2], cmdCalmaYesNo);
	    if (option < 0)
		goto wrongNumArgs;
	    CalmaDoLibrary = (option < 4) ? FALSE : TRUE;
	    return;

	case CALMA_ADDENDUM:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CalmaAddendum));
#else
		TxPrintf("Read-only cell defs will %sbe output to the GDS file.\n",
			(CalmaAddendum) ?  "not " : "");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[2], cmdCalmaYesNo);
	    if (option < 0)
		goto wrongNumArgs;
	    CalmaAddendum = (option < 4) ? FALSE : TRUE;
	    return;

	case CALMA_UNDEFINED:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CalmaAllowUndefined));
#else
		TxPrintf("Writing of GDS file with undefined cells is %sallowed.\n",
			(CalmaAllowUndefined) ?  "" : "dis");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[2], cmdCalmaAllowDisallow);
	    if (option < 0)
		goto wrongNumArgs;
	    CalmaAllowUndefined = (option < 2) ? FALSE : TRUE;
	    return;

	case CALMA_ABSTRACT:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CalmaAllowAbstract));
#else
		TxPrintf("Writing of GDS file with abstract cells is %sallowed.\n",
			(CalmaAllowUndefined) ?  "" : "dis");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[2], cmdCalmaAllowDisallow);
	    if (option < 0)
		goto wrongNumArgs;
	    CalmaAllowAbstract = (option < 2) ? FALSE : TRUE;
	    return;

	case CALMA_COMPRESS:
	    if (cmd->tx_argc == 2)
	    {
#ifdef HAVE_ZLIB
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewIntObj(CalmaCompression));
#else
		if (CalmaCompression == 0)
		    TxPrintf("Calma files are not compressed.\n");
		else
		    TxPrintf("Calma files are compressed with zlib compression level %d.\n", CalmaCompression);
#endif
#else	/* !HAVE_ZLIB */
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewIntObj(0));
#else
		TxPrintf("No file compression has been enabled.\n");
#endif
#endif	/* !HAVE_ZLIB */
		return;
	    }
#ifdef HAVE_ZLIB
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    value = atoi(cmd->tx_argv[2]);
	    if ((value < 0) || (value > 9))
		TxError("Bad compression value %d.  Value must be in the range 0 to 9.\n", value);
	    else
		CalmaCompression = value;
#else
	    goto wrongNumArgs;
#endif
	    return;

	case CALMA_CONTACTS:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CalmaContactArrays));
#else
		if (CalmaContactArrays)
		    TxPrintf("Contact areas are arrayed as subcells.\n");
		else
		    TxPrintf("Contact areas are output as individual cuts.\n");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[2], cmdCalmaYesNo);
	    if (option < 0)
		goto wrongNumArgs;
	    CalmaContactArrays = (option < 4) ? FALSE : TRUE;
	    return;

	case CALMA_DRCCHECK:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(!CalmaNoDRCCheck));
#else
		TxPrintf("GDS cells read from input file are%s checked for DRC.\n",
			(CalmaNoDRCCheck) ?  " not" : " ");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[2], cmdCalmaYesNo);
	    if (option < 0)
		goto wrongNumArgs;
	    CalmaNoDRCCheck = (option < 4) ? TRUE : FALSE;
	    return;

	case CALMA_MAGSCALE:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewDoubleObj((double)CalmaMagScale));
#else
		TxPrintf("Text magnification 1.0 = %g microns.\n", (double)CalmaMagScale);
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    if (StrIsNumeric(cmd->tx_argv[2]))
		 CalmaMagScale = (float)atof(cmd->tx_argv[2]);
	    else if (!strcmp(cmd->tx_argv[2], "default"))
		 CalmaMagScale = 1.0;
	    else
		goto wrongNumArgs;
	    return;

	case CALMA_FLATTEN:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CalmaFlattenUses));
#else
		if (CalmaFlattenUses)
		    TxPrintf("Small cells in input are flattened.\n");
		else
		    TxPrintf("Cells are never flattened on input.\n");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[2], cmdCalmaYesNo);
	    if (option < 0)
	    {
	        if (StrIsInt(cmd->tx_argv[2]))
		{
		    CalmaFlattenLimit = atoi(cmd->tx_argv[2]);
		    CalmaFlattenUses = (CalmaFlattenLimit > 0) ? TRUE : FALSE;
		}
		else
		    goto wrongNumArgs;
	    }
	    else
		CalmaFlattenUses = (option < 4) ? FALSE : TRUE;
	    return;

	case CALMA_FLATGLOB:

	    if (cmd->tx_argc == 2)
	    {
		if (CalmaFlattenUsesByName != NULL)
		{
		    int i = 0;
		    char *pattern;
#ifdef MAGIC_WRAPPER
		    Tcl_Obj *lobj = Tcl_NewListObj(0, NULL);
		    while (TRUE)
		    {
			pattern = CalmaFlattenUsesByName[i];
			if (pattern == NULL) break;
			i++;
			Tcl_ListObjAppendElement(magicinterp, lobj,
				Tcl_NewStringObj(pattern, -1));
		    }
		    Tcl_SetObjResult(magicinterp, lobj);
#else
		    TxPrintf("Glob patterns for cells to flatten:\n");
		    while (TRUE)
		    {
			pattern = CalmaFlattenUsesByName[i];
			if (pattern == NULL) break;
			i++;
			TxPrintf("   \"%s\"\n", pattern);
		    }
#endif
		}
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    if (!strcasecmp(cmd->tx_argv[2], "none"))
	    {
		int i = 0;
		if (CalmaFlattenUsesByName == (char **)NULL) return;
		while (TRUE)
		{
		    char *pattern = CalmaFlattenUsesByName[i];
		    if (pattern == NULL) break;
		    freeMagic(pattern);
		    i++;
		}
		freeMagic(CalmaFlattenUsesByName);
		CalmaFlattenUsesByName = (char **)NULL;
	    }
	    else
	    {
		char **newpatterns;
		char *pattern;
		int i = 0;

		if (CalmaFlattenUsesByName == (char **)NULL)
		    i = 1;
		else
		{
		    while (TRUE)
		    {
			pattern = CalmaFlattenUsesByName[i++];
			if (pattern == NULL) break;
		    }
		}
		newpatterns = (char **)mallocMagic((i + 1) * sizeof(char *));
		i = 0;
		if (CalmaFlattenUsesByName != (char **)NULL)
		{
		    while (TRUE)
		    {
		    	pattern = CalmaFlattenUsesByName[i];
		    	if (pattern == NULL) break;
		    	newpatterns[i] = StrDup((char **)NULL, pattern);
		    	i++;
		    }
		}
		newpatterns[i++] = StrDup((char **)NULL, cmd->tx_argv[2]);
		newpatterns[i] = (char *)NULL;

		i = 0;
		if (CalmaFlattenUsesByName != (char **)NULL)
		{
		    while (TRUE)
		    {
			pattern = CalmaFlattenUsesByName[i];
			if (pattern == NULL) break;
			freeMagic(pattern);
			i++;
		    }
		    freeMagic(CalmaFlattenUsesByName);
		}
		CalmaFlattenUsesByName = newpatterns;
	    }
	    return;

	case CALMA_ORDERING:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CalmaPostOrder));
#else
		if (CalmaPostOrder)
		    TxPrintf("GDS parser reads cells in post-order.\n");
		else
		    TxPrintf("GDS parser reads cells in the order "
				"encountered in the stream file.\n");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[2], cmdCalmaYesNo);
	    if (option < 0)
		goto wrongNumArgs;
	    CalmaPostOrder = (option < 4) ? FALSE : TRUE;
	    return;

	case CALMA_ARRAYS:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CalmaFlattenArrays));
#else
		TxPrintf("GDS are %s.\n",
			(CalmaFlattenArrays) ?  "flattened" : "hierarchical");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[2], cmdCalmaYesNo);
	    if (option < 0)
		goto wrongNumArgs;
	    CalmaFlattenArrays = (option < 4) ? FALSE : TRUE;
	    return;

	case CALMA_LOWER:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CalmaDoLower));
#else
		TxPrintf("GDS labels are %s.\n",
			(CalmaDoLower) ?  "lowercase only" : "mixed-case");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[2], cmdCalmaYesNo);
	    if (option < 0)
		goto wrongNumArgs;
	    CalmaDoLower = (option < 4) ? FALSE : TRUE;
	    return;

	case CALMA_MASKHINTS:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		if ((CalmaMaskHints != NULL) && !TTMaskIsZero(CalmaMaskHints))
		    Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(TRUE));
		else
		    Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(FALSE));
#else
		if ((CalmaMaskHints != NULL) && !TTMaskIsZero(CalmaMaskHints))
		    TxPrintf("Mask hints generated from GDS input.\n");
		else
		    TxPrintf("No mask hints generated from GDS input.\n");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    if (CalmaMaskHints == NULL)
	    {
		CalmaMaskHints = (TileTypeBitMask *)mallocMagic(sizeof(TileTypeBitMask));
		TTMaskZero(CalmaMaskHints);
	    }
	    option = Lookup(cmd->tx_argv[2], cmdCalmaYesNo);
	    if (option < 0)
		CIFParseReadLayers(cmd->tx_argv[2], CalmaMaskHints, FALSE);
	    else
	    {
		if (option < 4)
		    TTMaskZero(CalmaMaskHints);
		else
		    TTMaskSetMask(CalmaMaskHints, &DBAllTypeBits);
	    }
	    return;

	case CALMA_MERGE:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CalmaMergeTiles));
#else
		if (CalmaMergeTiles)
		    TxPrintf("Merge connected tiles into polygons on output\n");
		else
		    TxPrintf("Output individual tiles\n");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[2], cmdCalmaYesNo);
	    if (option < 0)
		goto wrongNumArgs;
	    CalmaMergeTiles = (option < 4) ? FALSE : TRUE;
	    return;

	case CALMA_POLYS:
	    if (cmd->tx_argc == 3)
	    {
#ifdef MAGIC_WRAPPER
		switch (CalmaSubcellPolygons)
		{
		    case CALMA_POLYGON_NONE:
		        Tcl_SetObjResult(magicinterp, Tcl_NewStringObj("none", 0));
			break;
		    case CALMA_POLYGON_TEMP:
		        Tcl_SetObjResult(magicinterp, Tcl_NewStringObj("temporary", 0));
			break;
		    case CALMA_POLYGON_KEEP:
		        Tcl_SetObjResult(magicinterp, Tcl_NewStringObj("keep", 0));
			break;
		}
#else
		if (CalmaSubcellPolygons == CALMA_POLYGON_NONE)
		    TxPrintf("Non-manhattan polygons read as-is.\n");
		else if (CalmaSubcellPolygons == CALMA_POLYGON_TEMP)
		    TxPrintf("Non-manhattan polygons placed in temporary subcells.\n");
		else
		    TxPrintf("Non-manhattan polygons placed in subcells.\n");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 4)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[3], cmdCalmaPolygonType);
	    if (option < 0)
	    {
		option = Lookup(cmd->tx_argv[3], cmdCalmaYesNo);
	    	if (option < 0)
		    goto wrongNumArgs;
		else if (option < 4)
		    CalmaSubcellPolygons = (unsigned char)CALMA_POLYGON_NONE;
		else
		    CalmaSubcellPolygons = (unsigned char)CALMA_POLYGON_TEMP;
	    }
	    else
		CalmaSubcellPolygons = (unsigned char)option;
	    return;

	case CALMA_SAVEPATHS:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CalmaRecordPaths));
#else
		TxPrintf("Paths in GDS cells read from input file are%s recorded"
				" as cell properties.\n",
			(CalmaRecordPaths) ?  " " : " not");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[2], cmdCalmaYesNo);
	    if (option < 0)
		goto wrongNumArgs;
	    CalmaRecordPaths = (option < 4) ? FALSE : TRUE;
	    return;

	case CALMA_NO_DUP:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CalmaNoDuplicates));
#else
		TxPrintf("Cell defs that exist before reading GDS will %sbe parsed.\n",
			(CalmaNoDuplicates) ?  "not " : "");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[2], cmdCalmaYesNo);
	    if (option < 0)
		goto wrongNumArgs;
	    CalmaNoDuplicates = (option < 4) ? FALSE : TRUE;
	    return;

	case CALMA_UNIQUE:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CalmaUnique));
#else
		TxPrintf("Cell defs that exist before reading GDS will %sbe renamed.\n",
			(CalmaUnique) ?  "not " : "");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[2], cmdCalmaYesNo);
	    if (option < 0)
		goto wrongNumArgs;
	    CalmaUnique = (option < 4) ? FALSE : TRUE;
	    return;

	case CALMA_NO_STAMP:
	    /* CALMA_DATESTAMP is the current implementation.		*/
	    /* CALMA_NO_STAMP is retained for backwards-compatibility.	*/
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		if (CalmaDateStamp == NULL)
		    Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(FALSE));
		else if (*CalmaDateStamp == 0)
		    Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(TRUE));
		else
		    Tcl_SetObjResult(magicinterp, Tcl_NewStringObj("fixed", -1));
#else
		if (CalmaDateStamp != NULL)
		    TxPrintf("Structures will contain a header creation date "
			"stamp of %ld.\n", (long)(*CalmaDateStamp));
		else
		    TxPrintf("Structures will contain a default header creation date "
			"stamp.\n");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[2], cmdCalmaYesNo);
	    if (option < 0)
		goto wrongNumArgs;

	    /* yes|no:  If "yes", then set date stamp to zero.  If "no", then
	     * leave date stamp pointer NULL, and current date will be used for
	     * the timestamp.
	     */
	    if (option >= 4)
	    {
		if (CalmaDateStamp == NULL) 
		    CalmaDateStamp = (time_t *)mallocMagic(sizeof(time_t));
		*CalmaDateStamp = (time_t)0;
	    }
	    else
	    {
		if (CalmaDateStamp != NULL) 
		{
		    freeMagic((char *)CalmaDateStamp);
		    CalmaDateStamp = (time_t *)NULL;
		}
	    }
	    return;

	case CALMA_PATHS:
	    if (cmd->tx_argc == 3)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CalmaSubcellPaths));
#else
		if (CalmaSubcellPaths)
		    TxPrintf("Wire paths placed in subcells.\n");
		else
		    TxPrintf("Wire paths read as-is.\n");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 4)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[3], cmdCalmaYesNo);
	    if (option < 0)
		goto wrongNumArgs;
	    CalmaSubcellPaths = (option < 4) ? FALSE : TRUE;
	    return;

	case CALMA_READONLY:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CalmaReadOnly));
#else
		TxPrintf("GDS cells read from input file are set read-%s.\n",
			(CalmaReadOnly) ?  "only" : "write");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[2], cmdCalmaYesNo);
	    if (option < 0)
		goto wrongNumArgs;
	    CalmaReadOnly = (option < 4) ? FALSE : TRUE;
	    return;

	case CALMA_RESCALE:
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CIFRescaleAllow));
#else
		TxPrintf("Internal grid rescaling %sallowed\n",
			(CIFRescaleAllow) ?  "" : "dis");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[2], cmdCalmaYesNo);
	    if (option < 0)
		goto wrongNumArgs;
	    CIFRescaleAllow = (option < 4) ? FALSE : TRUE;
	    if (!CIFRescaleAllow)
		CIFWarningLevel = CIF_WARN_LIMIT;
	    return;

	case CALMA_DATESTAMP:
	    /* CALMA_NO_STAMP is retained for backwards-compatibility. */
	    if (cmd->tx_argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		if (CalmaDateStamp != NULL)
		    Tcl_SetObjResult(magicinterp, Tcl_NewIntObj(*CalmaDateStamp));
		else
		    Tcl_SetObjResult(magicinterp, Tcl_NewStringObj("default", -1));
#else
		if (CalmaDateStamp != NULL)
		    TxPrintf("Structures will contain a header creation date "
			"stamp of %ld.\n", (long)(*CalmaDateStamp));
		else
		    TxPrintf("Structures will contain a default header creation date "
			"stamp.\n");
#endif
		return;
	    }
	    else if (cmd->tx_argc != 3)
		goto wrongNumArgs;

	    option = Lookup(cmd->tx_argv[2], cmdCalmaYesNo);
	    if (option >= 0)
	    {
		/* option yes|no:  "yes" means set date stamp pointer to NULL
		 * and use current date as the timestamp.  "no" means set the
		 * date stamp to zero.
		 */
		if (option >= 4)
		{
		    if (CalmaDateStamp != NULL) 
		    {
			freeMagic((char *)CalmaDateStamp);
			CalmaDateStamp = (time_t *)NULL;
		    }
		}
		else
		{
		    if (CalmaDateStamp == NULL) 
			CalmaDateStamp = (time_t *)mallocMagic(sizeof(time_t));
		    *CalmaDateStamp = (time_t)0;
		}
	    }
	    else if (StrIsInt(cmd->tx_argv[2]))
	    {
		/* Otherwise, if value is an integer, use it for the date stamp */
		if (CalmaDateStamp == NULL) 
		    CalmaDateStamp = (time_t *)mallocMagic(sizeof(time_t));
		*CalmaDateStamp = atoi(cmd->tx_argv[2]);
	    }
	    else
	    {
	        TxError("Unrecognizable date stamp \"%s\".\n", cmd->tx_argv[2]);
		goto wrongNumArgs;
	    }
	    return;

	case CALMA_WARNING:
	    if (cmd->tx_argc == 2)
	    {
		TxPrintf("Warning display options: %s\n",
			cmdCalmaWarnOptions[CIFWarningLevel]);
	    }
	    else
	    {
		int suboption = Lookup(cmd->tx_argv[2], cmdCalmaWarnOptions);
		if (suboption < 0)
		{
		    TxError("\"%s\" isn't a valid gds warning option.\n",
				cmd->tx_argv[2]);
		    suboption = CALMA_WARN_HELP;
		}
		if (suboption == CALMA_WARN_HELP)
		    TxError("Valid options are: default, align, limit, "
			"redirect, and none.\n");
		else
		    CIFWarningLevel = suboption;

		if (suboption == CIF_WARN_REDIRECT)
		{
		    if (cmd->tx_argc == 4)
			StrDup(&CIFErrorFilename, cmd->tx_argv[3]);
		    else
			StrDup(&CIFErrorFilename, NULL);
		}
	    }
	    return;
	case CALMA_WRITE:
	    if (cmd->tx_argc != 3) goto wrongNumArgs;
	    namep = cmd->tx_argv[2];
	    goto outputCalma;

	case CALMA_READ:
	    if (cmd->tx_argc != 3) goto wrongNumArgs;

	    /* Check for various common file extensions, including	*/
	    /* no extension (as-is), ".gds", ".gds2", and ".strm".	*/

	    for (ext = 0; gdsExts[ext] != NULL; ext++)
		if ((f = PaZOpen(cmd->tx_argv[2], "r", gdsExts[ext], Path,
		    	(char *) NULL, &namep)) != (FILETYPE)NULL)
		    break;

	    if (f == (FILETYPE) NULL)
	    {
	        TxError("Cannot open %s.gds, %s.strm or %s to read "
			"GDS-II stream input.\n",
			cmd->tx_argv[2], cmd->tx_argv[2], cmd->tx_argv[2]);
	        return;
	    }

	    /* Ensure that there is a valid edit cell */
	    if (EditCellUse == NULL)
		DBWloadWindow(w, (char *)NULL, DBW_LOAD_IGNORE_TECH);

	    CalmaReadFile(f, namep);
	    (void) FCLOSE(f);
	    return;
    }

    /*
     * If control gets here, we're going to output GDS-II (stream)
     * into the file given by namep.
     */

outputCalma:
    dotptr = strrchr(namep, '.');

#ifdef HAVE_ZLIB
    /* Handle compression based on value of CalmaCompression */
    if (CalmaCompression > 0)
    {
	sprintf(writeMode, "w%d", CalmaCompression);
	fz = PaZOpen(namep, writeMode, (dotptr == NULL) ? ".gds.gz" : "", ".", (char *) NULL, (char **)NULL);
	if (fz == (gzFile)NULL)
	{
	    TxError("Cannot open %s%s to write compressed GDS-II stream output\n", namep,
			(dotptr == NULL) ? ".gds.gz" : "");
	    return;
	}
	if (!CalmaWriteZ(rootDef, fz))
	{
	    TxError("I/O error in writing compressed file %s.\n", namep);
	    TxError("File may be incompletely written.\n");
	}
	(void) gzclose(fz);
    }
    else
    {

#endif
	fp = PaOpen(namep, "w", (dotptr == NULL) ? ".gds" : "", ".", (char *) NULL, (char **)NULL);
	if (fp == (FILE *)NULL)
	{
	    TxError("Cannot open %s%s to write GDS-II stream output\n", namep,
			(dotptr == NULL) ? ".gds" : "");
	    return;
	}
	if (!CalmaWrite(rootDef, fp))
	{
	    TxError("I/O error in writing file %s.\n", namep);
	    TxError("File may be incompletely written.\n");
	}
	(void) fclose(fp);

#ifdef HAVE_ZLIB
    }
#endif

}
#endif /* CALMA_MODULE */

/*
 * ----------------------------------------------------------------------------
 *
 * CmdCellname --
 *
 * Implement the "cellname" and "instance" commands. List the child(ren),
 * parent(s), or name of the selected or indicated cell or instance.
 *
 * Usage:
 *	cellname [list] children|parents|exists|instances|
 *			celldef|self|childinst [name]
 * or
 *	cellname [list] topcells|allcells|window
 * or
 *	cellname delete [name]
 * or
 *	cellname writeable [name] [true|false]
 * or
 *	cellname rename [name] [newname]
 * or
 *	cellname [list] filepath [path|"default"]
 * or
 *	cellname property [name] [property_key [property_value]]
 * or
 *	instance orientation [name] [-def] [orient]
 *	instance abutment [name]
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Print out the names of all the cell currently loaded.
 *	Or (optionally) only the cell given.
 *
 * ----------------------------------------------------------------------------
 */

	/* ARGSUSED */

void
CmdCellname(
    MagWindow *w,
    TxCommand *cmd)
{
    bool is_cellname;
    bool dolist = FALSE;
    bool dodef = FALSE;
    bool doforce = FALSE;
    int option;
    int locargc = cmd->tx_argc;
    char *cellname = NULL, *orient = NULL;
    void (*func)();
    CellDef *newDef, *cellDef;

    static const char * const cmdCellOption[] =
    {
	"children 	list children of selected or named cell",
	"parents 	list parents of selected or named cell",
	"exists 	true if loaded, false if not, or name of selected",
	"self		true if loaded, false if not, or name of selected",
	"instances	list instances of a cell definition",
	"childinst 	list children instances of a cell definition",
	"celldef	name the cell definition of an instance",
	"allcells	list all cells",
	"topcells	list top-level cells",
	"window		list top-level cell of a layout window",
	"create		create a new cell definition",
	"delete		delete the named cell definition",
	"dereference	reload the named cell from the search paths",
	"filepath	list the full path of the file for the cell",
	"flags		list option flags of the indicated cell definition",
	"timestamp 	list the cell timestamp",
	"lock		lock the named cell (prevent changes to cell use)",
	"unlock		unlock the named cell (allow changes to cell use)",
	"property	list or set cell definition properties",
	"abutment	list instance abutment coordinates",
	"orientation	list or set instance orientation",
	"rename		rename the indicated cell",
	"writeable	make the cell definition read-only or read-write",
	"modified	true if modified, false if not",
	NULL
    };
    typedef enum { IDX_CHILDREN, IDX_PARENTS, IDX_EXISTS, IDX_SELF,
		   IDX_INSTANCE, IDX_CHILDINST, IDX_CELLDEF, IDX_ALLCELLS,
		   IDX_TOPCELLS, IDX_IN_WINDOW, IDX_CREATE, IDX_DELETE,
		   IDX_DEREFERENCE, IDX_FILEPATH, IDX_FLAGS, IDX_TIMESTAMP,
		   IDX_LOCK, IDX_UNLOCK, IDX_PROPERTY, IDX_ABUTMENT,
		   IDX_ORIENTATION, IDX_RENAME, IDX_READWRITE,
		   IDX_MODIFIED } optionType;

    static const char * const cmdCellnameYesNo[] = {
		"no", "false", "off", "0", "yes", "true", "on", "1", 0 };

    if (strstr(cmd->tx_argv[0], "in"))
	is_cellname = FALSE;
    else
	is_cellname = TRUE;

    func = (is_cellname) ? DBCellPrint : DBUsePrint;

    if (locargc > 1)
    {
	if (!strcmp(cmd->tx_argv[1], "list")) {
	    dolist = TRUE;
	    locargc--;
	}
    }
    
    /* Check for option at end of option list */

    if (*cmd->tx_argv[cmd->tx_argc - 1] == '-') {
	char *option = cmd->tx_argv[cmd->tx_argc - 1] + 1;
	if (!strcmp(option, "list")) {
	    dolist = TRUE;
	    locargc--;
	}
	else if (!strcmp(option, "def")) {
	    dodef = TRUE;
	    locargc--;
	}
	else if (!strcmp(option, "force")) {
	    doforce = TRUE;
	    locargc--;
	}
    }

    if (locargc > 5 || locargc < 2) goto badusage;

    option = Lookup(cmd->tx_argv[1 + ((dolist) ? 1 : 0)], cmdCellOption);
    if (option < 0) goto badusage;

    if ((locargc > 3) && (option != IDX_RENAME) && (option != IDX_DELETE) &&
		(option != IDX_DEREFERENCE) && (option != IDX_READWRITE) &&
		(option != IDX_PROPERTY) && (option != IDX_FILEPATH) &&
		(option != IDX_ORIENTATION) && (option != IDX_TIMESTAMP))
	goto badusage;

    if ((locargc > 4) && (option != IDX_PROPERTY))
	goto badusage;

    if (locargc >= 3) {
	switch (option) {
	    case IDX_ALLCELLS:
	    case IDX_TOPCELLS:
	    case IDX_IN_WINDOW:
		goto badusage;
		break;
	    default:
		cellname = cmd->tx_argv[2 + ((dolist) ? 1 : 0)];
	}
    }

    if (is_cellname)
    {
	/* These functions only work with cell uses (instances) */
	switch (option) {
	    case IDX_LOCK:
	    case IDX_UNLOCK:
		TxError("Cell definitions cannot be locked.  Use \"instance\"?\n");
		TxError("  or do you mean \"cellname writeable\"?\n");
		return;
	    case IDX_ORIENTATION:
		TxError("Cell definitions do not have orientations."
			"  Use \"instance\"?\n");
		return;
	    case IDX_ABUTMENT:
		TxError("Use \"property get FIXED_BBOX\" to get the cell "
			"abutment box.\n");
		return;
	}
    }
    else
    {
	/* These functions only work with cell definitions */
	switch (option) {
	    case IDX_TOPCELLS:
		TxError("Instances do not have a top level.  Use \"cellname\"?\n");
		return;
	    case IDX_IN_WINDOW: case IDX_READWRITE: case IDX_FLAGS:
	    case IDX_PROPERTY: case IDX_FILEPATH: case IDX_MODIFIED:
	    case IDX_DEREFERENCE: case IDX_TIMESTAMP:
		TxError("Function unimplemented for instances.\n");
		return;
	    case IDX_DELETE:
		TxError("Function unimplemented for instances.  Use \"delete\"\n");
		return;
	    case IDX_CREATE:
		TxError("Function unimplemented for instances.  Use \"getcell\"\n");
		return;
	    case IDX_RENAME:
		TxError("Function unimplemented for instances.  Use \"identify\"\n");
		return;
	}
    }

    switch (option) {
	case IDX_ALLCELLS:
	    (*func)(NULL, ALLCELLS, dolist);
	    break;
	case IDX_TOPCELLS:
	    (*func)(NULL, TOPCELLS, dolist);
	    break;
	case IDX_IN_WINDOW:
	    DBTopPrint(w, dolist);
	    break;
	case IDX_SELF:
	case IDX_EXISTS:
	    (*func)(cellname, SELF, dolist);
	    break;
	case IDX_CELLDEF:
	    (*func)(cellname, ((is_cellname == FALSE) ? OTHER : SELF), dolist);
	    break;
	case IDX_INSTANCE:
	    (*func)(cellname, ((is_cellname == FALSE) ? SELF : OTHER), dolist);
	    break;
	case IDX_CHILDREN:
	    (*func)(cellname, CHILDREN, dolist);
	    break;
	case IDX_CHILDINST:
	    (*func)(cellname, ((is_cellname == FALSE) ? CHILDREN : CHILDINST), dolist);
	    break;
	case IDX_PARENTS:
	    (*func)(cellname, PARENTS, dolist);
	    break;
	case IDX_MODIFIED:
	    (*func)(cellname, MODIFIED, dolist);
	    break;
	case IDX_PROPERTY:
	    if (cellname == NULL)
		cellDef = EditRootDef;
	    else
		cellDef = DBCellLookDef(cellname);

	    if (cellDef == (CellDef *) NULL)
		TxError("Unknown cell %s\n", cellname);
	    else
		CmdDoProperty(cellDef, w, cmd, 3 + ((dolist) ? 1 : 0));
	    break;

	case IDX_DELETE:
	    /* Unload the cell definition and free memory */
	    /* Make sure selections are cleared or they may	*/
	    /* contain references to the deleted cell def.	*/
	    if ((locargc == 4) && !strcmp(cmd->tx_argv[3 + ((dolist) ? 1 : 0)],
			"-noprompt"))
	    {
		SelectClear();
	        DBCellDelete(cellname, TRUE);
	    }
	    else if (locargc == 3)
	    {
		SelectClear();
	        DBCellDelete(cellname, FALSE);
	    }
	    else
		TxError("Delete cell command missing cellname\n");
	    break;

	case IDX_DEREFERENCE:
	    /* Unload the cell definition and re-read with search paths */
	    if (locargc == 3)
	    {
		if (cellname == NULL)
		    cellDef = EditRootDef;
		else
		    cellDef = DBCellLookDef(cellname);

		/* Reload cell with dereferencing */
		if (cellDef == NULL)
		{
		    TxError("No such cell \"%s\"\n", cellname);
		    break;
		}
		cmdFlushCell(cellDef, TRUE);
		SelectClear();
	    }
	    else
		TxError("Dereference cell command missing cellname\n");
	    break;

	case IDX_READWRITE:
	    if (cellname == NULL)
		cellDef = EditRootDef;
	    else
		cellDef = DBCellLookDef(cellname);
	    if (cellDef == (CellDef *) NULL)
	    {
		TxError("Unknown cell %s\n", cellname);
		break;
	    }

	    if (locargc == 3)
	    {
		if (cellDef->cd_flags & CDNOEDIT)
#ifdef MAGIC_WRAPPER
		    if (dolist)
			Tcl_SetResult(magicinterp, "read-only", 0);
		    else
#endif
		    TxPrintf("read-only\n");
		else
#ifdef MAGIC_WRAPPER
		    if (dolist)
			Tcl_SetResult(magicinterp, "writeable", 0);
		    else
#endif
		    TxPrintf("writeable\n");
	    }
	    else if (locargc == 4)
	    {
	    	int subopt = Lookup(cmd->tx_argv[3 + ((dolist) ? 1 : 0)],
					cmdCellnameYesNo);
	    	if (subopt < 0) goto badusage;
		else if (subopt >= 4)
		{
		    /* Check if file is already read-write */
#ifdef FILE_LOCKS
		    if (!(cellDef->cd_flags & CDNOEDIT) && (cellDef->cd_fd != -2))
#else
		    if (!(cellDef->cd_flags & CDNOEDIT))
#endif
			break;

		    /* Make file read-write */
#ifdef FILE_LOCKS
		    if (cellDef->cd_fd < 0)
			DBOpenOnly(cellDef, NULL, TRUE, NULL);

		    if (cellDef->cd_fd == -2)
		    {
			TxError("An advisory lock is held on cell %s.  Cell can now"
				" be made editable but is not writeable.\n",
				cellDef->cd_name);
		    }
#endif
		    if (cellDef->cd_flags & CDNOEDIT)
		    {
			cellDef->cd_flags &= ~CDNOEDIT;
			WindAreaChanged(w, &w->w_screenArea);
			CmdSetWindCaption(EditCellUse, EditRootDef);
		    }
		}
		else	/* "cellname writeable false" */
		{
		    /* Check if file is already read-only */
		    if (cellDef->cd_flags & CDNOEDIT)
			break;

		    /* Make file read-only */

		    cellDef->cd_flags |= CDNOEDIT;

#ifdef FILE_LOCKS
		    /* Release any advisory lock held on this file */

		    if (cellDef->cd_fd >= 0)
		    {
			close(cellDef->cd_fd);
			cellDef->cd_fd = -1;	 /* Set to initial state */
		    }
#endif

		    if (EditCellUse && (EditCellUse->cu_def == cellDef))
			EditCellUse = (CellUse *)NULL;

		    if (EditRootDef == cellDef)
			EditRootDef = (CellDef *)NULL;

		    WindAreaChanged(w, &w->w_screenArea);
		    CmdSetWindCaption(EditCellUse, (CellDef *)NULL);
		}
	    }
	    break;

	case IDX_FILEPATH:
	    if (cellname == NULL)
		cellDef = EditRootDef;
	    else
		cellDef = DBCellLookDef(cellname);
	    if (cellDef == (CellDef *) NULL)
	    {
		TxError("Unknown cell %s\n", cellname);
		break;
	    }
	    if (locargc <= 3)
	    {
		if (cellDef->cd_file == NULL)
		{
#ifdef MAGIC_WRAPPER
		    if (dolist)
			Tcl_SetResult(magicinterp, "default", 0);
		    else
#endif
			TxPrintf("default\n");
		}
		else
		{
		    char *pathend;

		    pathend = strrchr(cellDef->cd_file, '/');
		    if (pathend) *pathend = '\0';
#ifdef MAGIC_WRAPPER
		    if (dolist)
			Tcl_SetResult(magicinterp, cellDef->cd_file, 0);
		    else
#endif
		 	TxPrintf("%s\n", cellDef->cd_file);

		    if (pathend) *pathend = '/';
		}
	    }
	    else if (locargc == 4)
	    {
		char *filepath;

		filepath = cmd->tx_argv[3 + ((dolist) ? 1 : 0)];
		if (!strcmp(filepath, "default"))
		{
		    if (cellDef->cd_file != NULL)
		    {
			freeMagic(cellDef->cd_file);
			cellDef->cd_file = NULL;
		    }
		}
		else
		{
		    char *fullpath;
		    fullpath = (char *)mallocMagic(strlen(filepath) +
			strlen(cellDef->cd_name) + 2);
		    sprintf(fullpath, "%s/%s", filepath, cellDef->cd_name);

		    if (cellDef->cd_file != NULL)
			freeMagic(cellDef->cd_file);

		    cellDef->cd_file = fullpath;
		}
	    }
	    break;

	case IDX_TIMESTAMP:
	    if (cellname == NULL)
		cellDef = EditRootDef;
	    else
		cellDef = DBCellLookDef(cellname);
	    if (cellDef == (CellDef *) NULL)
	    {
		TxError("Unknown cell %s\n", cellname);
		break;
	    }
	    if ((locargc == 3) || (locargc == 2 && cellname == NULL))
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewIntObj(cellDef->cd_timestamp));
#else
	    	TxPrintf("Timestamp for cell %s = %d\n", cellname, cellDef->cd_timestamp);
#endif
	    }
	    else if (locargc == 4 || (locargc == 3 && cellname == NULL))
	    {
		int timestamp;
		if (StrIsInt(cmd->tx_argv[2 + ((cellname == NULL) ? 0 : 1)]))
		{
		    timestamp = atoi(cmd->tx_argv[2 + ((cellname == NULL) ? 0 : 1)]);
		    if (timestamp != cellDef->cd_timestamp)
		    {
			cellDef->cd_timestamp = timestamp;
			cellDef->cd_flags &= ~CDGETNEWSTAMP | CDFIXEDSTAMP;
		    }
		}
		else
		{
		    TxError("Invalid timestamp \"%s\".\n",
			cmd->tx_argv[2 + ((cellname == NULL) ? 0 : 1)]);
		}
	    }
	    break;

	case IDX_FLAGS:
	    if (cellname == NULL)
		cellDef = EditRootDef;
	    else
		cellDef = DBCellLookDef(cellname);
	    if (cellDef == (CellDef *) NULL)
	    {
		TxError("Unknown cell %s\n", cellname);
		break;
	    }
#ifdef MAGIC_WRAPPER
	    /* All of the flags are included for completeness.  A number
	     * of these flags are set in self-contained routines and will
	     * never appear as the result of the "cellname flags" command.
	     * Mainly for diagnostics (in case a routine is not cleaning
	     * up its own flag settings, for example).
	     */
	    if (cellDef->cd_flags & CDAVAILABLE)
		Tcl_AppendElement(magicinterp, "available");
	    if (cellDef->cd_flags & CDMODIFIED)
		Tcl_AppendElement(magicinterp, "modified");
	    if (cellDef->cd_flags & CDNOEDIT)
		Tcl_AppendElement(magicinterp, "readonly");
	    if (cellDef->cd_flags & CDINTERNAL)
		Tcl_AppendElement(magicinterp, "internal");
	    if (cellDef->cd_flags & CDFIXEDSTAMP)
		Tcl_AppendElement(magicinterp, "fixed-timestamp");
	    if (cellDef->cd_flags & CDGETNEWSTAMP)
		Tcl_AppendElement(magicinterp, "timestamp-pending");
	    if (cellDef->cd_flags & CDSTAMPSCHANGED)
		Tcl_AppendElement(magicinterp, "child-timestamp-changed");
	    if (cellDef->cd_flags & CDFIXEDBBOX)
		Tcl_AppendElement(magicinterp, "fixed-bbox");
	    if (cellDef->cd_flags & CDBOXESCHANGED)
		Tcl_AppendElement(magicinterp, "bbox-changed");
	    if (cellDef->cd_flags & CDPROCESSED)
		Tcl_AppendElement(magicinterp, "bbox-processed");
	    if (cellDef->cd_flags & CDFLATGDS)
		Tcl_AppendElement(magicinterp, "flat-gds");
	    if (cellDef->cd_flags & CDFLATTENED)
		Tcl_AppendElement(magicinterp, "flattened-gds");
	    if (cellDef->cd_flags & CDPROCESSEDGDS)
		Tcl_AppendElement(magicinterp, "processed-gds");
	    if (cellDef->cd_flags & CDVENDORGDS)
		Tcl_AppendElement(magicinterp, "vendor-gds");
	    if (cellDef->cd_flags & CDVISITED)
		Tcl_AppendElement(magicinterp, "visited");
	    if (cellDef->cd_flags & CDDEREFERENCE)
		Tcl_AppendElement(magicinterp, "dereference");
	    if (cellDef->cd_flags & CDNOTFOUND)
		Tcl_AppendElement(magicinterp, "not-found");
#else
	    TxPrintf("Flag settings for cell %s:\n", cellname);
	    TxPrintf("in database: %s\n", (cellDef->cd_flags & CDAVAILABLE) ?
			"true" : "false");
	    TxPrintf("modified: %s\n", (cellDef->cd_flags & CDMODIFIED) ?
			"true" : "false");
	    TxPrintf("readonly: %s\n", (cellDef->cd_flags & CDNOEDIT) ?
			"true" : "false");
	    TxPrintf("vendor: %s\n", (cellDef->cd_flags & CDVENDORGDS) ?
			"true" : "false");
	    TxPrintf("fixed-bbox: %s\n", (cellDef->cd_flags & CDFIXEDBBOX) ?
			"true" : "false");
	    TxPrintf("fixed-timestamp: %s\n", (cellDef->cd_flags & CDFIXEDSTAMP) ?
			"true" : "false");
#endif
	    break;
	case IDX_RENAME:
	    /* Rename the cell and mark as modified.  Do not write to disk. */
	    if ((locargc == 3) && (EditRootDef != NULL))
	    {
		/* Assume cellname is the target name, and the current	*/
		/* cell is the one to have its name changed.		*/
		cellDef = EditRootDef;
		DBCellRename(cellDef->cd_name, cellname, doforce);
		break;
	    }
	    else if (locargc != 4) goto badusage;
	    DBCellRename(cellname, cmd->tx_argv[3 + ((dolist) ? 1 : 0)], doforce);
	    break;
	case IDX_CREATE:
	    newDef = DBCellLookDef(cellname);
	    if (newDef == (CellDef *) NULL)
	    {
		newDef = DBCellNewDef(cellname);
		DBCellSetAvail(newDef);
	    }
	    break;
	case IDX_ORIENTATION:
	    orient = (locargc == 4) ? cmd->tx_argv[3 + ((dolist) ? 1 : 0)] : NULL;
	    if ((cellname != NULL) && (orient != NULL))
	    {
		TxError("Cannot set orientation by name.  Use selection.\n");
		break;
	    }
	    /* FIXME reading the function documentation above DBOrientUse() this method
	     *  is only used to report orientation.  The selection interface should be
	     *  used to change orientation.  Which is conveyed in the TxError() above.
	     * So the 3rd argument here does not do anything.
	     * Maybe the intention here is to display the current orientation, if so
	     *  please remove this comment and the extra argument 'orient' below.
	     */
	    DBOrientUse(cellname, dodef/*, orient*/);
	    break;
	case IDX_ABUTMENT:
	    DBAbutmentUse(cellname, dolist);
	    break;
	case IDX_LOCK:
	    DBLockUse(cellname, TRUE);
	    break;
	case IDX_UNLOCK:
	    DBLockUse(cellname, FALSE);
	    break;
    }
    return;

badusage:
    TxError("Usage: %s [list] children|parents|self|exists|"
		"instances|celldef|delete [name]\n", cmd->tx_argv[0]);
    TxError("or:    %s [list] allcells|topcells|window\n", cmd->tx_argv[0]);
    TxError("or:    %s create name\n", cmd->tx_argv[0]);
    TxError("or:    %s rename name newname [-force]\n", cmd->tx_argv[0]);
    TxError("or:    %s [un]lock [name]\n", cmd->tx_argv[0]);
    TxError("or:    %s writeable [name] [true|false]\n", cmd->tx_argv[0]);
    return;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdCif --
 *
 * Implement the "cif" command.
 *
 * Usage:
 *	cif option args
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	There are no side effects on the circuit.  Various options
 *	may produce cif files, read cif, or display cif information
 *	on the screen.
 *
 * ----------------------------------------------------------------------------
 */

#ifdef CIF_MODULE

#define ARRAY		 0
#define HIER		 1
#define	AREALABELS	 2
#define COVERAGE	 3
#define CIF_DRC_CHECK	 4
#define HELP		 5
#define IDCELL		 6
#define ISTYLE		 7
#define CIF_LAMBDA	 8
#define CIF_LIMIT	 9
#define OSTYLE		10
#define CIF_PAINT	11
#define PREFIX		12
#define READ		13
#define RESCALE		14
#define CIF_SCALE	15
#define SEE		16
#define STATS		17
#define WARNING		18
#define CIF_WRITE	19
#define CIF_WRITE_FLAT	20
#define POLYGONS	21
#define UNFRACTURE	22

#define CIF_WARN_HELP  CIF_WARN_END	/* undefined by CIF module */

void
CmdCif(
    MagWindow *w,
    TxCommand *cmd)
{
    int option, yesno;
    const char * const *msg;
    char *namep;
    CellDef *rootDef, *paintDef;
    Rect box;
    TileType layer;
    TileTypeBitMask mask;
    FILE *f;
    bool wizardHelp;
    bool flatCif = FALSE;
    bool dolist = FALSE;
    bool doforall = FALSE;
    float curscale;
    int argc = cmd->tx_argc;
    int argshift;
    char **argv = cmd->tx_argv;

    static const char * const cmdCifWarnOptions[] = { "default", "none", "align",
		"limit", "redirect", "help", 0 };
    static const char * const cmdCifYesNo[] = { "no", "yes", 0 };
    static const char * const cmdCifInOut[] = { "input", "output", 0 };
    static const char * const cmdCifOption[] =
    {
	"*array layer		display CIF layer under box (array only)",
	"*hier layer		display CIF layer under box (hier only)",
	"arealabels yes|no	enable/disable us of area label extension",
	"coverage layer		print area coverage of indicated layer",
	"drccheck [yes|no]	mark all cells as needing DRC checking",
	"help		print this help information",
	"idcell yes|no	enable/disable use of cell ID extension",
	"istyle [style]	change style for reading CIF to style",
	"lambda in|out	show microns per lambda for the current style",
	"limit [value]	limit internal grid subdivision by scalefactor value",
	"ostyle [style]	change style for writing CIF to style",
	"paint ciflayer magiclayer [cellname]\n"
	"			generate CIF and paint into cell",
	"prefix [path]	prepend path to cell names in CIF output",
	"read file		read CIF from \"file\" into edit cell",
	"rescale [yes|no]	allow/disallow rescaling of internal grid",
	"scale in|out	show microns per internal units for the current style",
	"see layer		display CIF layer under box",
	"statistics		print out statistics for CIF generator",
	"warning [option]	set warning display options",
	"write file		output CIF for the window's root cell to \"file\"",
	"flat file		output flattened CIF for "
				"the window's root cell to \"file\"",
	"polygon subcells [yes|no]\n"
	"			put non-Manhattan polygons in subcells",
	"unfracture [yes|no]\n"
	"			optimize non-Manhattan geometry",
	NULL
    };

    if (argc == 1)
	option = CIF_WRITE;
    else
    {
	if (!strncmp(argv[1], "list", 4))
	{
	    dolist = TRUE;
	    if (!strncmp(argv[1], "listall", 7))
	    {
		doforall = TRUE;
	    }
	    argv++;
	    argc--;
	}
	option = Lookup(argv[1], cmdCifOption);
	if (option < 0)
	{
	    TxError("\"%s\" isn't a valid cif option.\n", argv[1]);
	    option = HELP;
	    argc = 2;
	}
    }

    /* Only check for the presence of a window on options requiring one */

    switch (option)
    {
	case HELP: case ISTYLE: case OSTYLE: case PREFIX:
	case AREALABELS: case WARNING: case CIF_LIMIT:
	case POLYGONS:
	   break;
	default:
	    windCheckOnlyWindow(&w, DBWclientID);
	    if (w == (MagWindow *) NULL)
	    {
		TxError("Point to a window first\n");
		return;
	    }
	    rootDef = ((CellUse *) w->w_surfaceID)->cu_def;

	    if (argc == 1)
	    {
		namep = strrchr(rootDef->cd_name, '/');
		if (namep == (char *) NULL)
		    namep = rootDef->cd_name;
		goto outputCIF;
	    }
    }

    switch (option)
    {
	case ARRAY:
	    if (argc == 4) {
	        if (!strncmp(argv[2], "write", 5))
		{
		    if (!strncmp(argv[3], "dis", 3))
			CIFArrayWriteDisable = TRUE;
		    else if (!strncmp(argv[3], "en", 2))
			CIFArrayWriteDisable = FALSE;
		    else
			TxPrintf("Subcell interaction output %s\n",
				(CIFArrayWriteDisable) ? "disabled" : "enabled");
		    return;
		}
		else goto wrongNumArgs;
	    }
	    else if (argc != 3)
	    {
		wrongNumArgs:
		TxError("Wrong arguments in \"cif %s\" command:\n",
		    argv[1]);
		TxError("    :cif %s\n", cmdCifOption[option]);
		TxError("Try \":cif help\" for more help.\n");
		return;
	    }
	    if (!ToolGetBox(&rootDef, &box))
	    {
		TxError("Use the box to select the area in");
		TxError(" which you want to see CIF.\n");
		return;
	    }
	    CIFSeeHierLayer(rootDef, &box, argv[2], TRUE, FALSE);
	    return;

	case HIER:
	    if (argc == 4) {
	        if (!strncmp(argv[2], "write", 5))
		{
		    if (!strncmp(argv[3], "dis", 3))
			CIFHierWriteDisable = TRUE;
		    else if (!strncmp(argv[3], "en", 2))
			CIFHierWriteDisable = FALSE;
		    else
			TxPrintf("Subcell interaction output %s\n",
				(CIFHierWriteDisable) ? "disabled" : "enabled");
		    return;
		}
		else goto wrongNumArgs;
	    }
	    else if (argc != 3) goto wrongNumArgs;
	    if (!ToolGetBox(&rootDef, &box))
	    {
		TxError("Use the box to select the area in");
		TxError(" which you want to see CIF.\n");
		return;
	    }
	    CIFSeeHierLayer(rootDef, &box, argv[2], FALSE, TRUE);
	    return;

	case COVERAGE:
	    if ((argc == 4) && !strcmp(argv[3], "box"))
	    {
		if (!ToolGetBox(&rootDef, &box))
		{
		    TxError("Box requested but no cursor box exists\n");
		    return;
		}
		CIFCoverageLayer(rootDef, &box, argv[2], dolist);
	    }
	    else if (argc == 3)
		CIFCoverageLayer(rootDef, &rootDef->cd_bbox, argv[2], dolist);
	    else
		goto wrongNumArgs;

	    return;

	case CIF_LAMBDA:
	case CIF_SCALE:
	    if (argc != 3) goto wrongNumArgs;
	    yesno = Lookup(argv[2], cmdCifInOut); /* "input" or "output" */
	    if (yesno < 0)
		goto wrongNumArgs;
	    if (yesno == 0)		/* option "input" */
		curscale = CIFGetInputScale(1000);
	    else
		curscale = CIFGetOutputScale(1000);

	    if (option == CIF_LAMBDA)
	    {
		TxPrintf("Warning:  \"cif lambda\" is deprecated;  use "
			"\"cif scale\" instead.  Units are not in lambda.\n");
	    }
#ifdef MAGIC_WRAPPER
	    Tcl_SetObjResult(magicinterp, Tcl_NewDoubleObj((double)curscale));
#else
	    TxPrintf("One %s internal unit is %2.1f microns.\n", (yesno) ?
			"output" : "input", curscale);
#endif
	    return;

	case CIF_LIMIT:
	    if (argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewIntObj(CIFRescaleLimit));
#else
		TxPrintf("Rescale limit is %d\n", CIFRescaleLimit);
#endif
		return;
	    }
	    else if (argc != 3)
		goto wrongNumArgs;
	    else if (!StrIsInt(argv[2]))
		goto wrongNumArgs;

	    CIFRescaleLimit = atoi(argv[2]);
	    if (CIFRescaleLimit > CIFMAXRESCALE)
	    {
		CIFRescaleLimit = CIFMAXRESCALE;
		TxError("Warning:  rescale limit set at maximum value of %d\n",
			CIFMAXRESCALE);
	    }
	    else if (CIFRescaleLimit < 1)
	    {
		CIFRescaleLimit = 1;
		TxError("Warning:  rescale limit set at minimum value of 1\n");
	    }
	    return;

	case AREALABELS:
	    if (argc > 3) goto wrongNumArgs;
	    if (argc == 3)
	    {
		yesno = Lookup(argv[2], cmdCifYesNo);
		if (yesno < 0)
		    goto wrongNumArgs;
		CIFDoAreaLabels = yesno;
	    }
	    TxPrintf("Output of CIF area labels is now %s\n",
		CIFDoAreaLabels ? "enabled" : "disabled");
	    return;

	case HELP:
	    if ((argc == 3)
		    && (strcmp(argv[2], "wizard") == 0))
		wizardHelp = TRUE;
	    else wizardHelp = FALSE;
	    TxPrintf("CIF commands have the form \":cif option\",");
	    TxPrintf(" where option is one of:\n");
	    for (msg = &(cmdCifOption[0]); *msg != NULL; msg++)
	    {
		if ((**msg == '*') && !wizardHelp) continue;
		TxPrintf("    %s\n", *msg);
	    }
	    TxPrintf("If no option is given, CIF is output for the");
	    TxPrintf(" root cell.\n");
	    return;

	case IDCELL:
	    if (argc > 3) goto wrongNumArgs;
	    if (argc == 3)
	    {
		yesno = Lookup(argv[2], cmdCifYesNo);
		if (yesno < 0)
		    goto wrongNumArgs;
		CIFDoCellIdLabels = yesno;
	    }
	    TxPrintf ("Output of CIF cell ID labels is now %s\n",
		CIFDoCellIdLabels ? "enabled" : "disabled");
	    return;

	case ISTYLE:
	    if (argc == 3)
		CIFSetReadStyle(argv[2]);
	    else if (argc == 2)
		CIFPrintReadStyle(dolist, doforall, !doforall);
	    else goto wrongNumArgs;
	    return;

	case POLYGONS:
	    if (argc == 3)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CIFSubcellPolygons));
#else
		if (CIFSubcellPolygons)
		    TxPrintf("Non-manhattan polygons placed in subcells.\n");
		else
		    TxPrintf("Non-manhattan polygons read as-is.\n");
#endif
		return;
	    }
	    else if (argc != 4)
		goto wrongNumArgs;

	    yesno = Lookup(argv[3], cmdCifYesNo);
	    if (yesno < 0)
		goto wrongNumArgs;
	    CIFSubcellPolygons = yesno;
	    return;

	case UNFRACTURE:
	    if (argc == 3)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CIFUnfracture));
#else
		if (CIFUnfracture)
		    TxPrintf("Non-manhattan geometry optimized.\n");
		else
		    TxPrintf("Non-manhattan geometry not optimized.\n");
#endif
		return;
	    }
	    else if (argc != 4)
		goto wrongNumArgs;

	    yesno = Lookup(argv[3], cmdCifYesNo);
	    if (yesno < 0)
		goto wrongNumArgs;
	    CIFUnfracture = yesno;
	    return;

	case PREFIX:
	    if (argc == 2)
		StrDup (&CIFPathPrefix, NULL);
	    else if (argc == 3)
		StrDup (&CIFPathPrefix, argv[2]);
	    else goto wrongNumArgs;
	    return;

	case OSTYLE:
	    if (argc == 3)
		CIFSetStyle(argv[2]);
	    else if (argc == 2)
		CIFPrintStyle(dolist, doforall, !doforall);
	    else goto wrongNumArgs;
	    return;

	case READ:
	    if (argc != 3) goto wrongNumArgs;
	    f = PaOpen(argv[2], "r", ".cif", Path,
		    (char *) NULL, (char **) NULL);
	    if (f == (FILE *) NULL)
	    {
		TxError("Cannot open %s.cif to read CIF.\n", argv[2]);
		return;
	    }
	    CIFReadFile(f);
	    (void) fclose(f);
	    return;

	case RESCALE:
	    if (argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(CIFRescaleAllow));
#else
		TxPrintf("Internal grid rescaling %sallowed\n",
			(CIFRescaleAllow) ?  "" : "dis");
#endif
		return;
	    }
	    else if (argc != 3)
		goto wrongNumArgs;

	    yesno = Lookup(argv[2], cmdCifYesNo);
	    if (yesno < 0)
		goto wrongNumArgs;
	    CIFRescaleAllow = yesno;
	    if (!CIFRescaleAllow)
		CIFWarningLevel = CIF_WARN_LIMIT;
	    return;

	case CIF_DRC_CHECK:
	    if (argc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp, Tcl_NewBooleanObj(!CIFNoDRCCheck));
#else
		TxPrintf("CIF cells are marked as%s requiring DRC checks.\n",
			(CIFNoDRCCheck) ?  " not" : " ");
#endif
		return;
	    }
	    else if (argc != 3)
		goto wrongNumArgs;

	    yesno = Lookup(argv[2], cmdCifYesNo);
	    if (yesno < 0)
		goto wrongNumArgs;
	    CIFNoDRCCheck = !yesno;
	    return;

	case SEE:
	    if (argc != 3) goto wrongNumArgs;
	    if (!ToolGetBox(&rootDef, &box))
	    {
		TxError("Use the box to select the area in");
		TxError(" which you want to see CIF.\n");
		return;
	    }
	    CIFSeeLayer(rootDef, &box, argv[2]);
	    return;

	case CIF_PAINT:
	    if ((argc != 4) && (argc != 5)) goto wrongNumArgs;
	    if (!ToolGetBox(&rootDef, &box))
	    {
		TxError("Use the box to select the area in");
		TxError(" which you want to paint CIF.\n");
		return;
	    }
	    if (argc == 5)
	    {
		paintDef = DBCellLookDef(argv[4]);
		if (paintDef == (CellDef *)NULL)
		{
		    paintDef = DBCellNewDef(argv[4]);
		    DBCellSetAvail(paintDef);
		}
	    }
	    else
		paintDef = rootDef;

	    layer = DBTechNoisyNameType(argv[3]);
	    if (layer >= TT_TECHDEPBASE)
	    {
		CIFPaintLayer(rootDef, &box, argv[2], layer, paintDef);

		/* Refresh the layout drawing */
		TTMaskSetOnlyType(&mask, layer);
		DBWAreaChanged(paintDef, &box, DBW_ALLWINDOWS, &mask);
		DRCCheckThis(paintDef, TT_CHECKPAINT, &box);
	    }
	    return;

	case STATS:
	    CIFPrintStats();
	    return;

	case WARNING:
	    if (argc == 2)
	    {
		TxPrintf("Warning display options: %s\n",
			cmdCifWarnOptions[CIFWarningLevel]);
	    }
	    else
	    {
		int suboption = Lookup(argv[2], cmdCifWarnOptions);
		if (suboption < 0)
		{
		    TxError("\"%s\" isn't a valid cif warning option.\n", argv[2]);
		    suboption = CIF_WARN_HELP;
		}
		if (suboption == CIF_WARN_HELP)
		    TxError("Valid options are: default, align, limit, "
			"redirect, and none\n");
		else
		    CIFWarningLevel = suboption;
		if (suboption == CIF_WARN_REDIRECT)
		{
		    if (argc == 4)
			StrDup(&CIFErrorFilename, argv[3]);
		    else
			StrDup(&CIFErrorFilename, NULL);
		}
	    }
	    return;

	case CIF_WRITE:
	    if (argc != 3) goto wrongNumArgs;
	    namep = argv[2];
	    goto outputCIF;

	case CIF_WRITE_FLAT:
	    if (argc != 3) goto wrongNumArgs;
	    namep = argv[2];
	    flatCif = TRUE;
	    goto outputCIF;
    }

    /* If control gets here, we're going to output CIF into the
     * file given by namep.
     */

    outputCIF:
    f = PaOpen(namep, "w", ".cif", ".", (char *) NULL, (char **) NULL);
    if (f == (FILE *) NULL)
    {
	TxError("Cannot open %s.cif to write CIF\n", namep);
	return;
    }
    if (flatCif == TRUE)
    {
	if (!CIFWriteFlat(rootDef, f))
	{
	    TxError("I/O error in writing file %s.\n", namep);
	    TxError("File may be incompletely written.\n");

	}
    }
    else
    {
	if (!CIFWrite(rootDef, f))
	{
	    TxError("I/O error in writing file %s.\n", namep);
	    TxError("File may be incompletely written.\n");

	}
    }
    (void) fclose(f);
}
#endif

/*
 * ----------------------------------------------------------------------------
 *
 * CmdClockwise --
 *
 * Implement the "clockwise" command.  Rotate the selection and the
 * box clockwise around the point.
 *
 * Usage:
 *	clockwise [degrees] [-origin]	or
 *	rotate [degrees] [-origin]
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Modifies the edit cell.
 *
 * Notes:
 *	"rotate" has been added as an alias for "clockwise" because the
 *	usual shorthand "clock" conflicts with the built-in Tcl command
 *	in the Tcl version.  "clockwise" is still kept for backward
 *	compatibility but the command must contain at least "clockw" for
 *	the interpreter to uniquely identify it.
 *
 *	The "-origin" option has been added to allow rotating stuff
 *	relative to the origin, instead of the lower-left corner.
 *
 * ----------------------------------------------------------------------------
 */

void
CmdClockwise(
    MagWindow *w,
    TxCommand *cmd)
{
    Transform trans, t2;
    int degrees, locargc;
    Rect rootBox,  bbox;
    CellDef *rootDef;
    bool noAdjust = FALSE;

    locargc = cmd->tx_argc;
    if (!strncmp(cmd->tx_argv[locargc - 1], "-orig", 5))
    {
	noAdjust = TRUE;
	locargc--;
    }

    if (locargc == 1)
	degrees = 90;
    else if (locargc == 2)
    {
	if (!StrIsInt(cmd->tx_argv[1])) goto badusage;
	degrees = atoi(cmd->tx_argv[1]);
    }
    else goto badusage;

    if (!ToolGetEditBox((Rect *)NULL)) return;
    if (degrees < 0) degrees += 360;

    switch (degrees)
    {
	case 90:
	    t2 = Geo90Transform;
	    break;
	case 180:
	    t2 = Geo180Transform;
	    break;
	case 270:
	    t2 = Geo270Transform;
	    break;
	default:
	    TxError("Rotation angle must be 90, 180, or 270 degrees\n");
	    return;
    }


    /* To rotate the selection, first rotate it around the origin
     * then move it so its lower-left corner is at the same place
     * that it used to be.
     */

    if (noAdjust)
    {
	GeoTransRect(&t2, &SelectDef->cd_bbox, &bbox);
	trans = t2;
    }
    else
    {
	GeoTransRect(&t2, &SelectDef->cd_bbox, &bbox);
	GeoTranslateTrans(&t2, SelectDef->cd_bbox.r_xbot - bbox.r_xbot,
		SelectDef->cd_bbox.r_ybot - bbox.r_ybot, &trans);
    }

    SelectTransform(&trans);

    /* Rotate the box, if it exists and is in the same window as the
     * selection.
     */

    if (ToolGetBox(&rootDef, &rootBox) && (rootDef == SelectRootDef))
    {
	Rect newBox;

	GeoTransRect(&trans, &rootBox, &newBox);
	DBWSetBox(rootDef, &newBox);
    }

    return;

    badusage:
    TxError("Usage: %s [degrees]\n", cmd->tx_argv[0]);
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdContact --
 *
 * Implement the "contact" command.
 *
 * Usage:
 *	contact <type>
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The area in the box is searched for the intersection of all residues
 *	of the contact type <type>.  The intersecting areas are replaced with
 *	the contact type.
 *
 * ----------------------------------------------------------------------------
 */

typedef struct CCS
{
    CellDef *rootDef;
    TileTypeBitMask *rmask;
    TileType tstart;
    Rect area;
    Rect clip;
    LinkedRect *lhead;
} CCStruct;

void
CmdContact(
    MagWindow *w,
    TxCommand *cmd)
{
    TileType type, rtype, ctype;
    TileTypeBitMask *rmask, smask;
    CCStruct ccs;
    Rect area;
    LinkedRect *lr = NULL;

    /* Forward declarations */
    int cmdContactFunc(Tile *tile, TileType dinfo, CCStruct *ccs);
    int cmdContactEraseFunc(Tile *tile, TileType dinfo, LinkedRect **lr);

    windCheckOnlyWindow(&w, DBWclientID);
    if ((w == (MagWindow *) NULL) || (w->w_client != DBWclientID))
    {
	TxError("Put the cursor in a layout window\n");
	return;
    }

    if ((cmd->tx_argc != 2) && (cmd->tx_argc != 3))
    {
	TxError("Usage: %s <contact_type>\n", cmd->tx_argv[0]);
	return;
    }
    if (!ToolGetEditBox(&area)) return;

    if (EditCellUse == NULL)
    {
	TxError("The cell in the window is not editable.\n");
	return;
    }

    if (cmd->tx_argc == 3)
    {
	if (!strcmp(cmd->tx_argv[1], "erase"))
	{
	    /* Erase a contact from the area of the box.  This acts	*/
	    /* differently from the "erase" command.  "erase" will 	*/
	    /* remove the contact and leave nothing behind.  "contact	*/
	    /* erase" will remove the contact and leave the residues	*/
	    /* behind.	The main difference is that this action cannot	*/
	    /* be done on a whole area;  the individual contacts need	*/
	    /* to be enumerated and the erase/paint done on each one.	*/

	    type = DBTechNoisyNameType(cmd->tx_argv[2]);

	    if (!DBIsContact(type))
	    {
	    	TxError("Error:  tile type \"%s\" is not a contact.\n",
			cmd->tx_argv[2]);
	    	return;
	    }
	    TTMaskSetOnlyType(&smask, type);

	    /* Add all stacked contact types containing "type" */
	    for (ctype = DBNumUserLayers; ctype < DBNumTypes; ctype++)
	    {
		rmask = DBResidueMask(ctype);
		if (TTMaskHasType(rmask, type))
		    TTMaskSetType(&smask, ctype);
	    }

	    /* Enumerate all tiles inside the box area containing contact "type" */
	    DBSrPaintArea((Tile *) NULL, EditCellUse->cu_def->cd_planes[DBPlane(type)],
			&area, &smask, cmdContactEraseFunc, (ClientData)&lr);

	    rmask = DBResidueMask(type);

	    free_magic1_t mm1 = freeMagic1_init();
	    while (lr != NULL)
	    {
    		GeoClip(&lr->r_r, &area);

		/* Erase contact type "type" and repaint with the residues */
		DBErase(EditCellUse->cu_def, &lr->r_r, type);
		for (rtype = 0; rtype < DBNumUserLayers; rtype++)
	    	    if (TTMaskHasType(rmask, rtype))
			DBPaint(EditCellUse->cu_def, &lr->r_r, rtype);

		freeMagic1(&mm1, lr);
		lr = lr->r_next;
	    }
	    freeMagic1_end(&mm1);

	    /* Refresh the layout drawing */
	    DBWAreaChanged(EditCellUse->cu_def, &area, DBW_ALLWINDOWS, &smask);
	    DRCCheckThis (EditCellUse->cu_def, TT_CHECKPAINT, &area);
	}
	else
	    TxError("Usage: %s erase <contact_type>\n", cmd->tx_argv[0]);
	return;
    }
    
    type = DBTechNoisyNameType(cmd->tx_argv[1]);
    if (type >= 0)
    {
	if (!DBIsContact(type))
	{
	    TxError("Error:  tile type \"%s\" is not a contact.\n",
			cmd->tx_argv[1]);
	    return;
	}

	/* Find the first residue type */
	rmask = DBResidueMask(type);
	for (rtype = 0; rtype < DBNumUserLayers; rtype++)
	    if (TTMaskHasType(rmask, rtype))
		break;

	/* Find all tiles of one residue type */
	ccs.lhead = (LinkedRect *)NULL;
	ccs.rmask = rmask;
	ccs.tstart = rtype;
	ccs.rootDef = EditCellUse->cu_def;
	ccs.clip = area;
	TTMaskSetOnlyType(&smask, rtype);
	DBSrPaintArea((Tile *) NULL, EditCellUse->cu_def->cd_planes[DBPlane(rtype)],
		&area, &smask, cmdContactFunc, (ClientData) &ccs);

	free_magic1_t mm1 = freeMagic1_init();
	while (ccs.lhead != NULL)
	{
	    TTMaskSetOnlyType(&smask, type);
	    TTMaskAndMask(&smask, &DBActiveLayerBits);
	    DBPaintMask(EditCellUse->cu_def, &ccs.lhead->r_r, &smask);
	    freeMagic1(&mm1, ccs.lhead);
	    ccs.lhead = ccs.lhead->r_next;
	}
	freeMagic1_end(&mm1);

	/* Refresh the layout drawing */
	DBWAreaChanged(EditCellUse->cu_def, &area, DBW_ALLWINDOWS, &smask);
	DRCCheckThis (EditCellUse->cu_def, TT_CHECKPAINT, &area);
    }
}

/* For each tile in the first residue type, search for tiles of the
 * next residue type, and add the intersection of areas to the linked
 * rectangle list.
 */

int
cmdContactFunc(
    Tile *tile,
    TileType dinfo,
    CCStruct *ccs)
{
    TileType stype;
    TileTypeBitMask smask;

    /* Forward declaration */
    int cmdContactFunc2(Tile *tile, TileType dinfo, CCStruct *ccs);

    TiToRect(tile, &ccs->area);
    GeoClip(&ccs->area, &ccs->clip);

    for (stype = ccs->tstart + 1; stype < DBNumUserLayers; stype++)
	if (TTMaskHasType(ccs->rmask, stype))
	    break;

    TTMaskSetOnlyType(&smask, stype);
    DBSrPaintNMArea((Tile *)NULL, ccs->rootDef->cd_planes[DBPlane(stype)],
		TiGetTypeExact(tile) | dinfo, &ccs->area, &smask,
		cmdContactFunc2, (ClientData)ccs);
    return 0;
}

int
cmdContactFunc2(
    Tile *tile,
    TileType dinfo,	/* (unused) */
    CCStruct *ccs)
{
    LinkedRect *newlr;
    Rect area;
    TiToRect(tile, &area);

    GeoClip(&area, &ccs->area);

    newlr = (LinkedRect *) mallocMagic(sizeof(LinkedRect));
    newlr->r_r = area;
    newlr->r_next = ccs->lhead;
    ccs->lhead = newlr;

    return 0;
}

/* For each contact tile found, attach the area to a LinkedRect structure
 */

int
cmdContactEraseFunc(
    Tile *tile,
    TileType dinfo,	/* (unused) */
    LinkedRect **lr)
{
    LinkedRect *newlr;
    Rect area;

    newlr = (LinkedRect *)mallocMagic(sizeof(LinkedRect));

    newlr->r_type = TiGetType(tile);
    TiToRect(tile, &newlr->r_r);
    newlr->r_next = *lr;
    *lr = newlr;

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdCopy --
 *
 * Implement the "copy" command.
 *
 * Usage:
 *	copy [direction [amount]]
 *	copy to x y
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The selection is copied.
 *
 * ----------------------------------------------------------------------------
 */

void
CmdCopy(
    MagWindow *w,
    TxCommand *cmd)
{
    Transform t;
    Rect rootBox, newBox;
    Point rootPoint, editPoint;
    CellDef *rootDef;
    int argpos;

    if (cmd->tx_argc > 4)
    {
	badUsage:
	TxError("Usage: %s [direction [amount]]\n", cmd->tx_argv[0]);
	TxError("   or: %s to x y\n", cmd->tx_argv[0]);
	return;
    }

    if (cmd->tx_argc > 1)
    {
	int indx, amountx, amounty;
	int xdelta, ydelta;

	if (!ToolGetEditBox((Rect *)NULL)) return;

	if (strcmp(cmd->tx_argv[1], "to") == 0)
	{
	    if (cmd->tx_argc != 4)
		goto badUsage;
	    editPoint.p_x = cmdParseCoord(w, cmd->tx_argv[2], FALSE, TRUE);
	    editPoint.p_y = cmdParseCoord(w, cmd->tx_argv[3], FALSE, FALSE);
	    GeoTransPoint(&EditToRootTransform, &editPoint, &rootPoint);
	    goto copyToPoint;
	}

	indx = GeoNameToPos(cmd->tx_argv[1], FALSE, FALSE);
	argpos = (indx < 0) ? 1 : 2;

	if (cmd->tx_argc >= 3)
	{
	    switch (indx)
	    {
		case GEO_EAST: case GEO_WEST:
		    amountx = cmdParseCoord(w, cmd->tx_argv[argpos], TRUE, TRUE);
		    amounty = 0;
		    break;
		case GEO_NORTH: case GEO_SOUTH:
		    amountx = 0;
		    amounty = cmdParseCoord(w, cmd->tx_argv[argpos], TRUE, FALSE);
		    break;
		default:
		    amountx = cmdParseCoord(w, cmd->tx_argv[argpos], TRUE, TRUE);
		    amounty = cmdParseCoord(w, cmd->tx_argv[cmd->tx_argc - 1],
				TRUE, FALSE);
		    break;
	    }
	}
	else
	{
	    if (indx < 0) {
		TxError("Improperly defined copy. . . direction needed.\n");
		return;
	    }
	    amountx = cmdParseCoord(w, "1l", TRUE, TRUE);
	    amounty = cmdParseCoord(w, "1l", TRUE, FALSE);
	}

	switch (indx)
	{
	    case GEO_NORTH:
		xdelta = 0;
		ydelta = amounty;
		break;
	    case GEO_SOUTH:
		xdelta = 0;
		ydelta = -amounty;
		break;
	    case GEO_EAST:
		xdelta = amountx;
		ydelta = 0;
		break;
	    case GEO_WEST:
		xdelta = -amountx;
		ydelta = 0;
		break;
	    case GEO_NORTHWEST:
		xdelta = -amountx;
		ydelta = amounty;
		break;
	    case GEO_NORTHEAST:
	    case -2:
		xdelta = amountx;
		ydelta = amounty;
		break;
	    case GEO_SOUTHWEST:
		xdelta = -amountx;
		ydelta = -amounty;
		break;
	    case GEO_SOUTHEAST:
		xdelta = amountx;
		ydelta = -amounty;
		break;
	    case GEO_CENTER:
		xdelta = 0;
		ydelta = 0;
		break;
	    default:
		ASSERT(FALSE, "Bad direction in CmdCopy");
		return;
	}
	GeoTransTranslate(xdelta, ydelta, &GeoIdentityTransform, &t);

	/* Move the box by the same amount as the selection, if the
	 * box exists.
	 */

	if (ToolGetBox(&rootDef, &rootBox) && (rootDef == SelectRootDef))
	{
	    GeoTransRect(&t, &rootBox, &newBox);
	    DBWSetBox(rootDef, &newBox);
	}
    }
    else
    {
	/* Use the displacement between the box lower-left corner and
	 * the point as the transform.
	 */

	MagWindow *window;

	window = ToolGetPoint(&rootPoint, (Rect *) NULL);
	if ((window == NULL) ||
	    (EditRootDef != ((CellUse *) window->w_surfaceID)->cu_def))
	{
	    TxError("\"Copy\" uses the point as the place to put down a\n");
	    TxError("    copy of the selection, but the point doesn't\n");
	    TxError("    point to the edit cell.\n");
	    return;
	}

	/* Recast the command as "copy to x y" so that it no longer
	 * depends on the pointer position, for command logging.
	 */
	GeoTransPoint(&RootToEditTransform, &rootPoint, &editPoint);
	sprintf(cmd->tx_argstring, "copy to %di %di\n", editPoint.p_x,
			editPoint.p_y);
	TxRebuildCommand(cmd);

copyToPoint:
	if (!ToolGetBox(&rootDef, &rootBox) || (rootDef != SelectRootDef))
	{
	    TxError("\"Copy\" uses the box lower-left corner as a place\n");
	    TxError("    to pick up the selection for copying, but the box\n");
	    TxError("    isn't in a window containing the selection.\n");
	    return;
	}
	GeoTransTranslate(rootPoint.p_x - rootBox.r_xbot,
	    rootPoint.p_y - rootBox.r_ybot, &GeoIdentityTransform, &t);
	GeoTransRect(&t, &rootBox, &newBox);
	DBWSetBox(rootDef, &newBox);
    }
    SelectCopy(&t);
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdCorner --
 *
 * Implement the "corner" command.  Find all paint touching one side
 * of the box, and paint it around two edges of the box in an "L"
 * shape.
 *
 * Usage:
 *	corner firstDirection secondDirection [layers]
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The edit cell is modified.
 *
 * ----------------------------------------------------------------------------
 */

/* Data passed between CmdCorner and cmdCornerFunc: */

int cmdCornerDir1;			/* First direction each wire must
					 * be extended.
					 */
int cmdCornerDir2;			/* Second direction each wire must
					 * be extended.
					 */
Rect cmdCornerRootBox;			/* Root coords of box. */
struct cmdCornerArea *cmdCornerList;	/* List of areas to fill. */

typedef struct cifpathlist
{
    TileType pathtype;
    CIFPath *pathhead;
    struct cifpathlist *cpl_next;
} CIFPathList;

typedef struct nmcornerpath
{
    bool hasErr;
    CIFPathList *pathlist;
} NMCornerPath;

void
CmdCorner(
    MagWindow *w,	/* Window in which command was invoked. */
    TxCommand *cmd)	/* Describes the command that was invoked. */
{
    TileTypeBitMask maskBits;
    Rect editBox;
    SearchContext scx;
    bool hasErr = FALSE;
    int locargc = cmd->tx_argc;
    bool dobevel = FALSE;
    NMCornerPath cmdPathList;

    /* Forward declarations */
    extern int cmdCornerFunc(Tile *tile, TileType dinfo, TreeContext *cxp);
    extern int cmdBevelFunc(Tile *tile, TileType dinfo, TreeContext *cxp);

    if (cmd->tx_argc < 3 || cmd->tx_argc > 5)
    {
	TxError("Usage: %s direction1 direction2 [layers]\n",
			cmd->tx_argv[0]);
	return;
    }

    windCheckOnlyWindow(&w, DBWclientID);
    if ( w == (MagWindow *) NULL )
    {
	TxError("Point to a window\n");
	return;
    }

    /* Find and check validity of directions. */

    cmdCornerDir1 = GeoNameToPos(cmd->tx_argv[1], TRUE, TRUE);
    if (cmdCornerDir1 < 0)
	return;
    cmdCornerDir2 = GeoNameToPos(cmd->tx_argv[2], TRUE, TRUE);
    if (cmdCornerDir2 < 0)
	return;
    if ((cmdCornerDir1 == GEO_NORTH) || (cmdCornerDir1 == GEO_SOUTH))
    {
	if ((cmdCornerDir2 == GEO_NORTH) || (cmdCornerDir2 == GEO_SOUTH))
	{
	    TxPrintf("Can't corner-fill %s and then %s.\n",
		    cmd->tx_argv[1], cmd->tx_argv[2]);
	    return;
	}
    }
    else
    {
	if ((cmdCornerDir2 == GEO_EAST) || (cmdCornerDir2 == GEO_WEST))
	{
	    TxPrintf("Can't corner-fill %s and then %s.\n",
		    cmd->tx_argv[1], cmd->tx_argv[2]);
	    return;
	}
    }

    /* Check for "bevel" keyword */

    if (locargc > 3)
    {
	if (!strncmp(cmd->tx_argv[locargc - 1], "bevel", 5))
	{
	    locargc--;
	    dobevel = TRUE;
	}
    }

    /* Figure out which layers to fill. */

    if (locargc < 4)
	maskBits = DBAllButSpaceAndDRCBits;
    else
    {
	if (!CmdParseLayers(cmd->tx_argv[3], &maskBits))
	    return;
    }

    /* Figure out which material to search for and invoke a search
     * procedure to find it.
     */

    if (!ToolGetEditBox(&editBox)) return;
    GeoTransRect(&EditToRootTransform, &editBox, &cmdCornerRootBox);
    scx.scx_area = cmdCornerRootBox;
    switch (cmdCornerDir1)
    {
	case GEO_NORTH:
	    scx.scx_area.r_ytop = scx.scx_area.r_ybot + 1;
	    scx.scx_area.r_ybot -= 1;
	    break;
	case GEO_SOUTH:
	    scx.scx_area.r_ybot = scx.scx_area.r_ytop - 1;
	    scx.scx_area.r_ytop += 1;
	    break;
	case GEO_EAST:
	    scx.scx_area.r_xtop = scx.scx_area.r_xbot + 1;
	    scx.scx_area.r_xbot -= 1;
	    break;
	case GEO_WEST:
	    scx.scx_area.r_xbot = scx.scx_area.r_xtop - 1;
	    scx.scx_area.r_xtop += 1;
	    break;
    }
    scx.scx_use = (CellUse *) w->w_surfaceID;
    scx.scx_trans = GeoIdentityTransform;

    if (dobevel)
    {
	cmdPathList.hasErr = FALSE;
	cmdPathList.pathlist = NULL;

	(void) DBTreeSrTiles(&scx, &maskBits,
		((DBWclientRec *) w->w_clientData)->dbw_bitmask,
		cmdBevelFunc, (ClientData) &cmdPathList);

	if (cmdPathList.hasErr)
	    TxError("There's not enough room in the box for all the wires.\n");

	while (cmdPathList.pathlist != NULL)
	{
	    /* It is a bit of a hack to use CIFPolyToRects here, but it	*/
	    /* beats rewriting the entire path parsing routine.		*/

	    LinkedRect *rectp;
	    PaintUndoInfo ui;
	    int pNum =  DBPlane(cmdPathList.pathlist->pathtype);
	    PaintResultType  *resultTbl =
			DBStdPaintTbl(cmdPathList.pathlist->pathtype, pNum);
	    Plane *plane = EditRootDef->cd_planes[pNum];

	    ui.pu_def = EditRootDef;
	    ui.pu_pNum = pNum;

	    rectp = CIFPolyToRects(cmdPathList.pathlist->pathhead, plane,
			resultTbl, &ui, FALSE);
	    {
		free_magic1_t mm1 = freeMagic1_init();
		for (; rectp != NULL; rectp = rectp->r_next)
		{
		    DBPaintPlane(plane, &rectp->r_r, resultTbl, &ui);
		    freeMagic1(&mm1, (char *)rectp);
		}
		freeMagic1_end(&mm1);
	    }
	    CIFFreePath(cmdPathList.pathlist->pathhead);
	    {
		free_magic1_t mm1 = freeMagic1_init();
		freeMagic1(&mm1, (char *)cmdPathList.pathlist);
		cmdPathList.pathlist = cmdPathList.pathlist->cpl_next;
		freeMagic1_end(&mm1);
	    }
	}
    }
    else
    {

	cmdCornerList = (struct cmdCornerArea *) NULL;

	(void) DBTreeSrTiles(&scx, &maskBits,
		((DBWclientRec *) w->w_clientData)->dbw_bitmask,
		cmdCornerFunc, (ClientData) &hasErr);

	if (hasErr)
	    TxError("There's not enough room in the box for all the wires.\n");

	/* Now that we've got all the material, scan over the list
	 * painting the material and freeing up the entries on the list.
	 */
	free_magic1_t mm1 = freeMagic1_init();
	while (cmdCornerList != NULL)
	{
	    DBPaint(EditCellUse->cu_def, &cmdCornerList->cca_area,
			cmdCornerList->cca_type);
	    freeMagic1(&mm1, (char *) cmdCornerList);
	    cmdCornerList = cmdCornerList->cca_next;
	}
	freeMagic1_end(&mm1);
}

    SelectClear();
    DBAdjustLabels(EditCellUse->cu_def, &editBox);
    DRCCheckThis(EditCellUse->cu_def, TT_CHECKPAINT, &editBox);
    DBWAreaChanged(EditCellUse->cu_def, &editBox, DBW_ALLWINDOWS, &maskBits);
    DBReComputeBbox(EditCellUse->cu_def);
}

/*
 * ----------------------------------------------------------------------------
 *
 * cmdCornerFunc --
 *
 * 	Search procedure called by DBTreeSrTiles from CmdCorner.  Called once
 *	for each tile that crosses the appropriate boundary of the box.
 *	Makes an L-shaped 90 degree turn to extend a wire out of an
 *	adjacent side.
 *
 * Results:
 *	Returns 0 to keep the search alive.
 *
 * Side effects:
 *	Adds paint tiles to the display list.  If there are tiles found
 *	that can't be cornered correctly, the clientData value is set
 *	to TRUE.
 *
 * ----------------------------------------------------------------------------
 */
int
cmdCornerFunc(
    Tile *tile,			/* Tile to fill with. */
    TileType dinfo,		/* Split tile information (unused) */
    TreeContext *cxp)		/* Describes state of search. */
{
    Rect r1, r2, r3;
    struct cmdCornerArea *cca;
    bool *errPtr = (bool *) cxp->tc_filter->tf_arg;

    /* Get the tile dimensions in root coordinates.  Clip to the box.
     */
    TiToRect(tile, &r1);
    GeoTransRect(&cxp->tc_scx->scx_trans, &r1, &r2);
    GeoClip(&r2, &cmdCornerRootBox);

    /* Generate r2 and r3, the first and second legs of the L-shaped
     * geometry to be painted for this tile.
     */

    r3 = r2;
    switch (cmdCornerDir1)
    {
	case GEO_NORTH:
	    if (cmdCornerDir2 == GEO_EAST)
	    {
		r2.r_ytop = r3.r_ytop = cmdCornerRootBox.r_ytop
			- (r2.r_xbot - cmdCornerRootBox.r_xbot);
		r3.r_xtop = cmdCornerRootBox.r_xtop;
	    }
	    else
	    {
		r2.r_ytop = r3.r_ytop = cmdCornerRootBox.r_ytop
			- (cmdCornerRootBox.r_xtop - r2.r_xtop);
		r3.r_xbot = cmdCornerRootBox.r_xbot;
	    }
	    r3.r_ybot = r3.r_ytop - (r2.r_xtop - r2.r_xbot);
	    if (r3.r_ybot < cmdCornerRootBox.r_ybot)
		*errPtr = TRUE;
	    break;

	case GEO_SOUTH:
	    if (cmdCornerDir2 == GEO_EAST)
	    {
		r2.r_ybot = r3.r_ybot = cmdCornerRootBox.r_ybot
			+ (r2.r_xbot - cmdCornerRootBox.r_xbot);
		r3.r_xtop = cmdCornerRootBox.r_xtop;
	    }
	    else
	    {
		r2.r_ybot = r3.r_ybot = cmdCornerRootBox.r_ybot
			+ (cmdCornerRootBox.r_xtop - r2.r_xtop);
		r3.r_xbot = cmdCornerRootBox.r_xbot;
	    }
	    r3.r_ytop = r3.r_ybot + (r2.r_xtop - r2.r_xbot);
	    if (r3.r_ytop > cmdCornerRootBox.r_ytop)
		*errPtr = TRUE;
	    break;

	case GEO_EAST:
	    if (cmdCornerDir2 == GEO_NORTH)
	    {
		r2.r_xtop = r3.r_xtop = cmdCornerRootBox.r_xtop
			- (r2.r_ybot - cmdCornerRootBox.r_ybot);
		r3.r_ytop = cmdCornerRootBox.r_ytop;
	    }
	    else
	    {
		r2.r_xtop = r3.r_xtop = cmdCornerRootBox.r_xtop
			- (cmdCornerRootBox.r_ytop - r2.r_ytop);
		r3.r_ybot = cmdCornerRootBox.r_ybot;
	    }
	    r3.r_xbot = r3.r_xtop - (r2.r_ytop - r2.r_ybot);
	    if (r3.r_xbot < cmdCornerRootBox.r_xbot)
		*errPtr = TRUE;
	    break;

	case GEO_WEST:
	    if (cmdCornerDir2 == GEO_NORTH)
	    {
		r2.r_xbot = r3.r_xbot = cmdCornerRootBox.r_xbot
			+ (r2.r_ybot - cmdCornerRootBox.r_ybot);
		r3.r_ytop = cmdCornerRootBox.r_ytop;
	    }
	    else
	    {
		r2.r_xbot = r3.r_xbot = cmdCornerRootBox.r_xbot
			+ (cmdCornerRootBox.r_ytop - r2.r_ytop);
		r3.r_ybot = cmdCornerRootBox.r_ybot;
	    }
	    r3.r_xtop = r2.r_xbot + (r2.r_ytop - r2.r_ybot);
	    if (r3.r_xtop > cmdCornerRootBox.r_xtop)
		*errPtr = TRUE;
	    break;
    }

    /* Clip the resulting geometry to the box, translate to edit cell
     * coords, and add to the paint list if non-NULL.
     */

    GeoClip(&r2, &cmdCornerRootBox);
    GeoTransRect(&RootToEditTransform, &r2, &r1);
    if (!GEO_RECTNULL(&r1))
    {
	/* Add this rectangle to the list. */

	cca = (struct cmdCornerArea *)
		mallocMagic(sizeof(struct cmdCornerArea));
	cca->cca_area = r1;
	cca->cca_type = TiGetType(tile);
	cca->cca_next = cmdCornerList;
	cmdCornerList = cca;
    }

    GeoClip(&r3, &cmdCornerRootBox);
    GeoTransRect(&RootToEditTransform, &r3, &r1);
    if (!GEO_RECTNULL(&r1))
    {
	cca = (struct cmdCornerArea *)
		mallocMagic(sizeof(struct cmdCornerArea));
	cca->cca_area = r1;
	cca->cca_type = TiGetType(tile);
	cca->cca_next = cmdCornerList;
	cmdCornerList = cca;
    }
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * Convenience function for cmdBevelFunc.  Allocates a CIFPath record.
 *
 * ----------------------------------------------------------------------------
 */

void
AddNewPoint(
    CIFPath **pathhead)
{
    CIFPath *newpoint;

    newpoint = (CIFPath *)mallocMagic(sizeof(CIFPath));
    newpoint->cifp_next = *pathhead;
    *pathhead = newpoint;
}

/*
 * ----------------------------------------------------------------------------
 *
 * cmdBevelFunc --
 *
 * 	Search procedure called by DBTreeSrTiles from CmdCorner.  Called once
 *	for each tile that crosses the appropriate boundary of the box.
 *	Makes a beveled 90 degree turn to extend a wire out of an
 *	adjacent side.
 *
 * Results:
 *	Returns 0 to keep the search alive.
 *
 * Side effects:
 *	Adds paint tiles to the display list.  If there are tiles found
 *	that can't be cornered correctly, the clientData value is set
 *	to TRUE.
 *
 * ----------------------------------------------------------------------------
 */
int
cmdBevelFunc(
    Tile *tile,			/* Tile to fill with. */
    TileType dinfo,		/* Split tile information (unused) */
    TreeContext *cxp)		/* Describes state of search. */
{
    Rect r1, r2, r3;
    CIFPathList *cpl;
    CIFPath *pptr, *pathhead = NULL;
    NMCornerPath *pathrecord = (NMCornerPath *) cxp->tc_filter->tf_arg;
    int wirewidth, boxwidth, boxheight;
    TileType pathtype;

    /* Get the tile dimensions in root coordinates.  Clip to the box.
     */
    TiToRect(tile, &r1);
    GeoTransRect(&cxp->tc_scx->scx_trans, &r1, &r2);
    GeoClip(&r2, &cmdCornerRootBox);

    /* Find the Manhattan rectangles like cmdCornerFunc.  Shrink
     * these to a minimum square or rectangle.  Finally, pick the
     * path points off of the two boxes to generate the nonManhattan
     * path.
     */

    r3 = r2;
    switch (cmdCornerDir1)
    {
	case GEO_NORTH:

	    pathtype = TiGetTopType(tile);
	    if (pathtype == TT_SPACE) return 0;
	    wirewidth = r2.r_xtop - r2.r_xbot;
	    if (cmdCornerDir2 == GEO_EAST)
	    {
		r3.r_ytop = cmdCornerRootBox.r_ytop
			- (r2.r_xbot - cmdCornerRootBox.r_xbot);
		r3.r_xtop = r3.r_xbot = cmdCornerRootBox.r_xtop;
		boxwidth = r3.r_xtop - r2.r_xbot;
		boxheight = r3.r_ytop - r3.r_ybot;

		if (boxwidth > boxheight)
		    r3.r_xbot = r2.r_xbot + boxheight;
		r3.r_xbot -= wirewidth;
	    }
	    else
	    {
		r3.r_ytop = cmdCornerRootBox.r_ytop
			- (cmdCornerRootBox.r_xtop - r2.r_xtop);
		r3.r_xbot = r3.r_xtop = cmdCornerRootBox.r_xbot;
		boxwidth = r2.r_xtop - r3.r_xbot;
		boxheight = r3.r_ytop - r3.r_ybot;

		if (boxwidth > boxheight)
		    r3.r_xtop = r2.r_xtop - boxheight;
		r3.r_xtop += wirewidth;
	    }
	    r3.r_ybot = r3.r_ytop - wirewidth;
	    if (r3.r_ybot < cmdCornerRootBox.r_ybot)
		 pathrecord->hasErr = TRUE;

	    if (boxheight > boxwidth)
		r2.r_ytop = r3.r_ytop - boxwidth;
	    else
		r2.r_ytop = r2.r_ybot;
	    r2.r_ytop += wirewidth;

	    /* Create nonManhattan path */
	    if (cmdCornerDir2 == GEO_EAST)
	    {
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xbot;
		pathhead->cifp_y = r2.r_ytop;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xbot;
		pathhead->cifp_y = r2.r_ybot;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xtop;
		pathhead->cifp_y = r2.r_ybot;
		if (boxheight > boxwidth)
		{
		    AddNewPoint(&pathhead);
		    pathhead->cifp_x = r2.r_xtop;
		    pathhead->cifp_y = r2.r_ytop - wirewidth;
		}
		else if (boxwidth > boxheight)
		{
		    AddNewPoint(&pathhead);
		    pathhead->cifp_x = r3.r_xbot + wirewidth;
		    pathhead->cifp_y = r3.r_ybot;
		}
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xtop;
		pathhead->cifp_y = r3.r_ybot;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xtop;
		pathhead->cifp_y = r3.r_ytop;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xbot;
		pathhead->cifp_y = r3.r_ytop;
	    }
	    else
	    {
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xtop;
		pathhead->cifp_y = r2.r_ytop;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xtop;
		pathhead->cifp_y = r2.r_ybot;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xbot;
		pathhead->cifp_y = r2.r_ybot;
		if (boxheight > boxwidth)
		{
		    AddNewPoint(&pathhead);
		    pathhead->cifp_x = r2.r_xbot;
		    pathhead->cifp_y = r2.r_ytop - wirewidth;
		}
		else if (boxwidth > boxheight)
		{
		    AddNewPoint(&pathhead);
		    pathhead->cifp_x = r3.r_xtop - wirewidth;
	    	    pathhead->cifp_y = r3.r_ybot;
		}
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xbot;
		pathhead->cifp_y = r3.r_ybot;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xbot;
		pathhead->cifp_y = r3.r_ytop;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xtop;
		pathhead->cifp_y = r3.r_ytop;
	    }
	    break;

	case GEO_SOUTH:
	    pathtype = TiGetBottomType(tile);
	    if (pathtype == TT_SPACE) return 0;
	    wirewidth = r2.r_xtop - r2.r_xbot;
	    if (cmdCornerDir2 == GEO_EAST)
	    {
		r3.r_ybot = cmdCornerRootBox.r_ybot
			+ (r2.r_xbot - cmdCornerRootBox.r_xbot);
		r3.r_xtop = r3.r_xbot = cmdCornerRootBox.r_xtop;
		boxwidth = r3.r_xtop - r2.r_xbot;
		boxheight = r3.r_ytop - r3.r_ybot;

		if (boxwidth > boxheight)
		    r3.r_xbot = r2.r_xbot + boxheight;
		r3.r_xbot -= wirewidth;
	    }
	    else
	    {
		r3.r_ybot = cmdCornerRootBox.r_ybot
			+ (cmdCornerRootBox.r_xtop - r2.r_xtop);
		r3.r_xbot = r3.r_xtop = cmdCornerRootBox.r_xbot;
		boxwidth = r2.r_xtop - r3.r_xbot;
		boxheight = r3.r_ytop - r3.r_ybot;

		if (boxwidth > boxheight)
		    r3.r_xtop = r2.r_xtop - boxheight;
		r3.r_xtop += wirewidth;
	    }
	    r3.r_ytop = r3.r_ybot + wirewidth;
	    if (r3.r_ytop > cmdCornerRootBox.r_ytop)
		pathrecord->hasErr = TRUE;

	    if (boxheight > boxwidth)
		r2.r_ybot = r3.r_ybot + boxwidth;
	    else
		r2.r_ybot = r2.r_ytop;
	    r2.r_ybot -= wirewidth;

	    /* Create nonManhattan path */
	    if (cmdCornerDir2 == GEO_EAST)
	    {
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xbot;
		pathhead->cifp_y = r2.r_ybot;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xbot;
		pathhead->cifp_y = r2.r_ytop;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xtop;
		pathhead->cifp_y = r2.r_ytop;
		if (boxheight > boxwidth)
		{
		    AddNewPoint(&pathhead);
		    pathhead->cifp_x = r2.r_xtop;
		    pathhead->cifp_y = r2.r_ybot + wirewidth;
		}
		else if (boxwidth > boxheight)
		{
		    AddNewPoint(&pathhead);
		    pathhead->cifp_x = r3.r_xbot + wirewidth;
		    pathhead->cifp_y = r3.r_ytop;
		}
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xtop;
		pathhead->cifp_y = r3.r_ytop;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xtop;
		pathhead->cifp_y = r3.r_ybot;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xbot;
		pathhead->cifp_y = r3.r_ybot;
	    }
	    else
	    {
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xtop;
		pathhead->cifp_y = r2.r_ybot;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xtop;
		pathhead->cifp_y = r2.r_ytop;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xbot;
		pathhead->cifp_y = r2.r_ytop;
		if (boxheight > boxwidth)
		{
		    AddNewPoint(&pathhead);
		    pathhead->cifp_x = r2.r_xbot;
		    pathhead->cifp_y = r2.r_ybot + wirewidth;
		}
		else if (boxwidth > boxheight)
		{
		    AddNewPoint(&pathhead);
		    pathhead->cifp_x = r3.r_xtop - wirewidth;
		    pathhead->cifp_y = r3.r_ytop;
		}
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xbot;
		pathhead->cifp_y = r3.r_ytop;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xbot;
		pathhead->cifp_y = r3.r_ybot;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xtop;
		pathhead->cifp_y = r3.r_ybot;
	    }
	    break;

	case GEO_EAST:
	    pathtype = TiGetRightType(tile);
	    if (pathtype == TT_SPACE) return 0;
	    wirewidth = r2.r_ytop - r2.r_ybot;
	    if (cmdCornerDir2 == GEO_NORTH)
	    {
		r3.r_xtop = cmdCornerRootBox.r_xtop
			- (r2.r_ybot - cmdCornerRootBox.r_ybot);
		r3.r_ytop = r3.r_ybot = cmdCornerRootBox.r_ytop;
		boxwidth = r3.r_xtop = r3.r_xbot;
		boxheight = r3.r_xtop = r2.r_ybot;

		if (boxheight > boxwidth)
		    r3.r_ybot = r2.r_ybot + boxwidth;
		r3.r_ybot -= wirewidth;
	    }
	    else
	    {
		r3.r_xtop = cmdCornerRootBox.r_xtop
			- (cmdCornerRootBox.r_ytop - r2.r_ytop);
		r3.r_ybot = r3.r_ytop = cmdCornerRootBox.r_ybot;
		boxwidth = r3.r_xtop = r3.r_xbot;
		boxheight = r2.r_xtop = r3.r_ybot;

		if (boxheight > boxwidth)
		    r3.r_ytop = r2.r_ytop - boxwidth;
		r3.r_ytop += wirewidth;
	    }
	    r3.r_xbot = r3.r_xtop - wirewidth;
	    if (r3.r_xbot < cmdCornerRootBox.r_xbot)
		pathrecord->hasErr = TRUE;

	    if (boxwidth > boxheight)
		r2.r_xtop = r3.r_xtop - boxheight;
	    else
		r2.r_xtop = r2.r_xbot;
	    r2.r_xtop += wirewidth;

	    /* Create nonManhattan path */
	    if (cmdCornerDir2 == GEO_NORTH)
	    {
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xtop;
		pathhead->cifp_y = r2.r_ybot;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xbot;
		pathhead->cifp_y = r2.r_ybot;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xbot;
		pathhead->cifp_y = r2.r_ytop;
		if (boxheight > boxwidth)
		{
		    AddNewPoint(&pathhead);
		    pathhead->cifp_x = r3.r_xbot;
		    pathhead->cifp_y = r3.r_ybot + wirewidth;
		}
		else if (boxwidth > boxheight)
		{
		    AddNewPoint(&pathhead);
		    pathhead->cifp_x = r2.r_xtop - wirewidth;
		    pathhead->cifp_y = r2.r_ytop;
		}
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xbot;
		pathhead->cifp_y = r3.r_ytop;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xtop;
		pathhead->cifp_y = r3.r_ytop;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xtop;
		pathhead->cifp_y = r3.r_ybot;
	    }
	    else
	    {
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xtop;
		pathhead->cifp_y = r2.r_ytop;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xbot;
		pathhead->cifp_y = r2.r_ytop;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xbot;
		pathhead->cifp_y = r2.r_ybot;
		if (boxheight > boxwidth)
		{
		    AddNewPoint(&pathhead);
		    pathhead->cifp_x = r3.r_xbot;
		    pathhead->cifp_y = r3.r_ytop - wirewidth;
		}
		else if (boxwidth > boxheight)
		{
		    AddNewPoint(&pathhead);
		    pathhead->cifp_x = r2.r_xtop - wirewidth;
		    pathhead->cifp_y = r2.r_ybot;
		}
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xbot;
		pathhead->cifp_y = r3.r_ybot;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xtop;
		pathhead->cifp_y = r3.r_ybot;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xtop;
		pathhead->cifp_y = r3.r_ytop;
	    }
	    break;

	case GEO_WEST:
	    pathtype = TiGetLeftType(tile);
	    if (pathtype == TT_SPACE) return 0;
	    wirewidth = r2.r_ytop - r2.r_ybot;
	    if (cmdCornerDir2 == GEO_NORTH)
	    {
		r3.r_xbot = cmdCornerRootBox.r_xbot
			+ (r2.r_ybot - cmdCornerRootBox.r_ybot);
		r3.r_ytop = r3.r_ybot = cmdCornerRootBox.r_ytop;
		boxwidth = r3.r_xtop = r3.r_xbot;
		boxheight = r3.r_xtop = r2.r_ybot;

		if (boxheight > boxwidth)
		    r3.r_ybot = r2.r_ybot + boxwidth;
		r3.r_ybot -= wirewidth;
	    }
	    else
	    {
		r3.r_xbot = cmdCornerRootBox.r_xbot
			+ (cmdCornerRootBox.r_ytop - r2.r_ytop);
		r3.r_ybot = r3.r_ytop = cmdCornerRootBox.r_ybot;
		boxwidth = r3.r_xtop = r3.r_xbot;
		boxheight = r2.r_xtop = r3.r_ybot;

		if (boxheight > boxwidth)
		    r3.r_ytop = r2.r_ytop - boxwidth;
		r3.r_ytop += wirewidth;
	    }
	    r3.r_xtop = r2.r_xbot + wirewidth;
	    if (r3.r_xtop > cmdCornerRootBox.r_xtop)
		pathrecord->hasErr = TRUE;

	    if (boxwidth > boxheight)
		r2.r_xbot = r3.r_xbot + boxheight;
	    else
		r2.r_xbot = r2.r_xtop;
	    r2.r_xbot -= wirewidth;

	    /* Create nonManhattan path */
	    if (cmdCornerDir2 == GEO_NORTH)
	    {
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xbot;
		pathhead->cifp_y = r2.r_ybot;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xtop;
		pathhead->cifp_y = r2.r_ybot;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xtop;
		pathhead->cifp_y = r2.r_ytop;
		if (boxheight > boxwidth)
		{
		    AddNewPoint(&pathhead);
		    pathhead->cifp_x = r3.r_xtop;
		    pathhead->cifp_y = r3.r_ybot + wirewidth;
		}
		else if (boxwidth > boxheight)
		{
		    AddNewPoint(&pathhead);
		    pathhead->cifp_x = r2.r_xbot + wirewidth;
		    pathhead->cifp_y = r2.r_ytop;
		}
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xtop;
		pathhead->cifp_y = r3.r_ytop;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xbot;
		pathhead->cifp_y = r3.r_ytop;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xbot;
		pathhead->cifp_y = r3.r_ybot;
	    }
	    else
	    {
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xbot;
		pathhead->cifp_y = r2.r_ytop;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xtop;
		pathhead->cifp_y = r2.r_ytop;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r2.r_xtop;
		pathhead->cifp_y = r2.r_ybot;
		if (boxheight > boxwidth)
		{
		    AddNewPoint(&pathhead);
		    pathhead->cifp_x = r3.r_xtop;
		    pathhead->cifp_y = r3.r_ytop - wirewidth;
		}
		else if (boxwidth > boxheight)
		{
		    AddNewPoint(&pathhead);
		    pathhead->cifp_x = r2.r_xbot + wirewidth;
		    pathhead->cifp_y = r2.r_ybot;
		}
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xtop;
		pathhead->cifp_y = r3.r_ybot;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xbot;
		pathhead->cifp_y = r3.r_ybot;
		AddNewPoint(&pathhead);
		pathhead->cifp_x = r3.r_xbot;
		pathhead->cifp_y = r3.r_ytop;
	    }
	    break;
    }

    /* If either rectangle is NULL after clipping, free the point */
    /* memory and return without adding the path.		  */

    GeoClip(&r2, &cmdCornerRootBox);
    GeoClip(&r3, &cmdCornerRootBox);
    if (GEO_RECTNULL(&r2) || GEO_RECTNULL(&r3))
    {
	free_magic1_t mm1 = freeMagic1_init();
	for (pptr = pathhead; pptr != NULL; pptr = pptr->cifp_next)
	    freeMagic1(&mm1, (char *)pptr);
	freeMagic1_end(&mm1);
	return 0;
    }

    /* Clip the point geometry to the box, and translate to edit cell
     * coords.
     */

    for (pptr = pathhead; pptr != NULL; pptr = pptr->cifp_next)
    {
	Point p = pptr->cifp_point;
	GeoClipPoint(&p, &cmdCornerRootBox);
	GeoTransPoint(&RootToEditTransform, &p, &pptr->cifp_point);
    }

    /* Create the path and add it to the list. */

    cpl = (CIFPathList *) mallocMagic(sizeof(CIFPathList));
    cpl->pathtype = pathtype;
    cpl->cpl_next = pathrecord->pathlist;
    cpl->pathhead = pathhead;
    pathrecord->pathlist = cpl;

    return 0;	/* Keep the search going */
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdCrash --
 *
 * Save cells to or recover cells from a crash backup file
 *
 * Usage:
 *	crash save|recover|archive|read [file]
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Loads the database with multiple cells from the crash recovery
 *	file, and removes the crash recovery file.
 *
 * ----------------------------------------------------------------------------
 */
void
CmdCrash(
    MagWindow *w,
    TxCommand *cmd)
{
    int option = -1;
    char *filename = NULL;
    static const char * const cmdCrashOpt[] = {"save", "recover", 0};

    if (cmd->tx_argc > 3)
	TxError("Usage: %s save|recover [filename]\n", cmd->tx_argv[0]);
    else if (cmd->tx_argc >= 2)
    {
	option = Lookup(cmd->tx_argv[1], cmdCrashOpt);
	if (option < 0)
	{
	    TxError("Usage: %s save|recover [filename]\n", cmd->tx_argv[0]);
	    return;
	}
    }
    if (cmd->tx_argc == 3) filename = cmd->tx_argv[2];

    switch(option) {
	case 0:			/* save */
	    DBWriteBackup(filename, FALSE, FALSE);
	    break;
	case 1:			/* recover */
	    DBFileRecovery(filename);
	    break;
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdCrosshair --
 *
 * Manipulate the crosshair highlight
 *
 * Usage:
 *	crosshair <x> <y>
 *	crosshair off
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The crosshair is enabled, moved, or disabled.
 * ----------------------------------------------------------------------------
 */
void
CmdCrosshair(
    MagWindow *w,
    TxCommand *cmd)
{
    Point pos;

    if (cmd->tx_argc == 2)
    {
	if (!strcmp(cmd->tx_argv[1], "off"))
	{
	    /* Erase and turn off */
	    pos.p_x = pos.p_y = MINFINITY;
	}
	else
	{
	    TxError("Usage: %s off|x y \n", cmd->tx_argv[0]);
	    return;
	}
    }
    else if (cmd->tx_argc == 3)
    {
	pos.p_x = cmdParseCoord(w, cmd->tx_argv[1], FALSE, TRUE);
	pos.p_y = cmdParseCoord(w, cmd->tx_argv[2], FALSE, FALSE);
    }
    else
    {
	TxError("Usage: %s off|x y \n", cmd->tx_argv[0]);
	return;
    }
    if (w)
	DBWSetCrosshair(w, &pos);
}


/*
 * ----------------------------------------------------------------------------
 *
 * CmdDelete --
 *
 * Implement the "delete" command.
 *
 * Usage:
 *	delete
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The selection is deleted.
 *
 * ----------------------------------------------------------------------------
 */

void
CmdDelete(
    MagWindow *w,
    TxCommand *cmd)
{
    if (cmd->tx_argc != 1) goto badusage;
    if (!ToolGetEditBox((Rect *)NULL)) return;

    SelectDelete("deleted", TRUE);
    return;

    badusage:
    TxError("Usage: %s\n", cmd->tx_argv[0]);
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdDown --
 *
 * Implement the "down" command.
 * Use the cell that is currently selected as the edit cell.  If more than
 * one cell is selected, use the point to choose between them. Load the
 * new edit cell into the window containing the point tool.
 *
 * Usage:
 *	down [<instname>]
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Sets EditCellUse.
 *
 * ----------------------------------------------------------------------------
 */

/* The following variable is set by cmdDownEnumFunc to signal that there
 * was at least one cell in the selection.
 */

static bool cmdFoundNewDown;

void
CmdDown(
    MagWindow *w,
    TxCommand *cmd)
{
    CellUse *use = NULL;
    Rect area, pointArea;
    int cmdEditRedisplayFunc(MagWindow *w, Rect *area);		/* External declaration. */
    int cmdDownEnumFunc(CellUse *selUse, CellUse *use, Transform *transform, Rect *area);		/* Forward declaration. */

    if ((w != NULL) && (cmd->tx_argc == 2))
    {
	CellUse *rootUse;
	SearchContext scx;

	rootUse = (CellUse *)w->w_surfaceID;
	bzero(&scx, sizeof(SearchContext));
	DBTreeFindUse(cmd->tx_argv[1], rootUse, &scx);
	use = scx.scx_use;
    }

    if ((use == NULL) && (cmd->tx_argc > 1))
    {
	TxError("Usage: edit\nMaybe you want the \"load\" command\n");
	return;
    }

    /* Record the current edit cell's area for redisplay (now that it's
     * not the edit cell, it will be displayed differently).  Do this
     * only in windows where the edit cell is displayed differently from
     * other cells.
     */

    GeoTransRect(&EditToRootTransform, &(EditCellUse->cu_def->cd_bbox), &area);
    (void) WindSearch(DBWclientID, (ClientData) NULL,
	    (Rect *) NULL, cmdEditRedisplayFunc, (ClientData) &area);

    /* Use the position of the point to select one of the currently-selected
     * cells (if there are more than one).  If worst comes to worst, just
     * select any selected cell.
     */

    (void) ToolGetPoint((Point *) NULL, &pointArea);
    cmdFoundNewDown = FALSE;

    if (use == NULL)
    {
	SelEnumCells(FALSE, (bool *) NULL, (SearchContext *) NULL,
		cmdDownEnumFunc, (ClientData) &pointArea);
    }
    else
    {
	EditCellUse = use;
	EditRootDef = use->cu_def;
	cmdFoundNewDown = TRUE;
    }
    if (!cmdFoundNewDown)
	TxError("You haven't selected a new cell to edit.\n");

    /* Now record the new edit cell's area for redisplay. */

    GeoTransRect(&EditToRootTransform, &(EditCellUse->cu_def->cd_bbox), &area);
    (void) WindSearch(DBWclientID, (ClientData) NULL,
	    (Rect *) NULL, cmdEditRedisplayFunc, (ClientData) &area);
    DBWloadWindow(w, EditCellUse->cu_def->cd_name, DBW_LOAD_IGNORE_TECH);

    if ((cmd->tx_argc == 1) && cmdFoundNewDown)
    {
	/* Recast the command with the instance name for logging */
	sprintf(cmd->tx_argstring, "down %s", EditCellUse->cu_id);
	TxRebuildCommand(cmd);
    }
}

/* Search function to find the new edit cell:  look for a cell use
 * that contains the rectangle passed as argument.  If we find such
 * a use, return 1 to abort the search.  Otherwise, save information
 * about this use anyway:  it'll become the edit cell if nothing
 * better is found.
 */

    /* ARGSUSED */
int
cmdDownEnumFunc(
    CellUse *selUse,		/* Use from selection (not used). */
    CellUse *use,		/* Use from layout that corresponds to
				 * selUse (could be an array!).
				 */
    Transform *transform,	/* Transform from use->cu_def to root coords. */
    Rect *area)			/* We're looking for a use containing this
				 * area, in root coords.
				 */
{
    Rect defArea, useArea;

    /* Save this use as the default next edit cell, regardless of whether
     * or not it overlaps the area we're interested in.
     */

    EditToRootTransform = *transform;
    GeoInvertTrans(transform, &RootToEditTransform);
    EditCellUse = use;
    EditRootDef = SelectRootDef;
    cmdFoundNewDown = TRUE;

    /* See if the bounding box of this use overlaps the area we're
     * interested in.
     */

    GeoTransRect(&RootToEditTransform, area, &defArea);
    GeoTransRect(&use->cu_transform, &defArea, &useArea);
    if (!GEO_OVERLAP(&useArea, &use->cu_bbox)) return 0;
    return 1;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdDrc --
 *
 * Implement the "drc" command.
 *
 * Usage:
 *	drc option
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Most options have no side effects.  The only major side
 *	effects are to turn continuous DRC on or off, or recheck an
 *	area of a cell.
 *
 * ----------------------------------------------------------------------------
 */

#define FLATCHECK	0
#define DRC_HALO	1
#define SHOWINT		2
#define DRC_STEPSIZE	3
#define CATCHUP		4
#define CHECK		5
#define COUNT		6
#define EUCLIDEAN	7
#define FIND		8
#define DRC_HELP	9
#define DRC_IGNORE	10
#define DRC_OFF		11
#define DRC_ON		12
#define DRC_STATUS	13
#define DRC_STYLE	14
#define PRINTRULES	15
#define RULESTATS	16
#define STATISTICS	17
#define WHY		18

void
CmdDrc(
    MagWindow *w,
    TxCommand *cmd)
{
    static int	drc_nth = 1;
    int		  option, result, radius;
    Rect	  rootArea, area;
    CellUse	* rootUse, *use;
    CellDef	* rootDef;
    Transform	  trans;
    MagWindow	* window;
    const char 	* const *msg;
    bool	wizardHelp;
    bool	incremental;
    bool	doforall = FALSE;
    bool	dolist = FALSE;
    bool	findonly;
    int		drc_start;
    int		count_total;
    LinkedIndex *DRCSaveRules = NULL;
    DRCCountList *dcl;
    int argc = cmd->tx_argc;
    char **argv = cmd->tx_argv;
#ifdef MAGIC_WRAPPER
    Tcl_Obj *lobj;
#endif

    static const char * const cmdDrcOption[] =
    {
	"*flatcheck             check box area by flattening",
	"*halo [d]		limit error checking to areas of d units",
	"*showint radius        show interaction area under box",
	"*stepsize [d]		change DRC step size to d units",
	"catchup                run checker and wait for it to complete",
	"check                  recheck area under box in all cells",
	"count [total]          count error tiles in each cell under box",
	"euclidean on|off	enable/disable Euclidean geometry checking",
	"find [nth]     	locate next (or nth) error in the layout",
	"help                   print this help information",
	"ignore	[<text>|none]   do not report on rules with this text",
	"off                    turn off background checker",
	"on                     reenable background checker",
	"status			report if the drc checker is on or off",
	"style			set the DRC style",
	"printrules [file]      print out design rules in file or on tty",
	"rulestats              print out stats about design rule database",
	"statistics             print out statistics gathered by checker",
	"why                    print out reasons for errors under box",
	NULL
    };

    if (argc < 2)
    {
	TxError("No option given in \":drc\" command.\n");
	option = DRC_HELP;
    }
    else
    {
	if (!strncmp(argv[1], "list", 4))
	{
	    dolist = TRUE;
	    if (!strncmp(argv[1], "listall", 7))
	    {
		doforall = TRUE;
	    }
	    argv++;
	    argc--;
	}

	/* Make sure "list" or "listall" is followed by additional arguments */
	if (argc < 2)
	{
	    TxError("No option given in \":drc\" command.\n");
	    option = DRC_HELP;
	}

	option = Lookup(argv[1], cmdDrcOption);
	if (option < 0)
	{
	    TxError("%s isn't a valid drc option.\n", argv[1]);
	    option = DRC_HELP;
	    argc = 2;
	}
	if ((argc > 2) && (option != PRINTRULES) && (option != FIND)
	    && (option != SHOWINT) && (option != DRC_HELP) && (option != EUCLIDEAN)
	    && (option != DRC_STEPSIZE) && (option != DRC_HALO) && (option != COUNT)
	    && (option != DRC_STYLE) && (option != DRC_IGNORE))
	{
	    badusage:
	    TxError("Wrong arguments in \"drc %s\" command:\n", argv[1]);
	    TxError("    drc %s\n", cmdDrcOption[option]);
	    TxError("Try \"drc help\" for more help.\n");
	    return;
	}
    }
    switch (option)
    {
	case FLATCHECK:
	    window = ToolGetBoxWindow(&rootArea, (int *) NULL);
	    if (window == NULL) return;
	    rootUse = (CellUse *) window->w_surfaceID;
	    DRCFlatCheck(rootUse, &rootArea);
	    break;

	case SHOWINT:
	    if (argc != 3) goto badusage;
	    radius = cmdParseCoord(w, argv[2], TRUE, FALSE);
	    if (radius < 0)
	    {
		TxPrintf("Radius must not be negative\n");
		return;
	    }
	    window = ToolGetBoxWindow(&rootArea, (int *) NULL);
	    if (window == NULL) return;
	    rootUse = (CellUse *) window->w_surfaceID;
	    if (DRCFindInteractions(rootUse->cu_def, &rootArea,
		radius, &area) <= 0)
	    {
		TxPrintf("No interactions in this area for that radius.\n");
		return;
	    }
	    ToolMoveBox(TOOL_BL, &area.r_ll, FALSE, rootUse->cu_def);
	    ToolMoveCorner(TOOL_TR, &area.r_ur, FALSE, rootUse->cu_def);
	    break;

	case DRC_STYLE:
	    if (argc == 2)
		DRCPrintStyle(dolist, doforall, !doforall);
	    else
		DRCSetStyle(argv[2]);
	    break;

	case CATCHUP:
	    DRCCatchUp();
	    break;

	case CHECK:
	    window = ToolGetBoxWindow(&rootArea, (int *) NULL);
	    if (window == NULL) return;
	    rootUse = (CellUse *) window->w_surfaceID;
	    DRCCheck(rootUse, &rootArea);
	    break;

	case COUNT:
	    count_total = -1;
	    if (argc == 3)
		if (!strncmp(argv[2], "total", 5))
		    count_total = 0;

#ifdef MAGIC_WRAPPER
	    if (count_total == -1) lobj = Tcl_NewListObj(0, NULL);
#endif
	    if ((window = w) == NULL)
	    {
		window = ToolGetBoxWindow(&rootArea, (int *) NULL);
		if (window == NULL) return;
	    }
	    else
		rootArea = w->w_surfaceArea;

	    rootUse = (CellUse *) window->w_surfaceID;
	    dcl = DRCCount(rootUse, &rootArea, doforall);
	    free_magic1_t mm1 = freeMagic1_init();
	    while (dcl != NULL)
	    {
		if (count_total >= 0)
		    count_total += dcl->dcl_count;
		else
		{
#ifdef MAGIC_WRAPPER
		    if (dolist)
		    {
			Tcl_Obj *pobj = Tcl_NewListObj(0, NULL);
			Tcl_ListObjAppendElement(magicinterp, pobj,
				Tcl_NewStringObj(dcl->dcl_def->cd_name, -1));
			Tcl_ListObjAppendElement(magicinterp, pobj,
				Tcl_NewIntObj(dcl->dcl_count));
			Tcl_ListObjAppendElement(magicinterp, lobj, pobj);
		    }
		    else
		    {
#endif
		    if (dcl->dcl_count > 1)
			TxPrintf("Cell %s has %d error tiles.\n",
				dcl->dcl_def->cd_name, dcl->dcl_count);
		    else if (dcl->dcl_count == 1)
			TxPrintf("Cell %s has just one error tile.\n",
				dcl->dcl_def->cd_name);
#ifdef MAGIC_WRAPPER
		    }
#endif
		}
		freeMagic1(&mm1, (char *)dcl);
		dcl = dcl->dcl_next;
	    }
	    freeMagic1_end(&mm1);

#ifdef MAGIC_WRAPPER
	    if ((count_total >= 0) || (!dolist))
	    {
		if (dolist)
		    Tcl_SetObjResult(magicinterp, Tcl_NewIntObj(count_total));
		else
		{
		    if ((DRCBackGround != DRC_SET_OFF) && (count_total == -1))
			count_total = 0;
		    if (count_total >= 0)
			TxPrintf("Total DRC errors found: %d\n", count_total);
		}
	    }
	    else if (dolist)
		Tcl_SetObjResult(magicinterp, lobj);

#else
	    if ((DRCBackGround != DRC_SET_OFF) && (count_total == -1))
		count_total = 0;
	    if (count_total >= 0)
		TxPrintf("Total DRC errors found: %d\n", count_total);
#endif
	    break;

	case EUCLIDEAN:
	    if (argc == 2)
	    {
		TxPrintf("DRC measurements are %s.\n", DRCEuclidean ?
			"Euclidean" : "Manhattan");
	    }
	    else
	    {
		if (!strcmp(argv[2], "on"))
		    DRCEuclidean = TRUE;
		else
		    DRCEuclidean = FALSE;
	    }
	    break;

	case FIND:
	    if ((window = w) == NULL)
	    {
		window = ToolGetBoxWindow(&rootArea, (int *) NULL);
		if (window == NULL) return;
	    }
	    else
		rootArea = w->w_surfaceArea;
	    if (argc > 3) goto badusage;

	    rootUse = (CellUse *) window->w_surfaceID;
	    rootDef = rootUse->cu_def;

	    incremental = FALSE;
	    findonly = FALSE;
	    if ((argc == 3) && StrIsInt(argv[2]))
	    {
		drc_nth = atoi(argv[2]);
		if (drc_nth <= 0) drc_nth = 1;
	    }
	    else
	    {
	        incremental = TRUE;
		drc_nth++;

		/* If 3rd argument is a string, then look only for
		 * rules containing the string text.
		 */
		if (argc == 3)
		{
		    int i;

		    findonly = TRUE;
		    DRCSaveRules = DRCIgnoreRules;
		    DRCIgnoreRules = NULL;
		    for (i = 1; i < DRCCurStyle->DRCWhySize; i++)
		    {
	 		if (strstr(DRCCurStyle->DRCWhyList[i], argv[2]) != NULL)
			{
			    LinkedIndex *newli;

			    newli = (LinkedIndex *)mallocMagic(sizeof(LinkedIndex));
			    newli->li_index = i;
			    newli->li_next = DRCIgnoreRules;
			    DRCIgnoreRules = newli;
			}
		    }
		}
	    }

	    drc_start = -1;
	    while (TRUE)
	    {
		/* May need to loop multiple times if some error types
		 * are being ignored.
		 */

		result = DRCFind(rootUse, &rootArea, &area, drc_nth);
		if (incremental && (result < 0))
		{
		    /* 2nd pass, if we exceeded the total number of errors */
		    drc_nth = 1;
		    result = DRCFind(rootUse, &rootArea, &area, drc_nth);
		}

		/* If looping on an incremental DRC error search, and errors
		 * are being ignored, and there are no non-ignored errors in
		 * the search area, then quit if we have looped back around
		 * to the starting point.
		 */
		if ((drc_start > 0) && (result == drc_start))
		    break;

		if (result > 0)
		{
		    ToolMoveBox(TOOL_BL, &area.r_ll, FALSE, rootDef);
		    ToolMoveCorner(TOOL_TR, &area.r_ur, FALSE, rootDef);
#ifdef MAGIC_WRAPPER
		    if (!dolist)
#endif
		    TxPrintf("Error area #%d:\n", result);
		    if (DRCWhy(dolist, rootUse, &area, findonly)) break;
		    /* Check for interrupt or this will go into an infinite loop */
		    else if (SigInterruptPending) break;
		    drc_nth++;
		}
		else if (result < 0)
		{
#ifdef MAGIC_WRAPPER
		    Tcl_SetObjResult(magicinterp, Tcl_NewIntObj(-1));
		    if (!dolist)
#endif
		    TxPrintf("There aren't that many errors");
		    break;
		}
		else
		{
#ifdef MAGIC_WRAPPER
		    Tcl_SetObjResult(magicinterp, Tcl_NewIntObj(0));
		    if (!dolist)
#endif
		    TxPrintf("There are no errors in %s.\n", rootDef->cd_name);
		    break;
		}

		if (drc_start < 0)
		    drc_start = result;
	    }
	    if (findonly)
	    {
		free_magic1_t mm1 = freeMagic1_init();
		/* Delete temporary rules */
		while (DRCIgnoreRules != NULL)
		{
		    freeMagic1(&mm1, DRCIgnoreRules);
		    DRCIgnoreRules = DRCIgnoreRules->li_next;
		}
		freeMagic1_end(&mm1);
		/* Replace temporary set of rules */
		DRCIgnoreRules = DRCSaveRules;
	    }
	    break;

	case DRC_HALO:
	    if (argc == 3)
	    {
		DRCTechHalo = cmdScaleCoord(w, argv[2], FALSE, TRUE, 1);
		if (DRCTechHalo > DRCCurStyle->DRCTechHalo)
		    DRCTechHalo = DRCCurStyle->DRCTechHalo;

		if (DRCTechHalo < DRCCurStyle->DRCTechHalo)
		    TxPrintf("Warning: rulechecking limited to halo of "
				"%d internal units (%d DRC units).\n",
				DRCTechHalo,
				DRCTechHalo * DRCCurStyle->DRCScaleFactorN
				/ DRCCurStyle->DRCScaleFactorD);
		else
		    TxPrintf("DRC checks all rules (halo of %d internal units, or"
				" %d DRC units)\n",
				DRCTechHalo,
				DRCTechHalo * DRCCurStyle->DRCScaleFactorN
				/ DRCCurStyle->DRCScaleFactorD);
	    }
	    else
	    {
		TxPrintf("DRC halo is %d internal units (%d DRC units)\n",
				DRCTechHalo,
				DRCTechHalo * DRCCurStyle->DRCScaleFactorN
				/ DRCCurStyle->DRCScaleFactorD);
		if (DRCTechHalo != DRCCurStyle->DRCTechHalo)
		    TxPrintf("Maximum rule distance is %d internal units "
				"(%d DRC units)\n",
				DRCCurStyle->DRCTechHalo,
				DRCCurStyle->DRCTechHalo * DRCCurStyle->DRCScaleFactorN
				/ DRCCurStyle->DRCScaleFactorD);
	    }
	    break;

	case DRC_HELP:
	    if ((argc == 3)
		    && (strcmp(argv[2], "wizard") == 0))
		wizardHelp = TRUE;
	    else wizardHelp = FALSE;
#ifdef MAGIC_WRAPPER
	    TxPrintf("DRC commands have the form \":drc [list|listall] option\",");
#else
	    TxPrintf("DRC commands have the form \":drc option\",");
#endif
	    TxPrintf(" where option is one of:\n");
	    for (msg = &(cmdDrcOption[0]); *msg != NULL; msg++)
	    {
		if ((**msg == '*') && !wizardHelp) continue;
		TxPrintf("    %s\n", *msg);
	    }
	    break;

	case DRC_IGNORE:
	    /* Ignore rules containing the given text, unless the text is "none",
	     * in which case clear the list of rules to ignore.
	     */
	    if (argc == 2)
	    {
		LinkedIndex *li;

		/* Print or list rules being ignored */

		li = DRCIgnoreRules;
		while (li != NULL)
		{
		    TxPrintf("%s\n", DRCCurStyle->DRCWhyList[li->li_index]);
		    li = li->li_next;
		}
		if (DRCIgnoreRules == NULL) TxPrintf("(none)\n");
		break;
	    }
	    if (argc != 3) goto badusage;
	    if (!strcasecmp(argv[2], "none"))
	    {
		while (DRCIgnoreRules != NULL)
		{
		    free_magic1_t mm1 = freeMagic1_init();
		    freeMagic1(&mm1, DRCIgnoreRules);
		    DRCIgnoreRules = DRCIgnoreRules->li_next;
		    freeMagic1_end(&mm1);
		}
	    }
	    else
	    {
		int i;
		LinkedIndex *newli;
		for (i = 1; i < DRCCurStyle->DRCWhySize; i++)
		{
		    if (strstr(DRCCurStyle->DRCWhyList[i], argv[2]) != NULL)
		    {
			newli = (LinkedIndex *)mallocMagic(sizeof(LinkedIndex));
			newli->li_index = i;
			newli->li_next = DRCIgnoreRules;
			DRCIgnoreRules = newli;
		    }
		}
	    }
	    break;

	case DRC_OFF:
#ifdef MAGIC_WRAPPER
	    /* Turn on long enough to force a break, then turn off */
	    DRCBackGround = DRC_SET_ON;
	    DRCStatus = DRC_IN_PROGRESS;
	    DRCBreak();
#endif
	    DRCBackGround = DRC_SET_OFF;
#ifdef MAGIC_WRAPPER
	    if (TxInputRedirect != TX_INPUT_REDIRECTED)
#endif
		TxSetPrompt('%');		/* Return prompt to "normal" */
	    break;

	case DRC_ON:
	    /* Don't allow overriding of DRC_NOT_SET */
	    if (DRCBackGround == DRC_SET_OFF) DRCBackGround = DRC_SET_ON;
	    break;

	case DRC_STATUS:
#ifdef MAGIC_WRAPPER
	    Tcl_SetObjResult(magicinterp,
			Tcl_NewBooleanObj((DRCBackGround == DRC_SET_OFF) ?
			FALSE : TRUE));
#else
	    TxPrintf("DRC checker is %s.\n", (DRCBackGround == DRC_SET_OFF) ?
			"off" : "on");
#endif
	    break;

	case PRINTRULES:
	    if (argc > 3) goto badusage;
	    if (argc < 3)
	    {
		DRCPrintRulesTable (stdout);
	    }
	    else
	    {
	        FILE *fp = fopen (argv[2], "w");
	        if (fp == NULL)
		{
		    TxError("Cannot write file %s\n", argv[2]);
		    return;
		}
		DRCPrintRulesTable (fp);
		fclose(fp);
	    }
	    break;

	case RULESTATS:
	    DRCTechRuleStats();
	    break;

	case STATISTICS:
	    DRCPrintStats();
	    break;

	case DRC_STEPSIZE:
	    if (argc == 3)
	    {
		DRCStepSize = cmdScaleCoord(w, argv[2], FALSE, TRUE, 1);
		TxPrintf("DRC step size is now %d internal units (%d DRC units))\n",
				DRCStepSize,
				DRCStepSize * DRCCurStyle->DRCScaleFactorN
				/ DRCCurStyle->DRCScaleFactorD);
	    }
	    else
	    {
		TxPrintf("DRC step size is %d internal units (%d DRC units)\n",
			DRCStepSize,
			DRCStepSize * DRCCurStyle->DRCScaleFactorN
			/ DRCCurStyle->DRCScaleFactorD);
		if (DRCStepSize != (16 * DRCCurStyle->DRCTechHalo))
		    TxPrintf("Recommended step size is %d internal units"
				" (%d DRC units)\n",
				(16 * DRCCurStyle->DRCTechHalo),
				(16 * DRCCurStyle->DRCTechHalo) *
				DRCCurStyle->DRCScaleFactorN /
				DRCCurStyle->DRCScaleFactorD);
	    }
	    break;

	case WHY:
	    window = ToolGetBoxWindow(&rootArea, (int *) NULL);
	    if (window == NULL) return;
	    rootUse = (CellUse *) window->w_surfaceID;

#ifdef MAGIC_WRAPPER
	    if (doforall)
	       DRCWhyAll(rootUse, &rootArea, NULL);
	    else
#endif
	    if (!DRCWhy(dolist, rootUse, &rootArea, FALSE))
		TxPrintf("No errors found.\n");
	    break;
    }
    return;
}

/*
 * ----------------------------------------------------------------------------
 *
 * cmdDropPaintCell ---
 *
 *	Callback function used by cmdDropFunc.  Called for each tile found in
 *	the edit cell hierarchy that matches paint that was in the selection.
 *	Paints layers from lMask (clientData) into the subcell containing the
 *	tile, within the area of the tile.
 *
 * Returns:
 *	Always returns zero to keep the search going.
 *
 * ----------------------------------------------------------------------------
 */

int
cmdDropPaintCell(
    Tile *tile,
    TileType dinfo,
    TreeContext *cxp)
{
    CellDef *cellDef = cxp->tc_scx->scx_use->cu_def;
    TileTypeBitMask *lMask = (TileTypeBitMask *)cxp->tc_filter->tf_arg;
    int pNum;
    TileType type;
    Rect area;

    if (dinfo & TT_SIDE)
        type = SplitRightType(tile);
    else
        type = SplitLeftType(tile);
    pNum = DBPlane(type);

    TiToRect(tile, &area);

    /* Clip to search area */
    GEOCLIP(&area, &cxp->tc_scx->scx_area);

    DBPaintMask(cellDef, &area, lMask);

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * cmdDropFunc ---
 *
 *	Callback function used by CmdDrop.  Called for each tile found in the
 *	selection.
 *
 * Returns:
 *	Always returns zero to keep the search going.
 *
 * ----------------------------------------------------------------------------
 */

int
cmdDropFunc(
    Tile *tile,
    TileType dinfo,
    ClientData clientData)
{
    TileTypeBitMask tMask, *lMask = (TileTypeBitMask *)clientData;
    SearchContext scx;
    TileType type;

    TiToRect(tile, &scx.scx_area);
    scx.scx_use = EditCellUse;
    scx.scx_trans = GeoIdentityTransform;

    if (dinfo & TT_SIDE)
	type = SplitRightType(tile);
    else
	type = SplitLeftType(tile);
    TTMaskSetOnlyType(&tMask, type);

    DBTreeSrTiles(&scx, &tMask, 0, cmdDropPaintCell, (ClientData)lMask);

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * cmdDropPaintFunc --
 *
 *	Callback function to SelEnumPaint() from CmdDrop().  Sets a bit in
 *	the type mask passed as clientData for the type of the tile found
 *	in the selection.
 *
 * ----------------------------------------------------------------------------
 */

int
cmdDropPaintFunc(
    Rect *rect,                 /* Not used. */
    TileType type,              /* Type of this piece of paint. */
    TileTypeBitMask *mask)      /* Place to OR in type's bit. */
{
    if (type & TT_DIAGONAL)
	type = (type & TT_SIDE) ? (type & TT_RIGHTMASK) >> 14 :
		(type & TT_LEFTMASK);
    TTMaskSetType(mask, type);
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdDrop --
 *
 *	Implement the ":drop" command.
 *
 * Usage:
 *	drop <layers>
 *
 * where <layers> is a list of paint layers.  The command requires an
 * existing selection, with the expectation that the selection contains
 * paint material that exists in subcells of the current edit cell.  The
 * "drop" command will copy the types in <layers> into every subcell in
 * the hierarchy of the current edit cell that contains selected material.
 *
 * The purpose of this command is to deal with issues arising from layers
 * in a vendor GDS file that must be in the same cell as a device layer
 * in order for the device to be extracted properly, but instead is placed
 * in a cell further up in the hierarchy.  A typical example is the deep
 * nwell layer, which isolates the transistor bulk terminal from the
 * substrate.  Without the deep nwell layer in the same cell as the
 * transistor, the transistor will be extracted with the bulk terminal
 * connected to the substrate.
 *
 * Note that the act of copying material down into a subcell means that
 * material is then present in all instances of the subcell.  There is
 * no implementation as yet to handle the case where some instances of the
 * same subcell require <layers> and some don't, which would necessitate
 * splitting the subcell into (at least) two different subcells, one
 * containing <layers> and one not.  This needs to be implemented.  A
 * possible implementation would be:  1st pass:  Find all subcells that
 * will be modified.  Make a list of the instances that will be modified.
 * After the first pass, check if the list of instances contains all
 * instances of the cell def.  If so, then just modify the cell def.  If
 * not, then make a copy the cell def and split off the instances that
 * get the modification to point to that cell def, then modify that cell
 * def.  2nd pass:  Run the "drop" command as before.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Subcells are modified by adding paint.
 *
 * ----------------------------------------------------------------------------
 */

void
CmdDrop(
    MagWindow *w,
    TxCommand *cmd)
{
    TileType i;
    TileTypeBitMask lMask, tMask;
    CellUse *checkUse;
    int pNum;
    Rect editBox;

    if (cmd->tx_argc != 2)
    {
	TxError("Usage: %s layers\n", cmd->tx_argv[0]);
	return;
    }

    if (!ToolGetEditBox(&editBox)) return;
    if (!CmdParseLayers(cmd->tx_argv[1], &lMask))
	return;

    checkUse = NULL;
    if (EditRootDef == SelectRootDef)
	checkUse = EditCellUse;
    if (checkUse == NULL)
    {
	if (w == (MagWindow *)NULL)
	    windCheckOnlyWindow(&w, DBWclientID);
	if (w) checkUse = (CellUse *)w->w_surfaceID;
    }
    if ((checkUse == NULL) || (checkUse->cu_def != SelectRootDef))
    {
	TxError("The selection does not match the edit cell.\n");
	return;
    }

    TTMaskZero(&tMask);
    SelEnumPaint(&DBAllButSpaceAndDRCBits, FALSE, (bool *)NULL,
	    cmdDropPaintFunc, (ClientData)&tMask);

    if (TTMaskIsZero(&tMask)) return;	/* Nothing selected */
	
    for (i = TT_SELECTBASE; i < DBNumUserLayers; i++)
    {
	if (TTMaskHasType(&tMask, i))
	{
	    for (pNum = PL_TECHDEPBASE; pNum < DBNumPlanes; pNum++)
		if (TTMaskHasType(&DBPlaneTypes[pNum], i))
		    DBSrPaintArea((Tile *)NULL, SelectDef->cd_planes[pNum],
			    &SelectUse->cu_bbox, &tMask,
			    cmdDropFunc, (ClientData)&lMask);
	}
    }

    DRCCheckThis(EditCellUse->cu_def, TT_CHECKPAINT, &editBox);
    DBWAreaChanged(EditCellUse->cu_def, &editBox, DBW_ALLWINDOWS, &tMask);
    DBReComputeBbox(EditCellUse->cu_def);
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdDump --
 *
 *	Implement the ":dump" command.
 *
 * Usage:
 *	dump cellName [child refPointChild] [parent refPointParent]
 *
 * where the refPoints are either "label" and a label name, e.g.,
 * "label SOCKET_A", a corner position, e.g., "ur", or an x-y pair of
 * coordinates, e.g., "100 200", or "1um 5um".  The words "child",
 * "parent", and "label" are keywords, and may be abbreviated.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Copies the contents of a given cell into the edit cell,
 *	so that refPointChild in the child cell (or the lower-left
 *	corner of its bounding box) ends up at location refPointParent
 *	in the edit cell (or the location of the box tool's lower-left).
 *
 * ----------------------------------------------------------------------------
 */

void
CmdDump(
    MagWindow *w,		/* Window in which command was invoked. */
    TxCommand *cmd)		/* Describes command arguments. */
{
    SearchContext scx;
    CellUse dummy;

    if (cmdDumpParseArgs("dump", w, cmd, &dummy, &scx))
	SelectDump(&scx);
}

/*
 * ----------------------------------------------------------------------------
 *
 * cmdDumpParseArgs --
 *
 * Do the real work of the "dump" and "getcell" commands, by reading
 * in the child cell to be used and figuring out the transform implied
 * by the reference point arguments to the command.
 *
 * Results:
 *      TRUE on success; FALSE if arguments were missing or
 *	incorrect, or if the cell couldn't be found.
 *
 * Side effects:
 *	Fills in *dummy so that dummy->cu_def points to the cell
 *	specified by name in cmd->tx_argv[] (see CmdDump() for a
 *	description of the syntax of these args).  Also fills in
 *	*scx so scx_use is dummy, scx_trans is the desired transform
 *	from dummy->cu_def back to root coordinates, and scx_area
 *	is the bounding box of dummy->cu_def.  (Scx is set up
 *	directly for a call to SelectDump()).
 *
 * ----------------------------------------------------------------------------
 */

    /*ARGSUSED*/
bool
cmdDumpParseArgs(
    char *cmdName,	/* Either "dump" or "getcell" */
    MagWindow *w,	/* Window in which command was invoked (UNUSED) */
    TxCommand *cmd,	/* Arguments to command */
    CellUse *dummy,	/* Filled in to point to cell mentioned in command */
    SearchContext *scx)	/* Filled in with the transform from the child cell's
			 * def to ROOT coordinates, the bounding box of the
			 * child cell in child cell coordinates, and with
			 * scx_use = dummy, where dummy->cu_def is the child
			 * cell itself.
			 */
{
    Point childPoint, editPoint, rootPoint;
    CellDef *def, *rootDef, *editDef;
    bool hasChild, hasRoot, hasTrans;
    Rect rootBox, bbox;
    const Transform *tx_cell;
    Transform trans_cell;
    char **av;
    char *cellnameptr, *fullpathname;
    int ac, clen;

    if (cmd->tx_argc < 2)
    {
	TxError("Missing cell name in \"%s\" command.\n", cmdName);
	goto usage;
    }

    if (EditCellUse == NULL)
    {
	TxError("No cell def being edited; cannot place cell use!\n");
	return FALSE;
    }

    /* cellnameptr should not include any path components */
    if ((cellnameptr = strrchr(cmd->tx_argv[1], '/')) != NULL)
    {
	cellnameptr++;
	fullpathname = (char *)mallocMagic(strlen(cmd->tx_argv[1]) + 2);
	strcpy(fullpathname, cmd->tx_argv[1]);

    	/* If the name still has ".mag" attached, then strip it. */
    	clen = strlen(fullpathname);
    	if ((clen > 4) && !strcmp(fullpathname + clen - 4, ".mag"))
	    *(fullpathname + clen - 4) = '\0';
    }
    else
    {
	cellnameptr = cmd->tx_argv[1];
	fullpathname = NULL;
    }

    /* If the name still has ".mag" attached, then strip it. */
    clen = strlen(cellnameptr);
    if ((clen > 4) && !strcmp(cellnameptr + clen - 4, ".mag"))
	*(cellnameptr + clen - 4) = '\0';

    /* Check for illegal characters in the cellname */
    if (CmdIllegalChars(cellnameptr, "", "Cell name"))
    {
	if (fullpathname) freeMagic(fullpathname);
	return (FALSE);
    }

    def = DBCellLookDef(cellnameptr);
    if (def == (CellDef *) NULL)
	def = DBCellNewDef(cellnameptr);

    if (fullpathname != NULL)
    {
	/* Check if def already exists.  If it points to a	*/
	/* different file, then force a rename of the cell and	*/
	/* flag a warning.					*/

	if (def->cd_file != NULL)
	{
	    /* Note: may want processing to see if absolute paths match */
	    if (strcmp(def->cd_file, fullpathname))
	    {
		char uniqchar;
		char *newcellname = (char *)mallocMagic(strlen(cellnameptr) + 3);
		TxError("Warning:  Cell file path mismatch.  Existing cell has"
			" path \"%s\", while %s path is \"%s\".\n",
			def->cd_file, cmdName, fullpathname);
		uniqchar = 'a';
		while (def != NULL)
		{
		    sprintf(newcellname, "%s_%c", cellnameptr, uniqchar);
		    def = DBCellLookDef(newcellname);
		    uniqchar++;
		}
		TxError("Renaming cell to \"%s\" to avoid conflict.\n", newcellname);
		def = DBCellNewDef(newcellname);
		StrDup(&def->cd_file, fullpathname);
		freeMagic(newcellname);
	    }
	}
	else
	    StrDup(&def->cd_file, fullpathname);
	freeMagic(fullpathname);
    }

    editDef = EditCellUse->cu_def;

    /*
     * The following line of code is a bit of a hack.  It's needed to
     * force DBCellRead to print an error message if it can't find the
     * cell.  Otherwise, if the cell wasn't found the last time it was
     * looked for then no new error message will be printed.
     */
    def->cd_flags &= ~CDNOTFOUND;
    if (!DBCellRead(def, TRUE, TRUE, NULL))
	return (FALSE);
    DBReComputeBbox(def);
    dummy->cu_def = def;
    dummy->cu_transform = GeoIdentityTransform;
    dummy->cu_expandMask = CU_DESCEND_SPECIAL;
    if (DBIsAncestor(def, EditCellUse->cu_def))
    {
	TxError("The edit cell is already a descendant of \"%s\",\n",
	    cmd->tx_argv[1]);
	TxError("    which means that you're trying to create a circular\n");
	TxError("    structure.  This isn't legal.\n");
	return FALSE;
    }

    /*
     * Get def's bounding box.  If def is an abstract view with CDFIXEDBBOX
     * set, then used the property FIXED_BBOX to set the bounding box.
     */
    bbox = def->cd_bbox;
    if (def->cd_flags & CDFIXEDBBOX)
    {
	PropertyRecord *proprec;
	bool found;

	proprec = DBPropGet(def, "FIXED_BBOX", &found);
	if (found)
	{
	    if ((proprec->prop_type == PROPERTY_TYPE_DIMENSION) &&
			(proprec->prop_len == 4))
	    {
		bbox.r_xbot = proprec->prop_value.prop_integer[0];
		bbox.r_ybot = proprec->prop_value.prop_integer[1];
		bbox.r_xtop = proprec->prop_value.prop_integer[2];
		bbox.r_ytop = proprec->prop_value.prop_integer[3];
	    }
	}
    }

    /*
     * Parse the remainder of the arguments to find out the reference
     * points in the child cell and the edit cell.  Use the defaults
     * of the lower-left corner of the child cell's bounding box, and
     * the lower-left corner of the box tool, if the respective reference
     * points weren't provided.  (Lower-left of the box tool is interpreted
     * in root coordinates).
     */

    /*
     * Examples:  getcell cellname v child 0 0 parent ll
     *		  getcell cellname child 0 0 parent 1um 1um
     *		  getcell cellname child 0 0
     *		  getcell cellname 90v child label VDD
     * etc.
     */

    av = &cmd->tx_argv[2];
    ac = cmd->tx_argc - 2;
    hasChild = hasRoot = hasTrans = FALSE;
    while (ac > 0)
    {
	static const char * const kwdNames[] = { "child", "parent",
					"0", "90", "180", "270",
					"v", "0v", "90v", "180v", "270v",
					"h", "0h", "90h", "180h", "270h", 0 };
	typedef enum {
		IDX_CHILD, IDX_PARENT,
		IDX_ZERO, IDX_90, IDX_180, IDX_270,
		IDX_VERT, IDX_ZERO_VERT, IDX_90_VERT, IDX_180_VERT, IDX_270_VERT,
		IDX_HORZ, IDX_ZERO_HORZ, IDX_90_HORZ, IDX_180_HORZ, IDX_270_HORZ
	} optionType;

	static const char * const refPointNames[] = {
			"ll", "lr", "ul", "ur", "label", 0 };
	typedef enum {
		IDX_LL, IDX_LR, IDX_UL, IDX_UR, IDX_LABEL
	} refPointType;

	Label *lab;
	int n, p;
	Point locp;

	n = Lookup(av[0], kwdNames);
	if (n < 0)
	{
	    TxError("Unrecognized parent/child keyword: \"%s\"\n", av[0]);
	    goto usage;
	}
	switch (n)
	{
	    case  IDX_CHILD:	/* "child" */
		if (ac < 2)
		{
		    TxError("Keyword must be followed by a reference point\n");
		    goto usage;
	        }
		else
		{
		    p = Lookup(av[1], refPointNames);
		    if (p == IDX_LL) /* lower left */
		    {
			childPoint.p_x = bbox.r_ll.p_x;
			childPoint.p_y = bbox.r_ll.p_y;
		    }
		    else if (p == IDX_LR) /* lower right */
		    {
			childPoint.p_x = bbox.r_ur.p_x;
			childPoint.p_y = bbox.r_ll.p_y;
		    }
		    else if (p == IDX_UL) /* upper left */
		    {
			childPoint.p_x = bbox.r_ll.p_x;
			childPoint.p_y = bbox.r_ur.p_y;
		    }
		    else if (p == IDX_UR) /* upper right */
		    {
			childPoint.p_x = bbox.r_ur.p_x;
			childPoint.p_y = bbox.r_ur.p_y;
		    }
		    else if ((p == IDX_LABEL) && (ac >= 3)) /* label */
		    {
			childPoint = TiPlaneRect.r_ur;
			(void) DBSrLabelLoc(dummy, av[2], cmdDumpFunc,
					    &childPoint);
			if (childPoint.p_x == TiPlaneRect.r_xtop &&
			    childPoint.p_y == TiPlaneRect.r_ytop)
			{
			    TxError("Couldn't find label \"%s\" in cell \"%s\".\n",
				    av[2], cmd->tx_argv[1]);
			    return FALSE;
			}
			av += 1;
			ac -= 1;
		    }
		    else if (ac >= 3)	/* Coordinate pair */
		    {
			childPoint.p_x = cmdParseCoord(w, av[1], TRUE, TRUE);
			childPoint.p_y = cmdParseCoord(w, av[2], TRUE, FALSE);
			av += 1;
			ac -= 1;
		    }
		    else
		    {
			TxError("Must provide two valid coordinates\n");
			goto usage;
		    }
		    av += 2;
		    ac -= 2;
		}
		hasChild = TRUE;
		break;

	    case  IDX_PARENT:	/* "parent" */
		if (ac < 2)
		{
		    TxError("Keyword must be followed by a reference point\n");
		    goto usage;
		}
		else
		{
		    p = Lookup(av[1], refPointNames);
		    if (p == IDX_LL)	/* lower left */
		    {
			if (!ToolGetBox(&rootDef, &rootBox) ||
			    (rootDef != EditRootDef)) goto box_error;
			rootPoint.p_x = rootBox.r_ll.p_x;
			rootPoint.p_y = rootBox.r_ll.p_y;
		    }
		    else if (p == IDX_LR) /* lower right */
		    {
			if (!ToolGetBox(&rootDef, &rootBox) ||
			    (rootDef != EditRootDef)) goto box_error;
			rootPoint.p_x = rootBox.r_ur.p_x;
			rootPoint.p_y = rootBox.r_ll.p_y;
		    }
		    else if (p == IDX_UL) /* upper left */
		    {
			if (!ToolGetBox(&rootDef, &rootBox) ||
			    (rootDef != EditRootDef)) goto box_error;
			rootPoint.p_x = rootBox.r_ll.p_x;
			rootPoint.p_y = rootBox.r_ur.p_y;
		    }
		    else if (p == IDX_UR) /* upper right */
		    {
			if (!ToolGetBox(&rootDef, &rootBox) ||
			    (rootDef != EditRootDef)) goto box_error;
			rootPoint.p_x = rootBox.r_ur.p_x;
			rootPoint.p_y = rootBox.r_ur.p_y;
		    }
		    else if ((p == IDX_LABEL) && (ac >= 3))	/* label */
		    {
			for (lab = editDef->cd_labels; lab; lab = lab->lab_next)
			    if (strcmp(lab->lab_text, av[2]) == 0)
				break;

			if (lab == NULL)
			{
			    TxError("Couldn't find label \"%s\" in edit cell.\n",
				    av[2]);
			    return FALSE;
			}
			editPoint = lab->lab_rect.r_ll;
			GeoTransPoint(&EditToRootTransform, &editPoint,
				      &rootPoint);
			av += 1;
			ac -= 1;
		    }
		    else if (ac >= 3)	/* Coordinate pair */
		    {
			editPoint.p_x = cmdParseCoord(w, av[1], TRUE, TRUE);
			editPoint.p_y = cmdParseCoord(w, av[2], TRUE, FALSE);
			av += 1;
			ac -= 1;
			GeoTransPoint(&EditToRootTransform, &editPoint,
				  &rootPoint);
		    }
		    else
		    {
			TxError("Must provide two valid coordinates\n");
			goto usage;
		    }
		    av += 2;
		    ac -= 2;
		}
		hasRoot = TRUE;
		break;
	    case  IDX_ZERO:	/* "0" */
		tx_cell = &GeoIdentityTransform;
transform_cell:
		if (ac < 2 )
		{
default_action:
		    {
			Rect r;

			GeoTransRect(tx_cell, &bbox, &r);
			GeoTranslateTrans(tx_cell, bbox.r_xbot - r.r_xbot,
					  bbox.r_ybot - r.r_ybot,
					  &trans_cell);
		    }
		    av += 1;
		    ac -= 1;
		}

		/*
		 * Error case:
		 * getcell cellname v 0 0 ->
		 *	read 0 in kwdNames
		 *	goto default transform
		 * av[1] = "0", "90", "180", "270" case ->
		 *	av[1] must mean editpoint coordinate
		 */

		else if (Lookup(av[1], kwdNames) >= 0 &&
			Lookup(av[1], kwdNames) != 2 &&
			strcmp(av[1], "90") &&
			strcmp(av[1], "180") &&
			strcmp(av[1], "270"))
		{
		    goto default_action;
		}
		else
		{
		    p = Lookup(av[1], refPointNames);
		    if (p == IDX_LL) /* lower left */
		    {
			editPoint.p_x = bbox.r_ll.p_x;
			editPoint.p_y = bbox.r_ll.p_y;
		    }
		    else if (p == IDX_LR) /* lower right */
		    {
			editPoint.p_x = bbox.r_ur.p_x;
			editPoint.p_y = bbox.r_ll.p_y;
		    }
		    else if (p == IDX_UL) /* upper left */
		    {
			editPoint.p_x = bbox.r_ll.p_x;
			editPoint.p_y = bbox.r_ur.p_y;
		    }
		    else if (p == IDX_UR) /* upper right */
		    {
			editPoint.p_x = bbox.r_ur.p_x;
			editPoint.p_y = bbox.r_ur.p_y;
		    }
		    else if ((p == IDX_LABEL) && (ac >= 3))	/* label */
		    {
			editPoint = TiPlaneRect.r_ur;
			(void) DBSrLabelLoc(dummy, av[1], cmdDumpFunc, &editPoint);
			if (editPoint.p_x == TiPlaneRect.r_xtop &&
				editPoint.p_y == TiPlaneRect.r_ytop)
			{
			    TxError("Couldn't find label \"%s\" in cell \"%s\".\n",
					av[1], cmd->tx_argv[1]);
			    return FALSE;
			}
			av += 1;
			ac -= 1;
		    }
		    else if (ac >= 3)	/* Coordinate pair */
		    {
			editPoint.p_x = cmdParseCoord(w, av[1], TRUE, TRUE);
			editPoint.p_y = cmdParseCoord(w, av[2], TRUE, FALSE);
			av += 1;
			ac -= 1;
		    }
		    else
		    {
			TxError("Must provide two valid coordinates\n");
			goto usage;
		    }
		    av += 2;
		    ac -= 2;

		    GeoTransPoint(tx_cell, &editPoint, &locp);
		    GeoTranslateTrans(tx_cell, editPoint.p_x - locp.p_x,
					editPoint.p_y - locp.p_y,
					&trans_cell);
		}
		hasTrans = TRUE;
		break;
	    case  IDX_90:		/* "90" */
		tx_cell = &Geo90Transform;
		goto transform_cell;
	    case  IDX_180:		/* "180" */
		tx_cell = &Geo180Transform;
		goto transform_cell;
	    case  IDX_270:		/* "270" */
		tx_cell = &Geo270Transform;
		goto transform_cell;
	    case  IDX_VERT:		/* "v" */
	    case  IDX_ZERO_VERT:	/* "0v" */
		tx_cell = &GeoUpsideDownTransform;
		goto transform_cell;
	    case  IDX_90_VERT:		/* "90v" */
		tx_cell = &GeoRef45Transform;
		goto transform_cell;
	    case  IDX_180_VERT:		/* "180v" */
		tx_cell = &GeoSidewaysTransform;
		goto transform_cell;
	    case  IDX_270_VERT:		/* "270v" */
		tx_cell = &GeoRef135Transform;
		goto transform_cell;
	    case  IDX_HORZ:		/* "h" */
	    case  IDX_ZERO_HORZ:	/* "0h" */
		tx_cell = &GeoSidewaysTransform;
		goto transform_cell;
	    case  IDX_90_HORZ:		/* "90h" */
		tx_cell = &GeoRef135Transform;
		goto transform_cell;
	    case  IDX_180_HORZ:		/* "180h" */
		tx_cell = &GeoUpsideDownTransform;
		goto transform_cell;
	    case  IDX_270_HORZ:		/* "270h" */
		tx_cell = &GeoRef45Transform;
		goto transform_cell;
	}
    }

    /*
     * Use the default values if explicit reference points weren't
     * provided.
     */
    if (!hasChild)
	childPoint = bbox.r_ll;
    if (!hasRoot)
    {
	if (!ToolGetBox(&rootDef, &rootBox))
	{
box_error:
	    TxError("The box's lower-left corner must point to the place\n");
	    TxError("    in the edit cell where you'd like to put \"%s\".\n",
		cmd->tx_argv[1]);
	    return FALSE;
	}
	else if (rootDef != EditRootDef)
	{
	    TxError("The box is in cell \"%s\", not in the edit cell \"%s\"\n",
			rootDef->cd_name, EditRootDef->cd_name);
	    return FALSE;
	}
	rootPoint = rootBox.r_ll;
    }
    if (!hasTrans)
    {
	trans_cell = GeoIdentityTransform;
    }

    scx->scx_use = dummy;

    /* Transform childPoint by trans_cell */
    if (hasChild)
    {
	Point refpoint;

	GeoTransPoint(&trans_cell, &childPoint, &refpoint);
	GeoTranslateTrans(&trans_cell, rootPoint.p_x - refpoint.p_x,
		rootPoint.p_y - refpoint.p_y, &scx->scx_trans);
    }
    else
    {
	GeoTranslateTrans(&trans_cell, rootPoint.p_x - childPoint.p_x,
		rootPoint.p_y - childPoint.p_y, &scx->scx_trans);
    }

    scx->scx_area = bbox;
    return TRUE;

usage:
    TxError(
	"Usage: %s cellName [child refPointChild] [parent refPointParent]\n",
	cmdName);
    TxError("       [transform [refPointTrans]],\n");
    TxError("       where the refPoints are one of:\n");
    TxError(
    "	    'label' followed by a single label name,\n");
    TxError(
    "       or 'll' for lower left corner, 'lr' for lower right corner,\n");
    TxError(
    "       'ul' for upper left corner, 'ur' for upper right corner,\n");
    TxError(
    "       or a pair of coordinates.  The transform is one of:\n");
    TxError(
    "       90, 180, 270, v, 90v, 180v, 270v, h, 90h, 180h, or 270h.\n");
    return FALSE;
}

/*
 * cmdDumpFunc --
 *
 * Search function used to locate positioning label.  It just computes
 * the lower-left corner of the label and aborts the search.
 *
 * Results:
 *	Always returns 1.
 *
 * Side effects:
 *	Sets *point to the lower-left corner of the label.
 */

    /* ARGSUSED */
int
cmdDumpFunc(
    Rect *rect,			/* Root coordinates of the label. */
    char *name,			/* Label name (not used). */
    Label *label,		/* Pointer to label (not used). */
    Point *point)		/* Place to store label's lower-left. */
{
    *point = rect->r_ll;
    return 1;
}

