/*
 * selOps.c --
 *
 * This file contains top-level procedures to manipulate the selection,
 * e.g. to delete it, move it, etc.
 *
 *     *********************************************************************
 *     * 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-8.0/select/selOps.c,v 1.11 2010/08/22 21:58:26 tim Exp $";
#endif  /* not lint */

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

#include "utils/magic.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "utils/stack.h"
#include "database/database.h"
#include "windows/windows.h"
#include "dbwind/dbwind.h"
#include "utils/main.h"
#include "select/select.h"
#include "select/selInt.h"
#include "textio/textio.h"
#include "utils/undo.h"
#include "plow/plow.h"
#include "utils/malloc.h"
#include "drc/drc.h"

/* C99 compat */
#include "commands/commands.h"
#include "netmenu/netmenu.h"

/* The following variables are shared between SelectStretch and the
 * search functions that it causes to be invoked.
 */

static int selStretchX, selStretchY;	/* Stretch distances.  Only one should
					 * ever be non-zero.
					 */
static TileType selStretchType;		/* Type of material being stretched. */
unsigned char SelectDoLabels = SEL_DO_LABELS;	/* Whether or not to select subcell labels */

typedef struct planeAndArea
{
    int pa_plane;			/* Plane of interest */
    Rect *pa_area;			/* Area affected */
    TileTypeBitMask *pa_mask;		/* Mask used in plane search */
    TileTypeBitMask pa_rmask;		/* Mask to generate during search */
} planeAndArea;

/* The following structure type is used to build up a list of areas
 * to be painted.  It's used to save information while a search of
 * the edit cell is in progress:  can't do the paints until the
 * search has finished.
 */

typedef struct stretchArea
{
    Rect sa_area;			/* Area to be painted. */
    TileType sa_type;			/* Type of material to paint. */
    struct stretchArea *sa_next;	/* Next element in list. */
} StretchArea;

static StretchArea *selStretchList;	/* List of areas to paint. */

/*
 * ----------------------------------------------------------------------------
 *
 * SelectDelete --
 *
 * 	Delete everything in the edit cell that's selected.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Stuff is removed from the edit cell.  If there's selected
 *	stuff that isn't in the edit cell, the user is warned.
 *
 * ----------------------------------------------------------------------------
 */

void
SelectDelete(msg, do_clear)
    char *msg;		/* Some information to print in error messages.
			 * For example, if called as part of a move procedure,
			 * supply "moved".  This will appear in messages of
			 * the form "only edit cell information was moved".
			 */
    bool do_clear;	/* If TRUE, clear the select def before returning. */
{
    bool nonEdit;
    Rect editArea;

    extern int selDelPaintFunc(), selDelCellFunc(), selDelLabelFunc();

    if (EditCellUse == NULL)
    {
	TxError("The current cell is not editable.\n");
	return;
    }
    (void) SelEnumPaint(&DBAllButSpaceAndDRCBits, TRUE, &nonEdit,
	    selDelPaintFunc, (ClientData) NULL);
    if (nonEdit)
    {
	TxError("You selected paint outside the edit cell.  Only\n");
	TxError("    the paint in the edit cell was %s.\n", msg);
    }
    (void) SelEnumCells(TRUE, &nonEdit, (SearchContext *) NULL,
	    selDelCellFunc, (ClientData) NULL);
    if (nonEdit)
    {
	TxError("You selected one or more subcells that aren't children\n");
	TxError("    of the edit cell.  Only those in the edit cell were\n");
	TxError("    %s.\n", msg);
    }
    (void) SelEnumLabels(&DBAllTypeBits, TRUE, &nonEdit,
	    selDelLabelFunc, (ClientData) NULL);
    if (nonEdit)
    {
	TxError("You selected one or more labels that aren't in the\n");
	TxError("    edit cell.  Only the label(s) in the edit cell\n");
	TxError("    were %s.\n", msg);
    }

    DBReComputeBbox(EditCellUse->cu_def);
    GeoTransRect(&RootToEditTransform, &SelectDef->cd_extended, &editArea);
    DBWAreaChanged(EditCellUse->cu_def, &editArea, DBW_ALLWINDOWS,
	(TileTypeBitMask *) NULL);
    DRCCheckThis(EditCellUse->cu_def, TT_CHECKPAINT, &editArea);
    if (do_clear) SelectClear();
}

/*
 * ----------------------------------------------------------------------------
 *
 * SelectDeleteUses --
 *
 * 	Delete all cell uses in the edit cell that are selected.
 *	This function is used by "flatten -doinplace" when operating
 *	on a selection.  Since selected instances are flattened, the
 *	instances need to be deleted from the cell.  This routine is
 *	the same as SelectDelete() without the paint and label delete
 *	functions.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Cell uses are removed from the edit cell.  If there are selected
 *	uses that aren't in the edit cell, the user is warned.
 *
 * ----------------------------------------------------------------------------
 */

void
SelectDeleteUses(msg, do_clear)
    char *msg;		/* Some information to print in error messages.
			 * For example, if called as part of a move procedure,
			 * supply "moved".  This will appear in messages of
			 * the form "only edit cell information was moved".
			 */
    bool do_clear;	/* If TRUE, clear the select def before returning. */
{
    bool nonEdit;
    Rect editArea;

    extern int selDelPaintFunc(), selDelCellFunc(), selDelLabelFunc();

    if (EditCellUse == NULL)
    {
	TxError("The current cell is not editable.\n");
	return;
    }
    (void) SelEnumCells(TRUE, &nonEdit, (SearchContext *) NULL,
	    selDelCellFunc, (ClientData) NULL);
    if (nonEdit)
    {
	TxError("You selected one or more subcells that aren't children\n");
	TxError("    of the edit cell.  Only those in the edit cell were\n");
	TxError("    %s.\n", msg);
    }

    DBReComputeBbox(EditCellUse->cu_def);
    GeoTransRect(&RootToEditTransform, &SelectDef->cd_extended, &editArea);
    DBWAreaChanged(EditCellUse->cu_def, &editArea, DBW_ALLWINDOWS,
	(TileTypeBitMask *) NULL);
    DRCCheckThis(EditCellUse->cu_def, TT_CHECKPAINT, &editArea);
    if (do_clear) SelectClear();
}

/* Search function to delete paint. */

int
selDelPaintFunc(rect, type)
    Rect *rect;			/* Area of paint, in root coords. */
    TileType type;		/* Type of paint to delete. */
{
    Rect editRect;
    TileTypeBitMask tmask;
    TileType dinfo;

    /* Change diagonal side & direction according to the transform */

    if (type & TT_DIAGONAL)
    {
	dinfo = DBTransformDiagonal(type, &RootToEditTransform);
	TTMaskSetOnlyType(&tmask, type & TT_LEFTMASK);
    }
    else
    {
	dinfo = 0;
	TTMaskSetOnlyType(&tmask, type);
    }

    GeoTransRect(&RootToEditTransform, rect, &editRect);

    DBEraseValid(EditCellUse->cu_def, &editRect, &tmask, dinfo);
    return 0;
}

/* Search function to delete subcell uses. */

    /* ARGSUSED */
int
selDelCellFunc(selUse, use)
    CellUse *selUse;		/* Not used. */
    CellUse *use;		/* What to delete. */
{
    if (use->cu_flags & CU_LOCKED) return 0;

    DBUnLinkCell(use, use->cu_parent);
    DBDeleteCell(use);
    (void) DBCellDeleteUse(use);
    return 0;
}


/* Search function to delete labels.  Delete any label at the right
 * place with the right name, regardless of layer attachment, because
 * the selection can differ from the edit cell in this regard. */

int
selDelLabelFunc(label)
    Label *label;		/* Label to delete. */
{
    DBEraseLabelsByContent(EditCellUse->cu_def, &label->lab_rect, -1,
		label->lab_text);
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * SelectCopy --
 *
 * 	This procedure makes a copy of the selection.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The selection is copied, with the copy being transformed by
 *	"transform" relative to the current selection.  The copy is
 *	made the new selection.
 *
 * ----------------------------------------------------------------------------
 */

void
SelectCopy(transform)
    Transform *transform;	/* How to displace the copy relative
				 * to the original.  This displacement
				 * is given in root coordinates.
				 */
{
    SearchContext scx;

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

    /* Copy from SelectDef to Select2Def while transforming, then
     * let SelectAndCopy2 do the rest of the work.  Don't record
     * anything involving Select2Def for undo-ing.
     */

    UndoDisable();
    DBCellClearDef(Select2Def);
    scx.scx_use = SelectUse;
    scx.scx_area = SelectUse->cu_bbox;
    GeoTransTrans(transform, &SelectUse->cu_transform, &scx.scx_trans);
    (void) DBCellCopyAllPaint(&scx, &DBAllButSpaceAndDRCBits, CU_DESCEND_NO_LOCK,
		Select2Use);
    (void) DBCellCopyAllLabels(&scx, &DBAllTypeBits, CU_DESCEND_NO_LOCK, Select2Use,
		(Rect *) NULL);
    (void) DBCellCopyAllCells(&scx, CU_DESCEND_NO_LOCK, Select2Use, (Rect *) NULL);

    /* The selection now has the same instance names as the cell def.  If
     * copies are made, then there will be name conflicts, and the original
     * instance may be the one renamed.  The sensible thing to do is to make
     * sure that all conflicts are resolved within the select CellDef before
     * the copy is made.
     */
    DBSelectionUniqueIds(Select2Def, EditRootDef);

    DBReComputeBbox(Select2Def);
    UndoEnable();
 
    SelectClear();
    SelectAndCopy2(EditRootDef);
}

/*
 * ----------------------------------------------------------------------------
 *
 * SelectFlat --
 *
 * 	This procedure copies the selection into Select2Def, flattening
 *	it as it copies, then copies the result back into the selection
 *	cell.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The selection is flattened.  No changes are made in the edit cell.
 *
 * ----------------------------------------------------------------------------
 */

void
SelectFlat()
{
    SearchContext scx;

    /* Copy from SelectDef to Select2Def while transforming, then
     * let SelectAndCopy2 do the rest of the work.  Don't record
     * anything involving Select2Def for undo-ing.
     */

    UndoDisable();
    DBCellClearDef(Select2Def);
    scx.scx_use = SelectUse;
    scx.scx_area = SelectUse->cu_bbox;
    GeoTransTrans(&GeoIdentityTransform, &SelectUse->cu_transform, &scx.scx_trans);
    DBCellCopyAllPaint(&scx, &DBAllButSpaceAndDRCBits, CU_DESCEND_ALL, Select2Use);
    FlatCopyAllLabels(&scx, &DBAllTypeBits, CU_DESCEND_ALL, Select2Use);
    DBReComputeBbox(Select2Def);
    UndoEnable();

    /* Move everything from Select2 to Select */

    SelectClear();
    SelRememberForUndo(TRUE, (CellDef *) NULL, (Rect *) NULL);

    scx.scx_use = Select2Use;
    scx.scx_area = Select2Use->cu_bbox;
    GeoTransTrans(&GeoIdentityTransform, &Select2Use->cu_transform, &scx.scx_trans);

    DBCellCopyAllPaint(&scx, &DBAllButSpaceAndDRCBits, CU_DESCEND_SPECIAL,
		SelectUse);
    DBCellCopyAllLabels(&scx, &DBAllTypeBits, CU_DESCEND_SPECIAL, SelectUse,
		(Rect *)NULL);
    DBReComputeBbox(SelectDef);

    SelRememberForUndo(FALSE, SelectRootDef, &SelectUse->cu_bbox);

    /* Redisplay the select cell */
    DBWHLRedraw(SelectRootDef, &SelectDef->cd_extended, TRUE);
    DBWAreaChanged(SelectDef, &SelectDef->cd_extended, DBW_ALLWINDOWS,
	&DBAllButSpaceBits);

}

/* Structure used by selShortTileProc() below to save a cost and Tile pointer */

typedef struct _shortsearchdata {
    int cost;
    Tile *tile;
} ShortSearchData;

/*
 * ----------------------------------------------------------------------------
 *
 * selShortTileProc --
 *
 * Callback function for DBSrPaintArea in selShortFindReverse().  When
 * checking connected types on other planes, record the tile with the
 * minimum cost.
 *
 * ----------------------------------------------------------------------------
 */

int
selShortTileProc(tile, dinfo, ssd)
    Tile *tile;
    TileType dinfo;		/* (unused) */
    ShortSearchData *ssd;
{
    const int curr = (int)TiGetClientINT(tile);
    if (curr < ssd->cost)
    {
	ssd->cost = curr;
	ssd->tile = tile;
    }
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 *  selShortFindReverse --
 *
 *	Trace back through a path found by selShortFindForward from destination
 *	to source, picking the lowest cost return path, and adding each tile
 *	found to the linked list.
 *
 *	Algorithm notes (for this and selShortFindForward):  Note that by not
 *	using one of the standard database search routines, TT_SIDE is NOT
 *	set on any tile.  To find out what side we're looking at, we keep
 *	a record of what direction we were traveling from the previous
 *	tile.  Given this and the diagonal direction, we know the two
 *	valid sides to search.
 *
 * Return value:
 *	0   search completed successfully
 *	1   search terminated with an error
 *
 * ----------------------------------------------------------------------------
 */

int
selShortFindReverse(rlist, tile, pnum, fdir)
   ExtRectList **rlist;
   Tile *tile;
   int pnum;
   int fdir;
{
    Tile *tp, *mintp;
    ExtRectList *newrrec;
    int mincost, minp, p, mindir;
    TileType ttype;

    mindir = fdir;
    mincost = (int)TiGetClientINT(tile);

    while (TRUE)
    {
	newrrec = mallocMagic(sizeof(ExtRectList));

	if (IsSplit(tile))
	{
	    newrrec->r_type = TiGetTypeExact(tile) & ~TT_SIDE;
	    switch(fdir)
	    {
		case GEO_NORTH:
		    ttype = SplitBottomType(tile);
		    if (!SplitDirection(tile)) newrrec->r_type |= TT_SIDE;
		    break;
		case GEO_SOUTH:
		    ttype = SplitTopType(tile);
		    if (SplitDirection(tile)) newrrec->r_type |= TT_SIDE;
		    break;
		case GEO_EAST:
		    ttype = SplitLeftType(tile);
		    break;
		case GEO_WEST:
		    ttype = SplitRightType(tile);
		    newrrec->r_type |= TT_SIDE;
		    break;
		default:
		    ttype = SplitRightType(tile);
		    if (ttype == TT_SPACE)
			ttype = SplitLeftType(tile);
		    else
			newrrec->r_type |= TT_SIDE;
		    break;
	    }
	}
	else
	{
	    ttype = TiGetTypeExact(tile);
	    newrrec->r_type = ttype;
	}

	/* Add this tile (area and type) to the linked list */

	TiToRect(tile, &newrrec->r_r);
	newrrec->r_next = *rlist;
	*rlist = newrrec;

	if ((int)TiGetClientINT(tile) == 0) return 0;	/* We're done */

	minp = pnum;

	/* Search top */
	if (IsSplit(tile))
	{
	    if (fdir == GEO_NORTH) goto leftside;
	    else if (SplitDirection(tile) && fdir == GEO_EAST) goto leftside;
	    else if (!SplitDirection(tile) && fdir == GEO_WEST) goto leftside;
	}

	for (tp = RT(tile); RIGHT(tp) > LEFT(tile); tp = BL(tp))
	{
	    const ClientData ticlient = TiGetClient(tp);
	    if (ticlient == CLIENTDEFAULT) continue;
	    const int curr = (int)CD2INT(ticlient);
	    if (curr < mincost)
	    {
		mincost = curr;
		mintp = tp;
		mindir = GEO_NORTH;
	    }
	}

	/* Search left */
leftside:
	if (IsSplit(tile))
	{
	    if (fdir == GEO_WEST) goto bottomside;
	    else if (SplitDirection(tile) && fdir == GEO_SOUTH) goto bottomside;
	    else if (!SplitDirection(tile) && fdir == GEO_NORTH) goto bottomside;
	}

	for (tp = BL(tile); BOTTOM(tp) < TOP(tile); tp = RT(tp))
	{
	    const ClientData ticlient = TiGetClient(tp);
	    if (ticlient == CLIENTDEFAULT) continue;
	    const int curr = (int)CD2INT(ticlient);
	    if (curr < mincost)
	    {
		mincost = curr;
		mintp = tp;
		mindir = GEO_WEST;
	    }
	}

	/* Search bottom */
bottomside:
	if (IsSplit(tile))
	{
	    if (fdir == GEO_SOUTH) goto rightside;
	    else if (SplitDirection(tile) && fdir == GEO_WEST) goto rightside;
	    else if (!SplitDirection(tile) && fdir == GEO_EAST) goto rightside;
	}

	for (tp = LB(tile); LEFT(tp) < RIGHT(tile); tp = TR(tp))
	{
	    const ClientData ticlient = TiGetClient(tp);
	    if (ticlient == CLIENTDEFAULT) continue;
	    const int curr = (int)CD2INT(ticlient);
	    if (curr < mincost)
	    {
		mincost = curr;
		mintp = tp;
		mindir = GEO_SOUTH;
	    }
	}

	/* Search right */
rightside:
	if (IsSplit(tile))
	{
	    if (fdir == GEO_EAST) goto donesides;
	    else if (SplitDirection(tile) && fdir == GEO_NORTH) goto donesides;
	    else if (!SplitDirection(tile) && fdir == GEO_SOUTH) goto donesides;
	}

	for (tp = TR(tile); TOP(tp) > BOTTOM(tile); tp = LB(tp))
	{
	    const ClientData ticlient = TiGetClient(tp);
	    if (ticlient == CLIENTDEFAULT) continue;
	    const int curr = (int)CD2INT(ticlient);
	    if (curr < mincost)
	    {
		mincost = curr;
		mintp = tp;
		mindir = GEO_EAST;
	    }
	}

	/* Search other connecting planes */
donesides:
	if (DBIsContact(ttype))
	{
	    ShortSearchData ssd;
	    PlaneMask pmask;

	    pmask = DBConnPlanes[ttype];

	    ssd.cost = mincost;
	    ssd.tile = (Tile *)NULL;
	    for (p = PL_TECHDEPBASE; p < DBNumPlanes; p++)
	    {
		if (PlaneMaskHasPlane(pmask, p) && (p != pnum))
		{
		    DBSrPaintArea((Tile *)NULL, SelectDef->cd_planes[p],
				&newrrec->r_r, &DBAllButSpaceAndDRCBits,
				selShortTileProc, &ssd);
		    if (ssd.cost < mincost)
		    {
			mincost = ssd.cost;
			mintp = ssd.tile;
			minp = p;
			mindir = GEO_CENTER;
		    }
		}
	    }
	}

	/* If mincost is still set to INT_MAX we have a real serious problem! */
	if (mincost == INT_MAX) return 1;

	/* Failsafe:  Avoid infinite recursion */
	if ((tile == mintp) || (TiGetClient(tile) == CLIENTDEFAULT))
	{
	    TxError("Failed to trace back shorting path.\n");
	    break;
	}

	/* Now we have the minimum cost neighboring tile;  continue search with it */
	tile = mintp;
	pnum = minp;
        fdir = mindir;

	/* Diagnostic */
	// TxPrintf("Cost = %d  Tile @ %d %d  plane %d  dir %d\n",
	//	mincost, tile->ti_ll.p_x, tile->ti_ll.p_y, pnum, fdir);
    }
    return 1;
}

/* Data structure used by selShortFindForward() to store a tile and */
/* the current search parameters, including cost, direction, plane, */
/* and the mask of connecting types.				    */

typedef struct _shortdata {
    int	   cost;
    Tile  *tile;
    TileType type;
    int    pnum;
} ShortData;

/*
 * ----------------------------------------------------------------------------
 *
 * NewSD --
 *
 *  Create and populate a ShortData structure.
 *
 * Return value:
 *	A pointer to an allocated and populated ShortData structure.
 *
 * ----------------------------------------------------------------------------
 */

ShortData *NewSD(cost, tile, type, pnum)
    int cost;
    Tile *tile;
    TileType type;
    int pnum;
{
    ShortData *sd;

    sd = (ShortData *)mallocMagic(sizeof(ShortData));

    sd->cost = cost;
    sd->tile = tile;
    sd->type = type;
    sd->pnum = pnum;

    return sd;
}

/*
 * ----------------------------------------------------------------------------
 *
 * selShortProcessTile --
 *
 *	Process a tile, finding if it extends the connection from the
 *	last position, and setting its cost to be one higher than the
 *	previous position.
 *
 * Return value:
 *	0 if tile was updated with new cost.
 *	1 if tile was unchanged.
 *
 * Side effects:
 *	Writes cost to the tile's ClientData record.
 *
 * ----------------------------------------------------------------------------
 */

int
selShortProcessTile(tile, cost, fdir, mask)
    Tile *tile;
    int cost;
    int fdir;
    TileTypeBitMask *mask;
{
    TileType ttype;

    /* Ignore tiles that were already processed.  This causes the	*/
    /* algorithm to find any valid path but not the best path.   That	*/
    /* choice keeps the algorithm fast and efficient.			*/

    if (TiGetClient(tile) != CLIENTDEFAULT) return 1;

    if (IsSplit(tile))
    {
	switch(fdir)
	{
	    case GEO_NORTH:
		ttype = SplitBottomType(tile);
		break;
	    case GEO_SOUTH:
		ttype = SplitTopType(tile);
		break;
	    case GEO_EAST:
		ttype = SplitLeftType(tile);
		break;
	    case GEO_WEST:
		ttype = SplitRightType(tile);
		break;
	    default:
		ttype = SplitLeftType(tile);
		if (ttype == TT_SPACE) ttype = SplitRightType(tile);
		break;
	}
    }
    else
	ttype = TiGetTypeExact(tile);

    /* Ignore space tiles */
    if (ttype == TT_SPACE) return 1;

    /* Ignore non-connecting tiles */
    if (!TTMaskHasType(mask, ttype)) return 1;

    /* If this tile is unvisited, or has a lower cost, then return and	  */
    /* keep going.  Otherwise, return 1 to stop the search this direction */

    const ClientData ticlient = TiGetClient(tile);
    const int curr = (int)CD2INT(ticlient);
    if (ticlient == CLIENTDEFAULT)
	TiSetClientINT(tile, cost);
    else if (curr > cost)
	TiSetClientINT(tile, cost);
    else
	return 1;

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * selShortFindForward --
 *
 *	Function for finding shorts.  The cell searched is always SelectDef.
 *
 * Results:
 *	None
 *
 * Side effects:
 *	Each tile visited has its ClientData record set to the current
 *	cost, in units equal to the steps from the source.
 *
 * ----------------------------------------------------------------------------
 */

void
selShortFindForward(srctile, srctype, srcpnum, desttile)
    Tile *srctile;
    TileType srctype;
    int srcpnum;
    Tile *desttile;
{
    TileType type;
    TileTypeBitMask *lmask;
    Tile *tile, *tp;
    int pnum;
    ShortData *sd;
    static Stack *ShortStack = (Stack *)NULL;

    int cost = 0;

    if (ShortStack == (Stack *)NULL)
	ShortStack = StackNew(64);

    /* Set the cost of the source tile to zero */
    TiSetClientINT(srctile, 0);

    /* Drop the first entry on the stack */
    sd = NewSD(cost, srctile, srctype, srcpnum);
    STACKPUSH(sd, ShortStack);

    while (!StackEmpty(ShortStack))
    {
	sd = (ShortData *)STACKPOP(ShortStack);
	tile = sd->tile;
	cost = sd->cost;
	pnum = sd->pnum;
	type = sd->type;
	freeMagic((char *)sd);

	lmask = &DBConnectTbl[type];

	/* Search top */
	if (IsSplit(tile))
	    if (TiGetTopType(tile) != type)
		goto srchleft;

	for (tp = RT(tile); RIGHT(tp) > LEFT(tile); tp = BL(tp))
	{
	    if (selShortProcessTile(tp, cost + 1, GEO_NORTH, lmask) == 0)
	    {
		sd = NewSD(cost + 1, tp, TiGetBottomType(tp), pnum);
		STACKPUSH(sd, ShortStack);
	    }
	}

	/* Search left */
srchleft:
	if (IsSplit(tile))
	    if (TiGetLeftType(tile) != type)
		goto srchbot;

	for (tp = BL(tile); BOTTOM(tp) < TOP(tile); tp = RT(tp))
	{
	    if (selShortProcessTile(tp, cost + 1, GEO_WEST, lmask) == 0)
	    {
		sd = NewSD(cost + 1, tp, TiGetRightType(tp), pnum);
		STACKPUSH(sd, ShortStack);
	    }
	}

	/* Search bottom */
srchbot:
	if (IsSplit(tile))
	    if (TiGetBottomType(tile) != type)
		goto srchright;

	for (tp = LB(tile); LEFT(tp) < RIGHT(tile); tp = TR(tp))
	{
	    if (selShortProcessTile(tp, cost + 1, GEO_SOUTH, lmask) == 0)
	    {
		sd = NewSD(cost + 1, tp, TiGetTopType(tp), pnum);
		STACKPUSH(sd, ShortStack);
	    }
	}

	/* Search right */
srchright:
	if (IsSplit(tile))
	    if (TiGetRightType(tile) != type)
		goto donesrch;

	for (tp = TR(tile); TOP(tp) > BOTTOM(tile); tp = LB(tp))
	{
	    if (selShortProcessTile(tp, cost + 1, GEO_EAST, lmask) == 0)
	    {
		sd = NewSD(cost + 1, tp, TiGetLeftType(tp), pnum);
		STACKPUSH(sd, ShortStack);
	    }
	}

	/* Search other connecting planes */
donesrch:
	if ((!IsSplit(tile)) && DBIsContact(type))
	{
	    PlaneMask pmask;
	    int p;

	    pmask = DBConnPlanes[type];
	    for (p = PL_TECHDEPBASE; p < DBNumPlanes; p++)
	    {
		if (PlaneMaskHasPlane(pmask, p) && (p != pnum))
		{
		    tp = PlaneGetHint(SelectDef->cd_planes[p]);
		    GOTOPOINT(tp, &tile->ti_ll);
		    PlaneSetHint(SelectDef->cd_planes[p], tp);
	    	    if (selShortProcessTile(tp, cost + 1, GEO_CENTER, lmask) == 0)
		    {
			sd = NewSD(cost + 1, tp, TiGetLeftType(tp), p);
			STACKPUSH(sd, ShortStack);
		    }
		}
	    }
	}
    }
}


/*
 * ----------------------------------------------------------------------------
 *
 * SelectShort --
 *
 *	This procedure, given two labels, finds the location of those
 *	labels in SelectDef.  One is marked as source, the other as
 *	destination.  Then the SelectDef paint (which is assumed to be
 *	a net selection that is all local to SelectDef) is recursively
 *	searched for connecting material.  Each tile ClientData is
 *	given a cost which is the number of steps from the source.
 *	At the end, the minimum cost path is traced from the destination
 * 	back to the source, and the path is saved as a linked list and
 *	passed back to the calling procedure.
 *
 * Results:
 *	A linked list of tiles representing the connecting path with the
 *	fewest steps between source and destination.
 *
 * Side effects:
 *	Tile database left with non-default ClientData.  It may be necessary
 *	to re-run the search routine to return all tiles to the default
 *	ClientData value.
 * ----------------------------------------------------------------------------
 */

ExtRectList *
SelectShort(char *lab1, char *lab2)
{
    Label *selLabel, *srclab = NULL, *destlab = NULL;
    Tile *srctile, *desttile;
    TileType srctype, desttype;
    int srcpnum, destpnum;
    Plane *plane;
    PlaneMask pmask;
    ExtRectList *rlist;

    CellUse *use;
    TileType ttype;
    Rect rect;
    SearchContext scx;
    MagWindow *window;
    DBWclientRec *crec;
    int windowMask;

    window = ToolGetBoxWindow(&rect, &windowMask);
    if (!window) return NULL;
    use = (CellUse *)window->w_surfaceID;

    /* Run the equivalent of "goto lab1 ; select net" */
    /* Make sure the selection is clear before starting */

    SelectClear();
    ttype = CmdFindNetProc(lab1, use, &rect, FALSE, NULL);
    if (ttype == TT_SPACE) return NULL;

    bzero(&scx, sizeof(SearchContext));
    scx.scx_use = use;
    scx.scx_trans = GeoIdentityTransform;
    scx.scx_area = rect;
    crec = (DBWclientRec *)window->w_clientData;

    SelectNet(&scx, ttype, crec->dbw_bitmask, (Rect *)NULL, FALSE);

    for (selLabel = SelectDef->cd_labels; selLabel != NULL; selLabel =
			selLabel->lab_next)
    {
	if ((srclab == NULL) && Match(lab1, selLabel->lab_text))
	    srclab = selLabel;

	if ((destlab == NULL) && Match(lab2, selLabel->lab_text))
	    destlab = selLabel;
    }

    /* Must be able to find both labels */
    if ((srclab == NULL) || (destlab == NULL)) return NULL;

    /* Must be able to find tiles associated with each label */

    pmask = DBTypePlaneMaskTbl[destlab->lab_type];
    for (destpnum = PL_TECHDEPBASE; destpnum < DBNumPlanes; destpnum++)
    {
	if (PlaneMaskHasPlane(pmask, destpnum))
	{
	    plane = SelectDef->cd_planes[destpnum];
	    desttile = PlaneGetHint(plane);
	    GOTOPOINT(desttile, &destlab->lab_rect.r_ll)
	    desttype = TiGetTopType(desttile);
	    if (TTMaskHasType(&DBConnectTbl[destlab->lab_type], desttype)) break;
	    desttype = TiGetBottomType(desttile);
	    if (TTMaskHasType(&DBConnectTbl[destlab->lab_type], desttype)) break;
	}
    }
    pmask = DBTypePlaneMaskTbl[srclab->lab_type];
    for (srcpnum = PL_TECHDEPBASE; srcpnum < DBNumPlanes; srcpnum++)
    {
	if (PlaneMaskHasPlane(pmask, srcpnum))
	{
	    plane = SelectDef->cd_planes[srcpnum];
	    srctile = PlaneGetHint(plane);
	    GOTOPOINT(srctile, &srclab->lab_rect.r_ll)
	    srctype = TiGetTopType(srctile);
	    if (TTMaskHasType(&DBConnectTbl[srclab->lab_type], srctype)) break;
	    srctype = TiGetBottomType(srctile);
	    if (TTMaskHasType(&DBConnectTbl[srclab->lab_type], srctype)) break;
	}
    }

    selShortFindForward(srctile, srctype, srcpnum, desttile/*, desttype*/);

    /* Now see if destination has been counted */
    if (TiGetClient(desttile) == CLIENTDEFAULT) return NULL;

    /* Now find the shortest path between source and destination */
    rlist = NULL;
    selShortFindReverse(&rlist, desttile, destpnum, GEO_CENTER);

    return rlist;
}


/*
 * ----------------------------------------------------------------------------
 *
 * selTransTo2 --
 *
 * 	This local procedure makes a transformed copy of the selection
 *	in Select2Def, ignoring everything that's not in the edit cell.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Select2Def gets modified to hold the transformed selection.
 *	Error messages get printed if the selection contains any
 *	non-edit material.
 *
 * ----------------------------------------------------------------------------
 */

void
selTransTo2(transform)
    Transform *transform;	/* How to transform stuff before copying
				 * it to Select2Def.
				 */
{
    int selTransPaintFunc();	/* Forward references. */
    int selTransCellFunc();
    int selTransLabelFunc();

    UndoDisable();
    DBCellClearDef(Select2Def);
    (void) SelEnumPaint(&DBAllButSpaceAndDRCBits, TRUE, (bool *) NULL,
	    selTransPaintFunc, (ClientData) transform);
    (void) SelEnumCells(TRUE, (bool *) NULL, (SearchContext *) NULL,
	    selTransCellFunc, (ClientData) transform);
    (void) SelEnumLabels(&DBAllTypeBits, TRUE, (bool *) NULL,
	    selTransLabelFunc, (ClientData) transform);
    DBReComputeBbox(Select2Def);
    UndoEnable();
}

/* Search function to copy paint.  Always return 1 to keep the search alive. */

int
selTransPaintFunc(rect, type, transform)
    Rect *rect;			/* Area of paint. */
    TileType type;		/* Type of paint. */
    Transform *transform;	/* How to change coords before painting. */
{
    Rect newarea;
    TileType loctype;

    /* Change diagonal direction according to the transform */
    if (type & TT_DIAGONAL)
    {
	loctype = DBTransformDiagonal(type, transform);
	loctype |= (loctype & TT_SIDE) ? (type & TT_LEFTMASK) << 14 :
		(type & TT_LEFTMASK);
    }
    else
	loctype = type;

    GeoTransRect(transform, rect, &newarea);
    DBPaint(Select2Def, &newarea, loctype);
    return 0;
}

/* Search function to copy subcells.  Always return 1 to keep the
 * search alive.
 */

    /* ARGSUSED */
int
selTransCellFunc(selUse, realUse, realTrans, transform)
    CellUse *selUse;		/* Use from selection. */
    CellUse *realUse;		/* Corresponding use from layout (used to
				 * get id). */
    Transform *realTrans;	/* Transform for realUse (ignored). */
    Transform *transform;	/* How to change coords of selUse before
				 * copying.
				 */
{
    CellUse  *newUse;
    Transform newTrans;

    if (selUse->cu_flags & CU_LOCKED) return 0;

    newUse = DBCellNewUse(selUse->cu_def, (char *) realUse->cu_id);
    if (!DBLinkCell(newUse, Select2Def))
    {
	freeMagic((char *) newUse->cu_id);
	newUse->cu_id = NULL;
	(void) DBLinkCell(newUse, Select2Def);
    }
    GeoTransTrans(&selUse->cu_transform, transform, &newTrans);
    DBSetArray(selUse, newUse);
    DBSetTrans(newUse, &newTrans);
    newUse->cu_expandMask = selUse->cu_expandMask;
    newUse->cu_flags = selUse->cu_flags;
    DBPlaceCell(newUse, Select2Def);

    return 0;
}

/* Search function to copy labels.  Return 0 always to avoid
 * aborting search.
 */

    /* ARGSUSED */
int
selTransLabelFunc(label, cellUse, defTransform, transform)
    Label *label;		/* Label to copy.  This points to label
				 * in cellDef.
				 */
    CellUse *cellUse;		/* (unused) */
    Transform *defTransform;	/* Transform from cellDef to root. */
    Transform *transform;	/* How to modify coords before copying to
				 * Select2Def.
				 */
{
    Rect rootArea, finalArea;
    int rootJust, finalJust;
    Point rootOffset, finalOffset;
    int rootRotate, finalRotate;

    GeoTransRect(defTransform, &label->lab_rect, &rootArea);
    rootJust = GeoTransPos(defTransform, label->lab_just);
    GeoTransPointDelta(defTransform, &label->lab_offset, &rootOffset);
    rootRotate = GeoTransAngle(defTransform, label->lab_rotate);

    GeoTransRect(transform, &rootArea, &finalArea);
    finalJust = GeoTransPos(transform, rootJust);
    GeoTransPointDelta(transform, &rootOffset, &finalOffset);
    finalRotate = GeoTransAngle(transform, rootRotate);

    (void) DBPutFontLabel(Select2Def, &finalArea, label->lab_font,
	    label->lab_size, finalRotate, &finalOffset, finalJust,
	    label->lab_text, label->lab_type, label->lab_flags,
	    label->lab_port);
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * SelectTransform --
 *
 * 	This procedure modifies the selection by transforming
 *	it geometrically.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The selection is modified and redisplayed.
 *
 * ----------------------------------------------------------------------------
 */

void
SelectTransform(transform)
    Transform *transform;		/* How to displace the selection.
					 * The transform is in root (user-
					 * visible) coordinates.
					 */
{
    if (EditCellUse == NULL)
    {
	TxError("The current cell is not editable.\n");
	return;
    }

    /* Copy from SelectDef to Select2Def, transforming along the way. */

    selTransTo2(transform);

    /* Now just delete the selection and recreate it from Select2Def,
     * copying into the edit cell along the way.
     */

    SelectDelete("modified", TRUE);
    SelectAndCopy2(EditRootDef);
}

/* Client data used by SelectExpand */

typedef struct selExpData {
    int sed_mask;	/* window mask */
    int sed_type;	/* DB_EXPAND, etc. */
    Rect *sed_box;	/* selection box if used, or NULL */
} SelExpData;

/*
 * ----------------------------------------------------------------------------
 *
 * SelectExpand --
 *
 * 	Expand or unexpand all of the selected cells according to
 *	expandType.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The contents of the selected cells will become visible or
 *	invisible on the display in the indicated window(s).
 *	Both the cell in the layout and the selection are updated
 *	so that they are synchonized with respect to the state of
 *	visibility.
 *
 * ----------------------------------------------------------------------------
 */

void
SelectExpand(mask, expandType, rootBox)
    int mask;			/* Bits of this word indicate which
				 * windows the selected cells will be
				 * expanded in.
				 */
    int expandType;		/* Operation to perform:  Expand,
				 * unexpand, or expand toggle.
				 */
    Rect *rootBox;		/* Area of root box, if selecting by
				 * cursor box area.
				 */
{
    /* Forward references */
    extern int selExpandFunc();

    SelExpData sed;

    sed.sed_type = expandType;
    sed.sed_box = rootBox;
    sed.sed_mask = mask;

    if (rootBox != NULL)
    {
	SearchContext scx;

	scx.scx_use = SelectUse;
	scx.scx_area = *rootBox;
	scx.scx_trans = GeoIdentityTransform;

	SelEnumCells(FALSE, (bool *) NULL, &scx, selExpandFunc, &sed);
	return;
    }

    SelEnumCells(FALSE, (bool *) NULL, (SearchContext *)NULL,
	    	selExpandFunc, &sed);
}

    /* ARGSUSED */
int
selExpandFunc(selUse, use, transform, sed)
    CellUse *selUse;		/* Use from selection. */
    CellUse *use;		/* Use to expand (in actual layout). */
    Transform *transform;	/* Not used. */
    SelExpData *sed;		/* Information for expansion */
{
    int expandType = sed->sed_type;
    int mask = sed->sed_mask;
    Rect *rootBox = sed->sed_box;

    /* If a rootBox is provided, then we are here trying to sync the
     * cells in the selection to those in the edit cell.  Need to
     * follow the policy of expandType:  If DB_EXPAND_SURROUND, then
     * selUse must be inside the rootBox.  If DB_EXPAND_OVERLAP, then
     * selUse must ovelap the rootBox.
     */
    if (rootBox != NULL)
    {
	if ((expandType & DB_EXPAND_SURROUND_MASK) == DB_EXPAND_OVERLAP)
	{
	    if (!GEO_OVERLAP(rootBox, &selUse->cu_bbox))
		return 0;
	}
	else /* (expandType & DB_EXPAND_SURROUND_MASK) == DB_EXPAND_SURROUND */
	{
	    if (!GEO_SURROUND(rootBox, &selUse->cu_bbox))
		return 0;
	}
    }

    /* If a rootBox was given, then the expansion is being done in the
     * edit cell, and the selection is being updated to reflect any
     * changes there, so only handle the selection.  Otherwise, we
     * have to sync the corresponding cells in the edit def here.
     */
    if ((expandType & DB_EXPAND_MASK) == DB_EXPAND)
    {
	/* Be sure to modify the expansion bit in the selection as well as
	 * the one in the layout in order to keep them consistent.
	 */
	DBExpand(selUse, mask, DB_EXPAND);
	if (!rootBox)
	{
	    DBExpand(use, mask, DB_EXPAND);
	    if (use->cu_parent == NULL)
		DBWAreaChanged(use->cu_def, &use->cu_bbox, mask, &DBAllButSpaceBits);
	    else 
		DBWAreaChanged(use->cu_parent, &use->cu_bbox, mask, &DBAllButSpaceBits);
	}
    }
    else if ((expandType & DB_EXPAND_MASK) == DB_UNEXPAND)
    {
	/* Don't change expansion status of root cell:  screws up
	 * DBWAreaChanged (need to always have at least top-level
	 * cell be expanded).
	 */

	if ((use->cu_parent == NULL) && !rootBox)
	{
	    TxError("Can't unexpand root cell of window.\n");
	    return 0;
	}

	if (use->cu_parent != NULL)
	{
	    /* Be sure to modify the expansion bit in the selection as well as
	     * the one in the layout in order to keep them consistent.
	     */
	    DBExpand(selUse, mask, DB_UNEXPAND);
	    if (!rootBox)
	    {
		DBExpand(use, mask, DB_UNEXPAND);
		DBWAreaChanged(use->cu_parent, &use->cu_bbox, mask,
			(TileTypeBitMask *) NULL);
	    }
	}
    }
    else	/* (expandType & DB_EXPAND_MASK) == DB_EXPAND_TOGGLE */
    {
	/* Don't change expansion status of root cell:  screws up
	 * DBWAreaChanged (need to always have at least top-level
	 * cell be expanded).
	 */

	if (use->cu_parent == NULL)
	{
	    if (!rootBox) TxError("Can't unexpand root cell of window.\n");
	    return 0;
	}

	/* Be sure to modify the expansion bit in the selection as well as
	 * the one in the layout in order to keep them consistent.
	 */

	if (DBDescendSubcell(selUse, mask))
	{
	    DBExpand(selUse, mask, DB_UNEXPAND);
	    if (!rootBox)
	    {
		DBExpand(use, mask, DB_UNEXPAND);
		DBWAreaChanged(use->cu_parent, &use->cu_bbox, mask,
			(TileTypeBitMask *) NULL);
	    }
	}
	else
	{
	    DBExpand(selUse, mask, DB_EXPAND);
	    if (!rootBox)
	    {
		DBExpand(use, mask, DB_EXPAND);
		DBWAreaChanged(use->cu_parent, &use->cu_bbox, mask, &DBAllButSpaceBits);
	    }
	}
    }
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * SelectArray --
 *
 * 	Array everything in the selection.  Cells get turned into
 *	arrays, and paint and labels get replicated.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The edit cell is modified in a big way.  It's also redisplayed.
 *
 * ----------------------------------------------------------------------------
 */

void
SelectArray(arrayInfo)
    ArrayInfo *arrayInfo;	/* Describes desired shape of array, all in
				 * root coordinates.
				 */
{
    extern int selArrayPFunc(), selArrayCFunc(), selArrayLFunc();

    /* The way arraying is done is similar to moving:  make an
     * arrayed copy of everything in Select2Def, then delete the
     * selection, then copy everything back from Select2Def and
     * select it.
     */

    UndoDisable();
    DBCellClearDef(Select2Def);
    (void) SelEnumPaint(&DBAllButSpaceAndDRCBits, TRUE, (bool *) NULL,
	    selArrayPFunc, (ClientData) arrayInfo);
    (void) SelEnumCells(TRUE, (bool *) NULL, (SearchContext *) NULL,
	    selArrayCFunc, (ClientData) arrayInfo);
    (void) SelEnumLabels(&DBAllTypeBits, TRUE, (bool *) NULL,
	    selArrayLFunc, (ClientData) arrayInfo);
    DBReComputeBbox(Select2Def);
    UndoEnable();

    /* Now just delete the selection and recreate it from Select2Def,
     * copying into the edit cell along the way.
     */

    SelectDelete("arrayed", TRUE);
    SelectAndCopy2(EditRootDef);
}

/* Search function for paint.  Just make many copies of the paint
 * into Select2Def.  Always return 0 to keep the search alive.
 */

int
selArrayPFunc(rect, type, arrayInfo)
    Rect *rect;			/* Rectangle to be arrayed. */
    TileType type;		/* Type of tile. */
    ArrayInfo *arrayInfo;	/* How to array. */
{
    int y, nx, ny;
    Rect current;

    nx = arrayInfo->ar_xhi - arrayInfo->ar_xlo;
    if (nx < 0) nx = -nx;
    ny = arrayInfo->ar_yhi - arrayInfo->ar_ylo;
    if (ny < 0) ny = -ny;

    current = *rect;
    for ( ; nx >= 0; nx -= 1)
    {
	current.r_ybot = rect->r_ybot;
	current.r_ytop = rect->r_ytop;
	for (y = ny; y >= 0; y -= 1)
	{
	    DBPaint(Select2Def, &current, type);
	    current.r_ybot += arrayInfo->ar_ysep;
	    current.r_ytop += arrayInfo->ar_ysep;
	}
	current.r_xbot += arrayInfo->ar_xsep;
	current.r_xtop += arrayInfo->ar_xsep;
    }
    return 0;
}

/* Search function for cells.  Just make an arrayed copy of
 * each subcell found.
 */

    /* ARGSUSED */
int
selArrayCFunc(selUse, use, transform, arrayInfo)
    CellUse *selUse;		/* Use from selection (not used). */
    CellUse *use;		/* Use to be copied and arrayed. */
    Transform *transform;	/* Transform from use->cu_def to root. */
    ArrayInfo *arrayInfo;	/* Array characteristics desired. */
{
    CellUse *newUse;
    Transform tinv, newTrans;
    Rect tmp, oldBbox;

    /* When creating a new use, try to re-use the id from the old
     * one.  Only create a new one if the old id can't be used.
     */

    newUse = DBCellNewUse(use->cu_def, (char *) use->cu_id);
    if (!DBLinkCell(newUse, Select2Def))
    {
	freeMagic((char *) newUse->cu_id);
	newUse->cu_id = NULL;
	(void) DBLinkCell(newUse, Select2Def);
    }
    newUse->cu_expandMask = use->cu_expandMask;
    newUse->cu_flags = use->cu_flags;

    DBSetTrans(newUse, transform);
    GeoInvertTrans(transform, &tinv);
    DBMakeArray(newUse, &tinv, arrayInfo->ar_xlo,
	arrayInfo->ar_ylo, arrayInfo->ar_xhi, arrayInfo->ar_yhi,
	arrayInfo->ar_xsep, arrayInfo->ar_ysep);

    /* Set the array's transform so that its lower-left corner is in
     * the same place that it used to be.
     */

    GeoInvertTrans(&use->cu_transform, &tinv);
    GeoTransRect(&tinv, &use->cu_bbox, &tmp);
    GeoTransRect(transform, &tmp, &oldBbox);
    GeoTranslateTrans(&newUse->cu_transform,
	    oldBbox.r_xbot - newUse->cu_bbox.r_xbot,
	    oldBbox.r_ybot - newUse->cu_bbox.r_ybot,
	    &newTrans);
    DBSetTrans(newUse, &newTrans);

    if (DBCellFindDup(newUse, Select2Def) != NULL)
    {
	DBUnLinkCell(newUse, Select2Def);
	(void) DBCellDeleteUse(newUse);
    }
    else DBPlaceCell(newUse, Select2Def);

    return 0;
}

/* Search function for labels.  Similar to paint search function. */
/* modified by harry eaton to increment numbers in array labels */

    /* ARGSUSED */
int
selArrayLFunc(label, use, transform, arrayInfo)
    Label *label;		/* Label to be copied and replicated. */
    CellUse *use;		/* (unused) */
    Transform *transform;	/* Transform from coords of def to root. */
    ArrayInfo *arrayInfo;	/* How to replicate. */
{
    int y, nx, ny, rootJust, rootRotate;
    Point rootOffset;
    Rect original, current;
    char *astr;
    int labx, laby, xi, yi, only1;	/* numeric parts of label */

    char *nmPutNums();			/* Forward reference */

    nx = arrayInfo->ar_xhi - arrayInfo->ar_xlo;
    if (nx < 0) nx = -nx;
    ny = arrayInfo->ar_yhi - arrayInfo->ar_ylo;
    if (ny < 0) ny = -ny;

    GeoTransRect(transform, &label->lab_rect, &original);
    rootJust = GeoTransPos(transform, label->lab_just);
    rootRotate = GeoTransAngle(transform, label->lab_rotate);
    GeoTransPointDelta(transform, &label->lab_offset, &rootOffset);
    current = original;

    /* get the existing numbers in the label */
    nmGetNums(label->lab_text, &labx, &laby);

    xi = yi = 0;
    if (nx > 0 && ny > 0)
	only1 = 0;
    else
	only1 = 1;
    for ( ; nx >= 0; nx -= 1)
    {
	current.r_ybot = original.r_ybot;
	current.r_ytop = original.r_ytop;
	for (y = ny; y >= 0; y -= 1)
	{
	    /* Eliminate any duplicate labels.  Don't use type in comparing
	     * for duplicates, because the selection's type could change
	     * after it gets added to the edit cell.  Any label with
	     * the same text and position is considered a duplicate.
	     */
	    yi = ny - y;
	    astr = nmPutNums(label->lab_text, labx + xi, laby + yi);
	    DBEraseLabelsByContent(Select2Def, &current, -1, astr);
	    DBPutFontLabel(Select2Def, &current, label->lab_font,
			label->lab_size, rootRotate, &rootOffset,
			rootJust, astr, label->lab_type,
			label->lab_flags, label->lab_port);
	    current.r_ybot += arrayInfo->ar_ysep;
	    current.r_ytop += arrayInfo->ar_ysep;
	    xi += only1;
	}
	xi += (1 - only1);
	current.r_xbot += arrayInfo->ar_xsep;
	current.r_xtop += arrayInfo->ar_xsep;
    }
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *	SelectStretch --
 *
 * 	Move the selection a given amount in x (or y).  While moving,
 *	erase everything that the selection passes over, and stretch
 *	material behind the selection.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The edit cell is modified.  The selection is also modified
 *	and redisplayed.
 * ----------------------------------------------------------------------------
 */

void
SelectStretch(x, y)
    int x;			/* Amount to move in the x-direction. */
    int y;			/* Amount to move in the y-direction.  Must
				 * be zero if x is non-zero.
				 */
{
    Transform transform;
    int plane;
    Rect modifiedArea, editModified;
    extern int selStretchEraseFunc(), selStretchFillFunc();
				/* Forward declarations. */

    if ((x == 0) && (y == 0)) return;

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

    /* First of all, copy from SelectDef to Select2Def, moving the
     * selection along the way.
     */

    GeoTranslateTrans(&GeoIdentityTransform, x, y, &transform);
    selTransTo2(&transform);

    /* We're going to modify not just the old selection area or the new
     * one, but everything in-between too.  Remember this and tell the
     * displayer and DRC about it later.
     */

    modifiedArea = Select2Def->cd_extended;
    (void) GeoInclude(&SelectDef->cd_extended, &modifiedArea);
    GeoTransRect(&RootToEditTransform, &modifiedArea, &editModified);

    /* Delete the selection itself. */

    SelectDelete("stretched", TRUE);

    /* Next, delete all the material in front of each piece of paint in
     * the selection.
     */

    selStretchX = x;
    selStretchY = y;
    for (plane = PL_SELECTBASE; plane < DBNumPlanes; plane++)
    {
	(void) DBSrPaintArea((Tile *) NULL, Select2Def->cd_planes[plane],
		&TiPlaneRect, &DBAllButSpaceAndDRCBits, selStretchEraseFunc,
		(ClientData) &plane);
    }

    /* To achieve the stretch affect, fill in material behind the selection
     * everywhere that it used to touch other material in the edit cell.
     * This code first builds up a list of areas to paint, then paints them
     * (can't paint as we go because the new paint interacts with the
     * computation of what to stretch).
     */

    selStretchList = NULL;
    for (plane = PL_SELECTBASE; plane < DBNumPlanes; plane++)
    {
	(void) DBSrPaintArea((Tile *) NULL, Select2Def->cd_planes[plane],
		&TiPlaneRect, &DBAllButSpaceAndDRCBits, selStretchFillFunc,
		(ClientData) &plane);
    }

    /* Paint back the areas in the list. */

    while (selStretchList != NULL)
    {
	TileTypeBitMask tmask;
	TileType type = selStretchList->sa_type;

	TileType tloc;
	tloc = (type & TT_DIAGONAL) ?  ((type & TT_SIDE) ?
		(type & TT_RIGHTMASK) >> 14 : (type & TT_LEFTMASK)) : type;
	TTMaskSetOnlyType(&tmask, tloc);

	DBPaintValid(EditCellUse->cu_def, &selStretchList->sa_area, &tmask, type);
	free_magic1_t mm1 = freeMagic1_init();
	freeMagic1(&mm1, (char *) selStretchList);
	selStretchList = selStretchList->sa_next;
	freeMagic1_end(&mm1);
    }

    /* Paint the new translated selection back into the edit cell,
     * select it again, and tell DRC and display about what we
     * changed.
     */

    SelectAndCopy2(EditRootDef);
    DBWAreaChanged(EditCellUse->cu_def, &editModified, DBW_ALLWINDOWS,
	(TileTypeBitMask *) NULL);
    DRCCheckThis(EditCellUse->cu_def, TT_CHECKPAINT, &editModified);
}

/*
 * ----------------------------------------------------------------------------
 *	selStretchEraseFunc --
 *
 * 	Called by DBSrPaintArea during stretching for each tile in the
 *	new selection.  Erase the area that the tile swept out as it
 *	moved.
 *
 * Results:
 *	Always returns 0 to keep the search alive.
 *
 * Side effects:
 *	The edit cell is modified.
 * ----------------------------------------------------------------------------
 */

int
selStretchEraseFunc(tile, dinfo, plane)
    Tile *tile;			/* Tile being moved in a stretch operation. */
    TileType dinfo;		/* Split tile information */
    int *plane;			/* Plane of tiles being searched */
{
    Rect area, editArea;
    int planeNum;
    planeAndArea pa;
    TileType type, t;
    TileTypeBitMask tmpmask, mask, *residueMask;
    PaintUndoInfo ui;
    PaintResultType selStretchEraseTbl[NT];
    extern int selStretchEraseFunc2();

    if (IsSplit(tile))
	type = (dinfo & TT_SIDE) ? SplitRightType(tile) :
			SplitLeftType(tile);
    else
	type = TiGetType(tile);

    TiToRect(tile, &area);

    /* Compute the area that this tile swept out (the current area is
     * its location AFTER moving), and erase everything that was in
     * its path.
     */

    if (selStretchX > 0)
	area.r_xbot -= selStretchX;
    else area.r_xtop -= selStretchX;
    if (selStretchY > 0)
	area.r_ybot -= selStretchY;
    else area.r_ytop -= selStretchY;

    /* Translate into edit coords and erase all material on the
     * tile's plane.
     */

    GeoTransRect(&RootToEditTransform, &area, &editArea);

    /* We need to erase all types that interact with "type", *not* all	*/
    /* types on "plane", due to the way stacked contacts are handled.	*/
    /* Contacts on different planes may stretch across one another	*/
    /* without effect, if the types are stackable.  Likewise, layer	*/
    /* types may stretch across contacts for which they are a residue.	*/

    planeNum = *plane;
    mask = DBPlaneTypes[planeNum];
    if (DBIsContact(type))
    {
	for (t = DBNumUserLayers; t < DBNumTypes; t++)
	{
	    if (t == type) continue;

	    /* Exclude all contact types for which this type should "pass through" */

	    if (TTMaskHasType(&mask, t))
	    {
		tmpmask = *DBResidueMask(t);
		if (TTMaskHasType(&tmpmask, type))
		{
		    TTMaskClearType(&tmpmask, type);
		    TTMaskClearMask(&mask, &tmpmask);
		}
	    }
	}
    }

    /* For stacked type tiles do not erase common residue
     * type of both contacts from layout.
     */

    if (type >= DBNumUserLayers)
    {
        t = (DBPlaneToResidue(type, planeNum));
        TTMaskClearType(&mask, t);
    }

    TTMaskAndMask(&mask, &DBActiveLayerBits);

    /* Erase all tile types specified in the processed mask.
     * Erase contacts in mask first using DBErase.  This prevents
     * contacts from leaving partial images of themselves on other
     * planes.  Then, remove the non-contact layers on this plane
     * only, such that contacts are not disturbed.
     */

    TTMaskZero(&tmpmask);

    selStretchEraseTbl[TT_SPACE] = (PaintResultType)TT_SPACE;
    for (t = TT_SPACE + 1; t < DBNumUserLayers; t++)
    {
	selStretchEraseTbl[t] = (PaintResultType)t;
	if (TTMaskHasType(&mask, t))
	{
	    if (DBIsContact(t))
	    {
		if (t == type)
		    DBErase(EditCellUse->cu_def, &editArea, t);
		else
		    TTMaskSetType(&tmpmask, t);
	    }
	    else
		selStretchEraseTbl[t] = (PaintResultType)TT_SPACE;
	}
    }
    for (; t < DBNumTypes; t++)
	selStretchEraseTbl[t] = (PaintResultType)t;

    /* Remove conflicting contact types, where they exist */

    pa.pa_area = &editArea;
    pa.pa_plane = planeNum;
    pa.pa_mask = &tmpmask;
    TTMaskZero(&pa.pa_rmask);

    /* Find all the types that need to be erased inside editArea and
     * generate a mask from them.  Then erase each type.
     */
    DBSrPaintArea((Tile *)NULL, EditCellUse->cu_def->cd_planes[planeNum],
		&editArea, &tmpmask, selStretchEraseFunc2, (ClientData)&pa);

    for (t = TT_TECHDEPBASE; t < DBNumTypes; t++)
	if (TTMaskHasType(&pa.pa_rmask, t))
	    DBErase(EditCellUse->cu_def, &editArea, t);

    ui.pu_pNum = planeNum;
    ui.pu_def = EditCellUse->cu_def;
    DBPaintPlane(EditCellUse->cu_def->cd_planes[planeNum], &editArea,
		selStretchEraseTbl, &ui);

    return 0;
}

int
selStretchEraseFunc2(tile, dinfo, pa)
    Tile *tile;
    TileType dinfo;		/* (unused) */
    planeAndArea *pa;
{
    TileType type = TT_SPACE;

    if (IsSplit(tile))
    {
	if (TTMaskHasType(pa->pa_mask, TiGetLeftType(tile)))
	    type = DBPlaneToResidue(TiGetLeftType(tile), pa->pa_plane);
	if (TTMaskHasType(pa->pa_mask, TiGetRightType(tile)))
	    type = DBPlaneToResidue(TiGetRightType(tile), pa->pa_plane);
    }
    else
	type = DBPlaneToResidue(TiGetType(tile), pa->pa_plane);

    if (type != TT_SPACE)
	TTMaskSetType(&pa->pa_rmask, type);

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *	selStretchFillFunc --
 *
 * 	This function is invoked during stretching for each paint tile in
 *	the (new) selection.  It finds places in where the back-side of this
 *	tile borders space in the (new) selection, then looks for paint in
 *	the edit cell that borders the old location of the paint.  If the
 *	selection has been moved away from paint in the edit cell, additional
 *	material is filled in behind the selection.
 *
 * Results:
 *	Always returns 0 to keep the search alive.
 *
 * Side effects:
 *	Modifies the edit cell by painting material.
 * ----------------------------------------------------------------------------
 */

int
selStretchFillFunc(tile, dinfo, plane)
    Tile *tile;			/* Tile in the old selection. */
    TileType dinfo;		/* Split tile information (unused) */
    int *plane;			/* Plane of tile being searched */
{
    Rect area;
    extern int selStretchFillFunc2();

    TiToRect(tile, &area);

    /* Check the material just behind this paint (in the sense of the
     * stretch direction) for space in the selection and non-space in
     * the edit cell.
     */

    if (selStretchX > 0)
    {
	area.r_xtop = area.r_xbot;
	area.r_xbot -= 1;
    }
    else if (selStretchX < 0)
    {
	area.r_xbot = area.r_xtop;
	area.r_xtop += 1;
    }
    else if (selStretchY > 0)
    {
	area.r_ytop = area.r_ybot;
	area.r_ybot -= 1;
    }
    else
    {
	area.r_ybot = area.r_ytop;
	area.r_ytop += 1;
    }

    if (IsSplit(tile))
    {
	if (selStretchX > 0)
	    selStretchType = SplitLeftType(tile);
	else if (selStretchX < 0)
	    selStretchType = SplitRightType(tile);
	else if (selStretchY > 0)
	    selStretchType = (SplitDirection(tile) ? SplitLeftType(tile) :
			SplitRightType(tile));
	else if (selStretchY < 0)
	    selStretchType = (SplitDirection(tile) ? SplitRightType(tile) :
			SplitLeftType(tile));
	if (selStretchType == TT_SPACE) return 0;
    }
    else
	selStretchType = TiGetType(tile);

    /* The search functions invoked indirectly by the following procedure
     * call build up a list of areas to paint.
     */

    (void) DBSrPaintArea((Tile *) NULL,
	    Select2Def->cd_planes[*plane], &area,
	    &DBSpaceBits, selStretchFillFunc2, (ClientData) &area);

    return 0;
}

/* Second-level filling search function:  find all of the edit material
 * that intersects areas where space borders a selected paint tile.
 */

int
selStretchFillFunc2(tile, dinfo, area)
    Tile *tile;				/* Space tile that borders selected
					 * paint.
					 */
    TileType dinfo;			/* Split tile information (unused) */
    Rect *area;				/* A one-unit wide strip along the
					 * border (i.e. the area in which
					 * we're interested in space).
					 */
{
    Rect spaceArea, editArea;
    int pNum;

    extern int selStretchFillFunc3();

    TiToRect(tile, &spaceArea);

    /* Find out which portion of this space tile borders the selected
     * tile, transform it back to coords of the old selection and then
     * to edit coords, and find all the edit material that borders the
     * selected tile in this area.
     */

    GeoClip(&spaceArea, area);
    spaceArea.r_xbot -= selStretchX;
    spaceArea.r_xtop -= selStretchX;
    spaceArea.r_ybot -= selStretchY;
    spaceArea.r_ytop -= selStretchY;
    GeoTransRect(&RootToEditTransform, &spaceArea, &editArea);

    for (pNum = PL_PAINTBASE; pNum < DBNumPlanes; pNum++)
    {
	if (DBTypeOnPlane(selStretchType, pNum))
	    (void) DBSrPaintArea((Tile *) NULL,
			EditCellUse->cu_def->cd_planes[pNum],
			&editArea, &DBActiveLayerBits, selStretchFillFunc3,
			(ClientData) &spaceArea);
    }
    return 0;
}

/* OK, now we've found a piece of material in the edit cell that is
 * right next to a piece of selected material that's about to move
 * away from it.  Stretch one or the other to fill the gap.  Use the
 * material that's moving as the stretch material unless it's a fixed-size
 * material and the other stuff is stretchable.
 */

int
selStretchFillFunc3(tile, dinfo, area)
    Tile *tile;			/* Tile of edit material that's about to
				 * be left behind selection.
				 */
    TileType dinfo;		/* Split tile information (unused) */
    Rect *area;			/* The border area we're interested in,
				 * in root coords.
				 */
{
    Rect editArea, rootArea;
    TileType type, stype;
    TileTypeBitMask *mask, *sMask, *tMask;
    StretchArea *sa;

    /* Compute the area to be painted. */

    TiToRect(tile, &editArea);
    GeoTransRect(&EditToRootTransform, &editArea, &rootArea);
    GeoClip(&rootArea, area);
    if (selStretchX > 0)
    {
	rootArea.r_xbot = rootArea.r_xtop;
	rootArea.r_xtop += selStretchX;
    }
    else if (selStretchX < 0)
    {
	rootArea.r_xtop = rootArea.r_xbot;
	rootArea.r_xbot += selStretchX;
    }
    else if (selStretchY > 0)
    {
	rootArea.r_ybot = rootArea.r_ytop;
	rootArea.r_ytop += selStretchY;
    }
    else
    {
	rootArea.r_ytop = rootArea.r_ybot;
	rootArea.r_ybot += selStretchY;
    }
    GeoTransRect(&RootToEditTransform, &rootArea, &editArea);

    /* Compute the material to be painted.  Be careful:  for contacts,
     * must use the master image.
     */

    if (IsSplit(tile))
    {
	if (selStretchX > 0)
	    type = SplitRightType(tile);
	else if (selStretchX < 0)
	    type = SplitLeftType(tile);
	else if (selStretchY > 0)
	    type = (SplitDirection(tile) ? SplitRightType(tile) :
			SplitLeftType(tile));
	else if (selStretchY < 0)
	    type = (SplitDirection(tile) ? SplitLeftType(tile) :
			SplitRightType(tile));
	else
	    type = TT_SPACE;
	if (type == TT_SPACE) return 0;   /* nothing to stretch */
    }
    else
	type = TiGetType(tile);
    sMask = DBResidueMask(selStretchType);

    /* If have type and stretch type are both contacts (fixed types)	*/
    /* and there exists a stacking contact type between them, then	*/
    /* we should be filling in the original type.			*/

    if (!DBIsContact(type) || !DBIsContact(selStretchType) ||
		(((stype = DBTechFindStacking(type, selStretchType))
		< DBNumUserLayers) && (stype >= TT_TECHDEPBASE)))
    {
	/* If type is a residue of selStretchType then we always 	*/
	/* fill with type.  Otherwise, we check the list of plow	*/
	/* types to see what is and isn't fixed.			*/

	if (!TTMaskHasType(sMask, type))
	    if (TTMaskHasType(&PlowFixedTypes, type)
			|| !TTMaskHasType(&PlowFixedTypes, selStretchType))
		type = selStretchType;
    }

    /* Otherwise, if we have two fixed types, and they both border the	*/
    /* selection area (as opposed to overlapping), and they share a	*/
    /* residue, then the type to paint is the shared residue type.	*/
    /* We must ignore the case type == selStretchType (fixed by Nishit,	*/
    /* 6/29/04.								*/

    else if (DBIsContact(type) && DBIsContact(selStretchType)
		&& (type != selStretchType))
    {
	if (((selStretchX < 0) && (editArea.r_xtop == area->r_xbot)) ||
	    ((selStretchX > 0) && (editArea.r_xbot == area->r_xtop)) ||
	    ((selStretchY < 0) && (editArea.r_ytop == area->r_ybot)) ||
	    ((selStretchY > 0) && (editArea.r_ybot == area->r_ytop)))
	{
	    TileTypeBitMask rmask;
	    tMask = DBResidueMask(type);
	    TTMaskAndMask3(&rmask, tMask, sMask);
	    for (type = TT_TECHDEPBASE; type < DBNumUserLayers; type++)
		if (TTMaskHasType(&rmask, type))
		    break;

	    if (type == DBNumUserLayers)
		return 0;	/* Types do not share any residues */
	}
    }

    /* Save around the area we just found. */

    sa = (StretchArea *) mallocMagic(sizeof(StretchArea));
    sa->sa_area = editArea;
    sa->sa_type = type;
    sa->sa_next = selStretchList;
    selStretchList = sa;

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * SelectDump --
 *
 *      Copies an area of one cell into the edit cell, selecting the
 *	copy so that it can be manipulated later.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      The edit cell is modified.
 *
 * ----------------------------------------------------------------------------
 */

void
SelectDump(scx)
    SearchContext *scx;			/* Describes the cell from which
					 * material is to be copied, the
					 * area to copy, and the transform
					 * to root coordinates in the edit
					 * cell's window.
					 */
{
    /* Copy from the source cell to Select2Def while transforming,
     * then let SelectandCopy2 do the rest of the work.  Don't
     * record any of the Select2Def changes for undo-ing.
     */

    UndoDisable();
    DBCellClearDef(Select2Def);
    (void) DBCellCopyAllPaint(scx, &DBAllButSpaceAndDRCBits, CU_DESCEND_SPECIAL,
		Select2Use);
    (void) DBCellCopyAllLabels(scx, &DBAllTypeBits, CU_DESCEND_SPECIAL, Select2Use,
		(Rect *) NULL);
    (void) DBCellCopyAllCells(scx, CU_DESCEND_SPECIAL, Select2Use, (Rect *) NULL);
    DBReComputeBbox(Select2Def);
    UndoEnable();

    SelectClear();
    SelectAndCopy2(EditRootDef);
}
