/*
 * 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.4/commands/CmdFI.c,v 1.4 2007/11/27 17:37:21 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 off
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None, except to enable or disable grid display.
 *
 * ----------------------------------------------------------------------------
 */

void
CmdGrid(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    int xSpacing, ySpacing, xOrig, yOrig;
    DBWclientRec *crec;

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

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

    /* Parse various options with two arguments */

    if (cmd->tx_argc == 2)
    {
	if (!strcmp(cmd->tx_argv[1], "box")
		|| !strcmp(cmd->tx_argv[1], "what"))
	{
#ifdef MAGIC_WRAPPER
	    char *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;
	}
	else if (!strcmp(cmd->tx_argv[1], "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;
	}
	else if (!strcmp(cmd->tx_argv[1], "help"))
	{
	    TxPrintf("Usage: grid [xSpacing [ySpacing [xOrig yOrig]]]]\n");
	    TxPrintf("or     grid <option>\n");
	    TxPrintf("where <option> is one of: on, off, state, box, or help.\n");
	    return;
	}
	else if (!strcmp(cmd->tx_argv[1], "off")
		|| !strcmp(cmd->tx_argv[1], "0"))
	{
	    if (crec->dbw_flags & DBW_GRID)
	    {
		crec->dbw_flags &= ~DBW_GRID;
		WindAreaChanged(w, (Rect *) NULL);
	    }
	    return;
	}
	else if (!strcmp(cmd->tx_argv[1], "on"))
	{
	    if (!(crec->dbw_flags & DBW_GRID))
	    {
		crec->dbw_flags |= DBW_GRID;
		WindAreaChanged(w, (Rect *) NULL);
	    }
	    return;
	}
    }

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

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

	    if (cmd->tx_argc == 5)
	    {
		xOrig = cmdParseCoord(w, cmd->tx_argv[3], FALSE, TRUE);
		yOrig = cmdParseCoord(w, cmd->tx_argv[4], 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;
    }
    else 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 (!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_pos);

    /* 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();
}


