/*ScianControls.c
  Controls, control panel stuff, etc. for scian
  Eric Pepke
  March 28, 1990
*/

#include "Scian.h"
#include "ScianTypes.h"
#include "ScianLists.h"
#include "ScianControls.h"
#include "ScianButtons.h"
#include "ScianTitleBoxes.h"
#include "ScianColors.h"
#include "ScianIDs.h"
#include "ScianSliders.h"
#include "ScianTextBoxes.h"
#include "ScianArrays.h"
#include "ScianErrors.h"
#include "ScianWindows.h"
#include "ScianDraw.h"
#include "ScianEvents.h"
#include "ScianObjWindows.h"
#include "ScianVisWindows.h"
#include "ScianIcons.h"
#include "ScianMethods.h"
#include "ScianScripts.h"
#include "ScianStyle.h"
#include "ScianHelp.h"
#include "ScianPreferences.h"
#include "ScianObjFunctions.h"
#include "ScianSymbols.h"
#include "ScianTrackControls.h"

ObjPtr tempRepObj;			/*Temporary icon*/
ObjPtr screenClass = NULLOBJ;		/*Class of all screen objects*/
ObjPtr controlClass = NULLOBJ;		/*Great mother of all controls*/
ObjPtr fieldClass = NULLOBJ;
ObjPtr panelClass = NULLOBJ;
ObjPtr greyPanelClass = NULLOBJ;	/*A panel with a grey background*/
ObjPtr corralClass = NULLOBJ;
ObjPtr switchClass = NULLOBJ;
ObjPtr controlFieldClass = NULLOBJ;	/*Text field class*/
ObjPtr allSelected = NULLOBJ;		/*All the selected objects*/

#define EDITGRAVITY	4
#define CELLHEIGHT	25		/*Height of a cell*/
#define CELLFONTSIZE	12.0		/*Size of font in a cell*/
#define CELLTEXTDOWN	18		/*Offset of text from top of a cell*/
#define CELLTEXTRIGHT	6		/*Offset from left*/
#define CELLNAMEWIDTH	150		/*Width of a name*/

#define VB_ICON		1		/*View by icon*/
#define VB_NAME		2		/*View by name*/

#define LABELFONTSIZE   12.0    	/* size in points of label type */
#define LABELFONTFUDGE  1       	/* fudge strings up (or <0 = down) to center */

#define SWITCHRADIUS	15		/*Radius of a turn on a switch*/
#define SWITCHSLOPLEFT	10		/*Pixels to slop the center of a switch left*/
#define ARROWLENGTH	12		/*Length of an arrow*/
#define ARROWHEIGHT	6		/*Height of an arrow*/


ObjPtr flowLineClass;
#define FLOWARROWLENGTH	8		/*Length of an arrow*/
#define FLOWARROWHEIGHT	4		/*Height of an arrow*/

real xGrid = 16.0, yGrid = 16.0;	/*x and y grid steps*/
real xMid = 0.0, yMid = 0.0;		/*Midpoint of grid*/

static Bool IconConflict(ObjPtr, int, int);

#define GETSCROLL(object, xOff, yOff)					\
	{								\
	    ObjPtr var;							\
	    var = GetVar(object, VSCROLL);				\
	    if (var) yOff = -(int) GetSliderValue(var);			\
		else yOff = 0;						\
	    var = GetVar(object, HSCROLL);				\
	    if (var) xOff = -(int) GetSliderValue(var);			\
		else xOff = 0;						\
	}
	   
void StartPanel(left, right, bottom, top)
int left, right, bottom, top;
{
    SetClipRect(left, right, bottom, top);
    SetOrigin(left, bottom);
}

void StopPanel()
{
    RestoreOrigin();
    RestoreClipRect();
}

ObjPtr PanelBoundsInvalid(object, changeCount)
ObjPtr object;
unsigned long changeCount;
/*For an object, tests to see if it needs drawing.  Returns
  NULLOBJ	if it does not
  array[4]	giving bounds if it does
  ObjTrue	it it needs to be redrawn but does not know where

  Contents are assumed to be shifted in the bounds of owner
*/
{
    ObjPtr contents;
    ObjPtr myBounds;
    FuncTyp method;
    Bool firstTime;
    real boundsElements[4];
    Bool doubleNoBounds = false;
    Bool fromTop;
    Bool possiblyOpaque;

    firstTime = true;
    possiblyOpaque = true;

    MakeVar(object, APPEARANCE);

    fromTop = GetPredicate(object, TOPDOWN);

    MakeVar(object, CHANGEDBOUNDS);
    if (GetVarChangeCount(object, CHANGEDBOUNDS) > changeCount)
    {
	/*Object is not good, so return the bounds*/

	myBounds = GetVar(object, CHANGEDBOUNDS);
	if (myBounds && IsArray(myBounds) && RANK(myBounds) == 1
		&& DIMS(myBounds)[0] == 4)
	{
	    firstTime = false;
	    Array2CArray(boundsElements, myBounds);
	    possiblyOpaque = false;
	}
	else
	{
	    /*Pathological case*/
	    return ObjTrue;
	}
    }

    MakeVar(object, CONTENTS);
    contents = GetVar(object, CONTENTS);
    if (contents && IsList(contents))
    {
	/*Still, maybe some of the contents need to be drawn*/
	real testElements[4];
	real myBoundsElements[4];
 	ObjPtr test;
 	ThingListPtr runner;

	MakeVar(object, BOUNDS);
	myBounds = GetVar(object, BOUNDS);
	Array2CArray(myBoundsElements, myBounds);

	runner = LISTOF(contents);
	while (runner)
	{
	    test = BoundsInvalid(runner -> thing, changeCount);
	    if (test)
	    {
		/*Hey, the kid needs redrawing*/
		if (IsRealArray(test))
		{
		    /*It has a bounds*/
		    if (firstTime)
		    {
			Array2CArray(boundsElements, test);
			if (fromTop)
			{
			    boundsElements[0] += myBoundsElements[0];
			    boundsElements[1] += myBoundsElements[0];
			    boundsElements[2] += myBoundsElements[3];
			    boundsElements[3] += myBoundsElements[3];
			}
			else
			{
			    boundsElements[0] += myBoundsElements[0];
			    boundsElements[1] += myBoundsElements[0];
			    boundsElements[2] += myBoundsElements[2];
			    boundsElements[3] += myBoundsElements[2];
			}
			MakeVar(runner -> thing, OPAQUE);
			if (GetPredicate(runner -> thing, OPAQUE))
			{
			}
			else
			{
			    possiblyOpaque = false;
			}
		    }
		    else
		    {
			possiblyOpaque = false;
			Array2CArray(testElements, test);
			if (fromTop)
			{
			    testElements[0] += myBoundsElements[0];
			    testElements[1] += myBoundsElements[0];
			    testElements[2] += myBoundsElements[3];
			    testElements[3] += myBoundsElements[3];
			}
			else
			{
			    testElements[0] += myBoundsElements[0];
			    testElements[1] += myBoundsElements[0];
			    testElements[2] += myBoundsElements[2];
			    testElements[3] += myBoundsElements[2];
			}
			if (testElements[0] < boundsElements[0])
				boundsElements[0] = testElements[0];
			if (testElements[1] > boundsElements[1])
				boundsElements[1] = testElements[1];
			if (testElements[2] < boundsElements[2])
				boundsElements[2] = testElements[2];
			if (testElements[3] > boundsElements[3])
				boundsElements[3] = testElements[3];
		    }
		}
		else
		{
		    /*It doesn't have a bounds*/
		    if (firstTime)
		    {
			/*Try to use my bounds*/
			if (myBounds && IsArray(myBounds) && RANK(myBounds) == 1
				&& DIMS(myBounds)[0] == 4)
			{
			    Array2CArray(boundsElements, myBounds);
			}
			else
			{
			    doubleNoBounds = true;
			}
		    }
		}
		firstTime = false;
	    }
	    runner = runner -> next;
	}
    }

    /*Now return the bounds*/
    if (firstTime != true)
    {
#ifdef DONTCLIP
	possiblyOpaque = false;
#endif
	if (possiblyOpaque)
	{
	    SetVar(object, BACKNOTNEEDED, ObjTrue);
	}
	    if (doubleNoBounds)
	    {
		return ObjTrue;
	    }
	    else
	    {
		ObjPtr retVal;
		retVal = NewRealArray(1, 4L);
		CArray2Array(retVal, boundsElements);
		return retVal;
	    }
    }

    return NULLOBJ;
}

#define EXPANDSHIFTEDBOUNDS(a)					\
	if (firstTime)						\
	{							\
	    firstTime = false;					\
	    if (IsArray(a))					\
	    {							\
	        Array2CArray(boundsElements, (a));		\
		if (fromTop)					\
		{						\
		    boundsElements[0] += myBoundsElements[0] +	\
					 fieldDepth + xOff;	\
		    boundsElements[1] += myBoundsElements[0] +	\
					 fieldDepth + xOff;	\
		    boundsElements[2] += myBoundsElements[3] -	\
					 fieldDepth + yOff;	\
		    boundsElements[3] += myBoundsElements[3] -	\
					 fieldDepth + yOff;	\
		}						\
		else						\
		{						\
		    boundsElements[0] += myBoundsElements[0] +	\
					 fieldDepth + xOff;	\
		    boundsElements[1] += myBoundsElements[0] +	\
					 fieldDepth + xOff;	\
		    boundsElements[2] += myBoundsElements[2] +	\
					 fieldDepth + yOff;	\
		    boundsElements[3] += myBoundsElements[2] +	\
					 fieldDepth + yOff;	\
		}						\
	    }							\
	    else if (IsArray(myBounds))				\
	    {							\
	        Array2CArray(boundsElements, (myBounds));	\
	    }							\
	    else return (a);					\
	}							\
	else							\
	{							\
	    real temp[4];					\
	    if (IsArray(a))					\
	    {							\
	        Array2CArray(temp, (a));			\
		if (fromTop)					\
		{						\
		    temp[0] += myBoundsElements[0] +		\
					fieldDepth + xOff;	\
		    temp[1] += myBoundsElements[0] +		\
					fieldDepth + xOff;	\
		    temp[2] += myBoundsElements[3] -		\
					fieldDepth + yOff;	\
		    temp[3] += myBoundsElements[3] -		\
					fieldDepth + yOff;	\
		}						\
		else						\
		{						\
		    temp[0] += myBoundsElements[0] +		\
					fieldDepth + xOff;	\
		    temp[1] += myBoundsElements[0] +		\
					fieldDepth + xOff;	\
		    temp[2] += myBoundsElements[2] +		\
					fieldDepth + yOff;	\
		    temp[3] += myBoundsElements[2] +		\
					fieldDepth + yOff;	\
		}						\
	    }							\
	    else if (IsArray(myBounds))				\
	    {							\
	        Array2CArray(temp, (myBounds));			\
	    }							\
	    else return (a);					\
	    if (temp[0] < boundsElements[0])			\
			boundsElements[0] = temp[0];		\
	    if (temp[1] > boundsElements[1])			\
			boundsElements[1] = temp[1];		\
	    if (temp[2] < boundsElements[2])			\
			boundsElements[2] = temp[2];		\
	    if (temp[3] > boundsElements[3])			\
			boundsElements[3] = temp[3];		\
	}

#define EXPANDBOUNDS(a)						\
	if (firstTime)						\
	{							\
	    firstTime = false;					\
	    if (IsArray(a))					\
	    {							\
	        Array2CArray(boundsElements, (a));		\
	    }							\
	    else if (IsArray(myBounds))				\
	    {							\
	        Array2CArray(boundsElements, (myBounds));	\
	    }							\
	    else return (a);					\
	}							\
	else							\
	{							\
	    real temp[4];					\
	    if (IsArray(a))					\
	    {							\
	        Array2CArray(temp, (a));			\
	    }							\
	    else if (IsArray(myBounds))				\
	    {							\
	        Array2CArray(temp, (myBounds));			\
	    }							\
	    else return (a);					\
	    if (temp[0] < boundsElements[0])			\
			boundsElements[0] = temp[0];		\
	    if (temp[1] > boundsElements[1])			\
			boundsElements[1] = temp[1];		\
	    if (temp[2] < boundsElements[2])			\
			boundsElements[2] = temp[2];		\
	    if (temp[3] > boundsElements[3])			\
			boundsElements[3] = temp[3];		\
	}

ObjPtr FieldBoundsInvalid(object, changeCount)
ObjPtr object;
unsigned long changeCount;
/*For an object, tests to see if it needs drawing.  Returns
  NULLOBJ	if it does not
  array[4]	giving bounds if it does
  ObjTrue	it it needs to be redrawn but does not know where

  Contents are assumed to be shifted in their space
*/
{
    ObjPtr test;
    ObjPtr contents;
    real boundsElements[4];
    real myBoundsElements[4];
    ObjPtr myBounds;
    ObjPtr scrollBar;
    Bool firstTime = true;
    Bool fromTop = false;
    int fieldDepth = 0;
    int xOff, yOff;

    MakeVar(object, APPEARANCE);
    MakeVar(object, BOUNDS);
    fromTop = GetPredicate(object, TOPDOWN);

    myBounds = GetVar(object, BOUNDS);
    if (IsArray(myBounds))
    {
	Array2CArray(myBoundsElements, myBounds);
    }

    GETSCROLL(object, xOff, yOff);
    if (GetPredicate(object, BORDERTYPE))
    {
	fieldDepth = 1;
    }
    else
    {
	fieldDepth = EDGE;
    }

    MakeVar(object, CHANGEDBOUNDS);
    if (GetVarChangeCount(object, CHANGEDBOUNDS) > changeCount)
    {
	/*Object is not good, so return the bounds*/

	test = GetVar(object, CHANGEDBOUNDS);

	if (test && IsArray(test) && RANK(test) == 1
		&& DIMS(test)[0] == 4)
	{
	    EXPANDBOUNDS(test);
	}
	else
	{
	    return ObjTrue;
	}
    }

    MakeVar(object, CONTENTS);
    contents = GetVar(object, CONTENTS);
    if (contents && IsList(contents))
    {
	/*Still, maybe some of the contents need to be drawn*/
 	ThingListPtr runner;

	runner = LISTOF(contents);
	while (runner)
	{
	    test = BoundsInvalid(runner -> thing, changeCount);
	    if (test)
	    {
		EXPANDSHIFTEDBOUNDS(test);
	    }
	    runner = runner -> next;
	}
    }

    scrollBar = GetVar(object, HSCROLL);
    if (scrollBar);
    {
	test = BoundsInvalid(scrollBar, changeCount);
	if (test)
	{
	    EXPANDBOUNDS(test);
	}
    }

    scrollBar = GetVar(object, VSCROLL);
    if (scrollBar);
    {
	test = BoundsInvalid(scrollBar, changeCount);
	if (test)
	{
	    EXPANDBOUNDS(test);
	}
    }

    if (firstTime)
    {
	return NULLOBJ;
    }
    else
    {
	ObjPtr retVal;
	retVal = NewRealArray(1, 4L);
	CArray2Array(retVal, boundsElements);
	return retVal;
    }
}

#ifdef GRAPHICS
static ObjPtr DrawPanel(object)
ObjPtr object;
/*Draws a control panel*/
{
    int l, r, b, t;
    ObjPtr backColor, borderType;

    Get2DIntBounds(object, &l, &r, &b, &t);
    if (IsDrawingRestricted(l, r, b, t)) return ObjFalse;

    /*Setup the new panel area*/
    StartPanel(l, r, b, t);

    /*Get the color, if any, and draw it*/
    backColor = GetVar(object, BACKGROUND);
    if (backColor)
    {
	if (GetPredicate(object, BACKNOTNEEDED))
	{
	}
	else
	{
	    SetObjectColor(backColor);
	    FillRect(0, r - l, 0, t - b);
#if MACHINE == RS6000
	    /*KLUDGE for IBM*/
#if 1
	    FrameRect(0, r - l, 0, t - b);
#endif
#endif
	}
    }
    SetVar(object, BACKNOTNEEDED, ObjFalse);

    borderType = GetVar(object, BORDERTYPE);
    if (borderType)
    {
	FrameUIRect(0, r - l - 1, 0, t - b - 1, UIBLACK);
    }

    /*Draw the contents of the panel*/
    DrawList(GetVar(object, CONTENTS));
    StopPanel();

    return NULLOBJ;
}
#endif

static ObjPtr FindObjectField(object, name)
ObjPtr object;
char *name;
/*Searches a field and its contents for an object with name*/
{
    ObjPtr retVal = NULLOBJ;
    ObjPtr objName, contents;
    ObjPtr scrollBar;
    /*First check to see if I am the object*/
    objName = GetVar(object, NAME);
    if (objName && IsString(objName) && ObjectNameMatches(name, GetString(objName)))
    {
	if (!retVal)
	{
	    retVal = NewList();
	}
	PostfixList(retVal, object);
    }

    /*Now check my CONTENTS*/
    contents = GetVar(object, CONTENTS);
    if (contents)
    {
	ObjPtr foundObjects;
	foundObjects = FindNamedObject(contents, name);
	if (foundObjects)
	{
	    if (!retVal)
	    {
		retVal = NewList();
	    }
	    AppendList(retVal, foundObjects);
	}
    }

    /*Now check the scroll bars*/
    scrollBar = GetVar(object, HSCROLL);
    if (scrollBar)
    {
	objName = GetVar(scrollBar, NAME);
	if (objName && IsString(objName) && 0 == strcmp2(GetString(objName), name))
	{
	    if (!retVal)
	    {
		retVal = NewList();
	    }
	    PostfixList(retVal, scrollBar);
	}
    }
    scrollBar = GetVar(object, VSCROLL);
    if (scrollBar)
    {
	objName = GetVar(scrollBar, NAME);
	if (objName && IsString(objName) && 0 == strcmp2(GetString(objName), name))
	{
	    if (!retVal)
	    {
		retVal = NewList();
	    }
	    PostfixList(retVal, scrollBar);
	}
    }

    return retVal;
}

static ObjPtr ForAllFieldObjects(object, routine)
ObjPtr object;
FuncTyp routine;
/*Does routine on a field and its contents*/
{
    ObjPtr contents;
    ObjPtr scrollBar;

    (*routine)(object);

    /*Now check my CONTENTS*/
    contents = GetVar(object, CONTENTS);
    if (contents)
    {
	ForAllObjects(contents, routine);
    }

    /*Now check the scroll bars*/
    scrollBar = GetVar(object, HSCROLL);
    if (scrollBar)
    {
	ForAllObjects(scrollBar, routine);
    }
    scrollBar = GetVar(object, VSCROLL);
    if (scrollBar)
    {
	ForAllObjects(scrollBar, routine);
    }

    return ObjTrue;
}

static Bool IconConflict(icons, x, y)
ObjPtr icons;
int x, y;
/*See if the position conflicts with any of the icons*/ 
{
    ObjPtr locArray;
    real loc[2];

    if (IsList(icons))
    {
	ThingListPtr list;
	list = LISTOF(icons);
	while (list)
	{
	    locArray = GetFixedArrayVar("IconConflict", list -> thing, ICONLOC, 1, 2L);
	    if (!locArray)
	    {
		return false;
	    }
	    Array2CArray(loc, locArray);

	    if (ABS(x - loc[0]) < ICONXMINSPACE && ABS(y - loc[1]) < ICONYMINSPACE)
	    {
		return true;
	    }
	    list = list -> next;
	}
    }
    return false;
}

#ifdef PROTO
void DropIconInCorral(ObjPtr corral, ObjPtr icon)
#else
void DropIconInCorral(corral, icon)
ObjPtr corral, icon;
#endif
/*Drops icon in corral, if ICONLOC is null, trying to find a place for it to 
  fit.*/
{
    int x, y;			/*Trial icon spacing*/
    int left, right, bottom, top;
    int xmin, xmax;
    ObjPtr contents;
    ObjPtr locArray;
    real loc[2];
    char newName[256];		/*Trial new name*/
    int k;
    ObjPtr name;
    Bool stagger;
    Bool fromTop;

    fromTop = GetPredicate(corral, TOPDOWN);

    name = GetStringVar("DropIconInCorral", icon, NAME);
    if (name)
    {
	/*Create a new name*/
	for (k = 1; ; ++k)
	{
	    strncpy(newName, GetString(name), 255);
	    newName[255] = 0;
	    if (k > 1)
	    {
		sprintf(tempStr, " (%d)", k);
		strcat(newName, tempStr);
	    }
	    if (FindNamedObject(corral, newName))
	    {
		continue;
	    }
	    break;
	}
	SetVar(icon, NAME, NewString(newName));
    }

    Get2DIntBounds(corral, &left, &right, &bottom, &top);

    contents = GetListVar("DropIconInCorral", corral, CONTENTS);
    if (!contents)
    {
	return;
    }

    if (GetVar(icon, ICONLOC) == NULLOBJ)
    {
	/*Calculate a location*/

	if (GetPredicate(corral, SINGLECORRAL))
	{
	    x = (right - left) / 2;
	    y = GetPredicate(corral, TOPDOWN) ?
		(bottom - top) / 2 + 25 :
		(top - bottom) / 2 + 25;
	}
	else
	{
	stagger = GetPrefTruth(PREF_STAGGERICONS);
	xmin = ICONLEFTBORDER;
	xmax = right - left - ICONLEFTBORDER;
	if (xmax < xmin) xmax = xmin;
	for (y = fromTop ? - ICONTOPBORDER : ICONBOTBORDER; ; 
		    y += fromTop ? -ICONYSPACE : ICONYSPACE)
	{
	    k = 0;
	    for (x = xmin; x <= xmax; x += ICONXSPACE)
	    {
		int tempY;
		tempY = ((k & 1) && stagger) ? (y + (fromTop ? -ICONYSPACE / 2 : ICONYSPACE / 2)) : y;
		if (!IconConflict(contents, x, tempY))
		{
			y = tempY;
		    goto getoutahere;
		}
		++k;
	    }
	}
	}
getoutahere:
	loc[0] = x;
	loc[1] = y;
	locArray = NewRealArray(1, (long) 2);
	CArray2Array(locArray, loc);
	SetVar(icon, ICONLOC, locArray);
    }

    PostfixList(contents, icon);
    SetVar(icon, PARENT, corral);
    RecalcScroll(corral);
    ImInvalid(corral);
    return;
}

#ifdef PROTO
void DropIconSeriesInCorral(ObjPtr corral, ObjPtr icon)
#else
void DropIconSeriesInCorral(corral, icon)
ObjPtr corral, icon;
#endif
/*Drops icon, which is one of a series, in corral.  If ICONLOC is null, 
  will wait until next redraw and then find a connected block for all the
  icons.
*/
{
    int x, y;			/*Trial icon spacing*/
    int left, right, bottom, top;
    int xmin, xmax;
    ObjPtr contents;
    ObjPtr locArray;
    real loc[2];
    char newName[256];		/*Trial new name*/
    int k;
    ObjPtr name;
    Bool stagger;
    Bool fromTop;

    fromTop = GetPredicate(corral, TOPDOWN);

    name = GetStringVar("DropIconInCorral", icon, NAME);
    if (name)
    {
	/*Create a new name*/
	for (k = 1; ; ++k)
	{
	    strncpy(newName, GetString(name), 255);
	    newName[255] = 0;
	    if (k > 1)
	    {
		sprintf(tempStr, " (%d)", k);
		strcat(newName, tempStr);
	    }
	    if (FindNamedObject(corral, newName))
	    {
		continue;
	    }
	    break;
	}
	SetVar(icon, NAME, NewString(newName));
    }

    Get2DIntBounds(corral, &left, &right, &bottom, &top);

    contents = GetListVar("DropIconInCorral", corral, CONTENTS);
    if (!contents)
    {
	return;
    }

    PostfixList(contents, icon);
    SetVar(icon, PARENT, corral);

    ImInvalid(corral);
    return;
}

#ifdef INTERACTIVE
void SetScreenGrid(object)
ObjPtr object;
/*Sets the screen grid to a grid within object*/
{
    int l, r, b, t;
    real gridSize;

    Get2DIntBounds(object, &l, &r, &b, &t);
    xGrid = yGrid = ((real) (t - b)) / ((real) NGRIDSTEPS);
    xMid = (r - l) * 0.5;
    yMid = (t - b) * 0.5;
}
#endif

#ifdef GODLIKE
static ObjPtr globalEditPanel;
static ObjPtr globalEditObject;

#ifdef PROTO
int ClosestGravity(ObjPtr object, int coord, Bool vertical, int min, int max)
#else
int ClosestGravity(object, coord, vertical, min, max)
ObjPtr object;
int coord;
Bool vertical;
int min, max;
#endif
/*Returns closest gravity snap.  vertical true if coord is vertical.
  Returns -1 if none are valid*/
{
    int closest = -1;

    if (object == globalEditObject) return closest;

    if (IsList(object))
    {
	ThingListPtr runner;
	runner = LISTOF(object);
	while (runner)
	{
	    int closer;

	    closer = ClosestGravity(runner -> thing, coord, vertical, min, max);
	    if (closer >= 0 && ABS(closer - coord) < ABS(closest - coord))
	    {
		closest = closer;
	    }
	    runner = runner -> next;
	}
    }
    else
    {
	ObjPtr contents;
	ObjPtr bounds;

	contents = GetVar(object, CONTENTS);
	if (contents)
	{
	    int closer;

	    closer = ClosestGravity(contents, coord, vertical, min, max);
	    if (closer >= 0 && ABS(closer - coord) < ABS(closest - coord))
	    {
		closest = closer;
	    }
	}

	bounds = GetVar(object, BOUNDS);
	if (bounds)
	{
	    int l, r, b, t, closer;
	    Get2DIntBounds(object, &l, &r, &b, &t);

	    /*For now, just snap to minorborder snaps*/

	    if (vertical)
	    {
		if (b <= max && t >= min)
		{
		closer = l - MINORBORDER;
		if (ABS(closer - coord) < ABS(closest - coord))
		{
		    closest = closer;
		}
		closer = l + MINORBORDER;
		if (ABS(closer - coord) < ABS(closest - coord))
		{
		    closest = closer;
		}
		closer = r - MINORBORDER;
		if (ABS(closer - coord) < ABS(closest - coord))
		{
		    closest = closer;
		}
		closer = r + MINORBORDER;
		if (ABS(closer - coord) < ABS(closest - coord))
		{
		    closest = closer;
		}
		closer = l - MAJORBORDER;
		if (ABS(closer - coord) < ABS(closest - coord))
		{
		    closest = closer;
		}
		closer = l + MAJORBORDER;
		if (ABS(closer - coord) < ABS(closest - coord))
		{
		    closest = closer;
		}
		closer = r - MAJORBORDER;
		if (ABS(closer - coord) < ABS(closest - coord))
		{
		    closest = closer;
		}
		closer = r + MAJORBORDER;
		if (ABS(closer - coord) < ABS(closest - coord))
		{
		    closest = closer;
		}
		}
	    }
	    else if (l <= max && r >= min)
	    {
		ObjPtr ts;
		closer = t - MINORBORDER;
		if (ABS(closer - coord) < ABS(closest - coord))
		{
		    closest = closer;
		}
		closer = t + MINORBORDER;
		if (ABS(closer - coord) < ABS(closest - coord))
		{
		    closest = closer;
		}
		closer = b - MINORBORDER;
		if (ABS(closer - coord) < ABS(closest - coord))
		{
		    closest = closer;
		}
		closer = b + MINORBORDER;
		if (ABS(closer - coord) < ABS(closest - coord))
		{
		    closest = closer;
		}
		closer = t - MAJORBORDER;
		if (ABS(closer - coord) < ABS(closest - coord))
		{
		    closest = closer;
		}
		closer = t + MAJORBORDER;
		if (ABS(closer - coord) < ABS(closest - coord))
		{
		    closest = closer;
		}
		closer = b - MAJORBORDER;
		if (ABS(closer - coord) < ABS(closest - coord))
		{
		    closest = closer;
		}
		closer = b + MAJORBORDER;
		if (ABS(closer - coord) < ABS(closest - coord))
		{
		    closest = closer;
		}
		/*Text box*/
		if ((ts = GetVar(globalEditObject, TYPESTRING)) &&
		    (0 == strcmp2(GetString(ts), "text box")))
		{
		    closer = b - TEXTBOXSEP;
		    if (ABS(closer - coord) < ABS(closest - coord))
		    {
			closest = closer;
		    }
		}
		/*Radio button*/
		if ((ts = GetVar(globalEditObject, TYPESTRING)) &&
		    (0 == strcmp2(GetString(ts), "radio button")))
		{
		    closer = b - CHECKBOXSPACING;
		    if (ABS(closer - coord) < ABS(closest - coord))
		    {
			closest = closer;
		    }
		}
	    }
	}
    }
    return closest;
}

static ObjPtr PressEditBounds(object, x, y, flags)
ObjPtr object;
int x, y;
long flags;
/*Presses in an object for bounds editing*/
{
    ThingListPtr runner;

    if (IsList(object))
    {
	runner = LISTOF(object);
	while (runner)
	{
	    ObjPtr retVal;
	    retVal = PressEditBounds(runner -> thing, x, y, flags);
	    if (IsTrue(retVal))
	    {
		return retVal;
	    }
	    runner = runner -> next;
	}
	return ObjFalse;
    }
    else
    {
        real ob[4];
        ObjPtr boundsArray;
	ObjPtr contents;
        /*It's a magic drag*/

	contents = GetVar(object, CONTENTS);
	if (contents)
	{
	    ObjPtr retVal;
	    retVal = PressEditBounds(contents, x, y, flags);
	    if (IsTrue(retVal))
	    {
		return retVal;
	    }
	}

        boundsArray = GetVar(object, BOUNDS);
	if (boundsArray)
	{
	    Array2CArray(ob, boundsArray);
	    if (x >= ob[0] && x <= ob[1] &&
		y >= ob[2] && y <= ob[3])
	    {
		/*Click here!*/
		int initX, initY;
		Bool ml, mr, mb, mt;
		real newBounds[4];
		real oldNewBounds[4];

		if (flags & F_OPTIONDOWN)
		{
		    ObjPtr parent, contents;
		    parent = GetVar(object, PARENT);
		    contents = GetVar(parent, CONTENTS);
		    DeleteFromList(contents, object);
		    PostfixList(contents, object);
		}

		newBounds[0] = oldNewBounds[0] = ob[0];
		newBounds[1] = oldNewBounds[1] = ob[1];
		newBounds[2] = oldNewBounds[2] = ob[2];
		newBounds[3] = oldNewBounds[3] = ob[3];

		initX = x;
		initY = y;

		/*Determine bits to move based on position*/
		mr = x >= ob[0] + 0.75 * (ob[1] - ob[0]);
		ml = x <= ob[0] + 0.25 * (ob[1] - ob[0]);
		mt = y >= ob[2] + 0.75 * (ob[3] - ob[2]);
		mb = y <= ob[2] + 0.25 * (ob[3] - ob[2]);

		if (!(mr | ml | mb | mt))
		{
		    mr = ml = mb = mt = true;
		}
		while (Mouse(&x, &y))
		{
		    if (x != initX || y != initY)
		    {
			/*The mouse has moved*/
			ImInvalid(object);

			if (ml) newBounds[0] = ob[0] + x - initX;
			if (mr) newBounds[1] = ob[1] + x - initX;
			if (mb) newBounds[2] = ob[2] + y - initY;
			if (mt) newBounds[3] = ob[3] + y - initY;

			if (flags & F_SHIFTDOWN)
			{
			    /*Gravity drag*/
			    globalEditObject = object;
			    if (ml && mr && mb && mt)
			    {
				int closest1, closest2, w, h;
				/*Special case, maintain size*/

				w = newBounds[1] - newBounds[0];
				h = newBounds[3] - newBounds[2];
				closest1 = ClosestGravity(globalEditPanel,
						(int) newBounds[0], true,
						(int) newBounds[2], (int) newBounds[3]);
				
				closest2 = ClosestGravity(globalEditPanel,
						(int) newBounds[1], true,
						(int) newBounds[2], (int) newBounds[3]);

				if (closest1 >= 0 && ABS(closest1 - (int) newBounds[0]) <= EDITGRAVITY &&
				    closest2 >= 0 && ABS(closest2 - (int) newBounds[1]) <= EDITGRAVITY)
				{
				    if (ABS(closest1 - (int) newBounds[0]) <
					ABS(closest2 - (int) newBounds[1]))
				    {
					newBounds[0] = closest1;
					newBounds[1] = closest1 + w;
				    }
				    else
				    {
					newBounds[1] = closest2;
					newBounds[0] = closest2 - w;
				    }
				}
				else if (closest1 >= 0 && ABS(closest1 - (int) newBounds[0]) <= EDITGRAVITY)
				{
				    newBounds[0] = closest1;
				    newBounds[1] = closest1 + w;
				}
				else if (closest2 >= 0 && ABS(closest2 - (int) newBounds[1]) <= EDITGRAVITY)
				{
				    newBounds[1] = closest2;
				    newBounds[0] = closest2 - w;
				}

				closest1 = ClosestGravity(globalEditPanel,
						(int) newBounds[2], false,
						(int) newBounds[0], (int) newBounds[1]);
				
				closest2 = ClosestGravity(globalEditPanel,
						(int) newBounds[3], false,
						(int) newBounds[0], (int) newBounds[1]);

				if (closest1 >= 0 && ABS(closest1 - (int) newBounds[2]) <= EDITGRAVITY &&
				    closest2 >= 0 && ABS(closest2 - (int) newBounds[3]) <= EDITGRAVITY)
				{
				    if (ABS(closest1 - (int) newBounds[2]) <
					ABS(closest2 - (int) newBounds[3]))
				    {
					newBounds[2] = closest1;
					newBounds[3] = closest1 + h;
				    }
				    else
				    {
					newBounds[3] = closest2;
					newBounds[2] = closest2 - h;
				    }
				}
				else if (closest1 >= 0 && ABS(closest1 - (int) newBounds[2]) <= EDITGRAVITY)
				{
				    newBounds[2] = closest1;
				    newBounds[3] = closest1 + h;
				}
				else if (closest2 >= 0 && ABS(closest2 - (int) newBounds[3]) <= EDITGRAVITY)
				{
				    newBounds[3] = closest2;
				    newBounds[2] = closest2 - h;
				}
			    }
			    else
			    {
				int closest;

				if (ml)
				{
				    closest = ClosestGravity(globalEditPanel,
						(int) newBounds[0], true,
						(int) newBounds[2], (int) newBounds[3]);
				    if (closest >= 0 && ABS(closest - (int) newBounds[0]) <= EDITGRAVITY) newBounds[0] = closest;
				}

				if (mr)
				{
				    closest = ClosestGravity(globalEditPanel,
						(int) newBounds[1], true,
						(int) newBounds[2], (int) newBounds[3]);
				    if (closest >= 0 && ABS(closest - (int) newBounds[1]) <= EDITGRAVITY) newBounds[1] = closest;
				}
				
				if (mb)
				{
				    closest = ClosestGravity(globalEditPanel,
						(int) newBounds[2], false,
						(int) newBounds[0], (int) newBounds[1]);
				    if (closest >= 0 && ABS(closest - (int) newBounds[2]) <= EDITGRAVITY) newBounds[2] = closest;
				}

				if (mt)
				{
				    closest = ClosestGravity(globalEditPanel,
						(int) newBounds[3], false,
						(int) newBounds[0], (int) newBounds[1]);
				    if (closest >= 0 && ABS(closest - (int) newBounds[3]) <= EDITGRAVITY) newBounds[3] = closest;
				}
			    }
			}

			if ((newBounds[0] != oldNewBounds[0] ||
			     newBounds[1] != oldNewBounds[1] ||
			     newBounds[2] != oldNewBounds[2] ||
			     newBounds[3] != oldNewBounds[3]) &&
			     newBounds[0] < newBounds[1] &&
			     newBounds[2] < newBounds[3])
			{
			    boundsArray = NewRealArray(1, 4L);
			    CArray2Array(boundsArray, newBounds);
			    SetVar(object, BOUNDS, boundsArray);
			    oldNewBounds[0] = newBounds[0];
			    oldNewBounds[1] = newBounds[1];
			    oldNewBounds[2] = newBounds[2];
			    oldNewBounds[3] = newBounds[3];
			    DrawMe(object);
			}
		    }
		}
		return ObjTrue;
	    }
	}
    }
    return ObjFalse;
}
#endif

#ifdef INTERACTIVE
static ObjPtr PressPanel(object, x, y, flags)
ObjPtr object;
int x, y;
long flags;
/*Does a press in a panel beginning at x and y.  Returns
  true iff the press really was in the panel.*/
{
    int left, right, bottom, top;

    Get2DIntBounds(object, &left, &right, &bottom, &top);

    SetScreenGrid(object);

    if (x >= left && x <= right && y >= bottom && y <= top)
    {
	/*Hey!  It really was a click in the panel*/
	ObjPtr contents;
	ObjPtr retVal;

	/*See if it's a dragBuffer click*/
	if (dragBuffer)
	{
	    /*Yes it is.  Move dragBuffer and exit*/
	    dropObject = dragBuffer;
	    dragBuffer = NULLOBJ;
	    return ObjTrue;
	}

    	/*Setup the new panel area*/
	StartPanel(left, right, bottom, top);

        x -= left;
        y -= bottom;
	
        contents = GetVar(object, CONTENTS);
	if (contents && IsList(contents))
	{
#ifdef GODLIKE
	    ObjPtr panel;
	    if (GetPredicate(panel = GetVar(object, PARENT), SHOWBOUNDS))
	    {
		globalEditPanel = panel;
		PressEditBounds(contents, x, y, flags);
		StopPanel();
		return ObjTrue;
	    }
	    else
#endif
	    {
		/*It's a normal click*/
		retVal = PressObject(contents, x, y, flags);
	    }
	    if (TOOL(flags) == T_HELP && !IsTrue(retVal))
	    {
		ContextHelp(object);
	    }
	}
	else
	{
	    retVal = ObjFalse;
	}

	StopPanel();
	return retVal;
    }
    else
    {
	return ObjFalse;
    }
}
#endif

#ifdef INTERACTIVE
static ObjPtr DropInPanel(object, dropObj, x, y)
ObjPtr object, dropObj;
int x, y;
/*Drops object in a panel beginning at x and y.  Returns
  true iff the drop really was in the panel.*/
{
    int left, right, bottom, top;

    Get2DIntBounds(object, &left, &right, &bottom, &top);

    if (x >= left && x <= right && y >= bottom && y <= top)
    {
	/*Hey!  It really was a drop in the panel*/
	ObjPtr contents;

    	/*Setup the new panel area*/
	StartPanel(left, right, bottom, top);

        x -= left;
        y -= bottom;
	contents = GetVar(object, CONTENTS);
	if (contents && IsList(contents))
	{
	    DropList(contents, dropObj, x, y);
	}

	StopPanel();
	return ObjTrue;
    }
    else
    {
	return ObjFalse;
    }
}
#endif

#ifdef INTERACTIVE
static ObjPtr KeyDownContents(object, key, flags)
ObjPtr object;
int key;
long flags;
/*Does a keydown in something with a CONTENTS*/
{
    ObjPtr contents;
    contents = GetVar(object, CONTENTS);
    if (contents)
    {
	return KeyDownList(contents, key, flags);
    }
    else
    {
	return ObjFalse;
    }
}
#endif

#ifdef INTERACTIVE
static ObjPtr PressFieldContents(object, x, y, flags)
ObjPtr object;
int x, y;
long flags;
/*Presses the contents of a field*/
{
    ObjPtr contents;

    contents = GetListVar("PressFieldContents", object, CONTENTS);
    if (contents)
    {
	return PressObject(contents, x, y, flags);
    }
    else
    {
	return ObjFalse;
    }
}
#endif

void ScrollHome(field)
ObjPtr field;
/*Scrolls any field to home.*/
{
    ObjPtr scrollbar;

    scrollbar = GetVar(field, HSCROLL);
    if (scrollbar)
    {
	SetSliderValue(scrollbar, 0.0);
    }

    scrollbar = GetVar(field, VSCROLL);
    if (scrollbar)
    {
	SetSliderValue(scrollbar, 0.0);
    }
}

#ifdef INTERACTIVE
static ObjPtr PressField(object, x, y, flags)
ObjPtr object;
int x, y;
long flags;
/*Does a press in a field beginning at x and y.  Returns
  true iff the press really was in the field.*/
{
    int left, right, bottom, top;
    FuncTyp pressContents;		/*Routine to press the contents*/
    ObjPtr scrollBar;

    Get2DIntBounds(object, &left, &right, &bottom, &top);

    /*See if it's a press in a scrollbar*/
    scrollBar = GetVar(object, HSCROLL);
    if (scrollBar)
    {
	if (IsTrue(PressObject(scrollBar, x, y, flags)))
	{
	    return ObjTrue;
	}
    }
    scrollBar = GetVar(object, VSCROLL);
    if (scrollBar)
    {
	if (IsTrue(PressObject(scrollBar, x, y, flags)))
	{
	    return ObjTrue;
	}
    }

    if (x >= left && x <= right && y >= bottom && y <= top)
    {
	/*Hey!  It really was a click in the field*/
	int xOff, yOff;
	ObjPtr contentsPressed = ObjFalse;
	int fieldDepth;
	if (GetPredicate(object, BORDERTYPE))
	{
	    fieldDepth = 1;
	}
	else
	{
	    fieldDepth = EDGE;
	}

	GETSCROLL(object, xOff, yOff);

	/*This doesn't really count as interactive drawing*/
	interactiveMoving = false;

	SetClipRect(left + fieldDepth, right - fieldDepth, bottom + fieldDepth, top - fieldDepth);
	if (GetPredicate(object, TOPDOWN))
	{
	    SetOrigin(left + fieldDepth + xOff, top - fieldDepth + yOff);
            x -= left + fieldDepth + xOff;
            y -= top - fieldDepth + yOff;
	}
	else
	{
	    SetOrigin(left + fieldDepth + xOff, bottom + fieldDepth + yOff);
            x -= left + fieldDepth + xOff;
            y -= bottom + fieldDepth + yOff;
	}
	
	pressContents = GetMethod(object, PRESSCONTENTS);
	if (pressContents)
	{
	    contentsPressed = (*pressContents)(object, x, y, flags);
	}
	if (!IsTrue(contentsPressed))
	{
	    if (TOOL(flags) == T_HELP)
	    {
		ContextHelp(object);
		return ObjTrue;
	    }
	}

	RestoreOrigin();
	RestoreClipRect();
	return ObjTrue;
    }
    else
    {
	return ObjFalse;
    }
}
#endif

#ifdef INTERACTIVE
static ObjPtr DropInCorral(object, dropObj, x, y)
ObjPtr object, dropObj;
int x, y;
/*Drops dropObj in corral object at x and y.  Returns
  true iff the drop really was in the corral.*/
{
    int left, right, bottom, top;

    Get2DIntBounds(object, &left, &right, &bottom, &top);

    if (x >= left && x <= right && y >= bottom && y <= top)
    {
	/*Hey!  It really was a drop in the corral*/
	ObjPtr contents;
	int xOff, yOff;
	ObjPtr firstIcon;
	ThingListPtr restIcons;
	ThingListPtr runner;
	ObjPtr iconLoc;
	real loc[2];
	int xDisp, yDisp;
	int fieldDepth;

	if (GetPredicate(object, BORDERTYPE))
	{
	    fieldDepth = 1;
	}
	else
	{
	    fieldDepth = EDGE;
	}

	GETSCROLL(object, xOff, yOff);

	SetClipRect(left + fieldDepth, right - fieldDepth, bottom + fieldDepth, top - fieldDepth);
	if (GetPredicate(object, TOPDOWN))
	{
	    SetOrigin(left + fieldDepth + xOff, top - fieldDepth + yOff);
	    y -= top - fieldDepth + yOff;
	}
	else
	{
	    SetOrigin(left + fieldDepth + xOff, bottom + fieldDepth + yOff);
	    y -= bottom + fieldDepth + yOff;
	}
        x -= left + fieldDepth + xOff;
	x += iconXOff;
	y += iconYOff;

	/*Get the first icon and the rest of the icons*/
	if (IsList(dropObj))
	{
	    restIcons = LISTOF(dropObj);
	    firstIcon = restIcons -> thing;
	    restIcons = restIcons -> next;
	}
	else if (IsIcon(dropObj))
	{
	    firstIcon = dropObj;
	    restIcons = 0;
	}
	else
	{
	    ReportError("DropInCorral", "An object not an icon was dropped");
	    return ObjFalse;
	}

	/*Get the location of the first icon*/
	iconLoc = GetFixedArrayVar("DropInCorral", firstIcon, ICONLOC, 1, 2L);
	if (!iconLoc)
	{
	    return ObjFalse;
	}
	Array2CArray(loc, iconLoc);
	xDisp = x - loc[0];
	yDisp = y - loc[1];

	/*See if the first icon is already in the contents*/
	contents = GetVar(object, CONTENTS);
	if (contents && IsList(contents))
	{
	    ThingListPtr list;

	    list = LISTOF(contents);
	    while (list)
	    {
		if (list -> thing == firstIcon)
		{
		    /*Hey, it's just a move*/
		    real loc[2];
		    ObjPtr array;
		    loc[0] = x;
		    loc[1] = y;

		    array = NewRealArray(1, 2L);
		    CArray2Array(array, loc);
		    SetVar(firstIcon, ICONLOC, array);

		    runner = restIcons;
		    while (runner)
		    {
			array = GetFixedArrayVar("DropInCorral", runner -> thing, ICONLOC, 1, 2L);
			if (!array) return ObjFalse;
			Array2CArray(loc, array);
			loc[0] += xDisp;
			loc[1] += yDisp;
			array = NewRealArray(1, 2L);
			CArray2Array(array, loc);
			SetVar(runner -> thing, ICONLOC, array);
			runner = runner -> next;
		    }

		    RecalcScroll(object);
		    ImInvalid(object);

		    break;
		}
		list = list -> next;
	    }
	    if (!list)
	    {
		FuncTyp method;
		ObjPtr array;
		real loc[2];
		ObjPtr hScroll, vScroll;
		
		/*It must not have been already in the corral.  Give the
		  corral a chance to act on it*/
		hScroll = GetVar(object, HSCROLL);
		vScroll = GetVar(object, VSCROLL);
		method = GetMethod(object, DROPINCONTENTS);
		if (method)
		{
		    (*method)(object, firstIcon, x, y);
		    runner = restIcons;
		    while (runner)
		    {
			array = GetFixedArrayVar("DropInCorral", runner -> thing, ICONLOC, 1, 2L);
			if (!array) return ObjFalse;
			Array2CArray(loc, array);
			loc[0] += xDisp;
			loc[1] += yDisp;

			/*Trim location to be inside corral*/
			if (!hScroll)
			{
			    if (loc[0] < left - EDGE) loc[0] = left - EDGE;
			    else if (loc[0] > right - EDGE) loc[0] = right - EDGE;
			}
			if (!vScroll)
			{
			    if (loc[1] < bottom - EDGE) loc[1] = bottom - EDGE;
			    else if (loc[1] > top - EDGE) loc[1] = top - EDGE;
			}

			(*method)(object, runner -> thing, (int) loc[0], (int) loc[1]);
			runner = runner -> next;
		    }
		    ImInvalid(object);
		    RecalcScroll(object);
		}
	    }
	}
	RestoreOrigin();
	RestoreClipRect();

	return ObjTrue;
    }
    else
    {
	return ObjFalse;
    }
}
#endif

#ifdef INTERACTIVE
Bool DragIcons(objects, x, y)
ObjPtr objects;
int x, y;
/*Drags an icon starting at x and y
  Returns true if it succeeded*/
{
    Bool dragging;		/*True iff dragging*/
    int xDisp, yDisp;		/*X and Y displacements from global to local*/
    int xOff, yOff;		/*X and Y offsets for drawing icon*/
    int newX, newY;
    int lastX, lastY;
    ThingListPtr runner;	/*A runner for the list*/
    ObjPtr firstIcon;		/*The first icon in the list*/
    ThingListPtr restIcons;	/*The list of the rest of the icons*/
    ObjPtr locArray;
    real loc[2];		/*Location of the first icon*/

    if (IsList(objects))
    {
	/*It's a list of icons.*/
	restIcons = LISTOF(objects);
	firstIcon = restIcons -> thing;
	restIcons = restIcons -> next;
    }
    else
    {
	/*It's a single icon, kludge it up so that it will continue to work*/
	firstIcon = objects;
	restIcons = 0;
    }

    CurOffset(&xDisp, &yDisp);

    locArray = GetFixedArrayVar("DragIcons", firstIcon, ICONLOC, 1, 2L);
    if (!locArray)
    {
	return;
    }
    Array2CArray(loc, locArray);

    /*Set relative offset for icon*/
    iconXOff = loc[0] - x;
    iconYOff = loc[1] - y;

#ifdef INTERWINDRAG
    xOff = xDisp;
    yOff = yDisp;
#else
    xOff = 0;
    yOff = 0;
#endif
    xOff -= x;
    yOff -= y;

    dragging = false;
    
    lastX = x;
    lastY = y;
    while (Mouse(&newX, &newY))
    {
	if (ABS(newX - x) > 2 || ABS(newY - y) > 2)
	{
	    /*Start to drag*/
	    dragging = true;
	    pushattributes();
#ifdef INTERWINDRAG
	    FullScreen(true);
#endif
	    Mouse(&newX, &newY);
	    OverDraw(true);
	    SetUIColor(UIRED);

	    DrawIconGhost(firstIcon, newX + xOff, newY + yOff);
	    runner = restIcons;
	    while (runner)
	    {
		DrawIconGhost(runner -> thing, newX + xOff, newY + yOff);
		runner = runner -> next;
	    }
	    lastX = newX;
	    lastY = newY;
	    break;
	}
    }

    if (!dragging) return false;
    
    if (logging)
    {
	LogSelectedObjFunction(OF_PICK_UP);
    }

    while (Mouse(&newX, &newY))
    {
	if (newX != lastX || newY != lastY)
	{
	    /*Erase, move, and redraw*/
	    SetUIColor(UIBLACK);
	    DrawIconGhost(firstIcon, lastX + xOff, lastY + yOff);
	    runner = restIcons;
	    while (runner)
	    {
		DrawIconGhost(runner -> thing, lastX + xOff, lastY + yOff);
		runner = runner -> next;
	    }
	    SetUIColor(UIRED);
	    DrawIconGhost(firstIcon, newX + xOff, newY + yOff);
	    runner = restIcons;
	    while (runner)
	    {
		DrawIconGhost(runner -> thing, newX + xOff, newY + yOff);
		runner = runner -> next;
	    }

	    lastX = newX;
	    lastY = newY;
	}
    }
    SetUIColor(UIBLACK);
    DrawIconGhost(firstIcon, lastX + xOff, lastY + yOff);
    runner = restIcons;
    while (runner)
    {
	DrawIconGhost(runner -> thing, lastX + xOff, lastY + yOff);
	runner = runner -> next;
    }
    if (overDraw)
    {
	EraseAll();
	OverDraw(false);
    }
    if (dragging)
    {
#ifdef INTERWINDRAG
	FullScreen(false);
#endif
	popattributes();
    }
    gconfig();
    Mouse(&dropX, &dropY);
    dropX += xDisp;
    dropY += yDisp;
    return true;
}
#endif

static ObjPtr SelectIcon(object)
ObjPtr object;
/*Selects an object if it's an icon and represents something.  Only works
  as a parameter to ForAllObjects*/
{
    if (IsIcon(object) && GetVar(object, REPOBJ) && !IsSelected(object))
    {
	Select(object, true);
	return ObjTrue;
    }
    else
    {
	return ObjFalse;
    }
}

void SelectAllIcons(window)
ObjPtr window;
/*Selects all icons in the window*/
{
    if (logging)
    {
	if (scriptSelectP)
	{
	    Log("selectall\n");
	}
	InhibitLogging(true);
    }
    ForAllObjects(window, SelectIcon);
    if (logging)
    {
	InhibitLogging(false);
    }
}

static ObjPtr DeselectIcon(object)
ObjPtr object;
/*Deselects an object if it's an icon and represents something.  Only works
  as a parameter to ForAllObjects*/
{
    if (IsIcon(object) && GetVar(object, REPOBJ) && IsSelected(object))
    {
	Select(object, false);
	return ObjTrue;
    }
    else
    {
	return ObjFalse;
    }
}

void DeselectAll()
/*Deselects all the currently selected objects*/
{
    ThingListPtr runner, next;

    if (scriptSelectP)
    {
	Log("deselectall\n");
    }

    runner = LISTOF(allSelected);

    while (runner)
    {
	next = runner -> next;
	Select(runner -> thing, false);
	runner = next;
    }
    MakeMeCurrent(NULLOBJ);
}

void DoSelectAllIcons()
/*Selects all icons in the current window*/
{
    if (!selWinInfo)
    {
	return;
    }
    SelectAllIcons((ObjPtr) selWinInfo);
}

static Bool IsIconSelected(icon)
ObjPtr icon;
/*Returns true iff icon is selected*/
{
    ObjPtr repObj;
    repObj = GetVar(icon, REPOBJ);
    if (repObj) return IsSelected(repObj);
    else return IsSelected(icon);
}

#ifdef INTERACTIVE
static ObjPtr PressCorralContents(corral, x, y, flags)
ObjPtr corral;
int x, y;
long flags;
/*Does a press in the contents of a corral*/
{
    ObjPtr contents;
    ObjPtr pressedIcon = 0;	/*The icon pressed in*/
    ThingListPtr runner;
    FuncTyp method;

    contents = GetListVar("PressCorralContents", corral, CONTENTS);
    if (!contents) return ObjFalse;

    runner = LISTOF(contents);
    while (runner)
    {
    	ObjPtr icon;
	ObjPtr theLoc;
	real loc[2];

	icon = runner -> thing;
	theLoc = GetFixedArrayVar("PressCorralContents", icon, ICONLOC, 1, 2L);
	Array2CArray(loc, theLoc);

	if (x >= ((int) loc[0]) - 16 && x <= ((int) loc[0]) + 16 && 
	    y >= ((int) loc[1]) - 32 && y <= ((int) loc[1]) + 16)
	{
	    pressedIcon = icon;
	}

	runner = runner -> next;
    }

    if (pressedIcon)
    {
	Bool wasSelected;

	if (TOOL(flags) == T_HELP)
	{
	    ContextHelp(pressedIcon);
	    return ObjTrue;
	}

	wasSelected = IsIconSelected(pressedIcon);

	/*Something has been pressed.  
	  Deselect all other objects IF
	  1) It's a oneIcon field, or
	  2) It's not a oneIcon field and
	     the shift key is up and
	     the icon was not previously selected*/

	if (!wasSelected &&
	    (0 == (flags & F_SHIFTDOWN)) &&
	    (TOOL(flags) != T_ROTATE) &&
	    (0 == (flags & F_DOUBLECLICK)))
	{
	    DeselectAll();
	}

	/*Select or deselect the icon*/
	if (wasSelected)
        {
	    /*Already selected.  Only deselect if shift down*/
	    if ((flags & F_SHIFTDOWN) || (TOOL(flags) == T_ROTATE))
	    {
		Select(pressedIcon, false);
		pressedIcon = NULLOBJ;
		DrawMe(pressedIcon);
	    }
	}
	else
	{
	    Select(pressedIcon, true);
	    DrawMe(pressedIcon);
	}
    }
    else
    {
	/*Start a marquee drag*/
	int newX, newY;
	int curX, curY;	
	ObjPtr marquee;
	real marqueeBounds[4];
	Bool yesMarquee = false;

	curX = x;
	curY = y;

	if (TOOL(flags) == T_HELP)
	{
	    ContextHelp(corral);
	    return ObjTrue;
	}

	/*Deselect all other icons if the shift key is not down*/
	if (0 == (flags & F_SHIFTDOWN) && TOOL(flags) != T_ROTATE)
	{
	    DeselectAll();
	    DrawMe(corral);
	}
	Mouse(&newX, &newY);

	if (hasOverDraw)
	{
	    OverDraw(true);
	}
	while (Mouse(&newX, &newY))
	{
	    if (newX != curX || newY != curY)
	    {
		yesMarquee = true;
		curX = newX;
		curY = newY;
		if (curX > x)
		{
		    marqueeBounds[0] = x;
		    marqueeBounds[1] = curX;
		}
		else if (x > curX)
		{
		    marqueeBounds[0] = curX;
		    marqueeBounds[1] = x;
		}
		else
		{
		    yesMarquee = false;
		}

		if (curY > y)
		{
		    marqueeBounds[2] = y;
		    marqueeBounds[3] = curY;
		}
		else if (y > curY)
		{
		    marqueeBounds[2] = curY;
		    marqueeBounds[3] = y;
		}
		else
		{
		    yesMarquee = false;
		}
		
		if (yesMarquee)
		{
		    marquee = NewRealArray(1, 4L);
		    CArray2Array(marquee, marqueeBounds);
		}
		else
		{
		    marquee = NULLOBJ;
		}

		if (hasOverDraw)
		{
#ifdef GRAPHICS
		    EraseAll();
#endif
		    FrameUIRect(marqueeBounds[0], marqueeBounds[1],
			        marqueeBounds[2], marqueeBounds[3], UIRED);
		}
		else
		{
		    SetVar(corral, MARQUEE, marquee);
		    DrawMe(corral);
		}
	    }
	}
	if (hasOverDraw)
	{
	    EraseAll();
	    OverDraw(false);
	}
	SetVar(corral, MARQUEE, NULLOBJ);

	if (yesMarquee)
	{
	    /*Go throught list of icons to determine which ones should be pressed*/
	    runner = LISTOF(contents);
	    while (runner)
	    {
		ObjPtr icon;
		ObjPtr theLoc;
		real loc[2];

		icon = runner -> thing;
		theLoc = GetFixedArrayVar("PressCorralContents", icon, ICONLOC, 1, 2L);
		Array2CArray(loc, theLoc);

		if (loc[0] > marqueeBounds[0] - 16 &&
		    loc[0] < marqueeBounds[1] + 16 &&
		    loc[1] > marqueeBounds[2] - 8 &&
		    loc[1] < marqueeBounds[3] + 32)
		{
		    /*Select or deselect the icon*/
		    if (IsIconSelected(icon))
        	    {
			/*Already selected.  Only deselect if shift down*/
			if ((flags & F_SHIFTDOWN) || (TOOL(flags) == T_ROTATE))
			{
			    Select(icon, false);
			}
		    }
		    else
		    {
			Select(icon, true);
		    }
		}

		runner = runner -> next;
	    }
	}

	DrawMe(corral);
    }

    if (pressedIcon)
    {
	if ((flags & F_DOUBLECLICK) && IsIconSelected(pressedIcon))
	{
	    /*Do it's doubleclick function*/
	    DoDoubleClickFunction();
	}
	else
	{
	    /*Construct a list of icons and drag it*/
	    ObjPtr iconList;
	    iconList = NewList();

	    runner = LISTOF(contents);
	    while (runner)
	    {
		if (IsIconSelected(runner -> thing) &&
		    runner -> thing != pressedIcon)
		{
		    PrefixList(iconList, runner -> thing);
		}
		runner = runner -> next;
	    }
	    PrefixList(iconList, pressedIcon);
	    if (DragIcons(iconList, x, y))
	    {
		dropObject = iconList;
		AddToReferenceList(iconList);
	    }
	}
    }

    return ObjTrue;
}
#endif

#ifdef GRAPHICS
ObjPtr DrawField(object)
ObjPtr object;
/*Draws a field*/
{
    int left, right, bottom, top;
    ObjPtr backColor;			/*Color of the background*/
    FuncTyp drawContents;		/*Routine to draw the contents*/
    ObjPtr scrollBar;			/*Temporary place for the scroll bar*/

    Get2DIntBounds(object, &left, &right, &bottom, &top);
    if (!IsDrawingRestricted(left, right, bottom, top))
    {

    /*Draw the background*/
    backColor = (ObjPtr) GetVar(object, BACKGROUND);
    if (backColor)
    {
	SetObjectColor(backColor);
    }
    else
    {
	SetUIColor(UIICONPIT);
    }
    DrawInsetRect(left, right, bottom, top);
    SetClipRect(left + EDGE, right - EDGE, bottom + EDGE, top - EDGE);
    
    drawContents = GetMethod(object, DRAWCONTENTS);
    if (drawContents)
    {
	int xOff, yOff;
	int fieldDepth;
	GETSCROLL(object, xOff, yOff);
	if (GetPredicate(object, BORDERTYPE))
	{
	    fieldDepth = 1;
	}
	else
	{
	    fieldDepth = EDGE;
	}

	if (GetPredicate(object, TOPDOWN))
	{
	    SetOrigin(left + fieldDepth + xOff, top - fieldDepth + yOff);
	}
	else
	{
	    SetOrigin(left + fieldDepth + xOff, bottom + fieldDepth + yOff);
	}
	(*drawContents)(object);
	RestoreOrigin();
    }

    RestoreClipRect();
    DrawSunkenEdge(left, right, bottom, top);
    }

    scrollBar = GetVar(object, HSCROLL);
    if (scrollBar)
    {
	DrawObject(scrollBar);
    }

    scrollBar = GetVar(object, VSCROLL);
    if (scrollBar)
    {
	DrawObject(scrollBar);
    }
    return ObjTrue;
}
#endif

#ifdef GRAPHICS
ObjPtr DrawControlField(object)
ObjPtr object;
/*Draws a control field*/
{
    int left, right, bottom, top;
    ObjPtr backColor;			/*Color of the background*/
    FuncTyp drawContents;		/*Routine to draw the contents*/
    ObjPtr scrollBar;			/*Temporary place for the scroll bar*/
    ObjPtr borderType;			/*Type of border*/
    int fieldDepth;

    Get2DIntBounds(object, &left, &right, &bottom, &top);

    /*Draw the border*/
    borderType = GetVar(object, BORDERTYPE);
    if (borderType)
    {
	FrameUIRect(left, right, bottom, top, UIBLACK);
    }

    if (borderType)
    {
	fieldDepth = 1;
    }
    else
    {
	fieldDepth = EDGE;
    }

    SetClipRect(left + fieldDepth, right - fieldDepth,
		bottom + fieldDepth, top - fieldDepth);

    /*Get the color, if any, and draw it*/
    backColor = (ObjPtr) GetVar(object, BACKGROUND);
    if (backColor)
    {
	SetObjectColor(backColor);
    }
    else
    {
	SetUIColor(UICONTROLTRACK);
    }
    FillRect(left + fieldDepth, right - fieldDepth,
		bottom + fieldDepth, top - fieldDepth);

    /*Draw the contents of the panel*/
    drawContents = GetMethod(object, DRAWCONTENTS);
    if (drawContents)
    {
	int xOff, yOff;

	GETSCROLL(object, xOff, yOff);

	if (GetPredicate(object, TOPDOWN))
	{
	    SetOrigin(left + fieldDepth + xOff, top - fieldDepth + yOff);
	}
	else
	{
	    SetOrigin(left + fieldDepth + xOff, bottom + fieldDepth + yOff);
	}

	(*drawContents)(object);
	RestoreOrigin();
    }

    RestoreClipRect();
    if (!borderType)
    {
	DrawSunkenEdge(left, right, bottom, top);
    }

    scrollBar = GetVar(object, HSCROLL);
    if (scrollBar)
    {
	DrawObject(scrollBar);
    }

    scrollBar = GetVar(object, VSCROLL);
    if (scrollBar)
    {
	DrawObject(scrollBar);
    }
    return ObjTrue;
}
#endif

#ifdef GRAPHICS 
static ObjPtr DrawControlFieldContents(object)
ObjPtr object;
/*Draws a control field's contents*/
{
    ObjPtr contents;
    contents = GetVar(object, CONTENTS);
    if (contents)
    {
	DrawList(contents);
    }
    return ObjTrue;
}
#endif

#ifdef GRAPHICS
static ObjPtr DrawCorralContents(object)
ObjPtr object;
/*Draws the contents of an icon corral*/
{
    ObjPtr marquee;
    ObjPtr var;
    int viewBy;

    var = GetVar(object, VIEWBYWHAT);
    if (var)
    {
	viewBy = GetInt(var);
    }
    else
    {
	viewBy = VB_ICON;
    }

    if (viewBy == VB_ICON)
    {
	/*View by icon.  Just let the icons draw themselves*/
	real marqueeBounds[4];
	ObjPtr contents;
	Bool topDown;

	marquee = GetVar(object, MARQUEE);
	if (marquee)
	{
	    Array2CArray(marqueeBounds, marquee);
	    FrameUIRect(marqueeBounds[0] + 1.0,
			marqueeBounds[1] + 1.0,
			marqueeBounds[2] - 1.0,
			marqueeBounds[3] - 1.0, UIBLACK);
	    
	    FrameUIRect(marqueeBounds[0],
			marqueeBounds[1],
			marqueeBounds[2],
			marqueeBounds[3], UIWHITE);
	}

	/*Now draw the contents*/

	topDown = GetPredicate(object, TOPDOWN);

	contents = GetVar(object, CONTENTS);
	if (contents)
	{
	    ThingListPtr runner;
	    ObjPtr iconLoc;
	    Bool anyNull;
	    Bool anyNotNull;
	    real loc[2];
	    int minX, maxX, minY, maxY;
	    int x, y;
	    int xLeft, xRight;
	    int k;

	    /*See if any of the icons has a NULL ICONLOC*/
	    runner = LISTOF(contents);
	    anyNull = false;
	    anyNotNull = false;
	    while (runner)
	    {
		iconLoc = GetVar(runner -> thing, ICONLOC);
		if (iconLoc)
		{
		    Array2CArray(loc, iconLoc);
		    if (anyNotNull)
		    {
			/*Just another non-null location*/
			minX = MIN(minX, loc[0]);
			maxX = MAX(maxX, loc[0]);
			minY = MIN(minY, loc[1]);
			maxY = MAX(maxY, loc[1]);
		    }
		    else
		    {
			/*First non-null location*/
			minX = maxX = loc[0];
			minY = maxY = loc[1];
		        anyNotNull = true;
		    }
		}
		else
		{
		    anyNull = true;
		}
		runner = runner -> next;
	    }
	    if (anyNull)
	    {
		/*There must be at least some icons whose location is not
		  set*/
		int left, right, bottom, top;
		Bool stagger;

		stagger = GetPrefTruth(PREF_STAGGERICONS);

		Get2DIntBounds(object, &left, &right, &bottom, &top);
		
		if (GetPredicate(object, SINGLECORRAL))
		{
		    x = (right - left) / 2;
		    y = topDown ?
			(bottom - top) / 2 + 25 :
			(top - bottom) / 2 + 25;
		}
		else
		{
		    /*Find starting x and y below existing*/

		    xLeft = ICONLEFTBORDER;
		    xRight = right - left - ICONLEFTBORDER;
		    if (xRight < xLeft) xRight = xLeft;

		    if (!anyNotNull)
		    {
			/*They're all null, pick a reasonable start*/
			
			if (topDown)
			{
			    y = -ICONTOPBORDER;
			}
			else
			{
			    y = ICONBOTBORDER;
			}
		    }
		    else
		    {
			if (topDown)
			{
			    k = (-minY - ICONTOPBORDER + ICONYSPACE / 2) / ICONYSPACE;
			    ++k;
			    y = -ICONTOPBORDER - k * ICONYSPACE;
			}
			else
			{
			    k = (maxY - ICONBOTBORDER + ICONYSPACE / 2) / ICONYSPACE;
			    ++k;
			    y = k * ICONYSPACE + ICONBOTBORDER;
			}
		    }

		    x = xLeft;
		}

		/*Now go through list and find locations for the icons*/
		runner = LISTOF(contents);
		
		k = 0;
		while (runner)
		{
		    if (GetVar(runner -> thing, ICONLOC) == NULLOBJ)
		    {
			loc[0] = x;
			loc[1] = ((k & 1) && stagger) ? (y + (topDown ? -ICONYSPACE / 2 : ICONYSPACE / 2)) : y;
;
			iconLoc = NewRealArray(1, 2L);
			CArray2Array(iconLoc, loc);
			SetVar(runner -> thing, ICONLOC, iconLoc);

			x += ICONXSPACE;
			++k;
			if (x > xRight)
			{
			    x = xLeft;
			    y = topDown ? y - ICONYSPACE : y + ICONYSPACE;
			    k = 0;
			}
		    }
		    runner = runner -> next;
		}
		RecalcScroll(object);
	    }

	    DrawList(contents);
	}
    }
    return ObjTrue;
}
#endif

#ifdef GRAPHICS
static ObjPtr DrawSwitch(object)
ObjPtr object;
/*Draws a switch*/
{
    int left, right, bottom, top;
    ObjPtr var;
    int nCells;
    int outCell;
    int value;
    int k, x, y, cl, cr, c, y2;
    
    Get2DIntBounds(object, &left, &right, &bottom, &top);

    /*Draw the background*/
    FillUIRect(left, right, bottom, top, UIBACKGROUND);

    /*Get the parameters*/
    var = GetVar(object, NCELLS);
    if (!var || !IsInt(var))
    {
	return;
    }
    nCells = GetInt(var);

    var = GetVar(object, OUTCELL);
    if (!var || !IsInt(var))
    {
	return;
    }
    outCell = GetInt(var);

    var = GetVar(object, VALUE);
    if (!var || !IsInt(var))
    {
	return;
    }
    value = GetInt(var);

    /*Set up temporary vars to make drawing easier*/
    c = (left + right) / 2 - SWITCHSLOPLEFT;
    if (c < left + SWITCHRADIUS) c = left + SWITCHRADIUS;
    cl = c - SWITCHRADIUS;
    cr = c + SWITCHRADIUS;

    /*Draw the background*/
    SetUIColor(UIBLACK);
    for (k = 0; k < nCells; ++k)
    {
	y = top - (top - bottom) * k / nCells - (top - bottom) / nCells / 2;

	DrawUILine(left, y, cl, y, UIBLACK);
	if (k < outCell)
	{
	    /*Draw a curve downward*/
	    DrawArc(cl, y - SWITCHRADIUS, SWITCHRADIUS, 0, 90);
	}
	else if (k > outCell)
	{
	    /*Draw a curve upward*/
	    DrawArc(cl, y + SWITCHRADIUS, SWITCHRADIUS, -90, 0);
	}
	else
	{
	    /*Draw a junction*/
	    if (k > 0)
	    {
		/*Something coming in from above*/
		DrawArc(cr, y + SWITCHRADIUS, SWITCHRADIUS, 180, 270);
	    }
	    DrawUILine(cl, y, right, y, UIBLACK);
	    if (k < nCells - 1)
	    {
		/*Something coming in from below*/
		DrawArc(cr, y - SWITCHRADIUS, SWITCHRADIUS, 90, 180);
	    }
	}
    }

    /*Draw the vertical lines*/
    if (outCell > 0)
    {
	/*There's a top part*/
	y = top - (top - bottom) / nCells / 2;
	y2 = top - (top - bottom) * outCell / nCells - (top - bottom) / nCells / 2;
	DrawUILine(c, y - SWITCHRADIUS, c, y2 + SWITCHRADIUS, UIBLACK);
    }

    if (outCell < nCells - 1)
    {
	/*There's a bottom part*/
	y = top - (top - bottom) * outCell / nCells - (top - bottom) / nCells / 2;
	y2 = top - (top - bottom) * (nCells - 1) / nCells - (top - bottom) / nCells / 2;
	DrawUILine(c, y - SWITCHRADIUS, c, y2 + SWITCHRADIUS, UIBLACK);
    }

    /*Draw the highlighted path*/
    if (value >= 0)
    {
	SetLineWidth(3);

	if (value < outCell)
	{
	    /*Over, down, over*/
	    y = top - (top - bottom) * value / nCells - (top - bottom) / nCells / 2;
	    y2 = top - (top - bottom) * outCell / nCells - (top - bottom) / nCells / 2;
	    DrawUILine(left, y, cl, y, UIBLACK);
	    DrawArc(cl, y - SWITCHRADIUS, SWITCHRADIUS, 0, 90);
	    DrawUILine(c, y - SWITCHRADIUS, c, y2 + SWITCHRADIUS, UIBLACK);
	    DrawArc(cr, y2 + SWITCHRADIUS, SWITCHRADIUS, 180, 270);
	    DrawUILine(cr, y2, right - 2, y2, UIBLACK);
	}
	else if (value > outCell)
	{
	    /*Over, up, over*/
	    y = top - (top - bottom) * value / nCells - (top - bottom) / nCells / 2;
	    y2 = top - (top - bottom) * outCell / nCells - (top - bottom) / nCells / 2;
	    DrawUILine(left, y, cl, y, UIBLACK);
	    DrawArc(cl, y + SWITCHRADIUS, SWITCHRADIUS, -90, 0);
	    DrawUILine(c, y + SWITCHRADIUS, c, y2 - SWITCHRADIUS, UIBLACK);
	    DrawArc(cr, y2 - SWITCHRADIUS, SWITCHRADIUS, 90, 180);
	    DrawUILine(cr, y2, right - 2, y2, UIBLACK);
	}
	else
	{
	    /*Straight over*/
	    y2 = top - (top - bottom) * value / nCells - (top - bottom) / nCells / 2;
	    DrawUILine(left, y2, right - 2, y2, UIBLACK);
	}
	SetLineWidth(1);

	/*Draw half the arrow head*/
	FillTri(right - ARROWLENGTH, y2, right - ARROWLENGTH, y2 - ARROWHEIGHT, right, y2);

	/*Draw half the arrow head*/
	FillTri(right - ARROWLENGTH, y2, right - ARROWLENGTH, y2 + ARROWHEIGHT, right, y2);
    }
    else
    {
	/*Draw a single completion outcell*/
	y2 = top - (top - bottom) * outCell / nCells - (top - bottom) / nCells / 2;
	DrawUILine(cr, y2, right, y2, UIBLACK);
    }
    return ObjTrue;
}
#endif

#ifdef INTERACTIVE
static ObjPtr PressSwitch(object, mouseX, mouseY, flags)
ObjPtr object;
int mouseX, mouseY;
long flags;
/*Press in a switch*/
{
    int left, right, bottom, top;
    ObjPtr backColor;			/*Color of the background*/
    FuncTyp drawContents;		/*Routine to draw the contents*/
    ObjPtr var;
    int value;
    int trackCell;			/*The current cell to track*/
    int lastCell;			/*The last cell tracked in*/
    int nCells;				/*Number of cells in the switch*/

    Get2DIntBounds(object, &left, &right, &bottom, &top);

    if (mouseX < left || mouseX > right || mouseY < bottom || mouseY > top)
    {
	return ObjFalse;
    }

    if (TOOL(flags) == T_HELP)
    {
	ContextHelp(object);
	return ObjTrue;
    }

    SaveForUndo(object);

    /*Get the current value of the control*/
    var = GetIntVar("PressSwitch", object, VALUE);
    if (!var)
    { 
	return ObjFalse;
    }
    value = GetInt(var);

    /*Get ncells*/
    var = GetIntVar("PressSwitch", object, NCELLS);
    if (!var)
    { 
	return ObjFalse;
    }
    nCells = GetInt(var);

    /*Get lastCell*/
    trackCell = lastCell = nCells - 1 - (mouseY - bottom) * nCells / (top - bottom);
    if (lastCell >= nCells) lastCell = nCells - 1;
    if (lastCell < 0) lastCell = 0;
    SetVar(object, VALUE, NewInt(lastCell));
    DrawMe(object);

    InhibitLogging(true);
    while (Mouse(&mouseX, &mouseY))
    {
	if (mouseX < left || mouseX > right || mouseY < bottom || mouseY > top)
	{
	    trackCell = value;
	}
	else
	{
	    trackCell = nCells - 1 - (mouseY - bottom) * nCells / (top - bottom);
	    if (trackCell >= nCells) trackCell = nCells - 1;
	    if (trackCell < 0) trackCell = 0;
	}
	if (trackCell != lastCell)
	{
	    lastCell = trackCell;
	    SetVar(object, VALUE, NewInt(trackCell));
	    DrawMe(object);
	}
    }
    InhibitLogging(false);
    if (logging)
    {
	LogControl(object);
    }
    if (logging || (trackCell != value))
    {
	ChangedValue(object);
    }
    return ObjTrue;
}
#endif

ObjPtr RecalcCorralScroll(corral)
ObjPtr corral;
/*Recalculates the scroll parameters in an icon corral*/
{
    real minx, maxx, miny, maxy;
    real iconLoc[2], bounds[4];
    Bool boundsSet;
    int left, right, bottom, top;
    ObjPtr var, vScroll, hScroll;
    ObjPtr contents;
    ThingListPtr list;
    int fieldDepth;
    
    if (GetPredicate(corral, BORDERTYPE))
    {
	fieldDepth = 1;
    }
    else
    {
	fieldDepth = EDGE;
    }

    /*Get bounds*/
    Get2DIntBounds(corral, &left, &right, &bottom, &top);
    bounds[0] = left;
    bounds[1] = right;
    bounds[2] = bottom;
    bounds[3] = top;

    /*Look through icons*/
    contents = GetListVar("RecalcCorralScroll", corral, CONTENTS);
    if (!contents) return ObjFalse;
    list = LISTOF(contents);

    boundsSet = false;
    /*Go through the rest of the icons*/
    while (list)
    {
	var = GetVar(list -> thing, ICONLOC);
	if (var)
	{
            Array2CArray(iconLoc, var);

            if (boundsSet)
            {

                if (iconLoc[0] - ICONLEFTBORDER < minx)
                {
                    minx = iconLoc[0] - ICONLEFTBORDER;
                }
                if (iconLoc[0] + ICONRIGHTBORDER > maxx)
                {
                    maxx = iconLoc[0] + ICONRIGHTBORDER;
                }
                if (iconLoc[1] - ICONBOTBORDER < miny)
                {
                    miny = iconLoc[1] - ICONBOTBORDER;
                }
                if (iconLoc[1] + ICONTOPBORDER > maxy)
                {
                    maxy = iconLoc[1] + ICONTOPBORDER;
                }
            }
            else
            {
                minx = iconLoc[0] - ICONLEFTBORDER;
                maxx = iconLoc[0] + ICONRIGHTBORDER;
                miny = iconLoc[1] - ICONBOTBORDER;
                maxy = iconLoc[1] + ICONTOPBORDER;
                boundsSet = true;
            }
	}
	list = list -> next;
    }

    if (boundsSet)
    {
    /*Adjust hscroll*/
    hScroll = GetVar(corral, HSCROLL);
    if (hScroll)
    {
	SetPortionShown(hScroll, bounds[1] - bounds[0] - fieldDepth * 2);
	SetSliderRange(hScroll, maxx - (bounds[1] - bounds[0]) - fieldDepth * 2, minx, 20.0);
    }

    /*Adjust vscroll*/
    vScroll = GetVar(corral, VSCROLL);
    if (vScroll)
    {
	SetPortionShown(vScroll, bounds[3] - bounds[2] - fieldDepth * 2);
	if (GetPredicate(corral, TOPDOWN))
	{
	    SetSliderRange(vScroll, maxy, miny + (bounds[3] - bounds[2]) - fieldDepth * 2, 20.0);
	}
	else
	{
	    SetSliderRange(vScroll, maxy - (bounds[3] - bounds[2]) - fieldDepth * 2, miny, 20.0);
	}
    }

    /*Set the virtual bounds*/
    if (hScroll) bounds[1] -= BARWIDTH + CORRALBARBORDER;
    if (vScroll) bounds[3] -= BARWIDTH + CORRALBARBORDER;
    if (minx < bounds[0]) bounds[0] = minx;
    if (maxx > bounds[1]) bounds[1] = maxx;
    if (miny < bounds[2]) bounds[2] = miny;
    if (maxy > bounds[3]) bounds[3] = maxy;
    var = NewRealArray(1, 4L);
    CArray2Array(var, bounds);
    SetVar(corral, VIRTUALBOUNDS, var);
    }

    return ObjTrue;
}

ObjPtr RecalcControlFieldScroll(field)
ObjPtr field;
/*Recalculates the scroll parameters in a control field*/
{
    int minx, maxx, miny, maxy;
    real bounds[4];
    int left, right, bottom, top;
    ObjPtr var, vScroll, hScroll;
    ObjPtr contents;
    ThingListPtr list;
    int fieldDepth;
    
    if (GetPredicate(field, BORDERTYPE))
    {
	fieldDepth = 1;
    }
    else
    {
	fieldDepth = EDGE;
    }
    
    /*Get bounds*/
    Get2DIntBounds(field, &left, &right, &bottom, &top);
    bounds[0] = left;
    bounds[1] = right;
    bounds[2] = bottom;
    bounds[3] = top;

    /*Look through objects*/
    contents = GetListVar("RecalcControlFieldScroll", field, CONTENTS);
    if (!contents) return ObjFalse;
    list = LISTOF(contents);

    /*Get first object*/
    if (list)
    {
	Get2DIntBounds(list -> thing, &left, &right, &bottom, &top);
	list = list -> next;
    }
    else
    {
	left = right = 0;
	bottom = top = 0;
    }
    minx = left - MINORBORDER;
    maxx = right + MINORBORDER;
    miny = bottom - MINORBORDER;
    maxy = top + MINORBORDER;

    /*Go through the rest of the objects*/
    while (list)
    {
	Get2DIntBounds(list -> thing, &left, &right, &bottom, &top);

	minx = MIN(minx, left - MINORBORDER);
	maxx = MAX(maxx, right + MINORBORDER);
	miny = MIN(miny, bottom - MINORBORDER);
	maxy = MAX(maxy, top + MINORBORDER);
	list = list -> next;
    }

    /*Adjust hscroll*/
    hScroll = GetVar(field, HSCROLL);
    if (hScroll)
    {
	SetPortionShown(hScroll, bounds[1] - bounds[0] - fieldDepth * 2);
	SetSliderRange(hScroll, maxx - (bounds[1] - bounds[0] - fieldDepth * 2), minx, 20.0);
    }

    /*Adjust vscroll*/
    vScroll = GetVar(field, VSCROLL);
    if (vScroll)
    {
	SetPortionShown(vScroll, bounds[3] - bounds[2] - fieldDepth * 2);
	SetSliderRange(vScroll, maxy, miny + (bounds[3] - bounds[2]) - fieldDepth * 2, 20.0);
    }

    /*Set the virtual bounds*/
    if (hScroll) bounds[1] -= BARWIDTH + CORRALBARBORDER;
    if (vScroll) bounds[3] -= BARWIDTH + CORRALBARBORDER;

    if (minx < bounds[0]) bounds[0] = minx;
    if (maxx > bounds[1]) bounds[1] = maxx;
    if (miny < bounds[2]) bounds[2] = miny;
    if (maxy > bounds[3]) bounds[3] = maxy;
    var = NewRealArray(1, 4L);
    CArray2Array(var, bounds);
    SetVar(field, VIRTUALBOUNDS, var);

    return ObjTrue;
}

ObjPtr SetSwitchVal(object, value)
ObjPtr object;
ObjPtr value;
/*Sets the value of the object to value*/
{
    if (IsInt(value))
    {
	int newValue, oldValue;
	ObjPtr var;
	newValue = GetInt(value);
	var = GetIntVar("SetSwitchVal", object, VALUE);
	if (!var) return false;
	oldValue = GetInt(var);
	if (newValue != oldValue)
	{
	    SetVar(object, VALUE, NewInt(newValue));
	    ImInvalid(object);
	    ChangedValue(object);
	}
	if (logging)
	{
	    LogControl(object);
	}
	return ObjTrue;
    }
    else if (IsReal(value))
    {
	int newValue, oldValue;
	ObjPtr var;
	newValue = (int) GetReal(value);
	var = GetIntVar("SetSwitchVal", object, VALUE);
	if (!var) return false;
	oldValue = GetInt(var);
	if (newValue != oldValue)
	{
	    SetVar(object, VALUE, NewInt(newValue));
	    ImInvalid(object);
	    ChangedValue(object);
	}
	if (logging)
	{
	    LogControl(object);
	}
	return ObjTrue;
    }
    else
    {
	return ObjFalse;
    }
}

ObjPtr ReshapeCorral(object, ol, or, ob, ot, left, right, bottom, top)
ObjPtr object;
int ol, or, ob, ot;
int left, right, bottom, top;
/*Reshapes object, which used to exist within owner with edges ol, or, ob, ot
  to one which exists within owner with edges left, right, bottom, top.*/
{
	ObjPtr boundsArray;
	ObjPtr stickyInt;
	real bounds[4];
	real oldWidth, oldHeight;	/*Old width and height*/
	Bool sideLocked[4];		/*True iff side is locked*/
	Bool xStretch, yStretch;	/*Stretchiness in x and y*/
	int stickiness;		/*Side stickiness of the object*/
	real oldBounds[4];		/*Old bounds of the object*/
	ObjPtr contents;		/*Contents of the object, if any*/
	ObjPtr scrollBar;		/*Scroll bar to reshape*/
	real scrollBounds[4];		/*Bounds of scroll bar*/
	real wr, hr;			/*Width and height ratios*/

	wr = ((real) (right - left)) / ((real) (or - ol));
	hr = ((real) (top - bottom)) / ((real) (ot - ob));

	boundsArray = GetVar(object, BOUNDS);
	if (!boundsArray || !IsRealArray(boundsArray) || RANK(boundsArray) != 1 ||
	    DIMS(boundsArray)[0] != 4)
	{
	    return ObjFalse;
	}
	Array2CArray(bounds, boundsArray);
	Array2CArray(oldBounds, boundsArray);
	oldWidth = bounds[1] - bounds[0];
	oldHeight = bounds[3] - bounds[2];

	/*Get the object's stickiness*/
	stickyInt = GetVar(object, STICKINESS);
	if (stickyInt && IsInt(stickyInt))
	{
	    stickiness = GetInt(stickyInt);
	}
	else
	{
	    stickiness = 0;
	}

	if ((stickiness & STICKYLEFT) || (stickiness & FLOATINGLEFT))
	{
	    if (stickiness & FLOATINGLEFT)
	    {
		bounds[0] = (bounds[0] - ol) * wr + left;
	    }
	    else
	    {
		bounds[0] += left - ol;
	    }
	    if (!((stickiness & STICKYRIGHT) || (stickiness & FLOATINGRIGHT)))
	    {
		bounds[1] = bounds[0] + oldWidth;
	    }
	}
	if ((stickiness & STICKYRIGHT) || (stickiness & FLOATINGRIGHT))
	{
	    if (stickiness & FLOATINGRIGHT)
	    {
		bounds[1] = (bounds[1] - ol) * wr + left;
	    }
	    else
	    {
		bounds[1] += right - or;
	    }
	    if (!((stickiness & STICKYLEFT) || (stickiness & FLOATINGLEFT)))
	    {
		bounds[0] = bounds[1] - oldWidth;
	    }
	}

	if ((stickiness & STICKYBOTTOM) || (stickiness & FLOATINGBOTTOM))
	{
	    if (stickiness & FLOATINGBOTTOM)
	    {
		bounds[2] = (bounds[2] - ob) * hr + bottom;
	    }
	    else
	    {
		bounds[2] += bottom - ob;
	    }
	    if (!((stickiness & STICKYTOP) || (stickiness & FLOATINGTOP)))
	    {
		bounds[3] = bounds[2] + oldHeight;
	    }
	}
	if ((stickiness & STICKYTOP) || (stickiness & FLOATINGTOP))
	{
	    if (stickiness & FLOATINGTOP)
	    {
		bounds[3] = (bounds[3] - ob) * hr + bottom;
	    }
	    else
	    {
		bounds[3] += top - ot;
	    }
	    if (!((stickiness & STICKYBOTTOM) || (stickiness & FLOATINGBOTTOM)))
	    {
		bounds[2] = bounds[3] - oldHeight;
	    }
	}

	/*We've got a new bounds, put it back*/
	boundsArray = NewRealArray(1, 4L);
	CArray2Array(boundsArray, bounds);
	SetVar(object, BOUNDS, boundsArray);

	/*Reshape the scroll bars*/
	scrollBar = GetVar(object, VSCROLL);
	if (scrollBar)
	{
	    /*Get the bounds*/
	    real width;
	    boundsArray = GetFixedArrayVar("ReshapeCorral", scrollBar, BOUNDS, 1, 4L);
	    if (!boundsArray) return ObjFalse;

	    Array2CArray(scrollBounds, boundsArray);
	    width = scrollBounds[1] - scrollBounds[0];
	    if (scrollBounds[0] > oldBounds[1])
	    {
		/*It was on the right*/
		scrollBounds[0] = bounds[1] + (scrollBounds[0] - oldBounds[1]);
		scrollBounds[1] = scrollBounds[0] + width;
		scrollBounds[2] = bounds[2];
		scrollBounds[3] = bounds[3];
		
		boundsArray = NewRealArray(1, 4L);
		CArray2Array(boundsArray, scrollBounds);
		SetVar(scrollBar, BOUNDS, boundsArray);
	    }
	    else
	    {
		/*It was on the left*/
		scrollBounds[1] = bounds[0] + (scrollBounds[1] - oldBounds[0]);
		scrollBounds[0] = scrollBounds[1] - width;
		scrollBounds[2] = bounds[2];
		scrollBounds[3] = bounds[3];
		
		boundsArray = NewRealArray(1, 4L);
		CArray2Array(boundsArray, scrollBounds);
		SetVar(scrollBar, BOUNDS, boundsArray);
	    }
	}
	scrollBar = GetVar(object, HSCROLL);
	if (scrollBar)
	{
	    /*Get the bounds*/
	    real height;
	    boundsArray = GetFixedArrayVar("ReshapeCorral", scrollBar, BOUNDS, 1, 4L);
	    if (!boundsArray) return ObjFalse;

	    Array2CArray(scrollBounds, boundsArray);
	    height = scrollBounds[3] - scrollBounds[2];
	    if (scrollBounds[2] > oldBounds[3])
	    {
		/*It was on the top*/
		scrollBounds[0] = bounds[0];
		scrollBounds[1] = bounds[1];
		scrollBounds[2] = bounds[3] + (scrollBounds[2] - oldBounds[3]);
		scrollBounds[3] = scrollBounds[2] + height;
		
		boundsArray = NewRealArray(1, 4L);
		CArray2Array(boundsArray, scrollBounds);
		SetVar(scrollBar, BOUNDS, boundsArray);
	    }
	    else
	    {
		/*It was on the bottom*/
		scrollBounds[0] = bounds[0];
		scrollBounds[1] = bounds[1];
		scrollBounds[3] = bounds[2] + (scrollBounds[3] - oldBounds[2]);
		scrollBounds[2] = scrollBounds[3] - height;
		
		boundsArray = NewRealArray(1, 4L);
		CArray2Array(boundsArray, scrollBounds);
		SetVar(scrollBar, BOUNDS, boundsArray);
	    }
	}
	RecalcScroll(object);
    return ObjTrue;
}

ObjPtr NewPanel(superClass, left, right, bottom, top)
ObjPtr superClass;
int left, right, bottom, top;
/*Makes a new panel with bounds left, right, bottom, top*/
{
    ObjPtr retVal;
    ObjPtr contents;

    retVal = NewObject(superClass ? superClass : panelClass, 0);
    Set2DIntBounds(retVal, left, right, bottom, top);
    SetVar(retVal, CONTENTS, contents = NewList());
    SetVar(contents, PARENT, retVal);
    return retVal;
}

ObjPtr ChangeFieldScroll(scrollBar)
ObjPtr scrollBar;
/*Changes the field scrolling*/
{
    ObjPtr repObj;

    repObj = GetObjectVar("ChangeFieldScroll", scrollBar, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }
    /*For now, just need to invalidate the field*/
    ImInvalid(repObj);
    return ObjTrue;
}

ObjPtr NewControlField(left, right, bottom, top, name, scrollBars)
int left, right, bottom, top;
char *name;
int scrollBars;
/*Makes a new control field*/
{
    ObjPtr retVal;
    ObjPtr contents;
    ObjPtr hScrollBar = NULLOBJ, vScrollBar = NULLOBJ;

    retVal = NewObject(controlFieldClass, 0);
    SetVar(retVal, CONTENTS, contents = NewList());
    SetVar(contents, PARENT, retVal);
    SetVar(retVal, NAME, NewString(name));

    if (scrollBars & BARRIGHT)
    {
	right -= BARWIDTH + CORRALBARBORDER;
    }
    if (scrollBars & BARLEFT)
    {
	left += BARWIDTH + CORRALBARBORDER;
    }

    if (scrollBars & BARBOTTOM)
    {
	bottom += BARWIDTH + CORRALBARBORDER;
    }

    if (scrollBars & BARRIGHT)
    {
	vScrollBar = NewScrollbar(right + CORRALBARBORDER,
		    right + CORRALBARBORDER + BARWIDTH,
		    bottom,
		    top, "FVScroll");
	SetVar(retVal, VSCROLL, vScrollBar);
	SetVar(vScrollBar, PARENT, retVal);
	SetVar(vScrollBar, REPOBJ, retVal);
	SetMethod(vScrollBar, CHANGEDVALUE, ChangeFieldScroll);
    }
    else if (scrollBars & BARLEFT)
    {
	vScrollBar = NewScrollbar(left - CORRALBARBORDER - BARWIDTH,
		    left - CORRALBARBORDER,
		    bottom,
		    top, "FVScroll");
	SetVar(retVal, VSCROLL, vScrollBar);
	SetVar(vScrollBar, PARENT, retVal);
	SetVar(vScrollBar, REPOBJ, retVal);
	SetMethod(vScrollBar, CHANGEDVALUE, ChangeFieldScroll);
    }

    if (scrollBars & BARBOTTOM)
    {
	hScrollBar = NewScrollbar(left,
		    right,
		    bottom - CORRALBARBORDER - BARWIDTH,
		    bottom - CORRALBARBORDER,
		    "FHScroll");
	SetVar(retVal, HSCROLL, hScrollBar);
	SetVar(hScrollBar, PARENT, retVal);
	SetVar(hScrollBar, REPOBJ, retVal);
	SetMethod(hScrollBar, CHANGEDVALUE, ChangeFieldScroll);
	SetVar(hScrollBar, REPOBJ, retVal);
	SetMethod(hScrollBar, CHANGEDVALUE, ChangeFieldScroll);
    }

    if (scrollBars & OBJECTSFROMTOP)
    {
	SetVar(retVal, TOPDOWN, ObjTrue);
	if (vScrollBar)
	{
	    SetSliderRange(vScrollBar, 0.0, -10.0, 0.0);
	}
    }

    Set2DIntBounds(retVal, left, right, bottom, top);
    return retVal;
}

ObjPtr NewIconCorral(superClass, left, right, bottom, top, scrollBars)
ObjPtr superClass;
int left, right, bottom, top;
int scrollBars;
/*Makes a new panel with bounds left, right, bottom, top
  if superClass non-nil, uses that as the superclass of the object
  scrollBars is a bunch of flags for scroll bars AND OTHER STUFF*/
{
    ObjPtr retVal;
    ObjPtr hScrollBar = NULLOBJ, vScrollBar = NULLOBJ;
    ObjPtr contents;

    retVal = NewObject(superClass ? superClass : corralClass, 0);
    SetVar(retVal, CONTENTS, contents = NewList());
    SetVar(contents, PARENT, retVal);

    if (scrollBars & BARRIGHT)
    {
	right -= BARWIDTH + CORRALBARBORDER;
    }
    if (scrollBars & BARLEFT)
    {
	left += BARWIDTH + CORRALBARBORDER;
    }

    if (scrollBars & BARBOTTOM)
    {
	bottom += BARWIDTH + CORRALBARBORDER;
    }
    
    if (scrollBars & BARRIGHT)
    {
	vScrollBar = NewScrollbar(right + CORRALBARBORDER,
		    right + CORRALBARBORDER + BARWIDTH,
		    bottom,
		    top, "VScroll");
	SetVar(retVal, VSCROLL, vScrollBar);
	SetVar(vScrollBar, PARENT, retVal);
	SetVar(vScrollBar, REPOBJ, retVal);
	SetMethod(vScrollBar, CHANGEDVALUE, ChangeFieldScroll);
    }
    else if (scrollBars & BARLEFT)
    {
	vScrollBar = NewScrollbar(left - CORRALBARBORDER - BARWIDTH,
		    left - CORRALBARBORDER,
		    bottom,
		    top, "VScroll");
	SetVar(retVal, VSCROLL, vScrollBar);
	SetVar(vScrollBar, PARENT, retVal);
	SetVar(vScrollBar, REPOBJ, retVal);
	SetMethod(vScrollBar, CHANGEDVALUE, ChangeFieldScroll);
    }

    if (scrollBars & BARBOTTOM)
    {
	hScrollBar = NewScrollbar(left,
		    right,
		    bottom - CORRALBARBORDER - BARWIDTH,
		    bottom - CORRALBARBORDER,
		    "HScroll");
	SetVar(retVal, HSCROLL, hScrollBar);
	SetVar(hScrollBar, PARENT, retVal);
	SetVar(hScrollBar, REPOBJ, retVal);
	SetMethod(hScrollBar, CHANGEDVALUE, ChangeFieldScroll);
    }

    if (scrollBars & OBJECTSFROMTOP)
    {
	SetVar(retVal, TOPDOWN, ObjTrue);
	if (vScrollBar)
	{
	    SetSliderRange(vScrollBar, 0.0, -10.0, 0.0);
	}
    }

    Set2DIntBounds(retVal, left, right, bottom, top);

    return retVal;
}

ObjPtr NewSwitch(left, right, bottom, top, nCells, outCell, initValue, name)
int left, right, bottom, top;
int nCells, outCell, initValue;
char *name;
/*Makes a new switch with bounds left, right, bottom, top
  nCells is the number of cells, outCell is which cell is the output,
  initValue is the initial switch*/
{
    ObjPtr retVal;

	retVal = NewObject(switchClass, 0);
	Set2DIntBounds(retVal, left, right, bottom, top);
	SetVar(retVal, NCELLS, NewInt(nCells));
	SetVar(retVal, OUTCELL, NewInt(outCell));
	SetVar(retVal, VALUE, NewInt(initValue));
	SetVar(retVal, NAME, NewString(name));
	return retVal;
}

#ifdef PROTO
real ChooseGoodStep(real min, real max)
#else
real ChooseGoodStep(min, max)
real min;
real max;
#endif
/*Choose a good step to get between min and max*/
{
    real diff;
    int iexp;

    diff = max - min;
    iexp = ((int) (1000.0 + log10((double) diff))) - 1000;
    return (real) pow((double) 10.0, (double) iexp);
}

#ifdef PROTO
void ChooseGoodSliderParams(real *min, real *max, real *bigStep, real *littleStep, real *anchor)
#else
void ChooseGoodSliderParams(min, max, bigStep, littleStep, anchor)
real *min, *max, *bigStep, *littleStep, *anchor;
#endif
/*Chooses good slider params for data in min and max.  Sets all four params*/
{
    real diff;

    if (*max <= *min)
    {
	*max = *min + 1.0;
    }

    if (*min >= 0.0 && *max >= 0.0)
    {
	real powerMin, powerMax;
	int iexpMin, iexpMax;
	int multiplier;

	iexpMax = ((int) (1000.0 + log10((double) *max))) - 1000;
	if (*min == 0.0)
	{
	    iexpMin = iexpMax - 1;
	}
	else
	{
	    iexpMin = ((int) (1000.0 + log10((double) *min))) - 1000;
	}
	powerMin = (real) pow((double) 10.0, (double) iexpMin);
	powerMax = (real) pow((double) 10.0, (double) iexpMax);

	/*They're on the high side*/
	if (iexpMin < iexpMax)
	{
	    /*Lower bound might as well be zero*/
	    *min = 0.0;
	}
	else
	{
	    /*Lower min to nearest lower power of ten*/
	    multiplier = *min / powerMin;
	    *min = multiplier * powerMin;
	}
	/*Raise max to the next power of ten*/
	multiplier = *max / powerMax;
	if (*max > multiplier * powerMax)
	{
	    *max = (multiplier + 1) * powerMax;
	}
	else
	{
	    *max = multiplier * powerMax;
	}
	
	*bigStep = powerMax;
	*littleStep = *bigStep * 0.1;
	*anchor = *min;
    }
    else if (*min <= 0.0 && *max <= 0.0)
    {
	real powerMin, powerMax;
	int iexpMin, iexpMax;
	int multiplier;
	real temp;

	/*They're on the low side*/
	temp = *max;
	*max = -*min;
	*min = -temp;

	iexpMax = ((int) (1000.0 + log10((double) *max))) - 1000;
	if (*min == 0.0)
	{
	    iexpMin = iexpMax - 1;
	}
	else
	{
	    iexpMin = ((int) (1000.0 + log10((double) *min))) - 1000;
	}
	powerMin = (real) pow((double) 10.0, (double) iexpMin);
	powerMax = (real) pow((double) 10.0, (double) iexpMax);

	if (iexpMin < iexpMax)
	{
	    /*Lower bound might as well be zero*/
	    *min = 0.0;
	}
	else
	{
	    /*Lower min to nearest lower power of ten*/
	    multiplier = *min / powerMin;
	    *min = multiplier * powerMin;
	}
	/*Raise max to the next power of ten*/
	multiplier = *max / powerMax;
	if (*max > multiplier * powerMax)
	{
	    *max = (multiplier + 1) * powerMax;
	}
	else
	{
	    *max = multiplier * powerMax;
	}

	temp = *max;
	*max = -*min;
	*min = -temp;

	*bigStep = powerMax;
	*littleStep = *bigStep * 0.1;
	*anchor = *min;
    }
    else if (*min <= 0.0 && *max >= 0.0)
    {
	/*They span zero*/
	int multiplier;
	int iexp;
	real power;

	iexp = ((int) (1000.0 + log10((double) (*max - *min)))) - 1000;
	power = (real) pow((double) 10.0, (double) iexp);

	*min = -*min;

	/*Raise max to the next power of ten*/
	multiplier = *max / power;
	if (*max > multiplier * power)
	{
	    *max = (multiplier + 1) * power;
	}
	else
	{
	    *max = multiplier * power;
	}

	/*Raise min to the next power of ten*/
	if (*min > multiplier * power)
	{
	    *min = (multiplier + 1) * power;
	}
	else
	{
	    *min = multiplier * power;
	}

	*bigStep = power;
	*littleStep = *bigStep * 0.1;
	*min = -*min;
	*anchor = 0.0;
    }
}

static ObjPtr MakeCorralHelpString(object, class)
ObjPtr object, class;
/*Makes a corral help string for object in class*/
{
    if (GetPredicate(object, SINGLECORRAL))
    {
	SetVar(class, HELPSTRING, NewString("This corral can only hold a single icon.  You can select the icon by clicking it \
with the left mouse button \
or deselect it by clicking on the background.  If you drag another icon into this corral, \
it will replace any icon that is already there.  The icon can be deleted with the Delete \
item in the Object menu."));
    }
    else
    {
	SetVar(class, HELPSTRING, NewString("You can select a single icon within this corral by clicking it \
with the left mouse button.  \
You can select more than one icon by holding the Shift key down while you click on additional icons.  \
You can also click outside an icon and drag a rectangle around icons you would like to select.\n\
\n\
Items in the Object menu and some action buttons in the window \
will work on all selected objects."));
    }
    return ObjTrue;
}

#ifdef PROTO
Bool Select(ObjPtr obj, Bool flag)
#else
Bool Select(obj, flag)
ObjPtr obj;
Bool flag;
#endif
{
    ObjPtr repObj;
    FuncTyp selector;

    if (runningScript && !scriptSelectP)
    {
	return true;
    }

    if (IsSelected(obj) == flag) return false;

    if (GetPredicate(obj, REPOBJONLY) && (repObj = GetVar(obj, REPOBJ)))
    {
	obj = repObj;
    }

    if (logging && scriptSelectP)
    {
	char logLine[256];
	MakeObjectName(tempStr, obj);
	if (tempStr[0])
	{
	    sprintf(logLine, "%s %s\n", flag ? "select" : "deselect", tempStr);
	    Log(logLine);
	}
    }

    selector = GetMethod(obj, SELECT);
    if (selector)
    {
	(* selector)(obj, flag);
    }

    /*Now do the selection*/
    SetVar(obj, SELECTED, flag ? ObjTrue : ObjFalse);
    if (flag)
    {
	PrefixList(allSelected, obj);
    }
    else
    {
	DeleteFromList(allSelected, obj);
    }

    /*Defer a task to update all the windows*/
    DoUniqueTask(AdjustObjButtons);

    return true;
}

void ForAllSelectedObjects(task)
FuncTyp task;
/*Does the specified task for all selected objects*/
{
    ThingListPtr runner, next;
    runner = LISTOF(allSelected);
    while (runner)
    {
	ObjPtr object;
	object = runner -> thing;
	runner = runner -> next;
	if (IsIcon(object))
	{
	    object = GetVar(object, REPOBJ);
	}
	if (object)
	{
	    ObjPtr truth;
	    truth = (*task)(object);
	}
    }
}

#ifdef PROTO
Bool ParseInteger(int *value, char *s)
#else
Bool ParseInteger(value, s)
int *value;
char *s;
#endif
/*Parses an integer and puts it in value.  Returns true iff it was a good value.
  Prints out an error message and returns false if not*/
{
    char *t;
    double v;
    int intValue;
    int sign;

    /*First try to make a floating point value*/
    v = strtod(s, &t);
    if (s != t)
    {
	/*It made a number.  See if there's crap at the end*/
	while (*t)
	{
	    if (!isspace(*t))
	    {
		DoUniqueTask(DoNumberError);
		return false;
	    }
	    ++t;
	}

	intValue = v;

	if (intValue != v)
	{
	    DoUniqueTask(DoNonIntError);
	    return false;
	}
	*value = intValue;
	return true;
    }

    return false;
}

#ifdef PROTO
Bool ParseReal(real *value, char *s)
#else
Bool ParseReal(value, s)
real *value;
char *s;
#endif
/*Parses a number in string s and puts it in value.  Returns true iff it
  was a good value.  Understands normal floating point plus INFINITY, -INFINITY,
  and MISSING.  Can be abbreviated to I and M, don't have to be upper case.*/
{
    char *t;
    double v;
    int sign;

    /*First try to make a floating point value*/
    v = strtod(s, &t);
    if (s != t)
    {
	/*It made a number.  See if there's crap at the end*/
	while (*t)
	{
	    if (!isspace(*t))
	    {
		DoUniqueTask(DoNumberError);
		return false;
	    }
	    ++t;
	}
	*value = v;
	return true;
    }

    sign = 1;
    /*OK, so search for infinity or missing*/
    t = s;
    SKIPBLANKS(t);
    if (*t == '+')
    {
	++t;
    }
    else if (*t == '-')
    {
	sign = -1;
	++t;
    }
    if (toupper(*t) == 'I')
    {
	/*Infinity*/
	*value = sign > 0 ? PLUSINF : MINUSINF;
	return true;
    }
    else if (toupper(*t) == 'M')
    {
	/*Missing data*/
	*value = missingData;
	return true;
    }
    else
    {
	DoUniqueTask(DoNumberError);
	return false;
    }
}

#ifdef PROTO
void PrintNumber(char *s, real value)
#else
void PrintNumber(s, value)
char *s;
real value;
#endif
/*Prints value into s*/
{
    if (value == missingData)
    {
	strcpy(s, "Missing");
    }
    else if (value >= PLUSINF)
    {
	strcpy(s, "Infinity");
    }
    else if (value <= MINUSINF)
    {
	strcpy(s, "-Infinity");
    }
    else
    {
	sprintf(s, "%g", (float) value);
    }
}

ObjPtr NewFlowLine(left, right, bottom, top, name)
int left, right, bottom, top;
char *name;
/*Creates a new flow line*/
{
    ObjPtr retVal;

    retVal = NewObject(flowLineClass, 0);
    Set2DIntBounds(retVal, left, right, bottom, top);
    SetVar(retVal, NAME, NewString(name));
    return retVal;
}

static ObjPtr DrawFlowLine(flowLine)
ObjPtr flowLine;
/*Draws a flow line*/
{
    int left, right, bottom, top, y;

    SetLineWidth(3);

    Get2DIntBounds(flowLine, &left, &right, &bottom, &top);

    y = (bottom + top) / 2;

    DrawUILine(left + ARROWLENGTH, y, right - ARROWLENGTH, y, UIBLACK);

    /*Draw half the arrow head*/
    FillUITri(right - ARROWLENGTH, y, right - ARROWLENGTH, y - ARROWHEIGHT, right, y, UIBLACK);

    /*Draw half the arrow head*/
    FillUITri(right - ARROWLENGTH, y, right - ARROWLENGTH, y + ARROWHEIGHT, right, y, UIBLACK);

    /*Draw half the arrow head*/
    FillUITri(left + ARROWLENGTH, y, left + ARROWLENGTH, y - ARROWHEIGHT, left, y, UIBLACK);

    /*Draw half the arrow head*/
    FillUITri(left + ARROWLENGTH, y, left + ARROWLENGTH, y + ARROWHEIGHT, left, y, UIBLACK);

    SetLineWidth(1);
    return ObjTrue;
}

#ifdef PROTO
void SetSelection(ObjPtr object, ObjPtr selection)
#else
void SetSelection(object, selection)
ObjPtr	object, selection;
#endif
/*Sets a selection within a control*/	
{
    FuncTyp setter;
    ObjPtr retVal;

    setter = GetMethod(object, SETSELECTION);
    if (setter)
    {
	(* setter) (object, selection);
    }
}

#ifdef PROTO
void ChangedSelection(ObjPtr object)
#else
void ChangedSelection(object)
ObjPtr object;
#endif
/*Does a CHANGEDSELECTION on a control*/
{
    FuncTyp changer;

    changer = GetMethod(object, CHANGEDSELECTION);
    if(changer)
    {
	InhibitLogging(true);
	(* changer)(object);
	InhibitLogging(false);
    }
}

static ObjPtr SelectScreenObj(object, selectp)
ObjPtr object;
Bool selectp;
/*Selects a screen object*/
{
    Bool alreadySelected;

    alreadySelected = IsSelected(object);
    if (selectp == alreadySelected)
    {
	return ObjTrue;
    }

    ImInvalid(object);

    return ObjTrue;
}

void InitControls()
/*Initializes controls*/
{
    ObjPtr list;

    allSelected = NewList();
    AddToReferenceList(allSelected);

    screenClass = NewObject(NULLOBJ, 0);
    AddToReferenceList(screenClass);
    SetMethod(screenClass, DELETE, DeleteObject);
    DeclareDependency(screenClass, APPEARANCE, SELECTED);
    SetMethod(screenClass, SELECT, SelectScreenObj);

    controlClass = NewObject(screenClass, 0);
    AddToReferenceList(controlClass);
    list = NewList();
    PrefixList(list, NewSymbol(VALUE));
    SetVar(controlClass, SNAPVARS, list);
    SetVar(controlClass, TYPESTRING, NewString("control"));
    SetVar(controlClass, ACTIVATED, ObjTrue);
    SetMethod(controlClass, SHOWCONTROLS, NewControlWindow);
    SetVar(controlClass, STICKINESS, NewInt(STICKYTOP + STICKYLEFT));

    panelClass = NewObject(controlClass, 0);
    AddToReferenceList(panelClass);
    SetVar(panelClass, CLASSID, NewInt(CLASS_PANEL));
    SetVar(panelClass, NAME, NewString("Panel"));
#ifdef GRAPHICS
    SetMethod(panelClass, DRAW, DrawPanel);
#endif
    SetMethod(panelClass, BOUNDSINVALID, PanelBoundsInvalid);
#ifdef INTERACTIVE
    SetMethod(panelClass, PRESS, PressPanel);
    SetMethod(panelClass, DROPOBJECTS, DropInPanel);
    SetMethod(panelClass, KEYDOWN, KeyDownContents);
#endif
    greyPanelClass = NewObject(panelClass, 0);
    AddToReferenceList(greyPanelClass);
    SetVar(greyPanelClass, STICKINESS, NewInt(STICKYTOP + STICKYRIGHT + STICKYBOTTOM + STICKYLEFT));
    SetVar(greyPanelClass, BACKGROUND, NewInt(UIBACKGROUND));

    fieldClass = NewObject(controlClass, 0);
    AddToReferenceList(fieldClass);
#ifdef GRAPHICS
    SetMethod(fieldClass, DRAW, DrawField);
#endif
#ifdef INTERACTIVE
    SetMethod(fieldClass, PRESS, PressField);
#endif
    SetVar(fieldClass, NAME, NewString("Field"));
    SetMethod(fieldClass, FINDOBJECT, FindObjectField);
    SetMethod(fieldClass, FORALLOBJECTS, ForAllFieldObjects);
    SetVar(fieldClass, TYPESTRING, NewString("field"));
    SetMethod(fieldClass, BOUNDSINVALID, FieldBoundsInvalid);
#ifdef INTERACTIVE
    SetMethod(fieldClass, PRESSCONTENTS, PressFieldContents);
    SetMethod(fieldClass, KEYDOWN, KeyDownContents);
#endif
#ifndef DONTCLIP
    SetVar(fieldClass, OPAQUE, ObjTrue);
#endif

    corralClass = NewObject(fieldClass, 0);
    AddToReferenceList(corralClass);
    SetVar(corralClass, CLASSID, NewInt(CLASS_CORRAL));
    SetVar(corralClass, NAME, NewString("Corral"));
    SetVar(corralClass, VIEWBYWHAT, NewInt(VB_ICON));
#ifdef GRAPHICS
    SetMethod(corralClass, DRAWCONTENTS, DrawCorralContents);
#endif
#ifdef INTERACTIVE 
    SetMethod(corralClass, PRESSCONTENTS, PressCorralContents);
    SetMethod(corralClass, DROPOBJECTS, DropInCorral);
#endif
    SetMethod(corralClass, RECALCSCROLL, RecalcCorralScroll);
    SetMethod(corralClass, RESHAPE, ReshapeCorral);
    SetVar(corralClass, TYPESTRING, NewString("icon corral"));
    SetMethod(corralClass, MAKE1HELPSTRING, MakeCorralHelpString);

    switchClass = NewObject(controlClass, 0);
    AddToReferenceList(switchClass);
    SetVar(switchClass, NAME, NewString("Switch"));
#ifdef GRAPHICS
    SetMethod(switchClass, DRAW, DrawSwitch);
#endif
#ifdef INTERACTIVE
    SetMethod(switchClass, PRESS, PressSwitch);
#endif
    SetMethod(switchClass, SETVAL, SetSwitchVal);
    SetVar(switchClass, TYPESTRING, NewString("data switch"));
    SetVar(switchClass, HELPSTRING, NewString("To change the data path within a switch, click on the data path you desire."));

    controlFieldClass = NewObject(fieldClass, 0);
    AddToReferenceList(controlFieldClass);
#ifdef GRAPHICS
    SetMethod(controlFieldClass, DRAW, DrawControlField);
    SetMethod(controlFieldClass, DRAWCONTENTS, DrawControlFieldContents);
#endif
    SetMethod(controlFieldClass, RECALCSCROLL, RecalcControlFieldScroll);
    SetMethod(controlFieldClass, RESHAPE, ReshapeCorral);
    SetVar(controlFieldClass, TYPESTRING, NewString("field"));

    flowLineClass = NewObject(controlClass, 0);
    SetMethod(flowLineClass, DRAW, DrawFlowLine);
    AddToReferenceList(flowLineClass);

    InitButtons();
    InitSliders();
    InitTextBoxes();
    InitPerspecControls();
    InitTitleBoxes();
    InitTrackControls();
}
 
void KillControls()
/*Kills the controls*/
{
    KillTrackControls();
    KillTitleBoxes();
    KillPerspecControls();
    KillTextBoxes();
    KillSliders();
    KillButtons();
    RemoveFromReferenceList(flowLineClass);
    RemoveFromReferenceList(controlFieldClass);
    RemoveFromReferenceList(switchClass);
    RemoveFromReferenceList(corralClass);
    RemoveFromReferenceList(fieldClass);
    RemoveFromReferenceList(greyPanelClass);
    RemoveFromReferenceList(panelClass);
    RemoveFromReferenceList(controlClass);
    RemoveFromReferenceList(allSelected);
    RemoveFromReferenceList(screenClass);
}

