/*
 * CmdE.c --
 *
 * Commands with names beginning with the letter E.
 *
 *     ********************************************************************* 
 *     * 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/CmdE.c,v 1.8 2010/06/17 14:40:52 tim Exp $";
#endif  /* not lint */

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

#include "tcltk/tclmagic.h"
#include "utils/magic.h"
#include "utils/styles.h"
#include "utils/geometry.h"
#include "utils/utils.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "windows/windows.h"
#include "graphics/graphics.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 "extract/extract.h"
#include "select/select.h"


/*
 * ----------------------------------------------------------------------------
 *
 * CmdEdit --
 *
 * Implement the "edit" command.
 * Use the cell that is currently selected as the edit cell.  If more than
 * one cell is selected, use the point to choose between them.
 *
 * Usage:
 *	edit
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Sets EditCellUse.
 *
 * ----------------------------------------------------------------------------
 */

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

static bool cmdFoundNewEdit;

void
CmdEdit(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    Rect area, pointArea;
    CellUse *usave;
    CellDef *csave;
    int cmdEditRedisplayFunc();		/* Forward declaration. */
    int cmdEditEnumFunc();		/* Forward declaration. */
    bool noCurrentUse = FALSE;

    if (cmd->tx_argc > 1)
    {
	TxError("Usage: edit\nMaybe you want the \"load\" command\n");
	return;
    }
    usave = EditCellUse;
    if (usave != (CellUse *)NULL)
    {

        /* Record the current edit cell's area for redisplay (now that it's
         * not the edit cell, it will be displayed differently).  Do this
         * only in windows where the edit cell is displayed differently from
         * other cells.
         */
    
        GeoTransRect(&EditToRootTransform, &(usave->cu_def->cd_bbox), &area);
        (void) WindSearch(DBWclientID, (ClientData) NULL,
	    	(Rect *) NULL, cmdEditRedisplayFunc, (ClientData) &area);

	DBWUndoOldEdit(EditCellUse, EditRootDef, &EditToRootTransform,
			&RootToEditTransform);
    }
	
    /* Use the position of the point to select one of the currently-selected
     * cells (if there are more than one).  If worst comes to worst, just
     * select any selected cell.
     */
    
    (void) ToolGetPoint((Point *) NULL, &pointArea);

    cmdFoundNewEdit = FALSE;
    csave = EditRootDef;
    usave = EditCellUse;

    (void) SelEnumCells(FALSE, (bool *) NULL, (SearchContext *) NULL,
	    cmdEditEnumFunc, (ClientData) &pointArea);

    if (EditCellUse == (CellUse *)NULL)
    {
	TxError("No cell selected to edit.\n");
	EditCellUse = usave;
	EditRootDef = csave;
	return;
    }
    else if (!(EditCellUse->cu_def->cd_flags & CDAVAILABLE))
	DBCellRead(EditCellUse->cu_def, (char *)NULL, TRUE);

    if (EditCellUse->cu_def->cd_flags & CDNOEDIT)
    {
	TxError("File %s is not writeable.  Edit not set.\n",
		EditCellUse->cu_def->cd_file);
	cmdFoundNewEdit = FALSE;
	EditCellUse = usave;
	EditRootDef = csave;
	return;
    }

    if (!cmdFoundNewEdit)
	TxError("You haven't selected a new cell to edit.\n");

    CmdSetWindCaption(EditCellUse, EditRootDef);
    DBWUndoNewEdit(EditCellUse, EditRootDef,
		&EditToRootTransform, &RootToEditTransform);

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

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

/* Search function to handle redisplays for CmdEdit:  it checks to
 * be sure that this is a window on the edit cell, then if edit cells
 * are displayed differently from other cells in the window, the area
 * of the edit cell is redisplayed.  Also, if the grid is on in this
 * window, the origin area is redisplayed.
 */

int
cmdEditRedisplayFunc(w, area)
    MagWindow *w;			/* Window containing edit cell. */
    Rect *area;			/* Area to be redisplayed. */
{
    static Rect origin = {-1, -1, 1, 1};
    Rect tmp;
    DBWclientRec *crec = (DBWclientRec *) w->w_clientData;

    if (((CellUse *) w->w_surfaceID)->cu_def != EditRootDef) return 0;
    if (!(crec->dbw_flags & DBW_ALLSAME))
	DBWAreaChanged(EditRootDef, area, crec->dbw_bitmask,
	    &DBAllButSpaceBits);
    if (crec->dbw_flags & DBW_GRID)
    {
	GeoTransRect(&EditToRootTransform, &origin, &tmp);
	DBWAreaChanged(EditRootDef, &tmp, crec->dbw_bitmask,
	    &DBAllButSpaceBits);
    }
    return 0;
}

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

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

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

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

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

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

    /* It overlaps.  Now find out which array element it points to,
     * and adjust the transforms accordingly.
     */
    
    DBArrayOverlap(use, &useArea, &xlo, &xhi, &ylo, &yhi);
    GeoTransTrans(DBGetArrayTransform(use, xlo, ylo), transform,
	    &EditToRootTransform);
    GeoInvertTrans(&EditToRootTransform, &RootToEditTransform);
    return 1;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdElement --
 *
 * Implement the "element" command.
 * Handle general-purpose elements which are drawn on top of the layout.
 *
 * Usage:
 *	element name <add|delete|configure|cget> <line|rectangle|text> ...
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	May modify the hash table of elements.
 *
 * ----------------------------------------------------------------------------
 */

#define ELEMENT_ADD	0
#define ELEMENT_DELETE	1
#define ELEMENT_CONFIG	2
#define ELEMENT_NAMES	3
#define ELEMENT_INBOX	4
#define ELEMENT_HELP	5

#define ELEMENT_LINE	0
#define ELEMENT_RECT	1
#define ELEMENT_TEXT	2

#define OPTION_TEXT	0
#define OPTION_STYLE	1
#define OPTION_POSITION	2
#define OPTION_FLAGS	3

void
CmdElement(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{

    int option, type;
    char **msg;
    Rect area;
    int style;
    CellDef *def;
    CellUse *use;
    bool getopt;

    static char *cmdElementOption[] = {
	"add			create a new element",
	"delete		delete an existing element",
	"configure		configure or query an existing element",
	"names		print names of all elements",
	"inbox		print name of element nearest the box",
	"help		print help information",
	NULL
    };

    static char *cmdElementType[] = {
	"line		name style x1 y1 x2 y2",
	"rectangle		name style llx lly urx ury",
	"text		name style cx cy label",
	NULL
    };

    static char *cmdConfigureType[] = {
	"text		get (or) replace <string>",
	"style		get (or) add <style> (or) remove <style>",
	"position		get (or) <point> (or) <rect>",
	"flags		set element flags",
	NULL
    };

    option = ELEMENT_HELP;
    windCheckOnlyWindow(&w, DBWclientID);
    if (w == (MagWindow *) NULL) return;

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

    if (cmd->tx_argc > 1)
    {
	option = Lookup(cmd->tx_argv[1], cmdElementOption);
	if (option < 0) option = ELEMENT_HELP;
    }

    switch (option)
    {
	case ELEMENT_ADD:
	    if (cmd->tx_argc < 3)
	    {
		TxError("Usage:  element add <type> <name> <values...>\n");
		return;
	    }
	    type = Lookup(cmd->tx_argv[2], cmdElementType);
	    if (type < 0)
	    {
		TxError("Usage:  element add <type>..., where type is one of:\n");
		for (msg = &(cmdElementType[0]); *msg != NULL; msg++)
		    TxError("    %s\n", *msg);

		return;
	    }
	    else if (cmd->tx_argc >= 7)
	    {
		if (StrIsInt(cmd->tx_argv[4]))
		    style = atoi(cmd->tx_argv[4]);
		else
		{
		    style = GrGetStyleFromName(cmd->tx_argv[4]);
		    if (style < 0)
		    {
			TxError("Unknown style \"%s\".", cmd->tx_argv[4]);
			TxError("Use a number or one of the long names in the"
				" .dstyle file\n");
			return;
		    }
		}
		if (!StrIsInt(cmd->tx_argv[5])) goto badusage;
		area.r_xbot = atoi(cmd->tx_argv[5]);
		if (!StrIsInt(cmd->tx_argv[6])) goto badusage;
		area.r_ybot = atoi(cmd->tx_argv[6]);

		switch (type)
		{
		    case ELEMENT_LINE:
			if (cmd->tx_argc != 9)
			{
			    TxError("Usage:  element add %s\n",
					cmdElementType[ELEMENT_LINE]);
			    return;
			}
			if (!StrIsInt(cmd->tx_argv[7])) goto badusage;
			area.r_xtop = atoi(cmd->tx_argv[7]);
			if (!StrIsInt(cmd->tx_argv[8])) goto badusage;
			area.r_ytop = atoi(cmd->tx_argv[8]);
			DBWElementAddLine(w, cmd->tx_argv[3], &area, def, style);
			break;
		    case ELEMENT_RECT:
			if (cmd->tx_argc != 9)
			{
			    TxError("Usage:  element add %s\n",
					cmdElementType[ELEMENT_RECT]);
			    return;
			}
			if (!StrIsInt(cmd->tx_argv[7])) goto badusage;
			area.r_xtop = atoi(cmd->tx_argv[7]);
			if (!StrIsInt(cmd->tx_argv[8])) goto badusage;
			area.r_ytop = atoi(cmd->tx_argv[8]);
			DBWElementAddRect(w, cmd->tx_argv[3], &area, def, style);
			break;
		    case ELEMENT_TEXT:
			if (cmd->tx_argc != 8)
			{
			    TxError("Usage:  element add %s\n",
					cmdElementType[ELEMENT_TEXT]);
			    return;
			}
			DBWElementAddText(w, cmd->tx_argv[3], area.r_xbot,
				area.r_ybot, cmd->tx_argv[7], def, style);
			break;
		}
	    }
	    else
	    {
		TxError("Usage:  element add %s\n", cmdElementType[type]);
		return;
	    }
	    break;
	case ELEMENT_DELETE:
	    if (cmd->tx_argc != 3)
	    {
		TxPrintf("Usage:  element delete <name>\n");
		return;
	    }
	    DBWElementDelete(w, cmd->tx_argv[2]);
	    break;
	case ELEMENT_CONFIG:
	    if (cmd->tx_argc < 4)
	    {
		TxError("Usage:  element configure <name> <option...>\n");
		return;
	    }
	    type = Lookup(cmd->tx_argv[3], cmdConfigureType);
	    if ((type < 0) || (cmd->tx_argc < 4))
	    {
		TxError("Usage:  element configure <name> <option...>, "
				"where option is one of:\n");
		for (msg = &(cmdConfigureType[0]); *msg != NULL; msg++)
		{
		    TxError("    %s\n", *msg);
		}
		return;
	    }

	    if (cmd->tx_argc == 4)
		getopt = TRUE;
	    else
		getopt = !strncmp(cmd->tx_argv[4], "get", 3);

	    switch (type)
	    {
		case OPTION_TEXT:
		    if (getopt)
			DBWElementText(w, cmd->tx_argv[2], NULL);
		    else
		    {
			if (cmd->tx_argc == 5)
			    DBWElementText(w, cmd->tx_argv[2], cmd->tx_argv[4]);
			else if (cmd->tx_argc != 6)
			{
			    TxError("Usage:  element configure <name> text "
					"replace <string>\n");
			    return;
			}
			else
			{
			    DBWElementText(w, cmd->tx_argv[2], cmd->tx_argv[4]);
			}
		    }
		    break;
		case OPTION_STYLE:
		    if (getopt)
			DBWElementStyle(w, cmd->tx_argv[2], -1, 0);
		    else if (cmd->tx_argc != 6)
		    {
			TxError("Usage: element configure <name> style add|remove "
					" <style>\n");
			return;
		    }
		    else
		    {
			if (StrIsInt(cmd->tx_argv[5]))
			    style = atoi(cmd->tx_argv[5]);
			else
			{
			    style = GrGetStyleFromName(cmd->tx_argv[5]);
		    	    if (style < 0)
		    	    {
				TxError("Unknown style \"%s\".", cmd->tx_argv[5]);
				TxError("Use a number or one of the long names "
						"in the .dstyle file\n");
				return;
			    }
			}
			if (!strncmp(cmd->tx_argv[4], "add", 3))
			    DBWElementStyle(w, cmd->tx_argv[2], style, TRUE);
			else
			    DBWElementStyle(w, cmd->tx_argv[2], style, FALSE);
		    }
		    break;
		case OPTION_POSITION:
		    if (getopt)
		    {
			DBWElementPos(w, cmd->tx_argv[2], NULL);
		    }
		    else
		    {
			Rect crect;

			if (cmd->tx_argc >= 6)
			{
			    if (!StrIsInt(cmd->tx_argv[4]) ||
			    		!StrIsInt(cmd->tx_argv[5]))
				goto badrect;
			    crect.r_xbot = atoi(cmd->tx_argv[4]);
			    crect.r_ybot = atoi(cmd->tx_argv[5]);
			    crect.r_xtop = crect.r_xbot; /* placeholder */
			    crect.r_ytop = crect.r_ybot; /* placeholder */
			}
			if (cmd->tx_argc == 8)
			{
			    if (!StrIsInt(cmd->tx_argv[6]) ||
			    		!StrIsInt(cmd->tx_argv[7]))
				goto badrect;
			    crect.r_xtop = atoi(cmd->tx_argv[6]);
			    crect.r_ytop = atoi(cmd->tx_argv[7]);
			}

			if (cmd->tx_argc == 6 || cmd->tx_argc == 8)
			    DBWElementPos(w, cmd->tx_argv[2], &crect);
			else
			{
badrect:
			    TxError("Usage: element configure <name> position "
					"<x> <y> [<x2> <y2>]\n");
			    return;
			}
		    }
		    break;
		case OPTION_FLAGS:
		    if (getopt)
		    {
			TxError("(unimplemented function)\n");
		    }
		    else
		    {
			if (cmd->tx_argc >= 5)
			    DBWElementParseFlags(w, cmd->tx_argv[2], cmd->tx_argv[4]);
			else
			{
			    TxError("Usage: element configure <name> flags "
					"(flag)\n");
			    return;
			}
		    }
		    break;
	    }
	    break;
	case ELEMENT_NAMES:
	    DBWElementNames();
	    break;
	case ELEMENT_INBOX:
	    if (!ToolGetBox((CellDef **)NULL, &area))
	    {
		TxError("Box tool must be present\n");
		return;
	    }
	    DBWElementInbox(&area);
	    break;
	case ELEMENT_HELP:
badusage:
            for (msg = &(cmdElementOption[0]); *msg != NULL; msg++)
            {
                TxPrintf("    %s\n", *msg);
            }
	    break;
    }
}



/*
 * ----------------------------------------------------------------------------
 *
 * CmdErase --
 *
 * Implement the "erase" command.
 * Erase paint in the specified layers from underneath the box in
 * EditCellUse->cu_def.
 *
 * Usage:
 *	erase [layers | cursor]
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Modified EditCellUse->cu_def.
 *
 * ----------------------------------------------------------------------------
 */

/* The following information is used to keep track of cells to be
 * erased.  It is needed because we can't delete cells while searching
 * for cells, or the database gets screwed up.
 */

#define MAXCELLS 100
static CellUse *cmdEraseCells[MAXCELLS];
static int cmdEraseCount;

void
CmdErase(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    Rect editRect;
    TileTypeBitMask mask;
    extern int cmdEraseCellsFunc();

    windCheckOnlyWindow(&w, DBWclientID);
    if (w == (MagWindow *) NULL) return;

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

    if (!ToolGetEditBox(&editRect)) return;

    /*
     * Erase with no arguments is the same as erasing
     * everything underneath the box tool (ie, painting space)
     * Labels are automatically affected; subcells are not.
     */

    if (cmd->tx_argc == 1)
	(void) CmdParseLayers("*,label", &mask);
    else if (!strncmp(cmd->tx_argv[1], "cursor", 6))
    {
	CmdPaintEraseButton(w, &cmd->tx_p, FALSE);
	return;
    }
    else if (!CmdParseLayers(cmd->tx_argv[1], &mask))
	return;

    if (TTMaskEqual(&mask, &DBSpaceBits))
	(void) CmdParseLayers("*,label", &mask);
    TTMaskClearType(&mask, TT_SPACE);
    if (TTMaskIsZero(&mask))
	return;

    TTMaskAndMask(&mask, &DBActiveLayerBits);

    /* Erase paint. */
    DBEraseValid(EditCellUse->cu_def, &editRect, &mask, 0);

    /* Erase labels. */
    (void) DBEraseLabel(EditCellUse->cu_def, &editRect, &mask);

    /* Erase subcells. */
    if (TTMaskHasType(&mask, L_CELL))
    {
	SearchContext scx;
	int i;

	/* To erase cells, we make a series of passes.  In each
	 * pass, collect a whole bunch of cells that are in the
	 * area of interest, then erase all those cells.  Continue
	 * this until all cells have been erased.
	 */
	
	scx.scx_use = EditCellUse;
	scx.scx_x = scx.scx_y = 0;
	scx.scx_area = editRect;
	scx.scx_trans = GeoIdentityTransform;
	while (TRUE)
	{
	    cmdEraseCount = 0;
	    (void) DBCellSrArea(&scx, cmdEraseCellsFunc, (ClientData) NULL);
	    for (i=0; i<cmdEraseCount; i++)
	    {
		DRCCheckThis(EditCellUse->cu_def, TT_CHECKSUBCELL,
		    &(cmdEraseCells[i]->cu_bbox));
		DBWAreaChanged(EditCellUse->cu_def,
		    &(cmdEraseCells[i]->cu_bbox), DBW_ALLWINDOWS,
		    (TileTypeBitMask *) NULL);
		DBUnLinkCell(cmdEraseCells[i], EditCellUse->cu_def);
		DBDeleteCell(cmdEraseCells[i]);
		(void) DBCellDeleteUse(cmdEraseCells[i]);
	    }
	    if (cmdEraseCount < MAXCELLS) break;
	}
    }
    DBAdjustLabels(EditCellUse->cu_def, &editRect);

    /* Don't run expensive DRC when only labels were erased */
    TTMaskClearType(&mask, L_LABEL);
    if (!TTMaskIsZero(&mask))
	DRCCheckThis (EditCellUse->cu_def, TT_CHECKPAINT, &editRect);
    TTMaskClearType(&mask, L_CELL);
    SelectClear();
    DBWAreaChanged(EditCellUse->cu_def, &editRect, DBW_ALLWINDOWS, &mask);
    DBReComputeBbox(EditCellUse->cu_def);
}

int
cmdEraseCellsFunc(scx, cdarg)
SearchContext *scx;		/* Indicates cell found. */
ClientData cdarg;		/* Not used. */
{
    /* All this procedure does is to remember cells that are
     * found, up to MAXCELLS of them.
     */
    
    if (cmdEraseCount >= MAXCELLS) return 1;
    cmdEraseCells[cmdEraseCount] = scx->scx_use;
    cmdEraseCount += 1;
    return 2;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdExpand --
 *
 * Implement the "expand" command.
 *
 * Usage:
 *	expand
 *	expand toggle
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If "toggle" is specified, flips the expanded/unexpanded status
 *	of all selected cells.  Otherwise, aren't any unexpanded cells
 *	left under the box.  May read cells in from disk, and updates
 *	bounding boxes that have changed.
 *
 * ----------------------------------------------------------------------------
 */

void
CmdExpand(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    int windowMask, boxMask, d;
    Rect rootRect;
    CellUse *rootBoxUse;
    CellDef *rootBoxDef;
    int cmdExpandFunc();		/* Forward reference. */

    if (cmd->tx_argc > 2 || (cmd->tx_argc == 2 
	&& (strncmp(cmd->tx_argv[1], "toggle", strlen(cmd->tx_argv[1])) != 0)))
    {
	TxError("Usage: %s or %s toggle\n", cmd->tx_argv[0], cmd->tx_argv[0]);
	return;
    }

    windCheckOnlyWindow(&w, DBWclientID);
    if (w == (MagWindow *) NULL)
    {
	TxError("Point to a window first.\n");
	return;
    }
    windowMask = ((DBWclientRec *) w->w_clientData)->dbw_bitmask;
    d = DBLambda[1];
    rootBoxUse = (CellUse *)w->w_surfaceID;
    rootBoxDef = rootBoxUse->cu_def;

    /* Check for possible rescaling during the expansion, and adjust    */
    /* the cursor box and window to restore the original view.          */

    d = DBLambda[1];
    do 
    {
	if (d != DBLambda[1])
	{
            d = DBLambda[1] / d;
            DBScalePoint(&rootRect.r_ll, d, 1);
            DBScalePoint(&rootRect.r_ur, d, 1);
            ToolMoveBox(TOOL_BL, &rootRect.r_ll, FALSE, rootBoxDef);
            ToolMoveCorner(TOOL_TR, &rootRect.r_ur, FALSE, rootBoxDef);

            /* Adjust all window viewing scales and positions and redraw */

            WindScale(d, 1);
	    TxPrintf("expand: rescaled by %d\n", d);
	    d = DBLambda[1];
	    if (cmd->tx_argc == 2) break;	/* Don't toggle twice */
	}
	(void) ToolGetBoxWindow(&rootRect, &boxMask);

	if (cmd->tx_argc == 2)
	    SelectExpand(windowMask);
	else
	{
	    if ((boxMask & windowMask) != windowMask)
	    {
		TxError("The box isn't in the same window as the cursor.\n");
		return;
	    }
	    DBExpandAll(rootBoxUse, &rootRect, windowMask,
			TRUE, cmdExpandFunc, (ClientData)(pointertype) windowMask);
	}
    } while (d != DBLambda[1]);
}

/* This function is called for each cell whose expansion status changed.
 * It forces the cells area to be redisplayed, then returns 0 to keep
 * looking for more cells to expand.
 */

int
cmdExpandFunc(use, windowMask)
    CellUse *use;		/* Use that was just expanded. */
    int windowMask;		/* Window where it was expanded. */
{
    if (use->cu_parent == NULL) return 0;
    DBWAreaChanged(use->cu_parent, &use->cu_bbox, windowMask,
	    &DBAllButSpaceBits);
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CmdExtract --
 *
 * Implement the "extract" command.
 *
 * Usage:
 *	extract option args
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	There are no side effects on the circuit.  Various options
 *	may produce .ext files or change extraction parameters.
 *
 * ----------------------------------------------------------------------------
 */
#ifndef NO_EXT

#define EXTINCREMENTAL -1
#define	EXTALL		0
#define EXTCELL		1
#define	EXTDO		2
#define EXTHELP		3
#define	EXTLENGTH	4
#define	EXTNO		5
#define	EXTPARENTS	6
#define	EXTSHOWPARENTS	7
#define	EXTSTYLE	8
#define	EXTUNIQUE	9
#define	EXTWARN		10

#define	WARNALL		0
#define WARNDUP		1
#define	WARNFETS	2
#define WARNLABELS	3

#define	DOADJUST	0
#define	DOALL		1
#define	DOCAPACITANCE	2
#define	DOCOUPLING	3
#define	DOLENGTH	4
#define	DORESISTANCE	5

#define	LENCLEAR	0
#define	LENDRIVER	1
#define	LENRECEIVER	2

void
CmdExtract(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    char **msg, *namep, *arg;
    int option, warn, len, n;
    bool no, all;
    CellUse *selectedUse;
    CellDef	*selectedDef;
    bool dolist = FALSE;
    bool doforall = FALSE;
    int argc = cmd->tx_argc;
    char **argv = cmd->tx_argv;

    static char *cmdExtWarn[] =
    {
	"all			enable all warnings",
	"dup			warn when different nodes have the same name",
	"fets			warn about badly constructed fets",
	"labels		warn when subcell nodes are unlabelled",
	NULL
    };
    static char *cmdExtOption[] =
    {
	"adjust			compensate R and C hierarchically",
	"all			all options",
	"capacitance		extract substrate capacitance",
	"coupling		extract coupling capacitance",
	"length			compute driver-receiver pathlengths",
	"resistance		estimate resistance",
	NULL
    };
    static char *cmdExtLength[] =
    {
	"clear			clear the driver and receiver tables",
	"driver termName(s)	identify a driving (output) terminal",
	"receiver termName(s)	identify a receiving (input) terminal",
	NULL
    };
    static char *cmdExtCmd[] =
    {	
	"all			extract root cell and all its children",
	"cell name		extract selected cell into file \"name\"",
	"do [option]		enable extractor option",
	"help			print this help information",
	"length [option]	control pathlength extraction information",
	"no [option]		disable extractor option",
	"parents		extract selected cell and all its parents",
	"showparents		show all parents of selected cell",
	"style [stylename]	set current extraction parameter style",
	"unique [#]	generate unique names when different nodes\n\
			have the same name",
	"warn [ [no] option]	enable/disable reporting of non-fatal errors",
	NULL
    };

    if (argc > 1)
    {
	if (!strncmp(argv[1], "list", 4))
	{
	    dolist = TRUE;
	    if (!strncmp(argv[1], "listall", 7))
	    {
		doforall = TRUE;
	    }
	    argv++;
	    argc--;
	}
	option = Lookup(argv[1], cmdExtCmd);
	if (option < 0)
	{
	    TxError("\"%s\" isn't a valid extract option.", argv[1]);
	    TxError("  Type \"extract help\" for a list of valid options.\n");
	    return;
        }
    }
    else option = EXTINCREMENTAL;

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

    if ((option != EXTSTYLE) && (option != EXTHELP))
    {
	windCheckOnlyWindow(&w, DBWclientID);
	if (w == (MagWindow *) NULL)
	{
	    if (ToolGetBox(&selectedDef,NULL) == FALSE)
	    {
		TxError("Point to a window first\n");
		return;
	    }
	    selectedUse = selectedDef->cd_parents;
	}
	else
	{
    	    selectedUse = (CellUse *)w->w_surfaceID;
	}

	if (argc == 1)
	{
	    ExtIncremental(selectedUse);
	    return;
	}
    }

    switch (option)
    {
	case EXTUNIQUE:
	    all = TRUE;
	    if (argc > 2)
	    {
		if (argc != 3) goto wrongNumArgs;
		if (strcmp(argv[2], "#") != 0)
		{
		    TxError("Usage: extract unique [#]\n");
		    return;
		}
		all = FALSE;
	    }
	    ExtUnique(selectedUse, all);
	    break;

	case EXTALL:
	    ExtAll(selectedUse);
	    return;

	case EXTCELL:
	    if (argc != 3) goto wrongNumArgs;
	    namep = argv[2];
	    selectedUse = CmdGetSelectedCell((Transform *) NULL);
	    if (selectedUse == NULL)
	    {
		TxError("No cell selected\n");
		return;
	    }
	    ExtCell(selectedUse->cu_def, namep, FALSE);
	    return;

	case EXTPARENTS:
	    selectedUse = CmdGetSelectedCell((Transform *) NULL);
	    if (selectedUse == NULL)
	    {
		TxError("No cell selected\n");
		return;
	    }
	    ExtParents(selectedUse);
	    return;

	case EXTSHOWPARENTS:
	    selectedUse = CmdGetSelectedCell((Transform *) NULL);
	    if (selectedUse == NULL)
	    {
		TxError("No cell selected\n");
		return;
	    }
	    ExtShowParents(selectedUse);
	    return;

	case EXTSTYLE:
	    if (argc == 2)
		ExtPrintStyle(dolist, doforall, !doforall);
	    else
		ExtSetStyle(argv[2]);
	    return;

	case EXTHELP:
	    if (argc != 2)
	    {
		wrongNumArgs:
		TxError("Wrong number of arguments in \"extract\" command.");
		TxError("  Try \"extract help\" for help.\n");
		return;
	    }
	    TxPrintf("Extract commands have the form \"extract option\",\n");
	    TxPrintf("where option is one of:\n");
	    for (msg = &(cmdExtCmd[0]); *msg != NULL; msg++)
	    {
		if (**msg == '*') continue;
		TxPrintf("  %s\n", *msg);
	    }
	    TxPrintf("If no option is given, the root cell and all its\n");
	    TxPrintf("children are extracted incrementally.\n");
	    return;

#define	WARNSET(f)	((ExtDoWarn & (f)) ? "do" : "no")
	case EXTWARN:
	    if (argc < 3)
	    {
		TxPrintf("The following extractor warnings are enabled:\n");
		TxPrintf("    %s dup\n", WARNSET(EXTWARN_DUP));
		TxPrintf("    %s fets\n", WARNSET(EXTWARN_FETS));
		TxPrintf("    %s labels\n", WARNSET(EXTWARN_LABELS));
		return;
	    }
#undef	WARNSET

	    no = FALSE;
	    arg = argv[2];
	    if (argc > 3 && strcmp(arg, "no") == 0)
		no = TRUE, arg = argv[3];

	    option = Lookup(arg, cmdExtWarn);
	    if (option < 0)
	    {
		TxError("Usage: extract warnings [no] option\n");
		TxPrintf("where option is one of:\n");
		for (msg = &(cmdExtWarn[0]); *msg != NULL; msg++)
		{
		    if (**msg == '*') continue;
		    TxPrintf("  %s\n", *msg);
		}
		return;
	    }
	    switch (option)
	    {
		case WARNALL:		warn = EXTWARN_ALL; break;
		case WARNDUP:		warn = EXTWARN_DUP; break;
		case WARNFETS:		warn = EXTWARN_FETS; break;
		case WARNLABELS:	warn = EXTWARN_LABELS; break;
	    }
	    if (no) ExtDoWarn &= ~warn;
	    else ExtDoWarn |= warn;
	    return;
	case EXTDO:
	case EXTNO:
#define	OPTSET(f)	((ExtOptions & (f)) ? "do" : "no")
	    if (argc < 3)
	    {
		TxPrintf("The following are the extractor option settings:\n");
		TxPrintf("%s adjust\n", OPTSET(EXT_DOADJUST));
		TxPrintf("%s capacitance\n", OPTSET(EXT_DOCAPACITANCE));
		TxPrintf("%s coupling\n", OPTSET(EXT_DOCOUPLING));
		TxPrintf("%s length\n", OPTSET(EXT_DOLENGTH));
		TxPrintf("%s resistance\n", OPTSET(EXT_DORESISTANCE));
		return;
#undef	OPTSET
	    }

	    no = (option == EXTNO);
	    arg = argv[2];
	    option = Lookup(arg, cmdExtOption);
	    if (option < 0)
	    {
		TxError("Usage: extract do option\n");
		TxError("   or  extract no option\n");
		TxPrintf("where option is one of:\n");
		for (msg = &(cmdExtOption[0]); *msg != NULL; msg++)
		{
		    if (**msg == '*') continue;
		    TxPrintf("  %s\n", *msg);
		}
		return;
	    }
	    switch (option)
	    {
		case DOADJUST:		option = EXT_DOADJUST; break;
		case DOALL:		option = EXT_DOALL; break;
		case DOCAPACITANCE:	option = EXT_DOCAPACITANCE; break;
		case DOCOUPLING:	option = EXT_DOCOUPLING; break;
		case DOLENGTH:		option = EXT_DOLENGTH; break;
		case DORESISTANCE:	option = EXT_DORESISTANCE; break;
	    }
	    if (no) ExtOptions &= ~option;
	    else ExtOptions |= option;
	    return;
	case EXTLENGTH:
	    if (argc < 3)
		goto lenUsage;
	    arg = argv[2];
	    len = Lookup(arg, cmdExtLength);
	    if (len < 0)
	    {
lenUsage:
		TxError("Usage: extract length option [args]\n");
		TxPrintf("where option is one of:\n");
		for (msg = &(cmdExtLength[0]); *msg != NULL; msg++)
		{
		    if (**msg == '*') continue;
		    TxPrintf("  %s\n", *msg);
		}
		return;
	    }
	    switch (len)
	    {
		case LENCLEAR:
		    ExtLengthClear();
		    break;
		case LENDRIVER:
		    if (argc < 4)
		    {
driverUsage:
			TxError("Must specify one or more terminal names\n");
			return;
		    }
		    for (n = 3; n < argc; n++)
			ExtSetDriver(argv[n]);
		    break;
		case LENRECEIVER:
		    if (argc < 4)
			goto driverUsage;
		    for (n = 3; n < argc; n++)
			ExtSetReceiver(argv[n]);
		    break;
	    }
	    return;
    }

    /*NOTREACHED*/
}
#endif
