/*
 * CmdFI.c --
 *
 * Commands with names beginning with the letters F through I.
 *
 *     ********************************************************************* 
 *     * 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 char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-7.5/commands/CmdFI.c,v 1.7 2008/02/01 17:46:46 tim Exp $";
#endif  /* not lint */

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

#ifdef USE_READLINE
#ifdef HAVE_READLINE
#include <readline/readline.h>
#include <readline/history.h>
#else
#include "readline/readline/readline.h"
#include "readline/readline/history.h"
#endif
#endif

#include "tcltk/tclmagic.h"
#include "utils/magic.h"
#include "utils/geometry.h"
#include "utils/utils.h"
#include "utils/undo.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 "textio/textio.h"
#include "utils/macros.h"
#include "drc/drc.h"
#include "textio/txcommands.h"
#include "utils/styles.h"
#include "graphics/graphics.h"
#include "extract/extract.h"
#include "utils/malloc.h"
#include "select/select.h"
#include "sim/sim.h"
#include "gcr/gcr.h"

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

struct cmdFillArea
{
    Rect cfa_area;			/* Area to fill. */
    TileType cfa_type;			/* Type of material. */
    struct cmdFillArea *cfa_next;	/* Next in list of areas to fill. */
};

/* The following structure passes arguments needed by the	  */
/* DBWFeedbackAdd command to the feedPolyFunc() callback routine. */

struct cmdFPArg
{
   CellDef *def;
   int style;
   char *text;
};

#define FEEDMAGNIFY 20	/* Allow feedback to be drawn to 1/20 of an 	*/
			/* internal unit.				*/

/*
 * ----------------------------------------------------------------------------
 *
 * feedPolyFunc --
 *
 *	This procedure generates feedback entries in a polygonal area
 *	by calling DBWFeedbackArea per tile on simple 1-bit types in
 *	a plane.  The use of the temporary plane allows us to use the
 *	same path decomposition routine used for CIF input, polygon
 *	drawing, and wire segments.
 *
 * ----------------------------------------------------------------------------
 */

int
feedPolyFunc(tile, arg)
    Tile *tile;
    struct cmdFPArg *arg;
{
    Rect area;
    TiToRect(tile, &area);

    DBWFeedbackAdd(&area, arg->text, arg->def, FEEDMAGNIFY,
#ifdef NONMANHATTAN
        arg->style |
                (TiGetTypeExact(tile) & (TT_DIAGONAL | TT_DIRECTION | TT_SIDE)));
        /* (preserve information about the geometry of a diagonal tile) */
#else
        arg->style);
#endif
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdFeedback --
 *
 * 	Implement the "feedback" command, which provides facilities
 *	for querying and manipulating feedback information provided
 *	by other commands when they have troubles or want to highlight
 *	certain things.
 *
 * Usage:
 *	feedback option [additional_args]
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Depends on the option.
 *
 * ----------------------------------------------------------------------------
 */

#undef	CLEAR
#define ADD		0
#define CLEAR		1
#define COUNT		2
#define FIND		3
#define FEED_HELP	4
#define SAVE		5
#define WHY		6

void
CmdFeedback(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    static char *cmdFeedbackOptions[] =
    {
	"add text [style] [points...]	create new feedback area over box",
	"clear [substring]		clear all or selected feedback info",
	"count				count # feedback entries",
	"find [nth]			put box over next [or nth] entry",
	"help				print this message",
	"save file			save feedback areas in file",
	"why				print all feedback messages under box",
	NULL
    };
    static char *cmdFeedbackStyleNames[] =
    {
	"dotted", "medium", "outline", "pale", "solid", NULL
    };
    static int cmdFeedbackStyles[] =
    {
	STYLE_DOTTEDHIGHLIGHTS, STYLE_MEDIUMHIGHLIGHTS,
	STYLE_OUTLINEHIGHLIGHTS, STYLE_PALEHIGHLIGHTS,
	STYLE_SOLIDHIGHLIGHTS, -1
    };
    static int nth = 0;			/* Last entry displayed in
					 * "feedback find".
					 */
    int option, i, style, pstart;
    Rect box, r;
    char *text, **msg;
    CellDef *rootDef;
    HashTable table;
    HashEntry *h;
    FILE *f;

    if (cmd->tx_argc < 2)
    {
	badusage:
	TxPrintf("Wrong number of arguments for \"feedback\" command.\n");
	TxPrintf("Type \":feedback help\" for help.\n");
	return;
    }
    option = Lookup(cmd->tx_argv[1], cmdFeedbackOptions);
    if (option < 0)
    {
	TxError("%s isn't a valid feedback option.  Try one of:\n",
	    cmd->tx_argv[1]);
	TxError("    add        find\n");
	TxError("    clear      help\n");
	TxError("    count      save\n");
	TxError("    save\n");
	return;
    }
    pstart = 4;		/* argument # where point list starts, if any */
    switch (option)
    {
	case ADD:
	    style = STYLE_PALEHIGHLIGHTS;
	    if (cmd->tx_argc > 3)
	    {
		i = Lookup(cmd->tx_argv[3], cmdFeedbackStyleNames);
		if (i < 0)
		{
		    if (StrIsNumeric(cmd->tx_argv[3]))
			pstart = 3;
		    else
		    {
		        style = GrGetStyleFromName(cmd->tx_argv[3]);
			if (style < 0)
			{
			    TxError("%s isn't a valid display style.  Try one of:\n",
					cmd->tx_argv[3]);
			    TxError("    dotted, pale, mediaum, solid, outline,\n");
			    TxError("    or a long name from the .dstyle file\n");
			    break;
			}
			if (style < 1 || style >= TECHBEGINSTYLES)
	 		{
			    TxError("Numbered styles must be between 1 and %d\n",
					TECHBEGINSTYLES - 1);
			    break;
			}
		    }
		}
		else style = cmdFeedbackStyles[i];
	    }
	    if (cmd->tx_argc - pstart > 0)
	    {
		/* points must be in X Y pairs */
		if ((cmd->tx_argc - pstart) & 1) goto badusage;

	        if (w == NULL) return;
		rootDef = ((CellUse *) w->w_surfaceID)->cu_def;

		/* Read coordinates in FEEDMAGNIFY x internal units,	*/
		/* then scale the highlight accordingly.		*/

		if ((cmd->tx_argc - pstart) == 2)
		{
		    /* Single point highlight.  Style MUST be outlined	*/
		    /* or else the point won't be visible.		*/

		    if (GrStyleTable[style].outline == 0)
			style = STYLE_OUTLINEHIGHLIGHTS;

		    box.r_xbot = box.r_xtop = cmdScaleCoord(w, cmd->tx_argv[pstart++],
				FALSE, TRUE, FEEDMAGNIFY);
		    box.r_ybot = box.r_ytop = cmdScaleCoord(w, cmd->tx_argv[pstart++],
				FALSE, FALSE, FEEDMAGNIFY);

		    DBWFeedbackAdd(&box, cmd->tx_argv[2], rootDef, FEEDMAGNIFY, style);
		}
		else if ((cmd->tx_argc - pstart) == 4)
		{
		    /* Single line highlight.  Style MUST be outlined,	*/
		    /* or else the line won't be visible.		*/

		    if (GrStyleTable[style].outline == 0)
			style = STYLE_OUTLINEHIGHLIGHTS;
		    r.r_xbot = cmdScaleCoord(w, cmd->tx_argv[pstart++],
				FALSE, TRUE, FEEDMAGNIFY);
		    r.r_ybot = cmdScaleCoord(w, cmd->tx_argv[pstart++],
				FALSE, FALSE, FEEDMAGNIFY);
		    r.r_xtop = cmdScaleCoord(w, cmd->tx_argv[pstart++],
				FALSE, TRUE, FEEDMAGNIFY);
		    r.r_ytop = cmdScaleCoord(w, cmd->tx_argv[pstart++],
				FALSE, FALSE, FEEDMAGNIFY);
		    if (r.r_xbot != r.r_xtop && r.r_ybot != r.r_ytop)
		    {
			/* Line at an angle.  The hack of setting TT_SIDE */
			/* without setting TT_DIAGONAL allows the drawing */
			/* routine to treat this case as a line, not a	  */
			/* triangle.					  */

			style |= TT_SIDE;
			if ((r.r_xbot > r.r_xtop && r.r_ybot < r.r_ytop) ||
				(r.r_xbot < r.r_xtop && r.r_ybot > r.r_ytop))
			    style |= TT_DIRECTION;
		    }
		    GeoCanonicalRect(&r, &box);
		    DBWFeedbackAdd(&box, cmd->tx_argv[2], rootDef, FEEDMAGNIFY, style);
		}
		else
		{
		    Plane *plane;
		    PaintResultType ptable[2] = {1, 1}; /* simple 1 bit table */
		    TileTypeBitMask feedSimpleMask;
		    Point *plist;
		    int points, j;
		    struct cmdFPArg fpargs;

		    points = (cmd->tx_argc - pstart) >> 1;
		    plist = (Point *)mallocMagic(points * sizeof(Point));
		    fpargs.def = rootDef;
		    fpargs.style = style;
		    fpargs.text = cmd->tx_argv[2];

		    for (i = 0, j = pstart; i < points; i++) 
		    {
			plist[i].p_x = cmdScaleCoord(w, cmd->tx_argv[j++],
				FALSE, TRUE, FEEDMAGNIFY);
			plist[i].p_y = cmdScaleCoord(w, cmd->tx_argv[j++],
				FALSE, FALSE, FEEDMAGNIFY);
		    }
		    plane = DBNewPlane((ClientData)0);
		    TTMaskZero(&feedSimpleMask);
		    TTMaskSetType(&feedSimpleMask, 1);
		    PaintPolygon(plist, points, plane, ptable, (PaintUndoInfo *)NULL);
		    i = DBWFeedbackCount;
		    DBSrPaintArea((Tile *)NULL, plane, &TiPlaneRect, &feedSimpleMask,
			feedPolyFunc, (ClientData)&fpargs);
		    TiFreePlane(plane);

		    if (i == DBWFeedbackCount)
		    {
			/* Pathological condition---feedback area is so thin	*/
			/* that the decomposition to rectangles and triangles	*/
			/* was degenerate.  If so, then we can just treat the	*/
			/* plist as several lines and/or points.		*/

			if (GrStyleTable[style].outline == 0)
			    style = STYLE_OUTLINEHIGHLIGHTS;

			for (i = 0; i < points - 1; i++)
			{
			    r.r_xbot = plist[i].p_x;
			    r.r_ybot = plist[i].p_y;
			    r.r_xtop = plist[i + 1].p_x;
			    r.r_ytop = plist[i + 1].p_y;
			    if (r.r_xbot != r.r_xtop && r.r_ybot != r.r_ytop)
		    	    {
				style |= TT_SIDE;
				if ((r.r_xbot > r.r_xtop && r.r_ybot < r.r_ytop) ||
					(r.r_xbot < r.r_xtop && r.r_ybot > r.r_ytop))
			    	style |= TT_DIRECTION;
			    }
			    GeoCanonicalRect(&r, &box);
			    DBWFeedbackAdd(&box, cmd->tx_argv[2], rootDef,
						FEEDMAGNIFY, style);
			}
		    }
		    freeMagic(plist);
		}	
	    }
	    else
	    {
		w = ToolGetBoxWindow(&box, (int *) NULL);
	        if (w == NULL) return;
		rootDef = ((CellUse *) w->w_surfaceID)->cu_def;
		DBWFeedbackAdd(&box, cmd->tx_argv[2], rootDef, 1, style);
	    }
	    break;
	
	case CLEAR:
	    if (cmd->tx_argc == 3)
	        DBWFeedbackClear(cmd->tx_argv[2]);
	    else if (cmd->tx_argc == 2)
	        DBWFeedbackClear(NULL);
	    else
		goto badusage;
	    nth = 0;
	    break;
	
	case COUNT:
	    if (cmd->tx_argc != 2) goto badusage;
	    TxPrintf("There are %d feedback areas.\n", DBWFeedbackCount);
	    break;
	
	case FIND:
	    if (cmd->tx_argc > 3) goto badusage;
	    if (DBWFeedbackCount == 0)
	    {
		TxPrintf("There are no feedback areas right now.\n");
		break;
	    }
	    if (cmd->tx_argc == 3)
	    {
		nth = atoi(cmd->tx_argv[2]);
		if ((nth > DBWFeedbackCount) || (nth <= 0))
		{
		    TxError("Sorry, but only feedback areas 1-%d exist.\n",
			DBWFeedbackCount);
		    nth = 1;
		}
	    }
	    else
	    {
		nth += 1;
		if (nth > DBWFeedbackCount) nth = 1;
	    }
	    text = DBWFeedbackNth(nth-1, &box, &rootDef, (int *) NULL);
	    ToolMoveBox(TOOL_BL, &box.r_ll, FALSE, rootDef);
	    ToolMoveCorner(TOOL_TR, &box.r_ur, FALSE, rootDef);
	    TxPrintf("Feedback #%d: %s\n", nth, text);
	    break;
	
	case FEED_HELP:
	    if (cmd->tx_argc > 2) goto badusage;
	    TxPrintf("Feedback commands have the form \"feedback option\",\n");
	    TxPrintf("where option is one of:\n");
	    for (msg = cmdFeedbackOptions; *msg != NULL; msg++)
		TxPrintf("%s\n", *msg);
	    break;
	
	case SAVE:
	    if (cmd->tx_argc != 3) goto badusage;
	    f = PaOpen(cmd->tx_argv[2], "w", (char *) NULL, ".",
	        (char *) NULL, (char **) NULL);
	    if (f == NULL)
	    {
		TxError("Can't open file %s.\n", cmd->tx_argv[2]);
		break;
	    }
	    for (i = 0; i < DBWFeedbackCount; i++)
	    {
		int j, style;
		text = DBWFeedbackNth(i, &box, (CellDef **) NULL, &style);
		fprintf(f, "box %d %d %d %d\n", box.r_xbot, box.r_ybot,
		    box.r_xtop, box.r_ytop);
		fprintf(f, "feedback add \"");

		/* Be careful to backslash any quotes in the text! */

		for ( ; *text != 0; text += 1)
		{
		    if (*text == '"') fputc('\\', f);
		    fputc(*text, f);
		}
		fputc('"', f);
		for (j = 0; cmdFeedbackStyles[j] >= 0; j++)
		{
		    if (cmdFeedbackStyles[j] == style)
		    {
			fprintf(f, " %s", cmdFeedbackStyleNames[j]);
			break;
		    }
		}
		fprintf(f, "\n");
	    }
	    (void) fclose(f);
	    break;
	
	case WHY:
	    if (cmd->tx_argc > 2) goto badusage;
	    w = ToolGetBoxWindow(&box, (int *) NULL);
	    if (w == NULL) return;
	    rootDef = ((CellUse *) w->w_surfaceID)->cu_def;
	    HashInit(&table, 16, 0);
	    for (i = 0; i < DBWFeedbackCount; i++)
	    {
		Rect area;
		CellDef *fbRootDef;

		text = DBWFeedbackNth(i, &area, &fbRootDef, (int *) NULL);
		if (rootDef != fbRootDef) continue;
		if (!GEO_OVERLAP(&box, &area)) continue;
		h = HashFind(&table, text);
		if (HashGetValue(h) == 0) TxPrintf("%s\n", text);
		HashSetValue(h, 1);
	    }
	    HashKill(&table);
	    break;
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdFill --
 *
 * Implement the "fill" command.  Find all paint touching one side
 * of the box, and paint it across to the other side of the box.  Can
 * operate in any of four directions.
 *
 * Usage: 
 *	fill direction [layers]
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Modifies the edit cell definition.
 *
 * ----------------------------------------------------------------------------
 */

/* Data passed between CmdFill and cmdFillFunc: */

int cmdFillDir;				/* Direction in which to fill. */
Rect cmdFillRootBox;			/* Root coords of box. */
struct cmdFillArea *cmdFillList;	/* List of areas to fill. */

void
CmdFill(w, cmd)
    MagWindow *w;		/* Window in which command was invoked. */
    TxCommand *cmd;	/* Describes the command that was invoked. */
{
    TileTypeBitMask maskBits;
    Rect editBox;
    SearchContext scx;
    extern int cmdFillFunc();

    if (cmd->tx_argc < 2 || cmd->tx_argc > 3)
    {
	TxError("Usage: %s direction [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 position argument. */

    cmdFillDir = GeoNameToPos(cmd->tx_argv[1], TRUE, TRUE);
    if (cmdFillDir < 0)
	return;

    /* Figure out which layers to fill. */

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

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

    if (!ToolGetEditBox(&editBox)) return;
    GeoTransRect(&EditToRootTransform, &editBox, &cmdFillRootBox);
    scx.scx_area = cmdFillRootBox;
    switch (cmdFillDir)
    {
	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;
    cmdFillList = (struct cmdFillArea *) NULL;

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

    /* Now that we've got all the material, scan over the list
     * painting the material and freeing up the entries on the list.
     */
    while (cmdFillList != NULL)
    {
	DBPaint(EditCellUse->cu_def, &cmdFillList->cfa_area,
		cmdFillList->cfa_type);
	freeMagic((char *) cmdFillList);
	cmdFillList = cmdFillList->cfa_next;
    }

    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);
}

/* Important note:  these procedures can't paint the tiles directly,
 * because a search is in progress over the same planes and if we
 * paint here it may mess up the search.  Instead, the procedures
 * save areas on a list.  The list is post-processed to paint the
 * areas once the search is finished.
 */

int
cmdFillFunc(tile, cxp)
    Tile *tile;			/* Tile to fill with. */
    TreeContext *cxp;		/* Describes state of search. */
{
    Rect r1, r2;
    struct cmdFillArea *cfa;

    TiToRect(tile, &r1);
    GeoTransRect(&cxp->tc_scx->scx_trans, &r1, &r2);
    GeoClip(&r2, &cmdFillRootBox);
    switch (cmdFillDir)
    {
	case GEO_NORTH:
	    r2.r_ytop = cmdFillRootBox.r_ytop;
	    break;
	case GEO_SOUTH:
	    r2.r_ybot = cmdFillRootBox.r_ybot;
	    break;
	case GEO_EAST:
	    r2.r_xtop = cmdFillRootBox.r_xtop;
	    break;
	case GEO_WEST:
	    r2.r_xbot = cmdFillRootBox.r_xbot;
	    break;
    }
    GeoTransRect(&RootToEditTransform, &r2, &r1);
    cfa = (struct cmdFillArea *) mallocMagic(sizeof(struct cmdFillArea));
    cfa->cfa_area = r1;
    cfa->cfa_type = TiGetType(tile);
    cfa->cfa_next = cmdFillList;
    cmdFillList = cfa;
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdFindBox --
 *
 * Center the display on a corner of the box.  If 'zoom', then make the box
 * fill the window.
 *
 * Usage:
 *	findbox [zoom]
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The window underneath the cursor is moved.
 *
 * ----------------------------------------------------------------------------
 */

void
CmdFindBox(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    CellDef *boxDef;
    Rect box;

    if (w == NULL)
    {
	TxError("Point to a window first.\n");
	return;
    };

    if (!ToolGetBox(&boxDef, &box))
    {
	TxError("Put the box in a window first.\n");
	return;
    };

    if (boxDef != (((CellUse *) w->w_surfaceID)->cu_def))
    {
	TxError("The box is not in the same coordinate %s", 
		"system as the window.\n");
	return;
    };

    if (cmd->tx_argc == 1) 
    {
	/* center view on box */
	Point rootPoint;
	Rect newArea, oldArea;

	rootPoint.p_x = (box.r_xbot + box.r_xtop)/2;
	rootPoint.p_y = (box.r_ybot + box.r_ytop)/2;

	oldArea = w->w_surfaceArea;
	newArea.r_xbot = rootPoint.p_x - (oldArea.r_xtop - oldArea.r_xbot)/2;
	newArea.r_xtop = newArea.r_xbot - oldArea.r_xbot + oldArea.r_xtop;
	newArea.r_ybot = rootPoint.p_y - (oldArea.r_ytop - oldArea.r_ybot)/2;
	newArea.r_ytop = newArea.r_ybot - oldArea.r_ybot + oldArea.r_ytop;

	WindMove(w, &newArea);
	return;
    }
    else if (cmd->tx_argc == 2)
    {
	int expand;

	/* zoom in to box */

	if (strcmp(cmd->tx_argv[1], "zoom") != 0) goto usage;

	/* Allow a 5% ring around the box on each side. */

	expand = (box.r_xtop - box.r_xbot)/20;
	if (expand < 2) expand = 2;
	box.r_xtop += expand;
	box.r_xbot -= expand;
	expand = (box.r_ytop - box.r_ybot)/20;
	if (expand < 2) expand = 2;
	box.r_ytop += expand;
	box.r_ybot -= expand;

	WindMove(w, &box);
	return;
    };

usage:
    TxError("Usage: findbox [zoom]\n");
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdFindLabel --
 *
 * Find a label and set the box to it.
 *
 * Usage:
 *	findlabel [-glob] name
 *
 * Results:
 *	None.  In TCL, the -glob option will generate a list of matching
 *	nodes returned in the interpreter result.
 *
 * Side effects:
 *	The box may be moved.  If "-glob" is specified, in the non-Tcl
 *	version, matching label names are printed.
 *
 * ----------------------------------------------------------------------------
 */

int cmdFindLabelFunc(rect, name, label, cdarg) 
    Rect *rect;
    char *name;
    Label *label;
    Rect *cdarg;
{
    *cdarg = *rect;
    return 1;
}

void
CmdFindLabel(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    CellDef *boxDef;
    CellUse *labUse;
    Rect box, cmdFindLabelRect;
    char *labname;
    int found;
    bool doglob = FALSE;   /* csh-style glob matching (see utils/match.c) */
    int dbListLabels();	   /* forward declaration */

    if ((cmd->tx_argc == 3) && !strncmp(cmd->tx_argv[1], "-glob", 5))
	doglob = TRUE;
    else if (cmd->tx_argc != 2)
	goto usage;

    if (w == NULL)
    {
	TxError("Point to a window first.\n");
	return;
    };

    if (!ToolGetBox(&boxDef, &box))
    {
	TxError("Put the box in a window first.\n");
	return;
    };

    if (boxDef != (((CellUse *) w->w_surfaceID)->cu_def))
    {
	TxError("The box is not in the same coordinate %s", 
		"system as the window.\n");
	return;
    };

    labname = (cmd->tx_argc == 3) ? cmd->tx_argv[2] : cmd->tx_argv[1];
    labUse = EditCellUse;
    if (labUse == NULL) labUse = (CellUse *)w->w_surfaceID;

    if (doglob)
    {
	/* Pattern-matching label search */

	SearchContext scx;

	scx.scx_use = labUse;
	scx.scx_area = labUse->cu_def->cd_bbox;
	scx.scx_trans = GeoIdentityTransform;

	DBSearchLabel(&scx, &DBAllButSpaceAndDRCBits, 0, labname,
		dbListLabels, (ClientData) 0);
    }
    else
    {
	/* Exact-match label search (corrected by Nishit, 10/14/04) */

	found = DBSrLabelLoc(labUse, labname, cmdFindLabelFunc,
		(ClientData) &cmdFindLabelRect);
	if (found) {
	    if (cmdFindLabelRect.r_xbot == cmdFindLabelRect.r_xtop) 
		cmdFindLabelRect.r_xtop++;
	    if (cmdFindLabelRect.r_ybot == cmdFindLabelRect.r_ytop) 
		cmdFindLabelRect.r_ytop++;
	    ToolMoveBox(TOOL_BL,&cmdFindLabelRect.r_ll,FALSE,labUse->cu_def);
	    ToolMoveCorner(TOOL_TR,&cmdFindLabelRect.r_ur,FALSE,labUse->cu_def);
	} else {
	    TxError("Couldn't find label %s\n", labname);
	}
    }

    return;


usage:
    TxError("Usage: findlabel [-glob] label_name\n");
}

/*
 * Callback routine for listing pattern-matched labels.
 * Always return zero to keep the search going.
 */

int
dbListLabels(scx, label, tpath, cdarg)
    SearchContext *scx;
    Label *label;			/* Pointer to label structure	*/
    TerminalPath *tpath;		/* Full pathname of terminal	*/
    ClientData cdarg;			/* (unused)			*/
{
    char *n = tpath->tp_next;
    char c = *n;
    strcpy(n, label->lab_text);
#ifdef MAGIC_WRAPPER
    Tcl_AppendElement(magicinterp, tpath->tp_first);
#else
    TxPrintf("%s\n", tpath->tp_first);
#endif
    *n = c;
    return 0;
}


/*
 * ----------------------------------------------------------------------------
 *
 * CmdFlush --
 *
 * Implement the "flush" command.
 * Throw away all changes made within magic to the specified cell,
 * and re-read it from disk.  If no cell is specified, the default
 * is the current edit cell.
 *
 * Usage:
 *	flush [cellname]
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	THIS IS NOT UNDO-ABLE!
 *	Modifies the specified CellDef.
 *
 * ----------------------------------------------------------------------------
 */

void
CmdFlush(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    CellDef *def;
    int action;
    static char *actionNames[] = { "no", "yes", 0 };
    char *prompt;

    if (cmd->tx_argc > 2)
    {
	TxError("Usage: flush [cellname]\n");
	return;
    }

    if (cmd->tx_argc == 1)
    {
	if (EditCellUse != NULL)
	    def = EditCellUse->cu_def;
	else
	    def = ((CellUse *)w->w_surfaceID)->cu_def;
    }
    else
    {
	def = DBCellLookDef(cmd->tx_argv[1]);
	if (def == (CellDef *) NULL)
	{
	    /* an error message has already been printed by the database */
	    return;
	}
    }

    if (def->cd_flags & (CDMODIFIED|CDSTAMPSCHANGED|CDBOXESCHANGED))
    {
	prompt = TxPrintString("Really throw away all changes made"
			" to cell %s? ", def->cd_name);
	action = TxDialog(prompt, actionNames, 0);
	if (action == 0)	/* No */
	    return;
    }

    cmdFlushCell(def);
    SelectClear();
    TxPrintf("[Flushed]\n");
}


/*
 * ----------------------------------------------------------------------------
 *
 * CmdGetcell --
 *
 *	Implement the ":getcell" command.
 *
 * Usage:
 *	getcell cellName [child refPointChild] [parent refPointParent]
 *
 * where the refPoints are either a label name, e.g., SOCKET_A, or an x-y
 * pair of integers, e.g., 100 200.  The words "child" and "parent" are
 * keywords, and may be abbreviated.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *	Makes cellName a subcell of the edit cell, positioned 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
CmdGetcell(w, cmd)
    MagWindow *w;			/* Window in which command was invoked. */
    TxCommand *cmd;		/* Describes command arguments. */
{
    CellUse dummy, *newUse;
    Transform editTrans;
    SearchContext scx;
    CellDef *def;
    Rect newBox;

    /* Leaves scx.scx_trans set to the transform from the child to root */
    if (!cmdDumpParseArgs("getcell", w, cmd, &dummy, &scx))
	return;
    def = dummy.cu_def;

    /* Create the new use. */
    newUse = DBCellNewUse(def, (char *) NULL);
    if (!DBLinkCell(newUse, EditCellUse->cu_def))
    {
	(void) DBCellDeleteUse(newUse);
	TxError("Could not link in new cell\n");
	return;
    }

    GeoTransTrans(&scx.scx_trans, &RootToEditTransform, &editTrans);
    DBSetTrans(newUse, &editTrans);
    if (DBCellFindDup(newUse, EditCellUse->cu_def) != NULL)
    {
	DBCellDeleteUse(newUse);
	TxError("Can't place a cell on an exact copy of itself.\n");
	return;
    }
    DBPlaceCell(newUse, EditCellUse->cu_def);

    /*
     * Reposition the box tool to around the gotten cell to show
     * that it has become the current cell.
     */
    GeoTransRect(&EditToRootTransform, &newUse->cu_bbox, &newBox);
    DBWSetBox(EditRootDef, &newBox);

    /* Select the new use */
    SelectClear();
    SelectCell(newUse, EditRootDef, &scx.scx_trans, FALSE);

    /* Redisplay and mark for design-rule checking */
    DBReComputeBbox(EditCellUse->cu_def);
    DBWAreaChanged(EditCellUse->cu_def, &newUse->cu_bbox,
	DBW_ALLWINDOWS, &DBAllButSpaceBits);
    DRCCheckThis(EditCellUse->cu_def, TT_CHECKSUBCELL, &newUse->cu_bbox);

#ifdef MAGIC_WRAPPER
    /* If using the TCL wrapper, set the TCL return value to the	*/
    /* name of the new use.						*/
    if (newUse->cu_id)
	Tcl_SetResult(magicinterp, newUse->cu_id, TCL_VOLATILE);
#endif

}
#ifndef NO_SIM_MODULE

/*
 * ----------------------------------------------------------------------------
 *
 * CmdGetnode --
 *
 * Implement the "getnode" command.
 * Returns the name of the node pointed by the mouse
 *
 * Usage:
 *  	getnode
 *	getnode abort [string]
 *      getnode alias [on | off]
 *      getnode globals [on | off]
 *      getnode fast
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The GetNode hash tables may be modified.
 * ----------------------------------------------------------------------------
 */

void
CmdGetnode(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
#define TBLSIZE 50
#define STRINGS 0

    bool  is_fast = FALSE;

    /* check arguments to command */

    switch (cmd->tx_argc) {
	case 1 : 
	    break;

	case 2 : 
	    if (strcmp("abort", cmd->tx_argv[1]) == 0) {
		if (!SimInitGetnode) {
		    HashKill(&SimGetnodeTbl);
		    SimInitGetnode = TRUE;
   		    SimRecomputeSel = TRUE;
		}
		return;
	    }
	    else if (strcmp("fast", cmd->tx_argv[1]) == 0) {
		is_fast = TRUE;
	    }
	    else {
		if (!strcmp("alias", cmd->tx_argv[1])) {
		    TxPrintf("Aliases %s\n", (SimGetnodeAlias) ?  "on" : "off");
		    return;
		}
		else if (strncmp("global", cmd->tx_argv[1], 6) == 0) {
		    TxPrintf("Node names ending in ! are %s\n",
				(SimIgnoreGlobals) ? "local (off)" : "global (on)");
		    return;
		}
		else
		    goto badusage;
	    }
	    break;

	case 3 : 
	    if (strcmp("alias", cmd->tx_argv[1]) == 0) {
		if (strcmp("on", cmd->tx_argv[2]) == 0) {
		    if (!SimGetnodeAlias) {
			HashInit(&SimGNAliasTbl, 120, STRINGS);
		    }
		    SimGetnodeAlias = TRUE;
		    return;
		}
		else if (strcmp("off", cmd->tx_argv[2]) == 0) {
		    if (SimGetnodeAlias) {
			HashKill(&SimGNAliasTbl);
		    }
		    SimGetnodeAlias = FALSE;
		    return;
		}
		else
		    goto badusage;
	    }
	    else if (strncmp("global", cmd->tx_argv[1], 6) == 0) {
		if (strcmp("off", cmd->tx_argv[2]) == 0) {
		    SimIgnoreGlobals = TRUE;
		    return;
		}
		else if (strcmp("on", cmd->tx_argv[2]) == 0) {
		    SimIgnoreGlobals = FALSE;
		    return;
		}
		else
		    goto badusage;
	    }
	    else if (strcmp("abort", cmd->tx_argv[1]) == 0) {
		if (SimInitGetnode) {
		    HashInit(&SimGetnodeTbl, TBLSIZE, STRINGS);
		    SimInitGetnode = FALSE;
		}
		SimRecomputeSel = TRUE;
		HashFind(&SimGetnodeTbl, cmd->tx_argv[2]);
		return;
	    }
	    else {
		goto badusage;
	    }
	    break;

	default :
	    goto badusage;
    }

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

    if (SimGetnodeAlias) {			/* "erase" the hash table */
	HashKill(&SimGNAliasTbl);
	HashInit(&SimGNAliasTbl, 120, STRINGS);
    }
    return;


badusage:
    TxError("Usage: getnode [abort [str]]\n");
    TxError("   or: getnode alias [on | off]\n");
    TxError("   or: getnode globals [on | off]\n");
    TxError("   or: getnode fast\n");
}
#endif

/*
 * ----------------------------------------------------------------------------
 *
 * CmdGrid --
 *
 * Implement the "gridspace" command.
 * Toggle the grid on or off in the selected window.
 *
 * Usage:
 *	gridspace [spacing [spacing [xorig yorig]]]
 *	gridspace on|off|box|state|help|multiple
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None, except to enable or disable grid display.
 *
 * ----------------------------------------------------------------------------
 */

#define GRID_BOX	0
#define GRID_HELP	1
#define GRID_MULTIPLE	2
#define GRID_OFF	3
#define GRID_ON		4
#define GRID_STATE	5
#define GRID_TOGGLE	6
#define GRID_WHAT	7

void
CmdGrid(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    int option, locargc;
    int xSpacing, ySpacing, xOrig, yOrig, multiple;
    DBWclientRec *crec;
    char *boxvalues;
    static char *cmdGridOptions[] =
    {
	"box [values]	report the box representing the user grid",
	"help		print this message",
	"multiple [m]	set the grid multiple for drawing grids at large scales",
	"off		turn off the user grid",
	"on		turn on the user grid",
	"state		report the state of the user grid",
	"toggle		toggle the state (on/off) of the user grid",
	"what		(equivalent to option \"box\")",
	NULL
    };

    windCheckOnlyWindow(&w, DBWclientID);
    if (w == (MagWindow *) NULL) return;
    crec = (DBWclientRec *) w->w_clientData;
    locargc = cmd->tx_argc;

    if (locargc == 1)
	option = GRID_TOGGLE;
    else if ((locargc == 2) && !strcmp(cmd->tx_argv[1], "0"))
	option = GRID_OFF;
    else
	option = Lookup(cmd->tx_argv[1], cmdGridOptions);

    /* Process various options with two arguments */
    switch (option)
    {
	case GRID_BOX:
	    if (locargc > 2)
	    {
		locargc--;
		break;
	    }

	case GRID_WHAT:

#ifdef MAGIC_WRAPPER
	    boxvalues = (char *)Tcl_Alloc(50);
	    sprintf(boxvalues, "%d %d %d %d",
		crec->dbw_gridRect.r_xbot, crec->dbw_gridRect.r_ybot,
		crec->dbw_gridRect.r_xtop, crec->dbw_gridRect.r_ytop);
	    Tcl_SetResult(magicinterp, boxvalues, TCL_DYNAMIC);
	
#else
	    TxPrintf("Grid unit box is (%d, %d) to (%d, %d)\n", 
		crec->dbw_gridRect.r_xbot, crec->dbw_gridRect.r_ybot,
		crec->dbw_gridRect.r_xtop, crec->dbw_gridRect.r_ytop);
#endif
	    return;

	case GRID_STATE:

#ifdef MAGIC_WRAPPER
	    Tcl_SetObjResult(magicinterp,
			Tcl_NewBooleanObj(crec->dbw_flags & DBW_GRID));
#else
	    TxPrintf("Grid is %s\n", 
			(crec->dbw_flags & DBW_GRID) ? "on" : "off");
#endif
	    return;

	case GRID_HELP:

	    TxPrintf("Usage: grid [xSpacing [ySpacing [xOrig yOrig]]]]\n");
	    TxPrintf("or     grid <option>\n");
	    TxPrintf("where <option> is one of: "
			"on, off, state, box, what, help, or multiple.\n");
	    return;

	case GRID_OFF:

	    if (crec->dbw_flags & DBW_GRID)
	    {
		crec->dbw_flags &= ~DBW_GRID;
		WindAreaChanged(w, (Rect *) NULL);
	    }
	    return;

	case GRID_ON:
	    if (!(crec->dbw_flags & DBW_GRID))
	    {
		crec->dbw_flags |= DBW_GRID;
		WindAreaChanged(w, (Rect *) NULL);
	    }
	    return;

	case GRID_MULTIPLE:
	    if (locargc == 2)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetObjResult(magicinterp,
			Tcl_NewIntObj(GrGridMultiple));
#endif
	    }
	    else if (StrIsInt(cmd->tx_argv[2]))
	    {
		multiple = atoi(cmd->tx_argv[2]);
		if (multiple < 1 || multiple > 255)
		    TxError("Usage: grid multiple <integer value 1-255>\n");
		GrGridMultiple = (unsigned char)multiple;
	    }
	    else if (!strcmp(cmd->tx_argv[2], "off"))
		GrGridMultiple = (unsigned char)1;
	    else
		TxError("Usage: grid multiple <integer value 1-255>\n");
	    return;

	case GRID_TOGGLE:
 	    crec->dbw_flags ^= DBW_GRID;
	    break;
    }

    if ((option == GRID_BOX) || (option < 0))
    {
	int argstart = (option == GRID_BOX) ? 2 : 1;

	if ((locargc == 4) || (locargc > 5))
	{
	    TxError("Usage: %s [xSpacing [ySpacing [xOrig yOrig]]]]\n",
			cmd->tx_argv[0]);
	    return;
	}

	xSpacing = cmdParseCoord(w, cmd->tx_argv[argstart], TRUE, TRUE);
	if (xSpacing <= 0)
	{
	    TxError("Grid spacing must be greater than zero.\n");
	    return;
	}
	ySpacing = xSpacing;
	xOrig = yOrig = 0;

	if (locargc >= 3)
	{
	    ySpacing = cmdParseCoord(w, cmd->tx_argv[argstart + 1], TRUE, FALSE);
	    if (ySpacing <= 0)
	    {
		TxError("Grid spacing must be greater than zero.\n");
		return;
	    }

	    if (locargc == 5)
	    {
		xOrig = cmdParseCoord(w, cmd->tx_argv[argstart + 2], FALSE, TRUE);
		yOrig = cmdParseCoord(w, cmd->tx_argv[argstart + 3], FALSE, FALSE);
	    }
	}

	crec->dbw_gridRect.r_xbot = xOrig;
	crec->dbw_gridRect.r_ybot = yOrig;
	crec->dbw_gridRect.r_xtop = xOrig + xSpacing;
	crec->dbw_gridRect.r_ytop = yOrig + ySpacing;
	crec->dbw_flags |= DBW_GRID;
    }
    WindAreaChanged(w, (Rect *) NULL);
}

#ifdef USE_READLINE
void
CmdHistory(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
  int i;
  HIST_ENTRY *he;
  int list_reverse = 0;
  int list_numbers = 1;
  int print_help = 0;
  int num_to_list = history_length;

  if( cmd->tx_argc > 1 ) {
    for(i=1; i<cmd->tx_argc; i++) {
      if( strcmp(cmd->tx_argv[i], "help") == 0 ) {
        print_help = 1;
        break;
      } else if( strcmp(cmd->tx_argv[i], "n") == 0 ) {
        list_numbers = 0;
      } else if( strcmp(cmd->tx_argv[i], "r") == 0 ) {
        list_reverse = 1;
      } else {
        num_to_list = atoi(cmd->tx_argv[i]);
        if( num_to_list == 0 ) {
          TxError("Bad arg to history: '%s'\n", cmd->tx_argv[i]);
          print_help = 1;
          break;
        }
      }
    }
  }

  if( print_help ) {
    TxError("Usage: history [n] [r] [<num>]\n"
            "         'n'     suppresses printing numbers\n"
            "         'r'     prints the list in reverse\n"
            "         '<num>' is the number of entries to print\n"
            "         'help'  prints this message\n"
           );
  } else {
    int begin = MAX(history_length - num_to_list, 0);
    int end   = history_length;
    int j;

    for(i=begin,j=end-1; i<end; i++,j--) {
      int idx = (list_reverse) ? j : i;
      HIST_ENTRY *he = history_get(idx);
      if( he != (HIST_ENTRY *)NULL && he->line != (char *)NULL ) {
        if( list_numbers ) {
          TxPrintf("\t%4d %s\n", idx,  he->line);
        } else {
          TxPrintf("%s\n", he->line);
        }
      }
    }
  }
}
#endif


/*
 * ----------------------------------------------------------------------------
 *
 * CmdIdentify --
 *
 * Implement the "identify" command.
 * Sets the instance identifier for the currently selected cell.
 *
 * Usage:
 *	identify use_id
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Modifies the instance identifier for the selected cell (the
 *	first selected cell, if there are many).
 *
 * ----------------------------------------------------------------------------
 */

void
CmdIdentify(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    extern int cmdIdFunc();		/* Forward reference. */

    if (cmd->tx_argc != 2)
    {
	TxError("Usage: identify use_id\n");
	return;
    }

    if (CmdIllegalChars(cmd->tx_argv[1], "[],/", "Cell use id"))
	return;

    if (SelEnumCells(FALSE, (int *) NULL, (SearchContext *) NULL,
	    cmdIdFunc, (ClientData) cmd->tx_argv[1]) == 0)
    {
	TxError("There isn't a selected subcell;  can't change ids.\n");
	return;
    }
}

    /* ARGSUSED */
int
cmdIdFunc(selUse, use, transform, newId)
    CellUse *selUse;		/* Use from selection cell. */
    CellUse *use;		/* Use from layout that corresponds to
				 * selUse.
				 */
    Transform *transform;	/* Not used. */
    char *newId;		/* New id for cell use. */
{
    if (EditCellUse == NULL)
    {
	TxError("Top-level cell is not editable---cannot change identifier"
		" of child cell %s.\n", use->cu_id);
	return 1;
    }
    if (!DBIsChild(use, EditCellUse))
    {
	TxError("Cell %s (%s) isn't a child of the edit cell.\n",
	    use->cu_id, use->cu_def->cd_name);
	TxError("    Cell identifier not changed.\n");
	return 1;
    }

    if (!DBReLinkCell(use, newId))
    {
	TxError("New name isn't unique within its parent definition.\n");
	TxError("    Cell identifier not changed.\n");
	return 1;
    }

    /* Change the id of the cell in the selection too, so that they
     * stay in sync.
     */

    (void) DBReLinkCell(selUse, newId);

    DBWAreaChanged(use->cu_parent, &use->cu_bbox,
	(int) ~(use->cu_expandMask), &DBAllButSpaceBits);
    DBWHLRedraw(EditRootDef, &selUse->cu_bbox, TRUE);
    return 1;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdGoto --
 *
 * Implements goto command
 *
 * Usage:
 *	goto nodename [-nocomplain]
 *
 * Results:
 *	None.  In TCL version, returns the material type
 *	of the node, so that the node can be uniquely determined
 *	by an automated script.   If the node cannot be found, a
 *	null list is returned.
 *
 * Side Effects:
 *	changes box location.
 *
 * Notes:
 *	Due to the way global node names are handled, "getnode" and
 *	"goto" are not necessarily reversible if "getnode globals on"
 *	is set!  In this case, use the glob option for findlabel
 *	to determine any valid local label belonging to the global
 *	node (a node cannot be global unless it is labeled).  This
 *	method, although awkward, can be much faster for large layouts
 *	due to the time required for "getnode" to uniquely determine
 *	the name of a large network such as power or ground.
 *
 * ----------------------------------------------------------------------------
 */

void
CmdGoto(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    char		*nodename = cmd->tx_argv[1];
    char		*s,*s2;
    SearchContext	scx,scx2;
    CellUse		*use;
    Transform		trans, newtrans, tmp;
    Label		*label;
    Rect		localrect, rect;
    CellDef		*rootdef;
    int			pnum, xpos, ypos, locargc;
    char 		*xstr, *ystr;
    bool		locvalid;
    bool		nocomplain = FALSE;
    TileType		ttype;
     
    windCheckOnlyWindow(&w, DBWclientID);
    if ((w == (MagWindow *) NULL) || (w->w_client != DBWclientID))
    {
	TxError("Put the cursor in a layout window\n");
	return;
    }
    scx.scx_use = (CellUse  *) w->w_surfaceID;
    rootdef = scx.scx_use->cu_def;
    scx.scx_trans = GeoIdentityTransform;

    locargc = cmd->tx_argc;
    if (locargc == 3)
    {
	if (!strncmp(cmd->tx_argv[2], "-nocom", 5))
	{
	    nocomplain = TRUE;
	    locargc--;
	}
    }
    if (locargc != 2)
    {
     	TxError("usage: goto nodename [-nocomplain]\n");
	return;
    }
    s = nodename;
    trans = GeoIdentityTransform;
    use = scx.scx_use;
    while (s2 = strchr(s,'/'))
    {
     	*s2 = '\0';
	DBTreeFindUse(s,scx.scx_use,&scx2);
	use = scx2.scx_use;
	if (use == NULL)
	{
	    TxError("Couldn't find use %s\n",s);
	    return;
	}
	GeoTransTrans(DBGetArrayTransform(use,scx2.scx_x,scx2.scx_y)
	  		,&use->cu_transform,&tmp);
	GeoTransTrans(&tmp,&trans,&newtrans);
	trans = newtrans;
	scx = scx2;
        *s2 = '/';
	s = s2+1;
    }

    /* If this node name is in the format of automatically-generated */
    /* node names, then parse the node string for X and Y values and */
    /* go to that point (transformed to the top level of the design  */
    /* hierarchy).						     */

    /* see extract/extractInt.h for the format of the node, found in */
    /* extMakeNodeNumPrint(), which is a macro, not a subroutine.    */

    locvalid = FALSE;
    if ((xstr = strchr(s, '_')) != NULL)
    {
	bool isNeg = FALSE;

        /* The characters up to the leading '_' should match one of the	*/
	/* "short names" for a plane in this technology.		*/ 

	*xstr = '\0';
	for (pnum = PL_TECHDEPBASE; pnum < DBNumPlanes; pnum++)
	    if (!strcmp(s, DBPlaneShortName(pnum)))
		break;
	*xstr = '_';
	if (pnum != DBNumPlanes)
	{
	    xstr++;
	    if (*xstr == 'n')
	    {
	        isNeg = TRUE;
	        xstr++;
	    }
	    if (sscanf(xstr, "%d", &xpos) == 1)
	    {
	        if (isNeg) xpos = -xpos;
	        if ((ystr = strchr(xstr, '_')) != NULL)
	        {
		    isNeg = FALSE;
		    ystr++;
		    if (*ystr == 'n')
		    {
		        isNeg = TRUE;
		        ystr++;
		    }
		    if (sscanf(ystr, "%d", &ypos) == 1)
		    {
		        if (isNeg) ypos = -ypos;
		        localrect.r_xbot = xpos;
		        localrect.r_ybot = ypos;
		        localrect.r_xtop = xpos + 1;
		        localrect.r_ytop = ypos + 1;
			/* TxPrintf("Node is on the plane \"%s\"\n", 
					DBPlaneLongNameTbl[pnum]); */
		        locvalid = TRUE;
		    }
	        }
	    }
	}
    }

    /* This is the original version, and assumes a format for node	*/
    /* coordinates that is no longer generated by magic.  It is kept	*/
    /* here for backward compatibility.					*/

    if ((locvalid == FALSE) && (sscanf(s,"%d_%d_%d",&pnum,&xpos,&ypos) == 3))
    {
	xpos = ((xpos & 0x1)?-1:1)*xpos/2;
	ypos = ((ypos & 0x1)?-1:1)*ypos/2;
	localrect.r_xbot = xpos;
	localrect.r_ybot = ypos;
	localrect.r_xtop = xpos + 1;
	localrect.r_ytop = ypos + 1;
	locvalid = TRUE;
    }

    if (locvalid == TRUE)
    {
	int findTile();
	CellDef	 *targetdef = use->cu_def;
	Plane *plane = targetdef->cd_planes[pnum];

	ttype = TT_SPACE;	/* revert to space in case of failure */
	
	/* Find the tile type of the tile at the specified point which	*/
	/* exists on the plane pnum.					*/

	(void) TiSrArea(NULL, plane, &localrect, findTile, (ClientData) &ttype);
    }
    else
    {
	for (label = scx.scx_use->cu_def->cd_labels;label;label=label->lab_next)
	{
	    if (strcmp(label->lab_text,s) == 0) break;
	}
	if (label)
	{
	    localrect = label->lab_rect;
	    ttype = label->lab_type;
	}
	else
	{
	    if (!nocomplain)
     		TxError("Couldn't find label %s\n",s);
	    return;
	}
    }
    GeoTransRect(&trans, &localrect, &rect);
    ToolMoveBox(TOOL_BL, &rect.r_ll, FALSE, rootdef);
    ToolMoveCorner(TOOL_TR, &rect.r_ur, FALSE, rootdef);

    /* Return the tile type so we know what we're looking at if there	*/
    /* are multiple layers drawn at the indicated point.		*/ 

#ifdef MAGIC_WRAPPER
    Tcl_SetResult(magicinterp, DBTypeLongName(ttype), NULL);
#else
    TxPrintf("node %s is type %s\n", s, DBTypeLongName(ttype));
#endif
}

/*
 * ----------------------------------------------------------------------------
 * Filter function for finding the tile type of the specified node
 * ----------------------------------------------------------------------------
 */

int
findTile(tile, rtype)
    Tile *tile;
    TileType *rtype;
{
    TileType ttype;

#ifdef NONMANHATTAN
    if (IsSplit(tile))  
    {   
	if (SplitSide(tile))
	    ttype = SplitRightType(tile);
	else
	    ttype = SplitLeftType(tile);
    }
    else
#endif  
	ttype = TiGetTypeExact(tile);
    *rtype = ttype;
    return 1;			/* stop search */
}

/*
 * The following are from DBcellcopy.c; slightly modified for present 
 * purposes.
 */

#define FLATTERMSIZE	1024

void
FlatCopyAllLabels(scx, mask, xMask, targetUse)
    SearchContext *scx;
    TileTypeBitMask *mask;
    int xMask;		
    CellUse *targetUse;
{
    int flatCopyAllLabels();
    char pathstring[FLATTERMSIZE];
    TerminalPath	tpath;
    
    tpath.tp_first = tpath.tp_next = pathstring;
    tpath.tp_last = pathstring + FLATTERMSIZE;

    DBTreeSrLabels(scx, mask, xMask, &tpath, flatCopyAllLabels,
			(ClientData) targetUse);
}

int
flatCopyAllLabels(scx, lab, tpath, targetUse)
    SearchContext *scx;
    Label *lab;
    TerminalPath *tpath;
    CellUse	*targetUse;
{
    Rect labTargetRect;
    int targetPos;
    CellDef *def;
    char	labelname[1024];
    char *n, *f, c;

    def = targetUse->cu_def;
    if (!GEO_LABEL_IN_AREA(&lab->lab_rect, &(scx->scx_area))) return 0;
    GeoTransRect(&scx->scx_trans, &lab->lab_rect, &labTargetRect);
    targetPos = GeoTransPos(&scx->scx_trans, lab->lab_just);

    /* Eliminate duplicate labels.  Don't pay any attention to layers
     * in deciding on duplicates:  if text and position match, it's a
     * duplicate.
     */
    /* 9/12/05---The utility of eliminating duplicate labels has been
     * put into question by observing the O(n^2) behavior causing
     * significant flattening times for layouts with many labels.
     * Label text will not be duplicated anyway, because each label
     * contains the heirarchical names.
     */

    /* (void) DBEraseLabelsByContent(def, &labTargetRect, targetPos,
        -1, lab->lab_text); */

    /* (Added 9/10/04) Make sure that the target flattened label is
     * not a port; possibly we should retain ports taken from the
     * top level cell, but certainly not any others.
     */

    n = tpath->tp_next;
    f = tpath->tp_first;
    c = *n;
    strcpy(n, lab->lab_text);
    DBPutLabel(def, &labTargetRect, targetPos, f, lab->lab_type, 0);
    *n = c;

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdFlatten --
 *
 * Implements flatten command
 *
 * Usage:
 *	flatten [-<option>] destname
 *
 * Results:
 *	creates new cell def with all the desired info.
 *
 *
 * ----------------------------------------------------------------------------
 */

void
CmdFlatten(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
     int		rval, xMask;
     bool		dolabels;
     char		*destname;
     CellDef		*newdef;
     CellUse		*newuse;
     SearchContext	scx;
     CellUse		*flatDestUse;
     
    destname = cmd->tx_argv[cmd->tx_argc - 1];
    xMask = CU_DESCEND_ALL;
    dolabels = TRUE;

    rval = 0;
    if (cmd->tx_argc > 2)
    {
	int i;
	for (i = 1; i < (cmd->tx_argc - 1); i++)
	{
	    if (strncmp(cmd->tx_argv[i], "-no", 3))
	    {
	        rval = -1;
		break;
	    }
	    else if (strlen(cmd->tx_argv[i]) > 3)
	    {
		switch(cmd->tx_argv[1][3])
		{
		    case 'l':
			dolabels = FALSE;
			break;
		    case 's':
			xMask = CU_DESCEND_NO_SUBCKT;
			break;
		    case 'v':
			xMask = CU_DESCEND_NO_VENDOR;
			break;
		    default:
			TxError("options are: -nolabels, -nosubcircuits -novendor\n");
			break;
		}
	    }
	}
    }
    else if (cmd->tx_argc != 2)
	rval = -1;

    if (rval != 0)
    {
     	TxError("usage: flatten [-<option>...] destcell\n");
	return;
    }
    /* create the new def */
    if (newdef = DBCellLookDef(destname))
    {
    	 TxError("%s already exists\n",destname);
	 return;
    }
    newdef = DBCellNewDef(destname, (char *) NULL);
    ASSERT(newdef, "CmdFlatten");
    DBCellSetAvail(newdef);
    newuse = DBCellNewUse(newdef, (char *) NULL);
    (void) StrDup(&(newuse->cu_id), "Flattened cell");
    DBSetTrans(newuse, &GeoIdentityTransform);
    newuse->cu_expandMask = CU_DESCEND_SPECIAL;
    UndoDisable();
    flatDestUse = newuse;
    
    if (EditCellUse)
	scx.scx_use  = EditCellUse;
    else
	scx.scx_use = (CellUse *)w->w_surfaceID;

    scx.scx_area = scx.scx_use->cu_def->cd_bbox;
    scx.scx_trans = GeoIdentityTransform;

    DBCellCopyAllPaint(&scx, &DBAllButSpaceAndDRCBits, xMask, flatDestUse);
    if (dolabels)
	FlatCopyAllLabels(&scx, &DBAllTypeBits, xMask, flatDestUse);

    if (xMask != CU_DESCEND_ALL)
	DBCellCopyAllCells(&scx, xMask, flatDestUse, (Rect *)NULL);
    
    UndoEnable();
}


