/*
 * DBWtools.c --
 *
 * Implements tools for Magic.  Two tools are
 * provided:  a box and a point.  This module is a sort of
 * broker between the low-level display routines and the
 * high-level command routines.  It provides two kinds of
 * routines:  those invoked by the screen handler to change
 * the tool location, and those invoked by the command interpreter
 * to get information about the current tool location and relocate
 * the box.
 *
 *     *********************************************************************
 *     * 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/dbwind/DBWtools.c,v 1.1.1.1 2008/02/03 20:43:50 tim Exp $";
#endif  /* not lint */

#include <stdio.h>
#include "utils/magic.h"
#include "utils/geometry.h"
#include "utils/styles.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 "textio/textio.h"
#include "utils/main.h"
#include "textio/txcommands.h"

#define WINDOW_DEF(w)	(((CellUse *)(w->w_surfaceID))->cu_def)

/* The following rectangle defines the current box location.
 * The box is defined in terms of a particular root cell def
 * and an area within that def.  Note:  there is a notion
 * of "compatibility" between windows, as far as tools are
 * concerned.  Two windows are compatible if they have the same
 * root cell def.  If the box is placed in one window, it will
 * be displayed in all compatible windows.  The box may not have
 * one corner placed in one window and other corners in
 * incompatible windows.
 */

static CellDef *boxRootDef = NULL;	/* CellDef for the box */
static Rect boxRootArea;		/* Root def coords */

typedef struct _crosshairRec {
    Point    pos;	/* Crosshair position */
    CellDef *def;	/* CellDef that crosshair position references */
} CrosshairRec;

static CrosshairRec curCrosshair;		/* Crosshair position */

/*
 * If the following is DBW_UNITS_USER, the box gets snapped to the user's
 * grid always, instead of snapping to the usual 1x1 grid.  If the value
 * is DBW_UNITS_INTERNAL, the box gets snapped to the internal grid.
 */
int DBWSnapToGrid = DBW_UNITS_LAMBDA;

/*
 * The original behavior with respect to units was that un-suffixed
 * values follow whatever the snap setting is (DBWSnapToGrid, above).
 * Current behavior is that the original behavior is followed while
 * DBWUnits is set to DBW_UNITS_DEFAULT.  However, if the "units"
 * command is given, then displayed and entered units follow that
 * value independently of the snap setting.
 */
int DBWUnits = DBW_UNITS_DEFAULT;

/* Forward reference: */

extern int DBWToolDraw();


/*
 * ----------------------------------------------------------------------------
 *
 *	toolFindPoint --
 *
 * 	Returns the point in root coordinates.
 *	If DBWSnapToGrid is DBW_UNITS_USER, pick the nearest point that is
 *	aligned with the window's grid.  If DBWSnapToGrid is DBW_UNITS_LAMBDA,
 *	pick the nearest point that is an integer lambda value.
 *
 * Results:
 *	The return value is a pointer to the window containing the
 *	point, or NULL if no window contains the point.
 *
 * Side effects:
 *	The parameters rootPoint and rootArea are modified
 *	to contain information about the point's location.  RootPoint
 *	is the nearest lambda grid point to the point's actual
 *	location, while rootArea is a one-lambda-square box surrounding
 *	the point.  If rootPoint or rootArea is NULL, then that structure
 *	isn't filled in.
 * ----------------------------------------------------------------------------
 */

MagWindow *
toolFindPoint(p, rootPoint, rootArea)
    Point *p;			/* The point to find, in the current window. */
    Point *rootPoint;		/* Modified to contain coordinates of point
				 * in root cell coordinates.  Is unchanged
				 * if NULL is returned.
				 */
    Rect *rootArea;		/* Modified to contain box around point.  Is
				 * unchanged when NULL is returned.
				 */
{
    extern MagWindow *WindCurrentWindow;

    if (WindCurrentWindow == NULL) return NULL;

    if (WindCurrentWindow->w_client != DBWclientID) return NULL;

    if (!GEO_ENCLOSE(p, &WindCurrentWindow->w_screenArea)) return NULL;

    WindPointToSurface(WindCurrentWindow, p, rootPoint, rootArea);
    if (DBWSnapToGrid != DBW_UNITS_INTERNAL)
	ToolSnapToGrid(WindCurrentWindow, rootPoint, rootArea);
    return WindCurrentWindow;

}


/*
 * ----------------------------------------------------------------------------
 *	ToolGetPoint --
 *
 * 	Returns information about the point.  Used by command processors.
 *
 * Results:
 *	The return value is a pointer to the window containing the
 *	point, or NULL if no window contains the point.
 *
 * Side effects:
 *	The parameters rootPoint and rootArea are modified
 *	to contain information about the point's location.  RootPoint
 *	is the nearest lambda grid point to the point's actual
 *	location, while rootArea is a one-lambda-square box surrounding
 *	the point.  If rootPoint or rootArea is NULL, then that structure
 *	isn't filled in.
 * ----------------------------------------------------------------------------
 */

MagWindow *
ToolGetPoint(rootPoint, rootArea)
    Point *rootPoint;		/* Modified to contain coordinates of point
				 * in root cell coordinates.  Is unchanged
				 * if NULL is returned.
				 */
    Rect *rootArea;		/* Modified to contain box around point.  Is
				 * unchanged when NULL is returned.
				 */
{
    extern TxCommand *WindCurrentCmd;

    if (WindCurrentCmd == NULL)
	return NULL;
    else
	return toolFindPoint(&WindCurrentCmd->tx_p, rootPoint, rootArea);
}


/*
 * ----------------------------------------------------------------------------
 * ToolGetBox --
 *
 *	Returns the box CellDef and location in CellDef coords.
 *
 * Results:
 *	TRUE if the box exists.
 *
 * Side effects:
 *	The rootArea parameter is modified to contain the area
 *	of the box.  If rootArea is NULL, it is ignored.
 *	Same with rootDef.
 * ----------------------------------------------------------------------------
 */

bool
ToolGetBox(rootDef, rootArea)
    CellDef **rootDef;		/* Filled in with the root def of the box */
    Rect *rootArea;		/* Filled in with area of box.  Will be
				 * unchanged when NULL is returned.
				 */
{
    if (boxRootDef == NULL) return FALSE;
    if (rootDef != NULL)
	*rootDef = boxRootDef;
    if (rootArea != NULL)
	*rootArea = boxRootArea;
    return TRUE;
}

/* ToolScaleBox ---  Simple scaling of the root area box, no update needed */

void
ToolScaleBox(scalen, scaled)
    int scalen;
    int scaled;
{
    DBScalePoint(&boxRootArea.r_ll, scalen, scaled);
    DBScalePoint(&boxRootArea.r_ur, scalen, scaled);

}


/*
 * ----------------------------------------------------------------------------
 * ToolGetBoxWindow --
 *
 * 	Returns information about the current box location.  Used by
 *	command processing routines.
 *
 * Results:
 *	The return value is a pointer to a window containing the
 *	box, or NULL if the box doesn't exist in any window.  Note:
 *	the box may actually be in more than one window, so this
 *	isn't necessarily the only window containing the box.
 *
 * Side effects:
 *	The rootArea parameter is modified to contain the area
 *	of the box.  If rootArea is NULL, it is ignored.  The
 *	integer pointed to by pMask is modified to contain a
 *	mask of all windows containing the box (there may be more
 *	than one).  If pMask is NULL, it is ignored.
 * ----------------------------------------------------------------------------
 */

static int toolMask;		/* Shared between these two routines. */

MagWindow *
ToolGetBoxWindow(rootArea, pMask)
    Rect *rootArea;		/* Filled in with area of box.  Will be
				 * unchanged when NULL is returned.
				 */
    int *pMask;			/* Filled in with mask of all windows
				 * containing box.
				 */
{
    MagWindow *window;
    extern int toolWindowSave();

    /* Search through the windows and remember a window that has
     * the right root cell.  It's important to NOT search on the
     * area of the box (i.e. take any window with the box's root
     * definition, even if the box isn't visible in the window).
     * Otherwise, some commands won't work when the box goes
     * off-screen.  Also accumulate the mask bits.
     */

    toolMask = 0;
    window = NULL;
    if (boxRootDef != NULL)
	(void) WindSearch(DBWclientID, (ClientData) NULL, (Rect *) NULL,
	    toolWindowSave, (ClientData) &window);
    if ((window != NULL) && (rootArea != NULL)) *rootArea = boxRootArea;
    if (pMask != NULL) *pMask = toolMask;
    return window;
}

int
toolWindowSave(window, clientData)
    MagWindow *window;		/* Window that matched in some search. */
    ClientData clientData;	/* Contains the address of a location
				 * to be filled in with the window address.
				 */
{
    if (WINDOW_DEF(window) == boxRootDef)
    {
	*((MagWindow **) clientData) = window;
        toolMask |= ((DBWclientRec *) window->w_clientData)->dbw_bitmask;
    }
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * ToolGetEditBox --
 *
 *	Fill in the location of the box in edit cell coordinates.
 *
 * Results:
 *	TRUE if the box can indeed be put into edit cell coordinates.
 *	FALSE and an error message otherwise.
 *
 * Side effects:
 *	Sets *rect to be the coordinates of the box tool in edit cell
 *	coordinates, if TRUE was returned.
 *
 *	Prints an error message if the box is not found or the box
 *	is not in the edit cell coordinate system.
 *
 * ----------------------------------------------------------------------------
 */

bool
ToolGetEditBox(rect)
    Rect *rect;
{
    CellDef *editDef;

    if (boxRootDef == NULL)
    {
	TxError("Box must be present\n");
	return FALSE;
    }

    /* Added 8/4/2021---Don't require that EditRootDef be non-NULL. */

    if (EditRootDef == NULL)
    {
	MagWindow *w;
	w = ToolGetBoxWindow(rect, NULL);

	windCheckOnlyWindow(&w, DBWclientID);
	if (w == (MagWindow *) NULL)
	    editDef = EditCellUse->cu_def;
	else
	    editDef = ((CellUse *) w->w_surfaceID)->cu_def;
    }
    else
	editDef = EditRootDef;

    if (editDef != boxRootDef)
    {
	TxError("The box isn't in a window on the edit cell.\n");
	return FALSE;
    }
    if (rect != NULL)
	GeoTransRect(&RootToEditTransform, &boxRootArea, rect);
    return TRUE;
}


/*
 * ----------------------------------------------------------------------------
 *	ToolGetCorner --
 *
 * 	Returns the corner of the box closest to a given screen location.
 *
 * Results:
 *	An integer value is returned, indicating the corner closest to
 *	the given screen location.  The point must be in a window
 *	containing a root cell use, and the box must currently be in
 *	that window, or in another window with the same root cell use.
 *	If these conditions aren't satisfied, then the lower-left corner
 *	is returned.  Note:  "closeness" is determined not by screen
 *	closeness, but by closeness in root cell coordinates.
 *
 * Side effects:
 *	None.
 * ----------------------------------------------------------------------------
 */

int
ToolGetCorner(screenPoint)
    Point *screenPoint;

{
    Point p;
    MagWindow *w;
    Rect r;

    /* Make sure that the cursor is in a valid window. */

    w = toolFindPoint(screenPoint, &p, (Rect *) NULL);
    if (w == NULL) return TOOL_BL;
    if (WINDOW_DEF(w) != boxRootDef) return TOOL_BL;

    /* Find out which corner is closest.  Consider only the
     * intersection of the box with the window (otherwise it
     * may not be possible to select off-screen corners).
     */

    WindSurfaceToScreen(w, &boxRootArea, &r);
    GeoClip(&r, &w->w_screenArea);
    if (screenPoint->p_x < ((r.r_xbot + r.r_xtop)/2))
    {
	if (screenPoint->p_y < ((r.r_ybot + r.r_ytop)/2))
	    return TOOL_BL;
	else return TOOL_TL;
    }
    else
    {
	if (screenPoint->p_y < ((r.r_ybot + r.r_ytop)/2))
	    return TOOL_BR;
	else return TOOL_TR;
    }
}


/*
 * ----------------------------------------------------------------------------
 * dbwCrosshairInit --
 *
 *	Set the initial crosshair position to MINFINITY, where it will
 *	not display until explicitly set by a command.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Initializes static memory.
 *
 * ----------------------------------------------------------------------------
 */

void
dbwCrosshairInit()
{
    curCrosshair.pos.p_x = MINFINITY;
    curCrosshair.pos.p_y = MINFINITY;
    curCrosshair.def = (CellDef *)NULL;
    DBWHLAddClient(DBWDrawCrosshair);
}

/*
 * ----------------------------------------------------------------------------
 * dbwRecordCrosshair[X,Y]Pos --
 *
 * 	This procedure tells the highlight manager that the crosshair's
 *	current position needs to be erased or redisplayed.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Highlight redisplay information is logged.
 * ----------------------------------------------------------------------------
 */

void
dbwRecordCrosshairYPos(def, erase)
    CellDef *def;
    bool erase;			/* TRUE means crossair is being erased from its
				 * current position.  FALSE means the crosshair
				 * is being added at a new position.
				 */
{
    Rect xwire;

    xwire.r_xbot = MINFINITY;
    xwire.r_xtop = INFINITY;
    xwire.r_ybot = xwire.r_ytop = curCrosshair.pos.p_y;
    DBWHLRedraw(def, &xwire, erase);
}

void
dbwRecordCrosshairXPos(def, erase)
    CellDef *def;
    bool erase;			/* TRUE means crossair is being erased from its
				 * current position.  FALSE means the crosshair
				 * is being added at a new position.
				 */
{
    Rect xwire;

    xwire.r_ybot = MINFINITY;
    xwire.r_ytop = INFINITY;
    xwire.r_xbot = xwire.r_xtop = curCrosshair.pos.p_x;
    DBWHLRedraw(def, &xwire, erase);
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWDrawCrosshair --
 *
 * 	This procedure will redraw the crosshair in a given window, if
 *	the crosshair is to appear in that window.  It is called only by
 *	the highlight code.  The caller must lock the window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The crosshair is redrawn, unless it isn't supposed to appear.
 *
 * ----------------------------------------------------------------------------
 */

void
DBWDrawCrosshair(window, plane)
    MagWindow *window;		/* Window in which to redraw box. */
    Plane *plane;		/* Non-space tiles on this plane indicate
				 * which highlight areas must be redrawn.
				 */
{
    Point p;

    /* Unlike most highlights, which are related to the database and	*/
    /* therefore drawn on all windows, the crosshair only exists in	*/
    /* the cell where the pointer is active, and in any other window	*/
    /* with the same root cell.  It is therefore necessary to check	*/
    /* if the root def of the window matches the root def of the window	*/
    /* with active focus.  Otherwise, translating the crosshair		*/
    /* position to window coordinates makes no sense.			*/

    if (WINDOW_DEF(window) != curCrosshair.def) return;

    WindPointToScreen(window, &(curCrosshair.pos), &p);

    GrSetStuff(STYLE_YELLOW1);
    if (p.p_x > window->w_screenArea.r_xbot &&
		p.p_x < window->w_screenArea.r_xtop)
	GrClipLine(p.p_x, window->w_screenArea.r_ybot,
		   p.p_x, window->w_screenArea.r_ytop);

    if (p.p_y > window->w_screenArea.r_ybot &&
		p.p_y < window->w_screenArea.r_ytop)
	GrClipLine(window->w_screenArea.r_xbot, p.p_y,
		   window->w_screenArea.r_xtop, p.p_y);
}

/* DBWScaleCrosshair ---  Simple scaling of the crosshair point, no update needed */

void
DBWScaleCrosshair(scalen, scaled)
    int scalen;
    int scaled;
{
    DBScalePoint(&(curCrosshair.pos), scalen, scaled);
}

/*
 * ----------------------------------------------------------------------------
 *	DBWSetCrosshair --
 *
 * 	Change the location of the crosshair.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Information is recorded so that the crosshair will be redrawn.
 * ----------------------------------------------------------------------------
 */

void
DBWSetCrosshair(window, pos)
    MagWindow *window;
    Point *pos;			/* New crosshair location in coords of rootDef. */
{
    bool needUpdate = FALSE;

    if (WINDOW_DEF(window) != curCrosshair.def) needUpdate = TRUE;

    /* Erase */

    if (needUpdate || (curCrosshair.pos.p_x != pos->p_x))
    {
	/* Record the old and area of the vertical line for redisplay. */
	dbwRecordCrosshairXPos(curCrosshair.def, TRUE);
    }

    /* Do the same thing for the horizontal crosshair line */
    if (needUpdate || (curCrosshair.pos.p_y != pos->p_y))
    {
	dbwRecordCrosshairYPos(curCrosshair.def, TRUE);
    }

    /* Record the CellDef that this position is referenced to. */
    if (needUpdate) curCrosshair.def = WINDOW_DEF(window);

    /* Draw */

    if (curCrosshair.pos.p_x != pos->p_x)
    {
	/* Update the crosshair location. */
	curCrosshair.pos.p_x = pos->p_x;

	/* Record the new area for redisplay. */
	dbwRecordCrosshairXPos(curCrosshair.def, FALSE);
    }

    /* Do the same thing for the horizontal crosshair line */
    if (curCrosshair.pos.p_y != pos->p_y)
    {
	curCrosshair.pos.p_y = pos->p_y;
	dbwRecordCrosshairYPos(curCrosshair.def, FALSE);
    }
}

/*
 * ----------------------------------------------------------------------------
 * dbwRecordBoxArea --
 *
 * 	This procedure tells the highlight manager that the box's current
 *	area needs to be redisplayed.  It contains a special optimization
 *	for when the box is big:  record four separate areas, one along
 *	each side, instead of one huge area.  This means stuff inside
 *	the box doesn't have to be redisplayed.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Highlight redisplay information is logged.
 * ----------------------------------------------------------------------------
 */

void
dbwRecordBoxArea(erase)
    bool erase;			/* TRUE means box is being erased from its
				 * current area.  FALSE means box is being
				 * added to a new area.
				 */
{
    Rect side;

    if (((boxRootArea.r_xtop - boxRootArea.r_xbot) < 20)
	    || ((boxRootArea.r_ytop - boxRootArea.r_ybot) < 20))
    {
	DBWHLRedraw(boxRootDef, &boxRootArea, erase);
    }
    else
    {
	side = boxRootArea;
	side.r_xtop = side.r_xbot + 1;
	DBWHLRedraw(boxRootDef, &side, erase);
	side = boxRootArea;
	side.r_ytop = side.r_ybot + 1;
	DBWHLRedraw(boxRootDef, &side, erase);
	side = boxRootArea;
	side.r_xbot = side.r_xtop - 1;
	DBWHLRedraw(boxRootDef, &side, erase);
	side = boxRootArea;
	side.r_ybot = side.r_ytop - 1;
	DBWHLRedraw(boxRootDef, &side, erase);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWDrawBox --
 *
 * 	This procedure will redraw the box in a given window, if the
 *	box is to appear in that window.  It is called only by the
 *	highlight code.  The caller must lock the window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The box is redrawn, unless it isn't supposed to appear.
 *
 * ----------------------------------------------------------------------------
 */

void
DBWDrawBox(window, plane)
    MagWindow *window;		/* Window in which to redraw box. */
    Plane *plane;		/* Non-space tiles on this plane indicate
				 * which highlight areas must be redrawn.
				 */
{
    Rect screenArea;
    Rect side;
    int boxStyle = STYLE_SOLIDHIGHLIGHTS;
    extern int dbwBoxAlways1();	/* Forward reference. */

    /* Return if the window is not compatible with the box location
     * or if the box's area doesn't need to be redrawn.
     */

    if (boxRootDef != WINDOW_DEF(window)) return;
    if (!DBSrPaintArea((Tile *) NULL, plane, &boxRootArea,
	    &DBAllButSpaceBits, dbwBoxAlways1, (ClientData) NULL))
	return;

    /* Get the box coordinates in the coordinate system of the	*/
    /* edit cell.  If the box is outside of the edit cell, then	*/
    /* we draw it with style "medium_highlights" instead of	*/
    /* "solid_highlights".					*/

    if (EditRootDef == boxRootDef)
    {
	Rect editbox;
        GeoTransRect(&RootToEditTransform, &boxRootArea, &editbox);
	if (!GEO_OVERLAP(&editbox, &EditCellUse->cu_def->cd_bbox))
	    boxStyle = STYLE_MEDIUMHIGHLIGHTS;
    }

    /* Transform the box into screen coordinates, then draw it.  If the
     * box is a point, draw it as a cross (GrFastBox does this automatically)
     * and add a slightly solid center.  Otherwise, draw the box several
     * pixels wide so it will stand out from other highlights.
     */

    WindSurfaceToScreen(window, &boxRootArea, &screenArea);

    if ((screenArea.r_xbot == screenArea.r_xtop) &&
	    (screenArea.r_ybot == screenArea.r_ytop))
    {
	GrSetStuff(STYLE_OUTLINEHIGHLIGHTS);
	GrFastBox(&screenArea);
	GEO_EXPAND(&screenArea, 1, &screenArea);
	GrFastBox(&screenArea);
	return;
    }

    /* One more optimization here:  if the box is not flattened to
     * a line, but is so skinny that widening it will make it a solid
     * blob, then don't do the widening.  This is to make the box more
     * useable when features are very small.
     */

    if (((screenArea.r_xtop != screenArea.r_xbot) &&
	    (screenArea.r_xtop < screenArea.r_xbot + 4))
	    || ((screenArea.r_ytop != screenArea.r_ybot) &&
	    (screenArea.r_ytop < screenArea.r_ybot + 4)))
    {
	GrClipBox(&screenArea, STYLE_OUTLINEHIGHLIGHTS);
	return;
    }

    GrSetStuff(boxStyle);
    if ((screenArea.r_xbot >= window->w_screenArea.r_xbot) &&
	(screenArea.r_xbot <= window->w_screenArea.r_xtop))
    {
	side = screenArea;
	side.r_xtop = side.r_xbot + 2 - GrPixelCorrect;
	if (side.r_ytop != side.r_ybot) {
	    GrFastBox(&side);
	}
    }
    if ((screenArea.r_ybot >= window->w_screenArea.r_ybot) &&
	(screenArea.r_ybot <= window->w_screenArea.r_ytop))
    {
	side = screenArea;
	side.r_ytop = side.r_ybot + 1;
	if (!GrPixelCorrect) side.r_ybot--;
	if (side.r_xtop != side.r_xbot) {
	    GrFastBox(&side);
	}
    }
    if ((screenArea.r_xtop >= window->w_screenArea.r_xbot) &&
	(screenArea.r_xtop <= window->w_screenArea.r_xtop))
    {
	side = screenArea;
	side.r_xbot = side.r_xtop - 1;
	if (!GrPixelCorrect) side.r_xtop++;
	if (side.r_ytop != side.r_ybot) {
	    GrFastBox(&side);
	}
    }
    if ((screenArea.r_ytop >= window->w_screenArea.r_ybot) &&
	(screenArea.r_ytop <= window->w_screenArea.r_ytop))
    {
	side = screenArea;
	side.r_ybot = side.r_ytop - 2 + GrPixelCorrect;
	if (side.r_xtop != side.r_xbot) {
	    GrFastBox(&side);
	}
    }
}

int
dbwBoxAlways1(
    Tile *tile,			/* (unused) */
    TileType dinfo,		/* (unused) */
    ClientData clientdata)	/* (unused) */
{
    return 1;
}

/*
 * ----------------------------------------------------------------------------
 *	DBWSetBox --
 *
 * 	Change the location and/or size of the box.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Information is recorded so that the box will be redrawn.
 * ----------------------------------------------------------------------------
 */

void
DBWSetBox(rootDef, rect)
    CellDef *rootDef;		/* Root definition in whose coordinate system
				 * the box is defined.  It will appear in all
				 * windows with this as root cell.
				 */
    Rect *rect;			/* New box location in coords of rootDef. */
{
    /* Record the old and area of the box for redisplay. */

    dbwRecordBoxArea(TRUE);

    /* Save information for undo-ing. */

    DBWUndoBox(boxRootDef, &boxRootArea, rootDef, rect);

    /* Update the box location. */

    boxRootDef = rootDef;
    boxRootArea = *rect;

    /* Record the new area for redisplay. */

    dbwRecordBoxArea(FALSE);
}

/*
 * ----------------------------------------------------------------------------
 * DBWResetBox() ---
 *
 *	Make sure that boxRootDef is set to NULL if it is equal to the
 *	specified CellDef.  This is used by the cell delete function to
 *	make sure that if an edit cell is deleted, the boxRootDef is not
 *	pointing to an invalid area of memory.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Global variable boxRootDef may be set to NULL.
 *
 * ----------------------------------------------------------------------------
 */

void
DBWResetBox(CellDef *def)
{
    if (def == boxRootDef)
	boxRootDef = NULL;
}

/*
 * ----------------------------------------------------------------------------
 *	ToolMoveBox --
 *
 * 	Repositions the box by one of its corners.
 *	If the point given to reposition the box is in screen coordinates,
 *	the box corner is snapped to the user's grid (set with the :grid
 *	command) if DBWSnapToGrid is DBW_UNITS_USER.  If DBWSnapToGrid is
 *	DBW_UNITS_LAMBDA, the box corner is snapped to the nearest integer
 *	lambda value.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The box is repositioned so that the given corner is at the
 *	given screen or root cell position.  If the point is given
 *	in screen coordinates, it must fall within the active area
 *	of a window with a non-NULL root cell use.  An error message
 *	is output if this can't be done.  The size of the box isn't
 *	changed.  The box is marked for redisplay, but isn't actually
 *	redisplayed on the screen.
 * ----------------------------------------------------------------------------
 */

void
ToolMoveBox(corner, point, screenCoords, rootDef)
    int corner;			/* Specifies a corner in the format
				 * returned by ToolGetCorner.
				 */
    Point *point;		/* New position of corner, in screen
				 * coordinates.
				 */
    int screenCoords;		/* TRUE means point is in screen coordinates,
				 * FALSE means root cell coordinates.
				 */
    CellDef *rootDef;		/* Used only when screenCoords = FALSE, to
				 * give root cell def.
				 */
{
    Point p;
    MagWindow *w;
    int x, y;
    CellDef *newDef;
    Rect newArea;

    /* Find the current point location. */

    if (screenCoords)
    {
	w = toolFindPoint(point, &p, (Rect *) NULL);
	if ((w == NULL) || (w->w_client != DBWclientID))
	{
	    TxError("Can't put box there.\n");
	    return;
	}
	newDef = WINDOW_DEF(w);
    }
    else
    {
	p = *point;
	newDef = rootDef;
    }

    /* Move the box.  If an illegal corner is specified, then
     * move by the bottom-left corner.
     */

    switch (corner)
    {
	case TOOL_BL:
	    x = p.p_x - boxRootArea.r_xbot;
	    y = p.p_y - boxRootArea.r_ybot;
	    break;
	case TOOL_BR:
	    x = p.p_x - boxRootArea.r_xtop;
	    y = p.p_y - boxRootArea.r_ybot;
	    break;
	case TOOL_TR:
	    x = p.p_x - boxRootArea.r_xtop;
	    y = p.p_y - boxRootArea.r_ytop;
	    break;
	case TOOL_TL:
	    x = p.p_x - boxRootArea.r_xbot;
	    y = p.p_y - boxRootArea.r_ytop;
	    break;
	default:
	    x = p.p_x - boxRootArea.r_xbot;
	    y = p.p_y - boxRootArea.r_ybot;
	    break;
    }
    newArea = boxRootArea;
    newArea.r_xbot += x;
    newArea.r_ybot += y;
    newArea.r_xtop += x;
    newArea.r_ytop += y;

    DBWSetBox(newDef, &newArea);
}


/*
 * ----------------------------------------------------------------------------
 *	ToolMoveCorner --
 *
 * 	This procedure moves one corner of the box, leaving the other
 *	corners as fixed as possible.  Invoked by low-level screen
 *	handler.
 *
 *	If the point given to reposition the box is in screen coordinates,
 *	the box corner is snapped to the user's grid (set with the :grid
 *	command) if DBWSnapToGrid is DBW_UNITS_USER.  If DBWSnapToGrid is
 *	DBW_UNITS_LAMBDA, the box corner is snapped to the nearest integer
 *	lambda value.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The box is modified so that the given corner is at the given
 *	point.  As with ToolMoveBox, the point may either be given as
 *	a screen location, or as a location in the coordinates of a
 *	root cell.  If the new corner location is indicated in a window
 *	that isn't compatible with the current box window, then the
 *	whole box is moved.  Otherwise, the corner diagonally opposite
 *	the given corner isn't moved at all.
 * ----------------------------------------------------------------------------
 */

void
ToolMoveCorner(corner, point, screenCoords, rootDef)
    int corner;			/* The corner to be moved, in format
				 * returned by ToolGetCorner.
				 */
    Point *point;		/* Destination of corner. */
    int screenCoords;		/* TRUE means point is in screen coordinates,
				 * we look up window and translate to root
				 * cell coordinates.  FALSE means point is in
				 * coordinates of rootDef.
				 */
    CellDef *rootDef;		/* Root cell Def if screenCoords = FALSE,
				 * unused otherwise.
				 */
{
    Point p;
    MagWindow *w;
    CellDef *oldDef, *newDef;
    int tmp;
    Rect newArea;

    /* Find the current point location. */

    oldDef = boxRootDef;
    if (screenCoords)
    {
	w = toolFindPoint(point, &p, (Rect *) NULL);
	if ((w == NULL) || (w->w_client != DBWclientID))
	{
	    TxError("Can't put box there.\n");
	    return;
	}
	newDef = WINDOW_DEF(w);
    }
    else
    {
	p = *point;
	newDef = rootDef;
    }

    /* If the root def for the moved corner isn't the same as the
     * current box root def, then just move the whole durned box.
     * Also move the whole box if a weird corner is specified.
     */

    if ((newDef != oldDef) || (corner < 0) || (corner > TOOL_TL))
    {
	ToolMoveBox(corner, &p, FALSE, newDef);
	return;
    }

    /* Move the requested corner. */

    newArea = boxRootArea;
    switch (corner)
    {
	case TOOL_BL:
	    newArea.r_xbot = p.p_x;
	    newArea.r_ybot = p.p_y;
	    break;
	case TOOL_BR:
	    newArea.r_xtop = p.p_x;
	    newArea.r_ybot = p.p_y;
	    break;
	case TOOL_TR:
	    newArea.r_xtop = p.p_x;
	    newArea.r_ytop = p.p_y;
	    break;
	case TOOL_TL:
	    newArea.r_xbot = p.p_x;
	    newArea.r_ytop = p.p_y;
	    break;
    }

    /* If the movement turned the box inside out, turn it right
     * side out again.
     */

    if (newArea.r_xbot > newArea.r_xtop)
    {
	tmp = newArea.r_xtop;
	newArea.r_xtop = newArea.r_xbot;
	newArea.r_xbot = tmp;
    }
    if (newArea.r_ybot > newArea.r_ytop)
    {
	tmp = newArea.r_ytop;
	newArea.r_ytop = newArea.r_ybot;
	newArea.r_ybot = tmp;
    }

    DBWSetBox(newDef, &newArea);
}

/*
 * ----------------------------------------------------------------------------
 *
 * ToolSnapToGrid --
 *
 * Snap the point *p (in root cell coordinates) to the nearest
 * point in the user-defined grid or the nearest integer lambda value,
 * according to the setting of DBWSnapToGrid.  Also translates the rectangle
 * *rEnclose by the same amount by which the point *p was snapped.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Modifies *p.
 * ----------------------------------------------------------------------------
 */

void
ToolSnapToGrid(w, p, rEnclose)
    MagWindow *w;
    Point *p;
    Rect *rEnclose;
{
    DBWclientRec *crec = (DBWclientRec *) w->w_clientData;
    Rect *r;
    Rect lr;
    int xd, yd, xlo, xhi, ylo, yhi, xtmp, ytmp;

    if (crec == NULL || p == NULL)
	return;

    if (DBWSnapToGrid == DBW_UNITS_LAMBDA)
    {
	lr.r_xbot = lr.r_ybot = 0;
	lr.r_xtop = DBLambda[1] / DBLambda[0];
	if (lr.r_xtop < 1) lr.r_xtop = 1;
	lr.r_ytop = lr.r_xtop;
	r = &lr;
    }
    else
        r = &crec->dbw_gridRect;

    xd = r->r_xtop - r->r_xbot;
    yd = r->r_ytop - r->r_ybot;

    /*
     * The following is tricky because we want to ensure we bracket
     * the point p.
     */
    xtmp = p->p_x - r->r_xbot;
    if (xtmp < 0)
    {
	xhi = xd * (xtmp / xd) + r->r_xbot;
	xlo = xhi - xd;
    }
    else
    {
	xlo = xd * (xtmp / xd) + r->r_xbot;
	xhi = xlo + xd;
    }

    ytmp = p->p_y - r->r_ybot;
    if (ytmp < 0)
    {
	yhi = yd * (ytmp / yd) + r->r_ybot;
	ylo = yhi - yd;
    }
    else
    {
	ylo = yd * (ytmp / yd) + r->r_ybot;
	yhi = ylo + yd;
    }

    xtmp = (ABSDIFF(p->p_x, xlo) < ABSDIFF(p->p_x, xhi)) ? xlo : xhi;
    ytmp = (ABSDIFF(p->p_y, ylo) < ABSDIFF(p->p_y, yhi)) ? ylo : yhi;

    if (rEnclose)
    {
	rEnclose->r_xbot += xtmp - p->p_x;
	rEnclose->r_ybot += ytmp - p->p_y;
	rEnclose->r_xtop += xtmp - p->p_x;
	rEnclose->r_ytop += ytmp - p->p_y;
    }
    p->p_x = xtmp;
    p->p_y = ytmp;
}
